smuggle应用层模拟请求走私
课堂测试题
解题过程
使用Gemini辅助分析和写脚本
源码分析
app.py里搜索flag,发现只有路由/api/v1/data会返回flag:
1 |
|
这是一个HTTP请求走私检测与利用接口。接下来分析到达这个分支的条件
- 需要是管理员路径,符合才会提取参数,即路径需要为’/admin/report’
1 | ADMIN_PATH = '/admin/report' |
检测并拒绝可疑的代理头
1
2
3
4
5
6
7
8
9
10suspicious_headers = [
'A-Forwarded-For', 'Forwarded-For-Bypass', 'X-Custom-Forwarded-For',
'For-Forwarded', 'A-For-Forwarded', 'X-Forwarded-For-Original',
'Forwarded-For', 'X-Real-IP', 'X-Client-IP', 'Client-IP',
'X-Original-Forwarded-For', 'Forwarded', 'True-Client-IP'
]
for sus_header in suspicious_headers:
if sus_header in smuggled_headers:
return jsonify({"status": "ok", "processed": False, "timestamp": int(time.time())}), 200trace_bytes是否正确
1
2
3actual_trace_bytes = get_proxy_trace_bytes(request.headers)
if int(offset_header) != actual_trace_bytes:
return jsonify({"processed": False})其中get_proxy_trace_bytes是计算了PROXY_TRACE_HEADERS列表中的header总长
1
2
3
4
5
6def get_proxy_trace_bytes(headers):
return sum(
len(f"{header}: {headers.get(header)}\r\n")
for header in PROXY_TRACE_HEADERS
if headers.get(header)
)管理员ip和session验证,签名验证:is_admin_ip()检查是否是管理员ip,check_session_ready检查会话有效性,最后检查签名
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25if smuggled_ip and is_admin_ip(smuggled_ip):
cookie_header = smuggled_headers.get('Cookie', '')
session_token = None
smuggled_user_id = None
for part in cookie_header.split(';'):
part = part.strip()
if 'session_token=' in part:
session_token = part.split('=', 1)[1]
elif 'user_id=' in part:
smuggled_user_id = part.split('=', 1)[1]
if not check_session_ready(session_token, smuggled_user_id):
return jsonify({"status": "ok", "processed": False, "timestamp": int(time.time())}), 200
expected_signature = build_admin_signature(
smuggled_user_id,
session_token,
actual_trace_bytes,
smuggled_method,
smuggled_path
)
if trace_header != expected_signature:
return jsonify({"status": "ok", "processed": False, "timestamp": int(time.time())}), 200
is_admin_ip函数实际上是检查是不是内网ip。
1 | def is_admin_ip(ip): |
处理逻辑是先标准化,只提取第一个ip,通过ip前缀判断是否来自内网
| 前缀 | 完整范围 | CIDR | 类型 |
|---|---|---|---|
127. |
127.0.0.0 - 127.255.255.255 | 127.0.0.0/8 | 本地回环(localhost) |
10. |
10.0.0.0 - 10.255.255.255 | 10.0.0.0/8 | A类私有地址 |
192.168. |
192.168.0.0 - 192.168.255.255 | 192.168.0.0/16 | C类私有地址 |
172.16. |
172.16.0.0 - 172.31.255.255 | 172.16.0.0/12 | B类私有地址 |
check_session_ready的要求是session的depth需要>= 2
1 | def check_session_ready(token, user_id): |
build_admin_signature用user_id、token等参数通过sha256生成管理员签名
1 | def build_admin_signature(user_id, token, trace_bytes, method, path): |
要成功获取flag,需要通过上面所有检查
如何提升depth
搜索分析发现/con端点可以将depth提升到1,条件是content_length > 0,即POST报文
1 |
|
/api/v1/sync可以将depth提升到2,条件是depth >= 1和正确的router key
1 |
|
build_route_key逻辑如下,由此可以计算出正确的router key
1 | def build_route_key(user_id, token, trace_bytes): |
计算关键变量 trace_bytes
根据 app.py 中的 PROXY_TRACE_HEADERS 列表:
1 | PROXY_TRACE_HEADERS = [ |
我们需要计算以下四个 Header 在经过所有代理后的总长度,这四个Header分别可以在haproxy.cfg和httpd.conf文件中找到:
| Header 名称 | 值 (Value) | 计算长度 (Key + “: “ + Value + “\r\n”) | 字节数 |
|---|---|---|---|
| X-Haproxy-Version | “2.0.14” | X-Haproxy-Version: 2.0.14\r\n |
17+2+6+2 = 27 |
| X-Proxy-Instance | “frontend-01” | X-Proxy-Instance: frontend-01\r\n |
16+2+11+2 = 31 |
| X-Apache-Layer | “reverse-proxy” | X-Apache-Layer: reverse-proxy\r\n |
14+2+13+2 = 31 |
| X-Backend-Route | “layer3” | X-Backend-Route: layer3\r\n |
15+2+6+2 = 25 |
| 总计 (TRACE_BYTES) | 27 + 31 + 31 + 25 | 114 |
所以trace_bytes应该是114
题解思路
获取session:
- 访问
/获取user_id - 向
/con发送带请求体的 POST 请求,获取session_token
提升session的depth:
向/con端点发送POST请求,将depth提升到1
计算router key,请求/api/v1/sync,将depth提升到2
对走私的请求的构造:
- 请求路径为/admin/report
- 设置ip为127.0.0.1,trace_bytes为114
- 用build_admin_signature的逻辑计算出的签名
最后构造请求走私报文,提交到 /api/v1/data
需要注意一下代码要求 if body and '\r\n\r\n' in body,换行符需要很严谨地处理。题解脚本:
1 | import requests |
运行输出:
1 | [+] User ID: 71fe03fb-36cb-4efd-9a30-f1778aca3593 |
得到flag:
1 | flag{double_proxy_single_flag} |
技术点总结
多层反向代理架构:多层反向代理是指在客户端与后端服务器之间部署多个中转节点。每经过一层代理,代理服务器都可能根据配置对 HTTP 报文进行修改,比如修改
X-Forwarded-For头部以记录原始 IP,或插入自定义的追踪 ID。权限提升状态机:状态机是一种控制逻辑,用于确保用户必须按照预设的线性或分支路径执行操作。权限提升状态机中,用户初始处于未授权状态,必须通过一系列特定的行为来触发状态迁移。只有当前状态满足目标操作的准入条件时,系统才允许执行该操作并可能将其提升至下一状态。这种设计被用于防止越权访问。
应用层模拟请求走私:请求走私本质上源于解析歧义。传统走私是由于在代理与后端对 HTTP 协议标准的实现差异,而应用层模拟走私则利用了后端应用程序在处理报文内容时的二次解析漏洞。攻击者可以在请求体中嵌入一个完整的、伪造的 HTTP 报文,后端代码可能将嵌入数据当成另一个请求来处理。这种方式通常用于绕过前端代理的路径过滤或 IP 限制。
消息摘要与数字签名:消息摘要是利用散列函数将任意长度的数据映射为固定长度的字符串,具有不可逆性和雪崩效应。而数字签名则是摘要技术的进阶应用,通常结合“盐值”或私钥进行计算。其核心目的是保证数据的完整性和不可否认性。
IP 来源伪造与绕过:如果后端服务器未经校验就直接信任请求头中的
X-Forwarded-For字段,攻击者就可以在 HTTP 请求中手动构造该头部,填入受信任的地址,从而绕过原本针对外部网络的防火墙规则或身份验证逻辑。