WordPress login rate limiting (again)

We’ve talked before about WordPress login rate limiting. Attempts to guess WordPress administrator passwords are an ongoing problem, getting worse all the time.

The average WordPress site we host has received tens of thousands of malicious login attempts this month, with hundreds of thousands of different IP addresses being used in the attacks. We try to block the IP addresses that are responsible, but the ever increasing number of addresses means we can’t block all of them — an individual address often attempts a login only once a day for a given site. We need to adopt other tactics.

So we now track other information about visitors to see if they’re legitimate when they attempt to login. In particular, we make sure that visitors have visited the WordPress login page on a site before they send a login “POST” request with a username and password. If they haven’t, we redirect them back to the login page.

This rule blocks the vast majority of fake login attempts, and it should cause no problems for legitimate logins: in the worst case, the login screen might be redisplayed to a human visitor. Let us know if you have any problems with that happening.

You can do your part, too. Never use a password that appears in any dictionary, and never choose an “obvious” password related to your site. In the last month, we’ve seen two WordPress sites hacked because the owners used their real name (visible as the “author” of each post on their site), or the site name, as their password. The automated software that hackers use can “scrape” words like that from pages and submit them as an attempted password. Always use something unrelated.


  1. Cool!

    I’m guessing it’s a mod_security rule? Can you share it for the benefit of the community?

  2. Jean-Francois wrote:

    >I’m guessing it’s a mod_security rule?

    Yes, although it’s unfortunately tangled up in our existing wp-login.php rate-limiting rules and is therefore “non-obvious”. But the idea is simple: set a flag variable on GET requests to wp-login.php, using something like this:

    SecRule REQUEST_LINE "^get .*/wp-login\.php" "setvar:ip.wp-login-get=1,expirevar:ip.wp-login-get=1800"

    And then check POST requests to wp-login.php to see if the Referer ends in “/wp-login.php” but the flag is not set:

    SecRule REQUEST_LINE "^post .*/wp-login\.php" "chain"
    SecRule REQUEST_HEADERS:Referer "/wp-login\.php$" "chain"
    SecRule &IP:WP-LOGIN-GET "@eq 0" "setvar:tx.wp-login-post-without-get=1"
    SecRule TX:WP-LOGIN-POST-WITHOUT-GET "@eq 1" "redirect:%{REQUEST_HEADERS.referer}?force_get_before_post=1,status:303,sanitiseArg:pwd"

    If the flag is not set, the 303 redirect will force a GET redirect to wp-login.php so the user can re-enter their password.

    The extra “force_get_before_post=1” query string has two purposes: it lets you track it in your logs, and any subsequent POST from that page should also have it at the end of the Referer, preventing the “SecRule REQUEST_HEADERS:Referer” line from triggering again and again if the person is a legitimate user behind a proxy that changes IP addresses with each request.

    Note that the last two lines of the code above are broken into two separate rules (instead of the more obvious single rule) due to a bug in mod_security 2.5.12 and earlier that prevents macro expansion in chained redirect rules.

    There are, of course, several obvious ways around this. As an example, attackers can simply omit or change the Referer, or can do a GET before a POST. We try to detect such behaviors, too; if an IP address only ever accesses wp-login.php, for example, or if it only sends bogus or empty Referers, it could be blocked with separate rules. You have to use many techniques to block the majority of these attempts.

  3. I’ve tried about five different platforms for comments since I began learning how to build a website. My latest is wordpress with the buddypress plugin. If you hover over the active users or recent users in the aside/widgets it shows the username. (Post shows nickname)

    Just thinking about that… I’m being redirected by this (get before post) rule. Started about a week or two ago. I guess I have no question or solution but I thought I’d mention this. The widget can be removed…

  4. doug wilson wrote:

    >I’m being redirected by this (get before post) rule.


    As we discussed separately by e-mail, it sounds like you’re seeing the “in the worst case, the login screen might be redisplayed to a human visitor” situation on your site when you accidentally type the wrong username or password on your custom login screen. You see the login page one more time than you usually would.

    This is related to the custom login page on your site, and isn’t something that would happen to most people. Our e-mail message has more details and suggestions.