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

- Autore / Author: Diego Betto (https://diegobetto.com/about/)
- Pubblicato / Published: 2026-10-21
- Aggiornato / Updated: 2026-11-03
- URL: https://diegobetto.com/en/php2shell-php-critical-vulnerabilities/
- Tag: php, security, wordpress

On <time datetime="2026-10-20">20 October 2026</time> 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 <time datetime="2026-10-21">21 October 2026</time>, 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: <time datetime="2026-11-03T10:00:00Z">3 November 2026, 10:00 UTC</time> — 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](#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](#the-8512-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](https://www.php.net/archive/2026.php#2026-10-21-5) 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](https://github.com/php/php-src/security/advisories/GHSA-w8hw-w9fx-j79w) and [GHSA-6wmf-4rv5-p3rp](https://github.com/php/php-src/security/advisories/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](https://www.vulncheck.com/blog/php2shell-vulnerabilities) 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 <time datetime="2026-10-26">26 October 2026</time>, 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 <time datetime="2026-10-22">22 October</time>), 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 <time datetime="2026-10-21">21 October</time> 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 <time datetime="2026-10-22">22 October 2026</time>, 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 <time datetime="2026-10-23">23 October 2026</time> the PHP team published an [informational advisory](https://www.php.net/archive/2026.php#2026-10-23-1) 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:

```bash
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:

```bash
# 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.

  ```bash
  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.

  ```bash
  # 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](https://www.php.net/downloads.php?os=windows).

### 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:

```bash
# 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](https://github.com/php/php-src/tree/PHP-7.4-CVE-2026-109618-109619), [8.0](https://github.com/php/php-src/tree/PHP-8.0-CVE-2026-109618-109619) and [8.1](https://github.com/php/php-src/tree/PHP-8.1-CVE-2026-109618-109619), 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 <time datetime="2026-10-23">23 October</time>, 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](https://developers.cloudflare.com/changelog/post/2026-10-21-emergency-waf-release-cfmr/) 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

- **<time datetime="2026-10-20">20 October 2026</time>** — The PHP team announces a security release for two vulnerabilities rated CVSS 9.2.
- **<time datetime="2026-10-20">20</time>–<time datetime="2026-10-21">21 October 2026</time>** — CERT-EU publishes advisory 2026-018 with identifiers CVE-2026-109618 and CVE-2026-109619.
- **<time datetime="2026-10-21T05:00:00Z">21 October 2026, 05:00 UTC</time>** — GMO Flatt Security clarifies that `session.upload_progress.enabled=0` is not a complete fix.
- **<time datetime="2026-10-21">21 October 2026</time>** — Cloudflare reports early exploitation attempts and releases emergency WAF rules.
- **<time datetime="2026-10-21">21 October 2026</time>, 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.
- **<time datetime="2026-10-22">22 October 2026</time>** — Posts on X and PoCs on GitHub claim an RCE bypass on PHP 8.5.12.
- **<time datetime="2026-10-22">22 October 2026</time>** — VulnCheck makes a weaponized ASLR-bypass exploit for CVE-2026-109618 available to its Initial Access Intelligence customers.
- **<time datetime="2026-10-23">23 October 2026</time>** — PHP publishes an informational advisory on CVE-2026-115560 and recommends `session.upload_progress.enabled=0` until the next bugfix release.
- **<time datetime="2026-10-26">26 October 2026</time>** — 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

- [VulnCheck — PHP2Shell Vulnerabilities: CVE-2026-109618 and CVE-2026-109619](https://www.vulncheck.com/blog/php2shell-vulnerabilities)
- [Red Hot Cyber — Milioni di server PHP nel mirino (Italian)](https://www.redhotcyber.com/post/milioni-di-server-php-nel-mirino-scoperto-come-aggirare-le-difese-e-lanciare-attacchi/)
- [PHP.net — Informational advisory: Session upload progress](https://www.php.net/archive/2026.php#2026-10-23-1)
- [PHP.net — Security advisory: Critical remote code execution vulnerabilities, update now](https://www.php.net/archive/2026.php#2026-10-21-5)
- [GitHub — GHSA-w8hw-w9fx-j79w (CVE-2026-109618)](https://github.com/php/php-src/security/advisories/GHSA-w8hw-w9fx-j79w)
- [GitHub — GHSA-6wmf-4rv5-p3rp (CVE-2026-109619)](https://github.com/php/php-src/security/advisories/GHSA-6wmf-4rv5-p3rp)
- [PHP.net — PHP 8 ChangeLog](https://www.php.net/ChangeLog-8.php)
- [PHP.net — Upcoming Critical PHP Security Release](https://www.php.net/archive/2026.php#2026-10-20-1)
- [CERT-EU — Security Advisory 2026-018: Critical Vulnerabilities in PHP](https://cert.europa.eu/publications/security-advisories/2026-018)
- [The Hacker News — PHP2Shell: what actually protects you](https://thehackernews.com/2026/10/php2shell-what-actually-protects-you.html)
- [Cloudflare — Emergency WAF release](https://developers.cloudflare.com/changelog/post/2026-10-21-emergency-waf-release-cfmr/)
- [Serverplan — PHP2Shell: due vulnerabilità critiche in PHP (Italian)](https://www.serverplan.com/blog/php2shell-due-vulnerabilita-critiche-in-php-necessario-aggiornare-i-siti-web/)
- [PHP.net — Supported Versions](https://www.php.net/supported-versions.php)

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](https://diegobetto.com/en/http2shell-brotli-vulnerability-wordpress/).

If you manage WordPress sites, use this moment to go through my [WordPress security checklist](https://diegobetto.com/en/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](https://diegobetto.com/en/wpdockerize-wordpress-on-docker/) lets you set up a local environment in a few minutes.
