SQL Injection Vulnerability Found in LifterLMS Plugin Affecting 10K+ Sites

Published 20 August 2025
Table of Contents

LifterLMS

Unauthenticated SQL Injection

10k
CVSS 9.3

This blog post is about LifterLMS theme vulnerabilities. If you're a LifterLMS user, please update the plugin to version 8.0.7 or higher.

✌️ Our users are protected from this vulnerability. Are yours?

Web developers

Mitigate vulnerabilities in real-time without changing code.

See pricing
Plugin developers

Identify vulnerabilities in your plugins and get recommendations for fixes.

Request audit
Hosting companies

Protect your users, improve server health and earn additional revenue.

Patchstack for hosts

About the LifterLMS Theme and Plugin

The LifterLMS plugin, which has over 10,000 installations, is a secure, easy-to-use WordPress LMS plugin packed with features to easily create & sell courses online.

The security vulnerability

This plugin suffers from an SQL Injection vulnerability. The SQL Injection vulnerability itself allows any unauthenticated and also authenticated user to inject a malicious SQL query into a WordPress database query execution. The described vulnerability is patched in version 8.0.7 and assigned CVE-2025-52717.

The underlying vulnerable code exists in the get_voucher_by_code function:

        public function get_voucher_by_code( $code ) {

            global $wpdb;

            $table          = $this->get_codes_table_name();
            $redeemed_table = $this->get_redemptions_table_name();

            $query = "SELECT c.*, count(r.id) as used
                    FROM $table as c
                    LEFT JOIN $redeemed_table as r
                    ON c.`id` = r.`code_id`
                    WHERE `code` = '$code' AND `is_deleted` = 0
                    GROUP BY c.id
                    LIMIT 1";
            return $wpdb->get_row( $query );
        }

To be able to exploit the vulnerability, registration should be enabled on the website. The vulnerability only exists on the installations that allow students to register with a voucher code. The user input is sanitize_text_field(), but it is not enough for SQL Injection attacks. A similar function with the name redeem_voucher() also exists in a different part of the code.

    public function redeem_voucher() {

		if ( ! llms_verify_nonce( 'lifterlms_voucher_nonce', 'lifterlms_voucher_check' ) || ! get_current_user_id() ) {
			return null;
		}

		$voucher  = new LLMS_Voucher();
		$redeemed = $voucher->use_voucher( llms_filter_input_sanitize_string( INPUT_POST, 'llms_voucher_code' ), get_current_user_id() );

		if ( is_wp_error( $redeemed ) ) {
			llms_add_notice( $redeemed->get_error_message(), 'error' );
			return $redeemed;
		}

		llms_add_notice( __( 'Voucher redeemed successfully!', 'lifterlms' ), 'success' );
		return true;
	}

However, in this part of the code user inputted voucher value goes through the llms_filter_input_sanitize_string() function, which has proper sanitation.

function llms_filter_input_sanitize_string( $type, $variable_name, $flags = array() ) {

	$require_array = in_array( FILTER_REQUIRE_ARRAY, $flags, true );

	$string = llms_filter_input( $type, $variable_name, FILTER_UNSAFE_RAW, $require_array ? FILTER_REQUIRE_ARRAY : array() );

	// If we have an empty string or the input var isn't found we can return early.
	if ( empty( $string ) ) {
		return $string;
	}

	$string = $require_array ? array_map( 'wp_strip_all_tags', $string ) : wp_strip_all_tags( $string );

	if ( ! in_array( FILTER_FLAG_NO_ENCODE_QUOTES, $flags, true ) ) {
		$string = str_replace(
			array( "'", '"' ),
			array( ''', '"' ),
			$string
		);
	}

	return $string;
}

The patch

The developer decided to use a proper prepared statement to the affected variables to prevent SQL Injection. The patch can be seen in the below diff image:

Conclusion

For the SQL query process, always do a safe escape and format the user's input before performing a query. The best practice is always to use a prepared statement and also cast each of the used variables to its intended usage, for example, always cast a variable to an integer if the intended value of the variable should be an integer value.

Want to learn more about finding and fixing vulnerabilities?

Explore our Academy to master the art of finding and patching vulnerabilities within the WordPress ecosystem. Dive deep into detailed guides on various vulnerability types, from discovery tactics for researchers to robust fixes for developers. Join us and contribute to our growing knowledge base.

Timeline

3 June, 2025Vulnerability validated by us.
3 June, 2025We reach out to the vendor regarding the vulnerability.
11 June, 2025Vendor released and sent Patchstack the proposed fix.
01 July, 2025Vulnerability released on the Patchstack database
20 August, 2025Security advisory article publicly released.

🤝 You can help us make the Internet a safer place

Plugin developer?

Streamline your disclosure process to fix vulnerabilities faster and comply with CRA.

Get started for free
Hosting company?

Protect your users too! Improve server health and earn added revenue with proactive security.

Patchstack for hosts
Security researcher?

Report vulnerabilities to our gamified bug bounty program to earn monthly cash rewards.

Learn more

The latest in Security Advisories

Looks like your browser is blocking our support chat widget. Turn off adblockers and reload the page.
crossmenu