The Exploit
An unauthenticated attacker can upload arbitrary JSON files to a vulnerable Redux Framework installation without any authentication or capability checks.
POST /wp-admin/admin-ajax.php HTTP/1.1
Host: target.local
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary
------WebKitFormBoundary
Content-Disposition: form-data; name="action"
redux_color_scheme_import
------WebKitFormBoundary
Content-Disposition: form-data; name="nonce"
b1946ac92492d2347c6235b4d2611184
------WebKitFormBoundary
Content-Disposition: form-data; name="request_type"
import
------WebKitFormBoundary
Content-Disposition: form-data; name="file"; filename="scheme.json"
Content-Type: application/json
{"color_scheme":"<img src=x
------WebKitFormBoundary--
The attacker observes an HTTP 200 response with success: true and sees the malicious JSON stored in the WordPress upload directory. On subsequent page loads where the color scheme is rendered, the XSS payload fires in the context of the site's domain.
What the Patch Did
Before:
if ( isset( $_REQUEST['nonce'] ) && $this->sanitize_input( $_REQUEST['nonce'] ) === md5( 'color_scheme_import' ) ) {
// Process file upload with no capability check
if ( isset( $_FILES['file'] ) ) {
// ... upload logic
}
}
After:
[File completely deleted]
The patch removed the entire class-redux-color-scheme-import.php file rather than patching individual vulnerabilities. This eliminated the AJAX handler that processed color scheme imports without WordPress capability checks. The fixed version removes the endpoint entirely, forcing Redux to rely on its standard color picker infrastructure that enforces proper authentication through WordPress' native REST/admin mechanisms.
Root Cause
CWE-862: Missing Authorization combined with CWE-352: Cross-Site Request Forgery (Inadequate CSRF Protection)
The vulnerability lies in the Redux AJAX handler registered for the redux_color_scheme_import action. The handler checks for a nonce parameter in $_REQUEST['nonce'], but validates it against a static, hardcoded MD5 hash (md5( 'color_scheme_import' )) rather than WordPress' wp_verify_nonce() function. This nonce is not session-specific, time-limited, or user-specific — any attacker can calculate the expected value. The code performs no current_user_can() capability check before processing the uploaded file. The dataflow: attacker-controlled $_FILES['file'] enters the AJAX handler → minimal MIME type validation occurs (checking array keys instead of values) → file is written to disk via wp_filesystem → no capability boundary is crossed because none exists.
Why It Works
The load-bearing line of the patch is the file deletion itself — there is no single "security line" to point to because the fix was architectural rather than surgical. If you removed only the nonce check and added wp_verify_nonce(), the vulnerability would persist because no capability check exists. If you added only current_user_can( 'manage_options' ), stored XSS would still be possible if the uploaded JSON is rendered without sanitization. The engineer deleted the entire handler because all three defences were missing: proper CSRF validation via wp_verify_nonce(), capability checking via current_user_can(), and output escaping of the imported color values. Rather than patch three separate bugs in a non-standard import endpoint, removing it forced the feature to route through Redux's standard color picker, which already had these controls.
Hardening Checklist
- Always use
wp_verify_nonce( $_REQUEST['nonce'], 'action_name' )for CSRF validation on AJAX handlers, never custom MD5 hashes. WordPress generates unique, time-expiring tokens per session. - Gate file upload AJAX handlers with
current_user_can( 'manage_options' )or a more restrictive capability before touching$_FILES. Usewp_capability_check()if restricting to a specific role or custom capability. - Use
sanitize_text_field()orsanitize_meta()on user-supplied JSON keys and values before storing them, and applyesc_attr()orwp_json_encode()on output to prevent stored XSS through JSON-derived content. - Validate uploaded file MIME type via
wp_check_filetype()andwp_safe_remote_get()on the file content, notfinfo::file()alone. Compare the detected type against an allowlist, not via loosein_array()checks. - Store uploaded files outside the web root when possible, or in a dedicated directory with
.htaccessrules (<FilesMatch "\.json$"> Deny from all </FilesMatch>) if serving them at all.
References
- https://nvd.nist.gov/vuln/detail/CVE-2024-6828