In this article
安全なMCPサーバーの構築:重大な脆弱性を防ぐ開発者向けガイド
Model Context Protocol(MCP)は、AIの機能を拡張するデファクトスタンダードとして急速に普及しています。2024年11月のAnthropicによる発表以降、LLMをデータベース、ファイルシステム、API、コマンドラインツールに接続するMCPサーバーの構築に、多くの開発者が取り組んできました。しかし、AIの自律性を高めようと急ぐあまり、セキュリティは後回しにされがちであり、予測可能なセキュリティ上の影響への認識が高まっています。
厳しい現実があります。MCPサーバーは単にコードを増やすものであり、コードが増えればバグも増え、攻撃対象領域が広がり、セキュリティ上の脆弱性も増加します。MCPが特に危険なのは、サーバーが動作する環境にあります。APIキー、認証情報、知的財産を含む開発者のマシン上で動作し、プロンプトインジェクション(間接的なプロンプトインジェクションを含む)によって操作されるAIエージェントに制御されます。また、何十年にもわたってWebアプリケーションを悩ませてきたものと同種の脆弱性を抱えたまま公開されることも少なくありません。
このガイドでは、実際のCVEやセキュリティ調査を通じて、コマンドインジェクション、パストラバーサル、SQLインジェクションなど、現在MCPサーバーに影響を及ぼしている重大なセキュリティ脆弱性を解説します。脆弱性の見分け方だけでなく、最初から脆弱性に強いMCPサーバーを構築する方法も学べます。

コマンドインジェクション:最も多く見られるMCPの脆弱性
コマンドインジェクションは、MCPサーバーで最も頻繁に見つかる脆弱性の一つです。その手口は見覚えのあるものです。MCPサーバーがnpm、git、lsofなどのシステムコマンドを起動し、適切なサニタイズをせずにユーザーが制御できる入力を組み込んでしまいます。
プロンプトインジェクションの観点から考えることもできますが、これは最終的にリモートコマンド実行につながる敵対的なプロンプトです。
脆弱性を理解する
npmパッケージの情報を検索する、一見問題のなさそうなMCPサーバーを考えてみましょう。
server.tool(
"getNpmPackageInfo",
"Get information about an npm package",
{ packageName: z.string() },
async ({ packageName }) => {
const output = execSync(`npm view ${packageName}`, {
encoding: "utf-8",
});
return {
content: [{ type: "text", text: output }],
};
}
);この脆弱性は見つけにくいものの、深刻です。packageNameパラメーターが、文字列補間を通じてシェルコマンドに直接渡されています。ユーザーがAIアシスタントに「react; touch /tmp/pwnedの最新リリース日は?」と尋ねると、コマンドは次のようになります。
npm view react; touch /tmp/pwnedセミコロンで最初のコマンドが終了し、シェルは別のコマンドとしてtouch /tmp/pwnedを実行します。プロンプトを制御する攻撃者は、開発者のマシン上で任意のコマンドを実行できるようになります。
プロンプトが現実のCVEリスクにつながる
これは仮定の話ではありません。複数のMCPサーバーでコマンドインジェクションの脆弱性が見つかっています。
GitHub Kanban MCP Server(GHSA-6jx8-rcjx-vmwf)はユーザー入力をサニタイズせず、Git操作を通じた任意のコマンド実行を許していました。
iOS Simulator MCP Server(GHSA-6f6r-m9pv-67jw)でも同様の脆弱性が見つかりました。
Create MCP Server STDIOパッケージには不適切なツール定義が含まれ、システム監視機能がインジェクション攻撃にさらされていました。

セキュリティ対策:execではなく安全なNode.js APIのexecFileを使う
Node.jsのexec関数はシェルを起動してコマンドを実行するため、;、|、$()などのシェルメタ文字が危険になります。execFile関数は、シェルによる解釈を介さずにファイルを直接実行します。
// VULNERABLE: Uses shell interpolation
execSync(`npm view ${packageName}`);
// SECURE: Arguments passed as array, no shell
execFile('npm', ['view', packageName], (error, stdout) => {
// handle result
});安全な実装例:
import { execFile } from 'child_process';
server.tool(
"getNpmPackageInfo",
{ packageName: z.string() },
async ({ packageName }) => {
return new Promise((resolve, reject) => {
execFile('npm', ['view', packageName], { encoding: 'utf-8' },
(error, stdout) => {
if (error) reject(error);
resolve({
content: [{ type: "text", text: stdout }],
});
}
);
});
}
);execFileを使うと、悪意のある入力react; touch /tmp/pwnedはnpm viewに渡される単一の引数になります。そのため、任意のコマンドを実行するのではなく、安全にエラー処理されます。
コマンドインジェクションを防ぐその他の対策
許可リストに基づいて入力を検証する:パッケージ名を受け取る場合は、npmの命名規則に照らして検証する
コマンドと引数を分離する:コマンドインジェクションを防ぐ安全なAPIを使い、
execよりexecFileを優先するシェル引数をエスケープする:シェルの実行が避けられない場合は、
shell-quoteなどのライブラリを使う言語固有のAPIを使う:
npmを起動するのではなく、npmレジストリAPIを直接使う入力長を制限する:極端に長い入力は、インジェクション攻撃の試みである可能性が高い

