--- title: Enumerating Users on WordPress description: | Finding valid usernames can significantly improve your chances of breaking into a WordPress account. In this blog post I cover some of the methods I use to find valid users and how you can protect your own site against them. date: 2020-09-05 tags: - Security Advice - Websites --- WordPress is an extremely popular Content Management System (CMS) and as a result receives a lot of interest from hackers. WordPress has a bad reputation in some circles for being insecure, however if you are selective about the themes / plugins you install and keep it up to date, it is my belief that it is a nice system for both developers and users. However, assuming you keep everything up to date, in many cases the biggest security weakness is your credentials. If a malicious actor guesses your username and password, it doesn't matter how recently you did your updates, they are probably going to get in. When I am tasked with testing the security of a WordPress site, one of the first things I do is attempt to find usernames. In this blog post, I document some of the ways I do that. :::note For the purposes of this blog post, I have created a local WordPress site I can use for testing. Do not attempt these tactics unless you own the site you are testing or have explicit permission from the site owner to do so. ::: ## Trial and error ![Login form showing valid user](../../assets/wordpress/login-username-enumeration.png) The most simple way is to attempt to login in with common usernames, `admin` being one of the most common. You can see from the screenshots above that if you enter a correct username, the error message tells you that you have the password wrong; if you enter an incorrect username, the message tells you that there is an unknown username. This makes it trivial to tell if a username exists: try it and if you get the "incorrect password" error, you know you have a valid username. To fix this, you simply need to make wordpress return generic error messages: ```php /author/admin/`. However, you can also access them by manually specifying the users ID. For example `/?author=1` will redirect the visitor to `/author/admin/`, this gives the attacker an easy way to get usernames. ```bash for i in {1..5}; do curl -s -o /dev/null -w "%{redirect_url}\n" "example-wordpress.local/?author=$i" done ``` In a default WordPress installation, you will get something like this: ``` http://example-wordpress.local/author/admin/ http://example-wordpress.local/author/user1/ http://example-wordpress.local/author/user2/ http://example-wordpress.local/author/user3/ ``` We have just found 3 new usernames. One solution to this is to simply prevent WordPress queries from being able to look up a user by ID. ```php set_404(); status_header(404); nocache_headers(); $template = get_404_template(); if ($template && file_exists($template)) { include($template); } exit; } return $query_vars; } add_action('request', 'do_404_author_query'); ``` ## Rest API Out of the box, WordPress gives out a lot of information out about its users through the Rest API. I can pull out a list of all usernames with the following: ```bash curl -s example-wordpress.local/wp-json/wp/v2/users | jq '.[].name' ``` The easiest way to mitigate this is simply to remove the user endpoints. ```php function remove_users_endpoints( $endpoints ) { return array_filter( $endpoints, function($endpoint){ return (0 === preg_match( '/^\/wp\/v2\/users/', $endpoint )); } , ARRAY_FILTER_USE_KEY); } add_filter( 'rest_endpoints', 'remove_users_endpoints' ); ``` ## oEmbed Oembed is a protocol that allows websites to embed content from other sites. WordPress supports both embedding and being embedded. When a site requests to embed a page, it makes a request that looks like the following: `/wp-json/oembed/1.0/embed?url=http%3A%2F%2F/a-post-by-user-3/`. The WordPress server then returns something like this: ```json { "version": "1.0", "provider_name": "Example Wordpress", "provider_url": "http://example-wordpress.local", "author_name": "user3", "author_url": "http://example-wordpress.local/author/user3/", "title": "a post by user 3", "type": "rich", "width": 600, "height": 338, "html": "" } ``` This includes the author's name and a url for the author archive. In order to prevent wordpress from including this information in the oembed response, add the following to a plugin or to your themes functions.php file: ```php , I could do the following: ```bash $ curl http://example-wordpress.local/a-post-by-user-2/ | hq '.post-author a' attr href' http://example-wordpress.local/author/user2/ ``` Here we see the author is `user2`. However, sites often have hundreds or thousands of pages so doing this manually would be tedious. Once again, we can turn to the rest api. The following will go through the first 100 posts on the site and attempt to get the author's link from it. ```bash $ curl http://example-wordpress.local/wp-json/wp/v2/posts?per_page=100 | jq ‘.[].id’ | while read i; do curl -L …/?p=$i | hq '.post-author a' attr href done ``` It is worth noting here that the wordpress rest api limits requests to 100 results per request. This means that if a site has more posts / pages than that, you might need to use the `page=n` parameter which will give you the nth page of results. I don't normally find this necessary since it is normally the earliest pages that are created by high privileged accounts. The easiest way to rectify this is simply not to include the author's details in the page, although instructions on how to do this will vary depending on the theme in use. However, you may wish to be able to group blog posts by author. In this case, I would suggest only publishing content using low privileged accounts (author or contributor). This will mean that in the event that one of these accounts is compromised, the damage an attacker can do is limited. --- These steps should prevent most attempts at user enumeration although remember that security is not a plugin or a few lines of code copied from a blog on the internet. There are many layers that should be implemented and this is but one. For details on how best to secure your website, you should consult with an expert.