
CVE-2025-23211 の概念実証(PoC)。データプライバシー関連の脆弱性を再現し、認可されたセキュリティ検証、パッチテスト、緩和策の検証を支援します。
Tandoor Recipes におけるサーバーサイドテンプレートインジェクション(SSTI)脆弱性 CVE-2025-23211 を実証する、自己完結型で再現可能な環境です。この脆弱性はリモートコード実行につながります。このリポジトリは、脆弱なバージョンを起動してエクスプロイトを示し、次に修正済みバージョンを起動して同じ入力が拒否されることを示します。
| CVE | CVE-2025-23211 |
| Advisory | GHSA-r6rj-h75w-vj8v |
| Class | Server-Side Template Injection (Jinja2), CWE-94 / CWE-1336 |
| CVSS 3.1 | 9.9 (Critical) - AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H |
| Affected | Tandoor Recipes <= 1.5.23 |
| Fixed in | 1.5.24 |
Tandoor Recipes は、オープンソースのセルフホスト型食事計画・レシピ管理アプリケーションです。バックエンドは Gunicorn で提供される Django(Python)、フロントエンドは Vue.js のシングルページアプリケーション、データは PostgreSQL に保存されます。
Tandoor では、ユーザーがレシピの手順テキスト内に Jinja2 テンプレート式を記述できます。これは食材の分量をスケーリングするなどのタスクを目的とした機能です。これらの手順はサーバー上でレンダリングされます。
手順テキストは cookbook/helper/template_helper.py の render_instructions() によって処理されます。影響を受けるバージョンでは、テキストは Jinja2 のデフォルトの Template() に渡され、サンドボックスなしでレンダリングされます:
template = Template(instructions)
instructions = template.render(ingredients=ingredients, scale=scale)
デフォルトの Jinja2 環境は Python のオブジェクトグラフを公開しているため、レシピを編集できる認証済みユーザーは、空のタプルから subprocess.Popen にたどり着き、サーバー上で任意のコマンドを実行するテンプレート式を注入できます。デフォルトのコンテナは root として実行されるため、コマンドは root として実行されます。
手順は Jinja2 がレンダリングする前に Markdown 処理を経由し、その処理で生のアンダースコアが除去されます。そのため、エクスプロイトではアンダースコアを16進数エンコード(\x5f\x5f)し、ドット記法の代わりに attr() フィルターを使用します。payloads.txt を参照してください。
compose-vulnerable.yml Tandoor 1.5.23 (脆弱) と PostgreSQL
compose-patched.yml Tandoor 1.5.24 (修正済み) と PostgreSQL
payloads.txt 2つの概念実証ペイロードと期待される出力
enumerate-popen.txt 他のイメージで subprocess.Popen のインデックスを見つけるためのヘルパー
このリポジトリの証拠は以下のセットアップで取得されました。Compose v2 以降を備えた最近の Docker リリースであれば、同じ結果が再現されます。
脆弱なスタックを起動します:
docker compose -f compose-vulnerable.yml up -d
web コンテナのログにワーカーの起動が表示されるまで待ち、http://localhost:8080 を開きます。
最初のアカウントを登録します。最初のユーザーはスーパーユーザーになります。プロンプトが表示されたら Space 名を設定します。
新しいレシピを作成します。Steps > Instructions フィールドに payloads.txt の Payload 1 を貼り付け、保存します。
レシピを開いて表示します。レンダリングされた手順に <class 'subprocess.Popen'> が表示され、インジェクションが Python の内部に到達していることが確認できます。
Payload 2 で繰り返します。レシピを表示すると whoami が実行され、root が表示されます。
プロセスのユーザーを直接確認します:
docker compose -f compose-vulnerable.yml exec web_recipes whoami
脆弱なスタックを停止します:
docker compose -f compose-vulnerable.yml down
修正済みスタックを起動します:
docker compose -f compose-patched.yml up -d
同じペイロードで同じ手順を繰り返します。実行される代わりに、レシピの表示に Could not parse template code. Error: Undefined Error が表示されるようになります。
すべてをリセットして空のデータベースから開始するには、docker compose -f compose-vulnerable.yml down -v を実行します。-v フラグは名前付きボリュームを削除するため、次回の実行でデータベースがゼロから再作成されます。
パッチ(コミット e6087d5)は、サンドボックス化されていない Template() を Jinja2 の SandboxedEnvironment に置き換えます:
env = SandboxedEnvironment()
instructions = env.from_string(instructions).render(ingredients=ingredients, scale=scale)
サンドボックスは Jinja2 の属性ルックアップをオーバーライドし、__class__、__base__、__subclasses__、__getitem__ などの安全でない属性へのアクセスを禁止します。これらがなければ、ペイロードはプレーンなオブジェクトから subprocess.Popen への経路を持たないため、式は実行される代わりに失敗します。
このエクスプロイトはドット記法ではなく attr() フィルターを通じて属性に到達するため、修正済みコードではブロックされたアクセスが SecurityError ではなく UndefinedError として表面化します。どちらもサンドボックス版によって処理されます。
このリポジトリは、教育目的で公に開示され既に修正済みの脆弱性を再現するものです。ここで提供されるローカルコンテナに対してのみ実行してください。所有していない、またはテストを許可されていないシステムを対象にしないでください。