AWS WAF の CAPTCHA を試しつつログを CloudWatch Logs で確認した

2024.03.16

コーヒーが好きな emi です。

AWS WAF では通信を BLOCK(遮断)したり COUNT(数える)したりできるほか、CAPTCHA というアクションを設定できます。
CAPTCHA というアクションはワンクリックで簡単に有効化でき、以下画像のような「わたしはロボットではありません」的なパズルを表示して bot などのアクセスを防ぐことができます。

今回は AWS WAF の CAPTCHA アクションの挙動を試しつつ、ログを CloudWatch Logs で確認してみました。

0. 構成図と事前準備

以下のような構成で検証します。

事前準備として、ALB の後ろに Apache HTTP Server(httpd)をインストールした EC2(Amazon Linux 2023)を立てておきました。
準備に関しては以下ブログの 事前準備 をご参照ください。

WAF が無い状態で ALB の DNS 名あてにブラウザからアクセスすると、以下のように Hello world! が返ってくるようになっています。

1. AWS WAF の構築(CAPTCHA アクションの設定)

AWS WAF を構築し、CAPTCHA アクションの設定をします。

IP Sets

カスタムルールで使用するために IP Sets を設定しておきます。手元の端末からアクセス試行するので、手元の端末からインターネット向けに出ていく際のパブリック IP アドレスを登録しておきます。

カスタムルールグループとカスタムルール

カスタムルールグループ「test-rule-group」を作成し、以下のようにカスタムルール「test-rule」を含んでおきます。

カスタムルールは以下のように設定します。言葉で書くと

「/」から始まる URI パスへのアクセス、かつ、myIP という IP Sets に含まれる IP アドレスからのアクセスの場合 CAPTCHA アクションを実行する

というルールです。

項目 備考
If a request matchs all the statements (AND)
Statement 1
Inspect URI path
Match type Starts with string
String to match /
Text transformation None
AND
Statement 2
Inspect Originates from an IP address in
IP set myIP 作成しておいた IP set
IP address to use as the originating address Source IP address
Then
Action CAPTCHA
Immunity time (未設定) イミュニティタイムを設定すると、1 つのクライアントセッションで CAPTCHA が表示される間隔を制御できます。エンドユーザーが CAPTCHA に成功した後、ここで設定した時間だけ認証トークンが有効になり、ブラウザを再読み込みしたり再度アクセスしたりした際 CAPTCHA を表示しません。

今回はイミュニティタイムをルール側で設定しませんでした。ここで設定しない場合、 Web ACL 側で設定できるイミュニティタイムによってトークンの有効期間が制御されます。

Web ACL

作成したカスタムルールグループを Web ACL に設定します。

Web ACL の詳細画面で「Rules」タブを開き、カスタムルールグループを追加しておきます。

「Rules」タブ内の項目で「Web ACL CAPTCHA configuration」という場所があり、ここで CAPTCHA 認証トークンの有効期間が制御されます。デフォルトで Immunity time 300 秒となっています。

Web ACL にルールグループを設定する際、「ルールアクションのオーバーライド」と「ルールグループアクションを Count にオーバーライド」という、アクションを上書きする設定があるのですが、今回はデフォルトで、アクションの上書きはしないようにしておきます。

「Logging and metrics」タブでログの取得は有効化しておきます。今回は CAPTCHA、BLOCK、COUNT を有効にしてあります。

2. ブラウザからアクセス:CAPTCHA 成功画面

WAF の設定ができましたので、ALB の DNS 名あてにブラウザからアクセスしてみましょう。
以下のように CAPTCHA が表示されました。「開始」をクリックします。

パズルに回答して「確認」をクリックします。

成功すると「正解です」と表示されます。

1~2 秒ほど待つと Hello world! ページにリダイレクトされました。

2-1. ブラウザからアクセスした際の CAPTCHA 成功ログ

CloudWatch Logs insights(ログのインサイト)より、アクセスした時間とロググループを指定し以下のクエリを実行すると、ログが 2 件が出力されています。それぞれ CAPTCHAALLOW のログです。

