Il server che invia spam: script PHP di mailing, code di posta e reputazione del dominio

Come riconoscere un server che invia spam, trovare lo script PHP responsabile, svuotare le code di Postfix ed Exim e recuperare la reputazione del dominio.

Un server da cui escono decine di buste rosse, accanto a un pannello mailq con la coda quasi piena e un segnale di pericolo giallo

Il primo segnale di solito non arriva dal sito, ma dalla posta. I preventivi inviati ai clienti finiscono nella cartella spam, qualche destinatario risponde con un messaggio di errore che parla di «reputazione», il provider di hosting scrive che l’account è stato sospeso per invio massivo. Il sito sembra funzionare normalmente, eppure da quel server partono migliaia di email che nessuno in azienda ha scritto.

Uno script di spam non si vede dal browser: si vede nei log di posta, nella coda del server e, troppo tardi, nella reputazione del dominio.

Un sito WordPress compromesso è uno strumento prezioso per chi invia spam o phishing: ha un indirizzo IP con una buona storia, un dominio reale e, spesso, record DNS che rendono i messaggi credibili. In questo articolo vediamo come riconoscere il problema, come risalire allo script che invia, come gestire la coda di Postfix ed Exim, cosa fare se a essere abusate sono le credenziali SMTP e come recuperare la reputazione del dominio e dell’IP.

I sintomi di un server che invia spam

Alcuni segnali sono evidenti, altri si notano solo se si sa dove guardare:

  • la coda di posta del server contiene centinaia o migliaia di messaggi, molti in stato deferred perché rifiutati dai destinatari;
  • arrivano alla casella del mittente molti messaggi di mancata consegna (bounce) per email mai inviate da persone reali;
  • le email legittime, anche quelle transazionali di WooCommerce, finiscono in spam o vengono rifiutate con codici che citano blocklist o scarsa reputazione;
  • il carico del server cresce senza un corrispondente aumento di traffico web, con molti processi PHP o sendmail attivi;
  • il provider segnala l’abuso, limita la porta 25 o sospende l’account.

Prima di pensare al malware conviene escludere l’ipotesi più banale: un modulo di contatto senza protezione, usato da un bot per inviare messaggi attraverso la funzione «invia una copia a me stesso». Il risultato è simile, ma la soluzione è diversa (un CAPTCHA o la rimozione di quella funzione). Se invece i messaggi hanno oggetti e contenuti che il sito non potrebbe mai generare, come finte notifiche bancarie, farmaci o criptovalute, siamo quasi certamente davanti a uno script caricato da un attaccante.

Da dove parte la posta: le tre strade

Uno script PHP malevolo può inviare posta in tre modi, e ognuno lascia tracce in posti diversi. Il primo è la funzione mail() di PHP, che passa il messaggio al programma sendmail del sistema (fornito da Postfix o da Exim), da cui entra nella coda locale: è la strada più semplice e quella che lascia più tracce. Il secondo è una connessione SMTP autenticata, al server stesso o a un server esterno, con credenziali rubate: spesso quelle di una casella del dominio, lette da un plugin SMTP o indovinate. Il terzo è una connessione diretta alla porta 25 dei server destinatari, che scavalca del tutto il server di posta locale e si vede solo dalle connessioni di rete.

I kit di spam più diffusi sono script PHP di poche centinaia di righe, spesso offuscati e nascosti in cartelle di plugin o negli upload, che ricevono dall’esterno l’elenco dei destinatari e il testo, magari tramite una richiesta POST, e li inviano a ritmo controllato. Altri includono una copia di PHPMailer e usano SMTP; altri ancora sono semplici webshell con una funzione di invio integrata.

Schema dei tre percorsi con cui uno script PHP compromesso invia spam. Primo percorso: funzione mail(), programma sendmail, coda di Postfix o Exim, server destinatario; accanto, le tracce da cercare: mail.log di PHP, intestazione X-PHP-Originating-Script, riga pickup con uid nel log di posta. Secondo percorso: connessione SMTP autenticata con credenziali rubate, tracciata dal campo sasl_username nel log. Terzo percorso: connessione diretta alla porta 25 dei destinatari, visibile solo con ss o con le regole del firewall sull’uscita.
Le tre strade della posta in uscita e dove ciascuna lascia le sue tracce.

Trovare lo script che invia

