Un lunedì mattina arriva un avviso: un plugin installato su decine di siti ha una vulnerabilità sfruttabile senza autenticazione, qualcuno la sta già usando e lo sviluppatore non ha ancora pubblicato una versione corretta. Il pulsante «Aggiorna ora» non c’è, perché non c’è niente a cui aggiornare. È la situazione in cui si trovano ogni anno moltissimi amministratori di siti WordPress, ed è quella in cui le decisioni prese nelle prime ore contano più di tutto il resto.
Quando la patch non esiste, l’obiettivo non è correggere il plugin: è impedire che la richiesta di exploit raggiunga il codice vulnerabile, e sapere se l’ha già raggiunto.
In questo articolo vediamo come si sviluppa il ciclo di vita di una vulnerabilità, come capire se un sito è esposto, quali contromisure temporanee funzionano davvero (e quali no) e come leggere i log per scoprire se l’attacco è già andato a segno.
Cosa significa davvero «0-day» per un sito WordPress
In senso stretto una vulnerabilità 0-day è un difetto che viene sfruttato prima che chi mantiene il software abbia rilasciato una correzione: gli sviluppatori hanno avuto «zero giorni» per intervenire. Nell’ecosistema WordPress il termine si usa spesso in modo più largo, per indicare qualunque vulnerabilità nota pubblicamente e ancora senza patch. La distinzione pratica che interessa a chi gestisce un sito è un’altra: esiste o no una versione del plugin che chiude il problema?
Nel percorso ideale, la divulgazione coordinata, chi scopre il difetto lo segnala allo sviluppatore o a un programma che fa da intermediario, lo sviluppatore pubblica la correzione e solo dopo esce l’avviso pubblico, con l’identificativo CVE e i dettagli tecnici. Il percorso reale spesso si discosta: lo sviluppatore non risponde, il plugin è abbandonato, oppure la vulnerabilità viene trovata da chi la sfrutta invece che da chi la segnala. Il rapporto di Patchstack State of WordPress Security in 2025 conta 7.966 nuove vulnerabilità nell’ecosistema WordPress nel 2024, il 96% delle quali nei plugin; il 43% era sfruttabile senza autenticazione e il 33% non era ancora stato corretto al momento della divulgazione pubblica.
Il secondo dato è quello che pesa di più: in un caso su tre, il giorno in cui la vulnerabilità diventa pubblica non esiste un aggiornamento da installare. E dal momento della pubblicazione il tempo stringe, perché l’avviso descrive quasi sempre l’endpoint coinvolto, e da lì a uno script di scansione automatica il passo è breve. Nello stesso rapporto Patchstack racconta che, nel caso della vulnerabilità di esecuzione di codice in Bricks Builder, le campagne di sfruttamento automatizzato sono partite poche ore dopo la divulgazione.
Come capire se sei esposto
La prima domanda non è «come mi difendo», ma «il problema mi riguarda?». Per rispondere servono due cose: l’elenco esatto dei plugin installati, con le versioni, e una fonte affidabile di avvisi. Le fonti più usate sono il database di WPScan, quello di Patchstack, Wordfence Intelligence e, per le vulnerabilità con un CVE assegnato, il database NVD.
Con WP-CLI l’inventario richiede pochi secondi, anche su molti siti:
# Plugin, versione, stato e aggiornamenti disponibili
wp plugin list --fields=name,version,status,update --format=table
# Su un server con più installazioni: dove è presente il plugin?
find /var/www -maxdepth 6 -type d -path '*/wp-content/plugins/plugin-vulnerabile'
# Versione dichiarata nell'intestazione del file principale
grep -m1 -i '^[ *]*Version:' /var/www/*/wp-content/plugins/plugin-vulnerabile/plugin-vulnerabile.php
Attenzione ai plugin disattivati: un plugin disattivato non viene caricato da WordPress, ma i suoi file restano sul disco. Se la vulnerabilità si trova in un file PHP che si può richiamare direttamente dal browser, e che non verifica di essere eseguito dentro WordPress, il plugin disattivato è sfruttabile quanto quello attivo.
Poi va letto l’avviso, con attenzione ai dettagli che cambiano la valutazione: quali versioni sono coinvolte, se serve un account (e di che livello: un abbonato su un sito con la registrazione aperta non è un grande ostacolo), qual è l’endpoint o il parametro vulnerabile e che cosa si ottiene sfruttandolo. Un caricamento arbitrario di file o un’esecuzione di codice senza autenticazione richiede un intervento immediato; una vulnerabilità XSS che richiede un account da amministratore può aspettare qualche giorno con misure più leggere.
Disattivare, sostituire o tenere il plugin
La contromisura più efficace è anche la più semplice: se il codice vulnerabile non c’è, non si può sfruttare. Quando il plugin non è indispensabile, o esiste un’alternativa, la scelta migliore è rimuoverlo del tutto, non solo disattivarlo, per le ragioni viste sopra.
# Disattiva il plugin su un sito singolo
wp plugin deactivate plugin-vulnerabile
# Su una rete multisito attivata a livello di rete
wp plugin deactivate plugin-vulnerabile --network
# Rimuove anche i file (prima conviene conservarne una copia per l'analisi)
tar czf /root/plugin-vulnerabile-$(date +%F).tar.gz wp-content/plugins/plugin-vulnerabile
wp plugin delete plugin-vulnerabile
La copia prima della cancellazione non è un dettaglio: se il sito risulterà compromesso, i file del plugin, con le date di modifica e gli eventuali file estranei aggiunti nella sua cartella, sono parte delle prove.
Spesso però il plugin non si può togliere: gestisce i pagamenti, i moduli di contatto, il catalogo. In questi casi si cerca di togliere di mezzo solo la parte vulnerabile, lasciando funzionare il resto. È qui che entrano in gioco il virtual patching e il blocco dell’endpoint sul server web, due misure che hanno lo stesso scopo e che lavorano a livelli diversi.
Virtual patching con il WAF
Il virtual patching consiste nello scrivere, nel web application firewall, una regola che riconosce le richieste dirette al punto vulnerabile e le blocca prima che arrivino a WordPress. Il codice del plugin resta com’è, ma la richiesta pericolosa non lo raggiunge. I servizi WAF commerciali e alcuni plugin di sicurezza distribuiscono queste regole in automatico; su un server gestito si possono scrivere a mano, per esempio con ModSecurity o Coraza e il set di regole OWASP CRS.
Supponiamo che l’avviso indichi un caricamento di file senza autenticazione tramite l’azione AJAX pv_upload. Una regola ModSecurity mirata potrebbe essere questa:
# Blocca l'azione AJAX vulnerabile, sia in query string sia nel corpo POST
SecRule REQUEST_FILENAME "@endsWith /wp-admin/admin-ajax.php" \
"id:1009001,phase:2,deny,status:403,log,\
msg:'Virtual patch: plugin-vulnerabile pv_upload',chain"
SecRule ARGS:action "@streq pv_upload" "t:none,t:lowercase"
La regola lavora in fase 2, quando ModSecurity ha già letto il corpo della richiesta, e usa ARGS, che comprende sia i parametri della query string sia quelli inviati in POST. È un punto importante: molti exploit per WordPress passano il nome dell’azione nel corpo, e una regola che guarda solo l’URL non li vedrebbe.
Il virtual patching ha limiti da conoscere. Una regola deve descrivere la vulnerabilità, non l’exploit pubblicato: se blocca solo la stringa esatta della prova di concetto, basta cambiare una maiuscola o una codifica per aggirarla. Se invece è troppo larga, blocca anche l’uso legittimo del plugin. E una regola va sempre verificata, inviando una richiesta di prova innocua che la faccia scattare e controllando nei log che il blocco sia avvenuto.
Bloccare l’endpoint vulnerabile sul web server
Se non c’è un WAF, o in aggiunta a esso, si può bloccare il punto vulnerabile direttamente nella configurazione di NGINX, Apache o LiteSpeed. Il principio è lo stesso: la richiesta viene respinta con un 403 prima di arrivare a PHP-FPM, quindi il codice del plugin non viene nemmeno caricato.
Il caso più semplice è un file PHP del plugin richiamabile direttamente, oppure una rotta dell’API REST. Con NGINX la regola va nel virtual host, prima del blocco che passa i file PHP a PHP-FPM:
# File del plugin richiamato direttamente
location ^~ /wp-content/plugins/plugin-vulnerabile/includes/ {
deny all;
}
# Rotta REST vulnerabile, anche nella forma ?rest_route=
location ^~ /wp-json/pv/v1/ {
return 403;
}
if ($arg_rest_route ~* "^(/|%2F)pv(/|%2F)v1") {
return 403;
}
Il secondo blocco ricorda un dettaglio che si dimentica spesso: WordPress risponde alle chiamate REST anche con il parametro rest_route, senza passare da /wp-json/. Bloccare solo il percorso lascia aperta l’altra porta. Con Apache, e con LiteSpeed che legge i file .htaccess, lo stesso risultato si ottiene con mod_rewrite, da mettere prima delle regole di WordPress:
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_URI} ^/wp-json/pv/v1/ [NC,OR]
RewriteCond %{QUERY_STRING} (^|&)rest_route=(/|%2F)pv(/|%2F)v1 [NC,OR]
RewriteCond %{REQUEST_URI} ^/wp-content/plugins/plugin-vulnerabile/includes/ [NC]
RewriteRule ^ - [F]
</IfModule>
Le azioni AJAX sono il caso più difficile. Tutte passano da admin-ajax.php, quindi non si può bloccare il file senza rompere metà del sito, e il nome dell’azione viaggia spesso nel corpo della richiesta POST, che NGINX e Apache senza moduli aggiuntivi non ispezionano. Si può bloccare l’azione quando arriva in query string (con NGINX, if ($arg_action = "pv_upload") { return 403; } dentro location = /wp-admin/admin-ajax.php), ma per coprire anche il corpo serve un WAF. Come per il virtual patching, ogni regola va provata: prima con una richiesta che deve essere bloccata, poi navigando il sito per verificare che le funzioni legittime del plugin funzionino ancora.
Leggere i log: l’attacco è già passato?
Le contromisure proteggono dal momento in cui vengono applicate. Ma se il plugin era vulnerabile da mesi, e lo sfruttamento è iniziato prima dell’avviso, bisogna chiedersi se qualcuno è già entrato. I log del server web sono il primo posto in cui guardare, a patto di averli conservati abbastanza a lungo.
# Richieste all'endpoint vulnerabile, anche nei log ruotati e compressi
zgrep -h 'pv_upload\|/wp-json/pv/v1\|rest_route=%2Fpv' /var/log/nginx/access.log* | less
# Solo le POST andate a buon fine, raggruppate per IP
zgrep -h 'POST .*admin-ajax.php' /var/log/nginx/access.log* \
| awk '$9 == 200 {print $1}' | sort | uniq -c | sort -rn | head -20
# File PHP comparsi dopo la data del primo tentativo
find /var/www/sito -type f -name '*.php' -newermt '2026-09-01' -ls
Un limite da tenere presente è che il log di accesso non registra il corpo delle richieste POST: si vede che qualcuno ha chiamato admin-ajax.php, non quale azione ha invocato né cosa ha inviato. Per questo contano le altre fonti: il log di audit del WAF, se c’è, il log degli errori di PHP-FPM, e soprattutto le tracce che un exploit riuscito lascia sul sito. Le più comuni sono file PHP negli upload (ne abbiamo parlato nell’articolo sulle webshell negli upload), amministratori creati di nascosto, file nella cartella mu-plugins e attività pianificate aggiunte al cron.
Se si trova anche una sola di queste tracce, il problema non è più la vulnerabilità ma la compromissione: il blocco dell’endpoint impedisce nuovi ingressi, non rimuove quello che è già stato installato.
Payload sconosciuti e AI locale: un aiuto al triage
Quando una vulnerabilità è davvero nuova, non esistono firme né elenchi di indicatori di compromissione. Nei log si trovano migliaia di richieste verso l’endpoint coinvolto, e bisogna separare le scansioni innocue dai tentativi falliti e dai tentativi riusciti. È un lavoro ripetitivo, in cui i modelli linguistici sono utili se usati come assistenti: raggruppano richieste simili, decodificano parametri in base64 o URL-encoded, riconoscono pattern tipici come un file .phtml in un campo di caricamento o una catena eval(base64_decode(…)) in un parametro.
Nel nostro lavoro usiamo modelli open-weight, come Qwen e DeepSeek, eseguiti su hardware nostro. Il motivo principale è la riservatezza: i log contengono indirizzi IP, email, a volte parametri con dati personali, e non ha senso inviarli a un servizio esterno per classificarli. Abbiamo descritto l’approccio in dettaglio nell’articolo sull’analisi dei log con AI locale.
Il limite va detto chiaramente: un modello può sbagliare, e lo fa con la stessa sicurezza con cui ha ragione. Una richiesta classificata come «exploit» è un’ipotesi da verificare sul disco e nel database, non una conclusione; una richiesta classificata come «legittima» non esclude nulla. Il modello riduce il tempo necessario per arrivare alle richieste interessanti, ma chi decide quali regole scrivere e cosa cercare è una persona che conosce WordPress e il sistema su cui gira.
Quando arriva la patch
Il giorno in cui lo sviluppatore pubblica la correzione, la finestra 0-day si chiude e se ne apre un’altra: la patch esiste, ma finché il sito non è aggiornato il rischio resta, e anzi cresce, perché il confronto tra la versione vecchia e quella nuova mostra a chiunque dove si trovava il difetto. Conviene quindi:
- aggiornare il plugin appena possibile, con
wp plugin update plugin-vulnerabileo dal pannello, dopo aver verificato nel changelog che la versione corregga davvero la vulnerabilità segnalata; - controllare l’integrità dei file del plugin rispetto alla versione ufficiale, per esempio con
wp plugin verify-checksums plugin-vulnerabile, che funziona per i plugin pubblicati nella directory di WordPress.org; - ripetere le verifiche di compromissione sul periodo precedente all’aggiornamento, perché la patch non rimuove ciò che è stato installato prima;
- togliere le regole temporanee solo dopo queste verifiche, e annotarle: una regola dimenticata può bloccare una funzione legittima mesi dopo, senza che nessuno ricordi perché.
Resta un’ultima lezione, che riguarda il prima più che il dopo. Chi sa esattamente quali plugin ha installati, conserva i log per qualche settimana, riceve gli avvisi di sicurezza e ha già un modo per applicare una regola sul server, affronta una 0-day in un’ora. Chi deve ricostruire tutto al momento ci mette giorni, e nel frattempo il sito è esposto. Le misure di hardening di base non fermano una vulnerabilità sconosciuta, ma limitano quello che un attacco riuscito può fare.
Se una vulnerabilità ancora senza patch ha già colpito il tuo sito, il lavoro da fare è una bonifica completa: trovare ogni file e ogni voce del database aggiunti dall’attacco, compresa la persistenza a livello di sistema, e chiudere il punto d’ingresso. Lo facciamo con tempi garantiti di 6, 12 o 24 ore per installazione WordPress; per i dettagli puoi scriverci.