CVE-2025-55182 è una vulnerabilità critica che consente l'esecuzione di codice remoto prima dell'autenticazione nei React Server , con un punteggio CVSS pari a 10,0, il livello di gravità più alto possibile. Nell'ambito del programmaOPSWAT Fellowship, i nostri borsisti hanno condotto un'analisi tecnica approfondita di questa vulnerabilità, esaminandone la causa principale nel protocollo di deserializzazione React Flight, l'intera catena di sfruttamento e il suo impatto diffuso nell'ecosistema web moderno. Questo blog presenta i nostri risultati insieme a linee guida pratiche per i difensori.

React è diventata una delle librerie front-end più diffuse al mondo, alla base di una quota significativa delle moderne mobile web e mobile . I sondaggi tra gli sviluppatori di Stack Overflow classificano costantemente React tra i migliori framework web, con un'adozione che supera il 40% degli sviluppatori professionisti a livello globale. Parallelamente a questa crescita, il team di React ha introdotto React Server (RSC) come funzionalità principale di React 19: un cambiamento di paradigma che sposta la logica di rendering dal client al server, consentendo prestazioni ottimizzate e una più stretta integrazione tra il codice lato server e quello lato client.
Tuttavia, questa evoluzione architettonica ha introdotto una nuova superficie di attacco critica. Il 29 novembre 2025, il ricercatore di sicurezza Lachlan Davidson ha segnalato al programma Bug Bounty di Meta una vulnerabilità nella logica di deserializzazione lato server di React. Resa pubblica il 3 dicembre 2025 con il codice CVE-2025-55182, la falla consente l'esecuzione di codice remoto senza autenticazione tramite una singola richiesta HTTP appositamente costruita. Classificata come CWE-502 (Deserializzazione di dati non attendibili), la vulnerabilità non richiede alcuna autenticazione, nessuna interazione da parte dell'utente e nessuna configurazione speciale dell'applicazione: una distribuzione create-next-app predefinita creata per la produzione è immediatamente sfruttabile.

L'impatto è stato immediato e grave. Entro 48 ore dalla divulgazione pubblica, sono state osservate diverse campagne di sfruttamento in circolazione. Secondo la Shadowserver Foundation, sono stati identificati oltre 77.000 indirizzi IP pubblici potenzialmente vulnerabili. La telemetria di Cloudflare ha registrato oltre 582 milioni di tentativi di sfruttamento nella settimana successiva alla divulgazione, con un'intensità degli attacchi media di oltre 3.500 IP di origine unici all'ora e un picco di 16.585 IP di attacco simultanei. Wiz Research ha riferito che il 39% degli ambienti cloud conteneva istanze vulnerabili. Il 5 dicembre 2025 la CISA ha aggiunto la vulnerabilità al proprio catalogo delle vulnerabilità note sfruttate (KEV).
Gli autori delle minacce hanno agito con notevole rapidità e versatilità. Trend Micro ha documentato diverse campagne – tra cui quelle delle botnet “emerald” e “nuts” – che hanno utilizzato beacon Cobalt Strike, implant Sliver, l’agente di monitoraggio Nezha, tunnel Fast Reverse Proxy (FRP) e un nuovo payload denominato “Secret-Hunter”, che sfrutta strumenti open source per la raccolta di credenziali quali TruffleHog e Gitleaks. Threat Intelligence Google Threat Intelligence ha identificato distinti cluster di minacce legati alla Cina (UNC6600, UNC6586, UNC6588, UNC6603) che utilizzavano strumenti specializzati - tra cui il tunneler MINOCAT, il downloader SNOWLIGHT, la backdoor COMPOOD e la backdoor HISONIC - insieme ad attori legati all'Iran e a gruppi con motivazioni finanziarie che conducevano campagne di mining di criptovalute. AWS ha documentato che gruppi legati alla Cina stavano sperimentando codice di exploit già il 4 dicembre, prima che il codice completo di proof-of-concept fosse disponibile al pubblico.
Informazioni Server di React
React è una libreria JavaScript per la creazione di interfacce utente, gestita da Meta e da un'ampia comunità open source. Server React Server (RSC), introdotti con React 19, rappresentano un cambiamento fondamentale nel modo in cui le applicazioni React gestiscono il rendering. A differenza dei tradizionali componenti client che vengono eseguiti interamente nel browser, i componenti server vengono eseguiti sul server, producendo una rappresentazione serializzata dell'interfaccia utente che viene trasmessa in streaming al client. Questo design riduce la quantità di JavaScript inviata al browser, migliora le metriche relative al tempo di interattività e consente l'accesso diretto alle risorse lato server, come database e file system.

