PHP2Shell: CVE-2026-109618 and CVE-2026-109619 Unauthenticated PHP Remote Code Execution
Executive Summary
PHP2Shell is the name used by the vendors for two critical flaws in the PHP engine itself: CVE-2026-109618 (remote code execution through out-of-memory corruption) and CVE-2026-109619 (remote code execution through stale global state that survives into a later request). Both are rated CVSS v4 9.2 by the PHP security team. They sit in the code that parses request data before extensions load and before any application script is compiled, so a patched, well-written application is exposed exactly as much as a neglected one. Every runtime that serves web requests is in scope: PHP-FPM, Apache mod_php, php-cgi, FrankenPHP and LiteSpeed.
It is being exploited. Amazon Threat Intelligence recorded an attempt against its MadPot honeypots on 2026-10-17, four days before disclosure, from an address on a commercial anonymization network, and says it is unclear how the actor learned the details. More attempts followed on 2026-10-21 from five further addresses, with varied payloads that suggest several actors, and Amazon says PHP2Shell is now broadly exploited. CrowdStrike verified end to end that one anonymous POST can make an Apache mod_php worker start a shell and run a chosen command. Fixes shipped in PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35; PHP 7.4 to 8.1 have only minimal source backports and PHP 5.4 to 7.3 have no fix.
What an endpoint sensor can and cannot see. The exploit request itself is not in endpoint process telemetry. CrowdStrike reports that workers stay up, the server answers HTTP 200 and application logs show nothing, leaving one memory-limit fatal in the error log. What an endpoint sensor on the web server can see is the outcome: the PHP or web server process starting a shell, a download tool or a reverse shell, then discovery, credential reads, web shell drops and persistence run as the web account. The FBI, CrowdStrike and every other vendor in the source set point to that same break. This pack builds ten CQL hunts around it, adds five native checks for the log layer the sensor cannot read, and is explicit about the blind spots in section 15.
Two limits to know up front. First, memory disclosure (CVE-2026-109619, and the research site's application-agnostic read of worker memory) can leak passwords, tokens and keys with no code execution and therefore no child process, so a clean hunt does not prove no secrets were read. Second, no source publishes a payload hash, a second-stage domain or a web shell filename. The nine source IPs are the only atomic indicators, they are shared anonymization infrastructure, and they ship as detect, never prevent.
Source Review & Web Hunter Notes
| Tier | Source | Key Finding | Carry |
|---|---|---|---|
| 1 · Primary | Amazon Threat Intelligence: PHP2Shell exploitation prior to public disclosure in MadPot (snapshot 01) | First exploitation attempt 2026-10-17 (four days before disclosure), then multiple actors 2026-10-21 from 02:00 to 08:00 UTC and broad exploitation after the 14:00 UTC release. Publishes six source IPs with dates and states they include shared anonymization infrastructure: investigate, do not just block. Advises searching POSTs for PHP_SESSION_UPLOAD_PROGRESS, blocking multipart uploads where patching is not possible, and starting investigations from 2026-10-17. | yes |
| 2 · Vendor | Tenable: PHP2Shell FAQ (Satnam Narang) (snapshot 02) | Both CVEs CVSSv4 9.2, unauthenticated RCE. Fixed in 8.5.12, 8.4.27, 8.3.36 and 8.2.35; minimal source backports for 7.4 to 8.1; 5.4 to 7.3 no fix. Affects PHP-FPM, mod_php, php-cgi, FrankenPHP and LiteSpeed. Lists the PHP advisory interim settings (memory_limit=-1 enforced, session.upload_progress.enabled=0, fatal_error_backtraces=0 on 8.5, restart every worker), Cloudflare stopgap WAF rules, and watchTowr observing exploitation. | yes |
| 2 · News | Help Net Security: PHP2Shell critical PHP vulnerabilities patched (snapshot 03) | Patch detail and affected versions, distro and LTS vendor guidance, Remi repository caveat, minimal backports for 7.4, 8.0 and 8.1, Cloudflare WAF rules, and a 2026-10-22 update that Palo Alto Networks confirmed all PAN-OS and Panorama versions are affected with no fixed release yet. | yes |
| 2 · News | The Hacker News: Someone Tried to Exploit PHP2Shell 4 Days Before Disclosure (snapshot 04) | Timeline, the Amazon IPs (re-reported from the Amazon post, not independent of it), VulnCheck's three honeypot hosts and the php2shell-scan/109618 user agent (this article is the only snapshot carrying these), ELLIO's first request at 2026-10-21 08:36 UTC, FBI guidance to monitor PHP processes launching shells or downloading files, NSA and NCSC alerts, Cloudflare observing payload mutation against its WAF rules. | yes |
| 1 · Vendor | CrowdStrike: Protects Customers from Threats Delivered via PHP2Shell (snapshot 05) | Root cause (longjmp without unwinding on fatal errors), verified end to end that one anonymous POST makes an Apache mod_php worker launch a shell. The attack is nearly traceless at the HTTP and application layer; the only server-side trace is one memory-limit fatal in the error log ('Allowed memory size of N bytes exhausted' with 'in Unknown on line 0'). Existing Falcon IOAs cover web shell deployment, suspicious children of PHP or web server processes and malicious command execution. Endpoint behavioral monitoring is the primary line of defense. | yes |
| 2 · Vendor | Datadog Security Labs: PHP2Shell detection, analysis and remediation (snapshot 06) | Independently reproduced the OOM corruption on PHP 8.5.11 with one unauthenticated POST. Warns that community mitigations such as session.upload_progress.enabled=0 do not fully protect, and that successful command execution does not necessarily crash the worker. Investigate large form submissions, memory-exhaustion errors during request initialization and unexpected shell processes. Remediation steps for inventory, containers and worker restarts. | yes |
| 2 · Vendor | Prophet Security: Detecting and investigating CVE-2026-109618 and CVE-2026-109619 (snapshot 07) | States no indicator list existed and gives behavior checks: PHP workers (php-fpm, php-cgi, lsphp, frankenphp, httpd, apache2, and php-cgi.exe under w3wp.exe on IIS) spawning sh, bash, curl, wget, python, perl, cmd.exe, powershell.exe, certutil.exe; new files in web roots; new outbound connections from workers; web-user persistence. Log strings (Allowed memory size of, Out of memory (allocated, exited on signal 11, Maximum execution time of). Relays Wiz guidance on multipart anomalies and PHPSESSID cookies. Mitigations including pm.max_requests=1. | yes |
| 2 · Vendor | SOCRadar: PHP2Shell CVE-2026-109618 and CVE-2026-109619 (snapshot 08) | Detection guidance: alert when php-fpm, php-cgi, httpd or apache2 launch shells, script interpreters or download tools such as curl and wget. Mitigation summary consistent with the other vendors. | partial |
| 2 · Vendor | Check Point: PHP2Shell what we know (snapshot 09) | Pre-release hunting guidance (web servers spawning sh, bash, curl, wget, python or nc; isolate, preserve logs, hunt web shells, cron jobs and new accounts). Published before technical details and carries no indicators. | partial |
| 2 · Vendor | Abstract Security: PHP2Shell critical PHP RCE vulnerabilities (snapshot 10) | Network-layer rules (ModSecurity and Suricata style) for multipart request shapes and a PHP_SESSION_UPLOAD_PROGRESS correlate that should not be blocked on its own because it is the legitimate progress-bar field. Notes the upload-progress callback fires on any POST endpoint before application code and the exploit body is about 450 bytes. Web tier only, used for the audit-log hunts. | partial |
| 2 · Research | php2shell.com research site (snapshot 11) | Names the discoverers (Wiz Research, OpenAI Codex Security, Calif), confirms pre-authentication RCE demonstrations against WordPress, Drupal and Joomla and an application-agnostic memory disclosure that can expose passwords, tokens and keys without code execution. Patching the runtime is the only complete fix. | partial |
Decisions: (1) Nothing ships as prevent. The six Amazon addresses come from one report, and The Hacker News repeats them from that report, so they are not independently sourced; the three VulnCheck addresses appear only in The Hacker News snapshot. Amazon also says the addresses include shared anonymization infrastructure and should be investigated, not simply blocked. All nine ship as detect with medium or low severity. (2) No payload hash, domain or filename is shipped, because none appears in any fetched source; the Falcon CSV carries two clearly labelled REPLACE_WITH placeholders to be filled from your own incident evidence. (3) The scanner user agent php2shell-scan/109618 is single-sourced (VulnCheck via The Hacker News) and is kept as a hunt value, not an importable indicator. (4) The eleven snapshots carry process-behavior guidance from CrowdStrike, Prophet, SOCRadar and Check Point that is consistent across vendors; this is what the CQL is keyed on.
Source conflicts noted. Dates for the planned release differ between outlets: Tenable and The Hacker News say it was planned for 2026-10-22 and moved forward one day after the CVE IDs leaked on 2026-10-20, while the Amazon post gives 2026-10-21 14:00 UTC as the embargo release. The pack uses 2026-10-21 as the disclosure and fix date. The Hacker News also says the flaws are in every 7.x and 8.x release; Tenable and the research site say the earliest affected code path dates to PHP 5.4.0. The pack follows the broader Tenable statement. Early CVE listings carried a CVSS of 10.0 that the PHP Foundation called incorrect; the PHP rating of 9.2 is used.
Not used: the php.net security page and the 8.5.12 release page returned content that did not mention the CVEs when fetched, so no snapshot exists for them and they are not cited. The Cloudflare, watchTowr, Wiz, Calif, Unit 42 and Sucuri write-ups were not fetched; Cloudflare and Wiz points appear only as relayed by the cited vendors and are marked that way. No fetched page contained text directed at an automated reader or asking for action.
Hunt Brief & Attack Chain
Hypotheses (ordered by fidelity)
| # | Hypothesis | MITRE | Telemetry | Expected FP | Conf |
|---|---|---|---|---|---|
| H1 | After a successful exploit, a PHP or web server process starts a shell as its child | T1190, T1059.004, T1059.003, T1059.001 | Process creation with command line, image and parent (CrowdStrike: ProcessRollup2. SentinelOne: Process Creation. Defender: DeviceProcessEvents) | PHP mail() via sh, converters, deploy hooks | high |
| H2 | The operator opens a reverse shell from the web account | T1059.004, T1095 | Process creation with command line | Authorized lab validation | high |
| H3 | The web account fetches a second stage or starts an interpreter or dropper binary | T1105, T1059.006 | Process creation; executable or file written (CrowdStrike: ElfFileWritten, NewExecutableWritten, PeFileWritten) | Cache warmers, updaters, PECL builds | medium |
| H4 | The web account runs discovery and reads application and cloud secrets | T1033, T1082, T1016, T1552.001 | Process creation with command line | Monitoring and backup scripts | medium |
| H5 | The web account writes a PHP script or payload to disk | T1505.003, T1140 | Process creation with command line (file creation of .php is not reliably logged) | Deploy scripts, plugin installers | medium |
| H6 | The web account sets up persistence or new accounts | T1053.003, T1098.004, T1136.001, T1548.003 | Process creation with command line | Deploy tooling | medium |
| H7 | A web server talks to the reported source IPs, or a worker reaches new external destinations | T1190, T1071.001 | Network connection with owning process (CrowdStrike: NetworkConnectIP4, NetworkReceiveAcceptIP4) | Shared anonymization infrastructure; normal API calls | low |
| H8 | A tool on the host carries the upload-progress marker or scanner string | T1190, T1595.002 | Process creation with command line | Authorized scanners and lab tests | low |
Attack chain
| Step | Behavior | Telemetry | Hunt angle |
|---|---|---|---|
| 1 | Exploit request: crafted POST (often multipart) forces an out-of-memory or fatal-error path in request startup; the first trace is one memory-limit fatal in the error log | Web, proxy and WAF logs and the PHP error log. Outside endpoint telemetry | N1, N2 |
| 2 | Command execution as the web account: the worker starts a shell | Process creation with command line, image and parent (CrowdStrike: ProcessRollup2. SentinelOne: Process Creation. Defender: DeviceProcessEvents) | Q1, Q3 |
| 3 | Tooling and second stage: download tools, interpreters, dropped binaries | Process creation; executable written (CrowdStrike: ElfFileWritten, NewExecutableWritten, PeFileWritten); outbound network connection (CrowdStrike: NetworkConnectIP4) | Q2, Q7, Q9 |
| 4 | Discovery and credential harvesting from configuration files | Process creation with command line | Q4 |
| 5 | Web shell and persistence: PHP file drop, cron, SSH keys, new accounts | Process creation with command line; file listing on the host | Q5, Q6, N3, N5 |
| 6 | Memory disclosure only: worker memory read without code execution | None. No child process and no write | Not huntable. Patch, restart workers and rotate secrets |
Consolidated IOC Table
Nine IPv4 addresses, all detect. Amazon says its six are shared anonymization infrastructure not exclusive to this activity and recommends investigating observations rather than blocking. No domain, hash or filename is published by any fetched source.
| Type | Value | Confidence | Action | Context (source snapshot) | Expiry |
|---|---|---|---|---|---|
| IPv4 | 103.216.220.92 | medium | detect | Amazon MadPot: first PHP2Shell exploitation attempt 2026-10-17 (4 days before disclosure); anonymization network; geolocated to Australia per The Hacker News (AWS 01, THN 04) | 2027-01-22 |
| IPv4 | 31.223.184.166 | medium | detect | Amazon MadPot: exploitation attempt 2026-10-21 (AWS 01, THN 04) | 2027-01-22 |
| IPv4 | 37.19.200.5 | medium | detect | Amazon MadPot: exploitation attempt 2026-10-21 (AWS 01, THN 04) | 2027-01-22 |
| IPv4 | 134.209.159.64 | medium | detect | Amazon MadPot: exploitation attempt 2026-10-21 (AWS 01, THN 04) | 2027-01-22 |
| IPv4 | 103.114.163.188 | medium | detect | Amazon MadPot: exploitation attempt 2026-10-21 (AWS 01, THN 04) | 2027-01-22 |
| IPv4 | 95.217.98.37 | medium | detect | Amazon MadPot: exploitation attempt 2026-10-21 (AWS 01, THN 04) | 2027-01-22 |
| IPv4 | 85.11.167.243 | low | detect | VulnCheck honeypots: user agent php2shell-scan/109618 targeting CVE-2026-109618 (reported by The Hacker News only) (THN 04) | 2027-01-22 |
| IPv4 | 45.198.224.169 | low | detect | VulnCheck honeypots: user agent php2shell-scan/109618 targeting CVE-2026-109618 (reported by The Hacker News only) (THN 04) | 2027-01-22 |
| IPv4 | 109.71.252.97 | low | detect | VulnCheck honeypots: used against both CVE-2026-109618 and CVE-2026-109619 (reported by The Hacker News only) (THN 04) | 2027-01-22 |
Hunt values (not importable): scanner user agent php2shell-scan/109618 (VulnCheck via The Hacker News, single source); marker string PHP_SESSION_UPLOAD_PROGRESS in POST form uploads (Amazon; also a legitimate field); log strings Allowed memory size of N bytes exhausted with in Unknown on line 0 (CrowdStrike).
Affected Surface & Telemetry Matrix
| Surface | Telemetry | Priority | Gap |
|---|---|---|---|
| Linux web servers: PHP-FPM, Apache mod_php, php-cgi, FrankenPHP, LiteSpeed | Process creation with command line, image and parent (CrowdStrike: ProcessRollup2. SentinelOne: Process Creation. Defender: DeviceProcessEvents). Network connection with owning process (CrowdStrike: NetworkConnectIP4. SentinelOne: IP Connect. Defender: DeviceNetworkEvents). Executable written (CrowdStrike: ElfFileWritten) | critical | Exploit request body outside endpoint telemetry. Memory-only attacks leave no process telemetry. .php writes not reliably logged |
| Windows IIS with php-cgi.exe (w3wp.exe parent) and XAMPP-style stacks | Process creation with command line, image and parent (CrowdStrike: ProcessRollup2. SentinelOne: Process Creation. Defender: DeviceProcessEvents). Executable or file written (CrowdStrike: PeFileWritten, NewExecutableWritten. SentinelOne: File Creation. Defender: DeviceFileEvents) | critical | Request body outside endpoint telemetry. IIS app pool account names vary, so queries key on the parent process |
| Containers and images that bundle their own PHP | Host sensor sees container processes on a supported Linux host; image inventory from your registry scanner | critical | Ephemeral containers may be gone before you hunt. Image scanning, not the sensor, finds the vulnerable runtime. Rebuild, do not patch in place |
| Appliances and products that embed PHP (Help Net Security reports Palo Alto Networks confirmed all PAN-OS and Panorama versions affected; Fortinet and Cisco were still checking) | Vendor advisories and device logs | critical | No endpoint sensor on the appliance: outside endpoint telemetry. Follow the vendor advisory; no fixed PAN-OS release existed at the time of the report |
| End-of-life PHP (5.4 to 8.1) still serving traffic | Version inventory | critical | No upstream fix; only minimal source backports for 7.4 to 8.1 or a vendor/LTS build. Inventory gap is common (Modat counts about 3.47 million internet-facing PHP services on unsupported branches, per The Hacker News) |
| Reverse proxy, WAF and CDN in front of PHP | Access, WAF and proxy logs | high | Cloudflare WAF rules are a stopgap that covers only traffic routed through it, and actors mutate payloads. Outside endpoint telemetry |
ATT&CK Mapping
| Tactic | Technique | Observed or expected behavior | Query / Control |
|---|---|---|---|
| Initial Access | T1190 Exploit Public-Facing Application | Unauthenticated POST forcing the out-of-memory or fatal-error path; exploitation from 2026-10-17 | N1, N2, Q8, Q10; patch (M1051) |
| Execution | T1059.004 Unix Shell; T1059.003 Windows Command Shell; T1059.001 PowerShell | Worker starts sh, bash, cmd.exe or powershell.exe with the attacker's command (CrowdStrike verified) | Q1, Q3 |
| Execution | T1059.006 Python | Scripted interpreters and socket shells launched by the web account | Q2, Q3 |
| Persistence | T1505.003 Web Shell | PHP file dropped into a web-served path after the foothold (CrowdStrike names web shell deployment) | Q6, N3 |
| Persistence | T1053.003 Cron; T1098.004 SSH Authorized Keys; T1136.001 Local Account | User cron jobs, SSH keys and new accounts owned by the web user (Prophet, Check Point) | Q5, N5 |
| Privilege Escalation | T1548.003 Sudo and Sudo Caching | Attempts to use sudo or su from the web account | Q5 |
| Defense Evasion | T1140 Deobfuscate/Decode Files or Information | base64 decode to a shell or file | Q6 |
| Discovery | T1033, T1082, T1016, T1083 | whoami, uname, interface and file discovery from the web account | Q4 |
| Credential Access | T1552.001 Credentials In Files | Harvest of database and API credentials from configuration files (CrowdStrike) | Q4, N5 |
| Command and Control | T1105 Ingress Tool Transfer; T1071.001 Web Protocols; T1095 Non-Application Layer Protocol | curl or wget fetch of a second stage; reverse shell connection | Q2, Q3, Q7, Q8, Q9 |
| Reconnaissance | T1595.002 Vulnerability Scanning | Scanner user agent php2shell-scan/109618 against honeypots | N2, Q10 |
Native Audit-Log Hunts
Non-CQL checks, run on the host or against forwarded logs. They cover the layer the endpoint sensor cannot read: the exploit request and the PHP error log. Read-only; nothing here changes the host. Log paths are examples.
N1 · PHP and web server error logs: the startup-phase memory fatal
# N1. PHP and web server error logs (read-only). CrowdStrike names the memory-limit fatal as the only server-side trace. # Paths are examples: Debian/Ubuntu shown first, then RHEL family. Adjust for your layout. grep -hE 'Allowed memory size of [0-9]+ bytes exhausted.*in Unknown on line 0' /var/log/apache2/error.log* /var/log/nginx/error.log* /var/log/php*-fpm.log* /var/log/httpd/error_log* /var/log/php-fpm/*.log* 2>/dev/null # Variants relayed from Prophet Security and Wiz guidance: grep -hE 'Out of memory \(allocated|exited on signal 11|exit signal Segmentation fault|Maximum execution time of' /var/log/apache2/error.log* /var/log/php*-fpm.log* /var/log/php-fpm/*.log* /var/log/httpd/error_log* 2>/dev/null # Rotated archives: zgrep -hE 'Allowed memory size of [0-9]+ bytes exhausted.*in Unknown on line 0' /var/log/apache2/error.log*.gz /var/log/nginx/error.log*.gz /var/log/httpd/error_log*.gz 2>/dev/null # 'in Unknown on line 0' marks a startup-phase failure before any script ran; clusters from one source IP are the lead. Occasional in-script memory errors from known jobs are normal.
N2 · Web access logs: source IPs, scanner user agent and multipart anomalies
# N2. Web access log hunt (the only view of the exploit request). Requires access logs; request bodies are not in default logs. # Reported source IPs (Amazon MadPot and VulnCheck, via The Hacker News) and the scanner user agent, since 2026-10-17: grep -hE '103\.216\.220\.92|31\.223\.184\.166|37\.19\.200\.5|134\.209\.159\.64|103\.114\.163\.188|95\.217\.98\.37|85\.11\.167\.243|45\.198\.224\.169|109\.71\.252\.97|php2shell-scan' /var/log/apache2/access.log* /var/log/nginx/access.log* /var/log/httpd/access_log* 2>/dev/null # POSTs with a multipart content type to endpoints that never take uploads (needs a log format that records Content-Type, or WAF/proxy logs): # look for multipart/form-data POSTs sent with chunked transfer encoding and no declared length, and bursts from one source followed by HTTP 500s. # Amazon: look for POST requests containing the PHP_SESSION_UPLOAD_PROGRESS string in form uploads. This is visible only in WAF, proxy or ModSecurity audit logs: grep -rl 'PHP_SESSION_UPLOAD_PROGRESS' /var/log/modsec_audit* /var/log/apache2/modsec_audit* 2>/dev/null # Remember the field is also the legitimate progress-bar mechanism. Treat it as a correlate with the signals above, not as proof.
N3 · New or changed PHP files since 2026-10-17
# N3. New or changed PHP files since the earliest reported exploitation (2026-10-17). Read-only.
# Linux:
find /var/www /srv /usr/share/nginx /opt -xdev -type f \( -name '*.php' -o -name '*.phtml' -o -name '*.phar' -o -name '*.pht' \) -newermt 2026-10-17 -printf '%TY-%Tm-%Td %TH:%TM %u %p\n' 2>/dev/null | sort
# Writable upload and cache folders are the usual drop spot; list PHP files inside them (should normally be empty):
find /var/www -xdev -type f -name '*.php' \( -path '*/uploads/*' -o -path '*/cache/*' -o -path '*/tmp/*' \) 2>/dev/null
# Windows / IIS (PowerShell):
Get-ChildItem -Path C:\inetpub,C:\xampp\htdocs -Recurse -Include *.php,*.phtml,*.phar -ErrorAction SilentlyContinue | Where-Object { $_.LastWriteTime -ge [datetime]'2026-10-17' } | Select-Object FullName,LastWriteTime,Length
N4 · PHP version and configuration audit (host and containers)
# N4. Version and configuration audit. Run on every host AND inside every container image that bundles PHP.
# Fixed: 8.5.12, 8.4.27, 8.3.36, 8.2.35. Vendors may backport without changing the version, so also read the package changelog.
php -v ; php-fpm -v 2>/dev/null ; apachectl -M 2>/dev/null | grep -i php
php -i 2>/dev/null | grep -E '^(memory_limit|session.upload_progress.enabled|post_max_size)'
# Note: the CLI php may differ from the runtime that serves HTTP. Query the web runtime: php-fpm -i or a phpinfo page restricted to localhost.
# Debian / Ubuntu changelog check:
apt changelog php8.3-fpm 2>/dev/null | grep -iE 'CVE-2026-10961[89]'
# RHEL family changelog check:
rpm -q --changelog php-fpm 2>/dev/null | grep -E 'CVE-2026-10961[89]'
# Containers:
docker ps --format '{{.Names}} {{.Image}}' | while read n i; do echo "$n $i $(docker exec $n php -v 2>/dev/null | head -1)"; done
N5 · Web account persistence and secret exposure review
# N5. Web account persistence and secret exposure review (Linux). Read-only.
# Replace www-data with your web account (apache, nginx, php-fpm).
crontab -l -u www-data 2>/dev/null ; ls -la /var/spool/cron/crontabs/ /var/spool/cron/ 2>/dev/null
find / -xdev -name authorized_keys -newermt 2026-10-17 -ls 2>/dev/null
awk -F: '($3 >= 1000) {print $1, $3, $7}' /etc/passwd ; lastlog 2>/dev/null | grep -v 'Never'
ls -la --time-style=long-iso /tmp /var/tmp /dev/shm 2>/dev/null
# The research site and CrowdStrike both note memory disclosure and config-file credential harvesting: on any suspect host,
# treat database passwords, API keys, tokens and certificates readable by the web account as exposed.
Hunt Queries: CrowdStrike CQL, SentinelOne PowerQuery and Defender XDR KQL
Use your tenant's own address. SentinelOne does not document a link that pre-fills a query, so each "Copy & open SentinelOne" button copies the query and opens this console: paste it into PowerQuery.
Pick your portal. Microsoft documents a Share link in advanced hunting but not its URL format, so each "Copy & open Defender" button copies the query and opens Advanced hunting: paste it into the editor.
Looks for: a PHP interpreter or web server process (php-fpm, php-cgi, lsphp, frankenphp, httpd, apache2, nginx, w3wp) creating a shell as its child. This is the break the FBI and CrowdStrike both name as the post-exploitation signal, and CrowdStrike verified that a single anonymous POST can make an Apache mod_php worker start a shell. FP: PHP mail() runs sendmail through /bin/sh, and some applications call image or PDF converters through a shell. Baseline the parent and child pairs before 2026-10-20 and exclude the known ones.
Known limitations and deployment notes
- PowerQuery: A shell started by a process the agent did not observe starting is not captured.
- PowerQuery assumes: On a Process Creation event src.process.* is the creator and tgt.process.* the new process.
- PowerQuery assumes: src.process.user is populated on Process Creation events.
- KQL – Defender XDR: A shell started by a process the sensor did not observe starting is not captured.
- KQL – Defender XDR assumes: InitiatingProcessFileName holds the web server or PHP worker image name on Linux sensors.
// HUNT: PHP2Shell web server or PHP worker spawns a shell // MITRE: T1190, T1059.004, T1059.003, T1059.001 | CONF: high FP: medium COST: low // REQUIRES: Falcon sensor on the web server (Linux or Windows); process creation with parent and command line // FALSE POSITIVES: PHP mail() via sh -c sendmail, image or PDF converters launched through a shell, deploy hooks run by the web account // TUNING: baseline ParentBaseFileName + CommandLine for 30 days before 2026-10-20, then exclude the known pairs per host; add | CommandLine!=/sendmail/i only after confirming no web account abuses it; any shell with -i, a URL, base64 or an IP in CommandLine stays in scope // LOOKBACK: 30d (first reported exploitation 2026-10-17) #event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2)$/ | ParentBaseFileName=/^(php-fpm[0-9.]*|php-cgi[0-9.]*|lsphp[0-9]*|frankenphp|httpd|apache2|nginx|litespeed|lshttpd|w3wp)(\.exe)?$/i | FileName=/^(sh|bash|dash|zsh|ksh|ash|busybox|cmd|powershell|pwsh)(\.exe)?$/i | table([@timestamp, ComputerName, UserName, ParentBaseFileName, FileName, CommandLine, SHA256HashData], limit=200) | sort(@timestamp, order=desc)
4 required fields · View requirements
Required telemetry
- Process Creation
Required fields
event.time: Event timeevent.type: Event typesrc.process.name: Creator process nametgt.process.name: New process name
Recommended fields
tgt.process.cmdline: New process command line
Optional fields
tgt.process.image.sha256: Hash
Readiness test
event.type = 'Process Creation' src.process.name = * tgt.process.name = * | limit 5 | columns event.time, event.type, src.process.name, tgt.process.name
// HUNT: PHP2Shell web server or PHP worker spawns a shell // SCHEMA PROFILE: sentinelone-endpoint-ocsf // MITRE: T1190, T1059.004, T1059.003, T1059.001 // CONF: high | FP: medium // FALSE POSITIVES: PHP mail() via sh -c sendmail, image or PDF converters launched through a shell, deploy hooks run by the web account // EQUIVALENCE: equivalent-with-schema-assumptions - Same predicate on the Process Creation event: the creator (src.process.name) is a web server or PHP worker and the new process (tgt.process.name) is a shell. Case-insensitive matches reproduces the CQL /i intent. // REQUIRES: SentinelOne Linux or Windows agent on the web server with Process Creation events // TUNING: Baseline the creator and command line pairs for 30 days, then exclude the known pairs per host. Any shell with -i, a URL, base64 or an IP in the command line stays in scope. // LOOKBACK: 30d (set the console time picker; first reported exploitation 2026-10-17) // CONFIGURATION: No lookup objects required. // KNOWN GAPS: A shell started by a process the agent did not observe starting is not captured. // VALIDATION LEVEL: static-reviewed event.type = 'Process Creation' src.process.name matches '^(php-fpm[0-9.]*|php-cgi[0-9.]*|lsphp[0-9]*|frankenphp|httpd|apache2|nginx|litespeed|lshttpd|w3wp)([.]exe)?$' tgt.process.name matches '^(sh|bash|dash|zsh|ksh|ash|busybox|cmd|powershell|pwsh)([.]exe)?$' | columns event.time, endpoint.name, src.process.user, src.process.name, tgt.process.name, tgt.process.cmdline, tgt.process.image.sha256 | sort -event.time | limit 200
Automated validation: static-passed
3 required fields · View requirements
Required telemetry
- DeviceProcessEvents
Required fields
Timestamp: Event timeInitiatingProcessFileName: Creator process nameFileName: Created process name
Recommended fields
ProcessCommandLine: Created command line
Optional fields
SHA256: Hash. SHA256 is often unpopulated in DeviceProcessEvents; SHA1 is the safer pivot.
Fallbacks
DeviceName→DeviceId: Changes the displayed host identifier only.
Readiness test
DeviceProcessEvents | where Timestamp > ago(1d) | project Timestamp, InitiatingProcessFileName, FileName | take 5
// HUNT: PHP2Shell web server or PHP worker spawns a shell // SCHEMA PROFILE: microsoft-defender-xdr // MITRE: T1190, T1059.004, T1059.003, T1059.001 // CONF: high | FP: medium // FALSE POSITIVES: PHP mail() via sh -c sendmail, image or PDF converters launched through a shell, deploy hooks run by the web account // EQUIVALENCE: equivalent-with-schema-assumptions - Same process-creation predicate: the creator (InitiatingProcessFileName) is a web server or PHP worker and the created process (FileName) is a shell. The regex is case-insensitive, which reproduces the CQL /i intent. // REQUIRES: Defender for Endpoint process-creation telemetry (DeviceProcessEvents) on Linux or Windows web servers // TUNING: Baseline the creator and command line pairs for 30 days, then exclude the known pairs per host. Any shell with -i, a URL, base64 or an IP in the command line stays in scope. // LOOKBACK: 30d (Advanced Hunting retention; first reported exploitation 2026-10-17) // CONFIGURATION: No lookup objects required. // KNOWN GAPS: A shell started by a process the sensor did not observe starting is not captured. // VALIDATION LEVEL: schema-aligned // AUTOMATED VALIDATION: static-passed; static=passed; fixtures=0/0 DeviceProcessEvents | where Timestamp >= ago(30d) | where InitiatingProcessFileName matches regex @"(?i)^(php-fpm[0-9.]*|php-cgi[0-9.]*|lsphp[0-9]*|frankenphp|httpd|apache2|nginx|litespeed|lshttpd|w3wp)([.]exe)?$" | where FileName matches regex @"(?i)^(sh|bash|dash|zsh|ksh|ash|busybox|cmd|powershell|pwsh)([.]exe)?$" | project Timestamp, ReportId, DeviceId, DeviceName, AccountName, InitiatingProcessFileName, FileName, ProcessCommandLine, SHA1, SHA256 | sort by Timestamp desc | take 200
Looks for: second stage retrieval and interpreter launches by the web server: curl, wget, python, perl, ruby, socat, nc, certutil and similar started by a PHP or web server parent. FP: Health checks, cache warmers, WordPress cron and app-driven curl calls are common. Group by command line first and exclude the stable, known ones.
Known limitations and deployment notes
- PowerQuery: A tool started by a process the agent did not observe starting is not captured.
- PowerQuery assumes: On a Process Creation event src.process.* is the creator and tgt.process.* the new process.
- PowerQuery assumes: src.process.user is populated on Process Creation events.
- KQL – Defender XDR: A tool started by a process the sensor did not observe starting is not captured.
- KQL – Defender XDR assumes: InitiatingProcessFileName holds the web server or PHP worker image name on Linux sensors.
// HUNT: PHP2Shell web server parent starts a download tool or interpreter // MITRE: T1105, T1059.006, T1059.004, T1218 | CONF: medium FP: medium COST: low // REQUIRES: Falcon sensor on the web server; process creation with parent and command line // FALSE POSITIVES: application cache warmers, health checks, updater and cron scripts run through the web account // TUNING: groupBy CommandLine over 30 days to find the stable set, then exclude those exact command lines; keep anything with a raw IP, an unusual TLD, a pipe to sh or bash, or a write to a temp folder // LOOKBACK: 30d #event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2)$/ | ParentBaseFileName=/^(php-fpm[0-9.]*|php-cgi[0-9.]*|lsphp[0-9]*|frankenphp|httpd|apache2|nginx|litespeed|lshttpd|w3wp)(\.exe)?$/i | FileName=/^(curl|wget|python[0-9.]*|perl|ruby|nc|ncat|netcat|socat|tftp|ftp|certutil|bitsadmin|mshta|rundll32|regsvr32)(\.exe)?$/i | table([@timestamp, ComputerName, UserName, ParentBaseFileName, FileName, CommandLine], limit=200) | sort(@timestamp, order=desc)
4 required fields · View requirements
Required telemetry
- Process Creation
Required fields
event.time: Event timeevent.type: Event typesrc.process.name: Creator process nametgt.process.name: New process name
Recommended fields
tgt.process.cmdline: New process command line
Readiness test
event.type = 'Process Creation' src.process.name = * tgt.process.name = * | limit 5 | columns event.time, event.type, src.process.name, tgt.process.name
// HUNT: PHP2Shell web server parent starts a download tool or interpreter // SCHEMA PROFILE: sentinelone-endpoint-ocsf // MITRE: T1105, T1059.006, T1059.004, T1218 // CONF: medium | FP: medium // FALSE POSITIVES: application cache warmers, health checks, updater and cron scripts run through the web account // EQUIVALENCE: equivalent-with-schema-assumptions - Same predicate on the Process Creation event: the creator (src.process.name) is a web server or PHP worker and the new process (tgt.process.name) is a download tool or script interpreter. Case-insensitive matches reproduces the CQL /i intent. // REQUIRES: SentinelOne Linux or Windows agent on the web server with Process Creation events // TUNING: Group by command line over 30 days to find the stable set, then exclude those exact command lines. Keep anything with a raw IP, an unusual TLD, a pipe to sh or bash, or a write to a temp folder. // LOOKBACK: 30d (set the console time picker) // CONFIGURATION: No lookup objects required. // KNOWN GAPS: A tool started by a process the agent did not observe starting is not captured. // VALIDATION LEVEL: static-reviewed event.type = 'Process Creation' src.process.name matches '^(php-fpm[0-9.]*|php-cgi[0-9.]*|lsphp[0-9]*|frankenphp|httpd|apache2|nginx|litespeed|lshttpd|w3wp)([.]exe)?$' tgt.process.name matches '^(curl|wget|python[0-9.]*|perl|ruby|nc|ncat|netcat|socat|tftp|ftp|certutil|bitsadmin|mshta|rundll32|regsvr32)([.]exe)?$' | columns event.time, endpoint.name, src.process.user, src.process.name, tgt.process.name, tgt.process.cmdline | sort -event.time | limit 200
Automated validation: static-passed
3 required fields · View requirements
Required telemetry
- DeviceProcessEvents
Required fields
Timestamp: Event timeInitiatingProcessFileName: Creator process nameFileName: Created process name
Recommended fields
ProcessCommandLine: Created command line
Fallbacks
DeviceName→DeviceId: Changes the displayed host identifier only.
Readiness test
DeviceProcessEvents | where Timestamp > ago(1d) | project Timestamp, InitiatingProcessFileName, FileName | take 5
// HUNT: PHP2Shell web server parent starts a download tool or interpreter // SCHEMA PROFILE: microsoft-defender-xdr // MITRE: T1105, T1059.006, T1059.004, T1218 // CONF: medium | FP: medium // FALSE POSITIVES: application cache warmers, health checks, updater and cron scripts run through the web account // EQUIVALENCE: equivalent-with-schema-assumptions - Same process-creation predicate: the creator (InitiatingProcessFileName) is a web server or PHP worker and the created process (FileName) is a download tool or script interpreter. The regex is case-insensitive, which reproduces the CQL /i intent. // REQUIRES: Defender for Endpoint process-creation telemetry (DeviceProcessEvents) on Linux or Windows web servers // TUNING: Summarize by command line over 30 days to find the stable set, then exclude those exact command lines. Keep anything with a raw IP, an unusual TLD, a pipe to sh or bash, or a write to a temp folder. // LOOKBACK: 30d (Advanced Hunting retention) // CONFIGURATION: No lookup objects required. // KNOWN GAPS: A tool started by a process the sensor did not observe starting is not captured. // VALIDATION LEVEL: schema-aligned // AUTOMATED VALIDATION: static-passed; static=passed; fixtures=0/0 DeviceProcessEvents | where Timestamp >= ago(30d) | where InitiatingProcessFileName matches regex @"(?i)^(php-fpm[0-9.]*|php-cgi[0-9.]*|lsphp[0-9]*|frankenphp|httpd|apache2|nginx|litespeed|lshttpd|w3wp)([.]exe)?$" | where FileName matches regex @"(?i)^(curl|wget|python[0-9.]*|perl|ruby|nc|ncat|netcat|socat|tftp|ftp|certutil|bitsadmin|mshta|rundll32|regsvr32)([.]exe)?$" | project Timestamp, ReportId, DeviceId, DeviceName, AccountName, InitiatingProcessFileName, FileName, ProcessCommandLine | sort by Timestamp desc | take 200
Looks for: classic reverse shell constructs (bash with a TCP device redirect, nc with exec, socat exec, scripting-language socket shells, mkfifo pipes) where the process runs as the web account or under a web server parent.
Known limitations and deployment notes
- PowerQuery: A reverse shell built inside a script file rather than on a command line is not captured.
- PowerQuery assumes: On a Process Creation event src.process.* is the creator and tgt.process.* the new process.
- PowerQuery assumes: src.process.user is populated on Process Creation events.
- PowerQuery assumes: The CQL word boundaries are approximated with bracket classes, which can match slightly more.
- KQL – Defender XDR: A reverse shell built inside a script file rather than on a command line is not captured.
- KQL – Defender XDR assumes: AccountName holds the account of the created process on Linux sensors.
- KQL – Defender XDR assumes: The CQL word boundaries are approximated with bracket classes, which can match slightly more.
// HUNT: PHP2Shell reverse shell from a web account // MITRE: T1059.004, T1059.006, T1071.001, T1095 | CONF: high FP: low COST: low // REQUIRES: Falcon sensor on the web server; process creation with command line // FALSE POSITIVES: authorized penetration testing and lab validation of this very flaw // LOOKBACK: 30d #event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2)$/ | CommandLine=/(bash|sh)\s+-i|dev.tcp.[0-9a-z.\-]+.[0-9]+|mkfifo.*\b(nc|ncat|netcat)\b|\b(nc|ncat|netcat)\b.*\s-e\s|socat.*(exec|system):|python[0-9.]*\s+-c.*socket|perl\s+-e.*socket|php\s+-r.*fsockopen/i | (ParentBaseFileName=/^(php-fpm[0-9.]*|php-cgi[0-9.]*|lsphp[0-9]*|frankenphp|httpd|apache2|nginx|litespeed|lshttpd|w3wp)(\.exe)?$/i or UserName=/^(www-data|apache|nginx|php-fpm|httpd|nobody|wwwrun|www|lighttpd)$/i) | table([@timestamp, ComputerName, UserName, ParentBaseFileName, FileName, CommandLine], limit=200) | sort(@timestamp, order=desc)
5 required fields · View requirements
Required telemetry
- Process Creation
Required fields
event.time: Event timeevent.type: Event typetgt.process.cmdline: New process command linesrc.process.name: Creator process namesrc.process.user: Creator user
Recommended fields
tgt.process.name: New process name
Readiness test
event.type = 'Process Creation' tgt.process.cmdline = * | limit 5 | columns event.time, event.type, tgt.process.cmdline, src.process.name, src.process.user
// HUNT: PHP2Shell reverse shell from a web account // SCHEMA PROFILE: sentinelone-endpoint-ocsf // MITRE: T1059.004, T1059.006, T1071.001, T1095 // CONF: high | FP: low // FALSE POSITIVES: authorized penetration testing and lab validation of this very flaw // EQUIVALENCE: equivalent-with-schema-assumptions - Same reverse shell command line patterns on Process Creation, limited to a web server or PHP worker creator or a web service account. Word boundaries and whitespace classes are written with bracket classes; the result is slightly broader than the CQL on boundaries. // REQUIRES: SentinelOne Linux or Windows agent on the web server with Process Creation events // TUNING: Exclude authorized penetration testing and lab validation hosts by endpoint name. // LOOKBACK: 30d (set the console time picker) // CONFIGURATION: No lookup objects required. // KNOWN GAPS: A reverse shell built inside a script file rather than on a command line is not captured. // VALIDATION LEVEL: static-reviewed event.type = 'Process Creation' tgt.process.cmdline matches '(bash|sh)[ ]+-i|dev.tcp.[0-9a-z.-]+.[0-9]+|mkfifo.*(^|[ /])(nc|ncat|netcat)([ ]|$)|(^|[ /])(nc|ncat|netcat)([ ]|$).*[ ]-e[ ]|socat.*(exec|system):|python[0-9.]*[ ]+-c.*socket|perl[ ]+-e.*socket|php[ ]+-r.*fsockopen' (src.process.name matches '^(php-fpm[0-9.]*|php-cgi[0-9.]*|lsphp[0-9]*|frankenphp|httpd|apache2|nginx|litespeed|lshttpd|w3wp)([.]exe)?$' or src.process.user matches '^(www-data|apache|nginx|php-fpm|httpd|nobody|wwwrun|www|lighttpd)$') | columns event.time, endpoint.name, src.process.user, src.process.name, tgt.process.name, tgt.process.cmdline | sort -event.time | limit 200
Automated validation: static-passed
4 required fields · View requirements
Required telemetry
- DeviceProcessEvents
Required fields
Timestamp: Event timeProcessCommandLine: Created command lineInitiatingProcessFileName: Creator process nameAccountName: Account name
Recommended fields
FileName: Created process name
Fallbacks
DeviceName→DeviceId: Changes the displayed host identifier only.
Readiness test
DeviceProcessEvents | where Timestamp > ago(1d) | project Timestamp, ProcessCommandLine, InitiatingProcessFileName, AccountName | take 5
// HUNT: PHP2Shell reverse shell from a web account // SCHEMA PROFILE: microsoft-defender-xdr // MITRE: T1059.004, T1059.006, T1071.001, T1095 // CONF: high | FP: low // FALSE POSITIVES: authorized penetration testing and lab validation of this very flaw // EQUIVALENCE: equivalent-with-schema-assumptions - Same reverse shell command line patterns on process creation, limited to a web server or PHP worker creator or a web service account. Word boundaries and whitespace classes are written with bracket classes, slightly broader than the CQL. The regex is case-insensitive, which reproduces the CQL /i intent. // REQUIRES: Defender for Endpoint process-creation telemetry (DeviceProcessEvents) on Linux or Windows web servers // TUNING: Exclude authorized penetration testing and lab validation hosts by DeviceName. // LOOKBACK: 30d (Advanced Hunting retention) // CONFIGURATION: No lookup objects required. // KNOWN GAPS: A reverse shell built inside a script file rather than on a command line is not captured. // VALIDATION LEVEL: schema-aligned // AUTOMATED VALIDATION: static-passed; static=passed; fixtures=0/0 DeviceProcessEvents | where Timestamp >= ago(30d) | where ProcessCommandLine matches regex @"(?i)(bash|sh)[ ]+-i|dev.tcp.[0-9a-z.-]+.[0-9]+|mkfifo.*(^|[ /])(nc|ncat|netcat)([ ]|$)|(^|[ /])(nc|ncat|netcat)([ ]|$).*[ ]-e[ ]|socat.*(exec|system):|python[0-9.]*[ ]+-c.*socket|perl[ ]+-e.*socket|php[ ]+-r.*fsockopen" | where InitiatingProcessFileName matches regex @"(?i)^(php-fpm[0-9.]*|php-cgi[0-9.]*|lsphp[0-9]*|frankenphp|httpd|apache2|nginx|litespeed|lshttpd|w3wp)([.]exe)?$" or AccountName matches regex @"(?i)^(www-data|apache|nginx|php-fpm|httpd|nobody|wwwrun|www|lighttpd)$" | project Timestamp, ReportId, DeviceId, DeviceName, AccountName, InitiatingProcessFileName, FileName, ProcessCommandLine | sort by Timestamp desc | take 200
Looks for: whoami, id, uname, network discovery and reads of application secrets (wp-config, .env, settings.php, SSH keys, cloud credential files) by the web account or a web parent. CrowdStrike names credential harvesting from configuration files as the standard foothold activity. FP: Monitoring agents and health checks may run id or hostname as the web account. Exclude by exact command line after a 30 day baseline.
Known limitations and deployment notes
- PowerQuery: Credential files read by a built-in PHP function rather than a spawned command are not captured.
- PowerQuery assumes: On a Process Creation event src.process.* is the creator and tgt.process.* the new process.
- PowerQuery assumes: src.process.user is populated on Process Creation events.
- PowerQuery assumes: The CQL word boundaries are approximated with bracket classes, which can match slightly more.
- KQL – Defender XDR: Credential files read by a built-in PHP function rather than a spawned command are not captured.
- KQL – Defender XDR assumes: AccountName holds the account of the created process on Linux sensors.
- KQL – Defender XDR assumes: The CQL word boundaries are approximated with bracket classes, which can match slightly more.
// HUNT: PHP2Shell web account discovery and credential file reads // MITRE: T1033, T1082, T1016, T1552.001, T1083 | CONF: medium FP: medium COST: low // REQUIRES: Falcon sensor on the web server; process creation with command line // FALSE POSITIVES: monitoring or backup scripts run as the web account that call id, hostname or read config files // TUNING: exclude exact command lines from known monitoring and backup jobs per host; a web account reading id_rsa or cloud credential files has no routine reason and should not be excluded // LOOKBACK: 30d #event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2)$/ | (ParentBaseFileName=/^(php-fpm[0-9.]*|php-cgi[0-9.]*|lsphp[0-9]*|frankenphp|httpd|apache2|nginx|litespeed|lshttpd|w3wp)(\.exe)?$/i or UserName=/^(www-data|apache|nginx|php-fpm|httpd|nobody|wwwrun|www|lighttpd)$/i) | CommandLine=/whoami|uname\s+-[a-z]|\bid\s*($|;|\|)|ifconfig|ipconfig|netstat|systeminfo|nltest|printenv|etc.passwd|etc.shadow|wp-config|\.env\b|settings\.php|id_rsa|\.aws|\.ssh|\.git-credentials|net1?\s+(user|group|localgroup)/i | table([@timestamp, ComputerName, UserName, ParentBaseFileName, FileName, CommandLine], limit=200) | sort(@timestamp, order=desc)
5 required fields · View requirements
Required telemetry
- Process Creation
Required fields
event.time: Event timeevent.type: Event typetgt.process.cmdline: New process command linesrc.process.name: Creator process namesrc.process.user: Creator user
Recommended fields
tgt.process.name: New process name
Readiness test
event.type = 'Process Creation' tgt.process.cmdline = * | limit 5 | columns event.time, event.type, tgt.process.cmdline, src.process.name, src.process.user
// HUNT: PHP2Shell web account discovery and credential file reads // SCHEMA PROFILE: sentinelone-endpoint-ocsf // MITRE: T1033, T1082, T1016, T1552.001, T1083 // CONF: medium | FP: medium // FALSE POSITIVES: monitoring or backup scripts run as the web account that call id, hostname or read config files // EQUIVALENCE: equivalent-with-schema-assumptions - Same discovery and credential file command line patterns on Process Creation, limited to a web server or PHP worker creator or a web service account. Word boundaries and whitespace classes use bracket classes, slightly broader than the CQL. // REQUIRES: SentinelOne Linux or Windows agent on the web server with Process Creation events // TUNING: Exclude exact command lines from known monitoring and backup jobs per host. A web account reading id_rsa or cloud credential files has no routine reason and should not be excluded. // LOOKBACK: 30d (set the console time picker) // CONFIGURATION: No lookup objects required. // KNOWN GAPS: Credential files read by a built-in PHP function rather than a spawned command are not captured. // VALIDATION LEVEL: static-reviewed event.type = 'Process Creation' (src.process.name matches '^(php-fpm[0-9.]*|php-cgi[0-9.]*|lsphp[0-9]*|frankenphp|httpd|apache2|nginx|litespeed|lshttpd|w3wp)([.]exe)?$' or src.process.user matches '^(www-data|apache|nginx|php-fpm|httpd|nobody|wwwrun|www|lighttpd)$') tgt.process.cmdline matches 'whoami|uname[ ]+-[a-z]|(^|[ /;|])id[ ]*($|;|[|])|ifconfig|ipconfig|netstat|systeminfo|nltest|printenv|etc.passwd|etc.shadow|wp-config|[.]env([^a-z0-9_]|$)|settings\\.php|id_rsa|[.]aws|[.]ssh|[.]git-credentials|net1?[ ]+(user|group|localgroup)' | columns event.time, endpoint.name, src.process.user, src.process.name, tgt.process.name, tgt.process.cmdline | sort -event.time | limit 200
Automated validation: static-passed
4 required fields · View requirements
Required telemetry
- DeviceProcessEvents
Required fields
Timestamp: Event timeProcessCommandLine: Created command lineInitiatingProcessFileName: Creator process nameAccountName: Account name
Recommended fields
FileName: Created process name
Fallbacks
DeviceName→DeviceId: Changes the displayed host identifier only.
Readiness test
DeviceProcessEvents | where Timestamp > ago(1d) | project Timestamp, ProcessCommandLine, InitiatingProcessFileName, AccountName | take 5
// HUNT: PHP2Shell web account discovery and credential file reads // SCHEMA PROFILE: microsoft-defender-xdr // MITRE: T1033, T1082, T1016, T1552.001, T1083 // CONF: medium | FP: medium // FALSE POSITIVES: monitoring or backup scripts run as the web account that call id, hostname or read config files // EQUIVALENCE: equivalent-with-schema-assumptions - Same discovery and credential file command line patterns on process creation, limited to a web server or PHP worker creator or a web service account. Word boundaries use bracket classes, slightly broader than the CQL. The regex is case-insensitive, which reproduces the CQL /i intent. // REQUIRES: Defender for Endpoint process-creation telemetry (DeviceProcessEvents) on Linux or Windows web servers // TUNING: Exclude exact command lines from known monitoring and backup jobs per host. A web account reading id_rsa or cloud credential files has no routine reason and should not be excluded. // LOOKBACK: 30d (Advanced Hunting retention) // CONFIGURATION: No lookup objects required. // KNOWN GAPS: Credential files read by a built-in PHP function rather than a spawned command are not captured. // VALIDATION LEVEL: schema-aligned // AUTOMATED VALIDATION: static-passed; static=passed; fixtures=0/0 DeviceProcessEvents | where Timestamp >= ago(30d) | where InitiatingProcessFileName matches regex @"(?i)^(php-fpm[0-9.]*|php-cgi[0-9.]*|lsphp[0-9]*|frankenphp|httpd|apache2|nginx|litespeed|lshttpd|w3wp)([.]exe)?$" or AccountName matches regex @"(?i)^(www-data|apache|nginx|php-fpm|httpd|nobody|wwwrun|www|lighttpd)$" | where ProcessCommandLine matches regex @"(?i)whoami|uname[ ]+-[a-z]|(^|[ /;|])id[ ]*($|;|[|])|ifconfig|ipconfig|netstat|systeminfo|nltest|printenv|etc.passwd|etc.shadow|wp-config|[.]env([^a-z0-9_]|$)|settings\.php|id_rsa|[.]aws|[.]ssh|[.]git-credentials|net1?[ ]+(user|group|localgroup)" | project Timestamp, ReportId, DeviceId, DeviceName, AccountName, InitiatingProcessFileName, FileName, ProcessCommandLine | sort by Timestamp desc | take 200
Looks for: crontab edits, authorized_keys writes, new local accounts, service enablement, scheduled tasks and sudo use by the web account or a web parent, which are the follow-on steps after a foothold. FP: Deploy tooling that runs as the web account may edit cron or restart services. Exclude the named deploy jobs only.
Known limitations and deployment notes
- PowerQuery: A persistence file written directly by PHP code without a spawned command is not captured.
- PowerQuery assumes: On a Process Creation event src.process.* is the creator and tgt.process.* the new process.
- PowerQuery assumes: src.process.user is populated on Process Creation events.
- PowerQuery assumes: The CQL word boundaries are approximated with bracket classes, which can match slightly more.
- KQL – Defender XDR: A persistence file written directly by PHP code without a spawned command is not captured.
- KQL – Defender XDR assumes: AccountName holds the account of the created process on Linux sensors.
- KQL – Defender XDR assumes: The CQL word boundaries are approximated with bracket classes, which can match slightly more.
// HUNT: PHP2Shell web account persistence and account changes // MITRE: T1053.003, T1053.005, T1098.004, T1136.001, T1543.002, T1548.003 | CONF: medium FP: medium COST: low // REQUIRES: Falcon sensor on the web server; process creation with command line // FALSE POSITIVES: deployment tooling run as the web account that edits cron or restarts services // TUNING: allow-list exact deploy job command lines per host; never exclude useradd, chpasswd or authorized_keys writes by a web account // LOOKBACK: 30d #event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2)$/ | (ParentBaseFileName=/^(php-fpm[0-9.]*|php-cgi[0-9.]*|lsphp[0-9]*|frankenphp|httpd|apache2|nginx|litespeed|lshttpd|w3wp)(\.exe)?$/i or UserName=/^(www-data|apache|nginx|php-fpm|httpd|nobody|wwwrun|www|lighttpd)$/i) | CommandLine=/crontab|authorized_keys|useradd|adduser|usermod|chpasswd|systemctl\s+(enable|daemon-reload)|\bat\s+-f|schtasks|net1?\s+(user|localgroup).*add|sudo\s+-[a-z]|\bsu\s+-/i | table([@timestamp, ComputerName, UserName, ParentBaseFileName, FileName, CommandLine], limit=200) | sort(@timestamp, order=desc)
5 required fields · View requirements
Required telemetry
- Process Creation
Required fields
event.time: Event timeevent.type: Event typetgt.process.cmdline: New process command linesrc.process.name: Creator process namesrc.process.user: Creator user
Recommended fields
tgt.process.name: New process name
Readiness test
event.type = 'Process Creation' tgt.process.cmdline = * | limit 5 | columns event.time, event.type, tgt.process.cmdline, src.process.name, src.process.user
// HUNT: PHP2Shell web account persistence and account changes // SCHEMA PROFILE: sentinelone-endpoint-ocsf // MITRE: T1053.003, T1053.005, T1098.004, T1136.001, T1543.002, T1548.003 // CONF: medium | FP: medium // FALSE POSITIVES: deployment tooling run as the web account that edits cron or restarts services // EQUIVALENCE: equivalent-with-schema-assumptions - Same persistence and account change command line patterns on Process Creation, limited to a web server or PHP worker creator or a web service account. Word boundaries and whitespace classes use bracket classes, slightly broader than the CQL. // REQUIRES: SentinelOne Linux or Windows agent on the web server with Process Creation events // TUNING: Allow-list exact deploy job command lines per host. Never exclude useradd, chpasswd or authorized_keys writes by a web account. // LOOKBACK: 30d (set the console time picker) // CONFIGURATION: No lookup objects required. // KNOWN GAPS: A persistence file written directly by PHP code without a spawned command is not captured. // VALIDATION LEVEL: static-reviewed event.type = 'Process Creation' (src.process.name matches '^(php-fpm[0-9.]*|php-cgi[0-9.]*|lsphp[0-9]*|frankenphp|httpd|apache2|nginx|litespeed|lshttpd|w3wp)([.]exe)?$' or src.process.user matches '^(www-data|apache|nginx|php-fpm|httpd|nobody|wwwrun|www|lighttpd)$') tgt.process.cmdline matches 'crontab|authorized_keys|useradd|adduser|usermod|chpasswd|systemctl[ ]+(enable|daemon-reload)|(^|[ /;|])at[ ]+-f|schtasks|net1?[ ]+(user|localgroup).*add|sudo[ ]+-[a-z]|(^|[ /;|])su[ ]+-' | columns event.time, endpoint.name, src.process.user, src.process.name, tgt.process.name, tgt.process.cmdline | sort -event.time | limit 200
Automated validation: static-passed
4 required fields · View requirements
Required telemetry
- DeviceProcessEvents
Required fields
Timestamp: Event timeProcessCommandLine: Created command lineInitiatingProcessFileName: Creator process nameAccountName: Account name
Recommended fields
FileName: Created process name
Fallbacks
DeviceName→DeviceId: Changes the displayed host identifier only.
Readiness test
DeviceProcessEvents | where Timestamp > ago(1d) | project Timestamp, ProcessCommandLine, InitiatingProcessFileName, AccountName | take 5
// HUNT: PHP2Shell web account persistence and account changes // SCHEMA PROFILE: microsoft-defender-xdr // MITRE: T1053.003, T1053.005, T1098.004, T1136.001, T1543.002, T1548.003 // CONF: medium | FP: medium // FALSE POSITIVES: deployment tooling run as the web account that edits cron or restarts services // EQUIVALENCE: equivalent-with-schema-assumptions - Same persistence and account change command line patterns on process creation, limited to a web server or PHP worker creator or a web service account. Word boundaries use bracket classes, slightly broader than the CQL. The regex is case-insensitive, which reproduces the CQL /i intent. // REQUIRES: Defender for Endpoint process-creation telemetry (DeviceProcessEvents) on Linux or Windows web servers // TUNING: Allow-list exact deploy job command lines per host. Never exclude useradd, chpasswd or authorized_keys writes by a web account. // LOOKBACK: 30d (Advanced Hunting retention) // CONFIGURATION: No lookup objects required. // KNOWN GAPS: A persistence file written directly by PHP code without a spawned command is not captured. // VALIDATION LEVEL: schema-aligned // AUTOMATED VALIDATION: static-passed; static=passed; fixtures=0/0 DeviceProcessEvents | where Timestamp >= ago(30d) | where InitiatingProcessFileName matches regex @"(?i)^(php-fpm[0-9.]*|php-cgi[0-9.]*|lsphp[0-9]*|frankenphp|httpd|apache2|nginx|litespeed|lshttpd|w3wp)([.]exe)?$" or AccountName matches regex @"(?i)^(www-data|apache|nginx|php-fpm|httpd|nobody|wwwrun|www|lighttpd)$" | where ProcessCommandLine matches regex @"(?i)crontab|authorized_keys|useradd|adduser|usermod|chpasswd|systemctl[ ]+(enable|daemon-reload)|(^|[ /;|])at[ ]+-f|schtasks|net1?[ ]+(user|localgroup).*add|sudo[ ]+-[a-z]|(^|[ /;|])su[ ]+-" | project Timestamp, ReportId, DeviceId, DeviceName, AccountName, InitiatingProcessFileName, FileName, ProcessCommandLine | sort by Timestamp desc | take 200
Looks for: shell redirects, tee, curl or wget output flags and dd writing .php, .phtml or .phar files, base64 decoding, and chmod on temp files, run by the web account. This is the process-visible form of a web shell drop. FP: Deploy scripts and plugin installers legitimately write PHP files. Exclude by exact command line and by a change window.
Known limitations and deployment notes
- PowerQuery: A script written by PHP code itself (file_put_contents) rather than a spawned command is not captured; see the file write card.
- PowerQuery assumes: On a Process Creation event src.process.* is the creator and tgt.process.* the new process.
- PowerQuery assumes: src.process.user is populated on Process Creation events.
- PowerQuery assumes: The CQL word boundaries are approximated with bracket classes, which can match slightly more.
- KQL – Defender XDR: A script written by PHP code itself (file_put_contents) rather than a spawned command is not captured; see the file write card.
- KQL – Defender XDR assumes: AccountName holds the account of the created process on Linux sensors.
- KQL – Defender XDR assumes: The CQL word boundaries are approximated with bracket classes, which can match slightly more.
// HUNT: PHP2Shell web account writes a PHP script or payload by command line // MITRE: T1505.003, T1105, T1140, T1222.002 | CONF: medium FP: medium COST: low // REQUIRES: Falcon sensor on the web server; process creation with command line (file creation of .php is not reliably logged, see coverage map) // FALSE POSITIVES: deploy scripts, plugin or theme installers and package managers run as the web account // TUNING: exclude exact deploy command lines during a change window; keep every write of .php, .phtml or .phar to an upload, cache or temp folder // LOOKBACK: 30d #event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2)$/ | (ParentBaseFileName=/^(php-fpm[0-9.]*|php-cgi[0-9.]*|lsphp[0-9]*|frankenphp|httpd|apache2|nginx|litespeed|lshttpd|w3wp)(\.exe)?$/i or UserName=/^(www-data|apache|nginx|php-fpm|httpd|nobody|wwwrun|www|lighttpd)$/i) | CommandLine=/(>|tee\s+(-a\s+)?|\s-o\s*|\s-O\s*|of=)\s*\S*\.(php[0-9]?|phtml|phar|pht)\b|base64\s+(-d|--decode)|chmod\s+(\+x|[0-7]{3,4})\s+\S*(tmp|shm)/i | table([@timestamp, ComputerName, UserName, ParentBaseFileName, FileName, CommandLine], limit=200) | sort(@timestamp, order=desc)
5 required fields · View requirements
Required telemetry
- Process Creation
Required fields
event.time: Event timeevent.type: Event typetgt.process.cmdline: New process command linesrc.process.name: Creator process namesrc.process.user: Creator user
Recommended fields
tgt.process.name: New process name
Readiness test
event.type = 'Process Creation' tgt.process.cmdline = * | limit 5 | columns event.time, event.type, tgt.process.cmdline, src.process.name, src.process.user
// HUNT: PHP2Shell web account writes a PHP script or payload by command line
// SCHEMA PROFILE: sentinelone-endpoint-ocsf
// MITRE: T1505.003, T1105, T1140, T1222.002
// CONF: medium | FP: medium
// FALSE POSITIVES: deploy scripts, plugin or theme installers and package managers run as the web account
// EQUIVALENCE: equivalent-with-schema-assumptions - Same command line patterns for writing a PHP script or payload, on Process Creation, limited to a web server or PHP worker creator or a web service account. Whitespace classes use bracket classes, slightly broader than the CQL.
// REQUIRES: SentinelOne Linux or Windows agent on the web server with Process Creation events
// TUNING: Exclude exact deploy command lines during a change window. Keep every write of .php, .phtml or .phar to an upload, cache or temp folder.
// LOOKBACK: 30d (set the console time picker)
// CONFIGURATION: No lookup objects required.
// KNOWN GAPS: A script written by PHP code itself (file_put_contents) rather than a spawned command is not captured; see the file write card.
// VALIDATION LEVEL: static-reviewed
event.type = 'Process Creation' (src.process.name matches '^(php-fpm[0-9.]*|php-cgi[0-9.]*|lsphp[0-9]*|frankenphp|httpd|apache2|nginx|litespeed|lshttpd|w3wp)([.]exe)?$' or src.process.user matches '^(www-data|apache|nginx|php-fpm|httpd|nobody|wwwrun|www|lighttpd)$') tgt.process.cmdline matches '(>|tee[ ]+(-a[ ]+)?|[ ]-o[ ]*|[ ]-O[ ]*|of=)[ ]*[^ ]*[.](php[0-9]?|phtml|phar|pht)([^a-z0-9_]|$)|base64[ ]+(-d|--decode)|chmod[ ]+([+]x|[0-7]{3,4})[ ]+[^ ]*(tmp|shm)'
| columns event.time, endpoint.name, src.process.user, src.process.name, tgt.process.name, tgt.process.cmdline
| sort -event.time
| limit 200Automated validation: static-passed
4 required fields · View requirements
Required telemetry
- DeviceProcessEvents
Required fields
Timestamp: Event timeProcessCommandLine: Created command lineInitiatingProcessFileName: Creator process nameAccountName: Account name
Recommended fields
FileName: Created process name
Fallbacks
DeviceName→DeviceId: Changes the displayed host identifier only.
Readiness test
DeviceProcessEvents | where Timestamp > ago(1d) | project Timestamp, ProcessCommandLine, InitiatingProcessFileName, AccountName | take 5
// HUNT: PHP2Shell web account writes a PHP script or payload by command line
// SCHEMA PROFILE: microsoft-defender-xdr
// MITRE: T1505.003, T1105, T1140, T1222.002
// CONF: medium | FP: medium
// FALSE POSITIVES: deploy scripts, plugin or theme installers and package managers run as the web account
// EQUIVALENCE: equivalent-with-schema-assumptions - Same command line patterns for writing a PHP script or payload, on process creation, limited to a web server or PHP worker creator or a web service account. Whitespace classes use bracket classes, slightly broader than the CQL. The regex is case-insensitive, which reproduces the CQL /i intent.
// REQUIRES: Defender for Endpoint process-creation telemetry (DeviceProcessEvents) on Linux or Windows web servers
// TUNING: Exclude exact deploy command lines during a change window. Keep every write of .php, .phtml or .phar to an upload, cache or temp folder.
// LOOKBACK: 30d (Advanced Hunting retention)
// CONFIGURATION: No lookup objects required.
// KNOWN GAPS: A script written by PHP code itself (file_put_contents) rather than a spawned command is not captured; see the file write card.
// VALIDATION LEVEL: schema-aligned
// AUTOMATED VALIDATION: static-passed; static=passed; fixtures=0/0
DeviceProcessEvents
| where Timestamp >= ago(30d)
| where InitiatingProcessFileName matches regex @"(?i)^(php-fpm[0-9.]*|php-cgi[0-9.]*|lsphp[0-9]*|frankenphp|httpd|apache2|nginx|litespeed|lshttpd|w3wp)([.]exe)?$" or AccountName matches regex @"(?i)^(www-data|apache|nginx|php-fpm|httpd|nobody|wwwrun|www|lighttpd)$"
| where ProcessCommandLine matches regex @"(?i)(>|tee[ ]+(-a[ ]+)?|[ ]-o[ ]*|[ ]-O[ ]*|of=)[ ]*[^ ]*[.](php[0-9]?|phtml|phar|pht)([^a-z0-9_]|$)|base64[ ]+(-d|--decode)|chmod[ ]+([+]x|[0-7]{3,4})[ ]+[^ ]*(tmp|shm)"
| project Timestamp, ReportId, DeviceId, DeviceName, AccountName, InitiatingProcessFileName, FileName, ProcessCommandLine
| sort by Timestamp desc
| take 200Looks for: a new ELF binary (Linux) or PE file (Windows) written by a PHP or web server process, which is how a dropped second stage or miner lands. ElfFileWritten is the Linux event; the writer process name is in ContextBaseFileName. FP: PECL builds, Composer and app updaters running through the web account can write binaries. Exclude by writer plus path.
Known limitations and deployment notes
- PowerQuery: An extensionless binary written outside temp and shared-memory folders is not captured.
- PowerQuery: The nginx worker is left out, as in the CQL.
- PowerQuery assumes: On a File Creation event src.process.* is the acting process.
- PowerQuery assumes: The path and extension filter stands in for the CQL executable-written event types.
- KQL – Defender XDR: An extensionless binary written outside temp and shared-memory folders is not captured.
- KQL – Defender XDR: The nginx worker is left out, as in the CQL.
- KQL – Defender XDR assumes: FolderPath holds the directory of the written file on Linux sensors.
- KQL – Defender XDR assumes: The path and extension filter stands in for the CQL executable-written event types.
// HUNT: PHP2Shell executable written by a web server or PHP worker // MITRE: T1105, T1505.003, T1204.002 | CONF: medium FP: medium COST: low // REQUIRES: Falcon sensor on the web server; ElfFileWritten (Linux) or NewExecutableWritten and PeFileWritten (Windows). ⚠ validate in tenant that ContextBaseFileName is populated on these events // FALSE POSITIVES: PECL or extension builds, application updaters and Composer plugins run through the web account // TUNING: exclude known updater paths per host; keep writes under temp, shared-memory, upload and cache folders and anything the web account then executes // LOOKBACK: 30d #event_simpleName=/^(ElfFileWritten|NewExecutableWritten|PeFileWritten)$/ | ContextBaseFileName=/^(php-fpm[0-9.]*|php-cgi[0-9.]*|lsphp[0-9]*|frankenphp|httpd|apache2|litespeed|lshttpd|w3wp)(\.exe)?$/i | table([@timestamp, ComputerName, #event_simpleName, ContextBaseFileName, TargetFileName, SHA256HashData], limit=200) | sort(@timestamp, order=desc)
4 required fields · View requirements
Required telemetry
- File Creation
Required fields
event.time: Event timeevent.type: Event typesrc.process.name: Writing process nametgt.file.path: Written file path
Optional fields
tgt.file.sha256: Hash. Population on File Creation events varies by agent version.
Readiness test
event.type = 'File Creation' src.process.name = * tgt.file.path = * | limit 5 | columns event.time, event.type, src.process.name, tgt.file.path
// HUNT: PHP2Shell executable written by a web server or PHP worker // SCHEMA PROFILE: sentinelone-endpoint-ocsf // MITRE: T1105, T1505.003, T1204.002 // CONF: medium | FP: medium // FALSE POSITIVES: PECL or extension builds, application updaters and Composer plugins run through the web account // EQUIVALENCE: approximate-analytic - The CQL keys on Falcon executable-written sensor events. SentinelOne File Creation events do not flag a file as executable, so this hunts file creation by a web server or PHP worker and keeps executable extensions and writes under temp and shared-memory folders. It can miss an extensionless binary dropped outside those folders and can add noise the CQL does not have. // REQUIRES: SentinelOne Linux or Windows agent on the web server with File Creation events // TUNING: Exclude known updater paths per host. Keep writes under temp, shared-memory, upload and cache folders and anything the web account then executes. // LOOKBACK: 30d (set the console time picker) // CONFIGURATION: No lookup objects required. // KNOWN GAPS: An extensionless binary written outside temp and shared-memory folders is not captured. The nginx worker is left out, as in the CQL. // VALIDATION LEVEL: static-reviewed event.type = 'File Creation' src.process.name matches '^(php-fpm[0-9.]*|php-cgi[0-9.]*|lsphp[0-9]*|frankenphp|httpd|apache2|litespeed|lshttpd|w3wp)([.]exe)?$' tgt.file.path matches '[.](exe|dll|so|elf|bin|sh|scr|msi)$|^/(tmp|dev/shm|var/tmp)/' | columns event.time, endpoint.name, src.process.name, tgt.file.path, tgt.file.sha256 | sort -event.time | limit 200
Automated validation: static-passed
5 required fields · View requirements
Required telemetry
- DeviceFileEvents
Required fields
Timestamp: Event timeActionType: Action typeInitiatingProcessFileName: Writing process nameFileName: Written file nameFolderPath: Written file folder
Optional fields
SHA256: Hash. SHA256 is often unpopulated; SHA1 is the safer pivot.
Fallbacks
DeviceName→DeviceId: Changes the displayed host identifier only.
Readiness test
DeviceFileEvents | where Timestamp > ago(1d) | project Timestamp, ActionType, InitiatingProcessFileName, FileName, FolderPath | take 5
// HUNT: PHP2Shell executable written by a web server or PHP worker // SCHEMA PROFILE: microsoft-defender-xdr // MITRE: T1105, T1505.003, T1204.002 // CONF: medium | FP: medium // FALSE POSITIVES: PECL or extension builds, application updaters and Composer plugins run through the web account // EQUIVALENCE: approximate-analytic - The CQL keys on Falcon executable-written sensor events. DeviceFileEvents does not flag a file as executable, so this hunts file creation by a web server or PHP worker and keeps executable extensions and writes under temp and shared-memory folders. It can miss an extensionless binary dropped outside those folders and can add noise the CQL does not have. // REQUIRES: Defender for Endpoint file telemetry (DeviceFileEvents) on Linux or Windows web servers // TUNING: Exclude known updater paths per host. Keep writes under temp, shared-memory, upload and cache folders and anything the web account then executes. // LOOKBACK: 30d (Advanced Hunting retention) // CONFIGURATION: No lookup objects required. // KNOWN GAPS: An extensionless binary written outside temp and shared-memory folders is not captured. The nginx worker is left out, as in the CQL. // VALIDATION LEVEL: schema-aligned // AUTOMATED VALIDATION: static-passed; static=passed; fixtures=0/0 DeviceFileEvents | where Timestamp >= ago(30d) | where ActionType == "FileCreated" | where InitiatingProcessFileName matches regex @"(?i)^(php-fpm[0-9.]*|php-cgi[0-9.]*|lsphp[0-9]*|frankenphp|httpd|apache2|litespeed|lshttpd|w3wp)([.]exe)?$" | where FileName matches regex @"(?i)[.](exe|dll|so|elf|bin|sh|scr|msi)$" or FolderPath matches regex @"(?i)^/(tmp|dev/shm|var/tmp)" | project Timestamp, ReportId, DeviceId, DeviceName, InitiatingProcessFileName, FileName, FolderPath, SHA1, SHA256 | sort by Timestamp desc | take 200
Looks for: any endpoint connection involving the nine source addresses reported for PHP2Shell: six from Amazon MadPot (one on 2026-10-17, five on 2026-10-21) and three from VulnCheck honeypots. Amazon states these include shared anonymization infrastructure, so a hit is a lead to investigate, never a block decision. FP: The addresses are shared anonymization infrastructure that is not exclusive to this activity. A hit alone is not proof of compromise.
Known limitations and deployment notes
- PowerQuery: Inbound connections the agent does not log are not captured; the request itself is only visible in web access logs.
- PowerQuery assumes: IP Connect events carry the remote address in dst.ip.address for outbound and src.ip.address for inbound.
- PowerQuery assumes: event.network.direction is [D] documented only and is shown for triage, not used to filter.
- KQL – Defender XDR: Inbound connections the sensor does not log are not captured; the request itself is only visible in web access logs.
- KQL – Defender XDR assumes: RemoteIP holds the peer address for both inbound and outbound rows.
// HUNT: PHP2Shell reported source IPs seen on a web server // MITRE: T1190, T1071.001 | CONF: low FP: medium COST: low // REQUIRES: Falcon sensor; NetworkConnectIP4 for outbound and NetworkReceiveAcceptIP4 for inbound (inbound coverage varies by sensor and OS, validate in tenant) // FALSE POSITIVES: shared anonymization infrastructure used by unrelated traffic; Amazon says investigate rather than block // TUNING: treat a hit as a lead; pivot to Q1, Q2 and Q3 on the same host and to web access logs (N2) for the request; do not block on this query alone // LOOKBACK: 30d (first reported source activity 2026-10-17) #event_simpleName=/^(NetworkConnectIP4|NetworkReceiveAcceptIP4)$/ | in(field=RemoteAddressIP4, values=["103.216.220.92", "31.223.184.166", "37.19.200.5", "134.209.159.64", "103.114.163.188", "95.217.98.37", "85.11.167.243", "45.198.224.169", "109.71.252.97"]) | table([@timestamp, ComputerName, #event_simpleName, ContextBaseFileName, LocalPort, RemoteAddressIP4, RemotePort], limit=200) | sort(@timestamp, order=desc)
4 required fields · View requirements
Required telemetry
- IP Connect
Required fields
event.time: Event timeevent.type: Event typedst.ip.address: Destination addresssrc.ip.address: Source address
Recommended fields
src.process.name: Acting process name
Optional fields
event.network.direction: Direction
Readiness test
event.type = 'IP Connect' dst.ip.address = * | limit 5 | columns event.time, event.type, dst.ip.address, src.ip.address
// HUNT: PHP2Shell reported source IPs seen on a web server
// SCHEMA PROFILE: sentinelone-endpoint-ocsf
// MITRE: T1190, T1071.001
// CONF: low | FP: medium
// FALSE POSITIVES: shared anonymization infrastructure used by unrelated traffic; Amazon says investigate rather than block
// EQUIVALENCE: equivalent-with-schema-assumptions - Same indicator list matched against IP Connect events in either address position, which covers both the outbound and inbound halves of the CQL.
// REQUIRES: SentinelOne agent with IP Connect events; inbound coverage varies by agent and OS
// TUNING: Treat a hit as a lead. Pivot to Q1, Q2 and Q3 on the same host and to web access logs for the request. Do not block on this query alone.
// LOOKBACK: 30d (set the console time picker; first reported source activity 2026-10-17)
// CONFIGURATION: No lookup objects required.
// KNOWN GAPS: Inbound connections the agent does not log are not captured; the request itself is only visible in web access logs.
// VALIDATION LEVEL: static-reviewed
event.type = 'IP Connect' (dst.ip.address in ('103.216.220.92', '31.223.184.166', '37.19.200.5', '134.209.159.64', '103.114.163.188', '95.217.98.37', '85.11.167.243', '45.198.224.169', '109.71.252.97') or src.ip.address in ('103.216.220.92', '31.223.184.166', '37.19.200.5', '134.209.159.64', '103.114.163.188', '95.217.98.37', '85.11.167.243', '45.198.224.169', '109.71.252.97'))
| columns event.time, endpoint.name, event.network.direction, src.process.name, src.ip.address, src.port.number, dst.ip.address, dst.port.number
| sort -event.time
| limit 200Automated validation: static-passed
2 required fields · View requirements
Required telemetry
- DeviceNetworkEvents
Required fields
Timestamp: Event timeRemoteIP: Remote address
Recommended fields
InitiatingProcessFileName: Initiating process
Optional fields
ActionType: Action type
Fallbacks
DeviceName→DeviceId: Changes the displayed host identifier only.
Readiness test
DeviceNetworkEvents | where Timestamp > ago(1d) | project Timestamp, RemoteIP, RemotePort | take 5
// HUNT: PHP2Shell reported source IPs seen on a web server
// SCHEMA PROFILE: microsoft-defender-xdr
// MITRE: T1190, T1071.001
// CONF: low | FP: medium
// FALSE POSITIVES: shared anonymization infrastructure used by unrelated traffic; Amazon says investigate rather than block
// EQUIVALENCE: equivalent-with-schema-assumptions - Same indicator list matched against the remote address of network events, which covers both the outbound and inbound halves of the CQL when the sensor logs inbound connections.
// REQUIRES: Defender for Endpoint network telemetry (DeviceNetworkEvents); inbound rows appear as InboundConnectionAccepted only where the sensor logs them
// TUNING: Treat a hit as a lead. Pivot to Q1, Q2 and Q3 on the same host and to web access logs for the request. Do not block on this query alone.
// LOOKBACK: 30d (Advanced Hunting retention; first reported source activity 2026-10-17)
// CONFIGURATION: No lookup objects required.
// KNOWN GAPS: Inbound connections the sensor does not log are not captured; the request itself is only visible in web access logs.
// VALIDATION LEVEL: schema-aligned
// AUTOMATED VALIDATION: static-passed; static=passed; fixtures=0/0
DeviceNetworkEvents
| where Timestamp >= ago(30d)
| where RemoteIP in ("103.216.220.92", "31.223.184.166", "37.19.200.5", "134.209.159.64", "103.114.163.188", "95.217.98.37", "85.11.167.243", "45.198.224.169", "109.71.252.97")
| project Timestamp, ReportId, DeviceId, DeviceName, ActionType, InitiatingProcessFileName, LocalPort, RemoteIP, RemotePort
| sort by Timestamp desc
| take 200Looks for: external destinations contacted by PHP and web server worker processes, grouped so that destinations seen from only one or two hosts surface first. A worker that begins talking to a new address after 2026-10-17 is a lead for second stage fetches or callbacks. FP: PHP applications call payment, mail, update and API services, so the list is large on busy hosts. Exclude known service ranges and keep rare ones.
Known limitations and deployment notes
- PowerQuery: The nginx worker is left out, as in the CQL.
- PowerQuery: Only one process name per group is shown.
- PowerQuery assumes: On an IP Connect event src.process.* is the acting process.
- PowerQuery assumes: The CQL collects up to three process names per group; this keeps one with any().
- KQL – Defender XDR: The nginx worker is left out, as in the CQL.
- KQL – Defender XDR assumes: ConnectionSuccess is the ActionType for completed outbound connections.
- KQL – Defender XDR assumes: IPv6 peers are not in the CQL and a non-IPv4 RemoteIP is not filtered by the range test.
// HUNT: PHP2Shell rare external destinations from PHP and web workers // MITRE: T1071.001, T1105, T1041 | CONF: low FP: medium COST: medium // REQUIRES: Falcon sensor on the web server; NetworkConnectIP4 with ContextBaseFileName populated // FALSE POSITIVES: payment, mail, update, CDN and API calls made by the application // TUNING: exclude your known service destinations (use cidr for ranges); keep destinations seen from one or two hosts and any new destination first seen after 2026-10-17 // LOOKBACK: 14d #event_simpleName=NetworkConnectIP4 | ContextBaseFileName=/^(php-fpm[0-9.]*|php-cgi[0-9.]*|lsphp[0-9]*|frankenphp|httpd|apache2|litespeed|lshttpd|w3wp)(\.exe)?$/i | !cidr(RemoteAddressIP4, subnet=["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "127.0.0.0/8", "169.254.0.0/16"]) | groupBy([RemoteAddressIP4, RemotePort], function=[count(aid, distinct=true, as=hosts), count(as=conns), collect([ContextBaseFileName], limit=3)]) | sort(hosts, order=asc, limit=200)
5 required fields · View requirements
Required telemetry
- IP Connect
Required fields
event.type: Event typesrc.process.name: Acting process namedst.ip.address: Destination addressdst.port.number: Destination portagent.uuid: Endpoint identifier
Fallbacks
agent.uuid→endpoint.name: Changes the host count if names are reused.
Readiness test
event.type = 'IP Connect' src.process.name = * dst.ip.address = * | limit 5 | columns event.type, src.process.name, dst.ip.address, dst.port.number, agent.uuid
// HUNT: PHP2Shell rare external destinations from PHP and web workers // SCHEMA PROFILE: sentinelone-endpoint-ocsf // MITRE: T1071.001, T1105, T1041 // CONF: low | FP: medium // FALSE POSITIVES: payment, mail, update, CDN and API calls made by the application // EQUIVALENCE: equivalent-with-schema-assumptions - Same rarity analytic: external IP Connect destinations from web server and PHP worker processes, grouped by address and port and sorted by fewest distinct hosts first. Private, loopback and link-local ranges are excluded with the same five subnets as the CQL. // REQUIRES: SentinelOne agent with IP Connect events and the acting process populated // TUNING: Exclude your known service destinations. Keep destinations seen from one or two hosts and any new destination first seen after 2026-10-17. // LOOKBACK: 14d (set the console time picker) // CONFIGURATION: No lookup objects required. // KNOWN GAPS: The nginx worker is left out, as in the CQL. Only one process name per group is shown. // VALIDATION LEVEL: static-reviewed event.type = 'IP Connect' src.process.name matches '^(php-fpm[0-9.]*|php-cgi[0-9.]*|lsphp[0-9]*|frankenphp|httpd|apache2|litespeed|lshttpd|w3wp)([.]exe)?$' !(net_ipsubnet(dst.ip.address, '10.0.0.0/8') or net_ipsubnet(dst.ip.address, '172.16.0.0/12') or net_ipsubnet(dst.ip.address, '192.168.0.0/16') or net_ipsubnet(dst.ip.address, '127.0.0.0/8') or net_ipsubnet(dst.ip.address, '169.254.0.0/16')) | group hosts=estimate_distinct(agent.uuid), conns=count(), process_name=any(src.process.name) by dst.ip.address, dst.port.number | sort +hosts | limit 200
Automated validation: static-passed
6 required fields · View requirements
Required telemetry
- DeviceNetworkEvents
Required fields
Timestamp: Event timeActionType: Action typeInitiatingProcessFileName: Initiating processRemoteIP: Remote addressRemotePort: Remote portDeviceId: Device identifier
Readiness test
DeviceNetworkEvents | where Timestamp > ago(1d) | project Timestamp, ActionType, InitiatingProcessFileName, RemoteIP, RemotePort, DeviceId | take 5
// HUNT: PHP2Shell rare external destinations from PHP and web workers // SCHEMA PROFILE: microsoft-defender-xdr // MITRE: T1071.001, T1105, T1041 // CONF: low | FP: medium // FALSE POSITIVES: payment, mail, update, CDN and API calls made by the application // EQUIVALENCE: equivalent-with-schema-assumptions - Same rarity analytic: external connections from web server and PHP worker processes, grouped by address and port and sorted by fewest distinct devices first. Private, loopback and link-local ranges are excluded with the same five subnets as the CQL. // REQUIRES: Defender for Endpoint network telemetry (DeviceNetworkEvents) with the initiating process populated // TUNING: Exclude your known service destinations. Keep destinations seen from one or two devices and any new destination first seen after 2026-10-17. // LOOKBACK: 14d (Advanced Hunting retention allows up to 30d) // CONFIGURATION: No lookup objects required. // KNOWN GAPS: The nginx worker is left out, as in the CQL. // VALIDATION LEVEL: schema-aligned // AUTOMATED VALIDATION: static-passed; static=passed-with-warnings; fixtures=0/0 DeviceNetworkEvents | where Timestamp >= ago(14d) | where ActionType == "ConnectionSuccess" | where InitiatingProcessFileName matches regex @"(?i)^(php-fpm[0-9.]*|php-cgi[0-9.]*|lsphp[0-9]*|frankenphp|httpd|apache2|litespeed|lshttpd|w3wp)([.]exe)?$" | where not(ipv4_is_in_any_range(RemoteIP, dynamic(["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "127.0.0.0/8", "169.254.0.0/16"]))) | summarize hosts = dcount(DeviceId), conns = count(), processes = make_set(InitiatingProcessFileName, 3) by RemoteIP, RemotePort | sort by hosts asc | take 200
Looks for: the string PHP_SESSION_UPLOAD_PROGRESS or the scanner user agent php2shell-scan on any process command line. Amazon says to look for the marker in POST form uploads, which is a web-tier check. An endpoint sensor sees it only when a tool on the host itself carries the string, such as a local exploit test, a scanner, or an attacker pivoting from the server. FP: Authorized vulnerability scanners, the vendor check tools, and your own lab validation will match. Exclude those scan hosts and accounts.
Known limitations and deployment notes
- PowerQuery: The exploit request itself is outside endpoint telemetry; only a command line that carries the strings is seen.
- PowerQuery assumes: The new process command line is held in tgt.process.cmdline.
- KQL – Defender XDR: The exploit request itself is outside endpoint telemetry; only a command line that carries the strings is seen.
- KQL – Defender XDR assumes: ProcessCommandLine holds the full command line of the created process.
// HUNT: PHP2Shell upload-progress marker or scanner string on a command line // MITRE: T1190, T1595.002 | CONF: low FP: medium COST: low // REQUIRES: Falcon sensor; process creation with command line. This does NOT see HTTP request bodies; the exploit request itself is outside endpoint telemetry // FALSE POSITIVES: authorized vulnerability scans, vendor check tools and internal lab validation of this flaw // TUNING: exclude the scan hosts and service accounts you run on purpose; a hit on a production web server outside a change window warrants review of Q1 to Q3 on that host // LOOKBACK: 30d #event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2)$/ | CommandLine=/PHP_SESSION_UPLOAD_PROGRESS|php2shell-scan/i | table([@timestamp, ComputerName, UserName, ParentBaseFileName, FileName, CommandLine], limit=200) | sort(@timestamp, order=desc)
3 required fields · View requirements
Required telemetry
- Process Creation
Required fields
event.time: Event timeevent.type: Event typetgt.process.cmdline: New process command line
Recommended fields
src.process.name: Creator process name
Readiness test
event.type = 'Process Creation' tgt.process.cmdline = * | limit 5 | columns event.time, event.type, tgt.process.cmdline
// HUNT: PHP2Shell upload-progress marker or scanner string on a command line // SCHEMA PROFILE: sentinelone-endpoint-ocsf // MITRE: T1190, T1595.002 // CONF: low | FP: medium // FALSE POSITIVES: authorized vulnerability scans, vendor check tools and internal lab validation of this flaw // EQUIVALENCE: equivalent-with-schema-assumptions - Same two strings searched in the command line of Process Creation events. matches is case-insensitive by default, which reproduces the CQL /i intent. // REQUIRES: SentinelOne agent with Process Creation events. This does not see HTTP request bodies. // TUNING: Exclude the scan hosts and service accounts you run on purpose. A hit on a production web server outside a change window warrants review of Q1 to Q3 on that host. // LOOKBACK: 30d (set the console time picker) // CONFIGURATION: No lookup objects required. // KNOWN GAPS: The exploit request itself is outside endpoint telemetry; only a command line that carries the strings is seen. // VALIDATION LEVEL: static-reviewed event.type = 'Process Creation' tgt.process.cmdline matches 'PHP_SESSION_UPLOAD_PROGRESS|php2shell-scan' | columns event.time, endpoint.name, src.process.user, src.process.name, tgt.process.name, tgt.process.cmdline | sort -event.time | limit 200
Automated validation: static-passed
2 required fields · View requirements
Required telemetry
- DeviceProcessEvents
Required fields
Timestamp: Event timeProcessCommandLine: Created command line
Recommended fields
InitiatingProcessFileName: Creator process name
Fallbacks
DeviceName→DeviceId: Changes the displayed host identifier only.
Readiness test
DeviceProcessEvents | where Timestamp > ago(1d) | project Timestamp, ProcessCommandLine | take 5
// HUNT: PHP2Shell upload-progress marker or scanner string on a command line // SCHEMA PROFILE: microsoft-defender-xdr // MITRE: T1190, T1595.002 // CONF: low | FP: medium // FALSE POSITIVES: authorized vulnerability scans, vendor check tools and internal lab validation of this flaw // EQUIVALENCE: equivalent-with-schema-assumptions - Same two strings searched in the command line of process creation events. contains is case-insensitive, which reproduces the CQL /i intent. // REQUIRES: Defender for Endpoint process-creation telemetry (DeviceProcessEvents). This does not see HTTP request bodies. // TUNING: Exclude the scan hosts and service accounts you run on purpose. A hit on a production web server outside a change window warrants review of Q1 to Q3 on that host. // LOOKBACK: 30d (Advanced Hunting retention) // CONFIGURATION: No lookup objects required. // KNOWN GAPS: The exploit request itself is outside endpoint telemetry; only a command line that carries the strings is seen. // VALIDATION LEVEL: schema-aligned // AUTOMATED VALIDATION: static-passed; static=passed; fixtures=0/0 DeviceProcessEvents | where Timestamp >= ago(30d) | where ProcessCommandLine contains "PHP_SESSION_UPLOAD_PROGRESS" or ProcessCommandLine contains "php2shell-scan" | project Timestamp, ReportId, DeviceId, DeviceName, AccountName, InitiatingProcessFileName, FileName, ProcessCommandLine | sort by Timestamp desc | take 200
Detection Rules by Platform
CrowdStrike: Custom IOA
| IOA name | Field Patterns | Benign Exclusions | Deployment Path |
|---|---|---|---|
| Web server or PHP worker spawns a shell. Promote Q1 | Platform Linux or Windows; Parent image base name php-fpm*|php-cgi*|lsphp*|frankenphp|httpd|apache2|nginx|w3wp; Image sh|bash|dash|zsh|ksh|ash|busybox|cmd.exe|powershell.exe|pwsh.exe | Known parent and command line pairs found in the 30 day baseline (PHP mail() sendmail, named converters, deploy hooks). Do not exclude by parent alone | Custom IOA → Process Creation. Detect for 30 days, then Prevent only on hosts whose baseline is clean. Existing Falcon IOAs already cover suspicious children of PHP and web servers (CrowdStrike); this rule adds your own tuned scope |
| Reverse shell from a web account. Promote Q3 | Platform Linux or Windows; Command line regex (bash|sh)\s+-i|dev.tcp|mkfifo.*(nc|ncat|netcat)|(nc|ncat|netcat).*\s-e\s|socat.*(exec|system):|python.*-c.*socket|perl.*-e.*socket|php.*-r.*fsockopen; Parent as above or user www-data|apache|nginx|php-fpm|httpd|nobody | Authorized penetration tests and lab validation hosts only | Custom IOA → Process Creation. Detect, escalate every hit, then Prevent after one clean month |
SentinelOne: custom detection rules
| Rule | Source query | Run as | Notes |
|---|---|---|---|
| Web server or PHP worker spawns a shell. Promote Q1 | Q1, PowerQuery tab | Scheduled custom detection rule | Bind entities so the alert names the host instead of "Unknown Device". This pack's own text mentions a baseline or pilot; follow it as written |
| Reverse shell from a web account. Promote Q3 | Q3, PowerQuery tab | Scheduled custom detection rule | Bind entities so the alert names the host instead of "Unknown Device". This pack's own text mentions a baseline or pilot; follow it as written |
Create the rule with POST /web/api/v2.1/cloud-detection/rules, queryType: "scheduled" and queryLang: "2.0", with the query in data.scheduledParams.query. Use 2.0 operator spellings: a rule written with the 1.0 token ContainsCIS was accepted and never fired on a tenant. The API does not warn, so lint the body before deploying.
Microsoft Defender: custom detection rules
| Rule | Source query | Run as | Notes |
|---|---|---|---|
| Web server or PHP worker spawns a shell. Promote Q1 | Q1, KQL – Defender XDR tab | Custom detection rule, alert only, every hour | The query returns Timestamp, ReportId and DeviceId, which Microsoft recommends for Defender for Endpoint tables so alerts carry the device group scope and the process tree. Remove the Timestamp filter and the final sort and take before saving the rule: the service applies the rule's lookback itself. This pack's own text mentions a baseline or pilot; follow it as written |
| Reverse shell from a web account. Promote Q3 | Q3, KQL – Defender XDR tab | Custom detection rule, alert only, every hour | The query returns Timestamp, ReportId and DeviceId, which Microsoft recommends for Defender for Endpoint tables so alerts carry the device group scope and the process tree. Remove the Timestamp filter and the final sort and take before saving the rule: the service applies the rule's lookback itself. This pack's own text mentions a baseline or pilot; follow it as written |
Q2, Q4, Q5, Q6, Q7, Q8, Q9 and Q10 stay a hunt in this pack, not a rule. A rule can generate only 150 alerts per run. Continuous (NRT) frequency needs a single table and no comment lines, so delete the generated comment header from the query first.
Machine-Readable IOC Appendix
Grouped IOC Quick-Copy
Nine importable IPv4 addresses, all detect, none prevent: Amazon says they include shared anonymization infrastructure and no value has two independent sources. Two labelled REPLACE_WITH rows are placeholders because no source publishes a payload hash or second-stage domain.
type,value,action,severity,expiration,description,tags ipv4,103.216.220.92,detect,medium,2027-01-22,Amazon MadPot first PHP2Shell exploitation attempt 2026-10-17 shared anonymization infrastructure investigate rather than block,cve:CVE-2026-109618;cve:CVE-2026-109619 ipv4,31.223.184.166,detect,medium,2027-01-22,Amazon MadPot PHP2Shell exploitation attempt 2026-10-21 shared anonymization infrastructure investigate rather than block,cve:CVE-2026-109618;cve:CVE-2026-109619 ipv4,37.19.200.5,detect,medium,2027-01-22,Amazon MadPot PHP2Shell exploitation attempt 2026-10-21 shared anonymization infrastructure investigate rather than block,cve:CVE-2026-109618;cve:CVE-2026-109619 ipv4,134.209.159.64,detect,medium,2027-01-22,Amazon MadPot PHP2Shell exploitation attempt 2026-10-21 shared anonymization infrastructure investigate rather than block,cve:CVE-2026-109618;cve:CVE-2026-109619 ipv4,103.114.163.188,detect,medium,2027-01-22,Amazon MadPot PHP2Shell exploitation attempt 2026-10-21 shared anonymization infrastructure investigate rather than block,cve:CVE-2026-109618;cve:CVE-2026-109619 ipv4,95.217.98.37,detect,medium,2027-01-22,Amazon MadPot PHP2Shell exploitation attempt 2026-10-21 shared anonymization infrastructure investigate rather than block,cve:CVE-2026-109618;cve:CVE-2026-109619 ipv4,85.11.167.243,detect,low,2027-01-22,VulnCheck honeypot PHP2Shell source reported by single secondary article perishable investigate rather than block,cve:CVE-2026-109618;cve:CVE-2026-109619 ipv4,45.198.224.169,detect,low,2027-01-22,VulnCheck honeypot PHP2Shell source reported by single secondary article perishable investigate rather than block,cve:CVE-2026-109618;cve:CVE-2026-109619 ipv4,109.71.252.97,detect,low,2027-01-22,VulnCheck honeypot PHP2Shell source reported by single secondary article perishable investigate rather than block,cve:CVE-2026-109618;cve:CVE-2026-109619 sha256,REPLACE_WITH_SHA256_OF_DROPPED_PAYLOAD_FROM_YOUR_IR,detect,high,2027-04-22,No payload hash published in any source: fill from your own incident evidence,cve:CVE-2026-109618 domain,REPLACE_WITH_SECOND_STAGE_DOMAIN_FROM_YOUR_IR,detect,high,2027-01-22,No second stage domain published in any source: fill from your own incident evidence,cve:CVE-2026-109618
indicatorType,indicatorValue,action,title,description,expirationTime,severity,recommendedActions,rbacGroups,category,mitretechniques,GenerateAlert IpAddress,103.216.220.92,Audit,Amazon MadPot first PHP2Shell exploitation attempt 2026-10-1,Amazon MadPot first PHP2Shell exploitation attempt 2026-10-17 shared anonymization infrastructure investigate rather than block,2027-01-22T00:00:00.0Z,Medium,Investigate the device and pivot into the pack's queries,,,,True IpAddress,31.223.184.166,Audit,Amazon MadPot PHP2Shell exploitation attempt 2026-10-21 shar,Amazon MadPot PHP2Shell exploitation attempt 2026-10-21 shared anonymization infrastructure investigate rather than block,2027-01-22T00:00:00.0Z,Medium,Investigate the device and pivot into the pack's queries,,,,True IpAddress,37.19.200.5,Audit,Amazon MadPot PHP2Shell exploitation attempt 2026-10-21 shar,Amazon MadPot PHP2Shell exploitation attempt 2026-10-21 shared anonymization infrastructure investigate rather than block,2027-01-22T00:00:00.0Z,Medium,Investigate the device and pivot into the pack's queries,,,,True IpAddress,134.209.159.64,Audit,Amazon MadPot PHP2Shell exploitation attempt 2026-10-21 shar,Amazon MadPot PHP2Shell exploitation attempt 2026-10-21 shared anonymization infrastructure investigate rather than block,2027-01-22T00:00:00.0Z,Medium,Investigate the device and pivot into the pack's queries,,,,True IpAddress,103.114.163.188,Audit,Amazon MadPot PHP2Shell exploitation attempt 2026-10-21 shar,Amazon MadPot PHP2Shell exploitation attempt 2026-10-21 shared anonymization infrastructure investigate rather than block,2027-01-22T00:00:00.0Z,Medium,Investigate the device and pivot into the pack's queries,,,,True IpAddress,95.217.98.37,Audit,Amazon MadPot PHP2Shell exploitation attempt 2026-10-21 shar,Amazon MadPot PHP2Shell exploitation attempt 2026-10-21 shared anonymization infrastructure investigate rather than block,2027-01-22T00:00:00.0Z,Medium,Investigate the device and pivot into the pack's queries,,,,True IpAddress,85.11.167.243,Audit,VulnCheck honeypot PHP2Shell source reported by single secon,VulnCheck honeypot PHP2Shell source reported by single secondary article perishable investigate rather than block,2027-01-22T00:00:00.0Z,Low,Investigate the device and pivot into the pack's queries,,,,True IpAddress,45.198.224.169,Audit,VulnCheck honeypot PHP2Shell source reported by single secon,VulnCheck honeypot PHP2Shell source reported by single secondary article perishable investigate rather than block,2027-01-22T00:00:00.0Z,Low,Investigate the device and pivot into the pack's queries,,,,True IpAddress,109.71.252.97,Audit,VulnCheck honeypot PHP2Shell source reported by single secon,VulnCheck honeypot PHP2Shell source reported by single secondary article perishable investigate rather than block,2027-01-22T00:00:00.0Z,Low,Investigate the device and pivot into the pack's queries,,,,True
Column names and allowed values are from Microsoft's documented parameter table. Left out because the value is a placeholder, defanged or not valid for its type (fix it in the pack's CSV first): placeholder 2. Check each expirationTime before importing: an indicator whose date has passed is already expired. Download the sample CSV from the Import page to confirm the column order before a bulk import. Each file takes at most 500 indicators. Learn describes the import per entity type, so if a mixed file is rejected, split it by indicatorType. An imported indicator does nothing unless the matching Advanced features are on (for example Custom network indicators and Allow or block file).
This pack does not ship a SentinelOne import block: the import format could not be verified against SentinelOne's own documentation. Export a sample from your console and the block can be added with confidence.
# Behavioral signatures (detection logic, not IOCs) php-fpm | php-cgi | lsphp | frankenphp | httpd | apache2 | nginx | w3wp spawns sh, bash, dash, cmd.exe or powershell.exe web account or web parent runs curl, wget, python, perl, ruby, socat, nc, certutil or bitsadmin web account runs bash -i, a /dev/tcp redirect, nc -e, socat exec or a scripting-language socket shell web account runs whoami, uname, id, ifconfig, netstat or reads wp-config, .env, id_rsa, .aws, .ssh web account edits crontab or authorized_keys, runs useradd or chpasswd, or writes a .php, .phtml or .phar file ELF or PE file written by a PHP or web server process new external destination contacted by a PHP worker after 2026-10-17 PHP error log: "Allowed memory size of N bytes exhausted" with "in Unknown on line 0" (startup-phase fatal, before any script ran) PHP-FPM or Apache error log: "exited on signal 11", "Out of memory (allocated", clusters of "Maximum execution time of" web tier: multipart/form-data POST to an endpoint that takes no uploads, chunked with no declared length, followed by HTTP 500s web tier correlate only: PHP_SESSION_UPLOAD_PROGRESS form field (also the legitimate progress-bar field)
# Scanner user agent reported by VulnCheck (via The Hacker News, single source) php2shell-scan/109618 # Form field named by Amazon (legitimate progress-bar field, use as a correlate) PHP_SESSION_UPLOAD_PROGRESS # Error log strings (CrowdStrike, Prophet) Allowed memory size of in Unknown on line 0 Out of memory (allocated exited on signal 11 Maximum execution time of
# Patch / config audit (the fix) # Fixed: PHP 8.5.12, 8.4.27, 8.3.36, 8.2.35. Upgrading is the only complete remediation (Amazon, PHP). # 7.4, 8.0, 8.1: minimal source backports only, or a vendor/LTS build. 5.4 to 7.3: no fix. php -v ; php-fpm -v php -i | grep -E '^(memory_limit|session.upload_progress.enabled)' apt changelog php8.3-fpm | grep -iE 'CVE-2026-10961[89]' rpm -q --changelog php-fpm | grep -E 'CVE-2026-10961[89]' # Updating an application does not update the PHP runtime beneath it. # Restart every PHP worker after the update. Rebuild container images from a fixed base. # Interim settings only reduce paths and do not make the host safe (Datadog): see Playbook B.
Detection Validation Gates
| Gate | Check | Pass Criteria |
|---|---|---|
| Telemetry ready | Confirm a Falcon sensor runs on each web server and container host. Confirm process creation with command line, image and parent is populated for the PHP and web server accounts (CrowdStrike: ProcessRollup2. SentinelOne: Process Creation. Defender: DeviceProcessEvents). Test whether executable-written events carry the writer name (CrowdStrike: ElfFileWritten with ContextBaseFileName) and whether inbound connection events exist on your Linux sensor version | Q1 returns rows for a deliberate test (see Positive test); the file and inbound network questions are answered and recorded in the ticket |
| Benign baseline | Run Q1, Q2, Q4, Q5 and Q6 over 30 days on a patched, clean host. Run Q9 for 14 days to learn the normal destinations | Q3 returns zero rows. Q1, Q2, Q4, Q5 and Q6 return only known pairs, which are written into the exclusions |
| Positive test | Lab host only: as the web account, run a harmless shell command such as sh -c 'id' from a PHP test script, and run a command line that contains the literal text dev/tcp against a lab address. Never use a live exploit or a real payload | Q1 fires with parent, user and command line intact; Q3 fires on the marker command |
| Web-tier visibility | Confirm that web server error logs and access or WAF logs reach the SIEM | Logs forwarded, or the gap is documented in the ticket. N1 and N2 are the only checks for the exploit request |
| Promotion | Move Q1 and Q3 to Custom IOA Detect, then Prevent | No false positive for 30 days before Prevent mode |
Hardening: Tiered & Deployable
This is patchable: the PHP runtime upgrade is the fix. Platforms touched: Linux and Windows web servers, PHP-FPM and mod_php configuration, nginx or Apache, host firewall, containers and appliances. Controls cite MITRE mitigations plus a platform source; where no formal benchmark exists the control is flagged. The interim settings reduce specific paths and are not a substitute for the upgrade.
- Upgrade every PHP runtime to 8.5.12, 8.4.27, 8.3.36 or 8.2.35, or the vendor build that carries the fix, then restart every worker (M1051 Update Software; PHP security advisories as relayed by Tenable, Datadog, Help Net Security). Include containers, appliances and bundled interpreters. Check:
php -vplus the package changelog for CVE-2026-109618 and CVE-2026-109619, because vendors backport without changing the version. Playbook A. - Apply the PHP advisory interim settings only where you cannot patch today (M1042 Disable or Remove Feature or Program; Tenable, Prophet).
memory_limit=-1enforced withphp_admin_value,session.upload_progress.enabled=0, and on PHP 8.5fatal_error_backtraces=0, then restart workers. Datadog warns these do not fully protect. Bound memory at the service level becausememory_limit=-1removes the per-request cap. Playbook B. ⚠ best-practice, no formal benchmark - Refuse multipart uploads on endpoints that take none (M1050 Exploit Protection; Amazon and the PHP advisory as relayed by Prophet). Allow-list the real upload URLs at the proxy. Playbook C. Treat as a stopgap: Cloudflare says the same of its own rules and reports actors mutating payloads.
- If a host cannot be patched or fronted, take it offline or isolate it from the internet (M1030 Network Segmentation; UK NCSC and CERT-EU as relayed by Help Net Security and Tenable).
- Hunt from 2026-10-17 on every internet-facing PHP host (M1047 Audit; Amazon). Run N1 to N3, Q1 and Q3 first. Treat a hit as a compromise until ruled out.
- Egress-filter the web account (M1037 Filter Network Traffic). Allow only the database, mail and API destinations the application needs; this stops a second stage fetch and a reverse shell from leaving. Start log-only. Playbook D. Verify:
nft list table inet hp_egress. ⚠ best-practice, no formal benchmark for PHP applications - Sandbox PHP-FPM with systemd and make code read-only to the web account (M1048 Application Isolation and Sandboxing, M1022 Restrict File and Directory Permissions; systemd.exec hardening, CIS Benchmarks for Apache HTTP Server and NGINX for file permission settings). Verify:
systemd-analyze security php8.3-fpm. Playbook E. Pilot: CMS self-update and plugin install will stop working by design. - Mount temp and shared-memory areas noexec (M1038 Execution Prevention). Stops a dropped binary from running out of
/dev/shm. Verify:findmnt -no OPTIONS /dev/shm. Pilot: some installers need exec on /tmp. - Keep SELinux or AppArmor enforcing for web workers (M1048; DISA STIG for Apache Server 2.4 and distribution guidance). Verify:
getenforceoraa-status. A confined web domain turns many shell and file actions into denials you can alert on. - Windows and IIS: deny script host and shell execution to the app pool identity where the application does not need it (M1038 Execution Prevention; Microsoft AppLocker). Deploy in audit mode first and review events before enforcing. Verify:
Get-AppLockerPolicy -Effective | Select-Object -ExpandProperty RuleCollections. ⚠ best-practice, no formal benchmark for PHP on IIS - Forward web, proxy, WAF and PHP error logs to the SIEM (M1047 Audit). The exploit request and the memory-limit fatal are visible only there. Alert on
in Unknown on line 0clusters.
- Measure patch time for internet-facing, critical flaws in hours (M1051; Amazon). A 7 or 30 day SLA assumes a delay that no longer exists, and the first exploitation here preceded the advisory.
- Maintain a PHP inventory that covers runtimes, containers, appliances and end-of-life branches (M1051, M1047; Sonatype and Datadog via The Hacker News and Datadog). Retire PHP 8.1 and older where there is no vendor support.
- Immutable, rebuilt-from-image web tiers with a read-only root filesystem (M1048, M1022). A compromised worker then cannot persist, and recovery is a redeploy.
- Detect without a CVE signature (Amazon). Keep Q1, Q3 and the parent and child baseline running as permanent detections, because the next PHP flaw will also be exploited before signatures exist.
Deployable Playbooks
Every playbook lists prerequisites, whether a reboot is needed and a rollback. Replace the labelled REPLACE_WITH values and test on one host first.
Playbook A · Find every PHP runtime and patch it
# Playbook A. Find every PHP runtime and patch it # Prerequisites: root or sudo, a maintenance window per host, a recent backup or snapshot, vendor package list. # Reboot required: no. Restart PHP-FPM / Apache so old workers stop serving requests. # Step 1. Inventory (host, then containers). See N4 for the full audit. for b in $(command -v php php-fpm php-cgi lsphp 2>/dev/null); do echo "$b $($b -v 2>/dev/null | head -1)"; done # Step 2. Debian / Ubuntu: upgrade only the PHP packages that apt lists as upgradable apt list --upgradable 2>/dev/null | grep -i php sudo apt-get update && sudo apt-get install --only-upgrade $(apt list --upgradable 2>/dev/null | grep -i '^php' | cut -d/ -f1) # Step 2 (RHEL family): sudo dnf upgrade 'php*' # Step 3. Confirm the vendor fix landed (version numbers can stay the same on backports) apt changelog php8.3-fpm 2>/dev/null | grep -iE 'CVE-2026-10961[89]' # or: rpm -q --changelog php-fpm | grep -E 'CVE-2026-10961[89]' # Step 4. Restart every worker (replace the unit names with yours) sudo systemctl restart php8.3-fpm apache2 # Step 5. Containers: rebuild from a fixed base image and redeploy; do not patch a running container in place. # Rollback: reinstalling the previous package version restores the vulnerable runtime. Only do it to recover from a proven # compatibility regression, and only behind Playbook B and C as compensating controls. Prefer fixing the application instead. # Unsupported branches (8.1 and older, 5.4 to 7.3): move to a supported branch or a vendor/LTS build; there is no upstream release.
Playbook B · Interim PHP settings when you cannot patch yet
# Playbook B. Interim PHP settings when you cannot patch yet (reduces specific paths, does NOT make the host safe) # Source: PHP advisory settings as relayed by Tenable (02), Datadog (06), Prophet (07). Datadog warns these do not fully protect. # Prerequisites: root, config management or a config backup, ability to restart workers, a cgroup/container memory cap in place first. # Reboot required: no. Restart every PHP worker after the change. # Step 1. Back up sudo cp -a /etc/php /etc/php.bak-2026-10-22 # Step 2. PHP-FPM pool file (for example /etc/php/8.3/fpm/pool.d/www.conf). php_admin_* cannot be overridden by the application. php_admin_value[memory_limit] = -1 php_admin_flag[session.upload_progress.enabled] = off php_admin_value[post_max_size] = 8M ; PHP 8.5 only: php_admin_value[fatal_error_backtraces] = 0 ; Optional, if performance allows: pm.max_requests = 1 (recycles each worker after one request) # Apache mod_php equivalent, in the vhost or httpd.conf: php_admin_value memory_limit -1 php_admin_flag session.upload_progress.enabled off # Step 3. memory_limit=-1 removes the per-request ceiling, so bound memory at the service level BEFORE restarting: # /etc/systemd/system/php8.3-fpm.service.d/memory.conf [Service] MemoryMax=2G # Step 4. Apply and verify sudo systemctl daemon-reload && sudo systemctl restart php8.3-fpm php-fpm -tt 2>&1 | grep -E 'memory_limit|upload_progress' # Rollback: restore the saved config (sudo rm -rf /etc/php && sudo cp -a /etc/php.bak-2026-10-22 /etc/php), remove memory.conf, # daemon-reload, restart PHP-FPM. Applications that upload files with progress bars lose the progress feature while the flag is off.
Playbook C · nginx: refuse multipart on non-upload endpoints
# Playbook C. nginx: refuse multipart/form-data on endpoints that never take uploads
# Source: Amazon (01) and the PHP advisory (via Prophet 07) recommend blocking multipart where uploads are not needed.
# Prerequisites: a list of the real upload URLs; nginx 1.x; a staging vhost to test the allow-list.
# Reboot required: no (nginx reload).
# Step 1. In the http block: allow-list the upload paths (replace the placeholder with your own)
map $request_uri $hp_upload_ok {
default 0;
~^/REPLACE_WITH_YOUR_UPLOAD_PATH 1;
}
map "$content_type:$hp_upload_ok" $hp_block_multipart {
default 0;
"~*^multipart/form-data.*:0$" 1;
}
# Step 2. In each server block that fronts PHP
if ($hp_block_multipart) { return 403; }
# Step 3. Test, then reload
sudo nginx -t && sudo nginx -s reload
# Rollback: delete the two maps and the if line, then sudo nginx -t && sudo nginx -s reload.
# Caveat: this stops many exploit shapes at the proxy but is a stopgap, as Cloudflare says of its own WAF rules. Attackers mutate payloads (Cloudflare, via The Hacker News 04).
Playbook D · Egress control for the web account
# Playbook D. Egress control for the web account (Linux, nftables). Start in log-only mode.
# Prerequisites: nftables, the web account name, the list of destinations the application really needs (database, mail, APIs).
# Reboot required: no. Rules are runtime only until you persist them in your nftables config.
# Step 1. Create the table and chain (policy accept, so nothing breaks yet)
sudo nft add table inet hp_egress
sudo nft add chain inet hp_egress out '{ type filter hook output priority 0; policy accept; }'
# Step 2. Always allow loopback and your internal ranges for the web account
sudo nft add rule inet hp_egress out meta skuid "www-data" ip daddr '{ 127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 }' accept
# Step 3. Allow only the external destinations you list (replace the placeholder), then LOG everything else for 7 days
sudo nft add rule inet hp_egress out meta skuid "www-data" ip daddr '{ REPLACE_WITH_ALLOWED_API_CIDRS }' accept
sudo nft add rule inet hp_egress out meta skuid "www-data" log prefix '"hp-egress "'
# Step 4. After review of the log, change the last rule to drop: ... meta skuid "www-data" drop
# Verify: sudo nft list table inet hp_egress ; journalctl -k | grep hp-egress
# Rollback: sudo nft delete table inet hp_egress
# Why: Q2, Q3 and Q9 show what the web account reaches; this control stops a second stage fetch or reverse shell from leaving.
Playbook E · Sandbox PHP-FPM and make code read-only
# Playbook E. Confine the PHP-FPM service with systemd sandboxing and lock down the web root
# Prerequisites: systemd; know which directories the application must write (uploads, cache, sessions).
# Reboot required: no (service restart). Pilot on one host first.
# Step 1. Drop-in for the service (replace the unit name and writable path)
# /etc/systemd/system/php8.3-fpm.service.d/hardening.conf
[Service]
NoNewPrivileges=yes
ProtectSystem=full
ProtectHome=yes
PrivateTmp=yes
MemoryMax=2G
ReadWritePaths=/var/www/REPLACE_WITH_APP_WRITABLE_DIR /run/php
# Step 2. Make code read-only to the web account (code owned by root; only data folders writable)
sudo chown -R root:www-data /var/www/html
sudo find /var/www/html -type d -exec chmod 750 {} + ; sudo find /var/www/html -type f -exec chmod 640 {} +
sudo chown -R www-data:www-data /var/www/REPLACE_WITH_APP_WRITABLE_DIR
# Step 3. Temp areas: no execution of dropped binaries (add to /etc/fstab, then remount)
# tmpfs /dev/shm tmpfs defaults,noexec,nosuid,nodev 0 0
sudo mount -o remount,noexec,nosuid,nodev /dev/shm
# Step 4. Apply and score
sudo systemctl daemon-reload && sudo systemctl restart php8.3-fpm ; systemd-analyze security php8.3-fpm
# Rollback: remove hardening.conf, daemon-reload, restart the service; restore previous ownership from your config management or a saved
# 'getfacl -R /var/www > /root/www.acl' taken before Step 2 (setfacl --restore); remount /dev/shm with exec and drop the fstab line.
# Caveat: CMS self-update, plugin install and uploads that write PHP stop working by design. Update applications from the shell in a window.
Containment Runbook
| Phase | Actions | Owner | Evidence |
|---|---|---|---|
| Isolate | Network-contain the host in Falcon on a confirmed Q1, Q3 or Q6 hit, or on a web shell found by N3. Do not power off, to preserve volatile state. Block the host from reaching other internal systems. If it is a container, cordon the node and stop routing to the pod | SOC / IR | Containment timestamp, host and sensor ID |
| Isolate: by platform | CrowdStrike: network-contain the host. SentinelOne: network quarantine (Disconnect from Network; confirm the label in your console version). Microsoft Defender: Isolate device (full network isolation). | SOC / IR | Containment timestamp, host and sensor ID |
| Preserve | Copy web server access and error logs and PHP and PHP-FPM logs from 2026-10-17 onward before rotation, the file listing from N3, ps and network connection output, the web account crontab and any file found. Hash everything. Note that a successful exploit may have left only a memory-limit fatal in the error log | IR | Evidence bundle with hashes |
| Scope | Run Q1 to Q10 fleet-wide and N1 to N5 on every host that ran an unpatched PHP runtime after 2026-10-17. Pivot from any hit on the same host to the others. Look for web shells, new cron jobs, SSH keys and accounts, and outbound connections new since 2026-10-17. Decide which secrets the web account could read | IR / Threat Hunting | Scope list, hit and clean host tables |
| Eradicate | Rebuild the host or container from a patched image rather than cleaning in place (Prophet and Check Point both advise a rebuild). If a rebuild is not possible, patch (Playbook A), restart every worker, remove every dropped file and persistence item, and re-run Q1 to Q9 for 30 days | Sysadmin | Patched version evidence, N3 empty, hunts clean |
| Recover | Rotate every credential the web account could read: database passwords in application configuration, API keys, tokens, TLS private keys and session secrets. Because memory disclosure needs no code execution, rotate on any host that ran unpatched and was internet-reachable after 2026-10-17, even with no hit. Force logout of sessions | Sysadmin / Identity | Rotation tickets with dates |
| Improve | Apply the near-term hardening in section 12, forward web and PHP error logs to the SIEM, promote Q1 and Q3 to Custom IOA, and build the PHP inventory | Security engineering | Change records |
Detection Coverage Map
| Technique | Behavior | CQL | IOA | CrowdStrike coverage | SentinelOne | Defender XDR |
|---|---|---|---|---|---|---|
| T1190 | Exploit request (crafted POST, multipart, upload-progress callback) | n/a (N1, N2) | n/a | GAP web-tier. Outside endpoint telemetry. Visible only in access, WAF and PHP error logs (N1, N2) | GAP | GAP |
| T1190 | Upload-progress marker or scanner string on a host command line | Q10 | n/a | Partial only tools on the host itself; the HTTP form field is not visible | Partial: same limit as CrowdStrike | Partial: same limit as CrowdStrike |
| T1059.004 / T1059.003 / T1059.001 | Web server or PHP worker starts a shell | Q1 | IOA-1 | Good needs a sensor on the web server; IIS app pools and container hosts need validation | Good, with assumptions | Good, with assumptions |
| T1059.004 / T1095 | Reverse shell command line from the web account | Q3 | IOA-2 | Good catches the common constructs; novel or in-memory shells are missed | Good, with assumptions | Good, with assumptions |
| T1105 / T1059.006 | Download tools and interpreters started by the web account | Q2 | n/a | Partial FP from cache warmers and updaters | Partial: same limit as CrowdStrike | Partial: same limit as CrowdStrike |
| T1105 / T1505.003 | Executable written by a PHP or web server process | Q7 | n/a | Partial depends on the sensor emitting the event with the writer name; validate in tenant | Partial: Q7 approximate | Partial: Q7 approximate |
| T1505.003 | PHP web shell file created in a web path | Q6 (command line only) | n/a | GAP as a file event. A PHP file written by the PHP process itself, with no shell, is not reliably logged; N3 and file integrity monitoring are the reliable checks. Q6 sees only writes made through a command | GAP: same limit as CrowdStrike | GAP: same limit as CrowdStrike |
| T1033 / T1082 / T1016 / T1552.001 | Discovery and credential file reads by the web account | Q4 | n/a | Partial only when run as a process; file reads made by PHP itself are not visible | Partial: same limit as CrowdStrike | Partial: same limit as CrowdStrike |
| T1053.003 / T1098.004 / T1136.001 / T1548.003 | Persistence and account changes by the web account | Q5 | n/a | Partial process-run changes only; N5 covers the on-disk result | Partial: same limit as CrowdStrike | Partial: same limit as CrowdStrike |
| T1071.001 / T1190 | Known source IPs; rare outbound destinations from workers | Q8, Q9 | n/a | Partial shared and perishable infrastructure; inbound connection events vary by sensor; an exploit through a CDN shows the CDN address, not the attacker | Partial: same limit as CrowdStrike | Partial: same limit as CrowdStrike |
| T1005 / T1552 | Memory disclosure of secrets with no code execution | n/a | n/a | GAP no process or file activity to see. Mitigated only by patching, restarting workers and rotating secrets | GAP | GAP |
| T1041 / T1048 | Data staging and exfiltration | n/a | n/a | GAP no staging or upload step is described in any source, so there is nothing sourced to detect beyond Q9 | GAP | GAP |
Validation gates: (1) a sensor is installed on each web server and process telemetry carries parent, user and command line; (2) Q3 returns zero and Q1, Q2, Q4, Q5, Q6 return only baselined pairs in a 30 day clean window; (3) executable-written and inbound network event availability is tested and recorded; (4) a lab shell command fires Q1 and a marker command fires Q3, with no live payload; (5) web and PHP error logs are forwarded or the gap is documented.
Hunt Summary Ticket
TITLE: Hunt - PHP2Shell CVE-2026-109618 and CVE-2026-109619 (unauthenticated PHP RCE, exploited from 2026-10-17)
SEVERITY: Critical (CVSS v4 9.2; remote, unauthenticated; exploited in the wild before disclosure)
SCOPE: Every host, container and appliance that runs a PHP runtime serving web requests below 8.5.12, 8.4.27, 8.3.36 or 8.2.35
(PHP-FPM, mod_php, php-cgi, FrankenPHP, LiteSpeed; IIS with php-cgi). 8.1 and older have no official fix
HYPOTHESIS: An unauthenticated POST forces a PHP startup-phase fault and runs a command as the web account; the operator then starts
a shell, fetches a second stage or opens a reverse shell, harvests configuration secrets and may leave a web shell,
cron job or SSH key. Several actors exploit it; none is attributed. Memory disclosure may leak secrets with no process activity.
QUERIES RUN: Q1 web parent spawns shell (IOA) | Q2 download tools and interpreters | Q3 reverse shell (IOA) | Q4 discovery and
credential reads | Q5 persistence | Q6 script or payload write by command | Q7 executable written | Q8 nine source IPs |
Q9 rare worker egress | Q10 upload-progress marker | Native N1 to N5
DO FIRST: Upgrade PHP and restart every worker (Playbook A); hunt back to 2026-10-17: N1, N2, N3, then Q1 and Q3; where patching must wait,
apply Playbooks B and C as stopgaps; rotate secrets readable by the web account on any host that ran unpatched
FINDINGS: <pending analyst execution>
GAPS: The exploit request is outside endpoint telemetry (needs web, proxy, WAF or PHP error logs in the SIEM). PHP file creation by the PHP
process itself is not reliably logged. Memory disclosure leaves no process activity. The nine source IPs are shared infrastructure
ACTIONS: Patch or isolate; forward web and PHP error logs; egress-filter and sandbox the web account; rotate secrets; promote Q1 and Q3
to Custom IOA; build a PHP inventory that includes containers and appliances
OWNER: HuntPack
VERSION: v0.1 - 2026-10-22
Changelog
References
| Tier | Source | Used For | Access Date |
|---|---|---|---|
| 1 | Amazon Threat Intelligence: PHP2Shell exploitation prior to public disclosure in MadPot | Timeline, nine-IP source list, upload-progress hint, patch-in-hours guidance | 2026-10-22 |
| 2 | Tenable: PHP2Shell FAQ (Satnam Narang) | Fixed and affected versions, PHP interim settings, Cloudflare and watchTowr context | 2026-10-22 |
| 2 | Help Net Security: PHP2Shell critical PHP vulnerabilities patched | Patch detail, affected runtimes, appliance impact update | 2026-10-22 |
| 2 | The Hacker News: Someone Tried to Exploit PHP2Shell 4 Days Before Disclosure | Timeline, VulnCheck IPs and user agent, FBI guidance, ELLIO first request | 2026-10-22 |
| 1 | CrowdStrike: Protects Customers from Threats Delivered via PHP2Shell | Root cause, verified shell launch, error-log signature, Falcon coverage | 2026-10-22 |
| 2 | Datadog Security Labs: PHP2Shell detection, analysis and remediation | Independent reproduction, warning that interim settings do not fully protect | 2026-10-22 |
| 2 | Prophet Security: Detecting and investigating CVE-2026-109618 and CVE-2026-109619 | Behavior hunt checklist, log strings, mitigation settings | 2026-10-22 |
| 2 | SOCRadar: PHP2Shell CVE-2026-109618 and CVE-2026-109619 | Process-spawn detection guidance | 2026-10-22 |
| 2 | Check Point: PHP2Shell what we know | Pre-release hunting and response steps | 2026-10-22 |
| 2 | Abstract Security: PHP2Shell critical PHP RCE vulnerabilities | Web-tier rule shapes and the upload-progress correlate | 2026-10-22 |
| 2 | php2shell.com research site | Discoverers, memory disclosure impact, demonstrated targets | 2026-10-22 |