悪意あるevent-streamバックドアの事後分析
2018年12月6日
0 分で読めます先週、想像し得る最悪の事態が起きました。悪意あるパッケージflatmap-streamがnpmに公開され、後にユーザーright9ctrlによって広く使われているevent-streamパッケージの依存関係に追加されました。その後しばらくして、800万回ダウンロードされたこのパッケージにより、ウェブ上のあらゆる場所でアプリケーションが知らぬ間に本番環境で悪意あるコードを実行していました。事件が明るみに出た直後、先週のブログで速報として所感をお伝えしましたが、今回は事件の経過を時系列でたどりながら、より詳細な事後分析を行います。この問題を調査した多くの方々に感謝します。特に、悪意あるコードをリバースエンジニアリングしたGitHubユーザーのmaths22に感謝します。
event-streamパッケージとは?
event-streamパッケージは、ストリームの作成と管理に役立つユーティリティを提供するツールキットです。Dominic Tarr(npmjsでは~dominictarr)が作成したこのパッケージは、npmjsで彼が所有する422個のパッケージの1つです。event-streamパッケージには合計84回のリリースがあり、2011年のv0.5.2から始まり、2年前のバージョン3.3.4まで定期的にリリースされていました。
event-streamの開発期間を通じて、33人の異なるコントリビューターが貢献しました。ただし、貢献の大半は初期に行われ、それ以降は小規模な変更のみがレビューされています。

このプロジェクトは2,000を超えるスターを獲得し、139回フォークされ、62人のGitHubユーザーがプロジェクトの変更通知に登録していました。このプロジェクトは、スコープ付きパッケージを除き、他の3,931個のパッケージで使用されていました。
一連の出来事を時系列で見る
プロジェクトの歴史における主な節目と、今回の悪意ある攻撃に関する重要な出来事を時系列で示します。以下では、タイムライン上の各項目を詳しく見ていきます。

一連の経緯
悪意あるパッケージflatmap-streamが利用されるに至った経緯を見ていきます。ここで紹介する出来事は、公開されているGitHubの情報、Googleキャッシュ、npmを調査してまとめたものです。
2015年7月31日: GitHubユーザーのdevinusがevent-streamプロジェクトのissueにコメントし、flatmap機能が歓迎されるかどうかを質問しました。パッケージのメンテナーdominictarrは、ユーザーからの貢献を受け入れると前向きな返答をしています。

後に悪意あるユーザーと判明したright9ctrlが、この情報を利用してプロジェクトに対する巧妙なソーシャルエンジニアリング攻撃を計画・実行した可能性があると推測できます。
2018年8月5日: npmで「Antonio Macias」を名乗るユーザーが、flatmap-streamという悪意のないパッケージを作成し、公開しました。
次にAntonio Maciasは、event-streamプロジェクトにflatmapパッケージを使用するよう提案しました。GitHubユーザーright9ctrlはDominic Tarrに協力を申し出て、flatmap-stream依存関係を取り込んでflatmap機能を追加するために必要な変更を行うと伝えました。Dominicはright9ctrlの申し出を受け入れ、event-streamのGitHubプロジェクトへのコントリビューターとして追加しただけでなく、npmエコシステムでモジュールを公開する完全な権限をright9ctrlに付与しました。Dominicは後にインシデント報告の中で、問題に対処するために(つまり、感染したバージョン3.3.6をnpmから削除するために)、自分にはnpm上でモジュールを公開する権限がもうなかったと確認しています。
その後まもなく、right9ctrlはevent-streamのGitHubリポジトリに、一見問題のない一連のコミットをプッシュしました。
2018年9月4日:無害な依存関係のアップグレード。
2018年9月4日/5日:mapとsplitのサンプルを追加0cc、ee8、c08、05b
2018年9月5日:918でevent-streamバージョン3.3.5をリリース。
2018年9月9日:e31でflatmap-stream依存関係を追加。
2018年9月9日:599でevent-streamの新しいマイナーリリース3.3.6を作成。
2018年9月16日:908でevent-streamのコードからflatmap-streamを削除し、2bdで依存関係ツリーからも削除。メジャーバージョン4.0.0としてリリース
2018年9月20日:right9ctrlが60dでプロジェクトのキーワードを改善する、見た目上のコード変更をさらに追加。npmjs.comの公式レジストリで検索結果をさらに改善することが目的だったと推測されます。
2018年10月5日:新しいマイナーバージョンflatmap-stream@0.1.1が、圧縮されたソースコードにインジェクション攻撃を仕込んだ状態でリリースされました。event-streamをインストールすると、一時的な依存関係として感染したflatmapの新バージョン0.1.1も取得されるようになりました。
GitHubとnpmからプロフィールが削除されたright9ctrlユーザーが、その後event-streamプロジェクトで作業したことを示す証拠はありません。ただし、調査目的でGoogleキャッシュからアクセスできます。

