AWS WAF は、Web ACL(ルールの集まり)を CloudFront・ALB・API Gateway などに付けて、リクエストを許可・ブロック・カウントするサービスです。 ルールは JSON で書きます。最初は AWS のマネージドルール(Common・KnownBadInputs・SQLi)と、レートベースのルールを入れ、Count(数えるだけ)で様子を見てから Block に切り替える流れが、誤ってブロックするのを避けやすい進め方です。

目的 ルールの書き方 WCU(ドキュメント)
一般的な攻撃(XSS・LFI・サイズ制限など) ManagedRuleGroupStatement:AWSManagedRulesCommonRuleSet 700
既知の悪い入力(Log4j・Java デシリアライズなど) 同:AWSManagedRulesKnownBadInputsRuleSet 200
SQL インジェクション 同:AWSManagedRulesSQLiRuleSet 200
許可する IP・拒否する IP IPSetReferenceStatement —
国で絞る GeoMatchStatement —
回数で絞る(ログイン・API) RateBasedStatement —
特定のパスだけ除外 ScopeDownStatement・RuleActionOverrides・ラベル —

確認環境:AWS CLI 1.46.1(Linux)。AWS には接続せず、moto 5.2.4 のサーバーモードの wafv2 で、この記事のルールの JSON(マネージドルール3つ・IP セット・国・レートベース4種・スコープダウン・ラベルでの除外)を create-web-acl に渡して作成できること、update-web-acl のロックトークンの動き、ログ出力の設定(put-logging-configuration)が通ることを確かめました。型や必須項目の誤りは、CLI(botocore)が送る前に止める様子も確かめています。ルールの判定そのもの・WCU の計算・ログの中身・料金は、AWS のドキュメントと料金ページに基づいています(確認日 2026年10月13日)。

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「AWS WAFの設定|マネージドルールとレートベースのJSON例」

Web ACL の構成と評価の順番

リクエスト → Web ACL
   Priority 0  allow-office-ip    (Allow なら、ここで終わり)
   Priority 1  block-countries    (Block なら、ここで終わり)
   Priority 2  rate-limit-login
   Priority 10 CommonRuleSet      (グループ内のどれかが Block なら終わり)
   Priority 11 KnownBadInputs
   Priority 12 SQLi
   どれにも当たらない → DefaultAction(Allow)
要素 意味
Priority 小さい順に評価。Allow・Block に当たったら、そこで終わり(Count は続ける)
Action 自分で書いたルールの動作(Allow・Block・Count・Captcha・Challenge)
OverrideAction ルールグループ(マネージドルールなど)の動作。None(グループの判定どおり)か Count(全部を数えるだけ)
VisibilityConfig CloudWatch の指標名とサンプルの保存。全ルールで必須
DefaultAction どれにも当たらないときの動作。ふつうは Allow

Action と OverrideAction を取り違えるのが、JSON でつまずきやすいところです。自分で書いたルールは Action、ルールグループを参照するルールは OverrideAction です。

目的別のルール JSON

以下は1つの Web ACL の Rules 配列に入れる要素です。moto で create-web-acl --rules file://rules.json に渡し、作成されることを確かめました。

マネージドルール(Common・KnownBadInputs・SQLi)

[
  {
    "Name": "AWS-AWSManagedRulesCommonRuleSet",
    "Priority": 10,
    "Statement": {
      "ManagedRuleGroupStatement": { "VendorName": "AWS", "Name": "AWSManagedRulesCommonRuleSet" }
    },
    "OverrideAction": { "None": {} },
    "VisibilityConfig": { "SampledRequestsEnabled": true, "CloudWatchMetricsEnabled": true, "MetricName": "AWS-AWSManagedRulesCommonRuleSet" }
  },
  {
    "Name": "AWS-AWSManagedRulesKnownBadInputsRuleSet",
    "Priority": 11,
    "Statement": {
      "ManagedRuleGroupStatement": { "VendorName": "AWS", "Name": "AWSManagedRulesKnownBadInputsRuleSet" }
    },
    "OverrideAction": { "None": {} },
    "VisibilityConfig": { "SampledRequestsEnabled": true, "CloudWatchMetricsEnabled": true, "MetricName": "AWS-AWSManagedRulesKnownBadInputsRuleSet" }
  },
  {
    "Name": "AWS-AWSManagedRulesSQLiRuleSet",
    "Priority": 12,
    "Statement": {
      "ManagedRuleGroupStatement": { "VendorName": "AWS", "Name": "AWSManagedRulesSQLiRuleSet" }
    },
    "OverrideAction": { "Count": {} },
    "VisibilityConfig": { "SampledRequestsEnabled": true, "CloudWatchMetricsEnabled": true, "MetricName": "AWS-AWSManagedRulesSQLiRuleSet" }
  }
]

