[控场AI]
· 11 分钟阅读· 5,524 字

Claude Code桌面版256KB预览漏洞:可窃取SSH密钥

Claude Code桌面版256KB预览漏洞:可窃取SSH密钥

Claude Code Desktop 预览面板曾存在大小分支漏洞,超过 256KB 的 HTML 可绕过 CSP 读取本地凭证,修复版本为 1.30096.1。

Claude Code Desktop 的 HTML 预览面板在处理大于 256KB 的文件时,会将其写入临时文件并以 `file://` URL 加载,这条路径既没有内容安全策略,也没有不透明源保护,而且 `file://` 源在 Chromium 中可读取其他本地文件。攻击者只需发布一个包含超大覆盖率报告的仓库,脚本在预览时即可无提示执行,读取 SSH 密钥、AWS 凭证、GitHub Token 等并外发。Anthropic 首个补丁被研究人员用 `blob:` URL 创建的 Web Worker 绕过,最终修复于 8 月 17 日随 Claude Desktop 1.30096.1 发布。该漏洞的根本模式是按输入大小分支且只有一侧携带安全检查,这种反模式在流式上传处理器等场景中同样存在。

一个仓库里最无聊的文件,往往就是一份代码覆盖率报告。你克隆项目,让 Claude 帮忙看看测试覆盖情况,那份 HTML 报告在应用的预览面板里打开。据 Noma Labs 披露,在 8 月 17 日补丁发布之前,这就足以让藏在报告里的脚本读取你的 SSH 私钥、AWS 凭证和 GitHub Token,并发送到别人的服务器上。

rss source: Pad an HTML File Past 256KB and Claude Code Desktop's Preview Could Read Your SSH Keys

Noma Security 将这项研究命名为 SneakPeek。漏洞出在 Claude Code Desktop 应用的 HTML 预览功能,而不是模型本身。Anthropic 给它打了 CVSS v4.0 8.7 分(高危),并按 Noma 的说法,"将影响等同于在主机上执行代码"。修复通过自动更新随 Claude Desktop 1.30096.1 推送给用户。

这个洞已经补上了。但它仍然值得你花五分钟:确认自己在修复版本上,并搞清楚这个 bug 的形态——因为你很可能在自己的代码里犯同样的错。

三个缺口,只有超过 256KB 才对齐

Claude Code Desktop 有一个 Browser 面板。Anthropic 的文档说它可以"打开项目中的静态 HTML 文件、PDF、图片和视频",与 Claude 为检查自身改动而启动的开发服务器并列。Noma 的报告中描述了该面板如何加载 HTML 文件,而文件大小决定了走哪条路径:

  1. 小于 256KB 的文件以 data: URL 加载,带有 script-src 'none' 的内容安全策略(CSP)和不透明源(opaque origin),因此内嵌脚本被拦截。
  2. 大于 256KB 的文件被写入临时文件,并以 file:// URL 加载。这条路径从未获得 CSP,而在 Chromium 中,file:// 源的页面可以用 fetch() 读取其他本地文件。
  3. 一个请求过滤器确实会拦截 file:// 读取,但仅当面板正在代理开发服务器时才生效。当面板渲染磁盘上的静态 HTML 文件时,它并不活跃。

CSP、不透明源和请求过滤器都存在,但大文件路径一个都没拿到。撰写该报告的研究员对 CSO Online 说:"这些防护是真实存在的,它们只是没有覆盖每一条路径。"

小文件走了有人看守的门。超过 256KB 的文件走了侧门。

内容安全策略(CSP) 是浏览器用来限制页面能加载和执行哪些资源的机制,通过 HTTP 响应头或 <meta> 标签声明。文章提到的 script-src 'none' 就是一条 CSP 指令,意思是禁止执行任何脚本,包括内联脚本和外部脚本。不透明源(opaque origin) 指的是由 data:、blob: 等 URL 加载的页面所拥有的源,它被浏览器视为一个无法被查询、也无法访问其他源资源的独立隔离域,因此从这种源发起的跨源读取会被同源策略拦截。两者叠加,就能让 data: URL 路径下的恶意脚本既无法运行,也无法窃取本地数据。

