(中編からの続きです)
・実験3:ZAP HTTP SenderスクリプトにFiddlerスクリプトの小技を移植する
実験2の調査結果により、ZAPのHTTP Senderスクリプトは、ZAPが送受信するすべてのリクエストに干渉することができるようなので、これを使えば、ZAP+Fiddlerで行っていたような細かい挙動の変更をZAP単体でできるようになるかもしれません。
少し実験して確かめてみます。以下、「前編」のZAP+Fiddlerの小技集と対応しています。
小技1':リクエストに対し一定のウェイト(ディレイ)をかける
HTTP SenderスクリプトをECMAScriptで作成し、sendingRequest関数内に下記のコードを書けば、ZAPが発行するリクエストに対してウェイト(ディレイ)が掛けられます。
function sendingRequest(msg, initiator, helper) { java.lang.Thread.sleep(1000); }
ZAPの動的スキャンのスレッド数を1にし、上記コードと組み合わせることで動的スキャンの各リクエストに対し任意の頻度になるようウェイトをかけることが可能になります。
小技1-1':動的スキャン以外のリクエストに対してはウェイトをかけない
プロキシでウェイトを掛けるように設定していると、手動で診断対象サイトにアクセスした際に、大量の画像や静的コンテンツが読み込まれ、そのリクエストごとに所定のウェイトがかかることでページ遷移がとても重たくなる現象があります。
ZAP+Fiddlerの場合、静的コンテンツなどのリクエストの場合はウェイトをかけないコードにすることでその問題を回避しましたが、ZAPのHTTP Senderスクリプトでは別の解決方法があります。
HTTP Senderスクリプト下にデフォルトのテンプレートでECMAScriptを作成すると、コメント文中に色々と書いてある中に下記のリストが登場します。
// 'initiator' is the component the initiated the request: // 1 PROXY_INITIATOR // 2 ACTIVE_SCANNER_INITIATOR // 3 SPIDER_INITIATOR // 4 FUZZER_INITIATOR // 5 AUTHENTICATION_INITIATOR // 6 MANUAL_REQUEST_INITIATOR // 7 CHECK_FOR_UPDATES_INITIATOR // 8 BEAN_SHELL_INITIATOR // 9 ACCESS_CONTROL_SCANNER_INITIATORこれは、sendingRequest関数の引数initiatorに、ZAPのリクエストの種別が渡って来るということなので、この値で分岐するIF文を書けば、「普段の手動ブラウジングの時にはウェイトがかからないが、動的スキャンの場合のみリクエストに指定のウェイトがかかる」というコードにできます。
動的スキャン時のみウェイトがかかるサンプルコード:
function sendingRequest(msg, initiator, helper) { print('initiator:'+initiator +' / sendingRequest called for url=' + msg.getRequestHeader().getURI().toString()); if(initiator==2){ java.lang.Thread.sleep(5000); } }sendingRequest関数に上記のように書くと、
・手動でのリクエスト時(initiatorに1が渡って来る):
initiator:1 / sendingRequest called for url=http://localhost/zaptest/test.php?aaa=ccc→ウェイトが全くかからない
・動的スキャン時(initiatorに2が渡って来る):
initiator:2 / sendingRequest called for url=http://localhost/zaptest/test.php?aaa=0W45pz4p→ウェイトが1リクエストごとに5秒かかる(サンプル通りThread.sleep(5000)を指定していた場合)
小技2':基本認証を通す
アクセス先ホストがlocalhostの場合のみ、Authorizationヘッダを付けるサンプルコード:
function sendingRequest(msg, initiator, helper) { host = msg.getRequestHeader().getURI().getHost(); print('host:'+host); if(host=='localhost'){ msg.getRequestHeader().setHeader("Authorization", "Basic XXXXXX"); } }
小技3':特定のホストに対しては特定のプロキシを通す
ZAPのスクリプトから外部プロキシ設定を変更する手段が見つからなかったので、これを実現するコードは作成できませんでした。
小技4':ZAPが投げる危険な文字列をZAPのHTTPSenderでDROPする(サンプルコード)
Fiddlerスクリプトでは偽のレスポンスを返すという手段で防止していましたが、ZAPのHTTPSenderではリクエストを差し止めて投げないという方法が見つからなかったので、127.0.0.1にリクエストを向けるというやや力技で解決しました。
(たぶんリクエストを差し止めて投げない方法はちゃんと存在すると思うので、見つけられたら修正します)
function sendingRequest(msg, initiator, helper) { var reqhead = msg.getRequestHeader().toString(); print('reqhead:'+reqhead+"\n\n"); var reqbody = msg.getRequestBody().toString(); print('reqbody:'+reqbody); if (reqhead.indexOf(' OR 1=1 --') > -1 || reqhead.indexOf('%20OR%201=1%20--') > -1 ||reqbody.indexOf(' OR 1=1 --') > -1 || reqbody.indexOf('%20OR%201=1%20--') > -1) { msg.getRequestHeader().setURI(new org.apache.commons.httpclient.URI("http://127.0.0.1", false)); msg.getRequestBody().setBody(""); } }
・実験5:ZAP+ZAP 多段プロキシ構成を試してみる
OWASP ZAPは起動時に-dirオプションを付けて起動すると、起動ディレクトリを指定して起動することができます。
設定等は起動ディレクトリに保存されるので、同じバイナリを使って設定の違う二つのZAPを多重起動して動作させることが可能です。
前編の冒頭で解説したように、ZAPはログ機能が微妙なので、「ZAP+何か」の形で多段プロキシにしてログを別途記録したほうが良いのですが、FiddlerはWindows環境以外ではα版もしくはβ版であるため、Windows環境以外だとやや導入に不安を感じます。
では、ZAP+Burpだとどうかというと、Burp SuiteがFree版の場合、ログは記録できるものの、そのログをBurpSuiteで改めて読み込むことができないため、何かを調べたいときにGUI上ではなく、ログが全て記録されたテキストファイル内を検索する必要があります。(Burp Suite Professional版であればProjectという形でログの保存・復元が可能なようです)
Burp Suite Professionalが買えず、Win環境以外の場合、三大プロキシの二つがログを記録する用途に適さないので、じゃあZAPの先に別のZAPをもう一つ繋げて、ZAPのログをZAPで記録するというのはどうか? ということで実験してみます。
[ZAP多重起動のための手順(Windows環境)]
1. ZAPの起動ディレクトリを定めます
ここでは仮に
・C:\zaptest\dirtest1
・C:\zaptest\dirtest2
とします。
2. ZAPを起動させるバッチファイルを作成
[zap1.bat]
java -jar zap-2.5.0.jar -dir "C:\zaptest\dirtest1"
pause
[zap2.bat]
java -jar zap-2.5.0.jar -dir "C:\zaptest\dirtest2"
pause
3.ZAPを起動し、それぞれの設定を行います。
※WindowsDefenderが入っている場合、インストールするアドオンの一部がバックドアとして除外対象判定になることがあるので、ZAPの起動ディレクトリをスキャン対象から除外しておく必要があります。
[zap1.batで起動したZAP]
・ヘルプ - アップデートのチェック - マーケットプレイスで「Release」レベルのアドオンを全てインストール(外部プロキシサーバ設定前に実行)
・ツール - オプション - ローカルプロキシの値がlocalhost:8080での待ち受けになっているので、本例では localhost:7771 に変更
・ツール - オプション - ネットワーク - 外部プロキシサーバ利用をオンにし、外部プロキシサーバとして本例では localhost:7772 を設定
[zap2.batで起動したZAP]
・ヘルプ - アップデートのチェック - マーケットプレイスで「Release」レベルのアドオンを全てインストール(外部プロキシサーバ設定前に実行)
・ツール - オプション - ローカルプロキシの値がlocalhost:8080での待ち受けになっているので、本例では localhost:7772 に変更
[ブラウザ]
プロキシサーバとして localhost:7771 を設定
これで、ブラウザでサイトを見るとZAP1、ZAP2それぞれに履歴が記録されます。
[loggerとしてのZAPのテスト]
・ZAP1で(診断して良い)特定サイトに対し動的スキャンを実行し、セッションデータを保存・再読み込みを行ってみると、前編冒頭で書いたように、動的スキャンの結果は残りますが動的スキャンのログが消えてしまいます。
しかし、ZAP2の方ではZAP1の動的スキャンのログが全て「履歴」タブのところに記録されているためログを保存・再読み込みしても記録が消えません。
・また、前編冒頭で書いたリダイレクトの再送信でログが記録されない問題も、ZAP1では記録されませんが、ZAP2ではきちんとindex.php - index2.php - index3.php という遷移が履歴に残るので、ログとして正確な記録がZAP2のほうには残ります。
・逆に、ZAP1の動的スキャンの診断結果(XSSが出た等)は、ZAP2のほうには表示されずログに記録もされないので、動的スキャンの結果に関してはZAP1のほうを確認する必要があります。
このように、ZAP+ZAPで、片方のZAPでログを取るという構成は機能的には問題なく成立しそうな感じでした。
ただ、複数のZAPが立ち上がっていると、どっちがどっちだか分からなくなり、混乱の結果ログ用のZAPで動的スキャンをしてしまったり、見るほうを間違えたり、という事故が起こりそうなので、ログ用ZAPを別PCで立てるなど、何か混乱を防ぐような工夫が必要と思われます。
以上、OWASP ZAPの多段プロキシ構成に関するテクニック紹介、および実験の記録でした。