Gestione urgente di intrusioni su VPS: guida al ripristino rapido e sicuro per server Debian e Ubuntu
Il 22 giugno 2025, alle 6:40 del mattino, mi ha chiamato il titolare di una PMI piemontese che vende componenti per automazione industriale tramite un e-commerce B2B basato su Laravel 10. Il VPS era un Hetzner CPX41 - 8 vCPU AMD, 16 GB di RAM, 240 GB NVMe - con Debian 12 Bookworm, Nginx 1.22, PHP-FPM 8.2, MySQL 8.0 e Redis 7. Da circa quattro ore il sito non rispondeva: i clienti vedevano un timeout Cloudflare 522, il pannello Hetzner Cloud mostrava CPU al 100% costante, e il titolare non riusciva nemmeno a connettersi via SSH perché la sessione si bloccava dopo l'handshake. Il monitoring - un semplice script bash che faceva curl ogni cinque minuti e mandava email su fallimento - non aveva funzionato perché il relay SMTP sul server stesso era saturato. Il fatturato medio giornaliero del portale era intorno ai 12.000 euro, e ogni ora di downtime significava ordini persi verso competitor che avevano già ricevuto i listini aggiornati.
Ho gestito l'incidente seguendo il framework descritto nel NIST SP 800-61 Rev. 3, pubblicato nell'aprile 2025, che organizza la risposta in fasi strutturate: preparazione, rilevamento e analisi, contenimento-eradicazione-ripristino, e attività post-incidente. In questo articolo ti racconto ogni fase con i comandi reali, i tempi effettivi e le decisioni operative che ho preso - perché quando ti trovi con un server compromesso alle sei del mattino, la differenza tra un ripristino in quattro ore e un disastro che dura settimane sta nella sequenza precisa delle azioni.
Stai cercando un Consulente Informatico esperto per gestire un'emergenza di sicurezza sulla tua infrastruttura? Nel mio profilo professionale trovi l'esperienza concreta su incident response, hardening Linux e recupero di server compromessi presso Hetzner, OVH, Contabo e Digital Ocean. Contattami per una consulenza diretta.
Come si riconosce un'intrusione su un VPS unmanaged e perché i primi 15 minuti sono decisivi?
Un'intrusione su VPS unmanaged si manifesta quasi sempre con uno o più di questi segnali: consumo anomalo di CPU o RAM senza correlazione con il traffico applicativo, processi sconosciuti visibili con ps aux, connessioni di rete verso IP esterni su porte non standard, login SSH riusciti da IP non riconosciuti in /var/log/auth.log, file modificati di recente in directory di sistema come /tmp, /var/tmp o /dev/shm, e crontab con entry che non hai inserito tu. I primi quindici minuti dopo la scoperta sono critici perché l'attaccante, se ancora attivo, può esfiltrare dati, installare backdoor persistenti o cancellare i log che ti servono per capire cosa sia successo.
Nel caso del cliente piemontese, il primo segnale era stato la CPU al 100%. Ma il problema reale era un altro: qualcuno aveva effettuato un login SSH con l'utente deploy - un utente tecnico creato dal vecchio sysadmin con password debole e autenticazione a password abilitata - e aveva installato un cryptominer XMRig nella directory /dev/shm/.x/, nascosto con un nome di processo camuffato da [kworker/0:1] per sembrare un thread del kernel. Il crontab dell'utente www-data era stato modificato per scaricare e rieseguire il miner ogni quindici minuti da un server di command-and-control su un dominio .ru.
Il contenimento immediato: isolare senza distruggere le evidenze
La tentazione in questi casi è riavviare il server o spegnere tutto. È un errore grave: il riavvio cancella la memoria volatile, i processi attivi e le connessioni di rete che sono le tue prove primarie. Invece, la prima azione è l'isolamento di rete chirurgico. Ho effettuato l'accesso tramite la console VNC del pannello Hetzner Cloud - che funziona anche quando SSH è saturato - e ho eseguito questa sequenza:
# Fase 1: snapshot delle connessioni attive PRIMA di toccare qualsiasi cosa
ss -tulpan > /root/incident-connections-$(date +%Y%m%d-%H%M).txt
ps auxf > /root/incident-processes-$(date +%Y%m%d-%H%M).txt
last -a > /root/incident-logins-$(date +%Y%m%d-%H%M).txt
cat /var/log/auth.log | grep "Accepted" > /root/incident-accepted-ssh.txt
# Fase 2: bloccare TUTTO il traffico in ingresso tranne il mio IP
iptables -F
iptables -A INPUT -s 93.XX.XX.XX -j ACCEPT # il mio IP
iptables -A INPUT -i lo -j ACCEPT
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -P INPUT DROP
iptables -P FORWARD DROP
# Fase 3: kill del processo miner (dopo aver salvato le evidenze)
kill -9 $(pgrep -f '.x/kworker')L'ordine è fondamentale: prima salvi le evidenze, poi isoli la rete, poi fermi i processi malevoli. Se inverti la sequenza perdi informazioni irrecuperabili. Ho anche copiato l'intero contenuto di /dev/shm/.x/ in /root/evidence/ prima di rimuoverlo, perché quei file - il binario del miner, lo script di download, il file di configurazione con l'indirizzo del wallet Monero - sono le prove che ti servono sia per l'analisi forense sia per un'eventuale denuncia alla Polizia Postale.
La forensics: ricostruire la cronologia dell'attacco
Con il server isolato e il miner fermato, la CPU è tornata al 3% e SSH era di nuovo raggiungibile normalmente. Ho dedicato l'ora successiva alla ricostruzione cronologica dell'attacco, seguendo la metodologia del SANS Intrusion Discovery Cheat Sheet for Linux - un riferimento che consiglio a chiunque gestisca server Linux in produzione.
La ricostruzione ha rivelato una sequenza chiara. L'attaccante aveva iniziato un brute force SSH contro l'utente deploy il 18 giugno, quattro giorni prima della mia chiamata. Il file auth.log mostrava circa 8.000 tentativi al giorno da un pool di IP geolocalizzati in Ucraina e Russia, distribuiti su più sottoreti per evitare il ban di Fail2ban - che peraltro non era installato su questa macchina. Il 20 giugno alle 02:14 il login era riuscito: password indovinata, probabilmente da un dizionario standard. Da quel momento l'attaccante aveva operato in meno di tre minuti:
# Ricostruzione dall'analisi dei log e dei file timestamp
# 02:14:03 - Login SSH utente deploy da 91.243.XX.XX
# 02:14:18 - wget da server C2 verso /dev/shm/.x/
# 02:14:31 - chmod +x /dev/shm/.x/kworker
# 02:14:44 - Esecuzione del miner
# 02:15:01 - Inserimento crontab www-data (privilege escalation via sudo misconfiguration)
# 02:15:12 - Disconnessione SSHIl punto critico era l'escalation di privilegi: l'utente deploy aveva una regola in /etc/sudoers.d/deploy che gli permetteva di eseguire sudo -u www-data crontab -e senza password - una configurazione inserita dal vecchio sysadmin per semplificare i deploy automatici. L'attaccante l'aveva sfruttata per aggiungere la persistenza nel crontab di www-data, rendendo il miner immune a un semplice kill del processo.
Ho verificato anche l'integrità del filesystem con Lynis, uno strumento di auditing open source che considero indispensabile su qualsiasi server Debian o Ubuntu in produzione:
apt install -y lynis
lynis audit system --quickIl report Lynis ha confermato quello che sospettavo: il server aveva un hardening index di 47 su 100, con criticità su SSH (autenticazione a password abilitata, PermitRootLogin yes), assenza di IDS/IPS, nessun file integrity monitoring, e kernel parameters non ottimizzati. Era un VPS tirato su due anni prima con una configurazione stock di Debian e mai sottoposto ad hardening strutturato.
Eradicazione: eliminare ogni traccia dell'attaccante dal sistema
La fase di eradicazione è quella in cui la maggior parte delle risposte fai-da-te fallisce. Non basta rimuovere il file del miner e il crontab malevolo: se l'attaccante ha avuto accesso shell per quattro giorni, devi verificare sistematicamente ogni possibile punto di persistenza. La mia checklist di eradicazione per intrusioni su Debian e Ubuntu comprende nove controlli che eseguo in sequenza rigorosa:
# 1. Crontab di TUTTI gli utenti
for u in $(cut -d: -f1 /etc/passwd); do
echo "=== crontab $u ===" && crontab -u "$u" -l 2>/dev/null
done
cat /etc/crontab
ls -la /etc/cron.d/ /etc/cron.daily/ /etc/cron.hourly/
# 2. Chiavi SSH autorizzate di tutti gli utenti con shell
grep -E "/bash|/sh" /etc/passwd | cut -d: -f1,6 | while IFS=: read user home; do
echo "=== $user ===" && cat "$home/.ssh/authorized_keys" 2>/dev/null
done
# 3. Servizi systemd non standard
systemctl list-unit-files --type=service --state=enabled | grep -v "^lib/"
# 4. File SUID/SGID anomali
find / -type f \( -perm -4000 -o -perm -2000 \) -exec ls -la {} \; 2>/dev/null
# 5. File modificati nelle ultime 96 ore in directory di sistema
find /etc /usr/local/bin /var/spool -mtime -4 -type f -ls
# 6. Processi con path sospetti
ls -la /proc/*/exe 2>/dev/null | grep -v -E "(usr|lib|sbin)"
# 7. Moduli kernel caricati (possibili rootkit)
lsmod | grep -v -E "^(Module|ext4|dm_|raid|md_|nf_|xt_|ip_|tcp_)"
# 8. Utenti con UID 0 diversi da root
awk -F: '$3 == 0 && $1 != "root"' /etc/passwd
# 9. Verificare /etc/ld.so.preload (LD_PRELOAD hijacking)
cat /etc/ld.so.preload 2>/dev/nullNel caso specifico, l'eradicazione ha scoperto due elementi in più rispetto al miner: una chiave SSH pubblica dell'attaccante inserita in /home/deploy/.ssh/authorized_keys (accesso persistente anche dopo cambio password), e un alias bash in /home/deploy/.bashrc che mascherava ps con una versione filtrata che nascondeva i processi contenenti "kworker". Entrambi rimossi, documentati nel report.
Ho anche eseguito una scansione con rkhunter per escludere la presenza di rootkit kernel-level - nel nostro caso il risultato era pulito, il che confermava che l'attaccante era un operatore di cryptomining opportunistico, non un gruppo APT con capacità avanzate. Questa distinzione è importante perché determina il livello di paranoia della risposta: per un cryptominer la reinstallazione completa è raramente necessaria se la forensics è stata accurata, mentre per un rootkit kernel o un attacco mirato la reinstallazione da zero è l'unica opzione sicura.
Il ripristino: tornare operativi senza reintrodurre la vulnerabilità
Il ripristino non significa "riaccendere tutto come prima". Significa riportare il servizio online eliminando il vettore di attacco originale. Nel nostro caso il vettore era l'autenticazione SSH a password sull'utente deploy. Prima di riaprire il traffico ho applicato queste modifiche:
# Disabilitare autenticazione a password globalmente
sed -i 's/^#*PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config
sed -i 's/^#*PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config
sed -i 's/^#*PubkeyAuthentication.*/PubkeyAuthentication yes/' /etc/ssh/sshd_config
# Limitare gli utenti che possono fare SSH
echo "AllowUsers maurizio" >> /etc/ssh/sshd_config
# Verificare la configurazione prima di riavviare
sshd -t && systemctl restart sshdPoi ho installato e configurato Fail2ban con jail aggressivi per SSH e per il login web di Laravel - una configurazione che ho descritto in dettaglio nell'articolo su Fail2ban fermo su VPS con Laravel. Ho anche ruotato tutte le credenziali: password MySQL degli utenti applicativi, token API di terze parti memorizzati nel .env di Laravel, chiave APP_KEY di Laravel (con re-encryption dei dati cifrati), e password dell'utente admin del pannello e-commerce.
Il ripristino del servizio è stato graduale. Prima ho riaperto il traffico verso la porta 443 per verificare che il sito funzionasse correttamente, poi la porta 80 per il redirect HTTPS, e solo dopo aver verificato che tutto era stabile ho rimosso le regole iptables temporanee e attivato il firewall definitivo con ufw:
ufw default deny incoming
ufw default allow outgoing
ufw allow from 93.XX.XX.XX to any port 22 proto tcp # SSH solo dal mio IP
ufw allow 80/tcp
ufw allow 443/tcp
ufw enableIl sito è tornato operativo alle 10:47, quattro ore e sette minuti dopo la mia prima connessione. Il downtime totale dall'inizio dell'incidente - contando le quattro ore notturne in cui nessuno se ne era accorto - era stato di circa otto ore.
Hardening post-incidente: le misure che ho implementato nella settimana successiva
Il contenimento e il ripristino sono l'emergenza. L'hardening post-incidente è ciò che impedisce che succeda di nuovo. Nella settimana successiva ho implementato un piano strutturato in quattro aree, seguendo la stessa logica che applico nella checklist di hardening in quattordici giorni per PMI.
Accesso e autenticazione. SSH esclusivamente a chiave ED25519, porta cambiata dalla 22 a una non standard, accesso limitato a due IP statici (il mio e quello dell'ufficio del cliente) via AllowUsers e ufw, MFA con Google Authenticator per l'accesso al pannello admin Laravel. L'utente deploy è stato eliminato e sostituito con un utente dedicato senza accesso interattivo, usato solo per il deploy automatizzato via GitHub Actions con chiave deploy-only.
Monitoring e rilevamento. Ho installato OSSEC come HIDS (Host-based Intrusion Detection System), configurato per monitorare le modifiche ai file di sistema, i login SSH, le escalation di privilegi e le modifiche ai crontab. L'alerting va verso un canale Telegram del titolare e verso la mia email, con severità differenziate: le modifiche ai file di configurazione in /etc generano un alert immediato, le scansioni di porte un report giornaliero. In parallelo, ho configurato auditd per il logging granulare delle syscall critiche - un livello di visibilità che auth.log da solo non può dare.
Backup immutabili. Ho configurato BorgBackup verso una Hetzner Storage Box con append-only mode attivo: il server di produzione può creare nuovi backup ma non può cancellare o modificare quelli esistenti. Questo è fondamentale perché se un attaccante ottiene accesso root, la prima cosa che fa un operatore sofisticato è cancellare i backup per impedire il ripristino. Con append-only, i backup sopravvivono anche a un compromesso totale del server. La retention è 7 giornalieri + 4 settimanali + 6 mensili, con test di ripristino automatizzato ogni domenica notte.
Patch management. Ho abilitato unattended-upgrades per i pacchetti di sicurezza Debian, con email di notifica al titolare per ogni aggiornamento applicato. I pacchetti applicativi (Nginx, PHP, MySQL) li gestisco manualmente con cadenza quindicinale, perché un aggiornamento automatico di MySQL su un server di produzione è un rischio che non vale la comodità. Ho anche verificato che OpenSSH fosse aggiornato all'ultima versione patchata - dopo la vulnerabilità regreSSHion (CVE-2024-6387) del luglio 2024, considero il monitoraggio delle versioni SSH una priorità assoluta.
Per nuovi clienti Hetzner, puoi ottenere €20.00 di credito gratuito utilizzando questo link con codice sconto - una scelta ideale per aziende europee che necessitano conformità GDPR e data center in Germania.
Cosa ho imparato (ancora una volta) sulla gestione delle intrusioni nelle PMI
La lezione più importante di questo incidente non è tecnica - è organizzativa. Il server era stato compromesso per quattro giorni prima che qualcuno se ne accorgesse, e l'unico motivo per cui il titolare ha chiamato è che il miner aveva saturato la CPU al punto da rendere il sito irraggiungibile. Se l'attaccante avesse limitato l'uso della CPU al 30%, il miner sarebbe rimasto attivo per mesi senza che nessuno lo notasse. Questo è il pattern più comune che vedo nelle PMI: non è l'attacco sofisticato che fa danni, è l'assenza totale di monitoring e di un piano di incident response strutturato.
Un secondo punto che vale la pena sottolineare: l'autenticazione SSH a password su un VPS esposto a internet è, nel 2025, un invito a nozze per qualsiasi scanner automatizzato. I tool di brute force come Hydra e Medusa testano milioni di combinazioni al giorno, e le botnet che li operano scansionano l'intero spazio IPv4 in meno di 45 minuti. Se il tuo VPS ha anche solo un utente con password debole e SSH sulla porta 22, la domanda non è se verrà compromesso, ma quando.
La terza lezione riguarda i backup. Il cliente piemontese aveva un backup - un mysqldump giornaliero salvato nella stessa macchina, nella directory /var/backups/. In caso di ransomware o di cancellazione deliberata da parte dell'attaccante, quel backup sarebbe stato distrutto insieme al resto. Un backup che risiede sullo stesso server che deve proteggere non è un backup: è un'illusione di sicurezza. La regola minima è la 3-2-1: tre copie dei dati, su due supporti diversi, di cui una offsite. Con BorgBackup e una Storage Box da 1 TB il costo è inferiore a 5 euro al mese - un investimento irrisorio rispetto al costo di un giorno di downtime.
Se gestisci un VPS unmanaged presso Hetzner, OVH, Contabo, Digital Ocean o Aruba e sospetti un'intrusione - oppure vuoi prevenirla con un hardening serio prima che accada - la cosa peggiore che puoi fare è improvvisare. Un piano di incident management strutturato, testato e documentato è la differenza tra quattro ore di downtime e quattro settimane di caos. Nel mio lavoro di consulente ho gestito decine di incidenti su infrastrutture PHP/Laravel di PMI italiane, e l'esperienza mi ha insegnato che la preparazione vale dieci volte più della reazione. Se vuoi mettere in sicurezza la tua infrastruttura prima che sia troppo tardi, contattami.