8 ways to prevent brute force SSH attacks in Linux (CentOS/RHEL 7)


SSH, Featured Post, Linux, Security

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.

6 ways to prevent brute force SSH attacks in Linux (CentOS/RHEL 7)

 

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

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
IMPORTANT NOTE:
The syntax must be added in the provided order with 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

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.

[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.

 

Deepak Prasad

Deepak Prasad

He is the founder of GoLinuxCloud and brings over a decade of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive experience, he excels in various domains, from development to DevOps, Networking, and Security, ensuring robust and efficient solutions for diverse projects. You can connect with him on his LinkedIn profile.

Can't find what you're searching for? Let us assist you.

Enter your query below, and we'll provide instant results tailored to your needs.

If my articles on GoLinuxCloud has helped you, kindly consider buying me a coffee as a token of appreciation.

Buy GoLinuxCloud a Coffee

For any other feedbacks or questions you can send mail to admin@golinuxcloud.com

Thank You for your support!!

7 thoughts on “8 ways to prevent brute force SSH attacks in Linux (CentOS/RHEL 7)”

  1. 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.

    Reply
    • 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

      Reply
  2. 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.

    Reply

Leave a Comment