Kai Ole Hartwig
15 min read
Critical

wp2shell: CVE-2026-63030 + CVE-2026-60137 chain into unauthenticated RCE in WordPress Core — now in the CISA KEV

On 17 July 2026, WordPress released versions 6.8.6, 6.9.5, and 7.0.2, closing two vulnerabilities that together became known as “wp2shell”: CVE-2026-60137 (CVSS 9.1, SQL injection via the author__not_in parameter of WP_Query) and CVE-2026-63030 (CVSS 7.5, an interpretation-conflict flaw in the REST API batch endpoint /wp-json/batch/v1). On its own, the SQL injection is already critical, but it depends on a caller that passes a string instead of an array; the batch route confusion supplies exactly that caller — it lets unauthenticated sub-requests slip past the normal REST parameter validation. Chained together, the pair lets an anonymous attacker create administrator accounts and upload plugins: full remote code execution on WordPress 6.9.0-6.9.4 and 7.0.0-7.0.1, with no plugin dependency required. Public exploit tools and a working PoC appeared within hours of disclosure; on 21 July 2026 CISA added both CVEs to its Known Exploited Vulnerabilities catalog marked “actively exploited” — just four days after initial reports still described “no confirmed exploitation.”

TL;DR — 90 seconds

Affected?

WordPress 6.8.0-6.8.5 (SQL injection only, no RCE path), WordPress 6.9.0-6.9.4 and 7.0.0-7.0.1 (full RCE chain, both bugs present). 7.1 beta2 already contained both fixes during development.

Risk?

SQL injection (CVE-2026-60137, CVSS 9.1) plus REST API batch route confusion (CVE-2026-63030, CVSS 7.5) combine into unauthenticated remote code execution via /wp-json/batch/v1. CISA confirms active exploitation from at least 18/19 Jul 2026, KEV addition on 21 Jul 2026.

Immediate action?

Update to 6.8.6 / 6.9.5 / 7.0.2 immediately (WordPress has enabled forced auto-updates for affected installations — verify manually that your instance actually received it). If immediate patching isn't possible: block /wp-json/batch/v1 and ?rest_route=/batch/v1 at the edge/WAF.

Recommendation?

Patching alone isn't enough if your instance was unpatched and reachable during the 17-21 Jul 2026 window — additionally check for compromise indicators (rogue admin accounts, unknown plugin directories, webshells).

Criticality?

critical (hero badge) — CISA KEV listing, public exploit tools with more than two dozen documented PoC variants, active exploitation in the wild.

What is the problem?

“wp2shell” is the nickname coined by security researchers and exploit tooling for a chain of two WordPress core vulnerabilities that together give an unauthenticated attacker full code execution. Both were closed on 17 July 2026 with releases 7.0.2, 6.9.5, and 6.8.6, and are officially tracked as GHSA-ff9f-jf42-662q (CVE-2026-63030, reported by Adam Kues, Assetnote/Searchlight Cyber) and GHSA-fpp7-x2x2-2mjf (CVE-2026-60137, reported by TF1T, dtro, and haongo).

CVE-2026-63030 — batch route confusion (CWE-436, interpretation conflict)

The REST API batch endpoint (/wp-json/batch/v1 or ?rest_route=/batch/v1) lets clients bundle several sub-requests into a single HTTP call — convenient for avoiding many small API calls. The batch implementation, however, performs validation and execution of the sub-requests in separate loops. If wp_parse_url() fails while processing a sub-request path, the error is pushed into the validation array but not into the matches array used for actual execution. The result: a crafted sub-request can be smuggled past route resolution in a way that bypasses the endpoint-specific permission check that would normally block anonymous access — including the argument-schema validation that would otherwise force author__not_in into an array.

CVE-2026-60137 — SQL injection in WP_Query (CWE-89)

The author__not_in parameter of WP_Query expects an array of user IDs. If a string is passed instead, WordPress interpolates that value directly, unfiltered, into the raw SQL statement. Under normal circumstances the REST API's argument validation prevents exactly this — but the batch bug bypasses that validation for sub-requests, letting an attacker smuggle the parameter through as a raw string.

