AWS セキュリティ速報(2026-143-AWS)で紹介された PHP2Shell 緩和の AWS WAF ルールを試してみた

AWS セキュリティ速報(2026-143-AWS)で紹介された PHP2Shell 緩和の AWS WAF ルールを試してみた

PHP2Shell(CVE-2026-109618、CVE-2026-109619)の緩和ルール(SizeRestrictions_BODY と PHP2Shell)を AWS WAF に入れ、curl で止まるリクエストと通るリクエストを打ち分け、正当なリクエストが止まる副作用まで確かめました。
2026.10.22

はじめに

PHP の 2 件の脆弱性(通称 PHP2Shell。CVE-2026-109618、CVE-2026-109619)に対し、AWS セキュリティ速報 2026-143-AWS では、影響を受ける PHP のバージョンと修正版の情報のほか、すぐに更新できない場合の暫定対処として AWS WAF のルール 2 つが案内されています。

https://aws.amazon.com/security/security-bulletins/2026-143-aws/

この 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 のテンプレートで再現できます。

https://github.com/cm-suzuki-ryo/aws-waf-php2shell-post-guard

テンプレートは、上の 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 の背後へ送られます。

https://docs.aws.amazon.com/waf/latest/developerguide/waf-oversize-request-components.html

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 の記事には、この脆弱性への推奨される対応がまとまっているので、あわせて参照いただくことをおすすめします。

https://aws.amazon.com/jp/blogs/security/amazon-threat-intelligence-identifies-php2shell-exploitation-prior-to-public-disclosure-in-madpot/

この記事をシェアする

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

関連記事