Rapport CTI technique du 7 septembre 2026
Résumé exécutif
Sansec a publié le 5 septembre 2026 un avis d'urgence sur une vulnérabilité non corrigée de Magento Open Source et Adobe Commerce, baptisée StyleSmuggler, exploitée depuis le 4 septembre pour exécuter du code sans authentification sur les serveurs de boutiques en ligne et y installer une porte dérobée persistante. La société néerlandaise indique avoir reproduit la chaîne complète non authentifiée sur des installations propres de Magento Open Source 2.4.7, 2.4.8 et 2.4.9. À la date de rédaction, Adobe n'a publié ni avis, ni identifiant CVE, ni correctif, ni contournement officiel, et son index de bulletins de sécurité Commerce ne mentionne rien après la mise à jour du 11 août.
L'attaque se déroule en deux temps : un premier envoi injecte du code PHP dans un fichier que Magento écrit lui-même, journal système ou rapport d'erreur, puis un second déclenche le mail standard « Payment Transaction Failed Reminder » dont le rendu fait inclure et exécuter le fichier poisonné. Personne n'a besoin d'ouvrir le message, et l'attaque réussit même si l'envoi échoue. L'hébergeur néerlandais Disrex Group, qui a traité deux boutiques compromises, apporte la confirmation indépendante de l'exploitation et documente un point difficile à entendre pour les marchands : sa première victime tournait sur 2.4.8 avec le module de protection Sansec Shield installé, activé et licencié, et a été touchée quelques heures avant que les règles de blocage n'existent. Le niveau de patch n'a joué aucun rôle.
Chronologie
| Date et heure UTC | Événement |
|---|---|
| 11 août 2026 | Dernière mise à jour de sécurité Adobe Commerce publiée avant l'incident |
| 4 septembre 2026 | Début des attaques observées par Sansec |
| 4 septembre 2026, 23:10 | Première compromission chez Disrex (Store A, Magento Open Source 2.4.8, client Sansec Shield) |
| 5 septembre 2026, 00:55 | Deuxième compromission chez Disrex (Store B, Magento 2.4.7-p2) |
| 5 septembre 2026 | Publication de l'avis Sansec, du dépôt de mitigation Disrex, des correctifs non officiels ProxiBlue et du module Graycore |
| 5 septembre 2026, 10:00 | eComscan passe sur Store A et le déclare sain alors que 1 728 lignes de cron malveillantes sont présentes |
| 5 septembre 2026 | Nexcess et Liquid Web publient des avis d'incident identiques annonçant des mesures de précaution |
| 6 septembre 2026 | Adobe toujours silencieux, aucun CVE attribué |
| 8 septembre 2026 | Prochaine sortie de sécurité Adobe programmée. Aucune confirmation que ce bug y figure |
Fiche vulnérabilité
StyleSmuggler | CVSS non attribué | Magento Open Source et Adobe Commerce
Composant affecté : Magento Open Source, toutes versions actuelles selon Sansec, y compris 2.4.9. Reproduction confirmée sur 2.4.7, 2.4.8 et 2.4.9 en installation propre. Adobe Commerce et Adobe Commerce on Cloud ne font l'objet d'aucune reproduction publiée et Adobe n'a pas confirmé les versions affectées.
Identifiant : aucun. Pas de CVE attribué au 7 septembre 2026.
Classification probable : l'inclusion d'un chemin contrôlé par l'attaquant dans du code de compilation d'injection de dépendances relève de CWE-98 (inclusion de fichier PHP contrôlée à distance) combinée à CWE-117 (neutralisation incorrecte des données écrites dans un journal) pour la phase de poisoning. Cette classification est une lecture technique, aucun éditeur ne l'a publiée.
Description technique : la chaîne exploite deux propriétés de Magento qui n'ont rien d'exotique prises séparément. D'abord, Magento écrit dans ses propres fichiers du contenu partiellement contrôlé par la requête, en particulier var/log/system.log et les rapports générés dans var/report/. Ensuite, la plateforme embarque, sous setup/src/Magento/Setup/Module/Di/Code/, du code destiné au compilateur d'injection de dépendances en ligne de commande, code qui inclut un fichier dont le chemin lui est fourni. Ce code n'est pas censé être atteignable via HTTP.
La lecture de Disrex, obtenue en lisant les sources Magento sur la boutique compromise, est qu'une directive placée dans le texte injecté enchaîne des classes Magento légitimes jusqu'à atteindre ce compilateur, qui termine en incluant le fichier de log poisonné une seconde plus tôt. Trois fichiers sont nommés comme point d'arrivée de la chaîne. Sansec n'a pas confirmé cette lecture et n'a pas encore publié le détail de la chaîne, du dropper et de l'implant.
Le déclencheur est le mail « Payment Transaction Failed Reminder ». Le code s'exécute pendant le rendu du message, ce qui rend l'ouverture du mail inutile et rend l'attaque insensible à l'échec de la remise.
Conditions d'exploitation : aucune authentification. GraphQL activé selon la recommandation intérimaire de Sansec, ce qui vise en pratique les vitrines headless et les PWA. Les boutiques classiques et Hyvä n'ont généralement pas besoin de GraphQL.
PoC disponible : non publié. Sansec retient volontairement la chaîne complète. Disrex publie sa lecture du mécanisme et ses règles de blocage mais pas la requête assemblée. L'exploitation en cours signifie qu'un exploit fonctionnel circule déjà en privé.
Patch : aucun correctif éditeur. Les options disponibles sont l'arrêt temporaire de GraphQL recommandé par Sansec, les correctifs non officiels de Disrex et ProxiBlue, le module de durcissement Graycore, et deux réglages serveur indépendants de la faille détaillés plus bas.
Diagramme de la chaîne d'attaque

