Rimuovere un malware senza sapere da dove è entrato è come cambiare la serratura lasciando la finestra aperta. Il sito torna pulito, ma il punto d’ingresso resta lì, a disposizione del prossimo tentativo automatico. Per chiudere davvero un’infezione bisogna ricostruire il vettore d’attacco, e la fonte principale per farlo sono i log del server.
In questo articolo vediamo quali log servono, come si legge un attacco dentro milioni di righe e in che modo usiamo modelli di intelligenza artificiale aperti, come Qwen e DeepSeek, eseguiti sui nostri server, per smistare ciò che le regole tradizionali non riconoscono. Con una premessa: l’AI aiuta gli analisti, non li sostituisce.
Quali log servono
Ogni livello del server registra una parte della storia, e nessun log da solo basta:
- log di accesso del server web: ogni richiesta HTTP con indirizzo, data, metodo, percorso, codice di risposta, dimensione, referer e user agent. Si trovano in
/var/log/apache2/o/var/log/httpd/per Apache, in/var/log/nginx/per NGINX e, per LiteSpeed e OpenLiteSpeed, di solito in/usr/local/lsws/logs/o nella cartella dei log del singolo virtual host. Sui pannelli di hosting ogni dominio ha spesso il proprio file; - log degli errori del server web: richieste bloccate, file mancanti, errori di configurazione, e spesso gli avvisi PHP;
- log di PHP-FPM: il log degli errori del gestore dei processi e, se attivato, lo slow log, che per le richieste lente registra lo stack delle funzioni PHP in esecuzione. Quest’ultimo è prezioso, perché mostra quale file e quale funzione stava girando, cosa che il log di accesso non dice;
- log di sistema: accessi SSH e SFTP, esecuzioni di cron, eventuali log del firewall e del pannello di hosting.
Un limite da tenere presente: il formato di log standard non registra il corpo delle richieste POST. Si vede che qualcuno ha inviato dati a un certo indirizzo, non quali dati. La ricostruzione è quindi fatta di indizi da incrociare, non di una registrazione completa.
Ricostruire la sequenza di un attacco
Il punto di partenza è quasi sempre un file malevolo trovato durante la pulizia. Le sue date danno una prima finestra temporale: la data di modifica si può falsificare facilmente con touch, mentre quella di cambio di stato dell’inode (ctime) no, e di solito è la più affidabile.
stat wp-content/uploads/2026/09/cache.php
# Modify: 2024-01-12 10:14:02 <- probabilmente falsificata
# Change: 2026-09-14 03:12:47 <- momento reale della scrittura
Da lì si cercano nei log le richieste intorno a quel momento: una POST a un endpoint di un plugin, un caricamento di file, un accesso all’amministrazione da un indirizzo mai visto prima. E, subito dopo, le prime richieste dirette al nuovo file, che confermano che qualcuno lo sta usando.
# Richieste POST nella mezz'ora intorno alla scrittura del file
grep '14/Sep/2026:03:[0-4]' access.log | grep '"POST '
# Chi ha richiesto direttamente file PHP dentro uploads (non dovrebbe accadere)
grep -E '"(GET|POST) /wp-content/uploads/[^" ]*\.php' access.log
# Stesse ricerche sui log già ruotati e compressi
zgrep -h '"POST ' access.log.*.gz | awk '{print $1, $4, $7, $9}'
A volte la sequenza è lineare: una richiesta a un plugin vulnerabile, la comparsa del file, l’uso della backdoor. Più spesso è frammentata: la vulnerabilità è stata sfruttata settimane prima da un indirizzo, la backdoor viene usata oggi da un altro, e nel mezzo ci sono migliaia di richieste automatiche di scanner che provano di tutto su tutti i siti. Separare il rumore dal segnale è la parte più lunga del lavoro.
Dove le regole non bastano
Per la parte ripetitiva usiamo regole deterministiche: elenchi di percorsi sfruttati da vulnerabilità note, firme di strumenti di scansione diffusi, parametri tipici degli attacchi SQL injection e di inclusione di file. Queste regole scartano velocemente la gran parte del traffico malevolo generico, che su un sito WordPress esposto non manca mai.
Restano però le richieste che non corrispondono a niente di noto: parametri insoliti inviati a un endpoint poco usato, sequenze di chiamate che hanno senso solo se lette insieme, user agent costruiti apposta, errori PHP che compaiono una sola volta. È qui che si nascondono sia le varianti nuove di attacchi conosciuti, sia i possibili 0-day, cioè vulnerabilità non ancora pubbliche e per cui non esiste ancora una regola.
Come usiamo i modelli locali
Su questa parte non classificata usiamo modelli linguistici a pesi aperti, come quelli delle famiglie Qwen e DeepSeek, eseguiti su hardware nostro. Nel nostro processo svolgono alcuni compiti precisi:
- raggruppare e riassumere: migliaia di righe simili ma non identiche vengono ridotte a pochi gruppi, ciascuno con una descrizione di cosa sembrano fare;
- proporre ipotesi: per un gruppo di richieste anomale il modello suggerisce quale tipo di attacco potrebbe essere e quale componente sembra preso di mira, indicando le righe su cui basa l’ipotesi;
- spiegare codice: il codice PHP deoffuscato di una backdoor viene descritto in linguaggio naturale, con le funzioni pericolose e gli indirizzi esterni evidenziati;
- correlare fonti diverse: un errore nel log di PHP-FPM, una richiesta nel log di accesso e la data di un file vengono messi in fila in una possibile sequenza.
Ogni risultato del modello è un’ipotesi da verificare, non una conclusione. I modelli linguistici possono sbagliare con grande sicurezza, inventare un collegamento che non c’è o trascurare una riga importante. Per questo un analista controlla ogni ipotesi sulle prove originali: riproduce la richiesta in un ambiente isolato, verifica il codice del plugin coinvolto, confronta le date. Il vantaggio dell’AI è far arrivare prima l’analista alle righe giuste, non decidere al suo posto. Quando un’ipotesi porta a una vulnerabilità reale e non ancora nota, la segnaliamo in modo responsabile allo sviluppatore del componente.
Perché in locale
I log di un sito contengono dati personali: indirizzi IP, a volte indirizzi email o nomi nei parametri delle richieste, cookie di sessione, percorsi che rivelano l’attività degli utenti. Inviarli a un servizio di intelligenza artificiale in cloud significa trasferire quei dati a un soggetto terzo, con tutte le conseguenze previste dal GDPR e con il rischio concreto di esporre informazioni sensibili del cliente e dei suoi utenti.
Eseguire i modelli sui nostri server risolve il problema alla radice: i log non escono mai dalla nostra infrastruttura, non vengono usati per addestrare modelli di altri e restano sotto il nostro controllo per tutta la durata dell’analisi, dopo la quale vengono cancellati secondo gli accordi con il cliente. Prima dell’analisi, dove possibile, pseudonimizziamo comunque i dati che non servono all’indagine. I modelli aperti hanno anche un altro vantaggio: sappiamo esattamente quale versione stiamo usando, e il suo comportamento non cambia da un giorno all’altro per decisione di un fornitore esterno.
Cosa conservare, anche prima di un attacco
La ricostruzione è possibile solo se i log esistono. Molti hosting conservano i log di accesso per pochi giorni, e un’infezione scoperta dopo un mese non lascia più tracce del momento in cui è avvenuta. Alcune impostazioni fanno una grande differenza:
- conservare i log di accesso e di errore per almeno qualche mese, compressi, in una posizione che l’utente del sito non può modificare;
- attivare lo slow log di PHP-FPM, con una soglia ragionevole;
- sincronizzare l’orologio del server e usare lo stesso fuso orario in tutti i log, per poterli incrociare;
- registrare gli accessi SSH e SFTP e le modifiche ai file più delicati.
Quando interveniamo su un sito infetto, l’analisi dei log è parte del lavoro: la pulizia si chiude con l’indicazione del vettore d’attacco più probabile e con le misure per chiuderlo. Se vuoi sapere come è entrato il malware nel tuo sito, parliamone.