Fix versions

This commit is contained in:
Icex0
2026-07-18 02:04:58 +02:00
parent 1742aa3d19
commit f157decdb8
5 changed files with 44 additions and 32 deletions
+27 -23
View File
@@ -1,24 +1,25 @@
# wp2shell-poc # wp2shell-poc
Proof-of-concept for an unauthenticated SQL injection in WordPress core that chains to remote Independent proof-of-concept for the unauthenticated WordPress REST batch route-confusion
code execution, via REST batch route confusion (CVE-2026-63030). SQL injection associated with Searchlight Cyber's wp2shell advisory.
The exploit needs no credentials and no configuration beyond a reachable target. It reaches the The unauthenticated primitive reaches the injection through a single endpoint:
injection through a single endpoint: `POST /wp-json/batch/v1`. `POST /wp-json/batch/v1`.
The finding itself is that unauthenticated SQL injection — `check` confirms it and `read` This repository is not Searchlight Cyber's official checker and does not claim to reproduce
demonstrates arbitrary database read. The `shell` command is optional post-exploitation undisclosed wp2shell internals. `check` confirms the SQLi path, `read` demonstrates database
(recovered admin credentials → plugin upload → command execution), included to demonstrate full read, and `shell` is an optional post-authentication helper that requires valid administrator
impact; it is not the vulnerability. credentials before uploading a plugin webshell.
## Affected versions ## Affected versions
| Branch | Affected | Fixed in | Searchlight Cyber's advisory lists these wp2shell RCE exposure ranges:
| ------ | ------------- | -------- |
| 6.9.x | 6.9.0 6.9.4 | 6.9.5 |
| 7.0.x | 7.0.0 7.0.1 | 7.0.2 |
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 ## 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` 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. `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 The result is a boolean- and time-based blind SQL injection reachable pre-authentication. This PoC
database read access the administrator password hash can be recovered; once cracked, admin access can use that database read path to recover administrator password hashes. Turning those hashes into
yields code execution through a plugin upload. plugin-upload code execution is outside the unauthenticated primitive and depends on obtaining valid
administrator credentials.
## Requirements ## 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" ./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 Optional post-exploitation helper. This is not a pre-authentication RCE step: it requires valid
password *hash*, so supply the recovered plaintext here. 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 '<recovered>' --cmd id ./wp2shell.py shell http://target --user admin --password '<recovered>' --cmd id
@@ -112,9 +115,10 @@ path. Remove it when finished.
## Remediation ## Remediation
Update to WordPress 6.9.5 or 7.0.2. Until then, block both `/wp-json/batch/v1` and the Update to WordPress 7.0.2, or 6.9.5 if the site is on the 6.9 branch. Until then,
`rest_route=/batch/v1` query parameter at the edge, or require authentication for the batch block both `/wp-json/batch/v1` and the `rest_route=/batch/v1` query parameter at
endpoint via the `rest_pre_dispatch` filter. the edge, or require authentication for the batch endpoint via the
`rest_pre_dispatch` filter.
## Legal ## Legal
@@ -124,4 +128,4 @@ written permission to test. No warranty is provided and no liability is accepted
## References ## References
- WordPress 7.0.2 release announcement — <https://wordpress.org/news/2026/07/wordpress-7-0-2-release/> - WordPress 7.0.2 release announcement — <https://wordpress.org/news/2026/07/wordpress-7-0-2-release/>
- CVE-2026-63030 - Searchlight Cyber wp2shell advisory — <https://slcyber.io/research-center/wp2shell-pre-authentication-rce-in-wordpress-core/>
+2 -2
View File
@@ -5,11 +5,11 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "wp2shell-poc" name = "wp2shell-poc"
version = "1.0.0" 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" readme = "README.md"
requires-python = ">=3.8" requires-python = ">=3.8"
license = { text = "MIT" } license = { text = "MIT" }
keywords = ["wordpress", "rest-api", "sql-injection", "rce", "security", "cve-2026-63030"] keywords = ["wordpress", "rest-api", "sql-injection", "wp2shell", "security"]
classifiers = [ classifiers = [
"Environment :: Console", "Environment :: Console",
"Intended Audience :: Information Technology", "Intended Audience :: Information Technology",
+1 -1
View File
@@ -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" __version__ = "1.0.0"
+8 -6
View File
@@ -11,7 +11,7 @@ from . import __version__
from .client import BatchClient from .client import BatchClient
from .shell import AdminSession from .shell import AdminSession
from .sqli import BlindSQLi from .sqli import BlindSQLi
from .version import public_version_hints from .version import public_version_hints, version_status
try: try:
import readline # noqa: F401 - enables line editing/history for the interactive prompt 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:") info("Public WordPress version hints:")
for hint in hints: for hint in hints:
status = "affected range" if hint.affected else "not in affected ranges" print(
print(f" - {hint.version} via {hint.source} ({status}) - {_short(hint.detail)}") f" - {hint.version} via {hint.source} "
f"({version_status(hint.version)}) - {_short(hint.detail)}"
)
if any(hint.affected for hint in hints): 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 --------------------------------------------------------------- # -- commands ---------------------------------------------------------------
@@ -246,7 +248,7 @@ def _add_common(parser: argparse.ArgumentParser) -> None:
def build_parser() -> argparse.ArgumentParser: def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
prog="wp2shell", 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__}") parser.add_argument("--version", action="version", version=f"wp2shell {__version__}")
sub = parser.add_subparsers(dest="command", required=True) 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.add_argument("--max-length", type=int, default=128, help="max characters per value")
read.set_defaults(func=cmd_read) 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) _add_common(shell)
shell.add_argument("--user", required=True, help="admin username") shell.add_argument("--user", required=True, help="admin username")
shell.add_argument("--password", required=True, help="admin password (cracked from the hash)") shell.add_argument("--password", required=True, help="admin password (cracked from the hash)")
+6
View File
@@ -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) 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): def _get(client: BatchClient, path: str):
try: try:
return client.get(path) return client.get(path)