Sito PHP hackerato su Hetzner o OVH: il protocollo che applico nelle prime ore fra contenimento, forensics e ripartenza pulita
Il 19 ottobre 2024, sabato mattina alle 9:14, ho ricevuto la chiamata di un cliente di Verona che gestiva un portale di prenotazioni B2B per il settore turistico. Il portale era ospitato su un server dedicato Hetzner AX51 ed era stato online da quattro anni senza incidenti significativi. Quel sabato mattina avevano ricevuto due segnalazioni quasi simultanee: una da un cliente che lamentava che la homepage mostrava una pagina di donazione a una causa filo-russa al posto del catalogo prodotti, e una da Google Search Console che marcava l'intero dominio come "deceptive site" con advisory pubblico ai visitatori. Quando mi sono collegato in SSH al server alle 9:37, ho trovato la situazione classica del defacement opportunistico con persistence attiva: un file index.php modificato la notte precedente alle 03:41, una webshell PHP camuffata da wp-cache.php (su un sito che non usava WordPress, dettaglio rivelatore), e tre cron job nuovi inseriti con l'utente www-data che ogni 15 minuti scaricavano payload da un IP romeno.
Il primo riflesso del cliente al telefono è stato esattamente quello che sento in ogni incidente: "rimuovi subito quei file e fai ripartire il sito". Ho dovuto spiegare, con un titolare in panico che sentiva il business fermo nel fine settimana di punta della stagione, che no - esattamente quello non va fatto, e che dovevamo aspettare altre due ore prima di rimettere online qualunque cosa. Quattro ore dopo il sito era nuovamente online ma su un server completamente nuovo, con uno snapshot pulito del codice e un database ripristinato da un backup verificato. Il giorno seguente era hardened. Nella settimana successiva abbiamo notificato al Garante per la protezione dei dati personali e chiuso l'incidente con documentazione formale completa. Il cliente, a mente fredda, ha capito perché le due ore di attesa erano state la parte più importante di tutto l'intervento.
Perché "cancellare i file infetti e ripartire" è la prima cosa che ti distrugge l'incidente?
Il riflesso "cancello e riparto" è la trappola numero uno e quella che fa più danni strutturali. Cancellare i file infetti senza prima fare una raccolta strutturata di evidenze significa distruggere la possibilità di capire come l'attaccante è entrato, e quindi di evitare che rientri dalla stessa porta una settimana dopo - cosa che succede regolarmente nei clienti che hanno "pulito" un sito hackerato senza capire il vettore iniziale. Significa anche distruggere le evidenze necessarie a un'eventuale notifica al Garante per la protezione dei dati personali secondo l'articolo 33 del GDPR, che la tua PMI potrebbe essere obbligata a fare entro 72 ore se l'incidente ha coinvolto dati personali - e nella stragrande maggioranza dei siti PHP coinvolti, qualche dato personale c'è sempre, anche solo i form di contatto salvati a database.
Il protocollo che applico in queste situazioni è strutturato in cinque fasi tecniche più una giuridica, e segue lo schema del framework NIST SP 800-61 Rev. 2 Computer Security Incident Handling Guide, adattato alla scala di una PMI italiana che non ha un SOC interno né un team di response dedicato. Le fasi non sono opzionali né riordinabili: saltare anche un solo passaggio significa finire con un sistema che sembra pulito ma continua a essere compromesso, o con una notifica al Garante fatta senza poter descrivere cosa è successo.
Fase 1 - Contenimento immediato senza distruggere evidenze
Il contenimento ha un obiettivo unico: impedire all'attaccante di continuare a usare il sistema senza cancellare o modificare quello che ha già fatto. La tecnica corretta su un server Hetzner o OVH è isolare l'host a livello di rete senza spegnerlo, e senza toccare un solo byte del filesystem. Concretamente, il set di regole iptables che applico nei primi cinque minuti è il seguente, da eseguire molto attentamente perché un errore di ordinamento ti taglia fuori dal server:
iptables -I INPUT 1 -s IL_MIO_IP -j ACCEPT
iptables -I INPUT 2 -i lo -j ACCEPT
iptables -I INPUT 3 -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -P INPUT DROP
iptables -I OUTPUT 1 -o lo -j ACCEPT
iptables -I OUTPUT 2 -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -I OUTPUT 3 -p udp --dport 53 -j ACCEPT
iptables -I OUTPUT 4 -p tcp --dport 443 -d deb.debian.org -j ACCEPT
iptables -P OUTPUT DROP
iptables -L -n -v --line-numbersQuesto blocca in input tutto il traffico tranne la mia connessione SSH corrente e la loopback, e blocca in output tutto tranne DNS, pacchetti dei mirror aziendali e connessioni già stabilite. Le comunicazioni della webshell verso il C2 (command and control) sono immediatamente interrotte, il download di nuovi payload è impedito, e qualunque tentativo di esfiltrazione in corso viene troncato. Il server non è spento, non è rimosso nulla, non è modificato nessun file di applicazione. È la singola cosa più importante che si può fare nei primi cinque minuti.
L'errore tipico in questa fase è "spengo subito il server" oppure "stacco il cavo di rete dal pannello Hetzner Robot". Entrambe le cose sembrano prudenti e invece sono dannose: spegnere il server fa perdere tutto lo stato in memoria (processi attivi dell'attaccante, connessioni di rete in corso, payload caricati in /dev/shm o /tmp che vivono solo in RAM e non esistono sul disco), e cancella log di sicurezza che esistono solo in buffer volatili prima di essere flushati su disco. Il framework MITRE ATT&CK descrive nel dettaglio quanto stato attaccante vive solo in memoria e perché un dump del processo è spesso l'unico modo per ricostruire la catena di attacco. Sui server Hetzner Robot, il supporto può fornire (a pagamento) anche uno snapshot della macchina virtuale prima di qualsiasi operazione sul disco: vale i 50 euro che costa se l'incidente è serio.
Fase 2 - Forensics: raccolta strutturata delle evidenze
Una volta isolato il server a livello di rete, la Fase 2 è la raccolta forensics delle evidenze. Lo script che eseguo è preparato in anticipo - lo stesso per ogni cliente, versione controllata in un repository privato - e raccoglie in 10-15 minuti tutto quello che serve successivamente senza modificare nulla del filesystem. Il principio è: scrivere solo su un volume separato (/mnt/forensic), mai nella partizione di sistema, perché qualunque scrittura in /var o /tmp distorce il mtime di altri file e compromette la timeline degli eventi.
Gli artefatti critici che raccolgo sempre sono otto, nell'ordine in cui li eseguo:
FORENSIC=/mnt/forensic/incident-$(date +%Y%m%d-%H%M)
mkdir -p "$FORENSIC" && cd "$FORENSIC"
ps auxf > processes.txt
ss -tnp > connections.txt
cp -a /var/log/auth.log* /var/log/syslog* ./logs/
cp -a /var/log/nginx/ /var/log/apache2/ ./weblogs/ 2>/dev/null
find / -mtime -7 -type f 2>/dev/null > recent-files.txt
last -n 100 > last.txt && lastlog > lastlog.txt
cp -a /etc/passwd /etc/shadow /etc/sudoers.d/ ./etc/
for u in $(cut -d: -f1 /etc/passwd); do crontab -u "$u" -l 2>/dev/null \
| sed "s/^/$u: /" >> crontabs.txt; doneDa questa raccolta nascono tre documenti che produco entro la prima ora dall'isolamento: la timeline dell'attacco (quando è entrato, quale credenziale ha usato, quale exploit ha sfruttato), la lista degli indicatori di compromissione (file infetti con hash SHA256, IP di provenienza, domini C2, hash delle webshell), e la valutazione del danno (quali dati ha potuto vedere, quali ha potuto esfiltrare, se ha avuto accesso in scrittura al database). Il caso del cliente veronese del 2024 era esemplare proprio per la chiarezza della timeline: dai log auth.log si vedevano perfettamente le 03:41 del giovedì notte in cui un brute force SSH era riuscito a indovinare la password di un account di servizio creato due anni prima per uno script di import e mai cancellato dopo l'uso. Quel singolo dato ha cambiato tutta la valutazione del rischio: non era una vulnerabilità dell'applicazione Laravel, era una credenziale debole su un account di sistema dimenticato - un classico single point of failure umano nel ciclo di vita degli account che descrivo nel mio articolo sull'architettura cybersecurity per le PMI italiane.
Fase 3 - Eradicazione: mai pulire, sempre ricostruire
La Fase 3 è quella in cui cliente e consulente devono essere allineati su un principio non negoziabile: non si pulisce un sistema compromesso, si ricostruisce da zero su nuova infrastruttura. Tutti i tutorial online che promettono "10 step per pulire il tuo sito hackerato" sono pericolosi, perché un attaccante che è entrato una volta lascia quasi sempre più di un meccanismo di persistenza, e tu ne troverai uno o due ma non necessariamente tutti. Le webshell sofisticate hanno meccanismi di reinfection automatici: un cron job nascosto in un crontab utente non scontato che riscarica una shell ogni 6 ore se quella precedente è stata cancellata; un hook Git pre-commit che ricrea il backdoor se qualcuno fa un deploy; un modulo PAM modificato che apre un backdoor nella fase di autenticazione. Un singolo meccanismo di persistenza sopravvissuto alla "pulizia" significa che il sistema è ancora compromesso 24 ore dopo, indipendentemente da quanti file hai cancellato.
L'unica strategia che in dieci anni ho visto funzionare in modo affidabile è la seguente: provisioning di un server nuovo (Hetzner Robot ti consegna un nuovo dedicato in 30-60 minuti, OVH ancora più rapidamente tramite il pannello Bare Metal), installazione pulita dell'OS dalle ISO ufficiali, hardening minimo immediato nei primi 20 minuti, ripristino del codice da una versione fidata - idealmente dal repository Git ufficiale aziendale, mai dai file del server compromesso - ripristino del database da un backup anteriore alla data di compromissione (la timeline ricostruita in Fase 2 è esattamente ciò che ti dice quale backup usare senza portarsi dietro l'infezione), e poi switch del DNS al server nuovo con TTL bassato in anticipo. Lo so, è significativamente più lavoro che fare find . -name '*.php' -newer ... e cancellare a manina. Ma è l'unica strategia che funziona davvero ed è difendibile davanti a un auditor post-incidente. La ricostruzione è anche l'occasione operativa per applicare le fondamenta di un refactoring strutturato del codice PHP legacy, perché spesso il vecchio sistema aveva debiti tecnici che hanno facilitato la compromissione e che ora puoi rimuovere prima di rimettere il sito online.
Una nota critica sul backup: se il tuo backup più recente è già successivo alla data di compromissione - caso comune nei clienti che hanno backup giornalieri ma scoprono l'incidente solo dopo una settimana di persistenza silenziosa - il backup non può essere usato così com'è. Va ispezionato file per file prima dell'uso, oppure scartato in favore di uno snapshot più vecchio. Non è mai una situazione disperata, ma è significativamente più lavoro: ti costringe a usare il repository Git come fonte primaria del codice e a ricostruire i dati transazionali recenti partendo da snapshot più antichi più i log applicativi degli ultimi giorni.
Fase 4 - Hardening permanente: chiudere la porta da cui sono entrati
Una volta che il sito è nuovamente online sul server nuovo, la Fase 4 è l'hardening permanente. Il principio guida è semplice: chiudere prima di tutto la porta specifica da cui l'attaccante è entrato (identificata in Fase 2, non quella che tu sospetti), e poi applicare lo stack standard di difese. Nel caso del cliente veronese, la porta era SSH con autenticazione a password, e la fix immediata è stata disabilitare completamente l'autenticazione password per tutti gli utenti del sistema, forzare key-based con chiavi nuove generate per ogni operatore, cancellare ogni account di servizio non utilizzato attivamente, e mettere SSH dietro WireGuard accessibile solo da un bastion host dedicato:
sed -i 's/^#*PasswordAuthentication.*/PasswordAuthentication no/' \
/etc/ssh/sshd_config
sed -i 's/^#*PermitRootLogin.*/PermitRootLogin prohibit-password/' \
/etc/ssh/sshd_config
sed -i 's/^#*PubkeyAuthentication.*/PubkeyAuthentication yes/' \
/etc/ssh/sshd_config
echo "AllowUsers maurizio deploy" >> /etc/ssh/sshd_config
sshd -t && systemctl restart sshLe altre cinque misure dello stack standard che applico sempre nella Fase 4 di un post-incidente sono: fail2ban configurato per SSH e per l'applicazione web con ban duraturi (minimo 24 ore, non i default di 10 minuti); ModSecurity con OWASP CRS davanti a Nginx o Apache, partendo con paranoia level 1 e alzandolo gradualmente; Let's Encrypt con --must-staple per lo strict OCSP; log forwarding centralizzato verso un host dedicato (in modo che un futuro attaccante non possa cancellare i log dal server target, perché i log vivono già altrove dopo pochi secondi); e un sistema di backup verificato con restore test reale almeno mensile, non solo il tar nominale che nessuno ha mai provato a ripristinare. Tutte queste misure sono parte della checklist di hardening Laravel/Symfony NIS2-ready che applico nei primi 14 giorni di un progetto cliente, declinata qui in versione post-incidente: la differenza operativa è che dopo un incidente reale il cliente non discute più sull'opportunità di queste misure, accetta il piano senza obiezioni sul budget.
Fase 5 - Notifica al Garante e chiusura formale dell'incidente
La Fase 5 è quella che molti consulenti tecnici sottovalutano e che invece, sotto NIS2 in vigore dal gennaio 2025, è diventata critica e potenzialmente sanzionabile. Se l'incidente ha coinvolto dati personali - e nella maggior parte dei siti PHP coinvolge dati personali (utenti registrati, email, numeri di telefono, indirizzi di spedizione, in qualche caso carte di credito) - la PMI titolare del trattamento ha l'obbligo di valutare la notifica al Garante entro 72 ore dalla scoperta dell'incidente, secondo l'articolo 33 del GDPR. La decisione finale di notificare o meno spetta formalmente al titolare (cioè al cliente, non al consulente tecnico), ma la responsabilità di fornirgli le informazioni necessarie per decidere in modo informato è del consulente.
I tre dati che produco sempre per il cliente in Fase 5, in un singolo documento PDF formale firmato, sono: cosa è successo (timeline puntuale dei fatti basata sui log di forensics della Fase 2), quali dati sono stati potenzialmente coinvolti (lista delle tabelle MySQL accedute o esfiltrate, basata sui log di MySQL e di applicazione), e quali soggetti interessati sono coinvolti (numero stimato di utenti i cui dati personali erano potenzialmente esposti, e categorie di dati). Sotto la direttiva NIS2 (UE) 2022/2555 consultabile su EUR-Lex, gli obblighi di notifica si estendono ulteriormente a una notifica preliminare entro 24 ore al CSIRT Italia per gli incidenti che hanno impatto significativo sulla continuità del servizio, anche senza coinvolgimento di dati personali. È un layer giuridico in più che si aggiunge al GDPR e che molte PMI italiane non sanno ancora di dover applicare. Il flusso completo di notifica è esattamente quello che descrivo nel mio protocollo di incident response in 72 ore conforme NIS2 per PMI su stack Laravel e Symfony, che è il complemento naturale di questo articolo.
Un sito PHP hackerato su un server dedicato Hetzner o OVH non è un evento raro: è il risultato statisticamente atteso quando una PMI gestisce infrastruttura tecnica con processi informali, un solo account di servizio dimenticato o una libreria non aggiornata da diciotto mesi. Le cinque fasi che ho descritto - contenimento senza distruggere evidenze, forensics strutturata, eradicazione per ricostruzione, hardening orientato al vettore reale, notifica formale - sono il distillato di una decina di incidenti gestiti negli ultimi anni e si applicano sempre, indipendentemente dal vettore di compromissione iniziale. Se la tua PMI ha un sito PHP critico in produzione e non hai mai testato cosa succede in caso di compromissione - incluso il piano di provisioning di un server nuovo, la procedura di forensics, e la decisione di notifica al Garante - scopri come lavoro con i clienti su prevenzione e risposta agli incidenti di sicurezza web: in dieci anni di consulenza ho osservato che il fattore decisivo nella severità finale di un incidente è quasi sempre la velocità e la disciplina della reazione nelle prime due ore, non la quantità di tool di sicurezza comprati negli anni precedenti. Se invece sei già nell'incidente in questo momento e ti serve un consulente che applichi sul tuo progetto il protocollo che ho appena descritto, contattami per una consulenza: in caso di emergenza posso essere operativo entro poche ore con isolamento del server a livello di rete, raccolta forensics delle evidenze su volume separato e ricostruzione su infrastruttura nuova, riducendo al minimo il downtime e proteggendo le evidenze necessarie alla successiva notifica al Garante.