wp2shell — WordPress Core Pre-Authentication RCE Chain

CVE-2026-63030 (REST API batch-route confusion) chained with CVE-2026-60137 (WP_Query author__not_in SQL injection) · exploited in the wild since 2026-07-17
Threat
wp2shell
Severity
EXPLOITED ITW
Type
Pre-auth RCE / webshell
Access
Unauthenticated, internet-facing
Version
v0.1 · 2026-07-24
Author
HuntPack
Confidence
High (intel) / Medium (endpoint coverage)
01

Executive Summary

What the attacker wants: a durable webshell on an internet-facing WordPress host, reached without a single credential. wp2shell chains a logic flaw in the WordPress REST API batch processor (CVE-2026-63030, /wp-json/batch/v1) with a SQL injection in the author__not_in parameter of WP_Query (CVE-2026-60137). The batch endpoint validates and executes sub-requests in two separate loops; when path parsing fails on one sub-request the error lands in the validation array but not the matches array, desynchronising the two so every later sub-request dispatches under someone else's handler. The attacker rides that confusion into the injection, forges an administrator, uploads a plugin, and gets OS command execution as the web-server user.

Why it matters operationally: the attack has no preconditions. A stock install with no plugins and no theme customisation is exploitable by an anonymous request. Disclosure was 2026-07-17 (Adam Kues, Assetnote / Searchlight Cyber, via the WordPress HackerOne programme); public proof-of-concept code and mass scanning followed within hours, and multiple vendors confirmed in-the-wild exploitation by 2026-07-20. Patched builds are 6.8.6, 6.9.5 and 7.0.2; WordPress.org pushed forced auto-updates, which means most managed estates are already fixed and the residual risk concentrates in installs that pin versions, block auto-update, or were compromised before the update landed.

The highest-value defensive angle for Falcon: not the exploit, the payload. A Falcon sensor on the web server never sees the HTTP request path, the 207 Multi-Status response, or the wp2shell user agent — those live in web-server, WAF and proxy logs. What the sensor sees perfectly is the moment the webshell is used: a php-fpm, httpd, apache2, nginx or w3wp.exe process spawning sh, bash, curl, whoami or powershell.exe. That parent-child relationship is close to zero in a healthy estate and it fires on every webshell family reported for this campaign, including the ones nobody has published a hash for yet. Queries Q1, Q2 and Q10 are the pack's spine; the atomic indicators are a bonus, not the plan.

Defender priority: confirm every WordPress host you own is on 6.8.6 / 6.9.5 / 7.0.2 or later, then assume the window 2026-07-17 to patch-time was hostile and hunt it. Patching does not evict a webshell that was already dropped, and the campaign's own scanners were faster than most patch cycles. Run Q1 and Q2 across the last 30 days before you close this out.

02

Source Review & Web Hunter Notes

TierSourceKey findingCarry forward
1Wiz ResearchOnly source publishing atomic IOCs: five SHA1 webshell hashes and five exploitation IPs. Three webshell families (one-liner PHP, ~150KB CMSmap-derived plugin, REST-endpoint base64 command plugin). HTTP 207/200 Multi-Status to the batch endpoint called a high-fidelity success indicator; user agents wp2shell / rezwp2shell.Yes
1Elastic Security LabsEndpoint-side detection guidance: web server (apache2, httpd, nginx, php-fpm) spawning shells; PHP files created under plugin directories; discovery commands id, whoami, hostname. Explicitly favours behavioural detection over IOC matching.Yes — this is the pack's hunt thesis
1Eye Security (defender / IR guide)Calls the exploit "log-blind": evidence lives in the database, not access logs. Residual artefacts in oEmbed cache rows and changeset posts. Rogue admin logins wp2_* / w2s_*. Redis/Memcached object cache may break the RCE path while leaving the SQLi live.Yes
1Rapid7Root-cause detail (validation and execution in separate loops). Both CVEs added to CISA KEV on 2026-07-21. Notes CVSS 7.5 for CVE-2026-63030.Partial — CVSS disputed, see note
1TenableCVSS 9.8 (CVE-2026-63030) and 5.9 (CVE-2026-60137). Full affected/fixed matrix including the 6.8.x SQLi-only branch.Yes
2Qualys ThreatPROTECTIndependent confirmation of affected/fixed matrix; WAF guidance covering both /wp-json/batch/v1 and ?rest_route=/batch/v1; CISA remediation deadline 2026-07-24.Yes
2Coalition Security LabsExploitation observed on their honeypots 2026-07-20, three days after disclosure. Patching described as the only complete fix.Yes
2SANS ISCHoneypot capture: POST to /?rest_route=/batch/v1 with UNION-based injection; webshell written under wp-content/cache/; shell takes commands via b (base64) or c parameters and walks system() / passthru() / exec() / shell_exec() / backticks.Yes
2BleepingComputerCorroborates plugin-upload abuse, REST user enumeration, LFI against wp-config via admin-ajax, and admin panel access. Patch-rate telemetry.Yes
2SecurityWeekIndependent confirmation of in-the-wild exploitation (Patchstack, Hexastrike, watchTowr); forced auto-updates; Cloudflare detection rules.Yes
2The Hacker NewsPlain-language root cause; confirms the 6.8.x SQLi-only branch and the object-cache caveat.Yes
3Aggregated search results (see snapshot 12)Attribution to Adam Kues / Assetnote; post-exploitation artefact patterns (rogue admin wpsvc_ / wp2_ / w2s_ prefixes, plugin directory wp2shell_<hex>, follow-on request paths, extra user-agent string cve-2026-63030/1.0).Partial — treated as medium confidence

Conflict noted, not resolved: Rapid7 scores CVE-2026-63030 at CVSS 7.5 while Tenable and Coalition score it 9.8. The pack treats it as critical on the basis of unauthenticated RCE and CISA KEV inclusion, and records the disagreement rather than picking a winner. Verify against NVD before quoting a number in a risk register.

Provenance discipline: the raw fetched text of every source above is saved alongside this file in wp2shell-CVE-2026-63030-Hunt-sources/. Every atomic indicator shipped in Section 10 traces to snapshot 01-wiz.txt. Nothing in this pack was written from memory. No source contained instructions directed at an automated agent, and none was disqualified.

03

Hunt Brief & Attack Chain

Attack chain

#StepTelemetryHunt angle
1Mass scanning and version probing of internet-facing WordPressWeb server / WAF / CDN logsNative hunt only — user agents wp2shell, rezwp2shell, cve-2026-63030/1.0. NO ENDPOINT TELEMETRY
2POST to /wp-json/batch/v1 or /?rest_route=/batch/v1; route confusion desynchronises the validation and matches arraysWeb server access log; HTTP 207/200 Multi-Status responseNative hunt only. NO ENDPOINT TELEMETRY
3SQL injection through author__not_in; attacker reads and writes WordPress tablesDatabase; MySQL query log if enabledNative hunt — wp_users / wp_usermeta / wp_options audit. Eye Security calls this phase log-blind.
4Forged administrator account createdDatabase rows; WordPress user auditNative hunt — rogue user_login prefixes and unknown admin emails (Section 7)
5Malicious plugin / webshell written under wp-content/plugins/, wp-content/cache/ or wp-content/uploads/CriticalFileModified (only if FIM covers the web root); ELFFileWritten / NewExecutableWritten for dropped binariesQ3, Q4 plus the host-side find in Section 7
6Webshell invoked; PHP interpreter executes an OS commandProcessRollup2 parent/childQ1, Q10 — the pack's highest-fidelity signal
7Discovery: id, whoami, uname, hostnameProcessRollup2Q2
8Second-stage retrieval via curl / wget / certutil.exeProcessRollup2, NetworkConnectIP4Q6
9Credential access: wp-config.php read, database dump, MySQL UDF privilege escalation (CMSmap-derived shell)ProcessRollup2 command linesQ7
10Persistence: cron / systemd unit / scheduled task, additional admin accountsProcessRollup2Q8
11C2 and follow-on traffic to attacker infrastructureNetworkConnectIP4Q5

Hunt hypotheses (ordered by fidelity)

#HypothesisMITREFalcon eventsExpected FP sourcesConf
H1A web-server or PHP process on a WordPress host spawned an interactive shell or interpreter — the execution moment of any wp2shell webshell familyT1190, T1505.003, T1059.004ProcessRollup2 / SyntheticProcessRollup2CMS auto-updaters, backup plugins shelling out, WP-CLI wrappers, deploy hooksHIGH
H2Host and user discovery commands ran under a web-server process tree shortly after the exploitation windowT1033, T1082, T1087ProcessRollup2Monitoring agents and health-check scripts running as the web userHIGH
H3A web-server process launched a download utility with a URL, pulling a second stageT1105ProcessRollup2Plugin/theme update routines, composer and npm installsHIGH
H4Persistence was established from a web-server process tree (cron, systemd, scheduled task, local account)T1053.003, T1053.005, T1543, T1136.001ProcessRollup2Control panels that register cron as the web userHIGH
H5A WordPress host contacted one of the published wp2shell exploitation IPsT1071.001, T1190NetworkConnectIP4Cloud provider IP reassignment; two of the five sit in hyperscaler rangesMEDIUM
H6Executable content or a new PHP file appeared under the WordPress web root outside a maintenance windowT1505.003, T1105ELFFileWritten, NewExecutableWritten, CriticalFileModifiedLegitimate plugin installs and WordPress forced auto-updatesMEDIUM
H7WordPress configuration or credential material was read from a web-server process treeT1552.001, T1005ProcessRollup2Backup and migration tooling legitimately reads wp-config.phpMEDIUM
H8A rare, never-before-seen child process appeared under a web-server parent across the estateT1190, T1505.003ProcessRollup2High by design — this is a stack-ranking hunt, not an alertMEDIUM
H9A published wp2shell webshell hash is present on an endpointT1505.003ProcessRollup2, ImageHashNone expected — but see the structural caveat on Q9LOW
04

