Skip to main content

npmセキュリティ:サプライチェーン攻撃を防ぐ

著者
blog npm security meme

2022年11月8日

0 分で読めます

近年、npmのセキュリティはメディアで頻繁に取り上げられています。その多くは、npmレジストリそのものではなく、エコシステムで利用できるnpmパッケージに関するものです。開発者や、私たちが構築するソフトウェアに対するセキュリティリスクが高まるなか、サプライチェーン攻撃や、ソフトウェア開発ライフサイクルに関わるその他のセキュリティ脆弱性を防ぐ方法を理解することが、これまで以上に重要になっています。

サプライチェーン攻撃には、依存関係の混乱を狙う攻撃、オープンソースパッケージへの悪意あるコードやバックドアの仕込み、ビルドパイプラインのインフラ侵害など、さまざまな形があります。オープンソースライブラリやエコシステムに潜むセキュリティリスクは、開発者に差し迫った脅威をもたらします。

セキュリティ態勢を強化するには、どのようなソフトウェアセキュリティ対策を講じればよいでしょうか。この記事では、JavaScript開発者向けのnpmセキュリティ対策とサプライチェーンセキュリティツールをご紹介します(TypeScript開発者の方にも役立つ内容です)。

2022年におけるサプライチェーン攻撃の現状

アプリケーションセキュリティにおいて、開発者が重要な役割を担っていることを示す証拠が増えています。開発者は、peacenotwarモジュール、gmx-referenceを標的とした依存関係の混乱攻撃、Cobalt Strikeの依存関係の混乱攻撃など、セキュリティインシデントの発生に深く関わっています。これらは、近年npmセキュリティ関連で報じられた事例のほんの一部です。

プロジェクトで利用しているnpm依存関係や、異なるメンテナーの数はどれくらいだと思いますか?2019年6月7日に発表された学術研究では、このテーマに関する興味深い知見が共有されています。

平均的なnpmパッケージをインストールすると、79個のサードパーティパッケージと39人のメンテナーを暗黙のうちに信頼することになり、驚くほど大きな攻撃対象領域が生まれます。

サプライチェーン攻撃を狙う脅威アクターは、エコシステムに侵入し、マルウェアを仕込むための新しい手口を常に見つけ出します。その代表例として、2021年に公開された最近のセキュリティ調査記事では、次のことが明らかになりました。

期限切れドメインに関連付けられたメンテナーのメールアドレスが2,818件見つかりました。攻撃者はnpmアカウントを乗っ取ることで、8,494個のパッケージを乗っ取ることができます。[…] npmレジストリにあるパッケージの58.7%、メンテナーの44.3%が非アクティブであることも判明しました。

こうした懸念がある一方で、オープンソースソフトウェアは多くの革新的なテクノロジーを支え、組織が成果物をより迅速に提供するうえでも役立っています。オープンソースソフトウェアが世界にもたらす恩恵は計り知れませんが、サードパーティ製ソフトウェアライブラリに伴うリスクや、アプリケーションに組み込んだ際に影響が及ぶ範囲にも注意を払うことが重要です。

サプライチェーンセキュリティとは?

開発者がオープンソースのサプライチェーンセキュリティについて考える際、インストールするnpmパッケージ(npm install event-stream)や、プロジェクトにインポートするパッケージ(import colors)など、直接関わる部分に注目するのはよくあることです。しかし、ソフトウェアサプライチェーンの攻撃対象領域は、実際にははるかに広範です。

サプライチェーン攻撃とは?

ソフトウェア開発におけるサプライチェーン攻撃とは、脅威アクターがマルウェアやバックドア、その他の攻撃ペイロードをソフトウェアコンポーネント、またはソフトウェア関連のインフラに仕込み、それを利用して別のソフトウェアを作成させる攻撃です。つまり、脅威アクターは標的ソフトウェアのソースコードにある脆弱性を直接悪用したり、調査したりするわけではありません。

ソフトウェアアーティファクトのサプライチェーンレベルを表すSLSAは、略称として知られており、リスクが潜む統合上の弱点を見つけるための有用な指針です。以下は、Snykのサプライチェーンセキュリティホワイトペーパーから引用した図です。

