Skip to main content

悪意のあるパッケージを防ぐ方法

2016年3月27日

0 分で読めます

先週、CERTは悪意のあるnpmパッケージを公開または利用するリスクについてユーザーに注意を促しました。npmに限った問題ではありませんが、この重大なリスクはnpmのエコシステムで発生しやすい傾向があります。これは確かに攻撃の経路ですが、私の見解では、脆弱性というより、安全でないデフォルト設定の問題です。

この記事では、リスクの内容と自分を守る方法を解説します。問題の原因となったトレードオフを確認し、攻撃のシナリオを検証したうえで、自分の環境でリスクを軽減する方法をご紹介します。具体的な対策だけを知りたい場合は、リスク軽減策のセクションに進んでください。

npmのセキュリティに関する10のベストプラクティスも参考になります。ダウンロードできるPDFのチートシートに加え、npmのセキュリティガイドラインと安全なプラクティスを取り入れる方法を詳しく解説した記事も掲載されています。

攻撃の手口

悪意のあるパッケージを作成する5つの簡単な手順

問題の根本には、新しいパッケージを簡単に公開できることがあります。gitやpipなどと同様に、npmでは認証情報をドットファイル(.npmrc)に保存でき、対話形式のプロンプトなしで公開できます。

残念ながら、簡単にしすぎてしまう場合もあります。公開プロセスが簡素化されると、誤って公開してしまう可能性が高まりますが、それ以上に、攻撃者が意図的に悪意のあるコードを公開する道を開いてしまいます。悪意のあるバージョンのパッケージを公開するために、攻撃者がすべきことは次のとおりです。

  1. 開発者のマシン上でコードを実行する。

  2. ログイン中のnpmユーザーを特定する(npm whoami --silent)

  3. そのユーザーのパッケージの1つを一時フォルダーにダウンロードする

  4. package.jsonを編集し、悪意のあるpostinstallフックを追加して、バージョンを0.0.1上げる

  5. 公開する(npm publish)

このうち、難しいのは最初の手順だけです。残念ながら、多くの開発者はかなり無防備な状態で環境を使いがちです……

curl X | bashで何かをインストールしたことはありませんか?それは、攻撃者が侵入する方法の1つです。sudo bashすら必要ありません。あるいは、試しに使ってみるためだけに、npmパッケージをローカルにインストールすることはありませんか?どの依存関係にも、上記の処理を実行するpostinstallフックが仕込まれている可能性があります。

実際、私たち開発者はいろいろ試します。そのため、クリーンな環境を使っているとは限りません。そのような環境に、公開権限を持たせるべきではありません。

拡散は速く、検知は遅い

攻撃者は作業を終えたら、あとはコードがNodeのエコシステム全体に広がるのを待つだけです。このパッケージを利用する多くのユーザーは、パッチバージョン(0.0.X)を暗黙的に含むsemver範囲を指定して読み込むため、新しいバージョンをそのまま取得します。さらに、悪意のあるコードを被害者が所有するパッケージに自己伝播させ、より速く広がるワームを作ることもできます。

さらに厄介なのは、悪意のあるパッケージが公開されたとしても、それに気づく簡単な方法がないことです。npmは新しいリリースが公開されてもユーザーに通知しません。また、パッケージ間の差分を簡単に確認する方法もありません(gitとは異なります)。問題を発見する最善の方法は、新しいバージョン番号に気づくことです。開発者が1人でプロジェクトを積極的に保守している場合は、バージョンの変更に気づく可能性が高いでしょう。しかし、休眠状態のプロジェクトや大人数のチームで開発しているプロジェクトでは、見逃してしまうことがあります。

リスク軽減策

悪意のある公開を防ぐ方法(公開パッケージ)

npmパッケージの共同作業者であれば、自分の身を守る責任があります。攻撃者があなたになりすまして不正なリリースを公開できないようにしましょう。方法は簡単です。ログアウトした状態を保ちましょう。

