秘密鍵をWiresharkに登録してもTLS通信を解読できない?

WiresharkでTLS通信の中身を見るために秘密鍵を登録してみます。秘密鍵はサーバー側に登録されている鍵で、CentOSのApacheの場合はssl.confの「SSLCertificateKeyFile」に記載されている場所にあります。この秘密鍵をWireshark(ubuntu 18.04 LTSにインストール)に登録する手順は以下です。windowsの場合もだいたい同じだと思います。

Wiresharkを立ち上げてメニューバーの「編集」から「設定」を選択する。Wiresharkの設定画面が表示されるので左側の選択欄から「Protocols」→「SSL」と辿ると、画面の上部に RSA keys list があるので隣の「Edit」を押下する。

「Edit」を押下すると SSL Decrypt の画面が表示されるので、左下の「+」ボタンを押下する。IP address、Port、Protocol、Key Fileが入力できる状態になるので、各項目に入力を行う。

IP address・・・サーバーのIPアドレスを指定
Port・・・443と指定
Protocol・・・httpと指定
Key File・・・秘密鍵のファイルを指定
Password・・・パスワードを指定 ※秘密鍵にパスワードがある場合

入力ができたら、SSL Decrypt の画面の「OK」ボタンを押下、および、設定画面の「OK」ボタンを押下する。

[広告]

Wiresharkに秘密鍵が登録できたのでキャプチャをしてみます。キャプチャを開始するにあたり通信のはじめから取得する必要があるため、Wiresharkのキャプチャを開始してからWEBブラウザを立ち上げます。WEBブラウザを立ち上げたあとにWiresharkでキャプチャを開始しても通信のはじめからにはならないので、注意してください。ここで「はじめから」にこだわっているのはWEBブラウザを立ち上げるとサイトとの鍵の交換が始まってしまい最初に行われる鍵交換の部分のキャプチャを取り損なってしまうからです。

それではキャプチャしてみます。実際に取得したキャプチャは以下ですが、アプリケーションデータを見てみると、、、復号されてませんでした(悲)。以下の赤枠で囲った「Encrypted Application Data」の部分です。

ここで何かと調べた結果、どうやらクライアントとサーバーの鍵交換の方式がRSAでないとキャプチャしたデータからWiresharkが共通鍵を取りだせないらしい。RSAでは鍵の元となる情報をクライアント側からサーバー側に送り、相互で同じ共通鍵を生成するとのこと。Wiresharkは秘密鍵を使って、その鍵の元を盗み見して同じ方式で共通鍵を生成しようとする。

[広告]

実際に、どんな鍵交換方式を使っているのかキャプチャから確認してみました。TLSのネゴシエーションでは「Client Hello」と「Server Hello」を使って相互にやりとりする暗号スイートを決めています。暗号スイートとは、鍵交換、認証、共通鍵暗号、ハッシュ関数に何を使うかを決めるためにやり取りする情報です。今回の場合は以下でした。

Client Hello(クライアントからサーバーに提示しているクライアント側で使用可能な暗号スイートのリスト)

Server Hello(クライアントから受けっとた暗号スイートの中で、サーバーが対応していて、且つ、暗号強度が一番強いものを選んでクライアントに返す)

Sever Helloの内容を見てみると、TLSのネゴシエーションで暗号スイートを「TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256」に決めたようです。これは鍵交換方式が「ECDHE」、認証方式が「RSA」、共通鍵暗号方式が「鍵長128ビットのAESのGCMモード」、ハッシュ関数に「SHA-256」を使用するという意味です。つまり、鍵交換方式はECDHEを使っていてRSAではなかったです。そもそもRSAは秘密鍵が漏洩したらパケットをキャプチャして通信の中身を見られてしまうため強度の弱い方式として今はほとんど使われていないみたい。キャプチャしてみたけど、意味なかったです。。

なお、サーバー側がどんな暗号スイートに対応しているかは以下のサイトでチェックできます。ウェブサーバー名を入力して「Submit」ボタンを押下すれば暗号スイート以外にも対応しているSSL/TLSのバージョンなどを提示してくれます。サイトの安全性を総合的に判断してくれます。

