- AWS
AWS WAFの設定|マネージドルールとレートベースのJSON例
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期連続の黒字決算。