Fighting Brute Force Attack in WordPress

wordpress--brute-force-protection

Brute Force Attack is a daily problem for WordPress sites. What’s interesting is that you cannot prevent it from happening. It’s unavoidable. You can only make harder for the attacker to attack your sites.

If we use CMS with login feature to manage our content, we cannot remove/disable the login functionality because we need it to get access to manage the site.

You can use the strongest password, two factor authentication, etc. But it will not stop the attack to your site.

Every single login attempt will cost you server resource. You cannot cache this page to reduce the impact because WordPress need to validate each login attempt.

They can try to get access to your site, and fail. But they still can make your server collapse.
( or make you pay a lot of money if you use hosting that calculate price by pageviews )

But, we can try to discourage attacker by blocking IP addresses they use. If you use relatively good hosting, you probably have firewall system installed in your server to log and block attacker. But you can also install security plugin to add another layer of security. Several plugin for WordPress brute force protection:

  • Limit Login Attempts : un-maintained plugin, if I’m not mistaken WP Engine auto-activate this plugin for sites hosted there.
  • BruteProtect : Use their server to log IP addresses, kinda like Akismet for brute force attack. You need to register to their site to get API key for each of your site. Currently owned by Automattic.
  • Login Security Solution : similar with limit login attempt, maintained. And have multi-site support. This is the plugin I’m using.
  • And a lot more alternative…

It will reduce their attack, but because they seem to have unlimited number of IP Address, it’s actually (kinda) useless method to try to discourage them.

never give up

Quoting from Matt Mullenweg:

Supposedly this botnet has over 90,000 IP addresses, so an IP limiting or login throttling plugin isn’t going to be great (they could try from a different IP a second for 24 hours).

You can also read other sources to understand the scale of the attack:

Every single day in each sites I got hundreds of failed login attempt. Probably tens of thousands if it’s not protected by firewall and security plugins. It happen in every single site. Not even one site is free from brute force attack.

Several days ago, I ask for advice at Theme Hyrid Forum (private forum replies). I got several response. And from their response I create a custom solution for my sites and my clients sites.

I tested it in 10 different sites for 24 hours, the result is amazing. I got almost zero login attempt.

Even though it’s still premature to say that this solution is working. In this post I would like to share the custom solution I build to solve this problem.

It’s very simple. The idea is to disable wp-login.php and create custom login URL. There’s several plugins to “hide” WordPress login page, but none is suitable for my needs. It’s actually simple to do, but in the same time also complex. The code is not yet available for public, but in this post I would like to walk-through to the concept of this plugin (more like the plugin wire-frame).

I want to polish this plugin so it will be suitable for public use,
it’s going to take a lot of my time to build it. And I’m not sure if anyone interested in using this solution.
So, let me know in the comment if you think you want to use this solution for your site. I’ll make a time to write it and release this plugin to wp.org repository.

Create Custom “Hidden” Login Page.

So I create custom login template accessible by custom URL. For example,

https://shellcreeper.com/?logme=in.

So we need custom URL variable with specific value to get access to the page. So all login purpose only works from this URL.

It’s relatively simple to code:

  1. Register our custom query var using query_vars filter, for example logme.
  2. Decide the value to access login page, in this example the value is in.
  3. Use template_include filter to load our custom template if we load the url.
  4. In this template use wp_login_form() function to generate our login form.
  5. Create extra nonce to make this form unique. This nonce will be used to white list this form from wp-login.php (read section below).

Disable wp-login.php

This is the tricky part. wp-login.php is used as central user authentication. Not only for “login” purpose. WordPress use it for “logout”, “lost password”, “re-authenticate” user if session expired, etc. This is a very busy file.

Even login form created using wp_login_form() simply submit the data to wp-login.php. and wp-login.php will authenticate the request.

We don’t want to change other functionality in wp-login.php we only want to disable it for “login” purpose except if the login request is from our custom (hidden) login page. To do this:

  1. Hook to login_init action to intercept and validate the request.
  2. Verify our custom form nonce, and If the action is login redirect it to our custom login URL.
  3. If the action is to login, but the nonce is invalid, redirect it somewhere else (for example your error/404 page).
  4. Leave other action such as forget password, etc so wp-login.php will work as is.
  5. Add redirect to logout url using  filter if needed. As default it will redirect to wp-login.php, and because we disable it, user will be redirected to the page you set in step (3). Use it so they can be redirected to other pages/url.

Well, that’s it. In this site I even add the custom login URL in my site footer (so it’s not really a secret URL). and during this time period I got zero login attempt.

So I think we can safely say that by simply creating custom login URL and disable wp-login.php will cripple most of brute force attack in WordPress.

But lets push it further.

Dynamic Login URL for WordPress

Even though the solution to change the login URL is working well, but it’s not an ideal solution for protection.

The secret login URL is using “Security through obscurity” method, which is fine (it’s the same protection we do by not sharing our password publicly). But the better security method is “Security by Design“. The system is designed to be secure. (like using two factor authentication in addition to password method).

I don’t think we can 100% protect login form, because we need it to log-in. but we can design the system better,

Creating hidden/secret login URL (without sharing it to public) can also be a problem. If we forgot our secret URL, we cannot login to our own site. And if we have membership site, the login URL need to be a public because we are not the only one who need to login to the site.

Protect WordPress Login URL for Membership site.

The idea to address this issue is to create a dynamic (temporary) login URL. We can add this link in our site (maybe in navigation or sidebar) so we don’t need to remember our login URL.

This link is generated using wp nonce (valid for around 24 hour, as default), combined with user/visitor IP Address, User Agent, etc as unique identifier for each request. This is needed because wp nonce is designed for logged in user, because this URL is not for logged in user we need other method to identified each session.