パストラバーサル:サンドボックスからの脱出
MCPサーバーでは、設定ファイルの読み込み、ドキュメントへのアクセス、静的リソースの提供など、ファイルシステム操作がよく公開されています。パストラバーサルの脆弱性は、攻撃者がファイルパスを操作し、意図されたディレクトリの外にあるファイルへアクセスできる場合に発生します。
MCPサーバーのコードに見られる脆弱性のパターン
MCPリソースハンドラーによくあるパターン:
server.resource(
"pipeline-workflows",
new ResourceTemplate("pipeline-workflows://{name}", { list: undefined }),
async (uri, { name }) => {
const decodedName = decodeURIComponent(name);
let filePath = path.resolve(
process.cwd(),
'.github/workflows/',
`${decodedName}.yaml`
);
const fileContents = readFileSync(filePath, 'utf-8');
return {
contents: [{ uri: uri.href, text: fileContents }]
};
}
);攻撃者がpipeline-workflows://../../../../../../etc/passwdを要求すると、サーバーはそのまま/etc/passwdを読み込んでしまいます。開発者のマシンでは、次のような、より価値の高いファイルが標的になります。
~/.ssh/id_rsa(SSH秘密鍵)~/.aws/credentials(AWSアクセスキー).envファイル(アプリケーションのシークレット)~/.npmrc(npm認証トークン)
事例:Mastra AIフレームワークでパストラバーサルの脆弱性が発見される
@mastra/mcp-docs-serverパッケージには、見つけにくいパストラバーサルの脆弱性が含まれていました。コードにはセキュリティチェックが実装されていましたが、トラバーサルを検出した後も処理が続いていました。
async function readMdxContent(docPath, queryKeywords) {
const fullPath = path.resolve(path.join(docsBaseDir, docPath));
// Security check exists...
if (!fullPath.startsWith(path.resolve(docsBaseDir))) {
logger.error(`Path traversal attempt detected`);
return { found: false };
}
// ...
}
execute: async (args) => {
const result = await readMdxContent(path, queryKeywords);
if (result.found) {
return { /* ... */ };
}
// VULNERABILITY: Executes even after traversal detection!
const directorySuggestions = await findNearestDirectory(path, availablePaths);
// ...
}findNearestDirectory関数は検証されていないパスを使って動作し、意図された範囲外のディレクトリ一覧を漏えいさせていました。
MCPサーバーのリソースで安全にパスを処理する
ファイル操作では、多層防御を実装しましょう。
import path from 'path';
import { accessSync, readFileSync, constants } from 'fs';
function secureFilePath(baseDir, userPath) {
// Normalize and resolve the full path
const normalizedBase = path.resolve(baseDir);
const requestedPath = path.resolve(baseDir, userPath);
// Verify the path stays within bounds
if (!requestedPath.startsWith(normalizedBase + path.sep)) {
throw new Error('Path traversal detected');
}
// Verify the file exists and is readable
accessSync(requestedPath, constants.R_OK);
return requestedPath;
}
server.resource(
"docs",
new ResourceTemplate("docs://{filename}", { list: undefined }),
async (uri, { filename }) => {
const baseDir = path.resolve(process.cwd(), 'docs');
// Halt execution immediately on invalid paths
const safePath = secureFilePath(baseDir, filename);
const contents = readFileSync(safePath, 'utf-8');
return {
contents: [{ uri: uri.href, text: contents }]
};
}
);パストラバーサル対策チェックリスト
必ずパスを解決する:
path.resolve()を使って相対パスを絶対パスに変換する解決後のパスを検証する:解決後のパスが、想定したベースディレクトリ配下にあることを確認する
直ちに拒否する:トラバーサルの試みを検出した後、処理を続行しない
許可リストを使う:可能な場合は、明示的に許可したファイルのみにアクセスを制限する
ユーザーが指定する拡張子を避ける:ユーザーがファイル拡張子を指定できないようにする

