On 21 October 2026 the PHP project released an emergency security update for two critical vulnerabilities that allow unauthenticated remote code execution. The industry nickname is PHP2Shell, and it is a good nickname. The flaws are tracked as CVE-2026-109618 and CVE-2026-109619, both scored 9.2 under CVSS 4.0 by the PHP team. One security firm rated it a full 10.0.
If you run any PHP application, this is not a "schedule it for the next maintenance window" problem. Exploitation attempts were observed in the wild before the patch even went public, and proof-of-concept code was circulating on the same day. This post explains what the bug actually does, gives you a copy-and-paste way to check whether your servers are exposed, walks through the fix, and lays out the habits that stop the next one from being an emergency.
What PHP2Shell Actually Is
Most vulnerabilities live inside an application. A missing permission check in a controller, an unsanitised parameter, an outdated library. PHP2Shell is different in a way that matters a lot: the flaw is in the PHP runtime itself. The vulnerable code runs while PHP is still preparing the request, before your framework, your router, or your authentication middleware ever execute.
That single fact changes the threat model. A login wall does not protect you. An admin-only endpoint does not protect you. A script that only prints "Hello world" is enough to be a target. Any PHP process serving HTTP requests is in scope: PHP-FPM, Apache with mod_php, php-cgi, FrankenPHP, and LiteSpeed.
The two bugs
Both vulnerabilities follow the same pattern. Something interrupts PHP partway through a task, and PHP later treats the incomplete result as if it were valid.
- CVE-2026-109618, remote code execution through memory exhaustion. When PHP converts a form submission into the
$_POSTarray, it writes keys and values into internal hash tables. If memory runs out halfway through that write, the counters have already been incremented but the entry has not been fully built. PHP's cleanup routine then walks over that half-built data and treats attacker-influenced values as real pointers. The result is code execution during request teardown, a stage most applications never monitor. - CVE-2026-109619, remote code execution through stale state. A PHP worker can keep internal state that belongs to a request whose memory was already freed. When the next request arrives on the same worker, that leftover state can be used before normal initialisation resets it. Workers are reused by design, so one poisoned request can set up the next one.
The discovery is worth noting too. It was found independently in August 2026 by researchers using large language models to hunt for memory safety bugs in widely used open source code, then reproduced and weaponised quickly. In one case, an exploit was built and tested against each target's memory layout with automated iteration. The lesson is not that AI is dangerous. It is that the cost of turning a hard memory corruption bug into a working exploit has dropped sharply, so the time between "patch released" and "attacks in progress" is now measured in minutes.
Step One: Find Out If You Are Exposed
Do not assume your app is fine because your hosting panel says "recent PHP". Check the runtime that actually serves requests, not just what runs in your terminal. Containers and vendor packages often carry their own interpreter, and distributions frequently backport patches without changing the version string.
Check the FPM or web server runtime
# The version the web server actually uses
php-fpm -v
# On Debian/Ubuntu, check the installed package version
dpkg -l | grep -E 'php[0-9.]*-fpm|libapache2-mod-php'
# On RHEL/Alma/Rocky
rpm -qa | grep -E 'php-fpm|php-common'
# In a container image
docker run --rm <your-image> php -v
# Through a live request, if you have a health endpoint
curl -s https://your-site.example/health | headFor a faster sweep across several hosts, a one-liner over SSH works well enough for triage:
for host in web1 web2 web3; do
printf '%s: ' "$host"
ssh "$host" "php-fpm -v 2>/dev/null | head -1 || php -v | head -1"
doneRead the version numbers correctly
Both advisories list the same fixed releases for every supported branch. Use this table as your checklist and record a row per environment, including staging and anything internal-facing.
| Branch | Affected | First fixed release | Status |
|---|---|---|---|
| PHP 8.5 | before 8.5.12 | 8.5.12 | Supported |
| PHP 8.4 | before 8.4.27 | 8.4.27 | Supported |
| PHP 8.3 | before 8.3.36 | 8.3.36 | Supported |
| PHP 8.2 | before 8.2.35 | 8.2.35 | Supported |
| PHP 8.1 and older | all releases | Source backports only | End of life |
Step Two: Patch
The fix is a runtime update. No application code change, no config rewrite. The practical steps depend on how you deploy.
Debian and Ubuntu
sudo apt update
sudo apt install --only-upgrade php8.3-fpm php8.3-cli
sudo systemctl restart php8.3-fpm
php-fpm8.3 -vRHEL, Alma, Rocky, Amazon Linux
sudo dnf clean all
sudo dnf update php-fpm php-cli
sudo systemctl restart php-fpm
php-fpm -vContainers
Updating the package inside a running container does not survive the next deploy. Rebuild the image on a fixed base, then roll out new containers. Confirm the running container reports the fixed version, not just your Dockerfile:
docker build --no-cache -t your-app:php2shell-fix .
docker run --rm your-app:php2shell-fix php -v | head -1
# then roll out and verify against a live instance
kubectl rollout restart deployment/your-appShared hosting and managed platforms
On shared or managed hosting you usually cannot patch the binary yourself. Ask the provider for the exact PHP version serving your site and the date it was updated, and switch the site to a supported branch if the panel still offers an end-of-life version. Do not accept a vague "we handle updates" answer. Ask for a version number.
Step Three: Reduce Exposure While You Plan
The PHP team was explicit: these measures reduce specific attack paths, they do not make a server safe. Use them to buy time, not to replace the update. Apply them in configuration that loads before the request is parsed, so in php.ini, an FPM pool, or the web server config, never with ini_set() or a .user.ini file.
For CVE-2026-109618, the memory exhaustion path
; php.ini or FPM pool, admin level so apps cannot override
memory_limit = -1
post_max_size = 8M
upload_max_filesize = 6M- Setting
memory_limit = -1at administrator level removes the controllable failure. Check that no pool, directory or application setting puts a finite limit back. - Keep
post_max_sizesmall. 8M if you do not accept uploads, 32M if you do, and always keepupload_max_filesizebelow it. - Enforce decoded request size, field count, multipart part count and nesting depth at the proxy level too, and make sure those limits also cover requests without a usable
Content-Lengthheader. - Use container or cgroup memory limits as an availability boundary so one worker dying does not take down the host. Do not use
RLIMIT_ASorRLIMIT_DATA, which can trigger the very allocation failure the attack needs.
For CVE-2026-109619, the stale state path
; PHP 8.2 to 8.5
session.upload_progress.enabled = 0
; PHP 8.5 only
fatal_error_backtraces = 0- Both settings must be in configuration, then every worker restarted.
- Remove per-request settings that emit diagnostics before initialisation, and fix fatal errors and execution timeouts that an attacker can trigger, including in callbacks, upload handling and cleanup code.
- Hiding error messages with
error_reporting,display_errorsor@does nothing here. The attacker does not need to see the error to use the stale state.
At the edge
Controls in front of PHP stop malicious requests before they reach the vulnerable parser. A WAF, a reverse proxy with strict request body limits, or simply not exposing internal PHP apps to the internet all help. CERT-EU's advice for teams that cannot patch immediately was to isolate internet-facing applications. If you have an admin panel, a staging site, or a forgotten internal tool on a public IP, take it off the internet today rather than trusting a rule set.
Step Four: Check Whether You Were Already Hit
The patch is only half the job. Exploitation attempts started before disclosure, so treat every PHP host that was unpatched as potentially compromised and look for evidence.
What to look for
- Oversized form submissions or a burst of POST requests with very large bodies, especially multipart requests with huge numbers of fields.
- Memory exhaustion errors during request initialisation in logs, in patterns rather than one-offs.
- Worker crashes and restarts clustered together, or FPM pool logs showing repeated child termination.
- Unexpected shell processes spawned by the web server user, or outbound connections you cannot explain. Successful execution does not always crash the worker, so silence is not proof of safety.
- New or modified files in upload directories,
/tmp, or anywhere the web user can write. Compare against a known-good snapshot if you have one.
Quick log checks
# Memory exhaustion during bootstrap
grep -Ei 'allowed memory size.*exhausted' /var/log/php*fpm*.log | tail -50
# Large POST bodies in nginx access logs
awk '($9 == 200 || $9 == 500) && $10 > 5000000 {print}' /var/log/nginx/access.log | tail -50
# Processes spawned by the web user
ps -eo user,pid,ppid,cmd | grep -E 'www-data|nginx|apache' | grep -Ev 'php-fpm|nginx:|apache2'
# Recently changed files in web roots
find /var/www -type f -newermt '-7 days' -printf '%T+ %p\n' | sort -r | head -40If any of those return something you cannot explain, treat it as an incident rather than a curiosity: rotate credentials and secrets held on that host, review database and API access, and preserve logs before you clean anything up.
How to Prevent the Next PHP2Shell
You cannot code your way out of a runtime flaw. What you can do is make sure a runtime flaw never becomes a multi-day outage for you. These are the habits that separate teams who patched in an hour from teams who spent a week firefighting.
1. Know exactly what runtime you run, everywhere
Maintain an inventory that includes the PHP version, the package or image source, the owner, and the update process for every deployment. Include containers, build images and anything bundled by a vendor. If you cannot answer "what PHP version serves each of my sites" in five minutes, that is the gap to close first.
2. Subscribe to the sources that matter
The PHP news archive, the php-src security advisories on GitHub, your distribution's security tracker, and CERT-EU or your national CERT. During PHP2Shell, the leak on social media moved faster than the official announcement, so having a channel that surfaces early signals is worth the noise.
3. Keeping current is a process, not a project
Unpatched PHP branches are the root cause of most long-term exposure. If a site is still on 7.4 or 8.1, it has already missed years of fixes. Set a policy that supported versions are the only ones allowed in production, and give old apps a paid migration deadline rather than an open-ended exemption.
4. Patch the layer below your app too
This vulnerability was not in WordPress, Laravel or Symfony. It was in the interpreter underneath all of them. Framework updates are necessary but not sufficient. Your patch cadence has to cover the OS, the runtime, the web server and the container base image.
5. Limit the blast radius by default
- Give PHP-FPM pools their own user, and never let the web user write to application source directories.
- Run each site in its own container or pool with memory limits, so a crashed worker is an isolated event.
- Keep database, payment and API credentials scoped and rotatable. Assume a shell on the host.
- Put staging and internal tools behind authentication at the edge, or keep them off the public internet entirely. Half the risk in incidents like this sits in forgotten environments.
- Keep PHP's disabled functions list tight:
exec,shell_exec,system,passthru,proc_openandpopenare rarely needed by a typical web app.
6. Rehearse the rollback
The difference between patching in an hour and patching in a week is usually not the update itself, it is confidence. Have a tested path to roll back an image or package, and have a staging environment that mirrors production closely enough that a runtime bump on a Tuesday afternoon is routine instead of terrifying.
7. Watch for anomalies, not just errors
The tell for CVE-2026-109618 is not a stack trace, it is a shape: bursts of large POSTs, clustered memory exhaustion, workers dying and restarting together. Alert on those patterns. Most teams have plenty of error logging and almost no behavioural alerting, which is exactly the blind spot an exploit that works during request teardown exploits.
The Bottom Line
PHP2Shell is one of the more serious PHP issues in years because it sits below every framework, bypasses application authentication, and was exploited before the fix was even published. The response is straightforward, and it is not optional:
- Check the PHP version that actually serves your requests, on every environment, including containers and forgotten internal tools.
- Patch to 8.2.35, 8.3.36, 8.4.27 or 8.5.12, then restart every worker and verify from a live request.
- Apply the interim settings for
memory_limit,post_max_sizeandsession.upload_progress.enabledon anything you cannot patch today, and treat it as a countdown, not a solution. - Hunt for evidence of exploitation on every host that was unpatched, and preserve logs before cleaning up.
- Close the underlying gap with an inventory, a supported-version policy, and alerting on request anomalies.
If you would rather not do that triage alone, this is routine work for us. Laurel Jar handles PHP runtime upgrades, server hardening and incident checks across AWS, DigitalOcean, VPS and shared hosting, and we can tell you within an hour whether your fleet is exposed.
Further reading: the official PHP security advisory at php.net/archive/2026, the advisories GHSA-w8hw-w9fx-j79w and GHSA-6wmf-4rv5-p3rp, and the NVD entries for CVE-2026-109618 and CVE-2026-109619.

