01 Sep

What Happened With WordPress Plugin Vulnerabilities in August 2017

If you want the best information and therefore best protection against vulnerabilities in WordPress plugins we provide you that through our service.

Here is what we did to keep those are already using our service secure from WordPress plugin vulnerabilities during August (and what you have been missing out on if you haven’t signed up yet):

Plugin Security Reviews

Customers of the service can suggest and vote on plugins to have a security review done by us. This month we released details for a review of:

We don’t currently have any more plugins queue up for a review, so if you sign up now for the service, a plugin you suggest could be reviewed right away.

Plugin Vulnerabilities We Discovered and Publicly Disclosed This Month

We don’t just collect data on vulnerabilities in plugins that others have discovered, we also discover vulnerabilities through proactive monitoring of changes made to plugins, monitoring hackers activity, reviewing other vulnerabilities, and by doing additional checking on the security of plugins.

This month the most concerning vulnerability is a PHP object injection vulnerability in WP Smart Security, since that type of vulnerability is likely to be exploited and the vulnerability hasn’t been fixed yet.

Plugin Vulnerabilities We Helped Get Fixed This Month

Letting you know that you are using a vulnerable version of plugin is useful, but it is much more useful if you can fully protect yourself by simple updating to a new version. So we work with plugin developers to make sure that vulnerabilities get fixed. This month we helped to get vulnerabilities fixed in plugins that have 177,800+ active installs:

Plugin Vulnerabilities Added This Month That Are In The Current Version of the Plugins

Keeping your plugins up to date isn’t enough to keep you secure as these vulnerabilities in the current versions of plugins show:

Additional Vulnerabilities Added This Month

As usual, there were plenty of other vulnerabilities that we added to our data during the month. Most of the new vulnerabilities that were fixed this month are relatively minor.

07 Aug

WordPress Plugin Security Review: wpDataTables Lite

For our thirteenth security review of a plugin based on the voting of our customers, we reviewed the plugin wpDataTables Lite.

If you are not yet a customer of the service you can currently try it free for your first month and then start suggesting and voting on plugins to get security reviews after your first payment for the service. For those already using the service that haven’t already suggested and voted for plugins you can start doing that here.

The review was done on version Lite 1.2.2 of wpDataTables Lite. We checked for the following issues:

  • Insecure file upload handling (this is the cause of the most exploited type of vulnerability, arbitrary file upload)
  • Deserialization of untrusted data
  • Security issues with functions accessible through WordPress’ AJAX functionality (those are a common source of disclosed vulnerabilities these days)
  • Persistent cross-site scripting (XSS) vulnerabilities in publicly accessible portions of the plugin
  • Cross-site request forgery (CSRF) vulnerabilities in the admin portion of plugins
  • SQL injection vulnerabilities (the code that handles requests to the database)
  • Reflected cross-site scripting (XSS) vulnerabilities
  • Lack of protection against unintended direct access of PHP files
  • Insecure and unwarranted requests to third-party websites

Results

We found several vulnerabilities and an additional minor security issue. After notifying the developer of those issues, they have, for the most part, been resolved in version 1.2.3.

Cross-Site Request Forgery (CSRF) Vulnerability

The plugin functionality for deleting one of its tables lacked protection against cross-site request forgery (CRSF). That was fixed in version 1.2.3

SQL Injection Issues

That CSRF vulnerability also allowed for SQL injection. Here is how the function that does the deleting looks as of version 1.2.2:

