Understanding WordPress Mass Hacking

Disclaimer: the information contained in this post is purely for informational purposes. Do not use them to harm websites or for any malicious purpose.

What's the first rule in WordPress security? Easy: keep the core, plugins, and themes updated. Simple to say, more complex to achieve as we have to deal with bugs and compatibility on each update, especially if the site in question has lots of plugins.

In this article, we will demystify the "WordPress mass hacking process" and go through a real hacking scenario, to understand how the pain of keeping everything updated is worth it and learn how to better protect our WordPress sites.

If you never heard of WordPress massive hacking episodes, you can take a look at this recent story to have an idea.

That said, let's wear a black-hat and join the dark army of the internet for a while :)

Target a vulnerability as a hacker

WordPress massive attacks never target a specific website, they usually target a specific vulnerability and try to hack as many websites as they can.

For this reason, hackers will go after vulnerabilities found in popular themes or plugins to have a wider attack surface area. They can research the vulnerability themselves or even target existing bugs because as we know, users are slow to update sites and even old bugs can be largely exploitable. WordPress vulnerabilities can be found publicly all around the web, and this is one of the best sources: https://wpvulndb.com/.

Of course, vulnerabilities are not all the same. Different bugs will allow hackers to run different types of attacks with different purposes.

For example:

  • a remote code execution (like this) may allow hackers to encrypt the database and blackmail the website owner
  • an XSS (like this) would allow stealing users sensitive data
  • and so on...

The list is very long, and we will leave it (maybe) to another post, but for now, just keep in mind that the popularity of the vulnerable code and the type of bug are the main factors to determine the vulnerability to exploit.

For our "hands-on" lab, we will use a retired plugin that I already verified has not more in use for several years, to avoid spreading the word about vulnerabilities actively exploitable. The plugin is # WP Whois Domain < 1.0.0 which suffered from an XSS vulnerability.

The XSS resides in the func-whois.php file, which prints out an unescaped $domain variable, set earlier in the file as $domain = $_REQUEST['domain'];.

<form action="http://<?php echo $_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']; ?>" method="post">
  <div class="domain-example"><label for="domain-exam">eg: www.example.com</div>
  <div class="input-wrapper">
    <div class="enter-doamin-label"><label for="domain">Enter Domain/IP Address:</label></div>
    <div class="input-inner">
    <input type="text" name="domain" id="domain" value="<?=$domain;?>">
    <input type="submit" value="whois">

Identify the targets/fingerprinting

Identifying the websites containing the vulnerability is, of course, a critical part of a WordPress mass hacking attack, as it defines the attack surface area. Multiple sources are usually involved in this operation, but the most important ones are:

  • source code search
  • Google dork queries
  • scraping

The source code search is the preferred method because is more reliable and often gives useful information about the plugin version in use, so hackers can save time and effort on additional fingerprinting work. Code search is possible because plugins, themes, and core often output specific CSS/JS files, mark-up, and HTML comments, so basically they make themselves identifiable.

As an example, Yoast SEO adds an HTML comment like this: This site is optimized with the Yoast SEO plugin vx.x.x, so a hacker can detect that the site has Yoast and also the specific version. Another example can be Gravity Forms, which adds this JS file /gravityforms/js/placeholders.jquery.min.js?ver= which can be used to retrieve a list of sites that use the software and the software's version.

When the version is not immediately disclosed, it can be almost every time found in the plugin/theme readme or changelog. Of course, all this work gets automated in scripts to run it at a scale.

A Google dork query, instead, is a special Google search that leverages specific search operators to identify specific results. In our case, we know that the plugin outputs a form containing the text string Enter Domain/IP Address:, so we can use exact match operators and Google this query: "Enter Domain/IP Address:". In this case, it's important to use a query that is specific for the plugin, to avoid false positives.

The outcome of these operations is a simple text file containing all the website's URLs, where the hackers know there is vulnerable code running and they can perform an attack. The scraping comes in place when the hackers need additional information to attack (emails usually), so they search (and usually find) that on the website itself.

Write and run the exploit

