Searchlight Cyber wp2shell WordPress RCE discovered with GPT‑5.6 Sol Ultra
TL;DR – What happened and why it matters
Searchlight Cyber leveraged the GPT‑5.6 Sol Ultra model (costing about $25 in compute) to find a pre‑authentication SQL injection in WordPress’s batch REST API, then chained several logic bugs to achieve remote code execution (RCE). The full exploit chain demonstrates that modern LLMs can autonomously discover and weaponize zero‑day bugs in widely deployed software, a capability that exploit brokers value at up to $500 k.
The LLM‑driven discovery workflow
Conclusion: A carefully crafted multi‑agent prompt can make a large language model read, analyse, and fuzz an entire codebase without external references, yielding a viable zero‑day.
The researcher cloned the latest stable WordPress source into wordpress‑ctf/main/, removed the .git directory, and supplied an empty third_party/ folder. The prompt (see below) instructed the model to:
- Treat the task as a pure code‑analysis problem – no changelogs, no internet diffing.
- Spawn up to four agents and explore diverse attack surfaces (input parsing, serialization, race conditions, etc.).
- Persist for at least six hours before giving up.
Current task statement:
…
Your task is to identify the chain that allows for RCE …
Use multiagents aggressively …
Do not use changelogs, git history, or the internet …
Spend at least 6 hours on this before giving up.
The model reported a pre‑authentication SQL injection after four hours, which the researcher verified by deploying a fresh WordPress instance and confirming that the model could read the admin email.
The underlying bug: Batch API validation‑execution desynchronisation
Conclusion: A mismatch between validation and execution loops in class‑wp‑rest‑server.php lets an attacker validate one endpoint while executing another, bypassing all sanitisation.
WordPress normally validates a request in four steps (has_valid_params() → sanitize_params() → permission callback → endpoint callback). The batch API splits validation and execution into two separate loops:
- Validation loop – validates each sub‑request and stores a boolean or
WP_Errorin$validation. - Execution loop – uses
$matches(handler mapping) and$validationto run the request.
If a sub‑request triggers is_wp_error($single_request) the code continues without pushing a matching entry onto $matches. This shifts the indices so that $validation[i] no longer aligns with $matches[i]. Consequently, the system can execute request B using the validation result of request A.
The SQL injection primitive (the "sink")
Conclusion: By exploiting the desynchronisation, an attacker can feed a scalar author__not_in parameter to GET /wp/v2/posts, which is interpolated directly into SQL, yielding a classic string‑based injection.
The author__not_in handling code sanitises array elements with absint() but leaves scalar strings untouched:
if ( ! empty( $query_vars['author__not_in'] ) ) {
if ( is_array( $query_vars['author__not_in'] ) ) {
$query_vars['author__not_in'] = array_unique(
array_map( 'absint', $query_vars['author__not_in'] )
);
sort( $query_vars['author__not_in'] );
}
$author__not_in = implode( ',', (array) $query_vars['author__not_in'] );
$where .= " AND {$wpdb->posts}.post_author NOT IN ($author__not_in) ";
}
When the parameter is a scalar like "0) OR 1=1 -- ", it is concatenated verbatim, producing ... NOT IN (0) OR 1=1 -- ) and returning all rows.
Overcoming the GET‑restriction with recursive batch calls
Conclusion: A nested batch request can bypass the batch API’s prohibition on GET methods, allowing the injection to be executed.
The outer batch request injects a malformed request (POST http://:) to desynchronise validation. Inside the payload, the attacker nests another batch call whose inner request uses GET. Because validation of the method field occurs in the first loop, the inner GET is never checked, and the injection runs.
The final payload (trimmed for clarity) is:
POST /wp-json/batch/v1
Content-Type: application/json
{
"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"}
]
}
Executing this returns all rows from wp_posts, confirming the SQLi.
From SQLi to RCE: chaining the gadgets
Conclusion: The attacker leverages in‑memory post caching, embed handling, and WordPress’s customize_changeset mechanism to obtain temporary administrator privileges, then re‑enters the request lifecycle via the parse_request hook to create a new admin account and upload a back‑door plugin.
- Cache poisoning – The SQLi fabricates fake posts returned by a UNION query. WordPress caches these
WP_Postobjects for the duration of the request. - Embed abuse – By embedding a local post (
[embed]/?p=10[/embed]) WordPress creates anoembed_cacherow in the database. The attacker can control thepost_typeof this row, turning it into a normalpostafter cache‑database reconciliation. - Customize changeset – A fabricated
customize_changesetpost stores JSON that, when applied, runswp_set_current_user(1), temporarily granting administrator rights. - Cycle detection gadget – WordPress’s cycle‑detection logic updates a post’s
post_parentto0without touchingpost_content. By arranging a parent‑cycle among forged posts, the attacker forces WordPress to execute thecustomize_changesetwhile retaining the in‑memorypost_contentthey control. - Hook replay – The
parse_requestaction is invoked for a forged post of typerequest. This hook re‑executes the entire batch request under the assumed admin identity, allowing a second‑pass creation of an admin user. - Back‑door deployment – With a legitimate admin account, the attacker can upload a malicious plugin ZIP, achieving full code execution on the server.
Timeline and cost
Conclusion: The entire exploit chain was produced in roughly ten hours of model runtime, costing about $25 in compute on a $200/month subscription.
- Prompt engineering and model launch – ~2 h
- Model‑generated pre‑auth SQLi – ~4 h (including verification)
- Human analysis, chain stitching, and final payload construction – ~4 h
Community reaction on Hacker News
Conclusion: The write‑up sparked debate about the realism of $500 k bug bounties, the novelty of LLM‑assisted exploit development, and the need for better automated defenses.
- Some commenters questioned the $500 k figure, suggesting the prompt itself might be the commodity.
- Others highlighted the technical depth of the chain, noting that the recursive batch call and cache‑poisoning steps are unlikely to be found without AI assistance.
- A few raised concerns about guardrails, observing that GPT‑5.5 and later often block offensive security prompts, yet the authors succeeded.
- Several remarks praised the write‑up for exposing how LLMs can accelerate vulnerability discovery, while others warned against glorifying the “AI‑hacking” narrative.
Implications for security research
Conclusion: Large language models are becoming powerful autonomous assistants for vulnerability discovery, shifting the bottleneck from low‑level bug finding to high‑level orchestration and prompt engineering.
- Speed: Complex multi‑bug chains that would take weeks for a human can be assembled in hours.
- Skill shift: Researchers will need to focus on defining attack surfaces, crafting effective prompts, and validating model output.
- Defensive response: Traditional static analysis tools may miss the kinds of cross‑component gadgets LLMs can stitch together; runtime defenses, request‑level validation, and strict API sanitisation become more critical.
- Economic impact: If exploit brokers are willing to pay six‑figure sums for such chains, the market for AI‑generated zero‑days will likely expand, pressuring vendors to adopt AI‑assisted code review and fuzzing.
Mitigations for the specific WordPress bugs
Conclusion: Patching the batch API validation logic and tightening parameter handling eliminates the described attack surface.
- Synchronise validation and execution indices – Ensure that every
continuein the validation loop also pushes a placeholder onto$matches. - Enforce scalar sanitisation – Apply
absint()(or a prepared‑statement style escape) to scalarauthor__not_invalues before interpolation. - Disallow recursive batch calls – Reject batch payloads that contain a nested
/wp-json/batch/v1request. - Restrict embed handling – Validate that embedded URLs are external or that local embeds reference existing post IDs.
- Hard‑code cycle detection to reject malformed parent hierarchies before any database write.
WordPress core maintainers have been notified; a patch is expected in the upcoming 6.7 release.
Final thoughts
Conclusion: The wp2shell exploit demonstrates that LLMs can autonomously discover high‑impact zero‑days in mature software, and that the resulting exploit chains can be sophisticated enough to merit half‑million‑dollar payouts.
The research underscores a turning point: security teams must treat AI‑generated code analysis as a new attack vector, invest in AI‑assisted defensive tooling, and reconsider bounty structures that may inadvertently incentivise the sale of AI‑crafted exploits.
Sources
Related
- Dispatch
- Dispatch
- Project
- Dispatch
- Dispatch