前幾天,老張寫了兩篇關於FTP的文章:
給大家介紹了FTP的通信機制,然後又帶大家寫了一個玩具版的FTP服務端代碼。
今天繼續給大家帶來FTP系列的第三篇《窺探FTP通信細節》,通過抓包FTP的通信,將FTP的扒的底褲都不剩。
環境準備:
-
FTP客戶端測試腳本:依然選擇Python自帶的Ftplib來編寫測試腳本
-
Wireshark:一個網絡層的抓包工具
-
一臺主機:用於運行FTP客戶端腳本和Wireshark。ip爲192.168.16.1
-
一臺Linux虛擬機:運行Vsftpd作爲FTP服務端。ip爲192.168.16.129
-
FTP服務器的模式:選擇主動模式,使用Binary模式傳輸數據。
注:爲什麼不用老張的玩具版?使用成熟通用的Vsftpd是爲了能夠更好的幫助大家理解,避免不必要的歧義。
OK,請各位繫好安全帶,馬上開車了!
1. 連接FTP服務器,建立命令通道通道
import ftplib
ftp = ftplib.FTP()
ftp.connect("192.168.16.129", 21)
此時使用Wireshark抓包,可以看到:
經過TCP的三次握手,命令通道建立。此時FTP服務器會向客戶端發送一條220狀態碼的消息,表示命令通道已建立。但是注意,此時還沒有登錄鑑權。
2. 客戶端發送賬號密碼
ftp.connect("192.168.16.129", 21)
ftp.login("root", "root") # 此處密碼並非正式密碼,抓包時老張也機智的把密碼抹去了
此時報文消息如下:
可以看到賬號和密碼是通過兩條消息分別發送的。
3. 客戶端設置本次連接使用主動模式:
ftp.set_pasv(False)
此行爲完全是客戶端本地行爲,沒有同服務器之間進行信息交換。
4. 將本地文件上傳至服務器:
# 將本地文件上傳至服務器
with open("client", 'rb') as f:
ftp.storbinary("STOR upload_from_client", f, 1024)
雖然看起來只有一個STOR命令,但是此時卻是客戶端和服務器之間信息交換最繁忙的時候,爲了能夠講清楚,老張將整個過程拆解了一下。
4.1 傳輸上傳命令:
首先,客戶端通過命令通道通知服務端,本次數據傳輸將使用Binary模式。
然後,客戶端將自己爲數據通道準備的host及port發送給服務器。
注:關於端口號port的傳輸格式,可以參考上一篇的代碼實現。
最後,客戶端才發送上傳命令,通知服務器文件需要保存在默認文件夾,使用“upload_from_client”作爲文件名。
4.2 建立數據通道,傳遞數據:
可以看到,數據通道是需要時纔會建立,並不是一開始就建立好的,並且在數據傳輸完成之後立刻關閉。
還有另一個細節,主動模式下,服務器在收到上傳命令後,響應上傳命令和建立數據通道是同步進行的,這一點是我們的單線程玩具版不能比擬的。
4.3 服務器通知客戶端,上傳完成:
5. 下載服務器文件至本地:
# 下載服務器文件
with open("download_from_server", "wb") as f:
ftp.retrbinary("RETR server", f.write)
同上傳流程類似,這裏我們繼續拆解。
5.1 傳輸下載命令:
到這裏有沒有發現,其實下載和上傳的流程是幾乎一模一樣的。
5.2 建立數據通道,傳遞數據:
必須指出的是,每次客戶端建立數據通道使用的端口號並不是固定不變的。
5.3 服務器通知客戶端,下載完成:
一旦數據傳輸完成,服務器依然會發送一條消息通知客戶端。
6. 程序退出,命令通道關閉:
以上就是FTP的通信細節,不知道各位同學看完有沒有一絲疑惑?沒錯,老張之前也給大家強調過FTP協議是明文傳輸,你所有的祕密都不是祕密!