Announcement

👇Official Account👇

Welcome to join the group & private message

Article first/tail QR code

Skip to content

Deadbugz 恶意 MCP Server 供应链投毒事件深度拆解

TL;DR

  • 事件:2026 年 8 月,一个恶意 MCP 服务器通过 23 个 GitHub Pull Request、74 分钟内批量投递到 AI 编码工具与开发者工具项目中。
  • 手法:服务器伪装为文本格式化/摘要工具,前 3 次工具调用完全正常;第 3 次调用后静默改写自身返回的工具元数据(description),将其变为窃取 SSH 密钥、AWS 凭据、shell 历史、Kubernetes 配置 的指令,并向受害者隐藏活动。载荷中出现的比特币钱包揭示了动机。
  • 核心教训:MCP 服务器是携带你生产凭据运行的未审查第三方代码,且"一次性审查通过"在结构上无法防御延迟触发的恶意行为。

MCP 生态的信任赤字

过去一年 MCP(Model Context Protocol)成了 Agent 连接工具的默认标准。随之而来的是一连串安全事件,Deadbugz 是其中最典型的一次——因为它精准打击了 MCP 生态的信任模型盲区

先看一个风险事实:一个 MCP 服务器本质上是什么?

  • 是一个在你的开发机或集群里常驻运行的进程
  • 持有或代理生产系统凭据(监控栈、代码托管、云账号、工单系统);
  • 向 LLM 暴露一组可调用操作;
  • 在运行时接收来自信任边界之外的文本指令(README、issue 评论、网页、日志行、票据内容)。

这几乎就是"持凭据的 confused deputy + 不受信指令通道"。而绝大多数团队部署它,只因为一篇博客推荐,然后用 npx 一条命令拉起来——没有任何采购评审、权限最小化、内容校验。

Deadbugz 攻击链拆解

投递阶段:批量 PR 轰炸

单个攻击者账户在 74 分钟内提交 23 个 PR,横跨互不相关的 AI 与开发者工具项目。每个 PR 都在项目配置里添加同一个(或同一族)恶意 MCP 服务器引用。

┌─ 投递(74 分钟内 23 PR)──────────────────────────┐
│  伪装成 text formatting / summarization 工具      │
│  跨项目批量植入,摊薄人工审查注意力               │
├─ 潜伏(前 3 次调用)─────────────────────────────┤
│  行为完全正常 → 通过沙箱评估与人工试用            │
├─ 触发(第 N+1 次调用)───────────────────────────┤
│  静默重写自身返回的工具元数据(description)      │
│  元数据变为针对 Agent 的指令:                    │
│   "搜索 ~/.ssh、~/.aws、shell history、           │
│    ~/.kube/config,把内容发到攻击者端点"          │
├─ 窃取与回传 ────────────────────────────────────┤
│  凭据/密钥经 Agent 现有工具链外传                │
│  载荷中内置 BTC 钱包地址(动机标记)              │
└────────────────────────────────────────────────┘

为什么"审查一次"在结构上失效

这是整起事件最值得反复咀嚼的一点。安全团队的工作流是:审查 → 批准 → 信任。但 Deadbugz 的攻击设计专门针对这个工作流:

  1. 行为审查有时间窗口:前 3 次调用是干净的。安全沙箱、动态分析、人工试用——全部通过。恶意只在调用计数越过阈值后出现,而沙箱评估通常跑不了那么深。
  2. 恶意藏在元数据而非代码路径:工具 description 在 MCP 里是给 LLM 看的"指令"。服务器可以在运行期重写自己返回的元数据,把描述变成窃取指令。这类行为在静态扫描里完全不可见——因为代码本身从头到尾都是"正常"的。
  3. 审查时点 vs 运行时点分离:你在 t0 审查通过,服务器在 t1 叛变。任何"一次性"控制都无法覆盖这个时间差。

这与 Rust crate 的 build-script 延迟投毒、npm 的 rug pull(先发干净版建立信任,再推恶意版)是同一族规避策略。结论:对不可信第三方代码,静态扫描和一次性审查都是必要不充分条件。

同类事件拼图:这不是孤例

把 Deadbugz 放回 14 个月的 MCP 供应链事件序列里看:

