はじめに
2026年3月31日、npmパッケージ「axios」が乗っ取られました。
axiosはJavaScriptで最も使われるHTTPクライアントです。
週間ダウンロード数は1億回を超えます。
私自身、ほぼすべてのNode.jsプロジェクトで使っています。
事件を知ったとき、まず自分のプロジェクトを確認しました。
package-lock.jsonを開き、axiosのバージョンを探しました。
幸い影響を受けるバージョンではありませんでした。
しかし「もし週末にデプロイしていたら」と思うとぞっとしました。
この記事では事件の全容と技術的な仕組みを解説します。
そして「自分のプロジェクトは大丈夫か」を確認する手順を示します。
事件の時系列
攻撃は周到に準備されていました。
以下がUTC(日本時間に変換するには+9時間)での時系列です。
| 時刻 (UTC) | 出来事 |
|---|---|
| 3/30 05:57 |
plain-crypto-js@4.2.0 公開(無害なダミー版) |
| 3/30 23:59 |
plain-crypto-js@4.2.1 公開(マルウェア入り) |
| 3/31 00:21 |
axios@1.14.1 公開、latestタグ付与 |
| 3/31 01:00 |
axios@0.30.4 公開、legacyタグ付与 |
| 3/31 00:05頃 | Socket社の自動検知がplain-crypto-jsをフラグ |
| 3/31 約03:20 | 悪意あるバージョンがnpmから削除 |
攻撃者はまず無害なplain-crypto-js@4.2.0を公開しました。
これはnpmの自動スキャンを通過させるための布石です。
約18時間後に悪意あるコードを含む4.2.1を公開しました。
axiosメンテナーのjasonsaayman氏のアカウントが侵害されました。
アカウントのメールアドレスがifstap@proton.meに変更されていました。
そこから39分間で2つの悪意あるバージョンが公開されました。
影響を受けるバージョン: axios@1.14.1 と axios@0.30.4
安全なバージョン: axios@1.14.0 と axios@0.30.3
技術的分析 — 何が仕込まれたか
攻撃の起点: postinstallフック
axios@1.14.1のpackage.jsonには新しい依存が追加されていました。
{
"dependencies": {
"plain-crypto-js": "^4.2.1"
}
}
plain-crypto-js@4.2.1のpackage.jsonにはpostinstallフックがありました。
{
"scripts": {
"postinstall": "node setup.js"
}
}
npm installを実行するだけでsetup.jsが自動実行されます。
ユーザーの操作は一切不要です。
二重の難読化
setup.jsは巧妙に難読化されていました。
- 文字列を反転してBase64デコード
- XOR暗号(鍵:
OrDeR_7077、位置依存のインデックス7 * i^2 % 10)
重要な文字列はすべて配列stq[]にエンコードされて格納されていました。
実行時に初めてデコードされるため、静的解析を回避できます。
OS別のRAT配信
デコード後、スクリプトはOS判定を行い、C2サーバーに接続します。
C2サーバーはsfrclak[.]com:8000でした。
| OS | 配信方法 | 保存先 | 偽装名 |
|---|---|---|---|
| macOS | AppleScript + curl | /Library/Caches/com.apple.act.mond |
Appleデーモン |
| Windows | VBScript + curl | %TEMP%\6202033.ps1 |
Windows Terminal |
| Linux | curl + python3 | /tmp/ld.py |
なし |
各OS用のRAT(Remote Access Trojan)は同一のC2プロトコルを共有していました。
ただし実装言語は異なります。
- Windows: PowerShell
- macOS: C++
- Linux: Python
C2プロトコルの詳細
RATは以下の特徴を持っていました。
- 通信方式: HTTP POSTでBase64エンコードしたJSONを送信
- User-Agent: 古いIE8を偽装(
mozilla/4.0 (compatible; msie 8.0; ...)) - ビーコン間隔: 60秒
- セッションID: 16文字のランダム英数字
実行可能なコマンドはkill、peinject(バイナリ注入)、runscript、rundirの4種類です。
証拠隠滅
マルウェアは実行後に自身を消去しました。
-
fs.unlink(__filename)でsetup.jsを削除 - あらかじめ用意した無害な
package.mdをpackage.jsonにリネーム
事後にnode_modulesを調べても痕跡が残りません。
これが発見を困難にしていた要因です。
Googleの脅威分析チーム(GTIG)は、macOSバイナリがWAVESHAPERバックドアと一致すると報告しています。WAVESHAPERは北朝鮮系の脅威アクター「UNC1069」に帰属するマルウェアです。
影響範囲
axiosの週間ダウンロード数は約1億回です。
^1.14.0のようなキャレット範囲指定をしていた場合、npm installで自動的に1.14.1がインストールされます。
悪意あるバージョンは約2〜3時間公開されていました。
この間にnpm installを実行したプロジェクトが影響を受けた可能性があります。
特にCI/CDパイプラインは危険です。
ビルドのたびにnpm installを実行する構成が多いためです。
週末深夜(日本時間では月曜朝)という時間帯も計算されていたと考えられます。
同時期のLiteLLM事件
axiosの1週間前、Pythonエコシステムでも同様の事件が起きていました。
2026年3月24日、PyPIパッケージ「litellm」が侵害されました。
litellmはLLMのAPIプロキシとして人気があり、月間9,500万ダウンロードを誇ります。
攻撃手法
攻撃者(TeamPCP)はlitellmのCI/CDパイプラインを狙いました。
- litellmのGitHub ActionsでTrivyがaptから未固定で取得されていた
- 侵害されたTrivyを通じてPYPI_PUBLISH tokenが窃取された
- 窃取したトークンで
litellm@1.82.7と1.82.8が公開された
三層構造のマルウェア
litellmのマルウェアは3段階で動作しました。
- 第1層: Base64エンコードされたPythonコードを実行、AES-256-CBCで暗号化して送信
- 第2層: SSH鍵、Git認証情報、AWS/GCP/Azure認証情報、Kubernetesトークンを収集
- 第3層:
sysmon.pyとしてシステムサービスに登録、50分間隔でC2をポーリング
特に危険だったのは.pthファイルの悪用です。
Pythonは起動時にすべての.pthファイルを自動処理します。
litellmを直接importしなくても、Python起動時にマルウェアが実行されました。
axiosとlitellm、わずか1週間の間に2つのメジャーパッケージが侵害されました。これはOSSサプライチェーン全体の信頼性に関わる問題です。「有名パッケージだから安全」という前提はもう成り立ちません。
自分のプロジェクトを今すぐ確認する
1. lockfileでバージョンを確認する
# npm の場合
grep -r "axios" package-lock.json | grep "version"
# yarn の場合
grep -A1 "axios@" yarn.lock
# pnpm の場合
grep "axios" pnpm-lock.yaml | grep "version"
1.14.1または0.30.4が含まれていたら影響を受けています。
2. plain-crypto-jsの存在を確認する
# node_modules内を確認
ls node_modules/plain-crypto-js 2>/dev/null && echo "危険: 存在します" || echo "OK: 存在しません"
# lockfileも確認
grep "plain-crypto-js" package-lock.json yarn.lock pnpm-lock.yaml 2>/dev/null
3. 影響があった場合の対処
# 安全なバージョンに固定
npm install axios@1.14.2
# または
npm install axios@1.14.0
# node_modulesを完全に再構築
rm -rf node_modules package-lock.json
npm install
さらにCI/CD環境で影響があった場合は、以下も実施してください。
- npmトークンの無効化と再発行
- 環境変数に含まれるシークレットのローテーション
- ビルド成果物の再検証
今後の防御策
lockfileの厳密管理
package-lock.json(またはyarn.lock、pnpm-lock.yaml)を必ずコミットしてください。
CIではnpm ciを使い、lockfileと一致しないインストールを拒否します。
# CI環境では npm install ではなく npm ci を使う
npm ci
.npmrcでpostinstallスクリプトを無効化
# .npmrc
ignore-scripts=true
この設定によりpostinstallフックが実行されなくなります。
ただし一部のパッケージ(esbuild、sharpなど)は正当なpostinstallを使うため、プロジェクトごとに検証が必要です。
npmの provenance 機能を活用する
npm v9.5以降では、パッケージの来歴(provenance)を検証できます。
npm audit signatures
GitHub Actions OIDCから公開されたパッケージは署名が付与されます。
侵害されたアカウントからの手動公開には署名がありません。
脆弱性スキャナの導入
| ツール | 特徴 |
|---|---|
| Socket | サプライチェーン攻撃に特化。今回も6分で検知 |
| Snyk | 脆弱性データベースが充実。CI統合が容易 |
npm audit |
標準ツール。最低限これは実行する |
GitHub Dependabotの活用
# .github/dependabot.yml
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "daily"
open-pull-requests-limit: 10
Dependabotは脆弱なバージョンを検知してPRを自動作成します。
CI/CDパイプラインの防御(LiteLLMの教訓)
LiteLLM事件はCI/CDが攻撃経路でした。
以下を徹底してください。
# GitHub Actionsではアクションのバージョンをコミットハッシュで固定
- uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde8ef3282e2bf6f1 # v4.2.0
# Dockerfileでもツールのバージョンを固定
RUN curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin v0.58.2
タグ指定(@v4)ではなくSHAピンニングを使います。
タグは上書き可能ですが、コミットハッシュは不変です。
まとめ
今回の事件から得られる教訓を整理します。
- メンテナーアカウントの侵害は防ぎようがない。依存先を信頼するだけでは不十分
-
npm install一発でRATが入る。postinstallフックは強力な攻撃ベクター - 証拠隠滅まで自動化されており、事後調査では見つけにくい
- lockfileの厳密管理とCI/CDでの
npm ciが最低限の防御ライン - Socket等のサプライチェーン特化スキャナが実際に6分で検知した実績がある
axiosとlitellm、わずか1週間で2つのメジャーパッケージが侵害されました。
これを機に、lockfileの管理状況やCIの設定を改めて確認してみてはいかがでしょうか。
参考資料
- Inside the Axios supply chain compromise - Elastic Security Labs
- Axios NPM Distribution Compromised - Wiz Blog
- Supply Chain Attack on Axios - Socket
- North Korea-Nexus Threat Actor Compromises Axios - Google Cloud Blog
- Axios npm Package Compromised - Snyk
- Compromised litellm PyPI Package - Sonatype
- TeamPCP Backdoors LiteLLM - The Hacker News