18 Jul 2016

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( &$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.

Concerned About The Security of the Plugins You Use?

When you are a paying customer of our service, you can suggest/vote for the WordPress plugins you use to receive a security review from us. You can start using the service for free when you sign up now. We also offer security reviews of WordPress plugins as a separate service.

Leave a Reply

Your email address will not be published.