A brute-force attack consists of an attacker submitting many passwords or passphrases with the hope of eventually guessing correctly. The attacker systematically checks all possible passwords and passphrases until the correct one is found. In this article I will share various ways to prevent brute force SSH attacks in Linux platform.
This article was written while using CentOS 7, so it is safe to say that it also fully covers RHEL 7, Fedora, Oracle Enterprise Linux and generally the whole Red Hat family of operating systems and possibly Novell’s SLES and OpenSUSE.
Prevent brute force SSH attacks
There are multiple methods using which you can stop and prevent brute force SSH attacks. I will try to cover some of them in this article
1. Account lockout after X failed login attempts
You can implement account lockout functionality using pam_tally2
or pam_faillock
to prevent brute force SSH attacks. With this the system will lock any account after certain number of failed login attempts.
1.1 Lock account using pam_tally2
This module maintains a count of attempted accesses, can reset count on success, can deny access if too many attempts fail. pam_tally2
is an (optional) application which can be used to interrogate and manipulate the counter file. It can display users' counts, set individual counts, or clear all counts. Setting artificially high counts may be useful for blocking users without changing their passwords.
Below two configuration files must be modified to perform all the account lock or unlock related changes
/etc/pam.d/system-auth /etc/pam.d/password-auth
Add the below line in your /etc/pam.d/system-auth
and /etc/pam.d/password-auth
even_deny_root
from the highlighted section.auth required pam_tally2.so deny=3 even_deny_root unlock_time=600 onerr=fail account required pam_tally2.so
Here,
onerr=[fail|succeed] If something weird happens (like unable to open the file), return with PAM_SUCCESS if onerr=succeed is given, else with the corresponding PAM error code. deny=n Deny access if tally for this user exceeds n. unlock_time=n Allow access after n seconds after failed attempt. If this option is used the user will be locked out for the specified amount of time after he exceeded his maximum allowed attempts. Otherwise the account is locked until the lock is removed by a manual intervention of the system administrator.
Sample system-auth
and password-auth
file with the changes
auth required pam_env.so auth required pam_tally2.so deny=3 even_deny_root unlock_time=600 onerr=fail auth required pam_faildelay.so delay=2000000 auth sufficient pam_unix.so nullok try_first_pass auth requisite pam_succeed_if.so uid >= 1000 quiet_success auth required pam_deny.so account required pam_tally2.so account required pam_unix.so account sufficient pam_localuser.so account sufficient pam_succeed_if.so uid < 1000 quiet account required pam_permit.so password requisite pam_pwquality.so try_first_pass local_users_only retry=3 authtok_type= password sufficient pam_unix.so sha512 shadow nullok try_first_pass use_authtok password required pam_deny.so session optional pam_keyinit.so revoke session required pam_limits.so -session optional pam_systemd.so session [success=1 default=ignore] pam_succeed_if.so service in crond quiet use_uid session required pam_unix.so
onerr=fail
at the end or else the changes will not work.Next you can try to perform SSH to this node. From the rsyslog as you can see after three failed login attempts user 'deepak' was locked
# journalctl -f Aug 31 15:49:31 rhel-7.example unix_chkpwd[25921]: password check failed for user (deepak) Aug 31 15:49:31 rhel-7.example sshd[25919]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=10.0.2.2 user=deepak Aug 31 15:49:34 rhel-7.example sshd[25919]: Failed password for deepak from 10.0.2.2 port 61020 ssh2 Aug 31 15:49:34 rhel-7.example unix_chkpwd[25922]: password check failed for user (deepak) Aug 31 15:49:37 rhel-7.example sshd[25919]: Failed password for deepak from 10.0.2.2 port 61020 ssh2 Aug 31 15:49:38 rhel-7.example unix_chkpwd[25923]: password check failed for user (deepak) Aug 31 15:49:39 rhel-7.example sshd[25919]: Failed password for deepak from 10.0.2.2 port 61020 ssh2 Aug 31 15:49:41 rhel-7.example sshd[25919]: pam_tally2(sshd:auth): user deepak (1000) tally 4, deny 3
I have written another article with more information on steps to lock and unlock account using pam_tally2 with examples
1.2 Lock account using pam_faillock
pam_faillock
module maintains a list of failed authentication attempts per user during a specified interval and locks the account in case there were more than deny consecutive failed authentications.
This module also use the same pam.d
files to perform account lockout
/etc/pam.d/system-auth /etc/pam.d/password-auth
Add the below line in your /etc/pam.d/system-auth
and /etc/pam.d/password-auth
even_deny_root
from the highlighted section.auth required pam_faillock.so preauth silent deny=3 even_deny_root fail_interval=900 unlock_time=600 auth required pam_faillock.so authfail deny=3 even_deny_root fail_interval=900 unlock_time=600 account required pam_faillock.so
Sample system-auth
and password-auth
file with the changes
auth required pam_env.so auth required pam_faillock.so preauth silent audit deny=3 unlock_time=600 auth sufficient pam_unix.so nullok try_first_pass auth required pam_faillock.so authfail audit deny=3 fail_interval=900 unlock_time=600 auth requisite pam_succeed_if.so uid >= 1000 quiet_success auth required pam_deny.so account required pam_faillock.so account required pam_unix.so account sufficient pam_localuser.so account sufficient pam_succeed_if.so uid < 500 quiet account required pam_permit.so password requisite pam_cracklib.so try_first_pass retry=3 type= password sufficient pam_unix.so sha512 shadow nullok try_first_pass use_authtok password required pam_deny.so session optional pam_keyinit.so revoke session required pam_limits.so session optional pam_oddjob_mkhomedir.so session [success=1 default=ignore] pam_succeed_if.so service in crond quiet use_uid session required pam_unix.so
I have written another article with more information on steps to lock and unlock account using pam_faillock with examples
2. Ensure system is using Strong Hashing
Make sure that your system is already using SHA-256 or SHA-512 hashing which will make it harder for the hashes in our /etc/shadow
file and will prevent brute force SSH attacks.
Verify the existing hashing algorithm
# authconfig --test | grep algo
password hashing algorithm is sha512
To check the supported hashing algorithm
# authconfig --help | grep passalgo --passalgo=<descrypt|bigcrypt|md5|sha256|sha512>
To change the current hashing algorithm
# authconfig --passalgo=sha512 --update
To force all the non-root users to change their password in next login use the below one liner script
# for u in $(awk -F: '{if ( $1 != "root" && $2 ~ /^!?[[:alnum:]./$]/ ) print $1}' /etc/shadow); do chage -d0 $u; done
3. Allow or Deny ssh using hosts.allow and hosts.deny
tcpwrappers
are configured through the files /etc/hosts.allow
and /etc/hosts.deny
. To completely deny access to the SSH daemon, add the following text to /etc/hosts.deny
:
sshd: ALL
This will completely disable access to the SSH daemon from any client. If there are a few hosts that should be allowed to connect, add the following lines to /etc/hosts.allow:
sshd: 192.168.0.1, trusted.one-domain.com, 192.168.23.0/24, *.trusted.net
This would allow connections from the IP address 192.168.0.1, the hostname trusted.one-domain.com
, the network 192.168.23.0/24, and the domain *.trusted.net
while disallowing any other connections.
4. Apply SSH Rate Control using IPtables
You can use iptables to apply rate control over SSH to make sure no one is allowed to connect to your system at regular intervals to prevent brute force SSH attacks.
These 3 rules setup a hit rate of 5 new connection tries by minute, you need to adjust this values according to your needs. All the logs of this rule will appear in the /var/log/messages
file, prefix 'SSH-HIT-RATE
' is used to ease looking for the related entries.
-I INPUT <line number>
to define the order of these rules in your iptables.[root@rhel-7 ~]# iptables -A INPUT -p tcp --dport 22 -m state --state NEW -m recent --set --name SSH [root@rhel-7 ~]# iptables -A INPUT -p tcp --dport 22 -m state --state NEW -m recent --update --seconds 60 --hitcount 5 --rttl --name SSH -j LOG --log-prefix 'SSH-HIT-RATE: ' [root@rhel-7 ~]# iptables -A INPUT -p tcp --dport 22 -m state --state NEW -m recent --update --seconds 60 --hitcount 5 --rttl --name SSH -j DROP
The last rule is to ACCEPT SSH connection on port 22. This rule by default exist on newly installed system.
[root@rhel-7 ~]# iptables -A INPUT -p tcp --dport 22 -m state --state NEW -j ACCEPT
Verify the implemented iptables rules
[root@rhel-7 ~]# iptables -L Chain INPUT (policy ACCEPT) target prot opt source destination tcp -- anywhere anywhere tcp dpt:ssh state NEW recent: SET name: SSH side: source mask: 255.255.255.255 LOG tcp -- anywhere anywhere tcp dpt:ssh state NEW recent: UPDATE seconds: 60 hit_count: 5 TTL-Match name: SSH side: source mask: 255.255.255.255 LOG level warning prefix "SSH-HIT-RATE: " DROP tcp -- anywhere anywhere tcp dpt:ssh state NEW recent: UPDATE seconds: 60 hit_count: 5 TTL-Match name: SSH side: source mask: 255.255.255.255 ACCEPT tcp -- anywhere anywhere tcp dpt:ssh state NEW Chain FORWARD (policy ACCEPT) target prot opt source destination Chain OUTPUT (policy ACCEPT) target prot opt source destination
Check if iptables rules are working
Aug 31 17:42:07 rhel-7.example systemd[1]: Started Session 37 of user root. Aug 31 17:43:59 rhel-7.example systemd[1]: Started Session 38 of user root. Aug 31 17:44:04 rhel-7.example systemd[1]: Started Session 39 of user root. Aug 31 17:44:08 rhel-7.example systemd[1]: Started Session 40 of user root. Aug 31 17:44:10 rhel-7.example systemd[1]: Started Session 41 of user root.
After 5 successful session in one minute, the sixth session was blocked by our IPtables rule.
Aug 31 17:44:11 rhel-7.example kernel: SSH-HIT-RATE: IN=lo OUT= MAC=00:00:00:00:00:00:00:00:00:00:00:00:08:00 SRC=10.0.2.11 DST=10.0.2.11 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=52655 DF PROTO=TCP SPT=58974 DPT=22 WINDOW=43690 RES=0x00 SYN URGP=0
5. Using /etc/ssh/sshd_config
The most highly recommended action to prevent brute force SSH attacks, would be to prevent root from logging in directly via SSH. To do this you need change the line which reads:
#PermitRootLogin yes
to the following:
PermitRootLogin no
Alternatively, you can set up ssh keys for the user root and use
PermitRootLogin without-password
to allow the user root to log in using keys only.
There are various other SSHD values which can be implemented to further harden the SSHD configuration and prevent brute force attack
As a best practice it is recommended to use PasswordAuthentication no
in sshd_config
to make sure the password input always goes through this PAM conversation. But before making this change make sure you have enabled key based authentication to connect to your host. In any case a console based login.
6. Change SSHD Port Number
By default we all know that SSHD uses port 22 so it is very easy for any attacker to attempt to connect to your Linux box and also increases the chances of brute force SSH attacks. So to improve the security and prevent brute force SSH attacksyou can change SSH port no to any random number between 1025 and 65535, as attackers will assume that port 22 will be used for the ssh protocol. Find this line in /etc/sshd/sshd_config
:
#Port 22
Change it to something like this:
Port 1101
Port number 1101 is an example. Now you need to access to this server to the port 1101 when you do a ssh connection:
# ssh someuser@ssh-server:1101
7. Using Fail2Ban
Fail2ban scans log files and bans IPs that show the malicious signs -- too many password failures, seeking for exploits, etc. Generally Fail2Ban is then used to update firewall rules to reject the IP addresses for a specified amount of time, although any arbitrary other action (e.g. sending an email) could also be configured.
I have written another article with the steps to secure SSH and root login with fail2ban
With fail2ban you can create a rule to check frequent failed SSH login on your Linux host which will prevent brute force SSH attacks.
8. Two Factor Authentication using Google Authenticator
You can use Open Source Google Authenticator to enable two factor authentication using pam_google_authenticator.so
module. Once you enable 2 factor authentication, every time someone attempts to do SSH to your Linux host, the user will get a prompt for verification code before entering the user password.
Below is a snippet from such two factor authentication
login as: root Using keyboard-interactive authentication. Verification code: <--- Provide the OTP from your Google Authenticator App Using keyboard-interactive authentication. Password: <-- provide the password of user "root" Last login: Sat Mar 28 16:09:48 2020 from 10.0.2.2 [root@centos-8 ~]#
I have written another article with detailed steps to install and setup two factor authentication using Google Authenticator in Linux with examples
Lastly I hope the steps from the article to prevent brute force SSH attacks in Linux was helpful. So, let me know your suggestions and feedback using the comment section.
Superb articles, very precise and detailed explanation.
Thanks for your support.
I was interested in the rate limiting but then I realised if there is an attack on your system with 10’s/100’s/1000’s of attempts per minute you would get blocked by the rate limiting yourself and be unable to login to your own server, the rate limiting has effectively turned the brute force login attack in a DOS attack or have I missed something here.
What rate limiting configuration are you referring here?
In any way in such case if all SSH are blocked then you will still have console access to unblock your self
So there are two other methods,
1. Is to lockdown ssh access to a specific up or IP range
2. To stop ssh passwords but rather use SSH keys.
Thanks for sharing these.
1. Mentioned in point 3
2. Mentioned in point 5
Install fail2ban and use this jail:
https://github.com/VedranIteh/fail2ban-ssh-honeypot
Another good technique is “port knocking”.