4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

KongAdvent Calendar 2024

Day 7

Request Validator PluginでAPIリクエストを事前検証する

Last updated at Posted at 2024-12-06

本記事は「Kong Advent Calendar 2024」の7日目のエントリとして、Request Validator Pluginについて解説する。

APIセキュリティを考える際にデファクトの1つとして使われるのはOWASP API Security Top 10だが、この中で設計者・運用者が意図しないパスやパラメータへのアクセスについて言及されているものがいくつかある。
これはKong Gatewayを使っている場合、Routeを適切かつ厳格に設定していればある程度防げたりもするのだが、更にしっかり防ぎたい人にオススメなのがRequest Validator Pluginである。
このプラグインを使うと、リクエストの本文のスキーマやパラメータの検証を実施し、意図しないリクエストであればアップストリームに転送せずに弾くようなことが出来る。
今回はこのプラグインの使い方や効果について簡単に検証する。

Request Validatorの設定項目

Pluginの設定画面は以下のようになっている。
20241120095646.png

各項目の意味を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 検証対象がどこにいるか(queryheaderpathから指定)
- 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
 }

idnameは渡すことが可能で、必須パラメータを"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設計時のスキーマ定義はしっかりやっておいた方がよさそうだ。

4
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?