前の投稿 次の投稿

OWASP ZAP多段プロキシ構成について(前編 / ZAP+Fiddlerテクニック編)

OWASP ZAPは、単体で使うよりもFiddlerやBurp Suiteなどの他のローカルプロキシと組み合わせて多段プロキシの形にして利用するほうが便利です。

■なぜZAP+他プロキシの形にするか


OWASP ZAPはいろいろ便利な機能がある反面、ログ機能に関していまいちな挙動をいくつか持っています。

OWASP ZAPのログ機能のいまいちさの例(ZAP 2.5.0での現象):

・「動的スキャン」のログと「履歴」のログが別で、診断対象サイトにアクセスしたり動的スキャンした後に、いったんセッションファイルを保存してZAPを再起動してセッションファイルを開くと、「履歴」のログのみが残っていて「動的スキャン」のログは復活しない(実施した動的スキャンのログはZAPを落とすまでは見れるが、保存されない)。

・リダイレクトが発生するページにアクセスした際、ZAPの通常の履歴にはリダイレクトの各ページの遷移が出てくるが、そのページに対して動的スキャンを実施した場合、動的スキャン側のログではリダイレクトの履歴が省略される場合がある(あたかもそのページからレスポンスが来たかのようなログになっているが、実際はリダイレクト先からのレスポンスである、というケースがある)。

・同様に、リダイレクトが発生するページに対して履歴右クリック-「再送信」を行うと、履歴ウィンドウに途中のリダイレクトが省略されて1回のリクエスト→レスポンスであるかのようなログが記録される。


ZAP「再送信」の時の例
Locationヘッダでindex.php - index2.php - index3.php とリダイレクトするサンプルページを作成し、
・ブラウザでindex.phpへアクセス
・ZAPの履歴ウィンドウでindex.phpを右クリック-再送信
した時のスクリーンショット:
[ZAP]

[Fiddler]


再送信の時にリダイレクトが記録されないのは単にZAPのバグなのかもしれないのですが(後で時間できた時にまたissue上げておきます→上げました)、上で挙げたようにZAPのログ機能にはいまいちな仕様が含まれているため、それに備えて、OWASP ZAPは単体で使うよりも、多段プロキシの形にしてZAPの挙動を別のプロキシで記録しながら使うほうが安全です。

(実施した診断が原因かもしれない問題が発生した場合、診断時に何を投げたのかのログが完璧に残っていないと詳細な原因調査ができなくなり、推測で原因を特定しなければならなくなりますし、診断が原因でなかった場合でも潔白が証明できなくなります。診断時のログは全て記録しておいた方が良いです)

また、Fiddlerを単にLoggerとして使うだけでもメリットはあるのですが、Fiddler Scriptを利用して多段プロキシならではの合わせ技のようなこともできるので、今回はその合わせ技をいくつか紹介します。

■ZAP+Fiddler 多段プロキシの設定方法


設定の概略としては

【ブラウザ】
・ブラウザのプロキシ設定としてlocalhostの7777番ポートを設定(ブラウザからの通信がlocalhost:7777へ送信される)

【OWASP ZAP】
・localhostの7777番ポートで待ち受ける設定を行う
・ZAP自身のプロキシ設定としてlocalhostの8888番ポートを設定(ZAPからの通信がlocalhost:8888へ送信される)

【Fiddler】
・localhostの8888番ポートで待ち受ける設定を行う


このように設定することで

【ブラウザ】-7777番ポート-【OWASP ZAP】-8888番ポート-【Fiddler】-【診断対象サイト】


というプロキシツールを二つ経由して対象サイトへアクセスする状態が作れます。

構成図


・ブラウザの設定
ウェブブラウザには通常プロキシサーバーを設定する設定項目があります。

例えばFirefoxであれば、[ツール]-[オプション]-[詳細]-[ネットワークタブ]-[接続設定] の画面を開くとプロキシの設定画面が出てきます。

HTTPプロキシとSSLプロキシの欄に、ZAPを動作させるサーバ(通常ローカルPCでブラウザと一緒に起動すると思うのでlocalhostか127.0.0.1)と、ZAPが待ち受けているポート(本設定例では7777)を設定します。


・ZAPの設定
設定1:
ZAPのメニューの[ツール]-[オプション]-[ローカルプロキシ]の設定画面でZAPがどのポートで待ち受けるかの設定を行います。
本設定例ではAddtess:localhost ポート:7777に設定してください。