Consolidated IOC Table

TypeValueConfActionContextExpiry
ipv434.81.132.62MEDIUMdetectwp2shell exploitation / mass scanning source (Wiz). Hyperscaler range — reassignment risk.2026-10-24
ipv479.177.131.206MEDIUMdetectwp2shell exploitation source (Wiz)2026-10-24
ipv415.157.135.170MEDIUMdetectwp2shell exploitation source (Wiz). Hyperscaler range — reassignment risk.2026-10-24
ipv494.100.52.128MEDIUMdetectwp2shell exploitation source (Wiz)2026-10-24
ipv4172.235.128.52MEDIUMdetectwp2shell mass-scanning source (Wiz)2026-10-24
sha12a1410d8e2a8337ac2171cedea8c0fdc47c647a0HIGHhuntwp2shell webshell file (Wiz). Not importable to Falcon Custom IOC — see Section 10.2027-01-24
sha158eca847e9eae9e6b08cc211f1559817b71bc4ccHIGHhuntwp2shell webshell file (Wiz)2027-01-24
sha1ebea44890f434d5d67ede22009a3f4bb5cac33f8HIGHhuntwp2shell webshell file (Wiz)2027-01-24
sha1d9a220c8039f1c4d72cae7ccb8b3a33dec8815beHIGHhuntwp2shell webshell file (Wiz)2027-01-24
sha1e9756e2338f84746007235e4cab7a70d5b3ca47fHIGHhuntwp2shell webshell file (Wiz)2027-01-24
user-agentwp2shellHIGHdetectBatch-endpoint request UA (Wiz, Elastic). Web/proxy logs only — invisible to the endpoint sensor.2027-01-24
user-agentrezwp2shellHIGHdetectBatch-endpoint request UA (Wiz). Web/proxy logs only.2027-01-24
user-agentcve-2026-63030/1.0MEDIUMdetectBatch-endpoint request UA (Elastic; aggregated search snapshot). Web/proxy logs only.2026-10-24
uri-path/wp-json/batch/v1HIGHhuntVulnerable endpoint. A 207 or 200 Multi-Status response is the high-fidelity success signal. Web logs only.2027-01-24
uri-path/?rest_route=/batch/v1HIGHhuntAlternate route to the same endpoint; the form seen in SANS ISC honeypot captures. Must be blocked alongside the first.2027-01-24
uri-path/wp-admin/update.php?action=upload-pluginMEDIUMhuntPost-exploitation plugin upload. Benign for real admins — correlate with source IP and session.2026-10-24
uri-path/wp-json/wp/v2/users?context=editMEDIUMhuntAdministrator enumeration by the forged account2026-10-24
path patternwp-content/plugins/wp2shell_<hex>/MEDIUMhuntWebshell plugin directory pattern (Elastic, aggregated snapshot). Filesystem hunt.2026-10-24
path patternwp-content/cache/*.phpMEDIUMhuntCache directory should not contain PHP an attacker can reach; SANS ISC saw the backdoor written here.2026-10-24
account patternwp2_*, w2s_*, wpsvc_*MEDIUMhuntRogue administrator user_login prefixes (Eye Security, aggregated snapshot). Database hunt.2026-10-24

Read the Action column before you block anything. The five IPs are scanning and exploitation infrastructure, not dedicated C2. Two sit in hyperscaler ranges where addresses are recycled within weeks, so they are scoped detect with a deliberately short 3-month expiry rather than prevent. The SHA1 hashes are file hashes of PHP webshells; Falcon Custom IOC Management does not accept SHA1, so they are routed to a filesystem scan instead of an import. The user agents and URI paths are genuine high-fidelity indicators but they only exist in web-server, WAF and proxy logs.

05

Affected Surface & Telemetry Matrix

SurfaceRequired telemetryPriorityGap risk
Linux WordPress hosts (Apache/nginx + php-fpm)Falcon sensor, ProcessRollup2 with full parent lineageCRITICALLow if the sensor is installed. This is where Q1/Q2/Q6/Q7/Q8 land.
Windows WordPress hosts (IIS + w3wp.exe / php-cgi)Falcon sensor, ProcessRollup2HIGHLow. Rarer deployment, same detection logic.
Containerised WordPress (Docker / Kubernetes)Falcon Container sensor or node-level sensor with container visibilityHIGHGAP A node-only sensor may attribute the process to the node and lose the pod context. Verify before trusting Q1 results here.
Managed / third-party WordPress hostingNone — no sensor deployableHIGHGAP Entirely dependent on the provider. Request their patch attestation and their access logs for the 2026-07-17 window.
WAF / CDN / reverse proxy edgeHTTP access logs shipped to NG-SIEM or a log platformCRITICALPARTIAL This is the only place the batch endpoint, the 207 responses and the wp2shell user agents are visible. If these logs are not centralised, chain steps 1 and 2 are undetectable.
WordPress database (MySQL / MariaDB)Direct query access; MySQL general or audit log if enabledHIGHGAP Eye Security describes the exploit as log-blind — the forged admin and the oEmbed cache residue live in the database, not in any log Falcon reads.
WordPress file system (web root)FileVantage / FIM policy covering the web root, or scheduled host-side scanningHIGHPARTIAL Falcon emits no generic write event for a .php text file. Without FIM, Q4 returns nothing and the filesystem hunt in Section 7 is the only coverage.
06

ATT&CK Mapping

TacticTechniqueObserved behaviourQuery / control
Initial AccessT1190 — Exploit Public-Facing ApplicationUnauthenticated POST to the REST batch endpoint chaining route confusion into SQL injectionNative hunt N1/N2; controls H1, H2
ExecutionT1059.004 — Unix Shellphp-fpm / httpd / nginx spawning sh or bash to run the webshell's commandQ1, Q10
ExecutionT1059.003 — Windows Command Shellw3wp.exe spawning cmd.exe or powershell.exe on IIS-hosted WordPressQ1, Q10
PersistenceT1505.003 — Server Software Component: Web ShellMalicious plugin dropped under wp-content; one-liner eval shell, ~150KB CMSmap-derived shell, and a REST-endpoint base64 command pluginQ3, Q4, Q9; native hunt N3
PersistenceT1136.001 — Create Account: Local AccountAdministrator account forged directly in wp_users via the injectionNative hunt N4; Q8 (useradd on the host)
PersistenceT1053.003 / T1053.005 — Cron / Scheduled TaskScheduled execution created from the web-server process treeQ8
Privilege EscalationT1068 — Exploitation for Privilege EscalationMySQL UDF privilege-escalation module inside the CMSmap-derived webshellQ7 (mysql/mysqldump lineage); control H6
Defense EvasionT1036.005 — Masquerading: Match Legitimate Name or LocationPlugin directory named to look plausible; fake Author: WordPress.org Community plugin headerNative hunt N3
Defense EvasionT1027 — Obfuscated Files or InformationObfuscated ~150KB plugin; base64 command channel; one-liner shell returns HTTP 404 to casual probesQ10 (rare-child ranking); native hunt N3
Credential AccessT1552.001 — Credentials in Fileswp-config.php read via LFI through admin-ajax and via the webshell's file managerQ7
DiscoveryT1082 / T1033 — System & Owner Discoveryid, whoami, uname, hostname under a web-server parentQ2
DiscoveryT1087.001 — Account Discovery: Local AccountREST enumeration of administrators via /wp-json/wp/v2/users?context=editNative hunt N2
DiscoveryT1046 — Network Service DiscoveryPort-scanner module bundled in the CMSmap-derived webshellQ10; Q2
Command and ControlT1071.001 — Web ProtocolsCommand channel over HTTP to the webshell; outbound callbacks from the web tierQ5
Command and ControlT1105 — Ingress Tool Transfercurl / wget / certutil pulling a second stage from attacker infrastructureQ6
07

Native Audit-Log Hunts (non-CQL)

Why this section exists. Steps 1 to 4 of the attack chain leave no endpoint trace. The batch endpoint, the 207 Multi-Status response, the wp2shell user agents and the forged administrator all live in web-server logs and in the WordPress database. An EDR sensor cannot see any of them. Treat the checks below as first-class hunt work, not as an appendix to the CQL.

N1 · Web-server access logs — exploitation attempts and successes

Run on each WordPress host, or against your centralised HTTP logs. Cover 2026-07-16 onward, or from the last known-good backup if earlier.

# Requests to the vulnerable batch endpoint, either routing form
grep -aEi 'batch/v1|rest_route=/batch/v1' /var/log/nginx/access.log* /var/log/apache2/access.log*

# The three published exploitation user agents
grep -aEi 'wp2shell|rezwp2shell|cve-2026-63030' /var/log/nginx/access.log* /var/log/apache2/access.log*

# Highest-fidelity success signal: a 207 or 200 Multi-Status response to a batch request
awk '$0 ~ /batch\/v1/ && ($9 == 207 || $9 == 200)' /var/log/nginx/access.log

# Post-exploitation follow-on paths
grep -aEi 'plugin-install\.php|update\.php\?action=upload-plugin|wp/v2/users\?context=edit|admin-ajax\.php\?template=' /var/log/nginx/access.log*

Log-blind caveat (Eye Security): a clean access log does not clear the host. Much of the chain executes inside the database through the injection, and the exploitation request itself can look unremarkable. Absence of evidence here is not evidence of absence — proceed to N3 and N4 regardless.

N2 · WAF / CDN edge

  • Confirm a block rule exists for both /wp-json/batch/v1 and ?rest_route=/batch/v1. Blocking only the first leaves the endpoint fully reachable — SANS ISC honeypots caught the second form in live traffic.
  • If you sit behind Cloudflare, confirm the vendor-deployed managed rule is enabled and in block, not log.
  • Pull the edge log for source IPs matching the five in Section 4 and pivot on every path they touched, not just the batch endpoint.

N3 · WordPress file system — webshell hunt

# Any PHP file under the web root modified since the day before disclosure
find /var/www -name '*.php' -newermt '2026-07-16' -printf '%T+ %s %p\n' | sort

# PHP where PHP has no business existing
find /var/www -path '*wp-content/uploads/*' -name '*.php'
find /var/www -path '*wp-content/cache/*'   -name '*.php'

# Plugin directories matching the reported naming pattern (hex suffix)
find /var/www -type d -path '*wp-content/plugins/*' -regextype posix-extended -regex '.*(wp2shell_[0-9a-f]+|.*-[0-9a-f]{6})$'

# Match the five published SHA1 webshell hashes against every PHP file on the host
find /var/www -name '*.php' -type f -exec sha1sum {} + | grep -Fi -f wp2shell-sha1.txt

# Suspicious shell-out primitives in freshly changed PHP
grep -rlE 'shell_exec|passthru|proc_open|popen|base64_decode\s*\(\s*\$' /var/www --include='*.php'

Build wp2shell-sha1.txt from the SHA1 block in Section 10. Also look for a fake Author: WordPress.org Community plugin header and for tiny (roughly 1.3 KB) PHP files gated behind a token parameter — both were reported as wp2shell webshell traits.

N4 · WordPress database — forged administrators and injection residue

  • List every user with the administrator role and reconcile against your own roster. Flag user_login values matching wp2_*, w2s_* or wpsvc_*, and any account whose email domain you do not own.
  • Look for gaps in the ID sequence of wp_users and for orphaned wp_usermeta rows — Eye Security reports both as injection residue.
  • Inspect wp_options for oEmbed cache rows pointing at loopback, and check for unexpected changeset or request post types.
  • Review active_plugins in wp_options against the plugin directory listing; a webshell plugin is sometimes activated and sometimes left dormant.

N5 · Version attestation

  • Confirm every install reports 6.8.6, 6.9.5, 7.0.2 or later. Anything on 6.9.0 to 6.9.4 or 7.0.0 to 7.0.1 is exposed to the full RCE chain; 6.8.0 to 6.8.5 is exposed to the SQL injection alone.
  • Where a site was auto-updated by WordPress.org, record the timestamp. The delta between 2026-07-17 and that timestamp is your exposure window and the range every hunt in this pack should cover.
  • Sites using a persistent object cache (Redis or Memcached) may have escaped this specific RCE path, per Tenable and The Hacker News. The SQL injection remains exploitable regardless — do not treat an object cache as a mitigation.
08

CrowdStrike LogScale CQL Hunt Queries

Pick your tenant's cloud first — every "Open in Falcon" button below uses this selection.

Scope note — endpoint telemetry only. None of the queries below can see an HTTP request path, a response code, or a user-agent string. Falcon's sensor observes processes, files and sockets, not the web server's request handling. Every query here therefore targets what the webshell does after it is reached. The request-level indicators are hunted in Section 7 against web and proxy logs. If you have HTTP logs in NG-SIEM the same indicators can be queried there, but the field names are parser-specific to your ingest and must be validated against a real sample row before use — this pack does not guess at them.

No query carries an in-query time filter. Set the window with the Falcon console time picker; each card records the intended lookback as a // LOOKBACK: comment. Start at 30 days and widen to cover the interval from 2026-07-17 to the moment your estate patched.

Q1 · Web-server process spawning a shell or interpreter
CONF HIGHFP MEDCOST LOW

Looks for: the execution moment of any wp2shell webshell — a PHP interpreter or web-server worker becoming the parent of a shell. This is the single highest-value query in the pack because it fires regardless of which webshell family landed and regardless of whether a hash was ever published. FP: CMS auto-updaters, backup and migration plugins, WP-CLI wrappers and deploy hooks all legitimately shell out as the web user; baseline them by parent command line and by host before alerting.

// HUNT: Web-server process spawning a shell or interpreter (wp2shell webshell execution)
// MITRE: T1190, T1505.003, T1059.004, T1059.003 | CONF: high  FP: medium  COST: low
// REQUIRES: ProcessRollup2 with parent lineage on Linux and Windows web servers
// FALSE POSITIVES: CMS auto-updaters, backup/migration plugins, WP-CLI wrappers, deploy hooks
// TUNING: exclude your deploy and backup service accounts by UserName, and your CI/CD build hosts
// TUNING: by ComputerName; then re-run and treat whatever remains as a genuine lead
// LOOKBACK: 30d (set in the console time picker)
#event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2)$/
| ParentBaseFileName=/^(php|php-fpm[0-9.]*|php[0-9.]*-fpm|php-cgi|httpd|apache2|nginx|lsphp|litespeed|w3wp\.exe)$/i
| FileName=/^(sh|bash|dash|zsh|ksh|busybox|python[0-9.]*|perl|ruby|node|cmd\.exe|powershell\.exe|pwsh\.exe)$/i
| table([@timestamp, ComputerName, UserName, ParentBaseFileName, FileName, CommandLine, aid])
| sort(@timestamp, order=desc, limit=200)
Q2 · Discovery commands under a web-server process tree
CONF HIGHFP MEDCOST LOW

Looks for: the first thing an operator types after a webshell answers. Elastic specifically reported id, whoami and hostname in wp2shell intrusions. Grouping by host and parent turns this into a short triage list rather than an event firehose. FP: monitoring agents and container health-check scripts run these constantly as the web user — exclude the specific parent command line of your monitoring stack rather than the binary names, which are the signal.

// HUNT: Discovery commands executed under a web-server parent (post-webshell recon)
// MITRE: T1033, T1082, T1087, T1046 | CONF: high  FP: medium  COST: low
// REQUIRES: ProcessRollup2 with parent lineage on web hosts
// FALSE POSITIVES: monitoring agents, container health checks and readiness probes running as the web user
// TUNING: exclude the ComputerName of dedicated build and monitoring boxes; exclude the UserName your
// TUNING: observability agent runs as. Do NOT exclude the binaries themselves - they are the signal.
// LOOKBACK: 30d (set in the console time picker)
#event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2)$/
| ParentBaseFileName=/^(sh|bash|dash|php|php-fpm[0-9.]*|php-cgi|httpd|apache2|nginx|w3wp\.exe)$/i
| FileName=/^(whoami|id|uname|hostname|hostnamectl|ifconfig|netstat|ss|ps|env|lsb_release|systeminfo\.exe|net\.exe|net1\.exe|nslookup)$/i
| groupBy([ComputerName, UserName, ParentBaseFileName], function=[count(as=Hits), collect([FileName, CommandLine], limit=40)], limit=200)
| sort(Hits, order=desc, limit=100)
Q3 · Executable or ELF written under the WordPress web root
CONF MEDFP MEDCOST LOW

Looks for: a compiled second stage dropped into the WordPress content tree — a coin miner, a tunneller, or the shared object behind a MySQL UDF privilege escalation. A web content directory is the wrong place for a binary under any circumstance. FP: a handful of legitimate plugins ship compiled helpers, and restore-from-backup operations rewrite the whole tree; exclude your backup product's restore window and any vendor/ subpath you have deliberately allowlisted.

// HUNT: New executable or ELF written under the WordPress web root
// MITRE: T1505.003, T1105, T1068 | CONF: medium  FP: medium  COST: low
// REQUIRES: ELFFileWritten (Linux) / NewExecutableWritten (Windows) on web hosts
// FALSE POSITIVES: plugins shipping compiled helpers, composer vendor trees, backup restores
// TUNING: exclude the ComputerName plus time window of scheduled restores, and any vendor subpath
// TUNING: you have formally allowlisted; pivot every survivor on ContextProcessId into Q1
// LOOKBACK: 30d (set in the console time picker)
#event_simpleName=/^(ELFFileWritten|NewExecutableWritten)$/
| TargetFileName=/wp-content\/(plugins|uploads|cache|themes|upgrade)\//i
| table([@timestamp, ComputerName, UserName, TargetFileName, ContextProcessId, aid])
| sort(@timestamp, order=desc, limit=200)
Q4 · PHP file modified under wp-content (file-integrity telemetry)
CONF MEDFP MEDCOST LOW

Looks for: the webshell drop itself. Read the caveat in the query header before you trust an empty result set: Falcon emits no generic write event for a PHP text file, so this query only returns data if a FileVantage or equivalent FIM policy covers the WordPress web root. On an estate without that policy it will be silent no matter how many webshells are present, and the host-side find in Section 7 is your only coverage of this step. FP: WordPress force-updated itself during this campaign, so legitimate core and plugin writes cluster in exactly the window you are hunting — correlate every hit against the parent lineage from Q1 before escalating.

// HUNT: WordPress web-root PHP file modified (webshell plugin drop) via file-integrity telemetry
// MITRE: T1505.003, T1036.005 | CONF: medium  FP: medium  COST: low
// REQUIRES: CriticalFileModified - needs a FileVantage/FIM policy covering the WordPress web root.
// REQUIRES: Falcon does NOT emit a generic write event for .php text files. Without that policy this
// REQUIRES: query returns nothing even on a compromised host - see Section 7 N3 for the host-side scan.
// FALSE POSITIVES: WordPress forced auto-updates and normal plugin installs write here constantly
// TUNING: exclude the wp-content/upgrade staging path and your maintenance window, then correlate the
// TUNING: survivors against Q1 on the same ComputerName - a write with no shell nearby is usually benign
// LOOKBACK: 30d (set in the console time picker)
#event_simpleName=/^CriticalFileModified$/
| TargetFileName=/wp-content\/(plugins|cache|uploads|themes)\/.*\.php$/i
| table([@timestamp, ComputerName, UserName, TargetFileName, aid])
| sort(@timestamp, order=desc, limit=200)
Q5 · Connection to a published wp2shell exploitation IP
CONF HIGHFP LOWCOST LOW

Looks for: any host in the estate talking to the five IPs Wiz published. Note the direction problem: these addresses were observed scanning and exploiting, so the inbound half of the conversation is what most organisations saw, and Falcon's NetworkConnectIP4 records the endpoint's outbound side. A hit here means your host reached out to them, which is a materially worse finding than being scanned by them. FP: low, but two of the five sit in hyperscaler ranges where addresses recycle within weeks — confirm the timestamp falls inside the campaign window before treating a hit as attribution.

// HUNT: Endpoint connecting outbound to a published wp2shell exploitation IP
// MITRE: T1071.001, T1190 | CONF: high  FP: low  COST: low
// REQUIRES: NetworkConnectIP4
// FALSE POSITIVES: cloud IP reassignment - two of these five are in hyperscaler ranges. A hit dated
// FALSE POSITIVES: well outside 2026-07-17..now is far more likely to be a recycled address.
// LOOKBACK: 30d (set in the console time picker)
#event_simpleName=/^NetworkConnectIP4$/
| in(RemoteAddressIP4, values=["34.81.132.62", "79.177.131.206", "15.157.135.170", "94.100.52.128", "172.235.128.52"])
| table([@timestamp, ComputerName, ContextBaseFileName, RemoteAddressIP4, RemotePort, aid])
| sort(@timestamp, order=desc, limit=200)
Q6 · Download utility launched by a web-server process
CONF HIGHFP MEDCOST LOW

Looks for: second-stage retrieval — the step between "I have a one-liner eval shell" and "I have a 150KB file manager with database access". Requiring a URL-ish token in the command line keeps this much quieter than matching the binaries alone. FP: plugin and theme update routines, composer, and npm all fetch over HTTPS as the web user; exclude fetches to your package mirror and to WordPress.org rather than excluding curl outright.

// HUNT: Download utility launched by a web-server process (second-stage retrieval)
// MITRE: T1105 | CONF: high  FP: medium  COST: low
// REQUIRES: ProcessRollup2 with CommandLine
// FALSE POSITIVES: plugin/theme update routines, composer and npm installs run as the web user
// TUNING: exclude CommandLine values pointing at your internal package mirror and at wordpress.org;
// TUNING: exclude CI/CD hosts by ComputerName. Keep curl and wget in scope - narrow the destination.
// LOOKBACK: 30d (set in the console time picker)
#event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2)$/
| ParentBaseFileName=/^(php|php-fpm[0-9.]*|php-cgi|httpd|apache2|nginx|sh|bash|dash|w3wp\.exe)$/i
| FileName=/^(curl|wget|fetch|bitsadmin\.exe|certutil\.exe|powershell\.exe|pwsh\.exe|python[0-9.]*)$/i
| CommandLine=/(http:|https:|ftp:|--output|-OutFile|Invoke-WebRequest|DownloadString|DownloadFile|urlcache)/i
| table([@timestamp, ComputerName, UserName, ParentBaseFileName, FileName, CommandLine, aid])
| sort(@timestamp, order=desc, limit=200)
Q7 · WordPress config or credential material touched from a web process tree
CONF MEDFP MEDCOST LOW

Looks for: the credential-access step. wp-config.php holds the database credentials and the WordPress authentication salts; the reported LFI against admin-ajax.php and the webshell's own file manager both target it, and the CMSmap-derived shell bundles a MySQL UDF privilege-escalation module that needs those credentials first. FP: backup and migration tooling reads wp-config.php and runs mysqldump as designed — exclude your backup binary and its scheduled window, then look hard at anything left over.

// HUNT: WordPress config or credential material accessed from a web-server process tree
// MITRE: T1552.001, T1005, T1068 | CONF: medium  FP: medium  COST: low
// REQUIRES: ProcessRollup2 with CommandLine
// FALSE POSITIVES: backup, staging and migration tools legitimately read wp-config.php and dump the DB
// TUNING: exclude your backup product's FileName and its nightly window by hour; exclude WP-CLI db
// TUNING: export jobs launched from an interactive admin session rather than from a web-server parent
// LOOKBACK: 30d (set in the console time picker)
#event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2)$/
| ParentBaseFileName=/^(php|php-fpm[0-9.]*|php-cgi|httpd|apache2|nginx|sh|bash|dash|w3wp\.exe|cmd\.exe)$/i
| CommandLine=/(wp-config\.php|\/etc\/passwd|\/etc\/shadow|mysqldump|DB_PASSWORD|AUTH_KEY|\.my\.cnf)/i
| table([@timestamp, ComputerName, UserName, ParentBaseFileName, FileName, CommandLine, aid])
| sort(@timestamp, order=desc, limit=200)
Q8 · Persistence created from a web-server process tree
CONF HIGHFP LOWCOST LOW

Looks for: a webshell converting a transient foothold into something that survives a plugin cleanup — a cron entry, a systemd unit, a scheduled task, or a new local account. The web-server user should essentially never invoke these. A hit here is one of the strongest single signals in the pack. FP: low; some shared-hosting control panels register cron as the web user, which is worth confirming once per estate and then documenting.

// HUNT: Persistence mechanism created from a web-server process tree
// MITRE: T1053.003, T1053.005, T1543, T1136.001 | CONF: high  FP: low  COST: low
// REQUIRES: ProcessRollup2 with parent lineage
// FALSE POSITIVES: rare - some shared-hosting control panels register cron jobs as the web user
// LOOKBACK: 30d (set in the console time picker)
#event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2)$/
| ParentBaseFileName=/^(php|php-fpm[0-9.]*|php-cgi|httpd|apache2|nginx|sh|bash|dash|w3wp\.exe|cmd\.exe|powershell\.exe)$/i
| FileName=/^(crontab|at|systemctl|systemd-run|schtasks\.exe|sc\.exe|reg\.exe|useradd|adduser|usermod|chattr|ssh-keygen)$/i
| table([@timestamp, ComputerName, UserName, ParentBaseFileName, FileName, CommandLine, aid])
| sort(@timestamp, order=desc, limit=200)
Q9 · Published wp2shell webshell SHA1 in endpoint telemetry
CONF LOWFP LOWCOST LOW

Looks for: the five SHA1 hashes Wiz published. Rated CONF LOW deliberately, and the reason is structural rather than a data-quality hedge: these are hashes of PHP source files. PHP is interpreted, so a webshell never becomes a process image and normally never appears in ProcessRollup2 or ImageHash. Run this because it costs nothing and a hit would be decisive, but do not read a clean result as absence of the webshell. The hashes belong in the filesystem scan in Section 7 N3. FP: none expected — treat any hit as a confirmed incident.

// HUNT: Published wp2shell webshell SHA1 observed in endpoint process telemetry
// MITRE: T1505.003 | CONF: low  FP: low  COST: low
// REQUIRES: ProcessRollup2 / ImageHash
// NOTE: these five hashes are PHP webshell SOURCE FILES. PHP is interpreted, not executed as a process
// NOTE: image, so they will normally never surface here. A clean result proves nothing. The authoritative
// NOTE: check is the sha1sum sweep in Section 7 N3; Falcon Custom IOC Management cannot ingest SHA1.
// FALSE POSITIVES: none expected - any hit is a confirmed incident
// LOOKBACK: 90d (set in the console time picker)
#event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2|ImageHash)$/
| SHA1HashData=/^(2a1410d8e2a8337ac2171cedea8c0fdc47c647a0|58eca847e9eae9e6b08cc211f1559817b71bc4cc|ebea44890f434d5d67ede22009a3f4bb5cac33f8|d9a220c8039f1c4d72cae7ccb8b3a33dec8815be|e9756e2338f84746007235e4cab7a70d5b3ca47f)$/i
| table([@timestamp, ComputerName, UserName, FileName, SHA1HashData, CommandLine, aid])
| sort(@timestamp, order=desc, limit=100)
Q10 · Rare child processes of web-server parents (baseline sweep)
CONF MEDFP HIGHCOST MED

Looks for: everything Q1 and Q2 did not think of. Instead of an allowlist of suspicious binaries this ranks every child of a web-server parent by rarity and sorts ascending, so the payloads nobody has named yet float to the top. Run it once to build the estate baseline, then re-run weekly and read only the new arrivals. FP: high by construction — this is a stack-ranking hunt, not an alert, and it must never be promoted to a Scheduled Search without a hard exclusion list.

// HUNT: Rare child processes of web-server parents (catch-all for unnamed webshell payloads)
// MITRE: T1190, T1505.003 | CONF: medium  FP: high  COST: medium
// REQUIRES: ProcessRollup2 across every web host in the estate
// FALSE POSITIVES: high by design. The common rows are your normal; the sparse tail is the signal.
// TUNING: read only rows where Hosts is 1 or 2 - a child seen fleet-wide is infrastructure, not an
// TUNING: intrusion. Once baselined, exclude your CMS auto-update and backup binaries by FileName.
// TUNING: never promote this query to a Scheduled Search without that exclusion list in place.
// LOOKBACK: 30d (set in the console time picker)
#event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2)$/
| ParentBaseFileName=/^(php|php-fpm[0-9.]*|php[0-9.]*-fpm|php-cgi|httpd|apache2|nginx|lsphp|litespeed|w3wp\.exe)$/i
| groupBy([FileName], function=[count(as=Execs), count(ComputerName, distinct=true, as=Hosts), collect([CommandLine], limit=5)], limit=500)
| sort(Execs, order=asc, limit=120)
09

Custom IOA Recommendations & Alert Packaging

Custom IOA candidates

IOA namePatternExclusionsActionFrom
WEBSHELL — Web Server Spawns ShellProcess creation where the parent image is php-fpm, php, httpd, apache2, nginx, lsphp or w3wp.exe and the child image is a shell or scripting interpreterDeploy and backup service accounts; CI/CD hosts; documented control-panel helpersDetect (Critical) on Linux web host groups; start in Monitor for 7 daysQ1
WEBSHELL — Web Server Persistence AttemptParent image is a web-server or PHP process and the child image is crontab, systemctl, systemd-run, schtasks.exe, sc.exe, useradd or usermodControl panels that register cron as the web user (document per host group)Detect (High) — very low volume, safe to promote quicklyQ8
WEBSHELL — Web Server Credential File AccessParent image is a web-server or PHP process and the command line references wp-config.php, /etc/shadow or mysqldumpBackup product binary and its scheduled windowDetect (High) after one baseline cycleQ7
WEBSHELL — Web Server Retrieves Remote PayloadParent image is a web-server or PHP process, child is curl/wget/certutil.exe, command line contains a URL schemeInternal package mirror; wordpress.org update endpointsMonitor first — noisier than the other threeQ6

Not IOA candidates: Q2, Q3, Q4, Q5, Q9 and Q10 stay Investigate-only. Q2 and Q10 are volume hunts, Q3 and Q4 depend on FIM policy coverage that varies by host group, Q5 is a perishable atomic-IOC sweep better handled by IOC Management, and Q9 is structurally unlikely to fire at all. Promoting any of them without the tuning in their cards will generate noise that trains analysts to ignore the whole rule family.

Scheduled Search packaging (for the promoted IOAs)

FieldValue
Scheduled Search nameDETECT — Web Server Spawns Shell (wp2shell / CVE-2026-63030)
DescriptionFires when a PHP interpreter or web-server worker becomes the parent of a shell or scripting interpreter. This is the execution signature of every wp2shell webshell family reported to date and of web shells generally, so it retains value long after this campaign.
ScheduleEvery 15 minutes, 30-minute lookback with overlap
Alert subject[CRITICAL] Detection: wp2shell webshell execution — web server spawned a shell

Alert email body (template)

WHAT FIRED
A web-server or PHP process spawned an interactive shell or scripting interpreter
on a host in the WordPress server group.

  Host          : {{ComputerName}}
  User          : {{UserName}}
  Parent        : {{ParentBaseFileName}}
  Child         : {{FileName}}
  Command line  : {{CommandLine}}
  First seen    : {{@timestamp}}

WHAT IT MEANS
A healthy web server almost never launches a shell. This parent-child pair is the
execution signature of a web shell. Given active in-the-wild exploitation of
wp2shell (CVE-2026-63030 + CVE-2026-60137) against WordPress Core, treat this as a
probable post-exploitation event until proven otherwise.

IMMEDIATE ACTIONS
  1. Do not reboot or reimage yet - capture volatile state first.
  2. Network-contain the host in Falcon if the command line shows recon,
     credential access, or an outbound download.
  3. Confirm the WordPress version. Anything below 6.8.6 / 6.9.5 / 7.0.2 is
     vulnerable and the host should be treated as compromised.
  4. Run the Section 7 N3 filesystem hunt and the N4 database hunt on this host.
  5. Preserve the web-server access log before rotation removes the request that
     triggered the shell.

ESCALATION
  Confirmed webshell    -> incident response, severity 1
  Unexplained but clean -> hunt lead, 24h follow-up
  Matched exclusion     -> tune the rule, record the exclusion, close

L1 triage checklist

  1. Record host, user, parent, child and full command line from the detection before touching anything.
  2. Check the WordPress version on the host. Vulnerable version plus this alert equals a presumed compromise.
  3. Pivot the host into Q2, Q6, Q7 and Q8 scoped to the same ComputerName and a window of a few hours either side. A lone shell is ambiguous; a shell followed by whoami and curl is not.
  4. Run Q5 for the same host to see whether it reached any published exploitation IP.
  5. Run the Section 7 N3 filesystem hunt on the host — recently modified PHP, PHP in uploads or cache, and the SHA1 sweep.
  6. Run the Section 7 N4 database hunt — list administrators and reconcile against your roster.
  7. Grep the web-server access log for the batch endpoint and the three user agents, restricted to the hours before the alert.
  8. If two or more of steps 5 to 7 return findings, escalate to incident response and move to the Section 14 containment runbook. If all are clean and the parent command line matches a documented exclusion, tune and close with the exclusion recorded.
10

Machine-Readable IOC Appendix

Falcon IOC Management CSVbulk import
type,value,action,severity,expiration,description,tags
ipv4,34.81.132.62,detect,high,2026-10-24,wp2shell exploitation source,campaign:wp2shell
ipv4,79.177.131.206,detect,high,2026-10-24,wp2shell exploitation source,campaign:wp2shell
ipv4,15.157.135.170,detect,high,2026-10-24,wp2shell exploitation source,campaign:wp2shell
ipv4,94.100.52.128,detect,high,2026-10-24,wp2shell exploitation source,campaign:wp2shell
ipv4,172.235.128.52,detect,high,2026-10-24,wp2shell mass-scanning source,campaign:wp2shell
sha256,REPLACE_WITH_SHA256_OF_YOUR_OWN_RECOVERED_WEBSHELL,prevent,critical,2027-01-24,wp2shell PHP webshell recovered locally,campaign:wp2shell
# The five published webshell hashes are SHA1. Falcon Custom IOC Management accepts
# sha256 and md5 only, so these CANNOT be imported here. Run them against the
# filesystem sweep in Section 7 N3 and via Q9, and if you recover a sample locally,
# compute its SHA256 and replace the placeholder row above.
# 2a1410d8e2a8337ac2171cedea8c0fdc47c647a0
# 58eca847e9eae9e6b08cc211f1559817b71bc4cc
# ebea44890f434d5d67ede22009a3f4bb5cac33f8
# d9a220c8039f1c4d72cae7ccb8b3a33dec8815be
# e9756e2338f84746007235e4cab7a70d5b3ca47f
Behavioural signaturesdurable — survives IOC rotation
PROCESS LINEAGE
  php | php-fpm | php-cgi | httpd | apache2 | nginx | lsphp | w3wp.exe
    -> sh | bash | dash | zsh | busybox | python | perl | cmd.exe | powershell.exe
  same parents -> whoami | id | uname | hostname | ss | netstat
  same parents -> curl | wget | certutil.exe   (command line contains a URL)
  same parents -> crontab | systemctl | schtasks.exe | useradd

FILESYSTEM
  new or modified *.php under wp-content/plugins, wp-content/cache, wp-content/uploads
  plugin directory matching wp2shell_<hex> or <plausible-name>-<6 hex chars>
  ~1.3 KB PHP file carrying a fake "Author: WordPress.org Community" plugin header
  ~150 KB obfuscated PHP plugin (CMSmap-derived: file manager, DB access,
    port scanner, MySQL UDF privilege escalation)
  PHP calling shell_exec | passthru | proc_open | popen on decoded input
  webshell command parameters observed: c=<command> and b=<base64 command>

HTTP  (web / WAF / proxy logs only - NOT visible to the endpoint sensor)
  POST /wp-json/batch/v1        with HTTP 207 or 200 Multi-Status response
  POST /?rest_route=/batch/v1   with HTTP 207 or 200 Multi-Status response
  User-Agent contains: wp2shell | rezwp2shell | cve-2026-63030/1.0
  follow-on: /wp-admin/plugin-install.php?tab=upload
  follow-on: POST /wp-admin/update.php?action=upload-plugin
  follow-on: /wp-json/wp/v2/users?context=edit
  follow-on: admin-ajax.php with a traversal parameter aimed at wp-config

DATABASE
  wp_users rows with user_login prefix wp2_ | w2s_ | wpsvc_ and administrator role
  administrator accounts on email domains you do not own
  gaps in the wp_users ID sequence; orphaned wp_usermeta rows
  oEmbed cache rows in wp_options pointing at loopback
Named tooling & attributioncontext
CAMPAIGN   wp2shell
CVES       CVE-2026-63030  REST API batch-route confusion   (CVSS 9.8 Tenable / 7.5 Rapid7)
           CVE-2026-60137  WP_Query author__not_in SQLi      (CVSS 5.9 Tenable)
DISCOVERY  Adam Kues, Assetnote (Searchlight Cyber), via the WordPress HackerOne programme
DISCLOSED  2026-07-17          EXPLOITED ITW  from 2026-07-17, confirmed by 2026-07-20
KEV        both CVEs added to CISA KEV 2026-07-21; remediation deadline 2026-07-24

VULNERABLE   6.8.0 - 6.8.5  SQL injection only
             6.9.0 - 6.9.4  full RCE chain
             7.0.0 - 7.0.1  full RCE chain
PATCHED      6.8.6 | 6.9.5 | 7.0.2 | 7.1 beta 2   (WordPress.org forced auto-updates)

ROOT CAUSE  The batch API validates and executes sub-requests in two separate loops.
            A failure in wp_parse_url() on one sub-request path is pushed to the
            validation array but not the matches array, desynchronising the two so
            every later sub-request dispatches under a different request's handler.

WEBSHELL FAMILIES OBSERVED
  1. one-liner PHP eval backdoor, returns HTTP 404 to casual probes
  2. ~150 KB obfuscated CMSmap-derived plugin: file manager, DB access,
     port scanner, MySQL UDF privilege escalation
  3. plugin registering a custom REST endpoint that accepts base64 commands via POST

CAVEAT  Sites backed by a persistent object cache (Redis / Memcached) may not be
        exploitable via this specific RCE path. The SQL injection still is.
        Do not treat an object cache as a mitigation.
Patch & exposure auditrun first
# 1. Enumerate WordPress versions across the estate
find / -name version.php -path '*wp-includes*' 2>/dev/null \
  | while read -r f; do printf '%s ' "$f"; grep -m1 wp_version "$f"; done

# 2. With WP-CLI, per site root
wp core version --allow-root
wp core check-update --allow-root
wp user list --role=administrator --fields=ID,user_login,user_email,user_registered --allow-root
wp plugin list --fields=name,status,version --allow-root

# 3. Confirm the batch endpoint is actually blocked, both routing forms.
#    A 403 from the edge is the pass condition; 200 or 207 means still exposed.
curl -sk -o /dev/null -w '%{http_code} wp-json form\n'  https://SITE/wp-json/batch/v1
curl -sk -o /dev/null -w '%{http_code} rest_route form\n' 'https://SITE/?rest_route=/batch/v1'

# 4. Confirm PHP execution is denied where user content lands
curl -sk -o /dev/null -w '%{http_code} uploads\n' https://SITE/wp-content/uploads/probe.php

# 5. Falcon sensor coverage - every WordPress host must have one.
#    Any site on managed hosting with no sensor is an accepted, documented blind spot.

Provenance: every atomic indicator above (five IPv4 addresses, five SHA1 hashes) appears verbatim in the saved source snapshot 01-wiz.txt. The SHA256 row is a labelled placeholder because no SHA256 was published — it is deliberately not filled with a guess. Everything else in this appendix is a behavioural pattern or a configuration check, not an atomic claim.

11

Detection Validation Gates

GateCheckPass condition
1 · Telemetry readyRun #event_simpleName=/^ProcessRollup2$/ | groupBy([ComputerName], limit=500) restricted to your WordPress host groupEvery WordPress host you believe you own appears. Any missing host is a blind spot, not a clean result — record it before proceeding.
2 · Parent lineage populatedConfirm ParentBaseFileName is populated on those hosts, not emptyQ1, Q2, Q6, Q7, Q8 and Q10 all key on the parent. If it is blank on containerised hosts, every one of them is silently useless there.
3 · FIM coverageConfirm whether a FileVantage or equivalent policy covers the WordPress web rootEither it does and Q4 is live, or it does not and Q4 is formally recorded as a gap covered by the Section 7 N3 host scan. An untested silent query is the worst of the three outcomes.
4 · Benign baselineRun Q1 and Q10 over 30 days before alertingYou can name every recurring parent-child pair. Undocumented pairs are hunt leads, and the exclusion list for the IOAs is derived from this run, not invented up front.
5 · Positive testOn an isolated non-production WordPress host, have an authorised operator invoke a benign command through the normal PHP execution path (for example a scheduled maintenance script that calls id) and confirm Q1 and Q2 return itBoth queries return the event within the expected latency. This validates the detection path without creating or using any exploit code.
6 · Web log pathConfirm HTTP access logs from every WordPress host and from the WAF reach a searchable platformThe Section 7 N1 and N2 hunts return data. Without this, chain steps 1 and 2 are permanently invisible and the pack's coverage tops out at post-exploitation.
7 · PromotionMove Q1, Q7 and Q8 to Scheduled Searches in Monitor mode for 7 days, then to DetectFewer than roughly 5 unexplained hits per week per 100 web hosts. Above that, tune before promoting rather than raising the threshold.
12

Hardening — Tiered

Immediate — this week, no compatibility risk

H1 · Patch WordPress Core to 6.8.6, 6.9.5 or 7.0.2 (or later). MITRE M1051 Update Software. This is the only complete fix; every other control on this page buys time. WordPress.org pushed forced auto-updates, so most sites are already patched — your job is to prove it site by site rather than assume it. Verify with wp core version or the version enumeration in the Section 10 audit block.

H2 · Block both routing forms of the batch endpoint at the WAF or reverse proxy. MITRE M1037 Filter Network Traffic. Deny /wp-json/batch/v1 and ?rest_route=/batch/v1. Blocking only the first is the most common mistake and leaves the endpoint fully reachable — SANS ISC caught the second form in live honeypot traffic. Verify with the two curl probes in the Section 10 audit block; a 403 on both is the pass condition. Playbook P1.

H3 · Deny PHP execution under wp-content/uploads and wp-content/cache. MITRE M1022 Restrict File and Directory Permissions. Neither directory has any legitimate reason to serve executable PHP, and SANS ISC observed the backdoor written into the cache directory specifically. This single control neutralises a whole class of drop location. Playbook P1.

H4 · Set DISALLOW_FILE_MODS in wp-config.php. MITRE M1042 Disable or Remove Feature. Kills the plugin-upload path (/wp-admin/update.php?action=upload-plugin) that the campaign uses to install its webshell as a plugin, even for an account that has forged its way to administrator. TRADE-OFF this also disables in-dashboard updates, so pair it with a deployment pipeline that patches out of band — otherwise you trade a webshell risk for an unpatched-CMS risk. Playbook P2.

H5 · Audit and reset administrator accounts on every WordPress install. MITRE M1026 Privileged Account Management, M1018 User Account Management. The exploit forges an administrator directly in the database, so patching does not remove one that already exists. Reconcile the administrator list against your roster, delete anything unrecognised, and force a password reset estate-wide. Section 7 N4.

Near term — 1 to 4 weeks, pilot on a ring first

H6 · Make the web root read-only to the PHP worker except for the specific paths that need writes. MITRE M1022. If php-fpm cannot write to wp-content/plugins, the webshell has nowhere to land. Grant write access only to uploads (which H3 has already made non-executable) and to the upgrade staging path during maintenance. TRADE-OFF breaks in-dashboard plugin installation, which is the point; pair with H4 and a deployment pipeline. Playbook P3.

H7 · Egress-filter the web tier. MITRE M1037, M1030 Network Segmentation. A public web server should reach a package mirror, an update endpoint and its own database — nothing else. This breaks the second-stage download in chain step 8 and the C2 channel in step 11 even when the webshell itself succeeds, and it is what makes Q5 and Q6 actionable rather than merely observational.

H8 · Complete Falcon sensor coverage on every WordPress host, including containers, and enable FIM on the web root. Detection prerequisite, not a MITRE mitigation. Validation gates 1 to 3 in Section 11 are the acceptance test. Without this the entire CQL section is theoretical. ⚠ best-practice, no formal benchmark

H9 · Enforce multi-factor authentication on all WordPress administrator accounts. MITRE M1032 Multi-factor Authentication. Does not stop this specific chain — the forged account bypasses login entirely — but it closes the credential-reuse path that follows every wp-config.php disclosure, and the campaign explicitly targets that file.

H10 · Subscribe the WordPress estate to an inventory with version attestation. MITRE M1051 supporting control. The reason this CVE hurt was not difficulty of patching; it was not knowing which sites existed. Shadow WordPress installs on marketing subdomains are the standard finding in this campaign's post-mortems. ⚠ best-practice, no formal benchmark

Strategic — 1 to 3 months, architectural

H11 · Segment the web tier from the database and from the internal network. MITRE M1030 Network Segmentation. Assume the next pre-auth WordPress RCE lands too. The question that decides the blast radius is what the compromised web server can reach — the database it needs, and nothing else.

H12 · Move WordPress to an immutable, containerised deployment with a read-only root filesystem. MITRE M1022, M1042. Rebuild rather than clean. A webshell that cannot be written does not need to be found, and Eye Security's log-blind finding means finding it reliably is genuinely hard. This also converts remediation from forensic archaeology into a redeploy. ⚠ best-practice, no formal benchmark for WordPress specifically; follow CIS Docker/Kubernetes Benchmark guidance for the runtime.

H13 · Establish an emergency CMS patching SLA with virtual patching as the bridge. MITRE M1051, M1037. The defining fact of this campaign is timing: exploitation began the same day as disclosure and public proof-of-concept code appeared within hours, which vendors attributed in part to AI-assisted tooling. A patch cycle measured in weeks is no longer a control. Target managed WAF rule deployment within hours and core patching within 72 hours for internet-facing CMS.

13

Deployable Playbooks

P1 · Web-server configuration — block the batch endpoint, deny PHP in content directories

Apply on every reverse proxy and web server fronting WordPress, then reload. Test on one node before fleet rollout.

nginx

# /etc/nginx/snippets/wp2shell-mitigation.conf
# 1. Block the REST batch endpoint - BOTH routing forms.
location = /wp-json/batch/v1 { return 403; }
location ^~ /wp-json/batch/  { return 403; }

# The query-string routing form needs a map, since location does not match args.
# Place the map block in the http{} context:
#   map $arg_rest_route $wp2shell_batch { default 0; "~*^/batch/v1" 1; }
# then inside server{}:
if ($wp2shell_batch) { return 403; }

# 2. Deny PHP execution where user content and cache files land.
location ~* /wp-content/uploads/.*\.php$ { deny all; return 403; }
location ~* /wp-content/cache/.*\.php$   { deny all; return 403; }

# 3. Optional: block the three published exploitation user agents outright.
if ($http_user_agent ~* "(wp2shell|rezwp2shell|cve-2026-63030)") { return 403; }

Apache 2.4

# /etc/apache2/conf-available/wp2shell-mitigation.conf
<LocationMatch "^/wp-json/batch/v1">
    Require all denied
</LocationMatch>

# Query-string routing form
RewriteEngine On
RewriteCond %{QUERY_STRING} (^|&)rest_route=/batch/v1 [NC]
RewriteRule .* - [F,L]

# Deny PHP execution in user-content directories
<DirectoryMatch "^/var/www/.*/wp-content/(uploads|cache)/">
    <FilesMatch "\.(php|phtml|php[0-9]|phar)$">
        Require all denied
    </FilesMatch>
