Kong API Gateway向けセキュリティPlugin, sasankaをOSSで公開しました



![sasanka_logo_noclear.ai.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3456674/1b1d2fbb-6300-1e5c-8fee-5fdb6d0e34b0.png)

# はじめに

Kong API Gatewayに対してセキュリティ機能を追加するpluginであるsasankaをOSSで公開しました。
Kongと合わせてAPI Gateway, API Protection機能をOSSで利用することができます。

https://github.com/cybersecuritycloud/sasanka

LICENSEはApache2.0を採用しており、自由に使用可能です。
12個のPluginを公開しており、それぞれが独立したPluginであるため利用者はそのうちから必要なものを選択して使用することが可能です。


sasankaはKong API Gatewayにセキュリティ機能を付与するために開発が行われ、「ジャングルの守護神」を意味するKongに防火機能(FireWall)を追加する意味で、耐火性の高い山茶花からインスピレーションを得て「sasanka」と名付けました。


# Kong API Gatewayとは

Kong API Gatewayは、[Kong](https://konghq.com/)が開発したOSSのAPI Gatewayであり、Apache 2.0 LICENSEで公開されています。

https://github.com/Kong/kong


インストールは、こちらの[Document](https://docs.konghq.com/gateway/3.4.x/get-started/)を参考に以下のコマンドで実行できます。
```shell
curl -Ls https://get.konghq.com/quickstart | bash
```
Kong API Gatewayの記事は[こちら](https://prtimes.jp/main/html/rd/p/000000006.000098201.html)

## 目的

sasankaを導入することによって、Kong API Gatewayに対し一部のWAF機能の提供とAPI Protectionを実現することができます。
OSSとして公開することの目的は、sasankaを利用することによってAPI Securityの認知度向上、API開発者やエンジニア全般へのセキュリティ意識の向上と当社のミッションである「世界中の⼈々が安⼼安全に使えるサイバー空間を創造する」　ことを目的としています。


### sasankaによるセキュリティ

以下に防ぐことのできる攻撃の一例を列挙します。
注)ここで防げる攻撃は、<b>使用中のAPIに対して正しくsasankaが設定されていた場合に限ります</b>。sasankaはご自身で設定する必要があります。

- Injection
- CORSに関する攻撃の検知, 防御
- User-Agentによる検知, 防御
- libinjectionを用いたSQL injection, XSSの検知,防御
- ClickJacking
- オープンリダイレクト
- MassAssignment

Injectionを防ぐための公開されているルールセットは下記になります。この中からルールを取捨選択してssk-pmに設定することで、Injectionへの防御を強化することが可能です。sasankaではCoreRuleSetから自動的に設定を行うスクリプトを用意しています。
- [OWASP CoreRuleSet](https://github.com/coreruleset/coreruleset)

以下に強化できるセキュリティの例を列挙します。


- 検知ログ
- 検知時のカスタムレスポンス
- Redirectの制限
- Request,Response Parameterの型, 値, 文字数チェック
    - JSONSchemaのような形
- Telemtryの測定,出力


## 使い方

使い方の詳細は、[README](https://github.com/cybersecuritycloud/sasanka)に記載しており、[日本語版README](https://github.com/cybersecuritycloud/sasanka/blob/master/README.ja.md)も公開しています。
基本的な使い方としてはKong Pluginの一般的な使用方法と同様のため、Kong Pluginについての詳細は[Kong Document](https://docs.konghq.com/gateway/latest/kong-plugins/)を確認してください。

## 使用例

今回は、Ubuntu20.04の環境にsasankaを導入して、OWASP juiceshopにAPI Gatewayとして適用して実際に使用してみます。

|  |  | 
|:-----------|------------:| 
| OS       | Ubuntu20.04 |


### Kong
まず初めにKongをInstallしていきます。
KongのInstall自体は、[公式Document](https://docs.konghq.com/gateway/latest/install/linux/ubuntu/?_ga=2.197667439.1588441028.1692865532-474653714.1688376367#advanced-installation)よりインストール方法が提供されています。
dockerで起動するquickstartも提供されていますが、今回はCustom Pluginを追加するためパッケージからインストールを行います。

日本語でのDocumentは[こちら](https://www.xlsoft.com/jp/products/kong/docs/install-on-ubuntu.html)が参考になります。


<b>Kongのインストール</b>
```shell
# Ubuntu20.04,Kong 2.x.xの場合は以下。
echo "deb [trusted=yes] https://download.konghq.com/gateway-2.x-ubuntu-$(lsb_release -sc)/ \
 default all" | sudo tee /etc/apt/sources.list.d/kong.list
sudo apt-get update
sudo apt install -y kong-enterprise-edition=2.8.2.4

# Ubuntu22.04,Kong3.x.xの場合は以下。
# curl -Lo kong-enterprise-edition-3.5.0.2.amd64.deb "https://packages.konghq.com/public/gateway-35/deb/ubuntu/pool/jammy/main/k/ko/kong-enterprise-edition_3.5.0.2/kong-enterprise-edition_3.5.0.2_amd64.deb"
# sudo apt install -y ./kong-enterprise-edition-3.5.0.2.amd64.deb

```

あるいは[こちら](https://qiita.com/caunu-s/items/636aff1d04778fbbd779)の記事のようにDockerからインストールを行うことも可能です。

Kongのリポジトリからdefaultのconfigファイルを持ってきます。
```
curl https://raw.githubusercontent.com/Kong/kong/master/kong.conf.default > kong.conf
```

<b>PostgreSQLのインストールとKongの適用</b>
Kongでは、PostgreSQLを使用して設定を行うため、PostgreSQLのインストールとmigrationを行います。
Documentにも以下の記載があります。
> The following steps install the package only, without a data store. You will need to set one up after installation.


```shell
sudo apt-get install -y postgresql
sudo -u postgres createuser kong
sudo -u postgres createdb kong
sudo -u postgres psql -c "alter user kong with encrypted password 'yourpass';"
sudo -u postgres psql -c "grant all privileges on database kong to kong;"

# postgresのpasswordをyourpassに変更
# pg_password = yourpass
vi kong.conf

# DB migration
sudo kong migrations bootstrap -c kong.conf
sudo kong migrations up -c kong.conf
sudo kong migrations finish -c kong.conf
```

<b>Kongの起動</b>
```shell
sudo kong start -c kong.conf
```

<b>確認</b>
Kongがインストールされたことは、8000portにリクエストを投げて確認できます。
```shell 
curl localhost:8000
>>> 
{
  "message":"no Route matched with those values"
}
```
8000 portがhttpのAPI port, 8001 portがhttpのAdminAPI portとなっています。
Kongの転送先設定をしていないので、no Route matched とResponseが返ってきています。
これでKong自体のインストールは完了しました。

### sasanka

次にsasankaをInstallします。
インストールにはluarocksを使用するので、luarocksが未インストールの場合はluarocksをインストールします。
```shell
sudo apt install luarocks
```
git cloneして、rocksファイルをインストールします。
rocksファイルとは、luaで書かれたファイルをパッケージングしたもので`luarocks install`で対象のファイルを展開します。
```shell
git clone https://github.com/cybersecuritycloud/sasanka.git
cd sasanka
sudo luarocks install rocks/ssk-{select_rock}
```
luarocksを用いて全てのインストールを行う場合は以下のコマンドで可能です。
```
./install_all.sh
```

luarcoksを用いてインストールする代わりにlua pathに直接Copyすることでのインストールも可能です。
```shell
cp -r kong/plugins/ssk-* {your_lua_package_path} 
# default {your_lua_package_path} is /usr/local/share/lua/5.1/kong/plugins/
```

次に、kong.confの記述を変更します。kong.confの`plugins`にsasankaのpluginを追記していく形です。
今回は`sed`を利用して、変更します。
```shell
sed -i s/'#plugins = bundled'/'plugins = bundled,ssk-detecthandling,ssk-safehost,ssk-pm,ssk-cors,ssk-std-logger,ssk-ua-filter,ssk-libinjection,ssk-saferedirect,ssk-clickjacking,ssk-strictparameter,ssk-telemetry,ssk-allowkey'/ {your_kong_dir}/kong.conf
```

準備ができたのでここで、Kongを再起動します。
```shell
sudo kong restart -c {your_kong_dir}/kong.conf
```

sasankaが無事にKongにloadできたのを以下のコマンドで確認できます。
Kongでは、8001ポートがAdmin APIとして提供されているので`{Host}:8001`へのリクエストを送ることで現在の設定値が確認できます。
```shell
curl localhost:8001 | jq '.configuration.loaded_plugins' | grep ssk
>>>
  "ssk-telemetry": true,
  "ssk-strictparameter": true,
  "ssk-clickjacking": true,
...
```

### OWASP Juice Shop

次にKongのUpstream先であるWebApplicationを立ち上げます。今回は、OSSのOWASP juiceshopを利用します。
[OWASP Juice Shop](https://owasp.org/www-project-juice-shop/)はOWASPが公開しているセキュリティに関する脆弱性について学ぶための脆弱な実装がされたWebApplicationです。

インストール、実行はdockerで行います。(dockerのinstallは省略、[こちら](https://docs.docker.com/engine/install/ubuntu/)を参考)
```shell
docker pull bkimminich/juice-shop
docker run --rm -p 3000:3000 bkimminich/juice-shop
```

これで、該当のhost:3000にブラウザからアクセスすると以下のように表示されます。
![スクリーンショット 2023-08-29 16.52.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3456674/eaf3958c-4a02-0f2d-c44a-b6517f3c7fea.png)


### 転送先の設定

続いて、KongをAPI Gatewayとして先程立ち上げたjuiceshopに適用していきます。

今回はjuiceshopとKongを同じ環境上で立ち上げて、その環境上でAdminAPIを利用して設定を行なっていきます。

<b>Serviceの有効化</b>
```shell
curl -i -X POST http://localhost:8001/services \
    -d 'name=example-juice' \
    -d 'url=http://localhost:3000'
```

これだけでは、Upstreamにアクセスできません。現時点で`{KongのHost}:8000`にリクエストを投げると以下のような出力になります。
```
{"message":"no Route matched with those values"}
```

<b>Routeの有効化</b>
Serviceに対してRouteを有効化します。
```shell
curl -i -X POST http://localhost:8001/services/example-juice/routes/ \
        --data name=juice-route \
        --data 'paths[]=/mock'
```

<b>確認</b>
先程設定したhost+pathに対して、requestを投げてjuiceshopのhtmlが返ってくるのを確認できます。
```shell
curl localhost:8000/mock/
>>>
<!--
  ~ Copyright (c) 2014-2023 Bjoern Kimminich & the OWASP Juice Shop contributors.
  ~ SPDX-License-Identifier: MIT
  --><!DOCTYPE html><html lang="en"><head>
...
```
もちろんブラウザからも確認ができます。

### 削除

設定したRoute, Serviceを削除する際は以下のように対象を指定してDELETEで削除します。
```
curl -X DELETE http://localhost:8001/services/example-juice/routes/juice-route
curl -X DELETE http://localhost:8001/services/example-juice
```

### sasankaの適用

KongのServiceにjuiceshopのhostを適用することでKong API Gatewayからjuiceshopへ転送することができました。次にこのServiceに対して、sasankaを適用していきます。
`loaded_plugins`にsasankaのpluginが適用されている状態は、KongがsasankaをPluginとして認識していた状態であり、そのままではsasankaは動作しないため設定したServiceに対してsasankaを適用していきます。

sasankaでは、副作用が起こりにくくセキュリティを強化できるデフォルトの推奨設定を簡単に設定できるquickstartを提供しています。先程のService名である`example-juice`を引数として`quickstart.sh`を実行します。
```shell
# sasankaのdirectoryへ移動
cd {sasanka_dir}
./quickstart.sh example-juice
>>>
====================
Finished QuickStart

You can start sasanka Plugin by quickstart settings
====================
```
これでsasankaのデフォルト設定が完了しました。

### juiceshopでの確認

実際に、API Securityとして機能しているかをJuice Shopの一部の例を用いて確認します。
OWASP juiceshopの内容に関しては詳しく説明しませんが、公式の解説は[こちら](https://pwning.owasp-juice.shop/)。


### MassAssignment

今回は、主要なAPI Securityの一つ、Mass AssignmentについてJuice Shopを例にsasankaを設定してみます。
[MassAssignment](https://owasp.org/API-Security/editions/2019/en/0xa6-mass-assignment/)はRequestパラメータに対し、本来APIの想定していないkeyをParameterとして加えることで不正な処理が行われる脆弱性です。事例としてはこのようなものもあります。https://github.blog/2012-03-04-public-key-security-vulnerability-and-mitigation/

Juice Shopの例では、ユーザー作成時に本来であればemail, passwordをRequest Parameterとして入力してユーザー作成するところを攻撃者がemail, password, <b>role</b>をRequest Parameterとして入力して任意の権限を持つユーザーを作成できてしまうというものです。


今回はBurpSuiteを用いて、Juice Shopの脆弱性を確かめてみます。
ここでは、BurpSuiteの使い方については簡単な解説のみに留めておきます。

まず、BurpSuiteを起動し、適当にProjectを作成します。
次に、[Proxy]から、[Open Browser]を選択するとChroniumブラウザが起動します。
Chroniumブラウザ上で、先程KongのServiceとして設定したPathとKongのhost:portを入れると下の画像のように、Juice ShopのRequest, Responseが表示されます。
![スクリーンショット 2023-08-30 13.56.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3456674/9770e4a7-3eb4-3b77-4891-67df6179b64b.png)

ここから、MassAssignment脆弱性についてBurpSuiteで試してみます。
Juice Shopの[Account]を選択し、[Not yet a Customer?]を選択して、以下の画面からユーザーを作成します。

![スクリーンショット 2023-08-30 14.09.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3456674/f0d6a540-000d-ee54-f7e6-d8c96e4cf356.png)

すると、BurpSuite上では、以下のように実際のAPIのRequest,Responseを確認することができます。
![スクリーンショット 2023-08-30 14.10.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3456674/893c07f6-64f8-520d-4e02-5f89466cf2cf.png)

/mock/api/UsersにPOSTリクエストを送っていてResponseをよく見ると、roleというフィールドがあるのがわかります。
パスの部分を右クリックして、[Send to Repeater]を押します。[Repeater]を見ると、同様のリクエストを送ることができます。この時、Request Parameterにrole:adminを追加して送ってみます。

すると下のようにadmin権限を持つユーザーを作成できたことがわかります。ブラウザ上でも、お祝いがされます。(JuiceShopでは特定のサイバー攻撃が成功するとお祝いされます)
![スクリーンショット 2023-08-30 14.18.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3456674/81c7a955-3bd6-9c32-7ed2-7aae1dc7c38b.png)

では、このMassAssignmentを防ぐためにsasankaを設定していきます。
sasankaのPluginの一つに`ssk-allowkey`というPluginがあります。
このPluginでは、Requestに含まれるパラメータを検査しホワイトリスト形式でパラメータkeyを制限するというものです。

今回は、`/mock/api/Users/`というエンドポイントに対してのみ適用したいので、まずは先ほどのKongのServiceに対してRouteを作成して、そのRouteに対してPluginを適用するという方法で実現します。


#### Routeの作成と適用
まず、先ほどのServiceに連動したRoutesを作成します。
```
curl -i -X POST http://localhost:8001/services/example-juice/routes/ \
	 -H "Content-Type: application/json" \
	 -d '{"name": "api-user", "methods": ["POST", "GET"], "paths": ["/mock/api/Users"], "strip_path": false}'
```
次に、そのRouteに対してPluginを適用します。
注) この時のPathにはServicesは含まれません。


この時、正常系のリクエストのbody parameterのkeyをリストアップしておけばOKです
```
curl -X POST http://localhost:8001/routes/api-user/plugins \
	-H "Content-Type: application/json" \
	-d '{"name": "ssk-allowkey", "config": {"body": ["email", "password", "passwordRepeat", "securityQuestion", "securityQuestion.id", "securityQuestion.question", "securityQuestion.createdAt", "securityQuestion.updatedAt", "securityAnswer"]}}'
```
#### 結果

先程と同様に`role=admin`をbodyに加えて実行した結果が以下の画像です。しっかりとブロックされているのがわかります。
![スクリーンショット 2024-01-11 17.30.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3456674/56f67629-f67d-74c0-3478-b96b523add44.png)

正常系のリクエストを実行した場合の結果が以下の画像です。こちらは従来通りの動作を確認できます。
![スクリーンショット 2024-01-11 17.30.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3456674/c208a448-5314-222e-348f-44984119a952.png)

他のPathへのリクエストに対しては影響がありません。
![スクリーンショット 2024-01-11 17.31.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3456674/bad15467-6313-4c75-e40b-275a3443bb38.png)




### Bad UA

次にもう少し簡単な例を見てみます。
同じエンドポイントに対して、User-Agentを削除してリクエストを送ります。

すると以下のようなレスポンスが出力されます。
![スクリーンショット 2024-01-11 14.15.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3456674/0a65c332-63d6-1009-6bf3-757987a52921.png)

これは`quickstart.sh`にてUAが存在しないリクエストをブロックしてるためです。


### カスタムレスポンス

現在、Pluginが検知した時のレスポンスは`quickstart.sh`に記載している以下の部分で決定しています。検知したPluginのtagに対応するカスタムレスポンスを返すといった仕様となっています。
検知したPluginに対応するtagが存在しなければdefaultに対応するレスポンスを返します。
```shell
curl -i -X POST http://localhost:8001/services/${SERVICENAME}/plugins \ 
    -H "Content-Type: application/json" 
    -d '{"name": "ssk-detecthandling", 
        "config": {"filters" : 
            [{"tag" : "status_401","status" : 401,"headers" : [ {"key": "CustomHeader", "value": "CustomValue" },{"key": "CustomHeader2", "value" : "CustomValue2" }],"body" : "some error text","default" : true},
            {"tag" : "status409","status" : 409},
            {"tag" : "log"}]
            }
        }'
```

### 検知ログ

sasankaの検知ログを見てみましょう。検知ログは、標準出力or標準エラー出力として出力することができます。
Kongのデフォルトでは、`/usr/local/kong/logs/access.log ` or `/usr/local/kong/logs/error.log `に出力されます。
quickstart.shを実行した場合、以下のようなログが出力されています。
以下のような形でログが出力されているのを確認できます。

こちらはMassAssignmentを実行した時のssk-allowkeyによるログです。roleが原因でblockされていることがわかります。
```
[ssk-detect]{"args" = ["role"], "rule_id" = 2500}
```

こちらのlogはUAに関するログなので、先ほどのBadUA(no UA)の遮断に対してログを出力していることがわかります。
```text
[ssk-detect]{"args" = {}, "rule_id" = 701}
```

### その他機能
`quickstart.sh`では副作用の小さく、一般的なセキュリティを強化する機能が追加されていることがわかりました。他の機能に関しては、ssk-allowkeyのように各APIに合わせた設定が必要となります。
API Securityにおいては、API固有の欠陥やビジネスロジックの欠陥が脆弱性に繋がることが多いためこのように個別の設定をする必要があります。


## 今後の活動

今後のVersion Updateは随時行いますが詳細は未定となっております。
重大なバグに関しては随時修正、更新を行なっていく予定です。

### フィードバック

バグの報告や機能追加/修正の要望等はIssueとして挙げていただければ対応を行います。
Pull Requestも歓迎しております。


# 最後に

sasankaを様々な方々に実際に使っていただき、フィードバックをいただけると幸いです。
既にKong API Gatewayを導入済の方、導入されていない方も一度ご自身のAPI Securityについて見直すきっかけになればと思います。


# 参考文献

- [sasanka GitHub](https://github.com/cybersecuritycloud/sasanka/)
- [Kong Gatewayを触ってみる](https://qiita.com/caunu-s/items/636aff1d04778fbbd779)
- [Kong Document](https://docs.konghq.com/)
- [Kong Doc Service and Routes](https://docs.konghq.com/gateway/3.5.x/get-started/services-and-routes/)
- [APIセキュリティの重要性](https://www.akamai.com/ja/resources/executive-summary/gartner-magic-quadrant-evaluates-solutions-in-the-waap-market)


# 追記
`quickstart.sh`を実行した際の文言を`some error text`から`blocked`に変更しました。
適宜、変更してみてください。