Follow Cyber Kendra on Google News! | WhatsApp | Telegram

Add as a preferred source on Google

WP2Shell (CVE-2026-63030): Checker, Patch & Detection Guide

Is your WordPress site vulnerable to WP2Shell? Instant version checker, patch steps, WAF rules, detection scripts and IOCs for CVE-2026-63030

Last updated: 23 July 2026 · Now includes in-the-wild exploitation data and the WAF-bypass warning. Written for site owners, admins and defenders who need to act, not just read.

The 20-second version: WP2Shell is a pre-authentication remote code execution (RCE) chain in WordPress core — no plugin required, no login required. It affects WordPress 6.9.0–6.9.4 and 7.0.0–7.0.1. WordPress shipped emergency fixes on 17 July 2026: update to 7.0.2, 6.9.5, or 6.8.6 right now. Public exploit code exists. If you can't patch this minute, block /wp-json/batch/v1 and ?rest_route=/batch/v1 at your WAF. Scroll down for a version checker, patch steps, WAF rules, detection scripts and indicators of compromise.

Is my site affected? (Instant checker)

Type your exact WordPress version (find it in Dashboard → Updates, or the bottom-right of your admin screen) and hit check. Everything runs in your browser — nothing is sent anywhere.

Prefer an external second opinion? Searchlight Cyber (the team that found the bug) runs a free tester at wp2shell.com that probes your live site. You can also check our wp2shell vulnerability checker tool.

The facts at a glance

ItemDetail
NameWP2Shell (a.k.a. "wp2shell")
CVE IDsCVE-2026-63030 (REST API batch-route confusion → RCE) chained with CVE-2026-60137 (SQL injection in core)
TypeUnauthenticated / pre-auth Remote Code Execution
PreconditionsNone. Works against a stock install with zero plugins.
Affected (RCE)WordPress 6.9.0–6.9.4 and 7.0.0–7.0.1 (plus 7.1 beta1)
Affected (SQLi only)WordPress 6.8.0–6.8.5 — vulnerable to CVE-2026-60137 but not the full RCE chain
Not affectedAnything below 6.8.0
Fixed in7.0.2, 6.9.5, 6.8.6 (released 17 July 2026)
Entry pointThe REST batch endpoint: /wp-json/batch/v1
Discovered byAdam Kues, Searchlight Cyber
ExposureWordPress powers 500M+ sites; forced auto-updates were enabled for affected installs

How WP2Shell actually works (in plain English)

WordPress 6.9 introduced a REST batch endpoint — a convenience feature that lets a client bundle several API calls into one request. The flaw is a route-confusion bug: the batch handler can be tricked into resolving inner requests to routes it shouldn't, in a context where the normal permission checks don't apply the way you'd expect.

On its own that's a nasty logic bug. The problem is what it can reach: a second vulnerability, an SQL injection in core (CVE-2026-60137), that an anonymous request can now touch through the batch route. Chain the two together and an attacker walks a completely unauthenticated request from "hello, I'm nobody" all the way to running SQL — and from there to code execution on the server. That's the "2shell" in the name: WordPress to shell, no account needed.

The reason the security community is treating this as a five-alarm fire isn't cleverness — it's reach. There is no plugin dependency, no unusual configuration, no authenticated user required. If you're on an affected version and reachable from the internet, you're in scope. That's why WordPress.org took the rare step of forcing auto-updates.

It's being exploited right now (in-the-wild data)

This is no longer theoretical. Patchstack, which watches traffic from inside protected WordPress installs, published a timeline of the campaign that should end any "I'll patch next week" thinking:

  • 90 minutes. The first real exploitation attempts hit sensors roughly 90 minutes after 7.0.2 was released — about three hours after the fix was committed to WordPress core. Attackers diffed the patch, built a scanner, and pointed it at the internet before most owners had read the release notes.
  • 65,000+ blocked attempts from 1,500+ unique IPs in the days after disclosure — and because those mitigations only run on genuinely vulnerable sites, every one of those was aimed at an unpatched target that a network/server WAF had already let through.
  • 97% of blocked traffic hit the REST batch endpoint; roughly three-quarters carried a SQL injection attempt in author_exclude. Most was validation probing (AND (1=1), OR SLEEP(5)), but a small cluster from three IPs — 129.121.77.134, 91.202.233.61, 125.164.233.50 — ran the full chain straight to administrator creation.

