Kicking out SSH script kiddies with autoblock
I noticed recently that random people have been trying to brute-force SSH on this website – this is a pretty common thing to notice among Linux users unfortunately. Things like this will often appear in your logs (On Debian/Ubuntu it’s in /var/log/auth.log):
Failed password for invalid user pete from 72.22.77.196 port 59102 ssh2
Invalid user peter from 72.22.77.196
(pam_unix) check pass; user unknown
(pam_unix) authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=72.22.77.196
Failed password for invalid user peter from 72.22.77.196 port 59251 ssh2
Invalid user peter from 72.22.77.196
(pam_unix) check pass; user unknown
(pam_unix) authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=72.22.77.196
Failed password for invalid user peter from 72.22.77.196 port 59494 ssh2
Invalid user phil from 72.22.77.196
(pam_unix) check pass; user unknown
(pam_unix) authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=72.22.77.196
Invalid user peter from 72.22.77.196
(pam_unix) check pass; user unknown
(pam_unix) authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=72.22.77.196
Failed password for invalid user peter from 72.22.77.196 port 59251 ssh2
Invalid user peter from 72.22.77.196
(pam_unix) check pass; user unknown
(pam_unix) authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=72.22.77.196
Failed password for invalid user peter from 72.22.77.196 port 59494 ssh2
Invalid user phil from 72.22.77.196
(pam_unix) check pass; user unknown
(pam_unix) authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=72.22.77.196
Thanks 72.22.77.196, you’re a giant tool. So to get rid of you and your ilk, I wrote this Ruby script:
#!/usr/bin/ruby
ip_count = {}
blacklisted = {}
tables = `iptables –list`
STDIN.readlines.each do |x|
next unless x =~ /Failed.*ssh2/
m = /(\d*\.\d*\.\d*\.\d*)/.match(x)
next unless m and m[0]; addr = m[0]
ip_count[addr] ||= 0; ip_count[addr] += 1
next unless ip_count[addr] > 10
next if blacklisted[addr] or tables.include? addr
`iptables -I INPUT -s #{addr} -j DROP`
blacklisted[addr] = true
end
ip_count = {}
blacklisted = {}
tables = `iptables –list`
STDIN.readlines.each do |x|
next unless x =~ /Failed.*ssh2/
m = /(\d*\.\d*\.\d*\.\d*)/.match(x)
next unless m and m[0]; addr = m[0]
ip_count[addr] ||= 0; ip_count[addr] += 1
next unless ip_count[addr] > 10
next if blacklisted[addr] or tables.include? addr
`iptables -I INPUT -s #{addr} -j DROP`
blacklisted[addr] = true
end
Since the auth log is called different things depending on your distro, I made the script run from standard input. To make it run every half-hour, run crontab -e and add the following line:
0,30 * * * * cat /var/log/auth.log | ruby /root/ssh_block.rb
Really nice tip! But I think you want to change
tables = `iptables –list`
to
tables = `iptables –list –numeric`
otherwise you will end up with lots of duplicate entries in your iptable since
tables.include? addr
will compare the host name from the table with the IP address from the log, which will fail. (I don’t know any ruby, but I think that’s where the problem is…)
Fredrik Lanker
17 Apr 08 at 12:54 pm
@Fredrik: If you add an IP address into the table, will it do a reverse lookup? I thought that it’d keep whatever you put into it. If that’s not the case then I believe you’re right. Thanks for picking this up!
Paul Betts
17 Apr 08 at 6:20 pm