Web API 渗透测试的价值,不在于证明“某个请求能够返回数据”,而在于证明该请求违反了明确的身份、对象或功能权限规则,并且结果能够由开发、运维和复测人员独立复核。本文仅适用于获得书面授权的测试环境,使用占位域名、测试账号和脱敏数据,重点介绍如何以最小化请求验证对象级授权、功能级授权、认证状态和敏感数据暴露问题。

一、先确认授权边界和停止条件
测试开始前,应将授权内容整理成可执行的测试规则,而不是只保留一封笼统的“允许测试”邮件。至少确认以下事项:
- 目标范围:域名、IP、API 网关、环境名称、版本或租户范围。
- 测试时间窗:开始和结束时间,是否避开批处理、发布、结算等高风险时段。
- 允许的方法:是否允许身份与权限测试、错误配置检查、限速观察,以及是否禁止上传、删除、支付和管理操作。
- 测试账号:普通用户、受限用户、管理员或租户管理员等角色,账号应为专门创建的测试账号。
- 数据限制:只能访问授权测试数据;发现真实个人信息、令牌、密钥或生产数据时应立即停止扩大验证。
- 影响阈值:请求速率、并发数、单次返回量、失败次数和资源消耗上限。
- 停止条件:出现服务异常、数据写入、跨租户数据、敏感凭据、告警升级或无法判断影响时,暂停请求并联系授权方。
- 清理方式:测试产生的数据、会话、临时对象和配置变更由谁清理,如何确认清理完成。
OWASP Web Security Testing Guide 将 API 侦察视为独立测试环节,强调先了解目标和 API 结构,再开展后续验证。可以将 OWASP WSTG 的 API 侦察章节 作为方法参考,但具体测试仍应服从项目授权范围。
二、建立资产和接口清单
API 测试的第一个实际产物应是接口清单,而不是扫描器报告。清单用于回答“测试了什么”和“还有什么没有测试”。
可以从以下资料来源建立清单:
- OpenAPI、Swagger、GraphQL Schema 或内部接口文档;
- 前端 JavaScript、移动端测试包和业务流程中的请求记录;
- API 网关路由、反向代理配置和服务注册信息;
- 测试环境访问日志、调用方清单和版本发布记录;
- 授权范围内的被动流量观察结果。
建议为每个接口记录:
| 字段 | 示例 |
|---|---|
| 接口标识 | GET /api/v1/orders/{order_id} |
| 环境 | staging-api.example.test |
| 认证方式 | Bearer 测试令牌 |
| 预期角色 | 普通用户 |
| 对象范围 | 当前用户所属租户 |
| 操作类型 | 读取 |
| 数据敏感度 | 订单基础信息 |
| 是否产生副作用 | 否 |
| 测试状态 | 未开始、通过、异常、需复核 |
| 证据位置 | 请求编号、响应摘要、日志编号 |
清单中的目标应使用占位域名,例如 https://api.example.test,不要在报告或示例中泄露真实生产域名、内部主机名和令牌。对于文档中存在但实际不可用的接口,应记录“未验证”及原因,不能直接标记为安全。
三、先写出身份与权限假设
越权验证的核心不是随意替换参数,而是先定义服务端应该执行的授权规则。没有预期规则,就无法判断响应差异究竟是漏洞、业务设计还是测试误判。
1. 对象级授权假设
对象级授权关注“当前身份能否访问这个具体对象”。例如:
- 用户甲只能读取自己创建的订单;
- 租户甲的成员不能读取租户乙的项目;
- 普通用户只能修改自己名下的地址;
- 归档对象只能由具备特定职责的角色查看。
测试前准备两个同权限、不同对象范围的账号,例如 user_a 和 user_b,并在测试环境中准备各自独立的对象:
order_a属于user_a;order_b属于user_b;- 两个对象状态和字段结构尽量相似。
验证目标不是“把 ID 改大或改小”,而是确认服务端是否根据当前会话重新判断对象归属。对象标识可以是数字、UUID、路径片段、查询参数或请求体字段,均不应被客户端输入自动视为授权依据。
2. 功能级授权假设
功能级授权关注“当前角色能否执行这个动作”。例如:
- 普通用户可以查看自己的订单,但不能导出全租户订单;
- 项目成员可以读取项目,但不能修改成员权限;
- 运维角色可以查看运行状态,但不能变更业务数据;
- 管理功能不能仅凭隐藏按钮或前端路由限制。
功能级测试应覆盖同一接口在不同角色下的行为,并区分:
- 未登录;
- 已登录的普通用户;
- 已登录的受限角色;
- 具备管理权限的测试账号。
不要仅以 HTTP 状态码判断结果。200、201、204、403、404 和 401 都需要结合响应体、业务状态、审计日志和服务端实际变化解释。
四、采用最小化请求进行验证
低影响验证的原则是:一次只验证一个假设,优先使用只读接口,控制请求频率,不主动扩大数据量。
1. 建立基准请求
先使用账号 A 对其自身对象发起正常请求,保存以下信息:
- 请求方法和路径;
- 脱敏后的请求头;
- 必要的查询参数或请求体;
- 状态码、响应长度和关键字段;
- 请求时间、响应时间和追踪 ID。
示例使用占位目标和环境变量,不包含真实令牌:
export API_BASE="https://api.example.test"
export TOKEN_A="REDACTED_TEST_TOKEN_A"
curl --silent --show-error
--request GET "$API_BASE/api/v1/orders/order_a"
--header "Authorization: Bearer $TOKEN_A"
--header "Accept: application/json"
该请求的作用是建立“合法基线”,不是扫描接口。若基线请求本身失败,应先确认账号状态、接口版本、测试数据和环境配置,不要直接把失败结果解释为访问控制有效。
2. 进行单变量对照
在保持方法、请求头和其他参数不变的情况下,仅将对象替换为 order_b,并使用账号 B 做同样请求。对照表可以这样记录:
| 对照项 | 账号 A 访问 order_a | 账号 A 访问 order_b |
|---|---|---|
| 预期结果 | 允许 | 拒绝或返回统一不存在 |
| 实际状态码 | 200 | 待记录 |
| 响应是否含对象字段 | 是 | 待记录 |
| 服务端数据是否变化 | 否 | 待记录 |
| 是否产生审计日志 | 是 | 待记录 |
如果账号 A 能读取或修改 order_b,应先确认 order_b 是否确实属于不同用户或租户,是否存在共享、委托、团队协作或客服代办等业务规则。只有在业务规则明确禁止访问时,才能将其作为对象级授权问题提交。
3. 处理响应差异
有价值的证据不只是“返回 200”。应比较:
- 状态码;
- 响应正文中的对象标识、用户标识、租户标识;
- 返回字段数量和敏感字段;
- 分页总数、错误码和错误信息;
- 响应时间是否出现异常差异;
- 服务端审计日志中的主体、对象和决策结果。
对于未授权对象,服务端可能返回 403,也可能为避免对象枚举而返回统一的 404。这两种设计都可能合理,关键是未授权请求不能泄露对象内容,也不能产生未授权的状态变化。
五、重点检查认证状态和敏感数据暴露
认证状态
至少验证以下状态:
- 完全不带认证头;
- 使用格式错误或过期的测试令牌;
- 使用账号 A 的令牌访问账号 B 的对象;
- 令牌被撤销或退出登录后再次访问;
- 不同 API 版本或网关路径是否使用一致的认证策略。
应检查认证是否仅由前端控制、是否存在某个版本路径遗漏认证、是否将用户标识从请求参数直接作为可信身份。测试中不要尝试猜测真实令牌,也不要收集非测试用户的会话信息。
敏感数据暴露
即使对象级授权正确,也可能存在对象属性级问题。检查响应是否返回业务不需要的字段,例如:
- 密码哈希、访问令牌、刷新令牌;
- 内部服务地址、调试堆栈和数据库标识;
- 身份证件、支付信息或完整联系方式;
- 未脱敏的日志、密钥或第三方凭据;
- 不同角色不应看到的管理字段。
验证时只需确认字段是否出现、是否可由低权限账号获取,不应下载大量记录或进一步使用暴露的凭据。发现真实敏感数据后,应停止扩大读取范围,保留最小必要证据,并按约定通知授权方。

