## 一个数字引发的灾难
2024 年,Dell 的 partner portal API 因为一个简单的整数 ID 枚举,泄露了 4900 万条客户记录。攻击者没有利用零日漏洞,没有绕过 WAF,甚至没有伪造身份——他们只是用合法账号登录后,把 `/api/orders/1001` 里的 `1001` 改成了 `1002`、`1003`,直到拖完整张表。
这不是孤例。Salt Security 2024 年度报告显示,95% 的组织在过去 12 个月内遭遇过 API 安全事件,其中 23% 导致了实际数据泄露。OWASP API Security Top 10 自 2019 年发布以来,**失效的对象级授权(Broken Object Level Authorization,BOLA)** 连续两届稳居榜首。Gartner 在 2025 年初的预测已经应验:API 滥用已成为最频繁的攻击向量。
BOLA 的可怕之处在于它的"合法性"。攻击请求在语法上完全正确:有效的认证令牌、合规的 HTTP 方法、合理的参数格式。WAF 不会拦截,网关不会报警,日志里只留下一行状态码 200。问题出在业务逻辑层——服务器验证了"你是谁",却忘了问"这东西是不是你的"。
## 从 IDOR 到 BOLA:概念演进与授权间隙
BOLA 的前身是 OWASP 2007 年提出的 IDOR(Insecure Direct Object Reference,不安全的直接对象引用)。IDOR 描述的是**机制**:攻击者通过暴露或可预测的对象标识符直接引用内部资源。BOLA 描述的是**根因**:服务器未能验证请求者对特定对象的访问权限。
两者的关系是:IDOR 是 BOLA 最常见的利用技术之一,但 BOLA 的范围更广。即使对象标识符完全不暴露(例如使用不可预测的 UUID),只要服务器未在服务端强制校验所有权,攻击者通过其他途径获得标识符后仍然可以成功利用——这就是 BOLA 比 IDOR 更准确的命名原因。
在单体应用时代,授权是隐式的。服务器持有会话状态、控制数据库查询,整个授权决策在一个进程内完成。微服务与无状态架构拆散了这一模型:身份由外部 IdP 管理,数据分散在数百个服务中,API 网关只负责"查身份证",却不负责"查房产证"。F5 在 2026 年 2 月的技术分析中将这一断层称为 **"授权间隙"(Authorization Gap)**——成功登录与缺失所有权校验之间的空间。
```mermaid
flowchart LR
A[攻击者 User A] -->|合法登录| B[API Gateway]
B -->|JWT 有效| C[业务服务]
C -->|SELECT * FROM orders WHERE id=1234| D[数据库]
D -->|返回 User B 的数据| E[数据泄露]
style E fill:#fee2e2,stroke:#dc2626
```
上图揭示了一个关键事实:BOLA 不是编码 typo,而是架构设计问题。它要求防御方在请求链路的多个节点上植入所有权校验,而不是依赖开发者在每个接口里手写 `if (resource.ownerId === user.id)`。
## 六类真实模式:基于 100+ 份 Bug Bounty 披露的实证
2026 年 5 月,APIsec Research Labs 的 Bandana Kaur 在 arXiv 上发表了一项罕见的大规模实证研究。研究团队从 HackerOne 平台抽取了 200 份标记为 IDOR 或 Improper Access Control 的公开披露报告,经三标准筛选后得到 107 份可分类报告,其中 84 份(78.5%)被确认为严格意义上的 BOLA。研究同时揭示了一个重要噪声:约 21.5% 的报告在严格标准下并不属于 BOLA,说明平台上的标签计数显著高估了 BOLA 的实际比例。
基于这 84 份确认案例,论文提出了六类 BOLA 分类法。以下是每类的定义、占比(在确认案例中的分布)和典型攻击路径。
### 1. Direct Object Reference BOLA(直接对象引用型)
这是教科书级别的 BOLA。API 使用可预测的标识符(自增整数、短 UUID、时间戳序列)引用资源,且服务端未校验请求者是否为资源所有者。
**典型案例**:
```http
GET /api/v1/invoices/1001 HTTP/1.1
Authorization: Bearer eyJhbGciOiJSUzI1NiIs...
```
用户 A 登录后,将 `1001` 替换为 `1002` 到 `9999`,批量获取其他用户的订单详情。Salt Security 2024 报告中的 Dell 事件和某电信运营商 2025 年 1.1 亿客户元数据泄露均属于此类。
**标识符风险等级**:
| 标识符类型 | 可预测性 | 枚举难度 | 常见场景 |
|---|---|---|---|
| 自增整数 | 极高 | 极低 | 遗留系统、内部 API |
| 短 UUID(v1/v4 截断) | 中高 | 低 | 早期 SaaS |
| 雪花 ID(Snowflake) | 中 | 中 | 分布式系统 |
| 完整 UUID v4 | 低 | 高 | 现代应用 |
| Hash ID / nanoid | 低 | 高 | 安全优先设计 |
**工程要点**:使用 UUID 或 nanoid 只能提高枚举成本,不能替代服务端所有权校验。它们是"延缓"措施,不是"阻止"措施。
### 2. Action-Level Object BOLA(动作级对象型)
这是研究中占比最高的类别,达到 **41.7%**。其特点是:攻击者拥有对该对象类型的功能权限(例如"可以删除订单"),但缺少对**特定实例**的对象级权限(例如"不能删除别人的订单")。
OWASP 2023 将 BOLA(API1)与 BFLA(Broken Function Level Authorization,API5)分立,但实证数据显示大量漏洞恰好位于两者的交界——用户具备功能访问权,但缺乏对象级授权。传统的 BFLA 检测无法捕获这类问题,因为它检查的是"能否访问删除接口",而不是"能否删除这条特定的记录"。
**典型案例**:
```http
DELETE /api/v1/orders/1001 HTTP/1.1
Authorization: Bearer ...
```
后端校验了用户角色(`role === 'user'` 允许调用 DELETE),但未校验 `orders[1001].ownerId === currentUser.id`。结果:任何普通用户都可以删除任意订单。
### 3. Tenant Isolation BOLA(租户隔离型)
多租户 SaaS 的专属风险。API 在租户 A 的上下文中认证了用户,但未在数据库查询中注入 `tenant_id` 过滤条件,导致用户 A 可以访问租户 B 的数据。
**攻击路径**:
1. 攻击者在租户 A 注册账号,获得合法令牌
2. 通过文档或侦察得知租户 B 的资源 ID
3. 使用同一令牌访问租户 B 的资源
4. 服务端因缺少 `tenant_id` 校验而返回数据
PostgreSQL 的 Row-Level Security(RLS)是此类问题的最后一道防线,但前提是应用层在建立数据库连接时正确设置了 `SET ROLE` 或 `SET app.current_tenant_id`。
### 4. Workflow-Context BOLA(工作流上下文型)
资源访问权限依赖于业务流程状态,但 API 未校验状态上下文。例如:退款接口只允许对"未结算"订单操作,但攻击者通过直接调用 API 跳过了前端工作流,对"已结算"订单发起退款。
这类漏洞很难通过静态代码分析发现,因为授权逻辑的正确性取决于业务规则,而非纯技术约束。Carnegie Mellon SEI 2024 年的技术报告指出,BOLA 利用"往往源于设计问题而非纯实现缺陷"。
### 5. Chained Disclosure BOLA(链式泄露型)
攻击者无法直接猜测标识符,但通过某个合法 API 响应间接获取了其他对象的 ID,再利用这些 ID 发起越权访问。
**典型链路**:
```
GET /api/v1/projects/123 → 响应包含 members: [{userId: 456}, {userId: 789}]
GET /api/v1/users/456/profile → 泄露用户 456 的详细资料
```
即使所有标识符都是不可预测的 UUID,第一步的"过度数据暴露"仍然为第二步的 BOLA 提供了弹药。这正是 OWASP API3(Broken Object Property Level Authorization)与 API1 经常协同出现的原因。
### 6. Object Rebinding BOLA(对象重绑定型)
攻击者将原本属于自己的资源标识符,通过 API 的更新操作重新绑定到其他用户的资源上。例如:将发票中的 `customerId` 从自己的 ID 修改为其他用户的 ID,从而"劫持"该发票的查看权限。
这类漏洞在 GraphQL 可变操作(mutation)和 REST PATCH/PUT 接口中尤为常见,因为请求体允许客户端提交关联字段的变更。
### 六类模式汇总
```mermaid
graph TD
A[BOLA 六类模式] --> B[Direct Object Reference]
A --> C[Action-Level Object 41.7%]
A --> D[Tenant Isolation]
A --> E[Workflow-Context]
A --> F[Chained Disclosure]
A --> G[Object Rebinding]
B --> B1[整数枚举 / UUID 猜测]
C --> C1[有功能权限但无实例权限]
D --> D1[缺少 tenant_id 过滤]
E --> E1[跳过业务流程状态校验]
F --> F1[API1 + API3 协同]
G --> G1[PATCH/PUT 重绑定关联字段]
```
| 模式 | 核心特征 | 检测难点 | 防御重点 |
|---|---|---|---|
| Direct Object Reference | 可预测标识符 + 无所有权校验 | 代码审计易发现 | 服务端强制所有权过滤 |
| Action-Level Object | 功能权限通过,实例权限缺失 | 静态分析易误判为 BFLA | 数据库查询层双重校验 |
| Tenant Isolation | 跨租户数据泄露 | 多租户测试环境复杂 | RLS + 连接级租户上下文 |
| Workflow-Context | 业务流程状态被绕过 | 需理解业务规则 | 状态机校验不可前置到客户端 |
| Chained Disclosure | 合法响应泄露其他对象 ID | 单接口测试无法发现 | 响应脱敏 + 级联权限校验 |
| Object Rebinding | 通过更新操作劫持关联关系 | PATCH 请求体分析复杂 | 关联字段白名单 + 变更审计 |
## 工程化防御:从数据库查询层到边缘运行时
BOLA 的防御不能依赖开发者的个体谨慎。在数百个微服务、数千个端点的规模下,"每个接口手写所有权校验"是不可持续的人工策略。工程化防御的目标是将所有权校验转化为基础设施能力,降低正确实现的认知负担。
### 第一层:数据库查询层所有权过滤(最可靠)
最有效的 BOLA 防御是在数据访问层直接注入所有权条件。这样即使业务逻辑层遗漏了校验,数据库查询也不会返回越权数据。
**漏洞模式**:
```javascript
// 危险:只按 ID 查询,未限定所有者
const invoice = await db.invoices.findByPk(req.params.id);
```
**安全模式**:
```javascript
// 安全:查询条件同时包含 ID 和所有者
const invoice = await db.invoices.findOne({
where: {
id: req.params.id,
userId: req.user.id // 强制所有权过滤
}
});
if (!invoice) return res.status(404).end(); // 404 优于 403,不暴露资源存在性
```
**多租户场景**:
```sql
-- 应用层注入租户上下文
SET app.current_tenant_id = 'tenant_abc123';
-- 查询自动受 RLS 策略约束
SELECT * FROM orders WHERE id = 'ord_xyz';
```
PostgreSQL RLS 策略示例:
```sql
CREATE POLICY tenant_isolation ON orders
FOR ALL
TO app_user
USING (tenant_id = current_setting('app.current_tenant_id')::UUID);
```
**关键原则**:
- 404 优于 403:当资源存在但用户无权访问时,返回 404 可以避免信息泄露(攻击者无法确认该 ID 是否存在)
- 不要在客户端进行过滤:前端隐藏按钮或路由守卫不能替代服务端校验
- 批量查询同样要过滤:`GET /api/orders` 必须自动注入 `WHERE userId = ?`
### 第二层:策略引擎与外部化授权
当授权逻辑复杂(涉及角色、属性、关系、租户层级)时,将授权决策外部化到专用策略引擎比分散在业务代码中更易维护。
**Open Policy Agent (OPA)** 是目前最成熟的方案。OPA 将策略表示为 Rego 语言,服务通过 HTTP/gRPC 查询决策,典型延迟低于 1 毫秒。
```rego
package api.orders
default allow := false
allow if {
input.user.id == input.resource.ownerId
}
allow if {
input.user.role == "admin"
input.user.tenantId == input.resource.tenantId
}
```
**部署模式**:
| 模式 | 延迟 | 耦合度 | 适用场景 |
|---|---|---|---|
| OPA 中央集群 | 1-5ms | 低 | 策略统一管理的中小规模 |
| OPA Sidecar | <1ms | 中 | 微服务低延迟要求 |
| OPA Go SDK | 亚毫秒 | 高 | 性能敏感的核心路径 |
对于需要处理千万级权限检查的场景,Google 的 Zanzibar 模型及其开源实现(SpiceDB、OpenFGA、Warrant)提供了基于关系图的细粒度授权。这类系统适合有深层资源层次结构(文件夹→项目→文档)和共享语义("任何有链接的人可查看")的场景。
### 第三层:API 网关与边缘校验
API 网关作为请求的第一道关卡,可以承担一部分粗粒度授权职责。但需要注意:**网关只能做它能做的**——它可以验证 JWT 签名、检查角色声明、执行速率限制,但通常无法校验"用户 A 是否拥有订单 1234",因为这需要查询业务数据库。
网关层适合执行的 BOLA 相关控制:
- **标识符格式校验**:拒绝明显异常的 ID(如负数、过长字符串、路径遍历)
- **速率限制**:按用户 ID 限制枚举速度(例如单个用户每分钟对 `/api/orders/:id` 的访问不超过 60 次)
- **认证兜底**:拒绝无认证或认证过期的请求
```nginx
# Nginx 限流示例:按 JWT sub claim 限流
limit_req_zone $jwt_sub zone=per_user:10m rate=60r/m;
location /api/orders/ {
limit_req zone=per_user burst=10 nodelay;
proxy_pass http://backend;
}
```
### 第四层:运行时行为检测
即使上述三层全部就位,仍然可能存在绕过路径(例如通过 Chained Disclosure 获取合法 ID 后访问)。运行时行为检测作为最后一道防线,通过分析流量模式识别异常。
**BOLA 攻击的行为特征**:
- 同一用户在短时间内访问大量不同的对象 ID
- 访问的 ID 分布与历史基线显著偏离
- 响应体大小突然增大(可能表明批量数据泄露)
Cloudflare 2026 年推出的 API Shield 主动扫描采用了一种创新方法:将 API 视为**调用图**而非端点列表。要测试 BOLA,扫描器必须先执行"创世 POST"创建资源,再用攻击者凭据尝试访问该资源。这种依赖链是传统 DAST 工具难以处理的,因为传统工具通常独立测试每个端点。
```mermaid
sequenceDiagram
participant S as 扫描器
participant A as 合法用户
participant G as API Gateway
participant B as 业务服务
S->>A: 步骤1:以合法用户身份创建资源
A->>G: POST /api/orders (JWT_A)
G->>B: 转发创建请求
B-->>A: 返回 order_id: 9999
S->>A: 记录 order_id
S->>G: 步骤2:以同一用户访问(基线)
G->>B: GET /orders/9999 (JWT_A)
B-->>G: 200 OK
S->>G: 步骤3:以另一用户访问(攻击模拟)
Note over G: JWT_B 属于不同用户
G->>B: GET /orders/9999 (JWT_B)
B-->>G: 200 OK / 403 Forbidden
Note over S: 若返回 200,则标记 BOLA
```
Salt Security 采用被动检测方案:通过数天甚至数周的流量分析建立 API 行为基线,再检测偏离基线的异常。这种方法在稳定环境中有效,但在 CI/CD 管道和快速迭代的开发环境中难以发挥作用。
## 检测与验证:从形式化方法到 CI/CD 集成
防御的有效性需要可验证。BOLA 的检测面临一个独特挑战:它不像 SQL 注入那样有明确的恶意载荷签名。BOLA 的攻击请求在语法上完全合法,检测必须理解业务逻辑中的所有权关系。
### OpenAPI 驱动的自动化测试
如果 API 有规范的 OpenAPI/Swagger 定义,可以从中自动生成 BOLA 测试用例。其基本原理是:识别定义中包含资源 ID 参数的路径,为每个路径生成"合法用户创建资源 + 另一用户访问该资源"的测试序列。
**OWASP OFFAT** 是这一思路的开源实现。它从 OpenAPI 规范自动生成测试用例,覆盖 BOLA、SQLi、BOPLA 等漏洞,支持 CLI 和 Docker 部署,可集成到 CI/CD 流水线。
```bash
# 安装与基本使用
pip install offat
offat -f openapi_spec.yaml --test-api-security
# CI/CD 集成示例(GitHub Actions)
- name: API Security Scan
run: |
docker run --rm -v $(pwd):/spec owasp/offat \
-f /spec/openapi.yaml \
--output /spec/offat-report.json
```
**局限性**:OFFAT 等工具主要针对 Direct Object Reference BOLA,对 Workflow-Context 和 Chained Disclosure 等需要理解业务状态的类型覆盖有限。
### 形式化方法:OpenAPI → 有色 Petri 网
2025 年发表在《International Journal of Information Security》的一项研究提出了更严谨的方法:将 OpenAPI 规范自动转换为**有色 Petri 网(Colored Petri Nets)**,通过形式化分析检测 BOLA 漏洞。
研究团队开发了 Links2CPN 工具,输入 OpenAPI 规范,输出可分析的 Petri 网模型,再通过分析 Web 服务器执行轨迹检测攻击。在案例研究中,该方法对 BOLA 攻击的检测准确率超过 95%。
形式化方法的优势在于**可证明性**:如果模型验证通过,可以在特定假设下保证不存在某类 BOLA。但其工程门槛较高,更适合对安全性要求极高的金融、政务场景作为补充手段,而非日常敏捷开发的默认工具。
### 静态代码分析:BolaZ 与零信任防御
2025 年提出的 BolaZ 框架采用静态污点分析(taint tracking)自动识别 BOLA 风险点。其核心思想基于零信任的"最小权限"原则:通过分析资源 ID 在代码中的数据流,确定每个 API 端点"应该"只能访问哪些资源 ID,然后在运行时拦截越界访问。
BolaZ 的分析分为三个阶段:
1. **P-API 识别**:找出产生资源 ID 的 API(通常是 GET 列表接口)
2. **C-API 识别**:找出消费资源 ID 的 API(通常是 GET/DELETE/PATCH 详情接口)
3. **数据流关联**:分析前端代码中资源 ID 从 P-API 到 C-API 的传播路径,生成授权区间(MSG Interval)
在 10 个 GitHub SpringBoot 项目的评估中,BolaZ 成功防御了 3 个已知 CVE,并发现了 35 个新的 BOLA 漏洞(仅 1 个误报)。
**工具链对比**:
| 工具/方法 | 检测类型 | 集成阶段 | 覆盖模式 | 工程成本 |
|---|---|---|---|---|
| OWASP OFFAT | DAST / 黑盒 | CI/CD 运行时 | Direct Object Reference | 低 |
| Links2CPN | 形式化验证 | 设计/测试阶段 | 结构层面的 BOLA | 高 |
| BolaZ | 静态分析 | 开发阶段 | 数据流关联型 | 中 |
| Cloudflare API Scanner | DAST / 调用图 | 生产/预发布 | 依赖链型 BOLA | 中(商业) |
| Burp Suite + Autorize | 手动/半自动 | 渗透测试 | 全类型 | 高(人工) |
### 测试策略建议
单一工具无法覆盖所有 BOLA 模式。建议的分层测试策略:
1. **开发阶段**:BolaZ 或类似静态分析工具扫描代码变更,阻止新增 BOLA 进入主干
2. **CI 阶段**:OFFAT 基于 OpenAPI 规范执行自动化 BOLA 测试,作为门禁
3. **预发布阶段**:Cloudflare/Salt 等商业工具进行调用图级 DAST 扫描
4. **生产阶段**:运行时行为监控检测异常访问模式,告警可能的 BOLA 利用
## 风险边界与常见误区
### 误区一:UUID 等于安全
将自增整数改为 UUID v4 确实提高了枚举难度,但如果服务端仍然不校验所有权,攻击者一旦通过 Chained Disclosure 或其他途径获得 UUID,仍然可以成功利用。UUID 是**混淆层**,不是**控制层**。
### 误区二:网关鉴权足够
API 网关验证 JWT 签名和角色声明后,将请求标记为"已认证"并转发到后端。但如果后端盲目信任"已认证"标签而不校验对象所有权,BOLA 仍然成立。网关是"查身份证的保安",不是"查房产证的公证处"。
### 误区三:前端隐藏即安全
通过前端路由守卫或按钮显隐控制访问,在 BOLA 面前毫无作用。攻击者直接调用 API,完全绕过前端。
### 误区四:403 暴露存在性
当资源存在但用户无权访问时,返回 403 Forbidden 会泄露一个关键信息:该资源 ID 是存在的。这降低了攻击者的枚举成本。更安全的做法是在对象级授权失败时统一返回 404 Not Found,将"不存在"和"无权访问"合并为同一响应。
### 性能边界
在数据库查询层注入所有权条件可能带来性能影响,特别是在高并发、复杂关联查询场景下。建议:
- 对 `user_id` + `id` 或 `tenant_id` + `id` 建立复合索引
- 避免在查询中使用 `OR` 条件覆盖多个权限路径(可能触发全表扫描)
- 对于极复杂的授权逻辑,考虑在应用层缓存权限决策结果(TTL 5-30 秒)
### 跨服务边界
微服务架构中,服务 A 调用服务 B 获取资源时,服务 B 如何知道调用者是谁?常见模式:
- **服务令牌**:服务 A 使用自身的服务账号调用服务 B,但服务 B 无法区分"服务 A 代用户 X 调用"和"服务 A 代用户 Y 调用"
- **用户令牌透传**:将原始用户 JWT 透传给下游服务,保持身份链路,但增加了令牌泄露面和延迟
- **结构化令牌**:在内部使用包含 `originalUserId` 和 `callingServiceId` 的结构化令牌,兼顾可追溯性与最小权限
推荐第三种模式,并结合 mTLS 确保服务间通信不可伪造。
## 结论
BOLA 连续六年位居 OWASP API Security Top 10 榜首不是偶然。它是无状态分布式架构下"身份与数据分离"这一设计选择的直接后果。防御 BOLA 不需要新奇技术,但需要**工程纪律**:在数据访问层强制注入所有权过滤、将复杂授权逻辑外部化到策略引擎、在 CI/CD 中集成自动化 BOLA 测试、在生产环境部署行为异常检测。
最核心的一条经验是:**所有权校验必须在服务端、在数据库查询层、在每次访问时执行**。任何依赖客户端诚实、依赖标识符不可预测、或依赖开发者记忆的方案,在时间和规模的放大下都会失效。BOLA 不是拼写错误——它是架构设计缺口的系统性显现。
返回文章列表

应用安全··18 分钟阅读
BOLA 不是拼写错误:对象级授权失效的六类真实模式与工程化防御
基于100+份Bug Bounty披露实证,系统拆解BOLA的六类真实攻击模式,并给出从数据库查询层到边缘运行时的工程化防御方案。
评论
请 登录 后参与讨论。
加载中…
相关文章
应用安全··0 分钟
OWASP Top 10 2025 变化拆解:供应链与异常条件进入前十的工程信号
OWASP Top 10 2025 的两个新类别——Software Supply Chain Failures 和 Mishandling of Exceptional Conditions——不仅刷新了风险排名,更传递了一个明确的工程信号:应用安全的边界正在从'我写的代码'扩展到'我依赖的生态系统'和'代码出错时的行为'。
#OWASP#应用安全#供应链安全

应用安全··12 分钟
OAuth 2.1 时代浏览器应用的安全边界:从 PKCE 到 BFF 的工程审计
RFC 9700 与 OAuth 2.1 将 PKCE 从可选提升为强制、废弃了 Implicit Flow,但浏览器应用的令牌安全边界并未因此自动闭合。本文从授权码拦截、令牌窃取、XSS 与 CSRF 四条攻击链出发,逐项审计纯前端 SPA、Token-Mediating Backend 与 BFF 三种架构的真实安全边界,给出可落地的选型矩阵与检查清单。
#oauth#pkce#dpop

应用安全··12 分钟
Trustwave 把 ModSecurity 交给 OWASP 之后,CRS v4 + Coraza 才是新的默认线
从 Trustwave 退场到 OWASP 接管,再到 Go 原生引擎 Coraza,自建 WAF 的栈正在换底。本文用一次本地复现实测拆开 Coraza v3.7.0 + CRS v4.27.0 的部署边界、规则评估代价与误报取舍。
#waf#coraza#modsecurity