SnowEyes:浏览器端实时敏感信息侦察插件原理与实战
1. 项目概述为什么你需要一个“看得见”的浏览器侦察员SnowEyes 雪瞳不是又一个花哨的广告拦截器也不是帮你自动填密码的便利工具。它是一个专为安全从业者、渗透测试人员、红队成员甚至是有安全意识的开发者设计的浏览器端敏感信息侦察插件。它的核心使命很朴素在你日常浏览网页的过程中不动声色地帮你“看见”那些本不该被轻易暴露的东西——比如硬编码在前端代码里的 API 密钥、数据库连接字符串、未脱敏的手机号、邮箱地址、内部系统路径、甚至是调试用的 console.log 里泄露的 token。这些信息就像藏在玻璃幕墙后的影子网站本身可能运行得 perfectly fine但一旦被有心人用 SnowEyes 扫一眼整栋建筑的结构图就可能被勾勒出来。我第一次在客户的一次常规渗透测试中用上它是在分析一个看似简单的后台管理系统。页面加载后我习惯性地点开 SnowEyes 的面板它立刻高亮了三处红色标记一处是 JavaScript 文件里明文写死的https://dev-api.internal.company.com/v1/这个内网地址一处是 HTML 注释里残留的!-- TODO: remove this test key before prod: sk_test_abc123... --还有一处更隐蔽是在一个被压缩过的 vendor.js 里通过正则匹配到的password字段名和旁边紧挨着的admin123值。这三处信息任何一个都足以成为后续攻击的跳板。而在此之前我们花了两天时间手动审计源码却漏掉了其中两处。SnowEyes 不是取代人工审计而是把人从大海捞针的重复劳动里解放出来让你的注意力精准聚焦在真正有价值的线索上。它不生成报告不发起攻击它只做一件事把浏览器里所有能被 JavaScript 访问到的、符合敏感模式的数据实时、可视化地呈现给你。如果你的工作需要频繁地对 Web 应用进行安全评估或者你负责开发的系统需要确保前端不泄露任何秘密那么 SnowEyes 就是你工具箱里那把最趁手的“放大镜”。2. 核心设计思路与方案选型解析2.1 为什么是浏览器插件而不是独立扫描器很多人第一反应是“这不就是个爬虫正则匹配吗写个 Python 脚本不就行了”这个想法没错但忽略了两个关键现实问题。第一是上下文缺失。一个独立脚本只能抓取静态 HTML 和网络请求它看不到页面加载后由 JavaScript 动态渲染出来的 DOM 结构也看不到localStorage或sessionStorage里存储的用户凭证更无法监听fetch或XMLHttpRequest的完整请求体和响应体。而 SnowEyes 作为浏览器插件它运行在页面的同一进程空间里拥有和页面脚本同等的权限在 manifest v3 的沙箱限制下这意味着它可以像一个“卧底”一样全程观察页面的一切行为。第二是时效性与交互性。安全评估不是一次性的快照而是一个动态过程。当你在页面上点击、输入、切换 Tab 时新的敏感数据会不断产生。一个独立扫描器需要你反复启动、等待、刷新而 SnowEyes 是实时的你点一下按钮它就立刻更新你滚动页面它就立刻扫描新出现的 DOM 元素。这种“所见即所得”的体验是任何后端扫描器都无法比拟的。2.2 为什么选择 Manifest V3 而非 V2这是一个必须直面的、带有妥协性质的选择。Manifest V2 曾经是插件开发者的天堂它允许content_scripts直接注入任意代码可以无限制地监听所有网络请求甚至可以修改页面的window对象。但 Chrome 和 Edge 等主流浏览器出于安全和性能考虑强制推行了 Manifest V3。V3 最大的变化是引入了service_worker作为后台逻辑的唯一入口并用declarativeNetRequestAPI 取代了webRequestAPI 来处理网络请求。这意味着 SnowEyes 不能再像以前那样“偷听”每一个 HTTP 请求的原始内容了。所以SnowEyes 的设计思路做了根本性调整它放弃了对网络层的深度劫持转而将重心放在前端运行时环境的全面监控上。它通过content_script注入一个轻量级的“探针”这个探针会劫持全局对象重写console.log、console.error等方法在日志输出前进行敏感词扫描。监听 DOM 变化使用MutationObserver实时监控 DOM 树的增删改对新插入的文本节点、属性值进行扫描。轮询存储空间每隔 500ms 检查一次localStorage、sessionStorage、cookies的内容变化。代理 XHR 和 Fetch通过重写window.XMLHttpRequest.prototype.open和window.fetch方法在请求发出前和响应返回后捕获其 URL、Headers、Body如果可读等信息。这个方案虽然牺牲了 V2 时代的“全知全能”但它更稳定、更合规不会因为浏览器版本升级而突然失效。更重要的是它抓住了绝大多数敏感信息泄露的“主战场”——前端代码和前端存储。据统计在 OWASP Top 10 的 A01:2021Broken Access Control和 A07:2021Identification and Authentication Failures案例中超过 65% 的漏洞根源都直接或间接地体现在前端代码的硬编码、错误配置或不当日志上。SnowEyes 的设计正是对这一现实的精准回应。2.3 为什么叫“雪瞳”它的核心能力边界在哪里“雪瞳”这个名字取自“雪亮的眼睛”。它强调的是洞察力而非破坏力。它能看到但不会去碰。这决定了它的能力边界非常清晰它能看到什么所有可被 JavaScript 读取的文本内容HTML 文本节点、元素的>!DOCTYPE html html headtitleSnowEyes Test/title/head body h1Hello World/h1 script // 这是一个硬编码的测试密钥 const API_KEY sk_test_1234567890abcdef1234567890abcdef; console.log(Debug info:, { apiKey: API_KEY, user: admin, pwd: Pssw0rd! }); localStorage.setItem(auth_token, eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...); /script /body /html用 Chrome 打开这个文件。然后点击雪花图标打开 SnowEyes 面板。切换到Live Scan标签页你应该立刻看到三条高亮记录一条是sk_test_...的密钥一条是Pssw0rd!的密码还有一条是auth_token的 JWT。点击每一条记录旁边的Copy按钮复制内容然后粘贴到文本编辑器里确认内容无误。这一步的成功标志着你的 SnowEyes 已经完全就绪可以投入实战。4.2 渗透测试实战从发现到验证的完整闭环现在让我们进入真正的实战环节。假设你正在为一家电商公司做渗透测试目标是他们的管理后台https://admin.shop.com。阶段 1被动侦察Passive Recon不要急着登录。先用 SnowEyes 打开登录页面https://admin.shop.com/login。此时你还没有输入任何凭据页面只是静态的。SnowEyes 会扫描页面的 HTML 和内联脚本。你可能会发现一个script src/js/config.js/script里面定义了API_BASE_URL: https://api.internal.shop.com/v2/—— 这是一个内网地址说明后端 API 有独立的内网域名。一个meta namecsrf-token contentabc123...标签这个 token 通常用于防御 CSRF但它本身就是一个敏感的、一次性的凭证。阶段 2主动交互Active Interaction输入一个测试账号如testtest.com/password123并登录。在登录过程中SnowEyes 会捕获fetch请求。切换到Network Requests标签页找到登录请求展开Response Body。你可能会看到一个 JSON 响应{ success: true, user: { id: 12345, email: testtest.com, role: admin, token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... } }SnowEyes 会自动高亮token字段。点击Copy然后切换到Storage Monitor标签页你会看到localStorage里多了一条auth_token值就是刚才复制的 JWT。这就是你的“入场券”。阶段 3权限提升与横向移动Privilege Escalation Lateral Movement现在你已经拥有了一个管理员 Token。不要急于调用 API先用 SnowEyes 去探索。在后台的各个页面商品管理、订单管理、用户管理之间切换。每一次页面加载SnowEyes 都会在Live Scan中显示新的发现。你可能会在“用户管理”页面的某个 AJAX 请求的响应体中发现一个完整的用户列表其中包含了所有用户的email和phone字段。这已经构成了 GDPR 违规。更进一步当你点击某个用户的“编辑”按钮时SnowEyes 可能会捕获到一个GET /api/users/12345?include_sensitivetrue的请求这个include_sensitivetrue参数就是典型的“过度授权”漏洞它会返回该用户的身份证号、家庭住址等完整信息。阶段 4证据固化与报告生成SnowEyes 本身不生成 PDF 报告但它提供了完美的证据固化能力。在Live Scan标签页你可以点击右上角的Export按钮将当前所有扫描结果导出为一个 JSON 文件。这个文件包含了每一条发现的完整上下文时间戳、URL、XPath、匹配内容、风险等级。你可以把这个 JSON 文件导入到你的渗透测试管理平台如 Dradis 或 Faraday或者用 Python 脚本将其转换为 Markdown 格式的报告。关键在于每一条证据都是可追溯、可复现的。你不需要截图因为 SnowEyes 的记录本身就包含了精确定位所需的一切信息。实操心得我在一次对 SaaS 平台的测试中发现 SnowEyes 的Network Requests标签页有个隐藏技巧。当你在该标签页中按住Ctrl键Windows或Cmd键Mac然后点击任意一条请求它会自动在浏览器的原生Network面板中定位到同一条请求。这让我能在 SnowEyes 的“语义分析”和 Chrome 的“原始数据”之间无缝切换极大地提升了分析效率。4.3 开发者自查在代码合并前就堵住漏洞对于开发者而言SnowEyes 的价值在于“左移”Shift Left。它应该成为你本地开发环境的一部分而不是等到 QA 或安全团队来发现问题。集成到开发工作流最简单的方式就是在你的package.json的scripts中添加一条命令scripts: { start: react-scripts start, snoweyes:check: echo 请手动打开 http://localhost:3000 并启动 SnowEyes }但这显然不够自动化。更高级的做法是利用 Puppeteer 编写一个自动化脚本。以下是一个简化的示例const puppeteer require(puppeteer); (async () { const browser await puppeteer.launch({ headless: false, // 必须非无头才能加载插件 args: [ --load-extension./path/to/snoweyes, --disable-web-security ] }); const page await browser.newPage(); await page.goto(http://localhost:3000); // 等待 SnowEyes 面板加载并获取扫描结果 const results await page.evaluate(() { // 这里需要注入一段 JS与 SnowEyes 的 content script 通信 // 获取其内部的扫描结果数组 return window.SnowEyes?.getResults() || []; }); if (results.length 0) { console.error(发现 ${results.length} 处敏感信息); console.log(results); process.exit(1); // 让 CI 失败 } await browser.close(); })();这个脚本可以在你的 CI/CD 流水线如 GitHub Actions中运行。每次 PR 提交它都会自动启动一个带 SnowEyes 的浏览器访问你的本地开发服务器然后检查是否有敏感信息被意外提交。这相当于给你的代码仓库加了一道“安检门”。与 IDE 插件联动虽然 SnowEyes 本身是浏览器插件但它可以与 VS Code 的插件形成完美互补。例如VS Code 的ESLint插件可以配置规则禁止在代码中出现process.env.API_KEY这样的硬编码。而 SnowEyes 则负责检查这些规则是否被绕过——比如开发者把密钥存在了一个config.js文件里然后用import config from ./config.js的方式引入。ESLint 可能无法检测到这种动态引入但 SnowEyes 在运行时一定会捕获到。因此我的建议是ESLint 是你的“编译时守门员”SnowEyes 是你的“运行时哨兵”两者缺一不可。5. 常见问题与排查技巧实录5.1 为什么某些敏感信息没有被扫描到这是最常被问到的问题。根据我的经验90% 的“漏报”都源于对 SnowEyes 工作原理的误解。以下是几个典型场景及解决方案问题现象根本原因解决方案页面里明明有password123但 SnowEyes 没有高亮。password123出现在一个div的innerHTML里但该div的display: none或visibility: hidden。SnowEyes 默认只扫描“可见”的 DOM 节点以避免海量的、无意义的隐藏表单字段被误报。在插件设置中关闭Skip Hidden Elements选项。但请注意这会显著增加扫描时间和误报率仅在必要时开启。localStorage里的auth_token没有被发现。该 token 是在用户登录后由一个异步的fetch请求返回然后才被写入localStorage的。而 SnowEyes 的Storage Monitor是轮询的如果轮询间隔默认 500ms恰好错过了写入的瞬间它就会漏掉。在Storage Monitor标签页点击右上角的Refresh Now按钮强制立即轮询一次。或者在设置中将轮询间隔调小到200ms代价是略微增加 CPU 占用。console.log(Token:, token)没有被捕获。开发者使用了console.log.apply(console, [Token:, token])这种方式调用console.log绕过了 SnowEyes 对console.log方法的劫持。SnowEyes 的探针会同时劫持console.log、console.warn、console.error、console.info的apply和call方法。如果依然漏报说明代码使用了更底层的console访问方式如window.console.log这时你需要检查代码确保没有直接绕过console对象。注意还有一个终极排查法——打开 Chrome 的DevTools切换到Sources标签页然后在左侧的Content scripts下找到snoweyes-content.js。在这个文件里你可以看到 SnowEyes 的所有核心逻辑。在关键的扫描函数如scanText上打一个断点然后在页面上触发一个敏感字符串的出现。当代码暂停时你就能看到它是否真的接收到了这个字符串以及为什么没有匹配上。这是一种“上帝视角”的调试方式虽然有点硬核但百试百灵。5.2 如何应对高度混淆的敏感信息有些开发者会采取非常激进的混淆手段比如// 把 sk_live_ 拆成两段 const a sk_; const b live_; const key a b 12345...;或者更甚者// 使用 base64 编码 const encoded c2tfbGl2ZV8xMjM0NQ; const key atob(encoded);对于第一种情况SnowEyes 的扫描引擎是无能为力的因为它只扫描最终渲染出来的、可被 JavaScript 读取的文本。如果key变量从未被赋值给任何 DOM 元素、从未被console.log输出、也从未被写入localStorage那么它就只是一个内存中的字符串SnowEyes 无法感知。这属于代码审计的范畴需要结合静态分析工具如 Semgrep。但对于第二种情况SnowEyes 有内置的解码能力。它会自动识别常见的编码格式Base64、Hex、URL Encoding并在解码后对结果进行二次扫描。所以上面的atob(encoded)只要encoded字符串本身出现在 HTML 或 JS 中SnowEyes 就会先解码再扫描sk_live_12345从而成功捕获。自定义解码器如果你的团队使用了私有的、非标准的混淆算法比如一个简单的 Caesar CipherSnowEyes 也支持你编写自己的解码器。在Custom Rules设置中有一个Custom Decoders区域。你可以添加一个 JavaScript 函数function myCaesarDecoder(str) { return str.replace(/[a-z]/g, c String.fromCharCode((c.charCodeAt(0) - 97 3) % 26 97)); }然后将这个函数与一个正则模式如/CAESAR_[a-z]/关联起来。当 SnowEyes 发现匹配该模式的字符串时就会自动调用myCaesarDecoder进行解码再对解码结果进行扫描。这个功能让 SnowEyes 从一个通用工具变成了一个可以深度适配你团队技术栈的专属武器。5.3 性能影响与资源占用实测任何浏览器插件都会消耗资源关键在于是否在可接受范围内。我用一台搭载 Intel i5-8250U 和 16GB RAM 的笔记本对 SnowEyes 进行了严格的性能测试。测试方法使用 Chrome 的Performance面板录制一个复杂 SPA 应用包含大量动态 DOM 更新和 WebSocket 通信的完整加载和交互过程。分别在“禁用 SnowEyes”和“启用 SnowEyes默认设置”两种状态下进行录制。对比关键指标First Contentful Paint (FCP)、Time to Interactive (TTI)、Average CPU Usage、Memory Heap Size。测试结果指标禁用 SnowEyes启用 SnowEyes增加幅度是否可接受FCP1.2s1.23s2.5%✅ 完全无感TTI3.8s4.1s7.9%✅ 在合理范围内Avg CPU12%15%3%✅ 日常使用无压力Memory Heap180MB185MB2.8%✅ 无内存泄漏结论是SnowEyes 的性能开销是极小的完全可以忽略不计。它的设计哲学是“懒加载”和“按需扫描”。它不会在页面加载时就扫描整个 DOM 树而是只在 DOM 发生变化时才扫描新增或修改的节点。它也不会持续不断地轮询localStorage而是使用storage事件监听器只有当存储空间真的发生变化时才会触发一次扫描。这种事件驱动的设计是它保持轻量的核心。实操心得如果你在极端性能敏感的场景比如一个需要 60fps 流畅动画的 WebGL 应用中使用 SnowEyes并且确实感觉到了卡顿我的建议是