Per la strada di mail() PHP offre due direttive fatte apposta. mail.add_x_header aggiunge a ogni messaggio l’intestazione X-PHP-Originating-Script, con l’UID e il nome del file che ha chiamato mail(). mail.log scrive in un file ogni chiamata a mail(), con il percorso dello script, il numero di riga e i destinatari. Entrambe sono disattivate per impostazione predefinita, come riporta la documentazione di PHP, e conviene attivarle a livello di pool PHP-FPM, dove il sito non può cambiarle:

; nel pool PHP-FPM del sito, es. /etc/php/8.3/fpm/pool.d/sito1.conf
php_admin_flag[mail.add_x_header] = on
php_admin_value[mail.log] = /home/sito1/logs/php-mail.log

Se la coda contiene già messaggi, l’intestazione permette di risalire allo script anche dopo il fatto, sempre che fosse attiva quando sono stati generati. Con Postfix si legge un messaggio in coda con postcat; con Exim con exim -Mvh:

# Postfix: intestazioni di un messaggio in coda
postcat -h -q 4F2A91C0B3 | grep -i -E '^(from|to|subject|x-php-originating-script):'

# Exim: intestazioni di un messaggio in coda
exim -Mvh 1tXyZa-0001Ab-Cd | grep -i x-php-originating-script

Anche senza quell’intestazione i log di posta dicono molto. Postfix registra i messaggi entrati da sendmail con una riga del processo pickup che riporta l’UID dell’utente di sistema: sapere quale utente ha inviato restringe la ricerca a un solo sito o account. Exim, sui server cPanel, registra per i messaggi generati localmente la cartella di lavoro dello script (cwd=), che spesso indica direttamente la cartella in cui si trova il file malevolo.

# Postfix: messaggi entrati da sendmail, raggruppati per UID
grep 'postfix/pickup' /var/log/mail.log | grep -o 'uid=[0-9]*' | sort | uniq -c | sort -rn
getent passwd 1004    # a quale utente corrisponde l'UID più attivo

# Exim (cPanel): cartelle da cui sono partiti più messaggi
grep 'cwd=' /var/log/exim_mainlog | grep -v '/var/spool' \
  | grep -o 'cwd=[^ ]*' | sort | uniq -c | sort -rn | head

# Nei log di accesso, gli script PHP che ricevono molte richieste POST
awk '$6 ~ /POST/ {print $7}' /var/log/nginx/access.log | cut -d'?' -f1 \
  | sort | uniq -c | sort -rn | head -20

L’ultimo comando incrocia la posta con il traffico web: uno script di spam viene quasi sempre richiamato dall’esterno, e un file PHP sconosciuto che riceve migliaia di richieste POST da pochi indirizzi IP è un candidato forte. Su log molto grandi, o quando le richieste sono distribuite su molte fonti, questa correlazione è il tipo di lavoro in cui ci aiuta l’analisi dei log con modelli di intelligenza artificiale locali. Infine, una ricerca nei file per le funzioni tipiche aiuta a trovare eventuali altri script ancora inattivi:

grep -rl --include='*.php' -E 'mail\s*\(|fsockopen|PHPMailer|smtp_(host|pass)' \
  /home/sito1/public_html/wp-content/uploads /home/sito1/public_html/wp-content/plugins \
  | xargs -r ls -la

Ispezionare e svuotare la coda di posta

Una volta trovato e fermato lo script, la coda è ancora piena di messaggi che il server continuerà a tentare di consegnare per giorni, peggiorando la reputazione a ogni tentativo. Va svuotata, ma con criterio: in mezzo possono esserci email legittime, come conferme d’ordine o risposte ai clienti. Prima di cancellare, conviene mettere la coda in attesa e contare i messaggi per mittente.

# ── Postfix ──
postqueue -p | tail -n 1                 # riepilogo: quanti messaggi e quanti KB
postsuper -h ALL                         # mette tutta la coda in attesa (hold)
postqueue -p | awk '/^[0-9A-F]/ {print $7}' | sort | uniq -c | sort -rn | head

# Cancella solo i messaggi di un certo mittente
postqueue -p | awk 'BEGIN {RS = ""} /mittente-abusato@dominio\.it/ {print $1}' \
  | tr -d '*!' | postsuper -d -

postsuper -H ALL                         # rilascia ciò che resta

# ── Exim ──
exim -bpc                                # numero di messaggi in coda
exim -bp | exiqsumm | sort -rn | head    # riepilogo per dominio destinatario
exiqgrep -i -f 'mittente-abusato@dominio\.it' | xargs -r exim -Mrm

