Announcement

👇Official Account👇

Welcome to join the group & private message

Article first/tail QR code

Skip to content

Hermes Agent MCP 内存放大拒绝服务漏洞 CVE-2026-84289 深度分析

TL;DR

  • CVE-2026-84289:NousResearch Hermes Agent ≤ 0.18.2,MCP Tool 层无边界信任服务器元数据导致的内存放大 DoS。
  • 效果:一次认证对话请求,6,478 字节的工具 schema 被放大至 10,548,310 字节(约 1628 倍);单次运行接受的 MCP 描述达 10,485,760 字节。
  • 结果:进程内存与对话并发槽被无控制占用,/v1/chat/completions 返回 429 Too many concurrent runs (max 2),服务不可用,模型与工具调用成本同步膨胀。
  • 修复:0.20.5 引入 _MCP_HARD_RESULT_CAP_CHARS(200 万字符硬上限)+ _truncate_mcp_text_result(头部 40%/尾部 60% 截断)+ 50,000 字符预算溢写磁盘。

为什么这个漏洞值得单独讲

过去我们对 MCP 安全漏洞的注意力集中在三个方向:规范层(7 月 28 日无状态重构)、传输层(认证绕过、SSRF)、工具执行层(命令注入、路径穿越)。CVE-2026-84289 属于一个经常被忽略的第四方向——元数据与结果的无边界信任

MCP 服务器是运行在你机器上的不可信进程,但 Agent 框架在默认实现里却像信任"普通 JSON 接口"一样信任它返回的一切:工具列表、description、inputSchema、文本结果、structuredContent。攻击者不需要 RCE,不需要注入,只需要配置一个内容巨大的 MCP 服务器,让框架自己把自己撑死。这是经典的"输入放大"(amplification)攻击在 AI Agent 层的重现——类似 DNS 放大,但放大比更夸张。

漏洞机制拆解

问题代码路径

Hermes Agent 的 tools/mcp_tool.py 中有两条关键路径:

python
# 伪代码还原(基于公开披露的漏洞描述)
async def _discover_tools(self, session):
    # session.list_tools() 返回整个工具列表
    tools = await session.list_tools()
    # 全量直接存入,无预算、无长度检查
    self._tools = tools
    for tool in tools:
        # description 与 inputSchema 无上限透传给 LLM
        self._register_tool(tool.name, tool.description, tool.inputSchema)

async def _refresh_tools(self, session):
    # 与 _discover_tools 相同的无边界路径
    ...

三个无边界叠加:

  1. 注册环节无预算list_tools() 返回的 descriptioninputSchema 原样注册,毫不过滤长度与数量。而 MCP 规范允许服务器声明任意大的 JSON Schema。
  2. 调用结果无上限序列化:工具调用与资源读取的结果直接走 json.dumps(),没有大小上限。病理性大载荷被完整接受并分配。
  3. 元数据进入上下文:注册后的 schema 会作为系统上下文的一部分参与每一次对话的 token 计算——放大不仅是内存层面,还直接放大模型调用成本。

公开复现数据

指标基线攻击后放大比
工具 schema 大小6,478 字节10,548,310 字节~1628×
单次运行接受的 MCP 描述正常10,485,760 字节(10MB)
对话并发槽2(默认上限)被占满429

复现报告显示:攻击者只需在自己的 MCP 服务器配置中放入一个超大(例如多层嵌套、深递归的 inputSchema)或数量爆炸的工具列表,Hermes Agent 一旦连接并执行 list_tools,内存即被快速耗尽。当进程内并发运行数到达上限(默认 max 2),后续请求全部返回 429,形成持久 DoS——因为失败请求不会释放已经分配的工具元数据缓存。

攻击场景与攻击链

攻击者要让目标 Hermes Agent 加载恶意 MCP 服务器,途径很多:

┌─────────────────────────────────────────────────────────────┐
│ 1. 投递阶段(让目标信任恶意 MCP server)                      │
│    ├─ 供应链:伪装成热门 MCP server 的 npm/pip 包(参考      │
│    │   Deadbugz 23 PR 投毒事件)                             │
│    ├─ 社工:让开发者把"增强工具"加入配置文件                  │
│    └─ 配置注入:已被攻破的工具链间接写入 MCP 配置             │
├─────────────────────────────────────────────────────────────┤
│ 2. 触发阶段(一次对话即完成放大)                             │
│    ├─ 用户发送任意一条认证请求                                │
│    ├─ Agent 建立 MCP session → list_tools()                 │
│    ├─ 超大 schema 全量注册 + 进入上下文                       │
│    └─ json.dumps 无上限序列化超大结果                          │
├─────────────────────────────────────────────────────────────┤
│ 3. 后果阶段                                                  │
│    ├─ 内存耗尽 / 并发槽占满 → 429 DoS                        │
│    ├─ token 成本膨胀(超大 schema 反复计费)                  │
│    └─ 同进程内其它 Agent 任务被拖垮                           │
└─────────────────────────────────────────────────────────────┘