fields @timestamp, action, @logStream
| sort @timestamp desc
| limit 1000

開くとこのような詳細が表示されます。@logStream から該当のログストリームを表示すると、JSON 形式でログを表示することもできます。

トグル内に、JSON 形式のログを貼っておきます。

先に出た CAPTCHA ログ(クリックで展開)
{
    "timestamp": 1710512019685,
    "formatVersion": 1,
    "webaclId": "arn:aws:wafv2:ap-northeast-1:123456789012:regional/webacl/test-waf-webacl/xxxxxxxxxx",
    "terminatingRuleId": "test-rule-group",
    "terminatingRuleType": "GROUP",
    "action": "CAPTCHA",
    "terminatingRuleMatchDetails": [],
    "httpSourceName": "ALB",
    "httpSourceId": "123456789012-app/test-alb/xxxxxxxxxx",
    "ruleGroupList": [
        {
            "ruleGroupId": "arn:aws:wafv2:ap-northeast-1:123456789012:regional/rulegroup/test-rule-group/xxxxxxxxxx",
            "terminatingRule": {
                "ruleId": "test-rule",
                "action": "CAPTCHA",
                "ruleMatchDetails": null
            },
            "nonTerminatingMatchingRules": [],
            "excludedRules": null,
            "customerConfig": null
        }
    ],
    "rateBasedRuleList": [],
    "nonTerminatingMatchingRules": [],
    "requestHeadersInserted": null,
    "responseCodeSent": 405,
    "httpRequest": {
        "clientIp": "xx.xx.xx.xx",
        "country": "JP",
        "headers": [
            {
                "name": "If-Modified-Since",
                "value": "Tue, 12 Mar 2024 06:31:04 GMT"
            },
            {
                "name": "Connection",
                "value": "keep-alive"
            },
            {
                "name": "Cache-Control",
                "value": "max-age=0"
            },
            {
                "name": "Upgrade-Insecure-Requests",
                "value": "1"
            },
            {
                "name": "User-Agent",
                "value": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0"
            },
            {
                "name": "Accept",
                "value": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7"
            },
            {
                "name": "Referer",
                "value": "http://test-alb-xxxxxxxxxx.ap-northeast-1.elb.amazonaws.com/"
            },
            {
                "name": "Accept-Encoding",
                "value": "gzip, deflate"
            },
            {
                "name": "Accept-Language",
                "value": "ja,en;q=0.9,en-GB;q=0.8,en-US;q=0.7"
            },
            {
                "name": "Cookie",
                "value": "aws-waf-token=xxxxxxxxxx"
            },
            {
                "name": "If-None-Match",
                "value": "\"8d-61370cd6e059b\""
            },
            {
                "name": "Host",
                "value": "test-alb-xxxxxxxxxx.ap-northeast-1.elb.amazonaws.com"
            }
        ],
        "uri": "/",
        "args": "",
        "httpVersion": "HTTP/1.1",
        "httpMethod": "GET",
        "requestId": "1-65f45793-057babcb17e6d73c721a953b"
    },
    "captchaResponse": {
        "responseCode": 405,
        "failureReason": "TOKEN_EXPIRED"
    }
}

先に出たこの CAPTCHA ログ(2024-03-15T23:13:39.685+09:00)からは、ユーザーが Web サイトにアクセスした際、AWS WAF のルールグループ「test-rule-group」によって CAPTCHA アクションがトリガーされたことが分かります。
CAPTCHA レスポンスのステータスコードは 405 で、失敗理由は TOKEN_EXPIRED となっています。
ユーザーがまだ CAPTCHA を解いていないか、トークンの有効期限が切れたことを示しています。

