
AWS セキュリティ速報(2026-143-AWS)で紹介された PHP2Shell 緩和の AWS WAF ルールを試してみた
はじめに
PHP の 2 件の脆弱性(通称 PHP2Shell。CVE-2026-109618、CVE-2026-109619)に対し、AWS セキュリティ速報 2026-143-AWS では、影響を受ける PHP のバージョンと修正版の情報のほか、すぐに更新できない場合の暫定対処として AWS WAF のルール 2 つが案内されています。
この 2 つのルール(SizeRestrictions_BODY と PHP2Shell)を検証用の ALB に入れ、curl で次の 3 点を確かめた結果を紹介します。
- 止まるリクエストと通るリクエストの違い
- PHP_SESSION_UPLOAD_PROGRESS がボディの先頭 8 KB より後ろにあるときの結果
- 攻撃ではないリクエストが止まる条件
AWS セキュリティ速報が示す 2 つのルール
AWS セキュリティ速報は、AWS WAF を使っている場合の暫定対処として、CVE ごとに 1 つずつルールを挙げています。CVE-2026-109619 にはカスタムルール PHP2Shell、CVE-2026-109618 には AWS マネージドルールのコアルールセット(CRS)に含まれる SizeRestrictions_BODY です。
PHP2Shell
速報に載っているルール定義です。
{
"Name": "PHP2Shell",
"Priority": 0,
"Statement": {
"AndStatement": {
"Statements": [
{
"ByteMatchStatement": {
"SearchString": "POST",
"FieldToMatch": { "Method": {} },
"TextTransformations": [{ "Priority": 0, "Type": "NONE" }],
"PositionalConstraint": "EXACTLY"
}
},
{
"OrStatement": {
"Statements": [
{
"ByteMatchStatement": {
"SearchString": "PHPSESSID",
"FieldToMatch": {
"Cookies": {
"MatchPattern": { "All": {} },
"MatchScope": "KEY",
"OversizeHandling": "CONTINUE"
}
},
"TextTransformations": [{ "Priority": 0, "Type": "NONE" }],
"PositionalConstraint": "EXACTLY"
}
},
{
"ByteMatchStatement": {
"SearchString": "PHPSESSID",
"FieldToMatch": { "Body": { "OversizeHandling": "CONTINUE" } },
"TextTransformations": [{ "Priority": 0, "Type": "NONE" }],
"PositionalConstraint": "CONTAINS"
}
}
]
}
},
{
"ByteMatchStatement": {
"SearchString": "PHP_SESSION_UPLOAD_PROGRESS",
"FieldToMatch": { "Body": { "OversizeHandling": "CONTINUE" } },
"TextTransformations": [{ "Priority": 0, "Type": "NONE" }],
"PositionalConstraint": "CONTAINS"
}
}
]
}
},
"Action": { "Block": {} },
"VisibilityConfig": {
"SampledRequestsEnabled": true,
"CloudWatchMetricsEnabled": true,
"MetricName": "PHP2Shell"
}
}
次の 3 つをすべて満たすリクエストを Block します。
- メソッドが POST
- PHPSESSID が Cookie のキーに完全一致する、またはボディに含まれる
- ボディに PHP_SESSION_UPLOAD_PROGRESS が含まれる
定義から、次のことが読み取れます。
- 文字列は大文字小文字を区別して比較します(TextTransformations が NONE)
- Content-Type の条件はありません
- ボディの OversizeHandling は CONTINUE です。WAF が検査できる範囲(ALB では先頭 8 KB)だけを見て判定します
SizeRestrictions_BODY
速報は、CRS の SizeRestrictions_BODY を使うようにと書いています。CRS はマネージドルールグループなので、WebACL には次の形で入れます。名前と優先度は検証で使ったテンプレートの値です。
{
"Name": "aws-crs-size-restrictions-body",
"Priority": 5,
"Statement": {
"ManagedRuleGroupStatement": {
"VendorName": "AWS",
"Name": "AWSManagedRulesCommonRuleSet",
"RuleActionOverrides": [
{ "Name": "NoUserAgent_HEADER", "ActionToUse": { "Count": {} } },
{ "Name": "UserAgent_BadBots_HEADER", "ActionToUse": { "Count": {} } },
{ "Name": "SizeRestrictions_QUERYSTRING", "ActionToUse": { "Count": {} } },
{ "Name": "SizeRestrictions_Cookie_HEADER", "ActionToUse": { "Count": {} } },
{ "Name": "SizeRestrictions_URIPATH", "ActionToUse": { "Count": {} } },
{ "Name": "EC2MetaDataSSRF_BODY", "ActionToUse": { "Count": {} } },
{ "Name": "EC2MetaDataSSRF_COOKIE", "ActionToUse": { "Count": {} } },
{ "Name": "EC2MetaDataSSRF_URIPATH", "ActionToUse": { "Count": {} } },
{ "Name": "EC2MetaDataSSRF_QUERYARGUMENTS", "ActionToUse": { "Count": {} } },
{ "Name": "GenericLFI_QUERYARGUMENTS", "ActionToUse": { "Count": {} } },
{ "Name": "GenericLFI_URIPATH", "ActionToUse": { "Count": {} } },
{ "Name": "GenericLFI_BODY", "ActionToUse": { "Count": {} } },
{ "Name": "RestrictedExtensions_URIPATH", "ActionToUse": { "Count": {} } },
{ "Name": "RestrictedExtensions_QUERYARGUMENTS", "ActionToUse": { "Count": {} } },
{ "Name": "GenericRFI_QUERYARGUMENTS", "ActionToUse": { "Count": {} } },
{ "Name": "GenericRFI_BODY", "ActionToUse": { "Count": {} } },
{ "Name": "GenericRFI_URIPATH", "ActionToUse": { "Count": {} } },
{ "Name": "CrossSiteScripting_COOKIE", "ActionToUse": { "Count": {} } },
{ "Name": "CrossSiteScripting_QUERYARGUMENTS", "ActionToUse": { "Count": {} } },
{ "Name": "CrossSiteScripting_BODY", "ActionToUse": { "Count": {} } },
{ "Name": "CrossSiteScripting_URIPATH", "ActionToUse": { "Count": {} } }
]
}
},
"OverrideAction": { "None": {} },
"VisibilityConfig": {
"SampledRequestsEnabled": true,
"CloudWatchMetricsEnabled": true,
"MetricName": "aws-crs-size-restrictions-body"
}
}
SizeRestrictions_BODY は、ボディが 8,192 バイト(8 KB)を超えるリクエストを Block します。見るのはボディのサイズだけです。
CRS には SizeRestrictions_BODY 以外のルールも入っています。検証では SizeRestrictions_BODY だけの結果を見るため、他のルールのアクションを Count に上書きしました(上の RuleActionOverrides)。Count は、一致したことを WAF ログとメトリクスに記録して、リクエストは通す設定です。
検証環境
この検証環境は CloudFormation テンプレートにしてあり、GitHub のテンプレートで再現できます。
テンプレートは、上の 2 つのルールを含む WebACL を新しく作り、LoadBalancerArn で指定した ALB に関連付けます。本文に出てくる主なパラメータです。
- BlockPhpUploadProgress=true(既定値)で PHP2Shell が入ります。名前は block-php-upload-progress、優先度は 4 で、それ以外は AWS セキュリティ速報の定義と同じです
- BlockLargeBody=true で SizeRestrictions_BODY が入ります
- RuleAction=Count にすると、WebACL のルールがすべて Count になります。Block へ切り替える前に影響を見るときに使います
デプロイのコマンド(RuleAction=Count で入れる例)
README のデプロイ例に、BlockLargeBody=true の 1 行を加えたものです。検証用の ALB と、新しい WebACL を作る構成を前提にしています。ALB に別の WebACL が付いている場合、関連付けはこの WebACL に置き換わります。
aws cloudformation deploy \
--stack-name waf-post-guard \
--template-file template.yaml \
--region ap-northeast-1 \
--parameter-overrides \
AllowedCountries=JP \
RuleAction=Count \
BlockLargeBody=true \
LoadBalancerArn=arn:aws:elasticloadbalancing:REGION:ACCOUNT_ID:loadbalancer/app/NAME/ID
ALB は東京リージョンにあります。HTTPS リスナーが自己署名証明書なので、curl には -k を付けています。以下の結果は、特に書かない限り RuleAction=Block、BlockPhpUploadProgress=true、BlockLargeBody=true の構成です。各リクエストは 1 回ずつ送りました。
PHP2Shell の条件を curl で確かめる
PHP2Shell の 3 条件を満たす POST と、1 つだけ外した POST を curl で送り、レスポンスコードを見ました。
# Cookie の PHPSESSID + ボディの PHP_SESSION_UPLOAD_PROGRESS → 403
curl -sk -o /dev/null -w '%{http_code}\n' -X POST "https://<ALB の DNS 名>/" \
-b "PHPSESSID=dummy" \
-F "PHP_SESSION_UPLOAD_PROGRESS=dummy" \
-F "a=b"
# 両方ともボディのフィールド → 403
curl -sk -o /dev/null -w '%{http_code}\n' -X POST "https://<ALB の DNS 名>/" \
-F "PHPSESSID=dummy" \
-F "PHP_SESSION_UPLOAD_PROGRESS=dummy" \
-F "a=b"
| リクエスト | 結果 |
|---|---|
| POST、Cookie の PHPSESSID、ボディに PHP_SESSION_UPLOAD_PROGRESS | 403 |
| POST、ボディに PHPSESSID と PHP_SESSION_UPLOAD_PROGRESS | 403 |
| POST、Cookie が a=1; PHPSESSID=x、ボディに PHP_SESSION_UPLOAD_PROGRESS | 403 |
| POST、ボディに PHP_SESSION_UPLOAD_PROGRESS だけ | 200 |
| POST、Cookie の PHPSESSID だけ | 200 |
| PUT、ボディに PHPSESSID と PHP_SESSION_UPLOAD_PROGRESS | 200 |
3 条件のうち 1 つでも欠けると、200 で通ります。-F の代わりに --data-binary で multipart のボディを手で組んだ POST も、1 つ目のコマンドと同じ 403 になりました。
PHP2Shell が検査するのはボディの先頭 8 KB まで
許可されたリクエストは、検査していない部分も含めて全体がそのまま ALB の背後へ送られます。
SizeRestrictions_BODY を外した構成(BlockLargeBody=false、RuleAction=Block)で、multipart の POST を送りました。PHP_SESSION_UPLOAD_PROGRESS の位置だけを変えています。ボディは、大きな pad フィールドの後ろに PHPSESSID と PHP_SESSION_UPLOAD_PROGRESS(値はどちらも dummy)を置いた形です。表の値は、ボディ先頭を 0 としたときの PHP_SESSION_UPLOAD_PROGRESS の終端位置です。
| PHP_SESSION_UPLOAD_PROGRESS の終端位置(バイト) | PHP2Shell だけの結果 |
|---|---|
| 8,183 | 403 |
| 8,191 | 403 |
| 8,193 | 200 |
| 8,237 | 200 |
PHP_SESSION_UPLOAD_PROGRESS が先頭 8,192 バイトの内側に収まる POST は 403、収まらない POST は 200 でした。2 つの文字列をそれより後ろに置いた POST は PHP2Shell を通りますが、ボディが 8,192 バイトを超えているので、SizeRestrictions_BODY を入れていればそちらが止めます。
SizeRestrictions_BODY も Block にした構成(BlockLargeBody=true)で、同じ形の POST をボディ全体のサイズを変えて送ると、止めたルールは 2 つの文字列の位置で分かれました。2 つの文字列が先頭 8,192 バイトに収まる 8,196 バイトと 8,206 バイトの POST は、優先度 4 で先に評価される PHP2Shell が止めました。PHP_SESSION_UPLOAD_PROGRESS の終端が 8,192 バイトより後ろになる 8,256〜9,206 バイトの POST は、SizeRestrictions_BODY が止めました。
公開されている PoC は後者の形です。ソースを読むと(実行はしていません)、multipart のボディは 1,000 個を超えるダミーフィールドの後ろに PHPSESSID と PHP_SESSION_UPLOAD_PROGRESS を置く構造です。その後ろにファイルと巨大なフィールドが続きます。この順序を無害なダミーの値で再現した 1,056,388 バイトの POST を送ると、403 で止めたのは SizeRestrictions_BODY でした。2 つの文字列はボディ先頭から約 56 KB の位置にあります。
攻撃ではないリクエストが止まる条件
8 KB を超える正当なリクエストも SizeRestrictions_BODY で止まる
Count 構成(RuleAction=Count)で次の 5 種類を送ると、レスポンスはすべて 200 でした。WAF ログには、5 件とも SizeRestrictions_BODY に一致したと記録されました(nonTerminatingMatchingRules)。
| リクエスト | ボディのサイズ |
|---|---|
| JSON の POST | 20,013 バイト |
| 画像アップロード(multipart の POST) | 102,710 バイト |
| PUT | 15,000 バイト |
| PATCH | 15,000 バイト |
| JSON ボディ付きの GET | 20,013 バイト |
RuleAction=Block にすると、これらは 403 で止まります。
JSON や text/plain の本文でも PHP2Shell で止まる
次の 2 つの POST は、どちらも 403 になりました。
- Content-Type が application/json、ボディが
{"PHPSESSID":"dummy","PHP_SESSION_UPLOAD_PROGRESS":"dummy"} - Content-Type が text/plain、ボディが
PHPSESSID=dummy PHP_SESSION_UPLOAD_PROGRESS=dummy
PHP の解説を投稿するフォームのように、投稿の本文にこの 2 つの文字列を書く POST も止まります。
AWS セキュリティ速報のルールに「Content-Type ヘッダに multipart/form-data を含む」条件を AND で足したルールも試しました。Count で WebACL に追加して同じ POST を送ると、このルールは multipart の POST に一致し、上の JSON と text/plain の POST には一致しませんでした。Content-Type を付けずに --data-binary で送った POST(curl が application/x-www-form-urlencoded を付けます)にも一致しませんでした。速報の定義より止める範囲は狭くなります。
テンプレートの block-multipart-post を有効にした場合
テンプレートには block-multipart-post というルールも入っています(BlockMultipartPost=true で有効。既定は無効)。Content-Type に multipart/form-data を含む POST を、サイズや中身を問わず止めるルールです。AWS Security Blog の記事(まとめにリンクがあります)が暫定対処の候補に挙げる「multipart のファイルアップロードを止める」を WAF で行うルールです。正当なファイルアップロードや multipart のフォーム送信も止まります。
このルールを有効にしたとき、AWS セキュリティ速報の 2 つのルールとどう重なるかを見ました。BlockMultipartPost=true、RuleAction=Count で 3 種類の multipart POST を送り、WAF ログに記録された一致を表にしています。
| リクエスト | block-multipart-post | block-php-upload-progress(PHP2Shell) | aws-crs-size-restrictions-body |
|---|---|---|---|
| multipart POST、Cookie の PHPSESSID、ボディに PHP_SESSION_UPLOAD_PROGRESS | 一致 | 一致 | — |
| 通常の multipart POST | 一致 | — | — |
| 上の PoC の順序を再現した multipart POST(1,056,388 バイト) | 一致 | — | 一致 |
block-multipart-post は速報の 2 つのルールより先に評価されるので、有効にすると multipart の POST はここで止まり、後ろのルールまで評価が進みません。無効にした場合は、2 つの文字列を含まない通常の multipart POST は通ります。8 KB を超える multipart POST(102,710 バイトの画像アップロードなど)は、SizeRestrictions_BODY が止めます。
まとめ
AWS セキュリティ速報 2026-143-AWS が PHP の 2 件の脆弱性への暫定対処として案内する 2 つの AWS WAF ルール、SizeRestrictions_BODY と PHP2Shell を ALB で検証しました。
AWS WAF による緩和としてこの 2 つのルールの採用を検討している場合は、今回の評価結果を参考にお試しいただければと思います。
ただし、WAF のルールは暫定対処で、根本的な解決は修正版(8.5.12、8.4.27、8.3.36、8.2.35)への PHP の更新である点にご留意ください。
AWS Security Blog の記事には、この脆弱性への推奨される対応がまとまっているので、あわせて参照いただくことをおすすめします。










