PHP2Shell: the two PHP bugs behind CVE-2026-109618 and CVE-2026-109619, and how to tell if you were hit

PHP2Shell is the name that stuck to two memory-safety vulnerabilities in PHP’s core request handling, CVE-2026-109618 and CVE-2026-109619, both rated 9.2 under CVSS v4 and both allowing unauthenticated remote code execution [1][2]. PHP published fixed releases on 21 October 2026: 8.5.12, 8.4.27, 8.3.36 and 8.2.35 [1].

The reason this one deserves attention beyond the patch queue is where the bugs sit. They are in the parser and request lifecycle, which run before any application code, so authentication, framework middleware and application logic offer no protection at all. PHP’s own advisory says every installation serving web requests is at risk across PHP-FPM, Apache with mod_php, php-cgi, FrankenPHP and LiteSpeed, and calls the risk highest for mod_php, where a demonstrated exploit triggers before a line of application code runs [1]. Zscaler describes both as use-after-free flaws that can also disclose process memory, which may contain passwords, session identifiers, API keys and certificates belonging to other requests [7].

How the two PHP2Shell bugs corrupt request handling

Figure 1. Both bugs interrupt PHP during request parsing. The application never gets a chance to defend itself.

The two bugs, in plain terms

CVE-2026-109618, OOM corruption. When PHP parses a form submission it builds the $_POST array in a hash table, and the insertion path updates the table’s counters before it allocates a copy of the key [6]. If memory runs out at exactly that moment, PHP handles the error with longjmp(), which abandons the current context rather than unwinding it cleanly [1][2]. The half-finished entry stays in the table describing memory that was never allocated. During request cleanup PHP then reads that invalid data as if it were valid, and follows a pointer the attacker influenced while the memory was in an intermediate state [1][6].

CVE-2026-109619, stale global state. PHP keeps some request-owned state in globals for the duration of a request. On a fatal error that state can outlive the memory it points into. Workers handle requests sequentially, so a later request on the same worker can read or branch on that stale state before normal initialization resets it [1][2]. Zscaler notes the two chain together: the first shapes the contents of freed memory, the second reuses it, which is what turns a crash primitive into reliable control of the instruction pointer [7].

Neither bug requires credentials or user interaction. Both are triggered by a crafted request, and the parser processes it before the application sees anything [1][6]. That single sentence is the whole defensive problem: there is no application-layer control to tune, because the vulnerable code runs upstream of the application.

Affected versions, and what actually has a fix

BranchVulnerableFixedNotes
PHP 8.58.5.0 to 8.5.118.5.12Also set fatal_error_backtraces=0 if you cannot patch, as a partial measure [1]
PHP 8.48.4.0 to 8.4.268.4.27Supported branch [1]
PHP 8.38.3.0 to 8.3.358.3.36Supported branch [1]
PHP 8.28.2.0 to 8.2.348.2.35Supported branch [1]
PHP 8.1 and olderAll versionsNo upstream fixPHP provided minimal source backports for 7.4, 8.0 and 8.1; distributions and LTS vendors may ship patched packages [1][3]
PHP 5.4 to 7.xAll versionsNo upstream fixEnd of life. Migration is the only complete answer [7]

Two practical points about that table. First, patch levels are not the same as running versions: containers, golden images, serverless layers and vendor appliances may carry an older PHP than the repository or manifest suggests, so verify what the process actually runs. Second, PHP is embedded far beyond websites. NCSC-NL’s operational notice points at appliances, network devices and management interfaces where administrators cannot update PHP directly and must wait for a vendor [3]. Those are the assets most likely to still be vulnerable in six months.

CERT-EU also notes a third PHP RCE disclosed on 23 October, CVE-2026-115560, which only affects applications configured in an unusual way. PHP judged the risk unlikely and did not issue an out-of-band release for it, so it belongs on a normal patch cycle rather than this emergency one [4].

Evidence of exploitation in the wild

This is where the reporting needs care, because there is real evidence of exploitation attempts and of exploitation before disclosure, but no publicly named mass-compromise campaign.

