NGINX Ingress ControllerをWAF化する

この記事はNTTコムウェア Advent Calendar 2022 19日目の記事です。
https://qiita.com/advent-calendar/2022/nttcomware

こんにちは。NTTコムウェアの東です。
みなさん、Kubernetes(k8s)使ってますかー？

KubernetesにおいてIngressコントローラのデファクトスタンダードとも言える[NGINX Ingress Controller](https://kubernetes.github.io/ingress-nginx/)には、実はオープンソースのWeb Application Firewall(WAF)実装である[ModSecurityが組み込まれています](https://kubernetes.github.io/ingress-nginx/user-guide/third-party-addons/modsecurity/)。デフォルトではModSecurityは稼働しない設定になっていますが、設定さえ行えば有効化できます。そう、**無料でIngressをWAF化できちゃう**のです。使わない手はないですねー

そこで、本稿では、NGINX Ingress ControllerにおいてModSecurityによるWAF機能を有効化する方法について紹介します。

なお、本稿は、k8sの概要およびある程度の操作を習得している方を対象とします。

## 注意事項

* 本内容については、不正アクセスを助長するものではございません。
* 本ページの内容については、悪用を禁止致します。
   * あくまで自身のサイトのテスト目的での利用に限ります。
   * 悪用した場合、不正アクセス禁止法等で訴えられる可能性がございます。

## OWASP ModSecurity Core Rule Set(CRS)について

早速手順を、と言いたいところですが、ちょっとその前に「OWASP」および「CRS」について簡単に紹介します。名前だけでピンと来る方は本章読み飛ばしてもOKです。

ModSecurityは、攻撃とみなすべきアクセスパターンを多数定義したルールセットを元にアクセスが攻撃かどうかを判断します。現在のModSecurityはあくまでルールセットに基づき判断を行うだけのモジュールであり、ルールセットはModSecurityとは別に切り離して開発・メンテナンスされています。

このルールセットのうち、最も有力なものは、[OWASP](https://owasp.org/)というコミュニティがオープンソースとしてメンテしている「[Core Rule Set](https://coreruleset.org/)」と呼ばれるものです。「OWASP ModSecurity CRS」または単に「CRS」と表記されることもあります。

OWASP（Open Web Application Security Project）（読み方：オワスプ）とは、ソフトウェアやWebアプリケーションのセキュリティ分野の研究やガイドラインの作成、脆弱性診断ツールの開発、イベント開催など多岐にわたる活動をしているオープンソースコミュニティです。


さきほど「NGINX Ingress ControllerにはModSecurityが組み込まれている」と言いましたが、正確には実はModSecurityとCRSが組み込まれています。

というわけで、これ以降、NGINX Ingress ControllerにModSecurity + CRSを導入・有効化する手順を示します。

## 環境前提

筆者が動作確認に用いた環境は以下の通りです。
とはいえ、EKSに限らず[NGINX Ingress Controllerが稼働するk8s](https://kubernetes.github.io/ingress-nginx/deploy/)があればOKです。


* Kubernetes v1.24.7
   * Amazon EKSを用い構築

* NGINX Ingress Controller v1.5.1　（この後の手順で導入）
   * コンテナイメージ：registry.k8s.io/ingress-nginx/controller:v1.5.1
   * 同梱モジュール：
      * ModSecurity v3.0.8
      * OWASP ModSecurity CRS v3.3.4

## ModSecurity推奨設定ファイルの作成

下準備として、これから構築するModSecurityの各種推奨パラメータを記載した設定ファイルを作成します。
というのも、NGINX Ingress Controllerのコンテナイメージ内にはすでにModSecurityおよびCRSが導入されており、設定ファイルも配置されているのですが、リクエストボディの検知が無効だったりと、ちょっとイケてないんです。
また、NGINX Ingress Controllerでは任意のModSecurity設定を[modsecurity-snippet](https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/#modsecurity-snippet)で追加できますが、この場合[4096文字を越えられないという仕様](https://github.com/kubernetes/ingress-nginx/issues/8032)があり、多くの設定・ルールを追加できません。

そこで、ここでは、ModSecurityコミュニティの推奨をベースに設定ファイルを作成し、ConfigMapでアタッチします。

まず、ModSecurityコミュニティの推奨を以下コマンドでダウンロードします。
「v3/master」の部分はModSecurityのGitHubの現時点(2022/12)のデフォルトブランチです。今後変更されるかもしれないので、[GitHubのトップ](https://github.com/SpiderLabs/ModSecurity)を確認してください。

```shell
$ curl -L -O https://raw.githubusercontent.com/SpiderLabs/ModSecurity/v3/master/modsecurity.conf-recommended
```

ダウンロードしたModSecurityコミュニティの推奨をNGINX Ingress Controller向けにちょっと書き換えます。
書き換えたものをcustom-modsecurity.confとして別途出力しています。

```shell
$ cat modsecurity.conf-recommended | sed \
  -e "s/^SecRuleEngine DetectionOnly/SecRuleEngine On/" \
  -e "s/^SecAuditLog \/var\/log\/modsec_audit.log/SecAuditLog \/dev\/stdout/" \
  -e "s/^SecUnicodeMapFile unicode.mapping 20127/SecUnicodeMapFile \/etc\/nginx\/modsecurity\/unicode.mapping 20127/" \
  -e "s/^SecStatusEngine On/SecStatusEngine Off/" \
  -e '$aSecAuditLogFormat JSON' \
  -e '$aSecRuleRemoveById 920350' > custom-modsecurity.conf
```

書き換えのポイントは以下の通りです。

* SecRuleEngine
   * デフォルトのDetectionOnlyでは検出しログ出力するのみでアクセスは通すため、"On"とし検知した攻撃アクセスを拒否させる。
* SecAuditLog
   * ModSecurityのログをコンテナの標準出力に出力する。
* SecUnicodeMapFile
   * unicode.mappingのパスをNGINX Ingress Controllerイメージに合わせ書き換える。
* SecStatusEngine
   * NGINX起動時にステータスレポートをModSecurityプロジェクトチームに送信しない(Off)。
* SecAuditLogFormat
   * ログ形式をJSONに指定。(追記)
* SecRuleRemoveById
   * CRSの id:920350 というルールは、localhostからのアクセスを拒否するルールだが、k8sの場合、内部コンポーネントからのアクセスがlocalhostからあるためこのルールを無効化する行を追加。

:::note info
ここでは動作のための最低限の書き換えのみを行いましたが、その他ModSecurityのチューニングや独自追加ルールなどを盛り込むこともできます。
:::

書き換えたファイル(custom-modsecurity.conf)をベースにConfigMapを作ります。このConfigMapを後にNGINX Ingress Controller(Pod)に設定ファイルとしてアタッチします。
ネームスペース名の"ingress-nginx"はNGINX Ingress Controllerをデプロイする先に合わせ適宜書き換えてください。

```shell
$ kubectl create namespace ingress-nginx
$ kubectl -n ingress-nginx create configmap modsecurity-config \
  --from-file=custom-modsecurity.conf=custom-modsecurity.conf
```

## NGINX Ingress Controllerのデプロイ

筆者環境はAWS(EKS)なので[こちら](https://kubernetes.github.io/ingress-nginx/deploy/#aws)に従いマニフェストを直接指定する方法でデプロイします。

:::note info
もしhelmを使う場合、本記事下部「[参考) helmを使う場合](#参考helmを使う場合)」を参考にしてください。
:::

まず、NGINX Ingress Controllerのマニフェストを以下コマンドでダウンロードします。

```shell
$ curl -L -O https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.5.1/deploy/static/provider/aws/deploy.yaml
```

deploy.yamlに、[ModSecurityを有効化する設定](https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/#enable-modsecurity)および先ほど作成のConfigMap「modsecurity-config」をアタッチする設定を追記します。


```shell
$ vi deploy.yaml
→以下diffの内容を追記
```

以下、追記前後のdiffを示します。

```diff
--- deploy.yaml.org     2022-12-08 10:03:37.083369804 +0900
+++ deploy.yaml 2022-12-08 10:07:20.720523517 +0900
@@ -336,6 +336,10 @@
 apiVersion: v1
 data:
   allow-snippet-annotations: "true"
+  enable-modsecurity: "true"
+  enable-owasp-modsecurity-crs: "true"
+  modsecurity-snippet: |
+    Include /etc/nginx/owasp-modsecurity-crs/custom/custom-modsecurity.conf
 kind: ConfigMap
 metadata:
   labels:
@@ -509,6 +513,8 @@
         - mountPath: /usr/local/certificates/
           name: webhook-cert
           readOnly: true
+        - mountPath: /etc/nginx/owasp-modsecurity-crs/custom/
+          name: modsecurity-config
       dnsPolicy: ClusterFirst
       nodeSelector:
         kubernetes.io/os: linux
@@ -518,6 +524,9 @@
       - name: webhook-cert
         secret:
           secretName: ingress-nginx-admission
+      - name: modsecurity-config
+        configMap:
+          name: modsecurity-config
 ---
 apiVersion: batch/v1
 kind: Job
```

書き換えたdeploy.yamlでNGINX Ingress Controllerをデプロイします。

:::note warn
NGINX Ingress Controller用のServiceリソースがtype: LoadBalancerで作成され全世界に公開されます。それを意図しない場合(検証用途等)、[外部LB側でアクセス元IPアドレスを制限する](https://aws.amazon.com/jp/premiumsupport/knowledge-center/eks-cidr-ip-address-loadbalancer/)等をご検討ください。
:::

```shell
$ kubectl apply -f deploy.yaml
```

## 動作確認

起動したNGINX Ingress ControllerのPod内で"nginx -T"コマンドを実行し、NGINXがModSecurityモジュールをロードしているかどうかを確認します。

以下は、PODNAME環境変数にNGINX Ingress ControllerのPod名を記録し、kubectl execでコマンドを実行しています。"ModSecurity"というワードを含む行がいくつかあることが確認できます。

```shell
$ PODNAME=$( kubectl -n ingress-nginx get pod -l app.kubernetes.io/component=controller -o=jsonpath='{.items[0].metadata.name}' )
$ kubectl -n ingress-nginx exec -it $PODNAME -- nginx -T | grep -i modsecurity
2022/12/08 02:46:15 [notice] 579#579: ModSecurity-nginx v1.0.2 (rules loaded inline/local/remote: 7/782/0)
load_module /etc/nginx/modules/ngx_http_modsecurity_module.so;
        modsecurity on;
        modsecurity_rules '
        Include /etc/nginx/owasp-modsecurity-crs/custom/custom-modsecurity.conf
        modsecurity_rules_file /etc/nginx/owasp-modsecurity-crs/nginx-modsecurity.conf;
```

次に実際、攻撃っぽいアクセスをし、ちゃんと防いでくれるか確認してみます。Ingressのバックエンドとなるサンプルアプリを[こちら](https://kubernetes.github.io/ingress-nginx/deploy/#local-testing)に従い導入します。

```shell
$ kubectl create deployment demo --image=httpd --port=80
$ kubectl expose deployment demo
$ kubectl create ingress demo-localhost --class=nginx \
  --rule="demo.localdev.me/*=demo:80"
$ kubectl port-forward --namespace=ingress-nginx service/ingress-nginx-controller 8080:80
```

curlなどで、アクセス出来ることを確認します。

```shell
$ curl http://demo.localdev.me:8080/
<html><body><h1>It works!</h1></body></html>
```

このサンプルアプリに攻撃っぽい感じでアクセスしてみます。

```shell
$ curl -X POST -d "cmd=<script>" http://demo.localdev.me:8080/
<html>
<head><title>403 Forbidden</title></head>
<body>
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx</center>
</body>
</html>
```

"**403 Forbidden**"が返ってきました。

NGINX Ingress Controllerのログを確認してみると、以下のようにModSecurityが反応し、アクセスを防いでくれたようです。無事、ModSecurityが稼働していることが確認できました。

```shell
$ kubectl -n ingress-nginx logs $PODNAME | grep ModSecurity:
・・・
2022/12/08 03:01:34 [error] 844#844: *47316 [client 127.0.0.1] ModSecurity: Access denied with code 403 (phase 2). Matched "Operator `Ge' with parameter `5' against variable `TX:ANOMALY_SCORE' (Value: `15' ) [file "/etc/nginx/owasp-modsecurity-crs/rules/REQUEST-949-BLOCKING-EVALUATION.conf"] [line "81"] [id "949110"] [rev ""] [msg "Inbound Anomaly Score Exceeded (Total Score: 15)"] [data ""] [severity "2"] [ver "OWASP_CRS/3.3.4"] [maturity "0"] [accuracy "0"] [tag "application-multi"] [tag "language-multi"] [tag "platform-multi"] [tag "attack-generic"] [hostname "127.0.0.1"] [uri "/"] [unique_id "167046849467.029474"] [ref ""], client: 127.0.0.1, server: demo.localdev.me, request: "POST / HTTP/1.1", host: "demo.localdev.me:8080"
```

なお、NGINX Ingress Controllerのログには、ModSecurityの検知内容がJSON形式でさらに詳細に出力されています。

以下は、それ('^{"transaction":'で始まる行)をgrepしjqコマンドで整形したものです（長いので折りたたんでいます）。アクセス元やレスポンス内容、どのルールになぜ検知されたか、など詳細情報が得られます。

<details><summary>詳細ログ</summary>

```shell
$ kubectl -n ingress-nginx logs $PODNAME | grep '^{"transaction":' | jq
{
  "transaction": {
    "client_ip": "127.0.0.1",
    "time_stamp": "Thu Dec  8 03:01:34 2022",
    "server_id": "d0fb58befb07ce0cec85f68d9197f05ab5150f14",
    "client_port": 34376,
    "host_ip": "127.0.0.1",
    "host_port": 80,
    "unique_id": "167046849467.029474",
    "request": {
      "method": "POST",
      "http_version": 1.1,
      "uri": "/",
      "headers": {
        "Host": "demo.localdev.me:8080",
        "User-Agent": "curl/7.61.1",
        "Accept": "*/*",
        "Content-Length": "12",
        "Content-Type": "application/x-www-form-urlencoded"
      }
    },
    "response": {
      "body": "<html>\r\n<head><title>403 Forbidden</title></head>\r\n<body>\r\n<center><h1>403 Forbidden</h1></center>\r\n<hr><center>nginx</center>\r\n</body>\r\n</html>\r\n",
      "http_code": 403,
      "headers": {
        "Server": "",
        "Date": "Thu, 08 Dec 2022 03:01:34 GMT",
        "Content-Length": "146",
        "Content-Type": "text/html",
        "Connection": "keep-alive"
      }
    },
    "producer": {
      "modsecurity": "ModSecurity v3.0.8 (Linux)",
      "connector": "ModSecurity-nginx v1.0.2",
      "secrules_engine": "Enabled",
      "components": [
        "OWASP_CRS/3.3.4\""
      ]
    },
    "messages": [
      {
        "message": "XSS Attack Detected via libinjection",
        "details": {
          "match": "detected XSS using libinjection.",
          "reference": "v152,8t:utf8toUnicode,t:urlDecodeUni,t:htmlEntityDecode,t:jsDecode,t:cssDecode,t:removeNulls",
          "ruleId": "941100",
          "file": "/etc/nginx/owasp-modsecurity-crs/rules/REQUEST-941-APPLICATION-ATTACK-XSS.conf",
          "lineNumber": "38",
          "data": "Matched Data: XSS data found within ARGS:cmd: <script>",
          "severity": "2",
          "ver": "OWASP_CRS/3.3.4",
          "rev": "",
          "tags": [
            "application-multi",
            "language-multi",
            "platform-multi",
            "attack-xss",
            "paranoia-level/1",
            "OWASP_CRS",
            "capec/1000/152/242"
          ],
          "maturity": "0",
          "accuracy": "0"
        }
      },
      {
        "message": "XSS Filter - Category 1: Script Tag Vector",
        "details": {
          "match": "Matched \"Operator `Rx' with parameter `(?i)<script[^>]*>[\\s\\S]*?' against variable `ARGS:cmd' (Value: `<script>' )",
          "reference": "o0,8v152,8t:utf8toUnicode,t:urlDecodeUni,t:htmlEntityDecode,t:jsDecode,t:cssDecode,t:removeNulls",
          "ruleId": "941110",
          "file": "/etc/nginx/owasp-modsecurity-crs/rules/REQUEST-941-APPLICATION-ATTACK-XSS.conf",
          "lineNumber": "64",
          "data": "Matched Data: <script> found within ARGS:cmd: <script>",
          "severity": "2",
          "ver": "OWASP_CRS/3.3.4",
          "rev": "",
          "tags": [
            "application-multi",
            "language-multi",
            "platform-multi",
            "attack-xss",
            "paranoia-level/1",
            "OWASP_CRS",
            "capec/1000/152/242"
          ],
          "maturity": "0",
          "accuracy": "0"
        }
      },
      {
        "message": "NoScript XSS InjectionChecker: HTML Injection",
        "details": {
          "match": "Matched \"Operator `Rx' with parameter `(?i:(?:<\\w[\\s\\S]*[\\s\\/]|['\\\"](?:[\\s\\S]*[\\s\\/])?)(?:on(?:d(?:e(?:vice(?:(?:orienta|mo)tion|proximity|found|light)|livery(?:success|error)|activate)|r(?:ag(?:e(?:n(?:ter|d)|xit)|(?:gestur|leav)e|start|d (3146 characters omitted)' against variable `ARGS:cmd' (Value: `<script>' )",
          "reference": "o0,7v152,8t:utf8toUnicode,t:urlDecodeUni,t:htmlEntityDecode,t:jsDecode,t:cssDecode,t:removeNulls",
          "ruleId": "941160",
          "file": "/etc/nginx/owasp-modsecurity-crs/rules/REQUEST-941-APPLICATION-ATTACK-XSS.conf",
          "lineNumber": "181",
          "data": "Matched Data: <script found within ARGS:cmd: <script>",
          "severity": "2",
          "ver": "OWASP_CRS/3.3.4",
          "rev": "",
          "tags": [
            "application-multi",
            "language-multi",
            "platform-multi",
            "attack-xss",
            "paranoia-level/1",
            "OWASP_CRS",
            "capec/1000/152/242"
          ],
          "maturity": "0",
          "accuracy": "0"
        }
      },
      {
        "message": "Inbound Anomaly Score Exceeded (Total Score: 15)",
        "details": {
          "match": "Matched \"Operator `Ge' with parameter `5' against variable `TX:ANOMALY_SCORE' (Value: `15' )",
          "reference": "",
          "ruleId": "949110",
          "file": "/etc/nginx/owasp-modsecurity-crs/rules/REQUEST-949-BLOCKING-EVALUATION.conf",
          "lineNumber": "81",
          "data": "",
          "severity": "2",
          "ver": "OWASP_CRS/3.3.4",
          "rev": "",
          "tags": [
            "application-multi",
            "language-multi",
            "platform-multi",
            "attack-generic"
          ],
          "maturity": "0",
          "accuracy": "0"
        }
      }
    ]
  }
}
```

</details>

ただ、ModSecurityログ(JSON)は詳細がわかってよいのですが、長すぎてぱっと見よく分からない場合もあります。そこで、jqを駆使してなんとなくサマライズする方法を参考までに紹介します。

以下コマンドは、NGINX Ingress Controllerのログのうち、ModSecurityログ(JSON、'^{"transaction":'で始まる行)のみをgrepし、jqで以下情報のみを抽出しています。

* タイムスタンプ
* リクエスト情報
* 引っ掛かったルール
   * ルールID
   * ルールの概要


```shell
$ PODNAME=$( kubectl -n ingress-nginx get pod -l app.kubernetes.io/component=controller -o=jsonpath='{.items[].metadata.name}' )
$ kubectl -n ingress-nginx logs $PODNAME | grep '^{"transaction"' | \
  jq -r '."transaction" | { time_stamp:."time_stamp", request:."request",  messages:[."messages"[] | { ruleId:."details"."ruleId", message:."message" }] }'

{
  "time_stamp": "Thu Dec  8 07:00:13 2022",
  "request": {
    "method": "POST",
    "http_version": 1.1,
    "uri": "/",
    "headers": {
      "Host": "demo.localdev.me:8080",
      "User-Agent": "curl/7.61.1",
      "Accept": "*/*",
      "Content-Length": "12",
      "Content-Type": "application/x-www-form-urlencoded"
    }
  },
  "messages": [
    {
      "ruleId": "941100",
      "message": "XSS Attack Detected via libinjection"
    },
    {
      "ruleId": "941110",
      "message": "XSS Filter - Category 1: Script Tag Vector"
    },
    {
      "ruleId": "941160",
      "message": "NoScript XSS InjectionChecker: HTML Injection"
    },
    {
      "ruleId": "949110",
      "message": "Inbound Anomaly Score Exceeded (Total Score: 15)"
    }
  ]
}
```

上記の場合、ルールID 941100, 941110, 941160の3種類のルールに引っ掛かったようです。
（ルールID 949110は他ルールによる累積スコアの超過を示し、ModSecurityにより遮断される場合に必ず出るものなので除外しています。）

ちなみに、CRSのルール定義は[こちら](https://github.com/coreruleset/coreruleset/tree/v3.3/master/rules)にあります。ルールIDの上3桁毎にファイルが分かれています。
例えばルールIDが**941**100の場合、REQUEST-**941**-APPLICATION-ATTACK-XSS.confというファイルに[定義](https://github.com/coreruleset/coreruleset/blob/v3.3/master/rules/REQUEST-941-APPLICATION-ATTACK-XSS.conf#L38)があります。

## 千本ノック

千本ノックとして[こちらの記事](https://qiita.com/comware_hirousi/items/68daf3bbab2d29ce990e)を参考に、他の攻撃パターンでもアクセスしてみた結果、見事にほとんどを403で防いでくれました。以下に、curlコマンドとその結果出力されるModSecurityログの抜粋を記載します。

なお、ModSecurityログのうちルールID:949110については、ModSecurityにより遮断される場合に必ず出るものなので表記を省略します。

### User-Agentが不正なボット

```shell
$ curl http://demo.localdev.me:8080/ -H "User-Agent: Mozilla/5.0 (compatible; Nmap Scripting Engine; http://nmap.org/book/nse.html)"
```

```json
{
  "time_stamp": "Thu Dec  8 04:48:40 2022",
  "request": {
    "method": "GET",
    "http_version": 1.1,
    "uri": "/",
    "headers": {
      "Host": "demo.localdev.me:8080",
      "Accept": "*/*",
      "User-Agent": "Mozilla/5.0 (compatible; Nmap Scripting Engine; http://nmap.org/book/nse.html)"
    }
  },
  "messages": [
    {
      "ruleId": "913100",
      "message": "Found User-Agent associated with security scanner"
    },
    ・・・
  ]
}
```

### ローカルファイルインクルージョン(LFI)

```shell
$ curl http://demo.localdev.me:8080/script.php?page=../../../../../../etc/passwd
```

```json
{
  "time_stamp": "Thu Dec  8 04:49:11 2022",
  "request": {
    "method": "GET",
    "http_version": 1.1,
    "uri": "/script.php?page=../../../../../../etc/passwd",
    "headers": {
      "Host": "demo.localdev.me:8080",
      "User-Agent": "curl/7.61.1",
      "Accept": "*/*"
    }
  },
  "messages": [
    {
      "ruleId": "930100",
      "message": "Path Traversal Attack (/../)"
    },
    {
      "ruleId": "930110",
      "message": "Path Traversal Attack (/../)"
    },
    {
      "ruleId": "930110",
      "message": "Path Traversal Attack (/../)"
    },
    {
      "ruleId": "930120",
      "message": "OS File Access Attempt"
    },
    {
      "ruleId": "932160",
      "message": "Remote Command Execution: Unix Shell Code Found"
    },
    ・・・
  ]
}
```

### システムファイルの読取り

```shell
$ curl http://demo.localdev.me:8080/test.ini
```

```json
{
  "time_stamp": "Thu Dec  8 04:49:28 2022",
  "request": {
    "method": "GET",
    "http_version": 1.1,
    "uri": "/test.ini",
    "headers": {
      "Host": "demo.localdev.me:8080",
      "User-Agent": "curl/7.61.1",
      "Accept": "*/*"
    }
  },
  "messages": [
    {
      "ruleId": "920440",
      "message": "URL file extension is restricted by policy"
    },
    ・・・
  ]
}
```

### RFI(リモート上のファイルを参照できてしまう脆弱性)

```shell
$ curl http://demo.localdev.me:8080/display.php?FORMAT=http://192.168.11.1/test.txt
```

```json
{
  "time_stamp": "Thu Dec  8 04:49:50 2022",
  "request": {
    "method": "GET",
    "http_version": 1.1,
    "uri": "/display.php?FORMAT=http://192.168.11.1/test.txt",
    "headers": {
      "Host": "demo.localdev.me:8080",
      "User-Agent": "curl/7.61.1",
      "Accept": "*/*"
    }
  },
  "messages": [
    {
      "ruleId": "931100",
      "message": "Possible Remote File Inclusion (RFI) Attack: URL Parameter using IP Address"
    },
    ・・・
  ]
}
```

### クロスサイトスクリプティング(XSS)

```shell
$ curl 'http://demo.localdev.me:8080/?script=<script>alert("hello")</script>'
```

```json
{
  "time_stamp": "Thu Dec  8 04:50:17 2022",
  "request": {
    "method": "GET",
    "http_version": 1.1,
    "uri": "/?script=<script>alert(\"hello\")</script>",
    "headers": {
      "Host": "demo.localdev.me:8080",
      "User-Agent": "curl/7.61.1",
      "Accept": "*/*"
    }
  },
  "messages": [
    {
      "ruleId": "941100",
      "message": "XSS Attack Detected via libinjection"
    },
    {
      "ruleId": "941110",
      "message": "XSS Filter - Category 1: Script Tag Vector"
    },
    {
      "ruleId": "941160",
      "message": "NoScript XSS InjectionChecker: HTML Injection"
    },
    ・・・
  ]
}
```

### Amazon EC2メタデータ読取り

```shell
$ curl http://demo.localdev.me:8080/?site=http://169.254.169.254/latest/meta-data/
```

```json
{
  "time_stamp": "Thu Dec  8 04:50:36 2022",
  "request": {
    "method": "GET",
    "http_version": 1.1,
    "uri": "/?site=http://169.254.169.254/latest/meta-data/",
    "headers": {
      "Host": "demo.localdev.me:8080",
      "User-Agent": "curl/7.61.1",
      "Accept": "*/*"
    }
  },
  "messages": [
    {
      "ruleId": "931100",
      "message": "Possible Remote File Inclusion (RFI) Attack: URL Parameter using IP Address"
    },
    ・・・
  ]
}
```

### User-Agentが無い

User-Agentが無いというパターンは、本稿手順で構築のModSecurityでは検知されませんでした。

```shell
$ curl http://demo.localdev.me:8080/ -H "User-Agent: "
<html><body><h1>It works!</h1></body></html>
→遮断されなかった。
```

CRSには[User-Agentが無い場合のルール](https://github.com/coreruleset/coreruleset/blob/v3.3/master/rules/REQUEST-920-PROTOCOL-ENFORCEMENT.conf#L651)自体は用意されていますが、スコアが低め(Noticeレベル)であるので遮断には至らなかったようです。
ModSecurityは個々のルールを修正したり、独自ルールを追加したりもできるので、気になる場合はルールを書き替えてもいいかもしれません。

## さいごに

一部防御しないパターンもあるものの、無料で手軽にここまで防御してくれるなら、導入しない手は無いですね。
皆様も、是非NGINX Ingress Controller + ModSecurityを活用し、よりセキュアにk8sを運用し、心安らかにクリスマス＆年末をお過ごしください。

## 参考：helmを使う場合

helmを使ってNGINX Ingress Controllerを導入する場合、myvalues.yamlというファイルを以下のように作成し、helm実行時に"--values"で与えることで、ModSecurityを有効化し、ConfigMap「modsecurity-config」をアタッチできます。


```yaml:myvalues.yaml
controller:
  config:
    enable-modsecurity: "true"
    enable-owasp-modsecurity-crs: "true"
    modsecurity-snippet: |
      Include /etc/nginx/owasp-modsecurity-crs/custom/custom-modsecurity.conf

  extraVolumeMounts:
    - name: modsecurity-config
      mountPath: /etc/nginx/owasp-modsecurity-crs/custom/
  extraVolumes:
    - name: modsecurity-config
      configMap:
        name: modsecurity-config
```

```shell
$ helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
$ helm repo update
$ helm search repo ingress-nginx
NAME                            CHART VERSION   APP VERSION     DESCRIPTION
ingress-nginx/ingress-nginx     4.4.0           1.5.1           Ingress controller for Kubernetes using NGINX a...
$ helm install ingress-nginx \
  --namespace=ingress-nginx \
  --version 4.4.0 \
  --values myvalues.yaml \
  ingress-nginx/ingress-nginx
```

## 参考文献

https://kubernetes.github.io/ingress-nginx/
https://github.com/SpiderLabs/ModSecurity
https://github.com/coreruleset/coreruleset
https://coreruleset.org/
https://www.netnea.com/cms/nginx-modsecurity-tutorials/


※本稿に記載されている製品名、サービス名は、各団体の商標または登録商標です。