Key findings
- Two critical PHP flaws, CVE-2026-109618 and CVE-2026-109619 (CVSS 9.2), allow unauthenticated code execution or memory disclosure. Affected releases go back to PHP 5.4, and exploitability depends on configuration and attack path, with
mod_phpthe highest-risk deployment. - The attempts began before the fix landed on October 21, and most of what reached customer environments was blocked at the WAF; one retained payload was hunting for
.envsecrets. - The WAF logged one request without blocking it, and it drew a redirect; we did not confirm a successful compromise, and endpoint coverage of the targeted server was unverified.
- A blocked or redirected request says nothing about the server behind it, and the memory-disclosure path spawns no child, so the hunt has to reach process and file events on the host, from October 17 onward.
- Ten Vega detections cover the post-exploitation chain: shells spawned by PHP workers, reconnaissance and download commands, and cron, scheduled task, and account persistence, collected with the indicators in the PHP2Shell threat brief.
On October 21, 2026, the PHP project published security advisories and emergency updates for two critical vulnerabilities collectively known as PHP2Shell: CVE-2026-109618 and CVE-2026-109619. Both are rated CVSS v4.0 9.2 and can enable unauthenticated code execution or disclosure of sensitive process memory. Affected releases date back to PHP 5.4; exploitability varies by configuration and attack path.
Exploitation attempts didn’t wait for the patch. Vega observed requests matching public PHP2Shell tooling at several customer organizations before and after the release. Most were stopped at the WAF; one matching request was logged rather than blocked and received a redirect. One retained payload targeted .env secrets. We did not confirm a successful compromise in the activity reviewed.
How Vega customers are protected
Vega customers are protected by ten detections that cover what follows a successful PHP2Shell exploit, from the first process a web worker spawns to the cron job, scheduled task, or account that keeps an attacker in:
- Remote access: Reverse Shell Spawned by PHP Web Worker, Scripting Interpreter Reverse Shell, Named Pipe Reverse Shell via mkfifo, and Netcat or Socat Listener with Command Execution.
- Reconnaissance and payload download: Reconnaissance or Download Command Spawned by PHP Web Worker, PowerShell Download Cradle Spawned by Web Server Process, and post-exploitation child processes spawned by web and application servers.
- Persistence: Cron Job Written by Web Service Account or PHP Web Worker, Scheduled Task Created by a Windows Web Server Worker, and New User Account Created by Web Service Account or PHP Web Worker.
Customers also receive the linked PHP2Shell threat brief, which brings together these detections, the indicators, and the MITRE ATT&CK mapping. The four hunts at the end of this post go further than the detections: they baseline what web workers normally launch and surface writes to PHP scripts, configuration, cron, and SSH keys.
What the vulnerabilities do
Both flaws involve PHP retaining or using invalid internal state after request processing is interrupted. An attacker may turn that state into code execution with the web worker’s privileges or into disclosure of sensitive memory.
Affected deployments include Apache mod_php, PHP-FPM, php-cgi, FrankenPHP, and LiteSpeed. PHP identifies mod_php as the highest-risk deployment, with demonstrated exploitation before application code runs. Other paths depend on application behavior and configuration.
Successful exploitation does not necessarily cause a visible outage. CrowdStrike demonstrated an attack that left the worker running and returned HTTP 200, with a memory-limit error as the reported server-log artifact. That describes one demonstrated attack, not every PHP2Shell attempt.
What Vega observed
We observed requests matching public tooling before the release, suspicious POST bursts later that day, and further upload attempts the next morning. Scanning continued alongside this activity.
The day of the release
On the morning of October 21, before the fixed releases shipped, 64.95.12[.]7 sent requests matching public tooling to several organizations.
Requests used either php2shell-109618 or a fixed Chrome/126.0 user agent. Separately, a WAF matched the PHP_SESSION_UPLOAD_PROGRESS field. Most matching requests were blocked, and one WAF retained this payload:
system echo [redacted];cat .env||find / -maxdepth 4 -name .env -exec cat {} \;The apparent intent was to read .env from the working directory or, if that failed, search within four directory levels of / for other .env files and print their contents. The request shows an attempt to steal secrets, not that the command executed.
One matching request was logged rather than blocked and received a redirect. A redirect does not establish that the payload reached vulnerable PHP code. In the endpoint data reviewed after the request, we found no payload marker, matching .env search, or web-worker shell launch. We could not establish that the origin server itself was covered by that endpoint telemetry.
Later that day, groups of addresses sent POST bursts using several different user agents. Most requests were blocked; others had no recorded outcome. Where request bodies were unavailable, we classified the traffic as possible attempts rather than confirmed exploit payloads.
The morning after
On October 22, we observed suspicious upload requests from 143.198.216[.]142 targeting PHP endpoints. Recorded outcomes included blocked requests and timeouts. These observations alone do not establish successful exploitation or identify the technique used.
The scanners
Another 57 addresses used user agents explicitly naming PHP2Shell. Many described themselves as bug-bounty or passive-survey tools.
One family appeared from approximately 30 residential-network addresses within two minutes, consistent with, but not proof of, a residential proxy network. Its shared user agent provided a useful way to group the requests, though neither the label nor the source network established the operator’s intent.
On the hosts
On October 21, we searched the preceding 24 hours of endpoint data for PHP and web-server processes launching suspicious children. The matches investigated were consistent with legitimate monitoring and administration.
We found no confirmed malicious activity in those results. That does not establish that every targeted server was clean. The searches covered only hosts reporting the relevant events and commands matched by the queries. PHP can also read files, make network connections, or expose memory without launching a child process.
Amazon reported an exploitation attempt on October 17, while noting uncertainty about whether its early attempts would have succeeded against real systems. Investigations should therefore begin at least that far back, rather than at the public disclosure date.
What the telemetry can reveal
Each source answers a different question, and none of them alone proves exploitation. The table below sets out what each one can show and where it stops.
What should defenders do?
- Patch the serving runtime and restart workers. Use PHP 8.5.12, 8.4.27, 8.3.36, or 8.2.35, or equivalent vendor fixes. Include containers and bundled installations; updating CLI PHP alone may not update the web runtime.
- Verify WAF enforcement and coverage. Check actual actions, relevant hostnames, and origins reachable outside it.
- Treat configuration changes as temporary.
session.upload_progress.enabled=0and, on PHP 8.5,fatal_error_backtraces=0interrupt specific paths. Apply them before request processing and restart workers. They do not cover every variant.
For older PHP branches, seek vendor updates or upgrade. PHP supplied exceptional source backports for 7.4, 8.0, and 8.1. Follow its full mitigation guidance, including the warning that RLIMIT_AS and RLIMIT_DATA can induce the vulnerable allocation failure.
Hunt for it yourself
These four hunts are adapted from our PHP2Shell notebook and written for the Vega platform. Running them elsewhere requires adapting the source selectors, field mappings, and any unsupported query syntax. Check the selected time range and whether results are truncated. Treat matches as investigation leads: legitimate applications can launch utilities and write PHP files.
1. Public-tool fingerprints and possible session-file abuse
Finds PoC user agents, session-file URLs, and other suspicious request patterns. Prioritize sources targeting multiple PHP pages or followed by backend activity. These are hunting leads, not proof of exploitation.
@data_type:wulxt/Web-Access-Logs
| extend uri = tostring(url.url_string), ua = tostring(user_agent), site = tostring(url.domain)
| extend session_file_path = uri matches regex "(^|[^A-Za-z0-9]|%2[fF])sess_[A-Za-z0-9,-]{20,}|(?i:/var/lib/php[0-9.]*/sessions?|/tmp/sessions?/)"
| extend poc_user_agent = ua == "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0 Safari/537.36" or ua contains "php2shell"
| extend session_keyword = uri matches regex "(?i)(session\\.save_path|upload_progress_)"
| extend no_agent_post_to_php = http_method =~ "POST" and (isempty(ua) or ua == "-") and uri matches regex "(?i)\\.php([?/]|$)"
| where session_file_path or poc_user_agent or session_keyword or no_agent_post_to_php
| extend priority = case(session_file_path, 1, poc_user_agent, 2, session_keyword, 3, 4)
| extend reason = case(priority == 1, "Possible PHP session-file access", priority == 2, "User agent of public tooling", priority == 3, "URL names a PHP session setting", "POST to a PHP page with no user agent")
| summarize requests = count(), first_seen = min(timestamp), last_seen = max(timestamp), uris = make_set(uri, 5), statuses = make_set(status_code, 5), user_agents = make_set(ua, 3), sites = make_set(site, 5) by src_endpoint.ip, priority, reason
| order by priority asc, requests asc, last_seen desc2. Baseline PHP and web-worker children
Shows what workers normally launch and flags process combinations first observed since October 17. Start the search around October 10 for comparison. New arguments to an existing process combination may not be flagged.
@data_type:zkaee/EDR-Events
| where event_name in~ ("ProcessRollup2", "SyntheticProcessRollup2", "ProcessCreated", "Process Creation", "Process Create")
| where parent_process.name matches regex "(^|/)(php-fpm[0-9.]*|php[0-9.]*-fpm|php-cgi[0-9.]*|php-fcgi[0-9.]*|lsphp[0-9.]*|frankenphp|apache2|httpd|nginx|caddy|lighttpd|litespeed|lshttpd|openlitespeed)$"
or parent_process.path matches regex "(^|/)(php-fpm[0-9.]*|php[0-9.]*-fpm|php-cgi[0-9.]*|php-fcgi[0-9.]*|lsphp[0-9.]*|frankenphp|apache2|httpd|nginx|caddy|lighttpd|litespeed|lshttpd|openlitespeed)$"
or parent_process.cmd_line matches regex "(^|/)php-fpm[0-9.]*: (pool|master)"
| summarize events = count(), hunt_window_events = countif(timestamp >= datetime(2026-10-17T00:00:00Z)), first_seen = min(timestamp), last_seen = max(timestamp), process_cmd_lines = make_set(process.cmd_line, 10), parent_process_paths = make_set(parent_process.path, 3), parent_process_cmd_lines = make_set(parent_process.cmd_line, 5) by device.hostname, agent.uid, parent_process.name, process.name, process.path, actor.user.name
| extend first_seen_since_cutoff = first_seen >= datetime(2026-10-17T00:00:00Z)
| order by first_seen_since_cutoff desc, events asc, last_seen desc3. Suspicious commands started by PHP or web workers
Finds direct children running reverse-shell, download, reconnaissance, persistence, or .env-access commands. Compare hits with normal application activity and inspect the full process tree.
@data_type:zkaee/EDR-Events
| where event_name in~ ("ProcessRollup2", "SyntheticProcessRollup2", "ProcessCreated", "Process Creation", "Process Create")
| where parent_process.name matches regex "(^|/)(php-fpm[0-9.]*|php[0-9.]*-fpm|php-cgi[0-9.]*|php-fcgi[0-9.]*|lsphp[0-9.]*|frankenphp|apache2|httpd|nginx|caddy|lighttpd|litespeed|lshttpd|openlitespeed)$"
or parent_process.path matches regex "(^|/)(php-fpm[0-9.]*|php[0-9.]*-fpm|php-cgi[0-9.]*|php-fcgi[0-9.]*|lsphp[0-9.]*|frankenphp|apache2|httpd|nginx|caddy|lighttpd|litespeed|lshttpd|openlitespeed)$"
or parent_process.cmd_line matches regex "(^|/)php-fpm[0-9.]*: (pool|master)"
| summarize events = count(), first_seen = min(timestamp), last_seen = max(timestamp), event_names = make_set(event_name, 5), process_pids = make_set(process.pid, 5), parent_pids = make_set(parent_process.pid, 5) by device.hostname, agent.uid, parent_process.name, parent_process.path, process.name, process.path, process.cmd_line, actor.user.name
| where process.cmd_line has_any ("mkfifo", "whoami", "crontab", "xmrig", "kinsing", "fsockopen", "SOCK_STREAM", "os.dup2", "pty.spawn", "socket.socket", "IO::Socket", "JACKALPOC")
or process.cmd_line contains_cs "/dev/tcp/"
or process.cmd_line contains_cs "/dev/udp/"
or process.cmd_line contains_cs "/dev/shm/"
or process.cmd_line contains_cs "/var/tmp/"
or process.cmd_line contains_cs "0>&1"
or process.cmd_line contains_cs "/etc/shadow"
or process.cmd_line matches regex "(^|[ /;|&\"'])(bash|sh|dash)[ \\t]+-i([ \\t\"';|&]|$)|\\|[ \\t]*(/bin/)?(bash|sh|dash)([ \\t\"';|&]|$)|(^|[ /;|&\"'])chmod[ \\t]+([0-7]{3,4}|[a-z]*\\+x)([ \\t]|$)|(^|[ /;|&\"'])(python[0-9.]*|perl|php)[ \\t]+-(c|e|r)([ \\t]|$)"
or process.cmd_line matches regex "(^|[ \\t]-c[ \\t]+[\"']?[ \\t]*)(/usr)?(/s?bin/)?(id|uname[ \\t]+-[a-z]*a[a-z]*)([ \\t;|&\"']|$)"
or process.cmd_line matches regex "(^|[ \\t/;|&\"'])(curl|wget)([ \\t][^;|&]*)?([ \\t](-o|-O|-P|--output|--output-document)([ \\t=]|$)|/tmp/|/dev/shm/)"
or (process.cmd_line contains_cs ".env" and process.cmd_line matches regex "(^|[ /;|&\"'])(cat|head|tail|less|more|grep|strings|find)([ \\t]|$)")
or process.name in~ ("nc", "ncat", "netcat", "socat", "nmap", "masscan", "xmrig", "chattr", "useradd", "usermod", "busybox", "tftp")
or process.path matches regex "/(nc|ncat|netcat|socat|nmap|masscan|xmrig|chattr|useradd|usermod|busybox|tftp)$"
| order by events asc, last_seen desc4. Script, configuration, and persistence writes
Finds sensitive file writes by web workers or service accounts, including PHP scripts, configuration, cron, and SSH keys. Check file contents and deployment history, since framework-generated PHP can be legitimate.
@data_type:zkaee/EDR-Events
| where event_name in~ ("NewScriptWritten", "WebScriptFileWritten", "FileCreated", "FileCreate", "FileModified", "FileRenamed", "File Created", "File Creation", "File Modification", "File Rename", "FileWritten", "NewExecutableWritten")
| where file.path matches regex "(?i)\\.(php[0-9]?|phtml|pht|phar|inc)$|[/\\\\](\\.user\\.ini|\\.htaccess|php\\.ini)$"
or file.name matches regex "(?i)\\.(php[0-9]?|phtml|pht|phar|inc)$|^(\\.user\\.ini|\\.htaccess|php\\.ini)$"
or file.path contains_cs "/etc/cron"
or file.path contains_cs "/var/spool/cron/"
or file.path contains_cs "/.ssh/authorized_keys"
or file.path contains_cs "/etc/systemd/system/"
or file.path == "/etc/ld.so.preload"
| where process.name matches regex "(?i)(^|[/\\\\])(php-fpm[0-9.]*|php[0-9.]*-fpm|php-cgi[0-9.]*|php-fcgi[0-9.]*|lsphp[0-9.]*|frankenphp|apache2|apache|httpd|nginx|w3wp|caddy|lighttpd|litespeed|lshttpd|openlitespeed)(\\.exe)?$"
or process.path matches regex "(?i)[/\\\\](php-fpm[0-9.]*|php[0-9.]*-fpm|php-cgi[0-9.]*|php-fcgi[0-9.]*|lsphp[0-9.]*|frankenphp|apache2|apache|httpd|nginx|w3wp|caddy|lighttpd|litespeed|lshttpd|openlitespeed)(\\.exe)?$"
or actor.user.name in~ ("www-data", "apache", "nginx", "httpd", "nobody", "wwwrun", "lighttpd", "litespeed", "iusr")
or actor.user.name startswith "iis apppool"
| summarize events = count(), first_seen = min(timestamp), last_seen = max(timestamp), process_names = make_set(process.name, 5), process_paths = make_set(process.path, 5), process_cmd_lines = make_set(process.cmd_line, 5), actor_user_names = make_set(actor.user.name, 5), process_pids = make_set(process.pid, 5) by device.hostname, agent.uid, event_name, file.path, file.name
| order by events asc, last_seen descIOCs
Source addresses observed in the activity described in this post.
64.95.12[.]7173.212.243[.]74143.198.216[.]142144.91.119[.]110173.249.2[.]254103.83.80[.]36103.83.80[.]37103.83.80[.]38
Conclusion
PHP2Shell shows how quickly a long-hidden flaw can become an active threat. Blocking at the edge buys time, but knowing what happened on the host is what closes the investigation. Start that investigation no later than October 17, the earliest exploitation attempt reported so far, and treat every blocked request as a reason to look at the server it was aimed at.
Vega protects customers with the ten detections above, which pick up the shells, downloads, and persistence an exploit leaves behind. The threat brief on the Vega Threat Hub holds them with the indicators and the ATT&CK mapping in one place.