TL;DR — Are you affected?
- What: Two critical bugs in the PHP engine itself (CVSS 4.0: 9.2 each), nicknamed PHP2Shell. Both can lead to unauthenticated remote code execution and memory disclosure.
- Affected: Every PHP version from 5.4 up to 8.5.11.
- Fixed in: 8.5.12, 8.4.27, 8.3.36, 8.2.35. PHP 8.1 and older will not be patched upstream.
- Most exposed: Apache mod_php (exploit confirmed with default settings, before any of your code runs). PHP-FPM is also exploitable through one of the two bugs. FrankenPHP is likely exposed.
- Laravel: Affected like any PHP app. Updating Laravel does nothing here. You must update the PHP runtime.
- Exploitation: Researchers have shown working RCE against WordPress, Drupal and Joomla. NHS England's security team rates future exploitation as highly likely.
- Do now: Patch, restart every PHP worker, rebuild every container, then check for signs of compromise.
1. What happened
On October 21, 2026, the PHP project and Wiz Research disclosed two vulnerabilities in the core PHP runtime, published as GitHub advisories GHSA-w8hw-w9fx-j79w and GHSA-6wmf-4rv5-p3rp. Google Cloud, NHS England, the UK NCSC and ENISA issued guidance the same day.
These are not bugs in a framework, package or CMS. They live in how the PHP engine handles memory between and during requests, which is why they affect almost every PHP deployment at once.
2. How the two bugs actually work
You don't need this section to patch. But understanding it explains why certain setups are more exposed, and why some "quick fixes" don't help.
CVE-2026-109618 — RCE via out-of-memory corruption
When PHP runs out of memory, it uses a C mechanism called longjmp() to bail out of whatever it's doing and abort the request. The problem: in some places, PHP has already made new request memory reachable before finishing setting it up. If any allocation fails at that exact moment, PHP jumps out and leaves that memory half-initialized. The shutdown routine later reads it as if it were valid.
The dangerous part is that an attacker can cause the out-of-memory condition on demand while staying inside your configured limits. The advisory describes several ways through multipart (file upload/form) parsing:
-
A multipart request without a usable declared content length can pass the initial
post_max_sizecheck and keep sending data. - A single multipart part can carry an unlimited number of headers, which take far more memory inside PHP than on the wire.
-
Empty file fields don't count toward
max_file_uploadsbut still build metadata arrays, and nested field names multiply that work.
Why mod_php is worst hit: Apache mod_php always runs request shutdown, even if request startup failed. Body parsing happens during startup, so the whole attack completes before a single line of your application code executes. RCE was confirmed against mod_php with default memory and input limits.
PHP-FPM is safer for the startup path because it kills the child process when startup fails. But an out-of-memory error later in the request can still hit the bug. Source analysis says FPM is susceptible, though no complete FPM exploit for this CVE has been demonstrated yet.
CVE-2026-109619 — RCE via stale global state (use-after-free)
A PHP worker handles many requests over its lifetime. Some internal "global" state (error info, session state, object and extension state) temporarily points into memory owned by the current request. It must be reset before that memory is freed.
Sometimes it isn't:
-
A fatal error (including out-of-memory,
Maximum execution time exceeded, a failedrequire, a parse error) can abort an operation before its cleanup runs. - A warning during shutdown can re-populate error state after it was already cleared.
The memory gets freed, but the global pointer still points at it. If the next request on the same worker touches that state before normal initialization, PHP uses freed memory. With a crafted heap layout, that becomes RCE.
The advisory calls these two halves the producer (leaves stale state behind) and the consumer (reads it on the next request). Two consumers are confirmed:
-
INI warnings applied before request startup. Deprecated or invalid settings in
.htaccess, Apachephp_value/php_flag, or FPMPHP_VALUE/PHP_ADMIN_VALUEsent from nginx. Hiding errors with@,display_errors=Offorerror_reportingdoes not help, because the stale state is touched before PHP decides whether to report anything. -
Session upload progress. This feature is enabled by default, and it runs even if your app never calls
session_start().
Two confirmed exploit cases:
-
PHP 8.5 with
fatal_error_backtracesenabled: confirmed RCE against mod_php. - Session upload progress lifecycle: confirmed RCE against both mod_php and PHP-FPM (FPM needs a suitable application route that triggers a fatal error).
3. Exposure by setup
| How PHP runs | CVE-2026-109618 (OOM) | CVE-2026-109619 (stale state) |
|---|---|---|
| Apache mod_php | Confirmed RCE, default config, before app code runs | Confirmed RCE |
| PHP-FPM (nginx / Apache proxy) | Susceptible per source analysis, no full exploit shown | Confirmed RCE via session upload progress (needs a suitable app route) |
| FrankenPHP (incl. Laravel Octane on FrankenPHP) | Likely susceptible to app-independent attacks | No default early consumer found, not declared immune |
| LSAPI (LiteSpeed) | App-specific exploits likely, unvalidated | Conditional candidate |
| Shared / managed hosting | Depends on the SAPI underneath. Ask your host. | Same |
Bottom line: every setup should patch. mod_php should be treated as an emergency.
4. Fixed versions
| Branch | Vulnerable | Fixed in | Status |
|---|---|---|---|
| PHP 8.5 | ≤ 8.5.11 | 8.5.12 | Supported |
| PHP 8.4 | ≤ 8.4.26 | 8.4.27 | Supported |
| PHP 8.3 | ≤ 8.3.35 | 8.3.36 | Supported |
| PHP 8.2 | ≤ 8.2.34 | 8.2.35 | Supported |
| PHP 8.1 and older (back to 5.4) | All | None upstream | End of life — migrate |
Source: the two PHP advisories and the php.net October 21 release notice.
On distro packages: Ubuntu, Debian, RHEL and others often backport fixes without changing the upstream version number. A package reporting "8.1.x" might be patched by your vendor, and one reporting "8.3.x" might not. Check your distro's security tracker for both CVE IDs rather than trusting the version string alone.
5. The 15-minute check
Run these on each server. The key lesson: the CLI PHP and the PHP serving web requests are often different binaries.
# CLI version (what artisan, cron and queue workers use)
php -v
# FPM version (what nginx usually talks to). Name varies by distro:
php-fpm -v # or php-fpm8.3 -v, php-fpm83 -v
# Is Apache using mod_php? If this prints a php module, you're on mod_php.
apachectl -M 2>/dev/null | grep -i php
# Inside a running container
docker exec <container> php -v
# Risky settings that make exploitation easier
php -i | grep -E "session.upload_progress.enabled|fatal_error_backtraces|memory_limit"
To confirm what the web layer really runs, check the PHP version your app logs or reports in an authenticated admin page. Never leave a public phpinfo() page online.
Record for each environment: PHP version, SAPI (mod_php / FPM / FrankenPHP), how it's deployed, and whether it's internet-facing. Prioritize internet-facing + mod_php + EOL PHP first.
6. How to fix it
Step 1: Inventory everything that runs PHP
Production and staging web servers, FPM pools, queue workers, schedulers, Docker images, Kubernetes workloads, CI images, WordPress/Drupal/Joomla sites, managed hosting accounts, and appliances that bundle PHP. Don't stop at your repository: a repo can be current while production runs an old binary.
Step 2: Upgrade the runtime
- Package-managed servers: update PHP through your distro or a trusted repository (for example the Ondřej Surý PPA on Ubuntu), then confirm the package changelog lists both CVEs.
-
Docker: a floating tag such as
php:8.3-fpmwon't update itself. Rebuild with a fresh pull (docker build --pull) or pin to an explicit patched tag after confirming it exists on Docker Hub. - Kubernetes: roll out the rebuilt image and confirm new pods are running it.
- Cloud platforms: Google Cloud reports App Engine Standard (PHP 8.2+) is patched automatically, and Cloud Run is patched only if automatic base image updates are on. Images you build yourself are your responsibility. Check your own provider's bulletin.
- On PHP 8.1 or older: there is no upstream patch. Plan the upgrade to 8.2+ now, and apply the interim mitigations below until it ships.
Step 3: Restart every PHP process
A new binary on disk does nothing if old workers are still running. Restart PHP-FPM (a full restart, not a graceful reload, is the safe choice here), Apache, Octane, Horizon and queue workers.
Step 4: Test the critical paths
Login, payments, uploads, queues, scheduled jobs, API integrations, and that required extensions loaded. Patch releases rarely break things, but an emergency upgrade from an EOL branch will need real testing.
Step 5: Verify
Re-run the commands from section 5 against the live environment. Confirm no old instance is still reachable (old load-balancer targets, forgotten staging servers, preview environments). Save the results in your change record.
Step 6: Look for signs of compromise
Patching closes the door. It doesn't remove anyone already inside. Review for:
- Repeated PHP worker crashes or segfaults in Apache/FPM logs, especially clustered around large multipart POST requests.
-
New or modified PHP files in web-writable directories (
storage/,public/uploads/,wp-content/). -
Unexpected child processes spawned by the web user (
www-data,nginx,apache), or outbound connections from the web server. - New admin users, cron entries or SSH keys.
If you find evidence of compromise: isolate the host, preserve logs, rebuild from a known-good image rather than "cleaning" it, and rotate every secret the app could read (.env, database passwords, API keys, APP_KEY).
7. Can't patch today? Interim mitigations
These reduce risk. None of them fully fix either bug. The advisories are explicit that upgrading is the only complete mitigation. Set these administratively (php.ini or FPM pool config), then restart every worker.
| Setting / action | What it blocks | Trade-off |
|---|---|---|
session.upload_progress.enabled = 0
|
The confirmed session-based exploit for CVE-2026-109619 (mod_php and FPM) | Almost no app uses this feature. Worth keeping off even after patching. |
fatal_error_backtraces = 0 (PHP 8.5)
|
The confirmed mod_php exploit path for CVE-2026-109619 | Less detail in fatal error logs |
Block multipart/form-data at the proxy/WAF on routes that don't need uploads
|
The main out-of-memory trigger for CVE-2026-109618 | Must allow-list upload routes |
Enforce decoded body size at nginx/proxy; reduce max_input_nesting_level, max_input_vars, upload counts
|
Shrinks the OOM attack surface | Tune per app |
Remove per-request INI overrides (.htaccess php_value, nginx PHP_VALUE) that emit deprecation warnings
|
The early INI consumer for CVE-2026-109619 | Move settings to php.ini / pool config |
pm.max_requests = 1 (FPM)
|
Stops state carrying between requests on one worker | Significant performance cost |
memory_limit = -1
|
The confirmed controllable-OOM exploit for CVE-2026-109618 | Last resort. Opens the door to memory-exhaustion DoS and still isn't complete. Only use with container/cgroup memory limits per worker. |
Do not use RLIMIT_AS or RLIMIT_DATA to cap memory. The advisory warns that these make allocations fail, which triggers exactly the vulnerable path.
If a public-facing service can't be patched or mitigated, taking it offline is a legitimate business decision. NHS England's guidance recommends exactly that.
8. Laravel-specific notes
Laravel middleware cannot protect you here. On mod_php, CVE-2026-109618 is triggered during request body parsing, before Laravel boots. Validation, CSRF, auth and rate-limiting middleware never see the request.
What to check in a typical Laravel stack:
-
Forge / VPS with nginx + FPM: update the PHP package on each server, then restart the FPM service. Check whether nginx sites pass
PHP_VALUE(some legacy configs do). -
Laravel Sail / Docker: rebuild images with
--pull. Check CI images and queue-worker images too, not just the web image. - Laravel Octane: if you run Octane on FrankenPHP, treat it as exposed to CVE-2026-109618 and patch the FrankenPHP build's bundled PHP. The advisories don't analyze Swoole or RoadRunner specifically, so patch those too and don't assume immunity.
- Horizon and queue workers: they don't serve HTTP, but they share the same binary. Patch and restart them so you're not running mixed versions.
- Uploads: if your app accepts file uploads, prioritize it. Upload endpoints are the natural route for the multipart OOM trick.
Do you need to update Laravel itself? Not for these CVEs. But while you're upgrading PHP, check composer audit and confirm your Laravel version supports the PHP branch you're moving to. If you're stuck on PHP 8.1 because of an old Laravel version, that's now a security problem, not just technical debt.
9. Is PHP still a safe choice?
Yes. Every major runtime has had critical memory-safety bugs; what matters is whether the fix ships fast and whether you can deploy it fast. Here, fixes for all supported branches shipped on disclosure day.
The real lesson is operational. Teams that already knew which PHP version ran where, rebuilt images routinely, and stayed on supported branches will be patched today. Teams on PHP 7.4 with hand-built servers face a forced migration under pressure. Switching languages wouldn't change that; staying current would.
10. FAQ
Is Laravel vulnerable to PHP2Shell? Laravel itself has no bug here, but every Laravel app runs on PHP, so any app on an unpatched PHP runtime is exposed. Update PHP, not Laravel.
Does a WAF protect me? Partially. Blocking unneeded multipart requests and limiting request sizes helps against CVE-2026-109618. A WAF does little against the stale-state bug. Patch anyway.
I'm on shared hosting. What do I do? Ask your host directly whether the PHP serving your site is 8.2.35 / 8.3.36 / 8.4.27 / 8.5.12 or a vendor build with both CVEs patched, and when workers were restarted. Get the answer in writing.
I'm on PHP 7.4 or 8.1. Am I safe if nobody targets me? No. These attacks are application-independent on mod_php, which makes mass scanning practical. Apply the interim mitigations today and schedule the upgrade.
Does hiding errors (display_errors=Off, @) help? No. The vulnerable code runs before PHP decides whether to show or log the error.
Need a hand?
If you're not sure which PHP versions your servers, containers and workers are actually running, I offer a PHP2Shell patch check for PHP and Laravel applications: I'll inventory your runtimes, apply or plan the upgrade, set the interim mitigations where you can't patch yet, and give you a short written report of what was fixed and what's left.
References
- PHP advisory GHSA-w8hw-w9fx-j79w (CVE-2026-109618)
- PHP advisory GHSA-6wmf-4rv5-p3rp (CVE-2026-109619)
- PHP release announcement, October 21, 2026
- Wiz Research: PHP2Shell write-up
- Google Cloud: Responding to core PHP vulnerabilities
- NHS England Digital cyber alert CC-4875
- UK NCSC: Critical security issue affecting PHP
- ENISA joint statement
This article is general technical guidance, not a security assessment of your specific environment. Advisories may be updated; check the official sources before making production changes.