Shared publicly  - 
 
The WebSocket Protocol (RFC 6455) の歴史

WebSocket についての日本語で書かれた情報は結構増えてきていますが、仕様がこのようになった経緯について細かく述べたものは見かけません。一見すると何のためにあるのかよくわからないヘッダや、複雑に見えるフレームフォーマットについて、皆さんが持っているかもしれない疑問に答えてみようと思います。実装に役立つことは書いていません。

* Connection: Upgrade と Upgrade: websocket

WebSocket の接続リクエストは HTTP と同じフォーマットをしていますが、これは web サーバが HTTP と同じポートで WebSocket コネクションを受け付けることができるように、HTTP Upgrade という仕組みを用いたためです。WebSocket クライアントは Connection ヘッダに Upgrade を含めることで、HTTP ヘッダ終了後に別のプロトコルを話しましょうと提案し、それが The WebSocket Protocol であることを Upgrade ヘッダで伝えます。

WebSocket を HTTP と同じポートで待ち受けられることには、ポートブロッキングの回避や、HTTP との Cookie 共有などの点で意義があります。

* GET

Upgrade リクエストにおいて HTTP のメソッドが何であるかは特に意味を持ちません。しかし、既存の HTTP インフラを通る際、メソッドの値によってプロキシなどの振る舞いに何かしらの違いが出ることが予想されます。何を用いれば問題が起きる可能性を減らせるか議論が行われた結果、GET が選ばれました。CONNECT、OPTIONS、POST などの方が良いという意見や、全く新しいメソッド (WEBSOCKET) にしたほうが良いという意見もありました。

* Sec- とは何か

ブラウザ上の悪意あるスクリプトが XMLHttpRequest を用いて WebSocket サーバにアクセスすることを防ぐために付けられています。ヘッダ名が Sec- プレフィックスを持つ場合、XMLHttpRequest の setRequestHeader はそのようなヘッダを設定することができません。

WebSocket の opening handshake では Sec-WebSocket-Key を必ず送信しなければいけないので、XMLHttpRequest では WebSocket のコネクションを開くことができなくなるわけです。

* Sec-WebSocket-Key と Sec-WebSocket-Accept

接続しようとしているサーバが WebSocket を理解するサーバであることを確認するためにやりとりされます。悪意あるスクリプトによって、WebSocket が例えば SMTP サーバなどに対するクロスプロトコル攻撃に使用されないようにするため導入されました。それ以上の意味はありません。

クライアントは WebSocket コネクションそれぞれに対してランダムな値を選び、Sec-WebSocket-Key ヘッダとして送信します。正しい Sec-WebSocket-Accept の値は Sec-WebSocket-Key によって変わます。計算には WebSocket 用に生成された GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"、そして SHA-1 と Base64 の処理が必要です。これらの要請によって、WebSocket を待ち受けていないサーバに WebSocket クライアントが接続してしまう可能性をほぼ無くしています。

* 複雑なフレーム長エンコーディング

異なるネットワーク、プラットフォーム、アプリケーションを扱う様々な人が議論に参加して各自の立場で要望を述べました。

比較的小さなメッセージのやりとりは当然想定され、少なくとも数バイトのデータに対して、長さヘッダは1バイト程度ですませるようにと強い要望がありました。一方、とても長いデータを表現できるようにするべきと主張する人もいました。例えば、数GB のデータを sendfile(2) などで高速に送信したいとき、長さヘッダの表現力が小さいと困るというものです。(後に masking が導入されたため、クライアントからサーバの向きではこれは不可能になりました。)

なら、continuation bit を用いた Hixie 76 の方法でいいではないか、という意見もありましたが、複雑さ (多少の比較と乗算ですが) から却下されました。

様々な stakeholder をまとめて納得させるためにたどり着いたのが、現在の 3 モードのエンコーディングです。データサイズが 125 バイトまでなら 1 バイトで済み、64KB を超えるようなデータに対してなら 9 バイトも十分小さい。そして、最大で 63 bit ぐらい表現できれば十分だろう、という理屈です。

