[要注意] DNSリバインディングを悪用するSSRF脆弱性 – フロントエンド開発者も知っておくべき危険性
はじめに:なぜフロントエンドエンジニアがSSRFを知るべきか
皆さんは「SSRF(Server-Side Request Forgery)」という言葉を聞いたことがあるでしょうか?直訳すると「サーバーサイドリクエスト偽造」となり、その名の通り、サーバーが本来意図しない外部または内部のURLへリクエストを送信させられてしまう脆弱性です。フロントエンド開発者は主にクライアントサイドのセキュリティに注力することが多いですが、Webアプリケーション全体の安全性を確保するためには、バックエンドで発生するこのような脅威も理解しておくことが不可欠です。特に、最近注目されているAIエージェントと連携するアプリケーションでは、プロンプトインジェクションを通じてSSRFが悪用される可能性も指摘されており、その危険性は決して他人事ではありません。
GHSA-mrvx-jmjw-vggcの概要:SearXNG MCPサーバーの深刻な脆弱性
今回解説する脆弱性(GHSA-mrvx-jmjw-vggc)は、オープンソースのメタ検索エンジンSearXNGの「MCP(Meta-search Client Proxy)サーバー」で発見されました。MCPサーバーの`web_url_read`ツールには、サーバー内部ネットワークへの不正アクセスを許す重大なSSRF脆弱性があり、その深刻度は「High(CVSSv3.1: 7.1)」と評価されています。ウェブサイトのURLを読み込む機能に不備があり、攻撃者が細工したURLを使うと、内部システムにある機密情報が外部に漏洩する恐れがあります。非常に危険なので、すぐに対応が推奨されています。
脆弱性の詳細とメカニズム:DNSリバインディング攻撃とは?
このSSRF脆弱性の核心は、URL検証ロジックとDNS解決のタイミングのずれにあります。SearXNG MCPサーバーの`assertUrlAllowed()`というセキュリティチェック機能は、URLのホスト名(例: `example.com`)の「文字列」だけを見て、それがプライベートIPアドレス(例: `192.168.0.1`)や内部ホスト名に該当するかを判断します。
しかし、このチェックの時点では、そのホスト名が実際にどのIPアドレスに解決されるのか(DNS解決)は行われません。DNS解決は、セキュリティチェックが通過した後に、OSレベルで行われます。この「セキュリティチェックとDNS解決のタイミングのずれ」が、DNSリバインディングと呼ばれる攻撃手法の温床となります。
攻撃者は`nip.io`のようなワイルドカードDNSサービスや、自身でコントロールするDNSサーバーを利用します。例えば、`10.0.0.1.nip.io`のようなドメインは、見た目上は外部公開されているドメインですが、DNS解決されるとプライベートIPアドレスの`10.0.0.1`を返します。攻撃者は、まずこのドメインを`assertUrlAllowed()`に渡します。`assertUrlAllowed()`はホスト名が文字列的にプライベートIPや内部ホストではないと判断し、URLを許可してしまいます。しかし、実際にURLにアクセスする段階でそのホスト名が内部IPアドレスに解決され、サーバーは結果的に社内サービスにリクエストを送ってしまうのです。
これは、セキュリティガードレールが文字列だけを見て判断し、実際にリクエストが飛ぶ直前の「実態(IPアドレス)」を見ていないために発生する致命的な欠陥と言えます。
なぜ危険なのか?:情報漏洩と内部ネットワークへの足がかり
この脆弱性が悪用されると、攻撃者は以下のような行為が可能になります。
・**内部HTTPサービスの読み取り**:クラウドのメタデータエンドポイント(AWS EC2のIMDSなど)、社内API、管理画面、HTTPインターフェースを持つデータベース(例: Elasticsearch)など、MCPサーバーからアクセス可能なあらゆる内部HTTPサービスにアクセスし、情報を読み取ることができます。
・**機密データの漏洩**:クラウドプロバイダーの認証情報、内部サービスのトークン、設定情報、従業員の個人情報などの機密データを外部に漏洩させることができます。
・**内部ネットワークの調査**:ポートスキャンや内部サービスのバージョン情報を取得することで、内部ネットワークの構造を調査し、さらなる攻撃の足がかりとすることができます。
さらに、デフォルト設定では外部からの認証なしで`web_url_read`機能が呼び出せるため、インターネット経由で誰でもこの脆弱性を悪用できてしまいます。AIエージェントと連携しているSTDIOモードの場合でも、プロンプトインジェクションなどにより、AIエージェントが攻撃者の指定するURLを読み込んでしまうことで悪用される可能性も指摘されており、AI関連のプロジェクトに携わる方は特に注意が必要です。
影響を受ける環境と確認点
この脆弱性は、`mcp-searxng`バージョン1.6.0を使用している全ての環境に影響します(ただし、`MCP_HTTP_ALLOW_PRIVATE_URLS=true`と明示的に設定している場合を除く)。もし皆さんのプロジェクトでSearXNG MCPサーバーを利用している場合は、バージョンを確認し、速やかに対応を検討してください。また、直接SearXNGを使っていなくても、同様のURL検証ロジックが他のバックエンドサービスで使われていないか、今一度確認することをお勧めします。
対策:安全なURL検証の実装
推奨される対応策は、`assertUrlAllowed()`関数内で、実際にURLへアクセスする前にホスト名をDNS解決し、その結果得られたIPアドレスがプライベートIPではないかをチェックするよう修正することです。これにより、DNSリバインディングによるバイパスを防ぐことができます。
具体的には、Node.js環境であれば、`node:dns/promises`モジュールの`lookup`関数を使用してホスト名を解決し、その結果のIPアドレスがプライベートネットワークに属していないかを検証する処理を追加する必要があります。例えば、以下のようなロジックが考えられます。
```javascript import { lookup } from 'node:dns/promises'; import { isIP } from 'node:net'; async function assertUrlAllowed(url) { const hostname = new URL(url).hostname; // DNS解決を実行し、得られたIPアドレスをチェック try { const { address } = await lookup(hostname, 4); // IPv4を取得 // プライベートIPアドレス範囲のチェック // 例: 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 127.0.0.0/8 if (isIP(address) === 4 && ( address.startsWith('10.') || address.startsWith('172.16.') || address.startsWith('172.17.') || address.startsWith('172.18.') || address.startsWith('172.19.') || address.startsWith('172.20.') || address.startsWith('172.21.') || address.startsWith('172.22.') || address.startsWith('172.23.') || address.startsWith('172.24.') || address.startsWith('172.25.') || address.startsWith('172.26.') || address.startsWith('172.27.') || address.startsWith('172.28.') || address.startsWith('172.29.') || address.startsWith('172.30.') || address.startsWith('172.31.') || address.startsWith('192.168.') || address.startsWith('127.') // ループバックアドレス )) { throw new Error('Private IP address or loopback address detected after DNS resolution.'); } } catch (error) { // DNS解決に失敗した場合も許可しない throw new Error(`DNS resolution failed for ${hostname}: ${error.message}`); } // その他のホスト名文字列ベースのチェック (既存のロジックがあれば) // ... return true; } // 呼び出し元もasync/awaitで待機する async function processUrl(inputUrl) { try { await assertUrlAllowed(inputUrl); console.log('URL is allowed:', inputUrl); // 許可されたURLへのアクセス処理 } catch (error) { console.error('URL is not allowed:', error.message); } } ```
この修正によって`assertUrlAllowed`関数は非同期(`async`)に変更されるため、呼び出し元も`await`を使って待機するように修正が必要です。フロントエンドエンジニアの皆さんが直接この修正を行うことは少ないかもしれませんが、バックエンドエンジニアやインフラ担当者との連携において、このような技術的背景を理解していることは非常に重要です。
まとめ:Webセキュリティは全体で考える
今回のSearXNG MCPサーバーのSSRF脆弱性は、Webアプリケーションにおけるセキュリティチェックのタイミングや、DNS解決という基本的なネットワーク要素の理解がいかに重要であるかを浮き彫りにしました。直接フロントエンド開発に関わる機会が少ないSSRFであっても、その攻撃メカニズムや対策を理解することは、より堅牢なWebアプリケーションを構築するために不可欠です。
皆さんのプロジェクトで、外部URLを扱う機能がある場合は、それがたとえ画像取得やプレビュー表示のためであっても、今回のようなDNSリバインディングを考慮した強固なURL検証が行われているか、ぜひ一度確認してみてください。フロントエンドとバックエンドが連携して、多層的なセキュリティ対策を講じることが、現代のWebアプリケーション開発においては必須であると言えるでしょう。