EvidenceWhat it showsSource
Honeypot hit on 17 October 2026Amazon Threat Intelligence recorded a PHP2Shell exploitation attempt four days before public disclosure, originating from an IP associated with a commercial anonymization network. Against a honeypot, from anonymized infrastructure, during an embargo [5]AWS
Proofs of concept from 02:00 UTC on 21 OctoberPoCs appeared about twelve hours before the embargo was due to lift at 14:00 UTC, and exploitation attempts against AWS sensors followed from additional anonymized infrastructure with varying payloads, suggesting multiple actors [5]AWS
Broad exploitation after the patchOnce the embargo lifted, more PoCs appeared within hours and were used against sensors almost immediately. AWS states PHP2Shell is now seeing broad exploitation attempts in the wild [5]AWS
Multiple reports of active exploitationDatadog states that as of 21 October it had multiple reports of active exploitation in the wild, and was aware of broad untargeted scanning [6]Datadog
PoCs circulating, exploitation likelyCERT-EU notes technical details are being discussed publicly and initial PoCs have begun to circulate, raising the likelihood that attackers are already exploiting the vulnerabilities [4]CERT-EU
Not in CISA KEV, as of the 23 October catalogNeither CVE appeared in the Known Exploited Vulnerabilities catalog at the time of writing. Absence from KEV is not evidence of absence of exploitation, only that the US government has not added it to that specific list [8]CISA KEV

The honest summary: exploitation attempts are confirmed, including before disclosure, and scanning is broad. What has not been published is a named victim or a measured compromise count, so anyone claiming a specific number of breached organisations is going beyond the public record. The absence from KEV is worth noting because it is often treated as the arbiter of urgency, and here it lags evidence that multiple security firms already describe as active exploitation.

AWS also draws the strategic lesson explicitly: coordinated disclosure remains right, but defenders should not assume the disclosure date is the earliest date of exploitation, and should start investigating from at least 17 October rather than from the advisory [5].

Detection: three layers, correlated

No single signal proves exploitation. Datadog’s guidance is to correlate three layers: the incoming request, the PHP error log, and the processes a web worker spawns [6]. A refined exploit can return HTTP 200, leave the worker running and produce only a handful of log lines, so the absence of a crash means nothing on its own.

Layer 1: the request

SignalWhy it mattersWhere to look
Multipart part count far above baselinePublic tools pack hundreds of parts, with examples fitting roughly 900 parts into less than 1 MB. PHP’s allocations while parsing can greatly exceed the bytes on the wire, so body size is a weak signal and part count is a strong one [6]WAF or reverse proxy telemetry, request parsers that expose part counts, custom access logging
Deeply nested machine-generated field namesNames such as files[R00001][a][a][a] repeated across many parts create memory pressure through nested array creation. Monitor brackets per field name and nesting depth [6]WAF, body inspection logs, application firewalls with JSON or form structure awareness
A single oversized field name in a urlencoded bodyAn alternative path uses a field name of megabytes (3 to 60 MB depending on memory_limit) with a binary value containing null bytes and control characters [6]Proxy request-size limits and body inspection, not just Content-Length
PHPSESSID and PHP_SESSION_UPLOAD_PROGRESS in unexpected placesMost public PoCs begin with a multipart upload carrying these fields. They can be legitimate, since PHP documents upload progress and transparent sessions add identifiers, so the signal is their unexpected use combined with structural anomalies [6]Request body telemetry and WAF rules
Upload filenames containing PHP tags or serialized-object fragmentsFilenames containing [6]Upload inspection, WAF filename rules, file integrity monitoring on upload directories
Slow or never-finishing uploads, and bursts of silent connectionsA trickled large Content-Length keeps a session record alive, and a burst of stalled connections can occupy every worker but one so that two chained requests land on the same process [6]Connection timing metrics, worker saturation alerts, proxy timeouts
Set-Cookie PHPSESSID with an unusually long, percent-encoded or binary valueThis is the memory-disclosure stage returning heap contents. It can follow an otherwise unremarkable request [6]Response header logging, WAF response inspection