RSC si avvale di un protocollo di serializzazione personalizzato denominato “Flight” per codificare e trasmettere i dati tra client e server. Quando un client richiama una Server (precedentemente nota come Server ), il browser raggruppa gli argomenti della funzione in una richiesta HTTP strutturata utilizzando il formato Flight. Il server deserializza questo payload, esegue la funzione richiesta e invia il risultato in streaming al client. Questo stretto accoppiamento tra client e server (sebbene elegante dal punto di vista architettonico) implica che qualsiasi difetto nella logica di deserializzazione possa avere conseguenze immediate e catastrofiche, come dimostra CVE-2025-55182.
La vulnerabilità non riguarda solo React, ma l'intero ecosistema di framework basati su di esso. Next.js (che ha ricevuto un avviso separato, CVE-2025-66478, successivamente respinto in quanto duplicato), React Router, Waku, il plugin RSC di Parcel, il plugin RSC di Vite e RedwoodSDK sono tutti interessati. Anche le applicazioni che non definiscono esplicitamente Server potrebbero essere vulnerabili se il supporto RSC è abilitato nel framework.
Contesto tecnico
Prima di analizzare la vulnerabilità, è opportuno sottolineare tre concetti fondamentali alla base della catena di exploit: il comportamento della funzione `await` in JavaScript con oggetti `thenable`, l'attraversamento della catena dei prototipi e il modello di dati basato su chunk del protocollo React Flight.
Il comando `await` e gli oggetti `Thenable` in JavaScript
L'operatore `await` sospende l'esecuzione di una funzione asincrona fino a quando l'espressione attesa non viene risolta. Quando `await` incontra una Promise nativa, attende la risoluzione e restituisce il valore ottenuto. Tuttavia, `await` non richiede necessariamente una Promise nativa: qualsiasi oggetto dotato del metodo `.then()`, noto come "thenable", viene trattato come un costrutto simile a una Promise.
Quando `await` incontra un oggetto `thenable`, richiama il metodo `.then()` dell'oggetto, passando le callback `resolve` e `reject` fornite dal sistema. Il valore passato a `resolve` diventa il risultato dell'espressione `await`. Fondamentalmente, se il valore risolto è esso stesso un thenable, il metodo .then() di quell'oggetto annidato viene chiamato ricorsivamente fino a quando non si raggiunge un valore primitivo o una Promise risolta. Questo comportamento di risoluzione ricorsiva è centrale per lo sfruttamento della vulnerabilità CVE-2025-55182.
Percorso della catena del prototipo
Ogni oggetto JavaScript mantiene un collegamento interno al proprio prototipo, accessibile tramite la proprietà __proto__. Quando si accede a una proprietà di un oggetto, il motore JavaScript verifica innanzitutto le proprietà dell'oggetto stesso. Se la proprietà non viene trovata, il motore percorre la catena dei prototipi — risalendo lungo ogni collegamento __proto__ — fino a quando la proprietà non viene individuata o la catena termina con il valore undefined.
Un malintenzionato può sfruttare questo meccanismo di ereditarietà per accedere a proprietà che esulano dall'ambito previsto di un oggetto. Includendo __proto__ nei percorsi di accesso alle proprietà, un malintenzionato può raggiungere metodi e costruttori interni che l'applicazione non avrebbe mai dovuto rendere visibili. In JavaScript, l'espressione obj.__proto__.constructor.constructor restituisce il costruttore globale Function, che è in grado di creare ed eseguire funzioni arbitrarie a partire da una stringa immessa.
Il protocollo React Flight e il modello di dati basato su chunk
When a client invokes a Server Function, the browser sends an HTTP POST request with a multipart/form-data body. Each form field contains a numbered “chunk” of serialized data. The Flight protocol uses special string prefixes to encode data types: $<id> references the resolved value of another chunk, $@<id> references the raw chunk object itself, $W<id> represents a Set, $K<id> represents FormData, and $B<id> triggers the blob handler.
Si consideri una Server definita come segue:

