wp2shell: Critical Unauthenticated RCE Chain in WordPress Core

By

wp2shell is a critical pre-authentication remote code execution chain in WordPress Core that requires no plugins and affects default installations. In-the-wild exploitation has been confirmed, and administrators must immediately update to WordPress 6.9.5, 7.0.2, or 7.1 beta2.

The vulnerability was publicly disclosed on July 17, 2026, and patched the same day in WordPress 7.0.2, 6.9.5, and 7.1 beta2. Due to its severity, WordPress.org also enabled forced auto-updates across affected sites (WordPress, 2026).

The flaw chains together two bugs: a REST API batch-route confusion vulnerability in the default /wp-json/batch/v1 endpoint, tracked as CVE-2026-63030, which smuggles attacker-controlled input into an SQL injection in WP_Query's author__not_in parameter, tracked as CVE-2026-60137 (The Hacker News, 2026). When chained, the vulnerabilities allow an anonymous HTTP request to result in the creation of a rogue administrator account and ultimately lead to code execution.

The batch-route confusion bug was discovered and responsibly disclosed by Adam Kues of Assetnote/Searchlight Cyber, while the SQL injection was reported separately by TF1T, dtro, and haongo (WordPress, 2026). Unlike most WordPress security issues, which originate in plugins or themes, wp2shell exists in WordPress Core, affects bare installations, and is reachable through the batch endpoint, which is enabled by default (Eye Security, 2026).

Vulnerability Type (CWE)

The official WordPress GHSA advisories assigned no CWEs. The CWEs below come from the WPScan CNA and CISA-ADP CVE records (WPScan, 2026; CISA-ADP, 2026).

CWE-89: Improper Neutralization of Special Elements used in an SQL Command (SQL Injection) Assigned to CVE-2026-60137 by both WPScan (CNA) and CISA-ADP. The author__not_in query var accepts a scalar string that is interpolated directly into raw SQL with no escaping when the input is not an array.

CWE-436: Interpretation Conflict Assigned to CVE-2026-63030 by both WPScan (CNA) and CISA-ADP. The batch endpoint's parallel $matches / $validation array desync lets a validated request execute under a different request's handler, bypassing parameter sanitization and the method allow-list.

CVE-2026-63030

Base Score: 7.5 (High)

Attack Vector: Network (AV:N)

Attack Complexity: Low (AC:L)

Privileges Required: None (PR:N)

User Interaction: None (UI:N)

Scope: Unchanged (S:U)

Confidentiality: High (C:H)

Integrity: None (I:N)

Availability: None (A:N)

CVE-2026-60137

Base Score: 9.1 (Critical)

Attack Vector: Network (AV:N)

Attack Complexity: Low (AC:L)

Privileges Required: None (PR:N)

User Interaction: None (UI:N)

Scope: Unchanged (S:U)

Confidentiality: High (C:H)

Integrity: High (I:H)

Availability: None (A:N)

💡

CVE-2026-63030 — NIST/NVD has not officially assigned a CVSS base score yet. WPScan originally assigned a Base Score of 9.8 (Critical), but the National Vulnerability Database via CISA-ADP lowered it to 7.5 (High) due to its limited standalone impact.

💡

CVE-2026-60137 — NIST/NVD has not officially assigned its own CVSS base score yet. WPScan originally assigned a Base Score of 5.9 (Medium), but the National Vulnerability Database via CISA-ADP elevated it to 9.1 (Critical) due to its limited standalone impact.

Impacted Versions

The two vulnerabilities affect different WordPress versions, which determines the level of exposure. The SQL injection affects versions dating back to 6.8, while the route-confusion flaw—and therefore the full RCE chain—only affects versions 6.9 and later.

Version RangeExposureFixed In
< 6.8.0Not affected
6.8.0 – 6.8.5SQL injection only (no RCE chain)6.8.6
6.9.0 – 6.9.4Full unauthenticated RCE chain6.9.5
7.0.0 – 7.0.1Full unauthenticated RCE chain7.0.2
7.1 betaFull unauthenticated RCE chain7.1 beta2

Narrowing condition

The code-execution path depends on the site not using a persistent external object cache, such as Redis or Memcached, because those caches change how the underlying query is constructed. According to Cloudflare, this may reduce the blast radius for some larger deployments, but it provides no protection for standard WordPress installations, which do not use an external object cache by default. This is an incidental side effect, not a fix, and it does not prevent exploitation of the SQL injection. A default installation running an affected version of WordPress Core is sufficient exposure.

Mitigation Steps

Check if your instance is vulnerable at wp2shell.com/. If it is, update immediately—this is the only effective fix. Do not assume the forced auto-update reached your site; confirm the installed WordPress version directly in your dashboard.

WordPress BranchMitigation
6.8.xUpdate to 6.8.6 (patches SQLi only)
6.9.xUpdate to 6.9.5
7.0.xUpdate to 7.0.2
7.1 betaUpdate to 7.1 beta2

