AWS WAFでカウントされたトラフィックをフィールドインデックスを用いて効率良くCloudWatch Logs Insightsでクエリしてみたかった

AWS WAFでカウントされたトラフィックをフィールドインデックスを用いて効率良くCloudWatch Logs Insightsでクエリしてみたかった

AWS WAFでカウントされたトラフィックをフィールドインデックスを用いて効率良くCloudWatch Logs Insightsでクエリするのは難易度が高い
Clock Icon2025.03.17

カウントされたトラフィックを抽出したい

こんにちは、のんピ(@non____97)です。

皆さんはAWS WAFの運用をしていて、カウントされたトラフィックを抽出したいなと思ったことはありますか? 私はあります。

新規にルールを追加する際は、いきなりブロックするのではなく、まずはカウントモードとして様子を見ることがほとんどだと思います。

https://dev.classmethod.jp/articles/aws-waf-operations-and-maintenance/

では、カウントモード中に実際に検出されたトラフィックはどのように確認すれば良いでしょうか。

CloudWatch Logsに出力している場合はCloudWatch Logs Insights、S3場合に出力している場合はAthenaによるクエリが思い付きます。

ただ、最近AWS WAFのTraffic overviewダッシュボードに追加されたTop Insightsが便利すぎて、CloudWatch Logsに出力している場合も多いのではないでしょうか。

https://dev.classmethod.jp/articles/waf-console-top-insights-visualizations/

ということで、こちらの記事ではCloudWatch Logs Insightsを用いて、カウントされたトラフィックを抽出してみます。

いきなりまとめ

  • フィールドインデックスを用いて、AWS WAFのルールでカウントされたトラフィックを検出することは難しい
  • 現実的にはフィールドインデックスの効かないlikeを用いて検索することになりそう

どのような処理が必要かの検討

まず、どのような処理が必要かの検討をしてみましょう。

ログの配信にALLOWを含まないようにしている場合は、ログオブジェクト直下が"action" : "allow"であることを条件に、全てのルールでブロックされなかったトラフィックを確認することが可能です。

ただし、以下のような場合にはこれだけでは検出することができません。

  1. ログの配信対象にALLOWも含んでいる場合
  2. 最終的にブロックやCAPTCHA、Challengeされたトラフィックであっても途中のルールでカウントされたものを抽出したい場合

1つ目については「"action" : "count"で検出できるのでは?」と、つい思われがちですが、出来ません。ログオブジェクト直下のactionフィールドにはcountは含まれないためです。

action

The terminating action that AWS WAF applied to the request. This indicates either allow, block, CAPTCHA, or challenge. The CAPTCHA and Challenge actions are terminating when the web request doesn't contain a valid token.

Log fields for web ACL traffic - AWS WAF, AWS Firewall Manager, and AWS Shield Advanced

2つ目については、COUNTEXCLUDED_AS_COUNTが非終了アクションではないことが関係しています。

COUNTEXCLUDED_AS_COUNTと判定されたルールは、そのタイミングで以降のルール評価を行わないのではなく、優先度に基づいて継続してルールの評価を行います。

つまりは、対象の全ルールの評価をする過程でCOUNTEXCLUDED_AS_COUNTと判定されたとしても、最終的に許可されるのかブロックされるのかは後続のルールもしくはデフォルトアクション次第です。場合によってはブロックされることもあるでしょう。

結果として、"action" : "allow"のみで抽出する場合、最終的に許可されたものしか抽出することができません。

そのため、「ログオブジェクト直下が"action" : "allow"であることを条件に」だけではなく、別の手法で抽出する必要があります。

カウントされたトラフィックは以下のように記録されています。

  • WAF ログ直下の "nonTerminatingMatchingRules" フィールドに "action": "COUNT" として記録
  • "ruleGroupList" フィールド内の "nonTerminatingMatchingRules" フィールドに "action": "COUNT" として記録
  • "ruleGroupList" フィールド内の "excludedRules" フィールドに "exclusionType": "EXCLUDED_AS_COUNT" として記録

詳細は以下記事をご覧ください。

