Check, apply and test AWS WAF mitigations for PHP2Shell (CVE-2026-109618 and CVE-2026-109619),
the unauthenticated PHP RCE bugs fixed in PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35.
Upgrading PHP is the real fix. This tool is for the days between the advisory and the last server getting patched: it tells you which web ACLs are covered, adds the rules AWS recommends without dropping anything you already have, and lets you prove the block from outside. There's also a host script that checks PHP versions and the php.ini settings that reduce exposure.
Write-up with screenshots: Stopping PHP2Shell with AWS WAF. The Cloudflare side is covered in Cloudflare already blocks PHP2Shell. Is it protecting you?
| Command | |
|---|---|
check |
Lists every web ACL (CloudFront and regional) and says whether the PHP2Shell rule is there, whether it blocks or only counts, and whether an Allow rule runs before it and lets requests skip it. Exit code 0 only if every web ACL is OK. |
apply |
Adds the rules from the AWS guidance at the top of each web ACL and moves the existing rules down by 10, keeping their order. Dry run by default. Backs up each web ACL to backups/ before changing it. |
restore |
Puts a web ACL back from one of those backups. |
test |
Sends a harmless POST carrying the field the rule looks for and expects a 403. |
host/check-php-host.sh |
Runs on a PHP server: version check, session.upload_progress, fatal_error_backtraces, FPM pm.max_requests, optional scan for recently changed .php files, optional --fix. |
The rules (in rules/php2shell-rules.json):
| Rule | Default | Matches |
|---|---|---|
PHP2Shell |
Block | POST + PHPSESSID (cookie or body) + PHP_SESSION_UPLOAD_PROGRESS in the body |
Block-Multipart-FormData |
Count | any multipart/form-data request (would block uploads) |
Block-Oversize-Body |
Count | bodies over 8 KB |
RateLimit-Posts |
Count | more than 100 POSTs per IP per minute |
Needs Python 3.9+ and boto3. AWS CloudShell has both, so the quickest way is to clone it there.
git clone https://github.com/jaba0x/php2shell-guard && cd php2shell-guard
pip install -r requirements.txt # not needed in CloudShell
# What's protected right now?
./php2shell_guard.py check
./php2shell_guard.py check --scope regional --regions eu-central-1 us-east-1
# See the plan, then apply it
./php2shell_guard.py apply --acl my-web-acl
./php2shell_guard.py apply --acl my-web-acl --apply
# Only the main rule, in Count mode first
./php2shell_guard.py apply --core-only --core-count --apply
# Verify from outside
./php2shell_guard.py test https://www.example.com/
# Undo
./php2shell_guard.py restore backups/my-web-acl-20261022-150000.json --applyCloudFront web ACLs always live in us-east-1. Regional ones (ALB, API Gateway, AppSync) are scanned in the regions you pass with --regions.
IAM permissions: wafv2:ListWebACLs, wafv2:GetWebACL and wafv2:CheckCapacity for check and dry runs, plus wafv2:UpdateWebACL for --apply and restore.
curl -fsSLO https://raw.githubusercontent.com/jaba0x/php2shell-guard/main/host/check-php-host.sh
bash check-php-host.sh # report
bash check-php-host.sh --scan /var/www # plus .php files changed in the last 14 days
sudo bash check-php-host.sh --fix # write conf.d/99-php2shell.ini, then restart PHPRead the script before you run it with sudo. Distro packages (Ubuntu, Debian, RHEL) sometimes ship the fix as a backport under an older version number, so a "VULNERABLE" result there means "check your vendor's advisory".
update_web_aclreplaces the whole web ACL. The tool copies back the default action, custom response bodies, CAPTCHA and challenge settings, token domains and the body inspection config from what it read, and uses the lock token so it fails instead of overwriting someone else's change.- The new rules go above your
Allowrules on purpose. If an allowlist rule runs first, matching requests never reach the PHP2Shell rule. - Leave the three optional rules in Count until you've looked at their CloudWatch metrics for a day or two.
- A WAF can't see inside PHP's memory. CVE-2026-109619 is mostly out of its reach. Set
session.upload_progress.enabled = 0, setfatal_error_backtraces = 0on 8.5, and upgrade.
pip install -r requirements-dev.txt
pytest -qThe tests run against a mocked AWS (moto), so they don't touch a real account.
MIT