Skip to content
KitploitKITPLOIT
工具博客
提交
工具博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

··订阅源·联系·隐私·© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
工具/GitHubGitHub/diegslva/cve-2010-4221-lab
漏洞利用框架漏洞分析漏洞利用逆向工程渗透测试学习与教育二进制利用实验室与实践
GitHubdiegslva/cve-2010-4221-lab

cve-2010-4221-lab

从补丁到RCE:针对CVE-2010-4221(ProFTPD TELNET IAC栈溢出)手工构建的漏洞利用,并完整记录了由失败驱动的全过程

查看仓库
12小时14分前尚未审核

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享

CVE-2010-4221 — ProFTPD TELNET IAC 栈溢出:从补丁到远程代码执行

一个完全可复现的实验环境,以及一个手工编写的原始套接字漏洞利用程序,针对 CVE-2010-4221 —— ProFTPD 的 pr_netio_telnet_gets() 中的预认证栈缓冲区溢出, 作为漏洞研究与漏洞利用开发的学习练习而构建。

每一次失败都有记录。一帆风顺的路径是谎言;弯路才是教训。


法律与道德声明 —— 请先阅读

本仓库是一个教育性产物。它的存在是为了让那些负担不起导师或培训的人, 能够了解内存破坏漏洞利用究竟是如何诞生的:从补丁开始,经历失败, 最终在你自己拥有的实验环境中形成可用的概念验证。

红队工作 —— 真正的、专业的进攻性安全 —— 由一个词定义:授权。 专业人员所做的一切都发生在书面协议之内:一份签署的《交战规则》文件, 其中明确了范围、目标、允许使用的技术、时间窗口以及批准人员。 没有这份文件,完全相同的按键操作就不再是职业行为 —— 在地球上几乎 所有司法管辖区,这都是犯罪。

因此,以下是本仓库不可协商的契约:

  • 仅针对随附的 Docker 实验环境或你拥有的系统运行。
  • 绝不对任何未经明确书面授权的目标运行。
  • 如果你在学习:欢迎,这就是为你而建的。
  • 如果你在寻找用来对付他人的武器:请关闭此标签页。这个漏洞 来自 2010 年;它只会给你带来犯罪记录,而不会带来任何好处。

这门技艺值得学习。但只有伴随着随之而来的纪律,这门技艺才有价值。


漏洞

ProFTPD 在 FTP 控制通道上处理 TELNET 转义序列。在 TELNET 中, 0xFF(IAC,"Interpret As Command",解释为命令)是转义字节; 字面量 0xFF 以 0xFF 0xFF 的形式发送。

pr_netio_telnet_gets() 将客户端字节复制到栈缓冲区 (pr_cmd_read 的 char buf[PR_DEFAULT_CMD_BUFSZ+1],在 glibc 的 MAXPATHLEN=4096 下为 4104 字节),并通过 buflen —— 一个 size_t,无符号类型 —— 跟踪剩余空间。

漏洞路径(1.3.3a,netio.c):

root@kitploit:~
case TELNET_IAC:
  switch (cp) {
    ...
    default:
      *bp++ = TELNET_IAC;   // 写入 #1
      buflen--;             // 递减 #1
      telnet_mode = 0;
      break;
  }
  break;
...
*bp++ = cp;                 // 写入 #2
buflen--;                   // 递减 #2  <-- 两者之间没有检查

两次写入,两次递减,两者之间没有零值检查。当 buflen 恰好为 1 时,这对操作将其递减到 0,然后下溢到 SIZE_MAX(1800 京)。循环现在认为缓冲区是无限的, 并持续将攻击者控制的字节写入栈的上方 —— 覆盖保存的寄存器、 保存的 RBP 以及返回地址。

预认证。 该函数在 USER/PASS 被处理之前就已运行。

补丁

修复(提交 3cc69b8388, "Bug#3521 - Telnet IAC processing stack overflow",发布于 1.3.3c) 只有十二行。整个安全边界是:

root@kitploit:~
if (buflen == 0) {
  break;
}

参见 patch.diff。阅读补丁能告诉你伤口在哪里 —— 这就是技能所在。

实验环境

Dockerfile 从历史 Debian 快照源码编译 ProFTPD 1.3.3a, 故意设置为不安全状态(这就是 2010 年的样子):

  • -fno-stack-protector — 无金丝雀(canary)
  • -z execstack — 可执行栈(无 NX)
  • -no-pie — 固定二进制地址
  • 在 gdb 下运行,gdb 默认禁用 ASLR → 确定性栈
root@kitploit:~
docker build -t proftpd-133a .
docker rm -f lab133 2>/dev/null
docker run -d --name lab133 --cap-add SYS_PTRACE \
  --security-opt seccomp=unconfined -p 127.0.0.1:2122:21 \
  proftpd-133a sh -c 'gdb -batch -ex "set follow-fork-mode child" \
  -ex "run" -ex "continue" --args /usr/local/sbin/proftpd -n -d1 \
  > /tmp/gdb.txt 2>&1; sleep 600'
python3 exploit.py

预期输出:

root@kitploit:~
[S] 220 ProFTPD 1.3.3a Server (lab-iac) ...
[S] THE SERVER SAID: b'PWNED!!PWNED!!'

(Shellcode 写入文件描述符 0、1 和 2,因为我们不想依赖 知道哪一个承载控制通道 —— 其中两个会响应。)

