10 Apr

Vulnerability Details: Authenticated Information Disclosure Vulnerability in Duplicate Post

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.

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.
03 Aug

Authenticated Information Disclosure Vulnerability in Simple History

One of things we do to keep track of what vulnerabilities are being discovered in WordPress plugins is to monitor the WordPress support forums for threads related to plugin vulnerabilities. In addition to picking up the type of information we are looking for, we see an assortment of threads about security issues. One that we saw recently involved some one requesting that WordPress include the ability to log user activity, a “Support Representative” pointed them to a couple of plugins that do that. In response the original poster mentioned they were concerned about the additional security risk of the plugins:

I’ve been told by our web developer that keeping plugins to a minimum is the smartest route security-wise. Since these are not official WordPress plugins and don’t have very many ratings, would it increase or decrease the security of my websites to install one of them?

What we found interesting about this was that both of the logging plugins that had been mentioned were ones that we had recently found minor security issues with, so the potential of them introducing an additional security risk was not hypothetical. That doesn’t mean that you shouldn’t use logging plugins or other plugins, but it is a reminder that even plugins with a security purpose can introduce security risks.

Those two plugins were not the only logging plugins we have security issues with and we have now found another one that contained a security issue.

One frequent source of security issues being discovered these days is lack of proper restriction on who can access in functions that are made accessible through WordPress’ AJAX functionality. By default those functions are accessible to anyone who is logged in to WordPress, even though the functions are often intended to only accessible to high level users. For many websites where there is only a single Administrator account or small amount of trusted users these vulnerabilities don’t pose a risk, but for plugins that are intended to be used in environments where that isn’t the case it is more of a concern. That brings us to one such plugin, Simple History, which logs user activity.

In looking over that plugin we found that in the then current version, 2.7.4, a number of functions were available to anyone who is logged in. That included the function api(), which returns the data displayed on the plugin’s page, so anyone who is logged in can see everything that is logged. (Update 8/9/2016: While this function is accessible to anyone, there is actually a restriction in place elsewhere in the code that prevents access to it from allow unauthorized access to data through this.). It also included the function ajax_simple_history_filters_search_user(), which along with providing what was logged for a specified user, also displays some of the user’s details, including their email address.

After we notified the developer of the issue, version 2.7.5 was released that fixes the issue by adding a capability check to relevant functions:

// user must have list_users capability (default super admin + administrators have this)
if ( ! current_user_can("list_users") ) {
	wp_send_json_error();;
}

As reminder that you can’t rely on a plugin’s changelog to tell you whether a security issue has been fixed, the changelog entry for 2.7.5 makes no mention of any included security fix:

  • User logins using e-mail are now logged correctly. Previously the user would be logged in successfully but the log said that they failed.
  • Now only users with list_users capability can view the users filter and use the autocomplete api for users.
  • Add labels to search filters. (I do really hate label-less forms so it’s kinda very strange that this was not in place before.)
  • Misc other internal fixes

Proof of Concept

The following proof of concept will display a user’s details, including their email address.

Make sure to replace “[path to WordPress]” with the location of WordPress and “[username]” of the users you want to see the details of.

http://[path to WordPress]/wp-admin/admin-ajax.php?q=[username]&page_limit=10&action=simple_history_filters_search_user

Timeline

  • 7/28/2016 – Developer notified.
  • 7/30/2016 – Developer responds.
  • 8/3/2016 – Version 2.7.5 released, which fixes issue.
11 Jul

Protecting You Against Wordfence’s Bad Practices: Information Disclosure Vulnerability in WP Maintenance Mode

Wordfence is putting WordPress website at risk by disclosing vulnerabilities in plugins with critical details needed to double check their work missing, in what appears to be an attempt to profit off of these vulnerabilities. We are releasing those details so that others can review the vulnerabilities to try to limit the damage Wordfence’s practice could cause.

Wordfence describes the information disclosure vulnerability in WP Maintenance Mode version 2.0.6 as “allows a remote attacker to download the list of subscribers from WP Maintenance Mode who have asked to be notified when a site returns to full functionality. To exploit this vulnerability, an attacker simply needs to have a registered account on the victim site with no special permissions.”.

First off, most WordPress website don’t allow the public to create accounts, so saying the attacker “simply needs to have a registered account on the victim site” seems to be misleading at best.

