ぼちぼち日記

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

GnuTLSの脆弱性でTLS1.3の再接続を理解する(Challenge CVE-2020-13777)

(TLDR; めちゃくちゃ長くなったので、長文読むのが苦手な方は読まないようお願いします。)

1. はじめに

前回の「求む!TLS1.3の再接続を完全に理解した方(Challenge CVE-2020-13777)」 の記事にて、GnuTLSの脆弱性(CVE-2020-13777)のPoCを募集しました。 短い期間にも関わらず2名の方から応募を頂き、本当にありがとうございます。

また、応募しなかったけど課題に取り組んで頂いた方もいらしゃったようです。この課題を通じて、いろいろな方がTLS1.3仕様(RFC8446)に触れる機会を持っていただいたことを非常に嬉しく思います。

全くの初心者ではやはり課題が難しいとの意見もいただきました。今後はもう少し幅広い人に手をつけやすいよう工夫が必要であると感じていますが、はてさてどうしたらいいか、なかなか難しい。なんにせよ初めての試みでしたが、やってみてよかったと思っています。

2. 結果発表

応募を見させていただきました。結果、ペロトン(@prprhyt)さんが見事に解答されていると判断し、Vさんから(ブーストされた)3万円のアマゾン商品券と副賞として希望するラムダノート社の本1冊、私から1万円のアマゾン商品券を贈呈いたしました 。おめでとうございます!

応募内容を公開することを前提とした募集でしたので、この記事では応募解答についてコメントを付けながらTLS1.3の解説を進めます。

まずは課題の背景からです。

3. 課題の背景

今回のGnuTLSの脆弱性から課題を思いついたのは、前回のエントリーで述べた以下の理由です。

この脆弱性、実際にPoCを書いてみると非常に奥が深いです。TLS 1.3をちゃんと理解していないと無理です。

TLS 1.2 と TLS 1.3 でなぜ影響に違いがあるのか、そこはTLS 1.3の進化が見えるところです。またアナウンスには記述されていませんが、TLS 1.3の0-RTTを利用していた場合、この脆弱性によって大きな影響が新しく出てきます。

具体的にTLS1.2から1.3へどのような進化があったのかを見るには、その背景を理解しておくことが重要です。

そこで、今回の背景を理解するために必要な「Forward Secrecy(前方秘匿性)」と「TLS1.2におけるTicket方式の問題点」について説明します。

3.1. Forward Secrecy (前方秘匿性)

Forward Secrecy(前方秘匿性)は、TLSの機能を理解する上で非常に重要な概念です。ただ数年前私が初めてこの言葉に触れた時、何が「Forward」なのかすぐにわかりませんでした。

TLSを始めとした暗号通信は、通信している間だけセキュリティを確保できていればいいわけではありません。現在でもかなりのインターネット通信が傍受・保管されていると言われています。時間が経っても暗号通信した内容が外部に漏れないよう守られていることが必要です。

Forward Secrecyは、盗聴者が全部の通信データを保管しておき、数年後に暗号鍵が漏洩して通信データの内容がばれしてしまうリスクを防ぐ技術です。 「未来の安全」を確保するという意味で、時が進む「Forward」を指すと解釈して、やっと自分もこの言葉が腑に落ちました。日本語だと「通信後の秘匿性」という言葉にした方がわかりやすいんじゃないでしょうかね。

f:id:jovi0608:20200703085337p:plain

TLS1.2のRSA鍵交換は、クライアントで生成した暗号鍵(master secret)をサーバ証明書の公開鍵で暗号化し、サーバに送付します。この方法では、後日サーバ証明書秘密鍵が漏洩すると、TLSハンドシェイクデータから master secret を入手することが可能になってしまいます。Forward Secrecyが保てません。

そのため、現在ではTLS1.2でRSA鍵交換の利用は非推奨です。代わりにセッション確立時に一時的(Ephemeral)に暗号鍵を生成するECDHEの利用が推奨されています。

