To fix PHP2Shell, update PHP to 8.5.12, 8.4.27, 8.3.36 or 8.2.35, or to your Linux distribution's patched package, then restart Apache and every PHP-FPM worker. Confirm the fix with your package manager, not php -v. The two bugs, CVE-2026-109618 and CVE-2026-109619, allow unauthenticated remote code execution and were fixed on October 21, 2026.
The hard part is that the flaw lives in the PHP runtime, below WordPress, Laravel or whatever runs on top of it, so updating the application changes nothing. Distribution builds also keep their old version numbers: a fully patched Ubuntu 24.04 server still prints PHP 8.3.6. W3Techs reported on October 22, 2026 that PHP runs 69.3% of websites whose server-side language it can identify, and that 34.6% of those PHP sites are still on version 7 or 5, with no upstream fix at all.

What PHP2Shell Does Inside a PHP Worker
Both bugs turn one failed request into a weapon against the next request the same worker handles. The php-src advisory for CVE-2026-109618 explains that PHP aborts a request with longjmp() when it runs out of memory, and if that happens while request memory is only partly initialized, the shutdown code later treats it as valid. An attacker gets there with a crafted multipart upload, such as one part carrying an unbounded number of headers. The advisory for CVE-2026-109619 covers the other half of the pattern: state such as a PHP 8.5 fatal-error backtrace or session upload-progress data outlives the request that owned it, and the next request reads freed memory.
Where PHP runs decides how exposed it is. On Apache with mod_php, the out-of-memory path fires during request startup, before any application code runs, and the advisory reports full remote code execution with default settings. PHP-FPM kills a child whose startup fails, which blocks that exact sequence, but the PHP Foundation's October 21 post says remote code execution was still confirmed on FPM through application-level code paths. That difference is why Red Hat rates CVE-2026-109618 Important with a CVSS v3 score of 8.1 (RHEL defaults to FPM), while NVD carries php.net's CVSS 4.0 score of 9.2, Critical.
Wiz Research built the exploit chain, which it calls php2shell, with Gemini 4 Argon and GPT-5.6 Sol, and demonstrated unauthenticated code execution against the wordpress:latest, drupal:latest and joomla:latest container images. php2shell.com also credits OpenAI Codex Security and Calif. Wiz says one researcher finished in a week what would previously have taken a team months. The bug was not new: the PHP Foundation says a core developer found its first signs in 2024 while fixing a Quarkslab audit finding, and that the fix then stalled for months, which it calls "a structural failure." As of CISA's catalog released October 21, 2026, neither CVE is on the Known Exploited Vulnerabilities list.
The Fastest Way to Find Your PHP Sites
Start with an inventory, because PHP often runs where nobody remembers it, like an old WordPress marketing site. Run each domain through the Website Technology Detector, which flags PHP from response headers and WordPress, Drupal and Joomla from page markup, then open the HTTP Response Headers Checker and read the X-Powered-By header. Our test container answered with X-Powered-By: PHP/8.3.36.
Treat a missing header as unknown, not safe. php.net, wordpress.org and drupal.org all hid it when we checked on October 22, 2026, and even a visible version cannot tell a patched distribution build from an unpatched one.
Check the Version That Actually Runs
This script compares the running build with the first fixed release of its branch and prints the settings the php.net advisory uses as stopgaps:
php<?php // Compares this PHP build with the first upstream releases that fix // CVE-2026-109618 and CVE-2026-109619, then prints the settings the // php.net advisory uses as stopgaps. $fixed = ['8.2' => '8.2.35', '8.3' => '8.3.36', '8.4' => '8.4.27', '8.5' => '8.5.12']; $branch = PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION; echo 'PHP ' . PHP_VERSION . " ({$branch} branch, " . PHP_SAPI . ")\n"; if (!isset($fixed[$branch])) { echo "No upstream fix for {$branch}: upgrade, or get a backport from your vendor.\n"; } elseif (version_compare(PHP_VERSION, $fixed[$branch], '>=')) { echo "Upstream fix: yes, {$fixed[$branch]} or later\n"; } else { echo "Upstream fix: not by version number (needs {$fixed[$branch]}).\n"; echo "If PHP came from apt or dnf, check the package changelog instead.\n"; } foreach (['memory_limit', 'post_max_size', 'session.upload_progress.enabled', 'fatal_error_backtraces', 'expose_php'] as $key) { $value = ini_get($key); printf("%-32s %s\n", $key, $value === false ? 'n/a' : var_export($value, true)); }
On Ubuntu 24.04 with php8.3-cli installed from the security archive, it reports a miss even though the package is patched:
textPHP 8.3.6 (8.3 branch, cli) Upstream fix: not by version number (needs 8.3.36). If PHP came from apt or dnf, check the package changelog instead. memory_limit '-1' post_max_size '8M' session.upload_progress.enabled '1' fatal_error_backtraces n/a expose_php '1'
The official php:8.3-apache image, pulled on October 22, 2026, passes:
textPHP 8.3.36 (8.3 branch, cli) Upstream fix: yes, 8.3.36 or later memory_limit '128M' post_max_size '8M' session.upload_progress.enabled '1' fatal_error_backtraces n/a expose_php '1'
The Ubuntu miss is a false alarm, explained next. Also note that the CLI reads its own php.ini: on Ubuntu, /etc/php/8.3/cli/php.ini sets memory_limit = -1, while /etc/php/8.3/apache2/php.ini and /etc/php/8.3/fpm/php.ini set 128M. To see what web requests get, run the script through the web server once, then delete it.
Patch Ubuntu and Debian With apt
Ubuntu and Debian backport the fix into their own packages, so the version to compare is the package version on the Ubuntu and Debian trackers, as of October 22, 2026:
| Release | Package | First fixed version |
|---|---|---|
| Ubuntu 26.04 LTS | php8.5 | 8.5.4-0ubuntu1.5 |
| Ubuntu 24.04 LTS | php8.3 | 8.3.6-0ubuntu0.24.04.13 |
| Ubuntu 22.04 LTS | php8.1 | 8.1.2-1ubuntu2.28 |
| Debian 13 trixie | php8.4 | 8.4.27-1~deb13u1 |
| Debian 12 bookworm | php8.2 | 8.2.35-1~deb12u1 |
On Ubuntu 24.04, upgrade the PHP packages, confirm the CVE patch is in the changelog, and restart whatever serves PHP:
Bashsudo apt update sudo apt install --only-upgrade 'php8.3*' libapache2-mod-php8.3 zgrep -c 'CVE-2026-109618' /usr/share/doc/php8.3-common/changelog.Debian.gz sudo systemctl restart apache2 # or: sudo systemctl restart php8.3-fpm
On the patched package the zgrep count prints 2; zero means the fix is not installed, and "Skipping" notes from the wildcard are harmless. The restart is not optional: Apache and FPM keep running the old code until they restart.
In a fresh 24.04 container, apt-get install -s php pulls in apache2 and libapache2-mod-php8.3: a plain apt install php gives you mod_php, the setup the advisory rates most exposed.
Patch Docker Images by Rebuilding
The official php images on Docker Hub already carry the fixed releases: the 8.5.12, 8.4.27, 8.3.36 and 8.2.35 tags were pushed between 00:58 and 01:05 UTC on October 22, 2026. A floating tag such as php:8.3-apache only helps after you pull it and rebuild, because Docker reuses the cached copy:
Bash# pull the patched base image, rebuild, then restart docker compose build --pull docker compose up -d
In our test project they produced a container reporting PHP 8.3.36. Pin the exact release (FROM php:8.3.36-apache) if you want the Dockerfile itself to show which PHP ships; the Dockerfile Linter flags :latest and untagged base images that hide it.
Patch RHEL, AlmaLinux and Rocky Linux With dnf
Red Hat's bulletin RHSB-2026-013 lists RHEL 8, 9 and 10 as affected and, as of October 22, 2026, says mitigation guidance is still being prepared. Ask dnf whether a fixed package has reached your repositories, then upgrade:
Bashsudo dnf updateinfo list --cve CVE-2026-109618 sudo dnf upgrade 'php*'
On a fresh AlmaLinux 9 container on October 22, 2026, the first command returned nothing, which means no advisory yet. Until one lands, apply the stopgap below. php.net also warned that the volunteer-run rpms.remirepo.net would not have updates on release day.
If You Cannot Patch Today
Only the update is a complete fix, php.net says. The settings below cut off the demonstrated attack paths and must be set where applications cannot override them. For Apache with mod_php, drop this into /etc/apache2/conf-enabled/php2shell-stopgap.conf:
apache# Stopgap until PHP is patched: not a fix. From the php.net advisory for # CVE-2026-109618 and CVE-2026-109619. php_admin_* cannot be overridden by apps. <IfModule php_module> php_admin_value memory_limit -1 php_admin_value post_max_size 8M php_admin_flag session.upload_progress.enabled off </IfModule>
For PHP-FPM, add the same three settings to the pool, here [www]:
ini; Stopgap until PHP is patched: not a fix. Applies to the [www] pool. [www] php_admin_value[memory_limit] = -1 php_admin_value[post_max_size] = 8M php_admin_flag[session.upload_progress.enabled] = off
We tested both against the official 8.3 images with a script that calls ini_set('memory_limit', '256M'). Before the change the script raised its own limit to 256M. After a restart, through mod_php and through FPM alike, ini_set() printed bool(false) and the request reported memory_limit=-1 upload_progress=0.
ini_set() returning false is the point: php_admin_value locks the setting. memory_limit=-1 removes the predictable out-of-memory failure that CVE-2026-109618 relies on, so cap memory with a container or cgroup memory.max limit instead, and never with RLIMIT_AS or RLIMIT_DATA, which the advisory says trigger the same vulnerable path. Turning off session.upload_progress blocks the confirmed session route of CVE-2026-109619; on PHP 8.5, also set fatal_error_backtraces=0. Restart every worker after each change, and fix any INI deprecation warning that an .htaccess file or an nginx PHP_VALUE applies per request, because the advisory names those warnings as a trigger.
Common Errors and Warning Signs
php -v still prints 8.3.6 after apt upgrade. That is expected on Ubuntu, where the fix is backported into the 8.3.6 package. The changelog check above, or dpkg-query -W php8.3-common showing version 8.3.6-0ubuntu0.24.04.13, is the real answer.
Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate ... bytes) arriving in bursts. Running out of memory is how both exploit chains start, so a cluster of these from one client, especially on multipart POST requests followed by HTTP 500 responses, deserves a look. Wiz also lists unusually large or malformed PHPSESSID response cookies and unexplained PHP worker churn as signals. Paste access logs into the Nginx Log Analyzer, which reads Apache logs too, to see status codes and top client IPs side by side.
docker compose up -d still runs the old PHP. Compose reuses the local image. Run docker compose build --pull or docker compose pull first.
When Not to Rely on This
Do not treat the stopgap as a fix, and do not treat a clean log as proof: the advisory warns that the trigger can fire before PHP writes any message. PHP 8.1 and older get no fix from the PHP project. php.net published minimal source backports for 7.4, 8.0 and 8.1, and vendors such as Ubuntu Pro cover older packages, but the durable answer is a supported branch.
Conclusion
PHP2Shell is fixed on the server, not in WordPress, Laravel or composer.json. Install the patched PHP build, restart every process that serves PHP, and verify the fix against the package changelog or the fixed release number, never against what php -v prints. Until then, the php_admin_value stopgap cuts off the demonstrated attack paths, but php.net is clear that only the update is a complete fix.
If you only do one thing today: on Ubuntu or Debian, run the apt upgrade and restart; on Docker, rebuild with --pull; on RHEL-family servers with no advisory yet, apply the stopgap and recheck dnf updateinfo tomorrow; on managed hosting, ask your host which PHP build your site runs and when it was restarted.
Then plan the upgrades this release cannot reach. PHP 8.1 and older get no fix from the PHP project, and W3Techs counts 34.6% of identifiable PHP sites still on version 7 or 5. A server on one of those branches needs a vendor backport now and a supported release soon.
Related DevToolLab Tools
- Website Technology Detector - find which of your domains run PHP, WordPress, Drupal or Joomla before you start patching servers one by one.
- HTTP Response Headers Checker - read the
X-Powered-Byheader a site sends, and confirm it disappears after you setexpose_php = Off. - Nginx Log Analyzer - group access-log lines by status code and client IP to spot bursts of HTTP 500 responses on upload endpoints.
- Dockerfile Linter - catch
:latestand untaggedphpbase images so a rebuild ships the PHP release you expect.