The chain

Put together: an anonymous attacker sends a batch request to /wp-json/batch/v1 containing a crafted sub-request. Thanks to the route confusion, that sub-request triggers a WP_Query execution with a string payload in author__not_in, reads database contents via SQL injection (including password hashes, secrets, WordPress options), and is escalated from there to administrator account creation and plugin upload — the classic path to a PHP webshell and thus remote code execution, without needing a single third-party plugin.

Who is affected?

Version rangeStatusConditions
WordPress 6.8.0-6.8.5Only the SQL injection (CVE-2026-60137) is present — the batch endpoint doesn't exist yet in this series, so there's no unauthenticated RCE pathExploitable only if a plugin or theme itself passes untrusted input directly to author__not_in
WordPress 6.9.0-6.9.4Full RCE chain — both bugs presentNo login, no plugins needed; a default install is enough
WordPress 7.0.0-7.0.1Full RCE chain — both bugs presentNo login, no plugins needed; a default install is enough
WordPress 7.1 beta2 and later (development branch)Not affected — both fixes were already included before the coordinated release—
WordPress 6.8.6 / 6.9.5 / 7.0.2 and laterPatched—

That makes virtually every self-hosted or managed WordPress instance on 6.9.x or 7.0.x affected if it was reachable from the internet between 17 and 21 July 2026 (patch release to KEV addition) and wasn't updated immediately — regardless of installed plugins or themes, since the chain lives entirely in WordPress core. Relevance for this blog: even though the stacks run here are primarily TYPO3, Symfony, and Sylius, clients, partners, and shared hosting environments frequently run WordPress instances alongside them — WordPress's market share and ubiquity make this chain an incident with broad reach.

Impact

The immediate impact is full, unauthenticated remote code execution at the web server's operating-system level — not just data access. The documented exploitation path runs through SQL injection (read access to arbitrary database contents, including password hashes and secrets), creation of an administrator account, and upload of a malicious plugin that then acts as a PHP webshell. From there, virtually anything is possible: data exfiltration, dropping further backdoors, using the compromised server as a pivot into internal networks, crypto-mining, spam distribution, or SEO spam injection. Since no plugins or themes are required, every default installation of the affected version series is a valid target — the attack surface lies entirely in WordPress core, not in a site's individual configuration. Per VulnCheck, more than two dozen distinct PoC implementations were already circulating by 19 July 2026; Patchstack, Hexastrike, and WatchTowr independently report observed exploitation attempts over the weekend following disclosure.

Mitigation / immediate steps

Operational decision block

Step 1 — check version and update

 

# check current version (WP-CLI)
wp core version

# update to the latest patch version
wp core update
wp core update-db

# alternatively without WP-CLI: WordPress admin area > Dashboard > Updates
# or manually replace core files with 6.8.6 / 6.9.5 / 7.0.2

 

Step 2 — verify auto-update status

According to its own announcement, WordPress.org enabled forced auto-updates via the background update mechanism for affected versions, given the severity. That applies by default to minor/security releases — not, however, if auto-updates were explicitly disabled (e.g. via wp-config.php constants, hosting policy, or management tooling pinned to a major version). Such installations must be updated manually.

 

# check auto-update configuration in wp-config.php
grep -E "WP_AUTO_UPDATE_CORE|AUTOMATIC_UPDATER_DISABLED" wp-config.php

# check the cron/update log to confirm whether the forced patch has already landed
wp core version --extra

 

Step 3 — WAF mitigation if patching isn't immediately possible

 

# nginx: block the batch endpoint until patched
location ~* ^/wp-json/batch/v1 { deny all; return 403; }
location ~* "rest_route=/batch/v1" { deny all; return 403; }

# Apache (.htaccess)
RewriteEngine On
RewriteCond %{REQUEST_URI} ^/wp-json/batch/v1 [OR]
RewriteCond %{QUERY_STRING} rest_route=/batch/v1
RewriteRule .* - [F,L]