</DirectoryMatch>

# Block the published exploitation user agents
RewriteCond %{HTTP_USER_AGENT} (wp2shell|rezwp2shell|cve-2026-63030) [NC]
RewriteRule .* - [F,L]

Verify: nginx -t or apachectl configtest, reload, then run probes 3 and 4 from the Section 10 audit block. Both batch forms must return 403 and the uploads probe must not execute.

P2 · WordPress configuration hardening

// Add to wp-config.php ABOVE the "That's all, stop editing" line.

// Block plugin/theme install and edit from the dashboard. This closes the
// upload-plugin path the campaign uses to install its webshell, even for an
// account that has already forged its way to administrator.
define( 'DISALLOW_FILE_MODS', true );
define( 'DISALLOW_FILE_EDIT', true );

// Keep core security updates flowing even with FILE_MODS locked down.
define( 'WP_AUTO_UPDATE_CORE', 'minor' );

// Rotate every salt after a suspected compromise - this invalidates all
// existing sessions, including any the attacker holds.
// Generate fresh values at https://api.wordpress.org/secret-key/1.1/salt/

Verify: the Plugins and Themes screens should no longer offer Add New or the file editor. Confirm with wp plugin install hello-dolly --allow-root, which must fail.

P3 · Filesystem permissions — deny the PHP worker write access to the code tree