Once the attackers have defined the targets and decided "it can be profitable", it's time to write the exploit. Again, this operation is strongly related to the specific vulnerability and requires good code skills in a scripting language like Python, Ruby, Bash, Perl, etc...

Let's try to understand it using an easily exploitable bug: the famous layer slider LFI (local file inclusion) vulnerability. It was a bug that allowed anyone to steal database credentials stored in wp-config, by simply making a GET request to http://site-name.com/wp-admin/admin-ajax.php?action=revslider_show_image&img=../wp-config.php.

So, assuming that an initial attack can be only intended to steal and store sensitive credentials (to use later in further attacks), a Bash exploit could be as simple as:



for site in $(cat vuln_sites.txt); do
  wget --output-document $(echo $site | cut -d'/' -f3)$ext $site$path

Where vuln_sites.txt is the list of targets previously collected. The code is a simple script that downloads the content of /wp-config.php, where we all know the most important data for the WordPress install resides.

Back to our lab (XSS in WP Whois Domain < 1.0.0), the exploit would be much more complex than this, and the hackers could achieve a big variety of damages. But the basic concept is that the attacker can trick the victim to unknowingly execute malicious JavaScript code with a click of a link.

So, most probably the attack would start with a phishing email (like a fake WP password reset, or a comment notification), and end up with some malicious JS executed. If the victim is authenticated, the JS can hurt hard, and do things like:

  • steal cookies
  • steal password
  • publish/delete content on the user's behalf
  • cross-exploit other vulnerabilities

The attacks are usually run from different locations and IPs, to try to bypass firewalls, blacklists, and other protection layers.

How to protect yourself

Of course, as we wrote at the beginning of the article, keeping your theme, plugins, and WordPress core updated is always a must-have first line of defense. The WP ecosystem is very active, and usually, vulnerabilities are patched quickly and patches are released in no time.

On top of that, there is a whole series of "well-known" hardening you can put in place, which will protect your website from lots of common attacks. I link here a couple of very comprehensive guides by Kinsta and Sucuri.

That said, what I recommend against these specific massive attacks, is having two specific additional security systems in place:

  • an external firewall
  • on-site precautions to prevent information disclosure

Information disclosure prevention can look like a dumb thing, but as we've seen, massive attacks rely on information collected by scanning websites. If they are not able to collect any useful information, the site will probably be excluded from the target list.

I've seen scanning tools failing on targets with measures as simple as blocking direct access to txt,md files on the server.

So, if you are on Apache, you can simply add some directives in .htaccess:

<Files ~ "(.txt|.md)">
Order allow,deny
Deny from all

Or on NGINX, you can add a directive live:

location ~* \.(txt|md)$|/\. {
    deny all;

Another example can be concatenating CSS/JS in a single file, which prevents plugin enumeration by assets fingerprinting. Removing the script version is another good tip to limit the information disclosed by your website's HTML code.

Even more important is an external firewall. With external I mean that the firewall resides on another server that processes the HTTP requests before they touch the WP instance.

It is an effective security measure because many attacks against known vulnerabilities trigger firewall rules which recognize them by inspecting the HTTP request, and drop it even before it touches your site. Common payloads included in the URL, will also trigger firewall rules and be stopped at an early stage.

This is a firewall log (from Cloudflare) of a blocked HTTP request trying to exploit a LFI bug in WordPress:

  "action": "drop",
  "clientIP": "nginx.conf",
  "clientRequestHTTPHost": "redacted",
  "clientRequestHTTPMethodName": "GET",
  "clientRequestHTTPProtocol": "HTTP/1.1",
  "clientRequestPath": "/wp-content/plugins/wp-post-modal/public/includes/proxy.php",
  "clientRequestQuery": "?url=../../../../../wp-config.php",
  "datetime": "2020-05-01T12:45:42Z",
  "source": "waf",
  "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36",
  "matchIndex": 0,
  "sampleInterval": 1

With a firewall in place, we can also rate-limit the HTTP requests by IP. Website scanners have to do many requests (5-10K or more) to fingerprint the site, while a regular/legit user on a defined time interval does way fewer requests (hundreds). So, rate-limiting rules can be a powerful additional security layer.

Thanks for reading!