There are at least 3 critical issues that affect most WordPress sites and that we can try to contain by acting on the configuration of the web server: user enumeration, brute force attack and vulnerability on third party plugins (typically SQLi, RCE, Arbitrary/Unrestricted file upload, etc …). Let’s start with order.

1. User enumeration

WordPress allows the visualization of the posts of a specific user, valuing the author parameter with the numeric id of the interested user. So author=1 will show the last posts of the user with id 1, author=2 those of the user with id 2, and so on… Together with the posts of the user, the username is shown so, simply by increasing the value of the parameter author, we can get the list of active users on our WordPress.

You will surely know tools like WPscan that automate this technique, along with the enumeration of themes and plugins installed, the ability to make a brute-force attack, etc. …

No, it is not by blocking the enumeration of users that we will make our site more secure. One of the reasons (the most common but not the only one) why an attacker may be interested in knowing the username of active users on our WordPress, is that it can concentrate the efforts of a brute-force attack on a specific user. It follows that the most effective countermeasure we can adopt is to use a “strong” password, which is not contained in dictionaries or within the site. The configuration I’m about to show you is not a solution… it’s just a way I usually use to avoid enumeration from tools like wpscan.

If you use Nginx you will know that, like Apache, it allows you to work on virtually all parts of the HTTP request of a user-agent. The following configuration returns the status 302 (page temporarily moved) if the author parameter is valued in some way within the querystring:

location / {
if ($arg_author) {
return 302 /;
}
try_files $uri $uri/ /index.php$is_args$args;
}

This means that users who make the request /?author=1 will automatically be redirected to the home page, thus inhibiting the enumeration of users using wpscan (or similar).

 

2. Brute-Force Attack

As you knowo, using a weak password is the main problem during the brute-force attack. One of the countermeasures I usually take is to allow access to the /wp-admin/, /wp-login.php and /xmlrpc.php paths only to my IP (or the client’s IP class / web-agency / etc…). Nginx allows you to do this in this way:

location /wp-admin {
Allow 172.17.0.1;
deny all;

try_files $uri $uri/ /index.php$is_args$args;
}
location ~/(wp\-login\.php|xmlrpc\.php) {
Allow 172.17.0.1;
deny all;

includes snippets/fastcgi-php.conf;
fastcgi_pass unix:/var/run/php5-fpm.sock;
}
When this is not possible, I always try to limit the number of requests per second that a user can make to wp-login.php and xmlrpc.php, so as to exponentially increase the execution time of a brute-force attack:

http {
limit_req_zone $binary_remote_addr zone=bf:10m rate=1r/s;
...
server {
location ~/(wp\-login\.php|xmlrpc\.php) {
limit_req zone=bf burst=5;
includes snippets/fastcgi-php.conf;
fastcgi_pass unix:/var/run/php5-fpm.sock;
}
}
}
The first line creates an area that occupies 10 megabytes of memory, where Nginx maintains a table indexed by source IP that it uses to limit requests to “one per second”. Everything else is queued up to 5 requests, the next ones are rejected. The size of the $binary_remote_addr variable is always 4 bytes for each IPv4 or 16 bytes for each IPv6.

Why do I also invite xmlrpc.php to the party ? Because many brute-force attacks that we block on our Web Application Firewall occur using XMLRPC, especially the attack amplification technique that allows the evaluation of hundreds of username and password combinations with a single request.

3. 403 Forbidden

Under no circumstances, without exception, and in the most absolute way can and must happen that a user can: request content within the folder /wp-includes, request the PHP script /wp-config.php, run PHP scripts within /wp-content/uploads.

In the case of /wp-includes, I would block access to user-agents because it contains classes and functions that are included within the WordPress core and there should be no reason to make the path navigable, especially if you have not disabled the output of PHP errors that could expose system paths. The same goes for /wp-config.php.

location ~ /(wp\-config\.php|wp\-includes.*|xmlrpc.php) {
deny all;
}
As for blocking the execution of PHP scripts within /wp-content/uploads it is advisable to avoid the possibility of exploiting vulnerabilities such as (Arbitraty|Unrestricted) File Upload, by publishing and running “remote-shell” within, for example, http://themiddle.blog/wp-content/uploads/2017/09/22/shell.php

location /wp-content/uploads/ {
types {
image/gif gif;
image/jpeg jpeg jpg;
image/png png;
text/plain txt;
}

default_type application/octet-stream;
location ~ \.php$ {
break;
}
}
The above configuration limits content-types to images and text files. If the file extension is .php the content is served in text-plain and not interpreted.