La richiesta HTTP corrispondente contiene diversi campi del modulo, ciascuno composto da una chiave e un valore: il campo 0 contiene l'array degli argomenti con riferimenti quali "$W1" e "$K2", mentre i campi 1 e 2_* contengono i dati a cui tali riferimenti rimandano. Il server elabora ciascun campo man mano che arriva, memorizzando i risultati intermedi in oggetti denominati "chunk".

Un chunk è un oggetto thenable con quattro proprietà chiave: status (lo stato di risoluzione), value (i dati memorizzati), reason (informazioni sull'errore) e _response (un riferimento all'oggetto risposta padre). Quando il server incontra un chunk in attesa, viene richiamato il metodo .then() del chunk. Se lo stato del chunk è INITIALIZED, la callback di risoluzione riceve chunk.value. Se lo stato è PENDING, BLOCKED o CYCLIC, le callback vengono messe in coda per essere eseguite in un secondo momento.

Il chunk 0 rappresenta in genere l'array di argomenti della Server richiamata. Una volta ricevuti tutti i campi del modulo e risolti tutti i riferimenti interni, chunk_0.value contiene l'array di argomenti completamente assemblato, che viene quindi passato alla funzione di destinazione.
Gestione delle richieste end-to-end (da Next.js alla deserializzazione di React Flight)
Di seguito viene illustrato il modo in cui Next.js elabora una richiesta in entrata Server in condizioni normali, a partire dal livello HTTP fino al motore di deserializzazione React Flight.

Funzione handleAction() - Next.js
Quando viene richiamata una Server , la richiesta viene gestita dalla funzione `handleAction`. Questa funzione convalida i metadati, verifica le intestazioni e i token CSRF e conferma che la richiesta sia un'azione di recupero valida. Viene quindi creato uno stream denominato busboyStream per analizzare il corpo del modulo multipart. La funzione decodeReplyFromBusboy associa gli emettitori di eventi a questo stream, attivando le funzioni di gestione della deserializzazione ServerReact Serverman mano che vengono ricevuti i dati grezzi. Il valore restituito da decodeReplyFromBusboy è chunk_0; l'operatore await lo risolve e passa il valore assemblato alla Server richiamata.

