The Exploit
An unauthenticated attacker can register on a vulnerable WordPress installation and immediately grant themselves administrator privileges by injecting arbitrary user metadata during the registration request.
POST /wp-json/post-grid/v1/form-handler HTTP/1.1
Host: target.local
Content-Type: application/json
{
"id": "tutorRegisterInstructor",
"email": "[email protected]",
"password": "Password123!",
"username": "attacker",
"user_meta": {
"wp_user_level": "10",
"wp_capabilities": {
"administrator": true
}
}
}
The attacker observes a 200 OK response with "registerUserExist": "User register Success". Upon logging in with the supplied credentials, the attacker's account holds full administrator privileges despite never having been granted them through WordPress' standard role management interface.
What the Patch Did
Before:
$user_meta = $request->get_param('user_meta');
global $wpdb;
$table_prefix = $wpdb->prefix;
unset($user_meta[$table_prefix . 'capabilities']);
if (!empty($user_meta)) {
foreach ($user_meta as $metaKey => $metavalue) {
update_user_meta($new_user_id, $metaKey, $metavalue);
}
}
After:
[The entire tutorRegisterInstructor block was removed]
The patch eliminated the privilege escalation vector by removing the code path that accepted arbitrary user_meta parameters from unauthenticated REST API requests and wrote them directly to the user meta table using update_user_meta(). The vulnerable code attempted a blacklist-based defense by unsetting only wp_capabilities, but attackers could still inject alternative capability keys or metadata that grants elevated access through other vectors.
Root Cause
CWE-639: Authorization through User-Controlled Key and CWE-434: Unrestricted Upload of File with Dangerous Type.
The attack surface originates in the REST API endpoint handler for tutorRegisterInstructor registration. An unauthenticated POST request supplies a user_meta parameter (line: $request->get_param('user_meta')), which the code iterates over and passes directly to WordPress' update_user_meta($new_user_id, $metaKey, $metavalue) function without any validation of the $metaKey names. The blacklist attempt—unset($user_meta[$table_prefix . 'capabilities'])—only removes keys matching the exact string wp_capabilities, leaving other privilege-escalation paths open: wp_user_level, custom meta keys that plugins check for capability delegation, or database prefix variations. This crosses the trust boundary between the public internet and the authenticated user meta store.
Why It Works
The load-bearing line of the patch is the complete removal of the user_meta acceptance loop. If only the unset() statement remained, an attacker could still inject metadata via alternative key names. If the endpoint remained but applied a whitelist of safe meta keys, the fix would hold; instead, the vendor chose to delete the entire registration codepath, which signals that the feature was fundamentally incompatible with unauthenticated access. The removal of both the metadata loop and the file upload handling loop indicates that the engineers recognized defense-in-depth was already broken: validating filenames would not address the underlying privilege escalation, so rather than layer incomplete mitigations, they deleted the vulnerable surface.
Hardening Checklist
- Implement a whitelist of allowed user meta keys using
wp_json_schema_sanitize_array()or a custom closure that only permits registration-safe fields (e.g., first_name, last_name, description) and rejects anything containing "capability", "role", or "user_level". - Enforce capability checks on registration endpoints by gating the
tutorRegisterInstructorhandler behindcurrent_user_can('create_users')or an equivalent capability, removing unauthenticated access entirely. - Use WordPress nonces (
wp_verify_nonce()) on all registration forms and validate the nonce in the REST endpoint callback, ensuring the request originated from a form your site rendered. - Audit all
update_user_meta()calls on public-facing endpoints to confirm that meta keys cannot be controlled by request parameters; use static string literals for meta keys instead of interpolation from$_POST,$_GET, or REST params. - Log metadata modifications during registration and alert administrators if unexpected meta keys are written, catching similar attacks in the future even if a whitelist boundary is bypassed.
References
- https://nvd.nist.gov/vuln/detail/CVE-2024-9636