A critical PHP use-after-free (CVE-2026-109619, CVSS 9.2) lets unauthenticated attackers turn one failed request into code execution on the next. Researchers demonstrated it against default WordPress, Drupal and Joomla installs. Patches are out now.

PHP runs a large share of the world's websites, and the PHP project has just patched a flaw that could let an anonymous attacker take control of the servers behind them. The bug, tracked as CVE-2026-109619 and nicknamed PHP2Shell by the Wiz researchers who helped find it, carries a CVSS 4.0 score of 9.2 (Critical). It requires no login, no user interaction and no particular plugin. According to Wiz, researchers demonstrated it against default deployments of WordPress, Drupal, and Joomla. Fixed releases (PHP 8.2.35, 8.3.36, 8.4.27, and 8.5.12) shipped on October 21, 2026, and anyone running PHP on an internet-facing server should treat this as a patch-today event.
On October 21, 2026, the PHP Group published GitHub Security Advisory GHSA-6wmf-4rv5-p3rp and the corresponding CVE record. The advisory describes a class of memory-safety bugs titled simply RCE via stale global state. The official description reads:
PHP has various global state that temporarily points into request memory. In some scenarios, global state may not be correctly reset, or may be repopulated after being reset. The global state may then refer to request memory even after it has been freed. Subsequent requests may read or branch on this stale state before it is fully initialized. Multiple strategies can trigger this condition and produce remote code execution (RCE).
The CVE record credits three reporting parties: Wiz, Ryan at Calif.io, and OpenAI. It credits PHP core developers Arnaud Le Blanc, Ilija Tovilo, Jakub Zelenka, and Nora Dossche with developing the fix. A same-day post to the oss-security mailing list on Openwall confirmed that the flaw enables unauthenticated remote code execution against Apache mod_php and PHP-FPM, the two most common ways PHP is deployed in production. That post also stated that no complete generic configuration workaround exists.
The timeline in the Wiz Research write-up is fast. Wiz says it discovered the vulnerability on August 19, 2026 and demonstrated a working private exploit, called php2shell, on August 21, 2026, two days later, against WordPress, Drupal and Joomla. Wiz notified the PHP project on August 24, 2026, and the vendor acknowledged the report on September 21, 2026. The CVE identifier was reserved on October 12, 2026, and the CVE record, vendor advisory and patches went public on October 21, 2026. Wiz states the bug was discovered and weaponized "in a matter of days with the aid of frontier AI models." That detail matters for everyone who defends web servers, and I come back to it at the end of this piece.
The U.S. Cybersecurity and Infrastructure Security Agency (CISA) added its own SSVC triage to the CVE record on publication day. It lists Exploitation: none, Automatable: no, and Technical Impact: total. In plain English: nobody is known to be exploiting this yet, it is not trivially wormable, but if someone succeeds they own the affected process completely.
Most production PHP does not start a fresh program for every web page. Under PHP-FPM (FastCGI Process Manager) or Apache mod_php, a pool of long-lived worker processes handles request after request. Each request gets its own scratch area of memory, the request heap, which PHP wipes when the request finishes. The worker process itself, and certain global variables inside the PHP engine, persist from one request to the next.
The design assumes that any global variable pointing into the request heap gets cleared before that heap is freed. Think of a hotel housekeeper who must collect every room key before the guest's room is reassigned. CVE-2026-109619 covers cases where housekeeping gets interrupted. A key to a room that no longer belongs to anyone stays on the hook, and the next guest picks it up.
In memory-safety terms, this is a use-after-free (UAF): code reads or writes memory that has already been released and may now contain something else. If an attacker can control what fills that freed memory, a UAF can often be turned into full control of the program.
The CVE record maps the flaw to five weakness categories:
CWE | Name | How it applies |
|---|---|---|
CWE-416 | Use After Free | Global state dereferences freed request memory |
CWE-755 | Improper Handling of Exceptional Conditions | Fatal errors, OOM, timeouts skip the cleanup path |
CWE-824 | Access of Uninitialized Pointer | Next request reads state before it is initialized |
CWE-1325 | Improperly Controlled Sequential Memory Allocation | Allocation failures mid-operation leave inconsistent state (OPcache) |
CWE-1419 | Incorrect Initialization of Resource | Request startup does not reset state early enough |
The CVE record also tags the attack pattern as CAPEC-234: Hijacking a privileged process.
The PHP advisory frames exploitation as two separate HTTP requests that must be handled, one after the other, by the exact same worker process.
The consumer runs before the script's own error-handling settings take effect. As a result, the advisory notes that application-level error suppression is ineffective: turning off display_errors inside your code does not help, because the dangerous read happens before your code runs.
Wiz Research describes two specific bugs, which it calls primitives, and chains them together in its private php2shell exploit.
Primitive 1: fatal-error backtrace use-after-free. PHP keeps the backtrace of the most recent fatal error in an executor-global value named EG(last_fatal_error_backtrace). A backtrace is the list of function calls that led to the error. According to Wiz, an out-of-memory error can destroy the request heap without clearing that executor-global value. The result is a dangling pointer into freed memory, which PHP later accesses while emitting a diagnostic early in a subsequent request. The PHP advisory confirms the fatal error backtrace producer is specific to PHP 8.5. That is why one of the vendor's interim mitigations is the PHP 8.5 setting fatal_error_backtraces=0.
Primitive 2: OPcache type confusion. OPcache is PHP's built-in bytecode cache, enabled on most production servers. It stores compiled scripts in shared memory that all workers use. Wiz explains that during zend_accel_script_persist(), the routine that writes a compiled script into shared memory, PHP advances a process-local counter, CG(map_ptr_last), before it publishes the matching shared counter that other workers see. If an allocation failure happens between those two steps, the two counters disagree. This worker believes certain slots are in use, while shared state still marks them available. Another worker can then reuse a slot and find stale shared metadata that resolves to the wrong type. That is a type confusion: the program treats a piece of memory as one kind of object when it is actually another. Unlike Primitive 1, this one crosses from one worker process to another.
Wiz says chaining these primitives yields pre-authentication remote code execution independent of the application running on top of PHP. It also reports process memory disclosure: the ability to read data left over from other users' requests.
The following is not PHP source code and not an exploit. It is a simplified sketch of the general pattern described in the advisory, meant to show why an interrupted cleanup is dangerous:
1/* Simplified illustration of the stale-global-state bug class.2 * NOT actual php-src code. */34static backtrace_t *last_fatal_bt; /* process-global: survives across requests */56void on_fatal_error(request_t *req) {7 /* Producer: global now points INTO the request heap */8 last_fatal_bt = build_backtrace(req->heap);9 /* ...an OOM / fatal bailout jumps out here, skipping the reset... */10}1112void request_shutdown(request_t *req) {13 free_request_heap(req->heap); /* memory released... */14 /* ...but last_fatal_bt still points into it (dangling pointer) */15}1617void next_request_startup(request_t *next) {18 emit_early_ini_diagnostic(); /* Consumer: touches last_fatal_bt -> use-after-free */19 last_fatal_bt = NULL; /* the reset happens too late */20}The vendor's fix, which the advisory describes as a "complete state reset patch," makes sure this kind of global state is cleared on every exit path, including the exceptional ones, and before anything in the next request can touch it.
The sequence below combines the PHP advisory, the Openwall post and the Wiz write-up. The sources deliberately do not describe the heap-manipulation technique needed for reliable code execution, and neither does this article.
require failure, or Maximum execution time exceeded. Alternatively, a late-shutdown diagnostic repopulates the state after it was cleared.EG(last_fatal_error_backtrace), which now references freed, attacker-influenced memory.zend_accel_script_persist() between the local CG(map_ptr_last) advance and the shared-frontier publication. Another worker then reuses an affected slot, producing cross-worker type confusion.The vector is 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:
Metric | Value | What it means |
|---|---|---|
Attack Vector (AV) | Network | Exploitable remotely over the internet |
Attack Complexity (AC) | High | Requires heap shaping and precise sequencing |
Attack Requirements (AT) | Present | Depends on deployment conditions such as persistent workers and same-worker reuse |
Privileges Required (PR) | None | No account or login needed |
User Interaction (UI) | None | No victim needs to click anything |
Vulnerable System Confidentiality / Integrity / Availability | High / High / High | Full compromise of the PHP process |
Subsequent System C/I/A | None / None / None | Score does not assume spread to other systems, though attackers can still pivot in practice |
The result is a 9.2 Critical base score. No CVSS v3.1 score was provided by the CNA.
Vendor | Product | Affected Versions | Fixed Versions |
|---|---|---|---|
PHP Group | PHP (php-src) | 8.2.* before 8.2.35 | 8.2.35 |
PHP Group | PHP (php-src) | 8.3.* before 8.3.36 | 8.3.36 |
PHP Group | PHP (php-src) | 8.4.* before 8.4.27 | 8.4.27 |
PHP Group | PHP (php-src) | 8.5.* before 8.5.12 | 8.5.12 |
According to the PHP advisory and the Openwall post, the deployments most at risk are those using persistent PHP workers, chiefly PHP-FPM and Apache mod_php. That covers most WordPress, Drupal, Joomla, Laravel, Magento, and custom PHP sites, along with shared hosting providers and SaaS platforms built on PHP. Wiz says it achieved RCE against default installs of WordPress, Drupal and Joomla, and stresses that the bug lives in PHP itself rather than in any CMS. Keeping your plugins up to date will not protect you.
The sources disagree on one important point. Wiz states that PHP 5.4 and later releases are affected. The PHP Group's CVE record lists only the supported 8.2 through 8.5 branches. For patching, the vendor's list is authoritative, since those are the branches that have fixes. Organizations still running end-of-life PHP (5.x, 7.x, 8.0, 8.1) should assume they are vulnerable with no official patch coming, and should treat this as an urgent reason to upgrade.
The intelligence picture also has gaps. The sources do not fully enumerate which SAPIs (server interfaces) beyond mod_php and PHP-FPM are exploitable, or exactly which OPcache settings are sufficient. The advisory also mentions a related report, GHSA-w8hw-w9fx-j79w, but none of the analyzed sources explain how it relates to this CVE.
No confirmed exploitation in the wild, for now. None of the three references report in-the-wild attacks, threat-actor activity, or campaigns. CISA's SSVC rating of Exploitation: none agrees, and the CVE does not appear in CISA's Known Exploited Vulnerabilities catalog as of publication. There is also no public proof-of-concept code; Wiz has not released php2shell.
That should not reassure anyone for long. Wiz's own assessment is that the public disclosure, PHP's enormous install base, and a demonstrated pre-auth RCE make rapid exploitation likely. Patches tell skilled attackers where to look: comparing patched and unpatched source code is standard practice. And if one research team, aided by AI models, went from discovery to working exploit in two days, defenders should not count on weeks of grace.
What an attacker gets. Successful exploitation runs code with the privileges of the web worker (often www-data, apache, or nginx). From there, according to the advisories, an attacker could reach:
Wiz also highlights the memory disclosure angle: reading leftover data from other users' requests, potentially including passwords, authentication tokens, API keys, session identifiers, and TLS certificates belonging to other requests or the server itself. That is reminiscent of Heartbleed-style leaks. An attacker might not even need code execution to cause damage.
Worst case. On shared hosting, many customers' sites often run on the same PHP infrastructure. A single compromised worker, or the cross-worker OPcache type confusion, could expose other tenants on the same host. Large-scale opportunistic exploitation against WordPress sites would also hand criminals a ready-made botnet for SEO spam, credit card skimming, malware hosting and phishing.
Why it is not a 10. Exploitation is rated difficult. The attacker has to steer two requests to the same worker, trigger an interruption at exactly the right moment, and shape the heap. CISA rates the flaw not automatable. Those hurdles slow down mass exploitation; they do not stop a determined attacker.
The PHP advisory and the Openwall post agree that upgrading is the only fully reliable mitigation. Openwall states plainly that no complete generic configuration workaround exists.
First, check what you are running. Note that the command-line PHP and the web server's PHP can differ:
1# CLI version2php -v34# PHP-FPM version (binary name varies by distro)5php-fpm -v 2>/dev/null || php-fpm8.3 -v67# List PHP packages on Debian/Ubuntu8dpkg -l | grep -E '^ii\s+php[0-9.]*-(fpm|cli|common)'910# List PHP packages on RHEL/Alma/Rocky/Fedora11rpm -qa | grep -E '^php'If you use distribution packages (Debian, Ubuntu, Red Hat and others), the vendor may backport the fix without bumping to the upstream version number. Check your distribution's security tracker rather than relying on the version string alone.
After upgrading, fully restart all PHP workers. A worker still running old code stays vulnerable:
1# PHP-FPM (adjust service name to your version)2sudo systemctl restart php8.3-fpm34# Apache with mod_php5sudo systemctl restart apache2 # Debian/Ubuntu6sudo systemctl restart httpd # RHEL-family78# Containers: rebuild images on the patched base and redeploy9docker pull php:8.3-fpm && docker compose up -d --buildWiz recommends starting with internet-facing PHP applications, especially WordPress, Drupal and Joomla, and anything running PHP-FPM with OPcache. Next, audit the whole environment, including containers, CI images and forgotten staging boxes, so that no affected version or OPcache configuration remains. End-of-life PHP versions deserve particular attention because no patch is coming for them.
The PHP advisory lists several partial mitigations. None is a substitute for patching. Each must be set administratively, meaning in the main php.ini or pool configuration rather than per-request, and each requires restarting all workers.
a) PHP 8.5 only: disable fatal error backtraces. This removes the confirmed fatal backtrace producer. The trade-off is less diagnostic detail when fatal errors occur.
1; php.ini (PHP 8.5)2fatal_error_backtraces = 0b) Disable session upload progress to block the early session producer/consumer path. The trade-off is losing upload progress bars that rely on this feature.
1; php.ini2session.upload_progress.enabled = 0c) Recycle PHP-FPM workers after every request. If a worker never handles a second request, stale state has nothing to carry over into. Expect significant performance degradation from constant process creation.
1; /etc/php/8.3/fpm/pool.d/www.conf2pm.max_requests = 1This setting addresses the same-worker reuse that the fatal-error-backtrace and session attacks depend on.
d) Eliminate early INI diagnostics. Prefer static php.ini or pool startup configuration over per-request overrides via .htaccess (php_value / php_flag), PHP_VALUE, or PHP_ADMIN_VALUE, and remove deprecated or invalid directives. The advisory specifically calls out directives that trigger early deprecation notices, such as auto_detect_line_endings=1, filter.default, and legacy assert and session settings. The trade-off is less per-request configuration flexibility.
1# Hunt for per-request PHP overrides2grep -rniE 'php_(value|flag|admin_value|admin_flag)' /var/www --include=.htaccess3grep -rniE 'PHP_(ADMIN_)?VALUE' /etc/nginx /etc/apache2 /etc/httpd 2>/dev/null45# Look for deprecated directives the advisory calls out6php -i | grep -Ei 'auto_detect_line_endings|filter.default|assert\.'e) Eliminate attacker-reachable fatal errors and timeouts. Harden application paths so users cannot easily trigger fatal errors, memory exhaustion, or Maximum execution time exceeded conditions. This may require application code changes.
f) Eliminate warnings during cleanup. Fix warnings and diagnostics emitted during extension post-deactivation and server cleanup, which can repopulate stale state during late shutdown. This may require configuration changes and hardening of extension cleanup paths.
g) Edge and WAF protections. Wiz reports that out-of-the-box mitigations and WAF guidance were prepared in coordination with Automattic, Akamai, AWS, Cloudflare, Fastly, Google, Imperva, Microsoft, and Vercel. If your site sits behind one of these providers, confirm that the relevant rules are enabled. These rules may need tuning and may not block every exploitation strategy. The Openwall post likewise frames WAFs and network segmentation as defense-in-depth, not a fix.
h) Runtime protection. Wiz recommends eBPF-based runtime sensors that can detect and kill anomalous PHP worker behavior, and notes that Wiz Sensor version 1.0.12768 includes such termination rules. These can interrupt workers and affect availability if they produce false positives.
No Sigma, YARA, or formal detection signatures have been published. The PHP advisory and Wiz do describe behavioral signals. First, turn on comprehensive logging in startup configuration, ideally during an isolated audit:
1; php.ini - audit logging2log_errors = On3error_reporting = E_ALL4error_log = /var/log/php/php_errors.logThen review Apache, PHP-FPM, reverse proxy and service logs for:
auto_detect_line_endings=1, filter.default, or legacy assert/session settings, especially when they come from .htaccess, PHP_VALUE or PHP_ADMIN_VALUE overridessession_start(), a sign of possible producer/consumer probingPHPSESSID response cookies (Wiz), which may indicate memory disclosureSome quick starting points:
1# OOM and timeout fatals2grep -E 'Allowed memory size of [0-9]+ bytes exhausted|Maximum execution time of [0-9]+ second' /var/log/php/*.log34# Worker crashes in PHP-FPM5grep -Ei 'exited on signal|SIGSEGV|exited with code' /var/log/php*-fpm.log67# Bursts of HTTP 500s (combined log format)8awk '$9 == 500 {print $1, $4, $7}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | headIf you find suspicious sequences on a server that was unpatched, treat it as a potential compromise. Rotate the database credentials, API keys and secrets that the PHP process could read, invalidate user sessions, and look for web shells and unexpected scheduled jobs.
The most notable detail here may be the timeline rather than the bug. Wiz says frontier AI models helped it go from discovery to a working pre-authentication exploit against three of the web's most popular CMS platforms in about two days, and OpenAI appears in the CVE's own credits. This is a subtle bug class: an interrupted cleanup in one request, a stale pointer, and an early read in the next request, with a cross-process OPcache race as a second primitive. Bugs like this have historically taken specialists weeks or months to find and weaponize. If that work is getting faster for defenders, it is getting faster for attackers too, and the window between patch and mass exploitation will likely keep shrinking.
The flaw also shows a recurring weakness of long-running worker processes. PHP's "share nothing" reputation rests on the idea that every request starts clean. FPM and mod_php trade some of that isolation for speed, and every global that outlives a request becomes a possible channel between strangers. The vendor's complete state-reset patch addresses this class of problem, and I expect more scrutiny of how runtimes like PHP clean up after requests fail.
Over the next several weeks, watch for:
The takeaway is simple. If PHP is on your attack surface, upgrade to 8.2.35, 8.3.36, 8.4.27 or 8.5.12, restart every worker, and check your logs. If you are still on an end-of-life PHP release, this is the bug that should finally get you to upgrade.