Tuesday, July 26, 2005

A Cure for the Common SSH Login Attack

A Cure for the Common SSH Login Attack

-by Jon Scully

Introduction
A few months ago, I began seeing our 'secure' log files fill up with entries stating: "Failed password for illegal user [username]". I decided to search the Internet to find out if others were experiencing these attacks and, hopefully, find a solution. I did uncover a lot of information on the subject, but discovered only a few script-based solutions. None of these, however, seemed... well... elegant.

What I wanted was a way to stop the attacks altogether, yet allow ssh access from anywhere, when needed. In addition, I wanted to avoid using an approach that was so complicated it could lead to more pain than I was experiencing from the original problem.

    My requirements looked something like this:
  • Keep port 22 closed, until needed
  • Provide a simple way to open and close port 22 from any remote location
  • Ensure the method used is reasonably difficult for attackers to discover
  • Use an "elegant" method (i.e. not a lot of software)

The solution should behave similar to the following shell prompt activity:

    $ ssh name@hostname # No response (Ctrl-C to abort)
^C
$ telnet hostname 1600 # Telnet into port 1600 to open port 22
Trying 123.123.123.123...
^C
$ ssh name@hostname # Now logins are allowed
name@hostname's password:
.
.
.
$ telnet hostname 1601 # Telnet into port 1601 to close port 22
Trying 123.123.123.123...
^C

Note that the ports used to open and close port 22 should appear closed, as well. This approach would be a sort of simplified "port knocking" technique.

Proposed Solution
The 'recent' module in iptables is designed to detect malicious access attempts and then help block or at least honeypot the potential intruder with delays. I've sort of turned this module on its head and, instead, used it to let people in.

The following represents the contents of an iptables file, drawn from a Red Hat distribution (the usual path is /etc/sysconfig/iptables). The highlighted text outlines the changes needed to support our style of port knocking.

*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:RH-Firewall-1-INPUT - [0:0]
-A INPUT -j RH-Firewall-1-INPUT
-A FORWARD -j RH-Firewall-1-INPUT
-A RH-Firewall-1-INPUT -i lo -j ACCEPT
-A RH-Firewall-1-INPUT -s 10.0.0.0/24 -j ACCEPT
-A RH-Firewall-1-INPUT -p icmp --icmp-type any -j ACCEPT
-A RH-Firewall-1-INPUT -p 50 -j ACCEPT
-A RH-Firewall-1-INPUT -p 51 -j ACCEPT
-A RH-Firewall-1-INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
-A RH-Firewall-1-INPUT -m state --state NEW -m tcp -p tcp --dport 22 -m recent --rcheck --name SSH -j ACCEPT

-A RH-Firewall-1-INPUT -m state --state NEW -m tcp -p tcp --dport 80 -j ACCEPT
-A RH-Firewall-1-INPUT -m state --state NEW -m udp -p udp --dport 123 -j ACCEPT
-A RH-Firewall-1-INPUT -m state --state NEW -m tcp -p tcp --dport 443 -j ACCEPT
-A RH-Firewall-1-INPUT -m state --state NEW -m tcp -p tcp --dport 1599 -m recent --name SSH --remove -j DROP
-A RH-Firewall-1-INPUT -m state --state NEW -m tcp -p tcp --dport 1600 -m recent --name SSH --set -j DROP
-A RH-Firewall-1-INPUT -m state --state NEW -m tcp -p tcp --dport 1601 -m recent --name SSH --remove -j DROP
-A RH-Firewall-1-INPUT -j DROP
COMMIT

Note that there is a "close" port on either side of the "open" port. This should cause most linear port scans (ascending or descending) to leave port 22 closed, upon completion.

For an even more robust approach, use two separate "open" ports -- such as a knock at port 1300, followed by a knock at port 1600. Also keep in mind, once you have ssh-connected, you can then close the port without losing your established connection.

