The Exploit
An unauthenticated attacker can modify homepage SEO settings (title, meta description, breadcrumbs label, social metadata) by sending a REST request to the Rank Math plugin's update_site_editor_homepage endpoint with objectID parameter set to 0.
curl -X POST "https://target.wordpress.local/wp-json/rankmath/v1/updateSiteEditorHomepage" \
-H "Content-Type: application/json" \
-d '{
"objectID": 0,
"homepage_title": "Hacked - Buy Cheap Viagra",
"meta_description": "Malicious SEO spam",
"breadcrumbs_label": "Spam"
}'
The attacker receives a 200 OK response with the settings persisted to the WordPress options table. On the next page load, the injected SEO metadata appears sitewide — in breadcrumb navigation, Open Graph tags, and search engine snippets — without the attacker holding any WordPress user account or capability.
What the Patch Did
Before
public static function get_post_permissions_check( $request ) {
$object_id = $request->get_param( 'objectID' );
if ( $object_id === 0 ) {
return true;
}
After
public static function get_post_permissions_check( $request ) {
$object_id = $request->get_param( 'objectID' );
if ( $object_id === 0 ) {
if ( ! Helper::has_cap( 'titles' ) ) {
return new WP_Error(
'rest_cannot_edit',
__( 'Sorry, you are not allowed to edit homepage SEO settings.', 'seo-by-rank-math' ),
[ 'status' => rest_authorization_required_code() ]
);
}
return true;
}
The patch adds a WordPress capability check using Helper::has_cap( 'titles' ) before granting write access to homepage SEO settings. When objectID === 0 (the homepage), the code now verifies the caller holds the 'titles' capability — a Rank Math-specific permission that gates homepage title and metadata editing — before proceeding. Requests lacking this capability receive a WP_Error with HTTP 403 Forbidden.
Root Cause
CWE-862: Missing Authorization in the REST endpoint's permission callback.
The request parameter objectID flows from the REST request body directly into the permission check at line 103. The vulnerable code uses strict equality (===) to detect the special case of homepage editing ($object_id === 0), but unconditionally returns true without consulting WordPress's capability system. The trust boundary — between an unauthenticated caller and the site's SEO configuration — is crossed without validation. An attacker never needs to authenticate; the permission callback grants authorization to any caller when objectID is zero.
Why It Works
The load-bearing line is if ( ! Helper::has_cap( 'titles' ) ). Without it, the patched code still returns true on line 111, and the exploit succeeds. With it, the capability check gates access and the function returns an error object instead.
The engineers added the error construction (new WP_Error(...)) to provide a standard WordPress REST response; this transforms a silent authz bypass into an auditable denial. The rest_authorization_required_code() call ensures the HTTP status is correct (403 by default, or 401 if the caller is anonymous). These lines add defensive clarity, but the real security boundary is the capability check itself — Helper::has_cap() is where the attacker's request succeeds or fails.
Hardening Checklist
-
Register REST endpoints with a permission callback. Use the
permission_callbackargument inregister_rest_route()and never omit it, even for read-only endpoints. This prevents the server from serving sensitive data to unauthenticated or unprivileged users. -
Check capabilities for every object type in permission callbacks. Do not hardcode authorization logic based on request parameters alone (e.g.,
if ( $id === 0 )); always callcurrent_user_can()or a plugin-specific wrapper likeHelper::has_cap()to verify the caller's role and permissions. -
Audit REST endpoints that accept
objectID,post_id, orobject_idparameters. These are common vectors for object-level authorization bypasses. Ensure each distinct object type (homepage, post, setting) requires an explicit capability check, not just a presence check. -
Use
wp_verify_nonce()for state-changing requests. Although this plugin uses a capability check to fix the bug, adding a nonce check on POST/PUT endpoints provides defence against CSRF when combined with capability checks. -
Test permission callbacks with unauthenticated requests and restricted user roles. Automated tests should verify that endpoints reject requests from users lacking the required capability, not just from administrators. This catches zero-checks early.
References
- https://nvd.nist.gov/vuln/detail/CVE-2025-12714