React2Shell
Опубликовано 6 декабря 2025 г.
ответственна за множество бессонных ночей на этой неделе по всей отрасли. Это теперь печально известная React2Shell, обнаруженная Лакланом Дэвидсоном, которая теперь широко распространяется.
Я хотел бы поделиться своей точкой зрения по поводу этой атаки, как она возникла, что мы делаем и какие уроки извлекли.
Эта уязвимость — настоящая головоломка. Сочетание вопиющего упущения в проверке безопасности с потрясающе умным механизмом эксплуатации. Решение напоминает задачку на сообразительность… загадку, проверяющую навыки хакера, или, как называют это люди в этом сообществе, упражнение «CTF» (захват флага).
Сначала небольшое введение в JavaScript. JS славится своей высокой динамичностью. Типы динамические, а выполнение кода — тоже динамическое. Этот хак эксплуатирует эти две уникальные свойства языка, злоупотребляя его врожденной duck typing и прототипной природой, чтобы обмануть парсер React, и способностью JavaScript выполнять произвольный динамический код во время выполнения (или eval его), делая это самым серьезным классом уязвимости. CVE-2025-55182 в результате получает 10.0/10.0.
Давайте также разберемся с React. Уязвимость лежит в протоколе React Flight, который используется для кодирования входных и выходных данных для Server Functions и Server Components (RSC) React. Почему React нуждается в этом? Лучший способ думать о RSC — как о фреймворке «BFF» (Backend for the Frontend), не похожем на GraphQL. Чтобы делать сложные UI действительно быстрыми, React стримит сложные данные, соответствующие форме дерева рендеринга, за один хоп.
Flight — это протокол, который позволяет передавать эти данные туда-сюда. Он очень мощный и полный. Представьте, если бы JSON мог представлять данные, которые еще не готовы, например, Promise, чтобы ваш UI мог рендериться как можно быстрее, пока бэкенд или база данных еще не ответила. С большой силой приходит большая ответственность, так что Лаклан учуял беду.
Чтобы объяснить эксплойт, я использовал POC от @maple3142
, который короче, чем у Лаклана. Maple был первым, кто эксплуатировал уязвимость через 30 часов после выхода патча. Тот факт, что публичный фикс был доступен в GitHub для всех, и что на его взлом ушло столько времени, — свидетельство открытия Лаклана, в которое он вложил 100 часов работы. Этот казалось бы невинный тело запроса потребовал дней неустанного исследования и проб и ошибок.
Примечание: трудно сказать, имел ли Maple «помощь» или нет. Поскольку мы сотрудничали с коллегами из отрасли, чтобы защитить интернет, произошла утечка ключевой подсказки, которая обретет смысл позже.
Мне удалось упростить POC Maple еще больше — насколько я знаю, это минимально жизнеспособный эксплойт:
{
0: {
status: "resolved_model",
reason: 0,
_response: {
_prefix: "console.log('☠️')//",
_formData: {
get: "$1:then:constructor",
},
},
then: "$1:then",
value: '{"then":"$B"}',
},
1: "$@0",
}Если это тело отправить на любой сервер, работающий с уязвимым кодом Flight, например, современные приложения Next.js, строка console.log('☠️') будет вычислена на сервере. Эта строка может делать практически что угодно. Запускать программы, извлекать секреты, делать сетевые вызовы. Это так плохо, как только может быть.
Два ключевых ингредиента эксплойта — 0 и 1 — называются «Chunks» в жаргоне Flight. Точка входа в эксплойт — ключевая возможность Flight стримить данные Promise. Синтаксис для этого — @ в 1: chunk $@0. Это говорит: 0 — это Promise.
Теперь: что за дело с этим resolved_model? Это гениальная часть эксплойта. Хотя Flight предназначен для транспортировки «объектов пользователя», Лаклан нашел способ запутать React. Он по сути выражает «внутреннее состояние» — объект, который должен быть приватным и содержать внутреннюю бухгалтерию React. Каждый раз, когда React должен представлять «в полете» объект, он использует внутреннюю структуру данных, которая отслеживает его статус. resolved_model означает, что его value готов к использованию.
Ключ к хаку — часть then. В JS, если объект «крякает как Promise», то есть имеет then, то это Promise. Поскольку мы использовали $@0, React в итоге «await» (.then()) эти данные. Чтобы злоупотребить этой машиной, мы устанавливаем then: "$1:then", который будет вызван рекурсивно. Здесь происходит вопиющее упущение Flight. Этот синтаксис : предназначен для ссылок между объектами пользователя, а не для доступа к внутренним механизмам JS-runtime.
Патч CVE вводит проверки hasOwnProperty, чтобы предотвратить этот доступ. Это основное упущение безопасности в React, фундаментальное, аналогичное выходу за границы памяти в C.
Как только мы обманываем React, заставляя разрешить этот chunk, он инициализируется с захваченным, фальшивым _response. Следующий ключ атаки — доступ к механизму оценки кода. Вы заметите, что атака использует значение, ссылающееся на $B, что означает Blob.
Почему именно Blob? В React есть строки:
const backingEntry = response._formData.get(blobKey); return backingEntry;
Когда атакующий контролирует _formData и _prefix, остаётся лишь заставить .get оценивать код, а blobKey — предоставить его.
И здесь — последний ключ. В JS существует два основных механизма выполнения кода: eval и new Function. Но прямая их ссылка отсутствует. Зато есть: get: "$1:then:constructor". Обращаясь к then, мы получаем доступ к экземпляру Function, и затем к его constructor.
Финальный эксплойт семантически:
Function("console.log('☠️')//")()Это напоминание: нельзя доверять пользовательским данным. Особенно — что они не попытаются использовать внутренние механизмы JS (then, constructor) для захвата внутренних механизмов React (_response, status, и т.д.).
Мы выпустили патчи для React и Next.js и работали с коллегами по отрасли для быстрого внедрения мер защиты.
Файрволы могут помочь, но недостаточно — эксплойт слишком изощрённый. Vercel Firewall включает защиту для всех проектов, и программа вознаграждений
открыта для укрепления этих мер.
⚠️ Не пропускай обновление React и Next.js — установи патч немедленно.
Хочу поблагодарить Лаклана Дэвидсона за исследование и ответственное раскрытие. И всех коллег в сфере безопасности: @S1r1u5_, Hacktron AI, @infosec_au, Assetnote, а также команду Vercel.