135
75

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

axios乗っ取り事件の全容 — 39分間で何が起きたか、そして今すぐやるべき防御策

135
Posted at

はじめに

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.1axios@0.30.4
安全なバージョン: axios@1.14.0axios@0.30.3

技術的分析 — 何が仕込まれたか

攻撃の起点: postinstallフック

axios@1.14.1package.jsonには新しい依存が追加されていました。

{
  "dependencies": {
    "plain-crypto-js": "^4.2.1"
  }
}

plain-crypto-js@4.2.1package.jsonにはpostinstallフックがありました。

{
  "scripts": {
    "postinstall": "node setup.js"
  }
}

npm installを実行するだけでsetup.jsが自動実行されます。
ユーザーの操作は一切不要です。

二重の難読化

setup.jsは巧妙に難読化されていました。

  1. 文字列を反転してBase64デコード
  2. 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文字のランダム英数字

実行可能なコマンドはkillpeinject(バイナリ注入)、runscriptrundirの4種類です。

証拠隠滅

マルウェアは実行後に自身を消去しました。

  1. fs.unlink(__filename)setup.jsを削除
  2. あらかじめ用意した無害なpackage.mdpackage.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パイプラインを狙いました。

  1. litellmのGitHub ActionsでTrivyがaptから未固定で取得されていた
  2. 侵害されたTrivyを通じてPYPI_PUBLISH tokenが窃取された
  3. 窃取したトークンでlitellm@1.82.71.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.lockpnpm-lock.yaml)を必ずコミットしてください。
CIではnpm ciを使い、lockfileと一致しないインストールを拒否します。

# CI環境では npm install ではなく npm ci を使う
npm ci

.npmrcでpostinstallスクリプトを無効化

# .npmrc
ignore-scripts=true

この設定によりpostinstallフックが実行されなくなります。
ただし一部のパッケージ(esbuildsharpなど)は正当な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の設定を改めて確認してみてはいかがでしょうか。

参考資料

135
75
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
135
75

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?