https://dev.classmethod.jp/articles/count-recorded-in-waflog/

AWS公式ブログではCloudWatch Logs Insightsを使用する場合、以下のようなクエリで抽出をしていました。

https://aws.amazon.com/jp/blogs/news/aws-waf-log-analysis-considerations/

CloudWatch Logs Insights で “EXCLUDED_AS_COUNT” を抽出するためのクエリ
fields @timestamp, @message
| filter @message like "EXCLUDED_AS_COUNT"
| sort @timestamp desc
| parse @message '"excludedRules":[{*}]' as excludedRules
| display @timestamp, httpRequest.clientIp, httpRequest.uri, httpRequest.country, excludedRules
CloudWatch Logs Insights で “action”
fields @timestamp, @message
| filter @message like /"action":"COUNT"/
| sort @timestamp desc
| parse @message '"nonTerminatingMatchingRules":[{*}]' as nonTerminatingMatchingRules
| display @timestamp, httpRequest.clientIp, httpRequest.uri, httpRequest.country, nonTerminatingMatchingRules

しかし、こちらの手法ですとCloudWatch Logsのフィールドインデックスの使用ができません。

フィールドインデックスを使用した場合はスキャン量を減らすことができ、コストおよび実行時間を削減することが可能です。

https://dev.classmethod.jp/articles/cwlogs-support-field-index/

しかし、上述のクエリ例ではフィールドインデックスをサポートしないlikeが使用されています。

フィールドインデックスの改善の恩恵を受けるのfilter fieldName IN...は、 filter fieldName =...と を持つクエリのみです。を使用したクエリではインデックスは使用filter fieldName likeされず、選択したロググループのすべてのログイベントを常にスキャンします。

フィルター - Amazon CloudWatch Logs

そのため、フィールドインデックスを有効活用したい場合においては、こちらのクエリをそのまま採用することはできません。

ALLOWも出力しているなどログ配信量が多い場合に、フィールドインデックスが有効活用されない場合CloudWatch Logs Insightsを実行する度にかかるコストが気になります。

ということで、それらを意識しながらクエリを組み立てる必要があります。

ルールグループの個別ルール単位でCOUNTをしていない かつ CAPTCHAChallengeを使用していない場合

先に結論から紹介すると、全ての要件を満たすクエリを作り出すことはできませんでした。

要件を整理すると以下が挙げられます。

  1. フィールドインデックスを用いたコスト削減ができること
  2. ログの配信対象にALLOWCAPTCHAChallengeも含んでいる場合もCOUNTもしくはEXCLUDED_AS_COUNTのみ抽出できること
  3. カスタムルールのアクションをCOUNTに設定したルールの検出ができること
  4. ルールグループのアクションがCOUNTにオーバーライドされたルールグループの検出ができること
  5. ルールグループ内、個別のルールのデフォルトアクションをCOUNTに設定したルールの検出ができること
  6. ルールグループ内、個別のルールのアクションをCOUNTにオーバライドされたルールの検出ができること
  7. ルールグループ内、個別のルールのアクションをExcludedRulesを用いて、COUNTとしたルールの検出ができること

「手軽に」「コストを極力抑えるために」という意味合いで要件1の「フィールドインデックスを用いたコスト削減ができること」を満たそうとすると、どうしても実現できない要件があります。

例えば以下のクエリは以下の要件のみ満たします。

「1. フィールドインデックスを用いたコスト削減ができること」
「3. カスタムルールのアクションをCOUNTに設定したルールの検出ができること」
「4. ルールグループのアクションがCOUNTにオーバーライドされたルールグループの検出ができること」

fields @timestamp, 
       @message
| filterIndex nonTerminatingMatchingRules.0.action="COUNT"
| sort @timestamp desc
| parse @message '"nonTerminatingMatchingRules":[{*}]' as nonTerminatingMatchingRulesObject
| limit 100
| display @timestamp, 
       httpRequest.clientIp, 
       httpRequest.uri, 
       httpRequest.httpMethod, 
       httpRequest.country, 
       action, 
       terminatingRuleId, 
       httpRequest.requestId, 
       nonTerminatingMatchingRulesObject

