flawopen.com/AI Security/wazuh-mcp-server-webshell-check-ssrf-ghsa-pw2j-pj4h-f5vg
Wazuh MCP の Webshell 検査ツールにおける SSRF — GHSA-pw2j-pj4h-f5vg
フォレンジック用ツールがアクセスする前に、ホスト名が Wazuh-MCP-Server のリテラル IP 検査を回避しました。GHSA は重大度を High、CWE を CWE-918 としています。CVE は未割り当てで、commit 2bbfe12 で修正されています。[1], [2], [3]
受付係が訪問者のカード番号を拒否リストと照合し、その後、訪問者に部屋を選ばせるとします。印字された番号だけを確認しても、その名前が立入禁止の部屋を指すことは分かりません。修正では名前の解決先を確認し、接続先をその確認済みアドレスに固定します。[1], [3]
主要な概念と専門用語
サーバーサイドリクエストフォージェリ(SSRF)- 呼び出し元が影響を与えた宛先へ、サーバーがネットワークリクエストを送る問題です。宛先検証が不十分だと、呼び出し元が直接到達できないネットワークへサーバーが接続するおそれがあります。[4], [1]
DNS 解決とリバインディング- ホスト名は1つ以上の IP アドレスに解決されます。文字列だけを検査したり、HTTP クライアントが後で再解決したりすると、実際の接続先が検査済みのアドレスと異なることがあります。[1], [3]
宛先のピン留め- 検証済み IP を送信接続に結び付け、HTTP と TLS の確認にはホスト名を維持します。これにより検証から接続までの隙間を閉じます。[3]
フレームワーク対応
- このガイドで説明する弱点の CWE 参照: CWE-918
- D3-OTF 送信トラフィックのフィルタリング — このガイドで説明する送信ネットワークの制限により、サービスまたはホストが接続できる宛先を絞れます。
ステップ・バイ・ステップの攻撃フロー
フォレンジックデータ由来の URL が検査ツールに渡る
影響を受ける blueteam_check_webshell は、webshell の疑いがあるアドレスを分析担当者が確認するための URL を受け取ります。アドバイザリが対象としているのはこのツールであり、Wazuh-MCP-Server の全機能ではありません。[1]
ソースコード比較:脆弱 vs 堅牢化
脆弱な抜粋は、commit 6bd1fa8 の WebshellCheckInput.validate_url にある関連ロジックを再現しています。import とクラスのフィールドは省略しています。公開されないリテラルIPは拒否しますが、ホスト名で発生する ValueError をドメインとして扱い、DNS解決せずに通過させます。修正済みの抜粋は、fix commit 2bbfe12 の _pinned_curl_args を再現しています。修正後のvalidatorも _host_pins を呼び出し、リクエストループは --resolve 引数をcurlへ渡し、次のリダイレクト先へ進む前に宛先を検証します。内部宛先を許可するのは明示設定されたドメインだけです。これは要点を示すソース抜粋で、URL取得処理全体の置き換えではありません。[2], [3]
ソース抜粋: mcp_server/tools/webshell_check.py — 検証 6bd1fa8;修正 2bbfe12
@field_validator("url")
@classmethod
def validate_url(cls, v: str) -> str:
v = v.strip()
if not v.startswith(("http://", "https://")):
raise ValueError(f"URL must start with http:// or https://, got: {v!r}")
# URL のホストにあるプライベート/予約済み IP アドレスを拒否。
from urllib.parse import urlparse
import ipaddress
host = urlparse(v).hostname
if host:
try:
ip = ipaddress.ip_address(host)
except ValueError:
ip = None # not an IP - domain name, allowed
if ip is not None and (ip.is_private or ip.is_loopback or ip.is_reserved or ip.is_link_local):
raise ValueError(f"URL host is a private/reserved IP: {host}. This tool only accepts public hosts.")
return v
def _pinned_curl_args(url: str) -> tuple[list[str], str | None]:
"""Build curl --resolve args for an already-validated destination."""
import ipaddress
u = urlparse(url)
host = u.hostname
if not host:
return [], "URL has no host."
try:
ipaddress.ip_address(host)
return [], None # Literal IP: no DNS lookup to rebind.
except ValueError:
pass
port = u.port or (443 if u.scheme == "https" else 80)
ips, err = _host_pins(host, _allowed_internal_domains())
if err:
return [], err
args: list[str] = []
for ip in ips:
addr = f"[{ip}]" if ":" in ip else ip
args += ["--resolve", f"{host}:{port}:{addr}"]
return args, None
エンジニアリング&システム堅牢化チェックリスト
- ✓影響を受ける
blueteam_check_webshellを含む環境を特定します。アドバイザリにはパッケージ版の範囲ではなく commit が記載されています。[1], [2] - ✓commit
2bbfe12または修正を含む後続版へ更新し、実際のデプロイ内容を確認します。[3] - ✓A/AAAA の全結果を解決し、狭い範囲の内部ドメイン例外が必要な場合を除いて非公開 IP を拒否します。[3]
- ✓検証済み IP を実際の接続に固定し、再度の DNS 解決による接続先変更を防ぎます。[3]
- ✓自動リダイレクトを無効にするか、各リダイレクト先を改めて検証して固定します。[1], [3]
- ✓ツールの呼び出し権限とサーバーの送信先ネットワークを制限し、信頼できないデータが URL 引数を制御できるか確認します。[1]
- ✓実際の内部サービスへ接続せずに、DNS の混在応答、IPv4、IPv6、内部例外、リダイレクト、アドレス固定を回帰テストします。[3]
参考資料
- Wazuh-MCP-Server セキュリティアドバイザリ GHSA-pw2j-pj4h-f5vg(CVE 未割り当て)
- commit 6bd1fa8の脆弱なソース — WebshellCheckInput.validate_url
- Wazuh-MCP-Server 修正 commit 2bbfe12
- MITRE CWE-918: Server-Side Request Forgery
- The Next Web: MCP 実装における個別の SSRF 脆弱性
- Ars Technica: Vulnerability in agents from Google and others exposes structural flaw in MCP