php2shell: two PHP bugs that put much of the web one request from RCE
On October 21, 2026, the PHP project shipped an emergency fix for two critical bugs, CVE-2026-109618 and CVE-2026-109619, chained together as php2shell. Together they give unauthenticated remote code execution against a default PHP install. Here is what happened, who found it, how the disclosure went sideways, and exactly what to do about it.
session.upload_progress.enabled = On (the default), patch now. Fixed releases: 8.1.33, 8.2.28, 8.3.18, 8.4.4, and 8.5.12. If you cannot patch immediately, disable upload progress (see mitigation).The short version
php2shell is the nickname for a pair of bugs in the part of PHP that tracks file-upload progress. A specially shaped multipart POST forces PHP to run out of memory at just the wrong moment, which frees a session structure while PHP still holds raw pointers into it. Those dangling pointers become a write primitive on the heap. A companion bug leaks adjacent memory in the HTTP response, handing the attacker the address information needed to defeat ASLR. Chained, they produce reliable, unauthenticated remote code execution on a default PHP configuration.
Wiz Research estimates roughly 70 percent of internet-facing PHP applications are affected, putting php2shell in the same conversation as Log4Shell and Heartbleed for blast radius. The feature responsible has been on by default since PHP 5.4 in 2012.
What php2shell is
| CVE | Severity | Impact |
|---|---|---|
| CVE-2026-109618 | 9.8 Critical | Heap use-after-free in session upload progress, leading to remote code execution |
| CVE-2026-109619 | 9.1 Critical | Out-of-bounds read, giving in-band memory disclosure (an ASLR leak) |
Neither bug requires a login, a token, or any special application feature. They target PHP itself, not a framework or a plugin, so WordPress, Laravel, Drupal, Magento, and bespoke PHP all sit on the same vulnerable foundation. The only precondition is that session.upload_progress.enabled is on, which is the shipped default.
What this means for WordPress and other CMS sites
WordPress, WooCommerce, Drupal, Joomla, and Magento all run on PHP, and WordPress alone powers roughly 40 percent of the web. If your site runs on PHP, it sits on the same vulnerable foundation, no matter how well maintained your themes and plugins are.
The important nuance: php2shell is a bug in PHP itself, not in WordPress or any plugin. A WordPress core or plugin update will not fix it. The fix is at the PHP layer:
- Upgrade PHP to a patched release: 8.1.33, 8.2.28, 8.3.18, 8.4.4, or 8.5.12.
- If you cannot upgrade right away, disable
session.upload_progress. - On managed WordPress hosting, your host controls the PHP version, so ask them when they are patching PHP.
Who found it
Credit matters here, and there was early confusion, so to be clear: the bug was first discovered and first weaponized by @lcfr_hax of Calif Research, whose work convinced the PHP Foundation to invest in fixing the whole bug class and who then helped review and retest the patches. Wiz Research independently confirmed the issue and analyzed cloud-scale impact, and the PHP Foundation built and shipped the emergency release.

There is real prior art behind this. Calif had already published Finding and Exploiting a 21-Year-Old Vulnerability in PHP, part of their Month of AI-Discovered Bugs series, which built the AI-assisted engine-auditing methodology that later cracked open php2shell. Cyber Army did not discover these CVEs. Our contribution, covered below, was early detection of exploitation in the wild.
When the disclosure went sideways
Coordinated disclosure was scheduled for 14:00 UTC. Roughly ten hours early, a pseudonymous account posted the core memory primitive as a single image on X, and a separate pre-patch writeup appeared publicly. That turned a controlled release into a race: attackers now had the shape of the bug while most of the internet was still unpatched.
The window produced a flood of low-quality exploit code. Most public repositories claiming a working exploit were machine-generated and did not function: wrong field counts, wrong spray sizes, and offsets copied from one PHP build onto another. The reliable material came from researchers who understood the memory grooming, not from anyone who pasted a description into a model. The honest takeaway from the gap between leak and patch: a model could tell you what the bug was, but turning that into a reliable exploit was still expert work.
Government and agency warnings
The response escalated unusually fast. Japan's Prime Minister, @takaichi_sanae, posted a public appeal urging organizations to prepare for the patch, a rare level of head-of-government engagement for a single software CVE.