SQLi は OverrideAction: Count にして、まず数えるだけにしています。

Core rule set(AWSManagedRulesCommonRuleSet)の主なルールです(ドキュメント)。誤ってブロックしたとき、ログに出るのはこの名前です。

ルール名 ブロックするもの
NoUserAgent_HEADER User-Agent ヘッダーが無い
UserAgent_BadBots_HEADER nessus・nmap などの User-Agent
SizeRestrictions_QUERYSTRING クエリ文字列が 2,048 バイト超
SizeRestrictions_Cookie_HEADER Cookie ヘッダーが 10,240 バイト超
SizeRestrictions_BODY 本文が 8 KB(8,192 バイト)超
SizeRestrictions_URIPATH URI パスが 1,024 バイト超
EC2MetaDataSSRF_* EC2 のメタデータを抜き出そうとする試み
GenericLFI_*・GenericRFI_* ../../ などのファイル読み込み、外部 URL の読み込み
RestrictedExtensions_* .log・.ini などの拡張子
CrossSiteScripting_* XSS のパターン

ファイルのアップロードや大きな JSON を受ける API は、SizeRestrictions_BODY に当たりやすいです。後述の「特定のパスだけ除外」で扱います。

KnownBadInputs には Log4JRCE_*・JavaDeserializationRCE_*・Host_localhost_HEADER・PROPFIND_METHOD・ExploitablePaths_URIPATH など、SQLi には SQLi_QUERYARGUMENTS・SQLi_BODY・SQLi_COOKIE・SQLiExtendedPatterns_* などが入っています。

IP 制限(許可リスト・拒否リスト)

aws wafv2 create-ip-set --name office --scope REGIONAL \
  --ip-address-version IPV4 \
  --addresses 203.0.113.0/24 198.51.100.10/32
{
  "Name": "allow-office-ip",
  "Priority": 0,
  "Statement": {
    "IPSetReferenceStatement": { "ARN": "arn:aws:wafv2:ap-northeast-1:123456789012:regional/ipset/office/095a17db-3423-4b96-b2b4-f5fb51ec6ecb" }
  },
  "Action": { "Allow": {} },
  "VisibilityConfig": { "SampledRequestsEnabled": true, "CloudWatchMetricsEnabled": true, "MetricName": "allow-office-ip" }
}

Allow のルールに当たると、それより後ろ(マネージドルールなど)は評価されません。 社内の IP を素通しにするか、後ろのルールでも検査するかを決めてから Priority を付けます。/admin だけを社内の IP に限る例です。

{
  "Name": "admin-only-office",
  "Priority": 4,
  "Statement": {
    "AndStatement": {
      "Statements": [
        { "ByteMatchStatement": { "SearchString": "/admin", "FieldToMatch": { "UriPath": {} }, "TextTransformations": [{ "Priority": 0, "Type": "LOWERCASE" }], "PositionalConstraint": "STARTS_WITH" } },
        { "NotStatement": { "Statement": { "IPSetReferenceStatement": { "ARN": "arn:aws:wafv2:ap-northeast-1:123456789012:regional/ipset/office/095a17db-3423-4b96-b2b4-f5fb51ec6ecb" } } } }
      ]
    }
  },
  "Action": { "Block": {} },
  "VisibilityConfig": { "SampledRequestsEnabled": true, "CloudWatchMetricsEnabled": true, "MetricName": "admin-only-office" }
}

IP セット1つに入れられるのは、CIDR で 10,000 件までです(ドキュメント)。

国制限

{
  "Name": "block-countries",
  "Priority": 1,
  "Statement": { "GeoMatchStatement": { "CountryCodes": ["KP", "RU"] } },
  "Action": { "Block": {} },
  "VisibilityConfig": { "SampledRequestsEnabled": true, "CloudWatchMetricsEnabled": true, "MetricName": "block-countries" }
}

