Cloud Ace Tech Blog
🔍

Cloud Armor の誤検知をクエリで炙り出してチューニングする

クラウドエース株式会社 第一開発部 喜村拓未です。

Cloud Armor の事前構成 WAF ルールを本番環境に適用する前に、プレビューモードでログを確認しながら誤検知を炙り出し、ルール除外でチューニングした経験をもとに、その手順と実際に使ったクエリをまとめました。

Cloud Armor の事前構成 WAF ルールと誤検知

Cloud Armor の事前構成 WAF ルール(Preconfigured WAF rules)は、OWASP ModSecurity Core Rule Set をベースにした事前定義済みのルールセットです。SQL インジェクションやクロスサイトスクリプティングなど代表的な攻撃パターンを手軽に防御できますが、アプリケーション固有のリクエストパターンが攻撃と誤って判定される「誤検知」が発生することがあります。

誤検知が発生すると正常なユーザーのリクエストまでブロックされてしまうため、本番適用前にプレビューモードを活用して誤検知を洗い出し、必要なルールを除外するチューニング作業が重要です。

全体の流れ

本記事では以下の流れでチューニングを進めます。

  1. プレビューモードでルールを有効化する
  2. Cloud Logging でクエリして誤検知を炙り出す
  3. ルール除外設定でチューニングする
  4. 誤検知がなくなるまで 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 Ace Tech Blog
Cloud Ace Tech Blog

Discussion