#!/usr/bin/env bash
# Run per site root. Assumes the PHP worker runs as www-data and a separate
# deploy account owns the code. Adjust names to your environment.
set -euo pipefail
SITE_ROOT="/var/www/example-site"     # <-- set this
DEPLOY_USER="deploy"
WEB_USER="www-data"

# Code tree owned by the deploy account, readable but NOT writable by PHP
chown -R "${DEPLOY_USER}:${WEB_USER}" "${SITE_ROOT}"
find "${SITE_ROOT}" -type d -exec chmod 750 {} +
find "${SITE_ROOT}" -type f -exec chmod 640 {} +

# Uploads is the one place PHP legitimately writes. H3 has already made it
# non-executable at the web-server layer, so a dropped .php cannot be reached.
chown -R "${WEB_USER}:${WEB_USER}" "${SITE_ROOT}/wp-content/uploads"
find "${SITE_ROOT}/wp-content/uploads" -type d -exec chmod 750 {} +

# wp-config.php holds the DB credentials and the auth salts - tighten it hard.
chmod 400 "${SITE_ROOT}/wp-config.php"
chown "${DEPLOY_USER}:${WEB_USER}" "${SITE_ROOT}/wp-config.php"
chmod 440 "${SITE_ROOT}/wp-config.php"

