在 Savant Web Server 3.1 中经典的基于堆栈的缓冲区溢出,演示了通过精心构造的 HTTP 请求在 2000 年代初期实现的远程内存破坏。
Savant Web Server 3.1 中经典的基于栈的缓冲区溢出,展示了 2000 年代初通过精心构造的 HTTP 请求实现的远程内存破坏(这是一个 2002 年的漏洞,其对初学者提出的挑战远超大多数 2025 年的缓冲区溢出 CVE)。
本仓库是我教授内存破坏漏洞利用时使用的材料之一(除了日常工作,我还在不同的网络安全课程中授课,帮助培训下一代逆向工程师)。课堂上讲解基于栈的缓冲区溢出时,我会使用真实案例而非玩具示例,而此 CVE 正是我在挑战已有基本 EIP 覆盖经验的学生时反复使用的案例。
原因是 CVE 标识符中的年份并不能说明利用难度。大多数学生认为老 CVE 必然更简单——直接的溢出、JMP ESP 即可完成。但此漏洞证明事实并非如此。我讲授的某些 2025 年 CVE 从根本上看比此漏洞更基础,因为漏洞类型更简单或限制更少。CVE-2002-1120 要求理解一些现代漏洞利用所不需要的知识:为什么某些操作码不能用在请求的某些部分、如何在无条件跳转被阻止时构造一个始终触发的条件跳转、如何在 shellcode 无法放入常规位置时传递它、以及 egg hunter 如何解决本会使利用无法实现的空间问题。
这是我用于检验学生是否真正理解自己行为的 CVE。
此漏洞影响 Savant 3.1,一款 2002 年的免费 Windows HTTP 服务器。它在 2025 年仍值得研究的原因不是其年代,而是其引入的限制组合以及绕过这些限制所需的技术:
这些限制中的每一个本身都是一堂课。
Savant 是一款免费的 Windows HTTP 服务器开源软件,最初设计用于将任何台式计算机变成 Web 服务器。它监听 TCP 端口 80 并处理标准的 HTTP GET 请求。溢出发生在将 URI 路径复制到固定大小的栈缓冲区时,未验证其长度。
关键技术细节:
Savant 处理传入的 HTTP GET 请求,并将 URI 路径复制到固定大小的栈缓冲区,但不检查其长度。易受攻击逻辑的简化版本如下:
char uri_buffer[270];
strcpy(uri_buffer, uri_path);
发送一个 URI 长度约为 271 字节的 GET 请求会覆盖栈上的保存返回地址。溢出是受限的:发送明显更长的数据会导致崩溃方式使 EIP 不再可控,因此可用窗口很窄。
由于服务器的两个额外属性,利用比标准 EIP 覆盖更复杂:HTTP 方法字段也被复制到内存并执行,HTTP 请求体存储在独立的堆区域而非栈上(这两个属性后来成为利用链的关键部分)。
可通过发送具有足够长 URI 的 GET 请求来重现崩溃。无需认证。使用 Python 的示例:
import socket
HOST = '127.0.0.1'
PORT = 80
payload = b"A" * 271
request = (
b"GET /" + payload + b" HTTP/1.1\r\n"
b"Host: 127.0.0.1\r\n"
b"Connection: close\r\n"
b"\r\n"
)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
s.send(request)
s.close()
在调试器下执行时,崩溃显示 EIP 被用户控制的数据覆盖:
EIP = 41414141
确认保存的返回地址已被溢出破坏。
此仓库的目标不仅是演示崩溃,而是逐步展示完整的利用过程,包括使此案例比标准 EIP 覆盖更难的每一项限制以及绕过它们所做的每一项决策。
为保持主 README 的整洁,详细的利用说明、脚本和调试步骤放在本仓库的 Vulnerability 📂 文件夹中。
在那里您可以找到用于利用此 CVE 的完整工作流程,包括: