[Oracle Cloud] WAF を使って、Bot を検出してブロックしてみた

# はじめに

Web アプリケーションのセキュリティ対策は、非常に重要なものの、対策に専門知識が必要です。SQL インジェクション、クロスサイトスクリプティング、DDoS (BOT)攻撃、認証バイパスなど、多くの危険がインターネットにあふれています。「あなたこのアプリケーションのセキュリティを任せた！」と突然言われたときに、自信をもって対処できるでしょうか？ 

これらの脅威に対応するため、Web Application Firewall (WAF) が利用できます。Oracle Cloud では、WAF 機能を提供しており、次の特徴があります。

- 非常に多くのプロテクションルールを標準装備
- アクセス傾向を分析して、「これつかったらいいよ」とルール有効化を推奨してくれる機能
- Bot 対策のための高度な機能
- IPアドレスや国情報を使って、基本的なブロック機能



今回は、Bot 対策のための機能をいくつか触ってみましょう。Bot 対策は、OCI WAF では Bot Management というまとまりで、いくつかの機能が提供されています。

- JavaScript Challenge : アクセス元で JavaScript の実行を観察し、ブラウザかどうか判定。JavaScript を実行できない簡易的な Bot を検出
- Human Interaction Challenge : サイトの滞在時間、マウスの動き、ページスクロールなど、通常の人間であれば行う振る舞いを観察して、Bot を検出
- Device Fingerprint Challenge : OS, 画面の解像度, ブラウザのプラグインなど、アクセス元の情報を多角的に分析して、Botを検出
- CAPTCHA Challenge : Bot には読み取りにくい画像を表示して、人間がアクセスしているか確認
- Good Bot Whitelist : Google や Yahoo, New Relic など、サービス展開に有効な Bot をホワイトリストに追加する機能



今回は、Human Interaction Challenge と、Device Fingerprint Challenge の設定方法と、その挙動を確認してみます。Device Fingerprint Challenge のルールに引っ掛けることは難しかったため、Human Interaction Challenge で検出して、Block する動きをみていきます。



# Human Interaction Challenge

Human Interaction Challenge は、Bot を特定してブロックするための機能です。マウスの動き、サイトの滞在時間、ページスクロールなど、一般的なユーザーが行うような挙動を観察して判断する、高度な対策のようです。ようです、と言っているのは、中身のアルゴリズムがブラックボックスで、外からはわかりません。

WAF がリクエストを受け付けると、Human Interaction Challenge が様々なイベントをチェックして、一般的なユーザーか、Bot かを判断して、設定されたアクションを行います。



設定をしていきましょう。WAF の設定画面にある、Bot Management から、Edit Human Interaction Challenge で設定をします。

![1599288468868.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104247/a6da078d-bfc8-66a8-cce9-f6c4602aa225.png)



各種パラメータを入れて、Human Interaction Challenge を有効化します。各パラメータの意味を Document や挙動から調査した内容をまとめます。一部明確に書かれていない部分もあり、推測と観察結果が含まれています。

- SET HEADER FOR FAILED REQUEST : 
  Action に Detect(検出) を指定したときに利用する設定。Detect されたリクエストに、Detect した事を表す Header を追加して、Origin へリクエストを飛ばす。指定した任意の名前と、Detect した Request の累積数が Header に設定される。
- ACTION THRESHOLD (NUMBER OF REQUESTS) : 
  アクションを起こす条件を指定。Bot と思われるようなリクエストを、何回受信したらアクションを起こすのか、リクエスト数の閾値を指定。
  10 と設定したとき、Bot と思われるようなリクエストを10回受信しても Action をしない。11回目受信したときに、Action を行う。
  最初はその数値が適切かわからないと思うので、Document に記載されている推奨値が良いと思う。基本的な Ajax を使う Web アプリケーションでは10で指定、Ajax 頻繁に使用する Web アプリケーションは 100 を指定。 
- THRESHOLD EXPIRY PERIOD (SECONDS) : 
  `ACTION THRESHOLD` でカウントしている数が、リセットされるまでの間隔。指定した秒数で、定期的にリセットされる (動作検証の結果、このような挙動に見える)
- ACTION EXPIRE TIME (SECONDS) : 
  Bot 検知して 一時的に Block した状態から、Block 解除するまでの秒数を指定 (動作検証の結果、このような挙動に見える)
- INTERACTION THRESHOLD (NUMBER OF INTERACTIONS) : 
  Bot と判断するための条件。マウスを動かす、クリックをする、キーボードを打つ、といった人間が行う活動を 1 インタラクションとする。一定時間に、指定回数以上のインタラクションがあれば、人間の操作と判断する。
  OCI Console に次の英語の表示あり `To pass the challenge, the user should make X events (mouse movements, clicks, and so on) during the recording period.` 
- RECORDING PERIOD (SECONDS) : 
  ユーザのインタラクションを記録する秒数。おそらく、指定した秒数の間に、インタラクションの回数が`INTERACTION THRESHOLD` の閾値を超えると、人間のアクセスと判断される
- NAT SUPPORT
  有効にすると、NAT 環境で IP アドレスを共有している複数ユーザーの識別を試みるようになる。この機能を有効にすると、WAF 側での処理が増えるため、秒間 200リクエスト超えるようなたくさんのリクエストがある環境では、無効にするのが良い



設定例

![1599370518053.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104247/bdda7b9b-a609-b1eb-f1d6-60cf7b271078.png)



# Device Fingerprint Challenge

アクセス元ユーザーを IP アドレスだけではなく、様々な情報を基に識別して、Bot かどうかを検出する仕組みのようです。Oracle Cloud に関係なく、Device Fingerprint とは、アクセス元ユーザーの OS、IP アドレス、ブラウザの種類、画面解像度、インストール済みプラグイン、インストール済みフォント一覧、Useragent などの情報を使用して、ユーザーを特定する概念です。IP アドレスは NAT 環境では複数のユーザーで共有するため、複数の情報を基に多角的にユーザーを特定する概念です。Device Fingerprint は、アクセス元ユーザーによって、少しずつ異なるので、利用者を識別するための特徴点となります。



WAF の設定画面にある、Bot Management から、Edit Device Fingerprint Challenge で設定をします。

![1599326634430.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104247/f0c4ebd8-7b0e-dc35-0a43-75958501f7e2.png)



各種パラメータを入れて、Human Interaction Challenge を有効化します。各パラメータの意味を Document や挙動から調査した内容をまとめます。一部明確に書かれていない部分もあり、推測が含まれています。

- ACTION THRESHOLD (NUMBER OF REQUESTS) : 
  アクションを起こす条件を指定。Bot と思われるようなリクエストを、何回受信したらアクションを起こすのか、リクエスト数の閾値を指定。
  10 と設定したとき、Bot と思われるようなリクエストを10回受信しても Action をしない。11回目受信したときに、Action を行う。
  最初はその数値が適切かわからないと思うので、Document に記載されている推奨値が良いと思う。基本的な Ajax を使う Web アプリケーションでは10で指定、Ajax 頻繁に使用する Web アプリケーションは 100 を指定。 
- THRESHOLD EXPIRY PERIOD (SECONDS) : 
  `ACTION THRESHOLD` でカウントしている数が、リセットされるまでの間隔。指定した秒数で、定期的にリセットされる (動作検証の結果、このような挙動に見える)
- ACTION EXPIRE TIME (SECONDS) : 
  Bot 検知して 一時的に Block した状態から、Block 解除するまでの秒数を指定 (動作検証の結果、このような挙動に見える)

- MAX ADDRESS COUNT (IP ADDRESSES) : 
  同じ Device Fingerprint で許可される IP アドレスの最大数
- MAX ADDRESS COUNT EXPIRATION (SECONDS) : 
  MAX ADDRESS COUNT の数がリセットされるまでの時間 (秒)



設定例

![1599370567144.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104247/7cf590cb-0eb0-89d7-5573-bbf4b0f8af59.png)



# Bot検知確認

余談ですが、Human Interaction Challenge の機能を設定したため、WAF で保護しているWebページに JavaScript が組み込まれています。

```
> curl http://waf.sugioci.tokyo/
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {        width: 35em;        margin: 0 auto;        font-family: Tahoma, Verdana, Arial, sans-serif;    }</style>
<script type="text/javascript">(function(){ if (typeof(___zen) === "undefined") {setTimeout(arguments.callee, 50); return; }___zen.hic("__ZEHIC6475", 1599329407, 3, 15, 60); })()</script><script type="text/javascript" async="async" src="/__zenedge/assets/hic.js?v=1595328929"></script></head>
<body>
<h1>I am web01</h1>
<script type="text/javascript" async="async" src="/__zenedge/assets/f.js?v=1595328929"></script></body>
</html>
```



埋め込まれている JavaScript を確認してみると、こんな感じのソースコードです。
各種操作イベントを取得しているようなコードに見えます。

```javascript
(function(d) {
    function l() {
        g && (document.removeEventListener ? (document.removeEventListener("mousemove", a),
        document.removeEventListener("mousedown", a),
        document.removeEventListener("click", a),
        document.removeEventListener("mouseup", a),
        document.removeEventListener("keydown", a),
        document.removeEventListener("keypress", a),
        document.removeEventListener("keyup", a),
        document.removeEventListener("scroll", a)) : document.detachEvent && (document.detachEvent("onmousemove", a),
        document.detachEvent("onmousedown", a),
        document.detachEvent("onclick", a),
        document.detachEvent("onmouseup", a),
        document.detachEvent("onkeydown", a),
        document.detachEvent("onkeypress", a),
        document.detachEvent("onkeyup", a),
        document.detachEvent("onscroll", a)),
        g = !1)
    }
    function a() {
        f += 1;
        f < m || (0 == b && (b = Date.now ? Date.now() : (new Date).getTime() / 1E3,
        b = Math.round(b / 1E3)),
        document.cookie = h + "\x3d" + b + ";expires\x3d" + c + ";path\x3d/",
        l())
    }
    d.___zen = d.___zen || {};
    var h = "_undefined"
      , b = "N"
      , k = 0
      , g = !1
      , f = 0
      , m = 0
      , c = 0;
    d.___zen.hic = function(e, d, f, n, p) {
        m = f;
        h = e;
        b = d;
        k = n;
        e = new Date;
        c = e.getTime();
        c += 1E3 * p;
        e.setTime(c);
        c = e.toUTCString();
        document.cookie = h + "\x3dN;expires\x3d" + c + ";path\x3d/";
        document.addEventListener ? (document.addEventListener("mousemove", a),
        document.addEventListener("mousedown", a),
        document.addEventListener("click", a),
        document.addEventListener("mouseup", a),
        document.addEventListener("keydown", a),
        document.addEventListener("keypress", a),
        document.addEventListener("keyup", a),
        document.addEventListener("scroll", a)) : document.attachEvent && (document.attachEvent("onmousemove", a),
        document.attachEvent("onmousedown", a),
        document.attachEvent("onclick", a),
        document.attachEvent("onmouseup", a),
        document.attachEvent("onkeydown", a),
        document.attachEvent("onkeypress", a),
        document.attachEvent("onkeyup", a),
        document.attachEvent("onscroll", a));
        g = !0;
        0 < k && setTimeout(l, 1E3 * k)
    }
}
)(window);
```



それでは本題です。Bot Management の設定を行ったので、動作するか確認してみましょう。今回はシンプルに、1秒に10回のアクセスを curl で行ってみます。

```
watch -n 0.1 curl http://waf.sugioci.tokyo/
```



