diff --git a/README.md b/README.md index c4fb681..dade442 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,25 @@ # wp2shell-poc -Proof-of-concept for an unauthenticated SQL injection in WordPress core that chains to remote -code execution, via REST batch route confusion (CVE-2026-63030). +Independent proof-of-concept for the unauthenticated WordPress REST batch route-confusion +SQL injection associated with Searchlight Cyber's wp2shell advisory. -The exploit needs no credentials and no configuration beyond a reachable target. It reaches the -injection through a single endpoint: `POST /wp-json/batch/v1`. +The unauthenticated primitive reaches the injection through a single endpoint: +`POST /wp-json/batch/v1`. -The finding itself is that unauthenticated SQL injection — `check` confirms it and `read` -demonstrates arbitrary database read. The `shell` command is optional post-exploitation -(recovered admin credentials → plugin upload → command execution), included to demonstrate full -impact; it is not the vulnerability. +This repository is not Searchlight Cyber's official checker and does not claim to reproduce +undisclosed wp2shell internals. `check` confirms the SQLi path, `read` demonstrates database +read, and `shell` is an optional post-authentication helper that requires valid administrator +credentials before uploading a plugin webshell. ## Affected versions -| Branch | Affected | Fixed in | -| ------ | ------------- | -------- | -| 6.9.x | 6.9.0 – 6.9.4 | 6.9.5 | -| 7.0.x | 7.0.0 – 7.0.1 | 7.0.2 | +Searchlight Cyber's advisory lists these wp2shell RCE exposure ranges: -Versions before 6.9.0 are not affected by this chain. +| Version range | Status | +| ------------- | ------ | +| <= 6.8.5 | Not affected | +| 6.9.0 – 6.9.4 | Affected | +| 7.0.0 – 7.0.1 | Affected | ## How it works @@ -42,9 +43,10 @@ The PoC nests the primitive twice: dispatched under posts `get_items()`. There `author_exclude` maps to the `WP_Query` `author__not_in` query var, which the vulnerable build interpolates into SQL as a string. -The result is a boolean- and time-based blind SQL injection reachable pre-authentication. With -database read access the administrator password hash can be recovered; once cracked, admin access -yields code execution through a plugin upload. +The result is a boolean- and time-based blind SQL injection reachable pre-authentication. This PoC +can use that database read path to recover administrator password hashes. Turning those hashes into +plugin-upload code execution is outside the unauthenticated primitive and depends on obtaining valid +administrator credentials. ## Requirements @@ -80,10 +82,11 @@ generator, the homepage generator meta tag, and core asset `?ver=` query strings ./wp2shell.py read http://target --query "SELECT @@version" ``` -### shell — execute a command (remote code execution) +### shell — post-auth plugin webshell helper -Optional post-exploitation. Requires valid administrator credentials; the injection recovers the -password *hash*, so supply the recovered plaintext here. +Optional post-exploitation helper. This is not a pre-authentication RCE step: it requires valid +administrator credentials. If `read --preset users` recovers a password hash, supply the recovered +plaintext here after offline cracking. ``` ./wp2shell.py shell http://target --user admin --password '' --cmd id @@ -112,9 +115,10 @@ path. Remove it when finished. ## Remediation -Update to WordPress 6.9.5 or 7.0.2. Until then, block both `/wp-json/batch/v1` and the -`rest_route=/batch/v1` query parameter at the edge, or require authentication for the batch -endpoint via the `rest_pre_dispatch` filter. +Update to WordPress 7.0.2, or 6.9.5 if the site is on the 6.9 branch. Until then, +block both `/wp-json/batch/v1` and the `rest_route=/batch/v1` query parameter at +the edge, or require authentication for the batch endpoint via the +`rest_pre_dispatch` filter. ## Legal @@ -124,4 +128,4 @@ written permission to test. No warranty is provided and no liability is accepted ## References - WordPress 7.0.2 release announcement — -- CVE-2026-63030 +- Searchlight Cyber wp2shell advisory — diff --git a/pyproject.toml b/pyproject.toml index 2a02bc9..cd01ae8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,11 +5,11 @@ build-backend = "setuptools.build_meta" [project] name = "wp2shell-poc" version = "1.0.0" -description = "PoC for the WordPress REST batch route-confusion SQL injection to RCE chain (CVE-2026-63030)." +description = "PoC for the WordPress REST batch route-confusion SQL injection associated with wp2shell." readme = "README.md" requires-python = ">=3.8" license = { text = "MIT" } -keywords = ["wordpress", "rest-api", "sql-injection", "rce", "security", "cve-2026-63030"] +keywords = ["wordpress", "rest-api", "sql-injection", "wp2shell", "security"] classifiers = [ "Environment :: Console", "Intended Audience :: Information Technology", diff --git a/wp2shell/__init__.py b/wp2shell/__init__.py index 8237383..35c82fe 100644 --- a/wp2shell/__init__.py +++ b/wp2shell/__init__.py @@ -1,3 +1,3 @@ -"""wp2shell — PoC for the WordPress REST batch route-confusion SQLi/RCE chain (CVE-2026-63030).""" +"""wp2shell - PoC for the WordPress REST batch route-confusion SQLi.""" __version__ = "1.0.0" diff --git a/wp2shell/cli.py b/wp2shell/cli.py index ea24c37..69b65b4 100644 --- a/wp2shell/cli.py +++ b/wp2shell/cli.py @@ -11,7 +11,7 @@ from . import __version__ from .client import BatchClient from .shell import AdminSession from .sqli import BlindSQLi -from .version import public_version_hints +from .version import public_version_hints, version_status try: import readline # noqa: F401 - enables line editing/history for the interactive prompt @@ -77,10 +77,12 @@ def _print_version_hints(client: BatchClient) -> None: info("Public WordPress version hints:") for hint in hints: - status = "affected range" if hint.affected else "not in affected ranges" - print(f" - {hint.version} via {hint.source} ({status}) - {_short(hint.detail)}") + print( + f" - {hint.version} via {hint.source} " + f"({version_status(hint.version)}) - {_short(hint.detail)}" + ) if any(hint.affected for hint in hints): - warn("A public version hint falls in the affected range; verify internally or confirm with authorization.") + warn("A public version hint falls in the wp2shell affected range; verify internally or confirm with authorization.") # -- commands --------------------------------------------------------------- @@ -246,7 +248,7 @@ def _add_common(parser: argparse.ArgumentParser) -> None: def build_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser( prog="wp2shell", - description="WordPress REST batch route-confusion SQLi/RCE PoC (CVE-2026-63030).", + description="WordPress REST batch route-confusion SQLi PoC associated with wp2shell.", ) parser.add_argument("--version", action="version", version=f"wp2shell {__version__}") sub = parser.add_subparsers(dest="command", required=True) @@ -276,7 +278,7 @@ def build_parser() -> argparse.ArgumentParser: read.add_argument("--max-length", type=int, default=128, help="max characters per value") read.set_defaults(func=cmd_read) - shell = sub.add_parser("shell", help="execute a command (requires admin credentials)") + shell = sub.add_parser("shell", help="post-auth plugin webshell helper") _add_common(shell) shell.add_argument("--user", required=True, help="admin username") shell.add_argument("--password", required=True, help="admin password (cracked from the hash)") diff --git a/wp2shell/version.py b/wp2shell/version.py index dc16f4e..4201ac9 100644 --- a/wp2shell/version.py +++ b/wp2shell/version.py @@ -97,6 +97,12 @@ def is_affected_version(version: str) -> bool: return (6, 9, 0) <= parsed <= (6, 9, 4) or (7, 0, 0) <= parsed <= (7, 0, 1) +def version_status(version: str) -> str: + if is_affected_version(version): + return "wp2shell affected range" + return "not in wp2shell affected ranges" + + def _get(client: BatchClient, path: str): try: return client.get(path)