In Bangladesh? See BDIX VPS and hosting
Hosting Insights

php2shell: Critical PHP RCE (CVE-2026-109618/109619) and How We Patched Our Servers

SA

Shehab Ahmed

Systems Engineer, TOSHOST · Oct 22, 2026 · 11 min read

php2shell: Critical PHP RCE (CVE-2026-109618/109619) and How We Patched Our Servers
Keep your site up when it is attacked See DDoS-protected plans →

Short answer: PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35 fix two critical, unauthenticated remote code execution flaws, CVE-2026-109618 and CVE-2026-109619 (CVSS 9.2). Update the package, then reload PHP-FPM or restart the web server. PHP 8.1 and older receive no fix.

Last updated: October 23, 2026.

Update, October 23: PHP has disclosed a third issue, CVE-2026-115560. It is separate from the two fixed on October 21 and is not fixed by 8.2.35, 8.3.36, 8.4.27 or 8.5.12. PHP says it only affects applications that switch session.serialize_handler to php at runtime, rates the risk as unlikely, and recommends setting session.upload_progress.enabled=0 until the next routine release. See the update below. Also: PHP 8.4.27 and 8.5.12 have a crash regression with SimpleXML (details and fixes).

On October 21, 2026 the PHP project shipped a security release for two critical flaws, CVE-2026-109618 and CVE-2026-109619, nicknamed "php2shell". Both are rated CVSS 9.2 and both can lead to unauthenticated remote code execution. We run PHP on a lot of servers, so we started patching within a day and wrote down what actually happened. This is the field report, including the parts that did not go smoothly.

What are CVE-2026-109618 and CVE-2026-109619?

  • CVE-2026-109618: if a request hits memory_limit at the wrong moment, PHP can leave half-initialised internal data behind and later treat it as valid. That corrupts memory. On Apache with mod_php an unauthenticated request can trigger it before any application code runs. On PHP-FPM, code execution was demonstrated through application-level code paths.
  • CVE-2026-109619: a fatal error at the wrong point can leave internal state pointing at memory from a previous request. A later request on the same worker can then use freed memory.

Fixed versions: 8.5.12, 8.4.27, 8.3.36 and 8.2.35. PHP 8.1 and older get no fix from the PHP project. The PHP Foundation says it saw no working public exploit at release time, but proof-of-concept material leaked around the announcement, so treat the window as short. Sources: the PHP Foundation release post and the php-src advisories GHSA-w8hw-w9fx-j79w and GHSA-6wmf-4rv5-p3rp.

CVE-2026-109618 OOM memory corruption and CVE-2026-109619 stale state use-after-free, both CVSS 9.2, fixed in PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35
The two php2shell flaws and the fixed versions.

Is there a workaround if you cannot update yet?

Partly. PHP's advisory for CVE-2026-109619 lists session.upload_progress.enabled=0 as a partial mitigation for the session code path, and says to restart every PHP worker afterwards. It also says this "does not mitigate the other lifecycle paths", and that updating is the only reliable fix. For PHP 8.5 installations that cannot update, the advisory also suggests fatal_error_backtraces=0. Treat both as a stopgap for servers where a fixed package is not available yet.

We first wrote that this workaround was unverified, because the PHP Foundation's announcement post does not mention it. That was wrong: it is in the advisory itself (GHSA-6wmf-4rv5-p3rp). We have corrected this page.

Step 1: find which PHP versions you really run

We audited our servers over SSH with a read-only script that prints every PHP binary and its version, including cPanel EasyApache (/opt/cpanel/ea-php*) and CloudLinux alt-php (/opt/alt/php*) builds, not just the one on $PATH. The first finding: no server we could reach had a fixed version yet, and many hosting servers carry five or six PHP versions side by side. The default php -v tells you very little. This loop lists every PHP binary on a cPanel, CloudLinux or standard server:

for p in $(command -v php) /opt/cpanel/ea-php*/root/usr/bin/php /opt/alt/php*/usr/bin/php; do
  [ -x "$p" ] && echo "$p: $($p -v | head -1)"
done

Step 2: check that the fixed version exists in your repository

Refreshing package metadata costs nothing and installs nothing. When we checked on October 22, the Debian/Ubuntu packages from the Sury repository and cPanel's EA4 repository both already carried the fixed versions:

PHP branchFixed version
8.58.5.12
8.48.4.27
8.38.3.36
8.28.2.35
8.1 and olderNo fix from the PHP project

Step 3: how to patch PHP in order of risk

