Back Sploitus Exploit for Improperly Controlled Sequential Memory Allocation in Php
**Layered AWS WAF virtual patching for the PHP2Shell RCE vulnerabilities (CVE-2026-109618 and CVE-2026-109619), built with Terraform**
[
- [Project Structure](#project-structure)
- [Count → Block Rollout](#count--block-rollout)
- [Detection and Monitoring](#detection-and-monitoring)
On **October 21, 2026** the PHP project shipped emergency fixes for two critical (CVSS v4.0 **9.2**) unauthenticated remote-code-execution bugs, together known as **PHP2Shell**. AWS published guidance the same day on using **AWS WAF** to reduce exposure until the patches are rolled out.
This project turns that advisory into a **working, tested, reproducible deployment**:
* A realistic PHP workload (nginx + php-fpm) behind an **Application Load Balancer**
* An **AWS WAF web ACL** with eight layered rules: AWS managed rule groups (with `AWSManagedRulesPHPRuleSet` pinned to **Version_2.3**), custom signature, multipart, oversize and rate-limit rules, and an optional label-based exception
* A one-variable **Count → Block** rollout, the same way you'd ship it in production
* **Server-side hardening** that covers what a WAF can't (patched PHP, `php.ini`, FPM pool)
* **Detection**: KMS-encrypted WAF logs, saved Logs Insights queries, a per-rule dashboard and an SNS alarm
* An **automated test suite** that checks every layer's expected status code in both modes
* **CI** with `terraform validate`, tflint, checkov and shellcheck
The goal isn't just "turn on WAF". It's to show where each control works, where it doesn't (see [COVERAGE.md](docs/COVERAGE.md)), and to treat patching as the real fix.
| **Class** | Memory corruption when PHP runs out of memory mid-request | Stale global state reused by the request on the same worker |
| **Reached via** | `multipart/form-data` parsing, oversized bodies, header amplification, `fatal_error_backtraces` (8.5), INI diagnostics | `multipart/form-data` with `PHP_SESSION_UPLOAD_PROGRESS` and a `PHPSESSID` session |
| **When** | Before any application code runs | Before any application code runs |
| **Advisory** | GHSA-w8hw-w9fx-j79w | GHSA-6wmf-4rv5-p3rp |
| **Fixed in** | PHP 8.5.12, 8.4.27, 8.3.36, 8.2.35 (8.1 and older: no upstream fix) | same |
Both bugs fire *before* your code runs, so application input validation can't help. That's why an edge control (WAF) plus server configuration matters here.
C[Client / attacker] -->|HTTP| ALB[Application Load Balancerdrop_invalid_header_fields]
subgraph ACL[AWS WAF web ACL - REGIONAL - first 8 KB of body]
R10[10 RateLimit-Posts] --> R20[20 PHP2Shell-Signature]
R20 --> R30[30 AWS PHP rule setVersion_2.3 - PHP2Shell_BODY]
R55 --> R60[60 Block-Multipart-FormDataexcept /upload.php]
R60 --> R70[70 Block-Oversize-Bodyexcept /upload.php]
subgraph EC2[EC2 - Amazon Linux 2023 - IMDSv2 hop limit 1]
NGINX[nginx] --> FPM[php-fpm 8.4.27upload_progress offpm.max_requests=1cgroup memory limit]
ACL -->|logs| CWL[(CloudWatch Logsaws-waf-logs-*KMS encrypted)]
ACL -->|per-rule metrics| CW[CloudWatch dashboard]
CW --> ALARM[PHP2Shell activity alarm] --> SNS[SNS email]
| Priority | Rule | Type | Targets | Why it's there |
| 10 | `RateLimit-Posts` | Rate-based (100 POST / 60 s / IP) | Both | Slows retry-heavy exploitation |
| 20 | `PHP2Shell-Signature` | Custom | CVE-2026-109619 | `POST` + `PHPSESSID` (cookie key or body) + `PHP_SESSION_UPLOAD_PROGRESS`; version-independent backup for the managed rule |
| 30 | `AWSManagedRulesPHPRuleSet` | AWS managed, pinned **Version_2.3** | CVE-2026-109619 | Contains AWS's `PHP2Shell_BODY` virtual patch |
| 40 | `PHP2Shell-Label-Exception` | Label match (optional) | — | Blocks the `PHP2Shell_Body` label everywhere except one confirmed false-positive path |
| 50 | `AWSManagedRulesCommonRuleSet` | AWS managed | Generic | Baseline OWASP coverage; `SizeRestrictions_BODY` handed to rule 70 |
| 55 | `AWSManagedRulesKnownBadInputsRuleSet` | AWS managed | Generic | Log4j, Java deserialization and other known-bad payloads (added after a checkov finding) |
| 60 | `Block-Multipart-FormData` | Custom | Both | Multipart is the entry point for both bugs, so it's only allowed on `/upload.php` |
| 70 | `Block-Oversize-Body` | Size constraint, `OversizeHandling: MATCH` | CVE-2026-109618 | ALB inspects only 8 KB, so anything larger is treated as a match |
Plus server-side controls in [`app/`](app/):
| Patched `php:8.4.27-fpm-alpine` (Terraform **refuses** unpatched images) | `terraform/variables.tf` | both |
| `session.upload_progress.enabled = 0` (ini + `php_admin_flag`) | `app/php/*` | 109619 |
| `pm.max_requests = 1` (recycle every worker) | `app/php/zz-php2shell-pool.conf` | 109619 |
| `memory_limit = -1` + container cgroup memory limit | `app/php/*`, user data | 109618 |
| `php_admin_*` so scripts and `PHP_VALUE` can't override | `app/php/zz-php2shell-pool.conf` | both |
| `log_errors`, `E_ALL`, no `display_errors` | `app/php/zz-php2shell-hardening.ini` | visibility |
Reasoning behind each choice: **[docs/DESIGN-DECISIONS.md](docs/DESIGN-DECISIONS.md)**.
A WAF can't parse multipart internals, see PHP worker memory, or change server config. AWS says so in the advisory, and this project treats it as a design constraint:
| CVE-2026-109619 request pattern | **Full** |
| Multipart uploads | **Conditional**: only where the app takes none |
| Oversized bodies | **Partial**: header amplification fits under 8 KB |
| Repeated attempts | **Partial**: one request can be enough |
| `fatal_error_backtraces`, INI diagnostics | **None**: server config only |
Full matrix with the residual risk for each row: **[docs/COVERAGE.md](docs/COVERAGE.md)**.
│ ├── versions.tf # Provider + default tags
│ ├── variables.tf # Inputs, incl. patched-PHP and rule-version guards
│ ├── main.tf # Locals: rule_mode switch, metric names
│ ├── network.tf # VPC, 2 public subnets, security groups
│ ├── alb.tf # ALB, target group, HTTP/HTTPS listeners
│ ├── compute.tf # EC2 (IMDSv2, encrypted EBS, SSM, no SSH)
│ ├── waf.tf # Web ACL — the 8 rules — and ALB association
│ ├── logging.tf # KMS, WAF logs, saved Logs Insights queries
│ ├── monitoring.tf # Dashboard, alarm, SNS
├── tests/validate.sh # 8 cases, expected codes per mode
├── scripts/verify-waf.sh # Read-only AWS CLI checks from the advisory
├── queries/*.logsinsights # Deployed as saved CloudWatch queries
**Prerequisites:** an AWS account (sandbox recommended), AWS CLI v2 with credentials, Terraform ≥ 1.6, `curl`.
git clone
cp terraform.tfvars.example terraform.tfvars
# set allowed_ingress_cidrs to your IP: curl -s
Give the instance two minutes to bootstrap, then open the `alb_url` output. The status page shows PHP **8.4.27** and the hardened settings.
# 1. Deployed in count mode by default: nothing is blocked, everything is logged
./tests/validate.sh --mode count # generates sample matches
# 2. Review: dashboard_url output + Logs Insights saved query "03-count-mode-would-block"
./scripts/verify-waf.sh # confirm Version_2.3, no group-wide Count override
cd terraform && terraform apply -var rule_mode=block && cd ..
Full step-by-step procedure, false-positive handling and rollback guidance: **[docs/ROLLOUT-RUNBOOK.md](docs/ROLLOUT-RUNBOOK.md)**.
`tests/validate.sh` sends one request per layer. The payloads only match signatures (a cookie name, a form field name, a body size). **None of them is an exploit**, and the backend is patched.
| 2 | Small urlencoded `POST /` | 200 | 200 | No false positive on normal forms |
| 3 | multipart `POST /` | 200 | **403** | Rule 60 |
| 4 | multipart `POST /upload.php` | 200 | 200 | Upload exemption works |
| 5 | 10 KB body `POST /` | 200 | **403** | Rule 70 (beyond the ALB inspection limit) |
| 6 | `PHPSESSID` cookie + `PHP_SESSION_UPLOAD_PROGRESS` → `/upload.php` | 200 | **403** | Rules 20/30 still protect the exempt path |
| 7 | multipart `POST /upload.php/../index.php` | 200 | **403** | Path-normalization bypass is closed |
| 8 | 20 KB upload → `/upload.php` | 200 | 200 | Documents the known gap honestly |
* **WAF logs** go to `aws-waf-logs-php2shell-shield`, encrypted with a customer-managed KMS key (rotation on). The `Authorization` header is redacted; cookies stay visible because `PHPSESSID` is evidence.
* **Saved Logs Insights queries** (from [`queries/`](queries/)): blocked-by-rule, all PHP2Shell matches, count-mode would-block by path, top POST sources.
* **Dashboard**: Allowed/Blocked/Counted for the ACL plus each rule.
* **Alarm** `php2shell-shield-php2shell-activity`: fires on any PHP2Shell match, **counted or blocked**, so probes during the Count window aren't missed. Set `alert_email` to get notified.
Running cost is roughly **$1.50–2 per day** (us-east-1): ALB ~$0.55, WAF web ACL + 8 rules ~$0.43, public IPv4 ~$0.36, t3.micro ~$0.25, KMS and CloudWatch the rest. Request charges are negligible at lab volume.
The KMS key enters a 7-day pending-deletion window.
See [`screenshots/README.md`](screenshots/README.md) for the capture list.
* **Virtual patching buys time; it doesn't fix the bug.** The WAF covers the request pattern fully but can't touch worker memory or server config. Patching (enforced in Terraform here) is the real fix.
* **Inspection limits matter.** An ALB lets WAF see only 8 KB of the body. Without an `OversizeHandling: MATCH` size rule, an attacker can push the payload past what WAF inspects.
* **Exceptions can become bypasses.** A `STARTS_WITH` path exemption plus nginx's path normalization would have exempted every script. Decoding, normalizing and matching exactly fixes it, and a test proves it.
* **Roll out in Count first.** One variable moves the whole ACL between Count and Block, logging is on before any rule changes, and the alarm covers both modes.
* **Managed rule versions are part of the attack surface.** Rolling back to Version_2.2 silently removes the patch, so the variable validation refuses it.
**Q: A critical PHP RCE drops this morning. What do you do in the first hour?**
Inventory affected hosts and versions. Confirm WAF logging is on. Check that the PHP managed rule group is on the patched version (Version_2.3) and isn't overridden to Count. Deploy the extra rules in Count, review for hours, then Block. Patch and harden php.ini in parallel. The WAF buys time; the patch closes the bug.
**Q: Why not just block `multipart/form-data` everywhere?**
It breaks every file upload. Rule 60 blocks it everywhere except the one upload endpoint. That endpoint stays protected by the signature rules, patched PHP, and `session.upload_progress` being disabled.
On an ALB, WAF only inspects the first 8 KB of the body, and that can't be raised (CloudFront and API Gateway go up to 64 KB). A signature after byte 8,192 is invisible. Rule 70 uses `OversizeHandling: MATCH` to treat anything bigger as suspicious on paths that shouldn't receive large bodies.
**Q: You found a false positive in `PHP2Shell_BODY`. Now what?**
Don't disable the rule or roll the version back. Keep it in Count and add a label-based rule after it that blocks the `awswaf:managed:aws:php-app:PHP2Shell_Body` label everywhere except the narrowest possible path. Then patch that application first, because it's now unprotected by that rule. Here, that's `php2shell_exempt_path`.
**Q: Why both a custom rule and the managed rule?**
Defense in depth and version independence. If someone pins or rolls back the managed group, rule 20 still catches the known pattern, and its metric gives an unambiguous count of attempts.
**Q: Why `memory_limit = -1`? Isn't that dangerous?**
CVE-2026-109618 triggers when PHP hits its *own* limit mid-request. Removing PHP's limit and bounding memory with the container cgroup keeps PHP off the vulnerable path while still capping memory. `RLIMIT_AS` would trigger the same path.
**Q: What would you change for production?**
CloudFront in front (64 KB inspection, Shield Standard), Firewall Manager to push the ACL across accounts, private subnets with a NAT or VPC endpoints, flow logs and ALB access logs, logs shipped to a central security account (see Project 01), and ECS/EKS instead of a single instance.
* AWS Security Blog: *Using AWS WAF to apply mitigations for PHP2Shell RCE vulnerabilities (CVE-2026-109618 and CVE-2026-109619)*.
* GitHub advisories GHSA-w8hw-w9fx-j79w, GHSA-6wmf-4rv5-p3rp
* AWS WAF docs: PHP application managed rule group, managed rule group versioning, body inspection limits, oversize handling
> **Disclaimer:** This is an educational lab for defensive security. It contains no exploit code; test requests only match WAF signatures. Deploy it only in an AWS account you own.
The full story
This article is one source in a clustered incident — the cluster page carries the summary, timeline and every other outlet covering it.
