The front door to every PHP site just got a skeleton key, and the locksmith shows up at 2 PM
CVE-2026-109619 is a heap use-after-free (CWE-416) in PHP's php_session_rfc1867_callback() function within ext/session/session.c. When session.upload_progress.enabled is On — the default since PHP 5.4.0 — a crafted multipart/form-data POST containing a field matching session.upload_progress.name (default: PHP_SESSION_UPLOAD_PROGRESS) triggers the callback *before any application code runs*. The callback caches raw zval * pointers into HashTables that are freed during session re-decode; a subsequent upload-progress update dereferences the stale pointer via zval_ptr_dtor, and an attacker who wins the heap race can redirect execution through zend_call_function(). Third-party research (ikow/php2shell-poc, confirmed by ASAN) attributes this specifically to var_destroy __destruct reentry. All PHP versions from 5.4.0 through 8.5.11 are affected. Patches are expected as PHP 8.2.35 / 8.3.36 / 8.4.27 / 8.5.12, releasing today (2026-10-21 14:00 UTC). PHP 8.1 and earlier are EOL with no official fix. Note: The two php2shell CVEs (CVE-2026-109618 and CVE-2026-109619) are not yet publicly distinguished by PHP; third-party analysis attributes this CVE to the upload-progress handler UAF and CVE-2026-109618 to a separate multipart-parser flaw.
The vendor's CVSS v4.0 9.2 CRITICAL rating is warranted and possibly conservative. This is a pre-authentication, zero-click, network-reachable RCE in the runtime itself — not in a CMS plugin or optional extension, but in the PHP interpreter's default request-parsing path. The attack surface is every PHP process that accepts HTTP POST requests, which encompasses roughly 2.2 million internet-facing hosts by Censys count and untold millions behind reverse proxies. The 9.2 (rather than 10.0) reflects that heap-corruption exploitation is not deterministic across all builds and architectures, which is fair. But with a weaponized tool (PHP2Shell / Calif.IO) already circulating and Cloudflare confirming early attacks as of 07:43 UTC today, the practical exploitability gap is closing rapidly. noisgate holds this at CRITICAL 9.2 — unchanged from vendor.
5 steps from start to impact.
Attacker identifies PHP target
X-Powered-By: PHP/x.x.x), default error pages, and known PHP applications (WordPress, Laravel, Drupal, Magento) are trivial to fingerprint. Censys reports ~2.23 million hosts advertising PHP. Tools like httpx or Shodan dorks (http.component:php) enumerate targets at scale.- Network access to a host running PHP (HTTP/HTTPS)
- None — PHP is the most deployed server-side runtime on the internet
http.component:php; runZero asset inventory for PHP version fingerprintingCraft malicious multipart POST
multipart/form-data POST request containing a field named PHP_SESSION_UPLOAD_PROGRESS (or whatever session.upload_progress.name is set to — the default suffices). This triggers php_session_rfc1867_callback() during SAPI request initialization, *before the application's index.php is even entered*. The PHP2Shell tool (credited to Calif.IO) automates this payload generation. The ikow/php2shell-poc repository on GitHub provides a research PoC with lab setup.- Target PHP has
session.upload_progress.enabled = On(the default) - Target accepts multipart/form-data POST on any endpoint
- WAF rules blocking multipart POSTs with
PHP_SESSION_UPLOAD_PROGRESSfield — Cloudflare deployed rules 07:43 UTC today - Servers behind strict reverse proxies that strip or reject unexpected Content-Type headers
PHP_SESSION_UPLOAD_PROGRESS field; Cloudflare Managed Ruleset 'Generic Rules - Anomalous Execution'Trigger session re-decode and UAF
zval * pointers in the upload-progress bookkeeping HashTable. The re-decode calls zval_ptr_dtor on the prior tree, freeing those entries — but the callback still holds stale references. This is the use-after-free primitive. ASAN-confirmed by researcher Qi Deng (@ikow) in the php2shell-poc writeup. The var_destroy path allows __destruct reentry, giving the attacker a controlled function-call primitive.- Step 2 payload reaches the PHP SAPI parser
- Default session serializer configuration
- Heap layout is architecture-dependent; exploitation reliability varies across PHP builds, operating systems, and memory allocators (glibc vs jemalloc vs musl)
- ASLR and NX on modern Linux reduce one-shot reliability
Achieve code execution as PHP process user
zend_call_function(). The attacker now executes arbitrary code as the PHP-FPM worker user (typically www-data, apache, or nginx). From here, standard post-exploitation applies: reading wp-config.php / .env for database credentials, dropping webshells, establishing reverse shells, or pivoting to internal services. The PHP2Shell tool reportedly automates this end-to-end.- Successful heap reclamation (previous step)
- Not all PHP builds are equally exploitable — statically linked, hardened, or Alpine/musl builds may resist the current public exploit
- SELinux/AppArmor in enforcing mode can constrain post-exploitation
sh, bash, curl, wget, nc, python, perl, certutil.exe; Sysdig Falco / Aqua / EDR rules for container escape patternsLateral movement and data exfiltration
- Code execution achieved (Step 4)
- Database credentials or cloud metadata accessible from PHP process context
- Network segmentation between web tier and database tier
- Instance metadata service v2 (IMDSv2) requiring PUT token
- Secrets management (Vault, AWS Secrets Manager) with short-lived tokens rather than disk-stored credentials
php_session_rfc1867_callback) that contains the UAF for CVE-2026-109619, eliminating the trigger path. Apply via php_admin_flag[session.upload_progress.enabled] = off in each FPM pool config, or session.upload_progress.enabled = 0 in php.ini. Note: This mitigates CVE-2026-109619 specifically but third-party analysis suggests CVE-2026-109618 (the sibling multipart parser flaw) remains unaffected by this setting. Given active exploitation, deploy within hours — not the standard noisgate CRITICAL mitigation SLA of 3 days. Restart PHP-FPM/Apache after changing.multipart/form-data POST bodies and block requests containing the PHP_SESSION_UPLOAD_PROGRESS field name. Cloudflare has already deployed managed rules ('Generic Rules - Anomalous Execution'). For ModSecurity: SecRule REQUEST_BODY "PHP_SESSION_UPLOAD_PROGRESS" "id:1,phase:2,deny,status:403". This is a defense-in-depth layer — sophisticated attackers may find bypasses or use non-default field names if session.upload_progress.name was customized. Deploy within hours alongside the php.ini change.bash, sh, cmd.exe), download tools (curl, wget, certutil), or scripting interpreters (python, perl). Sysdig Falco, CrowdStrike Falcon, and similar EDR/runtime security tools can alert on this. This does not prevent exploitation but ensures you detect successful compromise within minutes rather than days. Deploy within hours.- Application-level input validation — the vulnerability fires during PHP's SAPI request initialization, *before* any application code (index.php, .htaccess handlers, framework middleware) executes. No amount of Laravel validation or WordPress nonce checking helps.
- session.use_strict_mode=1 alone — strict mode prevents session fixation by rejecting uninitialized session IDs, but the UAF occurs in the upload-progress callback regardless of session mode. It is a useful hardening measure but does not address the memory-safety flaw.
- Disabling file_uploads in php.ini — the multipart parser runs for any
multipart/form-dataPOST regardless of whether file uploads are enabled. Thesession.upload_progresscallback triggers on the field name, not on an actual file upload. Settingfile_uploads=Offdoes not prevent the vulnerable code path from executing. - PHP open_basedir / disable_functions — these restrict what PHP *scripts* can do, but the vulnerability achieves code execution inside the PHP interpreter itself, at the C level, bypassing all userland restrictions.
The supporting signals.
| In-the-Wild Exploitation | Active. Cloudflare CTO Dane Knecht confirmed early attacks at 07:43 UTC on 2026-10-21. Patchstack reports exploitation from multiple hosting partners starting ~07:34 UTC. Several Japanese platforms and The SEO Framework went offline preemptively. Huntress had *not* observed exploitation in their partner environments as of Oct 20 evening, but the window has since closed. |
|---|---|
| Proof of Concept | Public. ikow/php2shell-poc by Qi Deng (@ikow) — ASAN-confirmed UAF PoC with lab setup and writeup at qideng.substack.com. Weaponized tool PHP2Shell credited to Calif.IO circulating privately (sploitus.com indexes it). syhunt/php2shell-protection provides Apache mitigation and testing toolkit. watchTowr CEO Benjamin Harris confirmed reproduction of CVE-2026-109618; CVE-2026-109619 reproduction implied but not separately confirmed. |
| EPSS Score | Not yet available. NVD record is RESERVED; EPSS has not scored this CVE. Given active exploitation and public PoC, expect EPSS >0.9 (top 1%) once scored. |
| CISA KEV Status | Not listed as of 2026-10-21 10:00 UTC. Given confirmed exploitation, KEV addition is likely imminent. |
| CVSS Vector | Vendor published CVSS v4.0 9.2 only. No v3.1 vector published. Estimated v3.1 equivalent: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H (8.1) — AC:H reflects heap manipulation requirement. Some researchers (watchTowr, Florian Roth) claim effective 10.0 given default-config reachability. |
| Affected Versions | PHP 5.4.0 through 8.5.11 — the session.upload_progress feature was introduced in 5.4.0. PHP 8.1 and earlier are EOL with no official fix. Supported branches: 8.2, 8.3, 8.4, 8.5. |
| Fixed Versions | Expected: PHP 8.2.35 / 8.3.36 / 8.4.27 / 8.5.12 — release scheduled 2026-10-21 14:00 UTC. Unconfirmed until published. Distro backports (Debian, Ubuntu, RHEL, SUSE) will follow on their own timelines. ~884,000 hosts run unsupported PHP branches that will never receive an official fix. |
| Internet Exposure | Censys/Phoenix Security: ~2.23 million hosts advertising PHP headers. ~884,000 on unsupported branches (5.x/7.x/8.0/8.1). Top regions: US (228K), Japan (55K), Germany (46K). Major hosting: Amazon, Hostinger, Alibaba Cloud. True exposure is higher — many PHP sites behind proxies don't expose version headers. |
| Disclosure Timeline | 2026-10-20: php.net announces upcoming critical release. Same day: details leak via Hacker News (user 'petecooper', post deleted). CERT-EU assigns CVE-2026-109618 and CVE-2026-109619. 2026-10-21 07:34 UTC: Patchstack receives exploitation reports. 07:43 UTC: Cloudflare confirms attacks and deploys WAF rules. 14:00 UTC: Patch release scheduled. |
| Credited Researchers | Qi Deng (@ikow) — php2shell-poc research PoC. Calif.IO — PHP2Shell weaponized exploit tool. watchTowr (CEO Benjamin Harris) — independent reproduction. Sansec — early public analysis. CERT-EU — CVE assignment. |
Sources.
- Sansec — Critical PHP RCE: CVE-2026-109618 and CVE-2026-109619
- Huntress — Rapid Response: php2shell Leaked Critical PHP Vulnerabilities
- ikow/php2shell-poc — CVE-2026-109618/109619 Exploit Research PoC
- Patchstack — Major Security Alert: PHP Vulnerable to Critical RCE, Attacks Ongoing
- BleepingComputer — PHP Warns Admins of Two Critical Flaws, Emergency Patches Coming
- Phoenix Security — PHP Core RCE: Two Critical Remote Code Execution Flaws
- Brandefense — PHP2Shell: PHP RCE CVE-2026-109618, 109619
- syhunt/php2shell-protection — Emergency Apache Mitigation Toolkit
Why this verdict
- Zero friction on initial access: This is a pre-auth, zero-click, network-reachable RCE triggered by a single HTTP POST. No credentials, no user interaction, no application-specific conditions. The only prerequisite —
session.upload_progress.enabled=On— is the factory default on every PHP installation since 5.4.0 (2012). This means virtually 100% of unpatched PHP installations are vulnerable out of the box. - Attack surface is historically vast: PHP powers the backend of an estimated 75%+ of websites with known server-side technology. Censys identifies 2.23 million directly fingerprinted hosts; the true number behind CDNs and proxies is far larger. The vulnerability is in the *interpreter itself*, not in any specific application — WordPress, Laravel, Drupal, Magento, custom apps, and API backends are all equally exposed if they run PHP.
- Weaponized exploit in circulation with confirmed exploitation: The PHP2Shell tool is already weaponized and circulating. Cloudflare and Patchstack hosting partners confirmed active attacks starting ~07:30 UTC today, hours before the patch release. This is no longer a theoretical risk — it is an active incident.
- Heap UAF exploitation uncertainty provides the only downward pressure: Memory corruption exploits are not deterministic across all builds, architectures, and memory allocators. ASLR and NX reduce one-shot reliability. This is why the vendor rated 9.2 rather than 10.0, and why we do not upgrade above 9.2. However, the public PoC and weaponized tool demonstrate that this gap is surmountable for a motivated attacker across common Linux/x86_64 PHP-FPM deployments.
- Role multiplier: PHP is not a single-role component — it IS the web application tier for the majority of the internet. (a) *Low-value role:* dev sandboxes, local MAMP/WAMP — exploitation yields local code exec, limited blast radius. (b) *Typical role:* production web servers behind load balancers — exploitation yields access to database credentials, session stores, API keys, user data; blast radius is host-to-tenant-scale. (c) *High-value role:* shared hosting platforms (one PHP process serves hundreds of tenants — compromise is fleet-scale), WordPress multisite (domain-scale), e-commerce backends processing payment data (PCI-scope breach), internal admin panels (identity/privilege escalation), and CI/CD systems with PHP tooling. Since PHP's canonical deployment IS the web-facing application tier serving production traffic — easily >80% of installations — the high-value role floor is CRITICAL. The affected component is definitionally an internet-edge runtime processing untrusted input.
Why not higher?
The vendor's 9.2 accurately reflects that heap use-after-free exploitation is architecture-dependent and not 100% reliable on all builds. Musl-based Alpine containers, statically linked PHP, and non-standard memory allocators can break the current public exploit chain. A 10.0 would imply trivial, deterministic exploitation on every target, which overstates the current state — though reliability is improving as researchers refine heap-grooming techniques.
Why not lower?
Every downward-pressure factor that normally applies to RCE vulns is absent here. There is no authentication barrier, no user interaction requirement, no unusual configuration prerequisite, and no network segmentation assumption — the default config is vulnerable and the attack surface is the entire internet-facing PHP fleet. Active exploitation is confirmed. A weaponized tool exists. The affected population is measured in millions of hosts. Any severity below CRITICAL would be negligent.
Crowdsourced verification payload.
Run this script on each target host (or via your fleet management tool like Ansible/Salt/Puppet) as any user with read access to the PHP binary. Invoke: bash check_php2shell.sh — no arguments needed. The script checks all PHP binaries in PATH and common FPM locations, reports version and session.upload_progress.enabled status, and exits 2 (VULNERABLE), 0 (PATCHED), or 1 (UNKNOWN).
#!/usr/bin/env bash
# check_php2shell.sh — CVE-2026-109619 php2shell vulnerability checker
# Usage: bash check_php2shell.sh
# Exit codes: 0=PATCHED 1=UNKNOWN 2=VULNERABLE
set -euo pipefail
PATCHED_VERSIONS_82="8.2.35"
PATCHED_VERSIONS_83="8.3.36"
PATCHED_VERSIONS_84="8.4.27"
PATCHED_VERSIONS_85="8.5.12"
version_gte() {
# Returns 0 if $1 >= $2 (semantic version compare)
printf '%s\n%s' "$2" "$1" | sort -t. -k1,1n -k2,2n -k3,3n -C 2>/dev/null
}
check_php() {
local php_bin="$1"
local ver
ver=$($php_bin -r 'echo PHP_VERSION;' 2>/dev/null) || return 1
local major_minor
major_minor=$(echo "$ver" | cut -d. -f1,2)
local upload_progress
upload_progress=$($php_bin -r 'echo ini_get("session.upload_progress.enabled") ? "On" : "Off";' 2>/dev/null || echo "unknown")
echo "[*] Binary: $php_bin"
echo " Version: $ver"
echo " session.upload_progress.enabled: $upload_progress"
case "$major_minor" in
8.2) if version_gte "$ver" "$PATCHED_VERSIONS_82"; then echo " Status: PATCHED"; return 0; fi ;;
8.3) if version_gte "$ver" "$PATCHED_VERSIONS_83"; then echo " Status: PATCHED"; return 0; fi ;;
8.4) if version_gte "$ver" "$PATCHED_VERSIONS_84"; then echo " Status: PATCHED"; return 0; fi ;;
8.5) if version_gte "$ver" "$PATCHED_VERSIONS_85"; then echo " Status: PATCHED"; return 0; fi ;;
5.*|7.*|8.0|8.1)
echo " Status: VULNERABLE (EOL branch — no official fix available)"
return 2
;;
esac
if [ "$upload_progress" = "Off" ]; then
echo " Status: VULNERABLE (unpatched) but upload_progress is disabled (CVE-2026-109619 mitigated)"
return 2
fi
echo " Status: VULNERABLE"
return 2
}
FOUND=0
VULN=0
PHP_BINS=$(which php php-fpm php-cgi php8.2-fpm php8.3-fpm php8.4-fpm php8.5-fpm 2>/dev/null || true)
# Also check common paths
for p in /usr/bin/php /usr/sbin/php-fpm /usr/local/bin/php /usr/local/sbin/php-fpm; do
if [ -x "$p" ] && ! echo "$PHP_BINS" | grep -qF "$p"; then
PHP_BINS="$PHP_BINS $p"
fi
done
if [ -z "$PHP_BINS" ]; then
echo "[!] No PHP binaries found on this host."
echo "UNKNOWN"
exit 1
fi
for bin in $PHP_BINS; do
[ -x "$bin" ] || continue
FOUND=1
echo "---"
if ! check_php "$bin"; then
VULN=1
fi
done
echo "==="
if [ "$FOUND" -eq 0 ]; then
echo "UNKNOWN"
exit 1
elif [ "$VULN" -eq 1 ]; then
echo "VULNERABLE"
exit 2
else
echo "PATCHED"
exit 0
fi