Reducing SSH Login Attacks

For the last 10 years or so I’ve managed some kind of a Linux server environment. One of the biggest annoyances are constant brute force attacks on the SSH interface. One solution is to change the port, but that I just find to be not needed. So, in an effort to reduce the amount of attacks I have on my server, I wrote a little script many years ago in PHP that scanned the SSH log for failed log in attempts.

If an IP failed to login a certain number of times it was added to the “hosts.deny” file of the server, instantly dropping all connections from that IP and never talking to it again unless manually removed.

Recently I rewrote the PHP script in Python. There was really only one reason I did this, system resource utilization optimization. PHP, as great as it is, has to run according to the limits set within the PHP.INI file. Running two different versions of the file, one for the web environment and one for CLI just wasn’t worth it to me. So I rewrote it in Python to allow for better RAM usage. I also did some testing of the PHP script vs the Python script and the Python version seemed to run faster overall. So, I saved myself RAM usage and CPU usage. Was a good switch. Below is the Python script. The main stuff is fairly well spelled out as to what the variables do. Just put this baby on your Linux system and setup a cron job to process it. I have mine running hourly.

#!/usr/bin/python
import re

# Safe allowed IPs that can fail log in attempts, comma separated array
AllowedIPs = []

# SSH Log Files, comma separated array
SSHLogsArray = ['/var/log/secure']

# Ban Threshold, above this failed login attempts, the IP is banned
BanThreshold = 3

# Hosts.deny file
HostsDenyFile = '/etc/hosts.deny'

##### DO NOT EDIT BELOW HERE #####

ScannedIPs = []
RootIPBans = []
BannedIPs = []

# Load Already Banned IPs
def LoadHostsDeny():
	lines = [line.rstrip('\n') for line in open(HostsDenyFile)]
	for line in lines:
		ip_addr = re.search('([0-2]{0,1}[0-9]{0,1}[0-9]\.[0-2]{0,1}[0-9]{0,1}[0-9]\.[0-2]{0,1}[0-9]{0,1}[0-9]\.[0-2]{0,1}[0-9]{0,1}[0-9])', line)
		if ip_addr:
			BannedIPs.append(line[ip_addr.start():])
	print "Loaded "+str(len(BannedIPs))+" Banned IPs"
		
# Write IP Bans
def WriteHostsDeny():
	hostfile = open(HostsDenyFile, 'w');
	for bip in BannedIPs:
		hostfile.write('ALL: '+bip+'\n')
	hostfile.close()
	print "Wrote "+str(len(BannedIPs))+" Banned IPs"

# Print out the Banned IP list
def PrintIPList():
	print "Listing out Banned IPs (Total: "+str(len(BannedIPs))+")"
	for ip in BannedIPs:
		print ip

# Check for new bans and add them
def CheckSSHIPBans():
	for sip in ScannedIPs:
		if ScannedIPs.count(sip) > BanThreshold:
			ip_count = BannedIPs.count(sip)
			
			if ip_count == 0:
				BannedIPs.append(sip)

	for rip in RootIPBans:
		ip_count = BannedIPs.count(rip)
		
		if ip_count == 0:
			BannedIPs.append(rip)

# Scan SSH Logs
def ScanSSHLogs():
	for log in SSHLogsArray:
		lines = [line.rstrip('\n') for line in open(log)]
		for line in lines:
			ip_addr = re.search('([0-2]{0,1}[0-9]{0,1}[0-9]\.[0-2]{0,1}[0-9]{0,1}[0-9]\.[0-2]{0,1}[0-9]{0,1}[0-9]\.[0-2]{0,1}[0-9]{0,1}[0-9])', line)
			failed_login = re.search('Failed password for', line)
			root_login = re.search('Failed password for root', line)
			if ip_addr:
				ip = line[ip_addr.start():ip_addr.end()]
				
			if ip and failed_login:
				allowed_ips = 0
				for aip in AllowedIPs:
					if aip == ip:
						allowed_ips = 1
				if allowed_ips == 0:
					ScannedIPs.append(ip)
						
			if root_login:
				allowed_ips = 0
				for aip in AllowedIPs:
					if aip == ip:
						allowed_ips = 1
				if allowed_ips == 0:
					RootIPBans.append(ip)

LoadHostsDeny()
ScanSSHLogs()
CheckIPBans()
WriteHostsDeny()
PrintIPList()
23,003 views
Back To Top