【OWASP ZAP】認証設定 (3) HttpsenderやSeleniumスクリプトを認証に利用する方法

# はじめに
OWASP ZAPを使用し、ログインの必要なサイトに対して診断を実施してきました。
既存機能で対応できず、診断時にログインするためのスクリプトを作成してきたのですが、公式ドキュメントではわからない部分も多くあったので、実際にやってみてわかった部分を記事にまとめてみようと思います。
今回はHttpsender、Standalone、Seleniumのスクリプトの作成方法、利用例、スクリプト例を書きます。
次回以降、「Auto-Detect Authentication」やAutomation Framework、クロールしにくいサイトに対するSpider, Ajax Spider等を工夫して行う方法もまとめていきたいと考えています。

認証の共通設定項目やフォーム認証、ベーシック認証の設定方法については前回の記事を見てください。
[【OWASP ZAP】認証設定 (1) Form認証、ベーシック認証のわかりづらいところ](https://qiita.com/Shcb8Qyoj021/items/8da1a7663b77c7f5dad4)

ZAPの認証設定「Script-Based Authentication」と、認証に使用するスクリプトの作成方法や利用方法等については前回、記事を書きました。
[【OWASP ZAP】認証設定 (2) ログインスクリプトの作り方。Dockerでのスクリプト利用方法](https://qiita.com/Shcb8Qyoj021/items/2fa5377bb6105b9817fd)

# 目次
- Httpsenderスクリプト
    - 作成方法と使用方法
    - 利用例
        - フォーム認証＆ベーシック認証
- Stand aloneスクリプト
    - 作成方法と使用方法
- Seleniumスクリプト
    - 作成方法と使用方法
    - 利用例
- おわりに

# 動作環境
- ZAP GUI: 2.16.0
- Docker Image: zaproxy/zap-stable:20250110

# Httpsenderスクリプト
スパイダーやアクティブスキャン実行中に、Httpリクエスト送信・レスポンス受信する際に毎回実行されます。

## 作成方法と使用方法
作成は使用用途の近いcommunity-scriptsを参考にするとわかりやすいです。

[community-scripts/httpsender at main · zaproxy/community-scripts](https://github.com/zaproxy/community-scripts/tree/main/httpsender)

下記項目のいずれかまたは両方を作成します。
- function sendingRequest(msg, initiator, helper)
    httpリクエスト送信時に処理を実行する場合、この関数内に処理を記述します。
- function getReresponseReceived(msg, initiator, helper)
    httpレスポンス受信時に処理を実行する場合、この関数内に処理を記述します。

Httpsenderスクリプトは有効にすることで、Httpリクエスト送信時・レスポンス受信時にスクリプトが実行されるようになります。
スクリプトを有効にする方法は[前回の記事](https://qiita.com/Shcb8Qyoj021/items/2fa5377bb6105b9817fd)の「スクリプトの利用方法」を参照してください。


## 利用例
リクエスト送信時にスクリプトを実行できるので、認証用のヘッダーが必要なサイトに診断を実行するときに利用できます。
ベーシック認証のあるサイトやBearer Tokenが必要なサイトに対して診断が可能になります。

### フォーム認証＆ベーシック認証
ベーシック認証は、ZAPの認証機能で対応しているのですが、サイトによってはベーシック認証があり、そのサイト内でフォームログインも必要という場合があります。
ZAPの認証設定ではフォーム認証を設定し、Httpsenderスクリプトでベーシック認証用のヘッダーを設定することで、そうしたサイトにも診断が可能になります。

### スクリプトサンプル
下記スクリプトは毎回HTTPリクエスト送信時にベーシック認証用にAuthorization Headerを設定します。
実際に設定する値は、"Basic ~"の~の部分に「username:password」の文字列をBase64エンコードした文字列を設定します。

```js
var HttpHeader = Java.type('org.parosproxy.paros.network.HttpHeader');
var HttpSender = Java.type('org.parosproxy.paros.network.HttpSender');

function sendingRequest(msg, initiator, helper) {
    msg.getRequestHeader().setHeader("Authorization", "Basic xxxxxxxxxxxxxxxxxxxx");
    msg.getRequestHeader().setContentLength(msg.getRequestBody().length());
}

function responseReceived(msg, initiator, helper) {
}
```

# Standaloneスクリプト
Stand AloneスクリプトはGUIアプリであれば、スクリプトコンソール上で「実行」をクリックすることで実行でき、
APIであれば、「scriptActionRunStandAloneScript」をAPIリクエストすることで実行できます。

## 作成方法と使用方法
こちらも使用用途の近いcommunity-scriptsを参考にすると作成しやすいです。

[community-scripts/standalone at main · zaproxy/community-scripts](https://github.com/zaproxy/community-scripts/tree/main/standalone)

Stand Aloneスクリプトの場合は、認証スクリプトやHttpsenderスクリプトと違い、フォーマットに合わせて作成する必要はありません。

### GUI
Stand Aloneスクリプトはスクリプトを選択した状態でスクリプトコンソール上部に表示されている「実行」をクリックすることで、実行できます
### API
APIでStand Aloneスクリプトを実行する場合は、まずAPIでスクリプトをアップロードし、その後ロードする必要があります。（[前回記事参考]https://qiita.com/Shcb8Qyoj021/items/2fa5377bb6105b9817fd#api）
その後、[scriptActionRunStandAloneScript](https://www.zaproxy.org/docs/api/#scriptactionrunstandalonescript)のAPIを実行します。

下記サンプルはpythonのSDKを使用した場合のコードになります。
```py
import os
import requests
from zapv2 import ZAPv2


host = "" # zapのhostname/ip address
api_key = "" # zapのAPI Key
zap_url = f'http://{host}:8090'
proxies = {'http': zap_url, 'https': zap_url}
zap = ZAPv2(apikey=api_key, proxies=proxies)
local_filename = "" # uploadするscriptのファイル名
zap_filename = "" # upload後にzap側で保存されるファイル名
script_type = "standalone"
request_url = f"{zap_url}/OTHER/core/other/fileUpload/"
files = {
    'fileContents': ("", open(local_filename, "rb"), "multipart/form-data"),
    'apikey': (None, api_key),
    'fileName': (None, zap_filename)
}
request_headers = {'Accept': 'application/json', 'X-ZAP-API-Key': api_key}
requests.post(request_url, files=files, headers=request_headers)
script_engine = 'Graal.js'
script_filename = os.path.join("/zap/scripts", zap_filename)
zap.script.remove(scriptname=zap_filename)
response = zap.script.load(
    zap_filename, script_type,
    script_engine, script_filename
)
zap.script.run_stand_alone_script(scriptname=zap_filename)
```
### Automation Framework
Automation FrameworkではまだStand Aloneスクリプトを実行したことないのですが、下記のようなPlanファイルを作成することで実行できるようです。

```yaml
jobs:
  - type: script
    parameters:
      action: "add"
      type: "standalone"
      engine: "ECMAScript : Graal.js"
      name: "" # script名。自由に設定できる
      source: "" # scriptファイルのパス
  - type: script
    parameters:
      action: "run"
      type: "standalone"
      name: "" # action add時に設定したscript名
```
# Seleniumスクリプト
ZAPでSeleniumを使用して、ブラウザを起動するときに実行されるスクリプトです。
ajax spider実行時やZAPからブラウザを起動して「manual browsing」を行うときに実行されます。

## 作成方法と使用方法
作成や使用方法はcommunity-scriptが参考になります。


[community-scripts/selenium at main · zaproxy/community-scripts](https://github.com/zaproxy/community-scripts/tree/main/selenium)

下記項目を作成します。
function browserLaunched(ssutils) {
}

ssutilsを利用することで、web driverを利用したり、そのときアクセスするURLを取得できます。
```js
var wd = ssutils.getWebDriver();
var url = ssutils.waitForURL(5000);
```

## 利用例
community-scriptsではMFAでワンタイムパスワードを利用する場合や、local storageにtokenをセットするのに使用しています。
local storageにセットするのはcommunity-scriptの下記部分を参考にして実装できます。
```js
var wd = ssutils.getWebDriver();
var script =
  "document.cookie = 'token=" +
  token +
  "';\n" +
  "window.localStorage.setItem('token', '" +
  token +
  "');";
wd.executeScript(script);
```

# おわりに
今回はHttpsenderスクリプト、Stand aloneスクリプトとSeleniumスクリプトについて、作成方法や利用例等をご紹介しました。
他の利用方法等ありましたら、教えていただきたいです。次回以降、「Auto-Detect Authentication」やAutomation Framework、クロールしにくいサイトに対するSpider, Ajax Spider等を工夫して行う方法もまとめていきたいと考えています。