We went test systems first, then internal tools, then production, then customer hosting. On Debian and Ubuntu the safe pattern is to upgrade only the installed PHP packages, keep your existing config files, then reload FPM:

apt-get install -y --only-upgrade \
  -o Dpkg::Options::=--force-confold \
  $(dpkg -l | awk '/^ii +(php[0-9.]*|libapache2-mod-php[0-9.]*)(-|[ ])/{print $2}')
systemctl reload php8.4-fpm   # repeat per installed version

On cPanel EasyApache boxes:

yum -y update 'ea-php*'
systemctl restart ea-php84-php-fpm   # per installed version
# LiteSpeed: /usr/local/lsws/bin/lswsctrl restart   (graceful)
# Apache:    systemctl reload httpd

Our production site returned HTTP 200 before and after the upgrade. One trap to know about: if you run OPcache with validate_timestamps=0, PHP code is never re-read from disk. If you skip the FPM reload, the old code and the old binary keep running even though the package says it is upgraded.

Terminal showing php8.4 -v reporting PHP 8.4.27 and a successful php8.4-fpm reload
Verify the version, then reload FPM. Both steps matter.

What happened on our servers

  • Standard stacks were easy. On Debian, Ubuntu and cPanel EasyApache servers, the fixed packages installed cleanly. Afterwards each supported PHP version reported 8.2.35, 8.3.36, 8.4.27 or 8.5.12.
  • Vendor-modified stacks lagged. CloudLinux rebuilds EasyApache PHP in its own repository. At the time of writing its newest 8.2 build was still 8.2.34, and on some servers the update also stopped on a package conflict between CloudLinux-built and cPanel-built packages. We did not force that on live hosting. We are watching the vendor repository and will re-run the update when the fix lands.
  • Old branches get nothing. If you still run PHP 8.1 or older for any customer or application, no fix is coming. The only real answer is to move to a supported version.
  • Unreachable counts as unpatched. Some servers could not be reached from our admin machine during the run. Until we have confirmed each one, we treat it as unverified, not safe.

Lessons from doing this live

  1. Inventory beats speed. The version on $PATH is rarely the version your customers run.
  2. Package managers can hold you back. A fix being released is not the same as a fix being installable on your distribution.
  3. Vendor-modified stacks lag. If you run CloudLinux or similar, expect a gap after upstream. Plan for it.
  4. Reload is part of patching. An upgraded package with an unreloaded FPM pool is not patched.
  5. Verify with real content. A web server's default page can return 200 and look healthy. Our own first test did exactly that. We then confirmed real sites by response size and content from outside.
  6. Read the advisory, not just the news. We dismissed a workaround because the announcement post did not mention it. The advisory did. Check the primary advisory before you decide.

Your checklist

  1. List every PHP binary on every server, including EasyApache and alt-php.
  2. Check your repo for 8.2.35 / 8.3.36 / 8.4.27 / 8.5.12.
  3. Upgrade, then reload FPM or restart the web server.
  4. Confirm with php -v for each version, and check live sites from outside.
  5. Plan to retire PHP 8.1 and older.

Update: CVE-2026-115560, a third PHP issue

On October 23, two days after the emergency release, PHP published a short advisory for CVE-2026-115560. It is a separate session-handling issue. According to The Hacker News, an application is only exposed if it sets session.serialize_handler to php_binary or php_serialize, then switches it to php with ini_set() before session_start(), while session upload progress is still enabled. PHP considers this unlikely in practice and will fix it in its next routine release instead of an emergency one. No severity score or list of affected versions has been published yet, and no exploitation has been reported.

The recommended action is one line, and it is the same setting PHP already listed for CVE-2026-109619:

session.upload_progress.enabled=0

If you applied it on October 21 and left it in place, you need to do nothing more. After changing it, restart PHP-FPM or your web server so every worker picks it up. Patching to 8.2.35, 8.3.36, 8.4.27 or 8.5.12 is still required for the two critical flaws.

My site broke after the PHP update. How do I fix it?

Many site owners reported problems after October 21. Most "my site broke" reports have a small number of causes. Work through them in this order.

1. Confirm which PHP actually serves the site, then restart it