The U.S. National Security Agency's Cybersecurity Directorate issued a standalone public advisory as well, one of only a handful of times NSA Cyber has named a specific third-party CVE outside of known nation-state activity.

Root cause: stale pointers in upload progress
When session.upload_progress.enabled is on, PHP parses multipart uploads before your script runs and records progress into a small struct. The problem is that two of its fields were raw zval * pointers aimed directly into the session HashTable bucket array:
/* ext/session/php_session.h -- VULNERABLE (pre-patch) */
typedef struct _php_session_rfc1867_progress {
zend_long content_length;
zend_long upload_count;
/* RAW POINTERS INTO HashTable->arData */
zval *post_bytes_processed; /* stale after HT resize/free */
zval *current_file_bytes_processed; /* stale after HT resize/free */
...
} php_session_rfc1867_progress;If a crafted request forces an out-of-memory abort at the right moment, the session variables are freed mid-request and those pointers are left dangling. PHP then keeps writing byte counters through them while parsing the rest of the upload, turning four counter updates into a write-what-where primitive on the heap. The companion bug, CVE-2026-109619, abuses the same window to over-read adjacent memory and leak a library address, which removes ASLR as an obstacle.
The official fix
The fix is small and instructive: stop holding raw pointers into the hash table. The patched struct stores plain zend_long integer copies, and the counter writes re-resolve the live hash-table entries each time instead of trusting a cached pointer.
--- a/ext/session/php_session.h
+++ b/ext/session/php_session.h
@@ typedef struct _php_session_rfc1867_progress {
- zval *post_bytes_processed;
- zval *current_file_bytes_processed;
+ zend_long post_bytes_processed;
+ zend_long current_file_bytes_processed;Because the dangerous construct was a cached pointer into a structure that could be freed, replacing it with a value copy (plus a safe re-resolve helper) closes the use-after-free at its source. This is a good reminder that memory-safety bugs are often two lines of code, and that the hard part is finding them, not fixing them.
How exploitation works
We are deliberately describing this at the level of mechanism, not providing a recipe. At a high level the chain has three stages:
Stage 0 - Memory disclosure (CVE-2026-109619)
A crafted upload over-reads adjacent heap in the response,
leaking a library address. ASLR is now defeated.
Stage 1 - Out-of-memory trigger (CVE-2026-109618)
A multipart POST sized to blow past memory_limit frees the
session variables mid-parse. The upload-progress counters
are now dangling pointers into freed memory.
Stage 2 - Reclaim and divert
The freed allocation is reclaimed with an attacker-shaped
object. On request shutdown, a destructor that should be
benign is reached through attacker-controlled data and
lands in a libc call. -> code execution.Two properties make it dangerous in practice. It needs zero preconditions beyond the default feature being on, and on single-worker setups such as Apache prefork the groom is deterministic enough to be reliable in a handful of requests. That combination, no authentication plus reliability, is what moved php2shell from critical to national-infrastructure risk within hours.
Who is affected
PHP version matrix
| Branch | Status | Fixed version |
|---|---|---|
| PHP 5.4 to 8.0 | End of life | No patch - upgrade off these branches |
| PHP 8.1 | Vulnerable | 8.1.33 |
| PHP 8.2 | Vulnerable | 8.2.28 |
| PHP 8.3 | Vulnerable | 8.3.18 |
| PHP 8.4 (<= 8.4.3) | Vulnerable | 8.4.4 |
| PHP 8.5 (<= 8.5.11) | Vulnerable | 8.5.12 |
SAPI exposure
| SAPI | Vulnerable | Notes |
|---|---|---|
| Apache mod_php (prefork) | Yes | Single worker makes the exploit most reliable |
| PHP-FPM (nginx or Apache) | Yes | Multi-worker, attacker retries across workers |
| LiteSpeed | Yes | Same code path |
| CGI | Yes | If upload progress is enabled |
| CLI | No | No network exposure |
What to do right now
1. If you cannot patch this minute, disable upload progress
; php.ini
session.upload_progress.enabled = Off# Apache .htaccess
php_value session.upload_progress.enabled 02. Patch, which is the real fix
# Debian / Ubuntu
apt-get update && apt-get install --only-upgrade php8.4
# Docker: bump the base image
# FROM php:8.4.3-apache -> FROM php:8.4.4-apache
# Verify
php -v # expect 8.4.4, 8.3.18, 8.2.28, 8.1.33, or 8.5.12 or laterIf you run older, end-of-life PHP, treat this as the forcing function to upgrade. There is no patch coming for 5.x, 7.x, or 8.0.
Detection
The trigger leaves a recognizable shape in access logs: an unusually large multipart POST to a PHP endpoint, often paired with a second, smaller POST from the same client moments later. Hunt for that pattern first.
# Apache / nginx: large non-file POST bodies to PHP
grep "POST.*\.php" access.log | awk '$10 > 16777216'
# Then look for a same-IP small POST within ~2s of a >16MB POST
# (the OOM trigger followed by the reclaim request).At the network layer, these Suricata rules flag the two phases. Tune the sizes to your environment before deploying.
alert http $EXTERNAL_NET any -> $HTTP_SERVERS any (
msg:"php2shell CVE-2026-109618 oversized upload-progress POST";
flow:established,to_server;
content:"PHP_SESSION_UPLOAD_PROGRESS"; http_client_body;
dsize:>8000000;
classtype:attempted-admin; sid:2026109618; rev:1;
)
alert http $EXTERNAL_NET any -> $HTTP_SERVERS any (
msg:"php2shell CVE-2026-109619 memory-disclosure probe";
flow:established,to_server;
content:"PHP_SESSION_UPLOAD_PROGRESS"; http_client_body;
dsize:>1000; dsize:<5000;
classtype:attempted-recon; sid:2026109619; rev:1;
)What Cyber Army saw
Our AutoScan telemetry picked up novel exploitation patterns against monitored, in-scope infrastructure within hours of the X leak, before the official CVE identifiers were published. We confirmed working remote code execution against PHP 8.4.3 the same morning and pushed updated checks so that every PHP endpoint in a customer's authorized scope is now tested for the php2shell preconditions and for the large-multipart trigger signature.
This research and detection work was led by Cyber Army's threat research team, Harshith Barri and Devansh Dubey. To be explicit about credit: the vulnerability itself was discovered by @lcfr_hax and Calif Research. Our job is to tell our customers, fast, whether their exposed PHP is vulnerable and whether anyone has tried the door.
Our assessment
This is a Tier 1 emergency, for four reasons:
- The responsible feature has been on by default since 2012, so the exposed population is enormous.
- No authentication is required, and the exploit is reliable on common configurations.
- A working chain was public before the patch existed, which is the worst possible ordering.
- Roughly 70 percent of internet-facing PHP is in scope, which is Log4Shell-class breadth.
If you run PHP on anything public, the priority order is simple: patch to a fixed release, or disable session.upload_progress until you can, then review your access logs for the trigger signature. Do it today.
Sources
- Calif Research: php2shell writeup, by @lcfr_hax, the original discoverer.
- Calif Research: Finding and Exploiting a 21-Year-Old Vulnerability in PHP, the prior-art unserialize UAF and AI-audit methodology.
- Wiz: PHP2Shell, AI uncovers critical RCE affecting 70 percent of the internet.
- PHP Foundation: the October 2026 emergency security releases (8.1.33, 8.2.28, 8.3.18, 8.4.4, 8.5.12).
- PHP bug #78876: the 2020 resource-leak report that previewed this bug class.
- OSTIF and Quarkslab PHP audit, 2024.
- Public posts referenced above from @takaichi_sanae and @NSACyber.
- Related Cyber Army writing: Inside Mythos and AI pentest vs manual pentest.
Is your exposed PHP vulnerable to php2shell?
AutoScan tests your external attack surface the way an attacker would, including the php2shell preconditions, and tells you what is actually exploitable with evidence.
Request a live demo