echo "Verify: sudo -u ${WEB_USER} touch ${SITE_ROOT}/wp-content/plugins/probe"
echo "        that command MUST fail with Permission denied."

Pilot this one. Plugins that write into their own directory at runtime will break. Roll to a single non-production site first, exercise the full plugin set, and keep the exception list explicit rather than reverting the whole control.

P4 · Triage collection script (read-only)

#!/usr/bin/env bash
# Read-only evidence collection for a suspected wp2shell compromise.
# Collects only - changes nothing. Run before any cleanup.
set -uo pipefail
SITE_ROOT="${1:-/var/www}"
OUT="/var/tmp/wp2shell-triage-$(hostname)-$(date +%Y%m%d%H%M%S)"
mkdir -p "$OUT"

echo "[*] WordPress versions"
find "$SITE_ROOT" -name version.php -path '*wp-includes*' \
  -exec grep -H -m1 wp_version {} + | tee "$OUT/versions.txt"

echo "[*] PHP files changed since the day before disclosure"
find "$SITE_ROOT" -name '*.php' -newermt '2026-07-16' \
  -printf '%T+ %s %p\n' | sort | tee "$OUT/recent-php.txt"

echo "[*] PHP where PHP should not be"
find "$SITE_ROOT" -path '*wp-content/uploads/*' -name '*.php' | tee "$OUT/php-in-uploads.txt"
find "$SITE_ROOT" -path '*wp-content/cache/*'   -name '*.php' | tee -a "$OUT/php-in-uploads.txt"

