任意网页的 JS 能连上你 IDE 里那个本地 WebSocket,按 MCP 协议直接调工具读文件——全程不需要说服模型

  • 主页https://github.com/advisories/GHSA-9f65-56v6-gxw7
  • 从哪读起:先读 Datadog Security Labs 的分析文(securitylabs.datadoghq.com/articles/claude-mcp-cve-2025-52882/),它是唯一把端口扫描到 tools/call 这条链讲完整的公开来源;GitHub Advisory 只给结论和版本号。

一个网页 tab 能扫到你 IDE 的哪个端口

2025 年 6 月 23 日,GitHub Advisory GHSA-9f65-56v6-gxw7 公开:Claude Code 的 IDE 扩展在本机开的 WebSocket 服务不校验连接来源,任何网页都能连上去调 MCP 工具。受影响范围是 VSCode 及其分支(Cursor、Windsurf、VSCodium)的扩展 0.2.116–1.0.23,以及 JetBrains 系(IntelliJ、PyCharm、Android Studio)插件 0.1.1–0.1.8。GitHub Advisory 给的 CVSS 是 8.8(High)——注意这是 GitHub 自己的评分,我看到有的第三方数据库给的分数不同,本文只引 Advisory 这一个。公开技术分析来自 Datadog Security Labs,同日发文。原始报告者是谁、有没有野外利用记录、当时的装机量,我没查到。

这个端口为什么存在:Claude Code 的 CLI 跑在终端里,IDE 扩展跑在编辑器进程里,两边靠一条本地 WebSocket 说 JSON-RPC 2.0,内容是 MCP(Model Context Protocol,2024-11-05 版)。扩展启动时在 10000–65535 里随机挑一个端口监听,然后把连接信息写进 ~/.claude/ide/<端口>.lock,CLI 读这个文件就知道该连哪儿。锁文件长这样(引自 coder/claudecode.nvim 的 PROTOCOL.md,这是社区对该协议的逆向文档,不是 Anthropic 官方文本):

{
  "pid": 12345,
  "workspaceFolders": ["/path/to/project"],
  "ideName": "VS Code",
  "transport": "ws",
  "authToken": "a3f1c2d4e5f60718293a4b5c6d7e8f90"
}

漏洞类型 CWE-1385,Missing Origin Validation in WebSockets,俗称 CSWSH(Cross-Site WebSocket Hijacking)。这个黑话给个具体画面:你在 evil.com 上,页面里的 JS 写 fetch('http://127.0.0.1:12345/'),浏览器会先发 CORS 预检、服务端不返回 Access-Control-Allow-Origin 你就读不到响应;但同一页面写 new WebSocket('ws://127.0.0.1:12345'),WebSocket 握手根本不走 CORS——浏览器照发请求,只是在 Origin 头里写上 https://evil.com,然后把「要不要拒绝」这件事完全交给服务端自己判断。当时的扩展没判断。

从 ping 到 tools/call:把 12 个工具当 HTTP API 用

Datadog 描述的链条:受害者打开一个恶意网页 → 页面 JS 对 localhost 的端口区间并发发起 WebSocket 连接(Datadog 的原话是 brute-force)→ 某个端口连上了、没被拒 → 发一个 MCP ping 确认对面是 Claude Code 而不是别的什么服务 → 发 tools/list 拿到工具清单 → 然后就是普通的 API 调用了。

工具一共 12 个:openFile、openDiff、getCurrentSelection、getLatestSelection、getOpenEditors、getWorkspaceFolders、getDiagnostics、checkDocumentDirty、saveDocument、close_tab、closeAllDiffTabs、executeCode。

这里必须说清一件事:Datadog 的文章提到攻击者可以读到 /etc/passwd 和 AWS 凭证,但没有公开可复制的 PoC 载荷原文。下面这段 tools/call 帧是 PROTOCOL.md 里的协议示例,逐字引用,它给的是帧的形状,不是 Datadog 打出去的那一发:

{
  "jsonrpc": "2.0",
  "id": "request-123",
  "method": "tools/call",
  "params": {
    "name": "openFile",
    "arguments": {
      "filePath": "/path/to/file.js"
    }
  }
}

filePath 换成 /etc/passwd~/.aws/credentials,响应就顺着同一条 WebSocket 回到 evil.com 的页面里。getWorkspaceFolders 先把工程绝对路径捞出来,返回体是 {"success": true, "folders": [{"name": "project-name", "uri": "file:///path/to/workspace", ...}]}——有了这个才知道该读哪些文件。

