全部文章
安全事件6 分钟阅读

一个第三方脚本从 Polymarket 卷走 250 万美元:今天就审计你的脚本

Browser window with a row of puzzle-piece plugins, one glowing orange piece pulling data streams out of a safe

上周四,10 月 29 日,Polymarket 将交易所切换到“仅可撤单”模式。用户纷纷报告账户被掏空,大约一小时内公司确认了原因,用他们自己的话说:“我们的一家第三方服务商遭到入侵”,据 The Block 援引。

据 Finance Magnates 转述《华尔街日报》的报道,这家服务商是 DebugBear,一个代码运行在 Polymarket 网站上的性能监控服务。该报道引用的一项区块链分析显示,超过 250 万美元经由 900 多笔交易流入了与攻击者相关的钱包。Polymarket 表示将全额赔付受影响的用户。

没有人的密码泄露,也没有人的助记词被钓鱼。用户只是登录了一个从别人服务器加载 JavaScript 文件的网站,而那个文件被换掉了。

如果你的应用从第三方 CDN 加载分析、聊天组件、A/B 测试、会话回放或性能监控脚本,你就是把自己代码能做的一切都托付给了这些供应商。下面讲讲发生了什么、为什么即使你不经营交易所也与你有关,以及这周如何检查你自己的应用。

发生了什么

根据 SafeState 对研究人员分析的整理,攻击过程大致如下:

  • Polymarket 的网站从 DebugBear 的 CDN 加载了一个真实用户监控(RUM)脚本。这是很常见的配置,成千上万的网站都以同样方式接入几十家供应商。
  • 攻击者把这个文件替换成了恶意 JavaScript。由于浏览器每次都直接从供应商那里获取最新文件,每位访客都立即拿到了新版本。
  • 该脚本会检查是否存在有效的 Polymarket 会话,从浏览器存储中读取账户数据、钱包信息和交易 API 凭证,然后借用 Polymarket 自己的签名流程,把仓位转移或卖出到攻击者的钱包。

值得细想的一点是:研究人员指出,硬件钱包和强密码都没有用,因为这个脚本根本不需要私钥。它就在用户自己的浏览器会话里,以已登录用户的身份行事。

据同一份报告,Polymarket 的修复方式是一次移除该供应商脚本的生产环境提交,交易在当天下午就恢复了。SafeState 还指出,这并不是该平台今年遭遇的第一起供应商脚本事件。

为什么这与你的应用有关

一个指向 cdn.some-vendor.com 的 script 标签,拥有与你自己代码相同的权力。它可以读取 DOM、读取 localStorage 和 sessionStorage、带着用户凭证调用你的 API,还能改变页面显示的内容。在“你的代码”和“你在上线那周随手粘贴进来的分析代码片段”之间,没有任何沙箱。

这对快速交付的应用和 AI 构建的应用尤其重要,因为:

  • 脚本越积越多。每一句“加个分析”“加个聊天气泡”“加个错误追踪”的提示词,往往都会再添一个标签,事后很少有人删掉它们。
  • 令牌存放在浏览器存储里。很多入门模板把会话或 API 令牌放在 localStorage 中,页面上的任何脚本都能读到。
  • 通常没有设置 Content-Security-Policy。没有它,任何被注入或替换的脚本都可以从任意位置加载更多代码,并把数据发往任意地方。

你没法审计供应商的构建流水线,但你可以限制一个坏脚本能造成多大破坏。

如何检查你的应用

1. 列出页面加载的每一个脚本。打开你的应用,进入 DevTools 的 Network 面板,按 JS 过滤,然后刷新页面。或者在每个重要页面的控制台里运行:

[...document.scripts].map(s => s.src || '(inline)').filter(Boolean)

把所有不属于你的域名记下来。登录后的页面也要检查,而不仅仅是营销网站。标签管理器可能会加载更多脚本,而这些脚本不会出现在你的 HTML 源码里。

2. 检查这些标签中哪些带有 integrity 属性。在控制台中运行:

[...document.querySelectorAll('script[src]')]
  .filter(s => !s.src.startsWith(location.origin))
  .map(s => ({ src: s.src, sri: !!s.integrity }))

3. 查看响应头。运行 curl -sI https://yourapp.com,找 Content-Security-Policy。如果没有,或者其中包含 script-src * 或不带 nonce 的 'unsafe-inline',那它基本起不到什么限制作用。

4. 检查会话存放在哪里。在 DevTools 的 Application 面板下查看 Local Storage 和 Session Storage。如果你在里面看到访问令牌、刷新令牌或 API 密钥,页面上的任何脚本都能读取它们,而这正是 Polymarket 恶意载荷所做的事。