六、控制请求影响和测试节奏
API 测试不应把并发压测、批量枚举和破坏性操作混入普通权限验证。建议采取以下控制:
- 优先使用
GET、HEAD或明确无副作用的接口; - 每次只发送一个或少量对照请求,并设置合理间隔;
- 不使用大范围 ID 枚举来证明问题;
- 不测试删除、支付、发邮件、改密和批量导出等动作,除非授权明确允许;
- 需要验证写操作时,使用专门测试对象,并提前确认回滚方法;
- 遇到速率限制、错误率升高、队列堆积或响应时间明显上升时立即暂停;
- 记录测试工具的并发、超时、重试和代理配置,避免工具默认行为扩大影响。
如果需要确认写操作是否受权限控制,应优先在隔离环境中使用无业务价值的测试对象,并将请求体中的内容限制为无害标记。例如,先验证服务端是否返回拒绝,再由授权方通过日志或数据库审计确认没有发生变更。
七、形成可复核的漏洞证据
一条合格的发现应让不了解现场的开发人员能够复现和判断。报告至少包含以下字段:
- 标题:例如“普通用户可读取其他租户订单对象”;
- 资产与接口:环境、方法、路径和版本;
- 前置条件:测试账号角色、对象归属和必要配置;
- 预期授权规则:当前身份本应能做什么;
- 复现步骤:控制在最少请求数,使用脱敏参数;
- 实际结果:状态码、关键响应字段和是否产生状态变化;
- 对照结果:合法账号、拒绝场景或修复前后的差异;
- 影响范围:可能暴露的数据类型、受影响角色和租户边界;
- 证据:请求编号、响应摘要、网关日志或审计日志编号;
- 风险判断:机密性、完整性、可用性和业务影响;
- 修复建议:服务端授权位置、字段控制和测试要求;
- 清理结果:创建对象、会话、配置和临时数据是否已清除。
请求示例应脱敏:
GET /api/v1/orders/order_b HTTP/1.1
Host: api.example.test
Authorization: Bearer <TOKEN_A_REDACTED>
Accept: application/json
X-Test-Case: BOLA-001
响应可以只保留用于证明结论的字段:
{
"status": "success",
"order_id": "order_b",
"tenant_id": "tenant_b",
"customer_name": "<REDACTED>"
}
不要在报告中保留完整令牌、Cookie、个人信息、支付数据或可直接使用的密钥。截图也应进行遮罩,并说明截图时间、账号角色和环境。
八、风险判断不能只看状态码
越权问题的严重程度取决于对象和动作,而不是单一的 200 或 403。可以从以下维度判断:
- 是否跨用户、跨团队或跨租户;
- 读取的是普通业务信息还是身份、财务、凭据等敏感数据;
- 仅能读取,还是能够新增、修改、删除或授权;
- 是否需要已认证账号,还是完全未认证即可访问;
- 是否存在稳定、可重复的权限绕过;
- 是否可能影响大量对象,但不应通过批量请求进行实际验证;
- 是否已有审计日志和告警,能否追溯异常访问。
风险描述应区分“已证明的影响”和“合理推断的影响”。例如,单个测试对象被读取可以证明对象级授权失效;但不能据此断言整个数据库已被读取,除非有授权范围内的独立证据。
九、修复建议应落到服务端控制点
常见修复方向包括:
- 在服务端根据认证主体重新加载对象,并执行租户、用户和角色授权检查;
- 将授权逻辑放在业务服务或统一策略层,而不是只依赖前端隐藏按钮;
- 对读取、创建、更新、删除和导出分别定义权限;
- 使用字段白名单和角色化响应模型,避免直接序列化内部对象;
- 对管理接口、调试接口和旧版本 API 采用一致的认证与授权策略;
- 对失败访问记录主体、对象、动作、来源和结果,同时避免日志泄露令牌;
- 对高敏感操作增加重新认证、审批或二次确认;
- 根据业务需要设置分页上限、速率限制和资源消耗控制。
随机化对象标识只能降低枚举便利性,不能替代服务端授权。隐藏路径、修改前端路由或依赖网关规则,也不能替代每个业务接口自身的权限判断。
十、整改复测要证明“问题已关闭”
复测应复用原始测试条件,并保留修复前后的对照:
- 使用原账号 A 访问原对象
order_b; - 确认服务端拒绝访问或返回统一的无对象结果;
- 使用合法账号访问自身对象,确认正常业务没有被误伤;
- 检查读取、更新、删除、导出等相关动作是否同时修复;
- 检查不同 API 版本、网关路径和缓存层是否仍有差异;
- 检查响应中是否仍暴露不必要字段;
- 通过审计日志确认拒绝决策和主体信息正确;
- 清除复测产生的测试对象、令牌和临时配置。
复测结论可分为:
- 已修复:越权请求被拒绝,合法请求正常,日志和字段控制符合预期;
- 部分修复:某个方法或版本已修复,但仍有相关入口可访问;
- 未修复:原始条件下仍可重复;
- 无法验证:环境、账号、数据或日志条件不足,应明确列出缺失项。
最终,Web API 渗透测试应形成一条完整证据链:授权范围定义了可以测试什么,接口清单说明测试覆盖了什么,权限假设说明为什么该访问应被允许或拒绝,对照请求证明实际差异,日志和响应确认影响,整改复测则证明修复没有只停留在前端或单一路径。以这条证据链为主线,越权验证才会真实、可复核,也更值得开发和运维团队投入修复资源。

上海市 1F
先把授权边界写清楚,确实省很多麻烦