SQLインジェクション:「読み取り専用」という幻想
MCPサーバーがデータベース接続に対応するにつれ、SQLインジェクションの脆弱性が重大な懸念となっています。さらに問題なのは、多くのMCPデータベースサーバーに欠陥のある「読み取り専用」モードが実装され、誤った安心感を与えていることです。
安易な読み取り専用チェック
複数のMCPデータベースサーバーでは、クエリがSELECTで始まるかを確認して読み取り専用アクセスを強制しようとしています。
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const query = request.params.arguments?.query;
if (!query.trim().toLowerCase().startsWith("select")) {
throw new Error("Only SELECT queries are allowed");
}
const result = await client.query(query);
return { content: [{ type: "text", text: JSON.stringify(result) }] };
});この方法は、PostgreSQLに組み込まれた豊富な関数に対してまったく通用しません。
安全でないMCPサーバーの読み取り専用制限を回避する方法
PostgreSQLでは、SELECT文内から呼び出せる管理用関数が公開されています。
-- Denial of Service: Terminate database connections
SELECT pg_terminate_backend(pid) FROM pg_stat_activity
WHERE usename != 'admin';
-- Long-running resource exhaustion
SELECT pg_sleep(300);
-- Information disclosure: Expose all running queries
SELECT pid, usename, query FROM pg_stat_activity;
-- Configuration extraction
SELECT name, setting FROM pg_settings WHERE name LIKE '%password%';
-- Force checkpoint (performance disruption)
SELECT pg_start_backup('malicious_backup', true);
事例:SQLインジェクションを通じて悪用された実際のMCP脆弱性
Xata's MCP Serverには公式CVEが割り当てられていませんが、読み取り専用トランザクションを回避できました。
COMMIT; INSERT INTO users (name) VALUES ('attacker');読み取り専用トランザクションをCOMMITで終了させることで、攻撃者は書き込み操作を実行できました。
ExecuteAutomation Database Server(CVE-2025-59333):同様に不適切なアクセス制御により、クエリの連鎖実行が可能でした。
SELECT * FROM users; DROP TABLE users; --MCPサーバーに安全なデータベースアクセスを実装する方法
クエリの単一実行を徹底する:
// Force prepared statement (prevents multi-query)
const result = await client.query({
name: "single-query",
text: userQuery,
values: [],
});データベースレベルの権限を使う:
-- Create restricted user
CREATE USER mcp_readonly WITH PASSWORD 'secure_password';
-- Grant only SELECT on specific tables
GRANT SELECT ON public.users TO mcp_readonly;
GRANT SELECT ON public.products TO mcp_readonly;
-- Revoke dangerous function access
REVOKE EXECUTE ON ALL FUNCTIONS IN SCHEMA pg_catalog FROM mcp_readonly;クエリの許可リストを実装する:
const ALLOWED_QUERIES = {
"get_users": "SELECT id, name FROM users WHERE active = $1",
"get_products": "SELECT id, name, price FROM products WHERE category = $1"
};
server.tool("query_database", { queryName: z.string(), params: z.array(z.any()) },
async ({ queryName, params }) => {
const query = ALLOWED_QUERIES[queryName];
if (!query) throw new Error("Query not permitted");
return await client.query(query, params);
}
);危険な関数をブロックする:
const DANGEROUS_PATTERNS = [
/pg_\w+\(/i, // PostgreSQL system functions
/\bcopy\b/i, // COPY command
/\binto\s+outfile/i, // File operations
/\bload_file\b/i, // File loading
/\bexecute\b/i, // Dynamic execution
/\bsleep\b/i, // Sleep-based DoS
];
function validateQuery(query) {
for (const pattern of DANGEROUS_PATTERNS) {
if (pattern.test(query)) {
throw new Error("Query contains prohibited patterns");
}
}
}構造化されたアクセスにはMCP Resourcesを活用する:
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
const resourceUrl = new URL(request.params.uri);
const [schema, tableName] = resourceUrl.pathname.split('/').filter(Boolean);
// Validate against allowlist
if (!ALLOWED_TABLES.includes(tableName)) {
throw new Error('Table not permitted');
}
// Use parameterized query
const result = await client.query(
'SELECT * FROM information_schema.columns WHERE table_name = $1',
[tableName]
);
return { contents: [{ uri: request.params.uri, text: JSON.stringify(result) }] };
});
サードパーティ依存関係の保護
MCPサーバーはデータベースドライバー、HTTPクライアント、ユーティリティ関数などにnpmパッケージを利用します。依存関係はそれぞれ、サプライチェーンリスクになり得ます。
脆弱な依存関係:
pg、axios、lodashなどのパッケージに既知のCVEが存在する悪意のあるパッケージ:MCPサーバー開発者を狙うタイポスクワッティング攻撃
推移的な脆弱性:依存関係がさらに依存するパッケージにあるセキュリティ上の欠陥
軽減策
継続的にスキャンする:SnykなどのツールをCI/CDパイプラインに組み込み、デプロイ前に脆弱な依存関係を検出する
依存関係のバージョンを固定する:ロックファイル(
package-lock.json)を使い、整合性を検証する定期的に更新する:セキュリティアドバイザリを確認し、速やかにパッチを適用する
依存関係を最小限にする:各パッケージが攻撃対象領域となります。MCPサーバーでそのパッケージが必要かどうかを評価しましょう。
導入前に監査する:Snyk Security Databaseでパッケージの健全性スコアを確認する
推奨アクション:安全なMCPサーバーの構築

