我自己查了 NVD:MCP server 相关记录已到 482 条,85 条是严重级

·阅读约12分钟·Evergreen Tools Team

关于 MCP 安全,网上的文章有一个共同模式:引用一家安全公司的报告,报告引用另一个数字,数字没有出处。我不太想这么写,所以直接查了 NVD 的 API。2026 年 10 月 26 日 20:32 UTC,用关键词「MCP server」查到 482 条 CVE 记录,其中 421 条发布于 2026 年,包含 85 条严重级、215 条高危、136 条中危、44 条低危、2 条未定级;换成更窄的词组「Model Context Protocol」,结果只有 152 条。两个数字差了三倍多,而这恰好是本文想说清的事。

暗色电路背景上的挂锁

每一条记录背后,都是有人装过的一个 server

一、我到底跑了什么

用的是 NVD 的 REST API 2.0,端点 services.nvd.nist.gov/rest/json/cves/2.0,参数 keywordSearch 加 resultsPerPage=2000。不需要 API key,但有限速:不带 key 时每滚动 30 秒最多 5 次请求。我这个查询跑了两轮,用了两个关键词,都在限速之内。关键词搜索的行为是:在 CVE 记录里做全文匹配,主要是英文描述。这意味着「命中」只代表这条记录的文字里出现了这几个词,不代表这个漏洞的主体是 MCP server。这是所有基于关键词的统计的共同问题,也是我接下来要强调的关键点——你要看原始数据,而不是别人加工过的结论。

# 1) The exact query. No key needed, but NVD rate-limits: 5 requests
#    per rolling 30 seconds without a key. Published and fetched
#    2026-10-26T20:32Z.
import json, urllib.parse, urllib.request

def nvd(keyword):
    url = "https://services.nvd.nist.gov/rest/json/cves/2.0?" + urllib.parse.urlencode({
        "keywordSearch": keyword,
        "resultsPerPage": "2000",
    })
    req = urllib.request.Request(url, headers={"User-Agent": "nvd-check/1.0"})
    with urllib.request.urlopen(req, timeout=120) as r:
        return json.loads(r.read())

for kw in ("Model Context Protocol", "MCP server"):
    d = nvd(kw)
    print(kw, "->", d["totalResults"])

二、结果,原样贴出来

关键词「Model Context Protocol」:totalResults 152,年份分布 2025 年 23 条、2026 年 129 条;严重度 CRITICAL 26、HIGH 75、MEDIUM 42、LOW 9。关键词「MCP server」:totalResults 482,2025 年 61 条、2026 年 421 条;严重度 CRITICAL 85、HIGH 215、MEDIUM 136、LOW 44、未定级 2。两个关键词的检索时间分别是 20:32:38 和 20:32:59 UTC,同一分钟内。有个细节值得单独说:未定级不是安全的意思,是没有 CVSS v3/v4 评分,而这个桶里的东西往往没人细看。2026 年占绝对多数也符合直觉——MCP 的装机量在 2025 年底之后才真正起来。

# 2) Result, exactly as returned:

# keywordSearch="Model Context Protocol"
#   totalResults 152     2025: 23   2026: 129
#   severity     CRITICAL 26  HIGH 75  MEDIUM 42  LOW 9

# keywordSearch="MCP server"
#   totalResults 482     2025: 61   2026: 421
#   severity     CRITICAL 85  HIGH 215  MEDIUM 136  LOW 44  NONE 2

# Read that again: the broader keyword finds three times as many records.
# Neither number is "the number of MCP CVEs". Both are the number of
# records whose English description happens to contain those words.

三、为什么两个数字差三倍:关键词是产物,不是事实

152 和 482 之间的差额,主要来自两类记录。一类是对关键词更敏感的描述:很多 server 的 CVE 描述里写的是产品名加「MCP server」,而不是全称「Model Context Protocol」。另一类是纯噪声:「MCP」这三个字母在别的语境里也成立(比如某些厂商的产品缩写),于是被关键词命中了。所以正确的表述不是「MCP 有 482 个漏洞」,而是「NVD 里有 482 条记录的文字包含 MCP server 这几个词」。前者是一个惊人的论断,后者是一个可复现的事实。很多人引用这类数字时会默认做前一种解读,这是我建议你自己跑一遍查询的最实际的理由。

屏幕上滚动的安全日志

关键词计数衡量的是用词,不是风险

# 3) Bucket by severity and year straight from the response.
from collections import Counter

def summarise(d):
    sev, year = Counter(), Counter()
    for v in d["vulnerabilities"]:
        c = v["cve"]
        year[c["published"][:4]] += 1
        m = c.get("metrics", {})
        level = "UNSCORED"
        for k in ("cvssMetricV40", "cvssMetricV31", "cvssMetricV30"):
            if m.get(k):
                level = m[k][0]["cvssData"]["baseSeverity"]
                break
        sev[level] += 1
    return dict(year), dict(sev)

