世界を動かす技術を、日本語で。

NPMに悪意のあるパッケージが氾濫、86,000回以上ダウンロードされる

概要

  • NPMリポジトリ で100以上の 認証情報窃取パッケージ が発見
  • 攻撃者は Remote Dynamic Dependencies (RDD) の脆弱性を悪用
  • PhantomRaven キャンペーンで8万回以上ダウンロード実績
  • 多くのパッケージが 未検出・未削除 のまま残存
  • RDDによる 依存関係の不可視化セキュリティツールの盲点

NPMリポジトリの脆弱性と攻撃事例

  • 2023年8月以降、 100以上の認証情報窃取パッケージ がNPMリポジトリで発見
  • セキュリティ企業 Koi が攻撃手法と影響を報告
  • 攻撃者は Remote Dynamic Dependencies (RDD) 機能を悪用
  • RDDにより、 未検証・信頼されていないドメイン から依存パッケージを自動取得
  • PhantomRavenキャンペーンで 126個の悪意あるパッケージ をNPMにアップロード
  • 悪意あるパッケージの 総ダウンロード数8万6,000回超
  • 2024年6月時点で 約80個のパッケージが公開中

Remote Dynamic Dependencies (RDD)の仕組みと問題点

  • RDDは 依存関係の柔軟な取得 を可能にする機能
  • 通常の依存関係は NPM公式インフラ から取得、可視化される
  • RDDは HTTP経由の非暗号化通信 も許可
  • 攻撃者は http://packages.storeartifact.com/npm/unused-imports などのURLから悪意ある依存関係をダウンロード
  • RDD経由の依存関係は 開発者やセキュリティスキャナーから不可視
  • NPMの表示上、「 依存関係0件」と誤認されやすい
  • パッケージインストール時、 毎回攻撃者サーバーから“新鮮な”依存関係を取得
  • 依存関係は キャッシュ・バージョン管理・静的化されず、検知困難

セキュリティ上の盲点とリスク

  • RDDの仕組みにより 従来の静的解析ツールが無効化
  • 攻撃者は セキュリティ監視の抜け穴 を突いて侵入
  • 開発者や利用者は 気付かぬうちに悪意あるコードを実行
  • NPMエコシステム全体 への信頼性低下リスク
  • 今後さらなる攻撃手法の高度化 が懸念

Hackerたちの意見

詳細な説明が載ってる別の記事はこちら: https://www.bleepingcomputer.com/news/security/phantomraven-...

「npm installを実行すると、npmは単にパッケージをダウンロードするだけじゃない。コードを実行するんだ。具体的には、package.jsonに定義されたライフサイクルスクリプト、つまりpreinstall、install、postinstallフックを実行する。パッケージのインストールが、なぜあなたのコンピュータ上で任意のコマンドを実行することを許される正当な理由があるのか?」これは研究者のレポートからの引用だよ。 https://www.koi.ai/blog/phantomraven-npm-malware-hidden-in-i... 編集: 他にもターミナルを起動するケースを考えてたけど、質問はそのままだね: https://socket.dev/blog/10-npm-typosquatted-packages-deploy-...

知ってる簡単な例だと、Mediasoupプロジェクトがある。これはインターネットで動画をストリーミングするためのC++で書かれたライブラリだよ。Nodeパッケージとして公開されていて、JS APIを提供してる。インストールすると、適切なC++ソースをダウンロードして、その場でコンパイルするんだ。プロジェクトのメンテナはコードを書きたかっただけで、プリコンパイルされたビルドを管理したくなかったから、これが一番論理的なインストール方法だったんだ。ちなみに、しばらく前に最も一般的なプラットフォーム用のダウンロード可能なビルドを追加したけど、それ以外はインストール時にソースをビルドすることが期待されてた(今でもそうだと思う)。

pnpm v10はデフォルトで全てのライフサイクルスクリプトを無効にして、ユーザーにパッケージをホワイトリストに登録させる必要があるよ。 https://github.com/orgs/pnpm/discussions/8945

一つのユースケースはバイナリのダウンロードだね。例えば、mongo-memory-server [0]はインストール後にmongoDBのバイナリをダウンロードするよ。[0] https://www.npmjs.com/package/mongodb-memory-server

確か、Huskyがライフサイクルフックを使って、NPMインストール時にリポジトリに設定されたgitフックをインストールするってことをやってた気がする。

これで痛い目にあったのは、gulp用の画像圧縮ツールや古いバージョンのsassをコンパイルした時、あとopensslの時が特に印象に残ってる。npmパッケージをダウンロードするのに、Cコンパイルツールをいじる必要があるなんて理想的じゃないよね。

パッケージをダウンロードするだけじゃなくて、コードも実行するんだよね。特に、LLMがこういう書き方をするのは、トレーニングデータに多くの人間がそう書いてたからだと思うと、ちょっと辛いな。

Swift Package ManagerはSwiftスクリプトを実行するんだ。Package.swiftファイル(マニフェスト)は実際には実行されるソースファイルなんだと思う。ただ、かなりサンドボックス化されてるだろうから、利用するのは難しいかもね。

でも、公式のインストール手順がcurl | bashの形になってるプロジェクトがどれだけあるか見たことある?

趣味でやってるんだけど、こういう侵害からどうやって守られて、情報をキャッチアップすればいいのかな? 人気のあるガイドをよくフォローしてるし、信頼できる著者が書いたものも多いけど、依存関係をインストールする時にちょっと軽率になってるかも。元のプロジェクトが脱線しちゃったから、痛点を解決しようとしてるんだ。関連するかもしれないけど、ローカルサービスを動かしてる小さなホームラボもあって、たまに新しい技術を試してる。時々、ちょっと面白いものを作って、誰かの役に立つかもしれないけど、逆に自分がボットの標的になっちゃうんじゃないかって心配になる。どう始めればいい?

「趣味でやってるんだけど、こういう侵害からどうやって守られて、情報をキャッチアップすればいいのかな?」一般的なソフトウェアのケースでは、「Nodeを使わない方がいい」ってアドバイスするかな。それに加えて、外部監査や検証なしのパッケージバックエンドは避けた方がいい。PyPIにも問題があるし、Cargoも理論的には同じくらい悪いけど、実際には安全だよ。ゴールドスタンダードは、Debianが提供するソフトウェアを使うこと(Fedoraもいいし、Archはちょっと下だけど、Linuxの外のユーザー提出の混乱ほどひどくはない)。でも、君の質問はフロントエンドのウェブ開発についてのようだね。そこは私の領域じゃないから、同情以外のアドバイスはできない。>「時々、ちょっと面白いものを作って、誰かの役に立つかもしれないけど、逆に自分がボットの標的になっちゃうんじゃないかって心配になる。」まさにその通りだね。ソフトウェアを配布するのは難しい。プロセスのいろんなレベルでたくさんの作業が必要だし、誰かがそれをやることにコミットしないといけない。時間やリソースをコミットするつもりがないなら、消費可能な形で配布しない方がいいよ(もちろん、自分が作ったものを配布するのはいいけど、適切にライセンスされていれば、誰かがそれを商品化してくれるかもしれない)。NPMはそのオーバーヘッドをうまくやれると思ってたけど、振り返ってみると、早すぎて壊れた状況だったね。

人気のある依存関係を使って、リリースは少なくとも1年以上前のものを選べばOK。これで完了。もし何か問題があったら、もう誰かが見つけてるはずだから。趣味でやってるなら、それで十分だよ。

ローカルマシンで開発するのはやめた方がいいよ。絶対に。開発は、ローカルでもリモートでも、VMやコンテナの中で全部やるべき。VM内では一時的な認証情報を使うか、認証情報なしでやるのがベスト。例えば、ノートパソコンで直接git pullするんじゃなくて、マウントしたボリュームを持つ別のVMでやって、そのVMやコンテナで開発ツールを動かすのがいいよ。これでコードをサンドボックス化できるし、開発環境も再現可能になる。GitHub使ってるならcodespaces、GitLabならworkspacesを使おう。どちらも使ってないなら、UTMやVagrantみたいなツールをチェックしてみて。

FreeBSDみたいなオペレーティングシステムでは、システムのパッケージマネージャを使うだけで、特定の言語のパッケージマネージャを何個も使う必要がないんだ。ライブラリの作者にこれを押し戻すのが正しいと思うし、何百万ものエンドユーザーにとって痛い思いをさせるのはやめるべきだと思う。パッケージが重要な分布に受け入れられるまでの摩擦が問題なんだよね。