Installing a fixed package does not change running workers. Restart PHP-FPM (or your web server, or LiteSpeed's PHP) so the new version loads, and make sure the PHP shown in your shell is the one your site uses. On WordPress, Tools → Site Health → Info → Server shows the PHP version the site really runs. If you use OPcache, a reload also clears stale compiled code. Then clear any page or object cache plugin.

2. Workers crashing with "zend_mm_heap corrupted" (PHP 8.4.27 and 8.5.12 only)

This is a real regression, tracked as php-src#24488. In 8.4.27 and 8.5.12, a SimpleXML object caught in a reference cycle can be freed twice by the garbage collector, and the worker crashes. It needs the object's properties to be materialised first, for example through json_encode(), get_object_vars() or an (array) cast. Plain property access and foreach do not trigger it. PHP 8.3.36 and 8.2.35 are not affected, and 8.4.26 was not. WordPress importers, feed parsers and many plugins use SimpleXML, so this can show up as random 500 errors, blank pages or restarting workers. The WordPress hosting handbook has an open note about it.

The upstream issue was closed on October 22 with a fix, but at the time of writing the fix is not in a released version. Your options:

  • Move the affected site to PHP 8.3.36. It has the security fix and does not have this bug. This is the safest choice for WordPress sites.
  • Wait for the next patch releases (8.4.28 and 8.5.13 are the expected ones, but we have not seen release dates) and update again.
  • Do not roll back to an unpatched PHP. That removes the security fix and re-opens the two critical flaws.
  • A reported stopgap in the issue is zend.enable_gc=0. It stops the crash by turning off the garbage collector, so memory use can grow on long-running workers. Treat it as temporary and watch memory.
  • One reporter published a third-party extension that patches the bug. We did not install it. Review code like that before putting it on a production server.

To check whether you are hitting it, search the PHP-FPM and web server error logs for zend_mm_heap corrupted, double free or workers "exited on signal 11" or "signal 6". We scanned our own PHP-FPM, web server and system logs on servers running 8.4.27 and 8.5.12 and have not seen this signature so far.

3. Upload progress bars stopped working

If you disabled session.upload_progress.enabled as a mitigation, apps that show a live upload progress bar will stop showing it. Normal file uploads still work. WordPress core does not depend on the feature, but test any plugin with a custom upload progress bar. The setting must go in php.ini, a conf.d drop-in, .user.ini or (with mod_php) .htaccess, followed by a restart. It cannot be changed with ini_set() at runtime.

4. Still broken? Find the real error

  1. Read the PHP error log and the web server error log for the exact error and time.
  2. On WordPress, set define('WP_DEBUG_LOG', true); in wp-config.php, reproduce the problem, and read wp-content/debug.log. Turn it off again afterwards.
  3. With WP-CLI, run wp plugin deactivate --all on a copy of the site. If the error disappears, re-enable plugins one at a time.
  4. If you changed PHP branch at the same time (for example from 8.1 to 8.3), old plugins and themes may fail on newer PHP. Update them or ask the developer.

WordPress has confirmed that no WordPress core update is required for these PHP flaws. The fix belongs in PHP, so updating WordPress alone does not protect you.

Frequently asked questions

Which PHP versions fix CVE-2026-109618 and CVE-2026-109619?

PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35, released October 21, 2026. PHP 8.1 and older receive no fix from the PHP project.

Do I need to restart PHP after updating?

Yes. Reload PHP-FPM or restart the web server so running workers load the new binary. With OPcache validate_timestamps disabled, an unreloaded pool keeps running old code.

Is there a workaround if I cannot update yet?

Partly. PHP lists session.upload_progress.enabled=0 as a partial mitigation for the session path of CVE-2026-109619 and says updating is the only reliable fix.

Is CVE-2026-115560 fixed by the October 21 update?

No. It is a separate issue. PHP recommends setting session.upload_progress.enabled=0 until the fix ships in the next routine release.

My site crashed after updating to PHP 8.4.27 or 8.5.12. What should I do?

This matches a known SimpleXML regression (php-src#24488) that affects only 8.4.27 and 8.5.12. Move the site to PHP 8.3.36, or wait for the next patch release. Do not roll back to an unpatched version.

Does this affect PHP-FPM or only Apache mod_php?

Both. The PHP Foundation says the out-of-memory flaw can be triggered before application code runs on Apache with mod_php, and that code execution was also confirmed on PHP-FPM through application-level code paths.

Step-by-step guides: PHP2Shell for WordPress sites and how to patch any PHP site (Laravel, Docker, PHP-FPM).

Related guides: cPanel/WHM critical patch guide and nginx security updates 2026.

If you run PHP on shared or managed hosting and want help auditing your versions, talk to us.

Keep your site up when it is attacked

Always-on DDoS mitigation, hardened by default, with engineers on call 24/7.