Someone left every front door in the neighborhood unlocked and published the master key on Twitter
CVE-2026-63030 is the route-confusion half of the wp2shell chain — a pre-authentication remote code execution path in WordPress Core. The REST API /wp-json/batch/v1 endpoint processes validation and execution in separate loops; when wp_parse_url() fails on a crafted sub-request path, the validation array and the matches array fall out of sync, causing every subsequent sub-request to dispatch under the wrong handler. This desync lets an unauthenticated attacker bypass route-level permission checks and funnel a malicious author__not_in parameter directly into WP_Query, where CVE-2026-60137's unsanitized SQL injection completes the chain. The result is UNION-based SQLi that extracts admin password hashes, followed by admin login, malicious plugin upload, and full code execution — all from a single anonymous HTTP POST. Affected versions are WordPress 6.9.0–6.9.4 and 7.0.0–7.0.1; the fix shipped in 6.9.5 and 7.0.2 on July 17, 2026. The companion SQLi (CVE-2026-60137) also affects 6.8.x before 6.8.6.
The vendor rated this CRITICAL at CVSS 9.8 and that score is *entirely accurate*. There is no inflation here. The chain requires zero authentication, zero user interaction, zero non-default configuration, and zero special plugins. It works against a stock WordPress install exposed to the internet — which describes hundreds of millions of sites. WordPress shipped forced auto-updates the same day, but Censys data showed only ~86% of public WordPress sites were on the latest security patch, leaving an estimated 90 million sites in the blast radius. CISA added it to KEV within four days with a three-day remediation deadline. GreyNoise, CrowdSec, and F5 Labs all confirmed mass automated exploitation within 72 hours of disclosure. The vendor severity is not only justified, it may understate the operational urgency given the installed base.
6 steps from start to impact.
Locate target WordPress installation
<meta name="generator"> tags, /wp-login.php, and /wp-json/ API responses. Automated tooling like fullhunt/wp2shell-scan and nuclei templates fingerprint the exact core version and confirm the /batch/v1 route is reachable — all without sending an exploit payload.- Target runs WordPress 6.9.0–6.9.4 or 7.0.0–7.0.1
- REST API is reachable (default on)
- No persistent object cache (Redis/Memcached) is configured
- WordPress auto-updates may have already patched the site
- A WAF blocking
/batch/v1would stop reconnaissance
wp2shell-exposure.yaml; Rapid7 InsightVM content release July 20, 2026; Qualys QID available; Tenable plugin available. Version fingerprinting is trivial.Trigger batch route confusion
POST /wp-json/batch/v1 request with a crafted JSON body containing nested sub-requests. The first sub-request uses a malformed path (e.g. ///) that causes wp_parse_url() to fail. This failure populates the validation array but not the route-matches array, creating an off-by-one desynchronization. Every subsequent sub-request in the batch now dispatches under the *wrong* handler, bypassing the intended route's permission callback entirely.- Network access to the WordPress REST API endpoint
- No authentication required
- A WAF with a rule blocking nested batch requests or malformed paths would intercept this
- Rate limiting on the REST API endpoint could slow but not prevent exploitation
/wp-json/batch/v1 or ?rest_route=/batch/v1 with nested JSON arrays. F5 Labs captured the canonical payload at 735 bytes. CrowdSec published detection signatures July 20, 2026.Inject SQL via author__not_in
/wp/v2/posts with a crafted author_exclude parameter. Because the desync caused this request to dispatch under the wrong handler, WP_Query receives the attacker-controlled string without sanitization. The attacker closes the original NOT IN (...) clause with 0) and appends a UNION SELECT statement targeting wp_users and wp_usermeta tables, extracting admin usernames and password hashes in a single response.- Route confusion from step 2 is active
- WordPress database contains at least one admin user (always true on any install)
- A database firewall or SQL-aware WAF could detect UNION-based injection patterns
- Prepared statements in a hypothetical code fix would neutralize the injection
author_exclude parameter values in access logs; database query logs showing UNION SELECT against wp_users.Crack admin password hash or create admin account
-m 400) succeeds in minutes. Alternatively, some PoC variants use the SQLi to directly insert a new admin row into wp_users and wp_usermeta, bypassing the need to crack anything. The attacker now has full WordPress admin access.- Successful SQLi from step 3
- For hash cracking: admin password is not extremely strong (most are not)
- For direct INSERT: MySQL user has INSERT privileges (default for WordPress DB user)
- MFA on wp-admin login would block hash-cracking path (but not direct DB INSERT path)
- Strong, unique admin passwords slow the cracking path
wp_users without corresponding audit trail; wp-admin login from previously unseen geolocation.Upload malicious plugin for code execution
/wp-admin/plugin-install.php). The webshell executes arbitrary system commands as the web server user (typically www-data or apache). GreyNoise documented attackers uploading custom reconnaissance plugins, executing AMSI bypass scripts, and performing credential harvesting from readable config files including wp-config.php database credentials.- WordPress admin access from step 4
- Plugin upload functionality is enabled (default)
- Read-only filesystem or restricted
wp-content/pluginspermissions would block upload - File integrity monitoring would alert on new PHP files
/wp-content/plugins/ without admin action; PHP files with eval(), system(), exec(), or base64_decode() patterns; outbound connections from the web server to C2 infrastructure.Post-exploitation: data exfiltration and lateral movement
wp-config.php for database credentials, dumps the full database (all user PII, e-commerce transactions, session tokens), and pivots to other services reachable from the web server's network position. GreyNoise documented one campaign exfiltrating 18,000+ sensitive government records including plaintext passwords and law enforcement PII from a single compromised WordPress instance. The attacker also attempted privilege escalation on the underlying OS.- Code execution from step 5
- Web server has network access to database and potentially internal services
- Network segmentation limiting web server egress would constrain lateral movement
- Database access controls beyond wp-config.php credentials would limit data theft
/wp-json/batch/v1 or containing rest_route=/batch/v1. This is the highest-leverage mitigation — deploy within 3 days per the noisgate mitigation SLA for CRITICAL. The batch API endpoint is rarely used by legitimate front-end traffic; verify with your development team before deploying, but most sites can block this with zero functional impact.Disable REST API or add a rest_authentication_errors filter that requires authentication for all REST API endpoints. This blocks the entire unauthenticated attack surface. Deploy within 3 days. Residual risk: authenticated users with Subscriber or higher roles could still theoretically reach the batch endpoint, but the route confusion payload in known PoCs targets the unauthenticated path.wp core update) or your configuration management tool. Per the noisgate remediation SLA for CRITICAL, complete patching within 90 days, though given KEV status and active exploitation, the real deadline is immediately. CISA's BOD 26-04 required federal agencies to patch by July 24, 2026./wp-json/batch/v1; check wp_users for accounts created after July 17; scan wp-content/plugins/ for unknown PHP files; review wp-config.php access timestamps; check for cron jobs or persistence mechanisms added by the web server user. GreyNoise documented attackers backdating malicious admin accounts to 2025 to evade detection.- IP-based blocking — Attackers use thousands of distributed IPs across multiple ASNs and geographies (US, Iran, Singapore, Germany, France, Netherlands). Blocking individual IPs is whack-a-mole.
- Rate limiting alone — The exploit requires a single HTTP request. Rate limiting cannot stop a one-shot attack; it only slows enumeration of additional targets.
- WordPress application-level security plugins (Wordfence, Sucuri, iThemes) — While some updated their WAF rules post-disclosure, the vulnerability is in WordPress Core *before* plugin code executes. A plugin-level WAF that loads after WordPress Core initialization may not intercept the batch endpoint processing in time. Prefer network-level WAF/reverse proxy rules.
- Disabling XML-RPC — This vulnerability is in the REST API (
/wp-json/), not XML-RPC (/xmlrpc.php). Disabling XML-RPC provides zero protection against wp2shell.
The supporting signals.
| In-the-Wild Status | Actively exploited at mass scale. CISA KEV-listed July 21, 2026 (due date July 24). CrowdSec classified exploitation as "Rapid Escalation" by July 28. GreyNoise tracked a campaign (suspected Red Heron / Chinese-speaking actor in UTC+8) that compromised 49 organizations across 29 countries, including a western government entity with 18,000+ sensitive records stolen. F5 Labs captured canonical exploit payloads from automated scanning infrastructure. CrowdSec observed thousands of unique attacking IPs worldwide. |
|---|---|
| PoC Availability | Extensively weaponized. Multiple full-chain RCE PoCs on GitHub: mcipekci/wp2shell, Icex0/wp2shell-poc, 4minx/CVE-2026-63030, GhostInExile/CVE-2026-63030-Wp2Shell, c0gnit00/Wp2Shell, 0xsha/wp2shell. Scanners: fullhunt/wp2shell-scan, ZephrFish/wp2shell-scanner, zi3lak/wp2shell_scanner. Nuclei template available (projectdiscovery/nuclei-templates#16602). First PoC appeared within hours of disclosure. |
| EPSS Score | 0.10649 (10.6% probability of exploitation in 30 days) per user-provided data. Some aggregators report higher values post-KEV listing. Regardless of the exact EPSS percentile, the real-world exploitation data makes probability scoring academic — this is *already* being exploited at scale. |
| CISA KEV | Yes. Added July 21, 2026 with a July 24, 2026 remediation deadline (3 calendar days). Forensic triage flagged as required. Ransomware association: Unknown. Bound by BOD 26-04. |
| CVSS Vector | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H (9.8). Every base metric is at maximum severity except Scope (Unchanged). Network-attackable, no complexity, no privileges, no interaction, full CIA impact. This is the platonic ideal of a 9.8. |
| Affected Versions | CVE-2026-63030 (route confusion): WordPress 6.9.0–6.9.4, 7.0.0–7.0.1. CVE-2026-60137 (SQLi component): WordPress 6.8.0–6.8.5, 6.9.0–6.9.4, 7.0.0–7.0.1. Both must be present for the RCE chain. Note: the route confusion bug was introduced in 6.9.0, so WordPress ≤6.8.x has the SQLi but NOT the batch desync — no RCE chain on 6.8.x. |
| Fixed Versions | WordPress 6.8.6 (SQLi only), 6.9.5, 7.0.2 (both bugs). WordPress 7.1 Beta 2+ also contains the fix. Forced auto-updates were triggered globally on July 17, 2026. |
| Scanning / Exposure Data | GreyNoise: AS211736 carried ~50% of scanning volume; ports 80 and 8080 targeted. CrowdSec: thousands of unique IPs, led by US and Iran. F5 Labs: canonical payload is 735 bytes, identical across targets (automated). Censys (pre-disclosure): only ~14% of public WordPress sites on latest patch. Post-auto-update estimates: ~15% still vulnerable = ~90 million sites globally. |
| Disclosure & Researcher | Discovered by Adam Kues of Searchlight Cyber. Disclosed July 17, 2026 with coordinated WordPress security release. Searchlight Cyber withheld full technical details initially, then published the complete attack chain on July 20, 2026. First exploitation observed July 20, 2026 (same day as full writeup). |
| Related Vulnerabilities | CVE-2026-60137 (WP_Query author__not_in SQLi, CVSS 5.9 MEDIUM) is the mandatory chain partner. Neither CVE alone achieves unauthenticated RCE — the route confusion bypasses auth, and the SQLi provides the injection surface. The combined chain is branded wp2shell. |
Sources.
- CISA KEV Catalog Entry
- Rapid7 ETR: wp2shell Critical RCE in WordPress Core
- Horizon3.ai: CVE-2026-60137 & CVE-2026-63030 Attack Research
- GreyNoise: Government Records Stolen via WordPress Exploitation
- F5 Labs: wp2shell Captured Exploit Payload Analysis
- CrowdSec: WordPress wp2shell SQLi-to-RCE Tracking
- WordPress 7.0.2 Security Release
- Tenable: wp2shell Pre-Auth RCE FAQ
Why this verdict
- Zero authentication friction: The entire chain — from route confusion through SQLi to admin credential extraction — executes via a single unauthenticated HTTP POST to a default-enabled endpoint. No session, no cookie, no API key. This is the lowest possible attacker effort.
- Zero configuration friction: The vulnerability affects *stock WordPress installations* with no plugins, no custom configuration, and no unusual setup. The only precondition that might limit exploitation (persistent object cache via Redis/Memcached) is absent from the vast majority of WordPress deployments — most hosting providers ship WordPress without it.
- Mass weaponization and active exploitation: Multiple full-chain PoCs appeared within hours. Automated scanning tools, nuclei templates, and turnkey exploit scripts are publicly available. CISA KEV-listed within 4 days. GreyNoise confirmed campaigns compromising government entities. CrowdSec classified this as "Rapid Escalation" within 11 days. This is not theoretical — it is happening right now at industrial scale.
- Role multiplier — internet-facing web application tier: WordPress is the world's most popular CMS, powering ~40% of all websites. Every WordPress installation is, by definition, an internet-facing application handling HTTP traffic. Deployment roles include: (a) Low-value: personal blogs, dev sandboxes; (b) Typical: business marketing sites, corporate blogs; (c) High-value: e-commerce platforms (WooCommerce with PII/payment card data), government portals (confirmed compromised — 18,000+ law enforcement records stolen), enterprise customer portals, news organizations, healthcare patient portals. The chain succeeds identically across all roles because no role-specific configuration blocks it. The blast radius per compromised site is: full database contents (all user credentials, PII, payment data, session tokens) + code execution on the web server + lateral movement potential to backend databases and internal networks. For e-commerce and government deployments, this is mass data breach / regulatory catastrophe territory. Given that WordPress is *canonically* an internet-facing, data-bearing application — and the chain works on default installs — the verdict floor is CRITICAL.
- Installed base magnitude: ~600 million WordPress sites worldwide. Even with forced auto-updates, an estimated 90 million remained vulnerable weeks after disclosure. The attack surface is *orders of magnitude* larger than typical enterprise CVEs. A single automated scanning campaign can compromise thousands of sites per day with no human operator involvement.
Why not higher?
The CVSS 9.8 and CRITICAL rating are already at the ceiling of the scoring system. The only way this could be worse is if it were a wormable vulnerability with Scope: Changed (affecting the underlying OS or adjacent systems beyond the WordPress application), but the initial impact is constrained to the WordPress application context and its database, requiring post-exploitation steps for lateral movement.
Why not lower?
Every downgrade argument fails the reality test. Authentication? None needed. Complexity? Single HTTP request. Configuration dependency? Default installs are vulnerable. Exploitation maturity? Fully weaponized with mass scanning. Installed base? Largest CMS on the planet. Real-world impact? Government data breaches confirmed. There is literally no friction point in this chain that would justify any reduction from 9.8 CRITICAL. Even the persistent-object-cache exclusion works *in favor* of exploitation, since the vast majority of WordPress sites don't use one.
Crowdsourced verification payload.
Run this script on a remote auditor workstation (not the target) with curl installed. Pass the target WordPress site URL as the first argument: bash wp2shell_check.sh https://example.com. No special privileges required — it only performs safe, non-exploitative HTTP GET requests to fingerprint the WordPress version and check batch endpoint reachability.
#!/usr/bin/env bash
# wp2shell_check.sh — CVE-2026-63030 exposure checker (non-exploitative)
# Usage: bash wp2shell_check.sh https://target-wordpress-site.com
# Exit codes: 1=VULNERABLE, 0=PATCHED, 2=UNKNOWN
set -euo pipefail
TARGET="${1:?Usage: $0 https://target-site.com}"
TARGET="${TARGET%/}" # strip trailing slash
echo "[*] Checking CVE-2026-63030 (wp2shell) exposure for: $TARGET"
echo ""
# Step 1: Fingerprint WordPress version
echo "[*] Step 1: Fingerprinting WordPress version..."
VERSION=""
# Try generator meta tag
GEN=$(curl -sS -L --max-time 10 "$TARGET/" 2>/dev/null | grep -ioP 'content="WordPress \K[0-9]+\.[0-9]+\.?[0-9]*' | head -1)
if [ -n "$GEN" ]; then
VERSION="$GEN"
echo " Found version via meta tag: $VERSION"
fi
# Try feed generator if meta tag failed
if [ -z "$VERSION" ]; then
FEED=$(curl -sS -L --max-time 10 "$TARGET/feed/" 2>/dev/null | grep -ioP 'wordpress.org/\?v=\K[0-9]+\.[0-9]+\.?[0-9]*' | head -1)
if [ -n "$FEED" ]; then
VERSION="$FEED"
echo " Found version via feed: $VERSION"
fi
fi
# Try REST API root
if [ -z "$VERSION" ]; then
API=$(curl -sS -L --max-time 10 "$TARGET/wp-json/" 2>/dev/null | grep -oP '"version"\s*:\s*"\K[0-9]+\.[0-9]+\.?[0-9]*' | head -1)
if [ -n "$API" ]; then
VERSION="$API"
echo " Found version via REST API: $VERSION"
fi
fi
if [ -z "$VERSION" ]; then
echo " [!] Could not determine WordPress version."
echo ""
echo "RESULT: UNKNOWN — version fingerprinting failed. Verify manually."
exit 2
fi
# Step 2: Check if version is in the vulnerable range
echo "[*] Step 2: Checking version against vulnerable ranges..."
# Parse major.minor.patch
IFS='.' read -r MAJOR MINOR PATCH <<< "$VERSION"
PATCH=${PATCH:-0}
VULN_VERSION=false
# Vulnerable: 6.9.0-6.9.4 and 7.0.0-7.0.1
if [ "$MAJOR" -eq 6 ] && [ "$MINOR" -eq 9 ] && [ "$PATCH" -le 4 ]; then
VULN_VERSION=true
echo " Version $VERSION is in vulnerable range (6.9.0-6.9.4)"
elif [ "$MAJOR" -eq 7 ] && [ "$MINOR" -eq 0 ] && [ "$PATCH" -le 1 ]; then
VULN_VERSION=true
echo " Version $VERSION is in vulnerable range (7.0.0-7.0.1)"
else
echo " Version $VERSION is NOT in a vulnerable range."
fi
# Step 3: Check batch endpoint reachability
echo "[*] Step 3: Checking batch endpoint reachability..."
BATCH_REACHABLE=false
HTTP_CODE=$(curl -sS -o /dev/null -w '%{http_code}' --max-time 10 "$TARGET/wp-json/batch/v1" 2>/dev/null || echo "000")
echo " /wp-json/batch/v1 returned HTTP $HTTP_CODE"
# Also check alternate path
HTTP_CODE_ALT=$(curl -sS -o /dev/null -w '%{http_code}' --max-time 10 "$TARGET/?rest_route=/batch/v1" 2>/dev/null || echo "000")
echo " /?rest_route=/batch/v1 returned HTTP $HTTP_CODE_ALT"
if [ "$HTTP_CODE" != "403" ] && [ "$HTTP_CODE" != "404" ] && [ "$HTTP_CODE" != "000" ] && [ "$HTTP_CODE" != "401" ]; then
BATCH_REACHABLE=true
fi
if [ "$HTTP_CODE_ALT" != "403" ] && [ "$HTTP_CODE_ALT" != "404" ] && [ "$HTTP_CODE_ALT" != "000" ] && [ "$HTTP_CODE_ALT" != "401" ]; then
BATCH_REACHABLE=true
fi
# Step 4: Verdict
echo ""
echo "========================================"
if $VULN_VERSION && $BATCH_REACHABLE; then
echo "RESULT: VULNERABLE"
echo "WordPress $VERSION is in the affected range AND the batch endpoint is reachable."
echo "ACTION: Update to WordPress 6.9.5 / 7.0.2 immediately, or block /batch/v1 at WAF."
exit 1
elif $VULN_VERSION && ! $BATCH_REACHABLE; then
echo "RESULT: VULNERABLE (mitigated)"
echo "WordPress $VERSION is in the affected range but the batch endpoint appears blocked."
echo "ACTION: Still patch to 6.9.5 / 7.0.2 — the WAF rule is a compensating control, not a fix."
exit 1
elif ! $VULN_VERSION; then
echo "RESULT: PATCHED"
echo "WordPress $VERSION is not in the vulnerable range for CVE-2026-63030."
exit 0
else
echo "RESULT: UNKNOWN"
exit 2
fi