关键在于:放大发生在框架侧,而不是攻击者侧。攻击者只需一份很小的恶意配置,就能让受害者的 CPU、内存和 API 账单替自己干活。CVSS 只有 5.3,是因为它的直接后果是可用性损失;但对自托管 Agent 网关而言,一次成功的放大足以让整条产品线瘫痪。

修复方案解析(0.20.5)

NousResearch 在 0.20.5 中引入了三层防护,值得每个 Agent 框架借鉴:

python
# 修复要点 1:结果硬上限
_MCP_HARD_RESULT_CAP_CHARS = 2_000_000  # 200 万字符硬顶

# 修复要点 2:病理性大载荷截断 —— 头 40% + 尾 60%
def _truncate_mcp_text_result(text: str, cap_chars: int = 50000) -> str:
    if len(text) <= cap_chars:
        return text
    head_len = int(cap_chars * 0.4)
    tail_len = cap_chars - head_len
    return (
        text[:head_len]
        + f"\n...[TRUNCATED {len(text) - cap_chars} chars]...\n"
        + text[-tail_len:]
    )

# 修复要点 3:超出预算的内容溢写磁盘而非留在内存
# 结果按 50,000 字符预算处理,超限部分落到磁盘,避免一次性大分配

这套方案的设计思路很清晰:

  1. 硬顶兜底:无论服务器返回什么,客户端侧都有一道不可突破的字符上限(200 万)。框架作者不能假设上游"讲道理"。
  2. 截断保留信息:头 40%/尾 60% 的截断策略,兼顾错误信息(通常在头部)与结果核心(常在尾部)的可读性,避免截断后 LLM 完全无法利用结果。
  3. 预算外溢写磁盘:防止"截断后仍在内存里保留完整对象"的伪修复——真正的内存保护需要把大对象移出常驻内存。

这不是 Hermes 一家的问题

CVE-2026-84289 的深层价值在于它揭示了一个生态级模式:几乎所有 Agent 框架的 MCP 客户端实现都在做"无预算信任":

组件常见实现问题
list_tools() 结果工具数量、description 长度无上限
inputSchema深嵌套/超大 schema 直接进上下文
工具结果json.dumps() 无大小限制
资源/提示读取structuredContent 无预算透传
工具描述更新运行期 schema 漂移不被检测

对照 8 月披露的 MCP 服务器三类漏洞(路径穿越、明文 token 泄露、SSRF),CVE-2026-84289 把视线从"MCP 服务器是坏的"拉到"MCP 客户端框架默认不设防"——后者影响面更广,因为它是所有服务器的公共底座。

防御最佳实践

对框架作者 / Agent 平台

python
class McpResultBudget:
    """给 MCP 客户端加统一预算层"""
    MAX_TOOLS = 200              # 工具数量上限
    MAX_DESC_CHARS = 20000       # 单工具 description 上限
    MAX_SCHEMA_CHARS = 100000    # 单工具 inputSchema 上限
    MAX_RESULT_CHARS = 200000    # 单次结果上限
    MAX_JSON_DEPTH = 32          # schema 递归深度上限

    @classmethod
    def enforce_tools(cls, tools):
        if len(tools) > cls.MAX_TOOLS:
            raise McpBudgetExceeded(f"tools={len(tools)}")
        return [
            t for t in tools
            if len(t.description or "") <= cls.MAX_DESC_CHARS
            and _schema_size(t.inputSchema) <= cls.MAX_SCHEMA_CHARS
            and _schema_depth(t.inputSchema) <= cls.MAX_JSON_DEPTH
        ]

要点:

  • 在客户端做预算,不要依赖服务器自觉——MCP 服务器是不可信进程,任何 server 返回的数据都要按"外部输入"处理。
  • schema 深度也要限制,只限字节数挡不住深递归的 JSON。
  • 工具列表快照化:连接时取一次工具列表,缓存并校验,不要在每次请求时全量重取。

对 Agent 使用者 / 部署者

  1. 只接入可信 MCP 服务器:接入前审查配置、锁定版本(最好内容寻址),参考 Deadbugz 事件教训——npx 直跑等于每次执行都拉最新版。
  2. 给 Agent 进程设置资源上限:容器内存限制(--memory)、单请求超时、上下文长度预算,让放大攻击止于容器边界。
  3. 监控 schema 漂移:定期对比已注册工具列表与首次连接时的快照,alert 任何 description/schema 变化(这是 Deadbugz 类延迟投毒与放大攻击的共同信号)。
  4. 及时升级:Hermes Agent 用户升级到 ≥ 0.20.5;其它框架自查是否已有类似预算机制(很多还没有)。

结语

CVE-2026-84289 的攻击手法没有用到任何"高深"技巧——没有内存破坏、没有 RCE、没有注入。它只是把 MCP 服务器当作一个可以无限"说话"的对等方,而 Hermes Agent 选择了全盘接受。这提醒我们:在 Agent 架构里,凡是跨信任边界的数据,都必须有预算、有上限、有截断策略。今天放大的是内存,明天放大的可能就是你的 API 账单。把 MCP 客户端当作处理不可信输入的解析器来写,是每个 Agent 框架的必修课。

参考

上次更新于: