# 技術的な仕様

## 診断の仕組み

Shisho Cloud は、登録されたそれぞれの[**シナリオ**](#scenario)に対して擬似攻撃を送信し、レスポンスを精査して脆弱性を検出します。
シナリオは、依存関係を持つ**エンドポイント**のシーケンスです。

アプリケーションによっては、リクエストに認証ヘッダーやログイン後の Cookie など特定の情報を付与する必要がある場合があります。
[**認証設定**](#認証設定の-yaml-リファレンス)を登録することにより、巡回や診断でリクエストを送信する際に付与すべきヘッダー等を指定できます。
付与するヘッダー等の値は固定値だけでなく、ログイン操作を表すシナリオをリプレイして動的に抽出することもできます。

場合によっては、アプリケーションの一部機能を診断の対象外にしたいこともあるでしょう。
そのような場合には、[**スコープ**](#scope)を適切に設定することにより、アプリケーションの特定部分のみを診断することができます。

以降のセクションでは、それぞれの概念をより詳しく説明します。

## スコープ {#scope}

スコープは、アプリケーションの診断対象とする範囲を定義します。

デフォルトの入力方式では、URL の prefix を指定すると、その prefix から始まる URL が診断対象になります。
「スコープを追加」ボタンをクリックして、複数の prefix を指定することもできます。

![スコープ設定](/docs/ja/_md-assets/bf0ef76881-scope-input-default-mode.png)

トグルをクリックして上級者モードに切り替えると、prefix の代わりに正規表現を用いて、診断対象とする URL の範囲を定義できます。

![スコープ設定](/docs/ja/_md-assets/6f4bc45636-scope-input-advanced-mode.png)

## シナリオ {#scenario}

シナリオは、診断の単位であり、依存関係のあるエンドポイントのシーケンスを表します。例えば以下のようなリクエストの流れをシナリオとして表現でき、エンドポイントの関係性を踏まえた診断が可能となります。

- 入力画面の CSRF トークンを抽出し、それを付与して POST リクエストを送信する
- アイテムを削除するリクエストを送信する前に、アイテムを作成するリクエストを送信する
- アイテムの作成リクエストを送信した後に、アイテム情報が表示される画面をリクエストし、蓄積型の脆弱性を検出する

シナリオは1つ以上の**ステップ**から構成され、1つ1つのステップはリクエストを送信するエンドポイントを指します。

例えば以下の2ステップからなるシナリオは、アイテムを作成した後にアイテム一覧画面をリクエストするものです。これにより、アイテム作成に起因する蓄積型の脆弱性を検出することができます。

![](/docs/ja/_md-assets/cd24cecfbd-scenario-simple.png)

各ステップの YAML 入力欄には以下の詳細オプションを記述できます。

- ステップ間の入出力の関係を表すもの
  - `extractors`: リクエストの結果から値を抽出し、変数に格納して後続のステップで使えるようにする
  - `bindings`: 以前のステップの `extractors` で変数に格納した値を、このステップのリクエストパラメータに注入する
- 診断での取り扱いを調節するもの
  - `skipInjection`: そのステップに攻撃ペイロードを注入しない

![](/docs/ja/_md-assets/a8877489f7-scenario-step-yaml.png)

### `extractors`

リクエストの結果から値を抽出し、変数に格納して後続のステップで使えるようにする設定項目であり、後述する [**Extractor オブジェクト**](#extractor)の配列を指定します。配列の要素として複数の Extractor オブジェクトを指定すれば、複数の情報を抽出してそれぞれ変数に格納できます。

YAML 入力欄に例えば以下を入力すると、レスポンスボディに対して正規表現 `token=([a-z0-9]+)` によるマッチが行われ、括弧で囲まれた第1グループである `([a-z0-9]+)` にマッチした部分が `name` で指定された名前である変数 `token` に格納されます。

```yaml
extractors:
  - name: token
    type: regex
    part: body
    regex:
      - "token=([a-z0-9]+)"
    group: 1
```

#### Extractor オブジェクト {#extractor}

Extractor オブジェクトは、リクエストの結果から値を抽出する方法と、抽出した値を格納する変数名を定義します。

基本的な構造は以下の通りです。`name` にて指定した変数名は、後続するステップの `bindings` で用いられます。`type` には抽出方法のタイプを指定し、さらに抽出方法ごとの設定項目が続きます。

```yaml
name: "<格納先の変数名>"
type: "<抽出方法のタイプ>"
# ... <抽出方法に応じた設定> ...
```

抽出方法のタイプには以下のいずれかを指定します。

- `regex`: 正規表現を用いて文字列から値を抽出
- `xpath`: [XPath](https://developer.mozilla.org/en-US/docs/Web/XPath) を用いて HTML・XML 内の値を抽出
- `json`: [jq](https://jqlang.github.io/jq/) クエリを用いて JSON 内の値を抽出
- `cookie`: レスポンスからすべての Cookie を抽出

##### `regex` Extractor

正規表現を用いて値を抽出する Extractor です。

設定項目:

- `regex`: 抽出に用いる正規表現の配列
  - 構文については [RE2 のドキュメント](https://github.com/google/re2/wiki/Syntax)を参照してください
- `group`: 値を抽出するキャプチャグループの番号
  - デフォルト: `0` (マッチの全体)
- `part`: 正規表現でマッチする対象
  - `all`: レスポンス全体 (デフォルト)
  - `body`: レスポンスのボディ
  - `header`: レスポンスのヘッダー

例えば以下の Extractor は、レスポンスのヘッダーに対してマッチを行い、1番目のグループ、つまり `Location` ヘッダーの値の部分を `redirect_url` という変数に格納します。

```yaml
name: redirect_url
type: regex
part: header
regex:
  - "(?m)^Location: (.+?)$"
group: 1
```

##### `xpath` Extractor

[XPath](https://developer.mozilla.org/en-US/docs/Web/XPath) を用いて HTML や XML から値を抽出する Extractor です。

設定項目:

- `xpath`: 抽出に用いる XPath の配列
- `attribute`: 抽出する属性名
  - 未指定の場合: ノードのテキスト

例えば以下の Extractor は、1番目の `<form>` 要素の内部にある `<input name="csrf_token" value="...">` のような要素を検索し、その `value` 属性の値を `csrf_token` という変数に格納します。

```yaml
name: csrf_token
type: xpath
xpath:
  - '(//form)[1]//input[@name="csrf_token"]'
attribute: value
```

目的の要素を指す XPath は、[ブラウザの開発者ツールを用いて簡単に取得](https://devtoolstips.org/tips/en/copy-element-xpath/)できます。詳細な手順はブラウザごとに異なりますが、開発者ツール上で目的の要素を右クリックし、「Copy XPath」のようなメニューを選択すると、その要素を指す XPath がクリップボードにコピーされます。また、開発者ツールのコンソールでは、 `$x('//form[1]//input[@name="csrf_token"]')` のように `$x` 関数を用いて、XPath による検索結果を確認できます。

##### `json` Extractor

[jq](https://jqlang.github.io/jq/) クエリを用いて JSON から値を抽出する Extractor です。

設定項目:

- `json`: 抽出に用いる jq クエリの配列

例えば以下の Extractor は、レスポンスボディが `{"item": {"id": "XXXX", ...}}` のようなデータである場合に、 `XXXX` の部分を抽出して `item_id` という変数に格納します。

```yaml
type: json
name: item_id
json:
  - ".item.id"
```

#### `cookie` Extractor

レスポンスからすべての Cookie を抽出する Extractor です。

設定項目:

- なし

例えば以下の Extractor は、レスポンスヘッダーから `Set-Cookie: key=value` のような文字列を検索し、すべての `key=value` を `;` で連結した文字列を `cookies` という変数に格納します。

```yaml
type: cookie
name: cookies
```

たとえば、レスポンスヘッダーが以下のような場合、`cookies` には `cookie1=XXXX; cookie2=YYYY` という文字列が格納されます。

```http
Set-Cookie: cookie1=XXXX; ...
Set-Cookie: cookie2=YYYY; ...
```

### `bindings` {#bindings}

以前のステップの `extractors` で変数に格納した値を、このステップのリクエストパラメータに注入する設定項目です。

例えば以下の設定は、以前のステップで `csrf_token` という変数に格納された CSRF トークンを、リクエストボディの `.csrf_token` というパラメータに注入します。

```yaml
bindings:
  # extractorName を使う場合
  - type: body # 注入するパラメータのタイプ
    key: .csrf_token # 注入先
    extractorName: csrf_token # 注入する値を格納した変数名

  # template を使う場合
  - type: body
    key: .csrf_token
    template: "{{ .csrf_token }}" # 注入する値を指定したテンプレート
```

`type` には `pathParam`, `header`, `query`, `body` のいずれかを指定します。

`key` には注入先のパラメータを指定します。
タイプが `pathParam`, `header`, `query` の場合は、パラメータ名をそのまま指定します。例えばヘッダー `Custom-Header: XXXX` に注入したい場合は、`key: 'Custom-Header'` と指定します。
タイプが `body` の場合は、プロパティ名をドットで繋いで注入したい箇所を指定します。例えば、リクエストボディが `{"item": {"id": "XXXX", ...}}` のような JSON である場合に、`key: '.item.id'` と指定すると、`XXXX` の部分に値が注入されます。

`extractorName` には、以前のステップの `extractors` において、`name` フィールドに記載した変数名を指定します。

`template` には、変数を指定したテンプレートを記述します。テンプレート内では、変数を `{{ .変数名 }}` のように記述することができます。もちろん、`{{ ... }}` の前後に文字列を追加することで、変数の値を他の文字列と組み合わせることもできます。たとえば、`template: "Bearer {{ .token }}"` とすることで、`token` という変数の値を `Bearer XXXX` のような文字列に変換した上で注入することができます。

:::info

変数名は、半角英数字とアンダースコアのみを使用してください。ただし、変数名の先頭に数字を使用することはできません。

たとえば、`token` や `my_token`、`MyToken` などの変数名は有効です。

一方で、`123token` や `my-token` などの変数名は無効です。

:::

### `skipInjection`

あるステップに `skipInjection: true` と設定すると、診断時にそのステップに攻撃ペイロードが注入されなくなります。
例えば蓄積型の脆弱性を検出するために、1番目のステップで `POST /items/new` に攻撃リクエストを送信し、2番目のステップで `GET /items` をリクエストしてレスポンスに問題がないか検査するシナリオを定義したいとします。この目的のもとでは、攻撃ペイロードは1番目のステップだけに注入したいですから、2番目のステップには `skipInjection: true` を設定します。

## 認証設定の YAML リファレンス

#### `metadata`

`metadata` では認証設定のメタデータを指定します。
`description` には認証設定の説明を記入してください。ここに設定された内容は、一覧画面にてタイトルとして表示されます。
なお、`id` は自動的に生成されるため、変更する必要はありません。

```yaml
metadata:
  id: 2359978...
  description: "Cookie 用の認証設定"
```

#### `.spec.items`

`.spec.items` では、その認証設定で管理するデータ自体を定義します。
当該フィールドの要素に指定するオブジェクトには、以下のフィールドを指定してください。

- `name`: 認証情報の名前
- `type`: その認証情報が注入するパラメータのタイプ (`header`, `query`, `body`)
- `key`: その認証情報を注入するパラメータのキー名 (例: `Cookie`)
- `template`: その認証情報に注入する値を指定したテンプレート
- `value`: 認証情報の値 (静的に設定するとき以外は空値に設定してください)

以下に、リクエスト時に `Cookie` ヘッダーに注入すべき値を `cookie` という名前で定義する例を示します。

```yaml
spec:
  items:
    - name: cookie
      type: header
      key: Cookie
      value: ""
```

#### `.spec.satisfierSpec.steps`

`.spec.satisfierSpec.steps` では、認証処理のステップを定義します。
当該フィールドの要素に指定するオブジェクトには、以下のフィールドを指定してください。

- `type`: ステップの種別
  - `request`: リクエストを送信するステップ
  - `firebase`: Firebase 認証を行うステップ
  - `cognito`: Cognito 認証を行うステップ
  - `auth0`: Auth0 認証を行うステップ
- `spec`: ステップの具体的な設定

ここで、`spec` に指定すべきオブジェクトのスキーマは、`type` で指定された種別によって異なります。

`type` が `request` の場合は、以下のフィールドを指定することでそのステップで送信するリクエストの詳細を設定してください。

```yaml
spec:
  satisfierSpec:
    steps:
      - type: request
        spec:
          # HTTP メソッド
          method: POST

          # リクエストを送信する Origin (スキーム、ホスト、ポートの組み合わせ)
          origin: https://example.test

          # リクエストを送信するパス
          path: /api/v1/

          # リクエストヘッダー
          headers:
            - key: Authorization
              value: Bearer 123456789

          # クエリパラメータ
          queries:
            - key: id
              value: 1

          # リクエストボディ
          body:
            contentType: application/json
            parameter: { ... }

          # ステップ間で伝播させるパラメータのための Extractor の設定
          # 仕様は Extractor オブジェクトと同一
          extractors:
            - { ... }

          # あるステップで抽出したパラメータをこのステップで利用するための設定
          # 仕様は bindings と同一
          bindings:
            - { ... }
```

ここで、リクエストボディを設定する場合は、以下の内容を参考に `body` フィールドを設定してください。

:::info リクエストボディの指定方法

`body` フィールドには、以下のフィールドを指定してください。

- `contentType`: リクエストボディの Content-Type (`application/json`, `application/x-www-form-urlencoded` など)
- `parameter`: リクエストボディの内容

`parameter` には、リクエストボディの内容を指定してください。以下に、当該フィールドの仕様を示します。

- `type`: パラメータのタイプ (`object`, `array`, `string` など)
- `name`: パラメータの名前
- `value`: パラメータの値
- `properties`: そのパラメータが持つ子パラメータ
  - `type` が `object` の場合に指定
  - key-value 形式で、key は子要素の名前、value は `parameter` と同じ形式
- `items`: そのパラメータが持つ子パラメータ
  - `type` が `array` の場合に指定
  - 配列形式で、各要素は `parameter` と同じ形式

たとえば、リクエストボディが `{"item": {"id": "12345", "roles": ["admin"]}}` のような JSON である場合には、以下のように設定してください。

```yaml
body:
  contentType: application/json
  parameter:
    type: object
    properties:
      item:
        type: object
        name: item
        properties:
          id:
            type: string
            name: id
            value: "12345"
          roles:
            type: array
            name: roles
            items:
              - type: string
                value: admin
```

なお、[bindings](#bindings) を用いることで、特定の要素の値を動的に変更することができます。

:::

`type` が `firebase`, `cognito`, `auth0` の場合は、以下のフィールドを指定することでそのステップで認証処理を行うための詳細を設定してください。

```yaml
# Firebase 認証の場合
spec:
  satisfierSpec:
    steps:
      - type: firebase
        email: test@example.test
        password: testpassword
        apiKey: testapikey
        extractors:
          - {...}

# Cognito 認証の場合
spec:
  satisfierSpec:
    steps:
      - type: cognito
        email: test@example.test
        password: testpassword
        region: ap-northeast-1
        clientId: testclientid
        extractors:
          - {...}

# Auth0 認証の場合
spec:
  satisfierSpec:
    steps:
      - type: auth0
        email: test@example.test
        password: testpassword
        domain: example.test
        clientId: testclientid
        clientSecret: testclientsecret
        callbackUrl: https://example.test/callback
        extractors:
          - {...}
```

#### `.spec.satisfierSpec.extractors`

`.spec.satisfierSpec.extractors` では、各ステップで取得したすべてのレスポンスを結合した結果に対して実行する Extractor の設定を行います。Extractor の記法は [**Extractor オブジェクト**](#extractor) と同様です。

#### Extractor で抽出した値を Item に注入する

Extractor で抽出した値を Item に注入するためには、以下のいずれかの方法が利用できます。

1つ目は、**Item の Template 機能を利用する方法**です。

Item の `template` フィールドに、`{{ .変数名 }}` のようなテンプレートを記述することで、変数名が自身の `name` の値と一致する Extractor によって抽出された値を注入することができます。もちろん、`{{ ... }}` の前後に文字列を追加することで、変数の値を他の文字列と組み合わせることもできます。例えば、以下の例では `cookie` という名前の Extractor によって抽出された値を、`{{ .cookie }}` というテンプレートを用いて注入しています。

```yaml
spec:
  items:
    - type: header
      key: Cookie
      template: "{{ .cookie }}"
  satisfierSpec:
    steps:
      - type: ...
        spec:
          {...}
          extractors:
            - name: cookie
              type: regex
              part: header
              group: 1
              regex:
                - "Set-Cookie: (SESSID=[a-zA-Z0-9]+)"
```

2つ目は、**Extractor の名前と Item の名前を一致させる方法**です。

ある Extractor の `name` の値が、ある Item の `name` の値と一致する場合、その Extractor によって抽出された値は、その Item に注入されます。例えば、以下の例では `cookie` という名前の Extractor によって抽出された値は、`items` に定義された `cookie` という名前の Item に注入されます。

```yaml
spec:
  items:
    - name: cookie # Item の名前
      type: header
      key: Cookie
      value: ""
  satisfierSpec:
    steps:
      - type: ...
        spec:
          {...}
          extractors:
            - name: cookie # Extractor の名前
              type: regex
              part: header
              group: 1
              regex:
                - "Set-Cookie: (SESSID=[a-zA-Z0-9]+)"
```

#### `.spec.validatorSpecs`

`.spec.validatorSpecs` では、認証設定を更新するトリガーとなる条件を指定します。

この設定はエディターを開いた時点ですでに設定されていますが、更新の間隔を変更したい場合にのみ、`expiresIn` の値を変更してください。なお、当該フィールドには Go 言語において `time.Duration` として解釈可能な値を指定してください。

```yaml
spec:
  validatorSpecs:
    - type: BLANK
      config: {}
    - type: EXPIRE
      config:
        expiresIn: 10m
```
