
Предисловие
Привет! Меня зовут Андрей, я ведущий специалист кибербезопасности в Digital агентстве Фкор, а также её генеральный директор. Наша команда недавно столкнулась с серьезной проблемой. Как мы её прозвали в команде "зимняя уязвимость" реакта.
Наша команда планирует создать рубрику: "самые опасные уязвимости месяца", поэтому чтобы не пропустить анонсов рекомендуем следить за наших хабр аккаунтом
В данной статье мы расскажем вам как боролись с данной проблемой и как она зашла настолько глубоко, что мы чуть не обделались.
Как всё началось?
Сама уязвимость была обнаружена 3 декабря этого года, первая публикация о ней появилась на сайте nist.gov (Национальный институт стандартов и технологий США), а позднее на сайте Касперского. Данная проблема, сразу получила 10 баллов по шкале опасности по шкале CVSS 3.0. По-другому уязвимость назвали React4Shell. Данная уязвимость затрагивает версии 15.0.4 – 16.0.6 (Next JS) и React 19.0.0 – 19.2.0.
Давайте вернемся немного раньше, и вспомним, что весной 2025 года в Next JS была найдена уязвимость, которая позволяла обходить авторизацию через HTTP заголовок, в последствии прозванная как CVE-2025-29927. Уже тогда, у нашей команды возник вопрос, а всё ли так просто? Да, уязвимость критическая, но сколько таких уязвимостей может скрываться в React
Почему скорее всего все началось?
React Server Components (RSC) — это революционная архитектура, которая стирает грань между клиентом и сервером. Для её работы нужен был быстрый протокол обмена данными — Flight. И вот тут произошла ключевая ошибка.
Упрощённо, уязвимый код делал что-то вроде этого:
// Примерная логика уязвимой десериализации Flight-пакета
const data = deserializeFlightPayload(request.body);
// Система ДОВЕРЯЛА, что в `data` пришли только разрешённые серверные функции
// и без строгих проверок пыталась их создать/вызвать.Проблема №1: Механизм не имел строгого "белого списка" того, какие классы или функции можно инстанциировать. Атакующий мог подделать payload, указав путь к любому существующему в памяти серверному модулю (например, child_process.execSync), и система слепо выполняла его.
Проблема №2: Протокол Flight должен передавать не только данные, но ссылки на функции, промисы и React-компоненты. Это невероятно сложная задача, где традиционные методы безопасной десериализации часто ломаются.
Мы считаем, что коренная причина лежит глубже кода:
Гонка за инновациями: RSC — это «убийственная фича» для конкуренции с другими фреймворками. Давление на релиз было колоссальным.
Сложность как враг безопасности: Новая парадигма «изоморфных» компонентов создала слепую зону. Разработчики React, сфокусированные на клиенте, могли перенести ментальную модель «код на сервере = безопасный код» на серверные компоненты.
Эффект «это же не eval()»: Команда сознательно избегала опасных функций вроде
eval(), но создала уязвимость через косвенный вызов конструкторов или геттеров в десериализаторе, что оказалось не менее опасно.
Даже если вы год назад создавали React или Next проект, то ваши пакеты всё равно были уязвимы для данной проблемы
Как мы столкнулись с этой уязвимостью лицом к лицу
Вообще, все наши проекты содержат одни из самых последних версий Next.JS и React и регулярно мы проводим тестирование безопасности наших продуктов. У нас был очень мощный VPS сервер взятый в аренду у провайдера fornex.com. В нашем агентстве проводилось мероприятие, которое мы назвали неделей кейсов, суть мероприятия была проста — каждый день мы запускаем наш старый кейс, для того, чтобы пользователи смогли их потыкать и посмотреть. Это стало фатальной ошибкой нашей студии.
По началу всё было хорошо, мы устанавливали проекты, но в один момент (5 декабря, в 17:00) нагрузка процессора резко улетела в 100%, после установки проблемного проекта с версией Next.JS как раз попадающий под ряд версий с уязвимостью React4Shell. Мы по

Поставив сайт, мы начали готовить другие кейсы к установке на этот же сервер. Как вдруг, зайдя в панель управления сервером, я зашёл и увидел беспричинную нагрузку на сервер в 120%, да-да именно 120%, мне пришлось сразу же поднимать отдел кибербезопасности для выяснения обстоятельств. На удивление, то вредоносное ПО, которые было внедрено на наш сервер (а позже мы расскажем, что это было не просто вредоносное ПО, даже не майнер). Смогло обойти наши системы мониторинга, и они тупо не сработали, а в последствии сам процесс вируса убил наши системы слежки и сбора статистики о сервере.
Через top мы смогли посмотреть, какой процесс использует ресурсы нашего VPS сервера и поняли, да это был майнер.