* テキストは HyBi 00 (Hixie 76) の方法で良かったんじゃない?

HyBi 00 においてバイナリデータの取り扱いはほぼ future work 状態で放置されていましたが、さっさと利用可能にするべきという意見も出出し、バイナリデータ用フレーム構造についても真剣に議論が始まりました。その結果 2 種類のフレーム構造を持つのは良くない、簡素化しよう、フレーム長が予めわかったほうが処理もしやすい、といような流れになり、0xff で終端する方法はテキストメッセージでも使わないことが決まりました。

* Masking は何のため

上述の通り、少々複雑な opening handshake によってサーバに対するクロスプロトコル攻撃対策は済みましたが、今度は WebSocket と既存の HTTP proxy との間にセキュリティ問題があることがわかりました。

HTTP のメッセージに似せたデータを WebSocket 上に流し、中間にあるキャッシュ機能を持った HTTP proxy に、それを通常の HTTP メッセージだと誤認させ、記憶させることが可能かもしれないというのが指摘です。攻撃用スクリプトに HTTP リクエストに模したデータ列を送信させ、それに応じて攻撃者のコントロール下にあるサーバが HTTP レスポンスに模したデータ列を返します。キャッシュが偽装リクエスト内の host ヘッダと path をキーに、偽装レスポンスを記憶したら汚染成功です。

こうした攻撃を防ぐために導入されたのが masking です。スクリプトが送信するデータを決定 (send() 呼び出し) した後でランダムなキーを生成し、送信データと XOR を取ることで、スクリプトはお望みのデータ列を TCP 上に流せなくなるわけです。

* Sec-WebSocket-Key や masking は TLS/SSL の代わりではない

みなさんに誤解しないよう注意していただきたいことは、WebSocket のこれらのセキュリティ対策要素は TLS/SSL などに代わるものではないということです。Masking key も Sec-WebSocket-Key も TCP 上で丸見えです。Sec-WebSocket-Key の生成に用いられるのは公開されている情報と計算方法だけです。これらは、認証、秘匿、改ざん防止のいずれの機能も提供しません。あくまでクロスプロトコル攻撃とキャッシュ汚染を防ぐためのものです。

* Fragmentation

長さヘッダを用いてフレームの境界を伝える手法を採った結果、フレームの長さがわかるまでフレームの送信を開始できなくなりました。1 つのメッセージを 1 つのフレームに対応させた場合、メッセージ全体の長さが分かるまでバッファする必要があります。また、新たに導入された ping/pong などを定期的に送受信しようとしても、大きなメッセージがコネクションを占拠している間はできません。

こうした問題を解決するために fragmentation 機能が追加され、1 つのメッセージを複数のフレームに切り分けて表現できるようになりました。FIN ビットがメッセージの終端を伝え、最初の fragment 以外では opcode が continuation というタイプを用いるように定めることで、間に挿入されうるコントロールフレームと安心して区別出来るようになっています。

多重化機能の追加を予定していたことも導入理由の一つです。メッセージを小分けにすることで、1 つの仮想チャンネルが物理チャンネルを占拠しないようにバランスよく転送できるようになります。

* Sec-WebSocket-Version はなんで 13

RFC として標準化される前、IETF では未完成の仕様を Internet-Draft という名前で公開し、議論しながら改定していきます。WebSocket では互換性を失う変更が Internet-Draft に加わるたびに、Sec-WebSocket-Version ヘッダで送るべき番号をそのときの Internet-Draft のバージョン番号に上げることで複数のバージョンの安全な共存を可能にしていました。RFC の元になったのは Internet-Draft の version 17 ですが、互換性を失う変更が最期に起きたのが version 13 なため、RFC でも 13 がそのまま用いられています。
Translate
45
40
Takeshi Shimakage's profile photoYuji Akiyama's profile photo
Add a comment...