Node.js
Security
npm
セキュリティ

2018/11/27に判明したnpmパッケージ乗っ取りについて

概要

event-streamというnpmパッケージに攻撃コードが混入されました。攻撃コードはflatmap-streamというパッケージに含まれており、event-stream パッケージはこの flatmap-stream への依存性を追加される形で間接的に攻撃コードの実行を行う状態になっていました。
攻撃コードが分析された結果、copayというBitcoinウォレットからクレデンシャルを盗むことを目的とされていたことが確認されています。

参考リンク

影響をうけたパッケージ

  • event-stream@3.3.6
  • flatmap-stream@0.1.1

flatmap-stream@0.1.1 パッケージが直接的に攻撃コードを含んだパッケージで、event-stream@3.3.6 パッケージが flatmap-stream@0.1.1package.json経由で依存することで、event-stream@3.3.6に直接的・間接的に依存するすべてのパッケージが影響を受けました。

現在、影響を受けたパッケージはnpmレジストリからunpublishされています。

プロジェクトが影響をうけているか否かは npm ls event-stream flatmap-stream を実行し、上記パッケージの該当バージョンへの依存性が存在するかどうかを確認することで判断できます。
また、直接的にnpmを利用したプロジェクトだけでなく、Visual Studio Codeが影響を受けたExtensionについて注意喚起をする等、npmを直接使用していなくても影響をうけるケースが存在するようです。

タイムライン

以下の日時はJSTのものです。

  • 2018/11/21 6:26am: event-streamリポジトリのIssuesに問題が報告される(該当Issue)。
  • 2018/11/22 3:35pm: event-streamリポジトリのメンテナが、npmレジストリ上のevent-streamパッケージのpublish権限を他ユーザへ委譲し、本人は既にpublish権限を持っていないことを記載する(該当コメント1, 該当コメント2)。
  • 2018/11/27 2:31am: npmサポートによってnpmレジストリから flatmap-stream パッケージがunpublishされたことが確認される(該当コメント)。
  • 2018/11/27 3:24am: 攻撃コードの実行内容が発見され、copay が攻撃対象であったことが確認される(該当コメント1, 該当コメント2)。
  • 2018/11/27 3:36am: copay リポジトリに依存性攻撃を受けている旨がレポートされる(該当Issue)。
  • 2018/11/27 4:03am: copay サービスに攻撃コードが含まれたバージョンが本番反映されていなかったことが確認される(該当コメント)。

攻撃コード混入の手法

copayリポジトリに攻撃コードが注入されるまでに、event-streamパッケージ・flatmap-streamパッケージと複数の依存性をたどっています。
攻撃者は下記のような流れでcopayパッケージへ攻撃コードを注入した可能性があります(厳密な時間軸の前後関係は不明です)。

  1. 攻撃コードを含むflatmap-streamパッケージが作成される。
  2. event-streamパッケージのpublish権限が悪意あるユーザに付与される。
  3. 攻撃コードを含むflatmap-stream@0.1.1 への依存を追加したevent-stream@3.3.6がpublishされる。
  4. copayがパッケージ更新によりevent-stream@3.3.6へ依存する(該当コミット)。
  5. 攻撃コードを削除したevent-stream@4.0.0がpublishされる(最後に攻撃コードを削除したバージョンを最新版としてpublishすることで、第三者が攻撃コードを発見しにくくする意図があったと考えられます)。

攻撃コードの内容

攻撃コードは複数のステップを経て実行されます。詳細はGithub Issueで解析が行われていますので割愛しますが、大まかな流れだけ以下に記載したいと思います。解析結果へのリンクを貼っているので詳しくはそちらを参照してください。

index.min.jsの実行

flatmap-streamにはindex.jsindex.min.jsが存在しますが、index.min.jsにのみ攻撃コードがminifyされた形で含まれています。このminifyされたコードに含まれる攻撃コードがこちらです。require("./test/data") という文字列をhexエンコーディングし、攻撃ペイロードを隠蔽しています。

test/data.jsの実行

これが実行されると、test/data.jsファイルが実行されますが、その内容はこちらです。
dataという変数として、攻撃ペイロードがAESで暗号化されています。このAES暗号化のキーが process.env.npm_package_description を参照しており、攻撃対象のnpmパッケージから実行されない限り攻撃コードが実行されず、またコードから挙動を解析する際にも攻撃対象のパッケージがわからないと攻撃ペイロードを復号できないようになっています。

copay-dashパッケージのPackage Descriptionで上記ペイロードが復号できることが発見され、その結果copay(copay-dashはcopayのfork)が攻撃対象であると推測されるに至りました。

攻撃ペイロードの実行

復号された攻撃ペイロードの内容はこちらに記載されています。
Bitcoinウォレットのクレデンシャルを読み出して、外部サーバへ送信するようです。
GitHub Issue上では送信先のサーバのスキャンなども行われていますが、ここでは割愛します。

参考情報