
I tried out the AWS WAF rules introduced in the AWS Security Bulletin (2026-143-AWS) and the AWS Security Blog to mitigate PHP2Shell
This page has been translated by machine translation. View original
Introduction
For two PHP vulnerabilities (commonly known as PHP2Shell: CVE-2026-109618 and CVE-2026-109619), AWS Security Bulletin 2026-143-AWS provides information on affected PHP versions and fixed versions, as well as AWS WAF rules as an interim mitigation for cases where immediate updates are not possible.
The AWS Security Blog also introduces mitigation measures using AWS WAF.
This article presents the results of applying the two rules introduced as PHP2Shell mitigations (PHP2Shell and Block-Oversize-Body) to a test ALB and verifying the following three points using curl.
- The difference between requests that are blocked and those that pass through
- The result when PHP_SESSION_UPLOAD_PROGRESS is located after the first 8 KB of the body
- The conditions under which non-attack requests are blocked
The Two Rules Provided by AWS
The AWS Security Bulletin lists one rule per CVE as an interim mitigation for those using AWS WAF. The custom rule PHP2Shell is for CVE-2026-109619, and SizeRestrictions_BODY, included in the AWS Managed Rules Core Rule Set (CRS), is for CVE-2026-109618.
PHP2Shell
Below is the rule definition from the bulletin.
{
"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"
}
}
This blocks requests that satisfy all three of the following conditions.
- Method is POST
- A cookie key exactly matches PHPSESSID, or the body contains PHPSESSID
- The body contains PHP_SESSION_UPLOAD_PROGRESS
The following can be inferred from the definition.
- Strings are compared case-sensitively (TextTransformations is NONE)
- There is no condition on Content-Type
- The body's OversizeHandling is CONTINUE. The determination is made based only on the range WAF can inspect (the first 8 KB for ALB)
Block-Oversize-Body
Block-Oversize-Body is a rule that blocks requests where the body exceeds 8,192 bytes (8 KB), and it only looks at body size. It is listed as a custom rule in the AWS Security Blog.
The AWS Security Bulletin introduces SizeRestrictions_BODY from the CRS, which performs the same function.
The custom rule (Block-Oversize-Body) is superior in terms of WCU savings (700 for CRS), customizability, and ease of log tracking.
The AWS Security Blog also states that for those already using CRS (AWSManagedRulesCommonRuleSet), SizeRestrictions_BODY performs the same function.
This custom rule is included in the verification template. The name in the Blog is Block-Oversize-Body, and the name in the template is block-oversize-body. The following JSON is the template rule with the 403 custom response (the configuration that returns the rule name) removed. The name and priority are the same values as in the template. For the metric name, the template prepends the WebACL name.
{
"Name": "block-oversize-body",
"Priority": 5,
"Statement": {
"SizeConstraintStatement": {
"FieldToMatch": { "Body": { "OversizeHandling": "MATCH" } },
"ComparisonOperator": "GT",
"Size": 8192,
"TextTransformations": [{ "Priority": 0, "Type": "NONE" }]
}
},
"Action": { "Block": {} },
"VisibilityConfig": {
"SampledRequestsEnabled": true,
"CloudWatchMetricsEnabled": true,
"MetricName": "block-oversize-body"
}
}
Unlike PHP2Shell, the body's OversizeHandling is MATCH. The AWS Security Blog explains this is specified to also target bodies that exceed the inspection limit.
In the previous version of the template (commit cd6bafd), SizeRestrictions_BODY from the CRS was placed at priority 5. Even after switching to block-oversize-body in the current version, the matches for the same shaped requests and the position where the rule blocking at the 8 KB boundary switches were the same as in the previous version.
WAF log recording was split into two locations in the previous version.
- Previous version (CRS SizeRestrictions_BODY)
- The rule name aws-crs-size-restrictions-body attached to the WebACL appeared in nonTerminatingMatchingRules (Count configuration) or terminatingRuleId (Block configuration)
- The rule ID SizeRestrictions_BODY within the managed rule group appeared in ruleGroupList. In Count configuration, even though the request passed with a 200, the terminatingRule action in ruleGroupList was recorded as BLOCK
- block-oversize-body
- The rule name appears in a single location: nonTerminatingMatchingRules (Count configuration) or terminatingRuleId (Block configuration). The action matches the request outcome: COUNT for Count configuration and BLOCK for Block configuration. For Block, responseCodeSent also records 403
Verification Environment
This verification environment has been made into a CloudFormation template and can be reproduced from the GitHub repository (commit febe2c7).
The template creates a new WebACL containing the two rules above and associates it with the ALB specified by LoadBalancerArn. The main parameters mentioned in this article are as follows.
- BlockPhpUploadProgress=true (default) includes PHP2Shell. The name is block-php-upload-progress, priority is 4. The action follows RuleAction, returning 403 with the rule name on Block. Otherwise it is identical to the AWS Security Bulletin definition
- BlockLargeBody=true (default is false) includes block-oversize-body
- Setting RuleAction=Count makes all rules in the WebACL Count. Count records matches in WAF logs and metrics while allowing the request through. Use this to assess impact before switching to Block
Deployment command (example with RuleAction=Count)
This is the README deployment example with one line added for BlockLargeBody=true. It assumes a test ALB. If the ALB already has a WebACL attached, the association will be replaced by this WebACL. AllowedCountries=JP is the setting for a country-based rule that blocks POST requests from outside Japan. The template also includes a rate limiting rule. Please verify the impact of these rules before switching RuleAction to Block.
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
The ALB is in the Tokyo region. Since a self-signed certificate is used for the HTTPS listener, -k is appended to curl. Unless otherwise noted, the results below use the configuration RuleAction=Block, BlockPhpUploadProgress=true, BlockLargeBody=true (the deployment example uses RuleAction=Count). Which rule blocked the request was confirmed using the x-waf-blocked-by header in the 403 response (which contains the rule name) and the WAF logs. Each request was sent once.
Verifying PHP2Shell Conditions with curl
I sent a POST satisfying all three PHP2Shell conditions and POSTs with each condition individually removed via curl, and observed the response codes.
# Cookie PHPSESSID + body PHP_SESSION_UPLOAD_PROGRESS
curl -sk -o /dev/null -w '%{http_code}\n' -X POST "https://<ALB DNS name>/" \
-b "PHPSESSID=dummy" \
-F "PHP_SESSION_UPLOAD_PROGRESS=dummy" \
-F "a=b"
# Both as body fields
curl -sk -o /dev/null -w '%{http_code}\n' -X POST "https://<ALB DNS name>/" \
-F "PHPSESSID=dummy" \
-F "PHP_SESSION_UPLOAD_PROGRESS=dummy" \
-F "a=b"
| Request | Result |
|---|---|
| POST, Cookie PHPSESSID, body contains PHP_SESSION_UPLOAD_PROGRESS | 403 |
| POST, body contains both PHPSESSID and PHP_SESSION_UPLOAD_PROGRESS | 403 |
| POST, multiple cookies (a=1; PHPSESSID=x), body contains PHP_SESSION_UPLOAD_PROGRESS | 403 |
| POST, body contains only PHP_SESSION_UPLOAD_PROGRESS | 200 |
| POST, only Cookie PHPSESSID | 200 |
| PUT, body contains PHPSESSID and PHP_SESSION_UPLOAD_PROGRESS | 200 |
A POST with a manually constructed multipart body using --data-binary instead of -F also resulted in 403, the same as the first command.
PHP2Shell Inspects Only the First 8 KB of the Body
Allowed requests, including uninspected portions, are passed through in their entirety to behind the ALB.
In a configuration without block-oversize-body (BlockLargeBody=false, RuleAction=Block), I sent multipart POST requests. The body was structured with a large pad field followed by PHPSESSID and PHP_SESSION_UPLOAD_PROGRESS (both with value dummy), varying only the position of PHP_SESSION_UPLOAD_PROGRESS. The values in the table represent the end position of PHP_SESSION_UPLOAD_PROGRESS, with the beginning of the body as position 0.
| End position of PHP_SESSION_UPLOAD_PROGRESS (bytes) | Result with PHP2Shell only |
|---|---|
| 8,183 | 403 |
| 8,191 | 403 |
| 8,193 | 200 |
| 8,237 | 200 |
POSTs where PHP_SESSION_UPLOAD_PROGRESS fits within the first 8,192 bytes resulted in 403; POSTs where it did not fit resulted in 200.
When also setting block-oversize-body to Block (BlockLargeBody=true) and sending the same shaped POSTs with varying total body sizes, the blocking rule differed based on the position of the two strings.
| Total body size (bytes) | Position of the two strings | Blocking rule |
|---|---|---|
| 8,106, 8,196, 8,206 | Within the first 8,192 bytes | block-php-upload-progress (PHP2Shell) |
| 8,256–9,206 | PHP_SESSION_UPLOAD_PROGRESS end position is beyond 8,192 bytes | block-oversize-body |
The 8,196-byte and 8,206-byte POSTs also satisfy the block-oversize-body condition (exceeds 8,192 bytes). However, block-php-upload-progress at priority 4 is evaluated before block-oversize-body at priority 5 and blocks them first.
The publicly available PoC is of the latter form. Based on reading the source (not executed), the multipart body is structured with more than 1,000 dummy fields followed by PHPSESSID and PHP_SESSION_UPLOAD_PROGRESS. A file and a huge field follow after that. When I sent a 1,056,388-byte POST reproducing this order with harmless dummy values (the two strings located approximately 56 KB from the start of the body), it was blocked with 403 by block-oversize-body.
Conditions Under Which Non-Attack Requests Are Blocked
Legitimate Requests Exceeding 8 KB Are Also Blocked by block-oversize-body
In Count configuration (RuleAction=Count), I sent the following five types of requests and all responses were 200. The WAF logs recorded all five as matching block-oversize-body (nonTerminatingMatchingRules).
| Request | Body size |
|---|---|
| JSON POST | 20,013 bytes |
| Image upload (multipart POST) | 102,710 bytes |
| PUT | 15,000 bytes |
| PATCH | 15,000 bytes |
| GET with JSON body | 20,013 bytes |
With RuleAction=Block, these were all blocked with 403.
I also verified body sizes around the threshold (8,192 bytes) and sizes far exceeding the WAF inspection range (first 8,192 bytes) with RuleAction=Block. These are the results of sending harmless urlencoded POSTs with only the body size varied.
| Body size (bytes) | Result |
|---|---|
| 8,191 | 200 |
| 8,192 | 200 |
| 8,193 | 403 |
| 1,048,576 | 403 |
| 5,242,880 | 403 |
For POSTs of 8,193 bytes or more, REQUEST_BODY was recorded in oversizeFields of the WAF log. The inspected range (requestBodySizeInspectedByWAF) was 8,192 bytes.
PHP2Shell Also Blocks JSON and text/plain Bodies
The following two POSTs both resulted in 403.
- Content-Type application/json, body
{"PHPSESSID":"dummy","PHP_SESSION_UPLOAD_PROGRESS":"dummy"} - Content-Type text/plain, body
PHPSESSID=dummy PHP_SESSION_UPLOAD_PROGRESS=dummy
For example, if a form for posting PHP articles has a body containing both of these strings, that POST will also be blocked.
I also tested a rule that adds an AND condition of "Content-Type header contains multipart/form-data" to the AWS Security Bulletin rule. This rule compares Content-Type in lowercase. When added to the WebACL in Count mode and the multipart POST and the above two POSTs were sent, this rule matched the multipart POST but did not match the JSON or text/plain POSTs. It also did not match the POST sent without specifying Content-Type using --data-binary (where curl adds application/x-www-form-urlencoded). This rule has a narrower blocking scope than the bulletin's definition. A case-sensitive rule (not converting to lowercase) did not match a POST with Content-Type Multipart/Form-Data.
When block-multipart-post in the Template Is Enabled
The template also includes a rule called block-multipart-post (enabled with BlockMultipartPost=true; disabled by default). This is a rule that blocks any POST containing multipart/form-data in Content-Type, regardless of size or content. It is based on Block-Multipart-FormData from the AWS Security Blog with a POST condition added. Legitimate file uploads and multipart form submissions will also be blocked.
I examined how block-php-upload-progress (PHP2Shell) and block-oversize-body overlap when this rule is enabled. With BlockMultipartPost=true and RuleAction=Count, I sent three types of multipart POSTs and tabulated the matches recorded in the WAF logs (— indicates no match).
| Request | block-multipart-post | block-php-upload-progress (PHP2Shell) | block-oversize-body |
|---|---|---|---|
| Multipart POST, Cookie PHPSESSID, body contains PHP_SESSION_UPLOAD_PROGRESS | Match | Match | — |
| Normal multipart POST | Match | — | — |
| Multipart POST reproducing the PoC order (1,056,388 bytes) | Match | — | Match |
block-multipart-post is evaluated before block-php-upload-progress and block-oversize-body. When enabled with RuleAction=Block, multipart POSTs are stopped here and evaluation does not proceed to the subsequent rules. The multiple matches in the table occurred because evaluation continued under Count. When disabled, normal multipart POSTs without the two strings pass through if the body is 8,192 bytes or less. Those exceeding 8 KB (such as a 102,710-byte image upload) are blocked by block-oversize-body.
Summary
AWS Security Bulletin 2026-143-AWS and the AWS Security Blog introduce AWS WAF rules as an interim mitigation for two PHP vulnerabilities. Of these, PHP2Shell and Block-Oversize-Body were verified on an ALB.
If you are considering adopting these two rules as AWS WAF mitigations, we hope the evaluation results presented here will be useful as a reference.
However, please note that WAF rules are an interim mitigation, and the fundamental solution is to update PHP to a fixed version (8.5.12, 8.4.27, 8.3.36, or 8.2.35).
The following AWS Security Blog article summarizes mitigations using AWS WAF rules and PHP-side configurations. We recommend referring to it as well.
The background on how Amazon Threat Intelligence identified exploitation of the vulnerability prior to public disclosure, and the recommended response, can be found in the following article.
