归档说明:本文于 2026 年 11 月 12 日上线,页面日期用于系列归档;案例发生时间以正文为准。
最近在写一个接口审计工作台,初衷是让大模型先从访问日志里挑出值得排查的接口,再通过去认证重放,验证目标接口是否存在未授权访问风险。真正动手做下来,改得最多的一块并不是模型的提示词,而是把“建议去测”“已经测完”和“现有证据到底支持什么结论”这三件事彻底拆开。
一、看到 HTTP 200 之后,还得拆开看里面装了什么
举个最常见的场景:一个接口去掉认证头之后重放,依然返回了 HTTP 200,这时候能不能直接报未授权访问漏洞?
放到现在的审计工作台里,我会先追问三件事:
- 发出去的探测请求里,身份信息真的清干净了吗?
- 200 响应的正文里,装的到底是业务数据,还是一段报错提示或登录页?
- 就算拿到了业务数据,这份数据原本属于受保护的私有资料,还是本来就允许匿名查询的公开信息?
只要这三步里有一处没核实清楚,HTTP 200 就只是一个需要继续往下查的线索,还不能直接当成漏洞结论。
(注:下面的接口地址、字段和返回结果均为合成示例,不对应任何真实业务系统。)
假设授权测试范围内有一个接口 https://example.com/api/profile。带上正常登录凭证请求时,返回如下:
{"code":0,"data":{"display_name":"演示用户","department":"测试部门"}}去掉认证信息重放后,HTTP 状态码依然是 200,但响应正文变成了:
{"code":401,"message":"请先登录","data":null}不少系统习惯用“HTTP 200 + 业务错误码”来返回未登录提示。如果审计工具只看状态码,就会把这条正常的拦截响应误判成未授权访问。
反过来还有另一种情况:匿名请求确实拿到了 code: 0 和非空的 data,但里面装的是站点公告、公开商品列表或前端初始化配置。OWASP 在鉴权指南里也专门强调过认证与授权的区别:未登录用户本来就可以访问公开资源,而已登录用户也未必有权访问所有私有资源(参见 OWASP Authorization Cheat Sheet)。
所以在工作台的判定流程里,顺序是固定的:先核对实际发出的请求,再比对真实返回的响应正文,最后结合资源的业务属性判断权限边界。大模型可以帮忙解读陌生的字段含义、归纳报错格式,但最终给出的每一条风险结论,都必须能指回本轮探测留下的原始报文证据。
二、删掉一个 Authorization 头,不代表请求已经彻底匿名
“去认证重放”听起来就是把认证头删掉再发一次,但在实际工程里,这一步很容易漏掉隐蔽的身份载体。
只删掉一个 Authorization 请求头,请求未必就变成了匿名状态。浏览器或客户端环境里残留的 Cookie、自定义的 X-API-Key 头、甚至是 URL 查询参数里的 token 字段,都可能继续把当前会话身份带给服务端。针对具体业务系统,必须同时核对实际发出的底层请求头和客户端上下文,防止底层会话被自动带上(当然,这只是审计时的核查要求,不能反过来吹嘘当前工具已经自动识别了世面上所有的自定义认证机制)。
在这版工作台里,程序会依次核对真实探测记录、已移除的凭证字段、基线与探测响应对照,以及认定该资源受保护的依据。当 AI 节点提交“存在未授权风险”的判断时,不能只写一句“匿名请求返回成功”,必须具体指明响应里泄露了哪些敏感字段、为什么这些内容不该对匿名请求开放。如果输入证据本身不完整,结论就必须诚实地停在“待核实”。
三、404 必须和同一轮的带认证基线绑在一起看
另一个在开发过程中改了好几轮的细节,是怎么解释重放时遇到的 404。
最开始很容易顺着直觉想:既然历史访问日志里这个接口返回过 200,现在去掉凭证重放返回了 404,那就说明服务端用 404 屏蔽了未授权请求,鉴权防护是生效的。
但问题是,历史日志里的那次成功请求,可能发生在几天前甚至几周前。在这期间,接口路由可能已经下线、URL 里的某个临时参数可能早已失效,或者测试环境根本就变了。拿历史上的 200 和今天的 404 做跨期对比,很容易把一个早就废弃的接口当成“防护有效”。
后来我把工作台的规则收紧成了同轮对照:
- 只有在同一轮测试里,带认证的基线请求访问成功,紧接着的去认证探测请求返回
404,工作台才会按规则将其标记为“本次防护生效”; - 如果连同一轮的带认证基线请求也返回了
404,系统就会直接标为“重放异常”,不会给出任何安全背书。
即便两边都返回了 200,正文比对也得留个心眼:两份响应有差异,未必就是权限拦截生效了,也有可能是响应里的服务器时间戳、请求流水号(request_id)或者分页动态字段变了。最稳妥的做法是把原始响应完整留存下来,再针对业务数据字段做结构化比对,避免让粗糙的文本摘要把关键差异掩盖掉。