漏洞利用架构

root@kitploit:~
"SITE " + NOP 滑板 + shellcode + [\xff\xff 洪水] + 填充 + [ret] + "\n"
 ^^^^^^^^^^^^^^^^^^^^                             ^^^^
 shellcode 位于命令缓冲区内部             溢出尾部仅
 —— 无人触碰的区域                       传递一个地址
  1. "SITE " 保持 FTP 解析器存活 —— 命令被干净地处理。
  2. Shellcode 就是命令的内容。 缓冲区是栈上最安全的位置: 读取之后,只有 buf[4102] 被触碰(截断 NUL)。帧的活动局部变量 之下的所有内容都是平静的。
  3. IAC 洪水 将 buflen 驱动到下溢(关于奇偶性问题,参见"旅程")。
  4. ret 槽位(buf + 4152)接收 NOP 滑板中间位置的地址。 当 pr_cmd_read 在解析后命中 return 0 时,CPU 落入滑板 并滑入 shellcode。

这种倒置架构 —— 载荷在前,洪水居中,地址最后 —— 已针对规范的 Metasploit 模块(proftp_telnet_iac)进行了验证, 该模块使用相同的布局。他们的目标启用了 NX,因此需要带有 res 指针"四重解引用"的 ROP 链;我们的实验环境具有可执行栈, 因此单次直接返回就足够了。

旅程(本仓库的真正意义所在)

最终的漏洞利用程序只有 60 行。它的代价是:

  1. 盲目的 \xff 洪水 → 毫无效果。 服务器礼貌地关闭了会话。 根本原因:buflen 从 4102(偶数)开始,每对 IAC 递减 2 —— 它干净地落在 0 上,永远不会落在 1 上。下溢需要打破奇偶性。 教训:阅读状态机胜过盲目喷洒。

  2. 错误的缓冲区大小。 第一次校准尝试假设缓冲区为 1024 字节。 实际大小在 Linux/glibc 上是 MAXPATHLEN+8 = 4104。洪水在距离 目标 3KB 处停止。教训:测量目标,不要假设目标。

  3. 第一次 SIGSEGV。 循环 De Bruijn 模式(Aa0Aa1...)将返回槽位 定位在 buf+4152,经过两次交叉验证(帧数学 + 模式偏移)。 教训:循环模式是测量尺,不是漏洞利用。

  4. RIP 控制。 将槽位设置为 0x4141414141414141 导致 ret 指令本身崩溃 —— x86-64 拒绝非规范地址,故障落在 ret 上, 而我们的值在回溯中等待。教训:ret 上崩溃且帧中有你的值 = 已控制。

  5. ret 槽位上方的 shellcode → 被破坏。 8 个字节被堆指针 (0x4d7838 —— 后来被识别为 cmd_rec 池分配)覆盖。 槽位上方的栈帧属于在着陆与劫持之间仍在工作的函数。 教训:溢出不是最后一次写入;程序在你刚刚破坏的栈上继续运行。

  6. 槽位下方的"死区" → 也被涂写。 pr_cmd_read 自身的局部变量 (cmd、buflen、cp)恰好位于那里,并在解析期间持续被存储。

  7. 硬件监视点取证。 在 gdb 中使用 watch *(long*)ADDR 将谜团变成了摄像机:对被破坏地址的每次写入,都带有回溯, 按顺序呈现。教训:当问题是"谁写了这块内存?"时, 答案就在一条 gdb 命令之遥。

  8. 先阅读参考资料,然后理解它。 规范模块确认了倒置架构。 在建立自己的心智模型之后阅读他人的漏洞利用是学习; 在此之前阅读则是抄袭。

经验教训

  • size_t 永远不会变负 —— 它会变得巨大。 剩余空间计数器中的 整数下溢就是带额外步骤的栈溢出。
  • 奇偶性是一种武器。 当原语以 2 为单位递减时,你可以通过控制 奇偶性来控制下溢,而不仅仅是控制大小。
  • 坏字符是一个协议问题。 我们的 shellcode 刻意避免 \x0a (结束读取)并能在 \xff(TELNET 转义)下存活。
  • Prefork 服务器容忍崩溃。 子进程死亡,父进程继续接受连接: 无限次尝试。可靠性工程是漏洞利用的一部分。
  • 测量并利用同一个工件。 argv[0] 中 11 字节的差异 (构建树二进制 vs 已安装二进制)将整个栈偏移了 0x40, 并悄然使一个完美的漏洞利用失效。
  • 信号处理器会坦白。 ProFTPD 捕获 SIGSEGV 并记录 "terminating (signal 11)" —— 即使内核保持沉默, 目标也会告诉你它已经死亡。

参考资料

  • 修复提交:https://github.com/proftpd/proftpd/commit/3cc69b8388
  • CVE:https://nvd.nist.gov/vuln/detail/CVE-2010-4221
  • 规范模块:modules/exploits/linux/ftp/proftp_telnet_iac.rb (rapid7/metasploit-framework)
  • 姊妹实验环境(逻辑漏洞,同一守护进程):CVE-2015-3306 mod_copy

作者

由 diegslva 构建,公开学习 —— 从"从未写过漏洞利用"到 使用手工编写的 shellcode 实现预认证 RCE,在一个有记录的日内完成。 如果本仓库让你学到了什么,请将这份知识传递下去。

下载工具