https://www.ssllabs.com/ssltest/

今回使ってみたウェブサーバーの暗号スイートの診断結果は以下です。

以前は秘密鍵をWiresharkに登録すればTLS通信の中身を見れたのだろうけれども、今はもうこの方法はほとんど使えないと思います。このブログの別の記事で「ブラウザに共通鍵の情報をファイルに書き出させて、それをWiresharkに読み込ませる」ことでTLS通信の中身を覗いているので、そっちの方法でやってみてください。鍵交換方式が「RSA」以外でも解読できます。

追記:
RSAでは解読できるのにECDHEでは解読できない理由、および、SSLKEYLOGFILEを使えば解読できる理由を考察してみました。こちらまで。

DNSの再帰と反復、TCPフォールバックとEDNS0について。

DNSを使った名前解決の際にキャッシュDNSサーバーへの問い合わせと権威DNSサーバーへの問い合わせがありますが、それぞれ「再帰的問い合わせ」、「反復的問い合わせ」と言われています。どちらが再帰でどちらが反復だっけ?紛らわしいので整理しました。

そもそも再帰と反復の意味は国語辞典で調べると以下です。

「再帰」の意味:もう一度帰ってくること
「反復」の意味:同じことを何度も繰り返すこと

DNSサーバーの再帰的問い合わせと反復的問い合わせの仕組みを絵で書くと以下のようになります。

確かに権威DNSサーバーには何度も「反復」して問い合わせてます。キャッシュDNSサーバーには名前解決の要求をして応答が返って来ますが、これを「再帰」と呼んでいるのですね(再帰って当たり前のことのように思いますが)。

DNSの仕組みでもう一つ紛らわしいものがあって、TCPの53番ポートを使う場合とUDPの53番ポートを使う場合があります。TCPでもUDPでもどちらも53のポートです。なんの用途に53番ポートを使っているかというと名前解決とゾーン転送です。

TCPの53番ポート・・・ゾーン転送の際に使われる。
UDPの53番ポート・・・名前解決の際に使われる。

先ほど出てきた再帰的問い合わせと反復的問い合わせは名前解決なのでスタブリゾルバ、キャッシュDNSサーバー、権威DNSサーバーはみんなUDPの53番ポートを使ってやりとりしています。

ゾーン転送について説明します。通常の権威DNSサーバーはプライマリとセカンダリの2つのサーバーを用意します。冗長化ですね。このプライマリとセカンダリとでデータを同期する必要があり、その同期する作業をゾーン転送と呼んでいます。DNSのデータはゾーン情報と呼ばれているのでゾーン転送と言われるのでしょう。ゾーン転送の際にプライマリとセカンダリの権威DNSサーバーはTCPの53番ポートを使ってやりとりしています。

[広告]

基本的には上に書いたとおりです。ただ、、、名前解決の際はTCPの53番ポートが使われる場合があります。UDPって言ったじゃないか!と言われるかもしれませんが。。ここらへんが少々ややこしいのですが、DNSができたときからDNSの名前解決のメッセージは512バイトまでと決められていてDNSの応答メッセージのサイズが512バイトを超える場合は送信元にメッセージを512バイトに切り詰めたことを示す情報(TCビット)を送って、再度、送信元がTCPを使って問い合わせをし直します。これを「TCPフォールバック」と呼んでいます。

TCPフォールバックが起きることはDNSが出来た当時はほとんどありませんでした。その後、DNSの仕組みを悪用した犯罪(DNSキャッシュポイズニング)がでてきて、対抗策としてDNSSEC(Domain Name System Security Extensions)という仕組みが考え出されました。DNSキャッシュポイズニングとはキャッシュDNSサーバーに偽の情報を送りこみ、問い合わせてきたユーザーを偽のページに誘導することです。またDNSSECとは公開鍵暗号と電子署名の仕組みを利用してキャッシュDNSサーバーと権威DNSサーバーとの通信を安全にやりとりする技術です。

