Skip to content
The Future of Agentic Cyber Defense is Here: Vega II is Live | Watch on Demand
Blog

PHP2Shell: Hunting Exploitation Attempts That Started Before the Patch

22 Oct 202611 min
Pixel-art floppy disk with a leafy green label and an orange title band, on a sand-yellow background.

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_php the 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 .env secrets.
  • 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.

Figure 1 — Telemetry evidence for PHP2Shell
What each telemetry source shows for PHP2Shell activity, and its limitation.

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=0 and, on PHP 8.5, fatal_error_backtraces=0 interrupt 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 desc

2. 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 desc

3. 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 desc

4. 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 desc

IOCs

Source addresses observed in the activity described in this post.

  • 64.95.12[.]7
  • 173.212.243[.]74
  • 143.198.216[.]142
  • 144.91.119[.]110
  • 173.249.2[.]254
  • 103.83.80[.]36
  • 103.83.80[.]37
  • 103.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.

References

PHP2ShellPHPCVE-2026-109618CVE-2026-109619remote code executionWAFthreat huntingthreat research

Read more

All articles