Уязвимость React RCE (CVE-2025-55182) – проблема и причины возникновения

В начале декабря 2025 года была раскрыта уязвимость в React Server Components (CVE-2025-55182, также известная как React2Shell / React4Shell), оцененная CVSS 10.0. Это крайне серьёзная проблема, позволяющая выполнить удалённый код (RCE) без предварительной аутентификации. Уже наблюдаются публичные PoC и реальные попытки сканирования и атаки.

В этой статье мы более систематически разберём «кто, почему и насколько опасно».


Быстрый обзор



  • Суть проблемы Уязвимость в логике десериализации протокола Flight, используемого React Server Components (RSC), позволяет злоумышленнику отправить модифицированный запрос, после чего сервер выполнит произвольный JS‑код.

  • Основные затронутые компоненты

  • react-server-dom-webpack / -parcel / -turbopack 19.0 / 19.1.0 / 19.1.1 / 19.2.0
  • Встроенные в них Next.js 15.x, 16.x, 14.3.0‑canary.77 и новее (при использовании App Router)

  • Только браузерные «традиционные» React SPA Приложения, которые не используют RSC/Server Actions/серверный рендеринг, не затронуты.

  • Что делать, если вы раньше использовали Next.js 1. Проверьте версии react-server-dom-* и next в проекте. 2. Обновите React до 19.0.1 / 19.1.2 / 19.2.1 или новее. 3. Обновите Next.js до объявленных патч‑версий (15.0.5, 15.1.9, 15.2.6, 15.3.6, 15.4.8, 15.5.7, 16.0.7 и т.д.) или откатитесь на стабильную версию. 4. Если сервер уже открыт в интернете, проверьте логи, правила WAF и наличие подозрительных событий.


image

Что именно было найдено

Согласно информации, опубликованной командой React, уязвимость можно резюмировать так:

  • Цель: Реализация React Server Components (react-server-dom-*).
  • Уязвимый участок: Логика декодирования (десериализации) протокола Flight, используемая при передаче данных RSC от клиента к серверу.
  • Метод атаки:
  • Злоумышленник отправляет специально сформированный Flight‑payload на HTTP‑эндпоинт RSC/Server Actions.
  • Сервер «доверяет» этим данным и, десериализуя их, позволяет внутренним модулям загружать объекты, управляемые вводом злоумышленника.
  • В результате возможна выполнение произвольного кода на сервере (RCE).

Ключевой момент: атака возможна без логина/сессии (Unauthenticated). Любой Next.js RSC сервер, открытый в интернете, становится потенциальной целью с момента его публикации.


Точные условия возникновения уязвимости



1. Условия версии React/Next.js

  • Пакеты, связанные с RSC
  • react-server-dom-webpack
  • react-server-dom-parcel
  • react-server-dom-turbopack

  • Уязвимые версии

  • 19.0, 19.1.0, 19.1.1, 19.2.0

  • Патч‑версии

  • 19.0.1, 19.1.2, 19.2.1

  • Next.js (App Router)

  • Уязвимые: 14.3.0‑canary.77 и новее, 15.x, 16.x (с базовой структурой App Router, включающей RSC)
  • Патч‑версии: 15.0.5, 15.1.9, 15.2.6, 15.3.6, 15.4.8, 15.5.7, 16.0.7 и т.д.

2. Архитектурные/рантайм‑условия

Атака возможна, если соблюдены все следующие условия:

  1. Используется RSC * Next.js App Router или другой фреймворк, поддерживающий RSC.
  2. На сервере исполняется React‑код * Node.js и т.д. запускают RSC/Server Actions.
  3. Сервер доступен по HTTP извне * Интернет или внутренний сетевой доступ, позволяющий отправлять HTTP‑запросы.

Обратные случаи почти не затрагиваются:

  • CRA/Vite‑базированные чисто клиентские React SPA.
  • Публикация только статических бандлов в S3, CDN и т.п.
  • Структуры, где React работает только в браузере, а сервер – полностью отдельный стек (например, React + Django REST, React + FastAPI).

Почему сочетание React‑DRF и React‑FastAPI оказалось относительно безопасным

В структурах, где React используется совместно с Django REST Framework или FastAPI, проблема не проявляется. Это связано с тем, что:

  1. React не исполняется на сервере * React работает только в браузере, а Django/FastAPI – чистый HTTP‑API сервер. * Сервер не понимает протокол Flight и не обрабатывает RSC.
  2. Чёткое разделение коммуникаций * Связь React ↔ сервер осуществляется через JSON/HTTP API. * Сервер не загружает модули React по require() или на основе metadata от клиента.
  3. Отсутствие RSC/Server Actions/Flight * Эти технологии не используются, поэтому уязвимый путь отсутствует.

