Gestione urgente di intrusioni su VPS: guida al ripristino rapido e sicuro per server Debian e Ubuntu

Gestione urgente di intrusioni su VPS: guida al ripristino rapido e sicuro per server Debian e Ubuntu

Mi ha chiamato all'alba il titolare di una PMI del Nord Italia che gestisce un e-commerce B2B basato su Laravel 10. Il VPS era un Cloud unmanaged da 8 vCPU, 16 GB di RAM e 240 GB di NVMe, con Debian 12 Bookworm, Nginx 1.22, PHP-FPM 8.2, MySQL 8.0 e Redis 7. Da qualche ora il sito non rispondeva: i clienti vedevano un timeout 522 di Cloudflare, il pannello del provider mostrava CPU al 100% costante, e il titolare non riusciva nemmeno a connettersi via SSH perché la sessione si bloccava subito dopo l'handshake. Il "monitoring" era uno script bash che faceva curl ogni cinque minuti e mandava una mail su fallimento, e non aveva avvisato nessuno perché il relay SMTP girava sul server stesso, ormai saturo. Il fatturato medio giornaliero del portale era nell'ordine delle decine di migliaia di euro, e ogni ora di blocco significava ordini persi verso competitor che avevano già i listini aggiornati.

Su un VPS unmanaged non esiste un reparto che interviene al posto tuo: il sistema operativo, le patch, il firewall, i backup e la sicurezza sono interamente tua responsabilità. Quando una macchina così viene compromessa, la differenza tra un ripristino in poche ore e un disastro che si trascina per settimane sta quasi tutta nella sequenza precisa delle azioni: cosa tocchi, in che ordine, e soprattutto cosa NON tocchi. In questo articolo ti racconto il playbook che applico in questi casi, fase per fase, con i comandi reali e la logica difensiva dietro ognuno, organizzato secondo il ciclo di vita della risposta agli incidenti del NIST SP 800-61 Rev. 3, pubblicato nell'aprile 2025 e riallineato al Cybersecurity Framework 2.0.

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.

Cosa NON devi fare nei primi minuti dopo aver scoperto un VPS compromesso?

La risposta più importante di tutto l'articolo è una negazione: non riavviare il server e non spegnere nulla. L'istinto, davanti a una macchina che brucia CPU e non risponde, è "stacco e riaccendo". È l'errore che distrugge il caso. Un riavvio azzera la memoria volatile, termina i processi attivi, chiude le connessioni di rete e spesso ripulisce le directory in RAM: cancelli cioè esattamente le tue prove primarie e, peggio, perdi la possibilità di capire come l'attaccante è entrato e cosa ha toccato. La seconda cosa da non fare è iniziare a "pulire" a caso file e cartelle: senza prima fotografare lo stato del sistema, ogni rm cancella un indizio.

Il triage corretto è un'altra cosa. Significa stabilizzare l'incidente senza alterare lo stato del sistema più del necessario, e procedere in un ordine che preserva l'informazione: prima si osserva e si registra, poi si contiene la rete, e solo alla fine si fermano i processi malevoli. Su un VPS unmanaged hai un alleato che molti dimenticano: la console out-of-band del provider (VNC, KVM o seriale dal pannello), che funziona anche quando SSH è saturo o l'attaccante ha sabotato il demone. È da lì che ho lavorato in questo caso, perché la sessione SSH normale era inutilizzabile.

Nel concreto, il primo segnale era stato la CPU al 100%. Ma la causa era un'altra: qualcuno aveva fatto login SSH con l'utente deploy, un account tecnico creato in passato con password debole e autenticazione a password abilitata, e aveva piazzato un cryptominer in una directory nascosta dentro /dev/shm. Il processo era camuffato con un nome che imitava un thread del kernel ([kworker/0:1]), una tecnica di masquerading classica: i veri kworker sono thread del kernel e in ps se ne vedono in continuazione, quindi un processo con quel nome passa inosservato a un occhio distratto.

Il contenimento: isolare la rete e congelare lo stato

La prima sequenza che eseguo, dalla console del provider, registra lo stato volatile e poi taglia la rete. L'ordine è il messaggio: evidenze prima, isolamento poi, kill per ultimo.