file:// 源 则完全不同。它是浏览器中代表本地文件系统的特殊源。在 Chromium 中,file:// 页面默认被赋予读取其他本地文件的权限,fetch() 和 XMLHttpRequest 都能直接访问磁盘上的任意路径。这正是漏洞链的关键:只要预览面板把大文件当作本地文件直接加载,页面就绕过了 CSP 和不透明源的隔离,获得了读取 SSH 密钥、AWS 凭证等本地文件的能力。

攻击本会如何运行

Noma 的概念验证不需要漏洞利用工具包,只需要一个看起来正常的仓库:

  • 攻击者发布一个仓库,其中的覆盖率报告被填充到超过 256KB,里面藏着一个脚本。
  • 你克隆它,让 Claude 审查某个文件,比如那份报告。
  • 预览面板渲染它,脚本运行,Noma 称不会出现权限提示。
  • 脚本读取 SSH 密钥、AWS 密钥、npm 和 GitHub Token、Kubernetes 凭证、Docker 镜像仓库认证以及 Claude 自己的 OAuth Token 等文件,并通过 navigator.sendBeacon() 把它们外发。

Noma 的 TL;DR 还补充说,载荷也可以通过 WebRTC 获取你的真实 IP 地址。Noma 的帖子或新闻稿都没有报告野外利用。

navigator.sendBeacon() 是浏览器提供的一种异步数据发送 API,专为在页面卸载或后台执行时可靠地向服务器传输小批量数据而设计。它的特点是不会阻塞页面关闭,而且不需要等待服务器响应,因此经常被用于埋点上报。在这个攻击链中,它被用来把读取到的 SSH 密钥、云凭证和 Token 悄悄发送到攻击者控制的服务器,而且因为请求发生在预览面板内部,不会触发用户可见的弹窗或权限提示。

与之配合的 WebRTC 是一种允许浏览器直接进行点对点音视频和数据传输的技术。在网页环境中,WebRTC 的 ICE 候选收集机制会暴露本机的局域网 IP 以及经由 STUN 服务器获取的公网 IP。即便用户处于 NAT 或代理之后,攻击者也能借此获得比普通 HTTP 请求更精确的真实网络位置。Noma 在 TL;DR 中提到载荷可以通过 WebRTC 获取真实 IP,正是这个原理。

补丁被绕过了一次

时间线才是最该让你对"我们已经修好了"这句话保持警惕的部分:

  • 7 月 4 日:Noma 带着概念验证报告漏洞链。
  • 7 月 9 日:Anthropic 分诊并给出 8.7 分。
  • 7 月 24 日:Anthropic 的补丁——阻止页面向 file:// 发起 fetch() 和 XMLHttpRequest——被送去复测。同一天,Noma 把 fetch() 移进一个由 blob: URL 创建的 Web Worker,那是过滤器未覆盖的上下文。
  • 8 月初:Anthropic 确认绕过,开始更深的修复。
  • 8 月 17 日:修复随 Claude Desktop 1.30096.1 及更高版本通过自动更新发布。

Noma 对 Anthropic 的处理过程评价友善:据其帖子,该公司"从一开始就认真对待 Noma 的披露"。Anthropic 没有立即回应 CSO Online 的置评请求。CSO 报道称 Anthropic 正在准备一份 GitHub 安全公告,并已申请 CVE。Noma 的帖子将两者列为待定,尽管其新闻稿称它们已于 10 月发布。截至多个来源的公开信息,都没有给出 CVE 编号,所以在寻找之前,先查 GitHub 的公告数据库。

blob: URL 是浏览器在内存中创建的一种对象 URL,用来引用二进制大对象(Blob)。它看起来像一个普通的 URL,但指向的是页面自己生成的数据,而不是网络上的某个资源。Anthropic 首个补丁限制的是页面直接向 file:// 发起 fetch() 和 XMLHttpRequest,但研究人员把同样的请求放进一个由 blob: URL 创建的 Web Worker 中执行,就绕过了这个过滤器。

Web Worker 是浏览器中独立于主线程运行的脚本环境,它的特点是拥有自己的执行上下文和全局作用域。很多基于主页面请求特征的过滤器不会覆盖 Worker 内部发起的网络请求,这就形成了一个检测盲区。攻击者只需要让主页面创建一个 blob: URL,再用它实例化 Worker,在 Worker 内部发起对本地文件的读取,就能绕过只针对主页面 fetch() 的补丁。这也说明白名单式的补丁在面对可组合的浏览器 API 时往往不够,真正的修复需要回到加载路径本身,让所有入口都经过同一套信任边界。