次に出た ALLOW ログ(クリックで展開)
{
    "timestamp": 1710512033713,
    "formatVersion": 1,
    "webaclId": "arn:aws:wafv2:ap-northeast-1:123456789012:regional/webacl/test-waf-webacl/xxxxxxxxxx",
    "terminatingRuleId": "Default_Action",
    "terminatingRuleType": "REGULAR",
    "action": "ALLOW",
    "terminatingRuleMatchDetails": [],
    "httpSourceName": "ALB",
    "httpSourceId": "123456789012-app/test-alb/xxxxxxxxxx",
    "ruleGroupList": [
        {
            "ruleGroupId": "arn:aws:wafv2:ap-northeast-1:123456789012:regional/rulegroup/test-rule-group/xxxxxxxxxx",
            "terminatingRule": null,
            "nonTerminatingMatchingRules": [
                {
                    "ruleId": "test-rule",
                    "action": "CAPTCHA",
                    "ruleMatchDetails": [],
                    "captchaResponse": {
                        "solveTimestamp": 1710512033
                    }
                }
            ],
            "excludedRules": null,
            "customerConfig": null
        }
    ],
    "rateBasedRuleList": [],
    "nonTerminatingMatchingRules": [],
    "requestHeadersInserted": null,
    "responseCodeSent": null,
    "httpRequest": {
        "clientIp": "xx.xx.xx.xx",
        "country": "JP",
        "headers": [
            {
                "name": "Cookie",
                "value": "aws-waf-token=xxxxxxxxxx"
            },
            {
                "name": "Connection",
                "value": "keep-alive"
            },
            {
                "name": "Cache-Control",
                "value": "max-age=0"
            },
            {
                "name": "Upgrade-Insecure-Requests",
                "value": "1"
            },
            {
                "name": "User-Agent",
                "value": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0"
            },
            {
                "name": "Accept",
                "value": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7"
            },
            {
                "name": "Referer",
                "value": "http://test-alb-xxxxxxxxxx.ap-northeast-1.elb.amazonaws.com/"
            },
            {
                "name": "Accept-Encoding",
                "value": "gzip, deflate"
            },
            {
                "name": "Accept-Language",
                "value": "ja,en;q=0.9,en-GB;q=0.8,en-US;q=0.7"
            },
            {
                "name": "Host",
                "value": "test-alb-xxxxxxxxxx.ap-northeast-1.elb.amazonaws.com"
            }
        ],
        "uri": "/",
        "args": "",
        "httpVersion": "HTTP/1.1",
        "httpMethod": "GET",
        "requestId": "1-65f457a1-60e747375dc0e19a293269ed"
    },
    "captchaResponse": {
        "solveTimestamp": 1710512033
    }
}

次に出た ALLOW ログ(2024-03-15T23:13:53.713+09:00)からは、ユーザーが CAPTCHA を正常に解いた後、Web サイトにアクセスできたことが分かります。
ルールグループ「test-rule-group」の中で、「test-rule」が非終了ルール(nonTerminatingMatchingRules)としてマッチし、更に solveTimestamp が記録されています。これはユーザーが CAPTCHA を正常に解いたことを示しています。
最終的に、AWS WAF のデフォルトアクション("terminatingRuleId": "Default_Action")である「ALLOW」アクションが実行され、ユーザーは Web サイトへのアクセスを許可されました。

CAPTCHA は非終了アクションといい、アクション実行後もルールの評価を継続します。AWS WAF におけるリクエスト評価については以下ブログが非常にわかりやすいので参考にしてください、

AWS WAF のメトリクスとログについては以下の re:Post ページも参照ください。

3. curl でアクセス:CAPTCHA 失敗

次は手元の端末のコマンドプロンプトから curl コマンドで動作確認してみます。

実行コマンド

curl http://test-alb-xxxxxxxxxx.ap-northeast-1.elb.amazonaws.com:80

▼実行結果

C:\Users\kitani.emi>curl http://test-alb-xxxxxxxxxx.ap-northeast-1.elb.amazonaws.com:80
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Human Verification</title>
    <style>
        body {
            font-family: "Arial";
        }
    </style>
    <script type="text/javascript">
    window.awsWafCookieDomainList = [];
    window.gokuProps = {
"key":"xxxxxxxxxx",
          "iv":"xxxxxxxxxx",
          "context":"xxxxxxxxxx"
};
    </script>
    <script src="https://xxxxxxxxxx.ap-northeast-1.token.awswaf.com/xxxxxxxxxx/challenge.js"></script>
    <script src="https://xxxxxxxxxx.ap-northeast-1.captcha.awswaf.com/xxxxxxxxxx/captcha.js"></script>
