Skip to content

Back to the catalog

Icex0/wp2shell-poc
github.comrepo

Icex0/wp2shell-poc

wp2shell-poc is an independent proof-of-concept tool for the wp2shell vulnerability chain in WordPress core: an unauthenticated route-confusion bug in the REST /batch/v1 handler (serve_batch_request_v1) that desynchronizes matched-handler and validation arrays when a sub-request fails wp_parse_url(), letting a GET posts item-route request be dispatched under get_items() so the author_exclude parameter reaches WP_Query's author__not_in query var as unsanitized SQL. The tool's check/read/shell commands detect the route-confusion marker pattern, extract data via UNION/error-based/boolean-blind SQLi, and (without credentials) chain UNION fake-post rendering into oEmbed cache post creation, ID recovery via SQLi, and a poisoned batch request that creates a real WordPress administrator account, finishing with a plugin-upload webshell. Affects WordPress builds before 7.0.2 (or 6.9.5 on the 6.9 branch); links out to Searchlight Cyber's advisory and Elastic/Eye Security detection guidance.

TL;DR PoC tool exploiting WordPress's REST /batch/v1 route-confusion bug for pre-auth blind SQLi, admin account creation, and webshell RCE.
Document

What it is

This is the usage reference for wp2shell-poc, the proof-of-concept tool already described in the catalog summary. The page adds the actual CLI commands, flags, and a second attack primitive (the pre-auth marker probe) not covered in the summary.

How it works

Beyond the SQLi mechanism already summarized, the page describes a separate, lower-risk detection primitive: a benign "marker probe" that distinguishes patched from vulnerable builds without sending any SQLi payload. It sends a malformed /// path (triggers parse_path_failed), a spacer /wp/v2/posts request, and a /wp/v2/block-renderer/... request (batch-disallowed, so it should return block_cannot_read if reached). On vulnerable builds the route-confusion bug shifts the batch arrays, so the block-renderer handler gets reached anonymously and all three markers (parse_path_failed, block_cannot_read, rest_batch_not_allowed) appear together; fixed builds keep the arrays aligned and never produce that combination. This lets check default to a passive-only confirmation, with active SQLi only sent via an explicit flag.

For data extraction, the page specifies three selectable techniques with concrete costs: union (1 request per value, reads a forged post title via ||HEX(value)||), error (EXTRACTVALUE/UPDATEXML, ~15 bytes/request, requires WP_DEBUG_DISPLAY reflection), and blind (boolean binary search via the X-WP-Total header, ~8 requests per character). Default mode (auto) tries them in that order.

Preconditions / Affected

Error-based extraction only works if the target reflects MySQL errors (e.g., WP_DEBUG_DISPLAY on). UNION/blind techniques need no such reflection.

What to do

./wp2shell.py check http://target
./wp2shell.py check targets.txt                       # scan every URL in the file
./wp2shell.py check http://target --confirm-sqli       # adds active SQLi confirmation

./wp2shell.py read http://target                       # server fingerprint
./wp2shell.py read http://target --preset users         # dump user logins/password hashes
./wp2shell.py read http://target --query "SELECT @@version"
./wp2shell.py read http://target --technique union|error|blind

./wp2shell.py shell http://target --user admin --password '<recovered>' --cmd id
./wp2shell.py shell http://target --user admin --password '<recovered>' -i
./wp2shell.py shell http://target --cmd id              # pre-auth SQLi-to-admin bridge
./wp2shell.py shell http://target -i                    # pre-auth interactive

shell auto-removes both the uploaded plugin webshell (token/path-locked) and any auto-generated administrator account once the session ends. Requires only Python 3.8+ and the standard library; pip install . adds a wp2shell command to PATH.

Caveats

The page states the three check signals are independent: a version hint is only a hint, the marker pattern shows route confusion but not confirmed SQLi, and --confirm-sqli proves a payload reached the database — but a WAF can block the payload, so a failed confirmation does not prove the bug is absent. The tool explicitly states it is not Searchlight Cyber's official checker.

Organisations named here

  • Elastic Security Labs
  • Eye Security

Technologies named here

  • WordPress
  • wp2shell-poc
  • sergiointel/wp2shell-poc
  • Python
  • Searchlight Cyber

Techniques named here

  • REST batch route confusion
  • Method allow-list bypass via nested batch
  • Blind SQL injection via author_exclude parameter
  • UNION-based fake post extraction
  • Error-based extraction
  • Blind boolean binary search extraction
  • SQLi-to-admin privilege escalation chain
  • Plugin upload webshell
  • Passive fingerprinting and batch marker probe

Threat actors named here

  • wp2shell