カウントモードでWafCharmレポートを生成してみた
こんにちは。たかやまです。
WafCharm はWebに対する攻撃パターンをAIによって学習し、環境に応じたリクエストのパターン等を判別、AWS WAFのルールを最適化してくれるサービスとなります。
そんなWafCharmですが、どのような攻撃をされているか可視化するレポート機能があります。
引用元 : WafCharm月次レポート機能の紹介
こちらのレポート機能ですが、WafCharmをCountモードで運用している場合も生成されるか気になり試してみました。
さきにまとめ
- Countモード運用時でもレポートは生成される
- Countモード時は1リクエストで複数のルールにマッチすることがあり、Attack Country Ranking/Attack IP Rankingの検知数とAttack Typeの検知数が一致しないことがある
確認してみた
そもそもCountモードとは
Countモードはセキュリティルールに一致するリクエストをブロックせずに、一致したリクエストの数をカウントし、分析やルールのテストに役立てることができる設定モードです。
AWS WAFを本番運用する場合は、Countモードで運用し、ルールのテストや分析を行い、ルールの調整を行った後に、ブロックモードに切り替えるのが一般的になります。
WafCharmのCountモード設定はWafCharmコンソールのWeb ACL Configから設定できます。
WafCharmコンソールの設定はすべてのWafCharmが管理するすべてのルールグループに適用されるため、個別のルールごとにアクションを変更したい場合にはAWS WAFのコンソールから設定する必要があります。
WafCharmレポート機能の設定
WafCharmのレポート機能の設定はこちらのブログを参考にしてください。
今回の検証ではログ出力パターンで「S3直接出力」しているログレポート機能を実装しています。
Countモードでのレポート生成
今回はWafCharmをCountモードで設定し、GMOサイバーセキュリティ脆弱性診断をおこなってみました。
結果としてWafCharmレポートはCountモードでも問題なく生成されます*1。
1レポートは翌月の月初に閲覧可能になります。※検知がない場合はレポートは作成されません。
こちらが実際のWafCharmレポートです。
各項目は以下のような内容になっています。
- Attack Type : 攻撃種別ごとの分類
- Detected Rule Ranking : WafCharmが展開したルールで検知した上位Top10
- Attack Country Ranking : 攻撃元国ごとの上位Top10
- Attack IP Ranking : 攻撃元IPごとの上位Top10
レポートはカーソルを合わせると各セグメントの検知数を確認することができます。
こちらのレポートをもとに「どの程度悪意のあるリクエスト」があったのだろうと確認しているとAttack Country Ranking/Attack IP Rankingの検知数とAttack Typeの検知数が一致しないことに気づきました。
(Attack Country Ranking/Attack IP Rankingの検知数はTop10なので、10件以上ある場合はレポートでリクエストの全数を把握することはできなくなります。)
てっきり全体としての数値は一致するものと思い見ていたので、これではどれがリクエスト数として正しいのかわからなくなりました。。
WAFログについて調べてみるとWafCharmさんのブログに以下の通り記載されていました。
※あまり多いパターンではないかと思いますが、COUNTは複数のルールでマッチしている可能性があります。COUNT の詳細を確認する必要がある場合は該当のログ内容を全て確認することが推奨です。
https://www.wafcharm.com/jp/blog/amazon-cloudwatch-logs-wafcharm-analysis-ja/
なるほど?Countモード時には1リクエストに対して複数のルールがマッチしている可能性があるとのことです。となるとAttack Country Ranking/Attack IP Rankingの検知数に比べてAttack Typeの検知数が多いのは複数のルールでマッチしている可能性が考えられます。
WAFログの確認
詳細なログ情報はWAFログを確認する必要があります。今回はS3に保存しているWAFログに対してAthenaを実行して確認してみたいと思います。
Athenaの事前準備はこちらのブログを参考にしてください。
Athenaでテーブル(ここではwaf_logs
)を用意できたら以下のクエリを実行します。
SELECT from_unixtime(timestamp/1000, 'Asia/Tokyo') AS JST, rule.excludedrules, rule.rulegroupid FROM "waf_logs" CROSS JOIN UNNEST(rulegrouplist) AS t(rule) WHERE rule.excludedrules IS NOT NULL
以下の結果はCountされたリクエストを一覧しています。
excludedrules
*2にCountされたルールの内容が記録されますが、一部リクエストには複数のルールがマッチしていることがわかります。
2Countモードの設定方法によってはログがexcludedrules
ではなくnonTerminatingMactingRules
に記録されます。詳細はこちら(AWS WAF のログ分析に関する考慮事項)
このクエリの検出数は333
件で、WafCharmレポートのAttack Country Ranking/Attack IP Rankingの検出数と一致することがわかります。
クエリ結果
"JST","excludedrules","rulegroupid" "2023-05-16 10:55:24.000 Asia/Tokyo","[{""rulematchdetails"":""null"",""exclusiontype"":""EXCLUDED_AS_COUNT"",""ruleid"":""XSS-body-001""}]","arn:aws:wafv2:us-east-1:xxxxxxxxxxxx:global/rulegroup/WafCharm_Common_Basic_Group/494dbf2c-ad20-4a3f-974f-733f4b989550" "2023-05-16 10:55:22.000 Asia/Tokyo","[{""rulematchdetails"":""null"",""exclusiontype"":""EXCLUDED_AS_COUNT"",""ruleid"":""XSS-body-001""}]","arn:aws:wafv2:us-east-1:xxxxxxxxxxxx:global/rulegroup/WafCharm_Common_Basic_Group/494dbf2c-ad20-4a3f-974f-733f4b989550" "2023-05-16 10:55:26.000 Asia/Tokyo","[{""rulematchdetails"":""null"",""exclusiontype"":""EXCLUDED_AS_COUNT"",""ruleid"":""XSS-body-001""}]","arn:aws:wafv2:us-east-1:xxxxxxxxxxxx:global/rulegroup/WafCharm_Common_Basic_Group/494dbf2c-ad20-4a3f-974f-733f4b989550" "2023-05-16 10:55:31.000 Asia/Tokyo","[{""rulematchdetails"":""null"",""exclusiontype"":""EXCLUDED_AS_COUNT"",""ruleid"":""XSS-body-001""}]","arn:aws:wafv2:us-east-1:xxxxxxxxxxxx:global/rulegroup/WafCharm_Common_Basic_Group/494dbf2c-ad20-4a3f-974f-733f4b989550" "2023-05-16 10:55:33.000 Asia/Tokyo","[{""rulematchdetails"":""null"",""exclusiontype"":""EXCLUDED_AS_COUNT"",""ruleid"":""XSS-qs-001""}]","arn:aws:wafv2:us-east-1:xxxxxxxxxxxx:global/rulegroup/WafCharm_Common_Basic_Group/494dbf2c-ad20-4a3f-974f-733f4b989550" "2023-05-16 15:03:41.000 Asia/Tokyo","[{""rulematchdetails"":""null"",""exclusiontype"":""EXCLUDED_AS_COUNT"",""ruleid"":""XSS-qs-001""}]","arn:aws:wafv2:us-east-1:xxxxxxxxxxxx:global/rulegroup/WafCharm_Common_Basic_Group/494dbf2c-ad20-4a3f-974f-733f4b989550" "2023-05-16 15:04:06.000 Asia/Tokyo","[{""rulematchdetails"":""null"",""exclusiontype"":""EXCLUDED_AS_COUNT"",""ruleid"":""XSS-body-001""}]","arn:aws:wafv2:us-east-1:xxxxxxxxxxxx:global/rulegroup/WafCharm_Common_Basic_Group/494dbf2c-ad20-4a3f-974f-733f4b989550" "2023-05-16 11:08:27.000 Asia/Tokyo","[{""rulematchdetails"":""null"",""exclusiontype"":""EXCLUDED_AS_COUNT"",""ruleid"":""SQLi-qs-001""}]","arn:aws:wafv2:us-east-1:xxxxxxxxxxxx:global/rulegroup/WafCharm_Common_Basic_Group/494dbf2c-ad20-4a3f-974f-733f4b989550" "2023-05-16 11:08:26.000 Asia/Tokyo","[{""rulematchdetails"":""null"",""exclusiontype"":""EXCLUDED_AS_COUNT"",""ruleid"":""OScmdi-qs-001""}]","arn:aws:wafv2:us-east-1:xxxxxxxxxxxx:global/rulegroup/WafCharm_Common_Basic_Group/494dbf2c-ad20-4a3f-974f-733f4b989550" "2023-05-16 11:08:28.000 Asia/Tokyo","[{""rulematchdetails"":""null"",""exclusiontype"":""EXCLUDED_AS_COUNT"",""ruleid"":""SQLi-qs-001""}]","arn:aws:wafv2:us-east-1:xxxxxxxxxxxx:global/rulegroup/WafCharm_Common_Basic_Group/494dbf2c-ad20-4a3f-974f-733f4b989550" "2023-05-16 11:08:25.000 Asia/Tokyo","[{""rulematchdetails"":""null"",""exclusiontype"":""EXCLUDED_AS_COUNT"",""ruleid"":""SQLi-qs-001""},{""rulematchdetails"":""null"",""exclusiontype"":""EXCLUDED_AS_COUNT"",""ruleid"":""XXE-qs-001""}]","arn:aws:wafv2:us-east-1:xxxxxxxxxxxx:global/rulegroup/WafCharm_Common_Basic_Group/494dbf2c-ad20-4a3f-974f-733f4b989550" "2023-05-16 11:08:27.000 Asia/Tokyo","[{""rulematchdetails"":""null"",""exclusiontype"":""EXCLUDED_AS_COUNT"",""ruleid"":""SQLi-qs-001""}]","arn:aws:wafv2:us-east-1:xxxxxxxxxxxx:global/rulegroup/WafCharm_Common_Basic_Group/494dbf2c-ad20-4a3f-974f-733f4b989550" "2023-05-16 11:08:26.000 Asia/Tokyo","[{""rulematchdetails"":""null"",""exclusiontype"":""EXCLUDED_AS_COUNT"",""ruleid"":""OScmdi-qs-001""}]","arn:aws:wafv2:us-east-1:xxxxxxxxxxxx:global/rulegroup/WafCharm_Common_Basic_Group/494dbf2c-ad20-4a3f-974f-733f4b989550" "2023-05-16 11:08:25.000 Asia/Tokyo","[{""rulematchdetails"":""null"",""exclusiontype"":""EXCLUDED_AS_COUNT"",""ruleid"":""SQLi-qs-001""},{""rulematchdetails"":""null"",""exclusiontype"":""EXCLUDED_AS_COUNT"",""ruleid"":""XXE-qs-001""}]","arn:aws:wafv2:us-east-1:xxxxxxxxxxxx:global/rulegroup/WafCharm_Common_Basic_Group/494dbf2c-ad20-4a3f-974f-733f4b989550" "2023-05-16 11:08:26.000 Asia/Tokyo","[{""rulematchdetails"":""null"",""exclusiontype"":""EXCLUDED_AS_COUNT"",""ruleid"":""OScmdi-qs-001""}]","arn:aws:wafv2:us-east-1:xxxxxxxxxxxx:global/rulegroup/WafCharm_Common_Basic_Group/494dbf2c-ad20-4a3f-974f-733f4b989550" "2023-05-16 11:08:30.000 Asia/Tokyo","[{""rulematchdetails"":""null"",""exclusiontype"":""EXCLUDED_AS_COUNT"",""ruleid"":""SQLi-qs-001""}]","arn:aws:wafv2:us-east-1:xxxxxxxxxxxx:global/rulegroup/WafCharm_Common_Basic_Group/494dbf2c-ad20-4a3f-974f-733f4b989550" "2023-05-16 11:08:26.000 Asia/Tokyo","[{""rulematchdetails"":""null"",""exclusiontype"":""EXCLUDED_AS_COUNT"",""ruleid"":""OScmdi-qs-001""}]","arn:aws:wafv2:us-east-1:xxxxxxxxxxxx:global/rulegroup/WafCharm_Common_Basic_Group/494dbf2c-ad20-4a3f-974f-733f4b989550" "2023-05-16 11:08:28.000 Asia/Tokyo","[{""rulematchdetails"":""null"",""exclusiontype"":""EXCLUDED_AS_COUNT"",""ruleid"":""SQLi-qs-001""}]","arn:aws:wafv2:us-east-1:xxxxxxxxxxxx:global/rulegroup/WafCharm_Common_Basic_Group/494dbf2c-ad20-4a3f-974f-733f4b989550" "2023-05-16 11:08:29.000 Asia/Tokyo","[{""rulematchdetails"":""null"",""exclusiontype"":""EXCLUDED_AS_COUNT"",""ruleid"":""SQLi-qs-001""}]","arn:aws:wafv2:us-east-1:xxxxxxxxxxxx:global/rulegroup/WafCharm_Common_Basic_Group/494dbf2c-ad20-4a3f-974f-733f4b989550" ・・・
次に、excludedrules
内でCountされたルールごとにリストしたいと思います。
以下のクエリはexcludedrules
をJSON配列として解析し、ruleidを抽出しています。
SELECT from_unixtime(timestamp/1000, 'Asia/Tokyo') AS JST, json_extract_scalar(excludedrule_obj, '$.ruleid') AS ruleid, rule.excludedrules, rule.rulegroupid FROM "waf_logs" CROSS JOIN UNNEST(rulegrouplist) AS t(rule) CROSS JOIN UNNEST(CAST(json_parse(rule.excludedrules) AS ARRAY(JSON))) AS u(excludedrule_obj) WHERE rule.excludedrules IS NOT NULL
このクエリの検出数は353
件で、WafCharmレポートのAttack Typeの検出数と一致することがわかります。
クエリ結果
"JST","ruleid","excludedrules","rulegroupid" "2023-05-16 10:40:38.000 Asia/Tokyo","OScmdi-body-001","[{""rulematchdetails"":""null"",""exclusiontype"":""EXCLUDED_AS_COUNT"",""ruleid"":""OScmdi-body-001""}]","arn:aws:wafv2:us-east-1:xxxxxxxxxxxx:global/rulegroup/WafCharm_Common_Basic_Group/494dbf2c-ad20-4a3f-974f-733f4b989550" "2023-05-16 10:40:34.000 Asia/Tokyo","SQLi-body-001","[{""rulematchdetails"":""null"",""exclusiontype"":""EXCLUDED_AS_COUNT"",""ruleid"":""SQLi-body-001""},{""rulematchdetails"":""null"",""exclusiontype"":""EXCLUDED_AS_COUNT"",""ruleid"":""XXE-body-001""}]","arn:aws:wafv2:us-east-1:xxxxxxxxxxxx:global/rulegroup/WafCharm_Common_Basic_Group/494dbf2c-ad20-4a3f-974f-733f4b989550" "2023-05-16 10:40:34.000 Asia/Tokyo","XXE-body-001","[{""rulematchdetails"":""null"",""exclusiontype"":""EXCLUDED_AS_COUNT"",""ruleid"":""SQLi-body-001""},{""rulematchdetails"":""null"",""exclusiontype"":""EXCLUDED_AS_COUNT"",""ruleid"":""XXE-body-001""}]","arn:aws:wafv2:us-east-1:xxxxxxxxxxxx:global/rulegroup/WafCharm_Common_Basic_Group/494dbf2c-ad20-4a3f-974f-733f4b989550" "2023-05-16 10:40:41.000 Asia/Tokyo","OScmdi-body-001","[{""rulematchdetails"":""null"",""exclusiontype"":""EXCLUDED_AS_COUNT"",""ruleid"":""OScmdi-body-001""}]","arn:aws:wafv2:us-east-1:xxxxxxxxxxxx:global/rulegroup/WafCharm_Common_Basic_Group/494dbf2c-ad20-4a3f-974f-733f4b989550" "2023-05-16 10:46:03.000 Asia/Tokyo","SQLi-body-001","[{""rulematchdetails"":""null"",""exclusiontype"":""EXCLUDED_AS_COUNT"",""ruleid"":""SQLi-body-001""},{""rulematchdetails"":""null"",""exclusiontype"":""EXCLUDED_AS_COUNT"",""ruleid"":""XXE-body-001""}]","arn:aws:wafv2:us-east-1:xxxxxxxxxxxx:global/rulegroup/WafCharm_Common_Basic_Group/494dbf2c-ad20-4a3f-974f-733f4b989550" "2023-05-16 10:46:03.000 Asia/Tokyo","XXE-body-001","[{""rulematchdetails"":""null"",""exclusiontype"":""EXCLUDED_AS_COUNT"",""ruleid"":""SQLi-body-001""},{""rulematchdetails"":""null"",""exclusiontype"":""EXCLUDED_AS_COUNT"",""ruleid"":""XXE-body-001""}]","arn:aws:wafv2:us-east-1:xxxxxxxxxxxx:global/rulegroup/WafCharm_Common_Basic_Group/494dbf2c-ad20-4a3f-974f-733f4b989550" "2023-05-16 10:46:08.000 Asia/Tokyo","OScmdi-body-001","[{""rulematchdetails"":""null"",""exclusiontype"":""EXCLUDED_AS_COUNT"",""ruleid"":""OScmdi-body-001""}]","arn:aws:wafv2:us-east-1:xxxxxxxxxxxx:global/rulegroup/WafCharm_Common_Basic_Group/494dbf2c-ad20-4a3f-974f-733f4b989550" "2023-05-16 10:46:06.000 Asia/Tokyo","OScmdi-body-001","[{""rulematchdetails"":""null"",""exclusiontype"":""EXCLUDED_AS_COUNT"",""ruleid"":""OScmdi-body-001""}]","arn:aws:wafv2:us-east-1:xxxxxxxxxxxx:global/rulegroup/WafCharm_Common_Basic_Group/494dbf2c-ad20-4a3f-974f-733f4b989550" "2023-05-16 10:46:04.000 Asia/Tokyo","SQLi-body-001","[{""rulematchdetails"":""null"",""exclusiontype"":""EXCLUDED_AS_COUNT"",""ruleid"":""SQLi-body-001""},{""rulematchdetails"":""null"",""exclusiontype"":""EXCLUDED_AS_COUNT"",""ruleid"":""XXE-body-001""}]","arn:aws:wafv2:us-east-1:xxxxxxxxxxxx:global/rulegroup/WafCharm_Common_Basic_Group/494dbf2c-ad20-4a3f-974f-733f4b989550" "2023-05-16 10:46:04.000 Asia/Tokyo","XXE-body-001","[{""rulematchdetails"":""null"",""exclusiontype"":""EXCLUDED_AS_COUNT"",""ruleid"":""SQLi-body-001""},{""rulematchdetails"":""null"",""exclusiontype"":""EXCLUDED_AS_COUNT"",""ruleid"":""XXE-body-001""}]","arn:aws:wafv2:us-east-1:xxxxxxxxxxxx:global/rulegroup/WafCharm_Common_Basic_Group/494dbf2c-ad20-4a3f-974f-733f4b989550" "2023-05-16 10:46:13.000 Asia/Tokyo","SQLi-body-001","[{""rulematchdetails"":""null"",""exclusiontype"":""EXCLUDED_AS_COUNT"",""ruleid"":""SQLi-body-001""}]","arn:aws:wafv2:us-east-1:xxxxxxxxxxxx:global/rulegroup/WafCharm_Common_Basic_Group/494dbf2c-ad20-4a3f-974f-733f4b989550" "2023-05-16 10:46:07.000 Asia/Tokyo","OScmdi-body-001","[{""rulematchdetails"":""null"",""exclusiontype"":""EXCLUDED_AS_COUNT"",""ruleid"":""OScmdi-body-001""}]","arn:aws:wafv2:us-east-1:xxxxxxxxxxxx:global/rulegroup/WafCharm_Common_Basic_Group/494dbf2c-ad20-4a3f-974f-733f4b989550" "2023-05-16 10:46:09.000 Asia/Tokyo","OScmdi-body-001","[{""rulematchdetails"":""null"",""exclusiontype"":""EXCLUDED_AS_COUNT"",""ruleid"":""OScmdi-body-001""}]","arn:aws:wafv2:us-east-1:xxxxxxxxxxxx:global/rulegroup/WafCharm_Common_Basic_Group/494dbf2c-ad20-4a3f-974f-733f4b989550" "2023-05-16 10:46:12.000 Asia/Tokyo","SQLi-body-001","[{""rulematchdetails"":""null"",""exclusiontype"":""EXCLUDED_AS_COUNT"",""ruleid"":""SQLi-body-001""}]","arn:aws:wafv2:us-east-1:xxxxxxxxxxxx:global/rulegroup/WafCharm_Common_Basic_Group/494dbf2c-ad20-4a3f-974f-733f4b989550" "2023-05-16 10:46:11.000 Asia/Tokyo","SQLi-body-001","[{""rulematchdetails"":""null"",""exclusiontype"":""EXCLUDED_AS_COUNT"",""ruleid"":""SQLi-body-001""}]","arn:aws:wafv2:us-east-1:xxxxxxxxxxxx:global/rulegroup/WafCharm_Common_Basic_Group/494dbf2c-ad20-4a3f-974f-733f4b989550" "2023-05-16 10:46:24.000 Asia/Tokyo","SQLi-body-001","[{""rulematchdetails"":""null"",""exclusiontype"":""EXCLUDED_AS_COUNT"",""ruleid"":""SQLi-body-001""}]","arn:aws:wafv2:us-east-1:xxxxxxxxxxxx:global/rulegroup/WafCharm_Common_Basic_Group/494dbf2c-ad20-4a3f-974f-733f4b989550" "2023-05-16 10:46:22.000 Asia/Tokyo","SQLi-body-001","[{""rulematchdetails"":""null"",""exclusiontype"":""EXCLUDED_AS_COUNT"",""ruleid"":""SQLi-body-001""}]","arn:aws:wafv2:us-east-1:xxxxxxxxxxxx:global/rulegroup/WafCharm_Common_Basic_Group/494dbf2c-ad20-4a3f-974f-733f4b989550" "2023-05-16 10:46:23.000 Asia/Tokyo","SQLi-body-001","[{""rulematchdetails"":""null"",""exclusiontype"":""EXCLUDED_AS_COUNT"",""ruleid"":""SQLi-body-001""}]","arn:aws:wafv2:us-east-1:xxxxxxxxxxxx:global/rulegroup/WafCharm_Common_Basic_Group/494dbf2c-ad20-4a3f-974f-733f4b989550" "2023-05-16 10:46:33.000 Asia/Tokyo","SQLi-body-001","[{""rulematchdetails"":""null"",""exclusiontype"":""EXCLUDED_AS_COUNT"",""ruleid"":""SQLi-body-001""}]","arn:aws:wafv2:us-east-1:xxxxxxxxxxxx:global/rulegroup/WafCharm_Common_Basic_Group/494dbf2c-ad20-4a3f-974f-733f4b989550" ・・・
まとめると、Attack Country Ranking/Attack IP Rankingがリクエスト数になり、Attack Typeが検出されたルール数になります。結果としてリクエスト数を確認する場合はAttack Country Ranking/Attack IP Rankingの検出数を確認することが正しいことがわかります。
(Appendix) WafCharmレポートで確認できないTop11以上の結果を取得する
WafCharmレポートのRankingで確認できる内容はTop10までの結果です。
もしTop11以上の結果を知りたい場合には、以下のようなクエリを実行することで取得することができます。
SELECT json_extract_scalar(excludedrule_obj, '$.ruleid') AS ruleid, COUNT(*) AS count_ruleid FROM "waf_logs" CROSS JOIN UNNEST(rulegrouplist) AS t(rule) CROSS JOIN UNNEST(CAST(json_parse(rule.excludedrules) AS ARRAY(JSON))) AS u(excludedrule_obj) WHERE rule.excludedrules IS NOT NULL GROUP BY json_extract_scalar(excludedrule_obj, '$.ruleid') ORDER BY count_ruleid DESC
SELECT httprequest.country, COUNT(*) AS count_country FROM "waf_logs" CROSS JOIN UNNEST(rulegrouplist) AS t(rule) WHERE rule.excludedrules IS NOT NULL GROUP BY httprequest.country ORDER BY count_country DESC
※今回の検証では日本からのリクエストのみ
SELECT httprequest.clientip, COUNT(*) AS count_clientip FROM "waf_logs" CROSS JOIN UNNEST(rulegrouplist) AS t(rule) WHERE rule.excludedrules IS NOT NULL GROUP BY httprequest.clientip ORDER BY count_clientip DESC
※今回の検証では単一IPからのリクエストのみ
最後に
最初はCountモードでレポートが生成されるかどうかを調べていましたが、調査過程でレポートの見方に関して若干ハマりポイント?があったので、今回それらをまとめてみることにしました。
こちらの記事がどなたかの助けになれば幸いです。
以上、たかやま(@nyan_kotaroo)でした。