ECHDEでも楕円暗号曲線で利用する秘密鍵は生成されますが、TLSハンドシェイクが終了したらもう必要なくサーバで保管する必要はありません。なので後日漏洩して危殆化する可能性はありません。(実際はメモリ中に一時秘密鍵が残っていることもかもしれませんが)

3.2 TLS1.2におけるTicket方式の問題点

しかしTLS1.2も、ECDHEを使っていれば大丈夫、どんな時もForward Secrecyが保たれて安全というわけではありません。通常複数のサーバでTLSを運用する場合、再接続時にクライアントが別のサーバに振られてフルハンドシェイクしないよう、セッションIDやTLS Ticket方式が利用されることが多いです。

セッションID方式では、複数サーバ間でセッション情報を共有しておく仕組みが必要です。大規模になるとなかなか運用していくのが大変です。

その点 TLS Ticket方式は、セッション情報を暗号化してチケットにしてクライアントに渡しますので、大規模なセッション共有システムは必要ありません。再接続時に送られたチケットを復号化する暗号鍵だけをサーバ間で共有しておけばいいので楽です。

しかしTLS1.2での Ticket方式は、チケットを暗号化した鍵が漏洩すると Forward Secrecy がなくなるという致命的な問題を持っていました。

f:id:jovi0608:20200703085415p:plain

チケットの中には、再接続用に master secretなど入ったセッションデータがまるまる暗号化されて入っています。後日もしなんらかの理由でチケットを暗号した鍵が漏洩し攻撃者に渡るとしたら、master secret を使って昔のTLSの通信データの中身が丸わかりになってしまいます。

そのためTLS1.2でTicket方式を使う場合、チケットの暗号鍵をいかに安全に運用・管理していくのかが大きな課題となっています。

そのような課題を克服するため、TLS1.3では再接続の仕組みを大きく変更し、チケット利用でForward Secrecyがなくなる、といったTLS1.2におけるTicket方式の欠点の解消される仕組みを取り入れました。

こういった背景を知っていただいた上で、ペロトンさんの応募解答を見ていきましょう。

応募解答 と コメント

ペロトンさんの応募解答のオリジナルは、Challenge CVE-2020-13777(MITM, 0-RTT Application dataの復元)にあります。 ここでは一部抜粋しながらフォロー記事を書いていきますが、ぜひオリジナルも参照してください。また今回の課題のフォローブログも掲載予定と聞いております。楽しみにしましょう(判明後URLリンクを入れます)。すぐ公開されました。

atofaer.hatenablog.jp

フォロー記事を書くにあたり、オリジナルの応募原稿をどう損なわずにのがいいのか色々試行錯誤してしまい、このブログを公開するのに時間がかかってしまいました。結局、自分のコメントは赤字で (コメント) ... (コメント終) の中に書いています。

ペトロンさんの図は黒色実線、私が作成した図は赤色破線で囲って区別できるようにしています。少し見苦しいと思いますが、誰が書いた文章なのかを間違えないよう、注意してお読みください。

以下、ペトロンさんの応募解答+私のコメントになります。

Challenge CVE-2020-13777(MITM, 0-RTT Application dataの復元)

この解説文章・PoCの目的

(省略)

免責事項

(省略)

はじめに

(省略)

CVE-2020-13777について

(省略)

次のセクションでTLS1.3における影響について説明します。

1. TLS1.3でのMITM攻撃について

次の1つめの課題の解答をする章です。

  1. pcap中のTLS1.3 ClientHelloデータだけ使って、CVE-2020-13777によってTLS1.3のMITMが可能であることを証明してください。

(コメント)
この課題を考える時、「ClientHelloデータだけ」と書いていいかどうかすごく悩みました。実は不備があるからです。ペトロンさんから、今回の省略している部分(POCコード)の説明で、この条件の不備をちゃんと指摘されてました。

 このPoCの範囲外
 - CipherSuiteの推定
   - 簡単のため、攻撃者は1回目のCHLOのCipher Suitesのリストを見て先頭にあるTLS_AES_256_GCM_SHA384をサーバーが選択したと推定した仮定でハードコーディングしている

