AWS WAFの運用維持について考える(カウントモード<->ブロックモード)

AWS WAF で頭を悩ますカウントモードからブロックモードへの切り替え、その逆は一定の基準が必要です。基準を決める参考になれば幸いです。
2022.03.10

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは。
ご機嫌いかがでしょうか。
"No human labor is no human error" が大好きな ネクストモード株式会社 の吉井です。

AWS WAF をお使いでしょうか?
Web アプリケーションを防御する一つの機能として頼もしい存在ですが、その運用維持は楽ではありません。
導入して終わりではなく、むしろ日々の運用維持管理こそが重要です。ブロックしなければならないアクセスを防げない、逆に本来ブロックしてはいけないアクセスをブロックしていたことに気が付かないような事態に陥ると Web サイトを通じたビジネス機会の損失につながる可能性があるかもしれません。

楽ではない AWS WAF の運用維持について考えてみました。
今回は AWS Managed Rules for AWS WAF を対象にしています。

ログを保管

WAF ログを保管します。これが無いと何も始まりません。
2021年11月 のアップデートで WAF ログ保管はとてもシンプルになりました。
マネジメントコンソールだと以下の画面のように数クリックで設定可能です。

img

以下のエントリが詳しいので参照しログ設定をお願いします。

ログ検索手段

ログは保管するだけでは意味がありません。検索手段を整えておきます。

WAF ログを S3 に保管した場合は Athene で検索可能です。
以下の DDL で WAF ログ用のテーブルを作成しておきましょう。
"your-logs-bucket"、"your-logs-bucket/AWSLogs/account-id/WAFLogs/Region/web-acl-name" はご自身の環境に合わせて編集ください。

CREATE EXTERNAL TABLE `waf_logs`(
  `timestamp` bigint COMMENT 'from deserializer', 
  `formatversion` int COMMENT 'from deserializer', 
  `webaclid` string COMMENT 'from deserializer', 
  `terminatingruleid` string COMMENT 'from deserializer', 
  `terminatingruletype` string COMMENT 'from deserializer', 
  `action` string COMMENT 'from deserializer', 
  `terminatingrulematchdetails` array<struct<conditiontype:string,location:string,matcheddata:array<string>>> COMMENT 'from deserializer', 
  `httpsourcename` string COMMENT 'from deserializer', 
  `httpsourceid` string COMMENT 'from deserializer', 
  `rulegrouplist` array<struct<rulegroupid:string,terminatingrule:struct<ruleid:string,action:string,rulematchdetails:string>,nonterminatingmatchingrules:array<struct<ruleid:string,action:string,rulematchdetails:array<struct<conditiontype:string,location:string,matcheddata:array<string>>>>>,excludedrules:array<struct<ruleid:string,exclusiontype:string>>>> COMMENT 'from deserializer', 
  `ratebasedrulelist` array<struct<ratebasedruleid:string,limitkey:string,maxrateallowed:int>> COMMENT 'from deserializer', 
  `nonterminatingmatchingrules` array<struct<ruleid:string,action:string>> COMMENT 'from deserializer', 
  `requestheadersinserted` string COMMENT 'from deserializer', 
  `responsecodesent` string COMMENT 'from deserializer', 
  `httprequest` struct<clientip:string,country:string,headers:array<struct<name:string,value:string>>,uri:string,args:string,httpversion:string,httpmethod:string,requestid:string> COMMENT 'from deserializer', 
  `labels` array<struct<name:string>> COMMENT 'from deserializer')
PARTITIONED BY (year int, month int, day int)
ROW FORMAT SERDE 
  'org.openx.data.jsonserde.JsonSerDe' 
STORED AS INPUTFORMAT 
  'org.apache.hadoop.mapred.TextInputFormat' 
OUTPUTFORMAT 
  'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat'
LOCATION
  's3://your-logs-bucket/'
TBLPROPERTIES (
  'projection.day.digits'='2',
  'projection.day.range'='01,31',
  'projection.day.type'='integer',
  'projection.enabled'='true',
  'projection.month.digits'='2',
  'projection.month.range'='01,12',
  'projection.month.type'='integer',
  'projection.year.digits'='4',
  'projection.year.range'='2022,2122',
  'projection.year.type'='integer',
  'projection.enabled'='true', 
  'storage.location.template'='s3://your-logs-bucket/AWSLogs/account-id/WAFLogs/Region/web-acl-name/${year}/${month}/${day}'
)

AWS WAF 運用維持方針

AWS WAF 運用維持方針を決めておきましょう。
今回は以下の方針としました。

  • カウントモードで WAF 運用をスタート
  • 毎日 カウントモードのログを確認しながら順次ブロックモードへ移行
  • 毎日 ブロックモードのログを確認しながら過検知の有無を調査、カウントモードへ戻すことも検討
  • 安定したら毎日のログ確認をやめる
  • ルールセットのバージョンを最新を使用

カウントログ検索

カウントされたログを検索します。ルールによって検出されたアクセスが記録されています。
これを順次ブロックに変えていくことが目的です。

以下のような Athena クエリを投げました。