日本からだけ許可するなら、NotStatement で「JP 以外」をブロックします。

{
  "Name": "allow-only-jp",
  "Priority": 3,
  "Statement": {
    "NotStatement": { "Statement": { "GeoMatchStatement": { "CountryCodes": ["JP"] } } }
  },
  "Action": { "Block": {} },
  "VisibilityConfig": { "SampledRequestsEnabled": true, "CloudWatchMetricsEnabled": true, "MetricName": "allow-only-jp" }
}

1つの GeoMatchStatement に書ける国コードは 50 個までです(ドキュメント)。CloudFront の後ろにある ALB の Web ACL では、送信元が CloudFront の IP になるため、国の判定は CloudFront 側の Web ACL で行うか、ForwardedIPConfig でヘッダーの IP を使います。

レートベース(集約キーと評価ウィンドウ)

ログインのパスだけ、1 つの IP から 5 分に 100 回を超えたら、429 を返してブロックする例です。

{
  "Name": "rate-limit-login",
  "Priority": 2,
  "Statement": {
    "RateBasedStatement": {
      "Limit": 100,
      "EvaluationWindowSec": 300,
      "AggregateKeyType": "IP",
      "ScopeDownStatement": {
        "ByteMatchStatement": {
          "SearchString": "/login",
          "FieldToMatch": { "UriPath": {} },
          "TextTransformations": [{ "Priority": 0, "Type": "LOWERCASE" }],
          "PositionalConstraint": "STARTS_WITH"
        }
      }
    }
  },
  "Action": { "Block": { "CustomResponse": { "ResponseCode": 429 } } },
  "VisibilityConfig": { "SampledRequestsEnabled": true, "CloudWatchMetricsEnabled": true, "MetricName": "rate-limit-login" }
}
項目 値(API リファレンス・ドキュメント)
Limit 評価ウィンドウの間に許す件数。最小 10、最大 2,000,000,000
EvaluationWindowSec 60・120・300・600。既定 300(5 分)
判定の頻度 約 10 秒ごとに、ウィンドウの分だけさかのぼって数える
反映の遅れ 超えてから止まるまで、また下がってから解除まで、通常 30 秒以内(数分かかることもある)
1 ルールで止められる数 集約キー(IP など)の値で 10,000 個まで
1 つの Web ACL のレートベースのルール 10 個まで

レートベースは厳密な回数制限ではありません。 ドキュメントでも、可用性を守るための仕組みで、正確な回数の制限には向かないとされています。

AggregateKeyType 何ごとに数えるか 例
IP 送信元の IP 上の例
FORWARDED_IP X-Forwarded-For などのヘッダーの先頭の IP CDN の後ろ
CUSTOM_KEYS ヘッダー・Cookie・クエリ・パス・メソッド・ラベル・IP などの組み合わせ(最大 5 個) API キーごと
CONSTANT まとめて1つ(スコープダウンに当たる全件)。スコープダウンが必須 パス全体の流量
[
  {
    "Name": "rate-per-apikey-and-ip",
    "Priority": 0,
    "Statement": {
      "RateBasedStatement": {
        "Limit": 1000,
        "EvaluationWindowSec": 60,
        "AggregateKeyType": "CUSTOM_KEYS",
        "CustomKeys": [
          { "Header": { "Name": "x-api-key", "TextTransformations": [{ "Priority": 0, "Type": "NONE" }] } },
          { "IP": {} }
        ]
      }
    },
    "Action": { "Block": {} },
    "VisibilityConfig": { "SampledRequestsEnabled": true, "CloudWatchMetricsEnabled": true, "MetricName": "rate-per-apikey-and-ip" }
  },
  {
    "Name": "rate-forwarded-ip",
    "Priority": 1,
    "Statement": {
      "RateBasedStatement": {
        "Limit": 2000,
        "EvaluationWindowSec": 300,
        "AggregateKeyType": "FORWARDED_IP",
        "ForwardedIPConfig": { "HeaderName": "X-Forwarded-For", "FallbackBehavior": "MATCH" }
      }
    },
    "Action": { "Block": {} },
    "VisibilityConfig": { "SampledRequestsEnabled": true, "CloudWatchMetricsEnabled": true, "MetricName": "rate-forwarded-ip" }
  },
  {
    "Name": "rate-all-search",
    "Priority": 2,
    "Statement": {
      "RateBasedStatement": {
        "Limit": 5000,
        "EvaluationWindowSec": 60,
        "AggregateKeyType": "CONSTANT",
        "ScopeDownStatement": {
          "ByteMatchStatement": {
            "SearchString": "/search",
            "FieldToMatch": { "UriPath": {} },
            "TextTransformations": [{ "Priority": 0, "Type": "NONE" }],
            "PositionalConstraint": "STARTS_WITH"
          }
        }
      }
    },
    "Action": { "Count": {} },
    "VisibilityConfig": { "SampledRequestsEnabled": true, "CloudWatchMetricsEnabled": true, "MetricName": "rate-all-search" }
  }
]