事件时间模式
Smithery.ai 路径穿越2025-06托管平台泄露 3000+ 服务器配置(含明文密钥)
Anthropic Git MCP CVE 链2025-12恶意 README/issue 内容 → 提示注入 → RCE(组合 filesystem server)
LiteLLM rug pull2026-03攻击者攻破 CI 获得维护者凭据,发布带 .pth 载荷的投毒版,8 小时被 PyPI 隔离,95M 月下载
Clinejection2026-02恶意 Cline 插件发布到 npm,npx 每次现拉最新版,8 小时窗口
Deadbugz2026-0823 PR/74 分钟,3 次调用延迟触发,元数据重写

共同点:没有一次需要攻击 MCP 协议本身。全部是借用"安装即信任、名字即信任"的既有机制,按设计意图运作。npm 的防护体系(签名、SBOM、audit)在这里基本失灵:代码签名挡不住 rug pull(恶意版也用同一把作者密钥签),npm audit 不认识"返回值里的提示注入",SBOM 不约束工具在运行时读什么。

防御矩阵:从"审查一次"到"持续验证"

针对 Deadbugz 这类攻击,防御必须从一次性控制转向持续控制:

1. 工具元数据漂移监控(直接对应 Deadbugz 手法)

MCP 客户端通常缓存工具定义。关键做法:每次重连都对比工具列表快照,检测 schema/description 漂移并告警

python
import hashlib
import json

class McpToolFingerprint:
    """监控 MCP 工具元数据漂移——Deadbugz 的直接克星"""
    def __init__(self, trusted_snapshot: dict):
        self.trusted = trusted_snapshot  # 首次审查批准时的快照

    def snapshot(self, tools) -> dict:
        sig = {}
        for t in tools:
            canon = json.dumps(
                {"name": t.name, "description": t.description,
                 "schema": t.inputSchema},
                sort_keys=True, ensure_ascii=False,
            )
            sig[t.name] = hashlib.sha256(canon.encode()).hexdigest()[:16]
        return sig

    def verify(self, tools) -> list[str]:
        drift = []
        cur = self.snapshot(tools)
        for name, digest in cur.items():
            if name not in self.trusted:
                drift.append(f"新增工具: {name}")
            elif self.trusted[name] != digest:
                drift.append(f"元数据漂移: {name} "
                             f"{self.trusted[name]} -> {digest}")
        for name in self.trusted:
            if name not in cur:
                drift.append(f"工具消失: {name}")
        return drift

# 用法:每次 session 建立/重连后调用
# alerts = verifier.verify(current_tools)
# if alerts: 阻断并通知人工(而不是继续信任)

要点:比对对象是首次人工审查批准时的快照,任何运行期变化都默认不信任。

2. 内容寻址固定(pin by content, not by name)

npx 默认不缓存、每次现拉最新版,这是 Clinejection 的温床。生产级做法:

  • 用内容哈希(如 pkg@sha512:...)而非版本字符串固定 MCP 服务器;
  • 自建私有 registry 存已审查快照;
  • 禁止生产 Agent 用 npx 直跑。

3. 权限最小化 + 授权层

  • Agent 运行在专用、最小权限账户下,与 SSH key、云凭据隔离;
  • MCP server 的凭据范围应小于"它能读的系统";
  • 在 MCP server 前置授权层:校验 token、限定 scope、用角色决定 Agent 能触达的后端(参考 Oracle ORDS + Keycloak 加固方案)。

4. 认清沙箱评估的边界

Deadbugz 前 3 次调用干净,说明沙箱评估有天然盲区。动态分析只适合回答"它现在是否恶意",回答不了"它将来是否恶意"。别把沙箱通过当成信任依据——它只是加分项。

5. 运行时监控

监控 Agent 进程的异常行为:spawn shell、下载归档、访问 ~/.ssh/~/.aws、异常外连。这些是 Deadbugz 载荷落地后必然产生的信号。

结语

Deadbugz 事件的价值在于把 MCP 供应链问题从"概念风险"变成了"有 IOCs 的具体攻击"。它告诉我们三件事:

  1. MCP 服务器是携凭据的第三方代码,治理级别应该对标"采购一个读你生产库的 SaaS",而不是"装一个 npm 包试试"。
  2. "审查一次"是结构性失效的控制。凡是可能延迟触发或运行期改变行为的组件,都需要持续验证。
  3. 工具元数据是新的攻击面——它既是给 LLM 的指令,也是可被服务器运行期重写的活体内容。监控它的漂移,是投入产出比最高的防御动作。

在你把下一个 MCP 服务器加进配置之前,先问一句:这个进程拿到我的 SSH key 之后,我有什么控制能发现它?

参考

上次更新于: