How attackers fingerprint your WordPress website

How attackers fingerprint your WordPress website

Attackers have quite a few sneaky ways to gather information from your WordPress website. They can get their hands on details like the WordPress version you're using, the active plugins and their versions, and even info about your active users. In this article, we'll take a deep dive into the technical side of things and uncover how these attackers pull it off. We'll also demystify the popular tool, WPScan, and see what's going on under the hood. Armed with this knowledge, you'll be able to strengthen your WordPress site's defenses and stay one step ahead of these troublemakers. Let's get started!

Is it WordPress?

There are numerous methods to check if a target website is using WordPress, and one of the straightforward ways is through WordPress RSD (Really Simple Discovery).

Really Simple Discovery (RSD) is an XML format and a publishing convention for making services exposed by a blog, or other web software, discoverable by client software.
It is a way to reduce the information required to set up editing/blogging software to three well known elements: username, password, and homepage URL. Any other critical settings should either be defined in the RSD file related to the website, or discoverable using the information provided.

Wikipedia

To obtain the RSD link and information from a WordPress website, you can start by visiting the target website's homepage and view the page source code. Look for the following tag:

<link rel="EditURI" type="application/rsd+xml" title="RSD" href="http://example.com/xmlrpc.php?rsd" />

The "href" attribute contains the RSD link, pointing to the XML-RPC endpoint on the website. Open the RSD link (e.g., http://example.com/xmlrpc.php?rsd) in your web browser. This will lead you to the RSD document, which is an XML file containing information about the website's API and services available for external applications. It will looks like this:

$ curl -s 'https://example.com/xmlrpc.php?rsd'
<?xml version="1.0" encoding="UTF-8"?><rsd version="1.0" xmlns="http://archipelago.phrasewise.com/rsd">
	<service>
		<engineName>WordPress</engineName>
		<engineLink>https://wordpress.org/</engineLink>
		<homePageLink>https://example.com</homePageLink>
		<apis>
			<api name="WordPress" blogID="1" preferred="true" apiLink="https://example.com/xmlrpc.php" />
			<api name="Movable Type" blogID="1" preferred="false" apiLink="https://example.com/xmlrpc.php" />
			<api name="MetaWeblog" blogID="1" preferred="false" apiLink="https://example.com/xmlrpc.php" />
			<api name="Blogger" blogID="1" preferred="false" apiLink="https://example.com/xmlrpc.php" />
			<api name="WP-API" blogID="1" preferred="false" apiLink="https://example.com/wp-json/" />
		</apis>
	</service>
</rsd>


WordPress Version

First, let's start with the WordPress version. Figuring out which version of WordPress a website is running isn't too tricky. It turns out, WordPress loves to broadcast its version number all over the place. You can find it in various locations throughout the site.

#1 Generator Tags

One of the primary places where the WordPress version proudly declares itself is through the use of generator tags in the response body of the website. These tags are snippets of code embedded in the HTML source code of the web page and are intended to provide specific information about the website.

In the above examples, you can see two different generator tags. The first one reveals that the website is using WordPress version 5.8.2, while the second one indicates that WooCommerce version 5.9.1 is also in use. These tags are part of the default behavior of WordPress and certain plugins, like WooCommerce, and they are often present in the HTML code generated by the system.

#2 RSS Feed

The RSS feed, often accessible at the URL "/feed/", is another place where attackers can find the WordPress version of a website. The RSS feed is an XML-based format that allows users and applications to subscribe to updates from a website, such as blog posts or news articles.

Inside the XML response body of the RSS feed, you'll typically find a line like this:

This line reveals the WordPress version, which, in this case, is 5.8.2. Just like the generator tags in the HTML.

#3 wp-emoji-release.min.js

The WordPress Emoji JavaScript file, often named "wp-emoji-release.min.js", is used to handle emojis on WordPress websites. If you're a time traveler from 1980, and emojis are a mystery to you, Emojis are those fun little smiley faces and icons that add some visual flair to our content.

Now, you might wonder, what does this have to do with revealing the WordPress version? Well, in the URL of the "wp-emoji-release.min.js" file, you'll often find a query parameter like "ver=<version_number>". Here's an example:

#4 Guessing version via MD5 checksum

During my investigation into how WPScan guesses the WordPress version when it's not explicitly mentioned in the previously discussed places, I stumbled upon a fascinating technique. It involves using MD5 checksum of static contents, such as CSS, JavaScript, and images.

If you take a look at the .wpscan/db/ directory, you'll find a file named wp_fingerprints.json that contains something like this:

  $ cat .wpscan/db/wp_fingerprints.json | jq -r .
  ...
  "wp-admin/css/common.min.css": {
    "b1ccd63fd1eb7b7c83e0c18f4a41631e": [
      "4.8.15",
      "4.8.14",
      "4.8.13",
      "4.8.12",
      "4.8.11",
      "4.8.10",
      "4.8.9",
      "4.8.8",
      "4.8.7",
      "4.8.6",
      "4.8.5",
      "4.8.4",
      "4.8.3",
      "4.8.2",
      "4.8.1",
      "4.8"
    ],
    "19783c0c8c8225b3fc31ea04e77ee50a": [
      "4.7.19",
      "4.7.18",
      "4.7.17",
...


Imagine that WordPress has a "style.css" file that undergoes changes from one version to another. If you calculate the MD5 checksum of the content for each version of "style.css," you can compare the MD5 checksum of a target WordPress website with your list of MD5 checksums. This way, you can make an educated guess about which version the target website is related to based on the matched MD5 checksum.

Theme name and version

Getting hold of the WordPress theme name and version used on a website is surprisingly straightforward! All you need to do is fetch the response body of the website's homepage and search for /wp-content/themes/<theme-slug>/.

Once you've collected the theme slug, you can make a request for the "style.css" file at /wp-content/themes/<slug>/style.css. The magic lies within this file because it always contains header comments that hold crucial information. Among these comments, you will find the coveted "Theme Name: ..." and "Version: ..." details, unveiling the theme's identity and version number. For example:

$ curl -s 'https://<target>/wp-content/themes/hello-elementor/style.css' | head -20
/*
	Theme Name: Hello Elementor
	Theme URI: https://elementor.com/hello-theme/?utm_source=wp-themes&utm_campaign=theme-uri&utm_medium=wp-dash
	Description: A lightweight and minimalist WordPress theme for Elementor page builder.
	Author: Elementor Team
	Author URI: https://elementor.com/?utm_source=wp-themes&utm_campaign=author-uri&utm_medium=wp-dash
	Version: 2.8.1
	Stable tag: 2.8.1
	Requires at least: 5.9
	Tested up to: 6.2
	Requires PHP: 7.0
	License: GNU General Public License v3 or later.
	License URI: https://www.gnu.org/licenses/gpl-3.0.html
	Text Domain: hello-elementor
	Description: A lightweight, plain-vanilla, best suited for building your site using Elementor website builder. Visit https://elementor.com/hello-theme/ to learn more.
	Tags: accessibility-ready, flexible-header, custom-colors, custom-menu, custom-logo, featured-images, rtl-language-support, threaded-comments, translation-ready,
*/
/**
 * DO NOT CHANGE THIS FILE!
 * To override any of the settings in this section, add your styling code in the custom directory.

Plugins and versions

Finding the active plugins and their versions on a WordPress website isn't as straightforward as discovering the theme. But fear not, there are two primary approaches that can get the job done!

The first method involves fetching the response body of the website's homepage and searching for /wp-content/plugins/<slug>/. Much like we did with the theme, we then make a request for the readme.txt file at /wp-content/plugins/<slug>/readme.txt. This file contains vital header information, including the coveted version under "Stable tag: ...". Voilà! With this approach, we can unveil the versions of active plugins.

A real example identifying Akismet plugin version:

$ curl -s 'https://jquery.com/wp-content/plugins/akismet/readme.txt' | grep 'Stable'
Stable tag: 3.1.10

Now, the second approach takes a different route. We leverage the WordPress.org API to obtain a list of the most popular plugins. By making a request to "https://api.wordpress.org/plugins/info/1.2/?action=query_plugins&request[page]=1&browser=popular&request[per_page]=100", we can retrieve the top 100 popular plugins and their slugs. Armed with this list, we embark on a "bruteforce" mission, attempting to fetch each plugin's readme.txt file at /wp-content/plugins/<slug>/readme.txt. Lo and behold, among the header information, we'll find the versions of these plugins.

We can create an ugly Python script for doing the job:

import requests, re

# collect popular plugins from wordpress.org API
res = requests.get('https://api.wordpress.org/plugins/info/1.2/?action=query_plugins&request[page]=1&browser=popular&request[per_page]=100')

# json decode the response body
top_plugins = json.loads(res.text)

# loop through all plugin slugs
if "plugins" in top_plugins:
    for plugin in top_plugins["plugins"]:
        slug = plugin["slug"]
        
        # try to get the readme.txt file
        res = requests.get(
        	f'https://<target/wp-content/plugins/{slug}/readme.txt'
        )
        
        if res.status_code == 200:
        	# if readme exists, get the version from response
            m = re.search(r"Stable tag:\s*(.*)", res.text)
            if m:
				print(f"plugin:{slug} version:{m.group(1)}")

Plugin name and version in comments

Another technique to identify active plugins and their versions involves reading the response body of a website and examining HTML comments left by plugins. Many plugins not only expose their names but also proudly declare their respective versions through these comments.

An example by randomly browsing WordPress websites:

  • The Yoast SEO plugin confidently announces: <!-- This site is optimized with the Yoast SEO plugin v16.7 - https://yoast.com/wordpress/plugins/seo/ -->
  • The Cookie Notice plugin makes its presence known: <!-- Cookie Notice plugin v2.1.0 by Hu-manity.co https://hu-manity.co/ -->
  • The Catch Web Tools plugin showcases its capabilities: <!-- This site is optimized with the Catch Web Tools v2.7.3 - https://catchplugins.com/plugins/catch-web-tools/ -->
  • The All in One SEO plugin leaves its mark: <!-- All in One SEO 4.4.2 - aioseo.com -->

By inspecting these HTML comments within the response body, we can extract valuable information about active plugins and their respective versions. While these comments serve legitimate purposes for website owners and users, they can also inadvertently provide potential attackers with insights into the website's plugin ecosystem.

Plugin Name in wp-json API

Another avenue for obtaining a list of active plugins involves accessing the list of REST routes provided by the "wp-json" API.

The "wp-json" is a powerful API endpoint in WordPress that offers access to various site data and functionalities. It follows the principles of the REST (Representational State Transfer) architecture, providing a structured way to interact with the website's content and resources programmatically.

Within "wp-json," REST routes are the endpoints through which external applications can query and manipulate site data. These routes correspond to specific functionalities or resources of the WordPress installation, allowing developers to retrieve posts, pages, users, and more.

Many plugins leverage the "wp-json" API to expose their own functionalities and data. By doing so, these plugins create additional REST routes that can be accessed by external applications. These routes offer a standardized way to interact with plugin-specific features, retrieve information, and even perform actions.

To get a list of routes from wp-json, you can just request /wp-json and get the content "routes" JSON response:

$ curl -s 'https://example.com/wp-json' | jq -r '.routes | keys[]'

/
/akismet/v1
/akismet/v1/alert
/akismet/v1/key
/akismet/v1/settings
/akismet/v1/stats
/akismet/v1/stats/(?P<interval>[\w+])
/batch/v1
/oembed/1.0
/oembed/1.0/embed
/oembed/1.0/proxy
/redirection/v1
/redirection/v1/404
/redirection/v1/bulk/404/(?P<bulk>delete)
/redirection/v1/bulk/group/(?P<bulk>delete|enable|disable)
/redirection/v1/bulk/log/(?P<bulk>delete)
/redirection/v1/bulk/redirect/(?P<bulk>delete|enable|disable|reset)
/redirection/v1/export/(?P<module>1|2|3|all)/(?P<format>csv|apache|nginx|json)
...

By grep out all WordPress routes and extract the first part of the route path we can get a list of plugin slugs:

$ curl -s 'https://example.com/wp-json' | jq -r '.routes | keys[]' | \
     egrep -v '^(/wp/v[0-9]|/oembed/|/$)' | \
     egrep -o '^/[^/]+' | \
     sort | \
     uniq
     
/batch
/contact-form-7
/elementor
/mc4wp
/otgs
/tribe
/trx_addons
/wp-block-editor
/wpml
/wp-site-health
/yoast

Sometimes developers expose an endpoint with all information about the plugin, including the running version. For example, the wordpress-real-media-library plugin exposes a routes at /real-media-library/v1/plugin:

$ curl -s 'https://example.com/wp-json/real-media-library/v1/plugin' | jq
{
  "Plugin Slug": "",
  "Name": "Real Media Library",
  "PluginURI": "https://devowl.io/wordpress-real-media-library/",
  "Version": "4.20.4",
  "Description": "Organize uploaded media in folders, collections and galleries: A file manager for WordPress. Media management made easy!",
  "Author": "devowl.io",
  "AuthorURI": "https://devowl.io",
  "TextDomain": "real-media-library",
  "DomainPath": "/languages",
  "Network": false,
  "RequiresWP": "",
  "RequiresPHP": "",
  "UpdateURI": "",
  "Title": "Real Media Library",
  "AuthorName": "devowl.io"
}

Active users

When attackers gain access to a list of active users on a WordPress website, one common exploitation method they may employ is a brute force attack.

Brute force attacks involve automated tools that systematically try various username and password combinations until they discover the correct credentials for a user account. Having a list of active users allows attackers to focus their efforts on accounts with potential privileges or administrative roles.

When it comes to finding active users on a WordPress website, there are two popular approaches to choose from.

The first and more widely known method involves making a request to /?author=1. By analyzing the response body, you can collect the name of the author, which often (though not always) corresponds to their username. By incrementing the number after author=, you can systematically enumerate all active users present on the website. This approach has been used extensively to gather information about users associated with published content.

Now, the second approach takes advantage of the wp-json API. By making a request to /wp-json/wp/v2/users/1, you can obtain the user's slug, which frequently aligns with their username. Similar to the first method, you can increment the user ID in the URL to enumerate all active users accessible through the API. This API-based approach offers an alternative way to fetch user details and is increasingly popular due to its structured data format and compatibility with modern WordPress installations.

There's yet another less-popular way to guess user slugs, and it involves searching for the <origin>/author/<slug> link within the response body of a WordPress website. WordPress conveniently provides this link, which allows you to access all the content authored by a specific user.

When you locate the <origin>/author/<slug> link, you can extract the user's slug from it. This slug often corresponds to their username or a unique identifier associated with the user. By identifying and enumerating these user-specific links, you can gain insights into the users and the content they have created or contributed to on the website.


In conclusion

Understanding how attackers test and fingerprint WordPress websites is crucial for maintaining a robust defense against potential threats. Throughout this article, we explored the various methods attackers use to collect sensitive information from WordPress sites, such as identifying the WordPress version, active plugins, and active users.

We learned that attackers can easily uncover the WordPress version by examining generator tags in the response body, analyzing the RSS feed, or even through the WordPress Emoji JavaScript file. Similarly, they can deduce the active plugins and their versions by exploring the "readme.txt" files, leveraging the WordPress.org API, or searching for specific links within the response body.

Furthermore, we discovered how attackers benefit from obtaining lists of active users, with brute force attacks being a common exploitation method. Armed with user information, attackers can focus their efforts on cracking weak passwords and gaining unauthorized access to accounts with higher privileges.

As website owners, it is of utmost importance to prioritize security best practices. Regularly updating WordPress, themes, and plugins, using strong passwords, implementing multi-factor authentication, and employing security plugins can go a long way in safeguarding our websites from potential attacks.

By staying vigilant and understanding the tactics used by attackers, we can take proactive measures to protect our WordPress websites, ensuring a safer online environment for both ourselves and our users. Together, we can fortify our websites against emerging threats and continue to enjoy the benefits of a secure and thriving online presence.

Keep your guards up with PWNpress

Our mission is to make the internet a safer place by keeping your website secure. We offer a unique platform that scans the entire internet, searching for vulnerabilities and misconfigurations, to give you data and insightful statistics that help you stay informed about the latest security trends. https://pwnpress.io