</head>
<body>
    <div id="captcha-container"></div>
    <script type="text/javascript">
        AwsWafIntegration.saveReferrer();
        window.addEventListener("load", function() {
          const container = document.querySelector("#captcha-container");
          CaptchaScript.renderCaptcha(container, async (voucher) => {
              await ChallengeScript.submitCaptcha(voucher);
              window.location.reload(true);
          }
      );
    });
    </script>
    <noscript>
        <h1>JavaScript is disabled</h1>
        In order to continue, you need to verify that you're not a robot by solving a CAPTCHA puzzle.
         The CAPTCHA puzzle requires JavaScript. Enable JavaScript and then reload the page.
    </noscript>
</body>
</html>
C:\Users\kitani.emi>

レスポンスとして AWS WAF の CAPTCHA アクションで返される "Human Verification" ページの HTML が返ってきました。このようにコマンドで URL にアクセスしようとした場合、パズルは実施できません。

3-1. curl でアクセスした際の CAPTCHA 失敗ログ

CAPTCHA ログ(クリックで展開)
{
    "timestamp": 1710513097145,
    "formatVersion": 1,
    "webaclId": "arn:aws:wafv2:ap-northeast-1:1234567890:regional/webacl/test-waf-webacl/xxxxxxxxxx",
    "terminatingRuleId": "test-rule-group",
    "terminatingRuleType": "GROUP",
    "action": "CAPTCHA",
    "terminatingRuleMatchDetails": [],
    "httpSourceName": "ALB",
    "httpSourceId": "1234567890-app/test-alb/xxxxxxxxxx",
    "ruleGroupList": [
        {
            "ruleGroupId": "arn:aws:wafv2:ap-northeast-1:1234567890:regional/rulegroup/test-rule-group/xxxxxxxxxx",
            "terminatingRule": {
                "ruleId": "test-rule",
                "action": "CAPTCHA",
                "ruleMatchDetails": null
            },
            "nonTerminatingMatchingRules": [],
            "excludedRules": null,
            "customerConfig": null
        }
    ],
    "rateBasedRuleList": [],
    "nonTerminatingMatchingRules": [],
    "requestHeadersInserted": null,
    "responseCodeSent": 405,
    "httpRequest": {
        "clientIp": "xx.xx.xx.xx",
        "country": "JP",
        "headers": [
            {
                "name": "Accept",
                "value": "*/*"
            },
            {
                "name": "User-Agent",
                "value": "curl/8.0.1"
            },
            {
                "name": "Host",
                "value": "test-alb-xxxxxxxxxx.ap-northeast-1.elb.amazonaws.com"
            }
        ],
        "uri": "/",
        "args": "",
        "httpVersion": "HTTP/1.1",
        "httpMethod": "GET",
        "requestId": "1-65f45bc9-7e14f2064d21c2595953507c"
    },
    "captchaResponse": {
        "responseCode": 405,
        "failureReason": "TOKEN_MISSING"
    }
}

httpRequest フィールドの curl/8.0.1 から、curl コマンドを使って Web サイトにアクセスしたことが分かります。レスポンスとして CAPTCHA を含む HTML ページが返されました。
action フィールドは CAPTCHA となっており、terminatingRuleIdterminatingRuleType のフィールドから、「test-rule-group」ルールグループが CAPTCHA アクションをトリガーしたことがわかります。

captchaResponse.failureReason フィールドは TOKEN_MISSING となっており、リクエストに CAPTCHA トークンが含まれていなかったことを示しています。
captchaResponse.responseCode フィールドは 405 となっており、これは HTTP ステータスコード 405(Method Not Allowed)に対応しています。

4. ブラウザからアクセス:CAPTCHA 失敗画面

では、意図的に何度も CAPTCHA パズルを間違えてみます。

