Il tema premium costa qualche decina di euro, il page builder ha un abbonamento annuale, il plugin per le prenotazioni un altro. Poi qualcuno trova lo stesso pacchetto su un sito di download, in un canale Telegram o su un forum, completo e «già attivato», a costo zero. È così che molti siti WordPress si infettano da soli: non per una vulnerabilità scoperta da un attacco sofisticato, ma perché il malware è stato installato dal gestore del sito, con un clic su «Attiva».
In questo articolo vediamo come funziona questa filiera, cosa contiene davvero un pacchetto nulled, come riconoscerne i segni nel codice e come verificare l’integrità di plugin e temi rispetto agli originali del produttore. Con una nota finale sugli aspetti legali, che sono meno ovvi di quanto si pensi.
Cosa sono i plugin nulled
Un plugin o un tema «nulled» è la copia di un prodotto commerciale da cui è stato rimosso, o reso inefficace, il controllo della licenza. Il nome viene dal gergo della pirateria software: la verifica viene «annullata», così il prodotto funziona con tutte le funzionalità premium senza una chiave valida.
Le fonti sono varie: siti dedicati con migliaia di titoli, gruppi e canali di messaggistica, archivi condivisi, a volte venditori che propongono abbonamenti a prezzo stracciato a interi cataloghi. Non tutte queste fonti sono uguali, ma hanno un problema in comune: tra il produttore e il sito di chi installa c’è un intermediario sconosciuto che ha avuto il pacchetto in mano, lo ha modificato e non deve rendere conto a nessuno di cosa ci ha aggiunto.
Lo stesso vale per i temi «gratuiti» scaricati da siti che non sono la directory ufficiale di WordPress.org o il sito del produttore. Qui il problema è più vecchio dei pacchetti nulled: da anni circolano temi gratuiti con un footer codificato che inserisce link nascosti verso siti di scommesse, farmaci o prestiti, e che rompe il tema se qualcuno prova a toglierli. Oggi a quei link si aggiungono spesso loader veri e propri. Un tema gratuito pubblicato su WordPress.org passa invece da una revisione e da controlli automatici, e la sua versione è verificabile: non è una garanzia assoluta, ma è una differenza sostanziale rispetto a un file ZIP di origine ignota.
Perché qualcuno li regala
Chi distribuisce un pacchetto nulled sostiene dei costi: acquistare l’originale, rimuovere la licenza, mantenere un sito con aggiornamenti frequenti, pagare banda e server. Il ritorno non arriva dagli utenti che scaricano, ma dai siti su cui il pacchetto viene installato. Una volta dentro, un sito WordPress vale parecchio: può ospitare pagine di spam SEO, mostrare pubblicità invasive, reindirizzare il traffico mobile verso truffe, inviare email, fare da ponte per attacchi ad altri siti.
Il caso più documentato è quello di WP-VCD, una famiglia di malware diffusa proprio attraverso temi e plugin pirata. Wordfence l’ha descritta come il malware che installi da solo sul tuo sito, e nel suo report sulle minacce del 2020 lo indicava come la famiglia più diffusa tra i siti infetti rilevati dal proprio scanner, con il malware proveniente da componenti nulled presente in oltre il 17% di quei siti. Il meccanismo era esemplare: al primo caricamento il pacchetto installava una backdoor in tutti i temi presenti, contattava un server di comando e controllo e riportava le date dei file modificati ai valori precedenti, per non dare nell’occhio.
Come viene nascosto il malware nel pacchetto
Un pacchetto nulled contiene quasi sempre due tipi di modifiche. La prima è quella dichiarata, cioè la rimozione della licenza. Di solito si tratta di poche righe: la funzione che verifica la chiave restituisce sempre un esito positivo, oppure le richieste al server delle licenze vengono intercettate e ricevono una risposta finta. Un esempio tipico del secondo approccio:
// Le richieste al server delle licenze non partono mai:
// WordPress riceve subito una risposta che dice "licenza valida"
add_filter( 'pre_http_request', function ( $pre, $args, $url ) {
if ( str_contains( $url, 'licenze.produttore.example' ) ) {
return array(
'headers' => array(),
'body' => '{"license":"valid","expires":"lifetime"}',
'response' => array( 'code' => 200, 'message' => 'OK' ),
'cookies' => array(),
);
}
return $pre;
}, 10, 3 );
Questo codice in sé non è malevolo: impedisce solo la verifica. Il problema è la seconda modifica, quella non dichiarata. Nei pacchetti che analizziamo le forme più frequenti sono:
- un loader in un file dal nome credibile, come
class-plugin-updater.phpocompat.php, incluso da uno dei file principali; - codice nell’hook di attivazione, che viene eseguito una volta sola, crea un amministratore o scarica il carico vero e poi non lascia tracce evidenti;
- chiamate verso server esterni travestite da controllo degli aggiornamenti, da cui arrivano istruzioni o codice da eseguire;
- diffusione ad altri componenti: copie della backdoor in
functions.phpdi tutti i temi, in un mu-plugin o in file del nucleo, così la rimozione del plugin non basta; - codice offuscato con catene di
base64_decode,gzinflateestr_rot13, o nascosto dentro file che il prodotto originale distribuisce già codificati con ionCube.
L’ultimo caso è il più insidioso: se il plugin originale contiene file codificati, un file codificato in più o diverso non attira l’attenzione, e nessuno scanner può leggerne il contenuto. Ne parliamo più in dettaglio nell’articolo sui file offuscati e codificati.
I segnali da cercare nel codice
Se un sito ha componenti di provenienza incerta, una prima ricerca manuale aiuta a capire se c’è qualcosa da approfondire. Nessuno di questi schemi è di per sé una prova, perché anche il codice legittimo li usa, ma la loro combinazione in un posto inatteso è un buon motivo per guardare meglio:
cd wp-content/plugins/nome-plugin
# Decodifica ed esecuzione dinamica di codice
grep -rnE "(eval|assert)\s*\(|gzinflate|gzuncompress|str_rot13|create_function" --include='*.php' .
# Richieste verso l'esterno: da dove arrivano gli "aggiornamenti"?
grep -rnE "wp_remote_(get|post)|curl_init|fsockopen|file_get_contents\s*\(\s*['\"]https?://" --include='*.php' .
# Creazione di utenti o modifica dei ruoli dal codice di un plugin
grep -rnE "wp_(insert|create)_user|set_role\s*\(\s*['\"]administrator" --include='*.php' .
# Codice eseguito all'attivazione e intercettazione delle richieste HTTP
grep -rnE "register_activation_hook|pre_http_request" --include='*.php' .
# File PHP con righe lunghissime, tipiche del codice offuscato
find . -name '*.php' -exec awk 'length > 3000 { print FILENAME; nextfile }' {} +
Altri indizi stanno fuori dal codice: un plugin che non compare nell’area clienti del produttore, una versione che non esiste nel suo registro delle modifiche, una data di pubblicazione successiva all’ultima versione ufficiale, un file readme che cita un sito di download come fonte. E, a livello di sistema, file dei temi con date di modifica identiche al secondo, segno frequente di date riscritte ad arte.
Vale la pena guardare anche nel database. Molti pacchetti nulled salvano tra le opzioni lo stato di una licenza falsa, o l’indirizzo di un server di aggiornamento alternativo da cui ricevere istruzioni. Un elenco delle opzioni che contengono parole come license, update o api nel nome, letto con calma, rivela spesso domini che non hanno nulla a che fare con il produttore. Lo stesso vale per gli eventi di WP-Cron registrati dal plugin: se compare un’attività che nessuna documentazione ufficiale menziona, va capito cosa esegue.
# Opzioni legate a licenze e aggiornamenti, con il loro valore
wp option list --search='*license*' --fields=option_name,option_value --skip-plugins --skip-themes
wp option list --search='*update*' --fields=option_name --skip-plugins --skip-themes
# Eventi di WP-Cron registrati, con la prossima esecuzione
wp cron event list --fields=hook,next_run_relative,recurrence --skip-plugins --skip-themes
Verificare l’integrità rispetto all’originale
La ricerca per schemi dice dove guardare; la risposta definitiva arriva dal confronto con il pacchetto originale. Per i componenti presenti nel repository ufficiale di WordPress.org il confronto è automatico, perché WP-CLI scarica le impronte pubblicate e le verifica file per file:
# Versioni installate
wp plugin list --fields=name,status,version --skip-plugins --skip-themes
# Nucleo e plugin del repository ufficiale, confrontati con le impronte di WordPress.org
wp core verify-checksums --skip-plugins --skip-themes
wp plugin verify-checksums --all --skip-plugins --skip-themes
Per i plugin e i temi commerciali non esistono impronte pubbliche, e WP-CLI non ha un comando equivalente per i temi. Si procede a mano: si scarica dall’area clienti del produttore esattamente la stessa versione installata e si confrontano le due cartelle.
# Pacchetto originale scaricato dall'area clienti del produttore
mkdir -p /tmp/originale && unzip -q nome-plugin-3.2.1.zip -d /tmp/originale
# Elenco sintetico: file in più, file mancanti, file diversi
diff -rq /tmp/originale/nome-plugin wp-content/plugins/nome-plugin
# Dettaglio delle differenze nei file PHP modificati
diff -ru /tmp/originale/nome-plugin wp-content/plugins/nome-plugin | less
Nel risultato di diff contano soprattutto tre cose: i file presenti solo nella copia installata, che non hanno alcuna ragione di esistere; le modifiche ai file principali, dove di solito viene aggiunta l’inclusione del loader; e le differenze nei file codificati, che non si possono leggere ma si possono confrontare byte per byte. Per chi gestisce molti siti, conservare un archivio dei pacchetti originali scaricati dal produttore, con le relative impronte SHA-256, rende questo controllo una questione di minuti invece che di ore, e permette di rifarlo dopo ogni aggiornamento.
Se l’originale non è disponibile, perché la licenza non è mai stata acquistata o il prodotto non esiste più, non c’è modo di stabilire con certezza che il pacchetto sia integro. In quel caso la scelta prudente è sostituirlo con una versione acquistata o con un’alternativa, non cercare di ripulirlo riga per riga.
Ripulire un sito che ha usato componenti nulled
Scoperto il pacchetto contraffatto, l’istinto è eliminarlo e installare la versione ufficiale. È necessario, ma non sufficiente. Se il codice malevolo è stato eseguito anche una sola volta, bisogna presumere che abbia fatto ciò per cui era stato scritto: lasciare altre porte aperte. Per questo la pulizia deve partire dalla data di installazione del componente e comprendere:
- tutti i temi e i plugin, anche quelli disattivati, perché famiglie come WP-VCD si propagano agli altri componenti;
- i file del nucleo, da verificare con le impronte ufficiali e reinstallare se modificati;
- gli utenti, alla ricerca di amministratori creati dal codice e nascosti dall’elenco;
- il database, comprese opzioni con codice serializzato, trigger ed eventi;
- le attività pianificate di WordPress e del sistema;
- le credenziali, a partire da password del database e salt di WordPress, che il codice ha potuto leggere dal file di configurazione.
Un ultimo dettaglio pratico: i componenti nulled non ricevono aggiornamenti dal produttore, o li ricevono da fonti altrettanto dubbie. Anche quando non contengono malware, restano fermi a versioni con vulnerabilità note, che diventano a loro volta una porta d’ingresso.
Una nota legale ed etica
Sul piano legale la questione è più sfumata di quanto sembri. WordPress è distribuito con licenza GPL, e molti plugin e temi commerciali rilasciano il proprio codice PHP con la stessa licenza, che consente di copiarlo e ridistribuirlo. Non sempre, però, la GPL copre tutto il pacchetto: fogli di stile, script, immagini e font possono avere licenze diverse, e i marchi dei prodotti restano protetti. La licenza commerciale che si acquista, inoltre, riguarda di solito aggiornamenti, supporto e servizi collegati, che una copia non dà. E aggirare un sistema di licenza di un prodotto proprietario è tutt’altra cosa rispetto a ridistribuire codice GPL. Per i casi concreti conviene sentire un legale, non un forum.
Sul piano etico il discorso è più semplice. Dietro molti plugin c’è un’azienda piccola, a volte una sola persona, che vive delle licenze e le reinveste in sicurezza e aggiornamenti. E sul piano economico il conto è rapido: il costo annuale delle licenze di un sito tipico è una frazione di quello di una sola giornata di sito fermo, di una rimozione da una blacklist o di una notifica di violazione dei dati.
Un plugin nulled non è gratis: il prezzo lo decide qualcun altro, e lo incassa dal tuo sito.
Se il danno è già fatto
Capita spesso che il componente nulled sia stato installato anni prima, da un fornitore precedente o da un collaboratore, e che nessuno ricordi da dove arrivi. In questi casi il confronto con gli originali è il primo passo del nostro lavoro: individuiamo i componenti non verificabili, li sostituiamo con le versioni ufficiali in accordo con il cliente e seguiamo il codice malevolo ovunque si sia propagato, nei file, nel database e nel sistema. Quando un pacchetto contiene file codificati o varianti mai viste, l’analisi è supportata da modelli di intelligenza artificiale eseguiti in locale e verificata da un analista.
La pulizia di un’installazione WordPress è garantita entro 6, 12 o 24 ore a seconda dell’urgenza. Se sospetti che sul tuo sito ci sia un tema o un plugin di provenienza dubbia, scrivici: possiamo dirti con certezza se è integro, prima che diventi un problema.