Reconstruction à partir de la description publiée par Sansec et de la lecture indépendante de Disrex Group. Sources : Sansec, dépôt de mitigation Disrex.
Analyse technique
Étape 1 : poisoning du fichier écrit par Magento
L'attaquant envoie une requête dont un paramètre finit dans un fichier écrit par Magento. Deux emplacements comptent, et la différence entre les deux a des conséquences opérationnelles directes. La vérification publiée par Sansec cherche le marqueur X_TRACE_ dans var/report/. Or les deux infections traitées par Disrex sont passées par var/log/system.log, et auraient donc été manquées par cette seule vérification. Les deux répertoires doivent être fouillés.
Le marqueur lui-même a déjà bougé. Disrex a observé un en-tête déclencheur de la forme X-TRACE- suivi de dix caractères hexadécimaux le matin du 5 septembre, puis le même en-tête sans le mot TRACE l'après-midi. Une règle de détection doit cibler la forme, pas la chaîne exacte.
Étape 2 : déclenchement du rendu et exécution
Le second envoi déclenche le mail de relance de transaction échouée. Pendant le rendu du template, la directive injectée pousse la séquence de classes jusqu'au compilateur DI, qui inclut le fichier poisonné. Le code s'exécute avec les droits du processus PHP, donc de l'utilisateur du site.
Un signe d'échec est exploitable en détection : une TypeError levée par array_merge() avec un argument entier dans system.log, immédiatement après l'inclusion, indique que l'exploitation a réussi. Disrex précise toutefois qu'une variante plus discrète retourne un tableau vide et ne laisse rien dans le journal.
Le tell le plus accessible aux marchands ne demande aucun outillage. Le co-fondateur de Disrex, Rick Bouma, décrit un mail de transaction échouée envoyé au propriétaire de la boutique dont les variables de template n'ont jamais été résolues : un corps rempli de balises {{var ...}} brutes, une adresse client sur un domaine .invalid, un total à zéro, et le texte de repli Magento « an error occurred generating this content » à l'intérieur du bloc d'adresse. C'est le résidu de la tentative d'exploitation passée à travers le filtre de template. Le transfert de ce mail par le marchand a lancé l'enquête qui a trouvé l'implant dans l'heure.
Étape 3 : le dropper PHP
Le code exécuté tente six fonctions PHP successives pour démarrer un processus. Sur l'une des boutiques Disrex, les quatre premières étaient désactivées ; proc_open ne l'était pas, et le dropper s'en est servi. open_basedir n'a rien contenu du processus fils. Le dropper télécharge ensuite l'implant depuis 247.cdnflare[.]xyz et le lance.
Étape 4 : l'implant et sa persistance
L'implant est un binaire Rust statiquement lié, strippé, d'environ 1,9 Mo, compilé pour x86-64 et arm64. Il s'installe sous ~/.local/share/.gvfsd/gvfsd-user, dans le répertoire personnel du compte du site et non dans la racine web. Ce détail d'emplacement a une conséquence mesurée : le scan eComscan programmé sur Store A pointait sur le document root, l'implant était un répertoire au-dessus, et le scanner a rendu un verdict « sain » alors que 1 728 lignes de cron malveillantes étaient présentes. Défaut de portée, pas défaut de moteur.
La persistance passe par une écriture directe dans le fichier de spool /var/spool/cron/crontabs/, ce qui évite toute trace de remplacement de crontab dans les journaux système. L'entrée relance le processus toutes les cinq minutes, et l'implant la réécrit en moins d'une seconde après suppression. D'où l'ordre de nettoyage imposé : supprimer le cron avant de tuer le processus, jamais l'inverse.
Le masquage du processus mérite attention pour qui écrit des règles EDR. L'implant se présente sous le nom [kworker/u:8:0], qui appartient normalement à un thread noyau. Deux discriminants tiennent : un vrai thread noyau appartient à root et n'a pas de mémoire résidente, donc un nom entre crochets porté par l'utilisateur du site avec de la RSS réelle est l'implant. Et surtout, l'implant fixe sa ligne de commande à la chaîne entre crochets, ce qui signifie qu'une règle écrite contre le champ comm du processus ne matchera rien.
Étape 5 : collecte locale, sans réseau
Sur l'une des deux boutiques, l'implant n'a effectué aucune connexion sortante. Il maintenait 28 connexions vers l'instance Redis locale de la boutique sur le port 6379 et lisait le stockage de session Magento. Aucune des deux captures réseau, chacune de plus de 200 Mo prises pendant que l'implant tournait, ne contenait un seul paquet vers l'hôte de téléchargement ou l'adresse de commande et contrôle listée par Sansec. Une détection reposant uniquement sur du trafic sortant vers des IOC réseau serait passée à côté.
Disrex signale par ailleurs que le binaire en mémoire sur une boutique était un build différent du fichier sur disque, et recommande de hasher le processus depuis /proc/<pid>/exe en plus du fichier.
Indicateurs de compromission
| Type | Valeur |
|---|---|
| Processus | [kworker/u:8:0] appartenant à un utilisateur non root, avec mémoire résidente |
| Fichier | ~/.local/share/.gvfsd/gvfsd-user |
| Fichier | ~/.local/share/.gvfsd/.gvfsd_<8hex>.lock |
| Fichier | /tmp/.gvfsd_<8hex>.lock |
| Fichier | /tmp/.kw_<random><random> |
| Cron | */5 * * * * exec <home>/.local/share/.gvfsd/gvfsd-user, variante pointant vers /tmp/.kw_ |
| SHA-256 | e315687a1dfe61ef4a5a5642214db6d3b2b05d81391285eebc2af664641a26a7 (échantillon Sansec) |
| SHA-256 | 8334b434fa3fe9f59cebe9609b11e0b1fd19d10212c45c705adec1902a1d06ef (sur disque, deux boutiques Disrex) |
| SHA-256 | 251fabd50d7b18a8b5e1b3ef5d64e7198c17244778f6461fb1ab07f6169bf220 (en mémoire, une boutique Disrex) |
| Domaine | 247.cdnflare[.]xyz (hôte de téléchargement) |
| IP | 99.84.67[.]186:443 (C2 en WebSocket sur TLS, selon Sansec) |
| IP | 88.216.72[.]181 (source attaquante, selon Sansec) |
| IP | 5.181.86[.]133 (source envoyant en masse, selon Disrex) |
| Journal applicatif | En-tête de la forme X-TRACE-<10hex> ou variante sans le mot TRACE |
| Journal applicatif | Marqueur X_TRACE_ dans var/report/ |
| Journal applicatif | TypeError sur array_merge() avec argument entier dans system.log, juste après une inclusion |
| Signal métier | Rafale de mails « Payment Transaction Failed Reminder », en particulier avec balises {{var ...}} non résolues, domaine .invalid, total à zéro |
Note sur le blocage par IP : Disrex a relevé 26 adresses sources distinctes sur ses deux boutiques après déduplication et retrait de deux de ses propres serveurs de vérification. Deux relevaient d'infrastructure d'hébergement envoyant en masse, le reste d'un pool de proxys résidentiels envoyant deux à six requêtes chacun. Bloquer la seule adresse listée dans l'avis Sansec aurait arrêté moins d'un quart du trafic observé.
MITRE ATT&CK
Aucune cartographie officielle n'accompagne les publications Sansec ou Disrex. Le tableau ci-dessous est une lecture technique.
| Tactique | Technique | ID | Justification |
|---|---|---|---|
| Initial Access | Exploit Public-Facing Application | T1190 | Exploitation non authentifiée d'une boutique Magento exposée |
| Execution | Command and Scripting Interpreter: PHP | T1059.011 | Le dropper est du PHP exécuté pendant le rendu d'un template Magento |
| Execution | Native API | T1106 | Six fonctions PHP de création de processus tentées en séquence, proc_open retenue |
| Persistence | Scheduled Task/Job: Cron | T1053.003 | Écriture directe dans /var/spool/cron/crontabs/, relance toutes les cinq minutes |
| Defense Evasion | Masquerading: Match Legitimate Name or Location | T1036.005 | Processus déguisé en thread noyau [kworker/u:8:0], binaire caché sous un chemin d'apparence GVFS |
| Defense Evasion | Indicator Removal | T1070 | Écriture dans le spool contournant la journalisation de remplacement de crontab |
| Defense Evasion | Impair Defenses | T1562 | Installation hors du document root, hors de la portée des scans planifiés |
| Credential Access | Steal Web Session Cookie | T1539 | Lecture du stockage de session Magento dans Redis |
| Command and Control | Application Layer Protocol: Web Protocols | T1071.001 | C2 annoncé en WebSocket sur TLS, non observé sur l'une des deux boutiques |
| Command and Control | Ingress Tool Transfer | T1105 | Téléchargement de l'implant depuis 247.cdnflare[.]xyz |
Remédiation : checklist opérationnelle
- Recherche de compromission avant tout le reste. Chercher le processus entre crochets porté par un utilisateur non root avec de la mémoire résidente, les fichiers listés plus haut, et les entrées de cron dans le spool. Ne pas se fier au champ
comm. - Fouiller
var/log/system.logetvar/report/, en cherchant la forme de l'en-têteX-TRACE-plutôt que la chaîne exacte, ainsi que laTypeErrorsurarray_merge(). - Vérifier la boîte du propriétaire de la boutique : une rafale de mails de transaction échouée, surtout avec balises de template non résolues et domaine
.invalid, est le signal d'alerte le moins coûteux à surveiller. - Ajouter
proc_openàdisable_functionsdans la configuration PHP. Les quatre premières fonctions tentées par le dropper étaient déjà bloquées sur une victime, c'est celle-là qui a permis le lancement. - Monter
/tmp,/var/tmpet/dev/shmavec l'optionnoexec, de sorte qu'un binaire téléchargé ne puisse pas s'exécuter. Ces deux réglages serveur ne dépendent pas de la connaissance de la chaîne et passent avant toute règle applicative. - Désactiver temporairement GraphQL si la vitrine ne l'exige pas. Les vitrines headless et PWA en dépendent, les vitrines classiques et Hyvä généralement pas.
- Appliquer le correctif non officiel qui ajoute un contrôle
PHP_SAPI !== 'cli'aux trois méthodes des scanners d'injection de dépendances, de préférence en source patch composer pour qu'il survive aux déploiements. Vérifier avant application simageplaza/module-admin-permissionsest présent dans le vendor, car ce module appelleClassesScanner.phpen HTTP et son écran d'administration se cassera. - Ne pas empiler les correctifs Disrex et ProxiBlue : ce sont deux écritures indépendantes du même garde, sur les mêmes trois méthodes, avec le même contrôle. Leur convergence est un bon signe, leur superposition est une erreur.
- Les règles nginx et Apache publiées n'inspectent que la query string. Les mêmes paramètres envoyés en corps POST ou en JSON passent. Ces règles arrêtent la campagne telle qu'elle tourne aujourd'hui, pas la vulnérabilité.
- En cas de compromission avérée : préserver les preuves d'abord, supprimer le cron avant de tuer le processus, ne pas redémarrer car la copie sous
/procpeut être le seul binaire restant, ne pas lancercomposer installpour nettoyer car cela écrase les horodatages qui montrent ce qui a été touché. - Vider le stockage de session, puis renouveler
crypt/keydansapp/etc/env.php, tous les mots de passe administrateur, toutes les clés API des prestataires de paiement et tous les identifiants d'intégration présents dans ce fichier. - Hasher le processus en cours depuis
/proc/<pid>/exeen plus du fichier sur disque : les deux builds peuvent différer. - Ne pas considérer un résultat de scan comme rassurant sans avoir vérifié la portée du scan. L'implant s'installe au-dessus du document root.
Sources
- Sansec : StyleSmuggler research advisory
- The Hacker News : Unpatched Magento and Adobe Commerce Zero-Day Exploited to Backdoor Online Stores
- Disrex Group : dépôt de mitigation StyleSmuggler
- Disrex Group : lecture du mécanisme
- Disrex Group : liste d'indicateurs
- Disrex Group : mail d'alerte précoce
- Disrex Group : guide de nettoyage
- Disrex Group : compte rendu d'incident
- ProxiBlue : correctifs non officiels
- Graycore : module de durcissement Magento
- Adobe : index des bulletins de sécurité Magento
- Nexcess : page de statut incident