技術 約8分で読めます

週間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-jspostinstall フックで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:57plain-crypto-js@4.2.0 公開。CryptoJSのソースをコピーした無害なデコイ版。npm上に公開履歴を作り、ゼロヒストリーの検知アラームを回避する布石
3/30 23:59plain-crypto-js@4.2.1 公開。postinstall フックと難読化ドロッパーを追加した攻撃版
3/31 00:21axios@1.14.1 公開。侵害されたメンテナアカウントから手動publish。1.x系ユーザーを標的
3/31 01:00axios@0.30.4 公開。同アカウントから39分後にレガシー0.x系も汚染
3/31 ~03:15npm が両バージョンを unpublish。1.14.1の公開時間は約2時間53分、0.30.4は約2時間15分
3/31 04:26plain-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.jsondependenciesplain-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-jsrequireimport している箇所はゼロ。マニフェストに書かれているが一切使われていない「ファントム依存関係」で、postinstall フックを発火させることだけが目的だ。

plain-crypto-js 自体は正規の crypto-js を偽装している。作者名(Evan Vosberg)、説明文、リポジトリURLまで本物と同じ値をコピーしており、npmページをざっと見ただけでは区別がつかない。

RATドロッパーの難読化と復号

plain-crypto-jspostinstall で実行されるスクリプトは、単一のminified JavaScriptファイルで二層の難読化が施されている。

  1. XOR暗号層。エンコード済み文字列の配列 _e から各エントリを復号する。鍵はJavaScriptの parseInt を通して解析され、有効キーは 1997(文字列中の数字位置6〜9)。各文字はXOR演算 charCode ^ key[i % keyLen] で復元される
  2. 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段階で展開される。

  1. where powershell でPowerShellのバイナリパスを特定
  2. PowerShellバイナリを wt.exe(Windows Terminalの名前)としてコピー。正規プロセスに偽装する
  3. VBScriptを生成して wscript で実行。CreateObject("WScript.Shell").Run でウィンドウを完全に隠蔽(vbHide)してC2からPowerShell RATスクリプトをダウンロード・実行

VBScript、ダウンロードしたスクリプトとも実行後に自動削除される。

Linux

最もシンプルで、Node.jsの child_process.exec から直接シェルコマンドを実行する。curl でC2からPythonスクリプトをダウンロードし、/tmp/.npl に保存してバックグラウンドで python3 実行する。

フォレンジック回避の自己消去メカニズム

このマルウェアの厄介な点は、実行後に証拠を消す仕組みが組み込まれていることだ。

  1. postinstall.jsnode_modules/plain-crypto-js/ から削除
  2. package.json を削除
  3. あらかじめ同梱されていた 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 をインストールした環境は侵害されたと仮定して動く必要がある。

  1. axios@1.14.0(または0.30.3)にダウングレード
  2. node_modules/plain-crypto-js/ ディレクトリが存在するか確認。あればドロッパー実行済み
  3. macOSなら /Library/Caches/com.apple.amond、Linuxなら /tmp/.npl の存在を確認
  4. npmトークン・SSHキー・クラウド認証情報・CI/CDシークレットをローテーション
  5. npm install --ignore-scripts で再インストールし、意図しないフック実行を防止

npm は両バージョンを削除済みで、plain-crypto-js もセキュリティホールドに置き換わっている。ただし、ロックファイルにバージョンが固定されている環境やプライベートミラーにキャッシュが残っている環境では、引き続き注意が必要だ。