echo "[*] SHA1 of every PHP file - compare against the Section 10 hash list"
find "$SITE_ROOT" -name '*.php' -type f -exec sha1sum {} + | tee "$OUT/php-sha1.txt"

echo "[*] Shell-out primitives in PHP"
grep -rlE 'shell_exec|passthru|proc_open|popen|base64_decode\s*\(\s*\$' \
  "$SITE_ROOT" --include='*.php' | tee "$OUT/php-suspicious.txt"

echo "[*] Access-log hits"
grep -aEi 'batch/v1|rest_route=/batch/v1|wp2shell|rezwp2shell|cve-2026-63030' \
  /var/log/nginx/access.log* /var/log/apache2/access.log* 2>/dev/null \
  | tee "$OUT/access-hits.txt"

echo "[*] Cron for the web user"
crontab -l -u www-data 2>/dev/null | tee "$OUT/webuser-cron.txt"

echo "[+] Collected to $OUT - preserve this before remediating."
14

Containment Runbook

PhaseActionsOwnerEvidence to capture
0 · Triage
0–30 min
Confirm the WordPress version on the alerting host. Confirm whether the site was internet-reachable during 2026-07-17 to patch time. Decide compromised versus exposed — a vulnerable version plus a Q1 hit is a compromise until disproven.SOC L1Detection record, version output, exposure window
1 · Isolate
30–60 min
Network-contain the host in Falcon. Do not reboot, do not reimage, do not delete the plugin directory yet. If the site must stay up, fail over to a known-good build from before 2026-07-17 rather than cleaning in place.SOC L2Containment timestamp; running process list; established connections
2 · Preserve
1–3 h
Run playbook P4. Snapshot the web root and take a database dump. Copy access logs before rotation. Preserve the volume snapshot if the host is a cloud instance.IRP4 output bundle, DB dump, access logs, disk/volume snapshot
3 · Scope
3–8 h
Run Q1, Q2, Q6, Q7, Q8 across the whole web host group, not just the alerting host — the same scanner hit everything you expose. Run Q5 estate-wide. Run Section 7 N3 and N4 on every WordPress host. Reconcile administrator accounts across all sites.IR + HuntPer-host findings matrix; list of every host with any hit
4 · Eradicate
8–24 h
Rebuild from a known-good artefact — do not clean in place. Eye Security's log-blind finding means you cannot prove you found everything. Patch to 6.8.6 / 6.9.5 / 7.0.2, apply P1, P2 and P3, delete rogue administrators, rotate every wp-config.php salt and the database password, and rotate any credential that file exposed.Platform + IRRebuild record, patch attestation, credential rotation log
5 · Recover
24–72 h
Restore service from the rebuilt artefact behind the P1 WAF rules. Force a password reset for every WordPress account. Re-run the Section 10 audit block to confirm the batch endpoint returns 403 and uploads will not execute PHP.PlatformAudit block output showing 403 on both routing forms
6 · Monitor
72 h – 30 d
Promote Q1, Q7 and Q8 to Scheduled Searches per Section 9. Keep Q5's IOC entries live to their 2026-10-24 expiry. Re-run Q10 weekly and read only the new arrivals. Re-run Section 7 N3 and N4 at day 7 and day 30 — a second webshell dropped through a persistence mechanism you missed will surface there.SOCScheduled Search hit counts; weekly Q10 delta

