
越权漏洞在API测试里被低估到什么程度呢很多团队做了接口安全和渗透测试但报告里翻来覆去还是SQL注入、逻辑漏洞真正容易被业务方忽略的IDOR不安全的直接对象引用反而可能一直躺在生产环境里。我在一次线上事故复盘时发现攻击者仅仅是改了URL里的一个数字ID就拿到了另一个用户的订单详情这种问题如果靠人肉一个个接口去试费时间不说漏的概率还特别大。后来我花了两周时间把 Hadrian、Vespasian 这两个自动化检测工具配合 OWASP crAPI 靶场完整跑了一遍从部署到结果解读踩了不少坑也摸清了一条相对顺畅的路。这篇文章就是一份可以照着抄的实战记录适合正在做API安全建设、准备把越权检测落进CI流水线的同学也适合刚接触API安全测试、想找一套靶场练手的初学者。1. 越权漏洞检测的整体思路与工具选型1.1 越权漏洞的分类与危害越权漏洞本质上就是“访问控制失效”。按OWASP API Security Top 10的分类它对应的是“Broken Object Level Authorization”和“Broken Function Level Authorization”翻译成大白话就是水平越权和垂直越权。水平越权最常见也最容易出现在业务代码里。比如一个电商系统普通用户A登录后请求/api/v1/orders/1001能拿到自己的订单详情但如果把1001改成1002接口照样返回了用户B的订单信息那这就是典型的水平越权。问题出在后端只校验了“是否登录”没有校验“这条数据是不是属于当前登录用户”。垂直越权更直白普通用户调用了管理员才有权限的接口比如普通用户通过/api/v1/admin/users把某个用户角色改成管理员。这类漏洞表面看只是权限校验缺失实际上一旦被利用整个系统的信任边界就崩塌了。很多人觉得越权漏洞不如RCE、SQL注入严重但我在实际排查中发现越权漏洞的利用门槛极低只需要一个合法的普通账号然后通过抓包、改参数就能遍历大量数据。API接口又天然是机器对机器的服务攻击者可以写脚本批量遍历几分钟内拉走几百万条数据。crAPI这个靶场里就内置了至少三种越权场景非常适合用来验证自动化检测工具的能力。1.2 为什么选择 Hadrian Vespasian crAPI 这套组合先说结论crAPI 是靶场Hadrian 是扫描主程序Vespasian 是差异分析辅助引擎。这三者合在一起才构成一条完整的越权检测链路。crAPICompletely Ridiculous API是OWASP维护的一个故意充满漏洞的API靶场里面包含邮件、车辆、论坛等多个业务模块接口设计得很贴近真实项目。对我来说它最大的价值不是“有多难”而是“漏洞点相对明确”方便验证扫描器到底能不能发现问题。Hadrian 做的是API资产发现和越权检测。它能自动从 OpenAPI/Swagger 文档、HAR 文件里提取接口列表然后对每个接口做“身份替换”和“参数替换”测试。比如同一个请求分别用两个不同用户的 Token 去访问如果返回的结果在内容或状态码上出现异常就会标记为可疑越权。Vespasian 在这套组合里更像一个“裁判”。它专门负责做响应差异比对把两个用户请求同一资源时的响应体、状态码、响应头进行归一化对比。为什么要单独拆一个组件出来因为直接看状态码很容易漏报很多越权接口在状态码上都是200只有在响应体里的关键字段有差异。Vespasian 会把这种“微妙差异”抓出来再交给 Hadrian 的主进程做后续验证。我一开始也想只用一个工具搞定但试了几次后放弃了。越权检测的本质是“对比”不是“扫描”单独一个扫描器只能发现“接口存在”很难判断“数据是否越权”。所以难用组合拳Hadrian 负责广度Vespasian 负责深度。1.3 适用场景与前置条件这套方案适合以下三类场景第一类是上线前的API安全回归。每次发版前自动跑一遍越权检测不用等渗透测试排期。第二类是API资产底盘摸底。很多老项目接口文档缺失用 Hadrian 自动发现接口至少能知道系统里有哪些API。第三类是安全测试人员的日常工具链补充。手动测试时可以用它做第一次筛选把明显有问题的接口先标记出来再人工验证。前置条件不算苛刻一台能跑 Docker 的 Linux 机器或 macOS 机器8G 内存以上装好 Docker 和 Docker Compose。如果你是 Windows建议直接用 WSL2避免 Docker Desktop 的网络模式带来各种奇奇怪怪的问题。另外需要明确一点用这套工具扫描的目标必须是你有授权测试的系统千万别拿它扫生产环境里没有授权的业务。2. crAPI 靶场部署先有一块合法的试验田2.1 crAPI 是什么crAPI 的全称是 Completely Ridiculous API直译过来就是“完全荒唐的API”。这个项目故意把十几种真实世界常见的API漏洞做进了同一个业务系统里覆盖了越权、批量分配、用户枚举、敏感信息泄露等OWASP API Top 10的典型问题。它的业务场景模拟的是一个叫 crAPI 的车辆管理平台用户可以注册账号、创建车辆、提交加油记录、查看论坛帖子里面还带一个邮件服务。启动后你会发现它和真实的互联网产品没什么区别练习效果比那些只有一个登录接口的靶场强太多。crAPI 默认使用 Docker Compose 部署组件比较多包括 Web 前端、后端 API、MongoDB、RabbitMQ、Mailhog 等。这也是我建议大家先在本地跑通 crAPI 的原因组件多、端口多、依赖多如果连靶场都部署不起来后面的工具调试会更痛苦。2.2 Docker 环境准备部署之前先检查基础环境。我遇到过太多因为 Docker 版本太老导致的兼容性问题所以建议先跑这两个命令docker --version docker compose version我用的是 Docker 24.0 以上和 Docker Compose v2.20 以上整体比较稳定。如果你使用的是docker-compose带横杠的旧版本命令要替换成docker-compose后面的参数基本一样。还有一个容易忽略的点crAPI 的镜像总量不小包含多个服务建议给 Docker 预留至少 10G 磁盘空间。另外如果你在境内网络环境拉取 Docker Hub 镜像可能会很慢可以给 Docker daemon 配置一个镜像加速源或者使用代理具体配置方法这里不展开但确实会影响体验。2.3 拉取并启动 crAPIcrAPI 官方安装方式是把整个仓库克隆下来然后进入 deploy/docker 目录启动。我实践下来这种方式最不容易出错因为仓库里已经写好了完整的 docker-compose.yml 和配置文件git clone https://github.com/OWASP/crAPI.git cd crAPI/deploy/docker docker compose pull docker compose up -d第一次执行docker compose pull可能需要几分钟到十几分钟取决于网络状况。拉取完成后docker compose up -d会在后台启动所有容器。启动完成后一定要看下容器状态docker compose ps正常情况下你会看到 web、api、mongodb、rabbitmq、mailhog 等服务处于 running 或 healthy 状态。如果某个容器反复重启先看它的日志docker compose logs -f api我在第一次部署时就是某个镜像没拉全导致 api 容器一直 CrashLoopBackOff后来删除镜像重新 pull 才解决。crAPI 的 Web 门户默认监听在 8888 端口邮件服务 Mailhog 监听在 8025 端口。这两个端口都要确保没有被本机其他服务占用。启动完成后浏览器访问http://localhost:8888能看到 crAPI 的登录注册页面就算成功了。2.4 初始化账号与测试数据部署成功后第一件事是注册两个测试账号。为什么要两个因为越权检测的核心理念就是“用身份A访问身份B的数据”所以至少要有两个普通用户后面 Hadrian 才能做对比测试。我习惯注册userAexample.com和userBexample.com两个账号密码统一设置为Password123!。注册时 crAPI 会往 Mailhog 里发一封验证邮件打开http://localhost:8025在邮件列表里就能看到验证码。注册完成后随便登录一个账号在 Web 界面里创建一个车辆信息绑定到当前用户。这一步很关键因为越权检测需要数据对象存在。如果每个用户下面都没有数据扫描器想越权都找不到资源。创建完车辆后先用浏览器或 curl 调几个接口确认系统功能正常再进入下一步。注意crAPI 的邮件验证码有效时间很长但Mailhog 数据是存在容器内存里的如果容器重启邮件记录会丢失。建议注册完账号后顺手充一下业务数据免得容器重启后白忙活。3. Hadrian 与 Vespasian 的部署配置3.1 Hadrian 核心功能拆解Hadrian 在我的实际使用里承担了三件具体的事接口发现、扫描编排、结果汇总。接口发现这一块它支持从 OpenAPI JSON、Swagger、HAR 文件、以及爬虫自动抓取接口。我在 crAPI 上直接用了 OpenAPI 方式因为 crAPI 本身就暴露了/openapi.jsonHadrian 可以自动拉取并解析成完整的请求模板。爬虫方式我后来也试了虽然能找到一些隐藏接口但耗时更长而且可能爬到登录、注册这种不需要鉴权的接口干扰检测结果。扫描编排是 Hadrian 最核心的能力。它会把每个接口都生成一个“请求模板”然后自动做两种变换一是替换 Authorization 头里的 Token二是替换请求路径或请求体里的对象ID。变换之后再组合成大量的测试用例并发执行。这个过程中Hadrian 会严格控制速率避免把目标服务打挂。结果汇总方面Hadrian 输出 JSON 和 HTML 两种报告。JSON 适合喂给 CI 系统做断言HTML 适合人工查看。报告里会把每个接口的测试结果、判定理由、原始请求响应都记录下来。3.2 Vespasian 在检测链路中的角色Vespasian 这个名字在罗马史里是 Hadrian 之前的皇帝两个工具起这个名字大概也暗示了它们之间的前后依赖关系。在实际使用中Vespasian 更像是一个独立的响应差异分析服务它接收 Hadrian 传过来的“两个用户的同一请求响应”然后做归一化、对比、评分。越权检测里最难的不是“找到接口”而是“判断越权到底成不成立”。举个例子用 userA 的 Token 访问/api/v1/vehicles/1再用 userB 的 Token 访问同一个 URL。如果两个请求都返回 200但响应体里一个是 userA 的车辆信息另一个是 userB 的车辆信息这时候基本可以断定存在水平越权。但如果两个请求返回的都是一大段 JSON其中只有某个 time 字段在变你根本没法靠肉眼分辨差异。Vespasian 做的事情就是把这堆响应体结构化去掉 token、timestamp、随机数这些动态字段对比剩余的业务字段。如果 userB 的请求返回了 userA 独有的数据字段或者状态码从 200 变成了 403它就会给这个接口打一个“疑似越权”的标签。所以我的结论是Hadrian 解决“测什么、怎么测”的问题Vespasian 解决“结果怎么判断”的问题。少任何一个都不完整。3.3 安装方式与目录结构这两个工具我都推荐用 Docker Compose 一键部署。虽然源码编译方式也能跑但依赖的运行时版本、第三方库太多了我在本机编译时踩过不少版本兼容的坑换成容器镜像后问题少了很多。我习惯在项目根目录建一个docker-compose.yml内容大致如下services: hadrian: image: hadrian:latest container_name: hadrian volumes: - ./config:/app/config - ./reports:/app/reports - ./data:/app/data network_mode: host environment: - VESPASIAN_URLhttp://127.0.0.1:9090 command: scan --config /app/config/config.yaml vespasian: image: vespasian:latest container_name: vespasian volumes: - ./data:/app/data network_mode: host command: serve --port 9090这里有两个关键设计一是用network_mode: host让容器直接共享宿主机网络避免容器内的localhost访问不到宿主机上的 crAPI二是把配置文件和报告目录都挂载出来方便后续调整和读取结果。如果你不想用容器也可以直接用二进制方式部署。核心目录结构建议这样划分hadrian-vespasian/ ├── config/ │ └── config.yaml # 扫描主配置 ├── data/ │ ├── openapi.json # 导入的API文档 │ └── har_files/ # 可选HAR导入目录 ├── reports/ # 扫描报告输出目录 └── logs/ # 运行日志3.4 配置文件与关键参数解析Hadrian 的主配置文件是 YAML 格式我贴一份我在 crAPI 上调试好的最小可用配置target: base_url: http://localhost:8888 openapi: /openapi.json users: - username: userAexample.com password: Password123! - username: userBexample.com password: Password123! auth: login_endpoint: /api/v1/auth/login email_field: email password_field: password token_field: access_token detection: method: diff ignore_fields: - timestamp - created_at - updated_at threshold: 0.7 rate_limit: max_per_second: 10 timeout_seconds: 5这里面的每一项都是实战里验证过的关键点。base_url是 crAPI 的根地址注意不要加多余的斜杠。openapi是 OpenAPI 文档的相对路径Hadrian 会自动拼接成完整 URL 并拉取。users列表里配置两个测试账号Hadrian 首先会依次调用auth.login_endpoint获取 Token拿到之后缓存起来。如果你的目标系统是单点登录或者需要验证码那就不能简单依赖这个配置得在检测前手动获取 Token 并写到配置里。detection.ignore_fields特别重要。像 crAPI 这种系统本身有很多时间戳、随机数、请求ID字段如果在对比响应时不忽略这些字段很容易把正常响应也判成“有差异”。我把timestamp、created_at、updated_at全都列进去了。threshold是差异评分阈值0.7 表示 Vespasian 比较了两份响应后如果相似度低于 30%就判定为“存在越权嫌疑”。这个值在 crAPI 上不用调太紧0.5-0.7 都是合理的。rate_limit.max_per_second控制扫描速率。我当时一开始设置成 50 QPS结果 crAPI 直接开始返回 429扫描任务终止。后来调到 10稳定跑完。3.5 启动 Hadrian 与 Vespasian配置写好后启动顺序有讲究先启动 Vespasian再启动 Hadrian。docker compose up -d vespasian docker compose logs -f vespasian确认 Vespasian 监听在 9090 端口后再启动 Hadriandocker compose up -d hadrian docker compose logs -f hadrian如果一切正常Hadrian 的日志里会看到它先拉取 OpenAPI 文档然后打印出发现的接口数量再逐个登录用户获取 Token最后开始发送测试请求。我在 crAPI 上第一次跑的时候接口数量大概是 30 多个生成的测试用例接近 200 个十几分钟就全部跑完了。4. 越权检测自动化流程实操4.1 资产发现与接口导入自动化越权检测的第一步是把你到底要测哪些接口这件事确定下来。Hadrian 支持三种导入方式我建议按以下优先级选择第一种是 OpenAPI/Swagger 文档。crAPI 的/openapi.json就是现成的内容完整路径、参数、请求体都有。应对真实项目时如果后端团队维护了 Swagger这种方式最省事。第二种是 HAR 文件。你可以在浏览器开发者工具里录制一遍业务操作导出 HAR再导入 Hadrian。这种方式适合那些没有 API 文档的老项目缺点是无法覆盖没有被人工操作过的接口路径。第三种是爬虫自动抓取。Hadrian 会根据页面里的链接和 JS 请求自动发现接口但 crAPI 是前后端分离的 SPA 应用很多接口不是页面直达的爬虫漏掉的东西比较多。我在 crAPI 上直接用 OpenAPI 模式导入后看到如下日志[INF] Fetching OpenAPI spec from http://localhost:8888/openapi.json [INF] Parsed 36 endpoints, 142 operations [INF] Building request templates...这里有 142 个操作也就是 142 个接口动作后面生成的测试用例就是基于这些操作做的。4.2 双用户越权检测核心逻辑配置完账号后扫描器内部真正的越权检测逻辑是这样的Hadrian 先用 userA 和 userB 的账号分别登录拿到两个不同的 Token。然后对每个接口生成四类测试请求第一类userA 的 Token 访问 userA 自己的资源。这是基线请求用于确认接口正常。第二类userB 的 Token 访问 userA 的资源。这是关键的越权探测通过替换路径中的对象 ID 实现比如把/api/v1/vehicles/1改为/api/v1/vehicles/2同时将 Token 换成 userB 的。第三类完全去掉 Authorization 头访问受保护资源。用于检测是否存在未授权访问。第四类用普通用户 Token 访问管理员接口路径用于检测垂直越权。在 crAPI 上我手动验证了一个典型场景。首先用 userA 登录拿到 Token_Acurl -s -X POST http://localhost:8888/api/v1/auth/login \ -H Content-Type: application/json \ -d {email:userAexample.com,password:Password123!} | jq .access_token然后请求 userA 名下的车辆接口curl -i http://localhost:8888/api/v1/vehicles/1 \ -H Authorization: Bearer $TOKEN_A返回是一辆真实存在的数据。接着用 userB 的 Token 去请求同一个 URLcurl -i http://localhost:8888/api/v1/vehicles/1 \ -H Authorization: Bearer $TOKEN_B如果返回 200且响应体里出现了 userA 的车辆信息这就是一个 100% 的水平越权。手动做完这一遍再去看自动化扫描报告就能知道工具到底有没有把这个接口标记出来。4.3 扫描报告与告警解读扫描跑完后会自动生成 HTML 报告和 JSON 报告。报告里每个接口会有三个级别的判定高危越权、疑似越权、正常。高危越权通常意味着两个不同身份访问同一资源时返回的业务字段高度重合且资源确实属于另一个用户。疑似越权则可能只是响应里包含用户名、邮箱等标识信息但未必构成真正的数据泄露。正常就是两个身份拿到的结果完全不一致或者未授权用户被正确拦截。我拿到报告后习惯先看 JSON用 jq 按等级过滤出高危和疑似项jq .results[] | select(.verdict high) report.json在 crAPI 上跑完报告中确实出现了我手动验证的那个水平越权接口还额外发现了一个垂直越权接口也就是普通用户调用管理员接口后返回了管理员的用户列表。这一步是整套流程里最有成就感的时刻证明工具链路是通的。4.4 误报处理与规则调优自动化检测最怕的不是漏报而是“大量误报让你失去信心”。我在 crAPI 上初次跑的时候报告里有好几条疑似越权都是因为响应体里的时间戳和随机字符串导致的。后来通过修改ignore_fields把明显动态变化的字段过滤掉误报数量才降下来。还有一个更隐蔽的问题有些接口的响应体很大包含嵌套对象如果两个账号的某个集合字段本来就有差异容易被 Vespasian 误判为越权。这种情况下我建议先在报告里找到该接口人工对比一下两个响应体确认是业务差异还是真实越权。如果是业务差异就在规则里加一个path_pattern排除项detection: exclude_paths: - /api/v1/community/posts把社区帖子这种本身就允许公开访问的接口排除在越权检测范围之外可以显著降低噪音。真实项目中公开接口、静态资源接口、健康检查接口都应该提前配置到 exclude_paths 里。5. 常见问题与排查技巧实录5.1 部署阶段的经典问题我把部署 crAPI 和 Hadrian 过程中遇到的问题整理成了一张速查表方便你照着排查。现象可能原因解决办法crAPI 拉取镜像超时网络到 Docker Hub 慢配置镜像加速源后重新docker compose pullapi 容器 CrashLoopBackOff镜像下载不完整docker compose down -v后重新 pull再up -d8888 打不开端口被占用lsof -i :8888排查进程换端口后修改 compose 映射Mailhog 没有验证码注册时邮箱写错重新注册确保邮箱地址与登录邮箱一致Hadrian 报连接拒绝容器内只访问到 localhost改用network_mode: host或写宿主机局域网 IPVespasian 无响应启动顺序不对先启动 vespasian再启动 hadrian5.2 扫描阶段的坑扫描阶段最典型的是 Token 失效问题。crAPI 的 Token 过期时间我印象里是几小时内有效但如果你做长时间扫描或者测试环境有其他会话管理策略Hadrian 可能拿到的是过期 Token导致所有请求都返回 401扫描结果没有任何参考价值。解决方法是给 Hadrian 配置一个“重新登录周期”在每轮扫描开始前强制重新获取一次 Token。另外目标服务如果做了限流就一定要把rate_limit.max_per_second调低。我一开始把并发调得过高crAPI 直接给我返回 429扫描中断不说还触发了一批异常告警白白浪费时间。还有一个容易被忽视的坑OpenAPI 文档里有些接口是文件上传、WebSocket 升级之类的Hadrian 对这些接口的越权检测支持不一定好。我在报告里看到过几个明显误报都是上传接口后来直接 exclude 掉效果干净很多。5.3 自动化集成经验如果你不想每次手动敲命令跑扫描可以把这套流程接入 CI。我的建议是分成两个阶段第一阶段做接口资产盘点每天定时跑一遍资产发现第二阶段在每次发版前跑越权检测并把 JSON 报告归档到独立的目录。CI 里的脚本不需要写得多复杂核心就三步docker compose -f docker-compose.scan.yml up -d vespasian docker compose -f docker-compose.scan.yml run --rm hadrian scan --config /app/config/config.yaml python3 scripts/parse_report.py --threshold high第三步里可以写一段简单的 Python 脚本解析 JSON 报告如果发现高危越权就把退出码改成 1让流水线失败并通知到群里。这个做法在我后来接真实业务时非常管用能第一时间拦住新增的越权接口。以我这两周在 crAPI 上折腾的经验来看最值得投入的其实不是工具本身的部署而是“规则调优”。工具只是一把剑真正决定你能不能发现漏洞的是你对业务、对数据归属关系的理解。越权检测没有银弹但把这套组合拳搭好之后至少能帮你兜住绝大部分水平越权和垂直越权。最后再给一个小建议刚开始跑的时候别急着上生产先在 crAPI 这种靶场上把误报调干净再逐步扩大扫描范围你会发现这份投入非常值得。