BdThemes: kompromitované API spouštělo XSS v administraci WordPressu
Útočníci kompromitovali ekosystém pluginů BdThemes, aniž by změnili jediný soubor v oficiálním repozitáři WordPressu. Stačilo jim upravit vzdálený JSON feed s propagačními bannery, který pluginy načítaly do každé stránky administrace, a zneužít neescapovaný atribut HTML.
Důsledek je kritický: škodlivý JavaScript běží v prohlížeči přihlášeného administrátora. Může vytvořit cizí účet správce, nahrát plugin s webshellu, nainstalovat perzistentní zadní vrátka a odeslat výsledky na C2 server. Napadený web přitom nepotřeboval aktualizaci pluginu a soubory pluginu na disku zůstaly beze změny.
Co udělat hned
Používáš-li některý z dotčených pluginů, okamžitě prověř seznam uživatelů, adresáře pluginů včetně mu-plugins a tabulku wp_options na uvedené indikátory kompromitace. Pouhá kontrola integrity souborů tento útok nemusí odhalit.
Pluginy byly 7. srpna 2026 v adresáři WordPressu dočasně uzavřeny, zatímco pokračovala jejich kontrola. K 8. srpnu obě kompromitovaná API vracela čistý JSON. Nelze ale předpokládat, že tím automaticky zmizely účty, webshell nebo MU-pluginy, které útok mohl na web už uložit.
Jak útok fungoval
Dotčené pluginy obsahují interní komponentu Biggopti. Ta při načtení administrace získává propagační bannery z API a vykresluje je v wp-admin. Zdroj dat netvořil dynamický aplikační server: šlo o statické JSON soubory v DigitalOcean Spaces, které chránil Cloudflare.
JavaScript Biggopti se registruje na hook admin_init, takže se spustí bezpodmínečně při každém načtení libovolné stránky wp-admin. Z odpovědi API převezme hodnotu display_id a bez escapování ji vloží do HTML atributu id. Právě tady vzniká XSS (cross-site scripting): útočník může do hodnoty vložit vlastní HTML atribut nebo JavaScript, který prohlížeč administrátora spustí.
// Hodnota z nedůvěryhodné vzdálené odpovědi
var f = t.display_id || t.id || "default";
var m = "bdt-admin-biggopti-api-biggopti-" + f;
// Chybí escapování f před vložením do HTML
A = 'id="' + m + '"';Ve stejném kódu se atribut data-display-id escapoval funkcí, která nahrazuje &, <, >, " za HTML entity. Pro atribut id ale tato ochrana chyběla. Pozdější verze doplnily sanitizaci obsahu banneru přes DOMParser, která odstraňuje nebezpečné HTML tagy a inline handlery; neescapované display_id v id však zůstalo.
function escapeHtml(value) {
return (value || "")
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """);
}Chyba se objevila 1. března 2026 v Prime Slider 4.1.9, v SVN revizi r3471891, a následně se rozšířila do dalších pluginů. Neoprávněná osoba získala zápis do úložiště dodavatele a nahradila legitimní JSON připravenými záznamy. Hodnota display_id ukončila atribut id, přidala handler onanimationstart a CSS animaci; prohlížeč tak spustil payload přibližně do 10 ms od načtení administrační stránky.
Útočník v display_id skrýval přes String.fromCharCode() volání fetch() na externí skript. Ve zneužité odpovědi se banner tvářil jako letní nabídka „Build more. Pay less. This Summer“, ale ve skutečnosti se z něj stal spouštěč další fáze infekce.
Dotčené pluginy a zranitelnost
Zranitelnost v knihovně Biggopti má CVSS 5,4, tedy střední závažnost, a v okamžiku zveřejnění neměla opravu. Útočník však potřeboval kompromitovat server Sigmative API; za této podmínky mohl vložit libovolný skript do stránek, které otevře přihlášený uživatel.
Riziko se týká těchto pluginů a slugů:
- Element Pack Addons for Elementor (
bdthemes-element-pack-lite) - Prime Slider Addons for Elementor (
bdthemes-prime-slider-lite) - Pixel Gallery Addons for Elementor (
pixel-gallery) - Ultimate Post Kit Addons for Elementor (
ultimate-post-kit) - Ultimate Store Kit – Addon For WooCommerce, EDD and Elementor (
ultimate-store-kit) - Live Copy Paste for Elementor (
live-copy-paste) - Smart Admin Assistant (
smart-admin-assistant)
Primární payload: účet správce, webshell a perzistence
Pluginy, které používaly endpoint api-data-all-records, načítaly primární payload w2.js. Skript nejdřív kontaktoval C2 endpoint ia-cdn[.]com/fz/c, kterému předal origin napadeného webu. Pokud server odpověděl stavem skip nebo done, skript akci ukončil; jinak dostal instrukce pro další postup.
V aktivní relaci administrátora pak zneužil X-WP-Nonce a přes WordPress REST API založil nový účet s rolí administrátora. Když REST API neuspělo, přešel na klasické odeslání formuláře. Následně z C2 stáhl ZIP falešného pluginu a nahrál ho přes standardní formulář pro instalaci pluginu.
Falešný plugin používal slug wp-smart-thumbnails nebo podobně neutrálně znějící název. Obsahoval webshell emer-run.php, který útočník volal přímo přes URL. Tento soubor dále ukládal do adresáře Must-Use pluginů dva moduly pro perzistenci; získané vzorky měly časové údaje zpětně nastavené na září 2025, aby lépe splynuly s existujícími soubory. Názvy souborů se mezi jednotlivými infekcemi mohou lišit.
- První MU-plugin vytváří zadní vrátka pro magic login. Nepřihlášený útočník se s tokenem v parametru
?_wplogin=<token>dostane do administrace pod nejdéle registrovaným administrátorským účtem webu. - Druhý MU-plugin zakrývá stopy. Hookuje databázové dotazy WordPressu, skryje vytvořené cizí účty v seznamu uživatelů administrace a odpovídajícím způsobem sníží i celkový počet uživatelů.
- Další zaznamenaný MU-plugin se vydává za modul „Health Check“.
- V každé fázi skript odesílá výsledek na C2 přes
navigator.sendBeacon.
Alternativní payload: předvídatelné přihlašovací údaje
Druhá větev útoku používala x.js, který ležel přímo v infrastruktuře BdThemes a obsluhoval pluginy s endpointem api-data-records. Skript nevolil údaje náhodně: vypočítal je deterministicky z hostname napadeného webu. Útočník proto nemusel centrálně uchovávat seznam kompromitovaných webů – přihlašovací údaje pro konkrétní doménu si dokázal kdykoli znovu odvodit.
function makeCreds(host) {
var hash = 0;
for (var i = 0; i < host.length; i++) {
hash = ((hash << 5) - hash + host.charCodeAt(i)) | 0;
}
var suffix = Math.abs(hash).toString(36).slice(0, 6);
return {
user: "bd_" + suffix,
pass: "Bd@26!" + suffix + "x"
};
}Tak vzniká uživatelské jméno bd_ následované šesti znaky base36 a heslo ve tvaru Bd@26! + stejný suffix + x. Skript účet spojí s e-mailem na doméně @wordpress.org, případně se při vyhledávání stop počítá také s @developer.wordpress.org. Přes načtení /wp-admin/user-new.php získá nonce a formulářem vytvoří administrátora. Výsledek – včetně uživatelského jména, hesla, originu webu, URL odhlášení a aktuální URL – pošle na C2.
Po pokusu o vytvoření účtu si payload ukládá příznak do localStorage, pokud je úložiště prohlížeče dostupné. Pro odeslání dat preferuje navigator.sendBeacon; když není k dispozici, používá fetch() s metodou POST, keepalive, režimem no-cors a bez cookies.
Indikátory kompromitace
Při incident response hledej soubory, databázové volby i podezřelé účty. Kontroluj zejména následující hodnoty:
- Primární payload:
ia-cdn[.]com/fz/w2.js - C2 beacon endpoint:
ia-cdn[.]com/fz/c - Dříve kompromitované endpointy:
api[.]sigmative[.]io/prod/store/api/biggopti/api-data-all-records,api[.]sigmative[.]io/prod/store/api/biggopti/api-data-recordsaapi[.]sigmative[.]io/prod/store/api/biggopti/x.js - Webshell
emer-run.phps MD51024732009983dd5e54b4cf5593f04d4 - MU-plugin pro magic login
class-wp-token-validate.phps MD57719cd98a35ffad2771f26d1ceab7d27 - MU-plugin pro skrývání stop
class-wp-query-9d127ff3.phpnebo podobný soubor podle vzoruclass-wp-query-*.php; zachycený vzorek má MD59aadc3e5c5242b273bd17c5bdc358845 - MU-plugin maskovaný jako kontrola zdraví
wp-cache-optimizer.phps MD5e450ae5bc4bfc0d960dded06a76bb8e9 - Volba databáze
fz_emer_login_tokens, která ukládá tokeny magic loginu - Volba databáze
fz_emer_done_v1, která označuje dokončenou kompromitaci - Účty s e-mailovou adresou na
@wordpress.orgnebo@developer.wordpress.org - Uživatelská jména odpovídající vzoru
bd_a šesti alfanumerickým znakům
Časová osa
- 1. března 2026: Prime Slider 4.1.9 dostal Biggopti API JavaScript. Revize SVN
r3471891vložiladisplay_idbez escapování do atributuid; plugin používal endpointapi-data-records. - 10. května 2026: endpoint se změnil na
api-data-all-records. Do obsahu banneru přibyla sanitizace přesDOMParser, ale atributidzůstal zranitelný. - 23. června 2026: v kompromitované kampani začínalo datum banneru „Summer Sale“. Jde o nejdřívější možný okamžik aktivního XSS.
- 6. srpna 2026, 22:40 UTC: kompromitovaný
api-data-recordsměl poslední změnu. - 7. srpna 2026, 09:11 UTC: kompromitovaný
api-data-all-recordsměl poslední změnu. - 7. srpna 2026: útok se objevil v provozu a dotčené pluginy se uzavřely v adresáři WordPressu.
- 7. srpna 2026, 16:56 UTC: vznikly forenzní artefakty pro vyšetřování.
- 8. srpna 2026: oba API endpointy začaly vracet čistý JSON.
Proč je tento útok obtížné zachytit
Útočníci nepozměnili zdrojové soubory v repozitáři WordPress.org ani soubory nainstalovaného pluginu. Zneužili důvěru v datový kanál dodavatele a administrátorský prohlížeč. Kontrola hashů souborů proto před samotným spuštěním payloadu neměla co najít a webový aplikační firewall útok obvykle viděl jen omezeně.
C2 infrastruktura souvisí také s nedávnými supply-chain útoky na Advanced Responsive Video Embedder a OptinMonster. Nahrání škodlivých JSON záznamů i x.js přímo do bucketu dodavatele ukazuje na kompromitované přístupy ke cloudovému úložišti BdThemes nebo k jeho interní infrastruktuře.
Pokud na webu najdeš byť jediný indikátor, ber ho jako incident s možným vzdáleným spuštěním kódu. Zruš podezřelé účty, odstraň nalezené škodlivé soubory a databázové volby, prověř všechny MU-pluginy, změň přístupy administrátorů i hostingu a obnov důvěryhodný stav z ověřené zálohy. Signatury malwaru a pravidla WAF získali 7. srpna 2026 uživatelé Wordfence Premium, Care, Response a placeného Wordfence CLI; bezplatné varianty je dostanou po standardní 30denní prodlevě.
Reference / Zdroje
Jakub Dvořák
Linux administrátor a DevOps inženýr. Shell scripting a automatizace jsou mou vášní. Infrastruktura jako kód je budoucnost.
Všechny příspěvky