masterブランチでのビルドが成功したときにCI経由でのみ公開するようにし、semantic-releaseまたはカスタムスクリプトを使いましょう。手順は次のとおりです。

  1. 現在有効なトークンをすべて無効にする。トークンページから無効化できます。すべての共同作業者にも同じ対応を行ってください。

  2. CIに新しいトークンを設定する。RemyがTravis向けの詳しい手順を公開していますが、ほとんどのCIではシークレット変数を利用できます。トークンを設定したあとにnpm logoutを実行すると、トークンは無効になります。ログアウト状態に戻すには、代わりに~/.npmrcファイルを削除してください。

  3. 開発版にはgitを使う。トラブルシューティングなどでユーザーが一時的なパッケージバージョンを必要とする場合は、ブランチにコミットし、gitのコミットを使ってインストールするよう案内してください。

悪意のある公開を防ぐ方法(プライベートパッケージ)

プライベートパッケージを使っている場合、アクセスにはログインが必要なため、ログアウトしたままにはできません。幸い、npmは最近組織機能をリリースし、読み取り専用ユーザーにも対応しました。公開のリスクを負うことなく、アクセス権を付与できます。

その手順を改めてご紹介します。

  1. npm組織を作成します。ユーザーが1人だけの場合、最低料金が月額7ドル上がりますが、その価値は十分にあります。

  2. パッケージまたはスコープへの読み取り専用アクセス権を持つチームを作成します。

  3. ユーザーをメンバーとしてチームに追加します。読み取り専用ユーザーを1つ用意し、再利用することもできます。

  4. チームメンバー(自分を含む)は、これらの読み取り専用ユーザーとしてログインします

これらの手順を実施しても、コンピューターに見知らぬソフトウェアをインストールする際に注意が不要になるわけではありません。それでも、悪意のあるコードを拡散する手段にされる可能性は低くなります。

悪意のあるパッケージから自分を守る方法

npmパッケージを利用する側として、このリスクを完全に避けることはできません(他のパッケージマネージャーにも当てはまります)。リスクを軽減する唯一の方法は、インストールするパッケージを管理することです。

ローカルで開発している場合は、npm shrinkwrapを使って利用するパッケージのバージョンを固定することを検討してください。shrinkwrapを使うと、明示的に指定しない限り新しいバージョンは取り込まれず、利用中のバージョンが固定されます。そのため、更新ごとに手動で詳しく確認できます。手間はかかりますが、安全性は高まります。

組織の場合は、npm On-Siteの利用と、許可するパッケージのホワイトリスト登録を検討してください。こちらも少し手間はかかりますが、悪意のあるパッケージが入り込むリスクを軽減できます。安全性を確保するには、Read through cacheをOffに設定してください。

どちらの方法も新しいバージョンの取り込みを遅らせるため、古いバージョンに脆弱性が公表された際に、対処が遅れるおそれがあります。これらの方法を選ぶ場合は、npmの依存関係における既知の脆弱性を常に把握できるよう、Snykを利用することをおすすめします。

npmだけで解決できないの?

npmが機能を少し追加するだけで、このリスクをすべてなくせたら素晴らしいと思いませんか?

残念ながら、それはできません。このリスクは、オープンソースパッケージを使うことに伴うものです。多くの人が書いた小さなコードを数多く利用するということです。生産性向上には大きく役立つ一方で、パッケージの作者や、その意図に大きく依存することになります。

こうした動作はnpmに特有のものではないことも重要です。ほとんどのパッケージマネージャーで通知なしの公開が可能であり、ほぼすべてのパッケージマネージャーでインストール時にカスタムコードを実行できます。npmでリスクがより大きいのは、間接依存関係の数が多く、それらすべてを手作業で追跡するのがほぼ不可能だからです。

とはいえ、npmチームには次のような対応をしてもらえるとありがたいです。

  1. パッケージの新バージョンがリリースされたときに、すべての共同作業者にメールを送信する。これをデフォルトの動作とし、ユーザーが後からオプトアウトできるようにするべきです。

  2. 明示的に生成した自動化トークンを使わない限り、npm publishで認証情報を必須にする。これにより、デフォルトの動作(npm loginを使う場合)がより安全になります。これは互換性を破る変更になる可能性が高いため、npm 4より前に実現するとは考えにくいでしょう。

  3. 可能であれば、インストールスクリプトがインストール中に実行できる処理を制限する。これでリスクをなくすことはできませんが、攻撃をより困難にし、発生する可能性を低くできます。

CTFを始めよう

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