mirror of
https://github.com/Icex0/wp2shell-poc
synced 2026-07-18 19:05:08 +00:00
Fix versions
This commit is contained in:
@@ -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
@@ -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,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
@@ -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)")
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user