ソフトウェアサプライチェーンの各段階(ソース、ビルド、パッケージ、依存関係、利用)と、ソースおよびビルドの完全性に関するリスクを示す図。

現在のソフトウェア構築の基本に立ち返ると、ソフトウェアを開発する過程でおなじみの多くの段階が、実際には深刻な脅威にさらされていることがわかります。実際、Googleのセキュリティブログに掲載された次の記事では、ソフトウェアデリバリーの各段階で発生した実際のセキュリティインシデントが複数紹介されています。

npmセキュリティとサプライチェーンセキュリティの予防策

(1)npmのロックファイルへの不正な挿入を防ぐ

2019年9月、私はnpmエコシステムにおけるパッケージロックファイル固有のセキュリティリスクを明らかにする調査結果を公開しました。JavaScriptのパッケージマネージャーであるYarnとnpmの両方に、この問題があることが判明しました。

この脅威は、攻撃者がプルリクエストなどの手段を通じてソースコードの変更を提案できるようになったときに発生します。プルリクエストは、GitHubでオープンソースプロジェクトに貢献する際によく使われる方法です。この場合、攻撃者がpackage-lock.jsonやyarn.lockなどのロックファイルを変更して新しいnpmパッケージ依存関係を追加したり、既存パッケージのソースURLだけを書き換えたりすると、パッケージのインストール(npm installやyarn installなど)を実行するたびに、そのマルウェアが取得されてしまいます。

以下のスクリーンショットは、npm上のオープンソースパッケージに対するプルリクエストを通じて、悪意あるパッケージを挿入したときのものです。よく見て、見つけられるか試してみてください。

@octokit/restや@types/nodeのバージョン変更を含む、package.jsonの依存関係更新を示すGitHubのプルリクエスト

次のようなチェックリストで確認しているかもしれません。

  • 追加したパッケージはエコシステムで知られており、脆弱性もない ― 確認済み。

  • パッケージ名にタイポスクワッティングの形跡はない ― 確認済み。

  • これらのパッケージのバージョンは正規のもので、パッケージ自体に悪意はない ― 確認済み。

それでも、見つけるのは難しいでしょう。

yarn.lockファイルを拡大してみてください。パッケージマネージャーのロックファイルは機械的に生成されるもので、人間が読みやすく、管理しやすいようには作られていません。そのため、私が挿入した次のような悪意ある変更を見つけるのは非常に困難です。

npmのロックファイルで、依存関係がregistry.npmjs.org/msからgithub.com/lirantal/ms/tarball/masterに変更されているGitHubのコード差分

さらに、JavaScriptのパッケージマネージャーでは、GitHubのGistやソースコードリポジトリから直接パッケージをインストールするなど、非常に特殊なソースからのインストールも可能です。

つまり、攻撃者である私が完全に制御できる新しいソースの場所(resolvedキーなど)を指定するように、ロックファイルを変更するだけでよいのです。SHA512の整合性値もそれに合わせて設定できるため、警告が出ることもありません。

このプルリクエストがマージされると、次にプロジェクトでyarn installを実行したコントリビューターが、実質的に私の悪意あるパッケージを取得することになります。しかし、開発者がプロジェクトに触れるのを待つ必要すらありません。成熟したオープンソースプロジェクトの多くには、継続的インテグレーション(CI)環境があるからです。

このプルリクエストを作成しただけで、GitHub Actions(またはCircleCI)上の自動ビルドが開始される可能性は十分にあります。このプルリクエストをビルドする際にnpm installが実行され、CI環境のシークレットやAPIキーなどの機密情報にアクセスし、盗み出されるおそれがあります。

この種のロックファイルへの不正な挿入を防ぐには、lockfile-lintを使う方法があります。lockfile-lintを使えば、ロックファイルを検証し、固定された依存関係を記録する機械生成のスナップショットが、所定の信頼ポリシーに準拠しているか確認できます。