CUSTOM_KEYS では、集約キーのどれかが無いリクエストは、そのルールの数え方から外れます(API リファレンス)。x-api-key を付けないリクエストは、上の1つ目のルールでは数えられません。キーなしのリクエストを別のルールで絞っておきます。

Limit を 5 にして渡すと、CLI が送る前に止めました。

Parameter validation failed:
Invalid value for parameter Rules[1].Statement.RateBasedStatement.Limit, value: 5, valid min value: 10

特定のパスだけ除外する(3つの方法)

/api/upload で SizeRestrictions_BODY に当たって 403 になる、という場面を例にします。

方法 書き方 除外の範囲
① スコープダウン ManagedRuleGroupStatement.ScopeDownStatement に「/api/upload 以外」 そのパスはグループ全体で検査しない
② ルールだけ Count RuleActionOverrides で SizeRestrictions_BODY を Count 全パスでそのルールだけ止める
③ Count+ラベル ②にして、ラベルとパスの条件で自分のルールで Block そのパス・そのルールだけ外す(いちばん細かい)

① ScopeDownStatement:/api/upload には Core rule set を当てません。XSS などの検査もそのパスでは外れます。

{
  "Name": "AWS-AWSManagedRulesCommonRuleSet",
  "Priority": 10,
  "Statement": {
    "ManagedRuleGroupStatement": {
      "VendorName": "AWS",
      "Name": "AWSManagedRulesCommonRuleSet",
      "ScopeDownStatement": {
        "NotStatement": {
          "Statement": {
            "ByteMatchStatement": {
              "SearchString": "/api/upload",
              "FieldToMatch": { "UriPath": {} },
              "TextTransformations": [{ "Priority": 0, "Type": "NONE" }],
              "PositionalConstraint": "STARTS_WITH"
            }
          }
        }
      }
    }
  },
  "OverrideAction": { "None": {} },
  "VisibilityConfig": { "SampledRequestsEnabled": true, "CloudWatchMetricsEnabled": true, "MetricName": "AWS-AWSManagedRulesCommonRuleSet" }
}

② RuleActionOverrides:グループの中の1つのルールだけ、動作を Count に変えます(以前の ExcludedRules に代わる書き方)。

"ManagedRuleGroupStatement": {
  "VendorName": "AWS",
  "Name": "AWSManagedRulesCommonRuleSet",
  "RuleActionOverrides": [
    { "Name": "SizeRestrictions_BODY", "ActionToUse": { "Count": {} } },
    { "Name": "NoUserAgent_HEADER", "ActionToUse": { "Count": {} } }
  ]
}

③ Count+ラベル:Count にしたルールも、当たったリクエストにラベルを付けます。後ろのルールで「ラベルがある、かつ /api/upload ではない」ものを Block します。