SELECT 
  to_iso8601(from_unixtime(timestamp / 1000, 'Asia/Tokyo')) as time_ISO_8601,
  nonTerm.ruleid,
  httprequest.clientip,
  httprequest.headers,
  httprequest.uri,
  httprequest.args,
  httprequest.httpversion,
  httprequest.httpmethod,
  httprequest.country,
  labels
FROM 
  waf_logs,
  UNNEST(nonterminatingmatchingrules) t(nonTerm)
WHERE
    nonTerm.action = 'COUNT'
order by time_ISO_8601 desc

検索結果から判断

カウントログからブロックへの判断はとても難しいと思います。毎回悩みます。
私は以下のようなロジックで考えています。

  1. 明らかにブロックしてよいアクセスを決める
  2. 判断に迷うアクセスをあぶり出す
  3. 判断に迷うアクセスが無いルールはブロックへ

「明らかにブロックしてよいアクセス」とは何でしょうか?
システムに依りますので正解は無いのですが、”俺流” を紹介します。

※ Athena クエリ結果は見やすいとは言えず、クエリごとに料金が発生するので CSV ファイルでダウンロードしエクセル等でフィルターを工夫しながら開くことをお勧めします

国内アクセス

Athena クエリ結果の country 列に注目します。
システムが国内向けのサービスであれば「JP」以外はエクセルのフィルターで非表示にします。
検索エンジンや監視 SaaS、外部システム連携などはこの限りではないので、これらからのアクセスがある場合は JP 以外もケアください。

URI を見る

country を JP でフィルターしたら uri 列を見ます。
アプリケーションで想定しない URI へのアクセスはブロック候補です。

(例)

  • ../../../etc/passwd
  • .env
  • .git/config

アーギュメントを見る

URI の次はアーギュメント (クエリストリング) を見ます。
ここでもアプリケーションで想定していないアーギュメントを投げてくるアクエスはブロック候補です。

ヘッダーを見る

header 列を見ます。
host が自サービスの FQDN でない場合や IP アドレスの場合はブロック候補にしています。
※ 必ずもそうでないパターンもあります。

ブラウザから利用が前提のサービスであれば、UA からブラウザであるか否か判断することが可能です。
ただ、UA 偽装は容易なので過信は禁物です。

ブロックモードへ

ブロック候補が決まったらブロックモードへ変更します。
ルールセット全体をブロックできればそれでも大丈夫ですが、影響は大きい場合はルール単体をブロックします。

このクエリ例でいうと、ruleid 列がルールセット、labels がルールになります。

WebACL のルールセット編集から図のようにルール個別でカウント or ブロック設定が可能です。

判断に迷うアクセス

ブロックしてよいかどうしても判断に迷うアクセスはあると思います。
例えばルールセット AWSManagedRulesAnonymousIpList は VPN、Proxy、Tor からのアクセスを防御しますが、同時にホスティングプロバイダーからのアクセスも防御してしまいます。
ホスティングプロバイダーには AWS も含まれていますし正当なデータセンターも含まれているはずです。
このようなケースでは、スコープダウンステートメントの利用や、許可/拒否 IP アドレスリストを WebACL に含めるなどの策を講じ AWSManagedRulesAnonymousIpList はカウントモードで運用するといった決断をします。

ブロックログ検索

ブロックモードで変更した後はブロックログを確認し意図しないブロックが無いことを確認します。
過検知、誤検知を発見した際には一旦カウントモードへ戻し対応を検討します。

ブロックログのクエリ例です。

SELECT 
  to_iso8601(from_unixtime(timestamp / 1000, 'Asia/Tokyo')) as time_ISO_8601,
  terminatingruleid,
  httprequest.clientip,
  httprequest.headers,
  httprequest.uri,
  httprequest.args,
  httprequest.httpversion,
  httprequest.httpmethod,
  httprequest.country,
  labels
FROM 
  waf_logs
WHERE
  action = 'BLOCK'
order by time_ISO_8601 desc

ルールセットバージョンについて

AWS Managed Rules のルールセットは自動的にアップデートされます。アップデートすることで新しい脅威に対応しています。
基本的に自動アップデートで大丈夫なのですが、稀にアップデートにより突然ブロックされてしまうこともあります。

ブロックされてしまった場合でも慌てず、まずはブロックログ検索して事実を確認します。
ルールセットのアップデートが原因だった際にはルールセットのバージョンを一つ古いものへ一旦戻します。
ご注意いただきたいのが、古いバージョンは未来永劫使えるわけではなくそのうち最新バージョンへ置き換えてしまうということです。
最新バージョンへ強制的に置き換えられてしまう前にカウントモードにする、アプリケーション側で防御対策を講じる等を行います。

バージョン切り替えあたりはこちらのブログが詳しいです。

アップデート通知

ルールセットアップデートの通知を受け取るようにしましょう。
AWS からのお知らせもあるようですが、全てのアップデートが通知されるわけではなさそうでした。

まとめ

WAF 導入はほぼ必須に近いレベルで行っていますが、導入後の運用維持管理まで上手に引き継いでいられたかの反省を含めて書いてみました。
決まりきった運用方法はありませんし、脅威によって対応が変わってくると考えます。
正解を提示することはできませんが皆様の WAF 運用の助けになれば幸いです。

以上、吉井 亮 がお届けしました。