ぼちぼち日記

おそらくプロトコルネタを書いていることが多いんじゃないかと思います。

TLSとSPDYの間でGoogle Chromeがハマった脆弱性(CVE-2014-3166の解説)

1. はじめに、

Googleがハマったシリーズ第3弾。今度は Chrome の脆弱性がテーマです。

先日(8月12日頃)突然Google ChromeでSPDYの機能が一旦停止されました。CloudFareの人が気づいてspdy-devへのMLの問い合わせし、すぐGoogleのaglさんからの返事で計画的で一時的なものであることがわかりました。
twitterやfacebookもSPDYが全て使えなくなり非常に驚いたのですが、直後に Chrome の Stable/Beta/Dev チャンネルがアップデートされ、ほどなくして問題なくSPDYが使えるようになりました。

この理由は公式には明らかにされていませんが、Chromeのリリースアナウンスにヒントがありました。そこには、

High CVE-2014-3166: Information disclosure in SPDY. Credit to Antoine Delignat-Lavaud.

という脆弱性の修正項目がリストアップされています。おそらくGoogleは、脆弱性対策をしたChromeのバージョンアップを安全に行うため、一時的にSPDYの利用を停止したのではないかと推測されます。

Creditの記載からこの脆弱性は、直前の8月6日に米国で開催されたBlackHat 2014でのAntoine Delignat-Lavaud氏のセッション「The BEAST Wins Again: Why TLS Keeps Failing to Protect HTTP」が元ネタです。

このセッションの発表資料や論文・動画は、The BEAST Wins Again: Why TLS Keeps Failing to Protect HTTPに掲載されています。ただBlackHat講演時はまだChromeが未修正だったこともあり、SPDYの脆弱性に関する詳細な記載や動画は公開されていませんでした(現在は動画のみ掲載)。

現在の Chrome Stableは緊急避難的な脆弱性の修正で、根本的な修正は作業中でした。修正パッチはビルド後のエラーで数回 revert されましたが、やっと昨日のCanary(39.0.2129)で無事脆弱性対応が完了したようです。
そこで今回、このChromeの脆弱性(CVE-2014-3166)がどういう原因で発生して、どう対策されたのかについて解説をしてみたいと思います。

tl;dr

この記事はTLS・PKI・SPDYについて基礎的な知識を持つ比較的上級者向けのものです。この辺の知識がない初心者の方は、読んでもわけわからず、退屈してしまうかもしれないので注意して下さい。また当初、この脆弱性の鍵となる Public Key Pinning機能について一緒に説明を盛り込むつもりでしたが、あまりに長文になってしまう恐れがあり、断念しました。近いうちに別記事を出しますのでお待ちください。

2. CVE-2014-3166の脆弱性はどういうものか?

CVE-2014-3166について調べてると、 CVEデータを見ても詳細がはっきりしないです。概要を和訳すると、

Google Chrome の Windows, OS X, Linux の 36.0.1985.143 以前と Android の 36.0.1985.135 以前バージョンでは、Public Key Pinning実装がSPDY接続の特性を正しく考慮していないため、遠隔の攻撃者が複数のドメイン名を利用することによって重要な情報を入手することができる。

と書いてあるだけです。発表資料や論文を読むとなんとなく想像できますが、脆弱性修正後の動画「Impersonation exploit against SPDY connection pooling」を見るとよくわかりました。もう少し詳しく書くと、

悪意がある攻撃者が、不正なSSL証明書を使い、クライアントのDNS名前解決を操作できると、Googleのサーバに成りすますことができる。

ということでした(実際にはもうちょっと条件なり操作が必要です)。

悪意のある第三者がGoogleのサービスに成りすますことができれば、クッキーなり重要な情報を抜けますし、フィッシングで誘い込むことが可能になります。

ここでこの脆弱性の鍵となる2つの技術項目、SPDYコネクション集約とPublic Key Pinningについて説明します。

3. SPDYコネクション集約機能

