WordPress · Report #3923967
WordPress 7.1 Media Processing Race Enables Persistent PHP File Write and Author-to-RCE
How it was found
Hi Wordpress security team,
We are submitting this report in good faith and in the best interest of preventing this issue from shipping in the upcoming WordPress 7.1 release.
The vulnerability is not present on the current WordPress 7.0 branch (7.0.3), , while we have confirmed it in the WordPress 7.1 development line from Beta 2 through the current development source build we tested (7.1-RC2-63095-src).
WordPress 7.1 is currently scheduled for release on August 19, 2026. We are therefore reporting this before the final release so the issue can be reviewed before it reaches stable users.
If issues affecting pre-release/development versions are considered outside the scope of the HackerOne program, we respectfully ask that you allow us to withdraw/close the report from our side rather than resolving it as N/A.
Pre-condition
For the demonstrated direct-HTTP execution path, WordPress's selected temporary directory must be located under a PHP-served path.
If /tmp is not writable by the user running PHP, WordPress can fall back to another writable temporary directory. In our Docker reproduction, making /tmp non-writable for the PHP user causes WordPress to fall back to the wp-content directory.
We simulate this condition in Docker by making /tmp writable only by root.
One deployment scenario where this condition can occur is a containerized environment using readOnlyRootFilesystem, depending on how /tmp is mounted and configured. If /tmp is not provided as a writable tmpfs/emptyDir for the web/PHP user, WordPress may select another writable fallback directory.
Vulnerability
Under the validated preconditions, an authenticated user with the default Author role can exploit a race condition to achieve persistent arbitrary PHP execution.
By combining the media finalization, URL-media sideload, and image post-processing flows, the attacker can cause WordPress/Imagick to generate persistent image files with a .php extension while the image XMP metadata contains valid PHP code.
The race condition leading to RCE involves three REST actions in addition to the initial image upload.
Step 1
First, an image is uploaded using:
POST /wp-json/wp/v2/media?generate_sub_sizes=false
Step 2
Call:
POST /wp-json/wp/v2/media/{id}/finalize
This poisons the seed attachment metadata so original_image points to the attacker-chosen temporary .php path.
Step 3
Call:
POST /wp-json/wp/v2/media
with the url parameter.
This causes WordPress to fetch the attacker-controlled JPEG. A remote Content-Disposition header causes the predictable .php basename to temporarily exist in WordPress's selected temporary directory.
Race the above request with:
POST /wp-json/wp/v2/media/{id}/post-process?action=create-image-subsizes
The /post-process request races image processing against the temporary .php JPEG. When the race succeeds, Imagick opens the file as an image and WordPress generates persistent .php image derivatives.
Vulnerable Version
Affected: WordPress 7.1-RC2-63095-src — confirmed
Also reproduced in the WordPress 7.1 development line beginning with Beta 2.
Not affected: current WordPress 7.0 branch (7.0.3-src) in our testing.
Root cause of vulnerability :
-
/wp-json/wp/v2/media- the URL-media flow downloads an attacker-controlled remote image throughdownload_url().download_url()honors the remoteContent-Disposition: attachment; filename=malicious.phpvalue and renames the local temporary file to that attacker-influenced basename, allowing a valid image to briefly exist with an executable extension such as.php. -
/wp-json/wp/v2/media/{seed_id}/post-process?action=create-image-subsizesraces against the temporary file and invokeswp_update_image_subsizes()- which obtains the original-image path from attachment metadata and passes it into the image editor.Original path consumed for sub-size generation If the extension does not identify a known MIME type,
wp_get_image_editor()deliberately allows the selected image editor to determine the format from the file contents instead of rejecting it based on extension. The derivative filename generator reuses the source extension when no replacement extension is supplied, allowing.phpto propagate into generated sub-size filenames. -
/wp-json/wp/v2/media/{id}/finalizeallows attacker-controlled filename/path metadata to be persisted and later treated as a trusted local image path. The route acceptsfileandoriginal_imageas unrestricted strings, andfinalize_item()directly assigns the suppliedoriginal_imageinto attachment metadata without sufficient validation.
Steps to Reproduce
-
Clone the WordPress development repository and check out the exact tested source revision:
git clone https://github.com/WordPress/wordpress-develop.git cd wordpress-develop git checkout 3ca084eb3399222a08cc0573278eed6be0f80714Verify the version in:
wordpress-develop/src/wp-includes/version.phpExpected version:
7.1-RC2-63095-srcWP version bump commit:
https://github.com/WordPress/wordpress-develop/commit/3ca084eb3399222a08cc0573278eed6be0f80714
-
Install dependencies:
npm install -
Build WordPress:
npm run build:dev -
Start the Docker environment:
npm run env:start -
Install WordPress:
npm run env:install -
Create an Author user:
npm run env:cli -- user create attacker [email protected] \ --role=author \ --user_pass='Password123!' -
Verify the user was created:
npm run env:cli -- user list -
Make
/tmpnon-writable for non-root users:docker exec -u root wordpress-develop-php-1 chmod 755 /tmp -
Verify the
/tmppermissions:docker exec -u root wordpress-develop-php-1 ls -ld /tmpExpected permissions should resemble:
drwxr-xr-x -
From the directory containing the reproducer, run:
python3 race_poc.pyThe PoC uses the crafted image payload hosted at:
http://140.245.237.219/payload.jpgThe file is approximately 50 MB and contains PHP code inside its image metadata that executes:
phpinfo();as proof of PHP code execution.
-
To generate the crafted image payload locally instead, use the attached generator:
python3 gen_payload.pyEnsure the generated file is served with the following HTTP response headers:
Content-Type: image/jpeg Content-Disposition: attachment; filename="payload.php"
Impact
Under the validated preconditions, an attacker with a default Author account can achieve persistent remote code execution on the WordPress server.
Successful exploitation creates PHP-executable image derivatives that:
- Persist after the temporary source file is deleted.
- Can be invoked over HTTP without authentication.
- Execute with the privileges of the PHP/web-server user.
- May expose database credentials, authentication salts, private files, and other application secrets.
- Can enable database manipulation, administrator account creation, content compromise, malware persistence, and lateral movement.
The demonstrated RCE requires Imagick, a PHP-served writable temporary directory, PHP execution in the resulting location, and successful exploitation of the race window.