
Lab dimostrativo secure-by-default che mostra come l'indurimento dei container (immagini distroless, non-root, filesystem di sola lettura, segreti iniettati a runtime) possa neutralizzare una RCE critica di Next.js/React Server Actions (CVE-2025-55182 "React2Shell"), con distribuzioni sicure vs non sicure affiancate e log degli exploit.
Questo progetto dimostra una vulnerabilità critica di Remote Code Execution (RCE) in un'applicazione Next.js (in particolare tramite Server Actions) e come l'indurimento dell'infrastruttura neutralizzi efficacemente l'attacco anche quando la vulnerabilità nel codice rimane.
Mette a confronto un deployment "Non sicuro" standard con un deployment "Sicuro" indurito che utilizza immagini Distroless e file system in sola lettura.
Le vulnerabilità software sono inevitabili. Quando il codice fallisce, la tua infrastruttura deve impedire all'attaccante di espandere il proprio punto d'appoggio.
Una RCE Critica (CVE-2025-55182, nota anche come React2Shell) esiste nell'implementazione di React Server Components (RSC) utilizzata da Next.js.
spawnSync) senza autenticazione.curl, wget, ls, cat) per rubare segreti o scaricare malware.
child_process.spawnSync() di Node.js. Questo esegue i binari direttamente, senza bisogno di una shell (/bin/sh).chmod +x) e lo esegue.I seguenti log mostrano come appaiono i tentativi di attacco dalla prospettiva dell'applicazione. Questo contrasto evidenzia chiaramente l'efficacia delle misure di sicurezza.
logs/server.safe.log)I log mostrano ripetuti errori (ENOENT).
spawnSync tenta di eseguire ls, id, curl. L'immagine Distroless semplicemente non ha questi binari. Non si tratta solo dell'assenza di una shell; gli strumenti stessi non ci sono.[Instrumentation] Logging initialized. Writing to: /app/logs/server.safe.log
⨯ Error: NEXT_REDIRECT
... digest: '`{"step":"1. Write Initial Chunk to /tmp/hello_test","success":true}`'
⨯ Error: NEXT_REDIRECT
... digest: '`{"step":"2. Verify Binary was Written","verification":{...},"success":true}`'
⨯ Error: NEXT_REDIRECT
... digest: '`{"step":"4. Execute Binary","stdout":"","stderr":"","error_obj":{"message":"spawnSync /tmp/hello_test EACCES","code":"EACCES"},"success":true}`'
logs/server.unsafe.log)I log confermano l'esecuzione riuscita dei comandi e la manipolazione del file system.
[Instrumentation] Logging initialized. Writing to: /app/logs/server.unsafe.log
⨯ Error: NEXT_REDIRECT
... digest: '`{"step":"1. Write Initial Chunk to /tmp/hello_test","success":true}`'
⨯ Error: NEXT_REDIRECT
... digest: '`{"step":"4. Execute Binary","stdout":"Hello from Go binary!\\n","stderr":"","error_obj":null,"success":true}`'
⨯ Error: NEXT_REDIRECT
... digest: '`{"command":"id","args":[],"stdout":"uid=0(root) gid=0(root) ...","stderr":"","status":0,"signal":null}`'
⨯ Error: NEXT_REDIRECT
... digest: '`{"command":"cat","args":["/app/.env"],"stdout":"","stderr":"cat: can\'t open \'/app/.env\': No such file or directory\n","status":1,"signal":null}`'
(Nota: nei log non sicuri, cat /app/.env fallisce sopra perché il file si chiama .env nella root, ma ls -la nei log completi rivelerebbe la struttura delle directory.)
Tentativo di eseguire comandi shell standard.
id, ls, cat .env e accedere a dati sensibili.spawnSync /bin/sh ENOENT. Non c'è alcuna shell per eseguire comandi.Tentativo di bypassare gli "strumenti mancanti" caricando un binario personalizzato.
/tmp/malware.chmod +x.EROFS: read-only file system.Può un attaccante caricare un binario in una variabile ed eseguirlo direttamente dalla memoria?
global.payload = "..."), quindi eseguirlo.child_process di Node.js (spawn, exec) richiedono un percorso file. Non possono eseguire direttamente un buffer o una stringa.memfd_create (una syscall per creare un file anonimo in RAM).memfd_create nativamente. Accedervi richiederebbe un addon C++ (come ffi-napi) preinstallato in node_modules.gcc, make), un attaccante non può compilare questo addon al volo.Le immagini "Distroless" contengono solo la tua applicazione e le sue dipendenze runtime. Non contengono gestori di pacchetti, shell o strumenti UNIX standard.
ls), scaricare file (curl) o escalare i privilegi facilmente.Configura il runtime del container per montare il file system root come sola lettura.
docker-compose.yml:
read_only: true
tmpfs:
- /tmp:noexec # CRITICO: blocca esplicitamente l'esecuzione!
/tmp (scrittura riuscita), ma l'esecuzione fallisce con EACCES (Permesso Negato) a causa del flag noexec. Questo bilancia funzionalità (tmp scrivibile) e sicurezza.Non inserire file .env nelle immagini dei tuoi container. Se un attaccante può leggere file (ad esempio, cat .env), i tuoi segreti sono compromessi.
environment di Docker).Avvia l'Ambiente:
Sia l'applicazione sicura che quella non sicura sono definite in un unico file docker-compose.yml.
docker compose up --build -d
Esegui gli Exploit: Puoi eseguire gli exploit sulle porte specifiche per vedere la differenza.
Puntando all'App Non Sicura (Porta 3001):
# 1. RCE Standard (LotL) - HA SUCCESSO
python exploit/poc.py http://localhost:3001
# 2. Attacco Avanzato (BYOL) - HA SUCCESSO
python exploit/poc_advanced.py http://localhost:3001
Puntando all'App Sicura (Porta 3000):
# 1. RCE Standard (LotL) - FALLISCE (ENOENT)
python exploit/poc.py http://localhost:3000
# 2. Attacco Avanzato (BYOL) - FALLISCE (EACCES/EROFS)
python exploit/poc_advanced.py http://localhost:3000
Pulizia:
docker compose down
| Caratteristica | ❌ Ambiente Non Sicuro (Porta 3001) | ✅ Ambiente Sicuro (Porta 3000) |
|---|
| Immagine Base | node:20-alpine (Contiene ls, curl, wget, ecc.) | gcr.io/distroless/nodejs20-debian12 (Nessuna shell, nessuno strumento) |
| File System | Scrivibile (Default standard di Docker) | Sola Lettura (read_only: true) |
| Segreti | File .env su disco (Vulnerabile a cat .env) | Variabili d'Ambiente (Iniettate a runtime) |
| Utente | root (Default) | Non-root (Imposto da Distroless) |