設定2:
ZAPのメニューの[ツール]-[オプション]-[ネットワーク]の設定画面で[プロキシ・チェイン利用]の[外部プロキシサーバ利用]のチェックボックスをオンにすると、ZAPからの通信を中継するプロキシサーバを指定することができます。ここにFiddlerの待ち受けポートを設定します。
本設定例では「アドレス/ドメイン名」をlocalhost、ポートを8888に設定します。


・Fiddlerの設定

Fiddlerのメニューの[Telerik Fiddler Options]-[Connections]タブで「Fiddler listens on port」を8888(本設定例での値)に設定します。

あと、同じタブにある[Act as system proxy on startup]チェックボックスはオフにしたほうが良いと思います。これをオンにしているとFiddlerがシステムのプロキシとして働く設定で起動するので、ZAPから来た以外の全ての通信をキャプチャしてしまい、別ブラウザで開いたサイトの通信などもFiddlerのログに記録されてしまうからです。(これをオフにしておくと8888ポートに来た通信のみキャプチャします)


この設定を行った状態で、ブラウザからhttp通信のサイトを見ると、ZAPとFiddlerにそれぞれログが記録されることが確認できると思います。

※この設定で通信を問題なく中継できるのはhttpのサイトだけで、SSLを利用したhttpsプロトコルのサイトだとうまく中継できないと思います。httpsのサイトにアクセスできるようにするためには、この設定に加えて、別途、FiddlerやZAPの証明書を証明書ストアに登録する必要があります。

その手順について簡潔に解説して下さっているブログ記事がありましたので、参考資料としてリンクを張っておきます。

Fiddler、ZAPでhttps通信で証明書エラーを出さなくする(ブラウザルート証明書の登録) (linux-555様)
http://ameblo.jp/soft3133/entry-11774215969.html

■ZAP+Fiddler 小技集


・小技1:リクエストに対し一定のウェイト(ディレイ)をかける

FiddlerではFiddlerScriptをカスタマイズすることでFiddlerを通るリクエストやレスポンスに一定のウェイト(ディレイ)をかけることが可能です。
そのカスタマイズ機能を利用し、ZAPの動的スキャンのアクセスの速度をFiddler側で制御して、動的スキャン対象サーバーにかける負荷を緩めることが可能です。

Fiddlerの[Rules]-[Customize Rules...] を選択すると、FiddlerScript と呼ばれるFiddlerカスタマイズ用スクリプトが表示されます。

FiddlerScript のOnBeforeRequestという関数内部に(どこでもかまいませんが、冒頭あたりに)
System.Threading.Thread.Sleep(1000);
このコードを書いておくと、ZAPの動的スキャンなどの際に、1リクエストごとに所定のウェイト(上記であれば1000ミリ秒)が入ることになるため、動的スキャン対象サーバへ負荷をかけすぎずに診断を行うことができます。

またFiddlerのカラムにリクエスト時間を表示しておいたほうが何かと便利なので、stackoverflowの下記スレッド
How to display the request sent time and the response received time in Fiddler?

> For Fiddler 4.6.2, Right Click on any of the Column Headers on the Sessions pane.
> Customize Columns > Collection > SessionTimers > ClientBeginRequest and ClientDoneRequest

このカスタマイズを入れておくと良いと思います。

OWASP ZAPにも動的スキャン時にリクエストを遅延させる設定項目はあるのですが、

・ZAPの動的スキャンでは最大1秒しかウェイトを設定できない
→ Fiddler側で調整する形だと何秒でも設定できる。
・ZAPのウェイト指定だと動的スキャン中にウェイトの設定を柔軟に変えることができない
→ Fiddlerでウェイトを調整するとどのタイミングでも柔軟に調節できる

という理由によりOWASP ZAP単体のウェイト指定よりもFiddler側でウェイトをかける方式のほうが使い勝手が良いです。

また、多段プロキシを用いて診断の負荷を調節するような方法は汎用的なテクニックなので、ZAP以外にも外部プロキシが設定できるツール全般に使えます。例えばniktoなどにも外部プロキシの設定オプションがあるので、多段プロキシの形にすることでniktoのリクエスト間隔を調整することができます。


・小技1-1:静的なコンテンツへのリクエストに対してはウェイトをかけない

