做了十几年Web渗透测试的人,大概都习惯了这套固定动作:把Burp Suite或OWASP ZAP挂在浏览器和目标服务器之间,看着每一个HTTP请求和响应从socket上流过。在PHP、ASP.NET、Rails这类服务端渲染架构的年代,这套玩法几乎是无死角的——路由跳转、会话切换、鉴权挑战,全都在网络层发生,代理看得一清二楚。但今天随便打开一个用React、Next.js、Vue 3搭起来的单页应用(SPA),你会发现传统拦截代理突然变成了瞎子。为什么?SPA的运作逻辑变了。页面不是从服务器整页刷新,而是由浏览器里的JavaScript运行时维护一套复杂的、动态的、只存在于内存里的状态机。路由切换、权限判断、数据渲染全都在客户端瞬间完成,只有真正调API时才走网络。于是出现了三个网络代理看不见的盲区:
第一个是未链接的路由块(Unlinked Route Chunk)。现代打包工具(Webpack、Vite、Turbopack)做代码分割时,会把内部管理后台的路由(比如 `/admin/tenant-provisioning`、`/internal/feature-flags`)编译进独立的JS chunk里。普通用户导航不到这些路由,它们就永远不发HTTP请求。代理看到的流量是零,但这些路由签名和参数契约其实明晃晃地躺在客户端内存里。
第二个是临时内存状态与密钥。Redux、Pinia、Zustand这类响应式状态库,加上全局window命名空间,经常会在内存闭包里暂存临时bearer令牌(一种访问凭证)、未脱敏的用户标识、内部网关密钥、多租户元数据——这些东西不写进localStorage或cookie,网络层完全看不见。
第三个是跨源消息投递点(postMessage)。嵌入iframe或与第三方认证服务通信时,SPA依赖`window.postMessage`。如果事件监听器没严格校验`event.origin`,就会形成DOM型XSS和状态投毒漏洞(CWE-345),而这种攻击根本不经过网络层。
反过来也有问题:当你好不容易挖出一个未文档化的接口或敏感参数,下一步验证时没有护栏——越界测试第三方SSO、碰到云元数据端点(169.254.169.254)、把未打码的PII写进漏洞报告——这些都可能让你丢掉漏洞赏金计划资格,甚至惹上官司。
为了解决“从发现到验证”的全程盲区,作者开源了两件套工具:StateHunter——一个运行在Chrome DevTools里的Manifest V3扩展,用来做客户端侦察;AuditGuard——一个纯Python、零依赖的命令行框架,负责边界执行和安全港认证。
StateHunter的思路很聪明:它不往页面里注入笨重的第三方脚本,而是跑在隔离的DevTools检查桥里,被动地解析、分类客户端JavaScript执行。它做了三件核心事:
一是从压缩后的chunk里反混淆提取未链接路由。通过AST遍历和正则匹配,把Next.js App Router的动态路由定义、React Router/Vue Router的path声明、编译后的Axios/Fetch调用里的API路径全部抓出来,生成一份从未出现在导航菜单里的端点清单。你可以在目标应用上一键激活,立刻看到隐藏的管理路由和调试控制台。
二是审计运行时postMessage处理器。它用隔离的instrumentation shim钩住`window.addEventListener('message')`,记录消息签名并分析处理器的执行图。如果处理器用了通配符`*`作为目标源,或者直接`eval()`或`innerHTML = event.data`而没有严格schema清洗,就标记为高风险DOM投毒点,连同堆栈抓下来。
三是深度内存状态与高熵密钥提取。它递归检查全局window命名空间、sessionStorage键和响应式状态库,用Shannon熵和针对性正则去揪出泄露的JWT(JSON Web Token,一种携带签名信息的令牌)、云厂商密钥(AKIA、AIza开头那种)、私有租户ID和UUIDv4标识。
所有这些发现最终编译成一份`scope.yaml`清单,直接对接AuditGuard。这就是第二阶段的事——数学级别的边界强制。
AuditGuard解决的是验证阶段的“闯祸”问题。它不靠简单的子串匹配,而是把项目范围解析成数学边界树:CIDR(无类别域间路由,IP地址块表示法)、通配域名层级、严格URL路径排除。任何出界请求,比如试图打到链路本地元数据地址169.254.169.254,会立刻断掉socket,并记入防篡改的审计账本。
更硬核的是它内置了双角色IDOR(不安全的直接对象引用,指通过改ID就能越权访问他人数据的漏洞)检测矩阵。你准备两个测试账号user_a和user_b,AuditGuard先用user_a的令牌拿到资源,再用user_b的令牌重放同一请求,比较状态码、响应长度和AST内容差异。如果user_b也能拿到user_a的数据,就确认了IDOR/BOLA(对象级授权破坏)漏洞;如果返回403/404,说明防护到位。这个差分逻辑很干净,避免了手工猜测。
除此之外,AuditGuard还内置了纯Python的Nuclei兼容引擎(Nuclei是一个基于模板的漏洞扫描器,原本要求Go运行时),可以只读执行安全头检测、CORS配置检查、缓存策略验证这类模板;实现了完整的FIRST.org CVSS v3.1指标计算器(CVSS是通用漏洞评分系统,v3.1是当前主流版本),避免编造严重性;最后会生成一个Disclose.io安全港合规证书——把所有请求、时间戳、边界评估、限流事件写入追加式JSONL账本(JSON Lines,每行一个JSON对象),结束时对账本做SHA-256摘要并签名。这个证书在CFAA(美国计算机欺诈与滥用法)和DMCA反规避条款下能提供法律保护,只要严格按规则测试。
整个工作流是:Chrome里用StateHunter导出scope.yaml,AuditGuard导入后,先跑边界和路由诊断,再对候选中IDOR用双凭证探测,最后直接导出成HackerOne或Bugcrowd格式的报告。全程本地执行、零遥测、MIT协议。
我最觉得意外的不是功能多,而是作者把安全测试的“法律边界”也当成了工程问题来解。很多研究员挖到洞却不敢报,或者报了被踢出项目,就是因为边界模糊。AuditGuard用密码学账本证明“我确实没出界”,这个思路值得借鉴——任何需要对外汇报敏感操作的系统,都可以用类似的防篡改日志来建立信任。
这套工具的价值不只是给渗透测试者用。凡是维护SPA前端、在浏览器里处理敏感状态、或者做云原生应用的安全工程师,都可以直接用StateHunter扫描自己的应用,看看有没有隐藏路由、裸奔的令牌、不安全的postMessage处理器。AuditGuard的边界校验和IDOR探测模式,也可以移植到自动化安全测试流水线里。要注意的坑是:路由提取依赖正则和AST启发式,面对极端混淆的打包产物可能有漏网;CIDR/域名的边界判断在复杂的云拓扑(比如AWS API Gateway多区域自定义域名映射、Cloudflare Workers路由)下可能出误判。但作为开源框架,作者正在征集社区RFC,这些问题是可以迭代的。整体上,这是把SPA时代的安全盲区摊开、然后用工程化手段填平的一次扎实尝试。
内容与图片版权归原作者所有 · 原文: https://codeandcypher.com/posts/client-side-spa-recon-and-safe-harbor-verification/