WAF の Logs ページで、Bot として検知したものをフィルターして表示できます。画面左下のメニューで、時間帯や Log の Type などを指定できます。
`153.246.137.125` (自分の今の環境のIPアドレス) からのアクセスを、Human Interaction Challenge で検知したことがわかります。

![1599328061017.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104247/59361bba-7af9-1571-8114-b77cac80ab60.png)



Detect の Log のなかに実際の HTTP Request の内容が見えます。HTTP Header に、`Hic-Failed` が追加されています。Value が 193 となっており、193 回 Bot として Detect されていることがわかります。Human Interaction Challenge で指定した HTTP Header が追加されていますね。

![1599346518369.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104247/acbf8a10-69b6-dcde-f318-90230b173836.png)



Detect したアクセスを確認して、本当に悪意のあるユーザーだと判断できれば、Black List に追加することが出来ます。Bot Management の機能で自動的に Block が出来ますが、善良な一般ユーザーを Block してしまう危険性があるので、一旦は Detect で検知したものを人間の手で Black List へ追加するのが良いとおもいます。

一定期間、Detect されたものを検証して、品質が高そうだと判断できたら、自動 Block 設定へ変更するのが良いでしょう。



# IP アドレスベースのアクセスコントロール

Bot っぽい IP アドレスを検知したので、IP アドレスでBlock する設定をします。
なお、WAF には、IP Address List を、管理する機能がありますが、使用しないほうがいいと思います。IP Address List を WAF Policy に紐づけて、特定の IP アドレスを Block 出来ますが、紐づけたあとに IP Address List を変更しても Block されませんでした。反映するためには、もう一度紐づけをすればいいのですが、管理面でつらいです。WAF が、どの IP アドレスを Block しているのか、設定値で把握出来ない原因を生んでしまいます。この原因を生むくらいであれば、WAF Policy に直接 IP アドレス を設定するのがやりやすいと感じています

IP Address リストが良くないと思う理由

- Address List はリアルタイムに反映されない。WAF Policy 側から再度更新しないと反映されないので、現在有効なブロックされる IP アドレス一覧が分からなくなる
- WAF Policy に Address List を紐づけて Update を実施したあと、45 分の間完了を待っている途中で、紐づけた Address List に設定した IP アドレス一覧を変更すると、期待していない動作となる。Block されているはずの IP アドレスが、アクセスする度に Block されたり、通信できたりする。途中で変更したことで、Edge Point 間で反映の差異が発生してしまった挙動に見える。



それでは、Black List の設定をしていきましょう。WAF Policy ページで、Add Access Rule を押します。

![1599328426610.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104247/f0ccc7aa-7bf9-ef67-ccbe-1d703c3a4038.png)



Condition を `IP Address is` にして、IP Addresses に ブロックしたい IP アドレスをいれます。ちなみに、ここには複数の IP アドレスを改行で指定できます。指定した IP アドレスを全てブロックします。

![1599334375870.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104247/7490d9d1-3f52-3415-7e6d-1babb6027b8b.png)



Publish All を押すと、WAF Edge Point に設定が反映されます。全世界の Edge Point があり、45 分ほど設定に時間が掛かります。

![1599329020682.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104247/15833330-de5b-f49c-8186-e0a30c75e6ea.png)



Publish All を押します。

![1599329050873.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104247/3a440e30-e7a8-0a60-b433-4aafaabab0e5.png)



UPDATING となりました。45 分ほど掛かるので、待ちましょう。
**UPDATING 中に、指定した IP アドレスからアクセスすると、Block されるアクセスもあれば、正常に通信できるアクセスもあります。アクセスする度に、状況に応じて切り替わります。全 Edge Point に反映する途中なので、このような挙動になると推測しています**

![1599329079249.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104247/2baa63a0-6d77-d3da-4934-25fad14e5e37.png)



なお、Block したものは Log で検索可能です。

![1599329683894.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104247/aec3e389-2ccf-445f-1da1-1d3cd28aba5a.png)



Block された時の Response を確認します。403 Forbidden を返す設定にしたので、こんな感じになります。