Layer 2: the PHP error log

The characteristic line from a failed attempt is a memory exhaustion fatal error reported at an unknown location, which means PHP died during request initialization before any script ran [6]. For example: PHP Fatal error: Allowed memory size of 16777216 bytes exhausted (tried to allocate 3500032 bytes) in Unknown on line 0. On Apache, a wrong address guess shows up as AH00052: child pid ... exit signal Segmentation fault (11) [6].

Two calibration points matter. Memory exhaustion alone is not proof, because legitimate workloads exhaust memory too, so the alert should combine the error with an oversized request, an upstream connection reset or a worker crash. And a complete attack can produce as few as two to four such errors and then go quiet, so a low count is not reassurance; a handful on one worker in a short window deserves investigation [6].

Layer 3: what the worker spawns

The most direct evidence of success is a web server process spawning a shell. Datadog’s capture shows an Apache worker running as www-data spawning sh -c with a reverse-shell command, then bash holding an outbound connection [6]. Any shell launched by a web worker is worth investigating, especially when it coincides with the request and log signals above. Where applications legitimately shell out, baseline that behaviour so deviations are visible.

Add the usual post-exploitation checks: new files in upload or temporary directories, webshell-looking PHP files, new cron entries or systemd units, unexpected outbound connections from the web tier, and credential use from the web host. Because the bug can also leak memory, assume that secrets held in process memory may be exposed even when no crash occurred [6][7].

Mitigation: patch, then reduce the blast radius

PHP is direct: only an update is a complete fix [1][3]. The interim measures below reduce exposure to specific attack paths and do not make an installation safe. They are worth applying while a patch window is being arranged, not instead of arranging one.

MeasureWhat it doesLimits
Upgrade to 8.5.12, 8.4.27, 8.3.36 or 8.2.35Removes both vulnerabilities [1]None, other than change management
Vendor or distribution backports for older branchesBrings the fix to 7.4, 8.0 and 8.1 where an LTS vendor supports them [1][3]Source backports exist but you must build them; 5.4 to 7.x have no supported path [7]
memory_limit=-1 enforced above the applicationRemoves the controllable memory-limit failure behind CVE-2026-109618. Set it where applications cannot override it, such as php_admin_value in the FPM pool or Apache configuration [1]An underlying allocation failure can still reach the vulnerable path; an attacker who exhausts system memory can destabilise the host [1][6]
post_max_size and upload_max_filesize limits8M where uploads are unnecessary, 32M where they are, with upload_max_filesize below post_max_size [1]Reduces, does not remove, the ability to force the allocation failure
Limits at the proxy on decoded size, field count, part count, header count and nesting depthBlocks abusive requests before they reach the parser, including requests without a usable Content-Length [1]Requires a proxy that actually inspects deep enough; verify what the engine sees rather than what the rule says [6]
session.upload_progress.enabled=0 and a full worker restartBlocks the confirmed session paths for CVE-2026-109619. Apply in php.ini, an FPM pool or server configuration that loads before request parsing, not ini_set or .user.ini [1]Blocks the confirmed paths, not every variant [1]
fatal_error_backtraces=0 on PHP 8.5Blocks the confirmed fatal backtrace path [1]That path only [1]
Remove per-request settings that emit diagnostics before initializationRemoves another route to early diagnostics. Hiding output with error_reporting or @ does not help [1]Requires knowing which extensions and settings do this
Container or cgroup memory boundaryLimits the availability impact and contains a successful exploit to one workload. Prefer a policy that kills only the affected worker [1]Not a security boundary against RCE
Do not substitute RLIMIT_AS or RLIMIT_DATAThese can cause the very allocation failure the exploit needs [1]Counter-intuitive but explicit in the advisory
WAF or reverse-proxy virtual patchingBlocks known request shapes at the edge, and rules such as excessive multipart parts exist in commercial WAFs [7]A rule cannot inspect bytes the engine truncates, fields its parser discards, parts it never enumerates, or bodies it handles differently because they arrived chunked [6]
Isolate internet-facing applicationsCERT-EU recommends isolating exposed applications where immediate remediation is not possible [4]Reduces exposure, does not fix the runtime