function wpdatatables_delete_table( $id ){
    global $wpdb;
    if( empty( $id ) || !current_user_can('manage_options') ){ return false; }
 
    $wpdb->query("DELETE
									FROM {$wpdb->prefix}wpdatatables
									WHERE id={$id}");
    $wpdb->query("DELETE
									FROM {$wpdb->prefix}wpdatatables_columns
									WHERE table_id={$id}");
    $wpdb->query("DELETE
									FROM {$wpdb->prefix}wpdatacharts
									WHERE wpdatatable_id={$id}");
}

The SQL statement there lacks protection against SQL injection, either through the user of prepared statement or less ideally sanitization or validation.

The value of $id used in the statement comes from the following code:

$id = $_REQUEST['table_id'];
 
if (!is_array($id)) {
 
	wpdatatables_delete_table( $id );
} else {
	foreach ($id as $single_id) {
 
		wpdatatables_delete_table( $single_id );

Which passes user input to the function without sanitization or validation either.

There are a number of other locations in the code where the plugin has lacked proper security for SQL statements. The other one that we found vulnerable was the function wdt_get_table_by_id():

function wdt_get_table_by_id( $table_id ){
	global $wpdb;
 
	do_action( 'wpdatatables_before_get_table_metadata', $table_id );
 
	$query = "SELECT *
	  				FROM {$wpdb->prefix}wpdatatables
	  				WHERE id={$table_id}";

One of the avenues for getting to that function is through the function wpdatatable_shortcode_handler(), which as the name suggest handles the plugin’s shortcode. It is possible for any logged in user to access most shortcode’s through WordPress’ AJAX functionality, so those should be a check to make sure the user should have access as well as proper security surrounding of any user input that came come through that. In this case the code lacked either of those, passing an unsantized and unvalidated user input to the function wdt_get_table_by_id():

function wpdatatable_shortcode_handler( $atts, $content = null ) {
	global $wpdb, $wdt_var1, $wdt_var2, $wdt_var3, $wdt_export_file_name;
 
	extract( shortcode_atts( array(
		'id' => '0',
		'show_only_chart' => false,
		'no_scripts' => 0,
		'var1' => '%%no_val%%',
		'var2' => '%%no_val%%',
		'var3' => '%%no_val%%',
		'export_file_name' => '%%no_val%%',
		'table_view' => 'regular'
	), $atts ) );
 
	// Protection
	if(!$id){ return false; }
	$table_data = wdt_get_table_by_id( $id );

The SQL statement in the function wdt_get_table_by_id() was parameterized in version 1.2.3.

Lack of Protection Against Direct Access to Files

The plugin’s .php files lacked code at the beginning of the files to restrict direct access to them.  We didn’t see anything that could be exploited in the files without the restriction in place. Code to protect against that was added to many files in version 1.2.3.

12 Dec

Authenticated Persistent Cross-Site Scripting (XSS) Vulnerability in wpDataTables Lite

One of things we do to keep track of what vulnerabilities are out there in WordPress plugins, to provide our customers with the best data on them, is to monitor our websites for hacking attempts. In September we had request that looked like probing for usage of the plugin wpDataTables Lite, through a request for /wp-content/plugins/wpdatatables/Licensing/GPL.txt. Though when we went to look into this we noticed the plugin hasn’t have a file at that location, so it would seem to have been a request checking for something else. It looks like the hacker was a probably probing for usage of a page paid version of the same plugin, which had contained an arbitrary file upload vulnerability in the past. That vulnerability was due to an upload function be accessible to anyone (even if not logged in) through WordPress’ AJAX functionality. Once we saw that we took a quick look at the wpDataTables Lite to see if there were any issue along those lines and found that there is an authenticated persistent cross (XSS) vulnerability in the plugin as of version 1.1.

In the plugin no function are made accessible  for those that are not logged in, but there are 9 that are accessible to those logged in to WordPress. Since that makes them accessible to anyone who is logged in, if the functions are intended to only accessible to higher level users there needs to be code in the function to restrict access.

On of those AJAX accessible functions handles saving the plugins settings (in the file /controllers/wdt_admin_ajax_actions.php):

105
add_action( 'wp_ajax_wdt_save_settings', 'wdt_save_settings');

The settings page is only accessible by Administrator level users, but the wdt_save_settings() function doesn’t restrict it to them:

51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
function wdt_save_settings(){
 
	$_POST = apply_filters( 'wpdatatables_before_save_settings', $_POST );
 
	// Get and write main settings
	$wdtSiteLink = $_POST['wdtSiteLink'];
 
	$wpRenderFilter = $_POST['wpRenderFilter'];
	$wpInterfaceLanguage = $_POST['wpInterfaceLanguage'];
	$wpDateFormat = $_POST['wpDateFormat'];
	$wpTopOffset = $_POST['wpTopOffset'];
	$wpLeftOffset = $_POST['wpLeftOffset'];
	$wdtBaseSkin = $_POST['wdtBaseSkin'];
	$wdtTablesPerPage = $_POST['wdtTablesPerPage'];
	$wdtNumberFormat = $_POST['wdtNumberFormat'];
	$wdtDecimalPlaces = $_POST['wdtDecimalPlaces'];
	$wdtNumbersAlign = $_POST['wdtNumbersAlign'];
	$wdtCustomJs = $_POST['wdtCustomJs'];
	$wdtCustomCss = $_POST['wdtCustomCss'];
	$wdtMinifiedJs = $_POST['wdtMinifiedJs'];
	$wdtMobileWidth = $_POST['wdtMobileWidth'];
	$wdtTabletWidth = $_POST['wdtTabletWidth'];
 
 
	update_option('wdtSiteLink', $wdtSiteLink);
 
	update_option('wdtRenderCharts', 'below'); // Deprecated, delete after 1.6
	update_option('wdtRenderFilter', $wpRenderFilter);
	update_option('wdtInterfaceLanguage', $wpInterfaceLanguage);
	update_option('wdtDateFormat', $wpDateFormat);
	update_option('wdtTopOffset', $wpTopOffset);
	update_option('wdtLeftOffset', $wpLeftOffset);
	update_option('wdtBaseSkin', $wdtBaseSkin);
	update_option('wdtTablesPerPage', $wdtTablesPerPage);
	update_option('wdtNumberFormat', $wdtNumberFormat);
	update_option('wdtDecimalPlaces', $wdtDecimalPlaces);
	update_option('wdtNumbersAlign', $wdtNumbersAlign);
	update_option('wdtCustomJs', $wdtCustomJs);
	update_option('wdtCustomCss', $wdtCustomCss);
	update_option('wdtMinifiedJs', $wdtMinifiedJs);
	update_option('wdtMobileWidth', $wdtMobileWidth);
	update_option('wdtTabletWidth', $wdtTabletWidth);

It also doesn’t check for a valid nonce, so saving the settings is also vulnerable to cross-site request forgery (CSRF).

You can also see that no sanitization is done before saving the settings opening up the possibility of cross-site scripting (XSS) if the escaping is not done when they are output.

On the settings page the setting’s values are not escaped. Using the value for wdtCustomJs as an example, it retrieved from the database here (in the file /controllers/wdt_admin.php):

728
$tpl->addData('wdtCustomJs', get_option('wdtCustomJs'));

Then output in the file /templates/settings.inc.php:

<textarea name="wdtCustomJs" id="wdtCustomJs" style="width: 430px; height: 200px;"><?php echo (!empty($wdtCustomJs) ? stripslashes($wdtCustomJs) : '') ?></textarea><br/>

That value is also output on frontend pages that include tables from the plugin and is not escaped there either. That happens through the function wdt_render_script_style_block() in the file /controllers/wdt_functions.php:

404
405
406
407
408
409
410
411
412
413
414
415
416
function wdt_render_script_style_block(){
 
	$customJs = get_option('wdtCustomJs');
	$script_block_html = '';
	$style_block_html = '';
 
	if($customJs){
		 $script_block_html .= '<script type="text/javascript">'.stripslashes_deep($customJs).'</script>';
	}
	echo $script_block_html;
 
 
}

We notified the developer of the issue on September 8 and they responded the same day that they would fix it with the next release. Three months later a new version was put out, but it doesn’t contain anything that looks like an attempt to fix the issue.

Proof of Concept

The following proof of concept will cause an alert that says “XSS” to be shown on the website’s frontend pages that include tables from the plugin.

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="wdt_save_settings" />
<input type="hidden" name="wdtCustomJs" value='alert("XSS");' />
<input type="submit" value="Submit" />
</form>
</body>
</html>

Timeline

  • 9/8/2016 – Developer notified.
  • 9/8/2016 –  Developer responds.
  • 12/12/2016 – WordPress.org Plugin Directory notified.
  • 12/12/2016 – Plugin removed from WordPress.org Plugin Directory.
  • 12/13/2016 – Version 1.2.2, which fixes vulnerability, submitted to Plugin Directories’ repository.