PHP2Shell: CVE-2026-109618 and CVE-2026-109619 Unauthenticated PHP Remote Code Execution

Two critical memory-safety flaws in the PHP engine itself (CVSS v4 9.2) let an unauthenticated HTTP request run commands as the web server account, before any application code executes. Amazon saw the first exploitation attempt on 2026-10-17, four days before disclosure, and exploitation became broad from 2026-10-21. The fix is a PHP runtime upgrade. The request is nearly invisible on the server, so this pack hunts what the web account does next.
Threat
PHP2Shell: CVE-2026-109618 (OOM corruption) and CVE-2026-109619 (stale global state), multiple unattributed actors
Severity
EXPLOITED ITW · CVSS v4 9.2 · UNAUTH RCE
Type
Runtime memory corruption, web server side RCE, post-exploitation shell and web shell
Access
Network, no credentials, no user interaction; any reachable PHP endpoint
Fixed in
PHP 8.5.12, 8.4.27, 8.3.36, 8.2.35
Timeline
First attempt 2026-10-17 · CVE IDs leaked 2026-10-20 · fixes 2026-10-21
Version
v0.1 · 2026-10-22
Author
HuntPack
Confidence
High on the flaw, fixes and post-exploitation behavior; low on actors and on the nine source IPs
01

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.

⚠ Defender priority: (1) Upgrade every PHP runtime, including those inside containers and appliance images, then restart every worker (section 13, Playbook A). Updating an application does not update the PHP beneath it. (2) Investigate from 2026-10-17, not from the disclosure date: run Q1, Q3 and N1 to N3 on every internet-facing PHP host. (3) Where you cannot patch yet, apply the interim settings and the multipart restriction as stopgaps only (Playbooks B and C), and consider taking the host offline as the UK NCSC advises. (4) On any host that ran unpatched, treat secrets readable by the web account as exposed and rotate them.
02

Source Review & Web Hunter Notes

TierSourceKey FindingCarry
1 · PrimaryAmazon 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 · VendorTenable: 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 · NewsHelp 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 · NewsThe 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 · VendorCrowdStrike: 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 · VendorDatadog 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 · VendorProphet 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 · VendorSOCRadar: 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 · VendorCheck 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 · VendorAbstract 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 · Researchphp2shell.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.

03

Hunt Brief & Attack Chain

Hypotheses (ordered by fidelity)

#HypothesisMITRETelemetryExpected FPConf
H1After a successful exploit, a PHP or web server process starts a shell as its childT1190, T1059.004, T1059.003, T1059.001Process creation with command line, image and parent (CrowdStrike: ProcessRollup2. SentinelOne: Process Creation. Defender: DeviceProcessEvents)PHP mail() via sh, converters, deploy hookshigh
H2The operator opens a reverse shell from the web accountT1059.004, T1095Process creation with command lineAuthorized lab validationhigh
H3The web account fetches a second stage or starts an interpreter or dropper binaryT1105, T1059.006Process creation; executable or file written (CrowdStrike: ElfFileWritten, NewExecutableWritten, PeFileWritten)Cache warmers, updaters, PECL buildsmedium
H4The web account runs discovery and reads application and cloud secretsT1033, T1082, T1016, T1552.001Process creation with command lineMonitoring and backup scriptsmedium
H5The web account writes a PHP script or payload to diskT1505.003, T1140Process creation with command line (file creation of .php is not reliably logged)Deploy scripts, plugin installersmedium
H6The web account sets up persistence or new accountsT1053.003, T1098.004, T1136.001, T1548.003Process creation with command lineDeploy toolingmedium
H7A web server talks to the reported source IPs, or a worker reaches new external destinationsT1190, T1071.001Network connection with owning process (CrowdStrike: NetworkConnectIP4, NetworkReceiveAcceptIP4)Shared anonymization infrastructure; normal API callslow
H8A tool on the host carries the upload-progress marker or scanner stringT1190, T1595.002Process creation with command lineAuthorized scanners and lab testslow

Attack chain