Il comando postsuper -d ALL, che cancella tutto, è l’ultima risorsa, da usare solo dopo aver verificato che non ci siano messaggi da salvare. E prima di cancellare conviene conservare qualche messaggio di esempio con le intestazioni complete: servirà per capire da dove arrivava lo spam e, più avanti, per le richieste di rimozione dalle blocklist.

Quando a essere abusate sono le credenziali SMTP

Se nei log i messaggi di spam non passano da pickup ma arrivano da connessioni autenticate, il problema non è solo uno script sul sito: qualcuno possiede la password di una casella. Può averla letta dalle impostazioni di un plugin SMTP salvate nel database, da un wp-config.php esposto, oppure ottenuta con attacchi a forza bruta contro il server di posta. In Postfix il nome utente autenticato compare nel campo sasl_username; in Exim, sui server cPanel, nel campo A=dovecot_login: o A=dovecot_plain:.

# Postfix: invii per utente autenticato e per IP di provenienza
grep 'sasl_username=' /var/log/mail.log | grep -o 'sasl_username=[^ ,]*' | sort | uniq -c | sort -rn
grep 'sasl_username=info@dominio.it' /var/log/mail.log | grep -o 'client=[^,]*' | sort | uniq -c | sort -rn | head

# Exim: accessi autenticati per casella
grep -o 'A=dovecot_[a-z]*:[^ ]*' /var/log/exim_mainlog | sort | uniq -c | sort -rn | head

Una casella usata da decine di indirizzi IP in paesi diversi in poche ore è compromessa. La risposta è cambiare subito la password, chiudere le sessioni aperte, controllare le regole di inoltro della casella (spesso l’attaccante ne aggiunge una per ricevere copia della posta) e verificare dove quella password fosse salvata: se era nelle impostazioni di un plugin, va considerato compromesso anche il database del sito, e vanno cercati eventuali amministratori fantasma.

SPF, DKIM e DMARC: cosa proteggono e cosa no

I tre meccanismi di autenticazione della posta servono a dire ai server destinatari quali messaggi sono davvero del dominio. SPF elenca nel DNS gli indirizzi IP autorizzati a inviare per il dominio; DKIM firma ogni messaggio con una chiave privata la cui parte pubblica è pubblicata nel DNS; DMARC dice al destinatario cosa fare dei messaggi che non superano SPF o DKIM allineati al dominio del mittente, e dove inviare i rapporti. Da febbraio 2024 Google chiede a chi invia più di 5.000 messaggi al giorno verso account Gmail di avere tutti e tre, come indicato nelle linee guida per i mittenti.

# Controllare i record pubblicati
dig +short TXT dominio.it | grep spf1
dig +short TXT selettore._domainkey.dominio.it
dig +short TXT _dmarc.dominio.it

# Esempi di record
dominio.it.         TXT "v=spf1 ip4:203.0.113.10 include:_spf.provider.example -all"
_dmarc.dominio.it.  TXT "v=DMARC1; p=quarantine; rua=mailto:dmarc@dominio.it; adkim=s; aspf=s"

Il punto da capire è che questi record proteggono il dominio da chi invia da altri server fingendosi lui, non da uno script che gira sul server autorizzato. Se il malware usa mail() sul server indicato nell’SPF, o una casella con credenziali valide, i suoi messaggi supereranno i controlli, saranno firmati con DKIM e danneggeranno direttamente la reputazione del dominio. SPF, DKIM e DMARC sono quindi indispensabili, ma non sostituiscono la pulizia. I rapporti DMARC aggregati, invece, sono un ottimo strumento di diagnosi: mostrano da quali IP partono messaggi a nome del dominio e con quale esito, e spesso rivelano un abuso prima delle blocklist.

Diagramma dei controlli del server destinatario. Un messaggio a nome di dominio.it passa per tre verifiche nel DNS: SPF controlla l’IP di invio, DKIM controlla la firma, DMARC controlla l’allineamento e applica la policy. In alto, un server esterno che falsifica il mittente viene respinto con una croce rossa. In basso, uno script di spam sul server autorizzato supera tutti e tre i controlli con spunte verdi, e un’etichetta gialla avverte che la reputazione colpita è quella del dominio.
SPF, DKIM e DMARC fermano chi si finge il dominio da fuori, non lo script che invia dal server autorizzato.

Blocklist e richieste di rimozione