たとえば、lockfile-lintを設定し、yarn.lockに記載されたnpmパッケージの取得元が、公式のyarnpkg.orgミラー以外である場合にエラーを発生させることができます。

yarn.lockをnpx lockfile-lintでチェックし、パッケージmsのホストが無効だと報告するターミナル。

この潜在的なセキュリティリスクを防ぐ対策をまとめると、次のとおりです。

  1. 開発環境とCIでlockfile-lintを使う。

  2. 外部のコントリビューターがロックファイルを更新できないようにする。

  3. 依存関係を自動でアップグレードするボットを使い、信頼できる方法でロックファイルを更新する(公式レジストリから取得するもの)。ヒント:Snyk botがこれを行います。

(2)任意のコマンド実行を防ぐ

npm installコマンドを実行すると、インストールするパッケージによって、インストール処理の一環として任意のコマンドが実行される可能性があることは、今や周知の事実でしょう。

ある時点で次のコマンドを実行していたら、どうなっていたでしょうか。

npm install @vue/cli

あなたはnode-ipc npmパッケージの破壊行為の被害に遭っていたでしょう。メンテナーがパッケージを変更し、デスクトップフォルダーにファイルを作成するコマンドを実行するようにしたのです。node-ipc npmパッケージの別のバージョンでは、ハードドライブ内のファイルをたどってすべて消去するなど、より有害な動作も行われました。

依存関係が任意のコマンドを実行できるのは、npmパッケージマネージャーが依存関係のインストール処理にinstallライフサイクルフックを用意しているためです。多くの場合、モジュールのメンテナーは、インストール用アーティファクトの準備、リソースファイルの取得、インストール後のクリーンアップなど、正当な目的でこのフックを利用しています。また、インストール先のホスト上でコンパイルする必要があるネイティブモジュールのバインディングをクロスコンパイルする手段としても利用できます。

先ほど取り上げたnpmサプライチェーンに潜む弱点とは?という調査では、エコシステムにおけるinstallフックの利用状況について、次のような知見が示されています。

パッケージの2.2%(33,249個)がインストールスクリプトを使用していることがわかりました。つまり、97.8%のパッケージは、セキュリティのベストプラクティスとしてインストールスクリプトを使わないというnpmの推奨に従っている可能性があります。

実施すべき予防策をまとめると、次のとおりです。

  1. npm installコマンドに--ignore-scriptsフラグを追加し、npmパッケージの依存関係による任意のコマンド実行をスキップします。

  2. プロジェクトで一緒に作業するほかの開発者も保護できるよう、このフラグをプロジェクトの.npmrc設定ファイルに追加することも検討してください。

  3. このフラグを有効にすると、npmパッケージの正当なユースケースの一部が実行できなくなり、npm install処理が失敗する可能性があることに注意してください。

  4. --ignore-scriptsフラグは、ローカルファイルソースからインストールするパッケージや、Gitリポジトリからnpmパッケージをインストールする場合には適用されません。

(3)npmパッケージの無条件なアップグレードを避ける

継続的インテグレーション(CI)プロセスの一環として、すべての依存関係を最新バージョンに明示的にアップグレードする開発者もいます。テストや正常系のテストケースも実行し、依存関係が新しいバージョンをリリースしてもアプリケーションが期待どおりに動作することを確認するためです。アプリケーションを壊すことなく、将来のバージョンとの互換性を確保する方法です。

npmの最新バージョンに更新するには、次のコマンドを実行します。

npm update

または、専用のnpmパッケージを使う方法もあります。

npx npm-check-updates -u

こうしたアップグレードには正当な理由がありますが、いわゆるnpmパッケージの無条件なアップグレードは、潜在的なセキュリティインシデントを招くことにもなります。すでに説明したように、npm installの実行には大きな危険が伴います。

依存関係を無条件にアップグレードすると、依存関係の混乱を狙う攻撃などの脅威や、近年明らかになったcolorsのセキュリティインシデント、node-ipcのセキュリティインシデントなどに、不必要にさらされるという固有のセキュリティリスクが生じます。

