🕵️ React2Shell: Охота на артефакты эксплуатации

На прошлой неделе обнаружили RCE React2Shell (CVE-2025-55182), коммьюнити ИБ пестрит этой новостью. А тем временем уже начали её использовать “in the wild”. Однако, нам с вами куда интереснее какие следы оставляет эксплуатация данной уязвимости на стороне сервера.

Для начала разберемся какие есть способы эксплуатации. В ходе эксплуатации используется легитимный протокол React Server Components (RSC). Атакующий может доставить пейлоад тремя разными способами: через JSON, Multipart-форму или GET-параметры.


Вектор первый (классический описанный)

Атака через POST-запрос. Ключевой маркер — специфический заголовок Next-Action или rsc-action-id в логах реверс прокси:

grep -rE "next-action|rsc-action-id" /var/log/nginx/access.log

также можно чекнуть наличие бинарей:

grep -E 'wget|curl|bash|sh|python|nc' /var/log/nginx/access.log

Вектор второй (чаще используется для байпаса WAF)

В стандартных логах выглядит как легитимный POST. Для детекта требуется включать расширенные логи, с телом запроса client_body_in_file_only.

В случае, если расширенное логгирование включено, ищем Content-Type: multipart/form-data, в JSONе будет содержаться нагрузка:

grep -rE '\$@|"status":"resolved_model"|"\$L"|"\$B"' /var/log/

Если логгирование тела запроса включено не было

Тогда можно поискать артефакты выполнения эксплойта в директории с телом запроса. Параметр client_body_buffer_size (если оно было больше 8kb-16kb).

Важный нюанс: когда Nginx получает multipart запрос, который больше определенного размера (параметр client_body_buffer_size, обычно 8kb-16kb), он сбрасывает тело запроса на диск во временный файл по пути: /var/lib/nginx/body/ или /tmp/nginx_client_body/. Там можно найти:

ls -lart /var/lib/nginx/body/ | tail -20
strings /var/lib/nginx/body/* | grep -E "execSync|require.*child"

⏰ Критическое окно: Nginx удаляет временный файл сразу после обработки запроса (~100-200ms). Требуется быстрая реакция!

Если файл уже удален, остается только попытка восстановления:

sudo photorec /dev/sda1
# или
sudo extundelete /dev/sda1 --restore-all

После восстановления, пробуем найти в RECOVERED_FILES:

grep -rE "process.mainModule|execSync" ./RECOVERED_FILES/

Временные файлы приложения (Node.js/Next.js)

Если Next.js Server Action обрабатывает загруженные файлы (например, загрузка аватарки), он парсит multipart форму через FormData API. Когда Node.js парсит большой multipart, некоторые библиотеки (например, busboy, formidable) сбрасывают части файлов во временную директорию.

# Стандартная временная папка Node.js
ls -lart /tmp/ | grep -E "form|upload|tmp|node"

# Или в более специфичной папке
ls -lart /tmp/busboy* 2>/dev/null
ls -lart /tmp/formidable* 2>/dev/null

# Также проверяем /var/tmp
ls -lart /var/tmp/ | grep -E "form|node"

# Смотрим по времени доступа (когда были созданы)
find /tmp -type f -mmin -2 -size +100c  # Файлы измененные в последние 2 минуты

# Чекаем строки, похожие на JavaScript код (признак пейлоада)
strings /tmp/* | grep -E "execSync|require.*child_process|then.*proto"

Вектор третий (URL-encoded)

Вместо multipart/form-data атакующий может отправить данные как URL-encoded:

POST / HTTP/1.1
Host: localhost
Content-Type: application/x-www-form-urlencoded
Next-Action: [ACTION_ID]

cmd=touch%20/tmp/pwned.txt&other=data

В таком случае, в расширенных логах ищем:

grep "application/x-www-form-urlencoded" /var/log/nginx/access.log

grep -E "cmd=|bash|sh|python|nc|curl|wget" /var/log/nginx/access.log

grep -E "%20|%26|%7C|%60" /var/log/nginx/access.log | grep -E "bash|id|whoami"

Нюанс: URL-encoded часто более скрыта, чем multipart, потому что WAF’ы редко ловят этот Content-Type. Но в памяти процесса следы остаются одинаковые.


Post-Exploitation Artifacts

Даже если логирование выключено или payload доставлен скрытно, если RCE произошла, процесс Node.js ВСЕГДА спавнит bash/sh/python. Это первое, на что нужно смотреть.

Чекаем аномалии в дереве процессов

RCE = node спавнил bash. Это видно всегда, независимо от логирования:

# Визуализация дерева (лучший вариант)
ps -ef --forest | grep -A 2 "node" | grep -E "bash|sh|python"

# Или через pstree
pstree -ap | grep node -A 5

Исходящие соединения (Reverse Shell / C2)

Ищем ESTABLISHED соединения от процесса node на нестандартные порты:

# Через ss (быстро)
ss -tunap | grep node | grep ESTABLISHED

# Или через lsof
lsof -i -P -n | grep node | grep ESTABLISHED

Что ищем:

node     12345  www-data   45u  IPv4  98765  0t0  TCP 192.168.1.100:45678->attacker.com:4444 (ESTABLISHED)

🔍 Память и Swap (Last Resort)

Если сервер был выключен/crashed во время атаки, payload может остаться в swap или можно сделать дамп памяти живого процесса.

Дамп памяти процесса Node.js

# Через gdb
sudo gdb -p $(pgrep -f "node") -batch -ex "dump memory /tmp/mem.bin 0 0xffffffff" -ex quit

# Ищем паттерны
strings /tmp/mem.bin | grep -E "execSync|process.mainModule|cmd="

# Или через /proc напрямую
sudo dd if=/proc/$(pgrep -f "node")/mem of=/tmp/node_mem.bin

strings /tmp/node_mem.bin | grep "execSync"

Swap анализ

# Дампим swap
sudo dd if=/dev/sda2 of=/tmp/swap_dump.img  # если sda2 это swap

# Ищем следы
strings /tmp/swap_dump.img | grep -E "execSync|process.mainModule|resolved_model"

Для продвинутого анализа (Volatility)

# Если у вас есть дамп памяти 
volatility -f memory.dump linux_pslist  # Список процессов в момент дампа
volatility -f memory.dump linux_strings | grep "execSync"

🐳 Для Docker/Containers

Если приложение в контейнере:

# Ошибки протокола React Flight (признак попыток эксплуатации)
docker logs <container_id> 2>&1 | grep -E "ReactServerComponentsError|Digest|resolved_model"

# Процессы внутри контейнера
docker exec <container_id> ps aux | grep -E "sh|bash|nc|curl|wget"

# Анализ памяти контейнера
docker exec <container_id> strings /proc/self/mem | grep "execSync"

#DFIR #BlueTeam #React2Shell #CVE202555182 #Forensics #IncidentResponse