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