本記事は「Kong Advent Calendar 2024」の7日目のエントリとして、Request Validator Pluginについて解説する。
APIセキュリティを考える際にデファクトの1つとして使われるのはOWASP API Security Top 10だが、この中で設計者・運用者が意図しないパスやパラメータへのアクセスについて言及されているものがいくつかある。
これはKong Gatewayを使っている場合、Routeを適切かつ厳格に設定していればある程度防げたりもするのだが、更にしっかり防ぎたい人にオススメなのがRequest Validator Pluginである。
このプラグインを使うと、リクエストの本文のスキーマやパラメータの検証を実施し、意図しないリクエストであればアップストリームに転送せずに弾くようなことが出来る。
今回はこのプラグインの使い方や効果について簡単に検証する。
Request Validatorの設定項目
各項目の意味をConfiguration referenceから確認する。
パラメータ | 意味 |
---|---|
Body Schema(body_schema ) |
検証対象がリクエストボディの場合のスキーマの指定。このbody_schema か後述のparameter_schema のどちらかの指定は必須。例){"name":{"type": "string", "required": true}}
|
Content Type Parameter Validation(content_type_parameter_validation ) |
メディアタイプ(Content-Type)を検証するかどうか |
Parameter Schema(parameter_schema ) |
検証対象がパラメータの場合の各種設定やスキーマの指定 |
- in
|
検証対象がどこにいるか(query 、header 、path から指定) |
- name
|
パラメータ名 |
- required
|
ここで設定するパラメータがリクエスト内に必須かどうか |
- style
|
パラメータをチェックする際のデシリアライズの方法。設定値に関しては後述 |
- explode
|
パラメータの指定の仕方を指定。trueだとkey=value1&key=value2のようにkeyとvalueを1対1の形で渡すことが前提となるが、falseだとkey=value1,value2のようにvalueを文字列でまとめて渡す形になる |
- schema
|
パラメータのスキーマを指定例){ "type": "array", "items": {"type": "string"}}
|
Verbose Response(verbose_response ) |
エラー時に詳細メッセージを出すかどうか |
Version(version ) |
バリデータの選択。kong (Kong独自のもの)かdraft4 (JSON Schema Draft-4互換)が選択可能 |
Allowed Content Types(allowed_content_types ) |
ボディのスキーマチェック時、許可されるメディアタイプを指定。メディアタイプで許可する値についてはcharset を併せて指定する。(例:application/json; charset=UTF-8 ) |
parameter_schema.style
で指定できるデシリアライズの方法はKongのドキュメントには記載がないが、OpenAPI Sepcificationで言及されているのでこれが少し参考になると思う(RFCに飛ばされるものも多いが)。
恐らく以下のような感じになる(未検証)。
-
query
-
form
:キーと値のペアとしてエンコード -
spaceDelimited
:スペースで区切られた値としてエンコード -
pipeDelimited
:パイプ(|)で区切られた値としてエンコード -
deepObject
:オブジェクトのネストされた構造を表現するために使用
-
-
path
-
simple
:そのまま利用 -
label
:ドット(.)で区切られた形式でエンコード -
matrix
:セミコロン(;)で区切られた形式でエンコード
-
-
header
-
simple
:そのまま利用
-
検証の前提
以下の環境で確認する。
- Kong Gatewayはlocalhost上に構築済み(Docker)
- 検証用のアップストリームはKong提供のhttpbin(https://httpbin.konghq.com)を使用し、Proxyの
/httpbin
からアクセス出来るようにする
検証
Request Validatorを検証するにあたり、ServiceとRouteを作成する。
(設定内容の説明は割愛)
curl -s -X POST localhost:8001/services \
-H "Content-Type: application/json" \
-d '{
"name":"httpbin-svc",
"url":"https://httpbin.konghq.com"
}'
curl -s -X POST localhost:8001/services/httpbin-svc/routes \
-H "Content-Type: application/json" \
-d '{
"name":"httpbin-rt",
"paths":["/httpbin"]
}'
作成したRouteに対してRequest Validator Pluginを設定する。
なお、今回設定は全てUIから行ったが、APIやdeckからも設定可能。
検証1: クエリtags
に数値のみ指定されていることをチェック
特定のパラメータ(tags
)に絞って検証するので、parameter_schema
を使う。
以下で設定する。
-
verbose_response
:true
-
parameter_schema.in
:query
-
parameter_schema.name
:tags
-
parameter_schema.style
:form
-
parameter_schema.explode
:true
-
parameter_schema.schema
: 以下のスキーマを入力する
{
"type": "array",
"items": {
"type": "integer"
}
}
実際に動作させてみる。
クエリとしてtagsに文字列を指定し、他のクエリの影響も確認するためhoge=fuga
というクエリもセットで渡してみる。
$ curl "localhost:8000/httpbin/anything?tags=aaa&hoge=fuga"
{"message":"query 'tags' validation failed, [error] failed to validate item 1: wrong type: expected integer, got string","data":["aaa"]}
数値を期待しているのに文字列が与えられているためエラーとなった。
ちなみにverbose_response
を指定しない場合は以下となる。
{"message":"request param doesn't conform to schema"}
verbose_response
をつけると攻撃者にヒントを与えることになるので、実運用では外した方がよい。
次に数値を渡してみる。
$ curl "localhost:8000/httpbin/anything?tags=123&hoge=fuga"
{
"args": {
"hoge": "fuga",
"tags": "123"
},
"data": "",
これだと上手くいった。
検証2: ボディに許可されていないkeyを含む場合にエラーを返す
例えば以下のようなデータを受け取るアップストリームがあるとし、name
のみ必須でid
は任意、それ以外のパラメータは受け付けないようにしたいとする。
{
"name": 文字列,
"id": 数値
}
その場合、以下のような感じで設定する。
-
verbose_response
:true
-
content_type_parameter_validation
:true
-
version
:draft4
-
allowed_content_types
:application/json
-
body_schema
: 以下のスキーマ
{
"type": "object",
"properties": {
"id": {
"type": "integer",
"minimum": 1
},
"name": {
"type": "string",
"minLength": 1
}
},
"required": ["name"],
"additionalProperties": false
}
id
とname
は渡すことが可能で、必須パラメータを"required": ["name"]
で指定し、"additionalProperties": false
で他のパラメータを禁止している。
検証してみる。
最初に以下のような通って欲しいリクエストを試してみる。
$ curl "localhost:8000/httpbin/anything" \
-H "Content-type: application/json" \
-d '{
"name": "test",
"id": 123
}'
実行結果は以下となり、正常にリクエストがアップストリームに到達した。
{
"args": {},
"data": "{\n \"name\": \"test\",\n \"id\": 123\n }",
:(省略)
"json": {
"id": 123,
"name": "test"
},
:(省略)
次にリクエストをガードして欲しいケースを試してみる。
悪意のあるユーザがパラメータに当たりをつけてadmin: true
なんかを指定したとする。
$ curl "localhost:8000/httpbin/anything" \
-H "Content-type: application/json" \
-d '{
"name": "test",
"id": 123,
"admin": true
}'
結果は以下のようになり、追加のパラメータは禁止されていることが確認できる。
{"message":"additional properties forbidden, found admin"}
なお、必須パラメータを指定しなかった場合の結果は以下。
$ curl "localhost:8000/httpbin/anything" \
-H "Content-type: application/json" \
-d '{
"id": 123
}'
{"message":"property name is required"}
こちらも期待通りの結果となった。
まとめ
ということで、Request Validatorを使えば意図しないリクエストを防いだり、意図しない値をアップストリームに渡すのを防げることが確認できた。
ポイントとしてはガードするためのスキーマを如何に書くか、というところになりそう。
これが適切に用意できないとプラグインがあってもセキュリティは向上できないので、API設計時のスキーマ定義はしっかりやっておいた方がよさそうだ。