Skip to main content

npm JavaScriptパッケージマネージャーにおけるファイルシステム乗っ取り脆弱性を理解する

著者
prioritize vulns header

2020年1月7日

0 分で読めます

2019年12月11日、主要なJavaScriptパッケージマネージャー(npm、yarn、pnpm)すべてに影響するセキュリティ脆弱性が公表されました。セキュリティ研究者のDaniel Rufが発見したこの脆弱性を悪用すると、攻撃者はさまざまな手口で任意のファイルを上書きできます。

この記事の内容:

Node.jsのコマンドラインパッケージの仕組み

npmパッケージでは、パッケージマニフェストファイル(package.json)にbinキーを指定することがよくあります。このbinキーを使って、パッケージマネージャークライアント(npmやその他の代替ツールなど)は、コマンドプロンプトから利用できる実行ファイルをグローバルな$PATH環境変数に登録します。

Angular CLI(@angular/cli)、typescript、またはSnyk CLI(snyk —)など、コマンドラインパッケージをグローバルにインストールしたことがあれば、この機能を見たことがあるでしょう。これらは特定のnpmプロジェクトディレクトリに関係なく、パスが通っていればどこからでも利用できます。binキーの使われ方を理解するため、unpkgサイトでpackage.jsonを開き、Angular CLIプロジェクトを確認してみましょう。