If you cannot update immediately, all temporary mitigations should focus on preventing unauthenticated requests from reaching the batch endpoint. These measures may disrupt legitimate REST integrations and should be treated only as short-term stopgaps (Searchlight Cyber, 2026):

  • Block both /wp-json/batch/v1 and requests using the query parameter rest_route=/batch/v1 at the WAF. Cloudflare's managed WAF blocks the exploit chain for sites behind its network. However, blocking the endpoint can break the WordPress Block Editor (Gutenberg) and several modern plugin dashboards that rely heavily on the batch API to save data. Use this only as an absolute last resort if patching is delayed.
  • Install the Disable WP REST API plugin to block unauthenticated REST API access entirely.
  • Deploy a must-use plugin that rejects unauthenticated requests to /batch/v1 at rest_pre_dispatch. The WordPress advisory provides an example implementation (WordPress, 2026):
<?php
  /**
   * Plugin Name: Disable Unauthenticated REST Batch API
   * Description: Requires an authenticated WordPress user for REST batch requests.
   * Version: 1.0.0
   * Requires at least: 5.6
   * License: GPL-2.0-or-later
   */
  defined( 'ABSPATH' ) || exit; 

  function wporg_require_authentication_for_rest_batch( $result, $server, $request ) {
      if ( '/batch/v1' !== strtolower( untrailingslashit( $request->get_route() ) ) || is_user_logged_in() ) {
          return $result;
      }
      return new WP_Error(
          'rest_batch_authentication_required',
          'Authentication is required to use the batch API.',
          array( 'status' => 401 )
      );    
  }
  add_filter( 'rest_pre_dispatch', 'wporg_require_authentication_for_rest_batch', -1000, 3 );
  

Reset passwords even on SQL-injection-only sites running WordPress 6.8.x. The SQL injection can read the wp_users table, meaning an attacker may already have obtained password hashes. Force a password reset for every account, prioritizing administrators, rotate all API keys, and instruct users to change any passwords they reused elsewhere. Patching alone does not protect sites that may have been compromised before the update was applied (Eye Security, 2026).

Linux Forensics & Incident Response

Let the database rows and file timestamps lead; use logs only to corroborate source IP, timing, and entry point.

  • Database (primary evidence) Dump wp_users, wp_usermeta, and wp_options from a copy via mysqldump, not the live DB. Look for unexpected admin rows and the artifacts above.
  • Web root (/var/www/html or the site docroot) Hunt recently modified/created PHP files — find DOCROOT -name "*.php" -newermt "2026-07-16" -printf "%TY-%Tm-%Td %TH:%TM %p\n" is a fast first pass. For a faster, highly reliable check of core file integrity, run wp core verify-checksums via WP-CLI. This will instantly flag any core files the attacker modified to plant a backdoor.
  • Web server logs (/var/log/apache2/, /var/log/nginx/) Access log shows the batch entry point and any later webshell hits, but not the injection. The Apache error.log can leak an injected query if a UNION threw a SQL syntax error.
  • PHP / FPM logs (/var/log/php*-fpm.log, per-directory error_log) Fatal errors/warnings around the intrusion window corroborate.
  • Sessions & temp (/var/lib/php/sessions/, /tmp) Dropped payloads or staging files.
  • System & auth (/var/log/auth.log or secure, ~/.bash_history for www-data and service accounts, /etc/crontab, /etc/cron.*, per-user crontabs, /etc/passwd) Persistence and added local accounts.

Preserve evidence before beginning remediation. Create read-only images of the database and web root before making changes.

Exploit Process

A fully functional proof of concept has been publicly available on GitHub since shortly after disclosure and has been independently verified to execute the complete attack chain against an affected default installation (Icex0, 2026; Eye Security, 2026). Searchlight delayed publishing its technical analysis to give defenders the weekend to respond. However, because WordPress Core is open source and the security release identified the modified files, other researchers were able to reverse-engineer the patch and publish details of the exploit mechanism within a day (The Hacker News, 2026). The exploit chain proceeds as follows (Searchlight Cyber, 2026):

  1. Route confusion (CVE-2026-63030) The unauthenticated /wp-json/batch/v1 endpoint validates and executes sub-requests across three parallel arrays — requests, matches, and validation — inside WP_REST_Server::serve_batch_request_v1(). A malformed sub-request appends to validation but not matches (the continue skips it), shifting the arrays out of step by one. A validated request then executes under a different request's handler.
  2. Bypass the method allow-list The batch API rejects GET, but the SQLi sink is GET-only. The PoC nests the batch call recursively — the outer desync means the inner request's method field is never validated — allowing a GET to reach /wp/v2/posts.
  3. Reach the SQLi sink (CVE-2026-60137) Via the desync, author_exclude lands in WP_Query's author__not_in. A scalar string (rather than an array) skips the absint sanitization and is interpolated directly into raw SQL. A payload like 0) OR 1=1 -- confirms injection; UNION-based reads leak arbitrary DB values as a forged wp_posts-shaped row.
  4. Escalate to admin without cracking hashes UNION-forged fake posts poison the in-memory post cache; the oEmbed feature is abused to fabricate real oembed_cache rows, which are then recast (via cache/DB reconciliation and a self-parent cycle-detection gadget) into a customize_changeset that applies changes with administrator (user ID 1) authority. A crafted parse_request hook replays the batch request under the temporarily assumed admin role, allowing an embedded "create administrator" sub-request to succeed on the second pass.
  5. Code execution The attacker logs in as the generated administrator and uploads a backdoor plugin from a ZIP, achieving RCE.