Linuxを使ってるなら、コンテナでコーディングプロジェクトからシステムを隔離する簡単で安全な方法を作ろうとしたことがあるよ。詳しくは https://github.com/evertheylen/probox を見てみて。

NPMについては具体的には分からないけど、一般的にはね:特定のバージョンを選んで、そのバージョンの既知の良いチェックサムをビルドシステムで確認するのがいいよ。新しいパッケージは使う前に少なくとも4週間待って、特にあまり知られていないパッケージのプロジェクトのgitコミットをチェックするのがオススメ。

週に1Mダウンロード未満の依存関係は避けた方がいいよ。HonoやZodみたいに、依存関係がゼロのものを選ぶのがベスト。 https://npmgraph.js.org/?q=hono https://npmgraph.js.org/?q=zod 最近、Bunに切り替えたのは、NodeやDenoでダウンロードしなきゃいけないものがすでに多く含まれてるからなんだ(DBドライバー、S3クライアントなど)。

'numbsafari'が言ってた通り、もうホストを開発に使うのはやめた方がいいよ。AIアシスタントツールも含めてね。すべてをrunpodやdockerでコンテナ化する必要があるよ。

最近はちょっと論争になってるけど、すべての依存関係を潜在的なセキュリティの悪夢、バグの原因、将来的に解決しなきゃいけない問題として扱うべきだよ。依存関係は慎重に使って、最後の手段としてね。依存関係をベンダリングする(パッケージコードをプロジェクトにコピーすること)は助けになるよ。悪意のあるパッケージを止めることはできないけど、パッケージが悪意を持つようになるのは防げるから。必要なコードを依存関係から自分のコードにコピーするのもいいよ(クレジットとソースパッケージへのリンクをコメントに入れて)。パッケージが提供するものの一部だけが必要なときに便利だし、パッケージコードを読むことになるから、学習中にはいい練習になるよ。

(1) バカみたいな依存関係のあるパッケージは使わないこと。CLI版をライブラリに含めるパッケージは、開発者が恥をかくべきだよ。だいたい10〜20個のパッケージが追加されるから。機能を提供するライブラリと、コマンドラインからライブラリを使うためのCLIコマンドは絶対に混ぜちゃダメ。ライブラリはコマンドラインの無駄を省いた独立したパッケージであるべきだよ。(2) 依存関係が少ないパッケージを選ぶこと。例:commanderは依存関係がないし、minimistも今は依存関係がない。他のコマンドラインパーサーは昔は10〜20個の依存関係があったりした。(3) 1〜2行のJSで自分でできることはパッケージを使わないこと。ファイルをコピーするのにパッケージはいらないよ。fs.copyFileSyncでファイルをコピーできるし、fs.cpSyncでツリーをコピーできる。child_process.spawnでプロセスを生成できる。こういうことをするのにパッケージは必要ないよ。他にもパッケージを使わなくていい例はたくさんある。

ライフサイクルフックで依存関係を取得しているから、今は正当なものであっても、将来的にそうである保証はないんだ。依存関係の所有者が侵害される可能性もあるし、悪意があるかもしれないし、既存のバージョンを悪意のあるものに切り替える準備をしているパッケージの所有者かもしれない。インストール時のライフサイクルフックが今の形のままでいるのは難しいと思う。

人気のあるフレームワーク(AngularやVueみたいな)でプログラミングするためにNPMを使いたいけど、安全に使うにはどうしたらいいんだろう?トップレベルのフレームワーク(Angularなど)のあまり新しくないバージョンを選ぶだけで十分なのかな?NPMを何とか隔離して、postinstallフックみたいなコードがシステムに影響を与えないようにしつつ、普通に使える方法はあるのかな?

ちょっと安全にするための選択肢として、プロジェクトのルートにある.npmrcファイルにignore-scripts=trueを追加するのがあるよ。そうすればライフスタイルスクリプトは自動で実行されなくなる。ただ、これだとPnpmやBunほど良くはないんだ。自分のpostinstallスクリプトも実行されなくなっちゃうし(依存関係のだけじゃなくて)、信頼できるパッケージをホワイトリストにする方法もないからね。

