AWS WAF KnownBadInputs v1.26 の REMOVE_NULLS で NULL バイト挿入による検出回避を防げるか確認してみた
はじめに
AWS WAF のマネージドルール AWSManagedRulesKnownBadInputsRuleSet に、2026年7月15日付で v1.26 がリリースされました。このバージョンでは ReactJSRCE_BODY ルールに REMOVE_NULLS テキスト変換が追加されています。
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 で公開されています。
このルールの発火条件は非公開ですが、AWS-2025-030 で公開されたカスタムルールの参考実装から推測すると、おおむね以下のような要素が関係すると考えられます。
- HTTP メソッドが
POST Next-Actionまたはrsc-action-idヘッダーが存在する- ボディに
resolved_model相当のパターンを含む - ボディに
$@マーカーを含む
本記事ではこのうちボディ側の resolved_model と $@ をNULLバイトで分断し、検出の有無がバージョン間でどう変わるかを確認します。
検証内容
検証環境
バックエンドを持たない最小構成とし、ALB の固定応答(200 "OK")を Web ACL の背後に配置しました。WAF の判定結果は CloudWatch Logs に出力し、action や terminatingRuleId を確認します。
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.26、terminatingRule.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_BODY に REMOVE_NULLS テキスト変換が追加されました。
本検証では、v1.25 では Allow となった NULL バイト挿入ペイロードが、v1.26 では ReactJSRCE_BODY により Block されることを確認しました。少なくとも今回試した resolved_model および $@ を NULL バイトで分断するパターンについては、v1.26 で検出されるようになったことが確認できました。
マネージドルールのバージョンを固定しており、ReactJSRCE_BODY による検出を期待している場合は、v1.26 への更新を検討するとよさそうです。
なお、検証時点(2026年7月18日)では v1.26 はまだデフォルトバージョンに昇格しておらず、CurrentDefaultVersion は Version_1.25 でした。バージョンを固定せずデフォルト適用にしている場合も、検証時点では v1.25 が適用される状態だったため、実際にどのバージョンが有効になっているかを確認しておくとよいでしょう。