MCPサーバー開発者向け
すべての入力を検証する:LLMが生成したツール呼び出しのデータを決して信用しない
安全なAPIを使う:
execよりexecFileを、文字列補間よりパラメーター化クエリを優先する多層防御を実装する:単一のチェックではなく、複数の検証レイヤーを設ける
SASTツールを導入する:開発中の脆弱性検出にはSnyk Codeなどを使う
最小権限を適用する:データベースユーザー、ファイルシステムへのアクセス、ネットワーク権限を必要最小限にする
セキュリティモデルを文書化する:信頼境界と前提条件を明確にする
MCPサーバーの利用者向け
インストール前に監査する:ソースコード、特にツールハンドラーを確認する
動作を監視する:予期しないファイルアクセス、ネットワークリクエスト、コマンド実行がないか確認する
環境を隔離する:MCPサーバーをサンドボックス環境で実行する
最新の状態を保つ:セキュリティパッチを速やかに適用する
脆弱性を報告する:問題を発見したら、責任ある開示を実践する
組織向け
MCPポリシーを策定する:承認済みのサーバーとセキュリティ要件を定める
スキャンを導入する:デプロイ前にMCPサーバーのコードを自動スキャンする
開発者をトレーニングする:安全なMCP開発のパターンについてチームを教育する
監視・監査する:セキュリティレビューに備えてMCPサーバーのアクティビティを記録する
今後に向けて
Model Context ProtocolはAIの機能を拡張する強力なパラダイムですが、大きな力には責任が伴います。ここで取り上げたコマンドインジェクション、パストラバーサル、SQLインジェクションは、新しい脆弱性ではありません。何十年にもわたってWebアプリケーションを悩ませてきた同じ欠陥が、影響をさらに拡大させかねない新たな環境で表面化しているのです。
皮肉なことに、AIの能力を高めようと急ぐなかで、長年セキュリティ確保に苦労してきた脆弱な基盤の上に構築されたシステムへAIを接続してしまいました。脆弱なMCPサーバーにアクセスできるLLMは、知らぬ間に攻撃の経路となり、その知能が単純な文字列操作によって悪用される可能性があります。

今後は、セキュリティ上重要なあらゆるコードと同じ厳格さでMCPサーバー開発に取り組む必要があります。入力を検証する。安全なAPIを使う。多層防御を実装する。脆弱性をスキャンする。そして「読み取り専用」は危険な幻想であることを忘れないでください。
安全なMCPサーバーを構築するためのツールはそろっています。次の脆弱性がニュースになる前に、それらを活用するかどうかが問われています。
ツールポイズニングからシャドーイング、トキシックフローまで、新たなMCPサーバーの攻撃経路を理解しませんか?実際のインシデントを紐解き、実践的でフローを考慮した戦略を通じて攻撃への防御方法を学べるSecuring The MCP Servers Ecosystem eBook。
Fetch the Flag 2026で競い合おう!
スキルを試し、課題を解き、リーダーボードの頂点を目指しましょう。究極のCTFイベントに、2月12日午後12時(ET)から2月13日午後12時(ET)まで参加しましょう。