StepBehaviorTelemetryHunt angle
1Exploit 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 logWeb, proxy and WAF logs and the PHP error log. Outside endpoint telemetryN1, N2
2Command execution as the web account: the worker starts a shellProcess creation with command line, image and parent (CrowdStrike: ProcessRollup2. SentinelOne: Process Creation. Defender: DeviceProcessEvents)Q1, Q3
3Tooling and second stage: download tools, interpreters, dropped binariesProcess creation; executable written (CrowdStrike: ElfFileWritten, NewExecutableWritten, PeFileWritten); outbound network connection (CrowdStrike: NetworkConnectIP4)Q2, Q7, Q9
4Discovery and credential harvesting from configuration filesProcess creation with command lineQ4
5Web shell and persistence: PHP file drop, cron, SSH keys, new accountsProcess creation with command line; file listing on the hostQ5, Q6, N3, N5
6Memory disclosure only: worker memory read without code executionNone. No child process and no writeNot huntable. Patch, restart workers and rotate secrets
04

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.

TypeValueConfidenceActionContext (source snapshot)Expiry
IPv4103.216.220.92mediumdetectAmazon 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
IPv431.223.184.166mediumdetectAmazon MadPot: exploitation attempt 2026-10-21 (AWS 01, THN 04)2027-01-22
IPv437.19.200.5mediumdetectAmazon MadPot: exploitation attempt 2026-10-21 (AWS 01, THN 04)2027-01-22
IPv4134.209.159.64mediumdetectAmazon MadPot: exploitation attempt 2026-10-21 (AWS 01, THN 04)2027-01-22
IPv4103.114.163.188mediumdetectAmazon MadPot: exploitation attempt 2026-10-21 (AWS 01, THN 04)2027-01-22
IPv495.217.98.37mediumdetectAmazon MadPot: exploitation attempt 2026-10-21 (AWS 01, THN 04)2027-01-22
IPv485.11.167.243lowdetectVulnCheck honeypots: user agent php2shell-scan/109618 targeting CVE-2026-109618 (reported by The Hacker News only) (THN 04)2027-01-22
IPv445.198.224.169lowdetectVulnCheck honeypots: user agent php2shell-scan/109618 targeting CVE-2026-109618 (reported by The Hacker News only) (THN 04)2027-01-22
IPv4109.71.252.97lowdetectVulnCheck 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).

05

Affected Surface & Telemetry Matrix

SurfaceTelemetryPriorityGap
Linux web servers: PHP-FPM, Apache mod_php, php-cgi, FrankenPHP, LiteSpeedProcess 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)criticalExploit 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 stacksProcess 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)criticalRequest body outside endpoint telemetry. IIS app pool account names vary, so queries key on the parent process
Containers and images that bundle their own PHPHost sensor sees container processes on a supported Linux host; image inventory from your registry scannercriticalEphemeral 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 logscriticalNo 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 trafficVersion inventorycriticalNo 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 PHPAccess, WAF and proxy logshighCloudflare WAF rules are a stopgap that covers only traffic routed through it, and actors mutate payloads. Outside endpoint telemetry
06

ATT&CK Mapping

TacticTechniqueObserved or expected behaviorQuery / Control
Initial AccessT1190 Exploit Public-Facing ApplicationUnauthenticated POST forcing the out-of-memory or fatal-error path; exploitation from 2026-10-17N1, N2, Q8, Q10; patch (M1051)
ExecutionT1059.004 Unix Shell; T1059.003 Windows Command Shell; T1059.001 PowerShellWorker starts sh, bash, cmd.exe or powershell.exe with the attacker's command (CrowdStrike verified)Q1, Q3
ExecutionT1059.006 PythonScripted interpreters and socket shells launched by the web accountQ2, Q3
PersistenceT1505.003 Web ShellPHP file dropped into a web-served path after the foothold (CrowdStrike names web shell deployment)Q6, N3
PersistenceT1053.003 Cron; T1098.004 SSH Authorized Keys; T1136.001 Local AccountUser cron jobs, SSH keys and new accounts owned by the web user (Prophet, Check Point)Q5, N5
Privilege EscalationT1548.003 Sudo and Sudo CachingAttempts to use sudo or su from the web accountQ5
Defense EvasionT1140 Deobfuscate/Decode Files or Informationbase64 decode to a shell or fileQ6
DiscoveryT1033, T1082, T1016, T1083whoami, uname, interface and file discovery from the web accountQ4
Credential AccessT1552.001 Credentials In FilesHarvest of database and API credentials from configuration files (CrowdStrike)Q4, N5
Command and ControlT1105 Ingress Tool Transfer; T1071.001 Web Protocols; T1095 Non-Application Layer Protocolcurl or wget fetch of a second stage; reverse shell connectionQ2, Q3, Q7, Q8, Q9
ReconnaissanceT1595.002 Vulnerability ScanningScanner user agent php2shell-scan/109618 against honeypotsN2, Q10
07

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.
08

