安全测试-02-API鉴权越权与未授权访问怎么测

接口安全测试里,最常见的误判之一是:

  • 登录了,说明鉴权没问题
  • 页面上按钮看不到,说明普通用户做不了
  • 请求里带了 Token,说明接口是安全的

这些判断都太早了。

真正决定接口安全性的,不是“前端看起来限制住了没有”,而是下面这些事情在服务端是否真的成立:

  • 当前请求是不是必须有合法身份
  • 当前身份是不是有权访问这个资源
  • 当前身份是不是有权执行这个动作
  • 身份变化后,旧请求还能不能继续生效

所以 API 安全测试里,最先看的通常不是复杂漏洞,而是三件最基础也最高频的事:

  • 鉴权
  • 越权
  • 未授权访问

这篇文章就只讲这三类问题怎么测,重点不是术语,而是怎么在真实业务里更快把问题筛出来。

一、先把三个概念分清楚,不然后面很容易测乱

1. 未授权访问

本质是:

本来应该先登录、先校验身份的接口,没有做这件事。

常见表现:

  • 不带 Token 也能查数据
  • Cookie 失效后仍能访问关键接口
  • 内部接口直接暴露在外部链路上

2. 越权访问

本质是:

虽然登录了,但当前身份不应该访问这个资源或执行这个动作。

常见表现:

  • A 用户可以查看 B 用户的数据
  • 普通员工可以审批主管任务
  • 普通账号可以调管理员接口

3. 鉴权缺陷

这是更大的集合,不只是“有没有登录”。

它包括:

  • 身份校验缺失
  • Token、Cookie、Session 校验不完整
  • 权限模型和资源归属校验不完整
  • 敏感动作缺少二次确认

这三个概念如果不先分开,后面很容易把所有问题都笼统记成“权限问题”,最后提单也提不清楚。

二、哪些接口最值得优先测

如果是业务系统,我优先级通常是这样排的:

  • 用户详情、订单详情、审批详情、报表详情
  • 修改资料、改密码、绑定手机号、修改邮箱
  • 审批通过、驳回、删除、退款、封禁、开通权限
  • 导出、下载、批量操作
  • 管理后台接口
  • 内部运维接口、调试接口、回调接口

这些接口值得优先看,不是因为名字敏感,而是因为它们背后通常存在:

  • 资源归属
  • 角色差异
  • 数据边界
  • 状态变更

只要这几个约束存在,就必须问一句:

服务端到底有没有重新校验,而不是只信前端。

三、更常用的一套测试顺序

如果时间有限,不适合一开始把所有接口都扫一遍。
更高效的顺序通常是:

1. 先拿到完整业务链路

例如一个报销审批系统,先正常跑通:

  1. 员工登录
  2. 创建报销单
  3. 提交审批
  4. 主管审批
  5. 财务打款

这一步最重要的产出不是“功能通过”,而是:

  • 找出真正写操作的接口
  • 找出业务主键,比如 taskIdorderIdapplyId
  • 找出身份信息从哪里进入系统

2. 再准备最少三类账号

至少要有:

  • 普通用户 A
  • 普通用户 B
  • 管理员或更高权限账号 C

很多越权问题测不出来,不是系统没问题,而是测试只拿了一个账号在反复点。

3. 先测未授权,再测同级越权,再测垂直越权

这是更推荐的顺序:

未授权访问

  • 去掉 Token
  • 去掉 Cookie
  • 用过期 Token
  • 用空 Authorization 头

看接口是不是还能继续访问。

同级越权

  • 用 A 用户的请求
  • 替换成 B 用户的资源 ID
  • 看 A 是否能读到、改到、删到 B 的资源

垂直越权

  • 用普通用户身份
  • 重放管理员或审批人的关键请求
  • 看普通用户是否能做高权限动作

这样顺序清楚,问题类型也更容易归类。

四、一套可直接执行的最小验证骨架

如果你要把接口安全测试变成稳定动作,而不是想到才试一下,建议至少固定下面这套骨架。

1. 建一张接口风险表

通常可以先列成这样:

接口 方法 身份要求 资源归属要求 动作级权限要求 首测动作
/api/order/detail GET 必须登录 只能看自己的订单 orderId
/api/order/refund POST 必须登录 只能操作自己的订单 仅客服/财务可退款 换账号重放
/api/user/export POST 必须登录 仅本部门数据 仅管理员可导出 换角色重放
/api/apply/approve POST 必须登录 只能审批分配给自己的任务 审批角色 普通账号重放

这张表会直接决定你后面每个接口怎么测。

2. 固定 4 组基础动作

通常每个关键接口都会至少做这 4 组动作:

  1. 去鉴权信息
  2. 换资源 ID
  3. 换账号身份
  4. 重放旧请求

如果这 4 组动作都没问题,再考虑更细的签名、时间窗、nonce、流程串改。

3. 固定结果确认方式

不要只看响应码。

至少同时确认:

  • 响应内容是否暴露了不该暴露的数据
  • 列表页或详情页是否真的变化
  • 数据状态是否真实落库
  • 审计日志是否记录了错误身份的动作

很多接口表面返回 403,但数据已经提前被改了。
也有些接口返回 200,但实际没有落库,只是文案写得像成功。

五、最常见的 4 类问题

