投稿

AWS WAF の GenericLFI_BODY が PDF アップロードを誤検知してブロックする

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_overrideGenericLFI_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_transformationNONE でなく 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: COUNTOverriddenAction: 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 は、AWSManagedRulesUnixRuleSetUNIXShellCommandsVariables_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 を補足する多層防御として機能する。

トレンドのタグ