上に書いたコードの条件追加版です。単に
System.Threading.Thread.Sleep(1000);
だけを書くと、Fiddlerを通るすべてのリクエストに対しウェイト1秒が発生しますが、それだと、画像などの静的なコンテンツを大量に読み込むページなど、リクエスト数がやたら多いページにアクセスする場合、パフォーマンスの劣化が著しいということになります。

そのために、診断対象でない静的コンテンツやHTTPSの場合のCONNECTメソッドなどを除外する条件を追加したものが下記になります。
if (!oSession.HTTPMethodIs("CONNECT")
     && !oSession.oRequest.headers.ExistsAndContains("Accept", "image/") 
     && !oSession.uriContains(".js")){
    System.Threading.Thread.Sleep(1000);
}

・小技2:基本認証を通す

基本認証が設定されているページの診断の場合、ZAPの設定で基本認証を通すことも可能ですが、FiddlerScriptでリクエストヘッダにAuthorizationヘッダを追加するという方法があり、こちらほうが手軽です。(追加箇所はOnBeforeRequest関数内)
oSession.oRequest["Authorization"] = "Basic dGVzdDp0ZXN0";

しかし、上記のコードだと全リクエストヘッダに基本認証のID:パスワードのBase64値を付けて送信してしまうので、対象のサイト以外にアクセスした際にも、設定した基本認証のIDパスワードを送り付けてしまうことになるため、以下のように、「このホストだったら」というのを付けたほうが良いと思われます。
(ホスト単位以上にもっと細かい条件分けが必要な場合はFiddler公式ドキュメントのこのあたりを参照ください)
if(oSession.host === "example.com"){
    oSession.oRequest["Authorization"] = "Basic dGVzdDp0ZXN0";
}

・小技3:特定のホストに対しては特定のプロキシを通す

(追加箇所はOnBeforeRequest関数内)
if(oSession.host === "example.com" ){
    oSession["x-overrideGateway"] = "127.0.0.1:1234"; 
}

・小技4:ZAPが投げる危険な文字列をFiddlerでDROPする(サンプルコード)

ZAPが投げる診断文字列に、サーバ側に送信したくない特定の文字列が含まれている場合にFiddlerでDROPして偽のレスポンスを返すサンプルコードです。

このコードはもともとZAPのSQLインジェクション検査で投げられる「 OR 1=1 -- 」などの危険な文字列をDROPするために書いてみたのですが、ZAPのSQLインジェクション診断用文字列は危険なものが多く、診断文字列を部分的にDROPするよりThresholdをlowにして危険な文字列を投げない最低限の検査を行い、残りは手動で検査したほうがよさそうという結論になったため、このコードは現在は使っていません。

ただ、特定の文字列がリクエストヘッダもしくはボディに含まれていた場合にサーバに投げないでDROPするという処理はどこかで使う機会がありそうなので、自分用メモも兼ねてコードを公開しておきます。
(追加箇所はOnBeforeRequest関数内)
var bBlockOr = false;

// リクエストヘッダに危険なSQLiパターンがあれば
var ReqHeaders = oSession.oRequest.headers.ToString();
if(ReqHeaders.indexOf("+OR+1%3D1+--+") > -1 
 || ReqHeaders.indexOf(" OR 1=1 --") > -1 ){
    bBlockOr = true;
}

// リクエストボディに危険なSQLiパターンがあれば
var ReqBody = System.Text.Encoding.UTF8.GetString(oSession.requestBodyBytes);
if(ReqBody.indexOf("+OR+1%3D1+--+") > -1 
 || ReqBody.indexOf(" OR 1=1 --") > -1 ){
    bBlockOr = true;
}

// リクエストのブロック処理(偽のレスポンスを返す)
if(bBlockOr == true){ 
    oSession.utilCreateResponseAndBypassServer();
    oSession.oResponse.headers.SetStatus(503,"filtered (SQLi test blocked)");
} 


長くなったので記事を分けて前・中・後編とします。中編に続きます。

※記事中で紹介したサンプルコードはなるべく間違いのないように心がけておりますが、無保証です。利用する場合は自己責任にて改めて検証の上ご利用ください。
(何かミスを見つけたらお知らせください)

中編へ

Leave a Reply

Powered by Blogger.
© WEB系情報セキュリティ学習メモ Suffusion theme by Sayontan Sinha. Converted by tmwwtw for LiteThemes.com.