Diego Betto
The word PHP cracked and struck by lightning on a dark blue background

Diego Betto · ·15 min di lettura

PHP2Shell: two critical PHP vulnerabilities, update now

PHP2Shell: two critical PHP vulnerabilities (CVE-2026-109618, CVE-2026-109619, CVSS 9.2) allowing unauthenticated RCE. Fixed in PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35: who is exposed, how to update and what to do if you can't.

Condividi:XLinkedInFacebookWhatsApp

On the PHP security team announced an out-of-band security release to fix two critical vulnerabilities, both rated CVSS v4.0 9.2. The community has already nicknamed the issue PHP2Shell, because the real risk is going from an HTTP request to a shell on the server: unauthenticated remote code execution.

⛔ The fix is out: update now

On , slightly ahead of the 14:00 UTC schedule, PHP released 8.5.12, 8.4.27, 8.3.36 and 8.2.35, which fix both vulnerabilities. According to the PHP team there are no complete mitigations: updating is the only safe choice.

Last updated: — working ASLR-bypass exploit (VulnCheck) and internet exposure data.

⛔ Update 3 November: ASLR bypassed, 2.6 million exposed hosts

VulnCheck has built a working exploit for CVE-2026-109618 that bypasses ASLR on x86-64 with production-like settings (display_errors=off), and has extended it to most pre-patch releases of 8.2, 8.3, 8.4 and 8.5, including default WordPress Docker installs. Its scan counts 2,608,039 internet-facing PHP hosts, practically all vulnerable or likely vulnerable. Details in working exploits and exposure.

⚠ Update 23 October: claimed bypass on 8.5.12

Claims of an RCE on the patched PHP 8.5.12 are circulating. PHP has confirmed a residual issue (CVE-2026-115560) that only hits a specific session configuration, and recommends keeping session.upload_progress.enabled=0 even after updating, until the next bugfix release. Updating is still mandatory. Details in the 8.5.12 bypass claims.

At a glance

Name PHP2Shell (unofficial nickname)
CVE CVE-2026-109618 (GHSA-w8hw-w9fx-j79w), CVE-2026-109619 (GHSA-6wmf-4rv5-p3rp)
Severity CVSS v4.0 9.2 for both
Impact Unauthenticated remote code execution (RCE)
Who is at risk Every PHP installation serving web requests: PHP-FPM, Apache mod_php, php-cgi, FrankenPHP, LiteSpeed
Fixed versions 8.5.12, 8.4.27, 8.3.36, 8.2.35
Unsupported versions No official release; source backports for 7.4, 8.0 and 8.1
Exploitation Early attempts reported by Cloudflare and VulnCheck (including a webshell plant attempt), working ASLR-bypass exploit held privately by VulnCheck, no confirmed compromise, not in CISA KEV
Exposure 2,608,039 internet-facing PHP hosts, 99.998% vulnerable or likely vulnerable (VulnCheck)
Residual issue CVE-2026-115560: narrow session-handler case, fix in next bugfix release; keep session.upload_progress.enabled=0 meanwhile

What the two vulnerabilities are

The official advisory describes two flaws in how PHP manages internal request state:

  • CVE-2026-109618 — RCE via OOM corruption: out-of-memory handling can interrupt PHP while internal request data is only partially initialized. Request cleanup may then treat the invalid data as valid.
  • CVE-2026-109619 — RCE via stale global state: request-owned internal state can survive after its backing memory has been freed. A later request handled by the same worker may use that stale state before normal initialization resets it.

The PHP team considers the risk highest for mod_php, with a demonstrated exploit where an unauthenticated request triggers the attack before any application code runs. In other words: it does not matter how well written your application is, the problem is in the interpreter.

Full technical details are in the GitHub advisories GHSA-w8hw-w9fx-j79w and GHSA-6wmf-4rv5-p3rp.

Working exploits and exposure

Early on, the bugs were considered practical only on systems without ASLR (the memory randomization that hides where code and data live). That is no longer true. VulnCheck has built an exploit for CVE-2026-109618 that works in two stages: first it leaks memory addresses from the vulnerable process, defeating ASLR, then it uses them to get remote code execution. The initial target was PHP 8.4.26 on x86-64 with production-like settings, including display_errors=off: hiding errors does not stop the leak.

As of , according to VulnCheck, the exploit supports (all with mod_php and default settings):

Branch Vulnerable releases supported Architectures
8.2 8.2.28 – 8.2.34 x86-64
8.3 8.3.22 – 8.3.35 x86-64
8.4 8.4.10 – 8.4.26 x86-64, ARM64
8.5 8.5.10 – 8.5.11 x86-64, ARM64

It also works against WordPress on default Docker installs running those versions. The full tool is only available to customers of VulnCheck’s Initial Access Intelligence service (since ), not publicly. Among the hundreds of “PoC” repositories on GitHub, VulnCheck says only a handful are real exploits; at least one public PoC bypasses ASLR, but only on PHP 8.4.26 x86-64 with the default heap configuration.

This is not theoretical: on VulnCheck’s honeypots already recorded scanning and exploitation attempts, including an attempt to plant a webshell via PHP_SESSION_UPLOAD_PROGRESS. Expect mass exploitation attempts whether or not a given payload works.

How many servers are exposed

VulnCheck’s internet scan gives an idea of the scale (it covers only detectable hosts, so it is a floor, not the total):

  • 2,608,039 internet-facing hosts run PHP, about 1.86% of ~140 million IPs answering on the web;
  • 2,607,985 (99.998%) are vulnerable or likely vulnerable: 1,474,985 advertise a version below the fixed release of their branch, and 1,133,000 hide the version and are counted as likely vulnerable;
  • over 1.1 million (about 42%) run end-of-life branches with no official fix: over 700,000 are on PHP 3.x to 7.3, and around 400,000 on 7.4, 8.0 and 8.1, which only have source backports;
  • by application: WordPress 469,699 hosts, Laravel 124,866, Joomla 26,591, Drupal 24,116.

ℹ️ Hiding the version does not protect you

More than a million hosts were counted as “likely vulnerable” precisely because they do not expose the PHP version. Turning off expose_php reduces noise, but attackers do not need to know your version to try: they simply send the payload.

The 8.5.12 bypass claims

Since , several posts on X have claimed a working RCE on PHP 8.5.12, the very release that was supposed to close PHP2Shell, and GitHub has filled up with “PoC” repositories. The quality of the evidence varies a lot: some is technical write-ups, some is a 34-second video with no explanation.

On the PHP team published an informational advisory that confirms a residual issue, tracked as CVE-2026-115560, but limits it to an unusual combination. All of these must be true:

  • the configuration sets session.serialize_handler to php_binary or php_serialize;
  • the application switches it to php at runtime with ini_set() before calling session_start();
  • session.upload_progress is still enabled.

Reports of the bypass also mention the need for a suitable “gadget” (for example a destructor) in the application code. PHP considers the scenario unlikely, so it will fix it in the next regular bugfix release, not with another emergency release, and will publish the full advisory then.

What to do:

  • Do not hold back the update: 8.5.12, 8.4.27, 8.3.36 and 8.2.35 still close the two critical vulnerabilities, which are exploitable with no special conditions.
  • Set session.upload_progress.enabled=0 in php.ini or in the FPM pool and restart the workers, even on patched versions, until the next release. If you already did it on 21 October, leave it in place.
  • Check whether your code or plugins change the serializer at runtime:
grep -rn "serialize_handler" /var/www /etc/php 2>/dev/null

⚠ Do not run PoCs you found on GitHub

During incidents like this one, fake “exploits” that actually contain malware for whoever runs them show up regularly. If you want to verify your exposure, use a test environment and code you have read line by line, never production.

Who is exposed

In practice: anyone running an internet-facing PHP application. Not just custom apps, but every popular CMS and e-commerce platform too:

  • WordPress, Joomla, Drupal
  • PrestaShop, Magento, WooCommerce
  • frameworks such as Laravel and Symfony
  • admin panels and tools written in PHP (phpMyAdmin, Roundcube, etc.)

⛔ Updating your CMS does not update PHP

This is the most common misconception: updating WordPress, plugins or themes does not change the PHP version on the server. They are two separate layers. Here the vulnerability lives in the PHP interpreter, so PHP itself needs to be updated (by you or by your hosting provider).

How to update

1. Take an inventory of your PHP versions

A single server often runs several versions (CLI, FPM, separate pools per site, containers). Check them all:

# CLI version
php -v

# all installed versions (Debian/Ubuntu)
ls /usr/bin/php* /usr/sbin/php-fpm* 2>/dev/null

# active FPM services
systemctl list-units --type=service | grep -i php

# PHP version inside running Docker containers
docker ps --format '{{.Names}}' | xargs -I{} sh -c 'echo -n "{}: "; docker exec {} php -r "echo PHP_VERSION, PHP_EOL;" 2>/dev/null || echo "-"'

On a WordPress site, the version actually used by the web server is also shown under Tools → Site Health → Info → Server.

2. Install the fixed version

The target is 8.5.12, 8.4.27, 8.3.36 or 8.2.35, depending on your branch. Linux distributions and LTS vendors received the patches in advance, so packages are already landing.

  • Shared or managed hosting: your provider applies the patch. Ask them when the fixed build will be live and make sure the PHP version selected in your panel (cPanel, Plesk, etc.) is a supported one.

  • Debian/Ubuntu with Ondřej Surý’s repository (packages.sury.org / ppa:ondrej/php): the fixed packages are already available.

    sudo apt update
    sudo apt install --only-upgrade 'php*'
  • Distribution packages (official Debian, Ubuntu, RHEL, etc.): these backport fixes into the version you already run, so a patched package can keep an old-looking version number. Do not judge by php -v alone: check the package changelog for the CVE or compare the exact package version against your distribution’s advisory.

    # Debian/Ubuntu: look for the CVEs in the package changelog
    apt changelog php8.3-common 2>/dev/null | grep -E 'CVE-2026-10961[89]'
  • Remi’s repository (rpms.remirepo.net): per the PHP advisory it will not have updates today. The PHP team recommends switching to distribution packages or building from source so you can patch immediately.

  • Containers: rebuild your images from the new official php:* tags (e.g. php:8.3.36-fpm). Pulling the new image is not enough: you have to recreate the running containers.

  • Windows: binaries are on the php.net downloads page.

3. Restart and verify

Installing the package does not replace processes already running. Both flaws live in long-lived workers, so restart every PHP-FPM pool and Apache if you use mod_php:

# adjust to the versions you actually run
sudo systemctl restart php8.3-fpm php8.4-fpm
sudo systemctl restart apache2   # if you use mod_php

Then verify the version seen by the web server, not the CLI one: a temporary phpinfo() page (delete it right after), Site Health in WordPress, or the X-Powered-By header if it is enabled.

After updating, review your logs for anything suspicious that happened before the patch: crashes and segfaults of PHP workers, unusual requests, new files in web directories.

4. Unsupported versions (8.1, 8.0, 7.x, 5.x)

The PHP project no longer releases updates for 8.1 and older. Given the severity, though, the security team has published minimal source backports for 7.4, 8.0 and 8.1, for those who build from source or have no LTS vendor. Some distributions and extended-support vendors also ship backported fixes.

For anything older than 7.4 there is nothing: migrate, or isolate the application. And whatever happens, use this as the push to move to a supported version (8.2 is in security-only maintenance until the end of 2026, so aim for 8.4 or 8.5).

If you cannot update right away

The PHP advisory is clear: only an update is a complete solution. The following measures reduce exposure to specific attack paths but do not make an installation safe.

Against CVE-2026-109618 (OOM corruption)

  • Set memory_limit=-1, preferably in a way applications cannot override (php_admin_value in the FPM pool or Apache config), and check that no pool, directory or application setting puts a finite limit back. This removes the controllable memory-limit failure, but an underlying allocation failure can still reach the vulnerable path.
  • Set a low post_max_size: 8M for applications without uploads, 32M if uploads are needed, with upload_max_filesize below post_max_size.
  • Enforce limits at the web server or proxy on decoded request size, number of input fields, multipart parts, headers and nesting depth, including for requests without a usable Content-Length.
  • Use a container or cgroup memory limit as an availability boundary, ideally one that kills only the affected worker. Do not use RLIMIT_AS or RLIMIT_DATA instead: they can trigger the vulnerable allocation failure themselves.

