{"id":278,"date":"2026-08-09T10:45:22","date_gmt":"2026-08-09T08:45:22","guid":{"rendered":"https:\/\/helloblog.io\/fr\/attaque-bdthemes-xss-json-indicateurs-compromission\/"},"modified":"2026-08-09T10:45:22","modified_gmt":"2026-08-09T08:45:22","slug":"attaque-bdthemes-xss-json-indicateurs-compromission","status":"publish","type":"post","link":"https:\/\/helloblog.io\/fr\/attaque-bdthemes-xss-json-indicateurs-compromission\/","title":{"rendered":"Attaque BdThemes\u00a0: une XSS par JSON compromet des sites WordPress"},"content":{"rendered":"\n<p>Une r\u00e9ponse JSON distante, charg\u00e9e pour afficher une banni\u00e8re promotionnelle dans l\u2019administration, a suffi \u00e0 compromettre des sites WordPress sans modifier les fichiers des extensions install\u00e9es. Cette attaque de cha\u00eene d\u2019approvisionnement vise plusieurs extensions BdThemes et peut donner aux attaquants un acc\u00e8s administrateur persistant ainsi que l\u2019ex\u00e9cution de code \u00e0 distance.<\/p>\n\n\n\n<div class=\"wp-block-group callout callout-danger is-style-danger is-layout-flow wp-block-group-is-layout-flow\" style=\"border-width:1px;border-radius:8px;padding-top:1rem;padding-right:1.5rem;padding-bottom:1rem;padding-left:1.5rem\">\n\n<h4 class=\"wp-block-heading callout-title\">Action imm\u00e9diate<\/h4>\n\n\n<p>Les extensions concern\u00e9es ont \u00e9t\u00e9 ferm\u00e9es temporairement dans le r\u00e9pertoire officiel WordPress le 7\u00a0ao\u00fbt\u00a02026, pendant leur inspection. Si votre site utilise l\u2019une d\u2019elles, recherchez imm\u00e9diatement les indicateurs de compromission dans les comptes, les extensions, les fichiers et les options de la base de donn\u00e9es.<\/p>\n\n<\/div>\n\n\n\n<h2 class=\"wp-block-heading\">Une XSS inject\u00e9e par un flux JSON distant<\/h2>\n\n\n\n<p>Les extensions BdThemes embarquent <em>Biggopti<\/em>, un composant interne qui r\u00e9cup\u00e8re des banni\u00e8res promotionnelles et les affiche dans le tableau de bord WordPress. Les donn\u00e9es proviennent de l\u2019API du fournisseur, elle-m\u00eame aliment\u00e9e par des fichiers JSON statiques stock\u00e9s dans un bucket DigitalOcean Spaces prot\u00e9g\u00e9 par Cloudflare, et non par un serveur applicatif dynamique.<\/p>\n\n\n\n<p>La compromission ne modifie aucun fichier source dans le d\u00e9p\u00f4t WordPress.org ni sur le disque du site. Des attaquants ont obtenu un acc\u00e8s en \u00e9criture au bucket de stockage, puis ont remplac\u00e9 les r\u00e9ponses JSON l\u00e9gitimes par des r\u00e9ponses malveillantes qui exploitent une vuln\u00e9rabilit\u00e9 de cross-site scripting (XSS) dans Biggopti.<\/p>\n\n\n\n<p>Chaque extension qui utilise Biggopti charge un fichier JavaScript c\u00f4t\u00e9 client sur le hook <code>admin_init<\/code>. Le script s\u2019ex\u00e9cute donc sans condition \u00e0 chaque chargement d\u2019une page <code>wp-admin<\/code>, r\u00e9cup\u00e8re les donn\u00e9es de banni\u00e8re, puis ins\u00e8re directement <code>display_id<\/code> dans un attribut HTML <code>id<\/code>, sans l\u2019\u00e9chapper\u00a0:<\/p>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\" style=\"font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#24292e\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"54\" height=\"14\" viewBox=\"0 0 54 14\"><g fill=\"none\" fill-rule=\"evenodd\" transform=\"translate(1 1)\"><circle cx=\"6\" cy=\"6\" r=\"6\" fill=\"#FF5F56\" stroke=\"#E0443E\" stroke-width=\".5\"><\/circle><circle cx=\"26\" cy=\"6\" r=\"6\" fill=\"#FFBD2E\" stroke=\"#DEA123\" stroke-width=\".5\"><\/circle><circle cx=\"46\" cy=\"6\" r=\"6\" fill=\"#27C93F\" stroke=\"#1AAB29\" stroke-width=\".5\"><\/circle><\/g><\/svg><\/span><span role=\"button\" tabindex=\"0\" style=\"color:#e1e4e8;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\"><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly>\/\/ Valeur distante non \u00e9chapp\u00e9e.\nvar f = t.display_id || t.id || &quot;default&quot;;\n\n\/\/ La valeur devient une partie de l'attribut id dans le DOM.\nvar m = &quot;bdt-admin-biggopti-api-biggopti-&quot; + f;\nA = 'id=&quot;' + m + '&quot;';\n<\/textarea><\/pre><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#6A737D\">\/\/ Valeur distante non \u00e9chapp\u00e9e.<\/span><\/span>\n<span class=\"line\"><span style=\"color:#F97583\">var<\/span><span style=\"color:#E1E4E8\"> f <\/span><span style=\"color:#F97583\">=<\/span><span style=\"color:#E1E4E8\"> t.display_id <\/span><span style=\"color:#F97583\">||<\/span><span style=\"color:#E1E4E8\"> t.id <\/span><span style=\"color:#F97583\">||<\/span><span style=\"color:#9ECBFF\"> \"default\"<\/span><span style=\"color:#E1E4E8\">;<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color:#6A737D\">\/\/ La valeur devient une partie de l'attribut id dans le DOM.<\/span><\/span>\n<span class=\"line\"><span style=\"color:#F97583\">var<\/span><span style=\"color:#E1E4E8\"> m <\/span><span style=\"color:#F97583\">=<\/span><span style=\"color:#9ECBFF\"> \"bdt-admin-biggopti-api-biggopti-\"<\/span><span style=\"color:#F97583\"> +<\/span><span style=\"color:#E1E4E8\"> f;<\/span><\/span>\n<span class=\"line\"><span style=\"color:#79B8FF\">A<\/span><span style=\"color:#F97583\"> =<\/span><span style=\"color:#9ECBFF\"> 'id=\"'<\/span><span style=\"color:#F97583\"> +<\/span><span style=\"color:#E1E4E8\"> m <\/span><span style=\"color:#F97583\">+<\/span><span style=\"color:#9ECBFF\"> '\"'<\/span><span style=\"color:#E1E4E8\">;<\/span><\/span><\/code><\/pre><\/div>\n\n\n\n<p>Le m\u00eame code \u00e9chappe pourtant correctement la valeur lorsqu\u2019il construit l\u2019attribut <code>data-display-id<\/code>. La routine, vraisemblablement issue de la minification ou du bundling, remplace les caract\u00e8res <code>&amp;<\/code>, <code>&lt;<\/code>, <code>&gt;<\/code>, et <code>&quot;<\/code> par leurs entit\u00e9s HTML. Cette protection ne couvre pas l\u2019attribut <code>id<\/code> vuln\u00e9rable.<\/p>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\" style=\"font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#24292e\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"54\" height=\"14\" viewBox=\"0 0 54 14\"><g fill=\"none\" fill-rule=\"evenodd\" transform=\"translate(1 1)\"><circle cx=\"6\" cy=\"6\" r=\"6\" fill=\"#FF5F56\" stroke=\"#E0443E\" stroke-width=\".5\"><\/circle><circle cx=\"26\" cy=\"6\" r=\"6\" fill=\"#FFBD2E\" stroke=\"#DEA123\" stroke-width=\".5\"><\/circle><circle cx=\"46\" cy=\"6\" r=\"6\" fill=\"#27C93F\" stroke=\"#1AAB29\" stroke-width=\".5\"><\/circle><\/g><\/svg><\/span><span role=\"button\" tabindex=\"0\" style=\"color:#e1e4e8;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\"><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly>function p(t) {\n  return (t || &quot;&quot;)\n    .replace(\/&amp;\/g, &quot;&amp;amp;&quot;)\n    .replace(\/&lt;\/g, &quot;&amp;lt;&quot;)\n    .replace(\/&gt;\/g, &quot;&amp;gt;&quot;)\n    .replace(\/&quot;\/g, &quot;&amp;quot;&quot;);\n}\n<\/textarea><\/pre><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#F97583\">function<\/span><span style=\"color:#B392F0\"> p<\/span><span style=\"color:#E1E4E8\">(<\/span><span style=\"color:#FFAB70\">t<\/span><span style=\"color:#E1E4E8\">) {<\/span><\/span>\n<span class=\"line\"><span style=\"color:#F97583\">  return<\/span><span style=\"color:#E1E4E8\"> (t <\/span><span style=\"color:#F97583\">||<\/span><span style=\"color:#9ECBFF\"> \"\"<\/span><span style=\"color:#E1E4E8\">)<\/span><\/span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">    .<\/span><span style=\"color:#B392F0\">replace<\/span><span style=\"color:#E1E4E8\">(<\/span><span style=\"color:#9ECBFF\">\/<\/span><span style=\"color:#DBEDFF\">&#x26;<\/span><span style=\"color:#9ECBFF\">\/<\/span><span style=\"color:#F97583\">g<\/span><span style=\"color:#E1E4E8\">, <\/span><span style=\"color:#9ECBFF\">\"&#x26;amp;\"<\/span><span style=\"color:#E1E4E8\">)<\/span><\/span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">    .<\/span><span style=\"color:#B392F0\">replace<\/span><span style=\"color:#E1E4E8\">(<\/span><span style=\"color:#9ECBFF\">\/<\/span><span style=\"color:#DBEDFF\">&#x3C;<\/span><span style=\"color:#9ECBFF\">\/<\/span><span style=\"color:#F97583\">g<\/span><span style=\"color:#E1E4E8\">, <\/span><span style=\"color:#9ECBFF\">\"&#x26;lt;\"<\/span><span style=\"color:#E1E4E8\">)<\/span><\/span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">    .<\/span><span style=\"color:#B392F0\">replace<\/span><span style=\"color:#E1E4E8\">(<\/span><span style=\"color:#9ECBFF\">\/<\/span><span style=\"color:#DBEDFF\">><\/span><span style=\"color:#9ECBFF\">\/<\/span><span style=\"color:#F97583\">g<\/span><span style=\"color:#E1E4E8\">, <\/span><span style=\"color:#9ECBFF\">\"&#x26;gt;\"<\/span><span style=\"color:#E1E4E8\">)<\/span><\/span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">    .<\/span><span style=\"color:#B392F0\">replace<\/span><span style=\"color:#E1E4E8\">(<\/span><span style=\"color:#9ECBFF\">\/<\/span><span style=\"color:#DBEDFF\">\"<\/span><span style=\"color:#9ECBFF\">\/<\/span><span style=\"color:#F97583\">g<\/span><span style=\"color:#E1E4E8\">, <\/span><span style=\"color:#9ECBFF\">\"&#x26;quot;\"<\/span><span style=\"color:#E1E4E8\">);<\/span><\/span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">}<\/span><\/span><\/code><\/pre><\/div>\n\n\n\n<p>L\u2019historique SVN montre que Prime Slider, version\u00a04.1.9, a introduit cette injection non \u00e9chapp\u00e9e le 1er\u00a0mars\u00a02026. Les autres extensions ont ensuite re\u00e7u le m\u00eame code. Des versions ult\u00e9rieures ont ajout\u00e9 un assainisseur fond\u00e9 sur <code>DOMParser<\/code> pour le champ de contenu des banni\u00e8res, afin de supprimer les balises HTML dangereuses et les gestionnaires d\u2019\u00e9v\u00e9nements en ligne, mais elles ont laiss\u00e9 l\u2019injection dans <code>id<\/code> intacte.<\/p>\n\n\n\n<p>La charge utile plac\u00e9e dans <code>display_id<\/code> ferme l\u2019attribut <code>id<\/code>, ajoute un gestionnaire <code>onanimationstart<\/code>, puis d\u00e9clenche ce gestionnaire gr\u00e2ce \u00e0 une animation CSS en 10\u00a0millisecondes. Le JavaScript s\u2019ex\u00e9cute silencieusement dans le navigateur de chaque administrateur connect\u00e9 qui ouvre une page du tableau de bord. L\u2019attaque \u00e9chappe ainsi largement aux contr\u00f4les d\u2019int\u00e9grit\u00e9 bas\u00e9s sur les fichiers et reste peu visible pour les pare-feu applicatifs.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Extensions touch\u00e9es et gravit\u00e9<\/h2>\n\n\n\n<p>La vuln\u00e9rabilit\u00e9 Biggopti affecte diverses versions de ces extensions. Son score CVSS est de 5,4 sur 10, soit un niveau moyen, et aucun correctif n\u2019\u00e9tait disponible au moment du signalement.<\/p>\n\n\n\n<p>Les extensions concern\u00e9es sont les suivantes\u00a0:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n\n<li>Element Pack Addons for Elementor (<code>bdthemes-element-pack-lite<\/code>).<\/li>\n\n\n<li>Prime Slider Addons for Elementor (<code>bdthemes-prime-slider-lite<\/code>).<\/li>\n\n\n<li>Pixel Gallery Addons for Elementor (<code>pixel-gallery<\/code>).<\/li>\n\n\n<li>Ultimate Post Kit Addons for Elementor (<code>ultimate-post-kit<\/code>).<\/li>\n\n\n<li>Ultimate Store Kit &#8211; Addon For WooCommerce, EDD and Elementor (<code>ultimate-store-kit<\/code>).<\/li>\n\n\n<li>Live Copy Paste for Elementor (<code>live-copy-paste<\/code>).<\/li>\n\n\n<li>Smart Admin Assistant (<code>smart-admin-assistant<\/code>).<\/li>\n\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Une compromission en plusieurs \u00e9tapes<\/h2>\n\n\n\n<p>Le gestionnaire inject\u00e9 t\u00e9l\u00e9charge des scripts externes en fonction de la disponibilit\u00e9 de l\u2019infrastructure des attaquants. Deux charges utiles distinctes ont \u00e9t\u00e9 observ\u00e9es, selon l\u2019endpoint Biggopti interrog\u00e9 par l\u2019extension.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Charge utile principale\u00a0: <code>w2.js<\/code><\/h3>\n\n\n\n<p>Les extensions qui appellent l\u2019endpoint <code>api-data-all-records<\/code> re\u00e7oivent la charge utile principale <code>w2.js<\/code>. Elle s\u2019ex\u00e9cute dans la session authentifi\u00e9e de l\u2019administrateur et encha\u00eene les actions suivantes\u00a0:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n\n<li>Elle contacte le serveur de commande et de contr\u00f4le (C2) <code>ia-cdn[.]com\/fz\/c<\/code>, en lui transmettant l\u2019origine du site victime. Elle interrompt l\u2019attaque si le C2 r\u00e9pond <code>skip<\/code> ou <code>done<\/code>.<\/li>\n\n\n<li>Elle r\u00e9cup\u00e8re le nonce <code>X-WP-Nonce<\/code> de la session d\u2019administration et cr\u00e9e un nouvel administrateur via l\u2019API REST de WordPress. Si cette voie \u00e9choue, elle utilise les soumissions de formulaires classiques.<\/li>\n\n\n<li>Elle t\u00e9l\u00e9charge depuis le C2 une fausse extension ZIP et l\u2019installe avec le formulaire standard d\u2019envoi d\u2019extension. Cette extension adopte le slug <code>wp-smart-thumbnails<\/code>, ou un slug neutre similaire, et contient un webshell <code>emer-run.php<\/code> accessible directement par URL.<\/li>\n\n\n<li>Elle appelle <code>emer-run.php<\/code>, qui installe deux modules de persistance dans le r\u00e9pertoire des extensions indispensables (MU plugins). Les deux \u00e9chantillons collect\u00e9s portent un horodatage antidat\u00e9 \u00e0 septembre\u00a02025 afin de se fondre dans les dates l\u00e9gitimes du syst\u00e8me de fichiers, m\u00eame si les noms de fichiers peuvent varier entre infections.<\/li>\n\n\n<li>Le premier module MU ajoute une porte d\u00e9rob\u00e9e de connexion magique\u00a0: le param\u00e8tre d\u2019URL <code>?_wplogin=&lt;token&gt;<\/code> donne un acc\u00e8s administrateur sans authentification en ciblant l\u2019administrateur enregistr\u00e9 depuis le plus longtemps sur le site.<\/li>\n\n\n<li>Le second module MU dissimule l\u2019attaque pendant l\u2019analyse. Il intercepte les requ\u00eates de base de donn\u00e9es de WordPress, masque les comptes malveillants dans la liste des utilisateurs de l\u2019administration et diminue aussi le nombre total d\u2019utilisateurs affich\u00e9.<\/li>\n\n\n<li>Elle transmet les r\u00e9sultats de chaque phase au C2 avec <code>navigator.sendBeacon<\/code>.<\/li>\n\n<\/ol>\n\n\n\n<h3 class=\"wp-block-heading\">Charge utile alternative\u00a0: <code>x.js<\/code><\/h3>\n\n\n\n<p>Les extensions qui appellent <code>api-data-records<\/code> re\u00e7oivent le script alternatif <code>x.js<\/code>, h\u00e9berg\u00e9 directement sur l\u2019infrastructure de BdThemes. Ce script calcule des identifiants administrateur d\u00e9terministes \u00e0 partir du nom d\u2019h\u00f4te du site victime\u00a0:<\/p>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\" style=\"font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#24292e\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"54\" height=\"14\" viewBox=\"0 0 54 14\"><g fill=\"none\" fill-rule=\"evenodd\" transform=\"translate(1 1)\"><circle cx=\"6\" cy=\"6\" r=\"6\" fill=\"#FF5F56\" stroke=\"#E0443E\" stroke-width=\".5\"><\/circle><circle cx=\"26\" cy=\"6\" r=\"6\" fill=\"#FFBD2E\" stroke=\"#DEA123\" stroke-width=\".5\"><\/circle><circle cx=\"46\" cy=\"6\" r=\"6\" fill=\"#27C93F\" stroke=\"#1AAB29\" stroke-width=\".5\"><\/circle><\/g><\/svg><\/span><span role=\"button\" tabindex=\"0\" style=\"color:#e1e4e8;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\"><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly>function makeCreds() {\n  var host = siteKey();\n  var hash = 0;\n\n  for (var i = 0; i &lt; host.length; i++) {\n    hash = ((hash &lt;&lt; 5) - hash + host.charCodeAt(i)) | 0;\n  }\n\n  var n = Math.abs(hash).toString(36).slice(0, 6);\n  return {\n    user: 'bd_' + n,\n    pass: 'Bd@26!' + n + 'x'\n  };\n}\n<\/textarea><\/pre><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#F97583\">function<\/span><span style=\"color:#B392F0\"> makeCreds<\/span><span style=\"color:#E1E4E8\">() {<\/span><\/span>\n<span class=\"line\"><span style=\"color:#F97583\">  var<\/span><span style=\"color:#E1E4E8\"> host <\/span><span style=\"color:#F97583\">=<\/span><span style=\"color:#B392F0\"> siteKey<\/span><span style=\"color:#E1E4E8\">();<\/span><\/span>\n<span class=\"line\"><span style=\"color:#F97583\">  var<\/span><span style=\"color:#E1E4E8\"> hash <\/span><span style=\"color:#F97583\">=<\/span><span style=\"color:#79B8FF\"> 0<\/span><span style=\"color:#E1E4E8\">;<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color:#F97583\">  for<\/span><span style=\"color:#E1E4E8\"> (<\/span><span style=\"color:#F97583\">var<\/span><span style=\"color:#E1E4E8\"> i <\/span><span style=\"color:#F97583\">=<\/span><span style=\"color:#79B8FF\"> 0<\/span><span style=\"color:#E1E4E8\">; i <\/span><span style=\"color:#F97583\">&#x3C;<\/span><span style=\"color:#E1E4E8\"> host.<\/span><span style=\"color:#79B8FF\">length<\/span><span style=\"color:#E1E4E8\">; i<\/span><span style=\"color:#F97583\">++<\/span><span style=\"color:#E1E4E8\">) {<\/span><\/span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">    hash <\/span><span style=\"color:#F97583\">=<\/span><span style=\"color:#E1E4E8\"> ((hash <\/span><span style=\"color:#F97583\">&#x3C;&#x3C;<\/span><span style=\"color:#79B8FF\"> 5<\/span><span style=\"color:#E1E4E8\">) <\/span><span style=\"color:#F97583\">-<\/span><span style=\"color:#E1E4E8\"> hash <\/span><span style=\"color:#F97583\">+<\/span><span style=\"color:#E1E4E8\"> host.<\/span><span style=\"color:#B392F0\">charCodeAt<\/span><span style=\"color:#E1E4E8\">(i)) <\/span><span style=\"color:#F97583\">|<\/span><span style=\"color:#79B8FF\"> 0<\/span><span style=\"color:#E1E4E8\">;<\/span><\/span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">  }<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color:#F97583\">  var<\/span><span style=\"color:#E1E4E8\"> n <\/span><span style=\"color:#F97583\">=<\/span><span style=\"color:#E1E4E8\"> Math.<\/span><span style=\"color:#B392F0\">abs<\/span><span style=\"color:#E1E4E8\">(hash).<\/span><span style=\"color:#B392F0\">toString<\/span><span style=\"color:#E1E4E8\">(<\/span><span style=\"color:#79B8FF\">36<\/span><span style=\"color:#E1E4E8\">).<\/span><span style=\"color:#B392F0\">slice<\/span><span style=\"color:#E1E4E8\">(<\/span><span style=\"color:#79B8FF\">0<\/span><span style=\"color:#E1E4E8\">, <\/span><span style=\"color:#79B8FF\">6<\/span><span style=\"color:#E1E4E8\">);<\/span><\/span>\n<span class=\"line\"><span style=\"color:#F97583\">  return<\/span><span style=\"color:#E1E4E8\"> {<\/span><\/span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">    user: <\/span><span style=\"color:#9ECBFF\">'bd_'<\/span><span style=\"color:#F97583\"> +<\/span><span style=\"color:#E1E4E8\"> n,<\/span><\/span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">    pass: <\/span><span style=\"color:#9ECBFF\">'Bd@26!'<\/span><span style=\"color:#F97583\"> +<\/span><span style=\"color:#E1E4E8\"> n <\/span><span style=\"color:#F97583\">+<\/span><span style=\"color:#9ECBFF\"> 'x'<\/span><\/span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">  };<\/span><\/span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">}<\/span><\/span><\/code><\/pre><\/div>\n\n\n\n<p>Le compte suit donc le format <code>bd_<\/code> suivi d\u2019un hash base36 de six caract\u00e8res, et le mot de passe suit le format <code>Bd@26!&lt;hash&gt;x<\/code>. Le script associe ce compte \u00e0 une adresse en <code>@wordpress.org<\/code>. Les attaquants n\u2019ont pas besoin de conserver une liste centrale des sites compromis, car ils peuvent recalculer les identifiants d\u2019un domaine donn\u00e9. Les \u00e9quipes de r\u00e9ponse \u00e0 incident peuvent appliquer le m\u00eame principe pour rechercher le compte attendu sur un site suspect.<\/p>\n\n\n\n<p>Le script t\u00e9l\u00e9charge <code>\/wp-admin\/user-new.php<\/code> avec les cookies de la victime, cherche le nonce de cr\u00e9ation d\u2019utilisateur dans le HTML, puis envoie un formulaire <code>POST<\/code> qui demande le r\u00f4le <code>administrator<\/code>. Il transmet au C2 l\u2019\u00e9tat de la cr\u00e9ation, les identifiants g\u00e9n\u00e9r\u00e9s, l\u2019origine du site, l\u2019URL de d\u00e9connexion trouv\u00e9e et l\u2019URL de la page courante. Il privil\u00e9gie <code>navigator.sendBeacon<\/code>, puis bascule vers une requ\u00eate <code>fetch<\/code> <code>POST<\/code> sans cookies et en mode <code>no-cors<\/code>. Lorsqu\u2019il le peut, il enregistre aussi un indicateur dans <code>localStorage<\/code> afin de ne pas r\u00e9p\u00e9ter l\u2019op\u00e9ration dans le m\u00eame navigateur.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Port\u00e9e de la campagne<\/h2>\n\n\n\n<p>Le domaine C2 est li\u00e9 aux acteurs associ\u00e9s aux attaques de cha\u00eene d\u2019approvisionnement r\u00e9centes contre Advanced Responsive Video Embedder et OptinMonster. La campagne cherche \u00e0 installer, \u00e0 grande \u00e9chelle et sans bruit, des acc\u00e8s administrateur persistants et \u00e0 ex\u00e9cuter du code \u00e0 distance dans des environnements WordPress.<\/p>\n\n\n\n<p>Le vecteur est particuli\u00e8rement dangereux parce que les attaquants n\u2019ont pas eu \u00e0 acc\u00e9der aux sites vis\u00e9s ni \u00e0 modifier le code des extensions. Ils ont empoisonn\u00e9 un flux de banni\u00e8res consid\u00e9r\u00e9 comme fiable, ce qui a transform\u00e9 des avis d\u2019administration en m\u00e9canisme de diffusion de malwares. La pr\u00e9sence d\u2019enregistrements JSON malveillants et de <code>x.js<\/code> dans le bucket du fournisseur indique une compromission grave de ses identifiants de stockage cloud ou de son infrastructure interne.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Indicateurs de compromission \u00e0 rechercher<\/h2>\n\n\n\n<p>Inspectez les ressources externes, les fichiers, les comptes WordPress et les options de base de donn\u00e9es ci-dessous. Les deux endpoints JSON empoisonn\u00e9s ont ensuite renvoy\u00e9 des donn\u00e9es propres, mais leur consultation pass\u00e9e ou leurs traces dans les journaux restent des \u00e9l\u00e9ments utiles \u00e0 l\u2019investigation.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Ressources r\u00e9seau<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n\n<li>Charge utile principale\u00a0: <code>ia-cdn[.]com\/fz\/w2.js<\/code>.<\/li>\n\n\n<li>Endpoint C2 et de beacon\u00a0: <code>ia-cdn[.]com\/fz\/c<\/code>.<\/li>\n\n\n<li>Endpoint JSON empoisonn\u00e9, d\u00e9sormais nettoy\u00e9\u00a0: <code>api[.]sigmative[.]io\/prod\/store\/api\/biggopti\/api-data-all-records<\/code>.<\/li>\n\n\n<li>Endpoint JSON empoisonn\u00e9, d\u00e9sormais nettoy\u00e9\u00a0: <code>api[.]sigmative[.]io\/prod\/store\/api\/biggopti\/api-data-records<\/code>.<\/li>\n\n\n<li>Script alternatif\u00a0: <code>api[.]sigmative[.]io\/prod\/store\/api\/biggopti\/x.js<\/code>.<\/li>\n\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Fichiers et base de donn\u00e9es<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n\n<li>Webshell <code>emer-run.php<\/code>, empreinte MD5 <code>1024732009983dd5e54b4cf5593f04d4<\/code>.<\/li>\n\n\n<li>Porte d\u00e9rob\u00e9e MU de connexion magique <code>class-wp-token-validate.php<\/code>, empreinte MD5 <code>7719cd98a35ffad2771f26d1ceab7d27<\/code>.<\/li>\n\n\n<li>Module MU de furtivit\u00e9 <code>class-wp-query-9d127ff3.php<\/code>, ou un nom suivant le mod\u00e8le <code>class-wp-query-*.php<\/code>, empreinte MD5 <code>9aadc3e5c5242b273bd17c5bdc358845<\/code>.<\/li>\n\n\n<li>Module MU pr\u00e9sent\u00e9 comme contr\u00f4le d\u2019\u00e9tat <code>wp-cache-optimizer.php<\/code>, empreinte MD5 <code>e450ae5bc4bfc0d960dded06a76bb8e9<\/code>.<\/li>\n\n\n<li>Option de base de donn\u00e9es <code>fz_emer_login_tokens<\/code>, qui stocke les jetons de connexion magique.<\/li>\n\n\n<li>Option de base de donn\u00e9es <code>fz_emer_done_v1<\/code>, qui signale qu\u2019une compromission est termin\u00e9e.<\/li>\n\n\n<li>Comptes dont l\u2019adresse e-mail utilise <code>@wordpress.org<\/code> ou <code>@developer.wordpress.org<\/code>.<\/li>\n\n\n<li>Noms d\u2019utilisateur conformes \u00e0 <code>bd_<\/code> suivi de six caract\u00e8res alphanum\u00e9riques.<\/li>\n\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Chronologie de l\u2019incident<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n\n<li><strong>1er\u00a0mars\u00a02026\u00a0:<\/strong> Biggopti ajoute son JavaScript d\u2019API \u00e0 Prime Slider\u00a04.1.9, dans la r\u00e9vision SVN <code>r3471891<\/code>. <code>display_id<\/code> entre sans \u00e9chappement dans l\u2019attribut <code>id<\/code>, et l\u2019extension utilise alors <code>api-data-records<\/code>.<\/li>\n\n\n<li><strong>10\u00a0mai\u00a02026\u00a0:<\/strong> l\u2019extension bascule vers <code>api-data-all-records<\/code>. Un assainisseur <code>DOMParser<\/code> prot\u00e8ge le champ de contenu, mais l\u2019attribut <code>id<\/code> reste vuln\u00e9rable.<\/li>\n\n\n<li><strong>23\u00a0juin\u00a02026\u00a0:<\/strong> la date <code>start_date<\/code> de la campagne empoisonn\u00e9e, pr\u00e9sent\u00e9e sous la forme d\u2019une banni\u00e8re \u00ab\u00a0Summer Sale\u00a0\u00bb, constitue la date la plus ancienne \u00e0 laquelle la XSS a pu \u00eatre active.<\/li>\n\n\n<li><strong>6\u00a0ao\u00fbt\u00a02026 \u00e0 22:40 UTC\u00a0:<\/strong> derni\u00e8re modification observ\u00e9e de <code>api-data-records<\/code> empoisonn\u00e9.<\/li>\n\n\n<li><strong>7\u00a0ao\u00fbt\u00a02026 \u00e0 09:11 UTC\u00a0:<\/strong> derni\u00e8re modification observ\u00e9e de <code>api-data-all-records<\/code> empoisonn\u00e9.<\/li>\n\n\n<li><strong>7\u00a0ao\u00fbt\u00a02026\u00a0:<\/strong> l\u2019attaque est signal\u00e9e et observ\u00e9e en conditions r\u00e9elles\u00a0; le r\u00e9pertoire WordPress ferme les extensions pendant leur examen.<\/li>\n\n\n<li><strong>7\u00a0ao\u00fbt\u00a02026 \u00e0 16:56 UTC\u00a0:<\/strong> les artefacts de l\u2019enqu\u00eate sont captur\u00e9s.<\/li>\n\n\n<li><strong>8\u00a0ao\u00fbt\u00a02026\u00a0:<\/strong> les deux endpoints d\u2019API renvoient \u00e0 nouveau du JSON propre.<\/li>\n\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Auditez les sites qui utilisent ces extensions<\/h2>\n\n\n\n<p>Commencez par identifier les sept extensions concern\u00e9es sur chaque site. Contr\u00f4lez ensuite la liste r\u00e9elle des utilisateurs dans la base de donn\u00e9es, et pas seulement l\u2019\u00e9cran d\u2019administration, car le module de furtivit\u00e9 peut masquer les comptes cr\u00e9\u00e9s. Recherchez les adresses et noms d\u2019utilisateur indiqu\u00e9s, en tenant compte des identifiants d\u00e9terministes g\u00e9n\u00e9r\u00e9s depuis le nom d\u2019h\u00f4te.<\/p>\n\n\n\n<p>Examinez le r\u00e9pertoire des extensions install\u00e9es et celui des MU plugins pour trouver les fichiers, les mod\u00e8les de noms et les empreintes list\u00e9s. Cherchez enfin les options <code>fz_emer_login_tokens<\/code> et <code>fz_emer_done_v1<\/code> dans la table des options, ainsi que les connexions aux domaines et endpoints signal\u00e9s dans les journaux r\u00e9seau. L\u2019absence de modification des fichiers d\u2019une extension BdThemes ne permet pas d\u2019\u00e9carter une compromission\u00a0: l\u2019ex\u00e9cution initiale provenait du JSON distant charg\u00e9 dans le navigateur d\u2019un administrateur.<\/p>\n\n\n\n<div class=\"wp-block-group callout callout-warning is-style-warning is-layout-flow wp-block-group-is-layout-flow\" style=\"border-width:1px;border-radius:8px;padding-top:1rem;padding-right:1.5rem;padding-bottom:1rem;padding-left:1.5rem\">\n\n<h4 class=\"wp-block-heading callout-title\">Ne vous limitez pas aux fichiers de l\u2019extension<\/h4>\n\n\n<p>Traitez toute correspondance comme un incident complet\u00a0: un compte administrateur clandestin, un webshell et des modules MU peuvent survivre au retrait de l\u2019extension touch\u00e9e. Supprimez les m\u00e9canismes de persistance identifi\u00e9s et v\u00e9rifiez l\u2019ensemble des comptes, fichiers et options avant de remettre le site en service.<\/p>\n\n<\/div>\n\n\n<div class=\"references-section\">\n                <h2>R\u00e9f\u00e9rences \/ Sources<\/h2>\n                <ul class=\"references-list\"><li><a href=\"https:\/\/www.wordfence.com\/blog\/2026\/08\/psa-supply-chain-compromise-in-bdthemes-ecosystem-via-poisoned-api-response\/\" target=\"_blank\" rel=\"noopener noreferrer\">PSA: Supply Chain Compromise in BdThemes Ecosystem via Poisoned API Response<\/a><\/li><li><a href=\"https:\/\/www.wordfence.com\/blog\/2026\/07\/wordfence-prism-detected-backdoored-wordpress-plugin-within-two-hours-of-it-being-introduced\/\" target=\"_blank\" rel=\"noopener noreferrer\">Wordfence Prism Detected Backdoored WordPress Plugin Within Two Hours of it Being Introduced<\/a><\/li><li><a href=\"https:\/\/optinmonster.com\/security-incident-tampered-script-served-via-optinmonster-and-trustpulse\/#what-to-check-and-do\" target=\"_blank\" rel=\"noopener noreferrer\">Security Incident: Tampered Script Served via OptinMonster and TrustPulse<\/a><\/li><\/ul>\n            <\/div>","protected":false},"excerpt":{"rendered":"<p>Des banni\u00e8res d\u2019administration BdThemes ont diffus\u00e9 une XSS via un JSON distant. D\u00e9couvrez les extensions touch\u00e9es, la cha\u00eene d\u2019infection et tous les indicateurs \u00e0 auditer.<\/p>\n","protected":false},"author":14,"featured_media":277,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[9],"tags":[129,130,11,10,120],"class_list":["post-278","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-securite","tag-chaine-dapprovisionnement","tag-extensions-wordpress","tag-securite","tag-wordpress","tag-xss"],"_links":{"self":[{"href":"https:\/\/helloblog.io\/fr\/wp-json\/wp\/v2\/posts\/278","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/helloblog.io\/fr\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/helloblog.io\/fr\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/helloblog.io\/fr\/wp-json\/wp\/v2\/users\/14"}],"replies":[{"embeddable":true,"href":"https:\/\/helloblog.io\/fr\/wp-json\/wp\/v2\/comments?post=278"}],"version-history":[{"count":0,"href":"https:\/\/helloblog.io\/fr\/wp-json\/wp\/v2\/posts\/278\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/helloblog.io\/fr\/wp-json\/wp\/v2\/media\/277"}],"wp:attachment":[{"href":"https:\/\/helloblog.io\/fr\/wp-json\/wp\/v2\/media?parent=278"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/helloblog.io\/fr\/wp-json\/wp\/v2\/categories?post=278"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/helloblog.io\/fr\/wp-json\/wp\/v2\/tags?post=278"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}