WordPress REST API Exploitation and Plugin Staging
An attacker is exploiting the wp2shell WordPress Core RCE chain to upload and stage a malicious plugin by abusing the unauthenticated REST batch API.
Based on research by Elastic Security Labs 2026-09-20 9 steps · 3 queries T1059 T1190 T1505.003
Brief
Why This Hunt Matters
Elastic Security Labs recently published an analysis of wp2shell hits WordPress, detailing a pre-authentication RCE chain (CVE-2026-63030 and CVE-2026-60137). The exploit abuses the WordPress REST batch API to bypass authentication and eventually stage a malicious plugin. Because these plugins can contain any arbitrary PHP code, they is a versatile persistence mechanism. We designed this hunt to find the transition from API exploitation to file-system persistence.
How the Hunt Flows
The hunt begins by scoping the environment for vulnerable assets. The first query checks vulnerability findings for CVEs associated with wp2shell. This allows an analyst to prioritize hosts already known to be running vulnerable versions of WordPress, though the hunt continues even if specific CVE records are missing.
Next, the hunt moves into a parallel correlation phase. One branch monitors HTTP activity for requests targeting the /batch/v1 endpoint. It specifically looks for anomalies like the wp2shell User-Agent or common toolstrings like python and curl hitting these routes. This surface captures the initial exploitation attempt at the network level.
Simultaneously, the second branch monitors file activity on the host. It stacks the creation of PHP files within the WordPress plugin and upload directories. The query filters for files dropped by web server processes such as apache2, httpd, or php-fpm. By counting the prevalence of these files across the estate, the hunt highlights rare drops that do not align with standard administrative updates.
Finally, a triage step correlates these events. An analyst or agent examines if a host received a suspicious batch API request shortly followed by the creation of a rare PHP file. This correlation bridges the gap between a network-level exploit and its host-level impact, providing high confidence for a malicious verdict.
Blind Spots
This hunt has two primary limitations. First, it relies on HTTP telemetry. If the traffic is encrypted and the logging source does not have access to decrypted request details, the specific batch API parameters and User-Agents remain invisible. Second, the hunt focuses on the plugin-staging mechanism. If an adversary uses an alternate path, such as writing a shell directly to a cache directory via INTO OUTFILE, the file-activity query does not see the event.
In this series
Steps
-
Scope for vulnerable WordPress instances
Query · scopingIdentify hosts with reported vulnerabilities corresponding to the wp2shell exploit chain to prioritize the hunt.
reads hb_vulnerability_findingsqlSELECT device_uid, cve_uid, severity, affected_package_version, first_seen FROM hb_vulnerability_finding WHERE instr(',' || '{{vulnerable_cves}}' || ',', ',' || cve_uid || ',') > 0What a hit looks like. A list of device UIDs running vulnerable WordPress versions. Silence means no known-vulnerable instances are recorded in the inventory.
-
Detect batch API route confusion attempts
Query · detection candidateFind HTTP requests targeting the vulnerable WordPress batch endpoint, specifically looking for common PoC User-Agents or anomalous traffic patterns.
reads hb_http_activitysqlSELECT device_hostname, url_full, user_agent, src_endpoint_ip, time FROM hb_http_activity WHERE (LOWER(url_full) LIKE '%/batch/v1%' OR LOWER(url_full) LIKE '%rest_route=/batch/v1%') AND (LOWER(user_agent) LIKE '%wp2shell%' OR LOWER(user_agent) LIKE '%curl%' OR LOWER(user_agent) LIKE '%python%') AND ('{{scope_hosts}}' = '' OR instr(',' || '{{scope_hosts}}' || ',', ',' || device_hostname || ',') > 0) AND time >= datetime('now', '-{{lookback_days}} days')What a hit looks like. Requests to the batch API from unusual User-Agents. Any hit on 'wp2shell' is a high-confidence indicator of exploitation.
-
Identify rare PHP files written by web servers
Query · baselineStack-count the creation of new PHP files in WordPress plugin directories by the web server process to highlight anomalous staging activity.
reads hb_file_activitysqlSELECT LOWER(file_path) AS plugin_path, COUNT(DISTINCT device_hostname) AS host_count, MIN(time) AS first_seen FROM hb_file_activity WHERE (LOWER(file_path) LIKE '%/wp-content/plugins/%.php' OR LOWER(file_path) LIKE '%/wp-content/uploads/%.zip') AND (LOWER(process_name) LIKE '%apache%' OR LOWER(process_name) LIKE '%httpd%' OR LOWER(process_name) LIKE '%php-fpm%') AND activity_id = 1 AND ('{{scope_hosts}}' = '' OR instr(',' || '{{scope_hosts}}' || ',', ',' || device_hostname || ',') > 0) AND time >= datetime('now', '-{{lookback_days}} days') GROUP BY plugin_path HAVING host_count <= 2 ORDER BY host_count ASCWhat a hit looks like. A small number of hosts showing new PHP or ZIP files dropped into WordPress content directories by the web server runtime.
-
Triage WordPress exploitation and staging
Agent triageEvaluate whether the HTTP requests and file staging events together constitute a confirmed exploitation of CVE-2026-63030.
-
Route on verdict
DecisionDirect the workflow to immediate containment if the agent confirms exploitation.
-
Isolate compromised WordPress host
Response actionPrevent further exploitation, lateral movement, or data exfiltration by isolating the affected host.
-
Manual forensic review
Analyst taskVerify the findings and investigate post-exploitation activity such as shell execution or lateral movement.
-
Incident close-out
Analyst taskRecord the hunt results and update the vulnerability inventory.
Coverage
Scenario coverage
| Stage | Covered | How, or why not |
|---|---|---|
| Exploitation of WordPress REST batch API T1190 |
Yes | scoping-vulnerable-assets, http-batch-api-exploitation |
| Malicious plugin staging on disk T1505.003 |
Yes | rare-plugin-file-drops |
| Shell execution by web server process T1059 |
Out of scope | Covered by follow-on behavioral hunts in the series. |
| System and privilege reconnaissance T1082 · T1033 |
Out of scope | Covered by general Linux discovery hunts. |
| Indicator removal and cleanup T1070.004 |
Out of scope | Cleanup activity is the last stage and requires separate file-deletion logic. |
Blind spots
- Needs TLS decryption at the log source (hb_http_activity). Encrypted traffic may prevent seeing the User-Agent or full URL query parameters used in the exploit chain. It would answer Were the specific batch API parameters (SQL injection payloads) visible in the request?.
- Needs Full file monitoring outside wp-content/plugins/. If the attacker avoids the plugin-staging mechanism and writes directly to other writable directories, the rare-plugin query will not see the file drop. It would answer Did the attacker drop a webshell using INTO OUTFILE into the cache or uploads directory?.
Parameters & data
Parameters
| Parameter | Type | Default | What it is |
|---|---|---|---|
lookback_days | number | 14 | Days of history to examine. |
scope_hosts | list[host] | — | Optional list of hostnames to restrict the hunt; leave empty to scan the entire estate. |
vulnerable_cves | list[string] | CVE-2026-60137, CVE-2026-63030 | CVE IDs associated with the wp2shell vulnerability. |
Telemetry
| Source | Category | Telemetry |
|---|---|---|
| Endpoint telemetry (hb_ surfaces) | endpoint | endpoint |
| Web server / proxy logs | siem | network |
Source
---
analysis: While a single rule may alert on the plugin drop, this hunt correlates the
initial API exploit (network surface) with the resulting staging activity (file
surface) and uses prevalence to identify the specific malicious plugin among legitimate
site updates. This reduces false positives from authorized administrative plugin
installs.
blind_spots:
- id: missing-http-telemetry
question: Were the specific batch API parameters (SQL injection payloads) visible
in the request?
requires: TLS decryption at the log source (hb_http_activity)
risk: Encrypted traffic may prevent seeing the User-Agent or full URL query parameters
used in the exploit chain.
stage: exploit-wordpress-batch-api
- id: alternate-staging-paths
question: Did the attacker drop a webshell using INTO OUTFILE into the cache or
uploads directory?
requires: Full file monitoring outside wp-content/plugins/
risk: If the attacker avoids the plugin-staging mechanism and writes directly to
other writable directories, the rare-plugin query will not see the file drop.
stage: malicious-plugin-staging
coverage:
- stage: exploit-wordpress-batch-api
status: covered
steps:
- scoping-vulnerable-assets
- http-batch-api-exploitation
- stage: malicious-plugin-staging
status: covered
steps:
- rare-plugin-file-drops
- reason: Covered by follow-on behavioral hunts in the series.
stage: shell-spawn-from-web-server
status: out_of_scope
- reason: Covered by general Linux discovery hunts.
stage: post-exploitation-discovery
status: out_of_scope
- reason: Cleanup activity is the last stage and requires separate file-deletion logic.
stage: automated-self-cleanup
status: out_of_scope
guardrails:
claims: no_unsupported
evidence: citation_required
missing_data: not_benign
telemetry: untrusted
hunt:
applicability: campaign-specific
handoff: promote-to-detection
justification: WordPress RCE (wp2shell) provides a pre-authentication path to full
host compromise. Given the high prevalence of WordPress and the availability of
public PoCs, an unmitigated compromise on a production web server poses a critical
risk of data theft and lateral movement.
methodology: model-assisted
trigger: intel-report
hypothesis: An attacker is exploiting the wp2shell WordPress Core RCE chain to upload
and stage a malicious plugin by abusing the unauthenticated REST batch API.
labels:
- hunt
- attack.t1190
- attack.t1505.003
- attack.t1059
name: WordPress REST API Exploitation and Plugin Staging
parameters:
lookback_days:
default: '14'
description: Days of history to examine.
type: number
scope_hosts:
default: []
description: Optional list of hostnames to restrict the hunt; leave empty to scan
the entire estate.
type: list[host]
vulnerable_cves:
default:
- CVE-2026-60137
- CVE-2026-63030
description: CVE IDs associated with the wp2shell vulnerability.
from:
kind: article
observed: '2026-07-23'
ref: elastic-security-labs-wp2shell
type: list[string]
provenance:
authors:
- name: Huntbase hunt generation
org: huntbase.io
generated:
by: huntbase-hunt-generation
from: https://www.elastic.co/security-labs/blog/wp2shell-wordpress-rce-detection-elastic-defend
gates:
- dry-run
- lint
- critic
model: hb_google/gemini-3-flash-preview
rationale: Prioritize internet-facing hosts running WordPress 6.9.x or 7.0.x. If vulnerability
findings are incomplete, run the HTTP exploitation query across all systems identified
as web servers.
references:
- name: "Elastic Security Labs \u2014 wp2shell hits WordPress"
url: https://www.elastic.co/security-labs/blog/wp2shell-wordpress-rce-detection-elastic-defend
related:
- hunt: wordpress-webshell-execution
reason: This hunt focuses on the staging stage; a follow-on hunt is required to
detect the execution of commands via the webshell once it is staged.
relation: follows
scenario:
stages:
- name: Exploitation of WordPress REST batch API
observables:
- POST /?rest_route=/batch/v1
- POST /wp-json/batch/v1
- 'User-Agent: wp2shell'
- CVE-2026-63030
- CVE-2026-60137
slug: exploit-wordpress-batch-api
tactic: initial-access
techniques:
- T1190
- name: Malicious plugin staging on disk
observables:
- wp-content/plugins/wp2shell_*
- wp-content/uploads/wp2shell_*.zip
- temp-write-test-*
- wp-content/upgrade/wp2shell_*/
- wp-content/plugins/wp2shell_*.php
slug: malicious-plugin-staging
tactic: persistence
techniques:
- T1505.003
- name: Shell execution by web server process
observables:
- apache2 spawning dash
- httpd spawning sh
- php-fpm spawning bash
- sh -c -- id; whoami; hostname
slug: shell-spawn-from-web-server
tactic: execution
techniques:
- T1059
- name: System and privilege reconnaissance
observables:
- uname
- cat /etc/passwd
- find / -perm -u=s -type f
slug: post-exploitation-discovery
tactic: discovery
techniques:
- T1082
- T1033
- name: Indicator removal and cleanup
observables:
- rm -rf wp-content/plugins/wp2shell_*
- deletion of staged zip files under wp-content/uploads/
slug: automated-self-cleanup
tactic: defense-evasion
techniques:
- T1070.004
summary: Attackers leverage a pre-authentication RCE vulnerability in the WordPress
REST batch API (CVE-2026-63030) to stage malicious plugins or web shells on vulnerable
servers. Once established, the web server process is used to execute system shells
for reconnaissance and automated artifact cleanup.
series:
index: 1
slug: wp2shell-hits-wordpress-detecting-pre-auth-rce-from-plugin-drop-to-command-execution
title: 'wp2shell hits WordPress: detecting pre-auth RCE from plugin drop to command
execution'
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
web:
category: siem
name: Web server / proxy logs
telemetry:
- network
tlp: clear
type: investigation
---
# WordPress REST API Exploitation and Plugin Staging
This hunt identifies the early stages of the wp2shell attack chain (CVE-2026-63030 and CVE-2026-60137). It first scopes the environment for vulnerable WordPress versions, then correlates evidence of the specific batch API route confusion exploit with file-system staging events where the web server process drops new PHP files into plugin directories. By focusing on the web-server-to-file-drop relationship, the hunt remains durable against variations in the final shell payload.
## scoping-vulnerable-assets
<!-- Scope for vulnerable WordPress instances -->
Identify hosts with reported vulnerabilities corresponding to the wp2shell exploit chain to prioritize the hunt.
```sqlite target=endpoint role=scoping params=(vulnerable_cves=vulnerable_cves)
~~~yaml
expected: A list of device UIDs running vulnerable WordPress versions. Silence means
no known-vulnerable instances are recorded in the inventory.
reads:
- affected_package_version
- cve_uid
- device_uid
- first_seen
- severity
silence: not_evidence_of_absence
source: hb_vulnerability_finding
verified: dry-run
verified_at: '2026-09-20'
~~~
SELECT device_uid, cve_uid, severity, affected_package_version, first_seen FROM hb_vulnerability_finding WHERE instr(',' || '{{vulnerable_cves}}' || ',', ',' || cve_uid || ',') > 0
```
## correlate-exploitation-activity
<!-- Correlate exploitation and staging -->
parallel:
- → http-batch-api-exploitation
- → rare-plugin-file-drops
join: → triage-exploitation-evidence
## http-batch-api-exploitation
<!-- Detect batch API route confusion attempts -->
Find HTTP requests targeting the vulnerable WordPress batch endpoint, specifically looking for common PoC User-Agents or anomalous traffic patterns.
```sqlite target=web role=detection-candidate params=(lookback_days=lookback_days, scope_hosts=scope_hosts)
~~~yaml
expected: Requests to the batch API from unusual User-Agents. Any hit on 'wp2shell'
is a high-confidence indicator of exploitation.
reads:
- device_hostname
- src_endpoint_ip
- time
- url_full
- user_agent
silence: not_evidence_of_absence
source: hb_http_activity
verified: dry-run
verified_at: '2026-09-20'
~~~
SELECT device_hostname, url_full, user_agent, src_endpoint_ip, time FROM hb_http_activity WHERE (LOWER(url_full) LIKE '%/batch/v1%' OR LOWER(url_full) LIKE '%rest_route=/batch/v1%') AND (LOWER(user_agent) LIKE '%wp2shell%' OR LOWER(user_agent) LIKE '%curl%' OR LOWER(user_agent) LIKE '%python%') AND ('{{scope_hosts}}' = '' OR instr(',' || '{{scope_hosts}}' || ',', ',' || device_hostname || ',') > 0) AND time >= datetime('now', '-{{lookback_days}} days')
```
## rare-plugin-file-drops
<!-- Identify rare PHP files written by web servers -->
Stack-count the creation of new PHP files in WordPress plugin directories by the web server process to highlight anomalous staging activity.
```sqlite target=endpoint role=baseline params=(lookback_days=lookback_days, scope_hosts=scope_hosts)
~~~yaml
baseline:
compare: first_seen
window: '{{lookback_days}}d'
expected: A small number of hosts showing new PHP or ZIP files dropped into WordPress
content directories by the web server runtime.
prevalence:
by: device_hostname
key:
- file_path
rare_below: 3
reads:
- activity_id
- device_hostname
- file_path
- process_name
- time
silence: not_evidence_of_absence
source: hb_file_activity
verified: dry-run
verified_at: '2026-09-20'
~~~
SELECT LOWER(file_path) AS plugin_path, COUNT(DISTINCT device_hostname) AS host_count, MIN(time) AS first_seen FROM hb_file_activity WHERE (LOWER(file_path) LIKE '%/wp-content/plugins/%.php' OR LOWER(file_path) LIKE '%/wp-content/uploads/%.zip') AND (LOWER(process_name) LIKE '%apache%' OR LOWER(process_name) LIKE '%httpd%' OR LOWER(process_name) LIKE '%php-fpm%') AND activity_id = 1 AND ('{{scope_hosts}}' = '' OR instr(',' || '{{scope_hosts}}' || ',', ',' || device_hostname || ',') > 0) AND time >= datetime('now', '-{{lookback_days}} days') GROUP BY plugin_path HAVING host_count <= 2 ORDER BY host_count ASC
```
## triage-exploitation-evidence
<!-- Triage WordPress exploitation and staging -->
```agent target=hunter
cite: required
context:
- http-batch-api-exploitation
- rare-plugin-file-drops
max_iterations: 4
objective: Determine if any host shows a sequence where a REST batch request is followed
by the creation of a rare PHP file in a WordPress plugin or upload path by the web
server process.
success_criteria: A verdict of malicious, suspicious, or benign per host with citations
for the specific HTTP logs and file events.
tools:
- endpoint
- web
```
## determine-response
<!-- Route on verdict -->
if~: "The triage-exploitation-evidence verdict is malicious for at least one host, indicating a confirmed plugin drop via the batch API." (confidence: high, judge=hunter)
then: → isolate-compromised-host
indeterminate: → manual-forensic-review
unavailable: → manual-forensic-review (blind_spot: missing-http-telemetry)
else: → incident-close-out
## isolate-compromised-host
<!-- Isolate compromised WordPress host -->
```action target=endpoint
~~~yaml
approval: required
~~~
Isolate the host identified in the triage verdict. Preserve the WordPress directory and web server logs for forensic analysis before remediation.
```
→ manual-forensic-review
## manual-forensic-review
<!-- Manual forensic review -->
```manual target=analyst
Examine the plugin directory identified by the hunt. Check for shell spawns from the web server process (e.g., apache2 spawning sh or dash) and verify if any commands were executed by the staged PHP script. Check for evidence of SQL injection payloads in the web server access logs.
```
→ incident-close-out
## incident-close-out
<!-- Incident close-out -->
```manual target=analyst
Document the hosts examined and any identified compromises. If no activity was found on vulnerable hosts, mark them for urgent patching. Update vulnerability management records to reflect the patch status of WordPress instances.
```
→ 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, critic, then reviewed by a person.