特定の文字列をパラメタに含むリクエストを遮断する簡易的なWAFをつくるメモ（ModSecurity）

# はじめに

ちょっとした検証とか確認でWAFをさくっと作りたいと思ったので、その時のメモ。
`owasp/modsecurity`のdockerイメージがあることがわかったものの、用意されている環境変数一覧の中に、特定の文字列を含むようなリクエストを遮断するような設定をする変数がなかったのがきっかけです。

# やること

OWASPで用意している[ModSecurityのdockerイメージ](https://hub.docker.com/r/owasp/modsecurity)を使って、"test"というワードをパラメータに含むGET/POSTリクエストを遮断するWAFを作成します。
ModSecurityのイメージはapache, nginx, IISの３種類用意されていますが、今回はnginxを使用します。
もっと本格的なWAFを作ろうと思うと、Core Rule Set（CRS）を含むような設定を入れるといいと思いますが、今回はそこまではやらないです。

# 環境

```bash
% sw_vers
ProductName:	macOS
ProductVersion:	11.6
BuildVersion:	20G165
% docker --version
Docker version 20.10.12, build e91ed57
% docker-compose --version
docker-compose version 1.29.2, build 5becea4c
```

# やったこと

最終的に用意したファイル、ディレクトリは下の通りです。

```bash
% tree .
.
├── docker-compose.yml
├── modsecurity
│   ├── custom-error.html
│   ├── default.conf
│   ├── index.html
│   ├── log
│   └── modsecurity.conf
└── nginx
    ├── default.conf
    ├── index.html
    └── succeeded.html
```

`docker-compose.yml`の中身は下に載せています。
ここで、`waf`が今回のメインであるModSecurity、`nginx`は`waf`の後段に配置するアプリケーションサーバを想定しています。

```docker-compose.yml
version: "3"

services:

  waf:
    image: owasp/modsecurity:nginx
    ports:
      - "8080:80"
    restart: always
    volumes:
      - ./modsecurity/default.conf:/etc/nginx/conf.d/default.conf
      - ./modsecurity/index.html:/var/www/html/index.html
      - ./modsecurity/custom-error.html:/var/www/html/custom-error.html
      - ./modsecurity/modsecurity.conf:/etc/modsecurity.d/modsecurity-override.conf 
      - ./modsecurity/log:/var/log/nginx
    container_name: waf-nginx
    networks:
      - default

  nginx:
    image: nginx:1.20.2
    ports:
      - "80:80"
    restart: always
    volumes:
      - ./nginx/default.conf:/etc/nginx/conf.d/default.conf
      - ./nginx/index.html:/var/www/html/index.html
      - ./nginx/succeeded.html:/var/www/html/nginx/index.html
    container_name: nginx
    networks:
      - default

networks:
  default:
```

### アプリサーバ（nginx）の設定

`nginx`ディレクトリ以下にはnginx用の設定ファイル3つを配置しています。
`waf`経由でリクエストが来たときには、「nginx-succeeded」という文字列を表示するようにしています。
起動時の疎通確認用に`location /`の設定をしています。
POSTでも同じ結果を返すように`error_page`の設定を入れています。


```nginx/default.conf
server {
    listen       80;
    listen  [::]:80;
    server_name  localhost;

    location /nginx/ {
        root /var/www/html;
        index index.html;
    }

    location / {
        root /var/www/html;
        index index.html;
    }

    error_page 405 =200 $uri;

}
```

```nginx/index.html  
nginx
```

```nginx/succeeded.html
nginx-succeeded
```

### WAF（ModSecurity on nginx）の設定

`nginx`の設定（default.conf）の内容を↓に載せています。
`location`については、`http://localhost/nginx`のリクエストのときに後段の`nginx`にリクエストを渡すような設定にしています。
ModSecurityでリクエストが遮断されたときにはhttp status codeが403になるので、このとき「modsecurity-blocked!」という文字列が表示されるようにしています。

```modsecurity/default.conf     
server {
    listen       80;
    listen  [::]:80;
    server_name  localhost;

    location / {
        root /var/www/html;
        index index.html;
    }

    location /nginx {
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Host $host;
        proxy_set_header X-Forwarded-Server $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_redirect http:// https://;
        proxy_pass http://nginx/nginx/;
    }

    error_page 403 /custom-error.html;

    location = /custom-error.html {
        root /var/www/html;
        internal;
    }
}
```

ModSecurityの設定が↓になります。
重要なのが、以下の2点です。

- `SecRuleEngine On` ... デフォルトが`DetectionOnly`になっていて、検知だけして遮断しない設定なので、遮断するように`On`を指定。
- `SecRule ARGS_NAMES "test"` ... この`SecRule`がdockerコンテナに渡す環境変数として用意されていないので、configファイルを書いてコンテナに渡すようにしました。この設定でGETやPOSTなどのリクエストのパラメータ名が"test"であれば、ログ出力して遮断する設定にしています。POSTだとRequest Bodyを見ないといけないので、"phase:2"を指定しているところが注意点。

```modsecurity/modsecurity.conf        
SecRuleEngine On
SecRequestBodyAccess On
SecAuditEngine On
SecAuditLog /var/log/nginx/modsec_audit.log
SecAuditLogParts ABCFHZ
SecRule ARGS_NAMES "test" "id:3,phase:2,t:lowercase,deny,log"
```


```modsecurity/index.html       
modsecurity
```

```modsecurity/custom-error.html
modsecurity-blocked!
```

# 動作確認

`docker-compose up -d`で起動したあと、Google ChromeのAdvanced REST Clientで動作確認しました。

![modsecurity.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329888/c42a1bd1-4fd1-2ffe-081f-4aaa18bfcda0.gif)


# まとめ

簡単なWAFを作ってみました。
`SecRule`のところは色んなパラメータがあったり、正規表現が使えたりともっとできることは多いですが、`ARGS_POST`がうまく動かなかったりしてつまりました。結果的には`ARGS`や`ARGS_NAMES`で解決したのですが、今度また触る機会があれば、もうちょっと調べてみようと思います。

# 参考
- https://qiita.com/housu_jp/items/1c0204c8cf0ce32a7605
- https://atmarkit.itmedia.co.jp/ait/articles/1409/18/news010_4.html
- https://coreruleset.org/docs/rules/creating/
- https://github.com/SpiderLabs/ModSecurity/wiki/Reference-Manual-(v2.x)
- https://hub.docker.com/r/owasp/modsecurity
- https://qiita.com/ryounagaoka/items/fd641e39a196b47db875