@angular/cliのpackage.jsonファイルで、ngコマンドを./bin/ngに割り当てるbinキーを示しています。
npmがこのパッケージをインストールする際に、実行可能ファイルを定義するためにbin JSONキーを使用していることを示すng-cliのpackage.jsonフィールド(出典:unpkg https://unpkg.com/browse/@angular/cli)

上の例のように、このバージョンのパッケージでは、package.jsonマニフェストファイルがngという名前の実行ファイルを宣言しています。このファイルはパッケージに同梱され、./bin/ngから利用できます。

この./bin/ng JavaScriptファイルは、ファイルの先頭にUnixのシバン文を追加することで実行可能になります。この文は、ファイルの実行に使用するインタープリターをシェルに伝えます。Node.jsベースのアプリケーションの場合、Node.jsバイナリはユーザーのオペレーティングシステムにインストールされています。

Angular CLIの場合、実行ファイルの内容は次の図のとおりです。

Node.jsのバージョン確認と、サポート対象外のバージョンに関するエラーメッセージを示すAngular CLIのng実行ファイルのソースコード。
(出典: https://unpkg.com/browse/@angular/cli@6.0.4/package.json)

このセキュリティ脆弱性はnpmパッケージマネージャークライアントにどのような影響を与えるのか

この脆弱性の影響を理解するには、npm、特にインストール済みの実行ファイルをユーザーが利用できるようにする仕組みについて、もう少し詳しく見ていく必要があります。

npmクライアントは、グローバルにインストールされたパッケージが宣言する実行ファイル「bin」を特定のディレクトリで管理します。このディレクトリ内のリンクと、インストールされたパッケージの実行ファイルとの間にシンボリックリンクを作成します。

これらの実行ファイルが置かれるディレクトリを調べるには、npmクライアントで次のように実行します。

$ npm bin --global
/Users/lirantal/.nvm/versions/node/v10.16.3/bin

シェルで$PATH環境変数を確認すると、お使いのオペレーティングシステムに対応するディレクトリのエントリが見つかります。これにより、OS内のどのディレクトリからでも、binを宣言するグローバルインストール済みパッケージにアクセスできます。

ここまでの情報から、あるパッケージが別のパッケージの提供する実行ファイルをbinとして宣言できるのか、気になるところです。Danielがまさにその点を調査し、npm、yarn、pnpmといった主要なパッケージマネージャーにおける任意ファイル上書き脆弱性を発見しました。

このnpmのセキュリティ脆弱性は、実際にどのように発生するのか

Snykでは、DanielがSnykのセキュリティチームに提供した概念実証をもとに、この任意ファイル書き込み脆弱性を再現し、悪用の仕組みを実演します。

最初のパッケージでは、次のようにグローバル実行ファイルのセットを定義します。

{
  "name": "binary-replacer",
  "version": "1.0.0",
  "main": "index.js",
  "author": "Daniel Ruf <mac1@daniel-ruf.de>",
  "license": "MIT",
  "bin": {
    "curl-test": "./bin/index.js",
  }
}

実行ファイルcurl-testは、コンソールにテキストを表示するだけのシンプルなものです。

#!/usr/bin/env node
console.log('hi')

これをnpm install -g binary-replaceでグローバルにインストールし、シェルからcurl-testコマンドを実行すると、画面にhiと表示されます。

ここで、binary-replacer-2という別のパッケージがあり、最初のパッケージとまったく同じbinキー(curl-test)を持つ実行ファイルを定義しているとします。

{
  "name": "binary-replacer-2",
  "version": "1.0.0",
  "main": "index.js",
  "author": "Daniel Ruf <mac1@daniel-ruf.de>",
  "license": "MIT",
  "bin": {
    "curl-test": "./bin/index.js",
  }
}

ユーザーがこのパッケージをグローバルにインストールすると、npmは既存のシンボリックリンクを、このパッケージが提供する実行ファイルで上書きします。このデモの例では悪意のないファイルです。

#!/usr/bin/env node
console.log('hi 2')

では、悪意のあるパッケージが広く知られたグローバルパッケージを狙ったらどうなるでしょうか。可能性は数多くあります。Zeitのnowパッケージから、Sindre Sorhousのfkill CLIまで、さまざまなものが対象になり得ます。

さらにDanielは、npm、yarn、pnpmでこの脆弱性が発生するさまざまなパターンを示しました。たとえば、キーをcurl-testではなく、相対リンクの場所に置き換えたらどうなるでしょうか。

 "bin": {
    "../../aaa": "/bin/date"
  }

あるいは、ユーザーがよく使う、シェルプロンプトから実行できる有名なコマンドラインツールの絶対パスをキーに設定したらどうなるでしょうか。たとえば次のようなケースです。

"bin": {
  "/usr/bin/date": "./fake-date"
}

単独開発者のZoltan Kochanが主導するpnpmは、多くの場合、適切な動作をするため称賛に値します。相対パスが指定された場合に限り、脆弱性の影響を受けます。それでも問題であることに変わりはなく、修正済みのnpmバージョンにアップグレードする方法も用意されています。

このnpmのセキュリティ脆弱性が深刻な理由

npmのコマンドラインフラグ—ignore-scriptsを使ってインストールしたパッケージでも任意のファイルが上書きされるため、この脆弱性はさらに深刻です。このフラグを使えば、悪意のあるバイナリが同じ処理を実行するのを防げると思うかもしれません。

また、グローバルにインストールしたパッケージとその実行ファイルについて説明しましたが、プロジェクト内の推移的依存関係にも同様に当てはまります。通常のnpm installで依存関係をインストールする際、パッケージがプロジェクトディレクトリ内外のファイルを任意に上書きする可能性があります。その結果、マルウェアの埋め込みやロックファイル改ざん攻撃、その他のファイルシステム汚染につながることがあります。

自分も影響を受けるのか?何をすべきか

次の2つのケースでは、影響を受ける危険があります。

  1. ソーシャルエンジニアリングで誘導される、または攻撃者が人気パッケージに似た名前のパッケージを公開し、それをインストールしてしまうなどして、悪意のあるパッケージをインストールする。

  2. 悪意のないパッケージが侵害され、package.jsonのbinキーが変更されて、悪意のあるファイルの実行を含む上書きが行われる。可能性は非常に低いとはいえ、これまでに2度発生しています:[1]、[2]

npmパッケージマネージャークライアントはNode.js自体に同梱されており、npmの修正済みバージョン6.13.4への更新が必要だったため、この脆弱性はNode.jsのセキュリティリリースにもつながりました。

Danielはまず、このセキュリティ脆弱性の発見をSnykのセキュリティ研究チームに報告し、各プロジェクトおよび対象となるパッケージマネージャーのメンテナーと連携して責任ある形で公表できるよう支援しました。また、攻撃の種類について詳述した追加情報を自身のブログで公開しています。

以下のバージョンのnpm、yarn、pnpmを使用している場合、脆弱性の影響を受けます。直ちにアップグレードしてください。

npm、yarn、pnpmをプロジェクトの依存関係に含めるケースは一般的ではありませんが、Snykでプロジェクトを監視していて、いずれかの脆弱なバージョンが見つかった場合は、Snykの定期アラートで通知されます。さらに、GitHubリポジトリ(またはBitbucket)を接続していれば、これらのパッケージマネージャーを修正済みバージョンにアップグレードするプルリクエストを自動的に作成します。

npmなどのパッケージマネージャーを扱う際は、開発者向けのセキュリティ対策を実践することもおすすめします。日々の作業に役立つよう、従うべきセキュリティ規約とベストプラクティスをnpmのセキュリティ対策10選にまとめました。

オープンソースパッケージの脆弱性を見つけましたか?Snykはセキュリティコミュニティを大切にしており、オープンソースパッケージのセキュリティ脆弱性を責任ある形で開示することが、ユーザーのセキュリティとプライバシーの確保につながると考えています。オープンソースパッケージの脆弱性を見つけたと思われる場合は、Snyk Open Source Package Vulnerability Disclosureプログラムにご報告ください。あとは私たちが対応します。 https://snyk.io/vulnerability-disclosure/

CTFを始めよう

オンデマンドのバーチャル入門ワークショップを視聴して、CTFの課題の解き方を学びましょう。