# REPRO-2026-00294: WordPress 7.0.1 pre-auth fresh-administrator chain to RCE ## Summary Status: published Severity: critical Type: security Confidence: high ## Identifiers REPRO ID: REPRO-2026-00294 CVE: CVE-2026-63030 ## Package Name: Unknown Ecosystem: Unknown Affected: Unknown Fixed: Unknown ## Root Cause ## Summary WordPress 7.0.1 contains a composable pre-authentication chain in its stock REST and post-processing paths. A malformed REST batch entry desynchronizes parsed requests from matched handlers; a shifted posts handler then accepts an attacker-controlled scalar `author_exclude` value that reaches `WP_Query::author__not_in` without integer normalization. The resulting full-row SQL projection lets an unauthenticated attacker poison post-cache objects. Core oEmbed partial-update and hierarchy-repair behavior turns those transient objects into persistent forged posts, including a due Customizer changeset. While core applies that changeset under its recorded owner context, a forged `request` post with status `parse` invokes the stock `parse_request` hook and re-enters REST dispatch. A nested malformed batch consequently reaches the normal user-creation controller as the owner and creates a fresh attacker-chosen administrator. That new account can authenticate normally, use the core plugin uploader, and execute a command as the web server. WordPress 7.0.2 aligns batch arrays, integer-normalizes the ID list, and blocks nested top-level REST dispatch. ## Impact - **Affected component:** WordPress Core REST batch dispatch, `WP_Query`, post/oEmbed cache update behavior, Customizer changeset publication, and nested REST dispatch. - **Affected version exercised:** stock WordPress 7.0.1, official `wordpress:7.0.1-php8.2-apache` image pinned to `sha256:bfc320ed4f02dd3939186b8020de64203a48a939d6dedcf44cb92cf2368923f5`. - **Fixed control exercised:** WordPress 7.0.2 source at commit `855551c4477bd5a0407221c57dae123c4163b434`, over the same pinned base image. - **Database:** MySQL 8.0 pinned to `sha256:7dcddc01f13bab2f15cde676d44d01f61fc9f99fe7785e86196dfc07d358ae2b`, with `@@local_infile=0`, application-scoped grants, and no global `FILE` privilege. - **Risk:** Critical. An unauthenticated network attacker can create a new administrator without knowing, recovering, or reusing the sole owner's random password, then obtain arbitrary command execution as Apache's `www-data` identity. ## Impact Parity - **Disclosed/claimed maximum impact:** Unauthenticated remote code execution through creation of a fresh attacker-chosen administrator. - **Reproduced impact from this run:** Full unauthenticated fresh-administrator creation followed by normal login, core plugin upload, and target-originated command execution as `uid=33(www-data) gid=33(www-data) groups=33(www-data)`. The final generated script passed twice consecutively; each execution itself recreated and passed two clean vulnerable/fixed matrices. - **Parity:** `full`. - **Not demonstrated:** None of the claimed chain remains unproved. ## Root Cause Three missing invariants in WordPress 7.0.1 compose into the privilege transition: 1. **REST batch request/handler array desynchronization.** `WP_REST_Server::serve_batch_request_v1()` appends a parse error to its request array but, in the vulnerable source, does not append a corresponding entry to the matched-handler array before continuing. Subsequent requests are paired with the next route's handler and permission callback. 2. **Scalar `author__not_in` SQL injection.** A shifted posts-list handler retains attacker-controlled query parameters. In 7.0.1, `author__not_in` is normalized only when already represented as an array; a scalar can reach the interpolated `NOT IN (...)` SQL clause. A high page size and `orderby=none` disable the ID-only split-query boundary, allowing a complete 23-column forged `wp_posts.*` row. 3. **Nested top-level REST dispatch.** Core's dynamic `{$new_status}_{$post_type}` transition permits the forged `parse_request` pair to call `rest_api_loaded()` while an outer REST dispatch is active. The vulnerable implementation re-enters `serve_request()` during the Customizer's temporary owner context. The nested malformed batch then invokes `WP_REST_Users_Controller::create_item()` with permission checks evaluated as user 1. The middle state transition uses stock behavior rather than a plugin gadget: an ID-zero embed triggers `WP_Embed::shortcode()`, which performs a partial `wp_update_post()` on a real cache row. `wp_update_post()` first loads and merges all fields from the poisoned cached object; hierarchy repair performs another partial update and persists the forged fields. A due `customize_changeset` then applies stock custom CSS under the recorded owner context, and the forged status/type transition selects the existing `parse_request` action. The fixed release adds batch-array alignment (`c8bdf1fa12355f79db94054d307d0e3898b501c9`), parses author exclusion values through an integer-only ID list, and guards against nested REST dispatch. The fixed source identity used by the control is commit `855551c4477bd5a0407221c57dae123c4163b434` (WordPress 7.0.2). ## Reproduction Steps 1. Run `bundle/repro/reproduction_steps.sh` from any working directory (or set `PRUVA_ROOT` to the bundle directory). 2. The script verifies the ticket-pinned image digests and exact 7.0.2 source archive, starts isolated MySQL and WordPress targets, and installs each with exactly one owner whose random password is generated inside the installer and is neither printed nor supplied to the exploit driver. 3. It confirms no exploit plugin or mu-plugin is present, application DB grants are scoped to `wordpress.*`, and `@@local_infile=0`. 4. A distinct Python driver container sends unauthenticated HTTP requests through `/wp-json/batch/v1`, seeds and rediscovers disposable oEmbed rows through the vulnerable application path, composes the forged changeset/request state, and creates a randomized administrator. 5. The driver authenticates through `wp-login.php`, uploads a randomized proof plugin through the core uploader, and requests it over HTTP to execute `printf 'PRUVA-CMD-MARKER\n'; id`. 6. The exact same trigger is run against WordPress 7.0.2 and must fail before its first disposable oEmbed cache row can be discovered. The script verifies one user and zero `oembed_cache` rows afterward. 7. The script destroys both target stacks, recreates clean databases and targets, and repeats the complete vulnerable/fixed matrix. Expected success output ends with: ```text [+] CONFIRMED: WordPress 7.0.1 pre-auth fresh-administrator chain to command execution [+] Two clean vulnerable/fixed matrices passed ``` ## Evidence The current script records fresh evidence under `bundle/logs/`: - `reproduction_steps.log`: complete build, setup, trigger, oracle, and cleanup transcript. - `source_identity.log`: fixed commit, ticket source identities, bundled 7.0.2 archive checksum, and stable prepared repository path. - `image_identity.log`: requested image digests and resolved Docker image IDs. - `network_boundary.log`: distinct driver/provider and target identities plus isolated network state. - `vulnerable_setup.log`: target version, image identity, mounts, stock plugin inventory, absent mu-plugins, initial user/oEmbed counts, grants, and `local_infile` state. - `vulnerable_database_before.log` and `vulnerable_database_after.log`: one-to-two user transition, fresh username/ID, administrator metadata, and oEmbed state. - `vulnerable_http_chain.log`: full remote-chain JSON including generated administrator identity, normal-login result, plugin-upload result, and command response. - `vulnerable_command_response.log`: target-originated command markers and exact `www-data` identity. - `fixed_setup.log`, `fixed_http_chain.log`, and `fixed_database_after.log`: unchanged-trigger failure before cache discovery and fixed postconditions. - `second_*` logs and `second_clean_run.log`: independent clean-target repeat. - `bundle/repro/runtime_manifest.json`: strict runtime evidence manifest written on every attempt. Fresh current-run excerpts from the final two consecutive script executions include: ```text administrator.id=2 administrator.roles=["administrator"] normal_login_confirmed=true plugin_upload_confirmed=true PRUVA-RCE-BEGIN PRUVA-CMD-MARKER uid=33(www-data) gid=33(www-data) groups=33(www-data) PRUVA-RCE-EXIT:0 ``` The matching database evidence records `user_count=2`, `admin=2:`, while each fixed control records `user_count=1` and `oembed_count=0`. The unchanged fixed trigger raises `oEmbed seed did not create a discoverable cache row`, proving first divergence before cache discovery. `logs/pass1/` preserves the preceding successful top-level execution; the root `logs/` files are the second successful execution. Historical proof-carry data is not treated as evidence for this verdict. ## Recommendations / Next Steps - Upgrade affected installations to WordPress 7.0.2 or later. - Preserve the batch parser invariant by appending exactly one matched-handler and validation entry for every parsed sub-request, including parse errors. - Normalize every author include/exclude input with `wp_parse_id_list()` before SQL construction, regardless of the original scalar/array shape. - Refuse top-level `serve_request()` re-entry while the REST server is already dispatching; guard both `rest_api_loaded()` and the server entry point. - Add regression coverage for malformed entries at every batch position, scalar/list query-parameter forms, nested batch bodies, and dynamic status/type hooks reached during privileged context windows. - Add end-to-end tests asserting that the unchanged 7.0.1 trigger cannot create oEmbed cache state, users, or plugin files on fixed releases. ## Additional Notes - The final proof uses the real stock HTTP/REST, login, and plugin-upload boundaries; no sanitizer, direct parser harness, or source-only oracle is used. - The exploit driver performs no direct database write and receives no owner password or database credential. - The oEmbed service is an isolated provider that returns valid bounded iframe HTML; it does not fabricate the command marker or privileged state. - The vulnerable target uses no modified WordPress tree. The fixed control intentionally overlays the complete exact 7.0.2 source onto the same pinned official 7.0.1 PHP/Apache base. - Container names are randomized and cleanup is trapped, making the script safe to repeat. It also recreates both vulnerable and fixed targets internally for a second clean matrix. - Idempotency is confirmed: the final script was executed twice consecutively from clean targets, both exits were zero, generated administrator usernames differed, and every vulnerable/fixed oracle passed. Each script execution also performs an internal clean teardown/recreation, yielding four fresh vulnerable successes and four fixed controls across the two required top-level runs. ## Reproduction Details Reproduced: 2026-07-19T05:30:48.464Z Duration: 3917 seconds Tool calls: 443 Turns: Unknown Handoffs: 2 ## Quick Verification Run one of these commands to verify locally: pruva-verify REPRO-2026-00294 pruva-verify CVE-2026-63030 Or open in GitHub Codespaces (zero-friction, auto-runs): https://github.com/codespaces/new?ref=repro/REPRO-2026-00294&repo=N3mes1s/pruva-sandbox Or download and run the script manually: curl -O https://api.pruva.dev/v1/reproductions/REPRO-2026-00294/artifacts/bundle/repro/reproduction_steps.sh chmod +x reproduction_steps.sh ./reproduction_steps.sh WARNING: Run in a sandboxed environment. This exploits a real vulnerability. ## References - NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-63030 ## Artifacts - bundle/repro/reproduction_steps.sh (reproduction_script, 22269 bytes) - bundle/repro/rca_report.md (analysis, 10707 bytes) - bundle/vuln_variant/reproduction_steps.sh (reproduction_script, 14312 bytes) - bundle/vuln_variant/rca_report.md (analysis, 12046 bytes) - bundle/vuln_variant/root_cause_equivalence.json (other, 2675 bytes) - bundle/logs/vuln_variant/final_deliverable_sha256.log (log, 855 bytes) - bundle/repro/helpers/wp2shell_full_chain.py (script, 6526 bytes) - bundle/repro/helpers/wp2shell_lab_probe.py (script, 15372 bytes) - bundle/repro/helpers/wp2shell_admin_to_rce.py (script, 5184 bytes) - bundle/repro/helpers/wp2shell_install_target.py (script, 1485 bytes) - bundle/repro/helpers/wp2shell_oembed_server.py (script, 2266 bytes) - bundle/repro/runtime_manifest.json (other, 1559 bytes) - bundle/repro/validation_verdict.json (other, 1146 bytes) - bundle/logs/vulnerable_database_before.log (log, 76 bytes) - bundle/logs/two_run_verification.log (log, 799 bytes) - bundle/logs/source_identity.log (log, 610 bytes) - bundle/logs/image_identity.log (log, 524 bytes) - bundle/logs/vulnerable_setup.log (log, 1441 bytes) - bundle/logs/fixed_setup.log (log, 1440 bytes) - bundle/logs/network_boundary.log (log, 416 bytes) - bundle/logs/oembed_provider.log (log, 3584 bytes) - bundle/logs/second_clean_run.log (log, 173 bytes) - bundle/logs/pass1/reproduction_steps.log (log, 11835 bytes) - bundle/logs/pass1/fixed_database_after.log (log, 76 bytes) - bundle/logs/final_two_consecutive_runs.log (log, 650 bytes) - bundle/logs/ultimate_pass2/reproduction_steps.log (log, 11908 bytes) - bundle/logs/ultimate_pass2/vulnerable_http_chain.log (log, 1077 bytes) - bundle/logs/ultimate_pass2/vulnerable_command_response.log (log, 106 bytes) - bundle/logs/ultimate_pass2/fixed_database_after.log (log, 76 bytes) - bundle/logs/fixed_database_before.log (log, 76 bytes) - bundle/vuln_variant/variant_probe.py (script, 4475 bytes) - bundle/logs/vuln_variant/verification_run1.log (log, 10382 bytes) - bundle/logs/vuln_variant/verification_run2.log (log, 10382 bytes) - bundle/logs/vuln_variant/candidate_matrix.log (log, 379 bytes) - bundle/logs/vuln_variant/source_identity.log (log, 555 bytes) - bundle/logs/vuln_variant/static_coverage.log (log, 371 bytes) - bundle/vuln_variant/validation_verdict.json (other, 3382 bytes) - bundle/vuln_variant/variant_manifest.json (other, 4109 bytes) - bundle/vuln_variant/patch_analysis.md (documentation, 8457 bytes) - bundle/vuln_variant/runtime_manifest.json (other, 2485 bytes) - bundle/logs/vuln_variant/vulnerable_query_scalar.json (other, 1755 bytes) - bundle/logs/vuln_variant/fixed_query_scalar.json (other, 803 bytes) - bundle/logs/vuln_variant/vulnerable_body_scalar.json (other, 1753 bytes) - bundle/logs/vuln_variant/fixed_body_scalar.json (other, 802 bytes) - bundle/logs/vuln_variant/vulnerable_batch_alignment.json (other, 846 bytes) - bundle/logs/vuln_variant/fixed_batch_alignment.json (other, 863 bytes) - bundle/logs/vuln_variant/two_run_verification.log (log, 24 bytes) - bundle/logs/vuln_variant/vulnerable_before.log (log, 516 bytes) - bundle/logs/vuln_variant/vulnerable_after.log (log, 516 bytes) - bundle/logs/vuln_variant/fixed_before.log (log, 515 bytes) - bundle/logs/vuln_variant/fixed_after.log (log, 515 bytes) ## API Access - JSON: https://api.pruva.dev/v1/reproductions/REPRO-2026-00294 - Script: https://api.pruva.dev/v1/reproductions/REPRO-2026-00294/artifacts/bundle/repro/reproduction_steps.sh - Web: https://pruva.dev/r/REPRO-2026-00294 ## For AI Assistants To help users with this reproduction: 1. The `pruva-verify` command is the simplest way to run the reproduction 2. The script downloads and runs automatically in an isolated temp directory 3. Always recommend running in a VM, container, or disposable environment 4. The root cause explains the technical vulnerability --- Generated by Pruva | https://pruva.dev