Two architectural points are worth adding. Run web workers as unprivileged users with egress restrictions, because every documented post-exploitation path ends in a shell and an outbound connection. And inventory embedded PHP rather than only web applications: appliances and management interfaces running PHP cannot be fixed by your patch cycle, so they need vendor chasing and, where possible, network isolation [3].

If you think you were hit

  1. Preserve the host before rebuilding. PHP error logs, Apache or FPM logs, access logs with request sizes and timings, and any WAF telemetry are the evidence base.
  2. Assume memory disclosure. If an exploit reached the cleanup or disclosure stage, process memory may have exposed session identifiers, API keys, database credentials and certificates from other requests [7]. Rotate credentials and invalidate sessions rather than waiting for proof.
  3. Hunt for shells and persistence: processes spawned by the web user, outbound connections from the web tier, new files in upload and temp directories, cron and systemd changes, and modified PHP files.
  4. Check whether the worker count and traffic pattern match a state-reuse chain: a burst of stalled connections followed by two requests landing on the same worker [6].
  5. Patch to a fixed release, and verify the running version rather than the package manifest, including container images and any embedded runtime.

Sources

  1. PHP, “Security advisory: Critical remote code execution vulnerabilities, update now” (21 October 2026), the primary advisory covering fixed versions, affected SAPIs, and the partial mitigations reproduced here. · https://www.php.net/archive/2026.php#2026-10-21-5
  2. CVE Program records for CVE-2026-109618 and CVE-2026-109619, including the official descriptions, CVSS v4 metrics and affected version ranges. · https://cveawg.mitre.org/api/cve/CVE-2026-109618
  3. NCSC-NL, php2shell operational information repository (TLP:CLEAR), including the affected software inventory, the note on embedded PHP in appliances and the recommended actions. · https://github.com/NCSC-NL/php2shell
  4. CERT-EU Security Advisory 2026-018 on the PHP vulnerabilities, including the assessment that PoCs are circulating and the recommendation to isolate internet-facing applications where patching is not immediate. · https://cert.europa.eu/publications/security-advisories/2026-018/
  5. Amazon Threat Intelligence, “Amazon Threat Intelligence identifies PHP2Shell exploitation prior to public disclosure in MadPot” (October 2026), the primary evidence of pre-disclosure exploitation and of broad attempts afterwards. · https://aws.amazon.com/blogs/security/amazon-threat-intelligence-identifies-php2shell-exploitation-prior-to-public-disclosure-in-madpot/
  6. Datadog Security Labs, “PHP2Shell: critical vulnerabilities in PHP” (last updated 23 October 2026), for the independent reproduction, the exploitation reports, the patch analysis and the three-layer detection guidance. · https://securitylabs.datadoghq.com/articles/php2shell-critical-vulnerabilities-in-php/
  7. Zscaler ThreatLabz, “CVE-2026-109618 and CVE-2026-109619: PHP2Shell”, for the memory-disclosure impact, the chaining analysis, the affected version table and the WAF rule reference. · https://www.zscaler.com/blogs/security-research/cve-2026-109618-and-cve-2026-109619-php-vulnerable-unauthenticated-rce
  8. CISA, Known Exploited Vulnerabilities catalog, as retrieved from the official JSON feed on 23 October 2026, in which neither CVE appeared. · https://www.cisa.gov/known-exploited-vulnerabilities-catalog

The uncomfortable part

The bugs are in code that has to be correct for the web to work at all, in a language that runs a very large share of the internet, and the exploitation window started before the advisory existed. The patch is small and available. The part that takes longer is finding every PHP runtime you own, including the ones inside appliances you never think of as web servers, and proving that the version you patched is the version that is actually executing requests.

Published for defensive education. Version ranges, mitigation settings and exploitation evidence are taken from the sources listed above; verify the current state of the advisory and your vendors before acting.