BdThemes: una risposta JSON avvelenata compromette i plugin WordPress
Una compromissione della supply chain ha colpito l’ecosistema di plugin BdThemes senza modificare alcun file nel repository ufficiale di WordPress.org. Gli attaccanti hanno alterato una risposta JSON remota usata per mostrare banner promozionali nella Bacheca e hanno sfruttato una XSS per eseguire JavaScript nel browser di ogni amministratore autenticato.
I sette plugin coinvolti sono stati chiusi temporaneamente nella directory di WordPress durante l’ispezione. Se li hai usati, non limitarti a verificare gli aggiornamenti: controlla utenti, plugin, directory dei Must-Use plugin e tabella delle opzioni del database.
La vulnerabilità: una XSS nel campo display_id
Biggopti è un componente interno distribuito nei plugin BdThemes che scarica banner promozionali dall’API del fornitore e li renderizza nell’area amministrativa di WordPress. L’API legge file JSON statici da un bucket DigitalOcean Spaces protetto da Cloudflare, non da un application server dinamico.
La libreria Biggopti presenta una vulnerabilità Cross-Site Scripting (XSS) con punteggio CVSS 5,4 su 10, classificata come media e priva di correzione al momento della rilevazione. Il parsing insufficiente della risposta della Sigmative API consente a chi controlla quel server di iniettare script nelle pagine visualizzate dagli utenti.
Ogni plugin che usa Biggopti registra un asset JavaScript client-side sull’hook admin_init. Lo script parte quindi a ogni caricamento di qualunque pagina wp-admin, recupera i dati del banner e concatena display_id nell’attributo HTML id senza eseguire l’escaping lato client.
// Il valore remoto entra direttamente nell'attributo id.
var f = t.display_id || t.id || "default";
var m = "bdt-admin-biggopti-api-biggopti-" + f;
A = 'id="' + m + '"';Nello stesso codice, l’attributo data-display-id viene invece sottoposto a escaping: la funzione sostituisce &, <, >, e " con le rispettive entità HTML. Questa differenza lascia vulnerabile l’attributo id, anche se le versioni successive hanno introdotto un sanitizer basato su DOMParser per il solo campo content del banner: il sanitizer elimina tag HTML pericolosi e handler inline, ma non risolve l’iniezione in id.
function escapeHtml(value) {
return (value || "")
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """);
}La cronologia SVN colloca l’introduzione dell’attributo non sottoposto a escaping al 1° marzo 2026: Prime Slider 4.1.9, revisione SVN r3471891, ha poi propagato il comportamento agli altri plugin. Un soggetto non autorizzato con accesso in scrittura al bucket ha sostituito i JSON legittimi con record costruiti ad hoc.
Il valore ostile chiude l’attributo id e aggiunge un handler onanimationstart. Un’animazione CSS lo attiva entro 10 millisecondi, in silenzio, quando un amministratore apre una pagina della Bacheca. Il payload visto nel campo display_id usava eval(String.fromCharCode(...)) per scaricare JavaScript remoto da api.sigmative.io e impostava lo stile animation:wrapperSlideInTop .01s ease-out both.
Perché l’attacco elude i controlli basati sui file
Per diventare vittima non serve installare un aggiornamento del plugin. Gli attaccanti non modificano i file del plugin sul disco: la compromissione arriva interamente dall’API remota e parte nel browser dell’amministratore autenticato.
Di conseguenza, gli scanner che confrontano l’integrità dei file non trovano modifiche e un Web Application Firewall rileva con difficoltà il flusso. Il feed promozionale, considerato attendibile dal codice del plugin, diventa il veicolo che consegna il malware.
La catena di compromissione a più stadi
L’handler iniettato sceglie script esterni in base alla disponibilità dell’infrastruttura degli attaccanti. La campagna impiega un payload principale, w2.js, per i plugin che interrogano api-data-all-records, e un payload alternativo, x.js, per quelli che interrogano api-data-records.
Payload principale: w2.js
Eseguito nella sessione dell’amministratore, w2.js completa cinque passaggi:
- Verifica del C2: contatta
ia-cdn[.]com/fz/c, inviando l’origine del sito vittima per ricevere istruzioni di targeting. Interrompe l’esecuzione se il C2 risponde con lo statoskipodone. - Creazione di un amministratore non autorizzato: usa l’header
X-WP-Noncegià disponibile nella sessione amministrativa per creare un utente amministratore attraverso la REST API di WordPress; se il tentativo fallisce, passa ai normali invii dei moduli. - Installazione di una webshell: scarica dal C2 un archivio ZIP che imita un plugin e lo carica attraverso il modulo standard di upload dei plugin. Il falso plugin usa slug neutri come
wp-smart-thumbnailse contiene la webshellemer-run.php, richiamata direttamente tramite URL. - Backdoor nei Must-Use plugin: l’esecuzione di
emer-run.phpinstalla due moduli persistenti nella directory dei Must-Use plugin. I campioni osservati riportano date retrodatate a settembre 2025 per confondersi con i timestamp del filesystem; i nomi dei file possono cambiare tra un’infezione e l’altra. Il primo modulo abilita un login amministrativo senza autenticazione con il parametro URL?_wplogin=<token>e prende di mira l’amministratore registrato da più tempo. Il secondo aggancia le query del database di WordPress, nasconde gli account creati dall’elenco degli utenti amministrativi e riduce di conseguenza il conteggio totale degli utenti. - Esfiltrazione: invia al C2 gli esiti di tutte le fasi esecutive mediante
navigator.sendBeacon.
Payload alternativo: x.js
x.js risulta ospitato direttamente nell’infrastruttura BdThemes. Calcola credenziali amministrative deterministiche dall’hostname del sito vittima: crea un hash a 32 bit scorrendo i caratteri dell’host, ne usa i primi sei caratteri in base36 e costruisce utente e password.
function makeCreds() {
var host = siteKey();
var hash = 0;
for (var i = 0; i < host.length; i++) {
hash = ((hash << 5) - hash + host.charCodeAt(i)) | 0;
}
var n = Math.abs(hash).toString(36).slice(0, 6);
return {
user: 'bd_' + n,
pass: 'Bd@26!' + n + 'x'
};
}L’algoritmo genera quindi username nel formato bd_ seguito da sei caratteri alfanumerici e password nel formato Bd@26! seguito dallo stesso hash e dalla lettera x. Associa inoltre l’account a un indirizzo @wordpress.org. Poiché le credenziali dipendono dall’hostname, gli attaccanti non devono conservare centralmente l’elenco dei siti compromessi e chi svolge incident response può calcolarle per cercarle su un dominio sospetto.
Lo script recupera wp-admin/user-new.php nella sessione same-origin, estrae nonce e URL di logout dall’HTML, quindi invia un POST per creare l’utente con ruolo administrator. Registra l’esito – inclusi username, password, origine del sito, URL di logout e pagina corrente – verso il C2 con navigator.sendBeacon; in alternativa usa fetch con mode: 'no-cors', credentials: 'omit' e keepalive: true. Se disponibile, salva anche un flag in localStorage per non ripetere l’operazione nello stesso browser.
// Il payload evita una seconda esecuzione nel browser già marcato.
try {
if (localStorage.getItem(DONE_KEY)) return;
} catch (e) {}
modAdmin();Plugin e indicatori di compromissione
L’elenco dei plugin interessati comprende:
- Element Pack Addons for Elementor – Elementor Widgets, Elementor Templates, Elementor Addons – slug
bdthemes-element-pack-lite - Prime Slider Addons for Elementor – Widgets, Templates & Elementor Addons – slug
bdthemes-prime-slider-lite - Pixel Gallery Addons for Elementor – Easy Grid, Creative Gallery, Drag and Drop Grid, Custom Grid Layout, Portfolio Gallery – slug
pixel-gallery - Ultimate Post Kit Addons for Elementor – slug
ultimate-post-kit - Ultimate Store Kit – Addon For WooCommerce, EDD and Elementor – slug
ultimate-store-kit - Live Copy Paste for Elementor – Cross Domain Copy Paste & Page Duplicator – slug
live-copy-paste - Smart Admin Assistant – Dashboard and Site Enhancements – slug
smart-admin-assistant
Cerca inoltre questi indicatori nelle connessioni in uscita, nel filesystem e nel database:
- Payload principale:
ia-cdn[.]com/fz/w2.js. - Endpoint beacon del C2:
ia-cdn[.]com/fz/c. - Endpoint BdThemes che hanno distribuito JSON avvelenato, successivamente ripuliti:
api[.]sigmative[.]io/prod/store/api/biggopti/api-data-all-records,api[.]sigmative[.]io/prod/store/api/biggopti/api-data-recordseapi[.]sigmative[.]io/prod/store/api/biggopti/x.js. - Webshell
emer-run.php, MD51024732009983dd5e54b4cf5593f04d4. - Backdoor di magic login nei Must-Use plugin:
class-wp-token-validate.php, MD57719cd98a35ffad2771f26d1ceab7d27. - Modulo stealth nei Must-Use plugin:
class-wp-query-9d127ff3.phpo un nome affine al patternclass-wp-query-*.php, MD59aadc3e5c5242b273bd17c5bdc358845. - Modulo Must-Use plugin presentato come health check:
wp-cache-optimizer.php, MD5e450ae5bc4bfc0d960dded06a76bb8e9. - Opzione del database
fz_emer_login_tokens, che conserva i token per il magic login. - Opzione del database
fz_emer_done_v1, che indica il completamento della compromissione. - Account con indirizzi nei domini
@wordpress.orgo@developer.wordpress.org. - Username che corrispondono al pattern
bd_seguito da sei caratteri alfanumerici.
Cronologia della campagna
- 1° marzo 2026: Biggopti API JS entra in Prime Slider 4.1.9, revisione SVN
r3471891;display_idviene inserito nell’attributoidsenza escaping e il plugin usa l’endpointapi-data-records. - 10 maggio 2026: l’endpoint cambia in
api-data-all-records; arriva il sanitizerDOMParserper il campocontent, ma l’attributoidresta vulnerabile. - 23 giugno 2026: il campo
start_datenella campagna JSON avvelenata indica l’avvio dell’avviso “Summer Sale”; è la prima data possibile in cui la XSS poteva essere attiva. - 6 agosto 2026, 22:40 UTC: ultimo aggiornamento registrato per
api-data-recordsavvelenato. - 7 agosto 2026, 09:11 UTC: ultimo aggiornamento registrato per
api-data-all-recordsavvelenato. - 7 agosto 2026: l’attacco viene osservato attivamente e i plugin vengono chiusi nella directory di WordPress in attesa di revisione.
- 7 agosto 2026, 16:56 UTC: vengono acquisiti gli artefatti dell’indagine.
- 8 agosto 2026: entrambi gli endpoint API tornano a distribuire JSON pulito.
Cosa fare sui siti esposti
Il dominio C2 è collegato agli attori già associati agli attacchi alla supply chain contro Advanced Responsive Video Embedder e OptinMonster nei due mesi precedenti. L’accesso ai record JSON dannosi e a x.js nel bucket del fornitore indica una compromissione grave delle credenziali cloud o dell’infrastruttura interna di BdThemes.
Esamina subito l’elenco utenti e rimuovi ogni account amministrativo non autorizzato, con particolare attenzione ai pattern bd_****** e alle email indicate. Ispeziona le directory dei plugin e dei Must-Use plugin per i file e gli hash elencati; verifica poi wp_options per fz_emer_login_tokens e fz_emer_done_v1. Dato che l’attacco può aver installato una webshell e moduli persistenti, tratta ogni indicatore trovato come una compromissione completa del sito con esecuzione di codice remoto.
Dopo la bonifica, ruota le credenziali amministrative e quelle dell’hosting, verifica le connessioni in uscita verso gli indicatori elencati e riesamina i plugin BdThemes prima di riattivarli. Il JSON pulito distribuito dagli endpoint non rimuove gli account, le webshell o le backdoor che un payload già eseguito può aver lasciato nel sito.
Riferimenti / Fonti
- PSA: Supply Chain Compromise in BdThemes Ecosystem via Poisoned API Response
- Biggopti Library (Various Versions) – Cross-Site Scripting via display_id from Sigmative API
- Wordfence Prism Detected Backdoored WordPress Plugin Within Two Hours of It Being Introduced
- Security Incident: Tampered Script Served via OptinMonster and TrustPulse
Marco Bianchi
Sviluppatore di giochi e programmatore grafico. Unity e WebGL sono il mio campo. Amo i progetti visivamente spettacolari e la programmazione creativa.
Tutti gli articoli