AWS WAF KnownBadInputs v1.26 の REMOVE_NULLS で NULL バイト挿入による検出回避を防げるか確認してみた

AWS WAF KnownBadInputs v1.26 の REMOVE_NULLS で NULL バイト挿入による検出回避を防げるか確認してみた

AWS WAF マネージドルール AWSManagedRulesKnownBadInputsRuleSet の v1.26 で ReactJSRCE_BODY に REMOVE_NULLS テキスト変換が追加されました。NULLバイト挿入により v1.25 では検出されなかったペイロードが、v1.26 ではブロックされることを確認します。
2026.07.19

はじめに

AWS WAF のマネージドルール AWSManagedRulesKnownBadInputsRuleSet に、2026年7月15日付で v1.26 がリリースされました。このバージョンでは ReactJSRCE_BODY ルールに REMOVE_NULLS テキスト変換が追加されています。

https://docs.aws.amazon.com/waf/latest/developerguide/aws-managed-rule-groups-changelog.html

REMOVE_NULLS は、NULLバイト(0x00)を除去してからパターンマッチを行うテキスト変換です。これにより、NULLバイトを挿入してシグネチャを分断する形の回避を防ぐことができます。

本記事では、v1.25 と v1.26 で同一のNULLバイト挿入ペイロードを送信し、挙動にどのような差が出るかを実機で確認しました。

ReactJSRCE_BODY とは

ReactJSRCE_BODY は、Next.js の React Server Components (RSC) における RCE 脆弱性に対応するルールです。この脆弱性は AWS Security Bulletin AWS-2025-030 で公開されています。

https://aws.amazon.com/security/security-bulletins/AWS-2025-030/

このルールの発火条件は非公開ですが、AWS-2025-030 で公開されたカスタムルールの参考実装から推測すると、おおむね以下のような要素が関係すると考えられます。

  • HTTP メソッドが POST
  • Next-Action または rsc-action-id ヘッダーが存在する
  • ボディに resolved_model 相当のパターンを含む
  • ボディに $@ マーカーを含む

本記事ではこのうちボディ側の resolved_model$@ をNULLバイトで分断し、検出の有無がバージョン間でどう変わるかを確認します。

検証内容

検証環境

バックエンドを持たない最小構成とし、ALB の固定応答(200 "OK")を Web ACL の背後に配置しました。WAF の判定結果は CloudWatch Logs に出力し、actionterminatingRuleId を確認します。

Client ──▶ ALB (固定応答 200 "OK")

              │ 関連付け
           Web ACL (AWS WAF)


        CloudWatch Logs
項目 内容
VPC パブリックサブネット×2(ap-northeast-1a / ap-northeast-1c)
ALB 固定応答 200 "OK"(バックエンドなし)
Web ACL AWSManagedRulesKnownBadInputsRuleSet を関連付け
ログ出力 CloudWatch Logs
リージョン ap-northeast-1

マネージドルールのバージョンを v1.25 と v1.26 で切り替え、それ以外の条件は固定して比較しました。

テストパターン

4つのパターンを送信しました。Test 3・Test 4 は、ReactJSRCE_BODY の検出対象となる文字列の途中にNULLバイトを挿入し、シグネチャの分断を狙ったものです。

# パターン名 NULLバイト挿入位置 v1.25 期待結果 v1.26 期待結果
1 正常 GET リクエスト(ベースライン) なし Allow Allow
2 ReactJSRCE ペイロード(NULLバイトなし) なし Block Block
3 NULLバイト挿入(resolved_model 分断) resolved_model の間 Allow Block
4 NULLバイト挿入($@ マーカー分断) $@ の間 Allow Block

Test 3 と Test 4 ではNULLバイトを含むバイナリペイロードを送信します。以下は各ペイロードの hex ダンプです。

Test 3 — resolved_model 分断(44 bytes):