# 1. Fotografia dello stato volatile PRIMA di toccare qualsiasi cosa
ss -tulpan > /root/ir-connessioni-$(date +%Y%m%d-%H%M).txt
ps auxf  > /root/ir-processi-$(date +%Y%m%d-%H%M).txt
last -a  > /root/ir-login-$(date +%Y%m%d-%H%M).txt
grep "Accepted" /var/log/auth.log > /root/ir-ssh-accettati.txt

# 2. Bloccare TUTTO il traffico in ingresso tranne il mio IP di gestione
iptables -F
iptables -A INPUT -i lo -j ACCEPT
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -s 93.0.0.0/32 -j ACCEPT   # placeholder: il MIO IP, non quello vero
iptables -P INPUT DROP
iptables -P FORWARD DROP

Tagliando il traffico in ingresso (e idealmente anche l'egress verso il command-and-control) si ottiene il risultato che conta davvero: l'attaccante non può più impartire comandi e il miner non può più contattare il pool di mining né riscaricarsi. Solo a questo punto, e solo dopo aver copiato l'intera directory sospetta in un'area di evidenze, fermo il processo:

# 3. Conservare le evidenze, POI fermare il miner
mkdir -p /root/evidence
cp -a /dev/shm/.x/ /root/evidence/   # binario, script di download, config con il wallet
kill -9 $(pgrep -f '/dev/shm/.x/')

Il binario del miner, lo script di download e il file di configurazione con l'indirizzo del wallet Monero non sono spazzatura da cestinare: sono le prove che servono sia per la ricostruzione tecnica sia, eventualmente, per una denuncia alla Polizia Postale. Se inverti la sequenza (fermi prima e salvi dopo) perdi informazioni irrecuperabili, perché un file in /dev/shm vive in RAM e svanisce al primo riavvio o alla prima ripulitura.

Come si ricostruisce la cronologia di un attacco a un VPS?

Con il server isolato e il miner fermo, la CPU è tornata a percentuali normali e SSH era di nuovo raggiungibile. A quel punto la domanda da porsi è una sola: come è entrato, quando, e cosa ha toccato? Si risponde incrociando i log di sistema con i timestamp del filesystem, seguendo una metodologia di triage Linux ben rodata come quella del SANS Intrusion Discovery Cheat Sheet for Linux, un riferimento che tengo sempre a portata di mano.

La ricostruzione ha raccontato una storia lineare. L'ingresso era avvenuto via brute force SSH contro l'utente deploy: auth.log mostrava migliaia di tentativi al giorno da un pool di IP geolocalizzati tra Ucraina e Russia, distribuiti su più sottoreti per aggirare un eventuale ban automatico, che peraltro su questa macchina non c'era perché Fail2ban non era installato. Dopo qualche giorno la password è stata indovinata, probabilmente da un dizionario standard. Da quel momento l'attaccante ha operato in meno di tre minuti: login, download del payload da un server di comando in /dev/shm, chmod +x, esecuzione del miner, inserimento della persistenza nel crontab, disconnessione.

Il punto critico è stato l'escalation di privilegi. L'utente deploy aveva una regola in /etc/sudoers.d/deploy che gli permetteva di eseguire un comando come www-data senza password, una scorciatoia inserita in passato per semplificare i deploy automatici. L'attaccante l'ha sfruttata per scrivere la persistenza nel crontab di www-data, in modo che il miner si rilanciasse da solo anche dopo un semplice kill. È il pattern di gran lunga più comune: l'attaccante sa che gli ucciderai il processo, quindi accoppia sempre il payload a un watchdog in cron che lo rimette in piedi a intervalli regolari. Questo, dalla prospettiva difensiva, è il vero motivo per cui fermare il processo non è "risolvere": se non rimuovi il meccanismo che lo rilancia, in pochi minuti torna.

Per inquadrare la postura complessiva della macchina ho lanciato un audit con Lynis, strumento open source che considero indispensabile su qualsiasi server Debian o Ubuntu in produzione:

apt install -y lynis
lynis audit system --quick

Il report ha confermato i sospetti: hardening index basso, SSH con autenticazione a password e PermitRootLogin yes, nessun IDS, nessun file integrity monitoring, parametri del kernel di default. Era un VPS tirato su tempo prima con una configurazione stock di Debian e mai sottoposto a un hardening strutturato. Vale la pena fermarsi un attimo qui: la quasi totalità delle compromissioni che vedo nelle PMI non nasce da exploit sofisticati, ma da configurazioni di default lasciate esposte a internet. Non serve un attaccante bravo per bucare un server che chiunque può bucare.

La domanda corretta su un VPS esposto non è "verrò attaccato?", ma "quando il prossimo scanner automatizzato troverà la mia porta 22, troverà anche una password debole?". Le botnet scansionano l'intero spazio IPv4 in meno di un'ora: la superficie sbagliata viene trovata in giornata, non in anni.

Eradicazione: trovare ogni punto di persistenza, non solo il miner

La fase di eradicazione è dove la maggior parte delle risposte fai-da-te fallisce. Rimuovere il file del miner e il crontab malevolo è il minimo, non la fine. Se l'attaccante ha avuto una shell per giorni, devi verificare sistematicamente ogni meccanismo con cui un intruso si garantisce il ritorno. La mia checklist di eradicazione per intrusioni su Debian e Ubuntu è una sequenza fissa di controlli, perché la persistenza si annida nei posti più disparati e affidarsi alla memoria significa dimenticarne sempre uno:

# 1. Crontab di TUTTI gli utenti + cron di sistema
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 ogni utente con shell
grep -E "/bash|/sh" /etc/passwd | cut -d: -f1,6 | while IFS=: read -r user home; do
    echo "=== $user ===" && cat "$home/.ssh/authorized_keys" 2>/dev/null
done

# 3. Servizi systemd abilitati non standard
systemctl list-unit-files --type=service --state=enabled

# 4. Binari SUID/SGID anomali
find / -xdev -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 il cui binario su disco e' sospetto o assente
ls -la /proc/*/exe 2>/dev/null | grep -vE "(usr|lib|sbin|bin)"

# 7. Moduli kernel caricati (possibili rootkit)
lsmod

# 8. Utenti con UID 0 diversi da root (backdoor classica)
awk -F: '$3 == 0 && $1 != "root"' /etc/passwd

# 9. Hijacking del linker dinamico via LD_PRELOAD
cat /etc/ld.so.preload 2>/dev/null

Il controllo numero 6 merita una nota offensiva, perché è la chiave per smascherare il masquerading da kworker: un vero thread del kernel non ha un eseguibile su disco, quindi il link /proc/<pid>/exe di un kworker legittimo punta a nulla. Se invece trovi un processo chiamato kworker il cui /proc/<pid>/exe risolve a un file reale, soprattutto se quel file vive in /tmp, /var/tmp o /dev/shm, hai trovato il miner. È il segnale ad altissima affidabilità: non ti fidi del nome del processo, ti fidi del binario che c'è davvero dietro.

Nel caso specifico l'eradicazione ha scoperto due elementi oltre al miner: una chiave SSH pubblica dell'attaccante infilata in /home/deploy/.ssh/authorized_keys, che gli garantiva l'accesso anche dopo un cambio password, e un alias in /home/deploy/.bashrc che sostituiva ps con una versione filtrata per nascondere i processi contenenti kworker. Entrambi rimossi e documentati. Per chiudere, ho fatto una scansione anti-rootkit incrociando due strumenti complementari, rkhunter e chkrootkit, perché ognuno copre signature diverse e nessuno dei due, da solo, è esaustivo:

apt install -y rkhunter chkrootkit
rkhunter --update && rkhunter --check --skip-keypress
chkrootkit

Il risultato era pulito, il che confermava la natura dell'avversario: un operatore di cryptomining opportunistico, non un gruppo APT con capacità avanzate. La distinzione è operativa, non accademica: per un cryptominer, se la forensics è stata accurata, la reinstallazione completa è raramente necessaria; per un rootkit kernel-level o un attacco mirato, l'unica risposta sicura è la reinstall from scratch con ripristino dei soli dati verificati, perché non puoi fidarti più di nessun binario sul sistema.

Il ripristino: tornare operativi senza reintrodurre il vettore

Il ripristino non è "riaccendere tutto come prima". È riportare il servizio online eliminando la causa radice dell'incidente. Nel nostro caso la causa era l'autenticazione SSH a password sull'utente deploy. Prima di riaprire il traffico ho chiuso quel vettore:

# Disabilitare password e root login, imporre solo chiavi
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

# Restringere chi puo' fare SSH
echo "AllowUsers maurizio" >> /etc/ssh/sshd_config

# Validare la config PRIMA di riavviare (evita di chiudersi fuori)
sshd -t && systemctl restart ssh

Poi ho installato Fail2ban con jail per SSH e per il login applicativo, e ho ruotato tutte le credenziali: password degli utenti MySQL applicativi, token API di terze parti nel .env di Laravel, APP_KEY di Laravel (con re-encryption dei dati cifrati) e password dell'admin del pannello e-commerce. Questo passaggio è non negoziabile: dopo giorni di shell con privilegi, devi assumere che ogni segreto presente sul disco sia compromesso. Il ripristino del servizio è stato graduale, una porta alla volta, sostituendo le regole iptables temporanee con un firewall pulito una volta verificata la stabilità:

ufw default deny incoming
ufw default allow outgoing
ufw allow from 93.0.0.0/32 to any port 22 proto tcp   # SSH solo dal mio IP
ufw allow 80/tcp
ufw allow 443/tcp
ufw enable

Il sito è tornato operativo in poche ore dalla prima connessione. Il dato scomodo, però, era un altro: per la maggior parte del downtime nessuno si era accorto di nulla. Se l'attaccante avesse capato la CPU al 30% invece che al 100%, il miner sarebbe rimasto attivo per mesi, in silenzio, senza che il sito desse mai segni di sofferenza. È questo l'incubo reale, non il blocco rumoroso: il compromesso invisibile.

Hardening post-incidente: impedire che succeda di nuovo

Il contenimento e il ripristino sono l'emergenza. L'hardening post-incidente è ciò che trasforma l'incidente in un evento irripetibile, e nel ciclo NIST è la fase di miglioramento continuo. Nella settimana successiva ho implementato un piano su quattro aree, la stessa logica di una checklist di hardening sistematica per PMI.

Accesso e autenticazione: SSH esclusivamente a chiave ED25519, porta spostata dalla 22, accesso limitato a due IP statici via AllowUsers e firewall, MFA con applicazione TOTP per il pannello admin. L'utente deploy è stato eliminato e sostituito con un account dedicato senza shell interattiva, usato solo per il deploy automatizzato con una chiave deploy-only e privilegi minimi. La regola di sudoers che concedeva escalation senza password è sparita: ogni concessione sudo ora è esplicita, motivata e a privilegio minimo. Le tecniche avanzate per chi vuole spingersi oltre le basi le ho raccolte nella guida su SSH hardening avanzato.

Monitoring e rilevamento: ho configurato auditd per il logging granulare delle syscall critiche (un livello di visibilità che auth.log da solo non può dare) affiancato da un HIDS che sorveglia le modifiche ai file di sistema, i login SSH, le escalation di privilegio e i cambi ai crontab, con alert immediati sulle modifiche in /etc verso un canale di notifica del titolare e verso la mia mail. Il principio è semplice: se un compromesso può restare invisibile per mesi, la difesa non è il muro, è l'allarme. Senza rilevamento, qualsiasi hardening è cieco.

Backup immutabili: ho configurato BorgBackup verso uno storage offsite in modalità append-only. È un punto su cui voglio essere preciso, perché viene quasi sempre raccontato male: l'append-only efficace non è un flag sul client, è una restrizione imposta dal server. Si applica forzando, nelle authorized_keys del repository remoto, un comando vincolato del tipo:

# Nelle authorized_keys del server di backup
command="borg serve --append-only --restrict-to-repository /backups/cliente",restrict ssh-ed25519 AAAA...

Così il server di produzione può creare nuovi archivi ma non può cancellare o modificare quelli esistenti. È fondamentale perché la prima cosa che fa un attaccante con accesso root è distruggere i backup per impedire il ripristino e massimizzare la leva (è il manuale del ransomware). Con l'append-only imposto lato server, la storia dei backup sopravvive anche al compromesso totale della macchina di produzione. L'unica accortezza onesta da dichiarare: questa difesa protegge dal client compromesso, non da chi prende root sul server di backup stesso; la prune va eseguita manualmente sul server, e la passphrase del repository va custodita offline perché senza non c'è recupero possibile. La retention applicata è la classica 3-2-1 con rotazione giornaliera, settimanale e mensile, e un test di ripristino automatizzato: un backup che non hai mai ripristinato non è un backup, è una speranza.

Per un VPS unmanaged in cloud europeo con buon rapporto prezzo/prestazioni e conformità GDPR, Hetzner resta un riferimento solido (datacenter in Germania e Finlandia). Per le PMI italiane che vogliono un'infrastruttura gestita, NIS-compliant e con il dato che non lascia l'Italia, la prima scelta è RHX (datacenter a Milano e Padova, gestione sistemistica inclusa): la differenza tra "unmanaged" e "managed" è esattamente chi risponde alla chiamata delle sei del mattino.

Patch management: ho abilitato unattended-upgrades per i soli pacchetti di sicurezza, con notifica al titolare per ogni aggiornamento applicato, mentre i pacchetti applicativi critici (database in primis) li gestisco a mano con cadenza pianificata, perché un upgrade automatico di MySQL su una macchina di produzione è un rischio che non vale la comodità. E ho verificato che OpenSSH fosse alla versione patchata: dopo regreSSHion (CVE-2024-6387), tenere sotto controllo la versione di sshd è una priorità che non delego a nessuno.

Cosa insegna davvero un'intrusione su un VPS unmanaged

La lezione più importante di questo incidente non è tecnica, è di metodo. Il server era stato compromesso per 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 di rendere il sito irraggiungibile. Tutto il resto, dalla persistenza in cron alla chiave SSH backdoor, era stato fatto in silenzio. Il pattern che vedo ripetersi nelle PMI non è l'attacco geniale: è l'assenza totale di rilevamento abbinata a configurazioni di default. Un VPS unmanaged senza monitoring è una macchina che ti dirà di essere stata bucata solo quando smetterà di funzionare, e a quel punto il danno è già fatto.

C'è poi il tema del vettore. L'autenticazione SSH a password su una macchina esposta a internet, oggi, è un invito aperto: gli scanner automatizzati testano milioni di combinazioni al giorno e le botnet che li operano coprono l'intero spazio IPv4 in meno di un'ora. Se il tuo VPS ha anche solo un utente con password debole e SSH sulla porta standard, la domanda non è se verrà compromesso, ma quando. La prospettiva offensiva qui è il miglior strumento difensivo che conosco: ho imparato a chiudere certe porte perché so, da chi attacca, quanto sono facili da aprire. Non difendi bene ciò che non hai mai provato a violare.

E poi i backup. Una copia salvata sulla stessa macchina che dovrebbe proteggere, nella directory /var/backups, in caso di ransomware o cancellazione deliberata sarebbe distrutta insieme a tutto il resto. Un backup che vive accanto al dato da proteggere non è una rete di sicurezza, è un'illusione che costa cara nel momento sbagliato. Se gestisci un VPS unmanaged presso Hetzner, OVH, Contabo, Digital Ocean o Aruba e sospetti un'intrusione, oppure se vuoi prevenirla con un hardening serio prima che accada, la cosa peggiore che puoi fare è improvvisare alle sei del mattino sotto pressione. Un piano di incident response strutturato, testato e documentato è ciò che separa quattro ore di lavoro da quattro settimane di caos. Se vuoi mettere in sicurezza la tua infrastruttura prima che sia troppo tardi, o se il problema lo stai vivendo adesso, contattami per una consulenza diretta: tra una diagnosi fatta bene e la prima contromisura passano ore, non settimane.

Ultima modifica: