SECURITY ADVISORY / 01

CVE-2025-11923 Exploit & Vulnerability Analysis

Complete CVE-2025-11923 security advisory with proof of concept (PoC), exploit details, and patch analysis for lifterlms.

lifterlms products NVD ↗
Exploit PoC Vulnerability Patch Analysis

The Exploit

An authenticated student-level user can escalate to administrator by sending a single REST API PATCH request to their own user endpoint, supplying a roles array that includes administrator.

PATCH /wp-json/llms/v1/students/123 HTTP/1.1
Host: target.example.com
Authorization: Bearer <student_auth_token>
Content-Type: application/json

{
  "roles": ["administrator"]
}

The attacker receives a 200 OK response with their user record modified to include the administrator role. Within seconds, they can visit /wp-admin/ and access the WordPress dashboard with full administrative privileges.

What the Patch Did

Before

if ( get_current_user_id() === $request['id'] ) {
    return true;
}

if ( ! current_user_can( 'edit_students', $request['id'] ) ) {
    return llms_rest_authorization_required_error( __( 'You are not allowed to edit this student.', 'lifterlms' ) );
}

return $this->check_roles_permissions( $request );

After

if ( is_wp_error( ( new WP_REST_Users_Controller() )->update_item_permissions_check( $request ) ) ) {
    return llms_rest_authorization_required_error( __( 'You are not allowed to edit this user.', 'lifterlms' ) );
}

if ( ! empty( $request['roles'] ) ) {
    return $this->check_roles_permissions( $request );
}

return true;

The patch replaced a permissive self-modification bypass with a two-stage permission check. First, it delegates to WordPress's core WP_REST_Users_Controller::update_item_permissions_check() method, which enforces the standard WordPress user-edit capability model. Second, it gates role modifications behind an explicit additional check: only if the request contains a roles parameter does the plugin call check_roles_permissions(). The old code had zero guards on role changes made during self-edits — it returned true immediately when get_current_user_id() === $request['id'], skipping all downstream role validation.

Root Cause

CWE-284: Improper Access Control.

The vulnerability lies in the update_item_permissions_check() method of both the students and instructors REST controllers. When a user updates their own account (detected via get_current_user_id() === $request['id']), the old code short-circuits the permission check and returns true without inspecting the request body. The roles parameter — passed unsanitized in the JSON request body — flows directly into the user update without validation. The trust boundary crossed is the one between the request handler and the capability system: the code assumes that "self-edit is always allowed" and never checks whether the current user has the capability to modify roles, only to update their own profile. WordPress separates these: editing your own user record is a lower bar than changing your own role. The plugin conflated them.

Why It Works

The load-bearing line is the call to ( new WP_REST_Users_Controller() )->update_item_permissions_check( $request ). Removing it would leave the bug fully exploitable, because the old code's get_current_user_id() === $request['id'] check would still fire and return true before any role validation runs. The ! empty( $request['roles'] ) gate on the role permission check is the secondary guard: it prevents the plugin from calling check_roles_permissions() on requests that don't attempt role modification, avoiding unnecessary overhead. But it is not load-bearing — a student could still bypass it if the WordPress core check were absent. The engineer added the secondary check for defence-in-depth: even if a caller somehow bypasses the WordPress check, the plugin will still validate role changes as a second opinion. The architectural insight is that role changes are a special capability in WordPress and LLMS — they must not be deduced from a blanket "you can edit your own profile" permission.

Hardening Checklist

  • Audit all *_permissions_check() methods for shortcut logic: Search your codebase for if ( get_current_user_id() === $request['id'] ) return true patterns. Replace them with calls to WordPress's standard REST user controller, or explicitly check current_user_can( 'edit_users' ) at minimum.

  • Separate profile edits from capability edits: Use ! empty( $request['roles'] ) || ! empty( $request['capabilities'] ) conditionals to route role and capability modifications through a distinct, strict permission gate. Never treat them as incidental to a profile update.

  • Delegate to WordPress core where possible: Before writing custom permission checks, consult WP_REST_Users_Controller, WP_REST_Posts_Controller, and similar core controllers. Reuse their *_permissions_check() logic rather than reimplementing it; it receives security backports and is battle-tested.

  • Test permissions with minimal roles: Write integration tests that attempt to call every REST endpoint with subscriber, contributor, and author roles. Verify that privilege escalation vectors (role changes, capability grants) fail for all non-administrative accounts.

  • Use wp_die() or throw errors early on auth failures: Avoid patterns where a permission check returns true but a later operation fails silently. Return a WP_Error object early; it prevents the request body from being processed further.

References

  • https://nvd.nist.gov/vuln/detail/CVE-2025-11923

Frequently asked questions about CVE-2025-11923

What is CVE-2025-11923?

CVE-2025-11923 is a security vulnerability identified in lifterlms. This security advisory provides detailed technical analysis of the vulnerability, exploit methodology, affected versions, and complete remediation guidance.

Is there a PoC (proof of concept) for CVE-2025-11923?

Yes. This writeup includes proof-of-concept details and a technical exploit breakdown for CVE-2025-11923. Review the analysis sections above for the PoC walkthrough and code examples.

How does CVE-2025-11923 get exploited?

The technical analysis section explains the vulnerability mechanics, attack vectors, and exploitation methodology affecting lifterlms. PatchLeaks publishes this information for defensive and educational purposes.

What products and versions are affected by CVE-2025-11923?

CVE-2025-11923 affects lifterlms. Check the affected-versions section of this advisory for specific version ranges, vulnerable configurations, and compatibility information.

How do I fix or patch CVE-2025-11923?

The patch analysis section provides guidance on updating to patched versions, applying workarounds, and implementing compensating controls for lifterlms.

What is the CVSS score for CVE-2025-11923?

The severity rating and CVSS scoring for CVE-2025-11923 affecting lifterlms is documented in the vulnerability details section. Refer to the NVD entry for the current authoritative score.