5b 7b 22 73 74 61 74 75 73 22 3a 22 72 65 73 6f  [{"status":"reso
6c 76 65 64 5f 00 6d 6f 64 65 6c 22 2c 22 76 61  lved_.model","va
6c 75 65 22 3a 22 24 40 31 22 7d 5d              lue":"$@1"}]

offset 0x15 にNULLバイトが挿入されています。

Test 4 — $@ マーカー分断(44 bytes):

5b 7b 22 73 74 61 74 75 73 22 3a 22 72 65 73 6f  [{"status":"reso
6c 76 65 64 5f 6d 6f 64 65 6c 22 2c 22 76 61 6c  lved_model","val
75 65 22 3a 22 24 00 40 31 22 7d 5d              ue":"$.@1"}]

offset 0x26 にNULLバイトが挿入されています。

Test 1(ベースライン):

curl -s -o /dev/null -w "%{http_code}" "http://<ALB_DNS>/"

Test 2(ReactJSRCE ペイロード):

curl -s -o /dev/null -w "%{http_code}" -X POST "http://<ALB_DNS>/" \
  -H "Content-Type: text/plain" \
  -H "Next-Action: test-action-id" \
  -d '[{"status":"resolved_model","value":"$@1"}]'

Test 3(resolved_model 分断)— resolved_model の間にNULLバイトを挿入:

printf '[{"status":"resolved_\x00model","value":"$@1"}]' > /tmp/test3.bin
curl -s -o /dev/null -w "%{http_code}" -X POST "http://<ALB_DNS>/" \
  -H "Content-Type: text/plain" \
  -H "Next-Action: test-action-id" \
  --data-binary @/tmp/test3.bin

Test 4($@ 分断)— $@ の間にNULLバイトを挿入:

printf '[{"status":"resolved_model","value":"$\x00@1"}]' > /tmp/test4.bin
curl -s -o /dev/null -w "%{http_code}" -X POST "http://<ALB_DNS>/" \
  -H "Content-Type: text/plain" \
  -H "Next-Action: test-action-id" \
  --data-binary @/tmp/test4.bin

v1.25 での検証結果

マネージドルールを v1.25 に固定し、Test 1〜4 を送信した結果です。

# テストパターン ステータスコード 判定
1 正常 GET リクエスト 200 Allow
2 ReactJSRCE ペイロード(NULLバイトなし) 403 Block
3 NULLバイト挿入(resolved_model 分断) 200 Allow
4 NULLバイト挿入($@ マーカー分断) 200 Allow

NULLバイトを挿入した Test 3・Test 4 はいずれも Allow となり、ルールを回避できました。

Test 2 では ReactJSRCE_BODY が終端ルールとして Block を判定しました。

{
  "action": "BLOCK",
  "terminatingRuleId": "AWS-KnownBadInputs",
  "terminatingRuleType": "MANAGED_RULE_GROUP",
  "ruleGroupList": [
    {
      "ruleGroupId": "AWS#AWSManagedRulesKnownBadInputsRuleSet#Version_1.25",
      "terminatingRule": {
        "ruleId": "ReactJSRCE_BODY",
        "action": "BLOCK"
      }
    }
  ],
  "labels": [
    {"name": "awswaf:managed:aws:known-bad-inputs:ReactJSRCE_Body"}
  ]
}
v1.25 — Test 2: BLOCK ログ全文
{
  "timestamp": 1784387919150,
  "action": "BLOCK",
  "terminatingRuleId": "AWS-KnownBadInputs",
  "terminatingRuleType": "MANAGED_RULE_GROUP",
  "ruleGroupList": [
    {
      "ruleGroupId": "AWS#AWSManagedRulesKnownBadInputsRuleSet#Version_1.25",
      "terminatingRule": {
        "ruleId": "ReactJSRCE_BODY",
        "action": "BLOCK"
      }
    }
  ],
  "labels": [
    {"name": "awswaf:managed:aws:known-bad-inputs:ReactJSRCE_Body"}
  ],
  "httpRequest": {
    "httpMethod": "POST",
    "headers": [
      {"name": "Content-Type", "value": "text/plain"},
      {"name": "Next-Action", "value": "test-action-id"},
      {"name": "Content-Length", "value": "43"}
    ]
  },
  "requestBodySize": 43,
  "requestBodySizeInspectedByWAF": 43
}

一方、Test 3・Test 4 はマネージドルールにマッチせず、Web ACL のデフォルトアクション(Allow)で通過しました。

{
  "action": "ALLOW",
  "terminatingRuleId": "Default_Action",
  "terminatingRuleType": "REGULAR",
  "ruleGroupList": [
    {
      "ruleGroupId": "AWS#AWSManagedRulesKnownBadInputsRuleSet#Version_1.25",
      "terminatingRule": null,
      "nonTerminatingMatchingRules": []
    }
  ],
  "labels": []
}
v1.25 — Test 3: ALLOW ログ全文
{
  "timestamp": 1784387919177,
  "action": "ALLOW",
  "terminatingRuleId": "Default_Action",
  "terminatingRuleType": "REGULAR",
  "ruleGroupList": [
    {
      "ruleGroupId": "AWS#AWSManagedRulesKnownBadInputsRuleSet#Version_1.25",
      "terminatingRule": null,
      "nonTerminatingMatchingRules": []
    }
  ],
  "labels": [],
  "httpRequest": {
    "httpMethod": "POST",
    "headers": [
      {"name": "Content-Type", "value": "text/plain"},
      {"name": "Next-Action", "value": "test-action-id"},
      {"name": "Content-Length", "value": "44"}
    ]
  },
  "requestBodySize": 44,
  "requestBodySizeInspectedByWAF": 44
}

v1.26 での検証結果

v1.26 に切り替え、同じリクエストを送信した結果です。

# テストパターン ステータスコード 判定
1 正常 GET リクエスト 200 Allow
2 ReactJSRCE ペイロード(NULLバイトなし) 403 Block
3 NULLバイト挿入(resolved_model 分断) 403 Block
4 NULLバイト挿入($@ マーカー分断) 403 Block

v1.25 では Allow だった Test 3・Test 4 が、v1.26 では Block に変わりました。マッチしたルールを示す主要なログ項目は v1.25 の Block 時と同様で、ruleGroupId には Version_1.26terminatingRule.ruleId には ReactJSRCE_BODY が記録されました。

{
  "action": "BLOCK",
  "terminatingRuleId": "AWS-KnownBadInputs",
  "ruleGroupList": [
    {
      "ruleGroupId": "AWS#AWSManagedRulesKnownBadInputsRuleSet#Version_1.26",
      "terminatingRule": {
        "ruleId": "ReactJSRCE_BODY",
        "action": "BLOCK"
      }
    }
  ],
  "labels": [
    {"name": "awswaf:managed:aws:known-bad-inputs:ReactJSRCE_Body"}
  ]
}
v1.26 — Test 3: BLOCK ログ全文
{
  "timestamp": 1784387979115,
  "action": "BLOCK",
  "terminatingRuleId": "AWS-KnownBadInputs",
  "terminatingRuleType": "MANAGED_RULE_GROUP",
  "ruleGroupList": [
    {
      "ruleGroupId": "AWS#AWSManagedRulesKnownBadInputsRuleSet#Version_1.26",
      "terminatingRule": {
        "ruleId": "ReactJSRCE_BODY",
        "action": "BLOCK"
      }
    }
  ],
  "labels": [
    {"name": "awswaf:managed:aws:known-bad-inputs:ReactJSRCE_Body"}
  ],
  "httpRequest": {
    "httpMethod": "POST",
    "headers": [
      {"name": "Content-Type", "value": "text/plain"},
      {"name": "Next-Action", "value": "test-action-id"},
      {"name": "Content-Length", "value": "44"}
    ]
  },
  "requestBodySize": 44,
  "requestBodySizeInspectedByWAF": 44
}

比較表

# テストパターン v1.25 結果 v1.26 結果 備考
1 正常 GET リクエスト(ベースライン) Allow (200) Allow (200) ベースライン
2 ReactJSRCE ペイロード(NULLバイトなし) Block (403) Block (403) 両バージョンで検出
3 NULLバイト挿入(resolved_model 分断) Allow (200) Block (403) v1.26 で REMOVE_NULLS により防止
4 NULLバイト挿入($@ マーカー分断) Allow (200) Block (403) v1.26 で REMOVE_NULLS により防止

まとめ

AWSManagedRulesKnownBadInputsRuleSet v1.26 では、ReactJSRCE_BODYREMOVE_NULLS テキスト変換が追加されました。

本検証では、v1.25 では Allow となった NULL バイト挿入ペイロードが、v1.26 では ReactJSRCE_BODY により Block されることを確認しました。少なくとも今回試した resolved_model および $@ を NULL バイトで分断するパターンについては、v1.26 で検出されるようになったことが確認できました。

マネージドルールのバージョンを固定しており、ReactJSRCE_BODY による検出を期待している場合は、v1.26 への更新を検討するとよさそうです。

なお、検証時点(2026年7月18日)では v1.26 はまだデフォルトバージョンに昇格しておらず、CurrentDefaultVersionVersion_1.25 でした。バージョンを固定せずデフォルト適用にしている場合も、検証時点では v1.25 が適用される状態だったため、実際にどのバージョンが有効になっているかを確認しておくとよいでしょう。

参考リンク

この記事をシェアする

AWSのお困り事はクラスメソッドへ

関連記事