AWS WAF の GenericLFI_BODY が PDF アップロードを誤検知してブロックする
現象
ユーザーが PDF ファイルをアップロードすると「問題が発生しました」エラーが表示される。別のファイルは正常にアップロードでき、時間をおいても解消しない。サポート担当が同じファイルをアップロードすると成功する場合もある。
調査
S3 に保存されている WAF ログを確認すると、該当リクエストが BLOCK されていた。
{
"action": "BLOCK",
"terminatingRuleId": "AWSManagedRulesCommonRuleSet",
"ruleGroupList": [
{
"ruleGroupId": "AWS#AWSManagedRulesCommonRuleSet",
"terminatingRule": {
"ruleId": "GenericLFI_BODY",
"action": "BLOCK"
}
}
],
"labels": [
{ "name": "awswaf:managed:aws:core-rule-set:GenericLFI_Body" }
],
"oversizeFields": ["REQUEST_BODY"],
"requestBodySize": 111379,
"requestBodySizeInspectedByWAF": 8192
}原因
GenericLFI_BODY は リクエストボディ内の LFI(Local File Inclusion)パターン(../ 等のパストラバーサル)を検知するルール。PDF のバイナリデータにこのパターンと一致するバイト列が含まれており、誤検知が発生していた。
WAF はボディの先頭 8,192 bytes のみを検査する。multipart/form-data のバウンダリやフィールド順序はブラウザ・環境により異なるため、同じファイルでも環境によって検知されたりされなかったりする。
対処
AWSManagedRulesCommonRuleSet に scope-down statement を追加し、multipart/form-data リクエストを検査対象から除外する。
rule {
name = "AWSManagedRulesCommonRuleSet"
priority = 0
override_action { none {} }
statement {
managed_rule_group_statement {
vendor_name = "AWS"
name = "AWSManagedRulesCommonRuleSet"
# multipart/form-data(ファイルアップロード)を除外
scope_down_statement {
not_statement {
statement {
byte_match_statement {
search_string = "multipart/form-data"
positional_constraint = "STARTS_WITH"
field_to_match {
single_header { name = "content-type" }
}
text_transformation {
priority = 0
type = "NONE"
}
}
}
}
}
}
}
}同じ scope-down は AWSManagedRulesSQLiRuleSet でも運用実績がある。ファイルアップロードではボディにバイナリが含まれるのが正常であり、パターンマッチの誤検知が避けられないため、この除外は妥当。
scope_down_statement の問題点と代替案
PR レビューで「scope_down_statement は除外範囲が広すぎる」と指摘を受けた。
scope_down_statement は managed rule group 全体の評価条件を制御するため、multipart/form-data リクエストに対して AWSManagedRulesCommonRuleSet の全ルール(ヘッダーチェック・クエリストリングチェックを含む)がスキップされる。GenericLFI_BODY だけを除外したい場合には除外範囲が広すぎる。
rule_action_override では解決できない
「では rule_action_override で GenericLFI_BODY だけ count にすればよいのでは?」と考えるが、rule_action_override には Content-Type などの条件を付ける機能がない。GenericLFI_BODY を count にすると 全リクエスト(multipart 以外の通常リクエストへの LFI 攻撃も含む)がブロックされなくなり、scope_down より防御が弱くなる。
より外科的な対応として、ラベルを使った条件付きブロックがある。
AWS WAF のラベル機能
managed rule group のルールは、アクションが count に override されていてもマッチ時にリクエストへラベルを付与する。後続のカスタムルールでこのラベルを参照できる。
GenericLFI_BODY が付与するラベル:
awswaf:managed:aws:core-rule-set:GenericLFI_Body注意: ラベル名は
GenericLFI_BODY(ルール名)ではなくGenericLFI_Body(混合ケース)。WAF ログのlabels[].nameフィールドで確認できる(上記ログ参照)。
ラベルベースの実装
# 1. CommonRuleSet: GenericLFI_BODY を count に(ラベルは付与される)
rule {
name = "AWSManagedRulesCommonRuleSet"
priority = 0
override_action { none {} }
statement {
managed_rule_group_statement {
vendor_name = "AWS"
name = "AWSManagedRulesCommonRuleSet"
rule_action_override {
name = "GenericLFI_BODY"
action_to_use { count {} }
}
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "AWSManagedRulesCommonRuleSet-Metric"
sampled_requests_enabled = true
}
}
# 2. カスタムルール: GenericLFI_BODY ラベル + multipart 以外 → block
rule {
name = "BlockGenericLFI_BODY_NonMultipart"
priority = 10
action { block {} }
statement {
and_statement {
statement {
label_match_statement {
scope = "LABEL"
key = "awswaf:managed:aws:core-rule-set:GenericLFI_Body"
}
}
statement {
not_statement {
statement {
byte_match_statement {
search_string = "multipart/form-data"
positional_constraint = "STARTS_WITH"
field_to_match {
single_header { name = "content-type" }
}
text_transformation {
priority = 0
type = "NONE"
}
}
}
}
}
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "BlockGenericLFI_BODY_NonMultipart-Metric"
sampled_requests_enabled = true
}
}比較
| 方式 | 対象外にできるルール | ヘッダー/クエリ保護 | 実装の複雑さ |
|---|---|---|---|
| scope_down_statement | 全ルール(過剰) | multipart 時はスキップ | 低 |
| ラベル + カスタムルール | GenericLFI_BODY のみ | 維持される | 中 |
rule_action_override だけでは Content-Type による条件付けができないため、ラベル経由のカスタムルールが唯一の解となる。
また、Content-Type ヘッダーは case-insensitive であるため、text_transformation は NONE でなく LOWERCASE を使うこと。
text_transformation {
priority = 0
type = "LOWERCASE" # "Multipart/Form-Data" 等の表記にも対応
}staging での動作検証(2026-04-27)
get-sampled-requests と curl を組み合わせて、ラベルベースの動線を確認した。
GenericLFI_BODY が count になっていることの確認
aws wafv2 get-sampled-requests \
--web-acl-arn "<Web ACL ARN>" \
--rule-metric-name "AWSManagedRulesCommonRuleSet-Metric" \
--scope REGIONAL \
--time-window "StartTime=<開始>,EndTime=<終了>" \
--max-items 100 \
--region ap-northeast-1結果: Action: COUNT、OverriddenAction: BLOCK → rule_action_override が正常に機能している。
ラベルベースブロックの確認
# 非ファイルアップロードの LFI 試行
curl -X POST https://example.com/ \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "param=../../config/database.yml"
# → 403
aws wafv2 get-sampled-requests \
--rule-metric-name "BlockGenericLFIBodyExceptFileUpload-Metric" ...
# → Action: BLOCK ✅多層防御の挙動
../../etc/passwd 等の Unix パスを含む LFI は、AWSManagedRulesUnixRuleSet の UNIXShellCommandsVariables_BODY(priority 4)が先にブロックするため、ラベルベースルール(priority 7)に到達しない。
| リクエスト | GenericLFI_BODY(p=0) | UnixRuleSet(p=4) | ラベルルール(p=7) |
|---|---|---|---|
../../etc/passwd | COUNT + ラベル付与 | BLOCK(ここで止まる) | 未到達 |
../../config/database.yml | COUNT + ラベル付与 | マッチせず | BLOCK ✅ |
| multipart/form-data の PDF | COUNT + ラベル付与 | マッチせず | not_statement で通過 ✅ |
ラベルルールは UnixRuleSet をすり抜けた LFI を補足する多層防御として機能する。