[
  {
    "Name": "AWS-AWSManagedRulesCommonRuleSet",
    "Priority": 10,
    "Statement": {
      "ManagedRuleGroupStatement": {
        "VendorName": "AWS",
        "Name": "AWSManagedRulesCommonRuleSet",
        "RuleActionOverrides": [
          { "Name": "SizeRestrictions_BODY", "ActionToUse": { "Count": {} } }
        ]
      }
    },
    "OverrideAction": { "None": {} },
    "VisibilityConfig": { "SampledRequestsEnabled": true, "CloudWatchMetricsEnabled": true, "MetricName": "AWS-AWSManagedRulesCommonRuleSet" }
  },
  {
    "Name": "block-large-body-except-upload",
    "Priority": 20,
    "Statement": {
      "AndStatement": {
        "Statements": [
          { "LabelMatchStatement": { "Scope": "LABEL", "Key": "awswaf:managed:aws:core-rule-set:SizeRestrictions_Body" } },
          { "NotStatement": { "Statement": { "ByteMatchStatement": {
              "SearchString": "/api/upload", "FieldToMatch": { "UriPath": {} },
              "TextTransformations": [{ "Priority": 0, "Type": "NONE" }], "PositionalConstraint": "STARTS_WITH" } } } }
        ]
      }
    },
    "Action": { "Block": {} },
    "VisibilityConfig": { "SampledRequestsEnabled": true, "CloudWatchMetricsEnabled": true, "MetricName": "block-large-body-except-upload" }
  }
]

ラベルの名前はドキュメントに載っていて、SizeRestrictions_BODY なら awswaf:managed:aws:core-rule-set:SizeRestrictions_Body(末尾の大文字・小文字がルール名と違う)です。ラベルで判定するルールは、ラベルを付けるルールより後ろ(Priority が大きい)に置きます。

JSON の誤りで出たエラー

CLI は、型や必須項目の誤りを送る前に止めます。

誤り 出たエラー
Limit を文字列 "100" にした Invalid type for parameter Rules[2].Statement.RateBasedStatement.Limit, value: 100, type: <class 'str'>, valid types: <class 'int'>
VisibilityConfig が無い Missing required parameter in Rules[1]: "VisibilityConfig"
ActionToUse を "Count"(文字列)にした Invalid type for parameter ...ActionToUse, value: Count, type: <class 'str'>, valid types: <class 'dict'>

Count や None は、{"Count": {}} のように空のオブジェクトで書きます。

CLI で Web ACL を作って付ける

# REGIONAL:ALB・API Gateway など。CloudFront は --scope CLOUDFRONT --region us-east-1
aws wafv2 create-web-acl --name app-acl --scope REGIONAL \
  --default-action Allow={} \
  --visibility-config SampledRequestsEnabled=true,CloudWatchMetricsEnabled=true,MetricName=app-acl \
  --rules file://rules.json

# ALB に付ける(CloudFront は、ディストリビューションの設定で Web ACL の ARN を指定)
aws wafv2 associate-web-acl \
  --web-acl-arn arn:aws:wafv2:ap-northeast-1:123456789012:regional/webacl/app-acl/0374699c-... \
  --resource-arn arn:aws:elasticloadbalancing:ap-northeast-1:123456789012:loadbalancer/app/my-alb/50dc6c495c0c9188

moto で作った Web ACL のルールの並びです。

0   allow-office-ip Allow
1   block-countries Block
2   rate-limit-login    Block
10  AWS-AWSManagedRulesCommonRuleSet    None
11  AWS-AWSManagedRulesKnownBadInputsRuleSet    None
12  AWS-AWSManagedRulesSQLiRuleSet  Count

CloudFront 用の Web ACL は、--scope CLOUDFRONT に加えて --region us-east-1 が要ります(API リファレンス)。

更新はロックトークンを付けて全体を置き換える

update-web-acl は、ルールを1つ足すのではなく、Rules 全体を置き換えます。直前の get-web-acl で取った LockToken を付けます。

ID=$(aws wafv2 list-web-acls --scope REGIONAL --query "WebACLs[?Name=='app-acl'].Id" --output text)
LT=$(aws wafv2 get-web-acl --name app-acl --scope REGIONAL --id $ID --query LockToken --output text)
aws wafv2 update-web-acl --name app-acl --scope REGIONAL --id $ID --lock-token $LT \
  --default-action Allow={} \
  --visibility-config SampledRequestsEnabled=true,CloudWatchMetricsEnabled=true,MetricName=app-acl \
  --rules file://rules.json

同じロックトークンで2回目を送ると、moto でも次のエラーになりました。

An error occurred (WAFOptimisticLockException) when calling the UpdateWebACL operation: AWS WAF couldn't save your changes because someone changed the resource after you started to edit it. Reapply your changes.

Count モードから始める手順