Таким образом, уязвимость возникла только в структурах, где React и сервер тесно интегрированы (Next.js и подобные). В разделённых архитектурах (React‑SPA + REST API) проблемный код не существует.


Протокол Flight и прототип‑заполнения на стороне сервера

Технически уязвимость следует типичному шаблону: десериализация протокола Flight → прототип‑заполнение → RCE.

1. Паттерн проблемного кода

// Уязвимая версия (концептуальный код)
export function requireModule<T>(metadata: ClientReference<T>): T {
  const moduleExports = parcelRequire(metadata[ID]); // загрузка модуля
  return moduleExports[metadata[NAME]];              // прямое использование клиентского ввода
}
  • metadata[ID]: какой модуль загружать
  • metadata[NAME]: какой экспорт из модуля использовать

Проблема в том, что metadata[NAME] используется без проверки. Если злоумышленник подставит __proto__, constructor и т.п., он может изменить цепочку прототипов объекта.

2. Прототип‑заполнение → RCE

  • Где‑то в серверном коде создаётся «обычный» объект.
  • Если Object.prototype уже загрязнён, новые свойства попадают в объект.
  • Если в цепочке есть логика, запускающая spawnSync('sh') или аналог, код выполнится.
  • Таким образом, один HTTP‑запрос может привести к удалённому выполнению кода.

Что именно было исправлено

Ключевая часть патча – принудительное не‑доверие клиентских данных. Основное изменение – в функции requireModule.

// После патча (концептуальный код)
export function requireModule<T>(metadata: ClientReference<T>): T {
  const moduleExports = parcelRequire(metadata[ID]);

  // 👇 Проверяем, что NAME является собственным свойством модуля
  if (hasOwnProperty.call(moduleExports, metadata[NAME])) {
    return moduleExports[metadata[NAME]];
  }

  return undefined as any;
}

Эта строка блокирует доступ к __proto__, constructor и другим ключам, которые не являются собственными свойствами модуля, тем самым закрывая основной путь к прототип‑заполнению.

Патч также включал:

  • Усиленную проверку структуры Flight‑payload
  • Ограничение использования опасных полей
  • Дополнительные защитные механизмы в связанных кодовых путях

Как возникло структурное противоречие при расширении React на сервер

Изначально React был «только» библиотекой для браузера. С появлением RSC и Server Actions он перестал быть исключительно клиентским.

Next.js интегрировал эти возможности, создавая единую среду, где:

  • Клиентские и серверные компоненты, а также серверные действия выглядят как единое приложение.
  • Данные, отправляемые клиентом, напрямую влияют на загрузку серверных модулей и выполнение кода.

Это нарушило модель безопасности, основанную на предположении, что фронтенд не имеет доступа к серверным ресурсам.

Проблема переноса фронтенд‑предположений на сервер

В фронтенде:

  • Бандлы статичны и неизменны.
  • Загрузка модулей фиксирована на этапе сборки.
  • Пользовательский ввод редко меняет имя модуля или экспорт.

На сервере:

  • Ввод всегда ненадёжный.
  • Загрузка модулей и создание объектов напрямую связаны с ресурсами сервера.
  • Любое динамическое использование пользовательского ввода открывает путь к RCE.

Нужна была новая модель безопасности:

  • Строгая схема проверки Flight‑payload.
  • Белый список допустимых значений, контролируемых клиентом.
  • Подпись/проверка целостности запросов (HMAC и т.п.).
  • Предположение, что RSC всегда получает ненадёжный ввод.

Однако в реальной реализации традиционная модель «доверять фронтенду» была перенесена на сервер, что привело к уязвимости.


Вывод: при размывании границ фронтенда и бэкенда необходимо пересматривать модели безопасности

Уязвимость React/Next.js демонстрирует, что переход от чисто клиентской библиотеки к серверно‑клиентской среде требует пересмотра модели доверия. В классических архитектурах (React + DRF/FastAPI) такой уязвимости нет, потому что React не исполняется на сервере.

В Next.js, где протоколы сериализации/десериализации React глубоко интегрированы в серверный код, необходимо одновременно улучшать функциональность и создавать новые механизмы проверки и защиты.

В будущем появятся ещё более «гибридные» фреймворки, и этот случай служит предупреждением: удобство не должно заменять надёжную модель безопасности.