現在GoogleやFacebookなど大規模にSPDYをサービスに展開しているサイトは、SSLの証明書にワイルドカード証明書(*.google.comとか)を利用しています。SPDYではワイルドカード証明書を使うと、複数ドメイン宛てのHTTPリクエスト・レスポンスを1つのSPDY接続にまとめて集約し、多重化をより高めて通信の効率化を図ることができます。
ただし無条件にどんなドメイン宛てのリクエスト・レスポンスが一緒になるわけではありません。

  • クライアントからサーバへの接続アドレス(DNSで解決した時のIP)が同一である。
  • 接続先のホスト名がSSL証明書で認証できる。

といった条件が必要です。

例えばProxy接続しているクライアントは、ソケットの接続アドレスがDNSで解決したサーバのアドレスと異なるのでSPDYコネクションの集約されません。また同一のIPであれば、SSL証明書(X509書式)での Common Name や subjectAltName フィールドを見て、同一の証明書で認証できるかどうか調べます。Chromeはこのホスト名のチェックにRFC6125で規定されている方法を使っています。

実際にSPDYのコネクション集約がされているかどうかは、 chrome://net-internals/#spdy を見るとわかります。赤線で囲った部分を見ると複数ドメインが1つのSPDY接続に集約されているセッションです。

今回の脆弱性は、このSPDYのコネクション集約機能を利用してGoogleのサーバに成りすましを行うものでした。

4. Public Key Pinning機能

この機能の詳細は別記事で紹介する予定です。簡単に書くと、不正に発行された証明書かどうかブラウザがチェックできる機能です。
近年ブラウザに登録されている正式な認証局(CA)が外部から侵入を受け、不正なgoogle.comドメインなどの証明書が発行されてしまう事件が起きています。普通は正式な認証局から発行された不正なSSL証明書の見分けがつかないのですが、Public Key Pinning機能を使うと見破ることができます。

現在 IETF で仕様が検討され、IESGレビュー中でRFC化直前になっています。
今回の脆弱性は不正なSSL証明書とSPDYコネクション集約を使い、この Public Key Pinning のチェックのバイパスを狙うものでした。

5. CVE-2014-3166の脆弱性の原因と対策

やっと脆弱性の解説の本丸です。脆弱性の原因は、ChromeのSPDY実装でコネクションの集約を行う際に以下の2項目のチェックが抜けていたことです。

  1. 集約する既存のTLS接続がエラー(expireや証明書チェーンの検証に失敗等)になっていないか?
  2. 集約する際に対象のドメインの Public Key Pinning で登録されたものと既存のTLS接続の証明書が一致するか?

この抜けを利用すると、Googleサーバに成りすますには以下の手順になります。

  1. Googleドメインと攻撃者ドメインの両者で有効な証明書とSPDYサーバを用意する。
  2. キャッシュポイズニングなどによってクライアント側のDNS名前解決を操作して、攻撃者サーバ側にGoogleサービスの偽IPアドレスを向ける。
  3. クライアントから攻撃者ドメインのサーバ向けに最初のSPDYセッションを張らせる。
  4. 偽IPアドレスのGoogleサービスにアクセスするように誘導する。
  5. クライアントは不正なSSL証明書だとは気付かずに、攻撃者が成りすましたGoogleのサーバへのアクセスする。

という手順になります。

この手順を実際に試してみましょう。subjectAltNameで hoge.example.jp と *.iijplus.jp の二つのドメインを登録した証明書を作成します。ただしCAに侵入して不正発行するのはもっと難しいので、自己署名証明書を使います。

DNS情報を書き換える替わりに、hostsファイルで両ドメインを同じアドレスに向けます。 *.iijplus.jpドメインのPublic Key Pinningの設定をして、正式な証明書の公開鍵のハッシュ値をあらかじめブラウザに登録しておきます。このやり方は別記事で書きます。DNS情報を書き換えるのではなく、hostsファイルで同じアドレスに向けます。

脆弱性対策前のChrome Stable(36.0.1985.125)で接続してみます。