Do not clean in place. Three distinct webshell families were reported, one of them a ~150 KB obfuscated plugin with a file manager and a MySQL UDF privilege-escalation module, and the exploit writes to the database as readily as to disk. Deleting the plugin directory you found is not eradication. Rebuild from a known-good artefact and restore only content, never code.

15

Detection Coverage Map

TechniqueBehaviourCQLIOACoverage
T1190Exploitation request to the REST batch endpointGAP Web/WAF logs only (Section 7 N1, N2). No endpoint telemetry exists for this step.
T1190 / T1505.003SQL injection through author__not_in; database writesGAP Database-layer only. Explicitly log-blind per Eye Security. Section 7 N4.
T1136.001Administrator account forged directly in wp_usersGAP Native DB hunt (Section 7 N4). Falcon has no visibility into WordPress application accounts.
T1505.003Webshell PHP file written under wp-contentQ4PARTIAL Conditional on FIM policy coverage of the web root. Without it, Section 7 N3 is the only coverage.
T1105 / T1068Compiled second stage or UDF shared object dropped in the web rootQ3GOOD
T1059.004 / T1059.003Web-server process spawns a shell or interpreterQ1, Q10GOOD Primary detection. Fires on every reported webshell family.
T1033 / T1082 / T1046Discovery commands under a web-server parentQ2GOOD
T1105Second-stage download by a web-server processQ6GOOD
T1552.001 / T1005wp-config.php and credential material accessedQ7GOOD
T1053.003 / T1053.005 / T1543Persistence from a web-server process treeQ8GOOD
T1071.001Outbound to published exploitation infrastructureQ5PARTIAL Perishable. Five IPs, two in hyperscaler ranges, 3-month expiry.
T1505.003Known webshell file hash present on diskQ9GAP Structural: PHP source files do not appear in process telemetry, and Falcon Custom IOC does not accept SHA1. Covered by the Section 7 N3 sha1sum sweep.
T1027 / T1036.005Obfuscated plugin, fake author header, 404-returning shellQ10PARTIAL Rarity ranking catches unnamed payloads by their behaviour, not their disguise.

