2

Currently, wordpress default is returning 200 on either failed or successfull login attempt. The modern standard, used by all web application framework is to return 401 (or 403) on failed login attempts.

This allows third party tools (think waf, fail2ban, etc) to detect brute forcing attempt and block it from outside of wordpress.

I can't find where I can make this change or is there a plugin providing such a functionality.

Yes, I'm well aware of plugins who attempt to provide "brute force blocking" from inside of Wordpress. But besides being a problem on their own, they are prone to being shut from inside the Wordpress installation. And the defence is placed in the wrong level. Instead of being a perimeter defense, all those requests hit the actual wordpress Installation. So no, this isn't a good option for me.

Thanks for the help!

3 Answers 3

3
+25

WordPress handles login failed in two ways:

  1. If it is a bad credential, and both username and password have a value, then this action can be captured by wp_login_failed

  2. If both, or one, of the options are empty, then WordPress generates the error object as the first parameter in the authenticate filter; it does not open and wp_login_failed action captures this cause/event For what we have done here,

see comments in code:

add_filter( 'authenticate', function( $user, $username, $password ) {
    // forcefully capture login failed to forcefully open wp_login_failed action, 
    // so that this event can be captured
    if ( empty( $username ) || empty( $password ) ) {
        do_action( 'wp_login_failed', $user );
    }
    return $user;
} );

// to handle even you can handle the error like
add_action( 'wp_login_failed', function( $username ) {
    if ( is_wp_error( $username ) ) {
        // you return 401 with wp function, this action takes place before header sent.
        $wp_query->set_401();
        status_header( 401 );
        nocache_headers();

    }
} );

my answer is a combination of : Redirect user using the 'wp_login_failed' action hook if the error is 'empty_username' or 'empty_password' and How to force a 404 on WordPress

update: I wrote super simple plugin to do this WP-401-On-Failed-Login. It uses some wp auth hooks, and set_header() before content being sent.

2
  • 2
    There doesn't appear to be a set_401() function in the WP_Query class, so this won't work, I'm afraid.
    – Pat J
    Commented Nov 25, 2016 at 4:38
  • @amit-rahav thanks for answer, I suspect there is a bug in the code: Look like the 401 would be executed and sent only if there is an error on $username. But what if there is another error (Notably wrong password). Would it run? Or perhaps the variable the callback gets isn't really a username? Also: why do we also need $wp_query --> set_401() and does it even exist?
    – alonisser
    Commented Nov 25, 2016 at 13:06
1

If you have access to the server your site is running on, you could use modsecurity to return 401 from the web server (instead of through PHP).

Here's a tutorial I wrote a while back about blocking IPs and returning 401 through modsecurity:

https://smyl.es/how-to-block-wp-login-php-brute-logins-with-cpanel-mod-security-and-configserver-firewall/

3
  • This is based on apache. I use nginx. Although the 200 for wp-login can work perhaps? But quite annoying I can't get the correct result
    – alonisser
    Commented Nov 29, 2016 at 18:16
  • Yes, a 200 response means failed login, 302 means valid as it's issuing a redirect after verifying the login and setting cookie
    – sMyles
    Commented Nov 29, 2016 at 18:19
  • The reason you wouldn't want a 401 is because WordPress outputs the error on the wp-login page, thus is why it's a 200 instead of 302, because it just reloads the page showing the error instead of doing a redirect. That's why the modsecurity tutorial only returns a 401 after like 10 failed logins
    – sMyles
    Commented Nov 29, 2016 at 18:26
1

WordPress core (and others) disagree 401 (and 403) are the correct "modern standard" and that "all web application frameworks" use 401 (or 403) for failed logins.

See WordPress core Trac ticket #25446 to understand why failed logins return HTTP 200, and why this has not been changed to 401 (or 403).

You can solve the original problem by adjusting fail2ban and similar tools to filter HTTP POST requests to wp-login that result in HTTP STATUS CODE 200. Successfully posted logins get a 301 redirect.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.