Si cancellano tutti i file malevoli, si controlla che non ne resti nessuno, e dopo dieci minuti uno di quei file è di nuovo al suo posto. Non c’è nessuna voce nel cron, nessun evento di WP-Cron. In casi come questo la risposta è spesso in un processo già in esecuzione, partito prima della pulizia e ancora vivo in memoria, anche se il file da cui è nato non esiste più.
Perché un processo sopravvive al suo file
Su Linux un programma in esecuzione non ha bisogno che il suo file resti sul disco: una volta avviato, il sistema mantiene il contenuto finché il processo non termina. Chi attacca sfrutta questo comportamento avviando uno script PHP in background, per esempio con nohup php /tmp/.x.php &, e cancellandolo subito dopo. Il processo continua a girare, senza file da trovare, e può ricreare le backdoor a intervalli regolari, restare in ascolto su una porta o inviare spam.
Per non dare nell’occhio nell’elenco dei processi, lo script cambia spesso il proprio nome con cli_set_process_title(), una funzione di PHP, o viene avviato con un nome falso. Un nome molto usato è quello di un thread del kernel, come [kworker/0:1], perché nell’elenco di ps ce ne sono decine di simili.
Trovarli con /proc
Ogni processo ha una cartella in /proc con i collegamenti al proprio eseguibile, alla cartella di lavoro e ai file aperti. Quando il file di origine è stato cancellato, il collegamento lo dice esplicitamente:
# Eseguibili cancellati
ls -l /proc/*/exe 2>/dev/null | grep deleted
# File aperti cancellati (per esempio lo script PHP)
ls -l /proc/*/fd 2>/dev/null | grep -B1 deleted
# Dettagli di un processo sospetto
ps -o pid,ppid,user,lstart,cmd -p 12345
tr '\0' ' ' < /proc/12345/cmdline; echo
ls -l /proc/12345/exe /proc/12345/cwd
Un finto thread del kernel si riconosce facilmente: i thread veri hanno come processo padre kthreadd, con PID 2, non hanno un eseguibile associato e girano come root. Un processo chiamato [kworker/0:1] che gira con l’utente del sito, ha come padre un processo PHP e ha in /proc/<pid>/exe l’interprete PHP non è un thread del kernel.
Attenzione ai falsi positivi
Non tutti i processi con un eseguibile «deleted» sono malevoli. Quando il gestore dei pacchetti aggiorna un programma, i processi già avviati continuano a usare la vecchia versione, ormai cancellata, finché non vengono riavviati. Dopo un aggiornamento di PHP, per esempio, i processi di PHP-FPM possono comparire in questo elenco del tutto legittimamente. La differenza la fanno l’utente, il processo padre, la riga di comando e la data di avvio.
Salvare le prove prima di terminarlo
Il contenuto di un processo cancellato esiste solo in memoria: terminandolo, lo si perde per sempre. Prima conviene recuperarlo, insieme alle connessioni di rete aperte:
mkdir -p /root/evidenze/12345
cp /proc/12345/exe /root/evidenze/12345/exe
cp /proc/12345/fd/3 /root/evidenze/12345/script.php # il descrittore giusto va visto con ls -l
ss -tnp | grep 'pid=12345'
Lo script recuperato dice cosa faceva il processo, quali file ricreava e con quali server comunicava. Solo dopo lo si termina con kill, e si verifica che non ripartisca.
Chi lo ha avviato
Un processo in memoria non nasce da solo. Va trovato il meccanismo che lo ha lanciato la prima volta e che potrebbe rilanciarlo: una webshell, una voce @reboot nel crontab, un servizio utente di systemd. Senza questo passaggio, il prossimo riavvio del server può riportare tutto com’era.