窥探FTP通信细节

354 阅读3分钟

前几天,老张写了两篇关于FTP的文章:

  • 《吃透FTP》

  • 《200行代码实现玩具版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协议是明文传输,你所有的秘密都不是秘密!