手順 やること 見るもの
1 マネージドルールは OverrideAction: Count、自分のルールは Action: Count で入れる —
2 ログ出力を有効にする(下) —
3 数日〜1 週間、普段の利用で当たるルールを見る ログの nonTerminatingMatchingRules、CloudWatch の CountedRequests
4 正しい利用で当たるルールを、除外の3つの方法で外す —
5 OverrideAction: None・Action: Block に変える BlockedRequests
6 切り替え後もしばらくログを見る 403 の問い合わせ

Count で当たったルールは、ログの ruleGroupList[].nonTerminatingMatchingRules に action: COUNT で出ます。Athena のドキュメントのテーブル定義には、上書きする前の動作を入れる overriddenaction の列もあります。

誤ってブロックされたときにログで調べる

ログを出す

ログの出力先の名前は、aws-waf-logs- で始める必要があります(CloudWatch Logs のロググループ、S3 バケット、Firehose)。CloudWatch Logs のロググループは、Web ACL と同じリージョン・同じアカウントに作ります。

aws logs create-log-group --log-group-name aws-waf-logs-app

ブロックとカウントだけ残し、authorization ヘッダーを伏せる設定です。

{
  "ResourceArn": "arn:aws:wafv2:ap-northeast-1:123456789012:regional/webacl/app-acl/0374699c-...",
  "LogDestinationConfigs": ["arn:aws:logs:ap-northeast-1:123456789012:log-group:aws-waf-logs-app"],
  "RedactedFields": [{ "SingleHeader": { "Name": "authorization" } }],
  "LoggingFilter": {
    "DefaultBehavior": "DROP",
    "Filters": [
      {
        "Behavior": "KEEP",
        "Requirement": "MEETS_ANY",
        "Conditions": [
          { "ActionCondition": { "Action": "BLOCK" } },
          { "ActionCondition": { "Action": "COUNT" } }
        ]
      }
    ]
  }
}
aws wafv2 put-logging-configuration --logging-configuration file://logging.json

moto でも、この JSON で DefaultBehavior: DROP のログ設定が作られました。

ログの JSON の項目

項目 意味(ドキュメント) 調べるときの使い方
action 最終的な動作(ALLOW・BLOCK・CAPTCHA・CHALLENGE) BLOCK を探す
terminatingRuleId 判定を決めたルール(無ければ Default_Action) Web ACL の中のどのルールか
terminatingRuleType REGULAR・RATE_BASED・GROUP・MANAGED_RULE_GROUP マネージドかどうか
ruleGroupList[].terminatingRule.ruleId グループの中で当たったルール SizeRestrictions_BODY などの名前はここ
ruleGroupList[].nonTerminatingMatchingRules グループの中で Count で当たったルール Count モードの確認
rateBasedRuleList 評価したレートベースのルールと limitKey・maxRateAllowed レート制限の確認
terminatingRuleMatchDetails どこの何に当たったか(SQLi・XSS のみ) location・matchedData
httpRequest.clientIp・country・uri・args・httpMethod・headers リクエストの中身 どの利用者・どの画面か
labels 付いたラベル(最初の 100 個) ラベルで除外するときの名前

ドキュメントの例(ルールグループの中で XSS に当たってブロック)から抜き出したものです。

{
  "terminatingRuleId": "RG-Reference",
  "terminatingRuleType": "GROUP",
  "action": "BLOCK",
  "terminatingRuleMatchDetails": [{ "conditionType": "XSS", "location": "HEADER", "matchedData": ["<", "frameset"] }],
  "ruleGroupList": [{
    "ruleGroupId": "arn:aws:wafv2:us-east-1:123456789012:global/rulegroup/hello-world/c05lb698-...",
    "terminatingRule": { "ruleId": "RuleA-XSS", "action": "BLOCK", "ruleMatchDetails": null },
    "nonTerminatingMatchingRules": [{ "ruleId": "RuleB-SQLi", "action": "COUNT" }],
    "excludedRules": null
  }],
  "httpRequest": { "clientIp": "3.3.3.3", "country": "US", "uri": "/myUri", "args": "", "httpMethod": "GET" }
}

CloudWatch Logs Insights のクエリ

ロググループ aws-waf-logs-app を選んで実行します。

