PHP2Shell: critical RCE in the PHP interpreter (CVE-2026-109618 and CVE-2026-109619)
This guide was assembled from the disclosures of 2026-10-21 to 2026-10-23. Several details were still changing at the time of writing (exploitation status, affected downstream products).
Goal
Understand, based on primary and high-authority sources:
- What PHP2Shell is: the bug class, why it got that name, and why it differs from a classic web shell.
- How to simulate safely: set up an isolated lab to validate detection and protection without writing an exploit, PoC, or web shell.
- How to protect: patching (the only complete mitigation), interim mitigations, proxy/WAF limits, and host-level detection.
Note: this guide contains no exploit.
PHP2Shell is a memory safety flaw in the C interpreter of PHP. There is no legitimate defensive-learning path that requires reproducing the exploit, and a memory-corruption PoC is exactly the kind of artifact that gives an attacker a real offensive advantage. So here the "simulate" part is built around: (a) an isolated lab with a deliberately outdated PHP to study the crash telemetry, (b) the public open-source scanner to measure exposure, and (c) validating your own detection rules with benign triggers. At no point does the guide provide exploitation code. That choice is part of the content: a defender thinks about containment and detection, not about reproducing the weapon.
Context
On 2026-10-21, the PHP team published emergency fixes for two critical remote code execution (RCE) vulnerabilities in PHP, disclosed together under the nickname "PHP2Shell": CVE-2026-109618 and CVE-2026-109619. The fixes came with official GitHub advisories (GHSA-w8hw-w9fx-j79w and GHSA-6wmf-4rv5-p3rp).
Source: Help Net Security "PHP2Shell: PHP vulnerabilities patched" ; BleepingComputer "PHP ships emergency patches for critical PHP2Shell flaws".
Amazon Threat Intelligence reported that its MadPot honeypot recorded an exploitation attempt on 2026-10-17, four days before public disclosure, indicating that some actor already had details of the flaw during the embargo. The source IP was linked to a commercial anonymization service.
Source: Help Net Security (above).
Several vendors described the impact as "70% of the internet" / "almost half the web". That is vendor marketing framing, not a verifiable number: PHP is ubiquitous, but the fraction that is actually exploitable depends on SAPI, configuration, and version. Treat it as an order of magnitude, not as fact.
Source (framing examples): Wiz CVE-2026-109618 ; Dark Reading.
Why it matters to learn this now: unlike most PHP application bugs (SQLi, file upload, eval of input), these flaws are in the engine, are unauthenticated, and fire before any of your application code runs. This inverts several application-layer defense assumptions.
Key concepts
1. What "PHP2Shell" is (and why the name misleads)
"PHP2Shell" is a disclosure nickname for the pair of CVEs, not a product name nor a malware name. The "2Shell" describes the outcome, leading to code execution ("shell"), not the mechanism.
The common confusion is treating PHP2Shell as a classic "web shell". They are different things:
CLASSIC WEB SHELL (e.g.: c99, b374k) PHP2SHELL (CVE-2026-109618/619)
────────────────────────────────── ───────────────────────────────
The attacker manages to WRITE/INJECT The attacker sends ONE crafted
a malicious .php file (via upload, HTTP request that corrupts the
LFI, misconfig) and then accesses it. interpreter's own memory.
APPLICATION / configuration bug. MEMORY SAFETY bug in the C engine.
Fixed in code/app/permissions. Fixed ONLY by updating PHP.
WAF and input sanitization help a lot. WAF only reduces exposure; no fix.
The practical implication of this distinction is the most important concept of the session: there is no "sanitize your input" that solves PHP2Shell. The fix is in the interpreter binary.
2. CVE-2026-109618: RCE via corruption in an out-of-memory (OOM) condition
The publicly disclosed root cause: an out-of-memory (OOM) condition can interrupt PHP while internal request structures are only partially initialized. The request cleanup routine can then treat that invalid/partial data as if it were valid, leading to memory corruption and, from there, to a code execution path.
Source: Help Net Security.
Normal flow of a request in PHP:
[arrival] → allocate internal state → initialize (steps 1..N) → process → cleanup (free everything)
Flow under OOM in the middle of initialization:
[arrival] → allocate internal state → initialize (steps 1..K) ──✗ OOM──┐
│
PARTIAL state: steps K+1..N never ran │
▼
cleanup runs ASSUMING COMPLETE state → operates on garbage as if valid
→ memory corruption → execution
The exact vulnerable code path and the full CWE classification were not fully disclosed publicly at the time of writing, a deliberate decision to slow down weaponization. Wiz states this explicitly.
Source: Wiz CVE-2026-109618.
3. CVE-2026-109619: RCE via "stale" global state between requests
The disclosed root cause: an internal state belonging to one request can persist after the memory backing it has already been freed. A later request, served by the same worker, can use that "stale" state before normal initialization resets it. It is a pattern of the use-after-free / dangling pointer family exposed through worker reuse.
Source: Help Net Security.
A PHP-FPM / mod_php worker = a LONG-LIVED process that serves many requests:
Request A ─ creates state X (of the request) ─ ... ─ X's memory is FREED at the end
│
but a global pointer/state still references X
│
Request B ─ (SAME worker) uses stale X BEFORE reinitialization ─→ use-after-free ─→ execution
This is why worker reuse matters so much here: in FPM and mod_php the processes are long-lived and serve thousands of requests; this creates the cross-request contamination window that 109619 exploits.
4. Attack surface: why it "affects everyone"
The flaws affect PHP serving web requests across practically all SAPIs: PHP-FPM, Apache with mod_php, php-cgi, FrankenPHP, and LiteSpeed. Configurations with mod_php are at the highest risk (the interpreter runs inside the Apache process itself, in the same address space and often with more privilege).
Source: Help Net Security ; Picus Security.
The impact extends to products that embed PHP. As of 2026-10-22, Palo Alto Networks confirmed that all versions of PAN-OS and Panorama are affected and had not yet released a fix; Fortinet and Cisco published advisories but were still investigating their products.
Source: Help Net Security.
Characteristics that nullify application-layer defenses: the attack is unauthenticated and a single request is enough; on the confirmed path via mod_php it can run before the application code, so the application logs may record nothing.
Source: Picus Security.
CVSS: Wiz reports 9.2 (CNA score), "Critical", but notes that severity labels vary across vendors (from HIGH to LOW in some feeds). Confirm in the official advisory / NVD.
Source: Wiz CVE-2026-109618.
Is there a public/weaponized PoC? The sources contradict each other: Help Net Security speaks of "a working exploit already exists"; Wiz says in the body of the text that there was no confirmed public PoC at disclosure, while the metadata of the same page marks "Has Public Exploit: Yes". Treat it as probably yes, in restricted circles, and assume the window is open.
Sources: Help Net Security ; Wiz.
5. The bug class: memory safety in the engine (the lesson that generalizes)
The two CVEs are memory safety bugs in C code (OOM-induced corruption; stale state / use-after-free). This has three consequences you must internalize as a defender:
- It is not CWE-78 (command injection). There is no application input to "escape". The "validate input" mindset does not apply to the engine.
- The only complete fix is to swap the interpreter binary. Edge mitigations only narrow the window.
- Detection needs to look at the host, not just the HTTP. Since the payload can run before/outside the application, the reliable signals are interpreter crashes and anomalous process behavior, not URL signatures.
Practical example: isolated lab for detection and defense validation
The goal of this lab is not to exploit the flaw. It is to answer three defensive questions:
- Does my instrumentation see an out-of-the-ordinary crash/OOM of the interpreter?
- Do my rules fire when a web worker spawns a suspicious child process?
- Can I confirm the version actually served (not just the CLI one) and verify the patch?
0. Golden rule of the lab
Isolate for real: internal Docker network (no egress), snapshot/disposable, no real data, no real credentials. Do not run this on a host that also serves production.
1. Disposable environment (deliberately outdated PHP, ONLY for crash telemetry)
# docker-compose.yml — isolated lab; network with no internet egress
# Usage: study crash/OOM LOGS and validate DETECTION.
services:
web:
# Deliberately old image, so the worker can log crash/OOM.
image: php:8.2.10-fpm # < 8.2.35 (pre-patch) — lab only
networks: [labnet]
volumes:
- ./app:/var/www/html:ro
- ./php-lab.ini:/usr/local/etc/php/conf.d/zz-lab.ini:ro
# no 'ports:' -> publishes nothing on the host
proxy:
image: nginx:1.27-alpine
networks: [labnet]
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
ports:
- "127.0.0.1:8080:80"
networks:
labnet:
internal: true
2. Generate benign crash/OOM telemetry (no exploit) to calibrate alerts
The most frequently cited detection signals are bursts of SIGSEGV/SIGABRT in the php-fpm/Apache logs and OOMs of the Allowed memory size ... exhausted type. You can produce these signals in a completely benign way, just to see if your pipeline captures them:
<?php // benign-oom.php — NOT an exploit: just blows the memory_limit on purpose
// to generate the OOM log line you want to learn to alert on.
ini_set('memory_limit', '32M');
$buf = [];
while (true) { $buf[] = str_repeat('x', 1024 * 1024); } // allocates up to the limit
# trigger the benign OOM and watch the worker log
docker compose exec web php /var/www/html/benign-oom.php 2>&1 | tail -n 3
# Expected: "Fatal error: Allowed memory size of 33554432 bytes exhausted ..."
Build a baseline first: many crashes/OOMs are legitimate (traffic spikes, heavy libs like imagick/gs). The value of the alert is in the anomalous pattern (clustered on specific requests), not in the isolated event.
3. Validate the "suspicious child process from a web worker" rule
Recommended process telemetry: alert when a web worker (php-fpm, php-cgi, httpd/apache2 with mod_php, lsphp, frankenphp) spawns shells (sh, bash, cmd.exe, powershell.exe), interpreters (python, perl, ruby), network tools (curl, wget, nc, ncat, socat, certutil.exe), or recon (whoami, id, uname, hostname). Watch for reverse shell strings (/dev/tcp/, mkfifo, bash -i, nc -e, base64 -d | sh).
Source: Picus Security.
auditd rule (Linux) scoped to the web server user (euid=33 = www-data on Debian/Ubuntu; adjust to your environment):
# /etc/audit/rules.d/php-exec.rules
-a always,exit -F arch=b64 -S execve -F euid=33 -k php_exec
sudo augenrules --load
# then, BENIGN test: simulate a legitimate "child" leaving the web context
# to confirm the rule captures it — with no exploit involved:
sudo -u www-data id # should generate an event with the php_exec key
ausearch -k php_exec -i | tail
Tool recommendation.
- auditd (open-source, native): does well at capturingexecveby user/arch at low cost; limited because it is per-host, rules are verbose, and correlation/alerting is left to an external SIEM. Open-source alternative: Falco or Tracee (eBPF) provide richer process-tree rules and container context; Sysmon for Linux (Microsoft, free) is another option. On Windows, Sysmon Event ID 1 filtering byParentImage. For this context (mixed Linux fleet + containerized apps) the pair auditd for the minimal rule + Falco for the process tree covers it well; if you already have a commercial EDR that does process-tree, use it and treat the above as validation/second signal.
4. Measure exposure with a public scanner (not an exploit)
Searchlight Cyber released an open-source scanner, php2shell-scanner, to test exposure.
Source: Help Net Security.
Tool recommendation.
- php2shell-scanner (Searchlight, open-source): does well at quickly checking whether a target looks vulnerable (version/SAPI fingerprint) without exploiting; limited because a version scanner can give false positives/negatives (distro backports report "old" versions that are already fixed) and only sees what is exposed at the edge. Alternatives: SCA/host scanners like Trivy or Grype (open-source) cross-referencing the installed version with the advisory; commercial feeds (Wiz, Qualys, Tenable) for a large fleet. Validate the tool yourself (read the code, run it in the isolated lab) before trusting its verdict; it is a very recent project.
- Controlled adversary simulation: the Picus Threat Library carries threat ID 60730 "PHP Php2Shell RCE Campaign" (Web Application module), which tests whether the attack reaches your PHP assets and whether the controls flag it — useful for those already Picus customers; the open-source alternative is to build the case in the lab above + the scanner. Source: Picus.
5. Verify the patch the right way (the CLI php -v misleads)
Checking only the CLI version is insufficient: the binary serving the site (the PHP-FPM pool, the Apache module) can be different from the command-line PHP. Verify the runtime that actually serves each site, via a protected diagnostic endpoint.
Source: Picus Security.
<?php // diag-version.php — protect by IP/allowlist/auth; remove after rollout
header('Content-Type: text/plain');
echo 'runtime=' . PHP_VERSION . PHP_EOL; // REAL version serving this pool
echo 'sapi=' . PHP_SAPI . PHP_EOL; // fpm-fcgi, apache2handler, cli-server...
# compare: what the CLI says vs what the pool actually serves
php -v | head -1
curl -s http://127.0.0.1:8080/diag-version.php
Fixed versions (see the Protection section): 8.5.12, 8.4.27, 8.3.36, 8.2.35 (or your distro's fixed package / backport for 7.4/8.0/8.1).
How to protect (in priority order)
A. Update: the only complete mitigation
The PHP team is explicit: updating is the only complete mitigation. Fixed versions:
╔══════════════╦════════════════════════════════════════════════════════╗
║ Branch ║ Fixed version ║
╠══════════════╬════════════════════════════════════════════════════════╣
║ PHP 8.5 ║ 8.5.12 ║
║ PHP 8.4 ║ 8.4.27 ║
║ PHP 8.3 ║ 8.3.36 ║
║ PHP 8.2 ║ 8.2.35 ║
║ 7.4/8.0/8.1 ║ minimal source-code backports (branches out of ║
║ ║ official support) or upgrade to a supported branch ║
╚══════════════╩════════════════════════════════════════════════════════╝
Linux distributions and LTS vendors received the patch before public release; prefer the fixed package from your distro when it exists (the package version number may differ from upstream because of backporting, trust the vendor's changelog, not the "bare" number).
Source: Help Net Security ; BleepingComputer.
After updating, restart the workers (PHP-FPM, Apache), because long-lived processes keep running the old binary in memory until restart.
B. For what cannot be patched right away (appliances, PAN-OS, etc.)
The NCSC (UK) guidance was to take out of production any system that cannot be patched immediately. For affected appliances without a patch (e.g.: PAN-OS at the time of writing), restrict network exposure to the minimum and monitor.
Source: Help Net Security.
C. Interim mitigations (reduce exposure, do NOT fix)
While the patch has not arrived, the following settings reduce exposure; they should be reverted afterward when they cause harm:
; php-lab.ini / php.ini — INTERIM mitigation, does not replace the patch
memory_limit = -1 ; reduces the 109618 (OOM) window ...
; WARNING: -1 = DoS risk via memory exhaustion
post_max_size = 1M ; a small body makes request triggers harder
session.upload_progress.enabled = 0 ; disables the upload-progress vector
fatal_error_backtraces = 0 ; (PHP 8.5 only)
There is a follow-up advisory of 2026-10-23, CVE-2026-115560, which affects a narrow configuration; the recommended mitigation is session.upload_progress.enabled=0 and there was still no patch at the time of writing.
Source: Picus Security.
Note the trade-off: memory_limit=-1 swaps an RCE risk for a DoS risk. In a fleet, prefer patch + restart over relying on an interim measure.
D. Request-shape limits at the proxy/WAF
The PHP interim guidance maps to limits you can enforce at the proxy, kept even after the patch (defense in depth): decoded request size, field count, number of multipart parts, headers per part, and nesting depth. Indicators of a suspicious request shape: huge or deeply nested POST/multipart bodies, thousands of fields/parts, chunked requests without Content-Length, and PHP_SESSION_UPLOAD_PROGRESS sent to endpoints that never use it.
Source: Picus Security.
# nginx.conf (excerpt) — request-shape limits; keep after the patch
http {
client_max_body_size 1m; # body cap
client_body_buffer_size 16k;
large_client_header_buffers 4 8k;
# reject methods/nesting level outside the expected at the edge, per your app
}
Tool recommendation.
- WAF: Cloudflare published rules for known vectors, available to all customers; Check Point NGX carries the signatureasm_dynamic_prop_CVE_2026_109618("PHP Remote Code Execution"). They do well at blocking known attempts and giving visibility; they are limited because a signature covers known vectors, and since the flaw is unauthenticated memory corruption, new variations can slip through, so a WAF does not replace a patch. Open-source alternative: ModSecurity + OWASP CRS with request-shape limits (above). For this context: use the WAF you already operate to buy time, with the request-shape limits at the proxy as an independent net; treat both as window reduction, not a fix. Source: Help Net Security ; Picus.
E. Detection and response (assume the window was open)
Since there was exploitation from 2026-10-17 (before disclosure), after patching assume possible compromise and look for: new/modified .php files in web roots and upload directories, unexpected cron jobs, unknown processes/users, and outbound connections from the web server user. If something is wrong, rotate every secret the web user could read. Forward logs off the host (an attacker with permission can tamper with local logs). Remember: a native-code payload can open a socket without spawning a shell, so monitor outbound connections, not just process creation.
Source: Picus Security.
# post-patch sweep (run from a known-good inventory, with caution)
find /var/www -name '*.php' -mtime -14 -printf '%TY-%Tm-%Td %p\n' | sort # recent .php
sudo -u www-data crontab -l 2>/dev/null # web user's cron
ss -tunp | grep -Ei 'www-data|php-fpm|apache|nginx' || true # web uid connections
Common pitfalls
-
"I ran
apt upgrade php, so I'm safe."
Why it happens: the CLI version is confused with the version that serves the site. How to recognize it:php -vshows fixed, but the FPM pool / mod_php / an appliance embedding PHP is still old, and the workers were not restarted. How to avoid it: verify the runtime per pool (the diagnostic endpoint from the practical section), restart FPM/Apache, and inventory PHP embedded in appliances (PAN-OS, etc.). -
Treating it as application injection and relying on sanitization/WAF.
Why it happens: most PHP bugs we see day to day are application-layer. How to recognize it: the response plan talks about "validate input", "WAF rule" as the fix. How to avoid it: internalize that it is memory safety in the engine: WAF and edge limits only reduce the window; the fix is the binary. Reliable detection is on the host (anomalous crash/OOM, process tree, network egress), not on the URL. -
Turning on an interim mitigation and forgetting the trade-off / the rollback.
Why it happens:memory_limit=-1"solves" the OOM signal and stays. How to recognize it: months later the interim measure is still there, now turned into a DoS vector via memory exhaustion; or someone thinks interim = fixed. How to avoid it: treat the interim measure as a bridge with an expiry date, document it, and revert after the patch + restart.
Resources to go deeper
Primary sources (confirm here before deciding operations):
- Official PHP advisories on GitHub: GHSA-w8hw-w9fx-j79w and GHSA-6wmf-4rv5-p3rp. Open them manually at github.com/php/php-src/security/advisories/ and in the release notes for 8.2.35 / 8.3.36 / 8.4.27 / 8.5.12. It is the canonical reference for root cause and versions.
- NVD / CVE: CVE-2026-109618 and CVE-2026-109619 (for the official CVSS and CWE when published).
High authority (technical journalism and vendor research):
- Help Net Security: patch, timeline, root cause of the two CVEs: the best root-cause overview (OOM corruption / stale state), fixed versions, MadPot, downstream products.
- BleepingComputer "PHP ships emergency patches for critical PHP2Shell flaws": patch summary and timeline.
- Picus Security: detection, logging, request-shape limits, exposure testing: the best defensive source, with log indicators, auditd rule, process telemetry, interim mitigations, post-patch assessment.
- Wiz CVE-2026-109618 (vuln DB): CVSS 9.2, exploitation status, per-distro table. Caution: the page itself flags internal inconsistencies (it was AI-generated); use it only for orientation, confirm in the advisory.
- Dark Reading "'PHP2Shell' Bugs Signal Red Alert": industry context and reaction.
Exposure tool (validate it yourself before trusting it):
- php2shell-scanner (Searchlight Cyber, open-source): exposure scanner cited by Help Net Security; confirm the official repository and read the code before using it.