Conclusion
What's really convenient about this method is, when you're at a trusted location (say, in the office) and you unlock a target site (say, a home server), you need only knock once; The port will stay open for that specific source IP address, indefinitely (until you deliberately close the port or iptables is restarted). If you attempt to use the port from another location (say, a client's office), it will appear closed -- until you knock.

What I find most elegant about this approach is that you don't have to fill up your iptables with dozens of DROP entries in order to block the world of would-be attackers.

Sample Log
Here is a representative sample from a 'secure' log file:

Jan  7 09:58:47 hostname sshd[24729]: Illegal user test from [IP_ADDRESS_A]
Jan 7 09:58:50 hostname sshd[24729]: Failed password for illegal user test from [IP_ADDRESS_A] port 51250 ssh2
Jan 7 09:58:52 hostname sshd[24731]: Illegal user guest from [IP_ADDRESS_A]
Jan 7 09:58:54 hostname sshd[24731]: Failed password for illegal user guest from [IP_ADDRESS_A] port 51396 ssh2
Jan 7 09:58:56 hostname sshd[24733]: Illegal user admin from [IP_ADDRESS_A]
Jan 7 09:58:58 hostname sshd[24733]: Failed password for illegal user admin from [IP_ADDRESS_A] port 51546 ssh2
Jan 7 09:59:00 hostname sshd[24735]: Illegal user admin from [IP_ADDRESS_A]
Jan 7 09:59:03 hostname sshd[24735]: Failed password for illegal user admin from [IP_ADDRESS_A] port 51688 ssh2
Jan 7 09:59:04 hostname sshd[24737]: Illegal user user from [IP_ADDRESS_A]
Jan 7 09:59:07 hostname sshd[24737]: Failed password for illegal user user from [IP_ADDRESS_A] port 51828 ssh2
Jan 7 09:59:11 hostname sshd[24739]: Failed password for root from [IP_ADDRESS_A] port 51963 ssh2
Jan 7 09:59:15 hostname sshd[24741]: Failed password for root from [IP_ADDRESS_A] port 52114 ssh2
Jan 7 09:59:20 hostname sshd[24743]: Failed password for root from [IP_ADDRESS_A] port 52288 ssh2
Jan 7 09:59:22 hostname sshd[24745]: Illegal user test from [IP_ADDRESS_A]
Jan 7 09:59:24 hostname sshd[24745]: Failed password for illegal user test from [IP_ADDRESS_A] port 52419 ssh2
Jan 7 16:35:22 hostname sshd[25103]: Failed password for nobody from [IP_ADDRESS_C] port 53721 ssh2
Jan 7 16:35:25 hostname sshd[25105]: Illegal user patrick from [IP_ADDRESS_C]
Jan 7 16:35:28 hostname sshd[25105]: Failed password for illegal user patrick from [IP_ADDRESS_C] port 53832 ssh2
Jan 7 16:35:31 hostname sshd[25107]: Illegal user patrick from [IP_ADDRESS_C]
Jan 7 16:35:33 hostname sshd[25107]: Failed password for illegal user patrick from [IP_ADDRESS_C] port 53907 ssh2
Jan 7 16:35:39 hostname sshd[25109]: Failed password for root from [IP_ADDRESS_C] port 54003 ssh2
Jan 7 16:35:45 hostname sshd[25111]: Failed password for root from [IP_ADDRESS_C] port 54093 ssh2
Jan 7 16:35:50 hostname sshd[25113]: Failed password for root from [IP_ADDRESS_C] port 54181 ssh2
Jan 7 16:35:58 hostname sshd[25115]: Failed password for root from [IP_ADDRESS_C] port 54312 ssh2
Jan 7 16:36:04 hostname sshd[25117]: Failed password for root from [IP_ADDRESS_C] port 54395 ssh2
Jan 7 16:36:07 hostname sshd[25119]: Illegal user rolo from [IP_ADDRESS_C]
Jan 7 16:36:10 hostname sshd[25119]: Failed password for illegal user rolo from [IP_ADDRESS_C] port 54488 ssh2
Jan 7 16:36:14 hostname sshd[25121]: Illegal user iceuser from [IP_ADDRESS_C]
Jan 7 16:36:16 hostname sshd[25121]: Failed password for illegal user iceuser from [IP_ADDRESS_C] port 54577 ssh2
Jan 7 16:36:21 hostname sshd[25123]: Illegal user horde from [IP_ADDRESS_C]
Jan 7 16:36:23 hostname sshd[25123]: Failed password for illegal user horde from [IP_ADDRESS_C] port 54681 ssh2
Jan 7 16:36:26 hostname sshd[25125]: Illegal user cyrus from [IP_ADDRESS_C]
Jan 7 16:36:28 hostname sshd[25125]: Failed password for illegal user cyrus from [IP_ADDRESS_C] port 54786 ssh2
Jan 7 16:36:32 hostname sshd[25127]: Illegal user www from [IP_ADDRESS_C]
Jan 7 16:36:34 hostname sshd[25127]: Failed password for illegal user www from [IP_ADDRESS_C] port 54878 ssh2
Jan 7 16:36:37 hostname sshd[25129]: Illegal user wwwrun from [IP_ADDRESS_C]
Jan 7 16:36:40 hostname sshd[25129]: Failed password for illegal user wwwrun from [IP_ADDRESS_C] port 54966 ssh2
Jan 7 16:36:43 hostname sshd[25131]: Illegal user matt from [IP_ADDRESS_C]
Jan 7 16:36:46 hostname sshd[25131]: Failed password for illegal user matt from [IP_ADDRESS_C] port 55050 ssh2
Jan 7 16:36:50 hostname sshd[25133]: Illegal user test from [IP_ADDRESS_C]
Jan 7 16:36:53 hostname sshd[25133]: Failed password for illegal user test from [IP_ADDRESS_C] port 55152 ssh2
Jan 7 16:36:57 hostname sshd[25135]: Illegal user test from [IP_ADDRESS_C]
Jan 7 16:36:59 hostname sshd[25135]: Failed password for illegal user test from [IP_ADDRESS_C] port 55263 ssh2
Jan 7 16:37:02 hostname sshd[25137]: Illegal user test from [IP_ADDRESS_C]
Jan 7 16:37:04 hostname sshd[25137]: Failed password for illegal user test from [IP_ADDRESS_C] port 55366 ssh2
Jan 7 16:37:08 hostname sshd[25139]: Illegal user test from [IP_ADDRESS_C]
Jan 7 16:37:10 hostname sshd[25139]: Failed password for illegal user test from [IP_ADDRESS_C] port 55457 ssh2
Jan 7 16:37:13 hostname sshd[25141]: Illegal user www-data from [IP_ADDRESS_C]
Jan 7 16:37:16 hostname sshd[25141]: Failed password for illegal user www-data from [IP_ADDRESS_C] port 55548 ssh2
Jan 7 16:37:21 hostname sshd[25143]: Failed password for mysql from [IP_ADDRESS_C] port 55637 ssh2
Jan 7 16:37:26 hostname sshd[25145]: Failed password for operator from [IP_ADDRESS_C] port 55724 ssh2
Jan 7 16:37:33 hostname sshd[25147]: Failed password for adm from [IP_ADDRESS_C] port 55799 ssh2
Jan 7 16:37:42 hostname sshd[25149]: Failed password for apache from [IP_ADDRESS_C] port 55912 ssh2
Jan 7 16:37:52 hostname sshd[25151]: Illegal user irc from [IP_ADDRESS_C]
Jan 7 16:37:54 hostname sshd[25151]: Failed password for illegal user irc from [IP_ADDRESS_C] port 56036 ssh2

Disclaimer
The security gained from using the above information cannot be guaranteed. If you use the above information for any purpose, you do so at your own risk.

No comments: