Cloud Armor の誤検知をクエリで炙り出してチューニングする
クラウドエース株式会社 第一開発部 喜村拓未です。
Cloud Armor の事前構成 WAF ルールを本番環境に適用する前に、プレビューモードでログを確認しながら誤検知を炙り出し、ルール除外でチューニングした経験をもとに、その手順と実際に使ったクエリをまとめました。
Cloud Armor の事前構成 WAF ルールと誤検知
Cloud Armor の事前構成 WAF ルール(Preconfigured WAF rules)は、OWASP ModSecurity Core Rule Set をベースにした事前定義済みのルールセットです。SQL インジェクションやクロスサイトスクリプティングなど代表的な攻撃パターンを手軽に防御できますが、アプリケーション固有のリクエストパターンが攻撃と誤って判定される「誤検知」が発生することがあります。
誤検知が発生すると正常なユーザーのリクエストまでブロックされてしまうため、本番適用前にプレビューモードを活用して誤検知を洗い出し、必要なルールを除外するチューニング作業が重要です。
全体の流れ
本記事では以下の流れでチューニングを進めます。
- プレビューモードでルールを有効化する
- Cloud Logging でクエリして誤検知を炙り出す
- ルール除外設定でチューニングする
- 誤検知がなくなるまで 2〜3 を繰り返す
1. プレビューモードでルールを有効化する
プレビューモードとは、ルールが実際にリクエストをブロックせず、検知だけを行うモードです。本番トラフィックに影響を与えずに誤検知を調査するために活用します。
Cloud Armor のセキュリティポリシーでルールを追加・編集する際に「プレビューを有効にする」にチェックを入れることでプレビューモードが有効になります。
プレビューモードで検知されたリクエストは Cloud Logging に記録されます。通常の適用時(enforcedSecurityPolicy)と異なり、previewSecurityPolicy フィールドに記録される点に注意してください。
また、LB のバックエンドサービスのロギングが有効になっていることを事前に確認してください。ロギングが無効だとログが出力されません。
2. Cloud Logging でクエリして誤検知を炙り出す
クエリの基本形
Cloud Logging のログエクスプローラーで以下のクエリを実行すると、プレビューモードで DENY 判定されたリクエストの一覧が確認できます。
外部アプリケーション ロードバランサーの場合
resource.type="http_load_balancer"
jsonPayload.previewSecurityPolicy.outcome="DENY"
内部アプリケーション ロードバランサーの場合
resource.type="internal_http_lb_rule"
jsonPayload.previewSecurityPolicy.outcome="DENY"
セキュリティポリシーが複数ある場合はポリシー名でさらに絞り込みます。
resource.type="http_load_balancer"
jsonPayload.previewSecurityPolicy.name="セキュリティポリシー名"
jsonPayload.previewSecurityPolicy.outcome="DENY"
誤検知の炙り出し:AND NOT で除外しながら絞り込む
本記事のメインとなる手順です。
ログを確認すると、最初は大量の DENY ログが出てきます。ログエントリを開き jsonPayload.previewSecurityPolicy.preconfiguredExprIds を見ると、どの WAF ルールに引っかかったかが分かります。
ログを一つひとつ確認し、「これは正常なリクエスト(誤検知)である」と判断した場合は、そのリクエストの URL を AND NOT で除外するクエリに追加します。これにより、「まだ確認できていない DENY ログ」のみが表示されるようになり、未確認の誤検知を確実に洗い出すことができます。
※以下のクエリは例です。
resource.type="http_load_balancer"
jsonPayload.previewSecurityPolicy.outcome="DENY"
(httpRequest.requestUrl:"https://api.example.com/" OR httpRequest.requestUrl:"https://cms.example.com/")
AND NOT httpRequest.requestUrl="https://api.example.com/search"
AND NOT httpRequest.requestUrl="https://api.example.com/refresh"
AND NOT httpRequest.requestUrl="https://api.example.com/member/token"
AND NOT httpRequest.requestUrl="https://cms.example.com/save"
AND NOT に追加した URL は「誤検知と確認済みのエンドポイント」です。クエリ結果が 0 件になれば、対象ドメインの全 DENY ログを確認し終えたことになります。
地道な作業ではありますが、この方法は以下の点で確実です。
- 大量ログの中で「まだ見ていないもの」が明確になる
- 特定のエンドポイントを見落とすリスクがない
- 確認済みの URL が
AND NOTリストとしてそのまま記録に残る
3. ルール除外設定でチューニングする
誤検知と確認したエンドポイントについて、引っかかっていたルール ID を jsonPayload.previewSecurityPolicy.preconfiguredExprIds から確認し、ルール除外設定を行います。
evaluatePreconfiguredWaf での感度レベル指定と除外
現在は evaluatePreconfiguredWaf() の使用が推奨されています。感度レベルを指定してルール適用範囲を絞ることができます。
evaluatePreconfiguredWaf('sqli-stable', {'sensitivity': 1})
感度レベルは 0〜4 で指定し、数値が小さいほど誤検知リスクが低くなります。特定のルール ID を除外したい場合は以下のように指定します。
evaluatePreconfiguredWaf('sqli-stable', {'sensitivity': 2, 'opt_out_rule_ids': ['owasp-crs-v030001-id942190-sqli']})
evaluatePreconfiguredExpr での除外(従来の方法)
evaluatePreconfiguredExpr('sqli-stable', ['owasp-crs-v030001-id942190-sqli'])
複数ルールセットを組み合わせる場合:
evaluatePreconfiguredExpr('sqli-stable', ['除外するルールID1'])
|| evaluatePreconfiguredExpr('xss-stable', ['除外するルールID2'])
チューニング後の再確認
ルールを調整したら、再びプレビューモードのまましばらくトラフィックを流し、前述のクエリで DENY ログが残っていないか確認します。クエリ結果が 0 件になったらプレビューモードを解除し、本番環境に適用します。
参考:Log Analytics が使える場合のクエリ例
Log Analytics を利用できる環境であれば、以下のような集計クエリも活用できます(ログエクスプローラーのフィルタ構文ではなく SQL で記述します)。
ルール ID ごとの件数集計
SELECT
rule_id,
COUNT(*) AS hit_count
FROM (
SELECT
JSON_VALUE(rule_id_elem, '$') AS rule_id
FROM
`プロジェクトID._Default._AllLogs`,
UNNEST(JSON_QUERY_ARRAY(json_payload, '$.previewSecurityPolicy.preconfiguredExprIds')) AS rule_id_elem
WHERE
resource.type = "http_load_balancer"
AND JSON_VALUE(json_payload, '$.previewSecurityPolicy.name') = 'セキュリティポリシー名'
AND JSON_VALUE(json_payload, '$.previewSecurityPolicy.outcome') = 'DENY'
)
GROUP BY
rule_id
ORDER BY
hit_count DESC
URI パターン別の件数集計
SELECT
REGEXP_EXTRACT(http_request.request_url, r'https?://[^/]+(/.*)') AS path,
COUNT(*) AS hit_count
FROM
`プロジェクトID._Default._AllLogs`
WHERE
resource.type = "http_load_balancer"
AND JSON_VALUE(json_payload, '$.previewSecurityPolicy.name') = 'セキュリティポリシー名'
AND JSON_VALUE(json_payload, '$.previewSecurityPolicy.outcome') = 'DENY'
GROUP BY
path
ORDER BY
hit_count DESC
LIMIT 50
まとめ
本記事では、Cloud Armor の事前構成 WAF ルールの誤検知を Cloud Logging でクエリしながら炙り出し、ルール除外設定でチューニングする手順を紹介しました。
| 手順 | 内容 |
|---|---|
| プレビューモード有効化 | 本番トラフィックに影響なく検知ログを収集 |
AND NOT で地道に絞り込み |
見落としなく全 DENY ログを確認 |
| ルール除外設定 |
evaluatePreconfiguredWaf で感度調整・ルール除外 |
| 再確認・本番適用 | DENY ログが 0 件になったらプレビューを解除 |
地道な作業ではありますが、AND NOT を積み重ねて DENY ログをゼロにするアプローチは、見落としのない確実な方法です。(量が多いと大変ですが...)Cloud Armor を導入予定の方や、誤検知に悩んでいる方の参考になれば幸いです。
参考リンク
- Cloud Armor の概要 | Google Cloud
- 事前構成 WAF ルール | Google Cloud
- 事前構成 WAF ルールのチューニング | Google Cloud
- セキュリティ ポリシーの概要 | Google Cloud
最後まで読んでいただきありがとうございました。
Discussion