Hello there, ('ω')ノ
背景の直感(なぜ動くのか、噛み砕いた説明)
- HTTP/1.1 では「リクエストの終わり」を決める方法が主に Content-Length(ボディのバイト長を明示)と Transfer-Encoding: chunked(チャンクで区切る)の 2 種類あります。
- フロント(ロードバランサ等) と バック(アプリサーバ等) がどちらを「正しい終端」と見るかが違うと、同じ生データ列を別々に切り分けて読み取ってしまいます。
- CL.TE の場合:フロントは Content-Length を信じて「このリクエストはここで終わり」と判断し、バックへそのまま送る。しかしバックは Transfer-Encoding を見てチャンク処理を行い、「0」(ゼロ長チャンク)はリクエスト終了を表すため、その直後のバイト列が 次のリクエストの先頭として扱われる――これが「smuggle」の基本原理です。
- 結果:フロントから見れば 1 リクエストでも、バックでは「リクエスト A の終了 → 続きとして扱われたリクエスト B」が入る。今回の狙いはリクエスト B を
GET /404にしてしまい、それがバックで処理されるようにすることです。
準備(Burp Repeater の設定)と理由
- Burp Repeater を使う — なぜか:Reapter はリクエストを手で編集・再送するためのツールで、精密にヘッダや本文を操作できます。
- HTTP/1.1 に手動で切り替える — なぜか:ブラウザや Burp は相手が HTTP/2 をサポートすると自動的に HTTP/2 を使うことが多いですが、チャンクの挙動を突く手法は HTTP/1 系のメッセージフレーミング に依存します。Inspector → Request attributes で Protocol: HTTP/1.1 に切り替えてください。
- ラボのホスト名(YOUR-LAB-ID.web-security-academy.net)を使う — なぜか:演習対象のホストに対してのみ行うため。
実行ステップ(行動とその理由:一つずつ)
ステップ 1 — 下のリクエストを Burp Repeater にコピーする
(そのまま貼って使ってOK。YOUR-LAB-ID を自分のラボ ID に置き換えてください。)
POST / HTTP/1.1 Host: YOUR-LAB-ID.web-security-academy.net Content-Type: application/x-www-form-urlencoded Content-Length: 35 Transfer-Encoding: chunked 0 GET /404 HTTP/1.1 X-Ignore: X
なぜこの形か(行ごとの意味)
POST / HTTP/1.1:最初のリクエスト行。フロントはこれを通常の POST として扱います。Content-Length: 35:フロントが信じる長さ。フロントはこれを見て「ボディは 35 バイト」と判断します(ここでフロントは chunked を無視する実装設定とラボは仮定)。Transfer-Encoding: chunked:バックエンドが見るヘッダ。バックはこっちを優先してチャンク処理します。- 空行の後、
0:チャンクの終端を示すゼロ長チャンク(chunked の仕様では0単独は「このリクエストの終わり」を意味します)。ここでバックはリクエストを終え、以降のバイト列は 次のリクエストの先頭 として見ることになります。 - その直後に
GET /404 HTTP/1.1が来ているので、バックはそれを**別のリクエスト(内部的に来た GET)**として処理できます。 X-Ignore: X:余分なヘッダ(ここでは目印)――送信したデータがどのように解釈されたか確認する手がかりになることがあるため入れてあります(任意)。
要点:フロントは Content-Length をみて「ボディ 35 バイト分」を読んで終了と判断 → バックは Transfer-Encoding をみて「0」で終了 → その後の
GET /404を次のリクエストとして扱う。この「ずれ」を利用。
ステップ 2 — 同じリクエストを 2 回連続で送る(理由つき)
この続きはcodocで購入