# EOL PHP 7.4 / 8.0 / 8.1 and PHP2Shell: Virtual Patching with Cloudflare WAF and PHP-FPM Without Downtime
> End-of-life PHP branches get no upstream fixes. You can shrink exposure to CVE-2026-109618 and CVE-2026-109619 today, then upgrade on your schedule.

- **ID**: PAIN-2026-003
- **Rule**: HL-R3-EOLPHP
- **CVE**: PHP-EOL-UNPATCHED (CVE-2026-109618 Exposure)
- **Author**: Hajime Chiba (HadayaLab / 45x Google Cloud & DeepMind Skill Badges)
- **Published**: 2026-10-24
- **Canonical URL**: https://hadayalab.com/blog/eol-php-74-80-81-php2shell-virtual-patch-waf-vaccine


## 01. Observed Symptoms & Why Standard Package Audits Fail

Observed (support status): https://www.php.net/supported-versions.php lists PHP 7.4 and 8.0 as ended, and PHP 8.1 reached the end of its security support window on 2025-12-31. The PHP announcement for PHP2Shell is at https://www.php.net/archive/2026.php#2026-10-21-5. Re-read both before you act.

Inference (affected range): Upstream does not publish fixes for EOL branches, so a fixed 7.4.x, 8.0.x or 8.1.x release should not be expected. Whether those branches are inside the affected range of CVE-2026-109618 and CVE-2026-109619 is stated in the GitHub advisories (GHSA-w8hw-w9fx-j79w, GHSA-6wmf-4rv5-p3rp). Check it, and assume exposure until you have.

Symptoms of the legacy trap: (1) The server's OS vendor ships its own PHP 7.4 or 8.0 package, so 'apt upgrade' reports nothing to do. (2) WordPress, WooCommerce or Laravel 8/9 pins you to the old runtime and every plugin update feels risky. (3) composer audit and plugin scanners are clean, because the defect is in the interpreter. (4) Nobody owns the upgrade, so the quarter keeps slipping.

The operating constraint is that you cannot always upgrade this week without breaking checkout, a payment plugin or a custom Laravel package. The goal of this post is to separate 'reduce exposure now' from 'migrate safely'.


## 02. Root-Cause Forensic Analysis (Runtime & Protocol Level)

Observed (PHP manual behaviour): PHP's session upload-progress feature is triggered by a multipart form field whose name is configured by session.upload_progress.name (default PHP_SESSION_UPLOAD_PROGRESS). It is not triggered by an HTTP header. This matters for virtual patching: a rule that strips or blocks headers does not close this path. The rule must match the body field, and the authoritative control is switching the feature off in php-fpm.

Inference (why this control): HadayaLab's triage profile for the PHP2Shell pair removes the upload-progress code path from the request lifecycle. The advisory text decides whether that is sufficient for your branch. Treat the virtual patch as risk reduction, and keep EOL hosts off the open internet where you can.

Inference (limits of virtual patching): A WAF rule matches known request shapes. An engine-level defect on an unsupported branch may have other triggers. The reliable end state is a supported PHP release. Everything in Section 03 buys time.

Before choosing a target, scan the codebase for compatibility. The command below reports what breaks on PHP 8.3 or later across WordPress-aware and plain PHP code.

```bash
// Compatibility scan against a supported target (PHP 8.3+)
composer global require --dev squizlabs/php_codesniffer phpcompatibility/phpcompatibility-wp
phpcs -p --extensions=php --standard=PHPCompatibilityWP --runtime-set testVersion 8.3- --ignore=vendor,node_modules .
# Laravel: confirm framework floor. Laravel 11+ requires PHP 8.2+. Check composer.json 'require.php'.
```


## 03. Immediate Zero-Downtime Triage (Non-Breaking Mitigation)

Layer 1, php-fpm (authoritative): switch the feature off per pool. Validate the config, then reload gracefully. The only functional casualty is code that polls $_SESSION for upload progress.