如何修复

删掉不需要的脚本。这是收益最高的一步。SafeState 给运营方的建议,第一条就是维护第三方脚本清单,并把非必要的标签从账户、结账和交易页面中移除。你的登录、账单和设置页面很少需要热力图工具。

用 Subresource Integrity(SRI)固定并校验你保留的脚本。如果供应商提供带版本号的文件,就引用某个具体版本并加上哈希。一旦文件发生变化,浏览器会拒绝执行它:

<script
  src="https://cdn.example-vendor.com/sdk/4.2.1/sdk.min.js"
  integrity="sha384-REPLACE_WITH_REAL_HASH"
  crossorigin="anonymous"></script>

根据你审查过的那个确切文件,自己生成哈希:

curl -s https://cdn.example-vendor.com/sdk/4.2.1/sdk.min.js \
  | openssl dgst -sha384 -binary | openssl base64 -A

要注意其中的取舍:如果供应商在不带版本号的 URL 上原地更新脚本(监控和分析代码片段很常见这样做),SRI 就会让它失效。对于这类脚本,要么自托管一份固定版本、由你有意识地更新,要么接受你在信任供应商的线上文件,并让它远离敏感页面。

添加 Content-Security-Policy。CSP 告诉浏览器哪些来源可以提供脚本,以及页面可以把数据发往哪些来源。一个 Next.js 或 Express 应用的起点可以是这样:

Content-Security-Policy:
  default-src 'self';
  script-src 'self' https://cdn.example-vendor.com;
  connect-src 'self' https://api.yourapp.com;
  frame-ancestors 'none';
  base-uri 'self';
  object-src 'none'

对 CSP 在这里能起的作用要有清醒认识:如果供应商自己的域名在白名单里,CSP 拦不住从该域名提供的恶意文件。CSP 能做的是阻止这个脚本从新的域名拉取代码,或把窃取的数据发送到不在 connect-src 中的任意端点。先用 Content-Security-Policy-Report-Only 上线,看看哪些东西会出问题。我们关于添加 Content-Security-Policy 的指南会一步步带你完成。

Content-Security-Policy:完整配置指南

让会话令牌脱离 JavaScript 的触及范围。把会话令牌移到带有 HttpOnly、Secure 和 SameSite 标记的 Cookie 中,让页面脚本无法读取。这挡不住恶意脚本在页面打开期间以用户身份发起请求,但能防止令牌被窃取后在别处重复使用。参见 Secure、HttpOnly 和 SameSite Cookie 标记。

保持你自己的前端密钥干净。如果一个脚本能读取你的打包文件,它就能读到你打包进去的任何机密,而机器人早已在搜寻泄露的密钥和 .env 文件。看看我们关于客户端 JavaScript 中暴露的 API 密钥的指南,确认你没有在生产环境中发布 source map;如果你已经受过一次惊吓,我们关于在不停机的情况下轮换应用机密的文章会告诉你如何善后。

Secure、HttpOnly 和 SameSite:锁好你的会话 Cookie

要点清单

在关掉 DevTools 之前,请确认你已经:

  • 列出公开页面和登录后页面上的每一个第三方脚本。
  • 删除不再使用的脚本,尤其是登录、账单和账户页面上的。
  • 在供应商支持的地方固定版本并添加 integrity 哈希。
  • 无法固定版本的脚本,要么自托管,要么让它远离敏感页面。
  • 部署 Content-Security-Policy,并收紧 script-src 和 connect-src。
  • 把会话令牌放在 HttpOnly Cookie 中,而不是 localStorage。
  • 每当有人(或某个 AI 代理)添加新标签时,重新做一遍这次审计。

看看你的现状

如果你想快速建立基线,AppSafe 会检查你的安全响应头,包括是否设置了 Content-Security-Policy;它的已验证完整扫描还会检查 Subresource Integrity 和存在漏洞的 JavaScript 库等前端问题。你可以在示例报告中看看报告长什么样,或者搭配我们的 AI 构建应用上线前检查清单一起使用。

Polymarket 在暂停交易后约一小时内就查明了原因。很多小团队要过更久才会发现某个脚本被改动了。今天就为你的应用运行一次免费的 AppSafe 扫描,看看你的现状。

自动跑一遍这份清单

一次免费扫描即可检查你的应用是否存在所有这些问题,并为每个发现的问题给出一条 AI 修复提示词。

免费扫描我的应用