• Severity: CVSS 9.8 (Critical, WPScan CNA)
  • Affected: WordPress 6.9.0–6.9.4 and 7.0.0–7.0.1
  • Patched: July 17, 2026 · Exploitation: Public PoCs available; early in-the-wild activity reported but not yet independently confirmed

Introduction

CVE-2026-63030, better known as wp2shell, is a critical route-confusion vulnerability in the WordPress Core REST API. When chained with CVE-2026-60137, an SQL injection in WP_Query, an anonymous attacker can progress from one HTTP request to remote code execution.

The issue was disclosed on July 17, 2026. It requires no WordPress account, vulnerable plugin, or victim interaction. The known public RCE path applies when a persistent object cache is not in use, which includes a normal default WordPress installation.

NVD’s CISA-ADP maps the route-confusion flaw to CWE-436: Interpretation Conflict. WordPress classifies the combined issue as Critical, while the WPScan CNA assigns CVSS 3.1 9.8. Versions before WordPress 6.8 are not affected by either disclosed issue; WordPress 6.8.x is affected by the SQL injection, but not the full wp2shell RCE chain.

This is bad, and you should patch now.

Attribution note: The batch-route vulnerability was discovered and responsibly disclosed by Adam Kues of Assetnote / Searchlight Cyber. The companion SQL injection was reported by TF1T, dtro, and haongo. The code-path explanation below also draws on Maciej Szymczak’s public patch analysis. We are standing on the shoulders of people who did the hard work.


What Happened

WordPress exposes a REST batch endpoint that processes several subrequests in one call:

POST /wp-json/batch/v1

The same endpoint is reachable through the query-string route:

POST /?rest_route=/batch/v1

Inside WP_REST_Server::serve_batch_request_v1(), WordPress tracked each subrequest using parallel collections for the request, its validation result, and its matched route handler. Every index was supposed to describe the same subrequest.

A malformed path broke that relationship. WordPress stored the parsing error in the request and validation state, but failed to reserve the corresponding position in the handler-match state. Every later match shifted left by one index.

A request could therefore be validated using Route A’s schema, then passed to Route B’s permission and execution callbacks. This was not a universal authentication bypass, because Route B’s permission callback still ran. It was something subtler and more useful to an attacker: input validated under one interpretation could be consumed under another.

NVD currently maps this behaviour to CWE-436, which is a suitably polite name for “two parts of the program strongly disagreed about what request they were processing.”

CVE-2026-63030 REST batch state misalignment

Fig 1: A malformed batch item desynchronises request validation from handler dispatch


Attack Sequence

The attacker begins with an anonymous POST request to the REST batch endpoint. A deliberately malformed subrequest creates the missing handler-match entry, shifting the route selected for the requests that follow.

The attacker then uses the confused handler state to move data past a schema that would normally constrain it. Through nested batch processing, a scalar author-exclusion value can reach the posts query path and map to WP_Query::author__not_in.

Before the patch, WordPress normalised author__not_in as integer IDs only when the value was already an array. A scalar took another path and could be inserted into the NOT IN SQL expression without the expected integer conversion, producing SQL injection.

Public no-FILE implementations then use the injected query to return forged post-shaped rows that poison WordPress’s per-request object cache. The corrupted application state can temporarily obtain administrator context, re-enter REST dispatch, create an attacker-controlled administrator account, and use normal plugin-installation capabilities to reach code execution.

The reusable payloads, SQL expressions, and forged-object structures are intentionally omitted here. Defenders need the broken trust boundaries, not a weekend-ready shell script.

Two ordinary-looking bugs met inside an internet-facing API and decided to become pre-auth RCE. Teamwork, unfortunately, does make the dream work. :/

Why This Is Especially Dangerous

  • No authentication is required. An attacker needs neither credentials nor an existing session.
  • The flaw is in WordPress Core. Removing plugins and themes does not remove the vulnerable code path.
  • Default installations are exposed. WordPress does not enable a persistent object cache by default.
  • The vulnerable endpoint is public. Both the pretty-permalink path and query-string form must be considered.
  • Public PoCs exist. Patch diffing produced working implementations shortly after disclosure.
  • The post-exploitation ceiling is high. Successful exploitation can lead to administrator creation, site takeover, data theft, malicious plugin installation, and server-side code execution.
  • Internet-facing and forgotten sites are the main risk. Production, staging, campaign, and abandoned subdomain installations all count.

BleepingComputer reported that watchTowr was beginning to observe early signs of exploitation after public PoCs appeared. That report should be treated as an early warning rather than a mature, independently verified campaign assessment, but the practical response is the same: verify the patch instead of waiting for a nicer IoC list.


