## https://sploitus.com/exploit?id=92D668BD-62AB-585A-ACB3-79F211ED8C25
# wp2shell stock-core chain
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.
## CLI modes
```bash
# 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:
1. Render six unique same-site embeds from synthetic ID-zero posts, causing
ordinary `oembed_cache` rows to be created.
2. Use UNION rows to populate the request-local post cache with forged AโF
objects backed by those real row IDs.
3. Let oEmbed's sparse update and two hierarchy-loop repairs persist the forged
fields and publish a fabricated Customizer changeset.
4. Use `nav_menus_created_posts` while the Customizer has temporarily selected
the site's existing administrator.
5. Compose the dynamic `parse_request` hook from post status `parse` and type
`request`, re-enter REST, and create the supplied administrator through the
protected users endpoint.
6. 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.
## Disposable WordPress 7.0.1 lab
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.
```bash
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 -v
```
## Vulnerable-version matrix
The 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.
```bash
# 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-fixed
```
The default report is written to `matrix-results.tsv`. Override it with
`--report PATH`. See [ATTACK_WORKFLOW.md](ATTACK_WORKFLOW.md) for the complete
source-level workflow, assumptions, cleanup behavior, and phase boundaries.
## Legal
Use exclusively against systems you own or have explicit written permission to
test.