# ModSecurity rule (example)
SecRule REQUEST_URI "@contains /wp-json/batch/v1" "id:100063030,phase:1,deny,status:403"
SecRule ARGS:rest_route "@contains /batch/v1" "id:100063031,phase:1,deny,status:403"

 

Alternative: restrict anonymous REST API access generally via a security plugin until the core patch is applied. Blocking the batch endpoint is a stopgap — legitimate clients that use batch requests will stop working.

Detection / verification

Note: the indicators below come from public vendor writeups (Elastic Security Labs, Eye Security, InstaWP). They're snapshots of observed or scanner-author-expected attacker behavior — not fixed, permanently valid signatures. None of the sources contain publicly verified file hashes or IP lists; if you need those, check commercial feeds (e.g. VulnCheck's Initial Access Intelligence) directly.

Access logs: batch endpoint access

 

# identify access to the batch endpoint
grep -E "wp-json/batch/v1|rest_route=%2Fbatch%2Fv1|rest_route=/batch/v1" access.log

# POST requests with a nested "requests" array in the body are the actual attack pattern;
# that doesn't show up in standard access logs (only the URL, not the body) —
# HTTP 207 (Multi-Status) as a response code on batch requests is a supporting indicator,
# but legitimate batch usage also produces 207 — only clustering in the relevant window is notable
awk '$9 == 207' access.log | grep "batch/v1"

 

User-agent signatures

 

# search for tooling signatures (per public scanner/exploit repos)
grep -iE "wp2shell|rezwp2shell|cve-2026-63030" access.log

 

Filesystem: webshell and plugin artifacts

 

# suspicious plugin directories with a hex suffix
find wp-content/plugins -maxdepth 1 -type d -regextype posix-extended -regex ".*-[0-9a-f]{6}$"

# staging/temp files created during exploitation
find wp-content -maxdepth 2 -iname "temp-write-test-*"

# small PHP files with a command interface (?c=) as a generic webshell indicator
grep -rl "\$_GET\['c'\]" wp-content/plugins wp-content/uploads wp-content/cache 2>/dev/null

 

Database: rogue admin accounts

 

# check admin accounts for suspicious login patterns or domains
wp user list --role=administrator --fields=ID,user_login,user_email,user_registered

# filter for known patterns from public scanners (login prefixes and email domains
# vary by tooling version — treat this as a starting point, not a complete list):
# login prefixes: wp2_*, w2s_*, wpsvc_*
# email domains: @wp2shell.*, @shellcode.*, @wordpress-svc.internal, @wordpress-noreply.net

 

Recommended tools

Operator guidance

If you don't run WordPress

The WordPress angle isn't really the point of this post — the bug class is. CVE-2026-63030 is, at its core, route confusion in a batch/bulk API dispatcher: an endpoint that bundles several sub-requests performs validation and execution in separate code paths, letting sub-requests slip past the normal permission and argument checks. This pattern isn't WordPress-specific. Check your own platform landscape: does your Symfony/API Platform setup allow bulk operations that internally re-dispatch individual sub-requests? Does each sub-request run through the same voter/security stack as a regular HTTP request, or is the security check only performed once, for the outer request? Does the same apply to TYPO3 backend AJAX routes or custom bulk endpoints in a Sylius API layer? Anywhere a framework offers “many requests in one,” it's worth explicitly asking whether auth/permission checks run per sub-request or only per outer request.

If you or a client runs WordPress

Update to 6.8.6 / 6.9.5 / 7.0.2 immediately, regardless of whether the auto-update mechanism has already kicked in — verify manually. If an instance was unpatched and reachable from the internet between 17 and at least 21 July 2026, treat it as potentially compromised until proven otherwise: run the detection steps described above (rogue admin accounts, suspicious plugin directories, webshell patterns) before relying on the patch alone. For agencies and hosters managing WordPress for clients: this window potentially affects the entire 6.9.x/7.0.x customer base at once — a bulk scan of all managed instances is justified.

Decision block

Act today if: your own or a client's WordPress instance is on 6.9.x/7.0.x, publicly reachable, and not yet verified as patched. Monitor if: already verified on 6.8.6/6.9.5/7.0.2, or no WordPress in your own or managed estate — in which case the general lesson about batch API auth bypass still applies to your own architecture review.

