Incident response in 72 ore per Laravel e Symfony: guida operativa NIS2-ready per PMI

Incident response in 72 ore per Laravel e Symfony: guida operativa NIS2-ready per PMI

Il 18 agosto 2025, un venerdì sera alle 21:40, mi ha chiamato il titolare di una PMI lombarda - e-commerce B2C su Laravel 10, circa 4.200 clienti registrati, fatturato online di 1.8 milioni di euro l'anno - perché il suo fornitore di hosting (Hetzner) lo aveva avvisato che il suo VPS stava generando traffico anomalo in uscita verso IP in Russia e Cina. Il VPS era un CPX31 con Debian 12, Nginx, PHP-FPM 8.2, MySQL 8.0 e Redis 7. Il traffico anomalo era iniziato alle 18:00 circa - tre ore prima della chiamata - e consisteva in richieste HTTP POST verso tre endpoint esterni che il server non avrebbe dovuto contattare.

L'indagine ha rivelato che una dipendenza Composer di secondo livello - un pacchetto di utility per la gestione delle immagini, aggiornato automaticamente dal CI due giorni prima - conteneva una backdoor che esfiltraba dati dal database a ogni richiesta web. I dati potenzialmente esposti includevano nome, email, indirizzo e storico ordini dei 4.200 clienti. Con dati personali compromessi, il GDPR richiedeva la notifica al Garante Privacy entro 72 ore dalla scoperta - e la Direttiva NIS2, in vigore in Italia da ottobre 2024, aggiungeva obblighi di notifica al CSIRT nazionale con una timeline ancora più stretta: early warning entro 24 ore.

In questo articolo ti racconto le 72 ore di incident response - dal contenimento alla notifica - con il playbook operativo che applico a ogni incidente su applicazioni Laravel e Symfony.

Stai cercando un Consulente Informatico esperto per gestire incidenti di sicurezza sulle tue applicazioni PHP? Nel mio profilo professionale trovi l'esperienza concreta su incident response, forensics e compliance NIS2/GDPR. Contattami per una consulenza diretta.

Le prime 2 ore: contenimento senza distruggere le prove

La tentazione del venerdì sera - con il titolare nel panico e il sito che esfiltrava dati - è spegnere tutto, cancellare il pacchetto compromesso e riaccendere. È l'errore peggiore possibile: distruggi le prove forensi, perdi la possibilità di capire cosa è stato esfiltratto, e non sai se l'attaccante ha installato altre backdoor.

Il contenimento corretto è chirurgico: blocca il traffico malevolo senza toccare i file e i log che ti servono per la forensics.

# 1. Bloccare il traffico in uscita verso gli IP di esfiltrazione
iptables -A OUTPUT -d 91.XXX.XX.XX -j DROP
iptables -A OUTPUT -d 185.YYY.YY.YY -j DROP
iptables -A OUTPUT -d 45.ZZZ.ZZ.ZZ -j DROP

# 2. Mettere il sito in manutenzione (Laravel)
php /var/www/html/artisan down --secret="token-emergenza"

# 3. PRESERVARE LE PROVE - snapshot dei log e dei file
tar -czf /root/evidence-$(date +%Y%m%d-%H%M).tar.gz \
    /var/www/html/vendor/ \
    /var/www/html/storage/logs/ \
    /var/log/nginx/ \
    /var/log/auth.log

# 4. Dump del database prima di qualsiasi modifica
mysqldump -u root -p --single-transaction --all-databases > /root/db-evidence-$(date +%Y%m%d).sql

L'ordine è fondamentale: prima blocchi l'esfiltrazione (i dati che non escono da adesso in poi sono protetti), poi metti il sito in manutenzione (impedisci nuove richieste web che attiverebbero la backdoor), poi salvi le prove (tutto ciò che ti serve per la forensics e per la notifica alle autorità).

La timeline NIS2: 24-72 ore e 30 giorni

La timeline di notifica NIS2, Articolo 23 è strutturata in tre fasi che un playbook operativo deve rispettare in parallelo al lavoro tecnico:

Entro 24 ore (early warning): notifica al CSIRT nazionale (in Italia: CSIRT Italia, [email protected]) indicando: natura sospetta dell'incidente, se è di origine malevola, se ha potenziale impatto transfrontaliero. Non serve un report completo - serve un segnale tempestivo.

Entro 72 ore (incident notification): report più strutturato al CSIRT con: valutazione iniziale della severità e dell'impatto, indicatori di compromissione (IoC), misure di contenimento e ripristino adottate. Se l'incidente coinvolge dati personali, in parallelo scatta la notifica GDPR al Garante Privacy con la stessa deadline di 72 ore.

