はじめに
AWS WAFで新しいルールを設定したり、ルールを変更する際は、設定した内容で想定通り検知できるか検証が必要です。
検証用のウェブACLで行うのが一般的かと思いますが、本番用のウェブACLでも検証を行うことは可能です。
本番用のウェブACLでの検証では、カウントモードを使用します。
ルールのActionをCountにすることで、誤検知してもリクエストをそのまま通すようにできるため、実際のリクエストに影響がないよう対象のルールを検証することが可能です。
ただし、ルール変更の場合は、既存のルールをCountにするとセキュリティ低下のリスクがあるため、新たにカウントモードの検証ルールを追加し、既存のルールは変更しないようにしましょう。
WAFのログ出力の仕様変更
以前のAWS WAFのログの仕様では、カウントモードでルールを検知した場合、ログのActionフィールドに”COUNT”と記録されていました。
ですが、2022年11月のアップデート以降、Actionフィールドには、“ALLOW”・“BLOCK”・“CAPTCHA”のみ記録されるようになり、”COUNT”は記録されなくなりました。
そして、カウントモードで検知されたルールは、Actionフィールドには"ALLOW"と記録され、"COUNT"のアクションは別のフィールドに記録されるようになりました。
本稿では、新仕様以降、"COUNT"されたルールがどのようにログに出力されるかを解説し、Amazon Athenaを使った"COUNT"されたルールの検索方法を説明します。
rulegrouplistフィールド
AWS WAFのログフィールドのうち、ruleGroupListというフィールドには、動作したルールグループの情報がすべて格納されます。
COUNTされたルールやBLOCKされたルール、検知されなかったルールなど、動作したルールすべてのルールの情報がこのフィールドに記録されます。
また、このフィールドはarray型とstruct型とで複雑に構成されています。
AWS WAFのログのテーブル作成DDLの、rulegrouplistの部分は下記の様になっています。
`rulegrouplist` array < struct < rulegroupid: string, terminatingrule: struct < ruleid: string, action: string, rulematchdetails: array < struct < conditiontype: string, sensitivitylevel: string, location: string, matcheddata: array < string > > > >, nonterminatingmatchingrules: array < struct < ruleid: string, action: string, overriddenaction: string, rulematchdetails: array < struct < conditiontype: string, sensitivitylevel: string, location: string, matcheddata: array < string > > >, challengeresponse: struct < responsecode: string, solvetimestamp: string >, captcharesponse: struct < responsecode: string, solvetimestamp: string > > >, excludedrules: string > >,
struct型の中にさらにarray型を格納しているので、かなり複雑な構造をしているように見えますが、実際に格納されたデータを見てみるとそれほど複雑ではありません。
次に実際のデータを提示した上で、その構造について説明します。
rulegrouplistのデータ構造
rulegrouplistフィールドには、下記の様に動作したルールが並列に格納されます。
[ { rulegroupid=ルールグループやルールのIDもしくはARN, terminatingrule=リクエストを終了したルール, nonterminatingmatchingrules=リクエストに一致する非終了ルール, excludedrules=除外されているルールグループ内のルールのリスト } ]
rulegrouplistはarray型で、動作したルールが複数ある場合は、上記「{}(波括弧)」の内容が「,(コンマ)」続きで連結されています。
また、COUNTやBLOCK、検査してALLOWとなったルールすべてを並列に格納しており、Actionによってrulegroupidごとの情報の持ち方が異なります。
たとえば、COUNTされたルールは、下記の様にnonterminatingmatchingrulesの中に検知ルールの情報を複数格納します。
[ { rulegroupid=ルールグループやルールのIDもしくはARN, terminatingrule=null, nonterminatingmatchingrules= [ { ruleid=countルール1, action=COUNT, overriddenaction=ルールアクションのオーバーライド, rulematchdetails=リクエストに一致したルールに関する詳細情報 }, { ruleid=countルール2, action=COUNT, overriddenaction=ルールアクションのオーバーライド, rulematchdetails=リクエストに一致したルールに関する詳細情報 } ], excludedrules=除外されているルールグループ内のルールのリスト } ]
リスト化すると下記の様になります。
- rulegroupid(ルールグループやルールのIDもしくはARN)
- terminatingrule(リクエストを終了したルール)
- nonterminatingmatchingrules(リクエストに一致する非終了ルール)
- countルール1
- action("COUNT")
- overriddenaction(ルールアクションのオーバーライド)
- rulematchdetails(リクエストに一致したルールに関する詳細情報)
- countルール2
- action("COUNT")
- overriddenaction(ルールアクションのオーバーライド)
- rulematchdetails(リクエストに一致したルールに関する詳細情報)
- countルール1
COUNTされたルールをAthenaで検索する
nonterminatingmatchingrulesは、リクエストに一致する非終了ルールです。
リクエストに一致はするものの終了("BLOCK")とはならないルール、つまり"COUNT"ルールと解釈します。
そのため、COUNTされたルールは、rulegrouplistの、nonterminatingmatchingrulesの中のActionが"COUNT"のもの、となります。
以上を踏まえ、COUNTされたルールをAthenaで検索するクエリは下記の様になります。
取得件数が膨大になるので、日付を指定したり、limit
で取得件数を指定して実行するとよいでしょう。
SELECT t2.ruleid FROM "waf_logs" CROSS JOIN UNNEST(rulegrouplist) AS t(t1) CROSS JOIN UNNEST(t1.nonTerminatingMatchingRules) AS t(t2) WHERE t2.action='COUNT' AND DATE_FORMAT(FROM_UNIXTIME(timestamp/1000, 'Asia/Tokyo'), '%Y-%m-%d') = '2024-01-01'
また、検知数の多かったCOUNTルールを取得するクエリは下記の様になります。
SELECT count(t2.ruleid) as count, t2.ruleid FROM "waf_logs" CROSS JOIN UNNEST(rulegrouplist) AS t(t1) CROSS JOIN UNNEST(t1.nonTerminatingMatchingRules) AS t(t2) WHERE t2.action='COUNT' AND DATE_FORMAT(FROM_UNIXTIME(timestamp/1000, 'Asia/Tokyo'), '%Y-%m-%d') = '2024-01-01' GROUP BY t2.ruleid ORDER BY count DESC
さいごに
AWS WAFのログの、Actionフィールドのデータの持ち方が変わったことで、COUNTルールを検索する方法がシンプルではなくなりました。
AWS WAFを利用する上でログ集計は非常に重要ですので、本稿の情報が少しでも皆様のお役に立てば幸いです。