ルールグループの個別ルール単位でCOUNTをしていない かつ CAPTCHAChallengeを使用していない場合にのみ使用できます。以下の要件を満たすことが必要な場合は使用できません。

「2. ログの配信対象にALLOWCAPTCHAChallengeも含んでいる場合もCOUNTもしくはEXCLUDED_AS_COUNTのみ抽出できること」
「5. ルールグループ内、個別のルールのデフォルトアクションをCOUNTに設定したルールの検出ができること」
「6. ルールグループ内、個別のルールのアクションをCOUNTにオーバライドされたルールの検出ができること」
「7. ルールグループ内、個別のルールのアクションをExcludedRulesを用いて、COUNTとしたルールの検出ができること」

まず、「2. ログの配信対象にALLOWCAPTCHAChallengeも含んでいる場合もCOUNTもしくはEXCLUDED_AS_COUNTのみ抽出できること」という要件についてです。

これは、filterIndex nonTerminatingMatchingRules.0.action="COUNT"と関係があります。

nonTerminatingMatchingRulesは非終了ルールの配列です。また、actioncount以外も存在します。

nonTerminatingMatchingRules

The list of non-terminating rules that matched the request. Each item in the list contains the following information.

action
The action that AWS WAF applied to the request. This indicates either count, CAPTCHA, or challenge. The CAPTCHA and Challenge are non-terminating when the web request contains a valid token.

ruleId
The ID of the rule that matched the request and was non-terminating.

ruleMatchDetails
Detailed information about the rule that matched the request. This field is only populated for SQL injection and cross-site scripting (XSS) match rule statements. A matching rule might require a match for more than one inspection criteria, so these match details are provided as an array of match criteria.

Any additional information provided for each rule varies according factors such as the rule configuration, rule match type, and details of the match. For example for rules with a CAPTCHA or Challenge action, the captchaResponse or challengeResponse will be listed. If the matching rule is in a rule group and you've overridden its configured rule action, the configured action will be provided in overriddenAction.

Log fields for web ACL traffic - AWS WAF, AWS Firewall Manager, and AWS Shield Advanced

そのため、filterIndex nonTerminatingMatchingRules.0.action="COUNT"とした場合、COUNT以外のトラフィックを検出することができません。

また、配列の0番目を見ているため、0番目の要素がCAPTCHAChallengeなどCOUNT以外のactionが取られた場合もトラフィックを検出することができません。

filterIndex nonTerminatingMatchingRules.*.action="COUNT"filterIndex nonTerminatingMatchingRules.[*].action="COUNT"といった指定をすればできるのでは?」と思われるかも知れませんが、2025/3/17時点でCloudWatch Logs Insightsでそのような構文はサポートされていません。

つまりは配列内のフィールドとフィールドインデックスとの相性は良くありません。

結果として、CAPTCHAChallengeを使用している場合はこのクエリを使用することはできません。

「5. ルールグループ内、個別のルールのデフォルトアクションをCOUNTに設定したルールの検出ができること」
「6. ルールグループ内、個別のルールのアクションをCOUNTにオーバライドされたルールの検出ができること」
「7. ルールグループ内、個別のルールのアクションをExcludedRulesを用いて、COUNTとしたルールの検出ができること」

については、COUNTもしくはEXCLUDED_AS_COUNTとして判定されたルールは、ruleGroupListの配列の中のnonTerminatingMatchingRulesおよびexcludedRulesにあるというのがポイントです。

ruleGroupList.*.nonTerminatingMatchingRules配下にあることを確認してみましょう。

AWSManagedRulesCommonRuleSetNoUserAgent_HEADERGenericLFI_URIPATHRestrictedExtensions_QUERYARGUMENTSAWSManagedRulesKnownBadInputsRuleSetExploitablePaths_URIPATHCOUNTにオーバーライドしました。

この状態で以下のようにNoUserAgent_HEADERRestrictedExtensions_QUERYARGUMENTSExploitablePaths_URIPATHがマッチするようにアクセスします。

>  curl -I https://www.non-97.net/WEB-INF/web.xml?id=test.ini -H "User-Agent:"
HTTP/2 404
content-type: text/html
content-length: 12
date: Mon, 17 Mar 2025 00:17:38 GMT
last-modified: Tue, 25 Feb 2025 02:38:39 GMT
etag: "347dfa37997b9353b3da6992f8753439"
x-amz-server-side-encryption: AES256
accept-ranges: bytes
server: AmazonS3
x-cache: Error from cloudfront
via: 1.1 792d1dfcd0e864258cddb08b00eca5d8.cloudfront.net (CloudFront)
x-amz-cf-pop: NRT12-C3
alt-svc: h3=":443"; ma=86400
x-amz-cf-id: M1CG8alBGw4Bbqk1bhQWCImSGRyu9nt8bjfz1NcralRm28HLpfxI4g==
age: 17952
x-xss-protection: 1; mode=block
x-frame-options: SAMEORIGIN
referrer-policy: strict-origin-when-cross-origin
x-content-type-options: nosniff
strict-transport-security: max-age=31536000

404ということで、拒否はされていないようです。

WAFのログを見ます。

{
    "timestamp": 1742188608710,
    "formatVersion": 1,
    "webaclId": "arn:aws:wafv2:us-east-1:<AWSアカウントID>:global/webacl/website/84a704a7-6965-44af-887e-d196dcd881ac",
    "terminatingRuleId": "Default_Action",
    "terminatingRuleType": "REGULAR",
    "action": "ALLOW",
    "terminatingRuleMatchDetails": [],
    "httpSourceName": "CF",
    "httpSourceId": "ELGQOVCCUO3ME",
    "ruleGroupList": [
        {
            "ruleGroupId": "AWS#AWSManagedRulesCommonRuleSet",
            "terminatingRule": null,
            "nonTerminatingMatchingRules": [
                {
                    "ruleId": "NoUserAgent_HEADER",
                    "action": "COUNT",
                    "overriddenAction": "BLOCK",
                    "ruleMatchDetails": []
                },
                {
                    "ruleId": "RestrictedExtensions_QUERYARGUMENTS",
                    "action": "COUNT",
                    "overriddenAction": "BLOCK",
                    "ruleMatchDetails": [
                        {
                            "conditionType": "REGEX",
                            "location": "ALL_QUERY_ARGS",
                            "matchedData": null,
                            "matchedFieldName": ""
                        }
                    ]
                }
            ],
            "excludedRules": null,
            "customerConfig": null
        },
        {
            "ruleGroupId": "AWS#AWSManagedRulesKnownBadInputsRuleSet",
            "terminatingRule": null,
            "nonTerminatingMatchingRules": [
                {
                    "ruleId": "ExploitablePaths_URIPATH",
                    "action": "COUNT",
                    "overriddenAction": "BLOCK",
                    "ruleMatchDetails": [
                        {
                            "conditionType": "REGEX",
                            "location": "URI",
                            "matchedData": null,
                            "matchedFieldName": ""
                        }
                    ]
                }
            ],
            "excludedRules": null,
            "customerConfig": null
        },
        {
            "ruleGroupId": "AWS#AWSManagedRulesAmazonIpReputationList",
            "terminatingRule": null,
            "nonTerminatingMatchingRules": [],
            "excludedRules": null,
            "customerConfig": null
        },
        {
            "ruleGroupId": "AWS#AWSManagedRulesAnonymousIpList",
            "terminatingRule": null,
            "nonTerminatingMatchingRules": [],
            "excludedRules": null,
            "customerConfig": null
        }
    ],
    "rateBasedRuleList": [],
    "nonTerminatingMatchingRules": [],
    "requestHeadersInserted": null,
    "responseCodeSent": null,
    "httpRequest": {
        "clientIp": "<送信元IPアドレス>",
        "country": "JP",
        "headers": [
            {
                "name": "host",
                "value": "www.non-97.net"
            },
            {
                "name": "accept",
                "value": "*/*"
            }
        ],
        "uri": "/WEB-INF/web.xml",
        "args": "id=test.ini",
        "httpVersion": "HTTP/2.0",
        "httpMethod": "HEAD",
        "requestId": "M1CG8alBGw4Bbqk1bhQWCImSGRyu9nt8bjfz1NcralRm28HLpfxI4g==",
        "fragment": "",
        "scheme": "https",
        "host": "www.non-97.net"
    },
    "labels": [
        {
            "name": "awswaf:managed:aws:core-rule-set:NoUserAgent_Header"
        },
        {
            "name": "awswaf:managed:aws:known-bad-inputs:ExploitablePaths_URIPath"
        },
        {
            "name": "awswaf:managed:aws:core-rule-set:RestrictedExtensions_QueryArguments"
        }
    ],
    "ja3Fingerprint": "5ba6f86deff79afc9902f9927d1c1697",
    "ja4Fingerprint": "t13d3013h2_1d37bd780c83_e10b9050f4c9"
}