Entro 30 giorni (final report): report dettagliato con root cause analysis completa, impatto effettivo, misure di remediation, lezioni apprese. Se l'incidente è ancora in corso al giorno 30, un report intermedio è accettabile e il report finale è dovuto entro un mese dalla chiusura.

Nel caso lombardo, l'early warning al CSIRT è partito il sabato mattina alle 10:00 - entro 12 ore dalla scoperta. La notifica GDPR al Garante è stata preparata dal consulente legale del cliente domenica pomeriggio e inviata via PEC lunedì mattina - entro 60 ore. La timeline è stata rispettata, ma con margine minimo.

Ore 3-12: la forensics applicativa

Con il sito in manutenzione e le prove salvate, ho dedicato la notte alla forensics - ricostruire esattamente cosa era successo, quando, e quali dati erano stati esposti.

Il punto di partenza è il composer.lock: quali dipendenze sono state aggiornate nei giorni precedenti l'incidente?

# Confrontare il composer.lock attuale con il commit precedente
git log --oneline -5 -- composer.lock
# Trovare il diff esatto
git diff HEAD~2 -- composer.lock | grep "+"

Il diff ha mostrato che il pacchetto imagex/resize-helper era passato dalla versione 2.1.4 alla 2.1.5 - un minor update che il CI aveva applicato automaticamente con composer update due giorni prima. L'analisi del codice della versione 2.1.5 ha rivelato un file src/Helpers/CacheWarm.php che non esisteva nella 2.1.4 e che conteneva una funzione apparentemente innocua che in realtà codificava in base64 i risultati delle query Eloquent e li inviava via cURL a tre endpoint esterni.

# Verificare quando la backdoor è stata eseguita
grep "CacheWarm" /var/www/html/storage/logs/laravel.log | head -5
# Prima esecuzione: 2025-08-16 14:23:01 - due giorni prima della scoperta

Due giorni di esfiltrazione. Le query nel codice della backdoor facevano DB::table('users')->select('name', 'email', 'address')->get() e DB::table('orders')->select('*')->limit(1000)->get() - dati personali e storico ordini. Questi dettagli sono andati nella notifica GDPR come "dati potenzialmente compromessi".

Ore 12-48: il ripristino sicuro

Il ripristino non è "rimuovi il pacchetto compromesso e riparti". Se l'attaccante ha avuto accesso al codice dell'applicazione per due giorni, potrebbe aver installato altre backdoor che non hai ancora trovato. Il ripristino sicuro parte da una baseline verificata.

# 1. Checkout del codice da un commit precedente all'infezione
git checkout $(git log --before="2025-08-15" --format=%H -1)

# 2. Reinstallazione PULITA delle dipendenze (non dalla cache!)
rm -rf vendor/
composer install --no-dev --prefer-dist --no-cache

# 3. Rotazione di TUTTE le chiavi
php artisan key:generate    # nuova APP_KEY
# Ruotare manualmente: credenziali DB, API key terze parti, 
# JWT secret, webhook secrets, chiavi S3

# 4. Invalidare TUTTE le sessioni utente
php artisan session:table   # se non già presente
redis-cli -a "$REDIS_PASS" FLUSHDB  # se sessioni in Redis

# 5. Reset password obbligatorio per gli utenti admin
php artisan tinker --execute="App\Models\User::where('is_admin',true)->update(['password'=>bcrypt(Str::random(32))])"

# 6. Audit delle dipendenze
composer audit --locked

La rotazione delle chiavi è il passaggio che la maggior parte dei playbook sottovaluta. Se l'attaccante ha avuto accesso al codice per due giorni, ha potenzialmente letto il .env - e con esso ha le credenziali del database, le API key di Stripe/PayPal, i token di SendGrid, i webhook secret. Tutte queste credenziali devono essere considerate compromesse e ruotate, anche se non hai prove dirette che siano state esfiltrate.

Ore 48-72: hardening e chiusura

Le ultime 24 ore della finestra sono dedicate a chiudere il vettore di attacco originale e implementare le contromisure che impediscono che si ripeta.

Per il vettore specifico - dipendenza Composer compromessa - le contromisure sono:

  • Pinning esplicito delle dipendenze critiche in composer.json (niente più ^2.1, solo 2.1.4)
  • Audit automatico nella CI che blocca il deploy se composer audit rileva CVE
  • Review manuale di ogni aggiornamento di dipendenza prima del merge (niente più composer update automatico in CI)

Per l'hardening generale - valido per qualsiasi incidente su Laravel/Symfony - rimando alla checklist di hardening NIS2-ready in 14 giorni che copre segreti, header HTTP, rate limiting, logging, CI/CD security gate e backup verificato. E per il caso specifico di un sito PHP completamente compromesso - con backdoor nel filesystem, rootkit nel server, credenziali rubate - la procedura completa è documentata nell'articolo sul ripristino di un sito PHP hackerato.

La comunicazione verso i clienti: cosa dire e cosa non dire

Con dati personali potenzialmente esposti, il GDPR richiede - oltre alla notifica al Garante - anche la comunicazione agli interessati "senza ingiustificato ritardo" quando il rischio per i diritti è elevato. Il titolare dell'e-commerce lombardo doveva avvisare 4.200 clienti che i loro dati potrebbero essere stati compromessi.

La comunicazione che ho aiutato a redigere seguiva quattro principi. Primo: trasparenza su cosa è successo, senza tecnicismi inutili. "Abbiamo rilevato un accesso non autorizzato ai nostri sistemi" - non "una dependency supply chain attack ha sfruttato un pacchetto Composer compromesso". Secondo: chiarezza su quali dati sono coinvolti. "Nome, indirizzo email e storico ordini. Non sono stati coinvolti dati di pagamento" - la specifica sui pagamenti è fondamentale perché è la prima preoccupazione del cliente. Terzo: cosa abbiamo fatto per risolvere. "Abbiamo contenuto l'incidente, ripristinato i sistemi da una copia sicura e rafforzato le misure di sicurezza." Quarto: cosa deve fare il cliente. "Ti consigliamo di cambiare la password del tuo account."

La comunicazione è stata inviata via email il martedì - giorno 4 dopo la scoperta. Il tasso di churn (clienti che hanno chiesto cancellazione dati dopo la comunicazione) è stato del 2.3% - inferiore alla media del settore per incidenti di questo tipo, probabilmente grazie alla trasparenza e alla rapidità della comunicazione. Un'azienda che scopre un breach e lo nasconde rischia molto di più - sia legalmente (il GDPR prevede sanzioni aggravate per mancata notifica) sia reputazionalmente - di un'azienda che comunica rapidamente e con onestà.

Il playbook: avere un piano prima dell'emergenza

L'incident response non si improvvisa. Il playbook che installo su ogni progetto gestito è un documento di 3-4 pagine che risponde a queste domande prima che servano le risposte:

  • Chi chiamo? Referente tecnico (me), consulente legale, titolare. Con numeri di cellulare, non email.
  • Come contengo? Comandi esatti per mettere il sito in manutenzione, bloccare IP, preservare prove.
  • Dove sono le prove? Path dei log, credenziali del backup, accesso al pannello del provider.
  • Chi notifico? CSIRT Italia ([email protected]), Garante Privacy, clienti.
  • Qual è il rollback? Commit sicuro del codice, backup verificato del database, procedura di restore.

Questo documento deve essere accessibile offline - stampato e in una busta sigillata nell'ufficio del titolare - perché in un incidente il server potrebbe essere irraggiungibile e con esso qualsiasi documentazione digitale.

Cosa ho imparato (di nuovo) sulla gestione incidenti nelle PMI

La lezione più importante di questo incidente non è tecnica - è organizzativa. Il cliente non aveva un playbook di incident response. Non aveva un contatto CSIRT preconfigurato. Non aveva un consulente legale preavvisato per la notifica GDPR. Non aveva un canale di comunicazione d'emergenza con il suo team tecnico. Tutto questo è stato improvvisato il venerdì sera con il telefono che scottava.

Un'ENISA - l'Agenzia dell'Unione Europea per la Cybersecurity - sottolinea che la preparazione è l'80% dell'incident response. Avere un playbook scritto, testato e conosciuto dal team riduce il MTTR (Mean Time To Recovery) e il rischio legale in modo misurabile. La NIS2 richiede esplicitamente che le organizzazioni in scope abbiano procedure di gestione degli incidenti documentate e testate - non è un optional, è un obbligo di legge con sanzioni fino al 2% del fatturato globale per le entità essenziali.

Se la tua applicazione Laravel o Symfony è in produzione con dati di clienti e non hai un playbook di incident response, stai operando senza rete di sicurezza in un contesto normativo che richiede esplicitamente quella rete. Il costo di preparare un playbook - qualche giorno di lavoro - è incomparabile con il costo di improvvisare alle 21:40 di un venerdì sera con 4.200 clienti potenzialmente esposti e il Garante Privacy che aspetta la notifica. Contattami se vuoi costruire il tuo piano di incident response prima che ne abbia bisogno.

Ultima modifica: