Web アプリケーション診断
診断の仕組み
Shisho Cloud は、登録されたそれぞれのシナリオに対して擬似攻撃を送信し、レスポンスを精査して脆弱性を検出します。 シナリオは、依存関係を持つエンドポイントのシーケンスです。
アプリケーションによっては、リクエストに認証ヘッダーやログイン後の Cookie など特定の情報を付与する必要がある場合があります。 認証設定を登録することにより、巡回や診断でリクエストを送信する際に付与すべきヘッダー等を指定できます。 付与するヘッダー等の値は固定値だけでなく、ログイン操作を表すシナリオをリプレイして動的に抽出することもできます。
場合によっては、アプリケーションの一部機能を診断の対象外にしたいこともあるでしょう。 そのような場合には、スコープを適切に設定することにより、アプリケーションの特定部分のみを診断することができます。
以降のセクションでは、それぞれの概念をより詳しく説明します。
スコープ
スコープは、アプリケーションの診断対象とする範囲を定義します。
デフォルトの入力方式では、URL の prefix を指定すると、その prefix から始まる URL が診断対象になります。 「スコープを追加」ボタンをクリックして、複数の prefix を指定することもできます。
トグルをクリックして上級者モードに切り替えると、prefix の代わりに正規表現を用いて、診断対象とする URL の範囲を定義できます。
シナリオ
シナリオは、診断の単位であり、依存関係のあるエンドポイントのシーケンスを表します。例えば以下のようなリクエストの流れをシナリオとして表現でき、エンドポイントの関係性を踏まえた診断が可能となります。
- 入力画面の CSRF トークンを抽出し、それを付与して POST リクエストを送信する
- アイテムを削除するリクエストを送信する前に、アイテムを作成するリクエストを送信する
- アイテムの作成リクエストを送信した後に、アイテム情報が表示される画面をリクエストし、蓄積型の脆弱性を検出する
シナリオは1つ以上のステップから構成され、1つ1つのステップはリクエストを送信するエンドポイントを指します。
例えば以下の2ステップからなるシナリオは、アイテムを作成した後にアイテム一覧画面をリクエストするものです。これにより、アイテム作成に起因する蓄積型の脆弱性を検出することができます。
各ステップの YAML 入力欄には以下の詳細オプションを記述できます。
- ステップ間の入出力の関係を表すもの
extractors
: リクエストの結果から値を抽出し、変数に格納して後続のステップで使えるようにするbindings
: 以前のステップのextractors
で変数に格納した値を、このステップのリクエストパラメータに注入する
- 診断での取り扱いを調節するもの
skipInjection
: そのステップに攻撃ペイロードを注入しない
extractors
リクエストの結果から値を抽出し、変数に格納して後続のステップで使えるようにする設定項目であり、後述する Extractor オブジェクトの配列を指定します。配列の要素として複数の Extractor オブジェクトを指定すれば、複数の情報を抽出してそれぞれ変数に格納できます。
YAML 入力欄に例えば以下を入力すると、レスポンスボディに対して正規表現 token=([a-z0-9]+)
によるマッチが行われ、括弧で囲まれた第1グループである ([a-z0-9]+)
にマッチした部分が name
で指定された名前である変数 token
に格納されます。
extractors:
- name: token
type: regex
part: body
regex:
- "token=([a-z0-9]+)"
group: 1
Extractor オブジェクト
Extractor オブジェクトは、リクエストの結果から値を抽出する方法と、抽出した値を格納する変数名を定義します。
基本的な構造は以下の通りです。name
にて指定した変数名は、後続するステップの bindings
で用いられます。type
には抽出方法のタイプを指定し、さらに抽出方法ごとの設定項目が続きます。
name: "<格納先の変数名>"
type: "<抽出方法のタイプ>"
# ... <抽出方法に応じた設定> ...
抽出方法のタイプには以下のいずれかを指定します。
regex
: 正規表現を用いて文字列から値を抽出xpath
: XPath を用いて HTML・XML 内の値を抽出json
: jq クエリを用いて JSON 内の値を抽出cookie
: レスポンスからすべての Cookie を抽出
regex
Extractor
正規表現を用いて値を抽出する Extractor です。
設定項目:
regex
: 抽出に用いる正規表現の配列- 構文については RE2 のドキュメントを参照してください
group
: 値を抽出するキャプチャグループの番号- デフォルト:
0
(マッチの全体)
- デフォルト:
part
: 正規表現でマッチする対象all
: レスポンス全体 (デフォルト)body
: レスポンスのボディheader
: レスポンスのヘッダー
例えば以下の Extractor は、レスポンスのヘッダーに対してマッチを行い、1番目のグループ、つまり Location
ヘッダーの値の部分を redirect_url
という変数に格納します。
name: redirect_url
type: regex
part: header
regex:
- "(?m)^Location: (.+?)$"
group: 1
xpath
Extractor
XPath を用いて HTML や XML から値を抽出する Extractor です。
設定項目:
xpath
: 抽出に用いる XPath の配列attribute
: 抽出する属性名- 未指定の場合: ノードのテキスト
例えば以下の Extractor は、1番目の <form>
要素の内部にある <input name="csrf_token" value="...">
のような要素を検索し、その value
属性の値を csrf_token
という変数に格納します。
name: csrf_token
type: xpath
xpath:
- '(//form)[1]//input[@name="csrf_token"]'
attribute: value
目的の要素を指す XPath は、ブラウザの開発者ツールを用いて簡単に取得できます。詳細な手順はブラウザごとに異なりますが、開発者ツール上で目的の要素を右クリックし、「Copy XPath」のようなメニューを選択すると、その要素を指す XPath がクリップボードにコピーされます。また、開発者ツールのコンソールでは、 $x('//form[1]//input[@name="csrf_token"]')
のように $x
関数を用いて、XPath による検索結果を確認できます。
json
Extractor
jq クエリを用いて JSON から値を抽出する Extractor です。
設定項目:
json
: 抽出に用いる jq クエリの配列
例えば以下の Extractor は、レスポンスボディが {"item": {"id": "XXXX", ...}}
のようなデータである場合に、 XXXX
の部分を抽出して item_id
という変数に格納します。
type: json
name: item_id
json:
- ".item.id"
cookie
Extractor
レスポンスからすべての Cookie を抽出する Extractor です。
設定項目:
- なし
例えば以下の Extractor は、レスポンスヘッダーから Set-Cookie: key=value
のような文字列を検索し、すべての key=value
を ;
で連結した文字列を cookies
という変数に格納します。
type: cookie
name: cookies
たとえば、レスポンスヘッダーが以下のような場合、cookies
には cookie1=XXXX; cookie2=YYYY
という文字列が格納されます。
Set-Cookie: cookie1=XXXX; ...
Set-Cookie: cookie2=YYYY; ...
bindings
以前のステップの extractors
で変数に格納した値を、このステップのリクエストパラメータに注入する設定項目です。
例えば以下の設定は、以前のステップで csrf_token
という変数に格納された CSRF トークンを、リクエストボディの .csrf_token
というパラメータに注入します。
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
のような文字列に変換した上で注入することができます。
変数名は、半角英数字とアンダースコアのみを使用してください。ただし、変数名の先頭に数字を使用することはできません。
たとえば、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
は自動的に生成されるため、変更する必要はありません。
metadata:
id: 2359978...
description: "Cookie 用の認証設定"
.spec.items
.spec.items
では、その認証設定で管理するデータ自体を定義します。
当該フィールドの要素に指定するオブジェクトには、以下のフィールドを指定してください。
name
: 認証情報の名前type
: その認証情報が注入するパラメータのタイプ (header
,query
,body
)key
: その認証情報を注入するパラメータのキー名 (例:Cookie
)template
: その認証情報に注入する値を指定したテンプレートvalue
: 認証情報の値 (静的に設定するとき以外は空値に設定してください)
以下に、リクエスト時に Cookie
ヘッダーに注入すべき値を cookie
という名前で定義する例を示します。
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
の場合は、以下のフィールドを指定することでそのステップで送信するリクエストの詳細を設定してください。
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
フィールドを設定してください。
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 である場合には、以下のように設定してください。
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 を用いることで、特定の要素の値を動的に変更することができます。
type
が firebase
, cognito
, auth0
の場合は、以下のフィールドを指定することでそのステップで認証処理を行うための詳細を設定してください。
# 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 で抽出した値を Item に注入する
Extractor で抽出した値を Item に注入するためには、以下のいずれかの方法が利用できます。
1つ目は、Item の Template 機能を利用する方法です。
Item の template
フィールドに、{{ .変数名 }}
のようなテンプレートを記述することで、変数名が自身の name
の値と一致する Extractor によって抽出された値を注入することができます。もちろん、{{ ... }}
の前後に文字列を追加することで、変数の値を他の文字列と組み合わせることもできます。例えば、以下の例では cookie
という名前の Extractor によって抽出された値を、{{ .cookie }}
というテンプレートを用いて注入しています。
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 に注入されます。
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
として解釈可能な値を指定してください。
spec:
validatorSpecs:
- type: BLANK
config: {}
- type: EXPIRE
config:
expiresIn: 10m