wp2shell (CVE-2026-63030) is a critical pre-authentication remote code execution vulnerability chain found in WordPress core, impacting WordPress 6.9.x before 6.9.5 and 7.0.x before 7.0.2. Unlike most WordPress vulnerabilities that reside in third-party plugins, wp2shell lives entirely in core code, meaning an unauthenticated attacker can exploit a default installation with no plugins and no special configuration required. The potential for widespread exploitation across an estimated 500 million sites and the ability to escalate from anonymous access to full administrator privileges places it in the same category as historically significant WordPress vulnerabilities, making it one of the most serious security threats in the WordPress ecosystem.
Following this, a medium-severity SQL Injection vulnerability (CVE-2026-60137) was also disclosed, affecting WordPress 6.8.x before 6.8.6, 6.9.x before 6.9.5, and 7.0.x before 7.0.2. This vulnerability in the author__not_in parameter of WP_Query serves as the critical second piece of the exploit chain. Together, these vulnerabilities highlight the ongoing security challenges in the WordPress ecosystem.
WordPress exposes a REST API under /wp-json/ to handle data operations, including a batch endpoint at /wp-json/batch/v1 that has shipped by default since WordPress 5.6 in 2020. This batch endpoint allows a client to send multiple sub-requests in a single HTTP call. The server parses each request, matches it to a route handler, runs permission checks, and executes the callbacks. Some routes are protected, for example creating a new user at /wp-json/wp/v2/users requires the create_users capability, so an anonymous caller normally receives a 401 Unauthorized response.
The problem occurs in how the batch handler processes these requests. The handler maintains three parallel arrays indexed by position: $requests holds the parsed request objects, $matches holds the route-and-handler tuples, and $validation holds validation results. When a request member fails to parse, the code appends a WP_Error to validation and continues, but it never adds a placeholder to $matches. From that point on, $matches becomes one element shorter than $requests. The second loop then walks the requests by index and reads the corresponding match, causing every valid request after the error to read the handler tuple that belongs to a later request.
The consequence is that the entire handler tuple shifts, including its permission callback, execution callback, and schema. An unvalidated request can therefore reach a handler it was never checked for, such as the posts handler.
But the exploit doesn't end there. The route confusion alone isn't enough to achieve full exploitation. This is where CVE-2026-60137 comes in: the posts controller exposes a parameter called author_exclude that maps internally to the query variable author__not_in in WP_Query. Under normal routing, this parameter is validated against a schema that ensures it arrives as a clean integer array. But because route confusion delivers the request to the posts handler without schema validation, author__not_in can arrive as a raw, attacker-controlled scalar instead.
Here's where things get particularly dangerous: WP_Query only sanitizes author__not_in if it's already an array. A scalar string never reaches the sanitization logic, so it stays uncleaned and gets cast directly into the SQL query. This is the SQL injection.
Now, imagine this: the SQL injection lets the attacker influence what WP_Query returns, but MySQL isn't executing shell commands. So how does query manipulation become application control? The answer is object hydration. When WP_Query runs a query, it converts each returned database row into a WP_Post object and stores it in the request-local object cache. The attacker controls what the query returns, so the WP_Post objects that WP_Query builds and caches hold attacker-chosen field values.
To move the attack forward, WordPress has to act on the forged data. The oEmbed caching path is what the attack abuses: when oEmbed thinks a cached embed is stale, it re-saves the post and lets WordPress supply the remaining fields from get_post(). Since get_post() now returns the poisoned object, that step writes the attacker's chosen status, type, and parent into the database.
But writing a post does not create an administrator that can install plugins for code execution. The attacker needs legitimate core code to re-evaluate a user-creation request as an administrator, and the timing is the catch. One save makes WordPress temporarily act as an administrator, but that identity is switched on inside a publish routine and switched back the moment it ends. The save that re-enters the REST API must run inside that window. Two separate writes cannot do this, so the second save must be nested inside the first.
A stock safety routine supplies that nesting: the poisoned posts form forbidden parent loops, and WordPress repairs a loop by re-saving its posts, one repair running inside another. The first save publishes a Customizer changeset that briefly switches WordPress's current user to a remembered administrator. The second save sets a poisoned post's status and type so they join into parse_request, which triggers the REST loader to start handling a fresh user-creation request while the current user is still that administrator. A request that was first rejected with 401 Unauthorized is now re-evaluated as an administrator and succeeds with 201 Created. The attacker has created an administrator account without ever logging in.
Once the attacker has an administrator account, they can install plugins that allow them to execute code remotely.
In order to fix the disclosed vulnerabilities, please refer to WordPress's release notes for WordPress 7.0.2 and the GitHub security advisories for CVE-2026-63030 and CVE-2026-60137. Currently versions 7.0.2, 6.9.5 and 7.1 beta2 are patched and considered safe to use, it is recommended that you update your deployments immediately. Versions prior to 6.8 are not affected at all. You can use this PoC to check if your WordPress deployment is vulnerable.