Every WordPress administrator looking at site statistics becomes quickly aware that one of the most requested URLs is /wp-login.php. No, these are not your own logins into the WordPress backend. And no, these are not friendly visits, either.
Endless login attempts on wp-login.php
The problem with wp-login.php is that there is no security limit in place by default. Attackers can attempt as much logins using unlimited combinations of user and passwords as they want. And don't be fooled that a hacker would actually enter credentials manually – no these are programmed scripts (bots) which automatically use all sorts of login combinations. The login attempts go on and on and on… Until a successful combination was used and the attackers gain access to the WordPress backend. This method is called brute force attack.
Restrict access to known IP address(es)
Probably the easiest way to prevent these attacks is to restrict access to the WordPress login paths (which are /wp-login.php and /wp-admin/). If you are the only person logging into the WordPress backend, the access can be allowed for your own public IP address but denied for all others.
An Nginx configuration (here being used as a reverse proxy in front of the WordPress web server) to achieve this could look like this:
location /wp-admin {
allow 123.123.123.123; # my own public ip
allow 199.199.199.199; # the public ip of my friend
deny all;
include /etc/nginx/proxy.conf;
proxy_pass http://upstream;
}
location = /wp-login.php {
allow 123.123.123.123; # my own public ip
allow 199.199.199.199; # the public ip of my friend
deny all;
include /etc/nginx/proxy.conf;
proxy_pass http://upstream;
}
If your WordPress site is running on an Apache web server, you may also use the .htaccess file to restrict access to wp-login.php:
# IP restriction on WordPress login
<Files wp-login.php>
Order deny,allow
Deny from all
Allow From 123.123.123.123
Allow From 199.199.199.199
</Files>
# BEGIN WordPress
# The directives (lines) between "BEGIN WordPress" and "END WordPress" are
# dynamically generated, and should only be modified via WordPress filters.
# Any changes to the directives between these markers will be overwritten.
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
# END WordPress
But what if your blog needs to be accessed from multiple user which log in from many places and therefore from multiple IP addresses? In this case you should consider a rate limit approach.
Rate limits slow down attacks
A rate limit can help slowing down these automated brute force attacks – yet still allowing user logins at a "normal rate" to continue. There are multiple ways on how to configure a rate limit.
Rate limit in Nginx
If you are using Nginx as web server for WordPress or as a reverse proxy in front of the WordPress web server, you can easily configure rate limits.
The following example creates a connection limit zone named "wordpress". On the login path, the limit zone is applied with a value of "1", meaning 1 request per second (per IP address):
limit_conn_zone $binary_remote_addr zone=wordpress:10m;
server {
[...]
location /wp-admin {
include /etc/nginx/proxy.conf;
proxy_pass http://upstream;
}
location = /wp-login.php {
limit_conn wordpress 1;
include /etc/nginx/proxy.conf;
proxy_pass http://upstream;
}
}
Rate limit using a WordPress plugin
If you have no access to the web server configuration, you may also use WordPress plugins to achieve some rate limit restriction on your blog. I've made quite good experiences with the Limit Login Attempts Reloaded plugin. Once installed and activated, the plugin stops excessive logins from the same IP address and blocks the IP address after several unsuccessful login attempts. The plugin is configurable and the limits can be adjusted (for example how many retries after a failed login is allowed).
The plugin can be configured to automatically send an e-mail whenever a lockout was triggered. In the logs tab, the previous blocked IP's are shown, right next to the username which was used for the login:
Interestingly the attempted logins only happened with real user names, not with some random ones. How did the attackers know about them? Read on.
Hide the real user names
WordPress sites (usually) have an author page, the user names are nicely listed. And each WordPress has an internal author redirect URL, which is the same (by default) on every WordPress installation: /?author=1.
The problem in this case is that this URL (/?author=1) redirects to the profile page of the author with ID 1, which shows the real username in the address bar. In case of an e-mail address as user login, the username is shown without the @-sign and the dot is replaced by a dash. Example: A WordPress username of "[email protected]" would be shown as "meexample-com". Intelligent scripts therefore immediately know the user name to use in a brute force attack.
To "hide" the real user name, another WordPress plugin is a great help: Edit Author Slug by Brandon Allen. It allows to rewrite the URL (slug) for each author, therefore hiding (or renaming) the original login username.
The plugin adds an "Edit Author Slug" section into each WordPress user profile and can be edited in the WordPress Admin Backend:
With this method, the automated brute force attacks are misled and are using non-existant usernames for the login attempts.
Brute force logins on xmlrpc.php
WordPress comes (by default) with a file xmlrpc.php. This file allows to remotely execute tasks in WordPress, given a successful login. This login happens directly on /xmlrpc.php using POST parameters. When the correct login credentials were submitted to xmlrpc.php, the response contains a different output than without a successful login. Basically this means that xmlrpc.php can be compared to wp-login.php as an attacker can use both paths for brute force login attacks.
However only very few third party tools use xmlrpc.php (unfortunately the official WordPress mobile app uses it) and it is not needed by WordPress itself. Additionally to this, the XML-RPC connection, which this file provides, is considered a legacy way for remote task execution and is replaced by the newer WordPress REST API. This means it can safely be disabled or the access to it can be restricted.
In the following example for Nginx, the access to location /xmlrpc.php is simply denied:
location = /xmlrpc.php {
deny all;
}
And for Apache 2.4 and later:
<Files xmlrpc.php>
Require all denied
</Files>
[…] suspicious login activity, which they perceive as an attempted attack. See our related article how to improve security in your WordPress site for more […]