Honest read on this map. Four of the eleven attack-chain steps have no endpoint coverage at all, and that is a property of the threat rather than a shortfall in the queries: everything before the webshell executes happens inside HTTP request handling and inside the database, where an EDR sensor has no visibility. The pack is therefore strong from chain step 5 onward and blind before it. Two consequences follow. First, the web-log and database hunts in Section 7 are not optional garnish — they are the only coverage for the first half of the intrusion. Second, an estate that has centralised its HTTP logs will detect this campaign hours earlier than one relying on endpoint telemetry alone, which makes validation gate 6 the highest-leverage item in Section 11. Validation: gates 1 to 3 must pass before any result from this pack is trusted; gate 4 must complete before Q1, Q7 or Q8 are promoted to Detect.

16

Hunt Summary Ticket

TITLE       wp2shell - WordPress Core pre-auth RCE chain (CVE-2026-63030 + CVE-2026-60137)
SEVERITY    Critical - unauthenticated RCE, exploited in the wild, on CISA KEV

SCOPE       Every WordPress install in the estate, internet-facing first, including
            shadow installs on marketing subdomains and any managed/third-party hosting.
            Vulnerable: 6.8.0-6.8.5 (SQLi only), 6.9.0-6.9.4 and 7.0.0-7.0.1 (full RCE).
            Patched: 6.8.6 | 6.9.5 | 7.0.2. Hunt window: 2026-07-17 to the moment each
            site actually patched.

HYPOTHESIS  An unauthenticated attacker reached /wp-json/batch/v1 or /?rest_route=/batch/v1,
            chained route confusion into the author__not_in SQL injection, forged an
            administrator, and installed a PHP webshell as a plugin. If so, the web-server
            or PHP process on that host will have spawned a shell, run discovery commands,
            or established persistence.

QUERIES     Q1  Web server spawns shell or interpreter          PRIMARY
            Q2  Discovery commands under web-server parent      PRIMARY
            Q3  Executable or ELF written under the web root
            Q4  PHP modified under wp-content (needs FIM)       CONDITIONAL
            Q5  Outbound to published exploitation IPs
            Q6  Download utility launched by web server
            Q7  wp-config / credential material accessed
            Q8  Persistence created from web-server tree
            Q9  Published webshell SHA1 in telemetry            LOW YIELD BY DESIGN
            Q10 Rare children of web-server parents             BASELINE SWEEP

DO FIRST    1. Section 10 audit block - enumerate WordPress versions estate-wide.
            2. Q1 and Q2 over 30 days across the web host group.
            3. Section 7 N1 - grep web logs for the batch endpoint and the three UAs.
            4. Section 7 N4 - reconcile administrator accounts on every site.
            Steps 1 and 3 do not require Falcon and can start immediately.

FINDINGS    [ ] Unpatched WordPress instances found:      ____
            [ ] Q1 / Q2 hits requiring triage:            ____
            [ ] Rogue administrator accounts found:       ____
            [ ] Webshell files recovered:                 ____
            [ ] Batch-endpoint requests in web logs:      ____

GAPS        - No endpoint telemetry for chain steps 1-4 (HTTP request handling and
              database writes). Web/WAF logs and DB queries are the only coverage.
            - Q4 returns nothing without a FileVantage/FIM policy on the web root.
            - Managed/third-party hosting has no sensor - provider attestation only.
            - The five SHA1 hashes cannot be imported to Falcon Custom IOC Management.

ACTIONS     [ ] Patch to 6.8.6 / 6.9.5 / 7.0.2                       H1  Immediate
            [ ] Block BOTH batch-endpoint routing forms at the WAF   H2  Immediate
            [ ] Deny PHP execution in uploads and cache              H3  Immediate
            [ ] DISALLOW_FILE_MODS in wp-config.php                  H4  Immediate
            [ ] Audit and reset administrator accounts               H5  Immediate
            [ ] Import the Section 10 IOC CSV (5 IPv4, detect)
            [ ] Promote Q1, Q7, Q8 to Scheduled Search (Monitor 7d)

OWNER       HuntPack
VERSION     v0.1 - 2026-07-24
17

Changelog

v0.12026-07-24Initial pack. Ten CQL hunt queries built from twelve saved source snapshots. Hunt thesis is endpoint-side webshell behaviour (web-server process lineage) rather than HTTP request indicators, which the Falcon sensor cannot see; the request-level and database-level indicators are carried as native audit-log hunts in Section 7 and recorded as coverage gaps in Section 15. Five IPv4 indicators shipped as detect with a deliberately short 3-month expiry; five SHA1 webshell hashes routed to a filesystem sweep because Falcon Custom IOC Management does not accept SHA1. CVSS disagreement between Rapid7 (7.5) and Tenable/Coalition (9.8) recorded rather than resolved.
18

References

TierSourceUsed forSnapshotAccessed
1Wiz Research — Exploitation in the wild of wp2shellAll atomic IOCs (5 SHA1, 5 IPv4); webshell families; 207 Multi-Status signal; user agents01-wiz.txt2026-07-24
1Rapid7 — ETR: CVE-2026-63030 wp2shellRoot cause; CISA KEV date; CVSS 7.502-rapid7.txt2026-07-24
1Tenable — wp2shell FAQAffected/fixed matrix incl. 6.8.x; CVSS 9.8 / 5.9; object-cache caveat03-tenable.txt2026-07-24
1Eye Security — wp2shell defenders / IR guideLog-blind finding; database artefacts; rogue admin prefixes; forensic paths08-eye-security-defenders-guide.txt2026-07-24
1Elastic Security Labs — Detecting wp2shell end-to-endEndpoint hunt thesis: web server spawning shells; discovery commands; plugin-directory pattern09-elastic-security-labs.txt2026-07-24
2Qualys ThreatPROTECT — wp2shell exploited in the wildBoth routing forms; CISA deadline; independent version matrix05-qualys-threatprotect.txt2026-07-24
2Coalition Security Labs — wp2shell exploited in the wildHoneypot exploitation date; disclosure attribution; patch-only guidance06-coalition.txt2026-07-24
2SANS Internet Storm Center — WordPress exploitation underwayHoneypot capture; cache-directory drop; webshell command parameters10-sans-isc.txt2026-07-24
2BleepingComputer — Critical wp2shell flaws exploited to install webshellsPost-exploitation activity set; LFI via admin-ajax; patch-rate telemetry11-bleepingcomputer.txt2026-07-24
2SecurityWeek — wp2shell WordPress vulnerabilities exploited in the wildIndependent ITW confirmation; forced auto-updates; disclosure-to-exploit collapse04-securityweek.txt2026-07-24
2The Hacker News — New wp2shell WordPress Core flawPlain-language root cause; 6.8.x SQLi-only branch; object-cache caveat07-thehackernews.txt2026-07-24
3Aggregated search-result text (multiple vendors)Attribution to Adam Kues / Assetnote; rogue admin and plugin-directory patterns; follow-on request paths; third user-agent string. Treated as medium confidence.12-websearch-aggregate.txt2026-07-24

The raw fetched text of every source above is saved in wp2shell-CVE-2026-63030-Hunt-sources/ next to this file. No source contained instructions aimed at an automated agent, and no source was disqualified. Nothing in this pack was written from memory.