The takeaway from the researchers who watched it: 90 minutes from public patch to live exploitation isn't enough time to schedule a maintenance window. If you're on an affected version, treat this as already-happening, not a future risk.

Patch it — the 5-minute fix (do this first)

Patching is the only real fix. Mitigations below are stopgaps; this is the cure.

  1. Back up first. Snapshot your database and files (your host's one-click backup is fine). Core updates rarely break anything, but you want a rollback point.
  2. Go to Dashboard → Updates. If WordPress already auto-updated you (many sites did), you'll see you're on 7.0.2 / 6.9.5 / 6.8.6 — you're done, jump to detection to make sure you weren't hit before the patch landed.
  3. Click "Update Now." Land on the fixed release for your branch:
    • On the 7.0 branch → 7.0.2
    • On the 6.9 branch → 6.9.5
    • On the 6.8 branch → 6.8.6 (fixes the SQLi; consider moving to a current branch after)
  4. Confirm the version. Don't assume — check the number in your dashboard footer after the update completes.

Prefer the command line? With WP-CLI:

wp core update wp core version # confirm you're on 7.0.2 / 6.9.5 / 6.8.6 wp core verify-checksums # confirm core files weren't tampered with

That last command is doing double duty: verify-checksums compares your core files against WordPress.org's official hashes, so if an attacker modified a core file before you patched, it'll flag the mismatch.

Can't patch right now? Emergency mitigations

Maybe you have a fragile custom build, a change-freeze, or 200 sites to coordinate. These buy you time — they are not a permanent fix, and each can interfere with legitimate REST traffic. You must block both forms of the route; blocking only the pretty path leaves the query-string version wide open.

Option A — Block at your WAF / CDN

Block requests to the batch route in every form:

/wp-json/batch/v1 ?rest_route=/batch/v1 rest_route=/batch/v1 (in the POST body — see warning below)
⚠ Critical update (23 Jul 2026): Patchstack revealed that almost every WAF rule shipped in the first hours after disclosure — including early versions of their own — was bypassable. The rules only inspected the URL. But WordPress reads the rest_route query variable from the POST body before the query string, so an attacker can send a plain POST / with rest_route=/batch/v1 and the whole payload in the body — the URL never contains batch/v1 at all, and a URL-only rule waves it straight through. Patchstack confirmed live attacks using exactly this shape from around 17:00 UTC on 21 July. Your WAF rule must match the batch route in the URL and in the POST body. If yours only checks the URL, it's one request rewrite away from useless.

Attackers also spray path variants to dodge naive matching — block these too: /index.php?rest_route=/batch/v1, /wp/?rest_route=/batch/v1, /blog/?rest_route=/batch/v1, /wp-json?rest_route=/batch/v1.

If you're behind Cloudflare or Patchstack, managed rules for both CVEs were deployed for you (Patchstack has since updated theirs to close the POST-body bypass). It's still worth patching; a WAF rule is a filter, not a fix — and as this episode proves, filters have edges.

Option B — Drop-in "must-use" plugin (no WAF needed, bypass-resistant)

This is actually the stronger stopgap. Because it inspects the route after WordPress has resolved it — not the raw URL — it catches the batch route no matter how the request arrived, including the POST-body bypass that defeats URL-only WAF rules. Create wp-content/mu-plugins/block-batch.php with this. It rejects anonymous calls to the batch route while leaving logged-in admin traffic alone:

<?php /** * Emergency WP2Shell mitigation — blocks anonymous access to the REST batch route. * TEMPORARY. Remove after updating to 7.0.2 / 6.9.5 / 6.8.6. */ add_filter('rest_pre_dispatch', function ($result, $server, $request) { $route = $request->get_route(); if (strpos($route, '/batch/') !== false && !is_user_logged_in()) { return new WP_Error( 'rest_forbidden', 'Batch endpoint temporarily disabled (WP2Shell mitigation).', array('status' => 403) ); } return $result; }, 0, 3);

mu-plugins load automatically and can't be deactivated from the dashboard, which is exactly what you want for an emergency control. Delete the file once you've patched.

Were you already hit? Detection & log-hunting

Because public exploit code circulated quickly, patching doesn't answer the real question: did someone reach me first? Here's how to check.

1. Hunt your access logs for batch-endpoint abuse

The earliest signal is traffic to the batch route, especially with suspicious SQL-ish parameters. On most Apache/Nginx hosts:

# Any hits on the batch endpoint, all known forms + path variants grep -Ei "batch/v1|rest_route=/?batch" /var/log/nginx/access.log* # Higher-signal: batch traffic carrying SQL injection markers, # including the obfuscated spellings seen in the live campaign grep -Ei "batch/v1|rest_route=/?batch" /var/log/nginx/access.log* \ | grep -Ei "author.?exclude|author_?_?not_?_?in|UNION|SLEEP\(|SUBSTRING|CHAR_LENGTH|/\*!|/\*\*/|\bAnD\b|\bOr\b" # Privileged writes smuggled inside a batch body (admin creation / plugin install) grep -Ei "wp/v2/(users|plugins)" /var/log/nginx/access.log* \ | grep -Ei "administrator|roles" # Known full-chain source IPs from the observed campaign (context, not proof) grep -E "129\.121\.77\.134|91\.202\.233\.61|125\.164\.233\.50" /var/log/nginx/access.log*

Watch the parameter, not just the URL. The real campaign moved the batch route into the POST body to dodge URL-only filters, and the requests array can arrive as JSON, form-encoded (requests[0][path]=...), or in the query string. WordPress also normalizes author.exclude and author exclude back to author_exclude, so match those spellings too. A non-integer author_exclude value carrying UNION, SLEEP(, or comment-obfuscated AND/OR is one of the cleanest indicators of an attempt against this chain.

2. Look for dropped web shells

Successful exploitation typically drops a PHP payload. Scan for recently-changed or suspicious PHP files:

# PHP files created/modified in the last 7 days under your webroot find /var/www/html -name "*.php" -mtime -7 -print # Classic web-shell function calls hiding in your uploads/plugins grep -RilE "eval\(|base64_decode\(|system\(|shell_exec\(|passthru\(|assert\(" \ /var/www/html/wp-content/uploads /var/www/html/wp-content/plugins # PoC-tool artifacts: rogue plugin folders/zips named like wp2shell-xxxxxxxx find /var/www/html/wp-content/plugins -iname "wp2shell*" -print # Easy to miss: mu-plugins auto-load and never appear on the Plugins screen ls -la /var/www/html/wp-content/mu-plugins/

3. Verify core integrity

wp core verify-checksums # Any "File doesn't verify against checksum" line = investigate that file immediately.

4. Check the database for injected admin users / options

# New admin accounts you don't recognise wp user list --role=administrator --fields=ID,user_login,user_email,user_registered # Suspicious auto-loaded options (common persistence spot) wp option list --search="*eval*" --autoload=on

Indicators of Compromise (IOC)

The following are associated with public WP2Shell proof-of-concept tooling. Treat them as leads, not proof — they're trivial for an attacker to change, and some payloads self-delete after running, so their absence doesn't clear you. Use them to prioritise investigation, alongside the log and file checks above.

CategoryIndicator
Target endpointsBatch route in any form: /wp-json/batch/v1, ?rest_route=/batch/v1, or rest_route=/batch/v1 in the POST body; plus path variants /index.php, /wp/, /blog/, /wp-json?rest_route=...
Request bodyNested requests[] arrays (as JSON, form-encoded requests[0][path]=..., or query string) with malformed http:// / http:: paths
SQLi markersNon-integer author_exclude / author__not_in (also spelled author.exclude, author exclude) containing UNION, SLEEP(, SUBSTRING, CHAR_LENGTH, or obfuscated AnD/Or, inline comments /**/, /*!AND*/
Privilege escalationNested privileged writes inside a batch body: POST /wp/v2/users with "roles":["administrator"], or POST /wp/v2/plugins (plugin install/activate)
Full-chain source IPs129.121.77.134, 91.202.233.61, 125.164.233.50 (observed running the complete admin-creation chain; context only — IPs rotate)
User-Agentswp2shell-check/1.0, wp2shell-poc/1.0
Multipart boundaryBoundaries beginning ----wp2shell
Dropped filesPlugin ZIPs named wp2shell-<8-hex>.zip; files/dirs under wp-content/plugins/wp2shell-*; anything unexpected in wp-content/mu-plugins/ (auto-loads, hidden from Plugins screen)
Command channelRequests containing ?tok=<token>&c=<command>; cleanup requests with &rm=1
Response markersResponses containing WP2SHELL_OUT_START / WP2SHELL_OUT_END

If you find evidence of compromise: assume full server compromise. Patching does not evict an attacker who already has a shell. Take the site offline, rotate all secrets (database password, wp-config.php salts, API keys, admin passwords), restore from a known-clean pre-incident backup, then patch before bringing it back. When in doubt, engage an incident-response professional.

Public PoC code & research resources

Use responsibly. The repositories below contain working exploit code and are listed for defenders, researchers and penetration testers to understand and test the flaw on systems they own or are authorised to assess. Running exploits against a site you don't control is illegal in most jurisdictions. All links are external and open in a new tab. WatchTowr has reported in-the-wild exploitation, so treat any unpatched, internet-facing install as a live target.
ResourceTypeWhat it is
0xsha/wp2shell PoC (Python) Single-file, stdlib-only tool that unifies several public PoCs; forges a post via the UNION confusion, creates an admin, and drops a token-gated webshell. Verified end-to-end in-lab.
Icex0/wp2shell-poc PoC (Python) Independent PoC for the batch-route SQLi chain with three modes: check (confirms the SQLi path), read (DB read), and shell (plugin-backed command shell).
mverschu/CVE-2026-63030 PoC (Exploit) PoC exploit for the unauthenticated RCE; documents affected 6.9.0–6.9.4 / 7.0.0–7.0.1 and fixes in 6.9.5 / 7.0.2.
ProjectDiscovery Nuclei template Detection Community Nuclei template for non-intrusive detection of WP2Shell across many hosts at once (search the repo for “wp2shell” / CVE-2026-63030).
ZSec – Code Trace Deep Dive Analysis Line-by-line trace of how the batch route confusion reaches the author__not_in SQL injection. Best read for understanding the root cause.
Rapid7 – Emergent Threat Report Analysis Vendor breakdown of the chain, exploitation status and detection guidance.
Searchlight Cyber advisory Primary source The original disclosure from the team (Adam Kues) that found the bug.
wp2shell.com Live checker Searchlight's hosted tool that tests whether a live instance is exposed. Occasionally offline under load.

Repositories can be renamed or taken down without notice, and GitHub may remove PoCs that violate its policies — if a link 404s, search GitHub for “wp2shell” or “CVE-2026-63030” for current mirrors. We only link to code already public and widely cited by mainstream security outlets.

WP2Shell FAQ

What WordPress versions are vulnerable to WP2Shell?

The full remote code execution chain affects WordPress 6.9.0 through 6.9.4 and 7.0.0 through 7.0.1. WordPress 6.8.0–6.8.5 is affected by the underlying SQL injection (CVE-2026-60137) but cannot be driven to full RCE because the vulnerable batch route was introduced in 6.9. Versions below 6.8.0 are not affected. Fixed releases are 7.0.2, 6.9.5 and 6.8.6.

What are the CVE numbers for WP2Shell?

WP2Shell is a chain of two CVEs: CVE-2026-63030 (REST API batch-route confusion leading to RCE) and CVE-2026-60137 (SQL injection in WordPress core). They are exploited together.

Is there a public WP2Shell PoC or exploit?

Yes. Public exploit code and testing tools circulated shortly after disclosure on 17 July 2026, which is why patching immediately is critical. Searchlight Cyber, who discovered the flaw, deliberately withheld deep technical detail at disclosure to give defenders time, but working exploits are now in the wild.

Do I need a plugin to be vulnerable?

No. This is a WordPress core vulnerability. A stock install with no plugins on an affected version is exploitable by an anonymous, unauthenticated attacker.

I'm on Cloudflare — am I safe?

Cloudflare deployed managed WAF rules covering both CVEs for proxied sites across all plans, including free, which blocks known exploitation patterns. That's a strong safety net, but it's a filter, not a fix — you should still update WordPress to the patched version.

How do I know if my site was already hacked?

Search your access logs for POST requests to /wp-json/batch/v1 with SQL-like author_exclude / author__not_in parameters, scan for recently-modified or web-shell PHP files, run wp core verify-checksums, and check for unfamiliar administrator accounts (and the hidden wp-content/mu-plugins/ folder). See the detection section above for exact commands.

Is my WAF rule for WP2Shell enough?

Maybe not. Patchstack found that WAF rules matching only the URL (looking for batch/v1 in the path or query string) can be bypassed: WordPress reads the rest_route variable from the POST body before the query string, so an attacker can send POST / with rest_route=/batch/v1 in the body and the URL never shows the batch route. Live attacks using this shape were seen from 21 July 2026. Your rule must match the batch route in the URL and the POST body. The must-use plugin mitigation avoids this problem entirely because it inspects the resolved route, not the URL.

Sources & further reading

Post a Comment