阅读声明:本文是资料研读与合成示例,整理于 2026-09-30。所有请求均指向本地合成服务或授权测试环境,不提供对真实目标的枚举、越权或数据读取操作。示例中的账号、ID 和数据均为虚构。
问题、术语与适用边界
IDOR(不安全的直接对象引用)描述的是一种常见成因:请求携带了文档、工单、发票或账户的引用,服务端却没有确认当前主体是否能访问该对象。BOLA(Broken Object Level Authorization)是 API 语境下的同类问题。OWASP API Security Top 10 将对象级授权检查要求扩展到“每个使用用户提供 ID 访问数据源的函数”,因此它不只发生在 GET,也发生在更新、删除、导出和批量操作。
本篇不讨论如何猜测第三方 ID,也不把 UUID 当成授权方案。随机标识符能降低可枚举性,但拿到一个有效链接后,缺失的权限检查仍然会暴露对象。适用的工程边界是:你拥有代码和测试数据,或得到系统所有者明确授权。
先写授权模型,再写路由
把一次访问表达成四元组:主体 subject、动作 action、对象 object、上下文 context。例如“用户 A 在租户 alpha 中读取文档 D”,检查的不只是 A 已登录,还包括 D 的租户、所有者、共享关系、对象状态和当前动作。默认策略应为拒绝,只有命中明确的允许规则才放行。
allow(subject, action, object, context) =
subject.active
&& context.tenant == object.tenant
&& (object.owner_id == subject.id || share.allows(subject, object, action))
&& policy.permits(subject.role, action, object.type)
将这个模型写进服务端策略层或数据访问层,避免每个控制器各写一套 if。错误响应也要有设计:对象存在但不可见的场景可统一返回 404,避免泄露资源存在性;确实没有权限且资源可公开存在时再返回 403。不要依赖前端隐藏按钮、URL 中的租户参数或隐藏表单字段。
服务端对象级检查
最稳妥的查询方式是让数据集天然带上当前主体的范围,而不是先从全库取对象再“顺手”判断。下面是伪代码,重点是查询条件和授权判断在同一条服务器路径中:
# 推荐:把租户/所有者范围放进查询
document = repo.documents_for(user, tenant).find(params["id"])
return not_found() unless document
# 共享文档需要显式动作策略
deny() unless policy.allows(user, "read", document)
return json(document, fields: policy.readable_fields(user, document))
更新、删除、导出和异步任务都要重复授权判定;不要因为读取接口检查过,就假定后台队列也安全。批量接口要逐个过滤对象并记录被拒绝的数量,不能仅验证请求中第一个 ID。服务到服务的调用也要携带可验证的主体和租户上下文,不能用“内部网络”代替授权。
两账号本地测试矩阵
在本地合成服务建立两个普通用户和两个租户:A 属于 alpha,B 属于 beta;每个用户各有一份文档,并额外建立一份明确共享给 A 的文档。测试只使用固定的夹具 ID,不访问外部系统。矩阵至少覆盖读取、修改、删除和导出,且同时验证成功与拒绝的状态码、响应体和审计事件。
| 主体/动作 | 自己的对象 | 他人未共享对象 | 明确共享对象 |
|---|---|---|---|
| A:读取 | 200 + 仅可读字段 | 404(不泄露存在性) | 200 + 共享策略字段 |
| A:修改 | 200 或 204 | 404/403,数据不变 | 按共享权限拒绝或允许 |
| B:读取 | 200 + beta 范围 | 404(包括 alpha 对象) | 未共享则拒绝 |
| 任意:导出 | 只导出授权集合 | 拒绝并记录原因 | 按导出权限单独判断 |
把矩阵固化为集成测试,测试名称中写清楚主体、对象、动作和预期结果。对每个路由再加一个“认证有效但授权失败”的用例,因为很多实现只测 401 未登录,而没有覆盖 403/404 的业务边界。示例夹具和断言见 本地测试矩阵。
标识符、字段与函数级防线
使用不可预测的 UUID 或随机引用是纵深防御,可以降低日志、链接和截图泄露后的枚举价值,但不能替代对象授权。更重要的是不要把数据库自增 ID、内部租户 ID 和敏感字段直接暴露给不需要它们的客户端。响应序列化应采用允许列表,防止“对象级授权通过但字段级授权失败”的过度暴露;更新接口也应拒绝未声明字段,避免批量赋值改变角色、所有者或状态。
函数级授权要独立检查。例如普通成员可以读取自己的文档,却不能调用“批量导出”“成员管理”或“策略修改”接口。角色判断不能只看客户端传来的 role,而要从可信会话或令牌上下文获得。对管理员、客服代查和支持工单等高权限流程,记录授权理由、工单号和审批上下文,并设置最小范围和过期时间。
日志应记录主体、租户、对象类型、对象引用的脱敏形式、动作、判定结果、策略版本和追踪 ID。不要记录会话令牌、完整个人资料或可再次访问资源的下载 URL。监控可以按“同一主体短时间内大量拒绝”触发告警,但告警不是授权本身。
误报、限制与修复验证
单次 404 不能证明没有 BOLA:可能是夹具不存在、路由做了错误映射,或只保护了读取没有保护导出。反过来,统一 404 也不代表所有业务都适合隐藏存在性;客服和协作产品可能需要明确 403。测试应结合产品规则、租户模型和数据分类,由业务负责人确认“谁能对什么做什么”。
- 列出每个对象类型的所有操作:读、写、删除、分享、导出、异步处理和管理动作。
- 为每个租户建立至少两名主体,覆盖所有者、成员、共享者和管理员差异。
- 在服务端断言查询范围、字段允许列表和策略版本;前端按钮测试只作为补充。
- 回归测试后检查数据库未发生越权写入,并核对审计日志是否包含拒绝原因。
- 策略变更必须有失败用例;新增路由默认拒绝,直到加入明确的授权规则。
相关项目与资料来源
本文参考并综合以下一手资料(访问/整理日期:2026-09-30):
- OWASP Authorization Cheat Sheet:默认拒绝、每次请求校验和可维护的授权策略。
- OWASP API Security Top 10 (2023):API1 对象级授权、API3 属性级授权和 API5 函数级授权的边界。
- OWASP IDOR Prevention Cheat Sheet:对象引用、双账号验证、范围查询与 UUID 的纵深防御。
- OWASP Developer Guide:把访问控制规则写入单元和集成测试的建议。
- Secx 本地合成测试矩阵:固定夹具和预期结果,不连接第三方系统。