CCS Injection脆弱性(CVE-2014-0224)発見の経緯についての紹介

菊池です。CCS Injection脆弱性(CVE-2014-0224)発見の経緯について紹介します。

バグの簡単な解説

OpenSSLがハンドシェーク中に不適切な状態でChangeCipherSpecを受理してしまうのが今回のバグです。 このバグはOpenSSLの最初のリリースから存在していました。

Message flow for a full handshake

通常のハンドシェークでは、右の図のような順序でメッセージを交換します(RFC5246 The Transport Layer Security (TLS) Protocol Version 1.2 §7.3より作成)。

ChangeCipherSpecは必ずこの位置で行うことになっています。OpenSSLもChangeCipherSpecをこのタイミングで送信しますが、受信は他のタイミングでも行うようになっていました。これを悪用することで、攻撃者が通信を解読・改ざん可能です。

発見の困難さ

このバグが16年の間発見されなかった原因は、TLS/SSLを実装したことのある経験者が十分にレビューできてなかったことにあります。 もし、TLS/SSLを実装したことがあれば、自分の実装で検査している項目をOpenSSLでも同様に検査しているか確認すればすぐに見つかるはずです。

他にもファジング等の手法も有効のように見えますが、以下の今までの歴史を見るにやはりTLS/SSLの実装の知識が必要だったことが分かります。

CVE-2004-0079

最初の発見のチャンスは2004年にCodenomiconが発見したCVE-2004-0079です。このときに以下の検査が追加されました。

Fix null-pointer assignment in do_change_cipher_spec() revealed

これにより、new_cipherが設定されているときだけChangeCipherSpecを受理するようなりました。しかし、残念なことに、new_cipherはServerHelloの段階で設定されるので、正しい実装になりませんでした。

CVE-2009-1386

次の発見のチャンスは2009年に発見されたCVE-2009-1386です。

DTLS: SegFault if ChangeCipherSpec is received before ClientHello

これはDTLSで、いきなりChangeCipherSpecを受け取った場合に落ちる問題が直りましたが、やはり正しい実装になりませんでした。

DTLS fragment retransmission bug

OpenSSLのバグ番号1950でもChangeCipherSpecの挙動が修正されました。

DTLS fragment retransmission bug

これによりDTLSにおいて、運悪くパケットの順番が入れ替わってChangeCipherSpecを受け取った場合の検査が追加され、DTLSでは正しい実装になりました。 チケットにあるように、未計算のマスターシークレットが暗号に使われ、これがランダムなメモリを読み出して、ハンドシェークが失敗すると分析されています。 このとき使われるランダムだと思われていたメモリは、実は空のバイト列でした。これに気付いていれば、攻撃の可能性が分かったはずです。

正しい実装の容易さ・困難さ

ChangeCipherSpecを正しく実装するのは実は容易なことです。上に挙げたフローの順番通りのメッセージだけを送信し受信すればよいだけです。ただし、少しだけ落とし穴があって、ChangeCipherSpecは他のハンドシェークのメッセージとは異なるレコードを使います。RFCにはその理由が以下のように書いてあります。

  Note:          To help avoid pipeline stalls, ChangeCipherSpec is
                 an independent SSL Protocol content type, and is not
                 actually an SSL handshake message.
draft-ietf-tls-ssl-version3-00 §5.5より引用

個人的にはこの一文が今回の脆弱性の最大の原因ではないかと思っているのですが、これによると、ChangeCipherSpecが独立したレコードになっているのはパイプラインストールを防ぐためだそうです。しかし、実装してみれば分かりますが、ChangeCipherSpecの処理はTLS/SSLのハンドシェークの中で最も複雑な同期が必要な場所です。まず、ChangeCipherSpecを受け取る前に、ハンドシェークが正しい段階まで進んでいることを待つ必要があります。さらにハンドシェークはFinishedを受け取る前にChangeCipherSpecを受け取っていることを確認しなければなりません。

より正確に書くと、ChangeCipherSpecを受理するときには

  • ハンドシェークが正しい段階まで進んでいること(Finishedを受信する直前の状態)
  • ハンドシェークのフラグメントが全く残っていないこと
  • 直後のメッセージがFinishedであること

を確認しなければいけません。さらに細かく注意すると、Alert attack にあるように

  • アラートのフラグメントが全く残っていないこと(そもそもアラートのフラグメントは弾いてもよい)
  • 同様にHeartbeatのフラグメントも残っていないこと

も確認すべきです。

