idor-broken-object-authorization について
IDORやオブジェクトレベル認可の欠陥を許可された範囲で検証するAPIテストスキルです。複数アカウント、リクエスト内のID、HTTPメソッド、間接参照を調べ、オブジェクト単位と機能単位の問題を区別します。
主な機能
- 012アカウントでの検証方法
- 02リクエスト内の各箇所にあるIDの確認
- 03HTTPメソッド別の認可テスト
- 04間接参照とサブリソースの検査
- 05BOLAとBFLAの区別
- 06一括操作と隠しフィールドの確認
活用シーン
- 01アカウント間のデータ閲覧・変更の検証
- 02管理用エンドポイントの機能レベル認可確認
- 03添付ファイルなど関連リソースのアクセス制御テスト
- 04APIの状態遷移と一括操作の評価
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ yaklang/ hack-skills --skill idor-broken-object-authorizationインストール後、Claude Code で/idor-broken-object-authorizationを入力して読み込まれたか確認します。
詳細
スキル:IDOR/オブジェクトレベルの認可不備 — エキスパート向け攻撃プレイブック
AI LOAD INSTRUCTION:IDOR はバグバウンティで最も多く見つかる脆弱性です。このスキルでは、見落とされやすい IDOR の攻撃対象、あらゆる攻撃ベクトル(URL パラメーターだけではありません)、A-B テストの手法、BOLA と BFLA の違い、IDOR を連鎖させて影響を拡大する方法、テスターが繰り返し見落とす点を解説します。
1. IDOR、BOLA、BFLA の違い
| 用語 | 意味 | 影響 |
|---|---|---|
| IDOR | 安全でない直接オブジェクト参照 | 他のユーザーのデータの読み取り/変更 |
| BOLA | オブジェクトレベルの認可不備(OWASP API Top 10 A1) | IDOR と同じ意味で使われる API 用語 |
| BFLA | 機能レベルの認可不備 | 低権限ユーザーが高権限の機能(管理者エンドポイントなど)にアクセスすること |
重要な違い:
- BOLA = 所有する権限のないオブジェクト(他のユーザーに属するデータ)へのアクセス
- BFLA = 権限のない機能(管理者向け CRUD 操作、一括操作、ユーザー管理)へのアクセス
2. オブジェクト ID の場所(すべての場所)
URL のパスパラメーターだけでなく、ID は次の場所にも現れます。
URL path: GET /api/v1/users/1234/profile
URL query: GET /orders?order_id=982
Request body: {"userId": 1234, "action": "view"}
JSON fields: {"resource": {"id": 5678, "type": "invoice"}}
Headers: X-User-ID: 1234
X-Account-ID: 9999
Cookies: user_id=1234; account=org_5678
GraphQL args: query { user(id: "1234") { ... } }
Form fields: <input name="documentId" value="5678">
WebSocket msgs: {"event":"subscribe","channel_id":9999}
3. A-B テストの手法
最も体系的な IDOR テストのアプローチ:
Step 1: Create two test accounts: UserA and UserB
Step 2: Perform all actions as UserA, capture all requests
(profile edit, order view, password change, file access, etc.)
Step 3: Note every object ID created or accessed by UserA
Step 4: Authenticate as UserB
Step 5: Replay UserA's requests using UserB's session token
Step 6: If UserB can read/modify UserA's data → BOLA confirmed
Victim matters: for real bugs, target existing users, not test accounts.
Report evidence: show UserA owns the resource, UserB accessed it.
4. ID の種類とその影響
| ID のパターン | 例 | 備考 |
|---|---|---|
| 連番の整数 | id=1001 → id=1002 | 予測が容易で、ヒット率が高い |
| UUID v4 | 550e8400-... | 他のエンドポイントから UUID を見つける必要があります |
| UUID v1 | 時刻ベースの UUID | 時刻から予測可能です!タイムスタンプ/MAC を抽出します |
| 自分のデータに含まれる GUID | レスポンスで確認 | まず自分のアカウントデータからすべての UUID を収集します |
| ハッシュ化された ID | md5(user_id) | 連番の整数をハッシュ化して試します |
| エンコードされた ID | base64({"id":1001}) | デコード → 変更 → 再エンコード |
| 複合 ID | /api/users/1/orders/5 | どちらの ID も個別に検証される可能性があります |
5. 水平/垂直権限昇格
水平:UserA が UserB のデータにアクセスする(同じ権限レベル)
GET /api/account/1234/statement ← you are user 5678
垂直:低権限ユーザーが管理者専用の機能にアクセスする
POST /api/admin/users/delete ← normal user calling admin endpoint
GET /api/admin/all-users
PUT /api/users/1234/role {"role":"admin"}
複合:権限昇格につながる低権限 IDOR
GET /api/v1/users/1/details → read admin user's auth token
6. HTTP メソッドによる権限昇格
GET /resource/1234 が適切に制限されている場合は、他のすべてのメソッドもテストします。
GET /api/v1/users/UserA_ID ← might be blocked
POST /api/v1/users/UserA_ID ← different code path, might not check authz
PUT /api/v1/users/UserA_ID ← update another user's data
DELETE /api/v1/users/UserA_ID ← delete another user's account
PATCH /api/v1/users/UserA_ID ← partial update (often missed in authz checks)
有効な理由:認可ロジックはメソッドごとに実装されていることが多く、開発者が例外ケースを見落とすためです。
7. パラメーター汚染と型の混同
id=1234 が検証されている場合は、次を試します。
id[]=1234&id[]=5678 ← array — app may use first or last
id=5678&id=1234 ← duplicate — app may prefer first or last
{"id": "1234"} ← string vs int: might hit different code path
{"id": [1234]} ← array in JSON
{"userId": 1234, "id": 5678} ← two ID fields — which is used for authz?
JSON の型の混同:
{"userId": "1234"} vs {"userId": 1234}
ORM によっては、クエリで文字列と整数の処理が異なる場合があります。
8. BFLA(機能レベル)攻撃
テストする一般的な BFLA エンドポイント
# User management (admin-only in design):
GET /api/v1/admin/users
DELETE /api/v1/users/{any_user_id}
PUT /api/v1/users/{user_id}/role
# Bulk operations:
POST /api/v1/users/bulk-delete
GET /api/v1/export/all-data
# Billing/payment admin:
POST /api/v1/admin/subscription/modify
GET /api/v1/admin/payments/all
# Internal reporting:
GET /api/v1/reports/all-users-activity
隠れた管理者エンドポイントの見つけ方
- JS バンドルを確認します。管理者ルートがフロントエンドのコードで公開されていることがよくあります
- API ドキュメント(Swagger/OpenAPI)で「admin」、「internal」、「privileged」タグを確認します
/api/v1/admin/**、/api/v1/manage/**、/api/v1/internal/**を列挙します- API のベースパスに対して Burp の「Discover Content」を実行します
- 利用できる場合は、一般ユーザー向けドキュメントと管理者向けセクションのドキュメントを比較します
9. 間接的な IDOR(参照チェーン)
アプリがオブジェクト Aの権限を確認しても、参照先のオブジェクト Bの所有権は確認しない場合があります。
例:
UserA has permission to read their own messages.
GET /api/messages/1234 → checks: "does user own message 1234?" ✓
But: messages have attachments.
GET /api/attachments/5678 → doesn't check: "does attachment belong to message owned by user?"
テスト:親エンドポイントを経由せず、添付ファイル/サブリソースの ID を使って直接アクセスできるか確認します。
GraphQL のバリエーション:別途認可せずに、関連オブジェクトをインラインでクエリする場合:
query {
myProfile {
followers {
privateEmail ← accessing private field of OTHER users via relationship
}
}
}
10. 一括割り当て → 権限昇格
POST/PUT が JSON ボディを受け取る場合、公式 API ドキュメントに記載されていなくても、内部モデルのプロパティを設定できることがあります。
POST /api/v1/register
{
"username": "attacker",
"email": "[email protected]",
"password": "password",
"role": "admin", ← hidden field
"isAdmin": true, ← hidden field
"verified": true, ← skip email verification
"creditBalance": 9999 ← give self credits
}
隠れたフィールドの見つけ方:
- 管理者向けの「ユーザー作成」と通常の「登録」を傍受し、フィールドを比較します
- すべての可能なフィールドについて API ドキュメントを確認します
- 利用できる場合は、ソースコード(GitHub、JS バンドル)を確認します
- Burp でファジングします。一般的なプロパティ名を追加し、
200と400のどちらが返るか確認します
11. 状態遷移の悪用(ビジネスロジック IDOR)
リソースにステータス/状態がある場合:
order.status: pending → confirmed → shipped → delivered
テスト:状態をスキップできますか?
PUT /api/orders/1234 {"status": "delivered"} ← from "pending"
PUT /api/orders/1234 {"status": "refunded"} ← from "pending" (skip shipped)
他のユーザーの注文ステータスを設定できますか?
PUT /api/orders/UserA_order_id {"status": "cancelled"} ← as UserB
12. IDOR の簡易チェックリスト
□ Create 2 accounts (UserA + UserB)
□ Map all API calls that contain object IDs (Burp History export filter)
□ Test all HTTP verbs on each endpoint
□ Test ID in all locations: path, body, header, query, cookie
□ Try sequential IDs (−1, +1 from your own)
□ Try UUIDs/GUIDs collected from your own account data
□ Test sub-resources (attachments, comments, transactions)
□ Test admin endpoints directly (BFLA)
□ Test POST/PUT body for extra fields (mass assignment)
□ Compare JSON response field count vs documented fields (hidden fields)
□ Test state/status field modification
13. IDOR の体系的なテスト — 8 つのカテゴリ
| # | カテゴリ | テスト方法 |
|---|---|---|
| 1 | ID の直接参照 | URL 内の数値 ID/UUID を変更します:/api/users/123 → /api/users/124 |
| 2 | 推測可能な UUID | UUID が v1(時刻ベース)の場合、隣接する ID を計算できます |
| 3 | バッチ/一括操作 | /api/users/bulk?ids=123,456 — 他のユーザーの ID を追加します |
| 4 | エクスポート/ダウンロード | エクスポートエンドポイントから他のユーザーのデータが漏えいします:/export?user_id=* |
| 5 | リンクされたオブジェクトの IDOR | order.address_id を別のユーザーの住所に変更します |
| 6 | リソースの置き換え | 自分のプロフィールを別のユーザーのリソース ID で更新すると、上書きされます |
| 7 | 書き込み IDOR | 他のユーザーの ID を指定して PUT/PATCH/DELETE を実行し、そのデータを変更/削除します |
| 8 | ネストされたオブジェクト | /api/orgs/1/users/2 — 組織 ID を変更して、他の組織のユーザーにアクセスします |
テストの流れ
1. Create two test accounts (A and B)
2. Perform all CRUD operations as A, capture all request IDs
3. Replay each request replacing A's IDs with B's IDs
4. Check: Can A read B's data? Modify? Delete?
5. Test with: numeric IDs, UUIDs, slugs, encoded values
6. Test across: URL path, query params, JSON body, headers
14. ORM のフィルターチェーンによる漏えい
Django ORM のフィルターインジェクション
# Vulnerable: User.objects.filter(**request.data)
# Attacker sends: {"password__startswith": "a"}
# Django translates to: WHERE password LIKE 'a%'
# Character-by-character extraction:
POST /api/users/
{"username": "admin", "password__startswith": "a"} → 200 (match)
{"username": "admin", "password__startswith": "b"} → 404 (no match)
# Iterate through charset for each position
# Relational traversal:
{"author__user__password__startswith": "a"}
# Traverses: Author → User → password field
# On MySQL: ReDoS via regex
{"email__regex": "^(a+)+$"} → CPU spike if match exists
Prisma のフィルターインジェクション
// Vulnerable: prisma.user.findMany({ where: req.body })
// Attacker sends nested include/select:
{
"include": {
"posts": {
"include": {
"author": {
"select": {"password": true}
}
}
}
}
}
// Leaks password field through relation traversal
Ransack(Ruby on Rails)
# Ransack allows search predicates via query params:
GET /users?q[password_cont]=admin
# Searches: WHERE password LIKE '%admin%'
# Character extraction:
GET /users?q[password_start]=a → count results
GET /users?q[password_start]=ab → narrow down
# Tool: plormber (automated Ransack extraction)