When we went to look for the vulnerability we had a pretty good idea of where it should be, as with this type of issue usually there is a function accessible through AJAX that does not contain the proper checks to make only those intended to access it can. In version 2.0.6 of the plugin, in the file /includes/classes/wp-maintenance-mode-admin.php, we found what seemed to be the relevant function being registered for AJAX access:

41
add_action('wp_ajax_wpmm_subscribers_export', array($this, 'subscribers_export'));

But the connected function was already checking to make sure that lower level users could not access the function by exiting if the user cannot manage_options, which only Administrator level users normally have access to:

117
118
119
120
121
122
123
124
125
126
public function subscribers_export() {
	global $wpdb;
 
	try {
		// check capabilities
		if (!current_user_can('manage_options')) {
			throw new Exception(__('You do not have access to this resource.', $this->plugin_slug));
		}
 
		// get subscribers and export

At that point we went looking for something else that could do this particular export and came up empty. Looking at the changes made between 2.0.6 and 2.0.7 didn’t show any changes that would match this vulnerability. We then went back and found the code that checks the user’s capability was added in version 2.0.4. You can see that in version 2.0.3, the function’s code starts without any check first:

104
105
106
107
public function subscribers_export() {
	global $wpdb;
 
	$results = $wpdb->get_results("SELECT email, insert_date FROM {$wpdb->prefix}wpmm_subscribers ORDER BY id_subscriber DESC", ARRAY_A);

Not only did Wordfence claim the vulnerability was in version 2.0.6, which it wasn’t, but they claimed they notified the developer of the vulnerabilities in the week of June 26 – July 2. Version 2.0.4 was released on June 16, so something is not right here.

Proof of Concept

The following proof of concept will download the subscriber list, when logged in to WordPress.

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

http://[path to WordPress]/wp-admin/admin-ajax.php?action=wpmm_subscribers_export
05 Jul

Authenticated Information Disclosure Vulnerability in Log Emails

Recently we took a quick look over plugins that log emails sent through WordPress. Those emails have the potential to contain sensitive information, so the security of them is important. In two cases we found that the plugin allowed any logged in user to view emails logged by the plugin. In the case of the Log Emails plugin we found that issue went further than the other plugin, as it not only allowed you to view logged emails, but also to view any thing else stored as a post. That includes not only posts, but also pages, and any content that a plugin might store in that way, things like logged emails from this plugin.

The other plugin’s issue was due to making the function that allows viewing logged emails accessible via AJAX and then not checking to insure that the user should be allowed to view it. This plugin issues comes involves a rather obscure action hook admin_action_, which about the only reference we could find to was this StackExchange question. The practical effect is the same as this makes the function available to anyone logged in.

Here is where admin_action_ comes in to play in the file /includes/class.LogEmailsPostTypeLog.php:

38
add_action('admin_action_log_emails_view', array($this, 'viewLog'));

You can see that functions that this causes to run, viewLog(), didn’t do any check on who is doing the request or if they are trying to view a post that was created by the plugin in version 1.0.6 of the plugin:

219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
public function viewLog() {
	global $wpdb;
 
	$post_id = empty($_GET['post_id']) ? 0 : absint($_GET['post_id']);
	if (!$post_id) {
		return;
	}
 
	$post = get_post($post_id);
 
	// get next / prev links
	$sql = "
		select ID
		from {$wpdb->posts}
		where ID &lt; {$post->ID} and post_status='publish' and post_type = '" . self::POST_TYPE . "'
		order by ID desc limit 1
	";
	$previous = $wpdb->get_var($sql);
	$previous = $previous ? $this->getLogViewURL($previous) : false;
	$sql = "
		select ID
		from {$wpdb->posts}
		where ID > {$post->ID} and post_status='publish' and post_type = '" . self::POST_TYPE . "'
		order by ID asc limit 1
	";
	$next = $wpdb->get_var($sql);
	$next = $next ? $this->getLogViewURL($next) : false;
 
	// current page and list links
	$current = $this->getLogViewURL($post->ID);
	$list = admin_url('edit.php?post_type=' . self::POST_TYPE);
 
	// actions and filters just for this page
	add_filter('admin_body_class', array($this, 'logViewBodyClass'));
	add_filter('parent_file', array($this, 'fixLogViewMenuHierarchy'));
	add_action('admin_enqueue_scripts', array($this, 'adminEnqueueScripts'));
 
	// show the view
	require_once ABSPATH . 'wp-admin/admin-header.php';
	require LOG_EMAILS_PLUGIN_ROOT . 'views/log-detail.php';
	require ABSPATH . 'wp-admin/admin-footer.php';
}

After we contacted the developer about the issue they released version 1.1.0, which fixes the vulnerability by adding code to the viewLog() function to make sure that the requested post is a logged email and that the user has the ability to edit this type of post (which is defined in another location as a user who has the “activate_plugins capability) before displaying it:

265
266
267
268
269
270
271
272
273
274
275
276
$post_type = get_post_type_object($post->post_type);
 
if ($post->post_type !== self::POST_TYPE) {
	wp_die(__('This post is not an email log.', 'log-emails'));
}
 
if (!current_user_can($post_type->cap->edit_posts)) {
	wp_die(sprintf('<h1>%s</h1><p>%s</p>',
		__('Cheatin&#8217; uh?', 'log-emails'),
		__('You are not allowed to view email logs.', 'log-emails')),
		403);
}

Proof of Concept

The following proof of concept will display the post at ID 1, when submitted while logged in to WordPress.

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

http://[path to WordPress]/wp-admin/admin.php?action=log_emails_view&post_id=1

Timeline

  • 7/1/2016 – Developer notified.
  • 7/2/2016 – Version 1.1.0 released, which fixes vulnerability.
05 Jul

Authenticated Information Disclosure Vulnerability in Email Log

When thinking about the security of WordPress plugins the more obvious concern is vulnerabilities that lead to websites being hacked, but for more high profile website there should also be plenty of concern for other issues, like the leaking of potential sensitive information. A wide range of plugins interact with that type of information, but the security of them doesn’t seem to be very well looked after either by the developers or the public that are using them based on some the vulnerabilities we are discovering.

We recently took a look over plugins that allow logging emails sent by the website, which depending on what is included could be rather sensitive. In a couple of cases we found that the logged emails were viewable by anyone logged in to WordPress. In the first, Email Log, the logged emails are displayed by the function display_content_callback() which is accessed through AJAX request.

147
add_action( 'wp_ajax_display_content', array( $this, 'display_content_callback' ) );

Since that makes it accessible to anyone logged in to WordPress there should be a check to make sure the user should be able to access it, which in this case would be only Administrator level users. But in looking at the function in version 1.9 of the plugin you can see that doesn’t happen:

297
298
299
300
301
302
303
304
305
306
307
308
309
public function display_content_callback() {
	global $wpdb;
 
	$table_name = $wpdb->prefix . self::TABLE_NAME;
	$email_id   = absint( $_GET['email_id'] );
 
	$query      = $wpdb->prepare( 'SELECT * FROM ' . $table_name . ' WHERE id = %d', $email_id );
	$content    = $wpdb->get_results( $query );
 
	echo wpautop( $content[0]->message );
 
	die(); // this is required to return a proper result
}

After notifying the developer of the issue they released version 1.9.1, which fixes the the vulnerability by checking if the request is sent by someone who can “manage_options” before displaying the logged email:

297
298
299
300
301
public function display_content_callback() {
	global $wpdb;
 
	if ( current_user_can( 'manage_options' ) ) {
		$table_name = $wpdb->prefix . self::TABLE_NAME;

Proof of Concept

The following proof of concept will display the first logged email, when submitted while logged in to WordPress.

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

http://[path to WordPress]/wp-admin/admin-ajax.php?action=display_content&email_id=1

Timeline

  • 7/1/2016 – Developer notified.
  • 7/1/2016 – Version 1.9.1 released, which fixes vulnerability.
05 Jul

Authenticated Information Disclosure Vulnerability in Activity Log

Every additional plugin that you add to your WordPress website adds additional security risk, that includes security plugins. Recently we did a quick check over plugins designed to allow you to keep track actions taken by users on your website. In several of cases we found rather minor security vulnerabilities. One of those was an authenticated information disclosure vulnerability in the plugin Activity Log, which allows anyone logged in to WordPress to see the display name of other users on the website (which would normally be their first and last names), their role, and their user ID number. The value of that information would depend on if the users’ names was something that some would rather not be public and if there was some possibility that the information could be used to assist a malicious attacker in gaining further access to the website.

The vulnerability is due to the AJAX accessible function ajax_aal_get_properties(), in the file /classes/class-aal-settings.php, not having a check to make sure that the request is coming from a user who should be able to access to it (normally that would only be Administrator level users):

280
281
282
283
284
285
286
287
288
289
290
public function ajax_aal_get_properties() {
	$action_category = isset( $_REQUEST['action_category'] ) ? $_REQUEST['action_category'] : false;
 
	$options = AAL_Main::instance()->notifications->get_settings_dropdown_values( $action_category );
 
	if ( ! empty( $options ) ) {
		wp_send_json_success( $options );
	}
 
	wp_send_json_error();
}

Through that you can access the function get_settings_dropdown_values(), which allows you access to several pieces of data. The only one that seems to be important being the one display the user data:

131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
case 'user':
	// cache all data in case we need the same data twice on the same/upcoming pageloads
	if ( false === ( $results = wp_cache_get( $cache_key = 'notifications-users', 'aal' ) ) ) {
		// get all users
		$all_users = get_users();
		$preped_users = array();
 
		// prepare users
		foreach ( $all_users as $user ) {
			$user_role = $user->roles;
 
			// if user has no role (shouldn't happen, but just in case)
			if ( empty( $user_role ) )
				continue;
 
			$user_role_obj = get_role( $user_role[0] );
			$user_role_name = isset( $user_role_obj->name ) ? $user_role_obj->name : $user_role[0];
 
			$preped_users[ $user->ID ] = apply_filters( 'aal_notifications_user_format', sprintf( '%s - %s (ID #%d)', $user->display_name, $user_role_name, $user->ID ), $user );
		}
 
		wp_cache_set( $cache_key, $results = $preped_users, 'aal' ); // no need for expiration time
	}

We notified the developer and they claimed it “isn’t really a security issue” and have yet to fix it.

Proof of Concept

The following proof of concept, when logged in as a subscriber level or higher user, will display all users’ display names, roles, and user IDs.

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="aal_get_properties" />
<input type="hidden" name="action_category" value="user" />
<input type="submit" value="Submit" />
</form>
</body>
</html>

Timeline

  • 6/27/2016 – Developer notified.
  • 7/6/2016 – Version 2.3.0 released, which fixes vulnerability.
25 May

Protecting You Against Wordfence’s Bad Practices: Sensitive Data Exposure Vulnerability in Caldera Forms

Wordfence is putting WordPress website at risk by disclosing vulnerabilities in plugins with critical details needed to double check their work missing, in what appears to be an attempt to profit off of these vulnerabilities. We are releasing those details so that others can review the vulnerabilities to try to limit the damage Wordfence’s practice could cause.

Wordfence describes the vulnerability in Caldera Forms version 1.3.5.2 as “This vulnerability allows an attacker to gain access to potentially sensitive data that has been captured by a Caldera Form.”

The relevant change in the next version was to restrict certain AJAX functions to Administrator level users in the file /classes/admin.php.

Code in 1.3.5.2:

96
97
98
99
100
add_action("wp_ajax_toggle_form_state", array( $this, 'toggle_form_state') );
add_action("wp_ajax_browse_entries", array( $this, 'browse_entries') );		
add_action("wp_ajax_save_cf_setting", array( $this, 'save_cf_setting') );
add_action("wp_ajax_cf_dismiss_pointer", array( $this, 'update_pointer') );
add_action("wp_ajax_cf_bulk_action", array( $this, 'bulk_action') );

Code in 1.3.5.3:

96
97
98
99
100
101
102
if( current_user_can( Caldera_Forms::get_manage_cap( 'admin' ) ) ) {
	add_action( "wp_ajax_toggle_form_state", array( $this, 'toggle_form_state' ) );
	add_action( "wp_ajax_browse_entries", array( $this, 'browse_entries' ) );
	add_action( "wp_ajax_save_cf_setting", array( $this, 'save_cf_setting' ) );
	add_action( "wp_ajax_cf_dismiss_pointer", array( $this, 'update_pointer' ) );
	add_action( "wp_ajax_cf_bulk_action", array( $this, 'bulk_action' ) );
}

Wordfence’s description notably doesn’t mention that the attacker needs to be logged in to WordPress to exploit this, which severely limits the severity of the vulnerability.

The plugin developer also added the following code to check nonce’s to the functions update_pointer(), save_cf_setting(), and browse_entries():

self::verify_ajax_action();

Proof of Concept

The following proof of concept will show the entries for a form.

Make sure you are logged in to WordPress, ideally as a subscriber since they have the least capabilities. Also, make sure to replace “[path to WordPress]” with the location of WordPress and “[form id]” with the ID of the gallery you want to view the entries for (the form id can be found on the form’s page on the frontend).

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