So if attacker grab this URL, it will only valid for max 24 hour, and only valid for one IP Address.

They cannot abuse this URL to access login page with multiple IPs. So they need to grab different URL for each of their IPs, and there’s also time frame where they need to grab new URL because the URL already expired.

This is best combined with login protection plugin ( track and banned IPs ) to give even extra layer of security.

Of course If it’s not secure enough, we can extend and fine tune this dynamic URL in the future. If ( for example ) we need it to generate unique URL per hour, etc.

The cons for Dynamic Login URL is that we cannot bookmark it or share it (obviously).

Actually we can use this dynamic URL solution without fixed “secret” url as explained above.

In my test displaying the “secret” login URL for the last 24 hour (in my site footer navigation) do not seem to invite any attack (yet), and displaying this dynamic login URL for public is definitely saver than displaying the fixed secret URL.

To create this dynamic URL (it’s very simple):

  1. Generate nonce using wp_create_nonce() function.
  2. Create our own custom nonce using IP Address, User Agent, etc using md5() php function.
  3. Add this nonces to the URL.
  4. If we visit this url, validate the nonces. If valid, display the form,
  5. if not-valid, display error or redirect them to error page.

Let’s Play Hide and Seek.

In this site, and other sites where I implement this solution. If we try to visit wp-login.php , I redirect them to error page where I put the link  to request login url, like this:

request-login-url

And the target of “Lost Login URL?” link is a page with Dynamic Login URL:

dynamic-login-urlAnd the result is the same by adding this URL publicly in the site. almost zero failed login attempt (brute force attack) in all sites.

Other possible improvement is to create user name/email input and send the URL via email after verifying the email (maybe that’s too much, it’s like requesting new password every login), or maybe use captcha or other method to show the URL.

Note: there’s several login attempt happen in two site after I display dynamic login URL, but I think the attack is done manually, because it only happen less than 20 attempt in each site (24 hours test).

Login Notification

I still feel uneasy with all the login protection. Even if I can check the log of how many login attempt, I want to know if someone get access to my account (worst case scenario). So I create additional functionality in this plugin to send me email for every successful login.

There’s several plugins with this functionality, but it’s relatively simple plugin, so I wrote a quick code for it:

  1. Hook to wp_login to check successful login.
  2. Use wp_mail() function to send email with user data and other information you need such as IP Address, etc.

It’s not ideal, since I only create base functionality that works for my needs, but the ideal login email notification plugin would be:

Admin Login Notification:

  1. Send every successful login to admin email.
  2. Option to exclude user roles to track. We don’t need to get notification for all user.
  3. Options to modify admin email notification template.

User Login Notification:

  1. Send every successful login to the user email (who login).
  2. User preference option to opt-in/out this feature via profile edit screen.
  3. Ability for administrator to force enable/disable this feature and enable/disable user preference option.
  4. Options to modify user email notification template.

The only cons with email notification is that you will get email every time you login 🙂
I think it’s annoying sometimes. But I think it’s worth it.

NOTE: My login notification plugin now available at WordPress.org: f(x) Login Notification.

What do you think about this solution? What plugin you use to secure your WordPress site?
If you have suggestion and/or advice to protect WordPress site, share it in the comment.

Update 19 July 2016:

Since BruteProtect merged into Jetpack, I think it’s best to simply use the plugin (So, for my sites I no longer use this method or experiment further.

But I did create login noification plugin, and it’s available on WordPress.org: f(x) Login Notification.

6 Comments

  1. Anand Kumar

    Hello David,

    Nice article but if your wordpress is not community driven or membership site then it will be good to add basic authentication. It will literally stop all brute force attack on WordPress without affecting server resources much. Anything which passes through php will have huge impact on overall server performance.

    If you gonna create a plugin make sure to *teach* your users to cache `wp-login.php` page as it will become 404 and no caching plugin, by default, will cache that missing page. wp-login.php with 404 potentially have more problem than brute force.

    According to my experience, Limit login attempt + 2step authentication is the best solution if I couldn’t add basic auth for any reason.

    However, the idea is brilliant if we cache wp-login.php. Looking forward 🙂

    • David

      thank you anand,
      btw, I don’t plan to cache wp-login,
      I don’t think we should cache it in any way.
      I simply redirect visitor to other pages. (other url)

      I’m not setting wp-login to 404,
      that’s not what I did.

      and I don’t think that anything which passes through php will have huge impact on overall server performance. (database query is not the same as php).

  2. Atinder

    Well, recently Brute force Attacks has immensely increased, becoming a dangerous factor for all WordPress users, but it is a thing, which is fight-able, I mean, by using security methods, we can move brute force attacks out of the window. Although, it can be difficult for newbies, who just got started with WordPress, but he/she can learn by reading posts online and then can implement security.
    In my view, implementing only three tricks works very well, Changing Login Slug, A content Delivery network (CDN) and a Security Plugin, which bans IP address after a few Login attempts.

  3. Jim Walker

    Just curious. I recall you mentioned you were considering writing a plugin. Did you make further headway on that?

    Also, I have a suspicion the plugin, WP Limit Login Attempts, works similarly. Your thoughts in comparison to what you found worked nicely and the plugin above?

    • David

      Hi Jim, I’m sorry, Since BruteProtect merged into Jetpack, I think it’s best to simply use the plugin/module (So, for my sites I no longer use this method or experiment further.
      But I did create login noification plugin, and it’s available at WordPress.org: f(x) Login Notification.

      • Sarah

        Hey David,

        Great tool! We’ve actually decided to fill the gap and produce a new cloud powered brute force protection plugin called BruteGuard. It’s 100% free and works as a protection layer and learns from each person that is using the plugin so than more people are using it than stronger the whole network gets. Let me know if you have any additional questions.

Comments are closed.