You are not logged in.

#1 2022-04-08 11:19:08
Registered: 2022-02-11
Posts: 28

ssh brute force mitigation

Yesterday I noticed a number of IP addresses attempting to brute force my ssh server.  The server uses secure keys, so the attack is not very effective, but to further mitigate their attack I wrote a script to dynamically block addresses at the firewall if they fail to connect more than a certain number of times.  I wanted to share that script in case anyone is facing a similar issue or can suggest improvements.  The script is executed every fifteen minutes by systemd:


clients="$(journalctl -u sshd |
        grep "Unable to negotiate with" |
        grep -oE '((1?[0-9][0-9]?|2[0-4][0-9]|25[0-5])\.){3}(1?[0-9][0-9]?|2[0-4][0-9]|25[0-5])' |
        sort |
        uniq -c |
        sed -e 's/^[[:space:]]*//')"
while read client
        attempts=$(printf "$client" | cut -d ' ' -f1)
        address=$(printf "$client" | cut -d ' ' -f2)
        if [[ $attempts > $MAX_TRIES ]]
                echo "ssh brute force blocker | $(date +%Y-%m-%d) | $(date +%H:%M:%S) | added client to blacklist | client={{$address}}"
                echo "$address | $(date +%Y-%m-%d) | $(date +%H:%M:%S)" >> $SSH_OFFENDER_LIST
                iptables -I INPUT -s $address -j DROP
done <<< "$clients"
echo "ssh brute force blocker | $(date +%Y-%m-%d) | $(date +%H:%M:%S) | finished scanning for malicious clients"


#2 2022-04-08 17:16:50

Registered: 2018-08-30
Posts: 67

Re: ssh brute force mitigation


Why not use actual LIPS (Log-based Intrusion Protection System) like fail2ban and sshguard?

When it comes to security, don't go DIY unless it is for fun.

Remarks on your script:
Always put double quotes around your variables (unless you know why not).

NEVER EVER trust user input. ATM, your script isn't vulnerable to injection, but I see it coming...

Beware of not locking you up from your own server (3 failed attempts and the associated IP is blocked forever).

«Unable to negociate with» log messages arn't related to failed authentication by password or private key.
It happens prior to the authentication, first both the client and the server need to create a secure connection using common parameters set up on both side. If both side don't have matching parameters, the negotiation cannot succeed and the connection is dismissed. They wernt able to negociate.
The IP addresses you found in your logs associated to those failures are probably bots trying to find SSH server with weak parameters set.

Every 15 minutes, you pull all the logs from sshd.service, and then parse it. So you are going to parse the same log over and over again, blocking the same IP over and over again.

EDIT: if it is sh or Bash:

while read -a fields; do echo "1: ${fields[0]}, 2: ${fields[1]}"; done <<< "a b"

Instead of printf and cut.

Last edited by Koatao (2022-04-08 17:56:21)


#3 2022-04-08 18:32:11

Inspector Parrot
Registered: 2011-11-29
Posts: 26,900

Re: ssh brute force mitigation

While I agree with the above, that script is way over complicated for what it does.  You create a potentially very long string variable for no purpose other than to pipe it into another loop: cut out the middle man of the variable.

Then you use grep twice and sort and uniq and sed all in a pipeline when one single awk call will do the same result.  Worse yet, you use date every time through a loop but it's generating static results (and even worse yet you're running date *twice* each time through the loop when once would suffice even if you wanted it in the loop).  The following produces the same result (except for echoing to the screen which could be easily added) without any shell oops, huge variable redirections, or long pipeliens - just awk:



dstr=$(date "+| %Y-%m-%d | %H:%M:%S")
journalctl -u sshd | awk -v dstr="$dstr" -v max=$MAX_TRIES '
	/Unable to negotiate with/ { failed[$10]++; }
	END { for (ip in failed) if (failed[ip] > max) print ip, dstr; }

As noted in the previous post though, you should also include flags to journalctl to only retrieve the relevant time window so you don't keep recounting the same entries over and over again.

Also 3 seems to be a pretty low number if your goal is to stop brute-force attempts.  If a single IP has failed 3, 4, 5, or any other small number of times, that's more likely you fat-fingering a password, or some other human error.  Or perhaps it's someone else fat-fingering an IP address in an honest attempt to try to log in to their own server with a similar IP address.  I wouldn't really suspect a brute-force attack until the number of failed attempts was significantly larger than 3.

Lastly, the date string in both your script and mine is the time / date on which the script was run.  I'm not sure why that'd be of any interest - I don't know why you'd want that included.  In contrast, the date of the attempts in the journal might be relevant - if you wanted you could create another awk array for the most recent attempt from a given IP address.  This way you could detect if a given offender has since "gone away" on their own.

Note that this "going away on their own" is common - and depending on your definition these cases may or may not be considered true brute force attacks.  In my server logs there are always bursts of attempts from a given IP or a series of neighboring IPs with a couple dozen attempts.  These are not targetted brute force attacks specifically on my server that someone is really focused on breaking into.  It's just a quick dictionary-style attack on any random servers out there: a few dozen common username/password pairings are attempted; if they all fail, the "attacker" moves on.  There's little point in banning these attack sources as by the time you detect them they'll have been on their way anways.  If / when they come back, it will not be from the same IP address.

Last edited by Trilby (2022-04-08 18:49:13)

"UNIX is simple and coherent..." - Dennis Ritchie, "GNU's Not UNIX" -  Richard Stallman


Board footer

Powered by FluxBB