Hunt Queries: CrowdStrike CQL, SentinelOne PowerQuery and Defender XDR KQL

Show all queries in
Pick your tenant's cloud first: every "Open in Falcon" button below uses this selection.
⚠ Read this before running anything: none of these queries sees the exploit request. It is an HTTP body and is not in endpoint process telemetry. The queries find what the web account does after a successful exploit. They need a Falcon sensor on the web server. Set the time picker to 30 days (the first reported exploitation was 2026-10-17). Validate every query in your tenant, and baseline Q1, Q2, Q4, Q5 and Q6 on a clean host before alerting.
Q1 · Web server or PHP worker spawns a shell
CONF HIGHFP MEDIUMCOST LOW

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.

Validation: schema-aligned, static-reviewedAutomated: static-passedEquivalence: equivalent-with-schema-assumptionsProfiles: microsoft-defender-xdr, sentinelone-endpoint-ocsf
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)
Q2 · Web account runs a download tool or script interpreter
CONF MEDIUMFP MEDIUMCOST LOW

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.

Validation: schema-aligned, static-reviewedAutomated: static-passedEquivalence: equivalent-with-schema-assumptionsProfiles: microsoft-defender-xdr, sentinelone-endpoint-ocsf
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)
Q3 · Reverse shell command line from a web account
CONF HIGHFP LOWCOST LOW

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.

Validation: schema-aligned, static-reviewedAutomated: static-passedEquivalence: equivalent-with-schema-assumptionsProfiles: microsoft-defender-xdr, sentinelone-endpoint-ocsf
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)
Q4 · Web account reconnaissance and credential file reads
CONF MEDIUMFP MEDIUMCOST LOW

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.

Validation: schema-aligned, static-reviewedAutomated: static-passedEquivalence: equivalent-with-schema-assumptionsProfiles: microsoft-defender-xdr, sentinelone-endpoint-ocsf
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)
Q5 · Web account persistence and account changes
CONF MEDIUMFP MEDIUMCOST LOW

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.

Validation: schema-aligned, static-reviewedAutomated: static-passedEquivalence: equivalent-with-schema-assumptionsProfiles: microsoft-defender-xdr, sentinelone-endpoint-ocsf
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)
Q6 · Web account writes a script or payload from the command line
CONF MEDIUMFP MEDIUMCOST LOW

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.

Validation: schema-aligned, static-reviewedAutomated: static-passedEquivalence: equivalent-with-schema-assumptionsProfiles: microsoft-defender-xdr, sentinelone-endpoint-ocsf
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)
Q7 · Executable written by a web server or PHP worker
CONF MEDIUMFP MEDIUMCOST LOW

Looks 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.

Validation: schema-aligned, static-reviewedAutomated: static-passedEquivalence: approximate-analyticProfiles: microsoft-defender-xdr, sentinelone-endpoint-ocsf
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)
Q8 · Connections to or from the reported PHP2Shell source IPs
CONF LOWFP MEDIUMCOST LOW

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.

Validation: schema-aligned, static-reviewedAutomated: static-passedEquivalence: equivalent-with-schema-assumptionsProfiles: microsoft-defender-xdr, sentinelone-endpoint-ocsf
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)
Q9 · Rare outbound destinations from PHP and web workers
CONF LOWFP MEDIUMCOST MEDIUM

Looks 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.

Validation: schema-aligned, static-reviewedAutomated: static-passedEquivalence: equivalent-with-schema-assumptionsProfiles: microsoft-defender-xdr, sentinelone-endpoint-ocsf
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)
Q10 · Upload-progress marker or PHP2Shell scanner string on a command line
CONF LOWFP MEDIUMCOST LOW

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.

Validation: schema-aligned, static-reviewedAutomated: static-passedEquivalence: equivalent-with-schema-assumptionsProfiles: microsoft-defender-xdr, sentinelone-endpoint-ocsf
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)
09

Detection Rules by Platform

CrowdStrike: Custom IOA

