#!/usr/bin/python # AttackLog.py - Collect and format the "Unrecognized access" log from the SMC 7004BR router. # Usage: AttackLog.py [logfilename] # If logfilename is not given, the script formats the cache kept by the router # and then exits. If a log file name is given, the script will poll the router # three times a day, and put the results in the log file. # Copywright (c) 2003 - John W. Peterson (linux AT saccade DOT com) # Permission is granted to copy and use, so long as this copywrite notice # remains intact, and any improvements are forwarded back to the original author. import urllib, socket, time, os, sgmllib, htmllib, formatter, re, string, sys # This is the default address on the local network for the SMC router's home page. SMChost = '192.168.123.254' # The SMC router requires a password to access the log data. If the environment # variable "SMCPassword" is set, then that will be used for the password, otherwise # the script will prompt for it. def fold(s): """Wrap string s a bit to improve readability""" while len(s) > 0: print s[:80] s = s[80:] def safeGetHostname(ip_addr): """Get the hostname, but return UNKNOWN if DNS fails""" try: # gethostbyaddr is just too dog slow on Windows if (sys.platform != 'win32'): return socket.gethostbyaddr(ip_addr)[0] else: return ip_addr except: return 'UNKNOWN (%s)' % ip_addr class menuParser(htmllib.HTMLParser): """HTML parser class to extract the login information out of the SMC home page""" def __init__(self, router_password): htmllib.HTMLParser.__init__( self, formatter.NullFormatter() ) self.loginData = [] self.router_password = router_password def do_input( self, attrs ): """Grab all of the INPUT tags in order to build the POST data""" d = dict(attrs) if (d['type'] == 'HIDDEN'): self.loginData.append((d['name'], d['value'])) if (d['name'] == 'PSWD'): self.loginPSWD = d['value'] # This changes on each invocation if (d['type'] == 'PASSWORD'): self.loginData.append((d['name'], self.router_password)) def LoadPortDictionary(): """Load the IANA (http://www.iana.org/assignments/port-numbers) port numbers as a guide""" # The port-numbers file uses the syntax: # portname number/TYPE Description # where TYPE = TCP | UDB port_dict = {} portRegexp = re.compile('([^ \t]+)[ \t]+([0-9]+)/([tu][dc]p)') f = file('port-numbers.txt','r') lastPort = -99 while 1: s = f.readline() if (s == ''): break if (s[0] != '#'): p = portRegexp.search(s) if (p): port = int(p.group(2)) if (port != lastPort): port_dict[port] = p.group(1) f.close() return port_dict PortDict = LoadPortDictionary() def ReportAttacks(sinceTime = 0, logFile = None): """Query the SMC Barricade router for attacks on it, and report all attacks since sinceTime""" passwd = os.getenv('SMCPassword') if (passwd == None): passwd = raw_input('SMC Password:') os.environ['SMCPassword'] = passwd mp = menuParser( passwd ) login = urllib.urlopen('http://%s/menu.htm' % SMChost) loginText = login.read() mp.feed(loginText) login.close() mp.loginData = urllib.urlencode(mp.loginData) loginStr = """POST /cgi-bin/logi HTTP/1.1 Content-Type: application/x-www-form-urlencoded User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; .NET CLR 1.1.4322) Host: %s Content-Length: %d """ % (SMChost, len(mp.loginData)) if (loginText.find('Log in') > 0): # Log into the SMC... # Using urllib.urlopen waits for the status response before sending the # actual POST data. This hangs with the SMC server, which seems to expect # the data and header in one go. #login2 = urllib.urlopen('http://%s/cgi-bin/logi' % SMChost, mp.loginData) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((SMChost,80)) s.send(loginStr + mp.loginData) s.recv(2024) # Should check for successful login s.close() attackData = urllib.urlopen('http://%s/syslog.htm' % SMChost) attackText = attackData.read() # The data coming back from the SMC is actually a bunch of JavaScript code # that gets turned into a web page on the fly. raw_attacks = attackText.split(';') attacks = [] # This will parse the time (ms since now), TCP address and port out of the 'Unrecognized access' string logRegexp = re.compile("L2\(([0-9]+),.*from ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+):[0-9]+ to ([TU][DC]P) port ([0-9]+)'") now = time.time() if (logFile): log = file(logFile, 'a') log.write('----Checking at %s------\n' % time.strftime("%d-%b-%y %I:%M %p", time.localtime())) for a in raw_attacks: if a.find('Unrecognized access') != -1: m = logRegexp.search(a) if (m): when = now - int(m.group(1))/1000.0 if (when > sinceTime): whenStr = time.strftime("%d-%b-%y %H:%M:%S",time.localtime(when)) host = safeGetHostname(m.group(2)) kind = m.group(3) port = int(m.group(4)) if (PortDict.has_key(port)): portDesc = PortDict[port] else: portDesc = "unknown(%d)" % port attack = "%s (%10.2f) %s (%s) %s" % (whenStr, when, portDesc, kind, host) if (logFile): log.write(attack + "\n") else: print attack if (logFile): log.close() return when if (len(sys.argv) > 1): lastCheck = 0 while (1): lastCheck = ReportAttacks( lastCheck, sys.argv[1] ) time.sleep(8 * 3600) else: print ReportAttacks()