La funzione getChunk restituisce il chunk corrispondente a un determinato ID. Se tale chunk non esiste ancora, crea un ResolvedModelChunk (se i dati sono già presenti in response._formData) oppure un PendingChunk (se non sono ancora arrivati dati per quell'ID).

Quando decodeReplyFromBusboy restituisce chunk_0, il chunk rimane nello stato PENDING. L'operatore await chiama chunk_0.then() e memorizza temporaneamente le callback di risoluzione e di rifiuto rispettivamente in chunk_0.value e chunk_0.reason. Queste callback vengono riattivate dalla funzione wakeChunk una volta completata la risoluzione dei riferimenti.

Processo di deserializzazione - React Server
Quando busboyStream riceve un campo di dati grezzi completo, attiva l'emittitore di eventi "field", richiama la funzione resolveField e avvia la deserializzazione, convertendo i dati grezzi del modulo in oggetti JavaScript completamente costruiti. Le seguenti funzioni regolano questo processo.
resolveField(risposta, chiave, valore)

La chiave e il valore vengono aggiunti a `response._formData`. La funzione recupera quindi il chunk corrispondente all'ID che coincide con la chiave. Se quel chunk esiste già, viene chiamata la funzione `resolveModelChunk` per ricostruirlo. Questa risoluzione differita è necessaria perché un valore potrebbe contenere riferimenti a campi i cui dati grezzi non sono ancora arrivati; in tal caso, React Server un `PendingChunk` con callback personalizzati di risoluzione e rifiuto per gestire tali riferimenti in un secondo momento.
resolveModelChunk(chunk, valore, id)

resolveModelChunk crea un oggetto ResolvedModelChunk con lo stato RESOLVED_MODEL e vi inserisce i dati grezzi. Successivamente ricostruisce il chunk tramite initializeModelChunk e chiama wakeChunk per attivare eventuali callback di risoluzione e rifiuto in coda, completando così la risoluzione dell'oggetto o del riferimento.
initializeModelChunk(chunk)

initializeModelChunk imposta lo stato del chunk su CYCLIC — indicando che è in corso la risoluzione dei riferimenti — e avvia la deserializzazione. Crea un oggetto JavaScript grezzo a partire da chunk.value utilizzando JSON.parse, quindi passa questo oggetto alla funzione reviveModel.
reviveModel(risposta, oggettoGenitore, chiaveGenitore, valore, riferimento)

reviveModel elabora in modo ricorsivo ogni componente all'interno dell'oggetto analizzato. Quando incontra un valore stringa, chiama la funzione parseModelString per gestirlo.
parseModelString(risposta, oggetto, chiave, valore, riferimento)

La funzione `parseModelString` gestisce i diversi tipi di codifica in base al prefisso della stringa. Per i riferimenti che iniziano con $, viene chiamata la funzione `getOutlinedModel` per risolvere il riferimento tra blocchi.
getOutlinedModel(risposta, riferimento, oggettoGenitore, chiave, mappa)

getOutlinedModel suddivide il riferimento in base al delimitatore «:» per formare un percorso di accesso alla proprietà, quindi percorre tale percorso sull'oggetto chunk di destinazione per restituire il valore a cui si fa riferimento. Come illustrato in dettaglio nell'Analisi della vulnerabilità riportata di seguito, l'assenza di una verifica su questi nomi di proprietà costituisce il punto esatto in cui si manifesta la vulnerabilità.
Analisi delle vulnerabilità
Causa principale
CVE-2025-55182 originates from insufficient input validation in the getOutlinedModel() function within React’s server-side Flight reply handler (ReactFlightReplyServer.js). When a chunk reference includes a property path - such as $<id>:<prop1>:<prop2> - the function resolves it by traversing the specified properties on the target chunk object, computing the result as chunk[prop1][prop2].

Il difetto critico è che i nomi di queste proprietà non vengono mai verificati. Il server non verifica se le proprietà richieste siano proprietà proprie dell'oggetto o proprietà del prototipo ereditate. Un aggressore può quindi includere __proto__ nel percorso della proprietà per attraversare la catena del prototipo e raggiungere metodi interni che non dovrebbero mai essere accessibili da input controllati dall'utente. Ad esempio, il riferimento $1:__proto__:then viene risolto in Chunk.prototype.then, una funzione che l'aggressore può quindi invocare con argomenti controllati.

Codice vulnerabile
La catena di exploit sfrutta due distinti percorsi di codice nella logica di deserializzazione Flight di React.
Il primo è Chunk.prototype.then, che regola il comportamento dei chunk come thenable. Quando si applica l'operatore await a un chunk nello stato INITIALIZED, viene chiamato resolve(chunk.value). Se chunk.value è a sua volta un thenable (un oggetto dotato del metodo .then() ), l'operatore await richiama ricorsivamente chunk.value.then(). Questa risoluzione ricorsiva è il meccanismo attraverso il quale un malintenzionato reindirizza l'esecuzione verso una funzione arbitraria.
Il secondo è il gestore del prefisso $B (blob) all'interno della funzione parseModelString():

Nel caso $B, la funzione chiama response._formData.get(response._prefix + id). Sia _formData.get che _prefix sono proprietà dell'oggetto _response memorizzato all'interno del chunk. Controllando queste proprietà tramite la traversata della catena dei prototipi, un malintenzionato può reindirizzare questa chiamata per invocare il costruttore globale Function con codice arbitrario come argomento.
Sfruttamento
Through prototype chain traversal, an attacker reaches the global Function constructor via the path <any_object>.constructor.constructor. Because Chunk.prototype.then is a function, the path $1:constructor:constructor resolves to the global Function constructor, which accepts a string and returns a callable function containing that code.

Per dimostrare il potenziale impatto nella realtà, i nostri ricercatori hanno realizzato un payload di prova in linea con l'analisi documentata in modo indipendente da diversi team di ricerca sulla sicurezza. L'exploit opera in due fasi:
Fase 1 - Costruire il blocco fittizio:
The object delivered in field 0 acts as a fake chunk. Its then property is set to Chunk.prototype.then via the reference path $1:__proto__:then, allowing the Flight deserialization engine to invoke prototype-level behavior on this attacker-constructed object. The _response._formData.get property is pointed at the global Function constructor via $1:constructor:constructor, and _response._prefix is set to the malicious JavaScript code. The value field contains the string {"then": "$B0"}, instructing the blob handler to invoke itself on the same chunk when resolved. The status field is set to resolved_model so that initializeModelChunk is triggered when .then() is called, causing value to be parsed and the blob handler to fire.
Poiché a questo punto il campo 1 non è ancora stato ricevuto, React Server crea Server delle callback di risoluzione e di rifiuto per gestire il riferimento in sospeso.
Fase 2 - Risoluzione del trigger:
Una volta consegnato il campo 1 — contenente "$@0", un riferimento grezzo al chunk 0 —, il chunk in sospeso viene risolto e punta direttamente al chunk fittizio. Ciò attiva wakeChunk, che elabora i callback in coda e avvia la traversata della catena dei prototipi durante la risoluzione del riferimento. Una volta che il chunk falso è completamente risolto, wakeChunk viene chiamato di nuovo. Poiché la callback di risoluzione per il chunk falso è la funzione di risoluzione implicita di Node.js, essa invoca il metodo .then() del chunk e ne risolve il valore, costruendo ed eseguendo infine il codice dannoso iniettato tramite il costruttore Function.
Per eseguire l'exploit completo è sufficiente una sola richiesta HTTP:

Replacing {{COMMAND}} with any JavaScript code executes it on the server. The reason: -1 field prevents a toString() error during processing. The Next-Action header may contain any arbitrary value - even x - because the vulnerable deserialization occurs before the server validates the requested Server Function. This is what makes the vulnerability pre-authentication: the payload is processed during the deserialization phase, before any application-level authentication or authorization logic is reached.
In caso di successo dell'attacco, l'autore ottiene il pieno controllo del contesto di esecuzione di Node.js sul server, compreso l'accesso a `child_process` per l'esecuzione di comandi da shell, alle variabili d'ambiente contenenti le credenziali del database e API , al filesystem locale e agli endpoint dei metadati cloud che consentono il movimento laterale.
Prova di concetto
I nostri colleghi hanno riprodotto la vulnerabilità in un ambiente di laboratorio controllato utilizzando un'applicazione Next.js standard generata con create-next-app e configurata per la produzione, senza apportare alcuna modifica alla configurazione predefinita. La riproduzione ha confermato che il payload dell'exploit a richiesta singola descritto sopra consente di eseguire codice in remoto in modo affidabile.


La dimostrazione controllata ha dimostrato che un malintenzionato con accesso di rete a un server Next.js vulnerabile può eseguire codice Node.js arbitrario – tra cui l'avvio di una shell inversa tramite `child_process.exec()`, la lettura delle variabili d'ambiente e l'accesso al filesystem locale – senza fornire alcuna credenziale né attivare alcun controllo di autenticazione a livello di applicazione. Il valore arbitrario accettato per l'intestazione Next-Action conferma ulteriormente la natura pre-autenticazione della vulnerabilità: il server elabora e deserializza il payload prima di eseguire qualsiasi ricerca di azione o controllo di autorizzazione.
Mitigazione
Il team di React ha rilasciato delle patch il 3 dicembre 2025, lo stesso giorno in cui la vulnerabilità è stata resa pubblica. Le versioni corrette sono disponibili come React 19.0.1, 19.1.2 e 19.2.1. La patch aggiunge una rigorosa convalida delle proprietà in getOutlinedModel() e reviveModel(), bloccando esplicitamente la risoluzione delle proprietà del prototipo ereditate - tra cui __proto__, constructor e prototype - dai percorsi di riferimento controllati dall'utente nei payload Flight.
Le organizzazioni dovrebbero adottare immediatamente le seguenti misure:
- Aggiornare i pacchetti React a una versione corretta (19.0.1, 19.1.2 o 19.2.1) eseguendo npm install react-server-dom-webpack@latest, react-server-dom-parcel@latest o react-server-dom-turbopack@latest, a seconda dei casi.
- Aggiornare le dipendenze dei framework - Next.js, React Router, Waku e altri framework interessati hanno rilasciato le relative patch. Consultare l'avviso del team di React per conoscere i percorsi di aggiornamento specifici per ciascuna versione.
- Non affidatevi esclusivamente alle misure di mitigazione adottate dai fornitori di hosting: sebbene fornitori come Vercel abbiano implementato regole WAF temporanee in seguito alla divulgazione della vulnerabilità, si tratta di misure provvisorie che non sostituiscono l'applicazione delle patch ai pacchetti sottostanti.
- Controlla i log del server alla ricerca di richieste POST dotate di intestazioni Next-Action con corpi multipart/form-data contenenti i pattern $@ o __proto__ e verifica la presenza di chiamate impreviste a child_process o execSync nei log dell'applicazione.
Mitigazione con OPSWAT
OPSWAT , una tecnologia proprietaria integrata nella piattaforma MetaDefender™, offre la visibilità e il controllo necessari per difendersi da vulnerabilità come CVE-2025-55182. Poiché questa vulnerabilità risiede nei pacchetti npm open-source (react-server-dom-webpack, react-server-dom-parcel, react-server-dom-turbopack), le organizzazioni devono innanzitutto creare un inventario completo delle posizioni in cui questi componenti sono distribuiti all'interno della propria infrastruttura prima di poter procedere a una correzione efficace.

OPSWAT genera un inventario completo di tutti i componenti software, le librerie, i container e le dipendenze in uso. Durante l'analisi delle applicazioni o delle immagini dei container che includono pacchetti React vulnerabili, il sistema contrassegna automaticamente la vulnerabilità CVE-2025-55182 come "Critica" e fornisce indicazioni sulle versioni corrette disponibili, consentendo ai team di sicurezza di stabilire le priorità e intervenire prima che si verifichino attacchi.
OPSWAT è disponibile sia in MetaDefender – per l'analisi di singole applicazioni e immagini container – sia in MetaDefender Software Chain™ – per garantire la visibilità a livello di pipeline lungo l'intero ciclo di vita dello sviluppo. Insieme, consentono ai team di sicurezza di:
- Individua rapidamente i componenti vulnerabili - Identifica immediatamente quali applicazioni e container includono i pacchetti react-server-dom-* interessati nelle versioni vulnerabili, assicurandoti che nessuna distribuzione venga tralasciata.
- Garantire un aggiornamento proattivo dei patch - Monitorare costantemente le dipendenze open source per individuare i pacchetti obsoleti o non sicuri man mano che vengono pubblicati nuovi avvisi, riducendo così il periodo di vulnerabilità.
- Garantire la conformità e la trasparenza della catena di fornitura - Soddisfare i requisiti normativi mantenendo una documentazione verificabile di tutti i componenti software e delle loro vulnerabilità note.
La rapidità e la portata dello sfruttamento della vulnerabilità CVE-2025-55182 – oltre 582 milioni di tentativi solo nella prima settimana – evidenziano perché l'applicazione reattiva delle patch non sia più sufficiente. Sapere di cosa si dispone, dove viene eseguito e quando diventa vulnerabile è alla base di una difesa proattiva. Questa visibilità parte dall'SBOM.
Riferimenti
- Team React - Vulnerabilità critica di sicurezza nei Server di React
- Wiz Research - React2Shell (CVE-2025-55182): vulnerabilità critica in React
- Trend Micro - CVE-2025-55182: analisi di React2Shell, PoC Chaos e sfruttamento in ambiente reale
- Akamai - CVE-2025-55182: RCE tramite deserializzazione Server in React e Next.js
- Google Cloud Diversi autori di attacchi sfruttano la vulnerabilità React2Shell (CVE-2025-55182)
- AWS - I gruppi di hacker legati alla Cina sfruttano rapidamente React2Shell
- NVD - CVE-2025-55182
- Capire Server di React - Tony Alicea
- MDN Web Docs - Operatore `await`
- Codice sorgente React - ReactFlightReplyServer.js (v19.0.0)
- Codice sorgente di Next.js - action-handler.ts (v16.0.0)
