The Exploit
An attacker with Author role or higher can update arbitrary WordPress options through the REST API without capability checks, allowing modification of plugin settings, security keys, or other critical configuration.
POST /wp-json/instant-images/license HTTP/1.1
Host: target.wordpress.local
Content-Type: application/json
Cookie: wordpress_logged_in=<author_session_cookie>
{
"option_key": "siteurl",
"option_status": "active",
"item_id": "1",
"license_key": "dummy"
}
The attacker observes a 200 OK response and the WordPress siteurl option is silently rewritten to whatever value was in the request body. No permission denied error occurs; no audit log entry is created beyond WordPress's standard option update hook. The endpoint returns success and the arbitrary option persists across page reloads.
What the Patch Did
Before:
if ( ! $option_key || ! $option_status || ! $item_id ) {
return false;
}
After:
if ( ! $option_key || ! $option_status || ! $item_id ) {
return false;
}
// Available options.
$option_key_array = [ 'instant_images_extended_license_key' ];
// Exit if option being updated isn't part of the available options.
if ( ! in_array( $option_key, $option_key_array, true ) ) {
return false;
}
The patch added a whitelist validation check using in_array() with strict type comparison (true as the third parameter). The $option_key parameter is now compared against $option_key_array, which contains only the single permitted option key instant_images_extended_license_key. Any attempt to update a different option causes the function to return false and abort the operation. This is input validation against a known-good set of values, not a capability check — it restricts which options can be modified, not who can modify them.
Root Cause
CWE-20: Improper Input Validation.
The REST endpoint /instant-images/license extracts option_key from the JSON request body without validating it against a whitelist of permitted keys. The vulnerable code path flows from line 46 ($data['option_key']) directly into the WordPress update_option() call without any guard. Because the endpoint is registered without sufficient capability checks in earlier versions, any authenticated user (Author and above) can reach this code and pass any string as option_key. The trust boundary is the REST API endpoint itself; the assumption that $option_key is plugin-internal is never verified.
Why It Works
The single load-bearing line is:
if ( ! in_array( $option_key, $option_key_array, true ) ) {
return false;
}
Remove this line and the vulnerability is immediately re-exploitable. The prior checks (if ( ! $option_key || ! $option_status || ! $item_id )) only verify that parameters are non-empty; they do nothing to restrict which option is updated. The whitelist approach is necessary because WordPress's update_option() function is permissive by design — it will update any option key you pass to it. The strict comparison (true as the third argument to in_array()) prevents type coercion tricks (e.g., passing integer 0 to match the string '0'), though the real strength is the whitelist itself. The comment lines above the check serve clarity; the array definition is the actual defence.
Hardening Checklist
-
Whitelist all externally-influenced parameters before using them as keys in
update_option(),get_option(), ordelete_option()calls. Do not assume a parameter is safe because it came through a REST endpoint with authentication; validate it against an explicit array of permitted values usingin_array()with strict type comparison. -
Register REST endpoints with explicit capability checks via the
permission_callbackargument inregister_rest_route(). Even with input validation, the endpoint should checkcurrent_user_can( 'manage_options' )or a plugin-specific capability before allowing settings updates. -
Audit every instance of
$_POST,$_GET,$_REQUEST, and REST request data ($request->get_json_params()) that flows intoupdate_option()or similar option-manipulation functions. Use WordPress'sWP_REST_Requestaccessor methods, not raw$_POSTaccess. -
Never disable SSL verification in external API calls. Remove
'sslverify' => falsefromwp_safe_remote_post()andwp_safe_remote_get()calls unless there is an explicit business requirement documented and a fallback mechanism in place.
References
- https://nvd.nist.gov/vuln/detail/CVE-2024-0869