コドモン Product Team Blog

株式会社コドモンの開発チームで運営しているブログです。エンジニアやPdMメンバーが、プロダクトや技術やチームについて発信します!

AWS WAFルールを安全に本番適用するための段階的ロールアウト

こんにちは、プロダクト開発部の関です。

2026年1月、IPA(情報処理推進機構)が発表した「情報セキュリティ10大脅威 2026」で、「AIの利用をめぐるサイバーリスク」が初めてランクインし、ランサムウェア、サプライチェーン攻撃に次ぐ3位に位置づけられました。これは新たな脅威カテゴリが増えたというより、既存の攻撃がAIによって質的に変化していることを示す象徴的な変化です。

WAFに求められる役割も、当然この変化と無縁ではいられません。アプリ単体での堅牢化はもはや前提条件にすぎず、多層防御の一部としてのWAFをどう運用していくのかがますます重要になってきます。

攻撃の実態に応じて、WAFに新しいルールを導入していく必要があります。実際にコドモンで利用しているAWS WAFも、例えばLog4Shellという大規模な脆弱性が公表された際、Log4Shell対策のルールが加わりました。一方で、WAFの新規ルールを導入するときにいきなりブロック運用を始めるのは怖いですよね。AWSマネージドルールが自社の正規トラフィックと衝突するかは環境依存で、巻き込めば本番に影響を起こします。 本記事では、COUNTモードで観測してから段階的にBLOCKモードに変更していく実務プロセスを、コドモンでの運用を例に紹介します。

全体の流れ

具体の話に入る前に、コドモンで新規ルールを適用する際の流れを示しておきます。

  1. 新規に適用するルールの影響を調査する — ルールのドキュメントを読み、明らかに不要なものは適用対象から外す
  2. 新規に適用するルールをCOUNTモードで適用する — 明らかに不要なルール以外はまず観測モードで動かしてログを記録する
  3. 観測したいログを出力する — AWS WAFのLogging filterを使い、分析に必要なログのみ出力してコストを抑制する
  4. ログを分析する — 観測対象のリクエストを抽出し、攻撃か正規トラフィックかを判別する
  5. 除外ルールを作成する — 正規トラフィックがWAFのルールで検出されないように除外する
  6. 十分に観測した上でBLOCKモードに変更する — 誤検知がなくなるまで十分に観測を終えたらまとめてBLOCKモードに切り替える。切り替え後も観測を続け、誤検知を見つけたらすぐに除外設定をできるように準備しておく

以降の各節では、ステップ3以降を中心に掘り下げていきます。ステップ1・2の前提となるAWS WAFの基本的な概念や設定方法については、AWS WAF デベロッパーガイドを参照してください。

新規に適用するルールの影響を調査する

新規ルールを適用する前に、まずルールのドキュメントを一読します。AWSマネージドルール(AWSが提供する事前定義されたルールセット)であれば、各ルールグループにどんなルールが含まれ、何を検知するかが公式ドキュメント(例: ベースラインルールグループ)に記載されています。

この段階での目的は、明らかに不要なルールをリストアップして適用対象から外すことです。例えば、IISやWebDAVを使っていない環境ではPROPFIND_METHODのような特定のメソッドを検知するルールは不要です。

それ以外のルールについては、ドキュメントだけでは誤検知の有無を判別しきれないため、安全のため一律COUNTモードで適用します。「これは大丈夫だろう」と思ったルールが、本番で誤検知を起こすことは珍しくありません。判別に迷うものはCOUNTモードで動かすのが堅実です。

新規に適用するルールをCOUNTモードで適用する

影響調査では明らかに不要なルールを除外しました。残るのは、ドキュメントだけでは誤検知の有無が判別できないルールです。これらをCOUNTモードで適用し、観測フェーズに入ります。

COUNTモードはWAFルールで検出されたリクエストを記録するだけで、ブロックは行いません。リクエスト自体はそのまま通過し、後続のルール評価へ流れていきます。本番トラフィックへの影響を出さずに、ルールがどのようなリクエストを対象にするかを観測できる、というのがCOUNTモードの役割です。

COUNTモード化の方法はルールの種類によって異なります。

マネージドルールグループの場合は、RuleActionOverrideを使って個別ルールごとにCOUNTモードに設定します。観測したいルールをCOUNTモードに上書きし、それ以外はマネージドルール本来のアクション(多くの場合はBLOCKモード)で動かす形です。

なお、影響調査で「明らかに不要」と判定したルールの扱いには2つの方法があります。ルールグループ全体が不要であれば、そのルールグループを適用しなければよいだけです。ルールグループは使うが特定のルールだけが不要な場合は、RuleActionOverrideでそのルールだけCOUNTモードに上書きして無効化します。

観測したいログを出力する

