週間1億DLのaxiosがnpmで乗っ取られ、クロスプラットフォームRATを投下
2026年3月31日、npmで最も使われているHTTPクライアントライブラリ axios の2バージョン(1.14.1と0.30.4)が侵害された。週間1億ダウンロードを超えるパッケージへの攻撃で、StepSecurityが検出・報告した。攻撃者はメンテナのnpmアカウントを乗っ取り、正規のCI/CDパイプラインを迂回して手動で汚染バージョンを公開。注入された偽の依存関係 plain-crypto-js が postinstall フックでmacOS・Windows・Linuxの3プラットフォームに対応したRAT(Remote Access Trojan、遠隔操作マルウェア)をドロップする。
npmは約3時間後に両バージョンを削除し、plain-crypto-js もセキュリティホールドで置き換えたが、その間にインストールした環境は侵害されたと見なすべきだ。npmサプライチェーン攻撃としてはua-parser-js(2021年)やevent-stream(2018年)と並ぶ規模で、Clinejectionでも取り上げたメンテナアカウント乗っ取りパターンの最新かつ最大級の事例になった。
攻撃タイムライン
| 時刻 (UTC) | イベント |
|---|---|
| 3/30 05:57 | plain-crypto-js@4.2.0 公開。CryptoJSのソースをコピーした無害なデコイ版。npm上に公開履歴を作り、ゼロヒストリーの検知アラームを回避する布石 |
| 3/30 23:59 | plain-crypto-js@4.2.1 公開。postinstall フックと難読化ドロッパーを追加した攻撃版 |
| 3/31 00:21 | axios@1.14.1 公開。侵害されたメンテナアカウントから手動publish。1.x系ユーザーを標的 |
| 3/31 01:00 | axios@0.30.4 公開。同アカウントから39分後にレガシー0.x系も汚染 |
| 3/31 ~03:15 | npm が両バージョンを unpublish。1.14.1の公開時間は約2時間53分、0.30.4は約2時間15分 |
| 3/31 04:26 | plain-crypto-js がセキュリティホールドスタブに置き換え。マルウェアの公開時間は約4時間27分 |
Socketの自動マルウェア検出は plain-crypto-js@4.2.1 の公開から6分以内(00:05:41 UTC)に危険判定している。
メンテナアカウント侵害とOIDCバイパス
攻撃者はaxiosのリードメンテナのnpmアカウントを侵害し、登録メールアドレスをProtonMailに書き換えた。ここが技術的に重要なポイントで、axiosの正規リリースはGitHub ActionsからnpmのOIDC Trusted Publisher機構で公開される。OIDC Trusted Publisherとは、GitHub Actionsワークフローとnpmパッケージの紐付けを暗号的に検証する仕組みで、ワークフロー実行時に発行される短命のOIDCトークンでしか公開できない。
axios@1.14.1 のnpmレジストリメタデータにはこのOIDCバインディングがない。つまり攻撃者はGitHub Actionsを経由せず、盗んだ長命のnpmアクセストークンで直接CLIからpublishした。GitHubリポジトリにも1.14.1に対応するコミットやタグは存在しない。npmだけに存在する幽霊リリースだ。
偽依存関係 plain-crypto-js の正体
axios自体には悪意あるコードが1行もない。変更点はたった1つ、package.json の dependencies に plain-crypto-js@^4.2.1 が追加されただけだ。
| バージョン | dependencies |
|---|---|
| axios@1.14.0 (正規) | follow-redirects, form-data, proxy-from-env |
| axios@1.14.1 (汚染) | follow-redirects, form-data, proxy-from-env, plain-crypto-js@^4.2.1 |
axiosの全86ファイルを grep しても plain-crypto-js を require や import している箇所はゼロ。マニフェストに書かれているが一切使われていない「ファントム依存関係」で、postinstall フックを発火させることだけが目的だ。
plain-crypto-js 自体は正規の crypto-js を偽装している。作者名(Evan Vosberg)、説明文、リポジトリURLまで本物と同じ値をコピーしており、npmページをざっと見ただけでは区別がつかない。
RATドロッパーの難読化と復号
plain-crypto-js の postinstall で実行されるスクリプトは、単一のminified JavaScriptファイルで二層の難読化が施されている。
- XOR暗号層。エンコード済み文字列の配列
_eから各エントリを復号する。鍵はJavaScriptのparseIntを通して解析され、有効キーは1997(文字列中の数字位置6〜9)。各文字はXOR演算charCode ^ key[i % keyLen]で復元される - Base64 + 文字列反転層。エンコード済み文字列を反転し、
=を復元してBase64デコードした後、XOR層に通す
StepSecurityが _e 配列の全エントリを完全復号しており、C2 URLやOSごとのシェルコマンド、ファイルパスなどが平文で回収されている。
攻撃チェーンの全体像
graph TD
A[npm install axios@1.14.1] -->|依存関係解決| B[plain-crypto-js@4.2.1<br/>自動インストール]
B -->|postinstallフック| C{OS判定}
C -->|macOS| D[AppleScript生成<br/>/tmp配下]
C -->|Windows| E[VBScript生成<br/>PowerShellコピー]
C -->|Linux| F[curl + Python実行]
D -->|osascript実行| G[C2へPOST<br/>body: npm_mac]
E -->|wscript実行| H[C2へPOST<br/>body: npm_win]
F -->|sh -c実行| I[C2へPOST<br/>body: npm_nix]
G --> J[macOS RAT<br/>/Library/Caches/<br/>com.apple.amond]
H --> K[Windows RAT<br/>PowerShellスクリプト]
I --> L[Linux RAT<br/>/tmp/.npl]
J --> M[自己消去<br/>package.jsonをデコイに差替]
K --> M
L --> M
3プラットフォームとも同じC2エンドポイント sfrclak.com:8000 にPOSTリクエストを送り、POSTボディの npm_mac / npm_win / npm_nix でプラットフォームを区別する。npm_ プレフィックスはSIEM(セキュリティ情報イベント管理)やネットワークログで正規のnpmレジストリ通信に見せかける偽装だ。
OS別のRAT展開
macOS
AppleScriptファイルを /tmp に書き出して osascript で実行する。AppleScriptはC2にPOSTしてmacOS向けRATバイナリをダウンロードし、/Library/Caches/com.apple.amond に保存して実行権限を付与、バックグラウンドで起動する。
パスは意図的に選ばれている。/Library/Caches/ はインシデント対応でまず見ない場所で、com.apple.amond はAppleの逆引きDNS命名規則に似せた偽装名だ。amond は “Activity Monitor Daemon” の略と推測される。実行後、AppleScriptファイルは削除され、永続的に残るのはRATバイナリだけになる。
Windows
3段階で展開される。
where powershellでPowerShellのバイナリパスを特定- PowerShellバイナリを
wt.exe(Windows Terminalの名前)としてコピー。正規プロセスに偽装する - VBScriptを生成して
wscriptで実行。CreateObject("WScript.Shell").Runでウィンドウを完全に隠蔽(vbHide)してC2からPowerShell RATスクリプトをダウンロード・実行
VBScript、ダウンロードしたスクリプトとも実行後に自動削除される。
Linux
最もシンプルで、Node.jsの child_process.exec から直接シェルコマンドを実行する。curl でC2からPythonスクリプトをダウンロードし、/tmp/.npl に保存してバックグラウンドで python3 実行する。
フォレンジック回避の自己消去メカニズム
このマルウェアの厄介な点は、実行後に証拠を消す仕組みが組み込まれていることだ。
postinstall.jsをnode_modules/plain-crypto-js/から削除package.jsonを削除- あらかじめ同梱されていた
package.json.bak(バージョン4.2.0、postinstallフックなし)をpackage.jsonにリネーム
事後に node_modules/plain-crypto-js/ を調べても、クリーンなマニフェストしか見つからない。npm audit を実行しても何も検出されない。ただし、node_modules/plain-crypto-js/ ディレクトリ自体の存在が侵害の証拠になる。正規のaxiosにこの依存関係は存在しないので、このディレクトリがあればドロッパーは実行済みだ。
StepSecurity Harden-Runnerによる実行時検証
StepSecurityはGitHub ActionsランナーにHarden-Runner(カーネルレベルでネットワーク・プロセス・ファイル書き込みを監視するツール)を入れて axios@1.14.1 を実際にインストールし、静的解析の結果を実行時に裏付けた。
検証で確認された2つのC2通信が特に興味深い。
1つ目は npm install ステップ中の01:30:51Z。npm install 開始からわずか1.1秒後にはC2への接続が発生している。依存関係の解決が終わる前にドロッパーが動き出していた。
2つ目は別のワークフローステップ「Verify axios import and version」中の01:31:27Z。npm install は完了済みで、36秒後に全く別のステップでC2コールバックが検出された。ステージ2のPythonペイロード(C2から配信された攻撃コード本体)がバックグラウンドプロセスとして独立動作していた証拠だ。
npmサプライチェーン攻撃の変遷
今回の手口は「メンテナアカウント乗っ取り → 正規パッケージに依存関係注入」というパターンで、2021年のua-parser-js事件と同じ系統に属する。ただし洗練度が段違いだ。
| 要素 | ua-parser-js (2021) | axios (2026) |
|---|---|---|
| 悪意コードの配置 | パッケージ本体に直接埋め込み | 別パッケージに隔離(ファントム依存関係) |
| 対象OS | 特定OS向け | macOS, Windows, Linux 全対応 |
| 証拠隠滅 | なし | package.jsonをデコイに差替え、スクリプト自己削除 |
| 事前準備 | なし | 18時間前にデコイ版を公開してnpm履歴を偽装 |
| OIDC検証 | 当時は未導入 | OIDC Trusted Publisher導入済みだが長命トークンで迂回 |
3月だけ振り返っても、LiteLLMのPyPI汚染(TeamPCPがTrivy経由でCI/CDトークンを窃取)、telnyxのWAVステガノグラフィ、npmパッケージでのPastebinステガノグラフィと、パッケージレジストリを狙った攻撃が続いている。攻撃者の手口もPyPI・npm・OpenVSXとエコシステムを横断し、postinstallフック、モジュールインポート時の実行、WAV埋め込みなどトリガー方法を変えてきている。
影響を受けた場合の対応
axios@1.14.1 または axios@0.30.4 をインストールした環境は侵害されたと仮定して動く必要がある。
axios@1.14.0(または0.30.3)にダウングレードnode_modules/plain-crypto-js/ディレクトリが存在するか確認。あればドロッパー実行済み- macOSなら
/Library/Caches/com.apple.amond、Linuxなら/tmp/.nplの存在を確認 - npmトークン・SSHキー・クラウド認証情報・CI/CDシークレットをローテーション
npm install --ignore-scriptsで再インストールし、意図しないフック実行を防止
npm は両バージョンを削除済みで、plain-crypto-js もセキュリティホールドに置き換わっている。ただし、ロックファイルにバージョンが固定されている環境やプライベートミラーにキャッシュが残っている環境では、引き続き注意が必要だ。