Quiz Maker SQLi
Introduction
In this post, I’ll walk through the discovery and analysis of CVE-2024-10628, a high impact vulnerability (CVSS 3.1 Score: 7.5). This vulnerability allows unauthenticated attackers to perform unauthenticated SQLi attacks.
Background
During an offsite security assessment, I noticed the Quiz Maker plugin installed on a customer’s WordPress instance. As part of my standard methodology, I obtained Business version 8.8.0 of the plugin for local analysis, which led to the discovery of an unauthenticated SQLi.
Affected Versions
- Plugin: Quiz Maker Business
- Version: 7.0.0 - 8.8.0
- Plugin: Quiz Maker Developer
- Version: 20.0.0 - 21.8.0
- Plugin: Quiz Maker Agency
- Version: 30.0.0 - 31.8.0
Initial Analysis
My vulnerability discovery process began by identifying potential entry points. The wp_ajax_nopriv_{$action}
hook immediately stood out as it allows unauthenticated access.
I focused on the ays_questions_statistics_export
AJAX endpoint, which retrieves statistics of quiz interactions.
//quiz-maker.8.8.0/quiz-maker/admin/class-quiz-maker-admin.php
...
public function ays_questions_statistics_export() {
error_reporting(0);
global $wpdb;
$quiz_id = isset($_REQUEST['id']) ? $_REQUEST['id'] : null;
$quizes_table = $wpdb->prefix . 'aysquiz_quizes';
$quizes_questions_table = $wpdb->prefix . 'aysquiz_questions';
$sql = "SELECT question_ids FROM {$quizes_table} WHERE id = {$quiz_id};";
$results = $wpdb->get_var( $sql);
$questions_ids = array();
$questions_counts = array();
$questions_list = array();
if($results != ''){
$results = explode("," , $results);
foreach ($results as $key){
$questions_ids[$key] = 0;
$questions_counts[$key] = 0;
$sql = "SELECT question FROM {$quizes_questions_table} WHERE id = {$key} ; ";
$questions_list[$key] = $wpdb->get_var( $sql);
}
}
...
$quests = array();
$questions_headers = array(
array( 'text' => "question" ),
array( 'text' => "correctness" ),
array( 'text' => "correct_answers" ),
array( 'text' => "answered_questions" )
);
$export_data = array();
$quests[] = $questions_headers;
foreach ($questions_ids as $n => $a){
if ($a != 0 || $questions_counts[$n] != 0){
$score = round($a/$questions_counts[$n]*100, 1) . "%";
}else {
$score = 0 . "%";
}
$q = array(
array( 'text' => strip_tags(stripslashes(str_replace("\n", "", wpautop($questions_list[$n])))) ),
array( 'text' => $score ),
array( 'text' => $a ),
array( 'text' => $questions_counts[$n] )
);
$quests[] = $q;
}
$export_data = array(
'status' => true,
'type' => 'xlsx',
'data' => $quests
);
ob_end_clean();
$ob_get_clean = ob_get_clean();
echo json_encode($export_data);
wp_die();
}
...
Further inspection revealed, the id HTTP Request parameter was passed unsafely into multiple SQL queries.
Proof of Concept
Armed with the knowledge of multi-query injection potential, I crafted a sophisticated second-order SQL injection payload. This approach allows an attacker to:
- Inject a payload in the first query
- Use the result of that query to execute a subsequent malicious query
Patch Analysis
After reporting this vulnerability, the vendor addressed it in versions:
- Quiz Maker Business 8.8.0.100
- Quiz Maker Developer 21.8.0.100
- Quiz Maker Agency 31.8.0.100
The fix put in place was to properly sanitize the id HTTP Request parameter, effectively preventing SQL injection attacks.
Remediation and Disclosure
- Reported to vendor [16/10/2024]
- CVE assigned: CVE-2024-10628 [31/10/2025]
- Public Disclosure [25/01/2025]
- Patch released [28/01/2025]
Conclusion
This was a fun vulnerability to discover!
It provided me with the rare opportunity of a second order SQLi.
Thanks for Reading! If you found this analysis valuable, consider subscribing to my newsletter for more security insights.