现在该做什么

检查 Desktop 应用版本。 漏洞和修复都在 Desktop 应用的预览渲染器中,所以真正重要的是 Claude Desktop 的版本。Anthropic 的文档说要打开 macOS 上的 Claude → Check for Updates 或 Windows 上的 Help → Check for Updates,Linux 上则通过包管理器更新。你需要 1.30096.1 或更高版本。如果团队里某台机器自夏天起就关闭了自动更新,那台就是该看的。

想想那台机器的磁盘上有什么。 如果团队里有人在 8 月 17 日之前于 Desktop 应用中预览了来路不明的仓库里的 HTML,轮换那台机器上的凭证是廉价的预防措施。没有人报告过真实攻击,所以把它当作卫生习惯。

降低被盗文件的价值。 CSO 的信息源建议在开发者机器上使用限定范围、短时效的 Token,而不是长期凭证,并在运行时层观察文件读取和外发连接,因为 Agent 日志可能看不到这类活动。过期的 Token 无论在何处泄露都毫无用处。

把陌生人的仓库交给任何 Agent 之前,先看一眼。 下面这条命令列出新克隆中超过 256KiB 的 HTML 文件,跳过 node_modules:

find . -name '*.html' -size +256k \
  -not -path '*/node_modules/*'

大 HTML 文件本身不证明任何事。覆盖率报告和打包文档自己就会变大。它只是告诉你哪些文件值得先用纯文本编辑器看一眼。

修复已经到位。检查不受信任仓库携带了什么的习惯,仍然是你自己的事。

你自己的代码里要找的 bug

剥掉 Electron 的外壳,这个 bug 就是一次按大小的分支,而只有一侧带着安全检查。以下是 Noma 所描述模式的简化示意:

// Sketch of the pattern, not Anthropic's code
const LIMIT = 256 * 1024;

function previewUrl(html: string, tmpPath: string) {
  if (Buffer.byteLength(html) < LIMIT) {
    // CSP blocks scripts, origin is opaque
    const b64 = Buffer.from(html).toString("base64");
    return `data:text/html;base64,${b64}`;
  }
  // Big file: written to disk, loaded from file://
  // No CSP here, and the origin can read local paths
  return `file://${tmpPath}`;
}

大小分支通常出于性能考虑,而面向大输入的那条分支往往不继承任何防护。流式上传大文件、绕过验证器的上传处理器也有同样的形态。Black Duck 的网络安全治理负责人对 CSO Online 说:"依赖基于大小的阈值或客户端过滤器作为安全边界,本质上是脆弱的。"

当你审查这样的分支时,问一个问题:离开这个函数的每一条路径是否都经过同样的检查?如果答案取决于输入大小、文件类型或应用处于哪种模式,那么检查应该放在分支之后,而不是某一条分支臂里。

基于大小的分支 在工程中很常见,它本身不是错误。问题在于,当分支两侧走的是不同的信任边界时,安全检查如果只贴在某一侧,另一侧就成了无人看守的通道。文章中的 previewUrl 函数就是典型:小文件走 data: URL,天然继承 CSP 和不透明源;大文件走 file://,什么防护都没有。

类似的形态还有不少。例如上传接口对小于阈值文件直接读取内容做类型校验,大文件则流式写入磁盘后直接信任;或者 API 网关对小请求体做完整鉴权,大请求体走单独的快速通道。防御的正确做法是把检查放在分支语句之后,让所有路径在离开函数前都经过同一组校验,而不是在每个分支里重复或遗漏。

边界问题的本质

SneakPeek 起初是一个设计问题:同一个面板有两条进入路径,只有一条在信任边界之内。这个案例的价值不在于某个具体的 256KB 阈值,而在于它揭示了一个普遍模式——当你用性能优化思维设计分支时,安全防护很容易只在其中一条路径上生效。

Agent 工具正在越来越多地替你打开文件、渲染内容、执行命令。每一条"让 Agent 直接看"的捷径,都是一条新的信任路径。开发者在享受便利的同时,需要重新审视这些路径是否真的都在同一套防护之下。

#Claude Code Desktop漏洞#预览面板安全#256KB绕过#SSH密钥窃取#HTML预览CSP#AI编程工具安全#Noma Labs SneakPeek
分享:

相关推荐