Просто убивать процесс было тупо, мы понимали, что он вернется. Как минимум у него 100% есть автозагрузчик, нужно искать первопричину. На тот момент, мы не знали о уязвимости Next JS и React, поэтому подозрения на front-end часть ушли в конец.
Мы действовали по ранее созданному регламенту поиска и устранения проблем на нашем сервере:
Посмотреть открытые порты через ss - tulpn (если есть подозрительные, например высокие - закрыть)
Проверить crontab на автозагрузку процессов вредоносного ПО
Анализ директорий и подозрительных файлов через grep
Поиск потенциального источника, если найден - удаление родителя, исправление источника.
kill запущенного процесса, который потребляет процессы
Выгрузка логов, мониторинг систем
Сразу, что нас насторожило, это открытый порт 63464, мы его не открывали.
tcp LISTEN 0 1 0.0.0.0:63464 0.0.0.0:*
users:(("mjurd73tgfa8j6g",pid=11060,fd=0),
("mjurd73tgfa8j6g",pid=11058,fd=0),("mjurd73tgfa8j6g",pid=11055,fd=0))Мы конечно же закрыли его, НО ВДРУГ через минуту появился другой порт 64252. По итогу мы поняли, что будет проще заблокировать все порты кроме самых необходимых, экстренным решением стала блокировка портов от 9999 до 99999, и да, порт больше не
появлялся. Первый шаг был сделан, оставалось проверить cron на автозагрузку вредоносных процессов. И чёрт воз��ми, автозагрузчик был и там, но хотя это очевидно, майнеры как правило не ставятся без автозагрузки. Мы пытались отключить её, и тут мы получаем такую ошибку от Ubuntu
root@270985:~# sudo rm -f /etc/cron.d/rondo
rm: cannot remove '/etc/cron.d/rondo': Operation not permittedМы не смогли просто удалить софт из crontabа, удаление было защищено атрибутами, конечно же их можно снять, но даже после этого майнер из автозагрузчика не удалялся. Мы приняли решение остановить cron совсем, до поиска причины появления майнера. Позже оказалось, что дело было не только в crontab
"Вместо простого
cronмы обнаружили:
1. Системную службуmoneroocean_miner.service
2. Дубликаты в/dev/shm/и/tmp/с рандомными именами
*3. СкрытыйLD_PRELOAD-руткит вld.so.preload
Теперь уже мы сфокусировались на rondo и moneroocean так как именно они были первопричиной майнера с вероятностью >90 процентов. Искали мы источник проблемы через название fghgf и rondo. Спустя несколько минут, мы проверили основные директории сервера и нашли, проблема была в папке dev.
root@270985:~# sudo find / -type f -name "fghgf" 2>/dev/null | head -5
/dev/fghgf
root@270985:~#Это было очень хитро — майнер спрятался в директории /dev/, где обычно находятся устройства, а не исполняемые файлы. Мы удалили майнер, убили процесс, почистили cron. И вроде бы всё хорошо, как майнер вернулся ровно через полчаса, но уже намного сильнее.
на тот момент, наш отдел начал изучать новые уязвимости, и спустя несколько часов мы наткнулись на "React4Shell".
Майнер пропади, майнер уйди
Как мы и писали, майнер вернулся сильнее. Теперь мы не могли проверить нагрузку на сервер через top. Процесс был убит. Теперь майнер забрался глубже, у нас убивались основные и самые важные процессы для борьбы с майнером.

Теперь мы могли видеть нагрузку на процессор только из личного кабинета. Даже не смотря на закрытые порты, майнер всё равно смог проникнуть на сервер. У нас было несколько вариантов решения данной проблемы:
Сделать бекап, временно отключить сервер, чтобы разработать стратегию по борьбе с майнером
сделать бекап и Переустановить ОС.
Продолжать бороться вслепую, потому что большое количество важных процессов было убито.
Командой было принято решение, временно отключить сервер и разработать стратегию. Самым главным шагом стала как раз проверка пакетов node, именно тут мы наткнулись на то версия Next JS нашего проекта была 15.5.9. Почему мы не обновлялись сразу?
Проект, который мы устанавливали на наш сервер, был разработан несколько месяцев назад и не использовался клиентом. Сначала начали проверять back-end, было всё чисто. А только потом дело дошло до Front-end части.
Ровно в 5:41 этого дня мы наткнулись на уязвимость CVE-2025-55182 в нашем коде, проанализировав всё, обновили пакеты до последних и уже хотели запускать сервер как вдруг...

Сервер просто перестал включаться, мы ждали 20 минут пока он запустится - бесполезно, в последствии система начала уходить в GRUB сама. Благо мы сделали бекап нашего сервера, и переустановили OC на точно такую же.
Победа над майнером и урок на будущее
Как и следовало ожидать, майнер больше не появлялся, потому что мы исправили критическую уязвимость в Front-end части. Честно, у нас даже в голове не было первоначальных идей, почему сервер заражается. Полностью уязвимость мы искоренили за 10 часов.
Проанализировав поведение майнера, мы поняли, что первоисточник может быть любым, даже front-endом, хотя казалось бы, мы используем Next и React там же все безопасно, а вот и не всегда. После победы, разработали новые мониторинг системы и защиты для наших серверов, которые прозвали как secfrod. Закрыли все ненужные порты и разработали стратегию по противостоянию похожих угроз.
Мы настоятельно рекомендуем вам обновить вашу версию Next JS и React до последних, где отсутствует эта уязвимость, чтобы не повторять наших ошибок!