依存関係を最新の状態に保つために、次の対策を講じましょう。

  1. スマートな自動依存関係アップグレードツールを使いましょう。こうしたツールには、自動更新マニフェストやロックファイルの機能が組み込まれていることがよくあります。

  2. 変更履歴とリリースアーティファクトを慎重に確認しましょう。メンテナーが変更されていないか、変更されている場合は妥当な理由があるかを確かめてください。また、新しくリリースされたバージョンに対応するソースコードも公開されているか確認しましょう。

自動アップグレードと、悪意のあるnpmパッケージを取り込むリスクについては、特に注意が必要です。Snykは、公開から21日未満のバージョンへのアップグレードを推奨していません。機能上のバグが含まれていて後に公開停止となるバージョンや、アカウントが侵害されてリリースされたバージョン(アカウント所有者が悪意ある第三者に制御を奪われた場合)を避けるためです。

詳しくは、依存関係を自動PRでアップグレードする方法に関するSnykのドキュメントをご覧ください。

(4) 依存関係の混同を防ぐ

依存関係の混同とは、組織内で作成・使用されるパッケージが、プライベートプロキシやホスティングサービスに置かれている一方で、そのパッケージ名がnpmレジストリで登録可能な状態にある場合に発生する攻撃です。

残念ながら、ローカルのツール設定は簡単に誤った状態になります。これにnpmパッケージマネージャーによるパッケージ取得の仕組みが加わると、次のような状況が生まれます。同じ名前のパッケージが公開npmレジストリに存在し、インストール済みのものより新しいバージョンがある場合、npmはそのパッケージをインストールします。

Microsoftの従業員が注意を怠ったり、npmパッケージマネージャーや社内プロキシサーバーの設定に誤りがあったりすれば、次のような依存関係の混同攻撃によって、社内インフラが侵害されるおそれがありました。

npm install azure-core-tracing-samples-js

実際、従業員が次の信頼できるnpmパッケージ更新コマンドを実行していた場合にも、同じリスクが当てはまります。

npm update

その理由は、依存関係の混同攻撃の仕組みにあります。この攻撃は2021年に公に研究成果として発表されましたが、Snykのセキュリティリサーチチームは、この手法で脅威アクターが企業のインフラを標的にしている証拠を発見しました。さらに、セキュリティ研究者のNishant Jaintは、npmパッケージのエイリアスを使った依存関係の混同という別の攻撃経路の可能性を指摘しています。

依存関係の混同攻撃を検出・防止するには、Snykが提供する無料のオープンソースツールsnyncを利用できます。実行例は次のとおりです。

npx snync --directory ~/my-app --private “superlaser”

Testing project at ~/my-app
Reviewing your dependencies...

Checking dependency: death-star-hyper-reactor
   -> ⚠️ vulnerable
Checking dependency: superlaser
   -> ❌ suspicious

(5) NPMセキュリティ:マルウェアから事前に保護する

npmパッケージのインストールにnpm installコマンドを実行したところ、次のような出力が表示された経験があるでしょう。

$ npm install amp-html

added 1166 packages from 1172 contributors and audited 39128 packages in 112.505s
found 93911 high severity vulnerability

上記の問題は、パッケージにセキュリティ脆弱性が見つかっても、脆弱なパッケージをインストールした後で初めてその事実を知ることになる点です。amp-htmlのnpmパッケージには何が含まれているのでしょうか。その前に、パッケージをより安全にインストールし、主導権を取り戻す方法を見てみましょう。

パッケージをインストールする前に、依存関係の健全性に関する有用な情報を収集し、それをもとにnpmパッケージをプロジェクトに追加するか、npmjs.orgで「ほかを探す」かを判断できるとしたらどうでしょう。

ここで、私が開発したnpqというオープンソースプロジェクトを紹介します。npqを使うと、プロジェクトにREADMEがあるか、ライセンス要件に準拠しているか、関連するGitHubリポジトリがあるかといったシグナルに基づいて、事前にセキュリティ対策を講じ、パッケージをインストールするかどうかを十分な情報に基づいて判断できます。