COUNTモードで観測フェーズに入る際に、次に決めるのは「どのリクエストをログに残すか」です。何もせずにWAFのログをそのまま流すと、本来観測したくない正規トラフィックまで大量に記録され、ログのコストと分析ノイズが膨らみます。

そこで、WAFのLogging filterで「観測対象にしたいリクエストだけをログに残す」ように設定します。コドモンでは、各WAFルールが付与するラベルをKeep条件に列挙する形を取りました。観測したいルールを明示的に列挙することで、「現在どのルールを観測中か」が設定ファイルからそのまま読み取れます。

設定はこのような形になります(Terraformの例)。

logging_filter {
  default_behavior = "DROP"
  filter {
    behavior    = "KEEP"
    requirement = "MEETS_ANY"
    condition {
      label_name_condition {
        label_name = "awswaf:managed:aws:core-rule-set:xxx"
      }
    }
  }
}

なお、観測の結果「これはCOUNTモードのまま据え置く」と判断したルールについては、Logging filterのKeep条件からも該当のラベルを外します。これにより、Logging filterが「現在も観測対象としているルール」の選別装置として働きます。

ログを分析する

ログを分析するための準備をする

コドモンでは、WAFのログをS3に保存し、Athenaで参照しています。観測期間は、業務の周期を考慮して決めます。一定期間観測するのはもちろんですが、月末月初、年度末、年度始めなどに発生する、利用頻度の低い機能を取りこぼさないように期間を定めます。

WAFログのtimestampはミリ秒単位のUNIX時刻なので、扱いやすい形式に変換するViewをAthenaに作成しています。

CREATE OR REPLACE VIEW "waf_logs_jst" AS
SELECT
  date_format(from_unixtime(("timestamp" / 1000), 'Asia/Tokyo'), '%Y-%m-%d %H:%i:%S') AS datetime_jst,
  *
FROM
  "waf_logs"
ORDER BY datetime_jst DESC

Logging filterの設定を検証する

Logging filterの設定を誤るとログが残らないため、観測自体が失敗してしまいます。一定の観測期間を要した後にこれが発覚すると、観測の手戻りが発生してしまいます。

そこで、WAFコンソールのSampled requestsダッシュボード(直近のサンプルリクエストが見られる機能)を使って、Logging filterの設定が正しいかをチェックしておきます。具体的には、Sampled requestsで観測対象のリクエストを検知したら、Athenaで同じログが見えているかを確認します。

ログを抽出する

WAFログのlabelsフィールドは配列で、リクエスト評価時に付与されたすべてのラベルが入ります。特定のラベルが付与されているリクエストを取得するには、ANY_MATCHを使います。

SELECT *
FROM waf_logs_jst
WHERE datetime_jst LIKE '2026-05%'
  AND ANY_MATCH(labels, x -> x.name = 'awswaf:managed:aws:core-rule-set:xxx')

ラベル名を切り替えれば、観測対象のルールごとに検出されたリクエストを抽出できます。

抽出したログを分類する

抽出したログは、対処の方針別にいくつかのケースに分けられます。

マネージドルールが想定する「正常」と自社環境のズレ

マネージドルールは一般的なWebトラフィックを前提に設計されています。そのため、監視ツールや内部のサービス間通信、特殊なクライアントといった、ルールが想定していない正規トラフィックが検出されることがあります。典型例はNoUserAgent_HEADERのようなUser-Agent欠落を検知するルールで、ブラウザ以外のクライアントを使う場合に意図せず引っかかることがあります。

誤検知

正規ユーザーの普通の操作が、攻撃シグネチャに偶然一致したケースです。機能の性質によって誤って検出されやすいルールに傾向があり、過去にコドモンで観測した例を挙げると次のようになります。

機能 誤って検出されたルール
ファイルアップロード CrossSiteScripting_BODYPHPHighRiskMethodsVariables_BODY
HTMLコンテンツ編集 GenericLFI_BODYGenericRFI_BODYEC2MetaDataSSRF_BODY
ファイルダウンロード RestrictedExtensions_URIPath

これらは「対象機能で正規に発生しうる文字列」が、攻撃シグネチャと形が似てしまうために起きる誤検知です。

明確な攻撃