2018年10月29日:jaydensericがnodemonに対するissueを作成し、予期しない非推奨警告を報告しました。このメッセージは、EVP_BytesToKeyではなく、より新しいアルゴリズムを使用するというOpenSSLの推奨に沿ったものです。開発者はcrypto.scrypt()を使って鍵とIVを独自に導出し、crypto.createDecipheriv()を使ってDecipherオブジェクトを作成することが推奨されます。

2018年11月19日:NewEraCrackerがevent-streamに対するissueを作成。

2018年11月19日:NewEraCrackerがnodemonに対するissueを作成。

2018年11月20日:FallingSnowがインジェクション攻撃ではないかと疑う。
2018年11月20日:FallingSnowがevent-streamに対するissueを作成。
2018年11月26日:HackerNewsに投稿が掲載され、issueがソーシャルメディアで話題になりました。
2018年11月26日:flatmap-streamパッケージがnpmから削除されました。
2018年11月26日:複数のユーザーが脆弱性報告フォームからSnykに問題を報告しました。
2018年11月26日:Snykは悪意あるパッケージを脆弱性データベースに登録し、監視対象のすべてのプロジェクトに通知しました。
2018年11月26日:SnykのDanny Granderが、Node.js Foundation Security WGに問題を報告しました。
2018年11月27日:Snykがこの問題についてブログ記事を公開しました。
標的:Copay
flatmap-streamのコードをさらに詳しく調べると、これは安全なビットコインウォレットプラットフォームCopayを狙った、標的を絞った攻撃だったことがわかります。
悪意あるflatmap-streamのコードは何百万回もダウンロードされ、その何倍もの回数実行されました。攻撃者には、ここで数え切れないほどの悪事を働く機会がありました。しかし、Copayアプリのビルド時に実行される機会を待つという戦略を選びました。攻撃は成功し、Copayのバージョン5.0.2から5.1.0に組み込まれました。
復号コードは、npm_package_descriptionという名前の環境変数から鍵を探します。この環境変数には、ルートパッケージのdescriptionがnpmによって設定されます。クライアントアプリケーションがビットコインウォレットのCopayである場合にのみ復号される仕組みで、Copayはペイロードの復号に「A Secure Bitcoin Wallet」という鍵を使用していました。この文字列は、さまざまなnpmパッケージのdescriptionを総当たりで試したmaths22によって発見されました。
これを突き止めるため、ユーザーのmaths22は、さまざまなnpmパッケージのdescriptionを列挙し、それらを鍵としてペイロードを復号しました。しかし、それだけではありません。2つ目のペイロードは、特定のビルドコマンドを実行したとき、つまりiOS、Android、デスクトップアプリケーションをビルドするときにのみ実行されるものでした。
3つ目にして最後のペイロードは、別の依存関係、すなわち./node_modules/@zxing/library/esm5/core/common/reedsolomon/ReedSolomonDecoder.jsに挿入されるJavaScriptコードです。これは、ビルド時に実行された最初の2つのペイロードとは異なり、アプリ自体の中で実行されました。
悪意あるコードは、ウォレット残高がビットコインで100 BTC、またはBHC(Bitcoin Cash)で1,000 BHCを超えている場合、ビットコインとウォレットの秘密鍵を窃取しました。Copayはユーザーに次のようにアドバイスしました。
影響を受けたウォレットの12語のバックアップフレーズ(侵害された可能性のある秘密鍵に対応するもの)をインポートして、新しいウォレットへ資金を移動しようとしないでください。まず、影響を受けたウォレット(5.0.2~5.1.0)を更新し、その後、影響を受けたウォレットからバージョン5.2.0の新しいウォレットへ、Send Max機能を使ってすべての資金の取引を開始してください。
さらに、ユーザーは秘密鍵が侵害された可能性があると「想定」し、保有資産を「直ちに」安全な新しいv5.2.0のウォレットに移すべきだと提案しました。
事件と攻撃の事後分析から、これは入念に計画され、周到に実行された攻撃であり、専門家によって実施され、準備に数か月を要した可能性が高いことがわかります。
まとめ
この記事で説明した一連の出来事は、オープンソースのモデルが、尊重されなければいかに脆弱になり得るかを改めて示しています。event-streamのような広く使われているパッケージを利用して価値を得る人々のうち、ほんの一部でも支援していれば、悪意ある第三者による乗っ取りは簡単に防げたはずです。event-streamパッケージはnpmエコシステム全体で依存関係として利用され、少なくとも3,931個のパッケージに含まれていました。特に、@vue/cli-ui、vscode、nodemon、ps-treeなどのトップレベルパッケージに影響しました。
nodemonパッケージでJayden Sericがissueを作成するきっかけとなった非推奨メッセージがなければ、この悪意あるパッケージは発見されないままだった可能性もあります。そうでなければ、長い間見つからなかった可能性が高いでしょう。
Snykは責任ある情報開示を強く支持し、セキュリティ調査を文化の一環として実践しています。責任ある形で開示したい脆弱性を発見した場合は、責任ある情報開示フォームからぜひご連絡ください。
キャプチャー・ザ・フラッグを始めよう
オンデマンドのバーチャル入門ワークショップを視聴して、キャプチャー・ザ・フラッグの課題の解き方を学びましょう。