はい、CipherSuiteの推定が必要です。ClientHello だけでは、どのCipherSuiteを使った/使うのかわかりません。resumption PSKによる再接続では少なくとも前回の接続でのCipherSuiteのHASHがわかっていないと鍵の導出がで きないのです。本来ならServerHelloの情報も含めてと書くのが正確な課題の出し方です。

しかし、そもそもこんな「ClientHelloだけ」という不正確な条件をつけたのか。理由は、課題2を解いたことで課題1の証明するような応募を避けたかったことと、何をもって証明したとして見なせばいいのか、PSK認証の本質と私が考えていることについて述べてほしかったからです。後半の理由は後ほど詳細に書きます。
(コメント終)

PSKを使った認証

セッション再開におけるPSK(PreShared Key)はサーバーがクライアントを認証するための値です。 また、HandShakeやアプリケーションデータを暗号化/復号するための鍵のシードのようなものとしても利用されます。 PSKはexternal-PSKとresumption-PSKの二種類があり、PSKの共有方法によって分類されています。 external-PSKはTLSの帯域以外(つまり他の手段で)共有されるPSKです。 一方で、resumption-PSKは前回のセッションで用いたPSKです。セッション再開の認証に使うPSK候補が複数ある場合はサーバーからのNewSessionTicketの通知の際に次回のセッション再開と鍵の導出で使用するPSKと対応するPSK-IDが通知されます。クライアントはセッション再開時はPSK-IDに従って鍵の導出に使用するPSKを選択します。 今回はresumption-PSKを使った場合を例に説明します。以後は特に断りがない場合はPSKと書いてあるものはresumption-PSKと読み替えてください。

(コメント)
PSKについての説明です。内容は全く問題ありません。もう少し視野を上げてTLS1.3のハンドシェイクの種類で接続を分類すると下図のようになります。 f:id:jovi0608:20200703054834p:plain

TLS1.3には、大きくフルハンドシェイクとPSK接続の2種類のハンドシェイクが使われていると考えていいでしょう。正確には最初のハンドシェイクで鍵交換に失敗して2-RTTの接続してしまうハンドシェイク(Incorrect DHE Share)があります。しかし今回はわかりにくくなるので除きます。

フルハンドシェイクは、サーバ認証が必須です。通常はX.509証明書と秘密鍵を用いて認証を行います。クライアント認証も行えますがオプション扱いです(今回の図からは除いています)。

一方PSK接続の方は、応募解答に書いてある通り external PSK と resumption PSKの2つに別れます。external の方は「帯域外(out of bound)」での共有です。こういう意味での out of bound の和訳って困ってしまいますよね。私はそのままにしています。なお external PSKについては、既にプロトコル上の脆弱性が見つかっております。利用する場合には注意が必要であることをお忘れなく。

datatracker.ietf.org

resumption PSKの方は、応募解答に書いてあるとおり前回のハンドシェイクを基にPSKを共有する方式です。実はresumption PSKにも stateful と stateless の2種類あります。

statefull resumption PSKは、セッションIDをTicketに含め、セションデータはサーバ側で保持する方式です。クラスターを組んでいる場合は、複数サーバ間でセッション状態を共有しておく必要があります。TLS1.3ではセッションIDフィールドが廃止されましたが、こんなところに引っ越していたんです。

stateless resumption PSKは、セションデータを暗号化してTicketに含める方法です。今回のやり方になります。一旦クライアントにセッションデータを預けてしまうため、サーバクラスター間ではチケット内のセッションデータを復号するチケ ット鍵だけを共有しておけばいいわけです。これはTLS1.2の時代と変わりません。

少し前に知ったのですが、OpenSSL-1.1.1のTLSサーバでTLS1.3利用時にTicket無効化のオプションを設定しても、NewSessionTicketが送られてPSK接続になってしまいます。実はこの時、OpenSSLは stateful resumption PSK を使っていて、OpenSSLのメモリ内でセッシ ョンデータを保持して再接続を処理しています。本当にTicket利用を無効化したい場合は、送信するTicket数を明示的に0にするという設定をしないといけないです。

