其他
当 Web/API 请求暴露对象标识符、租户边界…
适用于任何有用户、对象 ID、多租户或权限边界的 Web/API 安全测试,用于发现越权读写与功能级越权。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 48/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 85/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 53/100 ×0.15
适用场景
当 Web/API 请求暴露对象标识符、通用
解决思路
- 区分 BOLA 与 BFLA:BOLA 是访问你不应拥有的对象(其他用户的数据);BFLA 是访问你不应被授权的功能(admin CRUD、批量操作、用户管理);IDOR 与 BOLA 同义,是 API 术语。
- 不要只检查 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})。
- 执行 A-B 测试:Step 1 创建两个测试账号 UserA 和 UserB;Step 2 以 UserA 执行所有操作并捕获所有请求(资料编辑、订单查看、密码修改、文件访问等);Step 3 记录 UserA 创建或访问的每个对象 ID;Step 4 以 UserB 认证;Step 5 使用 UserB 的会话 token 重放 UserA 的请求;Step 6 如果 UserB 能读取/修改 UserA 的数据,则确认 BOLA。对于真实漏洞,目标应是现有用户而非测试账号。报告证据时展示 UserA 拥有资源且 UserB 访问了它。
- 按 ID 模式测试:顺序整数(id=1001 → id=1002,易预测);UUID v4(需要从其他端点找到 UUID);UUID v1(基于时间,可预测,提取 timestamp/MAC);从自己数据中收集 GUIDs(先收集自己账号响应中的所有 UUID);哈希 ID(md5(user_id),尝试哈希顺序整数);编码 ID(base64({"id":1001}),解码→修改→重新编码);复合 ID(/api/users/1/orders/5,两个 ID 可能需独立验证)。
- 测试水平越权:以 UserA 身份访问 UserB 的数据,例如 GET /api/account/1234/statement(你实际是 user 5678)。
- 测试垂直越权:低权限用户访问 admin-only 功能,例如 POST /api/admin/users/delete、GET /api/admin/all-users、PUT /api/users/1234/role {"role":"admin"}。
- 测试组合越权:低权限 IDOR 导致提权,例如 GET /api/v1/users/1/details 读取 admin 用户的 auth token。
- 当 GET /resource/1234 被正确限制时,测试所有其他 HTTP 动词: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;因为授权逻辑常按方法实现,开发者会遗漏 PATCH 等边界情况。
- 当 id=1234 被校验时,尝试参数污染与类型混淆:id[]=1234&id[]=5678(数组,应用可能使用第一个或最后一个);id=5678&id=1234(重复参数,应用可能偏好第一个或最后一个);{"id": "1234"}(字符串 vs 整数,可能进入不同代码路径);{"id": [1234]}(JSON 数组);{"userId": 1234, "id": 5678}(两个 ID 字段,确认哪个用于授权)。
- 测试 JSON 类型混淆:{"userId": "1234"} vs {"userId": 1234};某些 ORM 对字符串和整数查询处理不同。
- 测试常见 BFLA 端点:GET /api/v1/admin/users、DELETE /api/v1/users/{any_user_id}、PUT /api/v1/users/{user_id}/role;批量操作 POST /api/v1/users/bulk-delete、GET /api/v1/export/all-data;计费/支付管理 POST /api/v1/admin/subscription/modify、GET /api/v1/admin/payments/all;内部报告 GET /api/v1/reports/all-users-activity。
- 查找隐藏 admin 端点:1 阅读 JS bundles,admin 路由常暴露在前端代码中;2 查看 API 文档(Swagger/OpenAPI)中的 admin、internal、privileged 标签;3 枚举 /api/v1/admin/**、/api/v1/manage/**、/api/v1/internal/**;4 在 API base path 上使用 Burp Discover Content;5 比较普通用户文档与 admin 部分文档(如有)。
- 测试间接 IDOR(引用链):应用检查对象 A 的权限但不检查被引用对象 B 的所有权;例如 UserA 有权读取自己的消息 GET /api/messages/1234,但消息有附件 GET /api/attachments/5678 不检查附件是否属于该消息;直接通过子资源 ID 访问附件/子资源,而不经过父端点。
- 测试 GraphQL 变体:内联查询相关对象而不单独授权:query { myProfile { followers { privateEmail } } },通过关系访问其他用户的私有字段。
- 测试批量赋值提权:当 POST/PUT 接受 JSON body 时,底层模型属性可能即使不在 API 文档中也可设置;发送隐藏字段,例如 POST /api/v1/register {"username":"attacker","email":"a@evil.com","password":"password","role":"admin","isAdmin":true,"verified":true,"creditBalance":9999}。
- 查找隐藏字段:1 拦截 admin “create user” 与普通 “register” 请求,diff 字段;2 阅读 API 文档中所有可能字段;3 如可用检查源代码(GitHub、JS bundles);4 用 Burp fuzz 常见属性名,并检查返回 200 还是 400。
- 测试状态机滥用(业务逻辑 IDOR):当资源有 status/state 时,例如 order.status: pending → confirmed → shipped → delivered;测试能否跳过状态:PUT /api/orders/1234 {"status": "delivered"}(从 pending);PUT /api/orders/1234 {"status": "refunded"}(从 pending,跳过 shipped)。
- 测试能否以 UserB 身份设置 UserA 订单状态:PUT /api/orders/UserA_order_id {"status": "cancelled"}。
- 使用快速 IDOR 清单:创建 2 个账号(UserA + UserB);用 Burp History export filter 映射所有包含对象 ID 的 API 调用;对每个端点测试所有 HTTP verbs;测试 ID 在所有位置:path、body、header、query、cookie;尝试自己的 ID 的 −1、+1 顺序 ID;尝试从自己账号数据收集的 UUIDs/GUIDs;测试子资源(attachments、comments、transactions);直接测试 admin 端点(BFLA);测试 POST/PUT body 的额外字段(mass assignment);比较 JSON 响应字段数与文档字段(隐藏字段);测试 state/status 字段修改。
- 系统化 IDOR 测试 8 个类别:1 直接 ID 引用:将 URL 中数字/UUID ID 改为 /api/users/123 → /api/users/124;2 可预测 UUID:如果 UUID 是 v1(基于时间),相邻 ID 可计算;3 批量/批量操作:/api/users/bulk?ids=123,456 添加其他用户 ID;4 导出/下载:导出端点泄露其他用户数据 /export?user_id=*;5 链接对象 IDOR:将 order.address_id 改为其他用户地址;6 资源替换:用其他用户资源 ID 更新自己资料,导致覆盖;7 写 IDOR:用其他用户 ID 执行 PUT/PATCH/DELETE,修改/删除其数据;8 嵌套对象:/api/orgs/1/users/2 更改 org ID 访问其他组织的用户。
- 执行系统化测试流程:1 创建两个测试账号 A 和 B;2 以 A 执行所有 CRUD 操作,捕获所有请求 ID;3 重放每个请求,将 A 的 ID 替换为 B 的 ID;4 检查:A 能否读取 B 的数据?修改?删除?5 使用数字 ID、UUID、slugs、编码值测试;6 跨 URL path、query params、JSON body、headers 测试。
- 测试 Django ORM filter injection:易受攻击代码 User.objects.filter(**request.data);攻击者发送 {"password__startswith": "a"},Django 翻译为 WHERE password LIKE 'a%';逐字符提取:POST /api/users/ {"username": "admin", "password__startswith": "a"} → 200(匹配),{"username": "admin", "password__startswith": "b"} → 404(不匹配),对每个位置迭代字符集;关系遍历:{"author__user__password__startswith": "a"} 遍历 Author → User → password 字段;在 MySQL 上通过 regex 造成 ReDoS:{"email__regex": "^(a+)+$"},若存在匹配则 CPU 飙升。
- 测试 Prisma filter injection:易受攻击代码 prisma.user.findMany({ where: req.body });攻击者发送嵌套 include/select:{"include": {"posts": {"include": {"author": {"select": {"password": true}}}}}};通过关系遍历泄露 password 字段。
- 测试 Ransack(Ruby on Rails):Ransack 允许通过查询参数使用搜索谓词:GET /users?q[password_cont]=admin,搜索 WHERE password LIKE '%admin%';字符提取:GET /users?q[password_start]=a → 计数结果;GET /users?q[password_start]=ab → 缩小范围;工具:plormber(自动化 Ransack 提取)。
适用边界 · 注意事项
- 未获得明确授权时不要测试;不要对生产系统进行破坏性 PUT/DELETE/PATCH 或修改真实用户数据。
- 如果目标没有对象标识符、多用户数据隔离或功能级权限边界,IDOR/BOLA/BFLA 测试不适用。
- 如果无法创建或获取至少两个有效会话/账号,A-B 测试和所有权对比无法可靠执行。