10 回くらいまで数えていたのですが、特にエラーが出たりやブロックされたりすることもなくずーっとパズルをやり続けられるので、途中で数えるのを諦めました。30~40 回くらいは間違えたと思います。

10 分くらい間違え続けると、日本語で出ていたエラー文章が英語になってきました。

永遠に繰り返せそうなので、ここで一旦ストップしてログを見てみることにします。

4-1. ブラウザからアクセスした際の CAPTCHA 失敗ログ

ずっと間違え続けましたが、ログは最初の 2、3 秒で 3 件出たっきりでした。

1 件目 CAPTCHA ログ(クリックで展開)
{
    "timestamp": 1710513805447,
    "formatVersion": 1,
    "webaclId": "arn:aws:wafv2:ap-northeast-1:123456789012:regional/webacl/test-waf-webacl/xxxxxxxxxx",
    "terminatingRuleId": "test-rule-group",
    "terminatingRuleType": "GROUP",
    "action": "CAPTCHA",
    "terminatingRuleMatchDetails": [],
    "httpSourceName": "ALB",
    "httpSourceId": "123456789012-app/test-alb/xxxxxxxxxx",
    "ruleGroupList": [
        {
            "ruleGroupId": "arn:aws:wafv2:ap-northeast-1:123456789012:regional/rulegroup/test-rule-group/xxxxxxxxxx",
            "terminatingRule": {
                "ruleId": "test-rule",
                "action": "CAPTCHA",
                "ruleMatchDetails": null
            },
            "nonTerminatingMatchingRules": [],
            "excludedRules": null,
            "customerConfig": null
        }
    ],
    "rateBasedRuleList": [],
    "nonTerminatingMatchingRules": [],
    "requestHeadersInserted": null,
    "responseCodeSent": 405,
    "httpRequest": {
        "clientIp": "xx.xx.xx.xx",
        "country": "JP",
        "headers": [
            {
                "name": "Cookie",
                "value": "aws-waf-token=xxxxxxxxxx"
            },
            {
                "name": "Connection",
                "value": "keep-alive"
            },
            {
                "name": "User-Agent",
                "value": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0"
            },
            {
                "name": "Accept",
                "value": "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8"
            },
            {
                "name": "Referer",
                "value": "http://test-alb-xxxxxxxxxx.ap-northeast-1.elb.amazonaws.com/"
            },
            {
                "name": "Accept-Encoding",
                "value": "gzip, deflate"
            },
            {
                "name": "Accept-Language",
                "value": "ja,en;q=0.9,en-GB;q=0.8,en-US;q=0.7"
            },
            {
                "name": "Host",
                "value": "test-alb-xxxxxxxxxx.ap-northeast-1.elb.amazonaws.com"
            }
        ],
        "uri": "/favicon.ico",
        "args": "",
        "httpVersion": "HTTP/1.1",
        "httpMethod": "GET",
        "requestId": "1-65f45e8d-6e961fa77284f1205c108a79"
    },
    "captchaResponse": {
        "responseCode": 405,
        "failureReason": "TOKEN_EXPIRED"
    }
}
2 件目 CAPTCHA ログ(クリックで展開)
{
    "timestamp": 1710513807658,
    "formatVersion": 1,
    "webaclId": "arn:aws:wafv2:ap-northeast-1:123456789012:regional/webacl/test-waf-webacl/xxxxxxxxxx",
    "terminatingRuleId": "test-rule-group",
    "terminatingRuleType": "GROUP",
    "action": "CAPTCHA",
    "terminatingRuleMatchDetails": [],
    "httpSourceName": "ALB",
    "httpSourceId": "123456789012-app/test-alb/xxxxxxxxxx",
    "ruleGroupList": [
        {
            "ruleGroupId": "arn:aws:wafv2:ap-northeast-1:123456789012:regional/rulegroup/test-rule-group/xxxxxxxxxx",
            "terminatingRule": {
                "ruleId": "test-rule",
                "action": "CAPTCHA",
                "ruleMatchDetails": null
            },
            "nonTerminatingMatchingRules": [],
            "excludedRules": null,
            "customerConfig": null
        }
    ],
    "rateBasedRuleList": [],
    "nonTerminatingMatchingRules": [],
    "requestHeadersInserted": null,
    "responseCodeSent": 405,
    "httpRequest": {
        "clientIp": "xx.xx.xx.xx",
        "country": "JP",
        "headers": [
            {
                "name": "If-Modified-Since",
                "value": "Tue, 12 Mar 2024 06:31:04 GMT"
            },
            {
                "name": "Connection",
                "value": "keep-alive"
            },
            {
                "name": "Cache-Control",
                "value": "max-age=0"
            },
            {
                "name": "Upgrade-Insecure-Requests",
                "value": "1"
            },
            {
                "name": "User-Agent",
                "value": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0"
            },
            {
                "name": "Accept",
                "value": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7"
            },
            {
                "name": "Accept-Encoding",
                "value": "gzip, deflate"
            },
            {
                "name": "Accept-Language",
                "value": "ja,en;q=0.9,en-GB;q=0.8,en-US;q=0.7"
            },
            {
                "name": "Cookie",
                "value": "aws-waf-token=xxxxxxxxxx"
            },
            {
                "name": "If-None-Match",
                "value": "\"8d-61370cd6e059b\""
            },
            {
                "name": "Host",
                "value": "test-alb-xxxxxxxxxx.ap-northeast-1.elb.amazonaws.com"
            }
        ],
        "uri": "/",
        "args": "",
        "httpVersion": "HTTP/1.1",
        "httpMethod": "GET",
        "requestId": "1-65f45e8f-7fa310de1a865d81291e9ba4"
    },
    "captchaResponse": {
        "responseCode": 405,
        "failureReason": "TOKEN_EXPIRED"
    }
}
3 件目 CAPTCHA ログ(クリックで展開)
{
    "timestamp": 1710513808478,
    "formatVersion": 1,
    "webaclId": "arn:aws:wafv2:ap-northeast-1:123456789012:regional/webacl/test-waf-webacl/xxxxxxxxxx",
    "terminatingRuleId": "test-rule-group",
    "terminatingRuleType": "GROUP",
    "action": "CAPTCHA",
    "terminatingRuleMatchDetails": [],
    "httpSourceName": "ALB",
    "httpSourceId": "123456789012-app/test-alb/xxxxxxxxxx",
    "ruleGroupList": [
        {
            "ruleGroupId": "arn:aws:wafv2:ap-northeast-1:123456789012:regional/rulegroup/test-rule-group/xxxxxxxxxx",
            "terminatingRule": {
                "ruleId": "test-rule",
                "action": "CAPTCHA",
                "ruleMatchDetails": null
            },
            "nonTerminatingMatchingRules": [],
            "excludedRules": null,
            "customerConfig": null
        }
    ],
    "rateBasedRuleList": [],
    "nonTerminatingMatchingRules": [],
    "requestHeadersInserted": null,
    "responseCodeSent": 405,
    "httpRequest": {
        "clientIp": "xx.xx.xx.xx",
        "country": "JP",
        "headers": [
            {
                "name": "Cookie",
                "value": "aws-waf-token=xxxxxxxxxx"
            },
            {
                "name": "Connection",
                "value": "keep-alive"
            },
            {
                "name": "User-Agent",
                "value": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0"
            },
            {
                "name": "Accept",
                "value": "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8"
            },
            {
                "name": "Referer",
                "value": "http://test-alb-xxxxxxxxxx.ap-northeast-1.elb.amazonaws.com/"
            },
            {
                "name": "Accept-Encoding",
                "value": "gzip, deflate"
            },
            {
                "name": "Accept-Language",
                "value": "ja,en;q=0.9,en-GB;q=0.8,en-US;q=0.7"
            },
            {
                "name": "Host",
                "value": "test-alb-xxxxxxxxxx.ap-northeast-1.elb.amazonaws.com"
            }
        ],
        "uri": "/favicon.ico",
        "args": "",
        "httpVersion": "HTTP/1.1",
        "httpMethod": "GET",
        "requestId": "1-65f45e90-38c8b5dc4da5b45f59be333c"
    },
    "captchaResponse": {
        "responseCode": 405,
        "failureReason": "TOKEN_EXPIRED"
    }
}