图 1:带认证基线与匿名请求必须同轮对照。基线失效应待核实,200 和 404 都须结合业务证据解释。(机制示意,点击图片可放大。)
四、“未验证”本身就应该是一个独立、明确的状态
随着后台判定逻辑收紧,前端页面的展示文案也跟着大改了一轮。
早期页面上有个栏目叫“重放建议”,原本想表达的是“AI 建议下一步去测哪些接口”;但实际看报告的人最关心的是“到底测了没有、测出来有没有风险”。如果把“建议去测”和“已经测出问题”混在一个列表里展示,看报告的人很容易误以为满屏都是已确认的漏洞。
后来我直接把主视图改成了“未授权风险分析”,并且把状态拆成了四类:
- 未验证:仅仅是从日志里筛出了候选接口,还没有真正发起重放探测(或者因为策略限制被跳过);
- 本次未复现:已经按规则完成了同轮重放,但在当前测试条件下没有观察到未授权数据泄露;
- 重放异常:探测过程出错、或者连带认证的基线请求都没跑通,当前证据不足以下安全结论;
- 存在风险:同轮基线有效、去认证请求确实拿到了不该公开的业务数据,且附有完整的响应证据。
这四种状态绝不能简单粗暴地二分为“安全”或“高危”。另外,目前工作台页面上展示的高、中、低危标签,主要来自大模型结合返回字段给出的初步危害评估,后台程序还没有接入统一的 CVSS 或量化定级公式。这一点在看报告和写文档时都得如实说明,不能包装成已经实现了标准化自动定级。
五、先用确定性规则圈定执行范围,再让 AI 在圈内筛选
哪些接口能真正发起重放探测,同样不能由大模型说了算。
我的做法是先把已授权的目标域名、允许测试的接口前缀以及允许执行的 HTTP 方法写进确定性的配置里,只让 AI 在这个安全圈内做候选筛选。访问日志里出现过某个第三方地址,不代表审计工具就有权对它发包重放;大模型觉得某个接口“很可疑、值得测”,更不能自动突破事先划定的授权边界。
涉及写操作的接口尤其要小心。像创建订单、修改资料、删除记录甚至触发短信通知的接口,一旦直接重放就会在业务系统里留下真实副作用。而且在很多遗留系统里,连 GET 请求都可能顺手改数据库,不能单凭 HTTP 方法名就断定它绝对只读。对于拿不准副作用的接口,宁可先保持“未验证”状态并注明原因,或者转去隔离环境用合成数据单测,也不要为了凑高验证覆盖率而盲目重放。
此外,这版工作台主要解决的是“去认证(匿名)未授权访问”的验证,还没有覆盖同级或跨级用户之间的对象级越权(BOLA / IDOR)。正如 OWASP API1:2023 所指出的,验证对象级授权需要准备两套不同用户的合法身份凭证与各自归属的资源 ID 做交叉对照,单靠“删掉认证头”这一招是测不出来的。
六、交付前,实际验证到了哪一步
最后补充一下这个模块在打包交付时的实际验收范围。
当时我把代码打成交付包后,在本地新建了一个干净的解压目录,从零安装依赖、初始化空数据库、拉起前端页面,并跑了一遍隔离测试集:一共通过了 52 项后台单元测试和 3 项端到端模拟测试。这组测试证明了交付包在本地隔离环境下的逻辑完整性(当然,到了接收方的真实环境里,还得结合真实的模型 API 和业务接口再做联调)。
工作台里的“一次性定时预约审计”功能也专门做过到点触发验证:测试时我配了一套空目标策略,到点后系统准时触发,跑出了“0 候选、0 重放、0 模型任务”的预期流转记录,证明定时调度和状态流转本身是通的。同时,交付包里彻底清掉了本地开发库、原始测试日志、API 密钥和未执行的预约任务。
折腾完这一圈,我更愿意把这个接口审计工作台看作一个“帮人理清线索、按规矩跑对照请求、老老实实留存证据”的工程助手。每次打开审计报告,我依然习惯顺着结论往回倒查:到底是哪条请求、剥掉了什么凭证、拿回了什么正文、跟哪条同轮基线做的对比。只要这根证据链能严丝合缝地对上,AI 给出的分析才真正立得住。