DNSSECによりDNSキャッシュポイズニングを防ぐことができるようになりましたが、DNSSECを使うことにより認証等の情報が追加されてDNSのメッセージサイズが512バイトを超えてしまうことが発生するようになりました。つまり、TCPフォールバックが発生します。名前解決にTCPを使うことはオーバーヘッドが高く応答に時間がかかるため、UDPの新たな仕組みとしてEDNS0(Extension Mechanisms for DNS version 0)が考え出されました。EDNS0ではメッセージサイズを512バイトから拡張しています(EDNS0の特徴はまだ他にもあるが割愛)。

なお、DNSSECおよびEDNS0が使われるのはキャッシュDNSサーバーと権威DNSサーバー間です。PCのスタブリゾルバとキャッシュDNSサーバー間はDNSSECを使わないのでDNSのメッセージが512バイトを超えることはあまりないと思いますが、超えた場合はTCPフォールバックが発生します。

ただ今後はIPv6が普及してきてDNSのメッセージサイズは大きくなるため、PCのスタブリゾルバとキャッシュDNSサーバー間もEDNS0が使われるようになるんじゃないかと思います。

[広告]

[2019/09/28 追記]
DNSキャッシュポイズニングの対抗策としてDNSSECを書きましたが、別の措置として「名前解決する際のポートをランダムに変更する」という手もあります。名前解決はポート53を使うという説明と食い違ってしまうので補足しておきます。

そもそもDNSはUDPを使用しているため通信の信頼性はDNSのプロトコルで行っています(TCPの場合は再送制御や順序制御をTCPが行ってくれているがUDPはそういうものがないためDNSの実装の中でデータが正しく届いているかを確認する必要がある)。

キャッシュDNSサーバーと権威DNSサーバー間の通信を考えてみます。キャッシュDNSサーバーのリクエスト(名前解決の要求)に対するレスポンス(権威DNSサーバーからの応答)の正当性を確認する際、レスポンスの中に含まれる以下の要素が合致すればその応答は正しいとキャッシュDNSサーバーは判断します。

  • リクエスト時に設定した「クエリーID」
  • キャッシュDNSサーバーの「IPアドレス」
  • キャッシュDNSサーバーの「ポート番号」

このうちIPアドレスは既知で、ポート番号もDNSのサーバーソフトから推測できます(もしくは、53番ポートのまま使っているサーバーもあります)。そうするとクエリーIDを一致させることができれば偽の応答をキャッシュDNSサーバーに送り込むことができます。このクエリーIDは16ビットの数値のため全部で65536通りしかありません。今のパソコンの処理能力であれば総当りで攻撃パケットを送り込むことが可能で、この攻撃パケットが権威DNSサーバーの応答より早くキャッシュDNSサーバーに届いてクエリーIDが一致すれば攻撃成功となります。

なので、この対策としてキャッシュDNSサーバーの送信する際のポートをランダムにするというのがDNSキャッシュポイズニングの対策となります。
※先に書いた「名前解決する際のポートをランダムに変更する」というのは以下の絵のことです。

ただこの方法は万全ではなく、ポート番号が増えた分の総当りさせるパターンを増やすことで攻撃成功の可能性を低くさせているだけです(とは言え、クエリーIDだけでは不十分なのでやっておいたほうが良い)。DNSSECのほうが安全です。

[2024/03/25 追記]
PCのスタブリゾルバとキャッシュDNSサーバーとの通信を保護する手段としてDoH(DNS over HTTPS)があり、これについても書いておきます。

通常の名前解決はUDPを使いますが、DoHではTCPの443番ポートを使います。TCPではPCとキャッシュDNSサーバーとの間でコネクションを確立するので攻撃者がパケットを偽装しにくくなります。またHTTPSを使用することで通信が暗号化されるので安全性が高まります。

DoHを使うにはキャッシュDNSサーバーがDoHに対応している必要があります。多くのパブリックDNS(Cloudflare社とAPNICが運営する1.1.1.1、Google社の運営する8.8.8.8など)は、DoHに対応しています。

PC側でも主要なWEBブラウザはDoHに対応しています。ただDoHを使うには設定画面でDoHを指定する必要があります。以下はFirefoxの例です。