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.

Compatibility scan against a supported target (PHP 8.3+) BASH
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'.
1:1 EMBEDDED SOLUTION PIPELINE · HL-R3-EOLPHP

5-Register Quarantine & Attestation Engine

AWAITING INSPECTION
REGISTER 1 In-Browser AST & Lockfile Inspector
$0 · ALWAYS FREE

Evaluated 100% locally in your browser inside a zero-exfiltration sandbox (connect-src 'none'). Your credentials and proprietary code never touch our servers.

REGISTER 2 Non-Breaking Vaccine & Config Diff
10 cr ($1.00)

Generates targeted unified .patch diffs and perimeter WAF rules. Non-breaking compatibility is mathematically verified against the complete dependency graph.

✓ NON-BREAKING VERIFIED: Compatible across 14 downstream dependencies (0 semver conflicts)
TARGET FILES: CRYPTOGRAPHICALLY VERIFIED
Cloudflare WAF / Coraza CRS Rule 941190 & reverse-proxy ingress
Perimeter Virtual Patch Shielding EOL PHP 7.4 / 8.0 / 8.1 runtimes
REGISTER 3 Signed CycloneDX 1.6 SBOM & Attestation PDF
20 cr ($2.00)

Official cryptographic audit certificate with DSSE Ed25519 digital signature, 7-day rolling attestation window, and compliance mapping for NIST CSF 2.0 and SOC2.

ATTESTATION SPEC: No Findings in Scope
Auditor Authority: 45x Google Cloud & DeepMind Skill Badges
Attestation Window: Valid for 7 days (next variant sweep required in 7 days)
Signature Spec: in-toto Statement v1 / DSSE Ed25519 envelope (hadayalab-attest-2026-ed25519-k1)
Inspect Sample Ed25519 Signed SBOM (/sbom/PAIN-2026-003.cdx.json) →
REGISTER 4 Side-Effect & Rollback Manifest
10 cr ($1.00)

Instant crash and compatibility analyzer. Computes peerDependency, PHP extension, and engine compatibility changes with a 1-command rollback manifest.

ROLLBACK INTEGRITY SPEC:
Atomic git-level rollback manifest with state-reversal guarantees for PAIN-2026-003.
REGISTER 5 Continuous CI/CD GitHub Actions OIDC Sweeper
1 cr / run ($0.10)

Automated weekly variant sweep via GitHub Actions OIDC (id-token: write). Auto-renews 7-day rolling attestation tokens without long-lived secrets.

AUTOMATION TARGET:
.github/workflows/hadayalab-attest.yml
Zero long-lived API secrets · OIDC Short-Lived JWT Verification

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%.

php-fpm pool (legacy paths vary: /etc/php/7.4/fpm/pool.d/www.conf, etc.) INI
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
Cloudflare WAF custom rule (action: Block) TEXT
http.request.method eq "POST" and http.request.body.raw contains "PHP_SESSION_UPLOAD_PROGRESS"
ModSecurity (self-hosted fallback) APACHE
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 canary: 5% of traffic to the hardened pool first NGINX
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.