Frequently asked questions about wp2shell

Where does the name “wp2shell” come from?+

The name was coined by security researchers and exploit tooling and plays on the path from “WordPress” (wp) to a shell (remote code execution via webshell) — following the common naming pattern for exploit chains that turn a prominent service directly into a command line on the target system.

What does the CISA KEV listing concretely mean?+

CISA's Known Exploited Vulnerabilities catalog lists only vulnerabilities with confirmed active exploitation in the wild — not theoretical risk. For US federal agencies, several secondary sources report a binding remediation deadline attached to it (reported: 4 Aug 2026); verify the exact directive and deadline against CISA's own KEV catalog entry, since direct access to the CISA page was technically blocked while researching this article and we relied on secondary reporting instead. For every other operator, the listing is a strong, independently confirmed urgency signal.

How mature is the public exploit tooling?+

Very mature and widely available. VulnCheck counted more than two dozen independent PoC implementations by 19 July 2026 alone; several scanner and exploit repositories circulate on GitHub (including multiple vendors' wp2shell-scan variants, wp2shell-poc, wp2shell-lab). That significantly lowers the barrier to entry for attackers — one reason CISA moved to “actively exploited” within days.

Is patching enough, or should I also rotate secrets?+

If your instance was unpatched and publicly reachable during the 17-21 July 2026 window, patching alone isn't enough. First check for compromise indicators (see the detection section); if you find any, or even if you're just unsure, treat database credentials, WordPress salts/secret keys, and any API keys stored in the database as potentially compromised and rotate them — the SQL injection allows read access to the entire database.

Is the SQL injection exploitable on its own, without the batch bug?+

Per the NVD description of CVE-2026-60137, direct exploitation requires a plugin or theme to itself pass untrusted input as a string to author__not_in — on WordPress 6.8.x without the batch bug that's theoretically possible but not reachable unauthenticated by default. Only the route confusion in CVE-2026-63030 turns it into a path exploitable without any plugin and without a login.

Do I need to worry if I don't run WordPress?+

Only WordPress instances are directly affected. It's indirectly relevant regardless: WordPress has enormous market share, so clients, partners, or shared hosting infrastructure frequently run WordPress sites alongside whatever else you operate. And the actual lesson — a batch/bulk API endpoint that lets sub-requests slip past regular auth checks — is a pattern that can occur in any framework with similar bulk operations.

Conclusion

wp2shell is a textbook example of how two bugs of seemingly different severity — a critical SQL injection and a “merely” high-rated route confusion — escalate together into full, unauthenticated remote code execution. The apparent CVSS discrepancy (9.1 versus 7.5) resolves on closer inspection: each CVE is scored in isolation for its own direct impact, not for the damage of the full chain — WordPress itself rates the SQL injection as critical and the route confusion as high, which matches the NVD scores exactly. What stands out most is the speed of escalation: from “public PoC, no confirmed exploitation” on 17/18 July to a CISA KEV listing marked actively exploited on 21 July — four days. Anyone running or managing WordPress should have patched and checked for compromise by now. Anyone who doesn't should still take a look at their own API architecture: anywhere requests get bundled together, it's worth asking whether every sub-request really goes through the same auth check as a standalone request.

Sources

I review your APIs and backend interfaces for exactly this kind of auth-bypass pattern.

Batch/bulk endpoints, internal request re-dispatchers, sub-request handling in Symfony, TYPO3, or Sylius — anywhere multiple operations get bundled into one request, I check whether every sub-request really goes through the same auth and validation stack as a standalone request.

Platform operations instead of advice on paper: I continuously patch, harden, and monitor your PHP stacks — including WordPress instances in mixed environments.

Book a call →

About the author

[Translate to English:] Foto von Kai Ole Hartwig.

Kai Ole Hartwig

Freelance DevSecOps consultant · OnlyOle Consulting

Programming since 2002 – self-taught, set up my own business with KO-Web in 2012. Over 100 projects, with a focus on security, performance, automation and quality. Today freelance: DevSecOps consulting, training and software development.