各リクエストは、ルールグループ内の終了ルール「test-rule」によって CAPTCHA アクションがトリガーされています。
全てのリクエストに対して 405(Method Not Allowed) のレスポンスコードが返されています。全てのリクエストには、aws-waf-token Cookie が含まれていますが、CAPTCHA レスポンスの失敗理由は全て "TOKEN_EXPIRED"となっており、CAPTCHA トークンが期限切れであったことを示しています。
1 件目と 3 件目のリクエストは "/favicon.ico" に対するリクエストで、2 件目は "/"(ルートパス)に対するリクエストです。これはブラウザが自動的にファビコンを要求しているからと思われます。
2 件目のリクエストには、If-Modified-Since ヘッダーとキャッシュ関連のヘッダーが含まれています。これはブラウザがキャッシュされたレスポンスを持っていることを示唆しています。

ちなみに、AWS WAF のログ取得はベストエフォートで、100% 完全に取得されるわけではないようです。

まれに、 AWS WAF ログの配信が 100% を下回ることもあり、ログはベストエフォートベースで配信されます。
AWS WAF ウェブ ACL トラフィックのログ記録 - AWS WAF、 AWS Firewall Manager、および AWS Shield Advanced

また、AWS マネージドルールのカウントは COUNT ではなく EXCLUDED_AS_COUNT で記録されるものもあります。取りこぼしたくない方はログにフィルタをかけず、すべてのログを取得するようにするといいでしょう。