Steps 1 through 4 occur before authentication. Only the final plugin-upload step is authenticated as the attacker-created admin. A complete exploit may involve anywhere from a dozen to several dozen requests, with the critical actions embedded in batch POST bodies that rarely surface in access logs (Eye Security, 2026).

Exploitation Status

Multiple security firms confirmed in-the-wild exploitation of unpatched WordPress instances on July 20, 2026 (SecurityWeek, 2026). This progression was expected: mass exploitation of WordPress vulnerabilities is well established, the proof of concept and patch are both public, and the bug affects default configurations—a combination that has historically led to widespread opportunistic scanning (The Hacker News, 2026). Rapid7 also released unauthenticated vulnerability checks for InsightVM and Nexpose in its July 20 content release (Rapid7, 2026). Treat this vulnerability as actively exploited and prioritize remediation immediately.

Timeline

DateEvent
Prior to July 17, 2026Vulnerabilities are disclosed to WordPress by Adam Kues (Searchlight Cyber / Assetnote) for CVE-2026-63030 and researchers TF1T, dtro, and haongo for CVE-2026-60137
July 17, 2026WordPress releases security updates 6.9.5 and 7.0.2 to fix both flaws, enabling forced automatic updates globally
July 18, 2026Public Proof-of-Concept (PoC) exploit scripts for the full "wp2shell" chain begin circulating on GitHub
July 20, 2026In-the-wild exploitation of unpatched WordPress instances is confirmed by multiple security firms.

TTPs & IOCs

Because rogue accounts and plugin directories use a fixed prefix followed by a random suffix, detection should match the prefix rather than an exact string. The database is the primary source of evidence, as a successful exploit may leave little or no trace in standard web server access logs. The absence of matching log entries should not be treated as evidence that a site is safe (Eye Security, 2026).

These indicators are based on the public proof of concept rather than the vendor advisory. At the time of writing, the primary disclosures do not include wp2shell-specific indicators of compromise, meaning a modified version of the exploit may not match them (SOCRadar, 2026).

Network-based:

  • Requests to the exploit endpoint: /wp-json/batch/v1 and /?rest_route=/batch/v1.
  • Batch POST bodies containing nested requests arrays and the author_exclude parameter carrying a scalar string value.

Host / database artifacts (residual evidence the PoC leaves behind):

  • Rogue admin logins matching wp2_* or w2s_*.
  • Rogue admin email domains @wp2shell.invalid or @wp2shell.shellcode.lol.
  • oembed_cache loopback rows in wp_posts.
  • customize_changeset posts with very high parent IDs.
  • Orphaned usermeta rows and user-ID gaps in wp_users / wp_usermeta.
  • Unexpected administrator rows; tampered active_plugins, siteurl / home, or injected _transient_ / object-cache entries in wp_options.

Note the PoC self-cleans the rogue admin and webshell but leaves the oEmbed cache rows and changeset/request posts — those are your durable detection anchors (Eye Security, 2026).

Centripetal's Perspective

A self-cleaning exploit chain like wp2shell illustrates why post-incident detection is a weak defense against pre-authentication vulnerabilities in WordPress Core. The critical steps are embedded in batch POST bodies that rarely appear in standard web server access logs, while the public proof of concept removes both the rogue administrator account and webshell after execution. This may leave only the oembed_cache and customize_changeset rows as the only potential residual evidence.

Because the intrusion is designed to leave minimal host-based traces, the most reliable point of control is the network edge, where the exploit request can be blocked before it reaches the vulnerable endpoint.

wp2shell is an unauthenticated, pre-authentication RCE vulnerability in WordPress Core, with a publicly available working exploit against a default installation. Confirm that your site is running a fixed release—6.8.6, 6.9.5, 7.0.2, or 7.1 beta2, depending on your branch. Verify the installed version directly; do not assume the forced update was successfully applied.

Then address the more difficult question: was the site compromised before it was patched? Run a compromise assessment, review all administrator accounts, and collect the artifacts identified above. Finding no indicators reduces the likelihood of compromise, but it does not prove the site was never accessed.

Resources

Know what’s coming. Stop what’s next.

Sign up for our free threat alert bulletin service here.

The Cybercrime Barrier Your Organization Deserves

Sign up for a custom demonstration from our security team of how we bring together the best minds and most complete collection of threat intelligence to provide you with a shocking level of relief.