npqツールはSnykと完全に統合されています。無料アカウントに登録すると、環境内で利用可能なSnyk APIトークンを検出し、Snyk Vulnerability Databaseを照会します。

私の開発環境では、次のようにnpmコマンドをnpqのエイリアスに設定しました。

alias npm=npq

これでnpm installを実行するたびに、実際にはnpqが呼び出され、先にチェックと検証が行われます。続行を選択すると、npqからnpmパッケージマネージャーのCLIに処理が戻り、インストールが続行されます。

Node.js v12.16.1を使用するバージョン1.0.0のプロジェクトが表示されたターミナルウィンドウ。npmコマンドを入力中。

npqをインストールして、脆弱性をテストするための随時利用ツールとして使うこともできます。

npq install amp-html

npmパッケージの依存関係の健全性を把握するもう一つの方法は、Snyk Advisorを利用することです。npmパッケージや代替パッケージ、類似パッケージを検索し、メンテナンス状況やセキュリティ状況を確認できる便利なツールです。

Snyk Advisorでamp-html npmパッケージを表示すると、次のようになります。

npmパッケージ「amp-html」のSnyk Advisorページ。健全性スコアは24/100で、セキュリティ上の問題、人気度の低さ、メンテナンスの停止が示されています。

(6) トロイの木馬型ソースコード攻撃

トロイの木馬型ソースコード攻撃とは、見えないソースコードの脆弱性に関する懸念を調査した、最近公開されたセキュリティ研究論文のタイトルです。

これはバックエンドのNode.jsプロジェクトのコード断片です。特定のページルートへの管理者アクセス権の付与を一部処理していますが、実はトロイの木馬型ソースコード攻撃が仕込まれています。問題に気づけるでしょうか。

