DEV Community

Cover image for PHP2Shell: The Critical PHP RCE Vulnerability (CVE-2026-109618, CVE-2026-109619) and How to Fix It
Muhammad Saud
Muhammad Saud

Posted on Originally published at logicstack.org

PHP2Shell: The Critical PHP RCE Vulnerability (CVE-2026-109618, CVE-2026-109619) and How to Fix It

PHP2Shell is the name for two critical vulnerabilities in PHP itself, CVE-2026-109618 and CVE-2026-109619, that let an unauthenticated attacker run code on a web server while PHP is still parsing the request, before any application code executes. Both flaws carry a CVSS v4.0 score of 9.2, emergency patches shipped on 21 October 2026, and the only complete fix is updating PHP.

Key takeaways

  • Both flaws sit in the PHP engine, not in WordPress, a plugin, or your code. Updating your CMS does not fix them.

  • Fixed versions: PHP 8.2.35, 8.3.36, 8.4.27 and 8.5.12, released 21 October 2026. Restart PHP-FPM or Apache after upgrading.

  • Every web-serving setup is in scope: PHP-FPM, Apache mod_php (highest risk), php-cgi, FrankenPHP and LiteSpeed, on versions going back to PHP 5.4.

  • PHP 8.1 and older get no official patched release. CISA's advice for systems that cannot be patched is blunt: take them offline.

  • Exploitation attempts were logged before the patch existed, so patch first, then check your logs back to at least 17 October.

What exactly is broken?

Two separate memory-safety bugs, one shared trait: both corrupt PHP's internal state while a request is being handled, and both can be reached by an attacker who sends a crafted request to a public website. No login, no user interaction, and the code runs with the web server account's permissions.

CVE-2026-109618: corruption when PHP runs out of memory

When PHP exhausts its memory limit in the middle of a request, its emergency bailout mechanism (zend_bailout, built on longjmp) can interrupt request handling at a moment when internal request data is only partly initialised. One confirmed path runs through session upload progress tracking while PHP parses a multipart form upload. Cleanup code then treats that half-built data as if it were valid. An attacker who controls the request can shape that invalid data and steer execution into running their own code.

CVE-2026-109619: stale state left behind after a request

The second flaw is a use-after-free. Some internal state is owned by a single request, but it can survive after the memory behind it has been freed. PHP workers are long-lived processes that handle many requests in sequence, so a later request on the same worker can pick up that leftover state before it is reset, and end up trusting memory the attacker now controls. The result is the same: remote code execution without authentication.

The detail that matters most is where this happens. Both bugs fire during request parsing and handling, before WordPress, Laravel, or your own code starts. A security plugin cannot help you here, because the plugin and its firewall load after the vulnerable step has already run.

Which PHP setups are affected?

PHP's advisory covers all installations serving web requests: PHP-FPM, Apache with mod_php, php-cgi, FrankenPHP and LiteSpeed. The project rates mod_php as the highest risk, because a demonstrated exploit there is triggered by an unauthenticated request before any application code runs. Researchers at Calif and Wiz traced the vulnerable code back to PHP 5.4, released in 2012, so any unpatched branch from the last fourteen years should be treated as exposed.

The scale is not small. OX Security, working from Shodan scan data, counts roughly two million PHP servers reachable on the public internet. Because the flaw is in PHP itself, every WordPress, Drupal, Joomla and Magento site inherits the risk from the PHP version underneath it, regardless of how well the site itself is maintained. Several government websites were taken offline as a precaution while their operators patched, according to reporting by The Register.

Is PHP2Shell being exploited right now?

Attempts started before the fix existed. Amazon's threat intelligence team logged an exploitation attempt against its MadPot honeypots on 17 October, days before the details leaked on 20 October. watchTowr's sensor network reports exploitation attempts, Cloudflare reports early attacks and has enabled WAF rules for the flaws across its plans, and VulnCheck sensors recorded an attempt to drop a webshell through a server-side session file, one of the confirmed paths for CVE-2026-109618.

The honest caveat: as of 22 October, no confirmed compromise of a named organisation had been publicly reported, and neither CVE had been added to CISA's Known Exploited Vulnerabilities catalogue. That is not a reason to wait. The technical details leaked a day before the patches, the patches themselves show attackers exactly where to look, and CISA's guidance is to patch immediately or take unpatched PHP systems offline until they can be updated.

How do I check which PHP version my site actually runs?

Start on the command line:

php -v
Enter fullscreen mode Exit fullscreen mode

Treat that answer with care. The command-line PHP can be a different build, and sometimes a different version, from the PHP that serves your website. Confirm through the web server itself with a temporary phpinfo page:

<?php phpinfo();
Enter fullscreen mode Exit fullscreen mode

Give the file a random name, read the version at the top of the page, then delete the file immediately. A phpinfo page left behind hands attackers your full server configuration. One more trap: Linux distributions backport security fixes without changing the upstream version number, and Ubuntu 24.04's patched PHP package still reports 8.3.6. If your PHP comes from a distribution or a host, compare the full package version against your vendor's security advisory, not just the table below.

What is the fix for PHP2Shell?

Update to the fixed release for your branch, or any newer release in that branch:

  • PHP 8.2: update to 8.2.35

  • PHP 8.3: update to 8.3.36

  • PHP 8.4: update to 8.4.27

  • PHP 8.5: update to 8.5.12

Then restart the workers. The old PHP processes keep running the vulnerable code until they are replaced, so an upgrade without a restart protects nothing:

sudo systemctl restart php8.3-fpm
sudo systemctl restart apache2
Enter fullscreen mode Exit fullscreen mode

Adjust the service name to your branch. On mod_php, the Apache restart is the one that matters. In containers, pull the fixed image tag (for example php:8.4.27-fpm), rebuild, redeploy, and verify the running container reports the new version. Afterwards, verify through the web server using the phpinfo check again, and delete that file when you are done.

What if I cannot patch today?

Three stopgaps, in order of strength. None of them is a fix.

Take it off the public internet. If the server cannot be patched today, CISA's stated fallback is to take PHP-based systems offline, or put them behind a VPN or an IP allowlist, until the update lands. It is disruptive, and it is the only stopgap with no known bypass.

Turn on WAF rules. Cloudflare has enabled PHP2Shell rules across its plans, including the free tier, and other WAF vendors ship equivalents. Rules blunt the known attack patterns, but attackers mutate payloads, and a WAF does nothing if your origin server can be reached directly. Treat this as buying hours, not safety.

Disable session upload progress, knowing its limits. This setting in php.ini closes one confirmed attack path linked to CVE-2026-109619:

session.upload_progress.enabled = 0
Enter fullscreen mode Exit fullscreen mode

Restart PHP after changing it. Be plain about what it does not do: it does not fix CVE-2026-109618, it does not cover every path into CVE-2026-109619, and the PHP project itself states that updating is the only complete fix.

How do I check whether my server was already hit?

Because attempts predate the patch, patching alone is not the end of the job for any server that sat public and unpatched. Work through this list:

  • Review access logs back to at least 17 October. The pattern that VulnCheck and Censys flag is a large multipart POST request carrying a PHP_SESSION_UPLOAD_PROGRESS field alongside a PHPSESSID cookie. A useful starting grep:
grep "PHP_SESSION_UPLOAD_PROGRESS" /var/log/apache2/access.log
Enter fullscreen mode Exit fullscreen mode
  • Inspect the PHP session storage directory (your session.save_path) for unexpected files, and check the web root and upload folders for new or recently modified .php files.

  • Watch the worker processes. Look for PHP worker processes (www-data, php-fpm) spawning shells or download tools such as sh, curl or wget. Healthy PHP workers do not do this.

  • Check persistence mechanisms: cron entries, scheduled tasks and user accounts for anything you did not create.

If you find signs of compromise, do not stop at patching. Preserve the logs, rebuild the server from a known-good state, and rotate every credential that lived on the box: database passwords, API keys and admin accounts.

Still running PHP 8.1 or older?

There is no official patched release for your branch. PHP 8.1, 8.0 and 7.4 are end of life; the PHP security team published minimal source backports for those three branches for people who build PHP themselves, and some distribution and LTS vendors ship patched packages of their own. PHP 7.3 and older get nothing. A backport only counts if the vendor confirms it covers CVE-2026-109618 and CVE-2026-109619 specifically, so ask for the fixed package version in writing. Until you are on a confirmed patched build, keep the system off the public internet where you can, and put the branch upgrade to PHP 8.3 or newer at the top of the list. That upgrade was already overdue; PHP2Shell just removed the last excuse for postponing it.

PHP2Shell is the rare vulnerability that matches its headlines: it lives in the interpreter, it needs no login, and people were probing for it before the fix existed. The response is just as simple. Check the version your site actually serves, move to the fixed release, restart the workers, then spend an hour in the logs. Everything else is a way to buy time until you do.


Originally published on LogicStack. Run the code examples right in your browser with our free interactive Python and SQL playgrounds — no setup, no sign-up.

Top comments (0)