I wanted to improve this script that I had originally written in bash and I took the opportunity to learn python (three days).
Please, comment! I need criticism… more precisely about the thread management, I understand that’s bad programming.
In fact the portscan takes about 4 minutes with 50 hosts… but work!
Usage of the script:
usage: python authfail.py FILE [FILE] Parse an auth.log FILE and return the ascending stats list with geolocalization about ip addresses that cause a sshd's auth failure. If you specify the [FILE] it checks if IP addresses which attacks came from and have sshd's port 22 open and save the results on it.
The auth.log file path is:
/var/log/auth.log
however may change…
This software is under GPLv3 license.
"""
Copyright (C) 2011 Simone Aonzo
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
import threading, Queue
import urllib2
import socket
import socket
import sys
import re
# Keyword in auth.log for invalid login
authFail = "Failed password for invalid user"
# Regex of ip address (tnx evilsocket for compile suggest!)
ipRegEx = re.compile(r"\b(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b")
# Website that provide api
apiHostIp = "http://api.hostip.info/get_html.php?ip="
# Dictionary and List initializer
dictBase = {}
listAtt = []
# sshd port
SSHPORT = 22
#scan flag
SCAN = False
# Usage
help = """
AuthFail 1.0 - by Six110
http://sixthevicious.wordpress.com/
========================================
usage:
python authfail.py FILE [FILE]
Parse an auth.log FILE and return the ascending stats list with
geolocalization about ip addresses that cause a sshd's auth failure.
If you specify the [FILE] it checks if IP addresses which attacks
came from and have sshd's port 22 open and save the results on it.
This software is released under GPLv3 license.
"""
# Check if input string match the regexp
def ipFormatChk (ipStr):
if ipRegEx.match(ipStr):
return True
else:
return False
# Convert a list of tuple into the ip address string
def fromRegexToString (line):
return (" ".join( ipRegEx.findall(line)[0] )).replace(" ",".")
# Class with data of the attacker
class Attacker(object):
def __init__(self, ip, hits, country, city, isopenssh=None):
self.ip = ip
self.hits = hits
self.country = country
self.city = city
self.isopenssh = isopenssh
# Class that implement portscanning with threading
class Scanner(threading.Thread):
def __init__(self, inq, outq):
threading.Thread.__init__(self)
self.setDaemon(1)
# queues for Attackers
self.inq = inq
self.outq = outq
def run(self):
while True:
# Retrive attacker from queue
a = self.inq.get()
host, port = a.ip, SSHPORT
sd = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
sd.connect((host, port))
except socket.error:
a.isopenssh = "False"
self.outq.put(a)
else:
a.isopenssh = "True"
self.outq.put(a)
sd.close()
#======================================================================#
alen = len(sys.argv)
if (alen < 2) or (alen > 3):
print help
quit()
if (alen == 3):
try:
fileOUT = open (sys.argv[2], 'w')
SCAN = True
except:
fileOUT = None
print "Error! I can't write this file -> " + sys.argv[2]
else:
fileOUT = None
try:
fileIN = open(sys.argv[1], "r")
except:
fileIN = None
print "Error! I can't read this file -> " + sys.argv[1]
sys.exit(1)
line = fileIN.readline()
# Read each line of the file
# if found an ip it search the dictionary for the entry
# if it's present, increment the counter
# otherwise add the ip and initialize the counter
while line:
if (line.find(authFail) != -1):
ipFound = fromRegexToString (line)
if ( ipFound in dictBase):
dictBase[ipFound] = dictBase.get(ipFound)+1
else :
dictBase[ipFound] = 1
line = fileIN.readline()
# Close the input files
fileIN.close()
items = 0
# Create the classes and add them to the list counting the entries
for ip, hits in dictBase.iteritems():
resp = urllib2.urlopen( apiHostIp+ip ).read()
country = resp [ resp.find('Country: ')+9 : resp.find('\n') ]
city = resp [ resp.find('City: ')+6 : resp.find('\n',resp.find('City: ')) ]
listAtt.append( Attacker(ip, hits, country, city) )
items += 1
# Free the dictionary
dictBase.clear()
# Start ssh scanner
if (SCAN):
toscan = Queue.Queue()
scanned = Queue.Queue()
scanners = [Scanner(toscan, scanned) for i in range(items)]
for scanner in scanners:
scanner.start()
for a in listAtt:
toscan.put(a)
listAtt.count
for c in range(0,items):
try:
scanned.get(16)
except Queue.Empty:
c = items
# Redefines the sort function with the total order relation
# for confront two Attacker classes
listAtt.sort (lambda x, y: cmp(y.hits, x.hits) )
for a in listAtt:
if ( fileOUT != None and SCAN == True and a.isopenssh == "True"):
fileOUT.write(a.ip+"\n")
print "\nIp: "+ a.ip + "\nHits: %d"% a.hits + "\nPort %s open: "%SSHPORT + str(a.isopenssh) + "\nCountry: %s"%a.country + "\nCity: %s\n"%a.city
# Close the output file
if (fileOUT != None):
fileOUT.close()