考えられるTLS1.3ハンドシェイクの形態を全部まとめると下表のように7種類になります。

f:id:jovi0608:20200703054829p:plain

ここでは細かく項目を説明しませんが、それぞれのハンドシェイク形態でやれること、セキュリティ状態が異なります。今回は3番目の statefull (* 1) stateless resumpition PSK が課題の対象となります。 (* 1 鹿野さん、間違いの指摘ありがとうございます。)
(コメント終)

セッションチケットとPSK

PSKはセッションチケットから導出可能です。これはセッションチケットの暗号化鍵が攻撃者に知られるとセッション再開の認証に必要な情報であるPSKを攻撃者が入手できることを意味します。 そして、これは攻撃者が正規のクライアントになりすますことができることを意味します。

順を追って説明します。CVE-2020-13777はサーバーが持つセッションチケットの暗号化鍵がall-0になる脆弱性です。 つまり、この脆弱性によって攻撃者はセッションチケットの暗号化鍵が0だと推測して、その内容を不正に復号することができます。 では、攻撃者がセッションチケットの内容を入手できると、どういった手順でPSKの導出をし認証のバイパスが可能になるのでしょうか。 それを明らかにするためにはまず、TLS1.3でセッションチケットの内容や認証の手順を理解する必要があります。 それでは正常時のセッションチケットを利用した認証を例に順を追って説明します。 次の図を見てください。 図中の登場人物は次のとおりです。

  • Alice: 正規のTLS1.3クライアント
  • Bob: 正規のTLS1.3サーバー

f:id:jovi0608:20200703054930p:plain f:id:jovi0608:20200703171211p:plain f:id:jovi0608:20200703054926p:plain

[図:セッション再開時のPSKによる認証]

図中の<>はセッションチケットの暗号化鍵で暗号化されたメッセージです。 図中の{}は[sender]handshake_traffic_secretで暗号化されたメッセージです。 図中の[]は[sender]application_traffic_secret_Nで暗号化されたメッセージです。

1つめの図はGnuTLSにおけるセッションチケットの書式を表しています。 そして、2つ目の図フルハンドシェイクの時の認証はフルハンドシェイクのシーケンスと認証部分の図です。図の通り、セッションチケットはサーバーからクライアントへNewSessionTicketによって通知されます。また、認証は証明書によって行われます。

(コメント)
惜しい、TLS1.3のフルハンドシェイク時の認証部分が違います。これを理解するため、サーバの秘密鍵を知らない攻撃者がTLS1.3でどこまで中間者攻撃ができるか思考実験をしてみましょう(下図)。

中間攻撃者は、平文でやり取りされるClientHello/ServerHelloを自由に書き換えることができます。それぞれの key_share拡張に入っている公開鍵情報を、中間攻撃者が作成した公開鍵に差し替えてやれば、攻撃者が生成する共有鍵をクライアント・サーバそれぞれに確立することが可能です。そのため、暗号化された EncryptedExtension や Certificate は中間攻撃者に筒抜けです。暗号化されているから認証されているわけではありません。

しかしCertificateVerifyまで来たら事情が異なります。中間攻撃者はサーバの秘密鍵を持っていないので、サーバ証明書の公開鍵に応じたハンドシェイク署名(CertificateVerify)を作成できないのです。勝手な秘密鍵でCertificateVerifyを偽造したとしても、クライアントは証明書の公開鍵を使って署名検証するため検証エラーになります。中間攻撃者はここでジエンドです。

f:id:jovi0608:20200703054824p:plain

中間攻撃者は暗号化された証明書を見れるのだから、中間攻撃者が証明書自体を改ざんしたらどうなるでしょうか? なりすまし用に作成した公開鍵に入れ替えてやれば、CertificateVerifyにあるハンドシェイク署名データの偽造できるかもしれません。

