Attaque BdThemes : une XSS par JSON compromet des sites WordPress
Une réponse JSON distante, chargée pour afficher une bannière promotionnelle dans l’administration, a suffi à compromettre des sites WordPress sans modifier les fichiers des extensions installées. Cette attaque de chaîne d’approvisionnement vise plusieurs extensions BdThemes et peut donner aux attaquants un accès administrateur persistant ainsi que l’exécution de code à distance.
Action immédiate
Les extensions concernées ont été fermées temporairement dans le répertoire officiel WordPress le 7 août 2026, pendant leur inspection. Si votre site utilise l’une d’elles, recherchez immédiatement les indicateurs de compromission dans les comptes, les extensions, les fichiers et les options de la base de données.
Une XSS injectée par un flux JSON distant
Les extensions BdThemes embarquent Biggopti, un composant interne qui récupère des bannières promotionnelles et les affiche dans le tableau de bord WordPress. Les données proviennent de l’API du fournisseur, elle-même alimentée par des fichiers JSON statiques stockés dans un bucket DigitalOcean Spaces protégé par Cloudflare, et non par un serveur applicatif dynamique.
La compromission ne modifie aucun fichier source dans le dépôt WordPress.org ni sur le disque du site. Des attaquants ont obtenu un accès en écriture au bucket de stockage, puis ont remplacé les réponses JSON légitimes par des réponses malveillantes qui exploitent une vulnérabilité de cross-site scripting (XSS) dans Biggopti.
Chaque extension qui utilise Biggopti charge un fichier JavaScript côté client sur le hook admin_init. Le script s’exécute donc sans condition à chaque chargement d’une page wp-admin, récupère les données de bannière, puis insère directement display_id dans un attribut HTML id, sans l’échapper :
// Valeur distante non échappée.
var f = t.display_id || t.id || "default";
// La valeur devient une partie de l'attribut id dans le DOM.
var m = "bdt-admin-biggopti-api-biggopti-" + f;
A = 'id="' + m + '"';Le même code échappe pourtant correctement la valeur lorsqu’il construit l’attribut data-display-id. La routine, vraisemblablement issue de la minification ou du bundling, remplace les caractères &, <, >, et " par leurs entités HTML. Cette protection ne couvre pas l’attribut id vulnérable.
function p(t) {
return (t || "")
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """);
}L’historique SVN montre que Prime Slider, version 4.1.9, a introduit cette injection non échappée le 1er mars 2026. Les autres extensions ont ensuite reçu le même code. Des versions ultérieures ont ajouté un assainisseur fondé sur DOMParser pour le champ de contenu des bannières, afin de supprimer les balises HTML dangereuses et les gestionnaires d’événements en ligne, mais elles ont laissé l’injection dans id intacte.
La charge utile placée dans display_id ferme l’attribut id, ajoute un gestionnaire onanimationstart, puis déclenche ce gestionnaire grâce à une animation CSS en 10 millisecondes. Le JavaScript s’exécute silencieusement dans le navigateur de chaque administrateur connecté qui ouvre une page du tableau de bord. L’attaque échappe ainsi largement aux contrôles d’intégrité basés sur les fichiers et reste peu visible pour les pare-feu applicatifs.
Extensions touchées et gravité
La vulnérabilité Biggopti affecte diverses versions de ces extensions. Son score CVSS est de 5,4 sur 10, soit un niveau moyen, et aucun correctif n’était disponible au moment du signalement.
Les extensions concernées sont les suivantes :
- 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).
Une compromission en plusieurs étapes
Le gestionnaire injecté télécharge des scripts externes en fonction de la disponibilité de l’infrastructure des attaquants. Deux charges utiles distinctes ont été observées, selon l’endpoint Biggopti interrogé par l’extension.
Charge utile principale : w2.js
Les extensions qui appellent l’endpoint api-data-all-records reçoivent la charge utile principale w2.js. Elle s’exécute dans la session authentifiée de l’administrateur et enchaîne les actions suivantes :
- Elle contacte le serveur de commande et de contrôle (C2)
ia-cdn[.]com/fz/c, en lui transmettant l’origine du site victime. Elle interrompt l’attaque si le C2 répondskipoudone. - Elle récupère le nonce
X-WP-Noncede la session d’administration et crée un nouvel administrateur via l’API REST de WordPress. Si cette voie échoue, elle utilise les soumissions de formulaires classiques. - Elle télécharge depuis le C2 une fausse extension ZIP et l’installe avec le formulaire standard d’envoi d’extension. Cette extension adopte le slug
wp-smart-thumbnails, ou un slug neutre similaire, et contient un webshellemer-run.phpaccessible directement par URL. - Elle appelle
emer-run.php, qui installe deux modules de persistance dans le répertoire des extensions indispensables (MU plugins). Les deux échantillons collectés portent un horodatage antidaté à septembre 2025 afin de se fondre dans les dates légitimes du système de fichiers, même si les noms de fichiers peuvent varier entre infections. - Le premier module MU ajoute une porte dérobée de connexion magique : le paramètre d’URL
?_wplogin=<token>donne un accès administrateur sans authentification en ciblant l’administrateur enregistré depuis le plus longtemps sur le site. - Le second module MU dissimule l’attaque pendant l’analyse. Il intercepte les requêtes de base de données de WordPress, masque les comptes malveillants dans la liste des utilisateurs de l’administration et diminue aussi le nombre total d’utilisateurs affiché.
- Elle transmet les résultats de chaque phase au C2 avec
navigator.sendBeacon.
Charge utile alternative : x.js
Les extensions qui appellent api-data-records reçoivent le script alternatif x.js, hébergé directement sur l’infrastructure de BdThemes. Ce script calcule des identifiants administrateur déterministes à partir du nom d’hôte du site victime :
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'
};
}Le compte suit donc le format bd_ suivi d’un hash base36 de six caractères, et le mot de passe suit le format Bd@26!<hash>x. Le script associe ce compte à une adresse en @wordpress.org. Les attaquants n’ont pas besoin de conserver une liste centrale des sites compromis, car ils peuvent recalculer les identifiants d’un domaine donné. Les équipes de réponse à incident peuvent appliquer le même principe pour rechercher le compte attendu sur un site suspect.
Le script télécharge /wp-admin/user-new.php avec les cookies de la victime, cherche le nonce de création d’utilisateur dans le HTML, puis envoie un formulaire POST qui demande le rôle administrator. Il transmet au C2 l’état de la création, les identifiants générés, l’origine du site, l’URL de déconnexion trouvée et l’URL de la page courante. Il privilégie navigator.sendBeacon, puis bascule vers une requête fetch POST sans cookies et en mode no-cors. Lorsqu’il le peut, il enregistre aussi un indicateur dans localStorage afin de ne pas répéter l’opération dans le même navigateur.
Portée de la campagne
Le domaine C2 est lié aux acteurs associés aux attaques de chaîne d’approvisionnement récentes contre Advanced Responsive Video Embedder et OptinMonster. La campagne cherche à installer, à grande échelle et sans bruit, des accès administrateur persistants et à exécuter du code à distance dans des environnements WordPress.
Le vecteur est particulièrement dangereux parce que les attaquants n’ont pas eu à accéder aux sites visés ni à modifier le code des extensions. Ils ont empoisonné un flux de bannières considéré comme fiable, ce qui a transformé des avis d’administration en mécanisme de diffusion de malwares. La présence d’enregistrements JSON malveillants et de x.js dans le bucket du fournisseur indique une compromission grave de ses identifiants de stockage cloud ou de son infrastructure interne.
Indicateurs de compromission à rechercher
Inspectez les ressources externes, les fichiers, les comptes WordPress et les options de base de données ci-dessous. Les deux endpoints JSON empoisonnés ont ensuite renvoyé des données propres, mais leur consultation passée ou leurs traces dans les journaux restent des éléments utiles à l’investigation.
Ressources réseau
- Charge utile principale :
ia-cdn[.]com/fz/w2.js. - Endpoint C2 et de beacon :
ia-cdn[.]com/fz/c. - Endpoint JSON empoisonné, désormais nettoyé :
api[.]sigmative[.]io/prod/store/api/biggopti/api-data-all-records. - Endpoint JSON empoisonné, désormais nettoyé :
api[.]sigmative[.]io/prod/store/api/biggopti/api-data-records. - Script alternatif :
api[.]sigmative[.]io/prod/store/api/biggopti/x.js.
Fichiers et base de données
- Webshell
emer-run.php, empreinte MD51024732009983dd5e54b4cf5593f04d4. - Porte dérobée MU de connexion magique
class-wp-token-validate.php, empreinte MD57719cd98a35ffad2771f26d1ceab7d27. - Module MU de furtivité
class-wp-query-9d127ff3.php, ou un nom suivant le modèleclass-wp-query-*.php, empreinte MD59aadc3e5c5242b273bd17c5bdc358845. - Module MU présenté comme contrôle d’état
wp-cache-optimizer.php, empreinte MD5e450ae5bc4bfc0d960dded06a76bb8e9. - Option de base de données
fz_emer_login_tokens, qui stocke les jetons de connexion magique. - Option de base de données
fz_emer_done_v1, qui signale qu’une compromission est terminée. - Comptes dont l’adresse e-mail utilise
@wordpress.orgou@developer.wordpress.org. - Noms d’utilisateur conformes à
bd_suivi de six caractères alphanumériques.
Chronologie de l’incident
- 1er mars 2026 : Biggopti ajoute son JavaScript d’API à Prime Slider 4.1.9, dans la révision SVN
r3471891.display_identre sans échappement dans l’attributid, et l’extension utilise alorsapi-data-records. - 10 mai 2026 : l’extension bascule vers
api-data-all-records. Un assainisseurDOMParserprotège le champ de contenu, mais l’attributidreste vulnérable. - 23 juin 2026 : la date
start_datede la campagne empoisonnée, présentée sous la forme d’une bannière « Summer Sale », constitue la date la plus ancienne à laquelle la XSS a pu être active. - 6 août 2026 à 22:40 UTC : dernière modification observée de
api-data-recordsempoisonné. - 7 août 2026 à 09:11 UTC : dernière modification observée de
api-data-all-recordsempoisonné. - 7 août 2026 : l’attaque est signalée et observée en conditions réelles ; le répertoire WordPress ferme les extensions pendant leur examen.
- 7 août 2026 à 16:56 UTC : les artefacts de l’enquête sont capturés.
- 8 août 2026 : les deux endpoints d’API renvoient à nouveau du JSON propre.
Auditez les sites qui utilisent ces extensions
Commencez par identifier les sept extensions concernées sur chaque site. Contrôlez ensuite la liste réelle des utilisateurs dans la base de données, et pas seulement l’écran d’administration, car le module de furtivité peut masquer les comptes créés. Recherchez les adresses et noms d’utilisateur indiqués, en tenant compte des identifiants déterministes générés depuis le nom d’hôte.
Examinez le répertoire des extensions installées et celui des MU plugins pour trouver les fichiers, les modèles de noms et les empreintes listés. Cherchez enfin les options fz_emer_login_tokens et fz_emer_done_v1 dans la table des options, ainsi que les connexions aux domaines et endpoints signalés dans les journaux réseau. L’absence de modification des fichiers d’une extension BdThemes ne permet pas d’écarter une compromission : l’exécution initiale provenait du JSON distant chargé dans le navigateur d’un administrateur.
Ne vous limitez pas aux fichiers de l’extension
Traitez toute correspondance comme un incident complet : un compte administrateur clandestin, un webshell et des modules MU peuvent survivre au retrait de l’extension touchée. Supprimez les mécanismes de persistance identifiés et vérifiez l’ensemble des comptes, fichiers et options avant de remettre le site en service.
Aminata Diallo
Développeuse junior et blogueuse technique. Python et Django sont mes domaines principaux, mais le monde JavaScript m'attire aussi. J'aime écrire des guides pour les débutants.
Tous les articles