窥探FTP通信细节


前几天,老张写了两篇关于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协议是明文传输
,你所有的秘密都不是秘密!