HTMLのChunkについて
前提
HTTP/1.1では使用されるが
HTTP/2以降のプロトコルでは廃止されているので注意
そもそもChunk転送(チャンク転送)とは何者?
Chunk転送は、HTTP/1.1で導入されたデータ転送方式のひとつ。
サーバーがレスポンスのコンテンツ長を事前に決定できない場合や、
ストリーミングデータのように順次データを送りたい場合に使われるもの。
ChatGPTの返答がリアルタイムに返ってくる動作だったり
動画ストリーミングに利用されている。
なぜにChunk転送の概念がでてきたのか
通常、HTTPレスポンスのヘッダにはContent-Length
が含まれる。
これにより、ブラウザやクライアントはレスポンスボディの終了位置を知ることができる。
しかし、以下のような場合にContent-Length
を指定するのが難しい。
- 動的に生成されるコンテンツ(ChatGPTとか)
- ストリーミング形式でのレスポンス(動画系)
このようなケースで使用されるのがチャンク転送エンコーディング。
Content-Length
でも実装できないか
疑問に感じた、そもそもChunkedが出てくる前はContent-Length
で何とかなってたはずで
ポーリング等で再現できそうだが、何が駄目なのかを調べてみた
Chunk転送が来る前のストリーミング
ストリーミングではなくプログレッシブダウンロードを行っていたContent-Length
は付与するけど、実際の再生は受信しながら再生になる
キャッシュとして端末に動画をダウンロードしながらダウンロードが終わった個所から再生していく形
プログレッシブではだめなの?
プログレッシブダウンロードはストリーミングと違い、
一時ファイルが保存される形になるので著作権面で注意する必要がある
また、動画として事前にファイルが用意できないライブ配信には不向きな点があった
そのため、ストリーミング配信が最近は主流になっている
Chunk転送が来る前の動的に生成されるコンテンツ(チャットなど)
- ポーリング
- ロングポーリング
- 定期的リロード
を活用して処理をトリガーすることで生成されるコンテンツを読み込んでいた
それぞれ簡単にまとめると
ポーリング:クライアント側で定期的に処理を実行し(10秒ごと等)新着があるかサーバーに問い合わせる
ロングポーリング:リクエストされた後に処理が更新されるまでレスポンスを返さない、クライアントはレスポンス
されたらすぐにリクエストをして待機
定期的リロード:ページをリロードしてコンテンツを再取得する
といった形で処理をトリガーしていた
ポーリング等ではだめなの?
いつ新着のコンテンツが発生するか分からないため
定期的 or 待機をする必要があり、無駄な処理が嵩む事になる
また、ロングポーリングに至っては接続が発生しっぱなしになるため、コネクションとしても効率が悪い
そのため、プロトコル的にも「イベント単位のプッシュ」に最適化されているチャンク転送の方が使い勝手がよい
Chunkedが出る前(HTTP/1.0とか)のKeep-Aliveは何だったのか?
HTTP/1.0や初期のHTTP/1.1では、一応「コネクションを閉じない」設定(Keep-Alive)だけはあった
ただし、レスポンス自体は最初にContent-Length
が必要で、ストリーミング的な更新は無理だった
チャンク転送の仕組み
チャンク転送では、以下のようにデータを**チャンク(塊)**に分けて送信している
サーバーがレスポンスを小分けにして送るようなイメージ
HTTP/1.1 200 OK
Transfer-Encoding: chunked
...
各チャンクの先頭にチャンクサイズ(16進数)を記載
チャンクサイズの後に改行(CRLF)
チャンクのデータ本体
チャンクの末尾にも改行(CRLF)
...
チャンク転送の終わりは、サイズ0
のチャンクを送信することで終わりを示す
SSE
チャンク転送を使用した技術にSSE
という通信方法がある
サーバー → クライアント(ブラウザ)にイベント形式のデータをストリーミングできる仕組みtext/event-stream
というMIMEタイプのレスポンスで実現する
JavaScriptのEventSource
で簡単に受信できるようになっている
例えば
const source = new EventSource('/sample');
source.onmessage = (event) => console.log(event.data);
上記のコードを書くことで、/sample
から送られてくるレスポンスを
イベントとして発火できるようになる
WebSocket
似たような仕組みに、WebSocketがある
ただ、似たような事を行っているように見えるが結構別物なので注意
こちらはWebSocketプロトコルで動作するのでHTTPではない
HTTPの仕組みと違い、双方向で通信ができることが特徴
チャット・ゲーム・株価などの双方向、対話的な通信に特化している
HTTPリクエストスマグリング
Chunkedを知るうえで有名な攻撃手法としてHTTPリクエストスマグリング
と呼ばれる攻撃手法ががあったので調べてみた
そもそもHTTPリクエストスマグリングとは何か?
サーバーやプロキシ(リバースプロキシ、ロードバランサー)間で、
HTTPリクエストの解釈に差があることを突く攻撃のこと
たとえば、同じリクエストをサーバーは「チャンク転送」と解釈するが、
プロキシはContent-Length
優先で解釈するなどの違いを利用する攻撃
POST / HTTP/1.1
Host: sample.com
Content-Length: 10
Transfer-Encoding: chunked
0
GET /sample HTTP/1.1
Host: sample.com
上記の場合
プロキシはContent-Length: 10
を信じてリクエストを終了する
しかしバックエンドサーバーはチャンク転送として扱い
その後のGET /admin
を別のリクエストとして解釈してしまう
違いを利用することで
- バックエンドに不正リクエストを送り込める
- セッション乗っ取り・キャッシュポイズニングなどを誘発する
を狙う攻撃手法の一つ
どうすればよいのか、、、?
対策として
- リクエストの曖昧なヘッダ(Content-LengthとTransfer-Encodingの併用など)を許可しない
- Webサーバーやリバースプロキシの設定を最新化し、既知の脆弱性に対応する
- 脆弱性スキャナ(Burp Suite など)でHTTPリクエストスマグリングを検査する
等がある
まとめ
Chunk転送(チャンク転送)はHTTP/1.1から使用できるTransfer-Encoding: chunked
のヘッダーを付ける事で使用できる
動的コンテンツやストリーミングレスポンスに使うもの
HTTP/2ではチャンク転送は廃止されている(ストリームとフレームという概念が存在)