Affected Versions

BranchVulnerable VersionsFix Available
Earlier than 6.8Not affected by either disclosed issueNot applicable
6.86.8.0–6.8.5: SQL injection only6.8.6
6.96.9.0–6.9.4: full wp2shell RCE chain6.9.5
7.07.0.0–7.0.1: full wp2shell RCE chain7.0.2
7.1 BetaBeta 1 affected by both issuesBeta 2

WordPress enabled forced automatic updates because of the severity, but administrators should still verify every installation. Automatic updates can fail, be disabled, or never reach the staging site nobody admits is publicly accessible.

WordPress wp2shell affected-version timeline

Fig 2: Exposure and fixed releases across affected WordPress branches


Mitigations

1. Update WordPress immediately

Use Dashboard > Updates > Update Now, or update with WP-CLI:

wp core version
wp core update
wp core verify-checksums

Required minimum releases are:

WordPress 6.8 branch: 6.8.6
WordPress 6.9 branch: 6.9.5
WordPress 7.0 branch: 7.0.2
WordPress 7.1 beta: Beta 2

Verify production, staging, development, regional, campaign, and legacy sites individually.

2. Temporarily block anonymous batch requests

Where immediate patching is impossible, block both endpoint representations:

/wp-json/batch/v1
/?rest_route=/batch/v1

Blocking only the /wp-json/ path leaves the query-string route available. Searchlight Cyber also published a temporary WordPress plugin that rejects anonymous /batch/v1 requests. These measures can break legitimate integrations and should remain temporary.

3. Confirm WAF protections are active

Cloudflare deployed rules for both CVEs across free and paid plans for traffic proxied through its WAF. Check Security > WAF > Managed rules and confirm the relevant rules are enabled with the action set to Block, not only Log.

Cloudflare’s published rule IDs are:

CVE-2026-60137
Managed Ruleset: 1c060d3a371549219ee290d7ed933fcc
Free Ruleset:    db003b39b7774859a8d588ce33697a1a

CVE-2026-63030
Managed Ruleset: 7dfb2bd4708d4b88b9911dc0550664b6
Free Ruleset:    ebd3f2df15c74ddcbf6220c9b5ec246a

A WAF reduces exposure while patching. It does not repair WordPress Core.

4. Restrict post-exploitation capabilities

Controls such as DISALLOW_FILE_MODS, restrictive filesystem permissions, least-privileged database accounts, and protected administrative workflows can disrupt parts of public exploitation chains. They do not fix either vulnerability, but they can reduce the damage if patching has not completed.


Indicators of Compromise

As of July 19, 2026, no authoritative set of stable attacker IP addresses, domains, payload hashes, or filenames had been published for wp2shell. Public PoCs and early exploitation reports exist, so hunt for behaviour rather than waiting for campaign-specific indicators.

On the WordPress host

  • Unknown administrator accounts or unexplained role changes.
  • New or modified plugins, themes, PHP files, or WordPress cron entries.
  • PHP-FPM or the web-server process spawning shells, downloaders, or unexpected child processes.
  • Unfamiliar outbound connections from the web application.
  • Unexpected changes to WordPress salts, options, active plugins, or filesystem permissions.

In traffic and application logs

  • Anonymous POST requests to /wp-json/batch/v1.
  • Requests using rest_route=/batch/v1.
  • Large or deeply nested JSON bodies containing multiple batch requests.
  • Malformed subrequest paths mixed with valid REST operations.
  • Unusual author-exclusion values in post-collection requests.
  • Repeated 207 Multi-Status, 400, or 500 responses involving the batch route.

The nested REST re-entry used by the public no-FILE chain occurs within the same PHP request, so it may not appear as a second inbound access-log entry. Application tracing, WAF request-body telemetry, and WordPress audit logs provide better visibility.

Configuration integrity

  • Confirm the Core version and verify checksums.
  • Compare installed plugins and themes against an approved inventory.
  • Review whether auto-updates failed or were disabled.
  • Confirm WAF overrides have not changed new rules from Block to Log.
  • Check every externally resolvable WordPress hostname, including staging and old campaign domains.

If suspicious administrator creation or code modification is found, preserve logs and database evidence before remediation. Then remove unauthorised access, rotate WordPress salts, administrator credentials, database credentials, hosting credentials, and any secrets available to the web process.

Continue monitoring the WordPress advisory, NVD record, and trusted threat-intelligence sources for updated exploitation status and campaign-specific IoCs.


References