04 May

Authenticated Information Disclosure Vulnerability in Page and Post Clone

The log message for version 1.1 of the plugin Page and Post Clone was “cookie exploit resolution”.  In looking at the changes made in that version to see if that was a vulnerability that we should add to our data we found that what was being fixed there was a cross-site request forgery (CSRF) vulnerability. As far we can think of, that seems of little consequence. In looking into that though we realized that the plugin did have a slightly more serious issue that we had previously also noticed in other plugins that provide the same functionality (one of the negatives of having so many WordPress plugins is that you can have the same vulnerabilities come up again and again as new plugins are introduced).

As of version 1.1 the plugin doesn’t check if the user cloning a page or post has the ability to edit the post, which could, for example, lead to a contributor-level user or author-level users gaining access to the contents of password protected posts.

Currently the only checking done in the function content_clone() is to see if the post ID of the page or post to clone is specified and for valid nonce (to prevent CSRF):

19
20
21
22
23
24
25
26
27
28
29
function content_clone(){
	global $wpdb;	
	if (! ( isset( $_GET['post']) || isset( $_POST['post'])  || ( isset($_REQUEST['action']) && 'content_clone' == $_REQUEST['action'] ) ) ) {
		wp_die('No post to duplicate has been supplied!');
	}
 
	/*
	 * Nonce verification
	 */
	if ( !isset( $_GET['clone_nonce'] ) || !wp_verify_nonce( $_GET['clone_nonce'], basename( __FILE__ ) ) )
		return;

The nonce is accessible to anyone that can the Pages and or Posts menu and had the edit_posts capability:

113
114
115
116
117
118
function content_clone_link( $actions, $post ) {
	if (current_user_can('edit_posts')) {			
		$actions['duplicate'] = '<a title="Clone!" href="' . wp_nonce_url('admin.php?action=content_clone&post=' . $post->ID, basename(__FILE__), 'clone_nonce' ) . '" rel="permalink">Clone</a>';
	}
	return $actions;
}

The code should check if the user can edit the specific post being cloned, as that would restrict them from gaining access to posts they could not otherwise access.

We notified the developer of the issue a week ago. We haven’t heard back from them and no new version has been released to fix the issue. In line with our disclosure policy, which is based on the need to provide our customers with information on vulnerabilities on a timely basis, we are now disclosing this vulnerability.

Proof Concept

Create a password protected post as one user and then as a separate Contributor-level user click the Clone link under that post.

Timeline

  • April 27, 2018 – Developer notified.
14 Feb

A Recently Closed Plugin Contains a Vulnerability That Allows Anyone Logged in to WordPress to View Directory Listings

Today we had somebody contact us asking if we had any insight in to why the plugin WordPress Backup to Dropbox was removed from the Plugin Directory (after seeing one of yesterday’s posts). Our guess on that would be that it has to do with the plugin no longer working, but while doing a quick look over the plugin we did find a vulnerability in it that allows anyone logged in to WordPress to view a list of files and directories in a directory on the server they specify.

The plugin makes the function backup_to_dropbox_file_tree() accessible to anyone logged in to WordPress through WordPress’ AJAX functionality:

376
add_action('wp_ajax_file_tree', 'backup_to_dropbox_file_tree');

That function will load up the file /Views/wpb2d-file-tree.php:

155
156
157
158
159
function backup_to_dropbox_file_tree()
{
    include 'Views/wpb2d-file-tree.php';
    die();
}

That file will list the files and directories located in specified directory on the server (as specified by the POST input “dir”).

Proof of Concept

The following proof of concept will return a listing of the files and directories in the root directory of the WordPress install, when logged in to WordPress. The contents of the resulting are hidden using “display: none” styling, so you either need to remove that styling or view the page’s source to see the results.

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?action=file_tree" method="POST">
<input type="hidden" name="dir" value="../" />
<input type="submit" value="Submit" />
</form>
</body>
</html>
20 Oct

Authenticated Information Disclosure Vulnerability in Duplicate Page

We recently went to a take a look at the details of a reflected cross-site scripting (XSS) vulnerability that had been disclosed in the plugin Duplicate Page we noticed that it also had a cross-site request forgery (CSRF) vulnerability. After that we remember that a similar plugin Duplicate Post had previously had a vulnerability that allowed lower level users to get access to password protected posts by duplicating them that was in part due to a lack of protection against CSRF and we then went to check if that was issue with that plugin as well. We found that it was possible.

With the other plugin its functionality was only intended to be used by Editor and Administrator-level users, while with this one the plugin ads links to do the duplication as long as the user has the “edit_posts” capability (in the file /duplicatepage.php):

178
179
180
if (current_user_can('edit_posts')) {
$actions['duplicate'] = '<a title="Duplicate this as '.$post_status.'" href="admin.php?action=dt_duplicate_post_as_draft&post=' . $post->ID . '" rel="permalink">'.__( "Duplicate This", "duplicate_page" ).'</a>';
}

That normally is available to contributor-level and above users.

The duplication is handled by the function dt_duplicate_post_as_draft(), which is accessible to anyone logged in because it is registered as an admin_action:

23
add_action( 'admin_action_dt_duplicate_post_as_draft', array(&$this,'dt_duplicate_post_as_draft') );

That function doesn’t perform any checks as to who is making the request, so anyone that is logged in can duplicate any post. Normally only contributor-level and above could then view the resulting post since it is stored as a draft by default. Through that they could gain access to the contents of posts they would normally not have access to, including password protected posts.

We notified the developer of other security issues in the plugin through their website on October 4 and planned to mention this once they responded, but they didn’t respond. We then notified them of this through the email address listed on the plugin’s page on wordpress.org on October 13, as well as mentioning the previous issues again. We have yet to hear back from them and a new version has not been released. In line with our disclosure policy, which is based on the need to provide our customers with information on vulnerabilities on a timely basis, we are now disclosing this vulnerability.

Proof of Concept

Log in to WordPress as a contributor-level user and visiting the following URL, with the value of “[path to WordPress]” replaced with the location of WordPress and  “[post ID]” replaced with the value of a password protected post on the website:

http://[path to WordPress]/wp-admin/admin.php?action=dt_duplicate_post_as_draft&post=[post ID]

You will now be able to view the contents of the post without having to enter a password.

Timeline

  • October 13, 2017 – Developer notified.
19 Sep

Authenticated Information Disclosure Vulnerability in Share Drafts Publicly

The changelog entry for version 1.1.4 of Share Drafts Publicly is “Added security enhancements.”. In looking over that we found a change was made to fix a cross-site request forgery (CSRF) vulnerability that existed with AJAX functionality to share a draft of a post or page publicly. The exploitability of that is limited since an attacker that causes a draft to be shared publicly would still have to guess a 6 character secret key generated using wp_generate_password() to be able to view the draft.

With a CSRF vulnerability you cannot see the result of the request because it is being made by someone else, but the response to the request here does return the secret key needed to view the draft, so there was the potential that WordPress users that don’t have access to a draft could use the functionality to view it since the AJAX request was accessible to anyone logged in to WordPress. In version 1.1.3 we found that anyone logged in could make any draft public. In looking at the changes made in 1.1.4, we found there was no change to deal with that issue.

In version 1.1.4 because of the new CSRF protection, a user would now need to have access to a valid nonce to be able to make a draft public.

The nonce is generated in the function scripts(), which is called when enqueueing admin scripts (in the file /share-drafts-publicly.php):

52
add_action( 'admin_enqueue_scripts', array( $this, 'scripts' ) );

The function will include the nonce when the function enqueue_script() is true:

77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
public function scripts() {
 
	// Localize strings.
	$localization = array(
		'nonce'  => wp_create_nonce( 'share-drafts-publicly' ),
		'postId' => get_the_ID() ? get_the_ID() : ( isset( $_GET['post'] ) ? absint( $_GET['post'] ) : 0 ),
	);
 
	wp_register_script( 'share-drafts-publicly', plugin_dir_url( __FILE__ ) . 'js/share-drafts-publicly.js', array( 'jquery' ), filemtime( plugin_dir_path( __FILE__ ) . 'js/share-drafts-publicly.js' ), true );
	wp_localize_script( 'share-drafts-publicly', 'shareDraftsPublicly', $localization );
 
	if ( $this->enqueue_script() ) {
		wp_enqueue_script( 'share-drafts-publicly' );
	}
 
}

That returns true when on the page /wp-admin/post.php:

120
121
122
123
124
125
126
127
public function enqueue_script() {
 
	// Get current page.
	global $pagenow;
 
	return 'post.php' === $pagenow;
 
}

So as long as a user can visit the page /wp-admin/post.php they would now be able to make any draft public. Without any plugins making something using that page available to lower level users, only users at the Contributor-level or above could get access to the nonce normally.

Less than an hour after we notified the developer of the issue they released version 1.1.5, which fixed the issue by adding the following code to the beginning of the functions make_draft_public() and make_draft_private():

if ( ! current_user_can( 'edit_posts', $post_id ) ) {
	return false;
}

That makes sure the user trying to make a draft public or private is able to edit it.

Proof of Concept

When logged in as a user that has access to some URL that uses /wp-admin/post.php, visiting the following URL will make the specified draft public.

Make sure to replace “[path to WordPress]” with the location of WordPress, “[post ID]” with the ID of the draft post you want to make public, and “[valid nonce] with a valid nonce that can be found on the URL that uses /wp-admin/post.php on the line that begins “var shareDraftsPublicly”.

http://[path to WordPress]/wp-admin/admin-ajax.php?action=share_drafts_publicly&make=public&post_id=[post ID]&nonce=[valid nonce]

Timeline

  • September 18, 2017 – Developer notified.
  • September 18, 2017 – Version 1.1.5 released, which fixes issue.
  • September 18, 2017 – Developer responds.
24 Aug

Authenticated Information Disclosure Vulnerability in Advanced Contact form 7 DB

One of the strengths of WordPress is the multitude of plugins available, if you need some functionality you are likely to find a plugin that provides it. There are downsides as well. With over 51,000 plugins in the Plugin Directory it isn’t surprising to find new plugins that duplicate functionality already provided by another plugin. One of the downsides of that is that we have seen a fair amount of situations where a vulnerability has been fixed in a plugin and then another similar plugin comes along that has that same vulnerability. In the case of a vulnerability we found in the plugin Advanced Contact form 7 DB, we found the same vulnerability we had found in a couple of other similar plugins. The vulnerabilities in the other plugin still haven’t been fixed, while this one has now been fixed, though you wouldn’t know that there was a security fix in the version that fixed it if you relied on the plugin’s changelog.

The plugin entered our radar when a piece of its code showed got flagged as part of our proactive monitoring for serious vulnerabilities in WordPress plugins. The code in question turned out to not be vulnerable, but based on the vulnerabilities we had found in similar plugins, which allowed people that shouldn’t be able to view the contents of contact form submissions, we checked to see if it was also an issue with this plugin and it turned out to be the case.

The plugin makes the function vsz_cf7_edit_form_ajax() available through WordPress’ AJAX functionality to anyone logged in to WordPress (in the file /includes/class-advanced-cf7-db.php):

194
$this->loader->add_action('wp_ajax_vsz_cf7_edit_form_value',$plugin_admin, 'vsz_cf7_edit_form_ajax');

The function vsz_cf7_edit_form_ajax(), in the file /admin/class-advanced-cf7-db-admin.php, will return the contents of a specified contact form submission:

699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
public function vsz_cf7_edit_form_ajax(){
	global $wpdb;
	//Check entry id set or not in current request
	$rid = ((isset($_POST['rid']) && !empty($_POST['rid'])) ? intval($_POST['rid']) : '');
	//If entry not empty 
	if(!empty($rid)){
		//Get entry related all fields information 
		$sql = $wpdb->prepare("SELECT * FROM ".VSZ_CF7_DATA_ENTRY_TABLE_NAME." WHERE `data_id` = %d", $rid);
		$rows = $wpdb->get_results($sql);
		$return = array();
		//Set all fields name in array
		foreach ($rows as $k => $v) {
			$return[$v->name] = html_entity_decode(stripslashes($v->value));
		}
		//All fields encode in JSON format and return in AJAX request
		exit(json_encode($return));
	}
}

The page that requests to that are intended to come from is limited to those with the “manage_options” capability, which would normally be Administrator-level users. The code in the function didn’t perform any check on what level of user is making the request.

Also worth noting is that the code is nearly identical to the vulnerable code in the plugin Contact Form 7 Database, yet there is no mention of the other developer in the copyright section of the plugin.

After we notified the developer of the issue the plugin was changed so that the first thing that happens in the function vsz_cf7_edit_form_ajax() is check to make sure the user has the “manage_option” capability:

700
701
public function vsz_cf7_edit_form_ajax(){
	if(!current_user_can( 'manage_options' )) return;

The changelog entries for the new version, 1.1.1, make no mention of a security fix being included:

  • Made changes to resolve issue of user feasibility when editing the form fields.
  • Minor tweak related to export functionality and attachment download functionality

Proof of Concept

The following proof of concept will show the first saved contact form submissions, when logged in to WordPress.

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="vsz_cf7_edit_form_value" />
<input type="hidden" name="rid" value="1" />
<input type="submit" value="Submit" />
</form>
</body>
</html>

Timeline

  • August 21, 2017 – Developer notified.
  • August 24, 2017 – Version 1.1.1 released, which fixes vulnerability.
09 Aug

Authenticated Information Disclosure Vulnerability in Cherry Team Members

The plugin Cherry Team Members had the same authenticated information disclosure that the Cherry Services List had. The vulnerability was caused by the fact that  contributor and author level users could duplicate posts that they would not have been able to edit. That could for example, have allowed them to gain access to the contents of password protected posts.

The plugin makes the function duplicate_post_as_draft() available to anyone logged in through the admin_action action (in the file /admin/includes/class-cherry-team-admin-columns.php):

41
add_action( 'admin_action_cherry_team_clone_post', array( $this, 'duplicate_post_as_draft' ) );

The only restriction that the plugin placed on accessing that function’s code and duplicating a post as of version 1.4.1 is that the user has the edit_posts capability, which is normally possessed by contributor level and above users:

84
85
86
87
88
function duplicate_post_as_draft() {
 
	if ( ! current_user_can( 'edit_posts' ) ) {
		wp_die( 'You don\'t have permissions to do this' );
	}

After we notified the developer of the plugin of the issue, version 1.4.2 was released, which fixes the vulnerability by checking if the user has the capability to edit the post being duplicated:

102
103
104
if ( ! current_user_can( 'edit_post', $_REQUEST['post'] ) ) {
	wp_die( 'You don\'t have permissions to do this' );
}

Proof of Concept

Log in to WordPress as a contributor-level user and visiting the following URL, with the value of “[path to WordPress]” replaced with the location of WordPress and  “[post ID]” replaced with the value of a password protected post on the website:

http://[path to WordPress]/wp-admin/admin.php?action=cherry_team_clone_post&post=[post ID]

Timeline

  • August 8, 2017 –  Developer notified.
  • August 9. 2017 – Version 1.4.2 released, which fixes vulnerability.
09 Aug

Authenticated Information Disclosure Vulnerability in Cherry Services List

While looking into a possible expansion of what we check during our security review of WordPress plugins  chosen by our customers we found that the plugin Cherry Services List had an authenticated information disclosure vulnerability. That was caused by the fact that contributor and author level users could duplicate posts that they would not have been able to edit. That could for example, have allowed them to gain access to the contents of password protected posts.

The plugin makes the function duplicate_post_as_draft() available to anyone logged in through the admin_action action (in the file /admin/includes/class-cherry-services-meta.php):

41
add_action( 'admin_action_cherry_services_clone_post', array( $this, 'duplicate_post_as_draft' ) );

The only restriction that the plugin placed on accessing that function’s code and duplicating a post as of version 1.4.1 is that the user has the edit_posts capability, which is normally possessed by contributor level and above users:

85
86
87
88
89
function duplicate_post_as_draft() {
 
	if ( ! current_user_can( 'edit_posts' ) ) {
		wp_die( __( 'You don\'t have permissions to do this', 'cherry-services' ) );
	}

After we notified the developer of the plugin of the issue, version 1.4.2 was released, which fixes the vulnerability by checking if the user has the capability to edit the post being duplicated:

104
105
106
if ( ! current_user_can( 'edit_post', $_REQUEST['post'] ) ) {
	wp_die( 'You don\'t have permissions to do this!', 'cherry-services' );
}

Proof of Concept

Log in to WordPress as a contributor-level user and visiting the following URL, with the value of “[path to WordPress]” replaced with the location of WordPress and  “[post ID]” replaced with the value of a password protected post on the website:

http://[path to WordPress]/wp-admin/admin.php?action=cherry_services_clone_post&post=[post ID]

Timeline

  • August 8, 2017 –  Developer notified.
  • August 9. 2017 – Version 1.4.2 released, which fixes vulnerability.
08 Jun

Authenticated Information Disclosure Vulnerability in Contact Form 7 Database

After noticing that another plugin that saves contact form submissions from the plugin Contact Form 7 made them publicly accessible we took a look other plugins that also save them to see if any of them had a similar issue. In doing that we found that the plugin Contact Form 7 Database made saved contact form submissions available to anyone logged in to WordPress.

The plugin makes the function cf7d_edit_value_ajax_func() available to those logged in, through WordPress’ AJAX functionality (in the file /admin/edit-value.php):

74
add_action('wp_ajax_cf7d_edit_value', 'cf7d_edit_value_ajax_func');

That function doesn’t perform any check as to the user role or capability before providing them with the content of a specified saved contact form submission:

75
76
77
78
79
80
81
82
83
84
85
86
function cf7d_edit_value_ajax_func()
{
    global $wpdb;
    $rid = ((isset($_POST['rid'])) ? (int)$_POST['rid'] : '');
    if (!empty($rid)) {
        $sql = $wpdb->prepare("SELECT * FROM ".$wpdb->prefix."cf7_data_entry WHERE `data_id` = %d", $rid);
        $rows = $wpdb->get_results($sql);
        $return = array();
        foreach ($rows as $k => $v) {
            $return[$v->name] = stripslashes($v->value);
        }
        exit(json_encode($return));

By comparison the page where that AJAX requests is made from by the plugin is limited to those with the manage_option capability, which using only Administrator-level users have (in the file /admin/init.php):

20
$menu = add_submenu_page('wpcf7', 'Database', 'Database', 'manage_options', 'cf7-data', 'cf7d_custom_submenu_page_callback');

When we notified the company behind the plugin of the vulnerability over a month ago and they responded “Our developers working to fix it.”, but the vulnerability has yet to be fixed.

Proof of Concept

The following proof of concept will show the first saved contact form submissions, when logged in to WordPress.

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="cf7d_edit_value" />
<input type="hidden" name="rid" value="1" />
<input type="submit" value="Submit" />
</form>
</body>
</html>

Timeline

  • May 3, 2017 – Developer notified.
  • May 3, 2017 – Developer responds.
10 Apr

Vulnerability Details: Authenticated Information Disclosure Vulnerability in Duplicate Post

From time to time vulnerabilities are fixed in plugin without someone putting out a report on the vulnerability and we will put out a post detailing the vulnerability. While putting out the details of the vulnerability increases the chances of it being exploited, it also can help to identify vulnerabilities that haven’t been fully fixed (in some cases not fixed at all) and help to identify additional vulnerabilities in ...


Our Vulnerability Details posts provide the details of vulnerabilities we didn't discover and access to them is limited to customers of our service due to other security companies trying to sponge off the work needed to create those instead of doing their own work.

For existing customers, please log in to your account to view the rest of the post.

If you are not currently a customer, 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 WordPress plugin 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.