案例研究

一个被改动的 ID:AI 渗透测试如何演变为完全账户接管

作者: Sintropyc · 案例研究

这是一次由 Sintropyc 自主 AI 渗透测试智能体全程完成的真实项目。它从一个可枚举的对象 ID 开始,最终抵达私有的管理员后台——而在途中,智能体否决了自己的第一个判定,认定其很可能是误报。所有可识别细节均已匿名化;测试已获应用所有者授权。

发起扫描IDOR 测试 →

一句话概述

一个自主 AI 智能体像真实攻击者那样测试了一个电商 SaaS。它发现只需改动一个整数就能读取其他会员的私密资料,随即将该判定否决为可能的误报,用真正独立的账户重新证明,然后一路打到管理员——以一个六十秒前刚注册的客户身份读取了私有的营收后台。每一步都是被证明的效果,而不是扫描器所谓的“可能”。

已授权 · 已匿名化

测试已获应用所有者授权,默认只读,在隔离沙箱中进行,使用经过阉割的可逆载荷。目标、其域名、真实用户及任何机密均已匿名化或省略——此处不指向任何真实的个人、凭据或系统。

为何这个漏洞屡屡得手

对象级授权失效——BOLA,即经典 IDOR 在 API 时代的近亲——位居 OWASP API 安全 Top 10 之首。这个漏洞平淡却无处不在:像 GET /api/members/{id} 这样的接口,会为你传入的任何 id 返回对象,而不检查你是否有权查看。改个数字,就读到了别人的数据。

扫描器对此束手无策。扫描器看到 GET /api/members/42 返回 200 OK 便不以为意——本来就该返回 200。这条记录属于你还是属于陌生人,是关于身份和业务语境的问题,而非 HTTP 状态码。这正是人工测试最常发现、而自动化工具最常漏掉的一类——也是我们的智能体最常证明的一类。

线索:一个被改动的 ID

目标是一个店面型 SaaS——一个与 JSON API 通信的单页应用。智能体梳理了 API,注意到两件对授权漏洞至关重要的事:会员 ID 是连续的整数,而会员对象中带有一个找回密码的提示字段——一个绝不应离开所有者自身会话的私密字段。

于是它注册了账户 A,并提出一个简单的问题:以 A 的身份,我能读取会员 1 吗?10 呢?两者都返回了完整资料,包括提示字段。表面看:教科书式的 IDOR。扫描器或急躁的测试者会截图 200 并写成 HIGH。

而我们的智能体没有。

被智能体丢弃的那个误报

在记录任何东西之前,智能体套用了它对每一条授权判定所坚持的标准:跨账户读取只有在两个身份确属不同所有者、且对私密资源没有任何合法共享访问时,才算真正的越权。它随即发现了自己证据中的漏洞——在许多应用中,自助注册的账户会悄悄落入同一个默认工作区。若 A 和“受害者”共享工作区,那么 A 读取那条记录并非越界,而是被授权的共享访问。真正的漏洞和正常功能在响应中可能一字节不差地相同。

区别所在

于是智能体降级并否决了自己的判定,给自己设了更难的任务:用明确彼此独立的所有者来证明。这正是访问控制测试中最大的误报来源。

它注册了第二个独立账户,确认二者是没有共享成员关系的不同所有者,然后重跑跨账户读取。成功了。仅凭 URL 中改动一个整数,一个账户就能读取另一位所有者的私密资料。此时它才成为一条发现——严重级别正好定在所展示的程度。

从读取到掌控整个商店

一个读取原语已经很糟。让它升级为接管的是下一个问题:如果服务器不检查谁能读取一个对象,它会检查谁能写入、以及能写入什么吗?它没有。

注册与资料更新接口接受客户端传来的 role 字段并原样保存。智能体注册了一个全新客户,并把自己的角色设为 admin。为证明这次提权是真实的,它使用了该权限:以一个刚注册几分钟的客户身份,打开了私有的管理员后台——会员总数与营收——并得到 200 OK。随后它在用户之间重复了这个把戏:一个客户改了另一个客户的角色。写入路径同样没有归属校验、没有角色校验。所有改动都用良性的、可自动还原的标记撤销了。

那些根本没有锁的门

智能体也试了不用钥匙的正门。若干 /admin/* 接口和一个用户列表接口,对完全未认证的请求返回了数据:商店配置、营收后台,以及每位会员的邮箱和全名。没有 token,没有会话——只是一个 GET。它还发现了经典的 token 缺陷:服务器接受 alg: none 的 JWT,并信任伪造的声明。在无法直接观测到签名密钥的地方,智能体如实说明,并将该部分标注为推断而非已证明。

如何确保你的应用不会这样

这些修复并不花哨,但确实管用:

  • 在服务端对每一次对象访问做授权,默认拒绝。在每个 /{id} 路由上,校验调用者是否可查看或修改这个具体对象。
  • 绝不把客户端输入绑定到特权字段。以白名单方式限定可写字段;role、tier、owner 应由服务器经管理员专用路径设置。
  • 把授权当作“所有者对所有者”。用两个真正独立的账户来测试。
  • 修好 token 层。拒绝 alg: none,固定算法,校验签名,轮换任何泄露的密钥。
  • 在证明授权之前,假定每个接口都是公开的——尤其是 /admin 与 /internal。

重点不是漏洞,而是证明

这里的每一条发现都由效果来证明,而不是靠特征来断言。一条被返回的他人记录。一次持久化并打开了门的角色变更。一个对需要会话的数据返回的未认证 200。而当证据不达标时,智能体会如实说明,并要么重新证明、要么给判定封顶。要证明,而非猜测。严重级别绝不超过实际展示的程度。

在你自己的应用上亲眼见证

一口价。一次完整测试,每条发现都用可用的漏洞利用证明,给出确切修复,并在你上线后进行复测。