With memory_limit=-1 a single request can eat all the RAM: that is exactly why the cgroup limit is part of the package.

Against CVE-2026-109619 (stale global state)

  • Set session.upload_progress.enabled=0 and restart every worker. It must be set in php.ini, in the FPM pool or in server config that applies before request parsing: do not rely on ini_set() or on .user.ini. It blocks the confirmed session paths, not other variants.
  • On PHP 8.5, also set fatal_error_backtraces=0 and restart the workers: it blocks the confirmed fatal-backtrace path only.
  • Remove invalid or deprecated settings applied per request that emit diagnostics before initialization. Hiding diagnostics with error_reporting, display_errors or @ does not help.
  • Fix fatal errors and execution timeouts that an attacker can trigger, including failures in callbacks, upload or import processing, and cleanup.

⚠ session.upload_progress is not a fix

Before the release, session.upload_progress.enabled=0 was being passed around on social media as “the” fix. The PHP advisory confirms it helps, but only against some paths of CVE-2026-109619, and does nothing against CVE-2026-109618. Useful as a stopgap, not as protection. Since , PHP also recommends keeping it on updated versions, because of CVE-2026-115560.

Network and blast radius

  • For applications that cannot be patched promptly, CERT-EU recommends considering isolating them from public networks. Short of that, put them behind a VPN, IP allowlist or authentication at the edge, and make sure the origin cannot be reached directly around those controls.
  • Cloudflare has pushed emergency WAF rules for the known exploit paths, free plan included. Its CTO calls them “a STOPGAP only”: they only see traffic going through Cloudflare and may miss new variants.
  • Shrink what an attacker would get if the server were compromised: less data stored on the server, fewer privileges for the PHP user, no credentials or cloud roles lying around, and logs shipped somewhere safe.

Timeline

  • — The PHP team announces a security release for two vulnerabilities rated CVSS 9.2.
  • – — CERT-EU publishes advisory 2026-018 with identifiers CVE-2026-109618 and CVE-2026-109619.
  • — GMO Flatt Security clarifies that session.upload_progress.enabled=0 is not a complete fix.
  • — Cloudflare reports early exploitation attempts and releases emergency WAF rules.
  • , just before 14:00 UTC — PHP releases 8.5.12, 8.4.27, 8.3.36 and 8.2.35 together with the security advisory and backports for 7.4, 8.0 and 8.1.
  • — Posts on X and PoCs on GitHub claim an RCE bypass on PHP 8.5.12.
  • — VulnCheck makes a weaponized ASLR-bypass exploit for CVE-2026-109618 available to its Initial Access Intelligence customers.
  • — PHP publishes an informational advisory on CVE-2026-115560 and recommends session.upload_progress.enabled=0 until the next bugfix release.
  • — VulnCheck extends its exploit to 8.2, 8.3, 8.4 and 8.5 (x86-64, and ARM64 for 8.4/8.5) and to default WordPress Docker installs, and publishes exposure data: 2.6 million internet-facing PHP hosts.

Sources

Updating PHP is not the end of the story for WordPress either: a separate bug in the Brotli library, nicknamed HTTP2Shell, has been exploited in the lab against WordPress running the patched PHP 8.3.36. I covered it in HTTP2Shell: the Brotli vulnerability that also hits WordPress.

If you manage WordPress sites, use this moment to go through my WordPress security checklist as well: updating PHP closes this hole, but not the others. And if you need to test compatibility with a newer PHP version before migrating production, WPDockerize lets you set up a local environment in a few minutes.

Last updated:

Condividi:XLinkedInFacebookWhatsApp
Diego Betto

Written by

Diego Betto

Co-Founder & CTO at PAPION. Senior software engineer with AI skills: React, TypeScript, Node.js, LLM and AI agent integration, and application security.