# Note the UNSCORED bucket. A record with no CVSS v3/v4 score is not
# safe, it is unmeasured - and keyword searches surface plenty of those.

四、四条值得单独读的记录

在这批结果里,有四条特别有代表性。CVE-2025-49596:MCP Inspector 0.14.1 之前版本存在远程代码执行,严重级。教训是调试工具也会进生产——很多人把 Inspector 一直挂在开发机上。CVE-2025-34072:Anthropic 已弃用的 Slack MCP Server 存在数据外泄,路径是自动链接预览(link unfurling),严重级。教训是任何「替你访问 URL」的工具都是一条出网通道。CVE-2025-5277:aws-mcp-server 存在命令注入,构造的提示词就能触发,严重级。教训是提示词文本属于不可信输入,要按查询串对待。CVE-2025-53365:MCP Python SDK 1.10.0 之前的版本有问题,高危。教训是 SDK 也是依赖,SDK 也会收到 CVE。

# 4) What the individual records look like when they matter.
#    All of these came back in the query above.
records:
  - id: CVE-2025-49596
    what: "MCP Inspector < 0.14.1, remote code execution"
    severity: CRITICAL
    lesson: "the debugging tool you leave running is production"

  - id: CVE-2025-34072
    what: "Slack MCP Server, data exfiltration via automatic link unfurling"
    severity: CRITICAL
    lesson: "any tool that fetches a URL on your behalf is an egress path"

  - id: CVE-2025-5277
    what: "aws-mcp-server, command injection from a crafted prompt"
    severity: CRITICAL
    lesson: "prompt text is untrusted input, treat it like a query string"

  - id: CVE-2025-53365
    what: "MCP Python SDK < 1.10.0"
    severity: HIGH
    lesson: "the SDK is a dependency; the SDK gets CVEs too"

五、把数字变成一条能跑的规则

这些数据如果不能变成策略,就只是内容。可落地的有四条。第一,锁精确版本,「^1.8」这种写法在安全场景里等于不锁。第二,把查询变成一个周期性任务,每周跑一次,然后对 CVE id 做 diff——新增的才是重点,总量变化没那么重要。第三,构建失败条件要明确写:任何已安装 server 或其 SDK 出现 CVSS ≥ 9.0,或者出现任何触碰已安装 server 名称的新 CVE,就挂。第四,每季度清一遍没人再调用的 server,因为真正的风险面不是你装了哪些,而是哪些还在被调用、还在持有凭据。最后提醒一句方法的局限:我查的是 NVD 的公开数据,不是全量漏洞数据库,用词不同结果不同,所以别把任何一个数字当真理,把查询脚本本身当成可复现的工具。

# 5) Turn the data into a policy you can actually run in CI.
#    Pin, track, and fail the build on a new critical.
mcp_dependencies:
  policy:
    pin: exact            # "1.8.0", never "^1.8"
    track: nvd_keyword    # re-run the query weekly, diff the ids
    fail_build_on:
      - "CVSS >= 9.0 in any installed MCP server or its SDK"
      - "any newly published CVE touching an installed server name"
    review_quarterly:
      - "servers nobody calls any more"   # the real risk surface
      - "servers with remote endpoints"

  runtime:
    egress_allowlist: true        # unfurling is the CVE-2025-34072 lesson
    no_secrets_in_env: true       # 88% of servers want credentials
    sandbox: "separate process, read-only mounts where possible"

📌 常见问题 FAQ

这些数字是什么时候、怎么取的?

2026 年 10 月 26 日 20:32 UTC,通过 NVD REST API 2.0 查询,参数 keywordSearch,无 API key。两个关键词分别返回 totalResults 152(Model Context Protocol)和 482(MCP server)。查询脚本可以在本文的代码块里找到,可以自行复现。

482 是不是意味着有 482 个 MCP 漏洞?

不是。关键词搜索是在 CVE 记录的文本里做匹配,命中的记录文字包含「MCP server」这几个词,不代表这条漏洞的主体就是某个 MCP server。数字是关键词产物,可复现,但不能直接当作漏洞数量使用。

为什么要用两个关键词?

因为它们差异极大,一个 152 一个 482,正好说明这类统计有多脆弱。只报其中一个数字会得出完全不同的印象,所以两个都报、并解释差异来源,比取平均值或者取大的那个更诚实。

92 条严重级是不是一定要马上处理?

那是我在两个关键词下分别看到的 CRITICAL 数量(26 和 85,已去重前的两组)。要不要处理取决于你的资产清单:已安装、在运行、还持有凭据的 server 才需要排期;从没装过的东西,它的 CVE 和你的风险无关。

我该怎么用这个方法?

把查询脚本放成一个每週运行的任务,保存返回的 CVE id 列表,跟上一轮的列表做差集;然后规定构建失败条件,比如已安装 server 或其 SDK 出现新的 CVSS ≥ 9.0。重点看增量,不要盯着总量。