
API越权漏洞在OWASP API Security Top 10 2023里占了两个席位——BOLA对象级授权缺失排第一BFLA功能级授权缺失排第五。名字听着很学术翻译成大白话就是水平越权和垂直越权。手动测越权是安全测试里最磨人的工作之一换一个资源ID可能就是一个漏洞改一个请求头可能就绕过了权限校验而传统扫描器对着CVE特征库扫半天也扫不出这类业务逻辑问题。今天我把踩过不少坑之后沉淀下来的这套自动化检测方案完整写出来用Hadrian做编排用Vespasian做执行引擎再用crAPI当靶场三段式工具链配合两个普通测试账号把越权测试从手搓请求变成跑一遍流水线。如果你正在做API安全测试、渗透测试或者公司打算在DevSecOps流水线里加一道API安全卡点这篇文章都适合你。我会从为什么需要这套组合讲起一路讲到实际跑出报告、解读结果最后把我在Windows和Linux两种环境里踩过的报错全部列出来。整个过程只面向授权测试和本地靶场请勿用于未授权系统。1. 这套工具链想解决什么问题1.1 越权漏洞难在“逻辑”而不在“特征”先澄清概念。水平越权指用普通用户身份访问另一个普通用户的私有资源典型场景是修改订单号遍历他人订单垂直越权指普通用户调用了管理员角色才能执行的接口典型场景是普通账号直接调用删除用户的接口。这两种漏洞的共同点是服务端认证没问题Token有效且属于当前用户但授权出了偏差服务端没有对上“谁”和“操作什么资源”之间的关系。传统扫描器为什么拿它没办法因为漏洞不存在于固定的输入特征里而存在于业务对象的归属关系里。以 /api/vehicle/{id} 为例在id里塞一个111可能正常塞一个222就暴露别人的车库扫描器如果不知道“当前会话是哪个用户、哪些车辆归属于当前用户”这层语义就永远无法区分这两次返回有什么不同。所以要自动化就必须先让工具理解API的资源语义这也是这套工具链的第一个核心设计原则测试不是盲扫而是先建模再攻击。再补一个很多人忽略的点不少团队会用接口鉴权覆盖率来评估风险但越权漏洞恰恰容易出现在覆盖率之外。同一个接口开发时可能写了“当前用户是否有权限操作该资源”的判断也可能漏了漏了之后WAF不会报警因为请求本身是合法用户发起的状态码大多是200。这必须靠专门的自动化工具结合多账号、实体替换、差异比对来做逻辑层面的判断。1.2 Hadrian、Vespasian、crAPI 各自扮演什么角色我看过不少团队做API自动化安全测试有的用Postman写脚本有的用Burp的Intruder手动爆破ID接口少的时候还能应付接口一多就维护不动了。这套组合的分工其实更像一个微型团队各自负责一个明确环节组件角色对应工作crAPI靶场提供一个故意留了大量越权漏洞的真实业务API用来验证工具是否真的好用Hadrian编排器/大脑解析OpenAPI/Swagger定义和采集到的流量建立API资源模型生成越权测试策略Vespasian执行引擎/手脚按Hadrian下发的策略批量发送HTTP请求维护多账号的Token记录响应和证据从名字能看出来Hadrian哈德良和Vespasian维斯帕先都是罗马帝国时期的皇帝这套工具圈喜欢用罗马皇帝命名。crAPI则完全是自嘲风格全称Completely Ridiculous API意思是“一个故意做得很荒谬的API”。把它当靶场非常合适内置了十几个常见API安全漏洞还专门设计了车辆、社区、优惠券、维修记录这些业务对象完美满足越权测试需要的资源归属关系。可能有朋友会问Hadrian既然能解析流量建模能不能直接发请求做检测可以但效率不高。Hadrian的设计思路是做判断而不是做请求它生成的每个测试用例都包含目标URL、方法、Header、Body和预期判定条件真正的高并发发送、超时重试、Token刷新这类脏活累活交给Vespasian。两者解耦还有个好处Hadrian可以部署在CI服务器上做策略编排Vespasian可以单独部署在离目标更近的执行节点上方便水平扩容。2. 三件套部署教程从 Docker 开始2.1 先确认基础环境Windows 最容易踩坑这套工具链的部署难度不高核心依赖就是Docker和Docker Compose。crAPI官方默认提供docker-compose方式一键拉起整个靶场Hadrian和Vespasian则依赖Python 3.9以上和一些Python库用Docker跑也完全可以。Windows用户需要重点检查一件事Docker Desktop是否已经正常启动。我遇到过一个很典型的报错消息是 failed to connect to the docker api at npipe:////./pipe/dockerdesktoplinuxen。这个报错的含义是Docker CLI无法通过Windows命名管道连接到Docker Desktop的Linux引擎说白了就是Docker Desktop没启动或者启动到一半卡住了。解决办法很直接打开Docker Desktop让引擎跑起来再执行 docker version 看到Server端有输出就说明通了。如果是公司电脑可能还需要到Docker Desktop的Settings - Resources里调整内存分配因为后面一次性要起七八个容器。Linux用户相对省事确保已安装docker和docker-compose插件即可。macOS用户注意Apple Silicon芯片的机器要优先使用arm64版本镜像crAPI的镜像默认支持多架构一般不用额外操心但如果拉镜像拉错了平台可以用 docker pull --platform linux/amd64 强制指定。2.2 一键拉起 crAPI 靶场crAPI的部署流程官方写得很简单但有几个细节新手很容易栽进去。先把代码拉下来git clone https://github.com/OWASP/crAPI.git cd crAPI/deploy cp .env.example .env docker compose up -d第一次启动会拉取大量镜像耗时取决于网络情况。启动完成后用 docker compose ps 查看状态正常情况下会看到web、api、mailhog、mongo、postgres、rabbitmq等容器处于Up状态。Web界面默认在 http://localhost:8888API基础路径默认是 http://localhost:8889/api。这里有个非常重要的点crAPI注册用户需要一个邮箱但它不会真的发邮件到外网而是发到内置的MailHog里。MailHog的界面在 http://localhost:8025SMTP端口是1025。注册完账号后去MailHog里找到对应邮件点击验证链接账号才算是激活状态。我试过直接在crAPI注册页填一个不存在的邮箱注册接口会返回成功但后续很多功能无法使用因为账号状态没有变成verified。所以正确姿势永远是注册 - 打开MailHog - 点验证链接 - 再登录。如果本机的8888或8889端口被占用可以在docker-compose.yml里改端口映射。但要注意只改宿主机端口还不够API的base URL也要同步修改否则前端页面请求API会失败。crAPI的容器内部用nginx做网络配置不建议新手进容器里改配置最简单方式是换一台机器或者停掉占用端口的进程。2.3 安装 Hadrian 和 VespasianHadrian和Vespasian的安装分为源码方式和Docker方式。我推荐在评估阶段直接使用源码方式方便看日志和调试批量跑平台的时候再换Docker。源码方式的基本步骤是这样的git clone hadrian项目仓库地址 cd hadrian python3 -m venv .venv source .venv/bin/activate pip install -r requirements.txt python hadrian.py --helpgit clone vespasian项目仓库地址 cd vespasian python3 -m venv .venv source .venv/bin/activate pip install -r requirements.txt python vespasian.py --help仓库地址建议直接在GitHub搜索hadrian api scanner和vespasian api engine优先选star多一些、commit日期比较新的版本。这两个项目在社区里可能存在不同fork不同版本对OpenAPI版本的支持程度不一样有的只支持OpenAPI 3.0有的已经兼容3.1。如果你的业务API是Swagger 2.0最好先确认工具版本是否支持或者先用swagger2openapi工具转成OpenAPI 3.0格式再喂给Hadrian。看到 --help 能正常输出说明工具安装成功。接下来做一次连通性测试让Hadrian读取crAPI的OpenAPI定义让Vespasian尝试访问crAPI的接口。如果这一步通了整条链路就通了。小提示先用一个小的OpenAPI文件测试解析能正确列出所有路径再接入crAPI先小后大、先验证再全量这是API自动化工具链的通用原则。3. 实战一把自动化越权检测的标准流程3.1 准备两个测试账号把业务基线喂给工具前面说过越权检测的底层逻辑是多账号、实体替换、差异比对所以至少需要两个测试账号。我在crAPI里注册两个账号aliceexample.com 和 bobexample.com密码都设为 Password123靶场环境无所谓强度。登录后分别给两个账号做一些数据填充操作。这一步很多人忽略但实际上非常重要。比如alice登录后添加一辆车、发一条社区帖子、创建一个优惠券申请bob也执行同样的操作。数据填充的意义在于越权检测需要目标资源存在才算数——如果alice想访问bob的车辆前提是bob确实有一辆车。没有基础数据工具再怎么替换ID也只能遇到空响应大量漏洞被埋没。数据填完之后需要采集一份业务基线流量。主流方式有三种一是让浏览器流量经过Burp Suite抓包导出HTTP历史记录二是用Postman的Collection Runner跑一遍关键业务然后导出Collection三是直接用Hadrian自带的流量采集模块抓包。我个人的建议是优先用Postman或OpenAPI定义因为结构化程度高Hadrian解析后的资源模型准确度也高。Burp导出的流量有时候会有重复请求和噪音需要先做一次去重。采集流量时有一个原则尽量用低权限账号去操作一个完整的业务闭环包括登录、查询、创建、修改、删除。理想情况下流量里应该覆盖资源ID出现在URL路径中、资源ID出现在请求体里、资源ID以查询参数形式出现三种情况。Hadrian对这三种形式的支持程度不完全一样路径参数最容易自动识别请求体参数需要依赖schema定义查询参数在部分版本里需要手动配置正则规则。3.2 配置文件里最关键的几个字段跑自动化检测之前需要给Hadrian一个明确的配置文件。我一般用YAML格式把目标信息、认证信息、用户对、检测开关分开管理。拿我本地的配置举例target: name: crapi-local base_url: http://localhost:8889 schema_file: ./openapi/crapi.json auth: token_endpoint: http://localhost:8889/realms/crapi/protocol/openid-connect/token grant_type: password accounts: - user: aliceexample.com password: Password123 role: normal - user: bobexample.com password: Password123 role: normal resources: path_parameters: [vehicleId, postId, couponId] body_parameters: [vehicle_id, post_id] checks: horizontal: true vertical: true mass_assignment: false rate_limit: falsetoken_endpoint指向的URL需要说明一下crAPI的认证走的是OIDC兼容服务实际负责颁发Token的端点不是业务API本身而是独立的认证服务。端口和路径以本机部署后实际抓包为准配置里的值只是示例。如果这里配错了Vespasian发送请求时会频繁遇到401日志里全是认证失败这时候先调试token_endpoint别急着怀疑漏洞检测逻辑。resource列表里的vehicleId、postId这些字段是告诉Hadrian“这些位置出现的数字要当成资源标识符需要尝试跨用户替换”。配置得越准确水平越权的检测效果越好。如果你用的是OpenAPI schemaHadrian理论上能自动推断资源路径参数但自动推断在某些情况下会把普通参数也当成资源ID产生大量无意义请求。保留一份手工维护的资源字段清单是提高检测精度的关键。3.3 运行检测资产发现、实体绑定、越权验证配置好之后启动Vespasian执行引擎让它监听来自Hadrian的任务然后再启动Hadrian指定配置文件和输出目录。# 终端1启动执行引擎 python vespasian.py --config ./vespasian.yaml --listen 127.0.0.1:7788 # 终端2启动编排器跑检测 python hadrian.py --config ./hadrian.yaml --output ./reports/run1整个检测过程可以拆成三个阶段。第一阶段是资产发现Hadrian会解析OpenAPI定义列出所有API路径、方法、参数按照“资源-动作”的维度建立索引。第二阶段是实体绑定工具会利用采集的基线流量把账号A和账号B各自拥有的资源ID记录下来比如alice有vehicleId1001bob有vehicleId1002。第三阶段才是真正的越权验证Vespasian拿着bob的token去请求alice的资源路径同时拿着alice的token去请求bob的资源路径把响应与基线响应进行比较。响应比较的规则是Hadrian的核心。它不会简单把“状态码不等于401/403”就判为漏洞而是会做更细的相似度分析如果原本alice访问自己的车辆返回200且带有明确的车辆信息而bob访问alice的车辆也返回200且响应体结构与基线一致就判定为疑似水平越权。如果返回的是带错误码的JSON即便状态码是200也不会判为漏洞因为服务端实际上拒绝了访问。一辆小型API的检测用例通常在几百到几千个Vespasian默认并发度是10到20个线程跑起来很快通常一两分钟就能完成。建议第一次跑把并发度调低一点比如4先观察日志输出确认Token刷新、请求重试这些机制都正常再逐步提高。跑完之后输出目录下会生成一个Markdown或HTML格式的报告里面有每个检测用例的请求、响应、判定结果和复现步骤。3.4 报告出来后先别急着数漏洞拿到报告我习惯先不看漏洞数量先看请求成功率。如果报告显示有大量请求因为网络错误或超时而失败说明执行引擎和目标之间的连通性有问题这时候报告里所谓的通过或失败都不具备参考价值。先解决日志里的连接错误再谈漏洞判定。第二件事是打开几条疑似越权的记录看证据是否完整。一个合格的越权证据应该包含三个要素是谁的Token请求身份、请求了哪个资源目标对象、响应里包含什么泄露的数据。如果响应体确实包含了其他用户的车辆信息、邮箱、手机号等敏感字段那基本可以确认是水平越权直接进复现环节。复现方法也很简单用报告里记录的原始请求在Burp或Postman里重放一遍确认不是自动化工具产生的误判。在crAPI靶场里比较典型的复现路径是登录bob的账号拿到bob的access_token然后请求alice的车辆ID对应的路径如果响应里出现了alice的车牌号、车型等信息说明对象级授权缺失被成功利用。这类问题在真实业务里通常是查询接口缺少“当前用户与资源归属关系”的校验修复方式是在服务端对资源Owner做一次比对。还要强调一点自动检测报告只能作为线索和证据索引最终的漏洞确认一定要有人工参与。工具的价值在于把逐个接口手工替换ID的重复劳动省掉把可疑点集中在几十条记录里而不是替代安全人员的判断。尤其是垂直越权普通用户拿管理接口去请求存在大量业务场景需要人工确认“这个接口是否真的属于管理功能”。4. 检测原理与规则设计让工具少误报4.1 水平越权BOLA的自动化判定逻辑水平越权的本质是身份与资源错配。自动化判定逻辑通常用三条请求的差异比对实现用户A访问自己的资源R_A得到基线响应Resp_A。用户B访问自己的资源R_B得到基线响应Resp_B。用户B访问用户A的资源R_A得到探测响应Probe。如果Probe与Resp_A近似返回了同样的资源数据且状态码是200或业务成功码就说明资源R_A并没有绑定到用户A的授权域B可以越权访问。但有个很trick的地方有些接口在越权时也会返回200响应体里却是错误信息JSON比如 {error: no permission}。这种情形不能只看状态码必须对响应体的业务字段做二次分析。Hadrian在实现上会把响应体做结构化提取先看HTTP状态码再看JSON里的status、code、message等字段最后做相似度计算。自定义规则时的经验是不要只看状态码要看业务语义上是否拒绝了当前请求。如果服务端返回200但body里明确写了Permission denied实际上是被拒绝的不应该判为漏洞否则会收获大量误报。还有一类水平越权不是通过路径参数而是通过请求体的对象引用。比如POST一个领券接口请求体里带 vehicle_id服务端可能只校验了“这个接口是否需要登录”却没有校验“这辆车是否属于当前用户”。Hadrian检测这类问题的逻辑是把基线请求体中的资源ID替换成另一个用户的资源ID然后比较响应。相比路径参数这类检测更依赖OpenAPI schema里的required字段和参数类型定义如果schema不完整工具可能压根识别不出请求体里哪些字段是资源引用所以能提供完整schema就尽量提供完整schema。4.2 垂直越权BFLA的自动化判定逻辑垂直越权的自动化检测稍微简单一些核心思路是身份与动作匹配。检测过程通常这样先从OpenAPI定义或管理页流量里提取出高风险动作比如用户管理、权限修改、删除资源、导出数据等然后用普通账号Token去调用这些动作如果返回结果中没有出现401、403等禁止访问信号就判定为疑似垂直越权。具体到实现Vespasian会维护一个角色-权限映射表路径匹配规则可以用正则比如匹配admin、user、role、permission等关键字的路径归入管理类。但这套启发式规则误报率不低因为很多真实业务里管理功能不一定在路径里有admin字样反而是在请求体或Header里通过角色字段控制。我自己的做法是先让Hadrian自动跑一遍启发式规则把垂直越权结果全部标为待确认再通过人工筛选真正的管理接口标准化成自定义规则后加入回归集。一个容易被忽略的检测点HTTP动词滥用。普通账号可能不能用GET访问别人的资源但用PUT、PATCH却可以修改别人资源或者DELETE可以删除别人资源。自动化工具如果只检测GET会漏掉大量高风险漏洞。建议把同一个资源路径的GET和写操作都纳入越权检测范围。我在crAPI里实测过很多越权漏洞恰好发生在POST和DELETE方法上只测GET的结果是全部通过换成全动词遍历之后立刻暴露真实问题。4.3 降低误报率的三条经验经验一开启响应差异阈值过滤。有些接口会给每个登录用户返回用户特定信息比如“你好alice”即使工具把资源ID替换成bob的ID响应里依然返回alice自己的信息这不是越权而是服务端忽略了传入ID。Hadrian的相似度算法在设计中会重点关注响应中出现的资源特定字段是否跟着变化初期把差异阈值调高一点能减少把无状态接口误判为越权的情况。经验二对拒绝结果要有统一业务标准。建议在配置文件中维护一个拒绝特征库把常见拒绝响应全部列进去包括401、403、404、400加invalid token、forbidden、not found、no permission、insufficient privileges等凡是命中拒绝特征库的响应都按访问被拒绝处理。这套特征库在不同业务里需要微调我用过一段时间后沉淀出了一个通用版本大概20条左右。经验三务必区分静态资源与业务资源。API里通常还有静态资源接口或健康检查接口这些接口没有资源归属概念不应该进入越权测试范围。如果在配置文件的exclude列表里把这些路径加进去检测速度和准确率都会提升还能避免报告里出现大量无意义的疑似越权。5. 常见报错与排查技巧实录我把这段时间实际踩过的坑整理成一张速查表按现象、原因、解决三列列出方便直接对照现象可能原因解决/排查方法docker compose up 后容器一直 Restarting端口被占用或内存不足用 docker compose logs 看具体报错调整Docker Desktop内存到4GB以上页面能开但接口全部失败前端与API host配置不一致检查8888端口前端页面里配置的API地址是否为localhost:8889crAPI注册后功能不生效账号未验证邮箱打开MailHog http://localhost:8025点击邮件里的验证链接工具报 api error: 400 invalid schema for function artifact传入的OpenAPI定义不符合工具版本要求用OpenAPI官方校验器先校验schema检查是3.0还是3.1必要时转换Docker CLI报failed to connect to the docker api at npipe...Docker Desktop引擎未启动Windows常见启动Docker Desktop等待引擎就绪后再执行 docker versionVespasian发请求大量401token_endpoint或client_id配置错误用Postman单独请求token端点确认能拿到access_token报告里全部接口都被判定为通过采集流量里没有包含有效业务对象数据确认两个账号都已添加车辆、发帖确认资源ID字段配置准确跑一半进程卡死无日志并发过高或目标限流降低并发数增加请求超时时间观察较慢的接口解析OpenAPI时报YAML/JSON格式错误schema文件不是标准OpenAPI规范先用swagger-editor打开检查或者用openapi-generator validate表格之外有几个场景值得单独展开。5.1 Docker Desktop 引擎连接失败的处理这个报错我见过的次数太多了尤其Windows环境failed to connect to the docker api at npipe:////./pipe/dockerdesktoplinuxen。这个npipe路径是Docker CLI和Docker Desktop引擎之间的命名管道报错时说明CLI找不到引擎。最常见的场景是用户启动终端后直接敲docker命令但Docker Desktop还在启动中引擎未就绪。解决方法是等Docker Desktop的图标变稳定后再操作或者使用WSL2模式时在WSL里执行 docker version 看Server是否有输出。如果重启Docker Desktop也无济于事可以打开任务管理器结束所有Docker相关进程再重新启动。还有一个稍隐蔽的情况公司电脑上同时安装了Docker Desktop和Podman或者Rancher DesktopDocker CLI默认上下文指向了别的引擎。此时执行 docker context ls 可以查看当前上下文用 docker context use desktop-linux 切回Docker Desktop。这个技巧虽然简单但能解决很多莫名其妙的连接问题。5.2 OpenAPI Schema 校验失败的处理工具报 api error: 400 invalid schema for function artifact 这类错误时先按几个方向排查。第一步确认传给Hadrian的schema文件是否能通过OpenAPI官方校验直接用在线Swagger Editor打开或者用openapi-spec-validator这个Python库做校验。第二步确认schema是OpenAPI 3.0还是3.1部分工具版本对3.1的某些高级特性支持不完善会导致解析阶段直接失败。第三步检查schema里是否有非标准扩展字段或自定义function定义。从我的经验来看真实业务团队导出的OpenAPI定义经常会带私有扩展字段有些是网关或RPC框架自动生成的比如x-rpc-method、x-router这类。Hadrian解析时如果遇到不能识别的字段默认是跳过还是一刀切报错取决于版本。如果报错实在解决不了可以写一个小清理脚本把非标准字段剥离只保留核心的paths、components、securityDefinitions。这样能保证解析成功率同时不损失关键测试面。5.3 认证Token过期导致结果不可用自动化检测跑长任务时token过期是必然事件。crAPI的access_token有效期很短如果扫描任务超过有效期Vespasian需要在任务执行中自动刷新。这个刷新能力要提前在配置里打开refresh_token的接口地址和client凭据否则任务后半段全部变成401报告没法用。我习惯在正式跑检测之前做一个十分钟冒烟测试让工具不停循环调用一个简单接口跑10分钟观察中途Token刷新是否正常、刷新后是否立即重试上一次失败的请求。如果冒烟测试里一直有401积累就说明重试机制配置不对。这个小步骤看似浪费时间但能避免一次大规模扫描白跑。5.4 网络与超时设置如果Hadrian部署在Docker容器里而目标crAPI也在Docker容器里两者之间的网络模式需要确认。最简单的做法是用host网络模式或者让Hadrian容器加入crAPI所在的docker network这样直接通过容器名访问目标比如 http://crapi-api:8889。如果用localhost访问可能会访问到Hadrian容器自身导致Connection refused。这个坑在微服务部署时非常常见提前规划好网络拓扑能省很多麻烦。超时参数一般按目标接口的P95响应时间来设置。先用工具统计一下目标API的平均响应时间比如都在50ms以内那把Vespasian的单请求超时设为3秒、重试2次就足够了。如果目标接口有慢查询响应时间可能到3到5秒超时要放宽到10秒否则报告里会充满超时项同样无法判断漏洞。6. 写在最后给准备上手的人最后分享一点个人实操心得。我第一次跑这套工具链的时候差点被报告里的海量疑似漏洞淹没后来发现是基线数据太脏、请求体里塞满了无关字段导致的。现在我的标准流程是先花半小时准备好干净的两个测试账号和数据再花十分钟把资源字段配置精确然后才启动自动化。这一步前置工作看似慢实际是让整个检测又快又准的关键。如果公司正准备把API越权检测接入CI流水线我的建议是先用crAPI把整条链路跑通产出一份标准报告模板再拿真实业务API的小规模子集做试点。千万别一上来就对全量生产接口开扫一方面是授权边界不清晰另一方面是误报会淹没真正有价值的问题。自动化工具是放大器你对它投入的准备工作和规则质量会被成倍放大成检测结果的准确率。这套Hadrian、Vespasian、crAPI的组合最终给我带来的价值就是把越权回归从手工一测一整天压缩到了流水线十分钟。按照我个人的习惯每次跑完报告还会把新发现的可疑接口补充到资源字段清单里越跑越准。希望这份实战记录能帮你少踩几个坑把API安全自动化这件事真正落下来。