Web ACL ダッシュボードから All trafic タブを見てみる

AWS WAF ではマネジメントコンソールで Web ACL ダッシュボードが提供されています。

今回の CAPTCHA 試行後にダッシュボードも確認してみました。

まずはダッシュボードに表示する時間を設定します。

どのアクションでフィルタするかは上部のフィルタ部分もしくはフィルタの下のボードでチェックを ON/OFF することでもフィルタできます。

Action totals では実行されたアクションごとに回数の折れ線グラフを表示できます。何度も CAPTCHA を実行したのですが、カウントとしてはやはり 3 回になっているようです。
非常に小さいですが、ALLOW の赤紫色の点も 23:15 のちょっと前にポチっと表示されています。

Top 10 rules では選択した時間範囲内で最も多くのリクエストに一致した 10 個のルールのリクエスト数が表示されます。カスタムルールグループで引っかかった CAPTCHA と、何も引っかからず最後まで評価されて Web ACL のデフォルトアクション ALLOW が実行されたのとで 2 種類だけ表示されていますね。

下記画像の左上では Bot 検出を確認できます。赤紫色っぽい部分が Non-bots(Bot ではない)、青い部分がUnverified(未検証)となっています。
その隣はアクセスしてきたクライアントのデバイスタイプです。今回ブラウザからのみアクセスしていたので 100% Desktop となっています。
その下はアクセスした国の Top 10 です。日本からしか試していないので青一色です。

次は CAPTCHA に関するダッシュボードです。
Successful CAPTCHA puzzle responses は成功した CAPTCHA パズルの応答で、縦軸は % です。

その右の CAPTCHA puzzles that were abandoned or not solved successfully は放棄されたまたは正常に解決されなかった CAPTCHA パズルの回答です。後半でたくさん間違えたので時間がたつにつれて線が 100% になっています。

CAPTCHA の料金

CAPTCHA ルールアクションをルールで使用、あるいはルールグループでルールアクションのオーバーライドとして使用すると、追加料金が請求されます。

ルールアクション 料金
Captcha 試行 1 万回につき $4.00

CAPTCHA チャレンジの結果は関係ありません。間違えたり制限時間を超えたりするなど、1 回の CAPTCHA レスポンスで複数の試行が含まれることもあります。

参考