FreeFloat FTP Server 1.0 中的缓冲区溢出,展示了单个不安全的处理程序如何在不同命令之间产生多个 CVE 条目。
该仓库是我教授内存破坏利用时使用的教材的一部分(除了日常工作之外,我还在不同的网络安全课程中任教,帮助培养下一代逆向工程师)。
CVE-2025-5548 是我用来向学生展示漏洞研究是一项触手可及的技能、并非只有拥有多年经验的高级研究员才能从事的案例。FreeFloat FTP Server 1.0 是一个小巧的老式 Windows 应用程序,易于搭建、易于运行、也易于崩溃。该溢出通过标准 FTP 命令触发,利用路径是一次干净的 vanilla EIP 覆盖,从模糊测试到获得可用 shell 的整个过程可以在一次会话中完成。
这个案例也与我的一次演讲直接相关:“通往你的第一个 CVE 之路”。在那次演讲中,我解释了初学者进入漏洞研究最现实的切入点之一,就是分析旧软件、理解其内部工作原理,并寻找缺失的长度检查——正是这类问题产生了此类漏洞。FreeFloat 是我用来展示发现并记录一个真实 bug 并不需要多年经验的例子之一,它需要的是好奇心、一个调试器以及一套有条理的方法。
这个案例在教学上之所以特别有效,在于它与一个更广泛的课题相关:CVE 是如何被分配的。FreeFloat 中同一个不安全的输入处理程序在多个不同的 FTP 命令(USER、PASS、NOOP 等)中都产生了溢出,其中每个命令都被独立上报并获得各自的 CVE 条目。当学生查看该二进制程序的 CVE 列表并看到数十个条目时,他们会很快明白:CVE 的数量并不等同于漏洞的数量,理解根本原因比罗列症状更有价值。
一旦学生通过此类案例理解了整个过程,我就会给他们一个真实的例子,说明接下来会发生什么。在练习了两周的缓冲区溢出和内存破坏之后,他们能够发现的那类漏洞已经存在于真实软件之中。下面的仓库记录了我发现的一个漏洞,专门用来向学生展示完整的闭环:发现、分析、文档编写与 CVE 申请,所选目标足够简单,能在学习早期就发现,例如 CVE-2025-70330。
该仓库是一个更大合集的一部分。如果你想练习涵盖广泛技术、漏洞类型和目标架构的漏洞利用,可以看看 Binary-Exploitation 仓库,那里整理并维护了所有这些材料,以及更多 CVE、方法论和漏洞利用练习。
该漏洞影响 FreeFloat FTP Server 1.0,这是一个非常老旧的 Windows FTP 服务器,编写时没有遵循现代安全实践。2025 年,一名研究人员上报了许多影响这一二进制程序的 CVE。
示例包括:
每个 CVE 都对应不同的 FTP 命令,但当对该程序进行逆向分析时,便会清楚地看到其中许多 CVE 都通向同一条易受攻击的代码路径。
这使得该案例不仅作为缓冲区溢出示例很有趣,同时也证明了:
在对该二进制程序进行逆向分析时,可以观察到所有 FTP 命令都由同一个分发函数处理。命令字符串存储在一个会话结构中,多个命令处理程序使用 strcpy、strcat 和 memcpy 等不安全函数将用户输入复制到固定大小的缓冲区中,却未检查所接收数据的长度。
正因为如此,即使 CVE 描述只提及某一个特定命令,不同的命令也可能覆盖栈。
例如,可以使用以下命令触发崩溃:
NOOP AAAAA...
USER AAAAA...
PASS AAAAA...
HOST AAAAA...
ANYTHING AAAAA...
唯一的区别在于覆盖返回地址所需的字节数。这意味着针对该软件上报的许多 CVE 都共享同一个根本原因。
FreeFloat FTP Server 处理通过 TCP 21 端口接收的命令,并将命令字符串存储在内部的会话结构中。随后,命令处理程序将用户可控的数据复制到本地栈缓冲区中,而没有进行适当的长度校验。
在对该二进制程序进行逆向分析时,可以观察到多个命令处理程序使用了固定大小的缓冲区和不安全的复制操作。该易受攻击逻辑的简化版本如下:
char buffer[256];
strcpy(buffer, user_input);
由于目标缓冲区大小固定且未检查输入长度,发送长字符串会导致复制操作写入超出缓冲区末尾的位置。
随着写入的数据越来越多,栈布局逐渐被破坏,直到保存的返回地址被覆盖。
当函数返回时,执行流程会跳转到用户可控的值,从而导致崩溃。在调试器下,可以看到指令指针被攻击者可控的数据覆盖。
可以通过在有效的 FTP 命令之后发送一个长字符串来复现该崩溃。使用 Python 的示例:
import socket
ip = "127.0.0.1"
port = 21
payload = b"A" * 500
s = socket.socket()
s.connect((ip, port))
s.recv(1024)
s.send(b"USER anonymous\r\n")
s.recv(1024)
s.send(b"PASS anonymous\r\n")
s.recv(1024)
s.send(b"NOOP " + payload + b"\r\n")
s.close()
在调试器下执行时,崩溃显示为:
EIP = 41414141
这证实了用户可控的数据覆盖了返回地址。
该仓库的目标不仅是展示崩溃,还要逐步演示利用该漏洞的完整过程,采用与开发漏洞利用时常用的相同方法论。
为了保持主 README 简洁,详细的漏洞利用笔记、脚本和调试器步骤都放在该仓库的 Vulnerability 📂 文件夹中。
在那里,你将找到利用该 CVE 的完整工作流程,包括模糊测试、偏移量发现、坏字符分析、gadget 搜索和 shellcode 执行。