Block 401 errors with fail2ban

OS type and version Ubuntu 20.04
Virtualmin version 7.7

I believe fail2ban in its default configuration does not block invalid login attempts. My website throws 401 errors on failed login and I’m trying to use them to get fail2ban to block such attempts. Here’s a sample from access logs: - - [26/Jun/2023:11:58:50 +0530] "POST /login HTTP/2.0" 401 3295 "" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/ Safari/537.36 Edg/114.0.1823.51"

I added a line in Fail2ban > Log Filters > apache-auth > Regex to match

^<HOST> -.*"(GET|POST|HEAD).*HTTP.*" 401.*"$

I’ve verified that it does match the above log line. The entire contents of regex to match field looks like this

        ^client (?:denied by server configuration|used wrong authentication scheme)\b
        ^user (?!`)<F-USER>(?:\S*|.*?)</F-USER> (?:auth(?:oriz|entic)ation failure|not found|denied by provider)\b
        ^Authorization of user <F-USER>(?:\S*|.*?)</F-USER> to access .*? failed\b
        ^%(auth_type)suser <F-USER>(?:\S*|.*?)</F-USER>: password mismatch\b
        ^%(auth_type)suser `<F-USER>(?:[^']*|.*?)</F-USER>' in realm `.+' (auth(?:oriz|entic)ation failure|not found|denied by provider)\b
        ^%(auth_type)sinvalid nonce .* received - length is not\b
        ^%(auth_type)srealm mismatch - got `(?:[^']*|.*?)' but expected\b
        ^%(auth_type)sunknown algorithm `(?:[^']*|.*?)' received\b
        ^invalid qop `(?:[^']*|.*?)' received\b
        ^%(auth_type)sinvalid nonce .*? received - user attempted time travel\b
        ^(?:No h|H)ostname \S+ provided via SNI(?:, but no hostname provided| and hostname \S+ provided| for a name based virtual host)\b
        ^<HOST> -.*"(GET|POST|HEAD).*HTTP.*" 401.*"$

I restarted Fail2ban and then I triggered some 401 errors but my ip does not show up in fail2ban logs. What am I doing wrong?

default is off, have you turned on?

Yes. It is on

how many fails did you do?
Maybe log path is wrong?

Personally I would leave alone.
If your web app is sending a 401 in response to a simple error on behalf of the user (for example a mis keyed password/username) then you are really going to annoy the genuine users. This is a problem that should be resolved by the web app (typically the login/registration form). What is the problem for the server? (OK it fills up the access log - but hey so do 404 responses) A 401 error tells the client that a mistake has been made and to try again. It frequently can even be sent in place of 404 errors (it all depends on how the web app has been designed) so we shouldn’t really interfere.

@stefan1959 I tried a couple of times. I wasn’t trying to get blocked. I was looking for a log like this

2023-06-07 21:23:45,500 fail2ban.filter         [769]: INFO    [apache-noscript] Found - 2023-06-07 21:23:45

The apache-auth filter is already working. I just added another regex to it that I mentioned above.

@Stegan My reason for doing this is to prevent brute force attacks. Of course, the filter will be lenient enough not to avoid annoying real users. I don’t think this should be handled by web app.

My experience with attacks is that they come from a very wide range of different IPs and only do maybe one or two attempts each so not enough to trigger Fail2ban.

Yes I’ve seen that in the logs.

Do you think fail2ban is useless?

What is the best way to prevent brute force attacks on login forms?

Sorry, but if the “attacker” is going to the trouble of actually loading the form it is the job of the app design of the form have a simple mechanism to prevent resubmission of the form (hidden fields on the form with a key, additional classes etc) My experience with attacks has always been they are a little less talented and don’t use a genuine form so are just dropped for not having a valid form input field.

Use fail2ban for IP attacks which is what it is good for.

Forms can be submitted directly, bypassing any client side security preventions.

No the server part of the app checks for them so as a form is POST’ed with for example a hidden key then the server simply ignores the whole request and redirects back to the client a new form and a message indicating the user has to try again. It is a very simple mechanism and cannot be easily bypassed many commercial web sites use it very effectively. valid keys are maintained in a database so forgery is not an option. Very simple to implement in app, no need to do anything with the VS. My clients that implement this just don’t care about 401 or any html status responses they just deal with them client side.

So you generate a unique key and save it to the database? Then when the form is submitted, you check if the key is valid? I assume, you will delete the key after that (one time use)?

How does it prevent brute force attacks though? Before login, I don’t have any reliable identifers of the account/device.

Firstly understand this has nothing to do with me. My clients on their VS have and are responsible for their own Apps - (I know for example many folk on here have clients that use WordPress and also use plugins to such Apps - in that case it would be down to them or the staff they employ to manage such apps) I don’t use WorPress (In fact I do not take on clients that use it. I don’t even use PHP on my VS.

But you are correct AFAIK the client app uses a “middleware” check on the route to identify the “key” and if it is in the database (often a Redis key/pair type of db) if it is then it it continues the route and deletes the key - if not it then redirects. The database has a ttl index so out of date keys get deleted.

As I indicated above a “brute force” attack does not originate from a form so it never gets a 401 as it is not handled by an app. I was curious so I just looked in the access log of one of my pretty active VS (an auction house) and I couldn’t find a single 401 sure a few 404 (really obscure urls) but most requests get a response from the app. So I can’t understand where the 401’s are coming from.

how are you triggering 401 errors ?

Thanks for the explanation.

I don’t allow Wordpress on my server either.

The 401s are sent by my websites (that I made). Like so:


why do this it’s far simpler to just bounce them with a redirect use a function like this

   $url = ""; 
   header("Location: $url"); // now send them off
   $ip = $_SERVER['REMOTE_ADDR']; // get the remote address
   file_put_contents('bad.txt',$ip.PHP_EOL,FILE_APPEND) // save the address to a file
   exit(); // stop the script

then you could run a script as root to add them to a fail2ban jail if you want to. I have coded such a script but to be fair it was not done for a failed login, with that I just send them off elsewhere

That explain where they are coming from (and you are using PHP I think) I guess this is different circumstances from professional/commercial websites. So it is your website that is sending 401 responses to the php var $loginFailed. Whatever that test is (a uri or posted form or the weather perhaps) it is still within your App.

Thanks @jimr1, that’s a good idea as well. I’ll try to implement it.

You were right. I guess I was sleep deprived. Didn’t see the problem right in front of me.

apache-auth looks in the error logs, not access logs. Created a new filter and jail and it worked.


1 Like

This topic was automatically closed 8 days after the last reply. New replies are no longer allowed.