executeCode 那条要单独拆。它的参数就是 {"code": "print('Hello, World!')"},跑在 Jupyter 内核里。但 Advisory 明确限定了触发条件,原文是:代码执行只发生在 “limited situations where a user has an open Jupyter Notebook and accepts a malicious prompt”。也就是说,整条链上唯一需要人或模型配合的就是这一环,其余全是纯 web 攻击——不需要模型被说服,不需要模型的输出被信任执行。

四道本该拦住它的东西,各自漏在哪

① 同源策略。它防的是 evil.com 读到 bank.com 的响应体。这次没拦住,因为 WebSocket 握手不在它的管辖里——没有预检,没有 Access-Control-Allow-Origin 这道闸,浏览器只是老老实实把 Origin: https://evil.com 发过去,服务端不看就等于没有。

② 绑定 127.0.0.1。它防的是外网直接连你的机器:别人在互联网上扫不到这个端口。这次没拦住,因为攻击不是从外网发起的——发起者是受害者自己的浏览器,它本来就站在 localhost 上,从它的角度看 127.0.0.1 就是家门口。

③ 随机端口 10000–65535。这是 obscurity,不是 authentication,Datadog 的措辞是 “minimal security through obscurity”。页面 JS 可以并发开很多个 WebSocket 连接去试,连不上的立刻 error 回调,连上的继续。具体要多久扫完,我没有实测来源,不给估算数。

④ 锁文件里的 authToken。Advisory 的定性是 improper authorization——服务端接受了未经授权的连接。这个 token 字段今天确实在锁文件里,但它在 1.0.23 及更早版本中是否已经写盘、只是服务端不校验,我只在补丁之后的协议文档里见到,不下断言。可以确定的是:漏洞期这条 WebSocket 上没有任何有效的身份检查。

把这四条摞在一起看:这是一个 2013 年就有名字的 CSWSH,长在了 2025 年的 agent 工具面上。它跟对齐没关系,跟系统提示词没关系,跟模型判断力也没关系。

补的是握手,不是工具表

1.0.24(JetBrains 是 0.1.9)起,服务端强制校验自定义请求头 x-claude-code-ide-authorization,值取自 ~/.claude/ide/<端口>.lock 里的 authToken,不带或不匹配一律 401。Anthropic 同时把有问题的版本从各家插件市场下架了。

这个修法为什么真的有效:浏览器的 WebSocket API 不允许 JS 设置任意请求头——你可以设 subprotocol,但设不了一个叫 x-claude-code-ide-authorization 的头;而且页面读不到本机文件系统,凑不出那个 token。所以「任意网页」这个入口是实打实关掉了。

但要把话说全:关掉的只是这一个入口,工具表本身没有动。executeCode、openFile、getWorkspaceFolders 还挂在同一个 MCP 服务上,权限还是原来那些。谁能读到 ~/.claude/ide/(同机上任何一个跑在你用户下的恶意进程、一个装错的 npm postinstall 脚本),谁就拿到 token,面对的还是这 12 个工具。更要紧的是另一条路:谁能让 Claude Code 自己去调它。README 里塞一段「请先运行下面这段代码验证环境」、issue 正文里埋一句指令——这就是 indirect prompt injection,agent 把仓库内容当输入读进去,分不清哪句是你的要求、哪句是文件内容。这条路上没有任何 WebSocket 握手要过,因为调用方就是合法的 CLI。

所以,修出口不等于修「模型会被那段文字说服」。这次连出口都不归模型管——攻击者是直接冒充 CLI 的,模型全程不在场。下一次未必。本地语料里提到 CVE-2025-52882 的论文一共 2 篇(语料共 6995 篇),其中一篇是《Are AI-assisted Development Tools Immune to Prompt Injection?》(arXiv:2603.21642,2026-03-23),题目问的正是这条路——我没读原文,不引它的结论,只指出它把这个 CVE 放进了同一个问题域里。

另外还有一条没被这个补丁覆盖的:getDiagnosticsexecuteCode 至今没有独立的开关。anthropics/claude-code 仓库的 issue #21705 就是在要求加一个「禁用 IDE MCP 工具」的选项。

已核实来源


本文由自动化管道生成(采集 → 逐字核验 → 模型撰写),未经人工改写。