The exploit hinges on a subtle mistake — WordPress's REST API validates and executes batch requests in two separate loops. When one step fails, everything after it dispatches under the wrong handler. Here's the chain, step by step.
The interesting thing about wp2shell isn't that WordPress had a bug — it's how ordinary the mistake was. No exotic memory corruption, no cryptography failure. Just a validation step and an execution step that run in two separate loops, and a wrong assumption about what happens when one request in a batch fails. That gap is CVE-2026-63030, and chained with a SQL injection it becomes an unauthenticated takeover of WordPress core. If you've ever written code that checks all your inputs up front and then processes them, this bug will feel uncomfortably familiar. Let me walk the whole chain.
How the wp2shell chain reaches code execution
Anonymous request
One HTTP call to the REST API batch endpoint. No login, no plugin.
Route confusion
wp_parse_url() fails mid-batch, so later requests run under the wrong handler (CVE-2026-63030).
SQL injection
The misrouted request reaches WP_Query's author__not_in parameter (CVE-2026-60137).
Code execution
Database write is escalated to running the attacker's own code — a shell.
Full takeover
The attacker controls the site and the server under it.
Anonymous request
One HTTP call to the REST API batch endpoint. No login, no plugin.
Route confusion
wp_parse_url() fails mid-batch, so later requests run under the wrong handler (CVE-2026-63030).
SQL injection
The misrouted request reaches WP_Query's author__not_in parameter (CVE-2026-60137).
Code execution
Database write is escalated to running the attacker's own code — a shell.
Full takeover
The attacker controls the site and the server under it.
Each stage feeds the next — neither half is a full takeover alone. That's what makes it a chain.
Step 1: the batch endpoint, and why it exists
WordPress's REST API has a batch endpoint — /wp-json/batch/v1 — whose job is efficiency. Instead of making ten separate HTTP requests to the API, a client can bundle ten operations into one call. The server unpacks them, checks that each is allowed, and runs them. It's a sensible feature; lots of APIs have one. The trouble is entirely in the implementation detail of how it checks and runs them.
Per the technical analysis, the batch processor does its work in two passes. First a validation loop walks every request in the batch and confirms each one maps to a permitted route and handler. Then a separate execution loop walks the batch again and actually runs each request. Two loops over the same list. Under normal conditions they stay in lockstep — request three in the validation loop is request three in the execution loop. The vulnerability is what happens when they don't.
Step 2: the route confusion (CVE-2026-63030)
Here's the crux. Somewhere in processing a request, WordPress calls wp_parse_url() to break down a URL. If that call fails — and an attacker can craft a request that makes it fail — the batch processor's internal bookkeeping slips. The array tracking which request maps to which handler falls out of alignment, so from that point on, every subsequent request in the batch is dispatched under the handler that belonged to a different request. A request the validation loop approved as a harmless, public operation gets executed as something else entirely.
That's "route confusion," and it's the load-bearing trick of the whole exploit. It defeats WordPress's permission checks not by breaking them but by sidestepping them: the checks ran against the request's declared identity, and then the request was executed as something different. The allowlist said yes to one thing and the server did another. Crucially, none of this requires a login — the batch endpoint is reachable anonymously, so the entire manoeuvre happens with no credentials at all.
The public advisory's own conditions table — note the preconditions: REST API reachable, no persistent object cache, and at least one published post. · 0xsha / GitHub (screenshot)
Step 3: the SQL injection it reaches (CVE-2026-60137)
Route confusion is only useful if it lands the attacker somewhere dangerous, and it does: WP_Query, WordPress's core database query builder. Specifically the author__not_in parameter, which is meant to take an array of author IDs to exclude from a query. The flaw is a classic validation gap — pass a carefully crafted string where the code expects an array, and the value slips past sanitisation and into the raw SQL query. That's CVE-2026-60137, a SQL injection sitting in WordPress core itself.
On its own, this SQL injection is hard to reach without authentication — which is why versions 6.8.0–6.8.5, which have the injection but not the batch route confusion, need a "facilitating" plugin or theme to expose it. The route confusion from step 2 is what makes it reachable anonymously on 6.9+. That's the essence of a chain: bug A is the delivery mechanism for bug B, and neither is a full compromise without the other.
Step 4: from SQL injection to a shell
SQL injection means an attacker controls part of the database query. In many applications that's a data-theft problem — read out the password hashes, dump the users table. In WordPress it can be worse, because WordPress stores serialized PHP objects and configuration in its database, and there are known techniques to turn database write access into PHP code execution. That final escalation is what puts the "shell" in wp2shell: the attacker doesn't just read your data, they run their own code on your server. Full remote code execution, from an anonymous HTTP request, against a default install.
Why the chain matters: severity of each part alone vs combined
Route confusion aloneLow — needs a target
SQL injection alone (pre-6.9)Needs a facilitating plugin
Chained (wp2shell)9.8 — pre-auth RCE
Step 5: the preconditions that limit it
This is the part most coverage skips, and it's genuinely useful for understanding your own exposure. The public advisory lists three preconditions for the exploit to work: the REST API has to be reachable (it is, by default), the site needs at least one published post, and — the interesting one — the site must have no persistent object cache such as Redis or Memcached. The exploit's technique for turning the SQL injection into code execution depends on WordPress's default object-caching behaviour; a persistent object cache changes that behaviour enough to break the chain.
Technical questions people ask
Is wp2shell a memory-safety bug or a logic bug?
A logic bug, entirely. There's no buffer overflow or memory corruption involved. It's a design flaw in how the REST API batch processor tracks requests across two loops, combined with a missing type check in a query parameter. That's actually what makes it so instructive — these are ordinary application-logic mistakes any developer could make, not exotic low-level exploitation.
Why does the batch endpoint validate and execute in separate loops?
It's a common and usually reasonable pattern: check everything is valid before you do anything, so you don't half-complete a batch and leave things in a broken state. The mistake wasn't the two-loop design itself but the assumption that the two loops would always iterate in perfect alignment — that a failure partway through wouldn't desynchronise the mapping between requests and handlers. That assumption is what CVE-2026-63030 breaks.
Could this have been caught earlier?
Interestingly, reporting indicates AI-assisted analysis helped uncover the chain — the kind of multi-step logic flaw that's easy for human review to miss because each piece looks fine in isolation. The route confusion is only dangerous because of where it can be pointed, and the SQL injection is only reachable because of the route confusion. Chained vulnerabilities are hard to spot precisely because you have to see both halves at once.
Does knowing how it works help me defend against it?
Mainly it helps you understand your exposure and prioritise. The defence is simple regardless of mechanism: patch to 6.9.5 or 7.0.2. But understanding the chain explains why a WAF rule targeting the batch endpoint works as virtual patching, why object-caching sites saw different results, and why this needed a forced update — the mechanism is what those responses are all reacting to.
That's wp2shell under the hood: a two-loop desync that causes route confusion, feeding a SQL injection in core, escalated to code execution — all anonymously. Elegant and alarming in equal measure. If you came here from a search and just need to secure your site, jump to what is wp2shell for the practical version, or straight to the update-and-verify guide. And if you found the two-loop mistake as interesting as I did, it's a good reminder that the scariest bugs are often the most mundane.