最初に hoge.example.jpのページに接続し、その後 demo-int.iijplus.jp に接続すると、SPDYセッションが集約されていることがわかります。自己署名証明書なので、SSLエラー通知の画面が出ているのですが、最初 hoge.example.jp を許可後 demo-int.iijplus.jp の接続には警告ページが出てきません。ブラウザのアドレスバーには警告マークが出てきます。

現状の Chrome Stable は、緊急避難的に一律にSPDYのコネクション集約を無効にした対策が施されていますが、昨日のChrome Canary(39.0.2129)より、SPDYコネクション集約時にSSLエラーとPublic Key Pinningチェックを行う処理が追加されました。脆弱性対策の有効性を確認するためCanaryで同様の試験をしてみます(ただしSPDY接続の分離を見るため Public Key Pinningの設定は外します)。SPDYセッションは次のようになりました。

不正なSSL証明書を使った hoge.example.jp と demo-int.iijplus.jp 宛ての接続が集約されておらず、別々のSPDY接続になっています。SSLエラーチェックが効いたことによるものです。その他の正式なGoogleサービスへの接続は問題なく集約され、Public Key Pinningの設定をしてみるとdemo-int.iijplus.jp への接続は遮断されてちゃんと証明書チェックが働いているのがわかりました。

この攻撃を本当にステルス的にするには、CAに侵入して不正なSSL証明書を入手し、クライアントネットワークにMITMの環境を仕込むというなかなか高いハードルが必要ですが、昨今のNSAによるネットワーク盗聴や改ざんの手法を聞くと絶対実現不可能とも言えないかと思います。

6. TLSとSPDYの間にある隙間

この脆弱性の発見者 Antoine Delignat-Lavaud 氏は、IETF の TLS WGのメーリングリストで、現状のTLSとSPDYの仕様の間で考慮されていない部分があるのが問題だと指摘しています(Re: Inter-protocol attacks)。

TLSを使ったSPDYの初期接続時は、クライアントはTLS上でサーバ証明書で接続サーバが正式なものか認証(Authentication)し、サーバはTLSの認証情報やSPDYの:hostヘッダなど使いどのページにアクセスできるのか認可(Authorization)が行われます。しかし、再接続時(resumption)は、TLSは session ID, ticket, channel ID 等の仕組みを使って再接続しますが、SPDYの再接続はTLS接続の再利用を行うのでTLSの再接続の仕組みが使えません。

初期接続時は TLSとSPDYの認証・認可の機能はある程度連携できているのに、再接続時は全く違うものになってしまっている。 Antoine Delignat-Lavaud 氏は、ここを明確にしてちゃんと仕様で定義しておく必要があるのではとTLS WGのMLに投げかけています。

今回の脆弱性は、まさにこのTLSとSPDYの間にハマった事例だと言えるでしょう。

現在LastCall終了目前の HTTP/2 の仕様ではこの部分について9.1.1 Connection Reuseの節で、subjectAltName やSNIの利用についての注意が記載されています。この記載で本当に十分かどうかは議論が分かれるところです。

彼の発表では、他にもTLSのハンドシェイクのSNI(Server Name Indicator)で指定したサーバ名とHTTP中のホストヘッダが異なる場合に例外処理がちゃんとされてないために脆弱性が存在していること(VirtualHost Confusion)を指摘しています。

彼の発表では、集約型のSSLホスティングやCDNでの exploit 動画を公開していますが、特に OpenProxyを突いたAkamaiの脆弱性にはびっくり、nsa.govのサイトを成りすましています。

他にも、分割されたTLSセグメントを切り捨てたことによる不完全なデータの受信を突くCookie Cutter脆弱性や、 2つのTLSセッションの master secret を同期させ、証明書を変更した renegotiation によって、不正データを入れ込む Triple Handshake などTLSネタが盛りだくさんです。

TLSセキュリティの最前線に興味のある方は、exploit 動画だけでも見ておいても損はないと思います。