09 Apr 2013

Blocking WordPress Brute Force Attacks against wp-login.php 

By - Apache 11 Comments

We had a support request recently that indicated a server was under heavy load due to a wordpress wp-login.php brute force login attack. This attack was impacting 3 customer servers from more than 500 different IP addresses. We needed a solution fast.

Others on the internet have reported tens of thousands of unique IPs involved in this attack.

There is currently a significant attack being launched at a large number of WordPress blogs across the Internet. The attacker is brute force attacking the WordPress administrative portals, using the username “admin” and trying thousands of passwords. It appears a botnet is being used to launch the attack and more than tens of thousands of unique IP addresses have been recorded attempting to hack WordPress installs.[1]

mod_security seemed like the perfect fit for this type of problem, and we came across a great blog post: http://www.frameloss.org/2011/07/29/stopping-brute-force-logins-against-wordpress/.

A few minor tweaks were required based on different mod_security versions, this seemed to work unless the attacker is only using a single IP address every few hours:

<IfModule mod_security2.c>
        # This has to be global, cannot exist within a directory or location clause . . .
        SecAction phase:1,nolog,pass,initcol:ip=%{REMOTE_ADDR},initcol:user=%{REMOTE_ADDR}
        <Location /wp-login.php>
                # Setup brute force detection. 

                # React if block flag has been set.
                SecRule user:bf_block "@gt 0" "deny,status:401,log,msg:'ip address blocked for 5 minutes, more than 15 login attempts in 3 minutes.'"

                # Setup Tracking.  On a successful login, a 302 redirect is performed, a 200 indicates login failed.
                SecRule RESPONSE_STATUS "^302" "phase:5,t:none,nolog,pass,setvar:ip.bf_counter=0"
                SecRule RESPONSE_STATUS "^200" "phase:5,chain,t:none,nolog,pass,setvar:ip.bf_counter=+1,deprecatevar:ip.bf_counter=1/180"
                SecRule ip:bf_counter "@gt 15" "t:none,setvar:user.bf_block=1,expirevar:user.bf_block=300,setvar:ip.bf_counter=0"
        </location>
</IfModule>

If you receive complaints about ModSecurity: No action id present within the rule, then you may need a modified ruleset as shown below. cPanel has a post about this change here:

<IfModule mod_security2.c>
        # This has to be global, cannot exist within a directory or location clause . . .
        SecAction phase:1,nolog,pass,initcol:ip=%{REMOTE_ADDR},initcol:user=%{REMOTE_ADDR},id:10011
        <Location /wp-login.php>
                # Setup brute force detection. 

                # React if block flag has been set.
                SecRule user:bf_block "@gt 0" "deny,status:401,log,msg:'ip address blocked for 5 minutes, more than 15 login attempts in 3 minutes.',id:10011"

                # Setup Tracking.  On a successful login, a 302 redirect is performed, a 200 indicates login failed.
                SecRule RESPONSE_STATUS "^302" "phase:5,t:none,nolog,pass,setvar:ip.bf_counter=0,id:10012"
                SecRule RESPONSE_STATUS "^200" "phase:5,chain,t:none,nolog,pass,setvar:ip.bf_counter=+1,deprecatevar:ip.bf_counter=1/180,id:10013"
                SecRule ip:bf_counter "@gt 15" "t:none,setvar:user.bf_block=1,expirevar:user.bf_block=300,setvar:ip.bf_counter=0"
       </location>
</IfModule>

Update 4/11/2013
After seeing a huge surge in search traffic to this page, and reviewing a thread over at webhostingtalk.com, it seems the attacker has adjusted it’s pattern to render this mod_security method ineffective at times.

A few additional options

Patrick on webhostingtalk.com suggested a good solution:

Edit /usr/local/apache/conf/httpd.conf and add the following near the other <Files></Files> lines:

<Files ~ "^wp-login.php">
Order allow,deny
Deny from all

Satisfy All
</Files>
ErrorDocument 403 "Not acceptable"

Restart Apache. It's not a fix, but if your server is going down because of the attacks it's better than nothing. I'm still trying to come up with a better solution than flat out blocking...

Cloudflare has posted a solution using thier DDoS protection services (this is also covered in their free plan).

[1]: http://blog.cloudflare.com/patching-the-internet-fixing-the-wordpress-br

