TL;DR
- PHP2Shell is a set of memory-safety bugs in the PHP engine itself, not in applications built on PHP. They are tracked as two CVEs, CVE-2026-109618 and CVE-2026-109619, each rated CVSS v4.0 9.2 (Critical) 234.
- Confirmed impact: unauthenticated remote code execution as the web worker user, plus remote disclosure of process memory 34.
- Fixed in PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35, released 2026-10-21. PHP says updating is the only complete fix 2.
- Active exploitation attempts were reported on disclosure day 78.
Public proof of concept
A working exploit is public. A proof-of-concept for CVE-2026-109618 and CVE-2026-109619 is published on GitHub: github.com/eness/php2shell-CVE-2026-109618-CVE-2026-109619-PoC.
Because runnable exploit code is in the open, the gap between disclosure and mass exploitation is effectively zero: commodity scanners can weaponize it within hours. Treat any unpatched, internet-facing PHP server as exposed. Patch now using the patch guide, and check your own logs with the read-only detection commands below.
This page links to the PoC for awareness and does not reproduce exploit code, payloads or attack steps.
Overview
This write-up is based mainly on Wiz Research's technical post, checked against PHP's own advisories and the other sources listed at the end. It explains how the bugs work. It deliberately leaves out exploitation parameters, payloads and request details.
On 2026-10-21 the PHP project shipped security releases for two critical vulnerabilities and published GitHub advisories GHSA-w8hw-w9fx-j79w ("RCE via OOM corruption", CVE-2026-109618) and GHSA-6wmf-4rv5-p3rp ("RCE via stale global state", CVE-2026-109619) 234. The name "PHP2Shell" comes from the researchers; php.net does not use it 2.
Both advisories give the vector CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N: network-reachable, no privileges or user interaction needed, but high attack complexity and attack requirements present 34.
Discovery was joint. Calif describes the bugs as "discovered by Calif, OpenAI and Wiz" 5, and Wiz says it worked with the PHP team, Calif and OpenAI 1. Cloudflare also names Quarkslab among the disclosing parties 8. The advisories credit reporters lcfr-hax, sagitzadik-wiz, tomash-areion and amyb-oai 34.
Background: fatal errors and reused workers
When PHP hits a fatal error, especially out-of-memory (OOM) on exceeding memory_limit, it historically used longjmp() to leave the current context. Native code could be interrupted part-way through updating its state, before request memory was fully initialized or before globals pointing into it were reset 35.
Under persistent SAPIs such as Apache mod_php and PHP-FPM, the worker process survives the fatal error and serves later requests. Per-process globals (the BG, PS, EG and CG families) can therefore keep pointers into a request heap that has already been freed. A later request on the same worker may use that stale state before initialization replaces it, which is a use-after-free 14.
Calif places this in a long history of PHP memory-limit problems. It cites a 2004 memory_limit vulnerability by Stefan Esser, PHP bug #78876 and a 2024 OSTIF/Quarkslab audit finding 5.
The bugs
Wiz describes four bug classes 1. The PHP advisories describe the two CVEs as root-cause classes rather than individual bugs, and the sources do not agree on how all four bugs map to the CVEs (see the note at the end of this section).
1. Upload-progress unserializer use-after-free
During multipart (RFC1867) parsing, Session upload-progress handling stores request-owned decoder bookkeeping in the BG(unserialize) global. An allocation failure can destroy the request heap while that state stays published. A later request on the same worker then treats attacker-reclaimed memory as live bookkeeping 1. Wiz and Sansec map this bug to CVE-2026-109618 16.
2. Remote process-memory disclosure via PHPSESSID
Upload-progress handling can initialize Session state early and publish a session ID in PS(id). If an OOM aborts the request, PS(id) can be left pointing to freed memory. A later multipart request on the same worker reaches cleanup before Session initialization resets the pointer. PHP then URL-encodes the reclaimed heap contents into a PHPSESSID response cookie. This works even if the application never calls session_start(), and the leaked memory can include other requests' passwords, tokens, API keys and certificates 16.
3. Fatal-error backtrace use-after-free
EG(last_fatal_error_backtrace) holds the most recent fatal error's backtrace and is not cleared when an OOM destroys the heap. A diagnostic emitted early in the next request, before the executor reinitializes, can touch the dangling backtrace 1. PHP's advisory describes this as a PHP 8.5 path with fatal_error_backtraces enabled, where the reset "is always missed during shutdown", and calls it the strongest demonstrated case 4. Mapped to CVE-2026-109619 146.
4. OPcache shared-memory type confusion
In zend_accel_script_persist(), PHP advances the process-local CG(map_ptr_last) frontier before publishing the shared one. An allocation failure between those two steps lets another worker reuse a slot for a different object type, which corrupts shared OPcache state across workers 1.
CVE mapping is not consistent across sources. Wiz does not clearly assign bugs 2 and 4 to a CVE 1. Sansec puts the memory disclosure under CVE-2026-109618 and the OPcache confusion under CVE-2026-109619 6. PHP's CVE-2026-109619 advisory lists memory disclosure among its confirmed impacts and names session upload progress as one of its confirmed cases 4. Wiz adds that more variants exist and that the patch fixes the root issue across them 1.
How the official fixes work
For CVE-2026-109618, the patch removes longjmp() from OOM handling. A soft OOM is now deferred to the next VM safe point, and a hard OOM above a higher threshold exits via _exit() 3. For CVE-2026-109619, the patch resets a wider set of request-owned global state 4.
How the bugs combine
Exploitation follows a producer/consumer pattern. A producer request leaves request-owned state behind after a fatal error. A consumer request on the same worker then uses it before initialization 4. Wiz reports these chains 1:
- Unserializer UAF plus memory disclosure (which removes address uncertainty): unauthenticated RCE on WordPress, Drupal and Joomla.
- Backtrace UAF: unauthenticated RCE on Drupal.
- OPcache type confusion: unauthenticated RCE on WordPress.
- Memory disclosure alone: leaks secrets without depending on the application.
Calif claims pre-auth RCE against WordPress 7.1.2, Drupal 11.4.3, Joomla 6.1.4, MediaWiki 1.46.2, Roundcube 1.7.4, Magento 2.4.9, phpMyAdmin 5.2.3, Nextcloud 35.0.1, Laravel 13.11, Symfony 8.1 and WHMCS 5. These demonstrations are self-reported and have not been independently verified.
Impact and affected versions
Versions. The official advisories list affected versions as 8.2.x before 8.2.35, 8.3.x before 8.3.36, 8.4.x before 8.4.27 and 8.5.x before 8.5.12 34. Wiz, Calif, Sansec and Cloudflare say the vulnerable code dates back to PHP 5.4.0 1568, and Calif notes that some paths apply only to specific versions or SAPIs 5. PHP 8.1 and older receive no official releases. PHP published minimal source backports for 7.4, 8.0 and 8.1 as php-src branches named PHP-x.y-CVE-2026-109618-109619 2.
SAPIs. PHP says every installation serving web requests is at risk: PHP-FPM, Apache mod_php, php-cgi, FrankenPHP and LiteSpeed. It rates risk highest for mod_php 2. For CVE-2026-109618, RCE is confirmed only on mod_php, and FPM, FrankenPHP and LSAPI susceptibility rests on source analysis 3. For CVE-2026-109619, the session upload-progress path was validated on both mod_php and PHP-FPM 4.
Reach. Wiz's headline figure of "70% of the Internet" is its own framing. Cloudflare cites W3Techs for PHP running "almost 70%" of websites 18. Reliability depends on the PHP build, extensions, allocator layout and whether the producer and consumer requests land on the same worker. Running multiple workers lowers reliability but does not remove the risk 34.
Timeline
Wiz's and Calif's timelines record different discovery dates because each describes its own work 15. Both are shown below.
| Date (2026) | Event | Source |
|---|---|---|
| Aug 1 | Calif begins finding RCE paths | 5 |
| Aug 11 | Calif reports its first bug to PHP | 5 |
| Aug 14-15 | Calif shares an RCE PoC; PHP confirms it works | 5 |
| Aug 19 | Wiz finds its first bug | 1 |
| Aug 20-21 | Wiz confirms unauthenticated RCE on Drupal, then WordPress; Calif reports a second bug (Aug 21) | 15 |
| Aug 24 | Wiz reports to the PHP security team | 1 |
| Sep 21 | PHP acknowledges Wiz's report and begins a coordinated fix (per Wiz) | 1 |
| Oct 20 | PHP pre-announces a release for Oct 21 at 14:00 UTC; details leak during the embargo | 27 |
| Oct 21, 03:02 UTC | Cloudflare WAF protection enabled by default | 8 |
| Oct 21, 14:00 UTC | Fixed releases and public disclosure | 26 |
| Oct 21 | Active exploitation attempts reported | 78 |
Detection
Application logs may show little, because the bugs sit below the application layer 1. Watch for 1:
- unusual multipart requests followed by memory-limit errors;
- repeated HTTP 500 responses;
- unexpectedly large or malformed
PHPSESSIDresponse cookies; - unexplained PHP worker churn.
PHP recommends an isolated audit with E_ALL logging, looking for repeated early warnings, cleanup-time diagnostics, fatal errors and timeouts. Clean logs do not prove safety, because some failures happen before PHP writes a message 4. For incident response, preserve logs, configuration and suspicious files before making changes, then hunt for web shells, unexpected cron jobs and new accounts 7.
Read-only checks you can run now
Adjust paths to your distribution. All of these only read logs and process lists.
# Out-of-memory fatal errors (the trigger condition) in Apache and PHP-FPM logs
sudo grep -h "Allowed memory size of" /var/log/apache2/error.log* /var/log/php*-fpm.log 2>/dev/null | tail -n 50
# Workers crashing: Apache children or FPM workers dying on signals
sudo grep -hE "exit signal (Segmentation fault|Aborted)" /var/log/apache2/error.log* 2>/dev/null | tail -n 20
sudo grep -hE "exited on signal" /var/log/php*-fpm.log 2>/dev/null | tail -n 20
# Bursts of HTTP 500 on POST requests, by client IP (combined log format)
sudo awk '$6 == "\"POST" && $9 == 500 {print $1}' /var/log/apache2/*access.log | sort | uniq -c | sort -rn | head
# Shells or download tools running as the web server user
ps -eo user,pid,ppid,etime,cmd --forest | awk '$1 == "www-data"' | grep -Ev "apache2|php-fpm|php-cgi"
# PHP files changed in web roots since the leak (possible web shells)
sudo find /var/www -name "*.php" -newermt "2026-10-20" -ls 2>/dev/null | head -n 50Oversized PHPSESSID response cookies usually don't appear in default access logs. Look for them in reverse-proxy, load-balancer or WAF logs that record response headers.
Mitigation
Patch. Upgrade to PHP 8.5.12, 8.4.27, 8.3.36 or 8.2.35, or to your distribution's patched package, then restart every PHP worker. Commands for each platform are in our patch guide. PHP says: "There are no complete mitigations, updating is the only safe choice." 27 Updating application code or plugins does not fix these bugs 8. Users of rpms.remirepo.net did not have updates on release day 2.
Partial interim measures, which reduce exposure to specific paths but do not make an installation safe 2:
- Set
memory_limit=-1administratively (php_admin_value), making sure nothing restores a finite limit. Monitor memory afterwards, because a single request can then use all of it 26. - Do not use RLIMIT_AS or RLIMIT_DATA, because they can trigger the vulnerable path. Use cgroup or container memory limits with care 23.
- Keep
post_max_sizelow and enforce request size, part count and header limits at the frontend. Block multipart/form-data there if the application does not use it 23. - Set
session.upload_progress.enabled=0in php.ini or the pool or server config (not viaini_set()or.user.ini) and restart workers 2. - On PHP 8.5, set
fatal_error_backtraces=0and restart workers 24. - Remove per-request INI settings that raise diagnostics before initialization, and consider
pm.max_requests=1(at a significant performance cost) 24. - WAF rules from Cloudflare and other vendors are defense in depth only 8.
Lessons
- Error paths are attack surface. A fatal error that skips cleanup in a long-lived worker becomes a cross-request use-after-free 4.
- Engine-level bugs bypass application hardening and application maintenance modes, because PHP parses the request before any application code runs 6.
- AI-assisted auditing played a central role: Wiz credits Wiz Atlas with Gemini 4 Argon and GPT-5.6 Sol, and Calif describes LLM-driven variant analysis 15. Claims about speed and effort are the vendors' own.
- Disclosure-to-exploitation windows are now hours, not weeks 78.
Sources
- Wiz Research: PHP2Shell: AI uncovers critical RCE vulnerabilities affecting 70% of the Internet (vendor research; product sections are marketing)
- PHP.net: 2026 news archive (security release announcements)
- GHSA-w8hw-w9fx-j79w: RCE via OOM corruption (CVE-2026-109618)
- GHSA-6wmf-4rv5-p3rp: RCE via stale global state (CVE-2026-109619)
- Calif: php2shell
- Sansec: PHP2Shell: Critical PHP RCE
- watchTowr Intel: PHP Remote Code Execution (PHP2Shell)
- Cloudflare: Cloudflare deploys protections against php2shell