# ブロックの多いルール(グループの中のルール名まで)
fields terminatingRuleId, ruleGroupList.0.terminatingRule.ruleId as innerRule
| filter action = "BLOCK"
| stats count(*) as cnt by terminatingRuleId, innerRule
| sort cnt desc
# 特定の利用者・パスがなぜブロックされたか
fields @timestamp, httpRequest.clientIp, httpRequest.uri, httpRequest.args,
       terminatingRuleId, ruleGroupList.0.terminatingRule.ruleId
| filter action = "BLOCK" and httpRequest.uri like "/api/upload"
| sort @timestamp desc
| limit 50
# レートベースでブロックされた IP
fields httpRequest.clientIp
| filter terminatingRuleType = "RATE_BASED"
| stats count(*) as cnt by httpRequest.clientIp
| sort cnt desc
| limit 20

ruleGroupList.0 は、配列の先頭の要素です。複数のルールグループが当たる構成では、ruleGroupList.1 なども見ます。Logs Insights の書き方は、関連記事「CloudWatch LogsのAWS CLI tail・検索・クエリ例」で扱っています。

Athena のクエリ(S3 に出したとき)

テーブルは、Athena のドキュメントにある WAF ログ用の CREATE EXTERNAL TABLE(パーティション射影つき、列は terminatingruleid・rulegrouplist・httprequest など)で作ります。そのうえでのクエリ例です。

-- 直近1日、グループの中で当たったルール別のブロック件数
SELECT rg.terminatingrule.ruleid AS inner_rule,
       terminatingruleid,
       count(*) AS cnt
FROM waf_logs_partition_projection
CROSS JOIN UNNEST(rulegrouplist) AS t(rg)
WHERE action = 'BLOCK'
  AND rg.terminatingrule IS NOT NULL
  AND log_time >= date_format(current_timestamp - interval '1' day, '%Y/%m/%d/%H/%i')
GROUP BY 1, 2
ORDER BY cnt DESC;
-- ブロックされた上位の IP と国
SELECT httprequest.clientip, httprequest.country, count(*) AS cnt
FROM waf_logs_partition_projection
WHERE action = 'BLOCK'
  AND log_time >= '2026/10/12/00/00'
GROUP BY 1, 2
ORDER BY cnt DESC
LIMIT 20;

log_time(パーティションの列)で絞ると、読むデータの量と料金を抑えられます(Athena のドキュメントの案内)。Athena の使い方の流れは、関連記事「CloudTrailで誰が操作したか調べる|lookup-eventsとAthena」で扱っています。

WCU の上限

WCU(Web ACL Capacity Units)は、ルールの重さを表す単位です。

項目 値(ドキュメント)
1 つの Web ACL の WCU の上限 5,000(変えられない)
追加の料金がかからない範囲 1,500 まで。超えると 500 WCU ごとに料金が加わる
マネージドルールの例 Common 700・KnownBadInputs 200・SQLi 200・AdminProtection 100・Linux 200・PHP 100・WordPress 100

この記事の3つのマネージドルールだけで 1,100 WCU です。自分のルールの WCU は、aws wafv2 check-capacity --scope REGIONAL --rules file://rules.json で確かめられます(API)。

料金

料金ページの例をもとにした、主な項目です(米ドル。リージョンで違うことがあります)。

項目 料金
Web ACL $5.00/月
ルール(マネージドルールグループも1つで1ルール) $1.00/月
リクエスト $0.60/100 万件
WCU が 1,500 を超えた分 500 WCU ごとに $0.20/100 万件
本文の検査を既定より広げた分 16 KB ごとに $0.30/100 万件
CAPTCHA $0.40/1,000 回
ログ 100 万リクエストあたり 500 MB の CloudWatch Logs などの取り込みを含む

Web ACL 1 つにルール 6 つ、月 1,000 万リクエストなら、$5 + $6 + $6 = 月 $17 の計算です(Bot Control などの有料のマネージドルールは別料金)。AWS の料金の確かめ方は、関連記事「AWSの料金を確認する方法|Cost Explorer・CLI・予算アラート」で扱っています。

よくある質問

Q. マネージドルールを入れたら、正しいリクエストが 403 になりました。 A. ログの ruleGroupList[].terminatingRule.ruleId でルール名を確かめ、そのルールだけを RuleActionOverrides で Count にするか、ラベルとパスで外します。グループ全体を外すと、ほかの検査も止まります。

