# PHP2Shell: the bug isn't in your app, it's in PHP itself

> CVE-2026-109618 and CVE-2026-109619 open the door to unauthenticated RCE in the PHP runtime. Updating your CMS isn't enough; you need to patch the PHP binary on the server.

- Author: Sedat Özdemir (Sr. Expert Pentest & Vulnerability Management Engineer)
- Date: 2026-11-02
- Category: news · Tags: php, rce, zero-day, web-security, patch-management
- URL: https://sedatozdemir.com.tr/en/blog/php2shell-acik-uygulamanizda-degil-phpnin-kendisinde

On 21 October 2026 PHP shipped an emergency release for two critical flaws, **CVE-2026-109618** and **CVE-2026-109619**. Together they're called PHP2Shell, both score 9.2 on CVSS 4.0, and neither needs authentication.

What bothered me most about this one is where the bug lives. When most of us see a headline like this, we go looking at WordPress plugins, Drupal modules or our own code first. Here the flaw is in the PHP runtime itself, so updating the CMS, removing plugins or adding a WAF rule won't close it on its own. You have to patch the PHP binary that answers web requests.

## What happened?

Here's the timeline Recorded Future put together (UTC):

- On 19 August Wiz Research finds the first flaw, and within two days it confirms unauthenticated RCE against Drupal and WordPress.
- On 24 August the flaws are reported privately to PHP.
- On 20 October PHP announces an out-of-band security release for 21 October.
- At 02:29 on 21 October a leaked archive is uploaded to VirusTotal, and the CVE IDs surface earlier than planned.
- At 07:34 the same day Patchstack reports multiple active exploitation incidents from its hosting partners.
- At 13:52 the fixes ship: **8.5.12, 8.4.27, 8.3.36, 8.2.35**.

So exploitation attempts started before the patch existed. How many systems were actually compromised is still something nobody can say for sure.

## How the two bugs work

CVE-2026-109619 is a use-after-free in session upload progress. The code that tracks upload progress keeps raw pointers to two values in the session table. A later session update frees that table and builds a new one without updating the pointers, so the attacker places a forged structure in the freed slot and gets the engine to follow it.

CVE-2026-109618 is memory corruption on out-of-memory. When PHP hits `memory_limit`, it `longjmp()`s straight to request shutdown and skips normal cleanup. Half-initialised structures are then read as valid during shutdown, and if the attacker filled memory with forged data beforehand, that means branching to a function pointer they chose.

What makes this really dangerous is that the session layer handles upload progress before your application code runs. Any endpoint that accepts file uploads can be a target, even one that ignores the file, and `session.upload_progress.enabled` is on by default.

## Who is affected?

Every PHP installation that serves web requests is affected: PHP-FPM, Apache `mod_php`, `php-cgi`, FrankenPHP and LiteSpeed. Reports list PHP 5.4 through 8.5 as in scope.

> [!IMPORTANT]
> If it were my fleet, I'd look at Apache + mod_php servers first. According to PHP, an exploit that fires before application code runs has been demonstrated on this setup. PHP-FPM blocks one OOM path, but RCE through the session lifecycle has also been validated on FPM under suitable conditions.

The scale is worrying too. In Qualys data, about 71% of 156 million PHP installations with a known version run end-of-life branches (5.4 through 8.1), and only 199,000 were patched in the first 12 hours after disclosure. If you're on PHP 7.4, 8.0 or 8.1, upstream only offers limited source backports, so you'll need your distribution's package or an upgrade.

## What we know, what we don't

What's confirmed: two CVEs, a 9.2 CVSS score, fixed releases, RCE demonstrated on mod_php, a pre-patch leak and real attack traffic.

What isn't confirmed is the number of successful compromises. PoCs are circulating on GitHub, but Recorded Future notes that the author of one later invalidated their own theory behind it. Don't conclude the bug can't be exploited just because the PoC you tried didn't work.

## Check your server in five minutes

The first trap is that `php -v` shows you the CLI version, while the binary serving the web may be a completely different one. You need to check the web side separately.

```bash
# CLI and FPM versions (may differ)
php -v
php-fpm8.3 -v 2>/dev/null || php-fpm -v

# Is Apache mod_php loaded? (highest risk)
apache2ctl -M 2>/dev/null | grep -i php

# Is upload progress on?
php -i | grep -E "session.upload_progress.enabled|memory_limit|post_max_size"

# Debian/Ubuntu package version (distro backports may keep the old number)
dpkg -l | grep -E "^ii\s+php[0-9.]*-(common|fpm|cli)"
```

Distribution packages can ship the fix under an older version number, so rather than trusting the number, check your distro's security advisory.

## Look for traces in your logs

The known exploit flow leaves a few traces on the request side: a `PHPSESSID`, a multipart POST carrying a `PHP_SESSION_UPLOAD_PROGRESS` field, a pause of more than a second, and lots of repeated form fields. On the server side you'll see workers crashing.

```bash
# Crashing PHP workers (since 20 October)
grep -E "SIGSEGV|exit signal Segmentation fault" /var/log/php*-fpm.log /var/log/apache2/error.log

# Requests carrying the upload progress field (if you log bodies, or in WAF logs)
grep -l "PHP_SESSION_UPLOAD_PROGRESS" /var/log/waf/*.log
```

A single match doesn't mean you've been compromised. If you have worker crashes since 20 October that you can't explain, though, I'd start incident response and rotate the credentials on that server (database passwords, API keys). One of the flaws can leak worker memory, so this step shouldn't be skipped.

## If you can't patch right away

These reduce risk, but they don't replace the patch:

```ini
; php.ini or FPM pool — ini_set() and .user.ini are NOT enough
session.upload_progress.enabled = Off

; PHP 8.5 only
fatal_error_backtraces = Off

; 8M if you don't take uploads, 32M if you do; keep upload_max_filesize below it
post_max_size = 8M
```

After changing the setting, restart all workers. You can also block multipart requests at the reverse proxy on endpoints that don't need them, and cap request size, field count and headers there. Giving each worker a memory limit through cgroups makes sense as well. If none of that is possible, I'd take the service off the internet and give each system an owner and a deadline.

> [!WARNING]
> On 23 October PHP disclosed a third, configuration-dependent flaw: **CVE-2026-115560**, again in session upload progress. Until a permanent fix ships, I'd keep `session.upload_progress.enabled = Off` even on patched versions.

## Do this now

1. List every PHP that answers web requests, including container images, admin panels and PHP bundled inside third-party apps.
2. Put the servers running Apache + mod_php at the top of that list.
3. Move to 8.5.12, 8.4.27, 8.3.36 or 8.2.35, or your distro's patched package, and restart the workers; the fix isn't live until they restart.
4. Rebuild images based on `php:8.x`, because the bug stays alive as long as the old image is running.
5. Keep upload progress turned off until CVE-2026-115560 is fixed.
6. Go through your logs from 20 October onward, and rotate credentials if you find crashes.

## Sources
- [Qualys TRU](https://blog.qualys.com/vulnerabilities-threat-research/2026/10/20/php-announces-critical-security-release-for-two-vulnerabilities)
- [Akamai](https://www.akamai.com/blog/security-research/understanding-php2shell-everything-you-need-to-know)
- [Recorded Future Insikt](https://www.recordedfuture.com/blog/php2shell-vulnerability-exploitation)
- [Fortinet](https://www.fortinet.com/blog/industry-trends/understanding-php2shell-vulnerabilities-and-reducing-risk-while-you-patch)