残念ながら、サーバ証明書がトラストアンカー(ルート証明書)を起点として電子署名がされているので、クライアント側で証明書の改ざんがチェックされエラーになります。認証局システムを破って不正なサーバ証明書を発行しない限り無理な話です。

f:id:jovi0608:20200703055009p:plain

なのでTLS1.3のフルハンドシェイク時の認証は、上図の範囲(Certificate, CertificateVerify, Finished)になります。
(コメント終)

そして3つ目の図セッション再開時のPSKによる認証はセッション再開時のシーケンスと認証部分の図です。クライアントはセッションを再開するタイミングでセッションチケットを送信します。そして、サーバーが受け取ったセッションチケットはセッシ ョンの再開の認証に使われます。 サーバーがセッションチケットの情報を受け取るとチケットの情報とサーバーが保持している情報を基にクライアントを認証し、認証が成功するとセッションを再開します。なお、PSKによるセッション再開時の認証が成功した場合、証明書による認証は行われません

より具体的な説明をします。まず、図GnuTLSのチケット書式の右側の"GnuTLSのチケットデータの中身の書式"を見てください。 サーバーは上から5つ目の項目のresumption_master_secretと7つ目の項目のnonceがあります。サーバーはこれらをHKDF-Expand-Label関数(※1)に入力してPSK(PreShared Key)を生成します。

psk=HKDF-Expand-Label(resumption_master_secret,
                        "resumption", nonce, Hash.length)

参考:RFC8446 4.6.1 https://tools.ietf.org/html/rfc8446#page-75 を基に作成

サーバーは上記の式でセッションチケットからpskを導出した後にbinder値(※2)を計算して、PSKを検証した後に自身が手元に持っているpskのリストと照らし合わせてpskがリストに存在するか、最初にこのpskが生成されてから規定以上の時間が経っていないかをチェックすることで認証を行います。

※1 HKDF-Expand-Labelは一方向性の性質を持つ鍵導出関数です。詳細については割愛しますが、出力から入力を導出できないことを覚えておいてください。 一方向性については後に登場するHKDF-Extractなど今回の説明で登場するHKDF-xxxという関数やderive_secretについても同様です。HKDFの仕様についてはRFC5869を参照してください https://tools.ietf.org/html/rfc5869

※2 簡単のため、binder値の計算については省略します。PoCに実装してありますので、興味のあるかたはそちらを参照してください。

(コメント)
「簡単のため、binder値の計算については省略します。」

くぅぅ、惜しい。自分としては、ここでbinder値を絶対に計算して欲しかったです。binder値こそがPSK認証の要の一つなんです。
まず、ここで実際にPSKの中身はどうなっているのか wireshark で見てみましょう。

f:id:jovi0608:20200703054951p:plain

四角で囲った部分がPSK拡張です。これはClientHelloの一番最後に配置されてないといけません(理由は後述)。 PSK拡張の中は、PSK Identity と PSK Binders で構成されています。PSK Identity は Identity と Obfuscated Ticket Age(難読化されたチケット経過時間) の2つが含まれています。Identityには、resumption PSKでは ticket そのものが入ります。

チケット経過時間がなぜ難読化されているのか? ClientHelloは平文であるため、チケットの経過時間が外部からわかるとその情報を使って色々いたずらされる可能性が出てきます。そのためクライアントは、チケットの経過時間にサーバから送られた難読化のための数値(ticket_age_add)を追加して、外から実際の経過時間がわからないように対策します。

PSK binders は、ClientHelloのハンドシェイクデータの先頭からPSK Binderの直前までのデータ(Truncated ClientHello)のハッシュ値を使います。このデータを resumption_master_secret から導き出した binder_key を使って Finished と同じような形でHMACした値です。

bindersの役割は2つあります。まずクライアントとサーバで同じ resumption_master_secret を持っていることを確認することです。もう一つは、Truncated ClientHelloが改ざんされていないことを保証することです。PSK拡張がClientHelloの一番最後に配置されないといけないのは、binderによるClientHelloの改ざん検知を最大限の範囲にするために必要な条件だからです。

