Incident response per sviluppatori: cosa fare nei primi 30 minuti di un'intrusione
Alle 2:14 di una notte di metà ottobre 2025, il telefono ha squillato. Era il responsabile IT di un'azienda del settore e-commerce che gestiva un portale B2B con circa 12.000 utenti attivi, ospitato su un VPS Hetzner CPX51 (16 vCPU, 32 GB RAM, 360 GB NVMe) con Debian 12, PHP-FPM 8.2 e MySQL 8.0. Il suo monitoring Uptime Robot gli aveva segnalato un aumento anomalo del tempo di risposta - da una media di 180 ms a picchi di 3.200 ms - e un carico CPU stabile all'87% da un'ora, senza alcuna correlazione con il traffico reale, che a quell'ora era praticamente nullo. Il primo istinto del responsabile IT era stato quello di riavviare il server. Gli ho chiesto di non farlo. Quella decisione - non toccare nulla finché non capisci cosa sta succedendo - è la singola azione più importante nei primi 30 minuti di un incidente di sicurezza. Tutto il resto viene dopo.
Nei vent'anni in cui ho gestito incidenti di sicurezza su infrastrutture PHP in produzione, ho affinato un runbook operativo che seguo in modo rigoroso ogni volta che ricevo questo tipo di chiamata. L'ordine delle azioni non è casuale: è calibrato per massimizzare la preservazione delle prove forensi, minimizzare il vantaggio dell'attaccante e dare al team di risposta il tempo di capire cosa è realmente successo prima di prendere decisioni irreversibili. In questo articolo ti consegno quel runbook, passo per passo, con i comandi reali che eseguo su un VPS Linux durante un'intrusione. Se sei uno sviluppatore PHP e non hai mai gestito un incidente dal vivo, questa è la guida che avresti voluto avere prima che succedesse.
Perché il tuo istinto di sviluppatore è il nemico della forensics
Lo sviluppatore che scopre un'anomalia sul proprio server ha un riflesso condizionato: sistemare. Riavviare il servizio che consuma CPU. Killare il processo sospetto. Cancellare il file strano trovato in /tmp. Aggiornare il pacchetto vulnerabile. Cambiare le password. Ognuna di queste azioni, presa nei primi minuti di un incidente, è potenzialmente devastante per la risposta - non perché sia sbagliata in sé, ma perché distrugge prove forensi che non potrai mai recuperare.
Quando riavvii un server, perdi il contenuto della RAM: processi attivi, connessioni di rete aperte, chiavi di cifratura in memoria, e spesso l'unica copia del malware fileless che l'attaccante ha iniettato senza mai toccare il disco. Quando killi un processo sospetto, perdi il suo memory map, i suoi file descriptor aperti, e la possibilità di capire da dove è stato lanciato e con quali permessi. Quando cancelli un file in /tmp, perdi l'artefatto che il tuo forensics analyst userebbe per ricostruire la catena di compromissione. Il NIST SP 800-61r3, la guida di riferimento aggiornata nel 2025 per la gestione degli incidenti di sicurezza, raccomanda esplicitamente di non alterare lo stato del sistema prima di aver raccolto le evidenze volatili, perché la perdita di dati volatili è irreversibile e compromette l'intera capacità investigativa.
La regola che insegno a ogni sviluppatore con cui lavoro è semplice: nei primi cinque minuti, il tuo unico lavoro è osservare e registrare. Non riparare, non contenere, non comunicare. Osservare e registrare. Il contenimento arriva dopo, ma arriva informato.
Come si preserva una scena del crimine digitale su un VPS Linux?
La risposta è: catturando prima le prove volatili (quelle che scompaiono con un reboot o con il passare del tempo) e poi quelle persistenti (quelle sul disco). L'ordine di volatilità, dalla più effimera alla più stabile, è: registri CPU e cache, contenuto della RAM, stato della rete e dei processi, contenuto del disco, log remoti. Nei primi minuti di un incidente, il tuo obiettivo è catturare i livelli 2 e 3 di questa scala prima che qualcuno faccia qualcosa di irreversibile.
Questo è lo script di triage iniziale che eseguo nei primi 5 minuti dopo essermi collegato via SSH al server compromesso. Non modifica nulla sul sistema - legge e scrive solo nella directory di output:
#!/bin/bash
# Triage iniziale - raccolta prove volatili
# Eseguire PRIMA di qualsiasi azione di contenimento o bonifica
# Lo script non modifica lo stato del sistema
set -euo pipefail
EVIDENCE_DIR="/root/evidence-$(date +%Y%m%d-%H%M%S)"
mkdir -p "${EVIDENCE_DIR}"
echo "[1/8] Timestamp e stato sistema..."
date -u > "${EVIDENCE_DIR}/00-timestamp.txt"
uptime >> "${EVIDENCE_DIR}/00-timestamp.txt"
w >> "${EVIDENCE_DIR}/00-timestamp.txt"
echo "[2/8] Processi attivi con albero completo..."
ps auxwwf > "${EVIDENCE_DIR}/01-processes.txt"
ps -eo pid,ppid,user,args --sort=-pcpu > "${EVIDENCE_DIR}/01-processes-cpu.txt"
echo "[3/8] Connessioni di rete attive..."
ss -tulnpa > "${EVIDENCE_DIR}/02-network-connections.txt"
ss -s > "${EVIDENCE_DIR}/02-network-stats.txt"
echo "[4/8] Utenti loggati e sessioni attive..."
who -a > "${EVIDENCE_DIR}/03-who.txt"
last -n 50 > "${EVIDENCE_DIR}/03-last-logins.txt"
lastlog > "${EVIDENCE_DIR}/03-lastlog.txt"
echo "[5/8] File modificati nelle ultime 24 ore..."
find / -xdev -mtime -1 -type f \
-not -path "/proc/*" -not -path "/sys/*" -not -path "/run/*" \
-not -path "${EVIDENCE_DIR}/*" \
-printf '%T+ %p\n' 2>/dev/null | sort -r > "${EVIDENCE_DIR}/04-recently-modified.txt"
echo "[6/8] Crontab di tutti gli utenti..."
for u in $(cut -d: -f1 /etc/passwd); do
echo "=== crontab: ${u} ===" >> "${EVIDENCE_DIR}/05-crontabs.txt"
crontab -u "${u}" -l 2>/dev/null >> "${EVIDENCE_DIR}/05-crontabs.txt" || true
done
cat /etc/crontab >> "${EVIDENCE_DIR}/05-crontabs.txt"
ls -la /etc/cron.d/ >> "${EVIDENCE_DIR}/05-crontabs.txt" 2>/dev/null
echo "[7/8] Hash dei binari critici (verifica tampering)..."
sha256sum /usr/bin/ps /usr/bin/ss /usr/bin/ls /usr/bin/find \
/usr/bin/who /usr/sbin/sshd 2>/dev/null > "${EVIDENCE_DIR}/06-binary-hashes.txt"
echo "[8/8] Log di autenticazione recenti..."
tail -500 /var/log/auth.log > "${EVIDENCE_DIR}/07-auth-log.txt" 2>/dev/null
tail -500 /var/log/syslog > "${EVIDENCE_DIR}/07-syslog.txt" 2>/dev/null
journalctl -n 500 --no-pager > "${EVIDENCE_DIR}/07-journal.txt" 2>/dev/null
# Calcola hash dell'intera directory di evidence per catena di custodia
find "${EVIDENCE_DIR}" -type f -exec sha256sum {} \; > "${EVIDENCE_DIR}/MANIFEST-SHA256.txt"
echo "Triage completato: ${EVIDENCE_DIR}"
echo "File raccolti: $(find "${EVIDENCE_DIR}" -type f | wc -l)"Due aspetti di questo script meritano attenzione. Il primo è il passo 7: l'hash dei binari di sistema. Se l'attaccante ha sostituito /usr/bin/ps con una versione modificata che nasconde i suoi processi (un rootkit classico), il tuo ps aux ti mentirà. Confrontando gli hash SHA-256 con quelli dei pacchetti originali (dpkg -V coreutils procps iproute2) puoi verificare se i binari sono stati alterati. Se lo sono, ogni output di quei comandi è inaffidabile e devi usare binari statici portati da un'immagine pulita. Il secondo aspetto è il MANIFEST-SHA256.txt finale: è la base della chain of custody digitale, il documento che certifica che le prove raccolte non sono state alterate dopo la raccolta. Se l'incidente finirà in un procedimento legale o in una notifica al Garante Privacy ai sensi del GDPR, quel file è la tua difesa.
Minuto 5-15: contenere la minaccia senza distruggere le prove
Una volta raccolte le prove volatili, puoi passare al contenimento. L'obiettivo è impedire all'attaccante di fare ulteriori danni e di esfiltrare dati, senza spegnere il server e senza killare processi sospetti (che a questo punto hai già documentato). Il metodo che uso è il contenimento a livello di rete: taglio le connessioni verso l'esterno mantenendo il server raggiungibile solo dal mio IP per continuare l'analisi.
Su un VPS Linux con iptables (o nftables), il contenimento di rete si fa con quattro regole:
# Contenimento di rete: blocca tutto il traffico in uscita
# tranne verso l'IP del consulente
# ATTENZIONE: eseguire SOLO dopo aver raccolto le prove volatili
# Salva le regole correnti come evidenza
iptables-save > /root/evidence-*/08-iptables-pre-containment.txt
# Permetti il traffico SSH dal mio IP di gestione
iptables -I INPUT 1 -s 203.0.113.50/32 -p tcp --dport 22 -j ACCEPT
iptables -I OUTPUT 1 -d 203.0.113.50/32 -p tcp --sport 22 -j ACCEPT
# Permetti DNS locale e loopback (necessari per i tool di analisi)
iptables -I OUTPUT 2 -o lo -j ACCEPT
iptables -I INPUT 2 -i lo -j ACCEPT
# Blocca TUTTO il resto del traffico in uscita
iptables -A OUTPUT -j DROP
echo "Contenimento attivo: il server non può comunicare con l'esterno"Queste quattro regole hanno un effetto immediato e potente: l'attaccante perde il canale di comando e controllo (C2), non può più esfiltrare dati, e non può ricevere istruzioni. Ma il server resta attivo, i processi continuano a girare (e puoi continuare ad analizzarli), e tu mantieni accesso SSH per proseguire l'indagine. L'alternativa - staccare il cavo di rete o spegnere il server - ti farebbe perdere tutto il contenuto volatile della RAM e tutti i processi attivi. La guida CISA ai playbook di incident response e vulnerability response raccomanda esplicitamente l'isolamento di rete come strategia di contenimento preferita rispetto allo spegnimento, proprio per preservare le evidenze volatili.
Un dettaglio pratico che molti sviluppatori dimenticano: se il server ospita un'applicazione web in produzione, il contenimento di rete interrompe il servizio verso gli utenti. Questo è intenzionale e accettabile: un server compromesso che continua a servire traffico espone i tuoi utenti a rischi attivi - redirect malevoli, injection nel DOM, furto di sessioni. La decisione di interrompere il servizio deve essere comunicata immediatamente agli stakeholder - il titolare dell'azienda, il legale, e se applicabile il DPO - ma non deve essere ritardata per "non creare disagio". Il disagio di un'interruzione controllata è sempre inferiore al danno di una compromissione che si propaga attraverso il tuo applicativo verso i dati dei tuoi utenti. Nel mio lavoro di consulenza sulla sicurezza infrastrutturale, trovi il dettaglio delle competenze che porto in questi scenari nel mio profilo professionale, e la capacità di prendere questa decisione rapidamente - interrompere, contenere, analizzare, ripristinare - è ciò che distingue una risposta professionale da una risposta improvvisata.
Minuto 15-30: leggere le prove e ricostruire la kill chain
Con le prove raccolte e la minaccia contenuta, hai guadagnato tempo per analizzare. I primi file da leggere sono quelli che hai appena raccolto nello script di triage, in questo ordine preciso:
Primo: i processi attivi (01-processes.txt). Cerca processi con nomi sospetti (stringhe random, nomi di binari di sistema ma con path anomali come /tmp/.hidden/ps), processi avviati da utenti che non dovrebbero avere shell (ad esempio www-data con un processo bash), processi con consumo CPU o memoria anomalo senza motivo applicativo. Nel caso della chiamata notturna, il file dei processi mostrava tre istanze di un binario chiamato kworker3 (che imita un nome legittimo del kernel) avviato da www-data, con parent PID che puntava al process manager PHP-FPM - segnale inequivocabile che l'intrusione era avvenuta attraverso una vulnerabilità dell'applicazione web.
Secondo: le connessioni di rete (02-network-connections.txt). Cerca connessioni ESTABLISHED verso IP esterni su porte non standard (sopra la 1024, tipicamente usate per C2), connessioni in LISTEN su porte che non conosci, e soprattutto connessioni da processi che non dovrebbero avere accesso alla rete. Il file delle connessioni rivelava una connessione TCP ESTABLISHED da kworker3 (PID 28417) verso un IP in un blocco di un provider cloud est-europeo, sulla porta 8443 - il canale C2 che le regole iptables di contenimento avevano appena interrotto.
Terzo: i file modificati di recente (04-recently-modified.txt). Questo file è spesso il più rivelatore: mostra tutti i file del filesystem modificati nelle ultime 24 ore, ordinati cronologicamente. Cerca modifiche ai file di configurazione di servizi in /etc/, nuovi file in directory temporanee (/tmp, /var/tmp, /dev/shm), modifiche ai file dell'applicazione web (script PHP nuovi o modificati nella document root), e soprattutto modifiche ai file di autenticazione (/etc/passwd, /etc/shadow, authorized_keys). Nel caso concreto, la lista mostrava sei file PHP nuovi nella directory vendor/autoload/ dell'applicazione Laravel - una directory che Composer non dovrebbe mai modificare dopo il deploy - e un file authorized_keys modificato per l'utente www-data, cosa che non ha nessuna ragione legittima di esistere.
La ricostruzione della kill chain era ormai chiara: l'attaccante aveva sfruttato una vulnerabilità dell'applicazione (un endpoint di upload che accettava file .php mascherati con doppia estensione .jpg.php), aveva deployato una web shell nella directory vendor, e da lì aveva stabilito persistenza con una chiave SSH sull'utente www-data e un processo di cryptomining mascherato da kernel worker. L'intera catena, dall'exploitation iniziale alla persistenza, aveva richiesto meno di quattro ore - un tempo perfettamente in linea con i dati del 2025 che indicano un tempo mediano di 5 giorni dall'intrusione iniziale al deployment del payload, sceso a poche ore per gli attacchi automatizzati.
Dopo i 30 minuti: il triage che decide il percorso
I primi 30 minuti definiscono il perimetro. Dopo, hai tre percorsi possibili, e la scelta dipende dalla gravità di ciò che hai trovato:
- Percorso A (compromissione limitata, nessun dato utente esposto): bonifica chirurgica - rimozione degli artefatti malevoli, patching della vulnerabilità, rotazione di tutte le credenziali, hardening post-incidente, ripristino del servizio. Tempo tipico: 4-8 ore
- Percorso B (compromissione estesa, possibile accesso ai dati utente): immagine forense del disco, bonifica su server pulito (non sullo stesso server compromesso), notifica GDPR al Garante Privacy entro 72 ore se confermata la violazione di dati personali, comunicazione agli utenti interessati, analisi approfondita delle modalità di compromissione. Tempo tipico: 2-5 giorni
- Percorso C (rootkit confermato, integrità del sistema operativo compromessa): non fidarti di nulla sul server - fai immagine forense bit-per-bit del disco, provvedi un nuovo server, rideploya l'applicazione da zero partendo dal repository git (non dal server compromesso), importa solo i dati dal backup più recente verificato pulito. Tempo tipico: 3-7 giorni
Nel caso della chiamata notturna, eravamo nel Percorso A: il mining malware operava come www-data, non aveva ottenuto privilegi root, e non c'erano evidenze di accesso al database o ai dati degli utenti. La bonifica è stata completata in sei ore: rimossi i sei file PHP malevoli dalla directory vendor, eliminata la chiave SSH abusiva da www-data, identificata e patchata la vulnerabilità di upload (un endpoint dimenticato che accettava file .php mascherati da immagini), ruotate tutte le credenziali applicative e di sistema, e ripristinato il servizio alle 8:30 del mattino - in tempo per l'apertura degli uffici del cliente. Ho documentato il framework completo di risposta e comunicazione normativa nel mio articolo sulla gestione delle prime 72 ore di un incidente NIS2-ready, e le misure preventive di hardening nel pezzo dedicato alla checklist NIS2-ready per applicazioni Laravel e Symfony.
La lezione che ripeto ogni volta, e che è il motivo per cui ho scritto questo runbook, è che la differenza tra un incidente gestito in sei ore e uno che paralizza un'azienda per tre settimane sta quasi sempre nei primi trenta minuti. Se il responsabile IT avesse riavviato il server quando mi ha chiamato, avremmo perso le prove volatili che ci hanno permesso di identificare il vettore di attacco in meno di un'ora. Se avesse killato il processo sospetto, non avremmo potuto tracciare la connessione C2 e capire che non c'era stata esfiltrazione di dati. Se avesse "ripulito" i file sospetti, non avremmo ricostruito la kill chain completa e avremmo rischiato di lasciare una backdoor attiva. In ognuno di questi scenari alternativi, il Percorso A si sarebbe trasformato in un Percorso B o C, con costi e tempi moltiplicati per un fattore cinque e l'obbligo di notifica al Garante che nel Percorso A non è stato necessario. Se gestisci applicazioni PHP in produzione su VPS Linux e non hai un runbook di incident response pronto - con ruoli definiti, script di triage testati e un piano di comunicazione verso stakeholder e autorità - contattami per costruirne uno insieme: in una giornata di lavoro mirato analizziamo la tua infrastruttura, definiamo le procedure, prepariamo gli script e facciamo un primo tabletop exercise per verificare che il piano funzioni prima che ne hai bisogno sul campo.