JavaScript
Node.js
Security
セキュリティ

セキュリティ インシデント flatmap-stream@0.1.1 と npm-run-all での対応

人気のある npm パッケージ event-stream (DL数: 2M/週) が悪意ある攻撃に利用されるというセキュリティ インシデントがありました。

私が管理する npm-run-all (DL数: 440K/週) も間接的に event-stream に依存していたため、インシデントの詳細と私の対応について触れておきます。

何をすべき?

あなたのプロジェクトが event-stream に依存している場合は、この依存を削除し、代替手段を探すと良さそうです。event-stream の作者は、このパッケージをメンテナンスしないと明言しています。ただ、これだけ騒ぎになったので、信用できる誰かが引き継ぐ可能性はあります。 [編集(2018/11/30)] 現在は npm 社がパッケージ管理を引き継いでいます。ただ、リポジトリは凍結されており、今後変更されることはないでしょう。

次に、あなたのプロジェクト上で以下のコマンドを実行し、

npm ls flatmap-stream

flatmap-stream (悪意あるコードを含んでいる) が存在した場合は、これに依存しているパッケージを最新にアップデートしましょう。nodemon, npm-run-all, @vue/cli 等のパッケージが間接的に依存していました。もし、最新にアップデートしても flatmap-stream への依存が消えない場合、原因になっているパッケージを削除し、代替品を探すと良いでしょう。

また、Visual Studio Code のユーザーは、拡張機能が悪意あるコードの影響を受けていないかチェックしましょう。Microsoft が影響を受けている拡張機能について列挙してくれました

何があったか?

攻撃者は巧妙に攻撃を秘匿しながら、copay というビットコイン ウォレットに悪意あるコードを注入し、ビットコインを窃盗しようとしました。どうやら未遂に終わったようです。

時系列を見てみましょう。

2017年10月
event-stream パッケージへの最後の (正常な) コミット。
2018年8月頃
flatmap-stream パッケージが作られた。まだ無害。
8月末
攻撃者が event-stream パッケージへの関与を開始した。event-stream 作者に対してメンテナンスを引き継ぎたい事を知らせ、権限を受け取った。
9月9日
攻撃者が event-stream@3.3.6 を公開。これに flatmap-stream@^0.1.0 への依存が追加された。まだ無害。
9月16日
攻撃者が event-stream@4.0.0 を公開。これから flatmap-stream@^0.1.0 への依存が削除された。メジャーバージョンアップなので、大半のパッケージは 3.x に依存し続ける一方、npm のページ等では最新版の依存関係しか表示しないため、依存を隠蔽する効果がある。
9月26日
copay が依存を更新して event-stream@3.3.6flatmap-stream@0.1.0 に依存した (copay#baab80)。準備完了。
10月5日
攻撃者が悪意あるコードを注入した flatmap-stream@0.1.1 を公開した。
10月23日
Node.js 11 がリリースされた。
10月26日
copay が依存を更新して flatmap-stream@0.1.1 に依存した (copay#9643f1)。注入完了。
10月29日
nodemon パッケージに、Node.js 11 で実行すると「非推奨 API を利用している」というシステム警告が出ると報告があった (nodemon#1442)。特に調査は行われず。
11月19日
event-stream リポジトリに、疑わしいコミットがある事を指摘する Issue が作られた (event-stream#115)。それ以上の調査は行われず。
11月21日
nodemonの非推奨警告の調査から悪意あるコードが再発見され、event-stream リポジトリに詳細を記載した Issue が作られた (event-stream#116)。これに対し、event-stream の作者は既にパッケージの管理権限を手放していることを告げた。
11月27日
Hacker News 等に event-stream#116 が転載され、大騒ぎになった。
flatmap-stream パッケージは npm リポジトリから削除された。
event-stream@3.3.6 は npm リポジトリから削除された。
event-stream パッケージの管理権限を npm 社が一時的に引き取った。
copay 開発メンバーは、悪意あるコードはどのプラットフォームにもデプロイされていなかったと報告した。また、 copay 5.0.2〜5.1.0 の間、悪意あるコードが注入されていたことを報告した。被害の詳細は調査中とのこと。

こうしてみると、攻撃者が時間をかけて慎重に行動していたことが窺えます。悪意あるコードが Node.js 11 で非推奨になった API を利用していたのは幸運だったでしょう。

なお、npm-run-all パッケージには11月22日に報告があり、私は24日土曜日に事態を認識して対処しました。現在の最新版には event-stream/flatmap-stream への依存はありません。

どんな攻撃だった?

非常に巧妙な狙撃のような攻撃でした。

攻撃コードは暗号化されており、環境変数 npm_package_description に格納された文字列をキーに復号してから実行するようになっていました。環境変数 npm_package_description というのは、npm-scripts 実行時に npm が自動的に設定する環境変数で、内容は package.jsondescription プロパティの値となります。つまり、パッケージの概要説明です。

パッケージの説明文はパッケージ毎に異なるので、ほとんどの場合は攻撃が失敗し、何もしません。反面、私たちが悪意あるコードを発見しても、攻撃対象のパッケージがどれか分からなければ攻撃コードを復号できず、解読できません。

そして、説明文が "A Secure Bitcoin Wallet" だったときだけ復号に成功し、ビットコインの窃盗という攻撃行動を行います。

狙撃です。

良いニュースは、大半の人はこの攻撃の影響を受けなかったことです。

今後の対策は...?

まず、私たち npm パッケージの開発者は、今回のインシデントを教訓としなければなりません。信用できると確認できない人物にコミット権限やリリース権限を与えないよう。

間接依存しているライブラリに攻撃コードが注入されるのでは、私たち開発者はたまったものではありません。これは氷山の一角なのでは? という不安はどうしてもついてまわります。
npm 社からはまだコメントが出ていませんが、不安を抑えるためにも、なんらかの仕組みが必要だと思います。審査強化という方向に行くのか、パッケージにパーミッションを持たせる方向に行くのか。

発表を待ちましょう。 [編集(2018/11/30)]: npm より The npm Blog — Details about the event-stream incident との記事が発表されました。特に追加の施策はなさそうです。