新規接続のフルハンドシェイクからの流れを図示したのが下図です。

f:id:jovi0608:20200703054819p:plain

サーバは、PSKを持つClientHelloを受け取ったら、

1. まずPSK identityを復号し、ticket データからセッションデータや resumption_master_secret を取得します。
2. Obfuscated Ticket Ageの難読化を解き、チケットの経過時間を取得します。そして、NewSessionTicket中で規定したチケットの有効期限内かどうかチェックします。
3. クライアントと同じ方法で resumption_master_secret から binder_key を導出します。
4. サーバ側で Truncated ClientHello のデータと binder_secret からbinder値を計算します。
5. クライアントから送られてきた binder値とサーバが計算したbinder値が一致していれば、双方同じ resumption_master_secret を事前に共有していた相手であると確認したこととなり、認証が終わります。

このように binder によるMAC検証は、PSK認証を行う大きな要素の一つだということがわかるでしょう。
(コメント終)

MITM攻撃の手順を考える

この節では攻撃者がTLS1.3を使っている架空のシステムに対してCVE-2020-13777を使い、MITM攻撃(中間者攻撃)をする場合の手順を考えます。 前の節でPSKを用いたセッション再開時の認証のおおまかな流れについて説明しました。 MITM攻撃行うためには暗号化された通信の内容を復号して再度暗号化する必要があります。 つまり、攻撃者が攻撃を成功させるには鍵の導出や合意に必要な情報を持っている必要があります。 次の図を見てください。

f:id:jovi0608:20200703054934p:plain 図:TLS1.3のKey Scheduling

図の引用元: RFC8446 7.1 https://tools.ietf.org/html/rfc8446#page-93

この図はTLS1.3のKey Schedulingを表しています。 図の左上に着目してください。PSKと書いてあります。つまり、PSKを持っているとTLSのearly dataの暗号化/復号に必要なkey, ivを導出するための秘密情報(Secret)が手に入ります。

(コメント)
この HKDF をベースとした TLS1.3 の鍵スケジュールは、TLS1.2から一番進化したところです。この部分もいろいろコメントしたい部分がありますが、これ以上分量を増やすのもなんなんで、やめておきます。またの機会に。
(コメント終)

ハンドシェイクやアプリケーションデータを暗号化/復号するkey, ivの導出はどうすればよいでしょうか。 key, ivを導出する式を次に示します。

   [sender]_write_key = HKDF-Expand-Label(Secret, "key", "", key_length)
   [sender]_write_iv  = HKDF-Expand-Label(Secret, "iv", "", iv_length)

式:暗号化/復号 key, ivの導出

引用: RFC8446 7.3 https://tools.ietf.org/html/rfc8446#section-7.3

[sender]はデータを暗号化して送信する側を示していて、clientかserverが入ります。Secretには暗号化対象のデータ種別に対応するtraffic secretを入れます。 具体的なSecretの例を3つ紹介します。

  • 送信側がクライアント, 受信側がサーバーでearly data(0-RTT Application Data)の暗号化, 復号を行う場合
    • client_early_traffic_secret から導出したkey, ivを選択
  • 送信側がクライアント, 受信側がサーバーでハンドシェイクの暗号化, 復号を行う場合
    • client_handshake_traffic_secret から導出したkey, ivを選択
  • 送信側がサーバー, 受信側がクライアントでアプリケーションの暗号化, 復号を行う場合
    • server_application_traffic_secret_N から導出したkey, ivを選択

なお、KeySchedulingの図について、[sender]handshake_traffic_secret, [sender]application_traffic_secretの導出に着目すると、 これらの導出には(EC)DHEの共有鍵が必要なことがわかります。攻撃者は(EC)DHEへの中間者攻撃を行うことで(EC)DHEの共有鍵を手に入れることができます。