```
> curl http://waf.sugioci.tokyo/
<html>
<head><title>403 Forbidden</title></head>
<body bgcolor="white">
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx</center>
</body>
</html>
<!-- a padding to disable MSIE and Chrome friendly error page -->
<!-- a padding to disable MSIE and Chrome friendly error page -->
<!-- a padding to disable MSIE and Chrome friendly error page -->
<!-- a padding to disable MSIE and Chrome friendly error page -->
<!-- a padding to disable MSIE and Chrome friendly error page -->
<!-- a padding to disable MSIE and Chrome friendly error page -->
```



Block されたとき、ブラウザから見るとこんな感じです。

![1599329546090.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104247/6aa224b3-f5c6-fb75-8f60-501e505d0d81.png)





# Bot Management で自動 Block

Bot Management で自動 Block をした時の挙動を確認しましょう。Edit を押します。

![1599346694019.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104247/1c2d4fce-c593-6d6f-de5b-f059e3f03e26.png)



次のパラメータを指定します。

- BLOCK に変更
- Response Code は 403 Forbidden
- Action Thread を 100回にする。Bot と検出されるまでに、怪しいリクエストを100回までは受け付ける。101 回目以降から、Bot として Block する。
- Action Expire Time を 10秒にする。Bot と検出されてから Block 解除するまでの時間を10秒とすることで、検証をしやすくする。本番環境ではもうちょっと長めでもいいと思う。

![1599359784252.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104247/0a03e572-42b8-7867-30bb-bc8df8b07aeb.png)



Publish All

![1599346876002.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104247/9687094d-0bce-0db6-d2a7-b5e24f52d556.png)



設定完了後、1秒に10回のアクセスを curl で行ってみます。

```
watch -n 0.1 curl http://waf.sugioci.tokyo/
```



100 回の `Action Thread` があり、最初の方はアクセスが正常に出来ています。何十秒か経ったあとに、ブロックされ始めたときのレスポンスです。403 が返ってきています。
なお、Block されてから 10秒後に 解除され、アクセスが正常に戻ります。

```
> curl -i http://waf.sugioci.tokyo/
HTTP/1.1 403 Forbidden
Date: Sun, 06 Sep 2020 02:03:41 GMT
Content-Type: text/html
Transfer-Encoding: chunked
Connection: keep-alive
Cache-Control: no-store
Cache-Control: no-cache, no-store, must-revalidate
Cache-Control: max-age=0
X-Zen-Fury: 5562caaa10fd9e820ef10fbdee07d384c5f6c75d
Server: ZENEDGE
X-Cdn: Served-By-Zenedge

<html>
<head><title>403 Forbidden</title></head>
<body bgcolor="white">
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx</center>
<script type="text/javascript" async="async" src="/__zenedge/assets/f.js?v=1595328929"></script></body>
</html>
<!-- a padding to disable MSIE and Chrome friendly error page -->
<!-- a padding to disable MSIE and Chrome friendly error page -->
<!-- a padding to disable MSIE and Chrome friendly error page -->
<!-- a padding to disable MSIE and Chrome friendly error page -->
<!-- a padding to disable MSIE and Chrome friendly error page -->
<!-- a padding to disable MSIE and Chrome friendly error page -->
```



ブロックされたときは、ブラウザから見ると、こんな感じです。

![1599329546090.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104247/467cc6bb-b7a6-2a1b-5ec6-83a44272781a.png)





# 参考URL

Human Interaction Challenge
https://docs.cloud.oracle.com/en-us/iaas/tools/oci-cli/2.9.6/oci_cli_docs/cmdref/waas/human-interaction-challenge/update.html

Device Fingerprint Challenge
https://docs.cloud.oracle.com/en-us/iaas/tools/oci-cli/2.9.6/oci_cli_docs/cmdref/waas/device-fingerprint-challenge/update.html

Fingerprint について
https://www.saitolab.org/fp_site/