IOA nameField PatternsBenign ExclusionsDeployment Path
Web server or PHP worker spawns a shell. Promote Q1Platform 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.exeKnown parent and command line pairs found in the 30 day baseline (PHP mail() sendmail, named converters, deploy hooks). Do not exclude by parent aloneCustom 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 Q3Platform 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|nobodyAuthorized penetration tests and lab validation hosts onlyCustom IOA → Process Creation. Detect, escalate every hit, then Prevent after one clean month
Q1 and Q3 are the two IOA promotions: a PHP worker starting a shell and a reverse shell command line have no routine reason to exist once baselined. Q2, Q4, Q5, Q6, Q7, Q8, Q9 and Q10 stay hunt or investigate only because they depend on a baseline, on a sensor event whose fields vary by tenant, or on perishable shared infrastructure. No IOA is built on the source IPs.

SentinelOne: custom detection rules

RuleSource queryRun asNotes
Web server or PHP worker spawns a shell. Promote Q1Q1, PowerQuery tabScheduled custom detection ruleBind 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 Q3Q3, PowerQuery tabScheduled custom detection ruleBind 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

RuleSource queryRun asNotes
Web server or PHP worker spawns a shell. Promote Q1Q1, KQL – Defender XDR tabCustom detection rule, alert only, every hourThe 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 Q3Q3, KQL – Defender XDR tabCustom detection rule, alert only, every hourThe 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.

10

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.

Falcon IOC Management CSVbulk import
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
Microsoft Defender indicators CSVSettings, Endpoints, Indicators, Import: audit only
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).

SentinelOne IOC importnot included

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 Signaturesdetection logic
# 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)
Hunt Values (strings)hunt only
# 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 Auditthe fix
# 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.
11

Detection Validation Gates

GateCheckPass Criteria
Telemetry readyConfirm 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 versionQ1 returns rows for a deliberate test (see Positive test); the file and inbound network questions are answered and recorded in the ticket
Benign baselineRun Q1, Q2, Q4, Q5 and Q6 over 30 days on a patched, clean host. Run Q9 for 14 days to learn the normal destinationsQ3 returns zero rows. Q1, Q2, Q4, Q5 and Q6 return only known pairs, which are written into the exclusions
Positive testLab 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 payloadQ1 fires with parent, user and command line intact; Q3 fires on the marker command
Web-tier visibilityConfirm that web server error logs and access or WAF logs reach the SIEMLogs forwarded, or the gap is documented in the ticket. N1 and N2 are the only checks for the exploit request
PromotionMove Q1 and Q3 to Custom IOA Detect, then PreventNo false positive for 30 days before Prevent mode
12

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.

Immediate: patch, restart, find and investigate
  • 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 -v plus 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=-1 enforced with php_admin_value, session.upload_progress.enabled=0, and on PHP 8.5 fatal_error_backtraces=0, then restart workers. Datadog warns these do not fully protect. Bound memory at the service level because memory_limit=-1 removes 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.
Near term: shrink what the web account can do (pilot first)
  • 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: getenforce or aa-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 0 clusters.
Strategic: shorten the patch window and remove the exposure class
  • 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.
13

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.
14

Containment Runbook

PhaseActionsOwnerEvidence
IsolateNetwork-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 podSOC / IRContainment timestamp, host and sensor ID
Isolate: by platformCrowdStrike: 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 / IRContainment timestamp, host and sensor ID
PreserveCopy 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 logIREvidence bundle with hashes
ScopeRun 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 readIR / Threat HuntingScope list, hit and clean host tables
EradicateRebuild 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 daysSysadminPatched version evidence, N3 empty, hunts clean
RecoverRotate 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 sessionsSysadmin / IdentityRotation tickets with dates
ImproveApply 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 inventorySecurity engineeringChange records
15

Detection Coverage Map

