官方 MCP 参考实现里的 git_init 能在任意目录建仓,三次工具调用把 ~/.ssh/id_rsa 读进聊天记录

  • 主页https://github.com/modelcontextprotocol/servers/security/advisories/GHSA-5cgr-j3jf-jw3v
  • 从哪读起:先读 GHSA-5cgr-j3jf-jw3v 的两句话描述和那条只删不改的补丁 commit eac56e7,再读 oddguan.com 那篇博客里三段原始 JSON-RPC —— 前者告诉你厂商怎么定性,后者告诉你实际打法长什么样。
  • 成名作GHSA-5cgr-j3jf-jw3v / CVE-2025-68143:Anthropic 官方 MCP 参考实现 mcp-server-git 的 git_init 不校验 repo_path,被用来在 ~/.ssh 建 Git 仓库并通过 git_diff_staged 把私钥原文吐回工具输出,厂商的修法是把这个工具整个删掉。

把 git_init 指到 ~/.ssh,三条 tool call 读走私钥

受影响的是 mcp-server-git,Anthropic 在 modelcontextprotocol/servers 仓库里维护的官方参考实现之一 —— 就是你在 Claude Desktop 或 Cursor 里配一行 uvx mcp-server-git,助手就能替你看 diff、暂存文件、写 commit 的那个东西。受影响版本:低于 2025.9.25。NVD 给的评分是 CVSS v3.1 8.8 HIGH / v4.0 6.5 MEDIUM,CWE-22(路径穿越)。

攻击链只有三步。以下三段 JSON-RPC 原样引自 Aonan Guan 的复现博客(单一来源,NVD 一手记录里没有逐字载荷):

第一步,建仓。git_init 收到什么路径就在什么路径 git init

{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"git_init","arguments":{"repo_path":"~/.ssh"}}}

服务端做的事:在 ~/.ssh 下生成一个 .git/。到这一步还没有任何数据泄露,但 ~/.ssh 从此在服务端眼里是「一个合法的 Git 仓库」了。

第二步,暂存。既然是仓库,git_add 就没理由拒绝:

{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"git_add","arguments":{"repo_path":"~/.ssh","files":["id_rsa"]}}}

私钥被读进 Git 的 index。第三步,取货:

{"jsonrpc":"2.0","id":5,"method":"tools/call","params":{"name":"git_diff_staged","arguments":{"repo_path":"~/.ssh"}}}

git diff --cached 对一个全新仓库里的新增文件,输出就是整份文件内容 —— 每行前面加个 +。这段输出以 tool result 的形式回到模型上下文,也就进了聊天记录、进了对话日志、进了任何你接了的可观测性后端。id_rsa 明文,原样。

git 的合法能力被借去当了外泄通道

oddguan 把这一步叫 capability laundering(能力洗白):单看每个工具都在做它该做的事,git_add 只是暂存、git_diff_staged 只是给人看变更 —— 没有一个工具「读了它不该读的文件」,是它们串起来越了界。

本来该拦住的是什么?mcp-server-git 的设计前提是「只操作已经存在的仓库」,所以 git_addgit_diffgit_log 这些工具都要求 repo_path 指向一个已有的 .git/,你没法拿它们去碰一个随便的目录。这是一道隐含的边界:仓库得先存在。git_init 正好是唯一能凭空造出这个前提的工具 —— 它一建仓,~/.ssh 就跨过了那道边界,后面的工具照单全收。

第二道没拦住的:git_diff_staged 从来不是设计来读任意文件的,它是给人 review 代码变更用的。但 diff 对「新增文件」的语义就是打印全文,它区分不了这份全文是源码还是私钥。工具的正常语义本身就是外泄语义,没有额外的 bug 可修 —— 除非你不让 ~/.ssh 变成仓库。

删工具 ≠ 修住注入:出口堵了,说服没堵

厂商修在哪一层?补丁 commit eac56e7git_init 整个删掉,理由就一句:服务端只该操作已有仓库,不需要建仓能力。这是堵出口 —— 断掉「把任意目录变成合法仓库」这条路,capability laundering 的第一环没了,链子就串不起来。

入口侧修没修?没有,也修不了。这条链的触发前提是:某段文字说服了模型去依次调用 git_init(~/.ssh)git_add(id_rsa)git_diff_staged。这段文字可以躺在一个 GitHub issue 正文、一份 README、一条你让助手总结的评论里 —— 你让助手「看一下这个仓库的最新改动」,它读进来的内容里夹了指令,它分不清哪句是你的要求、哪句是它读进来的数据。删掉 git_init 让这一具体载荷失效了,但「模型会被读进来的文字说服去调工具」这件事,这个补丁一个字都没碰。换一组还在的工具、换一个还没删的 MCP server,同样的说服照样成立。CVE 记录里那句「requires user interaction」说的就是这个入口 —— 它把 prompt injection 算成了「用户交互」,而不是漏洞。

同一个参考实现里的另外两刀

68143 不是孤立的。同一个 mcp-server-git 里还有 CVE-2025-68144(git_diff / git_checkout 的参数注入,CVSS 8.1)和 CVE-2025-68145(--repository flag 缺校验的路径穿越,7.1),两者都在 2025.12.18 修复。三个链起来、再配合可写文件的 Filesystem MCP server,能到 RCE:git_init 建仓 → 用 Filesystem 写一个带 clean filter 的恶意 .git/config → 写 .gitattributes 让 filter 生效 → git_add 触发时执行 filter 里的命令。

报告人这里有出入,两条都记下:68143 的凭据外泄 PoC 由 HackerOne 上的 @0dd(即博客作者 Aonan Guan)于 2025-09-10 提出并提交了补丁 PR #2757;而把三个 CVE 合并成 RCE 链、对外披露的是 Cyata 的 Yarden Porat(TheHackerNews 报道称其披露起于 2025 年 6 月)。GHSA 页面把 reporter 记为 @yardenporat、fix contributor 记为 @0dd。

本地语料 6239 篇里有 2 篇提到 CVE-2025-68143,其中 arXiv:2608.17275When Agents Act on Web3: An Attack-Surface Survey of MCP, Skills, and Tool Calling,2026-08-18)把它当 MCP 攻击面的样本引了一次。它是官方 reference implementation 这件事本身值得记一笔 —— 连拿来给整个生态抄的样板都漏成这样,别的第三方 MCP server 的边界假设没理由更牢。

已核实来源


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