最近の私の npm コマンドはこれ。攻撃面を大幅に減らせるよ。 alias npm='docker run --rm -it -v ${PWD}:${PWD} --net=host --workdir=${PWD} node:25-bookworm-slim npm'

  • 環境変数にはアクセスできない
  • 現在のディレクトリ(通常はJSプロジェクト)以外にはアクセスできない
  • .bashrcや他のファイルにはアクセスできない 参考: https://ashishb.net/programming/run-tools-inside-docker/

トリプルバックティックのコードブロックがあったらいいのに :(

確かにそれは助かるし、やる価値があるね。でもMacの場合、ネイティブ依存関係のせいで、開発をすべてコンテナに移さなきゃいけないかも。

それって、結局すぐに実行するためにダウンロードするだけのコードをサンドボックスするのはちょっとやりすぎじゃない?それに、pnpmをおすすめするよ。デフォルトでライフサイクルスクリプトを実行しなくなったから、実行するものをホワイトリストにできるんだ。

この攻撃にはたくさんのベクターがあるよ。もし悪意があったら、人気のプラグインやLSPをタイプミスで狙って、自動的にコードを実行するようにするだろうな。妥協されたneovimやvscodeは、ユーザー権限がたっぷりあって、完全なスクリプト言語やHTTPコール、システムコールができるからね。ほとんどのLSPはグローバルにインストールされるから、dockerコマンドでダウンロードしたかどうかは関係ない。

alias npm=... sandbox-runを使ってるよ: https://github.com/sandbox-utils/sandbox-run 上のシンプルなエイリアスはnode/npmにはうまくいくかもしれないけど、ローカルシステムにある他の多くのプログラムには一般化できないし、コンテナにマウントする必要があるリソースもあるからね…

86,000回のダウンロードのほとんどは、悪意のあるコードを探しているツールによる自動ダウンロードか、漏洩した認証情報を探している他の悪意のあるツールによるものだと思う。自分がどこにも宣伝していない新しいバージョンのパッケージを出すと、最初の1、2日で毎回何百回もダウンロードされる。86,000人がやられたわけじゃないし、もしかしたらゼロかもしれないね。

それか、誰も使わないゾンビプロジェクトのCIが繰り返しダウンロードしてるだけかもね。

TFAが言ってるように、LLMのトレーニングデータにあるけど実際には存在しないパッケージ名を狙ってるから、LLMによって幻覚的に生成されてるんだよね。今、悪いことが起こる可能性があるって全く知らない人たちが、自分のキラーアプリをビビッドにコーディングしてるのを見かけるけど、86,000人のうち80%以上がやられても驚かないな。

最近のnpm攻撃を考えると、npmを使って開発するのは安全なのかな。Reactプロジェクトを始めるたびに、何をするのか全く分からない追加のパッケージが何百もダウンロードされる。趣味でプログラミングを学んだ開発者としては、ThymeleafやプレーンJSみたいな他の安全な方法にこだわった方がいいのかな。FlaskやDjangoでバックエンドを構築する時は、必要なPythonパッケージを具体的にタイプするけど、フロントエンド開発は脆弱性のパンドラの箱みたいだね。

Pypiも同じ攻撃から免れているわけじゃないよ。「Pypi supply chain attack」でググると、マルウェアが含まれている(かなり少ないけど)パッケージがいくつか見つかる。中にはスペルミスじゃないものもあって、GitHub Actionsを通じてハッキングされた正当なパッケージもあったりして、正当なパッケージに悪意のあるペイロードが追加されてたんだ。

他のところも変わらないよ。最近jj(rust)をダウンロードしたら、470以上のパッケージがインストールされた。wan2gp(python)をダウンロードした時は211パッケージがインストールされた。

これはbunを使うことのあまり話題にされない利点の一つだね。

多くの依存関係はAIチャットボットによって「幻覚」とされる名前を使っている。開発者はしばしば必要な依存関係の名前をこれらのボットに問い合わせている。LLMの開発者や研究者は、幻覚の正確な原因や間違いを起こさないモデルの作り方をまだ理解していない。幻覚による依存関係の名前を発見した後、PhantomRavenはそれらを自サイトからダウンロードした悪意のあるパッケージに使用している。彼らが一般的なAIの幻覚パッケージ名を使っているのはとても興味深いと思ったよ。