ruleGroupList内のnonTerminatingMatchingRulesで検出されていることが分かります。

先述のとおり、配列内のフィールドとフィールドインデックスとの相性は良くないため、ruleGroupList.*.nonTerminatingMatchingRules.*.action="COUNT"といったことはできません。

こちらのクエリで実際にフィールドインデックスが効くことを確認してみましょう。

フィールドインデックスはnonTerminatingMatchingRules.0.actionを指定しました。

AWSManagedRulesCommonRuleSetAWSManagedRulesKnownBadInputsRuleSetOverride rule group action to countを有効にします。

個別ルールのオーバーライドは削除しておきます。

この状態で、COUNT非対象のリクエストを5回、COUNT対象のリクエストを5回実行します。

>  curl -I https://www.non-97.net/ -s >/dev/null
>  curl -I https://www.non-97.net/ -s >/dev/null
>  curl -I https://www.non-97.net/ -s >/dev/null
>  curl -I https://www.non-97.net/ -s >/dev/null
>  curl -I https://www.non-97.net/ -s >/dev/null

>  curl -I https://www.non-97.net/WEB-INF/web.xml?id=test.ini -H "User-Agent:" -s >/dev/null
>  curl -I https://www.non-97.net/WEB-INF/web.xml?id=test.ini -H "User-Agent:" -s >/dev/null
>  curl -I https://www.non-97.net/WEB-INF/web.xml?id=test.ini -H "User-Agent:" -s >/dev/null
>  curl -I https://www.non-97.net/WEB-INF/web.xml?id=test.ini -H "User-Agent:" -s >/dev/null
>  curl -I https://www.non-97.net/WEB-INF/web.xml?id=test.ini -H "User-Agent:" -s >/dev/null

実行結果は以下のとおりです。

1.フィールドインデックスが効いていること.png

フィールドインデックスを利用と表示されていることから、フィールドインデックスに基づいてクエリされていることが分かります。

なお、スキャンされたのが5レコードではなく、6レコードなのは謎です。何回か試しましたが、いずれも本来のレコード数+1となっていました。

他にもisempty()ispresent()など関数を用いてフィールドを加工した場合や否定を使ったクエリも考えましたが、いずれもインデックスは効きません。当然といえば当然です。RDBと同じですね。

https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/logs/CWL_QuerySyntax-operations-functions.html

AWS WAFでカウントされたトラフィックをフィールドインデックスを用いて効率良くCloudWatch Logs Insightsでクエリするのは難易度が高い

AWS WAFでカウントされたトラフィックをフィールドインデックスを用いて効率良くCloudWatch Logs Insightsでクエリしようとしてみました。

結論、難易度が高いです。

カウントされたルールの数などの専用のフィールドがなければ、フィールドインデックスを用いてカウントされたトラフィックのみを抽出するのは難しいと考えます。

現実的にはフィールドインデックスの効かないlikeを用いて検索することになりそうです。

この記事が誰かの助けになれば幸いです。

以上、クラウド事業本部 コンサルティング部の のんピ(@non____97)でした!

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.