SQL Injection Vulnerability in Quiz and Survey Master (QSM) Plugin Affecting 40k+ Sites

Published 29 January 2026
Table of Contents

Quiz and Survey Master (QSM)

SQL Injection

40K
CVSS 8.5

This blog post is about a Subscriber+ SQL injection vulnerability in the Quiz and Survey Master (QSM) plugin. If you're a QSM user, please update to at least version 10.3.2.

This vulnerability was discovered and reported by Patchstack Alliance community member Doan Dinh Van.

✌️ 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 Quiz and Survey Master plugin

The QSM plugin, with over 40,000 active installations, is a plugin for creating quizzes, surveys, and forms. It includes advanced features like multimedia support and a drag-and-drop quiz builder.

A screenshot of the plugin's animated banner, promoting the plugin's features.

The security vulnerability

In versions 10.3.1 and below, the QSM plugin is vulnerable to SQL injection, allowing any logged-in user to inject commands into the database. This means any Subscriber or higher user is able to perform a wide variety of unwanted actions, including potentially extracting sensitive information stored in the site's database.

This vulnerability has been patched in version 10.3.2 and is tracked with CVE-2025-67987.

The root cause of the issue lies in the qsm_rest_get_question function:

function qsm_rest_get_question( WP_REST_Request $request ) {
	// Makes sure user is logged in.
	if ( is_user_logged_in() ) {
		global $wpdb;
		$current_user = wp_get_current_user();
		if ( 0 !== $current_user ) {
			$question       = QSM_Questions::load_question( $request['id'] );
			$categorysArray = QSM_Questions::get_question_categories( $question['question_id'] );
			if ( ! empty( $question ) ) {
				$is_linking = $request['is_linking'];
				$comma_separated_ids = '';
				if ( 1 <= $is_linking ) {
					if ( isset( $question['linked_question'] ) && '' == $question['linked_question'] ) {
						$comma_separated_ids = $is_linking;
					} else {
						$linked_question = isset($question['linked_question']) ? $question['linked_question'] : '';
						$exploded_question_array = explode(',', $linked_question);
						if ( ! empty($linked_question) ) {
							$exploded_question_array = array_merge([ $is_linking ], $exploded_question_array);
						} else {
							$exploded_question_array = [ $is_linking ];
						}
						$comma_separated_ids = implode(',', array_unique($exploded_question_array));
					}
				}

				$quiz_name_by_question = array();
				if ( ! empty($comma_separated_ids) ) {
					$quiz_results = $wpdb->get_results( "SELECT `quiz_id`, `question_id` FROM `{$wpdb->prefix}mlw_questions` WHERE `question_id` IN (" .$comma_separated_ids. ")" );
--------------------------- CUT HERE --------------------------- 

The plugin works off an assumption that the is_linking parameter is an ID, however it does no validation or sanitizing of the parameter's value before including it in a larger list of question IDs. This list is eventually included directly in an SQL statement (WHERE `question_id` IN (" .$comma_separated_ids. ")"). Because the value is not validated (e.g., with is_int/intval to ensure the value is a number), and the SQL statement is not using a prepared statement (which ensures the value is sanitized before being integrated into the SQL query), a malicious user could send an abnormal value containing an SQL statement, and have that statement be executed as part of the $quiz_results query.

The patch

In version 10.3.2, the vulnerability is mitigated by validating the content of the is_linking parameter with intval. This forces the value to be an integer, regardless of the original content. By ensuring the value is an integer, it can be safely added to a query without risk of injection, as no additional SQL commands could be included.

A screenshot of a diff between 10.3.1 and 10.3.2, showing the fix.

Conclusion

Database calls can be dangerous. User-provided input can be untrustworthy. Combining the two is a recipe for disaster. Even when a particular value isn't intended to be directly provided by a user, any input coming from a request can be modified and needs to be validated before use.

Any input from the user should be validated, sanitized, or both before use. In this case, intval was used to both ensure the value was a number and sanitize it by forcing the value to become a number. When the data is able to be validated as something known and safe, that's great. For anything that may be free form or more complicated to validate, sanitization is a must. PHP itself includes some functions for type validation, and WordPress offers many other sanitization functions for many common use cases.

Regarding SQL specifically, prepared statements are highly recommended. Prepared statements are used to tell the database or the database access APIs, "This is the actual query" and "this is data for the query"; this means the data can be treated specifically as data, and its content won't be used as part of the query that contains it. Whenever possible, we recommend using wpdb::prepare on any SQL queries that may contain untrusted data.

Timeline

21 November 2025We received the vulnerability report and notified the vendor.
04 December 2025The vendor released 10.3.2, which patched this vulnerability.
28 January 2026We published the vulnerability entry to the Patchstack database.
29 January 2026Security advisory article publicly released.

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!

The latest in Security Advisories

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