こんにちは、SSTでWeb脆弱性診断用のツール(スキャンツール)開発をしている坂本(Twitter, GitHub)です。
先日、Proxy によりHTTPリクエストがどのように変わるかの解説記事を書きました。 また iOS のHTTP通信ライブラリの Proxy 対応状況についても調べています。 こうしたライブラリを使ったアプリであれば、WiFi接続からProxy設定をすれば自動でそれを参照し、BurpなどでHTTP(S)通信を見ることができます。
そうではなく、WiFi の Proxy 設定を参照しなかったり、そもそも Proxy に未対応の場合はどうすれば良いでしょうか?
Burp の場合 Invisible Proxy (別名: 透過型Proxy) 機能があり、これを使うと Proxy 未対応の通信でもBurpを通すことが可能になります。
本記事では PortSwigger 社の Burp 公式ドキュメントを元に、Invisible Proxy の仕組みや使い方について解説します。
※透過型 Proxy の英語表現として "Transparent Proxy" も使われています。 どちらも技術的な中身として通常のHTTP(S)通信をそのまま中継するところは同じです。 本記事では Burp の公式ドキュメントに従い "Invisible Proxy" 表記を採用します。
Invisible(透過型) Proxy の仕組み
通常のHTTP Proxy*1 を使うと、HTTPクライアントは以下のようにHTTPリクエストをカスタマイズし、Proxyサーバに送信します。*2
- HTTPの場合、リクエストラインを origin-form から absolute-form に変更する。
- HTTPSの場合、CONNECT メソッドを authority-form で送信し、Proxy サーバから200 OK が返ってきたらTLSによる暗号化通信を確立して通常の origin-form でのHTTPリクエストを送信する。
Invisible(透過型) Proxy は、上記のようなカスタマイズがされていない、通常のHTTPリクエストに対しても中継することが可能です。 中継先のWebサーバは以下のように認識します。
- HTTPの場合、Host リクエストヘッダーを接続先として使います。
- HTTPSの場合、Host リクエストヘッダーはTLS通信が確立した後に暗号化されて送られます。Proxy が実際のWebサーバとTLS接続を確立するタイミングではまだHostリクエストヘッダーが送られていないため、そのままでは接続先が分かりません。
HTTPクライアントを Invisible Proxy に接続させる方法については、例として次の2つが考えられます。
- HTTPクライアントを動かすホスト上で
/etc/hosts
を編集し、Invisible Proxy を通したいホスト名に対して、Invisible Proxy を動かすPCのアドレスをマッピングする。- Linux の場合 :
/etc/hosts
- Windows の場合 :
C:\Windows\System32\drivers\etc\hosts
- Linux の場合 :
- HTTPクライアントからの tcp:80/443 宛の接続を Invisible Proxy を動かすPCのアドレスに転送する。
- Linux の場合, iptables による forward 設定を行う。
HTTP クライアントに意識させずに、接続先を Invisible Proxy が動いているPCのアドレスに差し替えることがポイントとなります。
の方式では特定のホストのみ明示的に Invisible Proxy に接続させることになります。 特定のアプリの、特定サーバ向けの通信だけをキャプチャしたい場合は 1. の方式が簡単です。 ただし Burp を同じホストで動かす場合、Burp も
/etc/hosts
を参照します。 設定状況によっては接続ループが発生する可能性があり、公式ドキュメントの "Redirecting outbound requests" セクションで回避方法が紹介されています。 もしこの方式を使う場合は、必ず左記セクションを熟読してください。の方式ではIPレベルでの転送(forward)が可能な環境が必要です。 HTTPクライアントを動かすホスト上で iptables を設定してもいいですが、Linux Gateway 相当の機器を用意して、Gateway上で iptables により forward 設定を行うのもオススメです。 iptables や
/etc/hosts
を変更できないようなHTTPクライアントからの通信でも、途中の Linux Gateway 上で forward することで Burp の Invisible Proxy に接続し、HTTP(S)通信をキャプチャすることが可能です。
Invisible Proxy を使いたいユースケースや環境に応じてどちらの方式を使うか検討することになります。
Burp における Invisible Proxy の設定方法
それでは実際に Burp で Invisible Proxy を設定してみます。 公式ドキュメントに従い tcp:80, tcp:443 それぞれで Proxy Listener を作成します。
Proxy
タブ → Options
タブ → Proxy Listeners
→ "Add" ボタンをクリックし、以下のように tcp:80 に Bind した Listener を追加します。
今回は外部からも接続したいため、"Bind to address" に "All interfaces" を設定しています。
"Request handling" タブに移動し、"Support invisible proxying" にチェックを入れます。これにより Invisible Proxy が使えるようになります。
続いて tcp:443 用の Proxy Listener を作成します。
tcp:80 のときと同様に Proxy Listeners
の "Add" ボタンから tcp:443 に Bind した Listener を追加します。
同様に "Request handling" タブに移動し、"Support invisible proxying" にチェックを入れます。
以上で tcp:80 と tcp:443 の Invisible Proxy が設定できました。 では実際に Invisible Proxy 宛のHTTPリクエストを観察してみます。
実際のHTTPリクエストを観察
今回は以下のような環境を構築しました。
+------ Windows 10 (VirtualBox Host) ----------------------------------------+ | +--- Burp Proxy Listeners --+ | | +--- VirtualBox Linux Guest ------+ +---> | tcp:8080 (normal) | | | | curl http(s)://www.example.com/ | +---> | tcp:80 (invisible) | | | +---------------+-----------------+ +---> | tcp:443 (invisible) | | | | | | | | | +---------------------+ +---------------------------+ | | dest:10.0.2.2 (= VirtualBox NAT Host address) | +----------------------------------------------------------------------------+
- Windows 10 (VirtualBox Host) : Burp Suite Community Edition v2021.5.1 で以下の Proxy Listener を起動します。
- Bind to: 8080 (normal proxy)
- Bind to: 80 (invisible proxy)
- Bind to: 443 (invisible proxy)
- Linux (VirtualBox Guest)
- curl コマンドで Proxy 通信を行います。
- この Linux ゲストからは、Win10 ホストのIPアドレスが 10.0.2.2 になります。(VirtualBox NAT ネットワークのデフォルト)
Linuxゲストから http(s)://www.example.com/
へのGETアクセスをcurlコマンドで実行し、通常 Proxy / Invisible Proxy それぞれでどのようにHTTP(S)リクエストが変化するか Wireshark のパケットキャプチャで確認します。
- 最初は curl 側で通常通り Proxy を指定 (
-x
オプション)して HTTP(S) 通信を観察します。 /etc/hosts
でwww.example.com
を Win10 ホストの10.0.2.2
に設定します。- Proxy を設定せずに、curl で
http(s)://www.example.com/
にアクセスします。- → Proxy 設定なしの状態で Burp に接続することになるので、その時の通信内容を観察します。
通常のProxyでのHTTP(S) リクエスト
まず tcp:8080 の通常の Proxy Listener を使ったときのHTTPリクエストを確認します。
$ curl -x 10.0.2.2:8080 -v http://www.example.com/
→ Proxy を使ったHTTP通信のため、リクエストラインが absolute-form に調整されたHTTPリクエストが送られていました:
GET http://www.example.com/ HTTP/1.1 User-Agent: curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.21 Basic ECC zlib/1.2.3 libidn/1.18 libssh2/1.4.2 Host: www.example.com Accept: */* Proxy-Connection: Keep-Alive HTTP/1.1 200 OK (...)
続いてHTTPSリクエストを確認します。
$ curl -k -x 10.0.2.2:8080 -v https://www.example.com/
→ authority-form によるCONNECT メソッドが送信され、Proxy から 200 が返された後は TLS による暗号化通信に切り替わっています:
CONNECT www.example.com:443 HTTP/1.1 Host: www.example.com:443 User-Agent: curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.21 Basic ECC zlib/1.2.3 libidn/1.18 libssh2/1.4.2 Proxy-Connection: Keep-Alive HTTP/1.0 200 Connection established (以降、TLSによる暗号化通信)
ここまでは通常の HTTP Proxy を使った通信です。 続いて Invisible Proxy を使った通信を見ていきましょう。
Invisible Proxy へのHTTP(S) リクエスト
Invisible Proxy を使うということは、HTTPクライアントの視点に立つと Proxy 設定を無視する/使わないということで、curl コマンドでいうなら -x proxy-host:proxy-port
コマンドラインオプションを使わないことになります。
そのままだとHTTPクライアントは指定されたホストにダイレクトに接続しようとするので、これを Invisible Proxy に接続させる必要があります。
今回はcurl コマンドを実行する Linux ゲストの /etc/hosts
で、 www.example.com
を 10.0.2.2
にマップしてみました。
$ sudo vi /etc/hosts # -> 以下を追記 10.0.2.2 www.example.com
この状態で curl コマンドでHTTPリクエストを送信してみます。
$ curl -v http://www.example.com/
→ Burp で正常にHTTP通信がキャプチャできていました。 Wiresharkでパケットレベルで確認してみると、以下のように origin-form によるHTTPリクエスト、つまり Proxy を使わないときと同じリクエストが送信されていました。
GET / HTTP/1.1 User-Agent: curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.21 Basic ECC zlib/1.2.3 libidn/1.18 libssh2/1.4.2 Host: www.example.com Accept: */* HTTP/1.1 200 OK (...)
続いてHTTPSリクエストを送信してみます。
$ curl -k -v https://www.example.com/
→ Burp で正常にHTTPS通信がキャプチャでき、平文のHTTPリクエスト/レスポンスを確認できました。 Wiresharkでパケットレベルで確認してみると、最初からTLS通信が開始されており、Proxy を使わないときと同様の通信となっていました。 (パケット内容のスクリーンショットやテキストは省略)
ちなみに curl の -k
オプションはSSL/TLSにおけるサーバ証明書検証をスキップします。
-v
ではSSL/TLSの情報なども表示され、受信したサーバ証明書のサマリが表示されます。
それによると、Invisible Proxy 経由でのサーバ証明書は以下のようになっており、Burp のルートCAが www.example.com
用に生成したものであることが分かります。
$ curl -k -v https://www.example.com/ * About to connect() to www.example.com port 443 (#0) * Trying 10.0.2.2... connected * Connected to www.example.com (10.0.2.2) port 443 (#0) * Initializing NSS with certpath: sql:/etc/pki/nssdb * warning: ignoring value of ssl.verifyhost * skipping SSL peer certificate verification * SSL connection using TLS_DHE_RSA_WITH_AES_256_CBC_SHA * Server certificate: * subject: CN=www.example.com,OU=PortSwigger CA,O=PortSwigger,C=Po... ^^^^^^^^^^^^^^^^^^^^ → www.example.com 用のサーバ証明書 * start date: 7月 14 04:57:06 2021 GMT * expire date: 7月 14 04:57:06 2022 GMT * common name: www.example.com * issuer: CN=PortSwigger CA,OU=PortSwigger CA,O=PortSwigger,L=PortSwigger,ST=PortSwigger,C=Po... ^^^^^^^^^^^^^^^^^^^ → 発行者は PortSwigger CA, つまり Burp のルートCA > GET / HTTP/1.1 (...)
該当するTLS ClientHello メッセージを Wireshark で確認すると、 "server_name" (=SNI) 拡張フィールドに www.example.com
が格納されていました。
Burp はこれを見て、サーバ証明書を生成し、実際の接続先として使っているものと思われます。
以上より、Proxyを使わないHTTP(S)通信でも Burp の Invisible Proxy で通信内容をキャプチャし、正常に通信できることを確認できました。
HTTPクライアントと Invisible Proxy を同じマシンで実行するときの注意点
今回はHTTPクライアントと Burp を別のマシン(PC)に分離し、HTTPクライアント側の /etc/hosts
を使って Invisible Proxy に接続させて検証しました。
もし HTTP クライアントも Burp と同じマシン上で実行し、/etc/hosts
を使って Invisible Proxy = 127.0.0.1 に接続させるとどうなるでしょうか?
- HTTPクライアントが
www.example.com
に接続 →/etc/hosts
により 127.0.0.1 に接続。 - Invisible Proxy が Host リクエストヘッダー or SNI拡張からホスト名
www.example.com
を取り出し、接続 →/etc/hosts
により 127.0.0.1 に接続。 - 以下、無限ループ
このように無限ループが発生することがあり、公式ドキュメントでも "Redirecting outbound requests" のセクションで回避方法などが解説されています。 本記事ではこれらの検証は省略しますが、もし上記のような構成で検証する際は公式ドキュメントをご確認ください。
まとめ
本記事では Burp の Invisible (透過型) Proxy の仕組みと設定方法について解説し、実際に使ってみたときのHTTPリクエストを観察しました。
- HTTP Proxy に未対応のHTTPクライアントについて、HTTP通信をキャプチャするときに有用です。
- Burp で Invisible Proxy を使うときは、tcp:80/443 それぞれに bind した listener を追加し、"Request handling" 設定タブの "Support invisible proxying" にチェックを入れます。
- HTTPクライアントを Invisible Proxy に接続させるには、以下の方法があります。
/etc/hosts
で接続対象のホスト名を Invisible Proxy が動いているIPアドレスにマッピング- 特定アプリ/ホストのみ Invisible Proxy に通したい時に使います。
- HTTPクライアントとBurpが同一ホストにあり、 127.0.0.1 にマッピングしているときは接続ループの発生に注意してください。
- iptables 等で tcp:80/443 を Invisible Proxy が動いているIPアドレスに転送(forward)
/etc/hosts
の編集や iptables が使えない機器からのHTTP通信をキャプチャしたい場合に、Linux Gateway でiptablesを設定して使います。- アプリによっては標準以外のポート番号で接続することがあります。その場合、そのポート番号で listener 起動 && iptables でも forward 設定を追加することになると思われます。
- Invisible Proxy は接続先の情報を Host ヘッダーや TLS ClientHello メッセージの "server_name" (SNI) 拡張から取得します。
- うまくキャプチャできない/HTTPクライアントが接続セラーになるときは、HTTPクライアントが生成する Host ヘッダーや TLS ClientHello のSNI拡張について確認してください。
HTTP Proxy 未対応のHTTPクライアントについて、Burp でHTTP(S)通信をキャプチャしたい・・・そんな時に、本記事と Invisible Proxy が役に立てば幸いです。
おまけ1: tcp:80/443 を tcp:8080 に bind した Invisible Proxy にまとめて転送できるか?
本記事では tcp:80/443 それぞれで Burp の Proxy Listener を追加し、それぞれ Invisible Proxy を有効にしています。 これを tcp:8080 に bind した単一の listener にまとめることができるか、検証してみました。
- Burp では tcp:8080 の Proxy Listener 1つのみで、Invisible Proxy 有効化。
- HTTPクライアント側のホストでは tcp:80, tcp:443 の両方をInvisible Proxy のIPアドレスと tcp:8080 に forward (iptables)
結果として Burp の Dashboard で hostname
と hostname:8080
への接続エラーが多発し、HTTPクライアント側でもエラーとなってしまいました。
Invisible Proxy Listener 1つで tcp:80/443 の両方を受けることは諦め、公式ドキュメントの通り tcp:80/443 それぞれで Invisible Proxy 有効のlistener を追加することで正常通信が可能となりました。
なぜそうなるのか詳細な切り分け調査はできていません。 今の段階で言えることは、iptablesとBurpだけで構築するのであれば、公式ドキュメントどおりにポート番号ごとに listener を立ち上げるほうが無難と思われます。
おまけ2: redsocks や stone を組み合わせる方法について
redsocks や stone は、いずれも汎用的なTCP接続の中継ソフトです。
- stone
- redsocks
こうしたソフトには、付加機能として HTTP(S) を Invisible Proxy として受け取り、非透過型 Proxy に中継してくれる機能があったりします。 redsocks はその機能があり、 ProxyDroid に同梱されています。stone については調査不足です。
ProxyDroid は root 化したAndroid でProxy設定を行うアプリです。 仕組みとしては以下のようになっています。
- iptables でHTTP(S)を同梱した redsocks に転送
- redsock が Invisible Proxy として受け付けたHTTPリクエストを、非透過型Proxy( Burp の通常listener や Fiddler など) 用に変更して、中継
ProxyDroid は iptables を扱うため、root化が必要となっています。 また上記の通り一度内部で Invisible Proxy として受け取るため、アプリ側の Proxy 対応状況に影響を受けません。 root化した Android 端末があればアプリに依らず Proxy を通すことが可能となり、弊社の診断現場でも便利に使っています。
本記事は Burp の Invisible Proxy 機能の紹介でしたが、redsocks を Invisible Proxy として使えば、Invisible Proxy 未対応のHTTP Proxy に中継することも可能となります。 機会があれば、後日その構成についても紹介したいと思います。
*1: 透過型 Proxy の逆なので、"非" 透過型Proxy と呼ぶこともあります。
*2: 詳細は 「HTTP(S) Proxyを設定する」とはどういうことか、パケットレベルで解説 - SSTエンジニアブログ を参照してください。