11 Responses to “Blocking WordPress Brute Force Attacks against wp-login.php”

  1. Jacob Nicholson says:

    There is a large scale global brute force attack going on with WordPress sites. We also implemented (mod_security) rules in order to attempt to stop this. Unfortunately this can also leave your WordPress customers without access to their sites, if they for instance hit their WordPress admin dashboard and hit refresh 5 times.

    We recommended also using a (.htaccess) file to limit WordPress login access only to the customer’s IP address. That way they can still access their WordPress site, as IPs attempt to brute force them, and simply get 403 errors with denied access from the (.htaccess) rules.

    Here is an example (.htaccess) rule where 123.123.123.123 would be the customer’s local IP address:

    RewriteEngine on
    RewriteCond %{REQUEST_URI} ^/wp-login\.php(.*)$ [OR]
    RewriteCond %{REQUEST_URI} ^/wp-admin$
    RewriteCond %{REMOTE_ADDR} !^123\.123\.123\.123$
    RewriteRule ^(.*)$ – [R=403,L]

    Hope this information helps someone!

    – Jacob

  2. Alex says:

    I can confirm that we’re getting attacked by this too. We’re up to blocking 50k IPs atm, seems to be a botnet utilizing proxy servers in front to bombard with login attempt.

  3. bosun says:

    please we need help on our client sites. we have like 5 different wordpress site under one domain and the admin area has been blocked. Anytime we try to access the admin area of all the wp sites it gives a white page with the message

    “WordPress administrator area access disabled temporarily due to widespread brute force attacks.”

    Please what can be done to rectify this problem. it;s urgent please

  4. Ralph A says:

    The Problem: The attacks overwhelm the server, specifically mysql. Eventually, if the attack is serious enough, the server can/will become unresponsive, requiring a reboot. The sites will take longer and longer to come up during the attack as well, at times making you wonder if you are using dialup.

    Each time, a login attempt is made, it requires a connection to the database and an attempt to query the login details and match them against the data in the user profile. This is the weakness they are exploiting.
    Below is what needs to be done, manually at this point until either WordPress wakes up and builds it into their install/update code or someone else does.

    When someone, tries….www.mywebsite.com/wp-admin it currently gets redirected to wp-login.php
    What we need is:
    1) wp-login.php gets renamed to a file name selected by the installer and this value stored in wp-config.php for wordpress to use.
    2) wp-login.php gets manual code that pulls up a sequence code that is displayed (without using any mysql access), that the user must enter before they get to be redirected to the file named in the wp-config.pyp as the login file.
    The codes should be randomly generated using php code that allows you to write text to an image, again no mysql is used,
    3) after x attempts, we no longer even allow them to try to enter this code and they are redirected to an html file with instructions as to what to do if they are legit.

    While this is NOT the begin and end all to this problem we are facing, it will piss off these hackers and make their day miserable which is fine in my book.

    If anyone has any improvements to this, PLEASE post your comments…as we need to stick together to come up with a workable solution.

  5. Peter Stolmar says:

    This may not work for everyone, but .htaccess block by HTTP_REFERER – if the request for wp-login does not come from your site, block it. This will stop a lot of the automated attacks without needing to invoke mod_security or your firewall (or worse, a PHP process).

    See this article for detailed next steps including how to do the .htaccess block:

    http://calladeveloper.blogspot.com/2013/04/global-wordpress-brute-force-attacks.html

    Note that this attack has been very successful, compromising a conservatively estimated 10% or more of the accounts attempted. Many accounts are being used to send spam or attack other accounts. The article above includes steps on how to recover from a hacked site as well.

  6. Christopher Ivey says:

    Ideally, WordPress should integrate a good Turing test into their WP-Admin login. If you’re integrating a solution at the hosting end, I want to suggest my own technology, a CAPTCHA replacement called VouchSafe. (vouchsafe.com) It’s free to consume, (unless you want to set up an Enterprise on-premise install) and uses an API almost identical to reCaptcha – except it’s more effective and less annoying.

    Once attackers discover you’re securing hosted sites in this way they will likely move on to softer targets.

  7. Tommy says:

    @Cristopher Ivey The problem with having a solution inside wordpress is that it then have to run php to handle the requests.

    If 500.000 requests per hour hit your site and your php have to take care of it, your server will probably die. To serve 500.000 404 pages is far less server intensive and should be the recommended solution for anyone.

  8. Gloopy says:

    I manage 2 WordPress blogs.

    For the security side, I use .htaccess rules similar to above to restrict access to the admin side only to my local IP. Second, my “admin” user is not called “admin” but something else. Third, I use the “Bad Behavior” plugin which rejects most nuisances.

    But after all this there still are thousands of 403s and 404s each day and that IS a problem. 404s are more expensive resource-wise than 200s afaik, so that remains an issue.

  9. Craig Edmonds says:

    WordPress gives advice on it here: http://codex.wordpress.org/Brute_Force_Attacks

    The best solution that worked for me was to lock down the wp-login.php page with password protections (Password Protect wp-login.php).

    This totally slows down the attacks and lessens the load on your server even if the attack continues because in order to access the login page the robot is presented with server side username and password prompt which is not using php, hence no load on the server.

    The other way is to lock wp-login.php down with fixed ip.

  10. nmservers says:

    Problem with locking wp-login is that if you have subscribers, shoppers or any other kind of site which requires login you will have to publish that to your surfers / clients (and annoy them with double login).

  11. Admin says:

    The fast and easy solution we found was to simply rename wp-login.php, do a global find and replace of the new filename in the file (10 instances) and create a new blank wp-login.php to save the server having to do a 404.

    Works a treat. Of course we will probably have to redo it everytime WordPress gets updated.

Leave a Reply