ここまでの流れをまとめると次の図のように攻撃できます。 図中の登場人物は次のとおりです。 なお、前提としてAliceはセッション再開以前にCVE-2020-13777の脆弱性を持っているBobをやりとりをしていて、PSKを用いてセッションの再開を試みているものとします。

  • Alice: 正規のTLS1.3クライアント
  • Bob: 正規のTLS1.3サーバー(ただし、CVE-2020-13777の脆弱性を持っている)
  • Mallory: MITM攻撃を行う攻撃者

f:id:jovi0608:20200703054919p:plain

RFC 8446 2.3節より、 PSKでの認証時はサーバーは証明書による認証を行ないません。 そのため、認証のバイパスに成功するとそれ以後、Malloryは[sender/receiver]handshake_traffic_secretや [sender/receiver]application_traffic_secret_N を用いて鍵やivを導出し、ハンドシェイクやApplication Dataの暗号化/復号ができるため、MITM攻撃が成立します。また、PSKの導出にはセッション再開時のCHLOのみを使っているので設問の要件「ClientHelloデータだけ使って」という要件を満たします。

(コメント)
MITM攻撃の手順は上記の通りで問題ありません。resumption_master_secretから導出したPSKを攻撃者が作成できれば、芋づる式にTLS1.3で利用する各種暗号鍵を生成できることとなり、PSK接続のハンドシェイクを自由に盗聴・ 改ざんし、成りすましもできるようになります。

ただ惜しむらくは、binderのチェックを省略せず入れて欲しかったぁ。

今回Ticketデータの書式を課題で提示しましたが、もし間違ったオフセットで resumption_master_secret を抽出していたり、(今回の課題の仮定の範囲外ですが)書式自体の間違いや更新などがあったら、MITMが成功しません。中間攻撃者がMITMを確実に成功させるには、ClientHelloを受信した時点で中間攻撃者自身がbinderの計算を行い一致するかどうかを確認するのが一番確実です。

中間攻撃者は、binder値の一致をもって、PSK Identityから解読したクライアント・サーバ間で事前共有されている resumption_master_secret が入手できたと初めて客観的に証明できるわけです。それが「ClientHelloデー タだけ」 という指定を入れた意図でしたが、やっぱり意図通りに課題を作るのは難しいと実感しました。これは出題者の力不足です。

ただ binder値のチェックはちゃんとPOCにも入っていますし、PSK生成からのMITMの手順も間違っていないのでこの解答を正解とみなしたいと思います。
(コメント終)

2. 0-RTT Application dataの復号

次の2つめの課題の解答をする章です。

  1. pcap中の暗号化されたTLS1.3 の 0-RTTアプリケーションデータをCVE-2020-13777によって復号し、アプリケーションデータの平文を取得してください。

PSKを導出するまでは1と同じです。説明とPoCを次に示します。なお、PoCの実装に際して

  • セッションチケットの復号時のMACのチェックの成功可否
  • 導出したPSKのbinderのチェックの可否
  • 0-RTT Application Dataの復号時のMACのチェックの成功可否

これらをコードの実装のヒントとして利用しました。 PoCの方針は次の図0-RTT Application dataの復元の通りです。 図中の登場人物は次のとおりです。 前提として、これまでと同様にAliceはセッション再開以前にCVE-2020-13777の脆弱性を持っているBobをやりとりをしていて、PSKを用いてセッションの再開を試みているものとします。

  • Alice: 正規のTLS1.3クライアント
  • Bob: 正規のTLS1.3クライアント(ただし、CVE-2020-13777の脆弱性を持っている)
  • Eve: Aliceのパケットを盗聴し、CHLOのApplication dataを不正に復元しようとする攻撃者

PSK導出後、Key SchedulingにしたがってPSKからclient_early_traffic_secureを導出します。 次にclient_early_traffic_secureからkeyとivを導出(※3)し、nonce=iv xor packet_number(=0, ApplicationDataはCHLOパケットに含まれているため)(※4)を算出します。

(コメント)
IVの生成もTLS1.2が大きく変わった点です。TLS1.2まではIVは明示的に生成してTLSレコードにつけていましたが、TLS1.3ではそれが必要なくなりました。これによってIVの再利用のリスクを無くすこともでき、IV分のデータ長も節約できることになりました。
(コメント終)

