
PoC detector & safe validator for the WP2Shell WordPress vulnerability chain: CVE-2026-63030 (REST batch-route confusion) + CVE-2026-60137 (author__not_in SQL injection). For authorized security testing only.
CVE coverage: CVE-2026-63030 and CVE-2026-60137 Intended use: Authorized security testing, defensive validation, and disposable localhost labs only
WP2Shell is a WordPress Core vulnerability chain combining a pre-authentication
REST API batch-route confusion bug (CVE-2026-63030) with a WP_Query
author__not_in SQL injection primitive (CVE-2026-60137). This repository
provides a Python proof-of-concept scanner and safe validator so defenders can
fingerprint affected WordPress installations, confirm the vulnerable behavior
in an isolated lab, and verify remediation — without needing to extract data
or achieve code execution.
This project fingerprints WordPress installations and validates the two vulnerability primitives associated with the WP2Shell WordPress Core vulnerability chain:
WP_Query::author__not_in, which can result in SQL injection when attacker-controlled input reaches the parameter.When both conditions are present, an unauthenticated request may reach the vulnerable SQL construction through the WordPress REST API batch endpoint. Public advisories describe the combined impact as potentially leading to remote code execution.
This repository should be used only on systems you own or are explicitly authorized to test. Prefer an isolated Docker or virtual-machine lab bound to 127.0.0.1.
The current source file contains state-changing functionality, including database-data extraction attempts, file-write attempts, authentication workflows, administrator creation, plugin upload, and command execution.
WordPress affected releases can lose alignment between internal arrays used to track:
When a malformed batch member is accepted into one internal array but not another, later requests can become associated with the wrong handler. A request may therefore be validated as one route but executed using another route's callback.
Security impact:
author__not_in SQL InjectionThe affected WP_Query implementation does not consistently normalize author__not_in before using it to construct an SQL NOT IN (...) condition.
The parameter normally expects a list of integer author IDs. If a scalar string reaches the vulnerable query construction without the intended REST-schema validation, unsafe SQL structure can survive into the database query.
Security impact:
| WordPress branch | Affected | Fixed release |
|---|---|---|
| 6.8.x | CVE-2026-60137 only: 6.8.0–6.8.5 | 6.8.6 |
| 6.9.x | Both issues: 6.9.0–6.9.4 | 6.9.5 |
| 7.0.x | Both issues: 7.0.0–7.0.1 | 7.0.2 |
| 7.1 prerelease | Beta 1 affected | Beta 2 |
| Earlier than 6.8 | Not affected by these two CVEs | N/A |
WordPress released fixes on July 17, 2026 and enabled forced automatic updates for affected installations because of the severity.
Unauthenticated client
|
v
WordPress REST batch endpoint
|
v
Malformed batch member creates request/handler misalignment
|
v
Later request is validated against one route
but dispatched using another route's handler
|
v
Scalar author_exclude reaches WP_Query as author__not_in
|
v
Unsafe value reaches SQL NOT IN (...) construction
|
v
Blind SQL timing or Boolean oracle
|
v
Potential database compromise
|
v
Potential application-level compromise and RCE
The detector should stop after confirming the route-confusion and SQL-injection primitives. It does not need to extract data or execute commands to establish that an affected installation is vulnerable.
The tool:
http or https scheme if missing.The scanner checks for:
wp-content/ references.wp-includes/ references.wp/v2 namespace.readme.html fingerprints.Version evidence can come from:
readme.html.wp-includes/version.php file.Evidence is scored and reconciled. Conflicting remote version indicators lower confidence.
The scanner attempts to discover /batch/v1 through:
/?rest_route=/
/wp-json/
The route can be addressed using either:
/?rest_route=/batch/v1
/wp-json/batch/v1
The safe probe contains:
GET./batch/v1 request.A vulnerable server returns an outer 207 Multi-Status response in which the invalid post request is processed as a nested batch request.
The detector reports:
route-confusion-observed
when it sees:
parse_path_failed marker.207.responses array showing that the harmless internal request ran.A non-destructive SQLi validator should send paired requests that differ only by a constant Boolean condition:
False control -> no deliberate database delay
True test -> deliberate database delay
The validator must:
207.inconclusive.A repeatable gap between true and false samples confirms that attacker-controlled input reached SQL evaluation. No database contents need to be selected or extracted.
127.0.0.1.Check Python:
python3 --version
Optional syntax validation:
python3 -m py_compile WP2Shell_CVE-2026-63030_POC.py
Rename the supplied script to a predictable filename:
mv 'poc(1).py' WP2Shell_CVE-2026-63030_POC.py
chmod +x WP2Shell_CVE-2026-63030_POC.py
Display global help:
python3 WP2Shell_CVE-2026-63030_POC.py --help
Display the version:
python3 WP2Shell_CVE-2026-63030_POC.py --version
The script defines three commands:
remote Fingerprint and scan HTTP(S) WordPress targets.
local Read the installed WordPress version from a source tree.
exploit State-changing proof-of-concept path.
| Command | Status |
|---|---|
local | Implemented |
remote | CLI is defined, but run_remote() is currently a stub and returns an error |
exploit | Contains state-changing functionality; restrict to a disposable local lab and separate it from defensive scanning |
The source currently prints the following for remote:
Remote scanning not fully implemented in this snippet. Use 'local' or 'exploit'.
Restore a complete run_remote() implementation before advertising bulk remote scanning as functional.
The local command reads:
<wordpress-root>/wp-includes/version.php
and extracts $wp_version.
python3 WP2Shell_CVE-2026-63030_POC.py local \
--wordpress-root /var/www/html
python3 WP2Shell_CVE-2026-63030_POC.py local \
--wordpress-root /var/www/html \
--format json \
--output local-result.json
When the WordPress container is named wordpress, copy or mount the source tree to the host, or run the version check inside the container:
docker compose exec wordpress php -r \
'require "/var/www/html/wp-includes/version.php"; echo $wp_version, PHP_EOL;'
Use the provided compose.yaml to run a disposable local lab bound to 127.0.0.1:8080:
docker compose up -d
Once the first-time WordPress setup completes in the browser at http://127.0.0.1:8080, you can run:
python3 WP2Shell_CVE-2026-63030_POC.py remote \
--authorized \
--target http://127.0.0.1:8080 \
--active-probe \
--format json
To stop and remove the lab:
docker compose down -v
| Option | Description |
|---|---|
--wordpress-root PATH | Required directory containing wp-includes/version.php |
-f, --format | table, json, jsonl, or csv |
-o, --output FILE | Write the report to a file |
--fail-on | Exit-code policy: never, vulnerable, or unknown |
[
{
"selected_version": "7.0.1",
"version_assessment": "affected-wp2shell",
"verdict": "CONFIRMED_AFFECTED_VERSION",
"severity": "critical"
}
]
A local version result confirms that the installed release is in the published affected range. It does not, by itself, demonstrate runtime exploitability or the effect of a backported patch.
The remote parser supports the following arguments, but the supplied run_remote() implementation is currently incomplete.
Expected interface after restoring run_remote():
python3 WP2Shell_CVE-2026-63030_POC.py remote \
--authorized \
--target https://wordpress.example \
--active-probe
python3 WP2Shell_CVE-2026-63030_POC.py remote \
--authorized \
--target https://site-one.example \
--target https://site-two.example \
--active-probe
Create targets.txt:
https://site-one.example/
https://site-two.example/blog/
Expected interface:
python3 WP2Shell_CVE-2026-63030_POC.py remote \
--authorized \
--targets-file targets.txt \
--active-probe \
--format jsonl \
--output results.jsonl
cat targets.txt | python3 WP2Shell_CVE-2026-63030_POC.py remote \
--authorized \
--stdin
python3 WP2Shell_CVE-2026-63030_POC.py remote \
--authorized \
--target https://wordpress.example \
--active-probe \
--proxy http://127.0.0.1:8081
python3 WP2Shell_CVE-2026-63030_POC.py remote \
--authorized \
--target https://wordpress.example \
--header 'Authorization: Bearer TEST_TOKEN'
| Option | Purpose |
|---|---|
-u, --target URL | Target URL; repeatable |
-l, --targets-file FILE | One target per line; repeatable |
--stdin | Read targets from stdin |
--authorized | Required authorization acknowledgement |
--default-scheme | Scheme applied when omitted |
-c, --concurrency | Concurrent target workers |
--rate | Aggregate request rate |
--timeout | Per-request timeout |
--retries | Retry count |
--max-targets | Maximum number of accepted targets |
--max-body-bytes | Maximum retained response body |
-k, --insecure | Disable TLS verification |
--no-redirects | Disable redirects |
--allow-cross-host-redirects | Permit redirects to another host |
--proxy | HTTP/HTTPS proxy |
-H, --header | Custom header; repeatable |
--user-agent | Override User-Agent |
--fingerprint-level | quick, standard, or extended |
--active-probe | Send the safe route-confusion probe |
--rest-endpoint | query, pretty, or both |
--include-request-log | Add URL, status, and timing metadata |
-f, --format | table, json, , or |
For a non-destructive local validation of both primitives, use a dedicated validator that:
Example workflow:
python3 wp2shell_local_validator.py check \
--authorized \
--target http://127.0.0.1:8080 \
--sqli \
--delay 2 \
--samples 4 \
--warmups 2 \
--debug \
--dump-dir evidence \
--output result.json
Expected vulnerable-lab timing pattern:
False controls: approximately 0.03–0.10 seconds
True tests: consistently delayed
Expected verdict:
FULL_VULNERABILITY_PRIMITIVES_CONFIRMED
A timing test can execute the delay expression more than once, so a configured two-second delay may produce an observed delay near four seconds. The important signal is the repeatable separation between true and false conditions.
Best for interactive terminal use:
--format table
Best for evidence and integration:
--format json --output result.json
Best for large target sets:
--format jsonl --output results.jsonl
Best for spreadsheets and reporting:
--format csv --output results.csv
| Verdict | Meaning |
|---|---|
CONFIRMED_VULNERABLE_BEHAVIOR | Runtime route-confusion behavior was observed |
CONFIRMED_AFFECTED_VERSION | Local source version is in an affected range |
LIKELY_VULNERABLE | Remote version evidence indicates an affected release |
VULNERABLE_SQLI_ONLY | Version is affected by CVE-2026-60137 but outside the complete route-confusion range |
AFFECTED_VERSION_BUT_BEHAVIOR_NOT_OBSERVED | Affected version detected, but safe runtime behavior was absent |
PATCHED_VERSION | Version meets the published fixed boundary |
NOT_AFFECTED | Version is outside the affected branch |
POTENTIALLY_EXPOSED_VERSION_UNKNOWN | WordPress and the batch route were found, but the version is hidden |
WORDPRESS_VERSION_UNKNOWN | WordPress detected without reliable version evidence |
ERROR | The target could not be assessed |
NOT_WORDPRESS_OR_NOT_DETECTED | No reliable WordPress evidence |
The scanner uses policy-driven exit codes.
| Code | Meaning |
|---|---|
0 | No policy-triggering result, or --fail-on never |
2 | Vulnerable or affected result under the default policy |
3 | Unknown or inconclusive result when --fail-on unknown is selected |
1 | Argument, input, or incomplete-command error |
A vulnerability finding can therefore return a non-zero status intentionally.
Example:
python3 WP2Shell_CVE-2026-63030_POC.py local \
--wordpress-root /var/www/html \
--fail-on vulnerable
echo $?
A strong validation compares two clean environments.
WordPress 7.0.1
Expected:
route-confusion-observed
repeatable true/false SQL timing difference
WordPress 7.0.2
Expected:
route-confusion-not-observed
SQLi timing test not reached or no valid timing oracle
Always recreate the WordPress volume when changing versions. Reusing a volume can preserve old or automatically updated core files.
docker compose down -v
docker compose pull
docker compose up -d
Update immediately to one of the fixed releases or a later supported release:
Temporary controls should cover both batch endpoint forms:
/wp-json/batch/v1
/?rest_route=/batch/v1
Additional defensive actions:
author_exclude values.Blocking the endpoint is a temporary mitigation, not a substitute for updating WordPress Core.
Potential indicators include:
POST requests to either batch endpoint form.207 responses containing nested responses arrays.requests objects inside another batch member.author_exclude values.author__not_in expressions.FILE privilege and filesystem paths vary by deployment.INTO OUTFILE is usually restricted and cannot overwrite existing files.DISALLOW_FILE_MODS.WordPress 7.0.2 Security Release:
https://wordpress.org/news/2026/07/wordpress-7-0-2-release/
WordPress 7.0.2 Documentation and changed files:
https://wordpress.org/documentation/wordpress-version/version-7-0-2/
NVD — CVE-2026-63030:
https://nvd.nist.gov/vuln/detail/CVE-2026-63030
NVD — CVE-2026-60137:
https://nvd.nist.gov/vuln/detail/CVE-2026-60137
WordPress release archive:
https://wordpress.org/download/releases/
Use this project only when:
Do not expose deliberately vulnerable WordPress installations to the public Internet. Use disposable credentials, synthetic data, isolated networks, and clean snapshots. Destroy or reset the lab after testing.
The safest proof of vulnerability is the minimum evidence necessary to demonstrate the issue:
Affected version
+
Route-confusion behavior
+
Repeatable constant-condition SQL timing oracle
Credential theft, persistence, webshell installation, and command execution are unnecessary for confirming that the vulnerability exists.
jsonlcsv-o, --output | Write output to a file |
--fail-on | Exit-code policy |