A ‘brute force’ login attack is a type of attack against a website to gain access to the site by guessing the username and password, over and over again. WordPress is the most popular CMS and therefore it’s a frequent target of these type of attacks. wp-login.php
and xmlrpc.php
pages are the most common target from brute force attack by POST method. WordPress doesn’t have any built in protection to prevent these types of attacks, hence you may need to find some third-party solutions.
Starting with version 5.2.3 of LSWS, LSWS has a built-in WordPress brute force attack protection system. It will protect shared hosting WordPress environments from large-scale DDoS attacks, which may bring down entire servers.
The newly introduced WordPress Protection directive is: WordPressProtect (0|1|5-1000)
10
is default value and feature is enabled by default1000
.This directive can be placed in Apache configuration or .htaccess.
The above values specify the maximum numberwp-login.php
and xmlrpc.php
pages attempts allowed within 5 minutes before the IP is blocked.
This limit is handled using a quota system where limit = quota. Each POST attempt will decrease the quota by 1 with the quota increasing back to the set limit over time. The IP will be throttled starting at half of the limit, slowing more as the quota drops further. When the quota reached 0, the IP is blocked.
As long as LSWS version is 5.2.3 or above, LSWS WordPressProtect feature is enabled by default and does not need any extra configuration in the LSWS WebAdmin GUI or in Apache configurations.
One may want to overwrite it on the server level, virtual host level or even the .htaccess level. What 's the logic behind it?
Setting it on Apache server level configuration will override the setting for Apache based virtual host, but there is no impact on LSWS native virtual host, which can only be controlled by LSWS native settings.
Setting it on Apache virtual host level configuration will override server level configuration as well as .htaccess level of configuration, which means server administrator's virtual host setting will override end user's setting in .htaccess.
Let 's look at some examples for WHM/cpanel EA4 environment:
After you run the following, WordPressProtect feature will be enabled globaly automatically:
/usr/local/lsws/admin/misc/lsup.sh -f -v 5.2.3 (or above version)
You may want to overide the default limit 10
to other value, such as 5
. Then you will need to set it in server level of apache configuration file:
vi /etc/apache2/conf.d/includes/pre_main_global.conf
and add:
<IfModule Litespeed> WordPressProtect 5 </IfModule>
which will set the limit to 5
for all virtual hosts.
You can also disable it globally as:
<IfModule Litespeed> WordPressProtect 0 </IfModule>
No matter how the server level set, end user has ability to enable or disable it through .htaccess by placing the following:
<IfModule Litespeed> WordPressProtect 15 </IfModule>
or
<IfModule Litespeed> WordPressProtect 0 </IfModule>
However, as far as virtual host level setting is set(disabled or enabled), such as for http and domain examle.com, the feature is disabled in virtual host level include file, then directives in .htaccess will not have any effect anymore.
cd /etc/apache2/conf.d/userdata/std/2_4/$USER/example.com/ vi wordpress.conf
<IfModule Litespeed> WordPressProtect 0 </IfModule>
To verify and check how server and virtual host level set, you may run the following command:
cd /etc/apache2/ grep -i -r wordpressprotect *
The design logic looks like the following:
Server Level | VHost Level | .htaccess | Result |
---|---|---|---|
5 | x | x | 5 |
5 | x | 20 | 20 |
5 | 10 | x | 10 |
5 | 10 | 20 | 10 |
This test was conducted with WordPressProtect
set to 10
. We can see the time start to increase at Round 6 and finally get a connection error at Round 11.
Round: 1 Fail 0.626 Round: 2 Fail 0.615 Round: 3 Fail 0.605 Round: 4 Fail 0.581 Round: 5 Fail 0.595 Round: 6 Fail 1.619 Round: 7 Fail 2.615 Round: 8 Fail 3.611 Round: 9 Fail 4.602 Round: 10 Fail 5.604 Round: 11 Erro MSG: ('Connection aborted.', RemoteDisconnected('Remote end closed connection without response',))