Q. レートベースの Limit を 10 にしても、11 件目からすぐ止まりません。 A. 約 10 秒ごとの判定で、ドキュメントでも反映まで通常 30 秒以内(数分のこともある)とされています。厳密な回数制限には向きません。

Q. ALB の Web ACL で、国やレートの判定が CloudFront の IP になります。 A. CloudFront を経由していると、ALB から見た送信元は CloudFront です。CloudFront 側にも Web ACL を付けるか、FORWARDED_IP と ForwardedIPConfig で X-Forwarded-For の IP を使います。CloudFront の設定は、関連記事「CloudFront S3 OACの設定手順|バケットポリシー例と403の原因」で扱っています。

Q. ExcludedRules と RuleActionOverrides のどちらを使いますか。 A. 新しい書き方は RuleActionOverrides です。ActionToUse に Count 以外(Block・Allow・Captcha など)も指定できます。

まとめ

  • ルールは Priority の小さい順。自分のルールは Action、ルールグループは OverrideAction、全ルールに VisibilityConfig
  • まず Common(700)・KnownBadInputs(200)・SQLi(200)、レートベース(Limit 最小 10、ウィンドウ 60〜600 秒)
  • Count で始め、ログで当たるルールを確かめてから Block
  • 除外は スコープダウン(パス全体)/RuleActionOverrides(ルール全体)/Count+ラベル(パス×ルール)
  • 誤ブロックは、ログの ruleGroupList[].terminatingRule.ruleId で中のルール名を見る
  • WCU は上限 5,000、1,500 を超えると追加料金。CloudFront 用は us-east-1

参考・出典

確認日はいずれも 2026年10月13日です。

  • AWS WAF Developer Guide「Baseline rule groups」:https://docs.aws.amazon.com/waf/latest/developerguide/aws-managed-rule-groups-baseline.html
  • AWS WAF Developer Guide「Use-case specific rule groups」:https://docs.aws.amazon.com/waf/latest/developerguide/aws-managed-rule-groups-use-case.html
  • AWS WAF 開発者ガイド「JSON および YAML でのマネージドルールグループ設定の例」:https://docs.aws.amazon.com/ja_jp/waf/latest/developerguide/waf-using-managed-rule-groups-json-yaml.html
  • AWS WAF 開発者ガイド「レートベースのルールの例」:https://docs.aws.amazon.com/ja_jp/waf/latest/developerguide/waf-rule-statement-type-rate-based-examples.html
  • AWS WAF Developer Guide「Rate-based rule high-level settings」:https://docs.aws.amazon.com/waf/latest/developerguide/waf-rule-statement-type-rate-based-high-level-settings.html
  • AWS WAF Developer Guide「Rate-based rule aggregation options and keys」:https://docs.aws.amazon.com/waf/latest/developerguide/waf-rule-statement-type-rate-based-aggregation-options.html
  • AWS WAF Developer Guide「Rate-based rule caveats」:https://docs.aws.amazon.com/waf/latest/developerguide/waf-rule-statement-type-rate-based-caveats.html
  • AWS WAF Developer Guide「AWS WAF quotas」:https://docs.aws.amazon.com/waf/latest/developerguide/limits.html
  • AWS WAF Developer Guide「Log fields for protection pack (web ACL) traffic」:https://docs.aws.amazon.com/waf/latest/developerguide/logging-fields.html
  • AWS WAF Developer Guide「Log examples」:https://docs.aws.amazon.com/waf/latest/developerguide/logging-examples.html
  • AWS WAF Developer Guide「Sending web ACL traffic logs to an Amazon CloudWatch Logs log group」:https://docs.aws.amazon.com/waf/latest/developerguide/logging-cw-logs.html
  • Amazon Athena User Guide「Create a table for AWS WAF logs in Athena using partition projection」:https://docs.aws.amazon.com/athena/latest/ug/create-waf-table-partition-projection.html
  • AWS WAF API Reference「RateBasedStatement」「CreateWebACL」:https://docs.aws.amazon.com/waf/latest/APIReference/API_RateBasedStatement.html
  • AWS WAF の料金:https://aws.amazon.com/waf/pricing/

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「AWS WAFの設定|マネージドルールとレートベースのJSON例」

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?