Security
9 min read

PHP2Shell: How to Fix the Critical PHP Flaws and Prevent the Next Attack

Two remote code execution bugs in PHP itself were patched on 21 October 2026. They reach the vulnerable code before your framework boots, which means login screens and firewalls do not save you. Here is how to check, patch, and harden.

Laurel Jar

Laurel Jar

Oct 23, 2026
Share:
PHP2Shell: How to Fix the Critical PHP Flaws and Prevent the Next Attack

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.

⚠️
Short version: update PHP to 8.2.35, 8.3.36, 8.4.27 or 8.5.12 today. There is no complete mitigation. Everything else in this post reduces your exposure while you plan the upgrade, but none of it makes an unpatched server safe.

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 $_POST array, 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.

9.2
CVSS 4.0 severity, critical
5.4 to 8.5
Affected PHP branches
Oct 17
Exploitation observed before public disclosure

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 | head

For 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"
done

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

BranchAffectedFirst fixed releaseStatus
PHP 8.5before 8.5.128.5.12Supported
PHP 8.4before 8.4.278.4.27Supported
PHP 8.3before 8.3.368.3.36Supported
PHP 8.2before 8.2.358.2.35Supported
PHP 8.1 and olderall releasesSource backports onlyEnd of life
PHP 7.4, 8.0 and 8.1 do not get official binaries anymore, but the PHP security team did publish minimal source backports. If you are stuck on one of those branches, this is the moment to move to a supported version rather than carry a hand-patched runtime forever.

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 -v

RHEL, Alma, Rocky, Amazon Linux

sudo dnf clean all
sudo dnf update php-fpm php-cli
sudo systemctl restart php-fpm
php-fpm -v

Containers

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-app
💡
Restart is part of the patch. PHP keeps the old binary in memory for the life of a worker. If you update packages but never reload PHP-FPM, Apache or the container, you are still running vulnerable code. After restarting, verify the version from a live request, not just from the shell.

Shared 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 = -1 at administrator level removes the controllable failure. Check that no pool, directory or application setting puts a finite limit back.
  • Keep post_max_size small. 8M if you do not accept uploads, 32M if you do, and always keep upload_max_filesize below 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-Length header.
  • Use container or cgroup memory limits as an availability boundary so one worker dying does not take down the host. Do not use RLIMIT_AS or RLIMIT_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_errors or @ 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 -40

If 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_open and popen are 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:

  1. Check the PHP version that actually serves your requests, on every environment, including containers and forgotten internal tools.
  2. 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.
  3. Apply the interim settings for memory_limit, post_max_size and session.upload_progress.enabled on anything you cannot patch today, and treat it as a countdown, not a solution.
  4. Hunt for evidence of exploitation on every host that was unpatched, and preserve logs before cleaning up.
  5. 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.

Laurel Jar

About Laurel Jar

Laurel Jar is an innovation-led technical solutions provider helping growth brands leverage AI, automation, and cloud infrastructure to scale efficiently. We specialize in AI automation, API integration, cloud technology, and digital growth strategies.

Ready to implement these strategies?

Get Expert Advice