最後に復号をします。 暗号アルゴリズムは初回のセッションのCHLOのCipherSuitesのリストの最上位がCipher Suite: TLS_AES_256_GCM_SHA384 (0x1302)であることからAESのGCMモードで鍵長32byte, nonce12byte, MACの長さが16byteであると推定します。 AEADのassociateは(Early dataが入っているTLSレコードレイヤーのヘッダ+暗号文の長さ)であるのがGnuTLSや他のTLS実装のソースコードにより判明したためそれを利用します。

(コメント)
AEADで改ざん検知を行うAssociate Data部分にはTLSレコードレイヤが入りますが、TLS1.3ではこの部分は改ざんされても問題ないので特に入れ込む必要がないデータです。ただ形式証明を行う場合に入っていないと正確な分析ができないということで入れ込むことになりました。透過性の問題さえなければレコードレイヤー自体必要のない無駄なデータなんです。
(コメント終)

最後に末尾16byteをMACとして切り出し、残りを暗号文として復号関数に入力します。

f:id:jovi0608:20200703054913p:plain

ここまでの説明した方針に沿って実装したPoCを実行した結果を示します。 復元したApplication dataの平文をASCIIと16進数文字列で表示した結果は次の通りです。 また、PoCのコードはこの記事の末尾にあります。

$python3 main.py
b"Let's study TLS with Professional SSL/TLS!\n\n\x17"
4c6574277320737475647920544c5320776974682050726f66657373696f6e616c2053534c2f544c53210a0a17

図:PoCの実行結果

(コメント)
お見事! 正解は、「Let's study TLS with Professional SSL/TLS!\n\n」でした。最後の0x17は、Application の Content-Type (23) が入ったものなのでちょっと余分ですが、問題ないです。

このように、TLS1.3では 0-RTT で送信されるデータの Forward Secrecy は確保できません。Ticket鍵が漏洩すれば、後日保管しておいたpcapファイル中の暗号データから平文データを解読できます。

しかしハンドシェイク後に送受信されたアプリケーションデータのForward Secrecyは守れます。これは、PSK接続では PSKと(EC)DHEを組み合わせたハイブリットモードを用意しています。psk_key_exchange_mode拡張で psk_dhe_ke が指定されている場合にPSK/(EC)DHEのハイブリッドで鍵交換されます。

下図の通り、0-RTTのデータは(EC)DHEとハイブリッド鍵交換する前の鍵で暗号化されるので Forward Securityはないですが、ハンドシェイク後のアプリケーションデータは、PSKと(EC)DHEのハイブリッド鍵交換後に導出された暗号鍵で守られています。

f:id:jovi0608:20200703054813p:plain

(EC)DHEは再接続時に一時的に生成されるものなのですぐ消去することができます。攻撃者がresumption_master_secretを入手してPSKを導出しようと、再接続時の一時鍵(EC)DHEがわからなければアプリケーションデータの暗号鍵を導出することは不可能だからです。
(コメント終)

自身でPoCを実行して結果を確認するには下記のリンクからChallenge CVE-2020-13777のpcapファイルをダウンロードし、次に示すディレクトリの構成を参考にPoCの実行ファイル(ファイル名:main.py)をpcapファイルと同じ階層に配置してください。

(コメント)
以下PoCコードの説明が続きますが、省略します。続きは、オリジナルの応募解答か、ペロトンさんのブログを参照してください。
(コメント終)

(コメントまとめ)

いろいろ難癖つけてる感じに読めますが、短期間で見事な解答だと思います。ちょうど解答が「Let's study TLS with Professional SSL/TLS!」でペロトンさんもプロフェッショナルSSL/TLSをお持ちでないということでVさんからの副賞はプロフェッショナルSSL/TLSになりました。 十分本の内容を理解できる実力をお持ちだと思うので、しっかり読み込んでいただきたいと思います。
課題に取り組んでいただいた皆さん、ありがとうございました。
(コメントまとめ終)