過去に観測した代表的なパターンを挙げます。

  • 設定ファイル・クレデンシャルの探索: /.env/.aws/credentials/error.logなど。配置され得るディレクトリのバリエーション(/app//public//shared/配下など)を網羅的に試行されていました
  • 既知脆弱性をついたスキャン: PHPやSpring Bootなどの既知の脆弱性を狙ったリクエストも試行されていました

これらはリクエストの内容から正規トラフィックとの判別が明確で、そのままBLOCK対象として扱えます。

なお、攻撃者は既知の脆弱性を継続的にスキャンしてくるため、フレームワークやライブラリのバージョンアップ運用は欠かせません。加えて、近年はAIの活用によって脆弱性発見と攻撃コード作成のサイクルが大幅に短縮されています。2026年5月にはAI支援で開発されたゼロデイエクスプロイトの初の検出事例が報告され、既知脆弱性の悪用までの時間も年々短くなっています。WAFはアプリケーション側のパッチ適用が完了するまでの緩衝材として、多層防御の一部に位置づけるのが現実的です。

除外ルールを作成する

誤検知に分類されたリクエストをそのままにしておくと、BLOCKモードに変更した瞬間に正規トラフィックを巻き込んでしまいます。これを避けるために、誤検知を除外するルールが必要です。コドモンでは、RuleActionOverrideとカスタムルールを組み合わせて対応しています。

RuleActionOverride: マネージドルール内の個別ルールをCOUNTモードに変更

マネージドルールグループの中で、特定のルールだけをCOUNTモードに変更したい場合は、RuleActionOverrideを使います。グループ全体は通常通りBLOCKモードで動かしつつ、誤検知の多い特定のルールだけCOUNTモードに変更できます。例えばXSS検知ルールが誤検知を出しているなら、該当ルールをRuleActionOverrideでCOUNTモードに変更して観測を継続できます。

設定はこのような形になります(Terraformの例)。

statement {
  managed_rule_group_statement {
    name        = "AWSManagedRulesCommonRuleSet"
    vendor_name = "AWS"

    rule_action_override {
      name = "CrossSiteScripting_BODY"
      action_to_use {
        count {}
      }
    }
  }
}

COUNTモードに変更したルールは、攻撃と見なすべきリクエストも通してしまうので、正規トラフィックを検知しないように考慮した代替ルールを別途作成する必要があります。

カスタムルール: 除外条件を組み込む

RuleActionOverrideでCOUNTモードに変更したマネージドルールの代わりに、除外条件を組み込んだカスタムルールを作成します。コドモンでは、誤検知が発生しているパスを除外条件として組み込む形を取っています。マネージドルールが付与するラベル(例: awswaf:managed:aws:core-rule-set:CrossSiteScripting_Body)をLabelMatchStatementで参照し、除外パス以外のリクエストをBLOCK対象として検出する形です。

設定はこのような形になります(Terraformの例)。

action {
  count {}
}

statement {
  and_statement {
    statement {
      label_match_statement {
        scope = "LABEL"
        key   = "awswaf:managed:aws:core-rule-set:CrossSiteScripting_Body"
      }
    }

    statement {
      not_statement {
        statement {
          byte_match_statement {
            search_string         = "/api/upload/"
            positional_constraint = "STARTS_WITH"
            field_to_match {
              uri_path {}
            }
            text_transformation {
              priority = 0
              type     = "NONE"
            }
          }
        }
      }
    }
  }
}

rule_label {
  name = "custom:CrossSiteScripting-BODY-exclude-uri"
}

ポイントは2つあります。1つ目は、マネージドルール側をRuleActionOverrideでCOUNTモードに変更しておくことで、このカスタムルールが評価される時点でラベルだけが付いた状態になっていること。2つ目は、rule_labelで自前ラベルを付与しておくこと。この自前ラベルが、Logging filterのKeep条件と対応します。

除外したいパスが複数ある場合は、not_statementの中にor_statementを入れて複数のパスを列挙できます。

なお、除外条件としてContent-Typeのようなクライアント側で操作できる値で絞り込むこともできますが、攻撃者に偽装される可能性があるため、過信しないでください。

十分に観測した上でBLOCKモードに変更する

除外ルールを作成したら、もう一度観測フェーズに戻ります。新しいルール構成(マネージドルールのCOUNTモード化 + カスタムルール)が意図通り動作しているか、誤検知が解消されているか、攻撃が検出されているかを確認します。

これらを確認できたら、BLOCKモードに切り替えます。コドモンでは、観測期間中に新たな誤検知が出なくなったことを判断基準として、複数のルールをまとめて切り替えています。観測期間の目安は、利用頻度の低い機能を1周以上カバーできる長さです。

BLOCKモードに切り替えた後も、観測は続けます。BLOCKされたリクエスト数の急増をすぐに検知できる仕組みを用意しておき、新たな誤検知が見つかったらすぐに除外設定を追加できるようにします。

まとめ

本記事では、AWS WAFに新規ルールを導入する際の段階的ロールアウトのプロセスを、コドモンでの運用を例に紹介しました。COUNTモードでの観測、ログ分析による誤検知の特定、除外ルールの作成、そしてBLOCKモードへの切り替えという流れです。

WAFはアプリケーション側の堅牢化と組み合わせて初めて効果を発揮する、多層防御の一部です。AIによって攻撃の進化が早まる中、WAFを一度導入したら終わりではなく、新規ルールを継続的に観測しながら運用していく姿勢が、今後ますます重要になっていきます。