SSL リクエストは HTTP スニッファでいつも見えるとは限らない

 Charles Web ProxyやFiddlerなどのHTTPスニッファはHTTPリクエストはもちろんHTTPSリクエストも中身は見えないながらもリクエスト自身は表示する、と思っていた。そうではないといことが今日分かった。


HTTPSでは、クライアント(ブラウザ)とサーバーがハンドシェイク(暗号鍵の交換)をしたあとは暗号で通信が行われる。暗号鍵の交換はリクエスト毎に行われるわけではなく、複数のリクエストに渡って同じ暗号鍵が使われる。Charles やFiddlerなどのプロキシ方式のHTTPスニッファは暗号化された中身を見ることができないので(なりすましもできるが、ここでは話の簡単のため触れない)、外側だけを見てHTTPSリクエストを判断しないといけない。外側、というのは暗号化されたSSLデータを運ぶ層、すなわちTCPということになる。


ここで問題なのは、一つのHTTPもしくはHTTPSリクエストは通常複数のTCPパケットに分割されるということである。一つのHTTP(S)リクエストがいくつのTCPパケットに分割されるかは、リクエストのサイズに依存していてリクエスト毎に違う。

一つのリクエストにいくつのTCPパケットを必要するかという情報は、リクエストのサイズという形でHTTP(S)リクエストのヘッダに格納してある。

普通のHTTPリクエストであれば、HTTPスニッファはデータの中身を読むことができるので、HTTPヘッダからリクエストサイズを取り出して、リクエストの終わりを判断することができる。

 ところが、この技は内容が暗号化されているHTTPSでは使えない。例えば、連続する10個のTCPパケットが流れてきた場合、これが一つのHTTPSリクエストなのか二つのリクエストなのか、仮に二つとしても10個のパケットが二つのリクエストにどう割り振られているかというようなことは、パケットの中身が暗号化されているためHTTPスニッファには判断する術がない。HTTPスニッファにできるのは、せいぜいSSLのハンドシェイクを見張って(これは平文で行われる)それが一つのHTTPSリクエストの始まりだと判断するぐらいである。


Charles ProxyやFiddlerが実際にやっているのもこの程度のことらしく、HTTPSをこれらのスニッファで監視すると、あるSSLサーバーに対する最初のリクエストは表示するものの、そのリクエストは、実際には完了しているにも関わらず、決して完了とは表示されない。また、同じサーバーへの二度目のHTTPSリクエストは、スニッファには最初のリクエストの続きと認識されてしまうので、スニッファに表示されることはない。このようにして、実際には完了しているHTTPSリクエストが、スニッファには表示されないということが起こる。


HTTPSリクエストが起こっているかどうかを確実に知るためには、FireFoxのLive HTTP headersのようなブラウザープラグインタイプのスニッファを使う必要がある。このタイプのスニッファはブラウザが暗号を解読した後のデータを読むために、SSLにまつわる以上のような問題が起こることはない。


ここまで書いてきて思い出したが、あるパケットが「最後」のパケットとだと知らせるための方法がTCPにはちゃんと用意してある。HTTPSがリクエストの終わりにこの「最終パケット」を使えば、スニッファにもリクエストの終わりが判別できたのだが、実際にはそういう風にはなっていない。

クライアントとサーバーが最初に交換した暗号鍵を使い続ける限り両者間の通信は終わりでない、というのがリクエスト毎に最終パケットを使わない理由のようにも思えるが、確かなことは分からない。