Dopo un episodio di spam, l’IP del server e talvolta il dominio finiscono in una o più blocklist, liste consultate in tempo reale dai server di posta per decidere se accettare un messaggio. Le più usate sono quelle di Spamhaus, SpamCop e Barracuda; ciascuna ha il proprio sito con uno strumento di verifica e una procedura di rimozione. Il controllo si può fare anche dalla riga di comando, invertendo gli ottetti dell’IP:

# IP 203.0.113.10 → 10.113.0.203
dig +short 10.113.0.203.zen.spamhaus.org
dig +short 10.113.0.203.bl.spamcop.net
dig +short 10.113.0.203.b.barracudacentral.org
# nessuna risposta = non presente; 127.0.0.x = presente (il codice indica la lista)

Attenzione a un dettaglio: Spamhaus non risponde correttamente alle interrogazioni che arrivano da resolver DNS pubblici come quelli di Google o Cloudflare, e restituisce codici di errore nell’intervallo 127.255.255.x che non significano «presente in lista». Per un risultato affidabile la query va fatta da un resolver proprio o tramite lo strumento web.

La richiesta di rimozione va fatta solo dopo aver eliminato la causa. Le procedure chiedono di confermare che il problema è risolto, e un IP rimosso che torna a inviare spam viene reinserito, a volte con tempi di rimozione più lunghi. Per i grandi provider di posta ci sono strumenti dedicati: Google Postmaster Tools per la reputazione verso Gmail, Microsoft SNDS per Outlook e Hotmail. Se il dominio del sito è finito anche nelle liste di navigazione sicura, la procedura è quella descritta nell’articolo sulla rimozione dalla blacklist di Google.

Limitare i danni la prossima volta

Nessun sito è immune da vulnerabilità, ma si può fare in modo che una compromissione non si trasformi in migliaia di email. Le misure più efficaci agiscono sul server, dove il sito non può modificarle:

  • Limiti di invio per utente o per dominio. cPanel permette di impostare un massimo di email orarie per dominio; con Postfix si usa un servizio di policy per limitare i messaggi per mittente, con Exim le regole ratelimit nelle ACL. Un sito che normalmente invia venti email al giorno non ha motivo di poterne inviare mille in un’ora.
  • Uscita sulla porta 25 bloccata per gli utenti dei siti, consentita solo al server di posta, così uno script non può contattare direttamente i server destinatari.
  • mail.add_x_header e mail.log sempre attivi, per sapere in pochi secondi quale file ha inviato cosa.
  • Posta transazionale su un servizio SMTP dedicato, con credenziali proprie e limitate, separato dalle caselle del personale; e mail() disabilitata nel pool PHP se il sito non la usa.
  • Monitoraggio della coda, con un avviso quando supera una soglia: è spesso il segnale più precoce di una compromissione.
# Blocco dell'uscita sulla porta 25 per l'utente del sito (iptables, modulo owner)
iptables -A OUTPUT -p tcp --dport 25 -m owner --uid-owner sito1 -j REJECT

# Controllo periodico della coda di Postfix: avviso sopra i 200 messaggi
n=$(postqueue -j | wc -l); [ "$n" -gt 200 ] && echo "Coda di posta: $n messaggi" | mail -s "Allarme coda $(hostname)" admin@dominio.it
Linea temporale della risposta a un episodio di spam in sette fasi: fermare l’invio mettendo la coda in attesa, trovare lo script o la casella abusata, rimuovere il malware e la persistenza, ripulire la coda salvando i messaggi legittimi, cambiare le credenziali, verificare SPF, DKIM e DMARC, chiedere la rimozione dalle blocklist. Una nota blu sotto le prime fasi ricorda di conservare alcuni messaggi con le intestazioni complete; una nota rossa sotto l’ultima avverte che la rimozione va chiesta solo dopo aver eliminato la causa.
L’ordine della risposta: prima si ferma l’invio, per ultima si chiede la rimozione dalle blocklist.

Un server che invia spam è quasi sempre il sintomo di una compromissione più ampia: lo script di invio è arrivato attraverso una vulnerabilità, e accanto a lui ci sono di solito backdoor, attività pianificate o credenziali rubate. Nei nostri interventi trattiamo la posta come parte dell’analisi di sistema: log di posta e di accesso, code, pool PHP e configurazione del server di posta, oltre ai file e al database di WordPress. La pulizia garantita in 6, 12 o 24 ore per installazione comprende la ricerca dello script e dei suoi punti di ingresso; se il tuo dominio ha iniziato a finire nello spam senza motivo apparente, scrivici con qualche intestazione di esempio.

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 ↗