Layer 2, Cloudflare: block POSTs whose body contains the trigger field name. Availability caveat (Observed from Cloudflare's rules model; confirm for your plan): request-body fields in custom rules are plan-dependent and inspect a bounded prefix of the body. If your plan cannot inspect bodies, rely on Layer 1 and Layer 3. If you changed session.upload_progress.name, match your custom name.

Layer 3, self-hosted WAF: a ModSecurity rule on the same field name works where Cloudflare body inspection is not available.

Canary rollout: apply Layer 1 to a second pool and route a small slice of traffic to it first, so a regression shows up on 5% of requests, not 100%.

```ini
// php-fpm pool (legacy paths vary: /etc/php/7.4/fpm/pool.d/www.conf, etc.)
php_admin_flag[session.upload_progress.enabled] = off

; validate and apply (use your version's binary, e.g. php-fpm7.4):
;   php-fpm7.4 -tt && systemctl reload php7.4-fpm
```

```text
// Cloudflare WAF custom rule (action: Block)
http.request.method eq "POST" and http.request.body.raw contains "PHP_SESSION_UPLOAD_PROGRESS"
```

```apache
// ModSecurity (self-hosted fallback)
SecRequestBodyAccess On
SecRule ARGS_NAMES "@contains PHP_SESSION_UPLOAD_PROGRESS" "id:1090001,phase:2,deny,status:403,log,msg:'PHP upload-progress trigger blocked (PHP2Shell virtual patch)'"
```

```nginx
// Nginx canary: 5% of traffic to the hardened pool first
upstream php_stable { server unix:/run/php/php7.4-fpm.sock; }
upstream php_canary { server unix:/run/php/php7.4-fpm-canary.sock; }

split_clients $request_id $php_pool {
    5%  php_canary;
    *   php_stable;
}

location = /index.php {
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
    fastcgi_pass $php_pool;
}
```


## 04. Permanent Remediation & Cryptographic SBOM Attestation

Upgrade path that does not break production: (1) build a staging clone on a supported PHP 8.3 or 8.4 image; (2) run the compatibility scan above and fix findings in your own code first; (3) check each WordPress/WooCommerce plugin's declared minimum and tested-up-to PHP versions, and replace abandoned plugins rather than patching them; (4) for Laravel, raise the framework to a release that supports your target PHP before raising PHP; (5) run the test suite and a checkout smoke test; (6) cut over with the canary routing shown above.

Inference (extended support): Commercial extended-lifecycle vendors sell backported fixes for EOL branches. If you buy one, require their changelog to cite CVE-2026-109618 and CVE-2026-109619 by ID before you count the branch as covered.

Attestation: after cutover, record the new runtime in a CycloneDX SBOM and sign it (see PAIN-2026-005). Keep the FPM flag in config management as a permanent hardening default, since few applications need upload progress.

Primary sources: https://www.php.net/supported-versions.php | https://www.php.net/archive/2026.php#2026-10-21-5

HadayaLab operates with 45x Google Cloud & DeepMind Skill Badges (https://www.skills.google/public_profiles/49cdbdb6-20d4-4661-934a-d882c7f5c6cf). We deliver legacy PHP migrations in staged, reversible steps with the virtual patch live from day one.


## Frequently Asked Questions

### Is PHP 7.4, 8.0 or 8.1 vulnerable to CVE-2026-109618 and CVE-2026-109619?
Those branches are outside upstream security support, so no upstream fix should be expected. Whether they fall in the affected range is stated in GHSA-w8hw-w9fx-j79w and GHSA-6wmf-4rv5-p3rp. Assume exposure until you have checked, and apply the virtual patch while you plan the upgrade.

### Can I upgrade legacy PHP without breaking WordPress, WooCommerce or Laravel?
Yes, with a staged path. Scan with PHPCompatibility, fix your own code first, check each plugin's PHP requirements, raise Laravel before PHP, test on a clone, then cut over with canary routing. The virtual patch protects you during the process.

### Does a Cloudflare WAF rule fully virtual-patch PHP2Shell?
No. It blocks one known trigger shape, the PHP_SESSION_UPLOAD_PROGRESS multipart field, and body inspection depends on your plan. The authoritative control is php_admin_flag[session.upload_progress.enabled] = off in php-fpm. Neither replaces moving to a supported PHP release.

### Will turning off session.upload_progress cause downtime?
It needs only a graceful php-fpm reload, with no dropped requests. It breaks only features that read upload progress from the session, such as custom upload progress bars. Roll it to a canary pool first to confirm.


