Cloudflare WAF（Rate Limiting）で Brute Force 攻撃対策

# Brute Force 攻撃対策
以前紹介した[レートリミット](https://qiita.com/KeioCF/items/31b7014c940d5aba741e)を使って [Brute Force 攻撃](https://www.cloudflare.com/ja-jp/learning/bots/brute-force-attack/)対策をシミュレーションしてみます。
今回はユーザー名を切り替えて繰り返しログイン認証を試みるリクエストを例にします。

## ログイン認証がフォーム入力の場合

[POST](https://developer.mozilla.org/ja/docs/Learn/Forms/Sending_and_retrieving_form_data) メソッドで試します。 Body（application/x-www-form-urlencoded）の "username" にユーザー名（Email アドレス）が入ってくると想定します。

テストでは同じ IP から

 username=a%40example.com
 username=b%40example.com
 username=c%40example.com

への接続を 2 回繰り返します。

```shell: リクエスト
~ $ for i in `seq 0 1`; do for u in a b c; do echo -n $u-$i; curl https://nrt.oyama.cf/post -s -H 'content-type: application/x-www-form-urlencoded' -d "username=$u%40example.com&password=1" -v 2>&1 | grep "< HTTP/2"; done; done
```
レートリミットは 10 秒間に 1 回を上限に設定にしておきます。

### 単一の IP アドレスを対象にする
#### ユーザー名ごとにレート制限

- 単一の IP アドレスから複数ユーザー名への接続試行をカウント
- ユーザー名ごとにレートリミットを適用

![Screenshot 2023-11-01 at 14.24.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2218045/40b675bc-ed4a-a347-312d-56e35fe62411.png)

:::note
上記のリクエストマッチのフィルター、その他の設定は本実験用のものになります。実際の環境によってカスタマイズしてください。
:::

実行結果を見ると、それぞれの username ごとに 2 回目の接続からレートリミットされ、Status code が 429 で帰ってきています。（`Then take action...` 設定の通り）

```shell:ユーザー名ごとに 2 リクエスト目からブロック
a-0< HTTP/2 200
b-0< HTTP/2 200
c-0< HTTP/2 200
a-1< HTTP/2 429
b-1< HTTP/2 429
c-1< HTTP/2 429
```

キモはこちらです。
クラス分けに "IP" と ”Form input value of" で username を指定することでカウントを分けています。
![Screenshot 2023-11-01 at 20.01.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2218045/ef8d35b1-4c29-042b-4042-712265ce8852.png)


#### ユーザー名関係なく制限

- 単一の IP アドレスからの接続試行をカウント
- レートリミットを適用

ユーザー名を気にせず、同じ IP からのログイン試行をレートリミットする場合は、`With the same characteristics ...` を "IP" のみにします。

![Screenshot 2023-11-01 at 20.05.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2218045/16b6e261-7ce1-bb98-05c5-dd79172a1db4.png)

ユーザー名に関わらず、同一 IP からの接続がまるっとレートリミットされます。

```shell:2 リクエスト目からブロック
a-0< HTTP/2 200
b-0< HTTP/2 429
c-0< HTTP/2 429
a-1< HTTP/2 429
b-1< HTTP/2 429
c-1< HTTP/2 429
```

### 単一のユーザー名を対象にする

上記の例では、異なる IP は個別にレートリミットがされ、お互いに影響は受けません。

```shell:単一のユーザー名に複数の IP から同時接続の例-1
host1:~$ curl https://nrt.oyama.cf/post -s -H 'content-type: application/x-www-form-urlencoded' -d "username=a%40example.com&password=1" -v 2>&1| grep "< HTTP/2"
< HTTP/2 200

host2:~$ curl https://nrt.oyama.cf/post -s -H 'content-type: application/x-www-form-urlencoded' -d "username=a%40example.com&password=1" -v 2>&1 | grep "< HTTP/2"
< HTTP/2 200
```
これを

- 単一のユーザー名への接続試行をカウント
- レートリミットを適用

に変更します。

単一ユーザー名に対するアクセスを IP アドレスに関わらず制限するには、`With the same characteristics ...` を `Form input value of`  "username" のみにします。


![Screenshot 2023-11-01 at 20.12.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2218045/73f1120d-1718-1fb9-fa4f-a9e370f87cad.png)

結果を見ると、username=a%40example.com への接続が IP を問わずレートリミットされます。

```shell:単一のユーザー名に複数の IP から同時接続の例-2
host1:~$ curl https://nrt.oyama.cf/post -s -H 'content-type: application/x-www-form-urlencoded' -d "username=a%40example.com&password=1" -v 2>&1| grep "< HTTP/2"
< HTTP/2 200

host2:~$ curl https://nrt.oyama.cf/post -s -H 'content-type: application/x-www-form-urlencoded' -d "username=a%40example.com&password=1" -v 2>&1 | grep "< HTTP/2"
< HTTP/2 429
```

## ログイン認証が JSON での入力の場合

フォーム入力に加え、JSON での場合も同様に制御ができます。

![Screenshot 2023-11-01 at 16.15.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2218045/a1647f3b-fe8b-9b8d-7bf9-6e3fd6cb59b9.png)

```shell:JSON での入力
~ $ for i in `seq 0 1`; do for u in a b c; do echo -n $u-$i; curl https://nrt.oyama.cf/post -s -H 'content-type: application/json' -d '{"username":"'$u'@example.com", "password":"1"}' -v 2>&1 | grep "< HTTP/2"; done; done
a-0< HTTP/2 200
b-0< HTTP/2 200
c-0< HTTP/2 200
a-1< HTTP/2 429
b-1< HTTP/2 429
c-1< HTTP/2 429
```

## アクセス失敗のときだけカウントする
以前の[記事](https://qiita.com/KeioCF/items/31b7014c940d5aba741e#%E3%83%AC%E3%82%B9%E3%83%9D%E3%83%B3%E3%82%B9%E3%82%92%E6%9D%A1%E4%BB%B6%E3%81%AB) にあるようにサーバーのレスポンスを見て、アクセス失敗の試行だけカウントアップすることもできます。

たとえば、アクセス失敗時ににサーバーが 403 を返す（あるいは任意のヘッダーを返す）場合は下記のような設定で、アクセス失敗を繰り返す IP のみ制限することができます。
![Screenshot 2023-11-01 at 20.31.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2218045/3cab83d0-43f4-8afe-4098-81c128fa8903.png)

## その他の機能との合せ技

[Rate limiting rules](https://developers.cloudflare.com/waf/rate-limiting-rules/) 単体でもリクエストをルールにマッチさせる条件やマッチした場合のアクションなど、柔軟に定義、利用環境に合わせたカスタマイズが可能です。

ただ、その他の機能との合わせ技で、効果を高めることが期待できます。

たとえば [Bot Management](https://www.cloudflare.com/ja-jp/application-services/products/bot-management/) を組み合わせます。
Bot Score が 20 以下であれば、Block するというような WAF の [Custom Rules](https://developers.cloudflare.com/waf/custom-rules/) を定義しておけば、レートリミットの前に、不要なものはそもそもブロックする、ということができます。

![Screenshot 2023-11-01 at 18.23.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2218045/a1e5ab19-030c-94ff-b637-673297bad75f.png)


Bot score が 20 以下の端末からリクエストを投げると、403 が返ってきます（Custom rules による Block）。
```shell:レートリミット処理の前段でブロック
$ curl https://nrt.oyama.cf/post -s -H 'content-type: application/x-www-form-urlencoded' -d "username=a%40example.com&password=1" -v 2>&1 | grep "< HTTP/2"
< HTTP/2 403
```
Trace を見てみます。
![Screenshot 2023-11-01 at 18.19.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2218045/7d3f45fa-c595-9ba3-123c-19ce40cfa04f.png)

![Screenshot 2023-11-01 at 18.19.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2218045/8a22bb1b-3bf4-0c85-4f03-61fcdb66fa8c.png)

ルールの Action を Block から Log に変えた場合は下記になり、Rate Limiting Rules は Custom Rules のあとに来ています。
![Screenshot 2023-11-01 at 21.03.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2218045/f03c9ca3-5ba0-6711-255f-f0808b898bec.png)

認証そのものを [Access](https://developers.cloudflare.com/cloudflare-one/applications/configure-apps/) にオフロードするアーキテクチャも可能です。

API がエンドポイントの場合は、[API Shield](https://developers.cloudflare.com/api-shield/)との組み合わせもできます。

以上です。
どのようなケースで使えそうか、ご検討の材料になれば幸いです。