Threat Intelligence

StyleSmuggler : le zero-day Magento qui poisonne un log puis fait exécuter le fichier par un mail de relance

Admin CyberAfrik 07 September 2026 19 lectures
StyleSmuggler : le zero-day Magento qui poisonne un log puis fait exécuter le fichier par un mail de relance

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

Chaîne d'exploitation StyleSmuggler

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

  1. 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.
  2. Fouiller var/log/system.log et var/report/, en cherchant la forme de l'en-tête X-TRACE- plutôt que la chaîne exacte, ainsi que la TypeError sur array_merge().
  3. 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.
  4. Ajouter proc_open à disable_functions dans 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.
  5. Monter /tmp, /var/tmp et /dev/shm avec l'option noexec, 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.
  6. 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.
  7. 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 si mageplaza/module-admin-permissions est présent dans le vendor, car ce module appelle ClassesScanner.php en HTTP et son écran d'administration se cassera.
  8. 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.
  9. 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é.
  10. 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 /proc peut être le seul binaire restant, ne pas lancer composer install pour nettoyer car cela écrase les horodatages qui montrent ce qui a été touché.
  11. Vider le stockage de session, puis renouveler crypt/key dans app/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.
  12. Hasher le processus en cours depuis /proc/<pid>/exe en plus du fichier sur disque : les deux builds peuvent différer.
  13. 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

Tags : StyleSmugglerMagentoAdobe CommerceSansecDisrexzero-dayRCE non authentifiéeimplant Rust
Partager cet article