module.exports = fastify => (req, res, next, access = {}) => {
 if (!req.query.token && access.login) { // Redirect to login
   return fastify.login.view(req, res);
 }

 // Debug login for issue #1812
 if (req.session.user != "user‮ ⁦// Check if admin⁩ ⁦") {
    console.log("You are an admin.");
 }

 if (!req.query.token) { // Private access without token.
   return res.code(401).send(new Error('Missing token.'));
 }

 …

分かりやすいようにIDEでJavaScriptの構文ハイライトを有効にしました。上記のコード断片のスクリーンショットをご覧ください。

モジュールのエクスポート、ログインへのリダイレクト、管理者チェック、トークンがない場合の401レスポンスを示す、構文強調表示されたJavaScript。

Debug login for issue #1812に関する2つ目の条件分岐に注目してください。何が起きているのかを理解するために、このコードを実行可能なNode.jsコードに移してみましょう。

#!/usr/bin/env node

const accessLevel = "user";

 // Debug login for issue #1812
 if (accessLevel != "user‮ ⁦// Check if admin⁩ ⁦") {
    console.log("You are an admin.");
 }

このコードをnode test.jsで実行すると、次の出力が表示されます。

You are an admin.

でも、なぜでしょう?もう頭を抱えているかもしれませんね。

根本的な原因は、上記のソースコード断片のテキストに制御文字が含まれていることです。これらの文字は目に見えないように埋め込まれていますが、実際のソースコードのテキストを大きく変えてしまいます。テキストの表示順を左から右、または右から左へと変えるため、インタープリターやコンパイラーが読み取る意味と、人間が気づかずに読む内容が異なるものになります。

開発者は、コードがプロジェクトに投稿された際のコードレビューで、この種の問題を見つけ、見落とさずに済むのでしょうか。

Snyk Codeでソースコードを監視している場合、SQLインジェクションや安全でないコードインジェクションなどのセキュリティ脆弱性を検出すると報告します。さらに、Snyk Codeは、コード内のトロイの木馬型ソースコードに関連する問題も報告します。

管理者のみのアクセスを示唆するコメントが強調表示されたJavaScriptコードと、データフロー分析パネルを表示するSnyk Codeのインターフェース

Snykは、プロジェクト概要ページで検出したほかの脆弱性とともに、トロイの木馬型ソースコードの脆弱性を視覚的にすばやく確認できるようにします。

TrojanSourceConfusingUnicodeの検出結果、深刻度フィルター、スコア、強調表示されたJavaScriptコードを示すSnyk Codeの問題ダッシュボード。

Visual Studio CodeとGitHubプラットフォームはどちらもアップデートされ、目に見えない制御文字を含むソースコードファイルを表示した際に、視覚的なサインで警告するようになりました。

ESLintプラグインのeslint-plugin-anti-trojan-sourceは、ESLintを使ってJavaScriptコードベース内のトロイの木馬型ソースコード攻撃を検出・緩和するオープンソースツールです。

NPMセキュリティ:今後の対策

ソフトウェアサプライチェーンのセキュリティリスクは増え続けています。それだけでも懸念されますが、攻撃は開発者とそのエコシステムを狙うよう巧妙化しています。

npmセキュリティのベストプラクティスや関連トピックについて理解を深めるため、次の記事もぜひご覧ください。

  1. Daniel BermanによるSnykで悪意のあるパッケージとサプライチェーン攻撃を防ぐ。

  2. Ulises Gascónによるバックドアとは?Node.jsで作ってみよう。

  3. Liran TalとJuan Picadoによるnpmセキュリティのベストプラクティス10選。

セキュアコーディングの実践について学びたい方には、Snyk LearnにあるJavaScriptセキュリティの実践的なレッスンもおすすめです。どちらも短時間で受講でき、完全無料です。

NPMパッケージに悪意のあるものはありますか?

残念ながら、npmパッケージには実際に悪意のあるものがあり、タイポスクワッティング攻撃やソーシャルエンジニアリングによるevent-streamへのバックドア埋め込みなど、さまざまな手法で開発環境上で実行される悪意あるコードを仕込むことがあります。実際、node-ipcの事例のように、パッケージのメンテナー自身が悪意あるコードをnpmパッケージに仕込んだケースも報告されています。

NPMパッケージのセキュリティを確認するには?

Snykは、200件を超える悪意のあるnpmパッケージの発見など、悪意あるnpmパッケージを定期的に発見して報告しています。npmパッケージのセキュリティを確認するには、Snyk Advisorでオープンソースパッケージの健全性を評価するか、無料のSnyk CLIとリポジトリ連携を使用して、悪意のあるパッケージをスキャン・監視できます。

NPMの脆弱性はどのような影響をもたらしますか?

npmパッケージで見つかったセキュリティ脆弱性は、アプリケーションに影響を及ぼし、大きなリスクにさらす可能性があります。websocket-extensions npmパッケージの未修正バージョンを使用している場合、正規表現によるサービス拒否攻撃に対して脆弱です。同様に、Node.jsアプリケーションで静的ファイルのホスティングと配信にst npmパッケージの未修正版を使っている場合、ディレクトリトラバーサル攻撃の危険にさらされます。

開発者に愛され、セキュリティチームから信頼される。

Snykの開発者ファーストのツールは、ガバナンスやコンプライアンスのニーズに応える、統合された自動化セキュリティを提供します。

続きを読む

Blog

フロンティアモデルは脆弱性を発見した。攻撃者だけがエクスプロイトチェーンを見つけた。

静的解析で欠陥は見つかりましたが、ライブ攻撃テストで侵害につながる連鎖を実証できたのは唯一でした。Evo COS、Claude Security、Claude Code Securityを比較します。

feature insights context
Blog

自律型攻撃はすでに始まっている。防御もそのスピードに追いつかなければならない。

自律型攻撃者によって、防御に使える時間は短くなっています。継続的な検出、修復、検証、予防で、セキュリティチームが攻撃に歩調を合わせる方法をご紹介します。

Blog

AIコーディングエージェントが不適切なアクセス制御を繰り返し実装する理由

AIコーディングエージェントは、コンパイルが通りレビューも通過する一方で、あるテナントのデータを別のテナントに公開してしまう認可ロジックを生成することがあります。不適切なアクセス制御が検出しにくい理由と、その防止策をご紹介します。