RFCは次のように修正すべきです。

  • ChangeCipherSpecが別のレコードになっているのは、他のハンドシェークのフラグメントと混ざったレコードで送られないようにするため。
  • パイプラインストールを防ぐために、ChangeCipherSpecはサーバとクライアントの双方が送信する。

バグを発見するまでの過程

Heartbleedが公開されたときに、どのようにして次のバグを防ぐかが話題になっていました。ユニットテストを使うとか、各種アナライザーツールで検査するとか、フォークして綺麗に書き直してみるとか、APIも悪いとか、mallocを自作してはいけないとか、C言語はよくないから他の言語を使うべきとかいろいろありました。

他の言語の候補としてはATSが人気のようでしたが、自分の使い慣れているCoqでTLS/SSLを実装するとどうなるか考えていました。 プロトコルの安全性の証明などは大変ですし、実装の安全性にはたいして寄与しないので、見ただけで正しい実装をしているとすぐに確認できる程度のものを作ることを考えていました。

パーザとプリンタが正しく対になってることと、プリンタが自明に正しく実装されていることと、状態機械の動作が述語で表現できていることぐらいを目標にしていました。状態機械の一番複雑な部分がChangeCipherSpecにおける遷移なのは明らかだったので、そこだけ最初に考えてみて上に書いた条件を決定しました(一番複雑といっても、全く難しくはないです)。

次に、既存の実装が正しくこの条件をチェックしているかの確認を始めました。OpenSSL以外の実装はそれなりに検査をしていましたが、OpenSSLの実装はほとんど検査をしていないように見えました。その後、実際に攻略可能であることを確認しました。

バグを報告してから公開されるまでのタイムライン

  • 2014年4月22日 報告者がJPCERT/CCにバグを共有
  • 2014年5月01日 JPCERT/CCがOpenSSL Securityに初めて接触
  • 2014年5月02日 JPCERT/CCが詳細な報告とバグ検証コードをOpenSSL Securityに共有
    (この時点ではバグの詳細が完全に把握されていなかったため、本バグが汎用的な中間者攻撃に利用可能とは認識されていなかった)
  • 2014年5月09日 CERT/CCがOpenSSL Securityに初めて接触。新たなバグ報告と検証コードを送信し、完全な中間者攻撃が可能であることを示した
  • 2014年5月09日 OpenSSLはバグを検証し、CVE-2014-0224を割り当てた
  • 2014年5月12日 JPCERT/CCが、新たな検証コードをOpenSSLに共有
  • 2014年5月13日 OpenSSLが報告者に直接連絡をとり、最新のパッチと技術詳細を共有した
  • 2014年5月21日 JPCERTは、規定に基づいて製品にOpenSSLを組み込んでいるベンダに通知を行った旨をOpenSSLに連絡
  • 2014年5月21日 CERT/CCが、関連ベンダにあらかじめ通達する許可を要求
  • 2014年5月21日 OpenSSLは有力インフラ提供者2社とバグ修正をテストし、修正が機能することを確認
  • 2014年6月02日 CERT/CCはセキュリティ更新に関する配布リストを通知(詳細は公開せず)
  • 2014年6月02日 OS配布ベンダに注意喚起情報が通知され、パッチと勧告ドラフト版の要求を許可される。(0710)
    • この通知がなされたベンダは以下の通りです
    • Red Hat (0710), Debian (0750), FreeBSD(0850), AltLinux (1050), Gentoo (1150),Canonical (1150), IBM (1700), Oracle (1700),SUSE (2014-06-03:0820),Amazon AMI (2014-06-03:1330), NetBSD/pkgsrc (2014-06-04:0710),Openwall (2014-06-04:0710)
  • 2014年6月02日 Red Hatがパッチに問題を発見(1400)。ベンダに提供されたパッチを修正
  • 2014年6月02日 Canonicalがパッチに問題を発見(1700)。Stephenがパッチを更新し、ベンダに送信(1820)
  • 2014年6月03日 ops-trust(1015)とOpenSSL Foundation contractsの一部(0820)に対し、セキュリティアップデートが2014年6月5日に公開されることが通知された(詳細なし)
  • 2014年6月05日 セキュリティアップデートとセキュリティ勧告が公開された

以降は株式会社レピダムのWeb更新履歴

  • 2014年6月05日 セキュリティ勧告がされた直後にCCS Injection脆弱性(CVE-2014-0224)の概要・対策・発見経緯について掲載
  • 2014年6月06日 具体的な危険の例を追加しました。クライアントが安全な場合の認証ハイジャック可能性が否定される
  • 2014年6月09日 クライアント証明書を使用した場合について表現を修正

バグを報告してから公開されるまでのタイムラインはMark J Coxのブログより本人同意のもと引用