The WordPress user enumeration attack
Recently I was contacted by a company that was developing a WordPress web site for a real estate agency but after making the web site public it was immediately hacked. The webmaster did not want to put the website back online until he new why it was compromised. So I was asked to determine how the attacker had gained control over the web site and then to secure it.
The investigation
I downloaded the Apache access log files and loaded them in the HttpLogBrowser and I found several suspicious activities:
Suspicious activity
First, a plugin (ubh) was uploaded from a foreign IP address (Spain) (The real estate agency was located in France). You can see that in the following screenshot (Chronological order is upwards).
Secondly, two themes (maxbusiness and fuence) were uploaded from a Ukrainian IP address
A botnet involved
After the first theme (maxbusiness) was uploaded a few hours later there was much activity on a PHP file added by this theme and the activity came from many IP addresses. You can see that in the following screenshot. 900 IP addresses requested the PHP file /wp-content/themes/maxbusiness/fonts/qfsqjiul.php installed by the uploaded theme with a rate of 50 requests per hour. Among these IP addresses only a few requested the file several times.
When I was doing the investigation the rogue files were already removed so I could not see the code of this PHP file. However according to what the webmaster told me, the web hosting provider cut the hosting because the web site was sending too many E-mails. So it was most probable that the mails were sent by this PHP file controlled by a botnet.
The initial attack
It was also clear that the whole purpose of the attack was to use the web site to send spam E-Mail. However at this stage of the investigation I still didn’t know how the attacker could gain admin access to WordPress in order to upload themes. So I continued to analyze the traffic that came from outside the country and more particularly from Ukraine and I found the following suspicious activity from a second Ukrainian IP address just one hour before the malicious theme was uploaded.
I did not immediately understand the attack but there were requests to the XML-RPC module that is known to be used to brute force passwords. There were also requests to wp-login.php, the login page of WordPress. And there were also some other web requests that were cryptic to me because I was a newbie in WordPress security at that time.
I asked then to the webmaster to put the web site back online and when I ran a security audit with Hacker Target
I saw this in the result: Warning! User Enumeration is possible
That’s something I didn’t know. It’s possible to enumerate users on a default installation of WordPress.
If you take a look again at the screenshot of the attack (see below) you see several requests with author=N (with N=1 to 7) as query parameter. You also see that for author=1 the request is redirected to a URL that contains the name of the admin account (wpadmin). So it’s easy to guess the account used to administer WordPress. The other requests with author > 1 lead to a 404 (not found) error because there was only one account configured.
Weak password
What was worse is that when I got the password of the admin account I saw that it was identical to the account name. The Webmaster did this by thinking that the admin account name could not be guessed by someone outside and this was the mistake.
Trying the account name as password was probably the first thing the attacker did. A brute force attack to guess the password wasn’t even needed!
Securing the web site
Strong password
After understanding that, I immediately changed the admin password with a complex password generated by WordPress itself.
The lesson to learn from this is to never use a password that can be guessed from the account name because the account name is not a secret. Today it’s easy enough to use a complex password stored in a password manager.
Avoid user enumeration
However if needed there is a way to disable the user enumeration in WordPress and this is explained in the following article: Stop User Enumeration in WordPress
One of the two methods proposed by this article is to add the following lines in the .htaccess file located at the root of the WordPress site (If hosted by an Apache web server).
# Block User ID Phishing Requests
<IfModule mod_rewrite.c>
RewriteCond %{QUERY_STRING} ^author=([0-9]*)
RewriteRule .* http://example.com/? [L,R=302]
</IfModule>
Replace http://example.com with the base URL of the WordPress site. The effect will be to automatically redirect any request with “author=?” as query string to the root of the web site instead of the author page. We can see the effect by checking the Apache logs after the modification was done. Take a look at the following screenshot:
But this may not be enough because there is now a new way since WordPress 4.7 to enumerate users through the json API. The json API is only enabled if the Permalinks are not set to Plain in the settings. You can check if you are concerned by requesting the following URL on your WordPress site:
http://your-wordpress-site.com/wp-json/wp/v2/users/
If you are concerned you will get a json file with all authors with published content on the web site as in the following screenshot.
So here is an improved version of the rewrite rule that handles both ways to enumerate users and respond with a 403 status (forbidden). These lines need to be added before the line # BEGIN WordPress
where the WordPress directives start in the .htaccess.
<IfModule mod_rewrite.c>
RewriteCond %{REQUEST_URI} ^/wp-json/wp/v2/users [OR]
RewriteCond %{QUERY_STRING} ^author=([0-9]*)
RewriteRule ^ - [L,R=403]
</IfModule>
Now you get this when you try to get users through the json API:
So the user enumeration can be easily stopped but what is more difficult is that user names may also be displayed by the WordPress theme at different places on the web site and if that’s the case you will need to change the code of the theme possibly at several locations in order to completely hide all account names.
Conclusion
We saw how a WordPress web site could be easily hacked by an attacker because the webmaster used the account name as password. We saw how it was possible to investigate the attack with the Apache access logs and the HttpLogBrowser to analyze them. Then we finally saw how to secure the web site against such an attack.
As last advice, it’s always important to understand how a web site was hacked. Otherwise you cannot learn from your mistakes. The webmaster was maybe young and unexperienced but he had the good reflex to not put the web site back online until the problem was understood and fixed.
jnh said
Fixed some typos in the URL rewrite rule
Simon said
Good analysis and nice how-to for HttpLogBrowser.
Thanks also for the link to HackerTarget.
jnh said
Added mention of the user enumeration with the WordPress json API and added an improved version of rewrite rules