Searchlight Cyber wp2shell WordPress RCE discovered with GPT‑5.6 Sol Ultra for $25

Searchlight Cyber wp2shell WordPress RCE discovered with GPT‑5.6 Sol Ultra for $25

TL;DR

Searchlight Cyber leveraged GPT‑5.6 Sol Ultra to find a pre‑authentication SQL injection in WordPress’s Batch API, chained it through cache poisoning, embed abuse, and a malicious customize_changeset to achieve remote code execution (RCE) on a default WordPress install—all for roughly $25 in compute time. The exploit demonstrates why exploit brokers are willing to pay $500 k for a WordPress RCE.


The Prompt That Started the Hunt

The researcher fed GPT‑5.6 Sol Ultra a six‑hour, four‑agent prompt adapted from OpenAI’s Cycle Double Cover (CDC) prompt. The prompt instructed the model to:

  • Treat the WordPress source tree as a closed codebase (no internet, no changelogs).
  • Explore a wide range of attack surfaces (input parsing, serialization, race conditions, etc.).
  • Aggressively spawn up to four agents, dynamically re‑balancing effort based on progress.
  • Produce a complete pre‑authentication RCE chain that can read /flag on a typical MySQL‑backed deployment.

The researcher cloned the latest stable WordPress release into wordpress‑ctf/main/ and left third_party/ empty to force the model to work solely with the core code.

The Core Bug: Batch API Validation Desynchronisation

WordPress’s REST Batch API validates all sub‑requests in a first loop, then executes them in a second loop. The validation loop populates two parallel arrays, $matches (handler references) and $validation (validation results). When a request fails is_wp_error($single_request), the code continues without pushing a corresponding entry into $matches:

if ( is_wp_error( $single_request ) ) {
    $has_error    = true;
    $validation[] = $single_request;
    continue;               // <-- $matches not updated
}

This off‑by‑one shift means the validation result at index i can be applied to the handler at index i+1. An attacker can therefore validate a harmless request and execute it with the handler of a different, vulnerable request.

The First Exploit Primitive: A Pre‑Auth SQL Injection

The GET /wp/v2/posts endpoint builds a SQL WHERE clause using the author__not_in parameter. When the parameter is a scalar string, WordPress interpolates it directly without escaping:

$author__not_in = implode(',', (array) $query_vars['author__not_in']);
$where .= " AND {$wpdb->posts}.post_author NOT IN ($author__not_in) ";

Normally the REST schema forces author__not_in to be an array, causing each element to be sanitized with absint. By using the Batch API desynchronisation bug, the attacker can feed a scalar string (e.g., "0) OR 1=1 -- ") to the validation of a different request while the execution occurs on the GET /wp/v2/posts handler, yielding a classic SQL injection that returns all rows.

Payload Example (recursive batch call)

POST /wp-json/batch/v1
{
  "requests": [
    {"method":"POST","path":"http://:"},
    {"method":"POST","path":"/wp/v2/posts","body":{
      "requests":[
        {"method":"GET","path":"http://:"},
        {"method":"DELETE","path":"/wp/v2/posts/1","body":{"author_exclude":"0) OR 1=1 -- "}},
        {"method":"GET","path":"/wp/v2/posts"}
      ]
    }},
    {"method":"POST","path":"/batch/v1"}
  ]
}

The outer request desynchronises the HTTP method validation; the inner request desynchronises the author_exclude parameter, delivering the injection.

From SQLi to RCE: Chaining Gadgets

The researcher let GPT‑5.6 continue beyond the SQLi, producing a multi‑stage chain:

  1. Cache Poisoning – WordPress caches WP_Post objects per request. By returning crafted rows via the SQLi, the attacker controls the in‑memory representation of posts, including their post_content.
  2. Embed Abuse – Embedding a local post ([embed]/?p=10[/embed]) creates an oembed_cache row in the database. The attacker can fabricate such rows with arbitrary post_type and post_content.
  3. Customize Changeset – A customize_changeset post stores a JSON diff that is applied with the user_id embedded in the payload. By forcing the changeset’s user_id to 1 (the admin), WordPress temporarily runs with administrator privileges.
  4. Parent‑Cycle Gadget – WordPress detects cycles in the post‑parent hierarchy and, when found, calls wp_update_post(['ID'=>X,'post_parent'=>0]). This call does not overwrite post_content, allowing the attacker‑controlled content to persist.
  5. Hook Replay – The cycle gadget can be used to trigger the parse_request action ("{$new_status}_{$post_type}"). Invoking do_action('parse_request', …) re‑executes the entire Batch API request as the assumed admin.
  6. Admin Account Creation – On the second pass, a previously‑failed request to create a new admin user now succeeds because the request runs with admin rights.
  7. Back‑door Plugin Upload – With an admin account, the attacker uploads a malicious plugin ZIP, achieving persistent code execution on the server.

Cost, Timeline, and Impact

  • Compute cost: $25 (≈ 50 % of a $200 weekly GPT‑5.6 Sol Ultra subscription).
  • Time to full chain: just over 10 hours of model runtime plus a day of human analysis.
  • Scope: WordPress powers > 500 million sites; a pre‑auth RCE in the default configuration is a critical, high‑severity vulnerability.
  • Market value: Exploit brokers reportedly pay $500 k for a reliable WordPress RCE, explaining the headline claim.

Community Reaction (Hacker News Comments)

  • Some commenters doubt the $500 k figure, suggesting the article may be overstating broker payouts.

    "There is no evidence that $500k has been paid or would be paid for an exploit like this one." – Zsfe510asG

  • Others note the technical depth, comparing it to past AI‑assisted exploits and praising the write‑up.

    "That was an incredible writeup! … LLMs level the field." – cadamsdotcom

  • A few raise concerns about model guardrails and the ethics of AI‑generated exploits.

    "I’m surprised GPT‑5.6 didn’t block that prompt due to guardrails." – raesene9

  • Skeptics ask for verification of the vulnerability’s public disclosure and patch status.

    "Is this real? … I’m not seeing any mention where they report this to WP or the patch." – tantalor

Why This Matters

  • Proof of concept: The chain proves that a state‑of‑the‑art LLM can discover a novel, pre‑authentication WordPress RCE with minimal compute cost.
  • Economic incentive: The $500 k bounty figure shows a strong market demand for such zero‑days, motivating both defenders and attackers to adopt AI‑assisted research.
  • Defensive implications: Traditional static analysis missed the Batch API desynchronisation bug; defenders need to incorporate AI‑driven code‑understanding tools to surface similar logic‑flaws.
  • Future of security research: As LLMs become more capable, the bottleneck shifts from bug‑finding to prompt engineering, exploit chaining, and verification—skills that will dominate next‑generation security teams.

Mitigation steps (as of the publication date):

  1. Update WordPress to the latest release where the Batch API validation bug is patched.
  2. Disable the Batch API (/wp-json/batch/v1) if not required.
  3. Harden the REST API with authentication and rate‑limiting.
  4. Monitor for unexpected oembed_cache rows or customize_changeset creations.
  5. Employ AI‑assisted static analysis to detect validation/execution mismatches in future code.

The full technical details, including the exact payloads and the GitHub‑style proof‑of‑concept, are available on the Searchlight Cyber research page linked above.

Sources