1. 只校验“有没有登录”,不校验“是不是这个人”

这种问题非常常见。

例如:

  • 用户详情接口会校验 Token 合法
  • 但不会校验 userId 是否属于当前登录用户

结果就是:

  • 任何登录用户只要改 userId
  • 都能查到别人的资料

这类问题往往不难测,但很容易漏,因为测试同学习惯只用自己账号看自己数据。

2. 页面按钮有限制,但后端接口没限制

这是管理后台和审批流里最常见的一类。

前端做了:

  • 普通用户看不到“删除”
  • 普通用户看不到“审批通过”

但后端没做:

  • 当前用户角色校验
  • 当前用户是否为任务处理人校验

这种问题在 Burp 或 Postman 里重放一下就能出来。

常见场景:

  • 退出登录后旧请求还能继续生效
  • 密码修改后旧会话仍然有效
  • 角色变更后旧 Token 权限不变

这类问题尤其适合做回归用例,因为非常容易被后续改动重新带回来。

4. 批量接口只校验了入口,不校验每条资源

例如:

1
2
3
{
"ids": [1001, 1002, 1003]
}

系统可能只校验“当前用户能调用批量删除接口”,却没校验这几个 id 是否都属于当前用户可操作范围。

结果就是批量接口往往比单个接口更容易出越权。

六、测试时很容易踩的几个坑

1. 只测读接口,不测写接口

会先测查询接口越权,但真正业务影响更大的,通常是:

  • 修改
  • 删除
  • 审批
  • 导出
  • 退款

读越权和写越权要分开看,后者通常风险更高。

2. 只看 HTTP 状态码

200401403 只能说明一部分问题。
真正要看的是:

  • 数据有没有泄露
  • 状态有没有变化
  • 动作有没有被执行

3. 忽略缓存和异步处理

有些接口第一次查是旧缓存,看起来像没问题。
但稍后刷新列表、查看操作日志、等异步任务执行完,问题才真正暴露出来。

4. 用错测试数据,导致误判

例如两个用户本来就有相同部门权限,结果换账号后仍能看到同一批数据。
这不一定是越权,而可能是数据本来就共享。

所以测试前一定先把资源归属关系准备干净。

七、真实案例:为什么“只有主管能审批”的系统,普通员工也能审批

场景

一个 OA 审批系统里,员工提交请假单后,只有直属主管可以审批。
功能测试时流程表现正常:

  • 员工提交申请
  • 主管登录后可以看到审批按钮
  • 普通员工页面看不到审批按钮

团队最初认为这条链路没风险。

执行

先用主管账号抓到了审批请求:

1
2
3
4
5
6
7
8
POST /api/leave/approve HTTP/1.1
Authorization: Bearer xxxx
Content-Type: application/json

{
"applyId": "L20210325019",
"result": "pass"
}

然后做了两步:

  1. 退出主管账号,登录普通员工账号
  2. 保留请求体不变,只替换成普通员工的 Authorization

现象

接口返回 200,响应内容显示“审批成功”。
刷新申请列表后,这条请假单状态真的变成了“已通过”。

这说明问题不是接口文案不严谨,而是服务端真实执行了审批动作。

排查

继续看代码和日志后,问题链路很清楚:

  • 前端通过角色控制隐藏了审批按钮
  • 后端接口只校验了用户是否登录
  • 后端还校验了 applyId 是否存在
  • 但没有校验当前用户是否为该申请的审批人

也就是说,系统只做了“身份存在”校验,没有做“动作权限”校验。

修复

最后给出的修复建议有 4 条,不只是“补个 if”:

  1. 审批接口必须按当前登录用户重新校验审批权限
  2. 服务端必须校验申请单对应审批链和当前处理人
  3. 审批日志里记录操作者、原状态、目标状态和审批节点
  4. 增加自动化回归用例:
  • 普通员工重放审批请求
  • 预期返回 403
  • 申请状态不能变化
  • 审计日志不能记录成功审批

这类问题特别典型地说明了一点:

“页面不可见”从来不等于“服务端不可执行”。

八、怎么把这类测试稳定沉淀下来

如果只靠测试同学临场想起来改一下包,这类问题很难持续发现。
更合适的做法是沉淀成两层:

第一层:手工安全检查清单

上线前对高风险模块固定检查:

  • 未登录是否还能访问
  • 普通用户是否能看别人数据
  • 普通用户是否能做高权限动作
  • 退出登录后旧请求是否还能生效
  • 批量接口是否逐条校验资源权限

第二层:关键回归用例自动化

把最关键的越权场景做成稳定用例,例如:

  • 普通用户重放审批请求
  • A 用户请求 B 用户订单详情
  • 普通账号调用管理员导出接口
  • 旧 Token 在角色变更后继续访问接口

这样后面权限模型改动时,问题才不会反复回归。

九、写在最后

API 安全测试里,最值得先做的不是追求覆盖很多漏洞名词,而是把最基本的边界先守住:

  • 该登录的,必须登录
  • 该隔离的,必须隔离
  • 该限制角色的,必须按角色限制
  • 该失效的旧会话,必须真的失效

如果这几件事没有做到,再复杂的安全建设都会显得很虚。

而对于测试来说,这类问题也最值得优先去抓,因为它们最接近真实业务风险,发现成本也最低,修复收益通常也最大。