Post-RCE Persistence and Credential Theft
An attacker has exploited a PHP core vulnerability (CVE-2026-109618/109619) and is using the web worker context to harvest credentials or establish persistence via reverse shells and web shells.
Based on research by Elastic Security Labs 2026-10-22 9 steps · 3 queries T1053.003 T1071.001 T1190 T1505.003 T1552.001 T1552.005
Brief
Why this hunt?
Elastic Security Labs recently published an analysis of CVE-2026-109618 and CVE-2026-109619, titled "php2shell: Assessing Elastic detection coverage relevant to CVE-2026-109618 and CVE-2026-109619" (https://www.elastic.co/security-labs/threat-command/php2shell-php-rce-detection). These vulnerabilities allow unauthenticated remote code execution in the PHP core. While many detections focus on the initial HTTP request, network-level patterns are often obfuscated or missed by generic signatures. This hunt focuses on the post-exploitation outcomes where the attacker moves from the web request into the operating system.
How the Hunt Flows
The first phase identifies the scope of the hunt using the hb_software_inventory surface. The query searches for hosts running PHP, Apache, or Nginx. This step ensures the hunt focuses on assets capable of being exploited by the php2shell vulnerabilities and reduces the processing load on the rest of the estate.
Once the assets are identified, the hunt performs a parallel fan-out to examine two distinct types of malicious behavior. The first branch analyzes the hb_file_activity surface. It looks for web worker processes or their immediate children accessing sensitive files like .env, AWS credentials, or the system shadow file. It also identifies the creation of new PHP or phtml files on the web root, which often indicates the staging of a web shell. The query uses a prevalence filter to hide files accessed by many hosts, highlighting rare and potentially unauthorized actions.
The second branch of the fan-out monitors the hb_process_activity surface. It specifically looks for web workers spawning shell primitives and networking tools like netcat, socat, or mkfifo. It also checks command lines for /dev/tcp redirections and pty spawns. Because web workers rarely spawn these binaries in a production environment, any matches represent a high-priority triage event.
The final phase uses an automated agent to correlate these findings. The agent compares the file access events with the process spawns on a per-host basis. It looks for functional transitions where a web worker first accesses a credential file and then executes an outbound connection. An analyst then reviews the verdict to confirm if the activity aligns with a standard deployment or indicates a successful RCE.
Blind Spots
This hunt relies on comprehensive endpoint telemetry. If a Linux web node does not have an active agent, the process and file activity remain invisible to these queries. Additionally, an attacker may use fileless execution techniques within the PHP interpreter, such as eval() calls that do not spawn a sub-process. This hunt cannot see in-memory execution that avoids the process and file surfaces.
In this series
Steps
-
Identify PHP-capable assets
Query · scopingScope the hunt to hosts running PHP or major web server packages.
reads hb_software_inventorysqlSELECT DISTINCT device_hostname FROM hb_software_inventory WHERE (LOWER(package_name) LIKE '%php%' OR LOWER(package_name) LIKE '%apache%' OR LOWER(package_name) LIKE '%nginx%') AND asset_scope = 'endpoint'What a hit looks like. A list of hosts that likely have a web stack capable of being exploited by PHP RCE. Silence suggests the estate has no indexed PHP/web software.
-
Web worker access to credentials and persistence
Query · baselineIdentify web workers reading credential files or writing new PHP/phtml files.
reads hb_file_activitysqlSELECT device_hostname, process_name, file_path, activity_name, COUNT(DISTINCT device_hostname) AS host_count, MIN(time) AS first_seen FROM hb_file_activity WHERE (instr(',' || '{{web_worker_names}}' || ',', ',' || LOWER(process_name) || ',') > 0 OR instr(',' || '{{web_worker_names}}' || ',', ',' || LOWER(parent_process_name) || ',') > 0) AND (instr(',' || '{{credential_paths}}' || ',', ',' || LOWER(file_path) || ',') > 0 OR (activity_id = 1 AND (LOWER(file_path) LIKE '%.php' OR LOWER(file_path) LIKE '%.phtml' OR LOWER(file_path) LIKE '%avatar.php.jpg'))) AND ('{{scope_hosts}}' = '' OR instr(',' || '{{scope_hosts}}' || ',', ',' || device_hostname || ',') > 0) AND time >= datetime('now', '-{{lookback_days}} days') GROUP BY device_hostname, process_name, file_path, activity_name HAVING host_count <= 5What a hit looks like. A web worker process accessing sensitive files or creating new PHP scripts on a small subset of hosts. Normal application behavior should be widespread and excluded by the prevalence check.
-
Web worker spawning shells and network tools
Query · triageIdentify web workers spawning shell primitives or redirection tools used for interactive access.
reads hb_process_activitysqlSELECT device_hostname, process_name, process_cmd_line, parent_process_name, time FROM hb_process_activity WHERE (instr(',' || '{{web_worker_names}}' || ',', ',' || LOWER(parent_process_name) || ',') > 0 OR instr(',' || '{{web_worker_names}}' || ',', ',' || LOWER(actor_process_name) || ',') > 0) AND (instr(',' || '{{reverse_shell_tools}}' || ',', ',' || LOWER(process_name) || ',') > 0 OR process_cmd_line LIKE '%/dev/tcp/%' OR process_cmd_line LIKE '%pty.spawn%' OR process_cmd_line LIKE '%fsockopen%') AND ('{{scope_hosts}}' = '' OR instr(',' || '{{scope_hosts}}' || ',', ',' || device_hostname || ',') > 0) AND time >= datetime('now', '-{{lookback_days}} days')What a hit looks like. A web worker spawning netcat, socat, or shell primitives. This is a high-confidence signal of post-exploitation command-and-control.
-
Triage worker behavior
Agent triageCorrelate findings to determine if the web worker actions indicate a successful RCE intrusion.
-
Route on triage verdict
DecisionDirect the response based on the agent's confidence and findings.
-
Isolate affected host
Response actionContain the breach by isolating the host from the network.
-
Manual forensic review
Analyst taskFinal analyst verification of the agent's findings.
-
Close out PHP hunt
Analyst taskFinalize the hunt and record coverage gaps.
Coverage
Scenario coverage
| Stage | Covered | How, or why not |
|---|---|---|
| Reverse shell communication T1071.001 |
Yes | web-worker-reverse-shells |
| Credential and identity harvesting T1552.001 · T1552.005 |
Yes | web-worker-credential-access |
| Webshell and system persistence T1505.003 · T1133 · T1053.003 |
Yes | web-worker-credential-access |
| PHP core RCE exploitation T1190 |
Out of scope | Belongs to another part of the 'php2shell: Assessing Elastic detection coverage relevant to CVE-2026-109618 and CVE-2026-109619' series. |
| Web worker spawning shell T1059 |
Out of scope | Belongs to another part of the 'php2shell: Assessing Elastic detection coverage relevant to CVE-2026-109618 and CVE-2026-109619' series. |
| System and identity enumeration T1087 · T1082 |
Out of scope | Belongs to another part of the 'php2shell: Assessing Elastic detection coverage relevant to CVE-2026-109618 and CVE-2026-109619' series. |
| Payload staging and interpreter pivot T1059 · T1105 |
Out of scope | Belongs to another part of the 'php2shell: Assessing Elastic detection coverage relevant to CVE-2026-109618 and CVE-2026-109619' series. |
Blind spots
- Needs Endpoint agent on all Linux web nodes. A host without an agent provides no telemetry for process spawns or file reads, making RCE outcome invisible. It would answer Are all web server nodes enrolled in process and file monitoring?.
- Needs PHP interpreter-level auditing. In-memory only execution bypasses process and file surfaces; detection relies on networking or script-block logging which may be incomplete. It would answer Can we see PHP execution that uses eval() without spawning a shell?.
Parameters & data
Parameters
| Parameter | Type | Default | What it is |
|---|---|---|---|
credential_paths | list[path] | /.env, /.aws/credentials, /var/run/secrets/kubernetes.io/serviceaccount/token, /etc/shadow | Sensitive file paths commonly targeted for harvesting. |
lookback_days | number | 14 | Days of history to examine. |
reverse_shell_tools | list[string] | nc, socat, ncat, netcat, mkfifo | Binary names often used to establish interactive shells. |
scope_hosts | list[host] | — | Optional list of hostnames to focus on. |
web_worker_names | list[string] | php-fpm, apache2, httpd, nginx, lsphp, php-cgi | Process names of common web workers and PHP runtimes. |
Telemetry
| Source | Category | Telemetry |
|---|---|---|
| Endpoint telemetry (hb_ surfaces) | endpoint | endpoint |
Source
---
analysis: A single rule for 'bash from apache' exists but is often disabled for noise;
this hunt uses prevalence and multi-surface correlation (process + file) to identify
rare credential access that a standing rule cannot baseline effectively.
blind_spots:
- id: no-linux-endpoint-telemetry
question: Are all web server nodes enrolled in process and file monitoring?
requires: Endpoint agent on all Linux web nodes
risk: A host without an agent provides no telemetry for process spawns or file reads,
making RCE outcome invisible.
stage: c2-reverse-shell-primitives
- id: fileless-php-execution
question: Can we see PHP execution that uses eval() without spawning a shell?
requires: PHP interpreter-level auditing
risk: In-memory only execution bypasses process and file surfaces; detection relies
on networking or script-block logging which may be incomplete.
stage: c2-reverse-shell-primitives
coverage:
- stage: c2-reverse-shell-primitives
status: covered
steps:
- web-worker-reverse-shells
- stage: credential-access-harvesting
status: covered
steps:
- web-worker-credential-access
- stage: persistence-webshell-and-system
status: covered
steps:
- web-worker-credential-access
- reason: 'Belongs to another part of the ''php2shell: Assessing Elastic detection
coverage relevant to CVE-2026-109618 and CVE-2026-109619'' series.'
stage: initial-access-php-rce
status: out_of_scope
- reason: 'Belongs to another part of the ''php2shell: Assessing Elastic detection
coverage relevant to CVE-2026-109618 and CVE-2026-109619'' series.'
stage: execution-web-worker-shell
status: out_of_scope
- reason: 'Belongs to another part of the ''php2shell: Assessing Elastic detection
coverage relevant to CVE-2026-109618 and CVE-2026-109619'' series.'
stage: discovery-system-enumeration
status: out_of_scope
- reason: 'Belongs to another part of the ''php2shell: Assessing Elastic detection
coverage relevant to CVE-2026-109618 and CVE-2026-109619'' series.'
stage: execution-payload-staging
status: out_of_scope
guardrails:
claims: no_unsupported
evidence: citation_required
missing_data: not_benign
telemetry: untrusted
hunt:
applicability: campaign-specific
handoff: keep-as-periodic-hunt
justification: PHP core vulnerabilities are critical (9.2) and unauthenticated.
Proactively hunting for post-exploitation behavior ensures that even if initial
RCE occurred, the attacker did not gain persistent control or harvest sensitive
credentials.
methodology: model-assisted
trigger: intel-report
hypothesis: An attacker has exploited a PHP core vulnerability (CVE-2026-109618/109619)
and is using the web worker context to harvest credentials or establish persistence
via reverse shells and web shells.
labels:
- hunt
- attack.t1190
- attack.t1552.001
- attack.t1552.005
- attack.t1071.001
- attack.t1505.003
- attack.t1053.003
- command and control
- credential access
- discovery
- execution
- initial access
- persistence
name: Post-RCE Persistence and Credential Theft
parameters:
credential_paths:
default:
- /.env
- /.aws/credentials
- /var/run/secrets/kubernetes.io/serviceaccount/token
- /etc/shadow
description: Sensitive file paths commonly targeted for harvesting.
from:
kind: article
observed: '2024-10-21'
ref: php2shell
type: list[path]
lookback_days:
default: '14'
description: Days of history to examine.
from:
kind: manual
observed: '2024-10-21'
ref: standard-retention
type: number
reverse_shell_tools:
default:
- nc
- socat
- ncat
- netcat
- mkfifo
description: Binary names often used to establish interactive shells.
from:
kind: article
observed: '2024-10-21'
ref: php2shell
type: list[string]
scope_hosts:
default: []
description: Optional list of hostnames to focus on.
from:
kind: manual
observed: '2024-10-21'
ref: analyst-scoping
type: list[host]
web_worker_names:
default:
- php-fpm
- apache2
- httpd
- nginx
- lsphp
- php-cgi
description: Process names of common web workers and PHP runtimes.
from:
kind: article
observed: '2024-10-21'
ref: php2shell
type: list[string]
provenance:
authors:
- name: Huntbase hunt generation
org: huntbase.io
generated:
by: huntbase-hunt-generation
from: https://www.elastic.co/security-labs/threat-command/php2shell-php-rce-detection
gates:
- dry-run
- lint
model: hb_google/gemini-3-flash-preview
rationale: Prioritize Linux servers running external-facing PHP applications. If the
software inventory is outdated, run the hunt unscoped to capture all web workers.
references:
- name: 'php2shell: Assessing Elastic detection coverage relevant to CVE-2026-109618
and CVE-2026-109619'
url: https://www.elastic.co/security-labs/threat-command/php2shell-php-rce-detection
related:
- hunt: initial-access-php-rce
reason: This hunt focuses on post-exploitation outcomes; initial HTTP-level exploitation
is a separate analysis of web logs.
relation: out-of-scope-alternative
- hunt: php2shell-web-to-shell-staging
relation: follows
scenario:
stages:
- name: PHP core RCE exploitation
observables:
- CVE-2026-109618
- CVE-2026-109619
- unauthenticated HTTP request targeting PHP worker (php-fpm, mod_php, php-cgi)
slug: initial-access-php-rce
tactic: initial-access
techniques:
- T1190
- name: Web worker spawning shell
observables:
- parent_process_name in ('apache2', 'httpd', 'nginx', 'php-cgi', 'php-fpm', 'lsphp')
- process_name in ('bash', 'dash', 'sh', 'zsh', 'ksh')
- process_args in ('-c', '-cl', '-lc')
slug: execution-web-worker-shell
tactic: execution
techniques:
- T1059
- name: System and identity enumeration
observables:
- id
- whoami
- uname -a
- hostname
- cat /etc/os-release
- sudo -l
- find / -perm -u=s -type f
- getcap -r /
- reading /etc/passwd
- reading /etc/shadow
slug: discovery-system-enumeration
tactic: discovery
techniques:
- T1087
- T1082
- name: Payload staging and interpreter pivot
observables:
- python3 -c
- perl -e
- ruby -e
- node -e
- php -r
- curl -O
- wget -O
- echo <b64> | base64 -d | sh
- openssl enc -d
- xxd -r
slug: execution-payload-staging
tactic: execution
techniques:
- T1059
- T1105
- name: Reverse shell communication
observables:
- /dev/tcp/
- mkfifo
- nc
- socat
- pty.spawn
- fsockopen
slug: c2-reverse-shell-primitives
tactic: command-and-control
techniques:
- T1071.001
- name: Credential and identity harvesting
observables:
- reading .env files
- reading ~/.aws/credentials
- accessing /var/run/secrets/kubernetes.io/
- HTTP request to 169.254.169.254 (IMDS)
slug: credential-access-harvesting
tactic: credential-access
techniques:
- T1552.001
- T1552.005
- name: Webshell and system persistence
observables:
- writing .php or .phtml to web root
- avatar.php.jpg (polyglot/double extension)
- adding cron entries
- creating systemd units
- modifying ~/.ssh/authorized_keys
slug: persistence-webshell-and-system
tactic: persistence
techniques:
- T1505.003
- T1133
- T1053.003
summary: Unauthenticated remote code execution vulnerabilities in PHP core (CVE-2026-109618
and CVE-2026-109619) allow attackers to gain code execution in the context of
web worker processes. Post-exploitation involves web servers spawning shells for
system discovery, staging second-stage payloads via interpreters, and establishing
persistence through webshells or system configuration.
series:
index: 2
slug: php2shell-assessing-elastic-detection-coverage-relevant-to-cve-2026-109618-and-cve-2026-109619
title: 'php2shell: Assessing Elastic detection coverage relevant to CVE-2026-109618
and CVE-2026-109619'
total: 2
severity: high
targets:
analyst:
name: Tier-2 analyst
role: analyst
endpoint:
category: endpoint
name: Endpoint telemetry (hb_ surfaces)
telemetry:
- endpoint
hunter:
agent: true
name: Hunt agent
tlp: clear
type: investigation
---
# Post-RCE Persistence and Credential Theft
This hunt targets the post-exploitation objectives of the php2shell vulnerabilities. It specifically examines web worker processes (php-fpm, apache2, nginx) for convergent malicious behavior: accessing cloud credentials, environment files, or instance metadata, and spawning interactive shell primitives like netcat or socat. The hunt uses a parallel fan-out to analyze file activity and process execution across multiple surfaces, identifying rare or unauthorized actions originating from the web stack.
## identify-php-assets
<!-- Identify PHP-capable assets -->
Scope the hunt to hosts running PHP or major web server packages.
```sqlite target=endpoint role=scoping
~~~yaml
expected: A list of hosts that likely have a web stack capable of being exploited
by PHP RCE. Silence suggests the estate has no indexed PHP/web software.
reads:
- package_name
- device_hostname
- asset_scope
silence: not_evidence_of_absence
source: hb_software_inventory
verified: dry-run
verified_at: '2026-10-22'
~~~
SELECT DISTINCT device_hostname FROM hb_software_inventory WHERE (LOWER(package_name) LIKE '%php%' OR LOWER(package_name) LIKE '%apache%' OR LOWER(package_name) LIKE '%nginx%') AND asset_scope = 'endpoint'
```
## correlate-post-rce
<!-- Correlate post-RCE activity -->
parallel:
- → web-worker-credential-access
- → web-worker-reverse-shells
join: → triage-worker-behavior
## web-worker-credential-access
<!-- Web worker access to credentials and persistence -->
Identify web workers reading credential files or writing new PHP/phtml files.
```sqlite target=endpoint role=baseline params=(web_worker_names=web_worker_names, credential_paths=credential_paths, scope_hosts=scope_hosts, lookback_days=lookback_days)
~~~yaml
baseline:
compare: first_seen
window: '{{lookback_days}}d'
expected: A web worker process accessing sensitive files or creating new PHP scripts
on a small subset of hosts. Normal application behavior should be widespread and
excluded by the prevalence check.
prevalence:
by: device_hostname
key:
- process_name
- file_path
rare_below: 5
reads:
- device_hostname
- process_name
- file_path
- activity_name
- time
silence: not_evidence_of_absence
source: hb_file_activity
verified: dry-run
verified_at: '2026-10-22'
~~~
SELECT device_hostname, process_name, file_path, activity_name, COUNT(DISTINCT device_hostname) AS host_count, MIN(time) AS first_seen FROM hb_file_activity WHERE (instr(',' || '{{web_worker_names}}' || ',', ',' || LOWER(process_name) || ',') > 0 OR instr(',' || '{{web_worker_names}}' || ',', ',' || LOWER(parent_process_name) || ',') > 0) AND (instr(',' || '{{credential_paths}}' || ',', ',' || LOWER(file_path) || ',') > 0 OR (activity_id = 1 AND (LOWER(file_path) LIKE '%.php' OR LOWER(file_path) LIKE '%.phtml' OR LOWER(file_path) LIKE '%avatar.php.jpg'))) AND ('{{scope_hosts}}' = '' OR instr(',' || '{{scope_hosts}}' || ',', ',' || device_hostname || ',') > 0) AND time >= datetime('now', '-{{lookback_days}} days') GROUP BY device_hostname, process_name, file_path, activity_name HAVING host_count <= 5
```
## web-worker-reverse-shells
<!-- Web worker spawning shells and network tools -->
Identify web workers spawning shell primitives or redirection tools used for interactive access.
```sqlite target=endpoint role=triage params=(web_worker_names=web_worker_names, reverse_shell_tools=reverse_shell_tools, scope_hosts=scope_hosts, lookback_days=lookback_days)
~~~yaml
expected: A web worker spawning netcat, socat, or shell primitives. This is a high-confidence
signal of post-exploitation command-and-control.
reads:
- device_hostname
- process_name
- process_cmd_line
- parent_process_name
- time
silence: not_evidence_of_absence
source: hb_process_activity
verified: dry-run
verified_at: '2026-10-22'
~~~
SELECT device_hostname, process_name, process_cmd_line, parent_process_name, time FROM hb_process_activity WHERE (instr(',' || '{{web_worker_names}}' || ',', ',' || LOWER(parent_process_name) || ',') > 0 OR instr(',' || '{{web_worker_names}}' || ',', ',' || LOWER(actor_process_name) || ',') > 0) AND (instr(',' || '{{reverse_shell_tools}}' || ',', ',' || LOWER(process_name) || ',') > 0 OR process_cmd_line LIKE '%/dev/tcp/%' OR process_cmd_line LIKE '%pty.spawn%' OR process_cmd_line LIKE '%fsockopen%') AND ('{{scope_hosts}}' = '' OR instr(',' || '{{scope_hosts}}' || ',', ',' || device_hostname || ',') > 0) AND time >= datetime('now', '-{{lookback_days}} days')
```
## triage-worker-behavior
<!-- Triage worker behavior -->
```agent target=hunter
cite: required
context:
- web-worker-credential-access
- web-worker-reverse-shells
max_iterations: 4
objective: Determine if the rare file access and process execution patterns indicate
a compromised web node.
success_criteria: A detailed verdict per host citing rows that show unauthorized transitions
from web requests to system-level access.
tools:
- endpoint
```
## route-on-triage
<!-- Route on triage verdict -->
if~: "the triage verdict is malicious for at least one host involving credential theft or reverse shells" (confidence: high, judge=hunter)
then: → isolate-affected-host
indeterminate: → manual-forensic-review
unavailable: → manual-forensic-review (blind_spot: no-linux-endpoint-telemetry)
else: → close-out-php-hunt
## isolate-affected-host
<!-- Isolate affected host -->
```action target=endpoint
~~~yaml
approval: required
~~~
Isolate the host, revoke any cloud credentials found in the agent context, and collect memory/web-root artifacts.
```
→ manual-forensic-review
## manual-forensic-review
<!-- Manual forensic review -->
```manual target=analyst
Review the cited rows. Verify if the web worker access to .env or AWS credentials is part of a standard application deployment. Check the timing of file writes against known RCE disclosure windows.
```
→ close-out-php-hunt
## close-out-php-hunt
<!-- Close out PHP hunt -->
```manual target=analyst
Document the hosts examined. If no malicious activity was found, record this as evidence of absence for the php2shell scenario.
```
→ end
Run it
Take this hunt into your environment.
Open it in Huntbase to run every step against your own connections, with Scout weighing the evidence and your analysts in command. Or take the open hunt.md file anywhere that reads the format.
Machine-drafted by huntbase-hunt-generation using hb_google/gemini-3-flash-preview, gated by dry-run, lint, then reviewed by a person.