⚠ What endpoint telemetry cannot see here: the exploit request. The HTTP body is not in endpoint process telemetry, and CrowdStrike reports the attack leaves HTTP 200 responses, stable workers and clean application logs. The CQL in this pack does not detect the exploit; it detects the web account's behavior after it. A successful memory disclosure, or in-process PHP code that never starts a child process, produces nothing for these queries to see.
TechniqueBehaviorCQLIOACrowdStrike coverageSentinelOneDefender XDR
T1190Exploit request (crafted POST, multipart, upload-progress callback)n/a (N1, N2)n/aGAP web-tier. Outside endpoint telemetry. Visible only in access, WAF and PHP error logs (N1, N2)GAPGAP
T1190Upload-progress marker or scanner string on a host command lineQ10n/aPartial only tools on the host itself; the HTTP form field is not visiblePartial: same limit as CrowdStrikePartial: same limit as CrowdStrike
T1059.004 / T1059.003 / T1059.001Web server or PHP worker starts a shellQ1IOA-1Good needs a sensor on the web server; IIS app pools and container hosts need validationGood, with assumptionsGood, with assumptions
T1059.004 / T1095Reverse shell command line from the web accountQ3IOA-2Good catches the common constructs; novel or in-memory shells are missedGood, with assumptionsGood, with assumptions
T1105 / T1059.006Download tools and interpreters started by the web accountQ2n/aPartial FP from cache warmers and updatersPartial: same limit as CrowdStrikePartial: same limit as CrowdStrike
T1105 / T1505.003Executable written by a PHP or web server processQ7n/aPartial depends on the sensor emitting the event with the writer name; validate in tenantPartial: Q7 approximatePartial: Q7 approximate
T1505.003PHP web shell file created in a web pathQ6 (command line only)n/aGAP 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 commandGAP: same limit as CrowdStrikeGAP: same limit as CrowdStrike
T1033 / T1082 / T1016 / T1552.001Discovery and credential file reads by the web accountQ4n/aPartial only when run as a process; file reads made by PHP itself are not visiblePartial: same limit as CrowdStrikePartial: same limit as CrowdStrike
T1053.003 / T1098.004 / T1136.001 / T1548.003Persistence and account changes by the web accountQ5n/aPartial process-run changes only; N5 covers the on-disk resultPartial: same limit as CrowdStrikePartial: same limit as CrowdStrike
T1071.001 / T1190Known source IPs; rare outbound destinations from workersQ8, Q9n/aPartial shared and perishable infrastructure; inbound connection events vary by sensor; an exploit through a CDN shows the CDN address, not the attackerPartial: same limit as CrowdStrikePartial: same limit as CrowdStrike
T1005 / T1552Memory disclosure of secrets with no code executionn/an/aGAP no process or file activity to see. Mitigated only by patching, restarting workers and rotating secretsGAPGAP
T1041 / T1048Data staging and exfiltrationn/an/aGAP no staging or upload step is described in any source, so there is nothing sourced to detect beyond Q9GAPGAP

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.

16

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
17

Changelog

v0.12026-10-22Initial pack. Covers CVE-2026-109618 and CVE-2026-109619 (first exploitation attempt 2026-10-17, disclosure and fixes 2026-10-21; fixed in PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35). 10 CQL hunts, 5 native hunts, 2 IOA candidates, 9 atomic indicators (all IPv4, all detect, none prevent) and 2 labelled placeholders. Built around web server to shell behavior because the exploit request is outside endpoint telemetry. Eleven source snapshots saved. SentinelOne PowerQuery and Microsoft Defender KQL tabs added to Q1-Q10: SIEM Query Rosetta release 0.1.0 (public-beta; static-reviewed translations).
18

References

TierSourceUsed ForAccess Date
1Amazon Threat Intelligence: PHP2Shell exploitation prior to public disclosure in MadPotTimeline, nine-IP source list, upload-progress hint, patch-in-hours guidance2026-10-22
2Tenable: PHP2Shell FAQ (Satnam Narang)Fixed and affected versions, PHP interim settings, Cloudflare and watchTowr context2026-10-22
2Help Net Security: PHP2Shell critical PHP vulnerabilities patchedPatch detail, affected runtimes, appliance impact update2026-10-22
2The Hacker News: Someone Tried to Exploit PHP2Shell 4 Days Before DisclosureTimeline, VulnCheck IPs and user agent, FBI guidance, ELLIO first request2026-10-22
1CrowdStrike: Protects Customers from Threats Delivered via PHP2ShellRoot cause, verified shell launch, error-log signature, Falcon coverage2026-10-22
2Datadog Security Labs: PHP2Shell detection, analysis and remediationIndependent reproduction, warning that interim settings do not fully protect2026-10-22
2Prophet Security: Detecting and investigating CVE-2026-109618 and CVE-2026-109619Behavior hunt checklist, log strings, mitigation settings2026-10-22
2SOCRadar: PHP2Shell CVE-2026-109618 and CVE-2026-109619Process-spawn detection guidance2026-10-22
2Check Point: PHP2Shell what we knowPre-release hunting and response steps2026-10-22
2Abstract Security: PHP2Shell critical PHP RCE vulnerabilitiesWeb-tier rule shapes and the upload-progress correlate2026-10-22
2php2shell.com research siteDiscoverers, memory disclosure impact, demonstrated targets2026-10-22