「HTTP(S) Proxyを設定する」とはどういうことか、パケットレベルで解説
こんにちは、SSTでWeb脆弱性診断用のツール(スキャンツール)開発をしている坂本(Twitter, GitHub)です。
社内で診断ツールやプロキシを開発していることもあり、通信関連のトラブル相談を受けることがあります。
その中で「そもそも HTTP(S) Proxyを設定するとはどういうことか?」を解説する機会が何度かありましたので、これを機にエンジニアブログで紹介したいと思います。
当ブログの読者としてはWebエンジニアや情報セキュリティ関係者が多いと思います。
そうした方であれば「Proxyを設定する」経験がある方もいらっしゃるでしょう。
では「Proxyを設定する」とはどういうことか、ご存知でしょうか?
本記事ではその疑問について、Wiresharkを用いたパケットレベルの観察を元に解説します。
パケット観察1: HTTP Proxy ON/OFF
curlコマンドでHTTPリクエストを行い、Proxy ON/OFF とでパケットレベルでの違いを観察します。
Wiresharkでパケットキャプチャし、対象のTCPストリームを追跡した結果のスクリーンショットを紹介していきます。
HTTP Proxy OFF
まずは HTTP Proxy 未設定で curl コマンドからHTTPリクエストをしてみます。
$ curl http://(管理下のWebサイト)/
このときのHTTP通信をWiresharkでキャプチャした結果:
教科書どおりのHTTPリクエストとなっています。
http://(ホスト名)/(URLパス)
が、実際のHTTPメッセージとして以下のように送信されています。
GET /(URLパス) HTTP/1.1
Host: (ホスト名)
...
HTTPリクエストの先頭行をリクエストラインとも呼びます。
リクエストライン中のHTTPメソッドに続くリクエスト先について、この形式を「origin-form」としてRFC7230では規定しています。(後述)
HTTPリクエストの送信先については、本来のホスト(originサーバ)宛になっています。
HTTP Proxy ON
続いて HTTP Proxy を設定した curl コマンドからHTTPリクエストをしてみます。
curl では -x (proxy-hosti):(proxy-port)
で HTTP Proxy を設定します。
$ curl -x (proxy-host):(proxy-port) http://(管理下のWebサイト)/
このときのHTTP通信をWiresharkでキャプチャした結果:
こちらではリクエストラインの形式が以下のように変化しています。
GET http://(ホスト名)/(URLパス) HTTP/1.1
Host: (ホスト名)
...
RFC7230ではこの形式を「absolute-form」として規定しています。(後述)
HTTPリクエストの送信先については、Proxy 宛に送信しています。
origin-form と absolute-form
紹介した2つの”form” は RFC7230, Hypertext Transfer Protocol (HTTP/1.1): Message Syntax and Routing の “5. Message Routing” -> “5.3. Request Target” で規定されています。
- 5.3.1. origin-form
- https://tools.ietf.org/html/rfc7230#section-5.3.1
- HTTPクライアントが、実際のWebサーバ(“origin”) に直接接続するときに使うものとされています。
- 5.3.2. absolute-form
- https://tools.ietf.org/html/rfc7230#section-5.3.2
- HTTPクライアントが Proxy を介して接続する時に使うものとされています。
HTTP Proxy をサポートするクライアントは、Proxy ON/OFF に応じて origin-form / absolute-form を切り替えてパケットを生成していることを確認できました。
続けてHTTPSによる暗号化通信でどうなるか見てみます。
パケット観察2: HTTPS Proxy ON/OFF
curlコマンドでHTTPSリクエストを行い、Proxy ON/OFF とでパケットレベルでの違いを観察します。
Wiresharkでパケットキャプチャし、対象のTCPストリームを追跡した結果のスクリーンショットを紹介していきます。
HTTPS Proxy OFF
まずは HTTPS Proxy 未設定で curl コマンドからHTTPSリクエストをしてみます。
$ curl https://(管理下のWebサイト)/
このときのHTTPS通信をWiresharkでキャプチャした結果:
TLSによる暗号化通信のため、パケットキャプチャのレベルでは実際のHTTPメッセージを見ることはできません。*1
仮に見れたとすると、以下のような origin-form でのリクエストを送信しています。
GET /(URLパス) HTTP/1.1
Host: (ホスト名)
...
TLS通信の接続先は、本来のホスト(originサーバ)宛になっています。
HTTPS Proxy ON
続いて HTTPS Proxy を設定した curl コマンドからHTTPSリクエストをしてみます。
$ curl -x (proxy-host):(proxy-port) https://(管理下のWebサイト)/
このときのHTTPS通信をWiresharkでキャプチャした結果:
最初の方に平文で CONNECT (ホスト名):443 HTTP/1.1
というパケットが見えます。
HTTP/1.1 200 OK
が返されたあと、TLS通信のハンドシェイク(ClientHello
)が始まり、以降は暗号化通信が続いています。
WiresharkでTCP通信を追跡してみると、CONNECTメソッドは以下のようになっていました。
https://(ホスト名)/(URLパス)
を元に、以下のようなHTTPリクエストになっています。
CONNECT (ホスト名):443 HTTP/1.1
Host: (ホスト名):443
...
RFC7230ではこの形式を「authority-form」として規定しています。(後述)
CONNECT メソッドに対して 200 が返されたあとは、同じTCPセッションでTLSによる暗号化通信が開始されています。
なおCONNECT メソッドおよびTLS通信の接続先は、Proxy 宛になっています。
CONNECT メソッド と authority-form
まず authority-form についてです。
この形式はRFC7230 の “5. Message Routing” -> “5.3. Request Target” で規定されています。
- 5.3.3. authority-form:
- https://tools.ietf.org/html/rfc7230#section-5.3.3
- CONNECT メソッドで使うものとされています。
CONNECT method は RFC7231, Hypertext Transfer Protocol (HTTP/1.1): Semantics and Content の “4. Request Methods” -> “4.3.6. CONNECT” で規定されています。
- 4.3.6. CONNECT
- https://datatracker.ietf.org/doc/html/rfc7231#section-4.3.6
- Proxy に対してトンネル(ネストされた通信経路)を作る時に使うものとされています。
HTTPS 通信での Proxy をサポートするクライアントは、Proxy ON ではまず CONNECT メソッドを authority-form で送信し、Proxy から 200 が返されたらそのままTLS通信を開始する仕組みになっていることを確認できました。
Burp の Repeater 機能で遭遇したトラブル
普段、HTTP通信の origin-form と absolute-form の違いを意識する必要はありません。
しかし Burp の Repeater 機能と上流プロキシを組み合わせると、これらを知っていないと解決に時間のかかるトラブルに遭遇します。
実際に筆者が遭遇したトラブルは以下のようなものでした。
- 上流Proxyを使用していない状態で Burp で
http://
の通信ログを採取し、Repeaterに送り再送信して正常応答を確認。 - 上流Proxy をON にしてもう一度 Repeater で送信すると、エラー応答になる。(大分昔の話なので、具体的なエラー状況は忘れてしまいました)
- Repeater のリクエストラインを absolute-form 形式にすれば、正常応答になる。(= 解決)
何が起こったのでしょうか。
まず上流Proxy OFF でのHTTPリクエストを Repeater で送信した時点では、Repeater のリクエストは origin-form で作られています。
上流Proxy をON にして Repeater で同じリクエストを送信すると、Repeater はリクエストラインを変更せずに origin-form のまま上流Proxy に送信していたのです。
上流Proxy では absolute-form を想定しているため、origin-form のリクエストに対してエラーを返していました。
Repeater はリクエスト内容をノータッチで送信するようです。
Burp の Repeater 機能は診断で主に使われるので、診断員が編集したHTTPリクエストを可能な限り「そのまま」送ることを考慮した仕様と思われます。
手動で absolute-form に修正すると 上位 Proxyで受けてつけてくれて、結果として正常応答を受信できました。
まとめ
- 「HTTP(S) Proxyを設定する」とはどういうことか、パケットレベルで観察し、RFC7230/7231 と一緒に確認しました。
- HTTP(S) Proxy に対応したブラウザやHTTP通信ライブラリでは、Proxy 設定ON/OFF に応じて次のようにHTTPリクエストを生成します。
- origin-form : HTTP(S), Proxy-OFF, 接続先 → origin サーバ
- absolute-form : HTTP, Proxy-ON, 接続先 → Proxy
- CONNECT + authority-form : HTTPS, Proxy-ON, 接続先 → Proxy
- Burp の Repeater で、origin-form/absolute-form の違いが原因のトラブルに遭遇した事例を紹介しました。
パケットレベルで観察することで、「HTTP(S) Proxy を設定する」とはどういうことか、自信を持って解説できるようになれば幸いです。
Burp に限らず HTTP Proxy を使ったときの通信トラブルの調査にも役立ちますので、トラブル遭遇時はこの記事を思い出してみてください。
*1:Webサーバとブラウザにそれぞれ設定を行えばWiresharkで復号することも可能ですが、本記事では省略します。