セキュリティ インシデント 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 社がパッケージ管理を引き継いでいます。ただ、リポジトリは凍結されており、今後変更されることはないでしょう。

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

```console
npm ls flatmap-stream
```

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

また、Visual Studio Code のユーザーは、拡張機能が悪意あるコードの影響を受けていないかチェックしましょう。Microsoft が[影響を受けている拡張機能について列挙してくれました](https://code.visualstudio.com/blogs/2018/11/26/event-stream)。

## 何があったか?

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

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

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

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

なお、[npm-run-all] パッケージには11月22日に報告があり、私は24日土曜日に事態を認識して[対処しました](https://github.com/mysticatea/npm-run-all/releases/tag/v4.1.5)。現在の最新版には `event-stream`/`flatmap-stream` への依存はありません。

## どんな攻撃だった?

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

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

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

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

狙撃です。

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

## 今後の対策は...?

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

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

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

[copay]: https://www.npmjs.com/package/copay
[event-stream]: https://www.npmjs.com/package/event-stream
[nodemon]: https://www.npmjs.com/package/nodemon
[npm-run-all]: https://www.npmjs.com/package/npm-run-all
[@vue/cli]: https://www.npmjs.com/package/@vue/cli