| | #!/usr/local/bin/python |
---|
| | # Build a report of all unauthorized access attempts |
---|
| | |
---|
| | #################### |
---|
| | # Imports |
---|
| | #################### |
---|
| | |
---|
| | import commands |
---|
| | import re |
---|
| | import sys |
---|
| | |
---|
| | #################### |
---|
| | # Booleans |
---|
| | #################### |
---|
| | |
---|
| | FALSE = 0 == 1 |
---|
| | TRUE = not FALSE |
---|
| | |
---|
| | DONE = FALSE |
---|
| | |
---|
| | #################### |
---|
| | # Constants And Literals |
---|
| | #################### |
---|
| | |
---|
| | ANS = ";; ANSWER SECTION:" |
---|
| | AUTH = ";; AUTHORITY SECTION:" |
---|
| | DIG = "/usr/bin/dig -t ptr -x " |
---|
| | LOG = "/var/log/messages" |
---|
| | OUT = "./abusers" |
---|
| | |
---|
| | VERSION = "$Id: abck,v 1.1 2001/07/16 20:00:50 tundra Exp $" |
---|
| | |
---|
| | #################### |
---|
| | # Data Structures |
---|
| | #################### |
---|
| | |
---|
| | # Dictionary of keywords indicating attack, and position of host address/name |
---|
| | # in their log records |
---|
| | |
---|
| | AttackKeys = { |
---|
| | "refused" : 8, |
---|
| | "unauthorized" : 7 |
---|
| | } |
---|
| | |
---|
| | # Cache dictionary of all attacking hosts discovered this run of the program |
---|
| | |
---|
| | NameCache = {} |
---|
| | |
---|
| | #################### |
---|
| | # Regular Expression Handlers |
---|
| | #################### |
---|
| | |
---|
| | # Regular Expression which describes a legit IP quad address |
---|
| | |
---|
| | IPQuad = r"^((\d{1,3}\.){3}(\d\d?\d?))$" |
---|
| | |
---|
| | #################### |
---|
| | # Function Definitions |
---|
| | #################### |
---|
| | |
---|
| | # Return the ending substring of a host name with 'depth' number of dots |
---|
| | # in it |
---|
| | |
---|
| | def HostDepth(host, depth=2): |
---|
| | |
---|
| | # Break the address down into components |
---|
| | components = host.split(".") |
---|
| | |
---|
| | # And return the recombined pieces we want |
---|
| | return '.'.join(components[-depth:]) |
---|
| | |
---|
| | #################### |
---|
| | |
---|
| | # Check a name, see if it's an IP quad, and if so, return reverse. |
---|
| | # If not, return the original name. |
---|
| | # |
---|
| | # This is better than a socket.gethostbyaddr() call because |
---|
| | # this will return the authority information for an address |
---|
| | # if no explicit reverse resolution is found. |
---|
| | |
---|
| | def CheckIPReverse(hostquad): |
---|
| | |
---|
| | # If it's an IP address in quad form, reverse resolve it |
---|
| | if re.match(IPQuad, hostquad): |
---|
| | |
---|
| | DIGResults = commands.getoutput(DIG + hostquad).splitlines() |
---|
| | |
---|
| | ansname = "" |
---|
| | authname = hostquad |
---|
| | # Results must either have an Answer or Authority record |
---|
| | if ( DIGResults.count(ANS) + DIGResults.count(AUTH)): |
---|
| | |
---|
| | i = 0 |
---|
| | while i < len(DIGResults): |
---|
| | |
---|
| | if DIGResults[i].startswith(ANS): |
---|
| | ansname = DIGResults[i+1].split()[4] |
---|
| | |
---|
| | if DIGResults[i].startswith(AUTH): |
---|
| | authname = DIGResults[i+1].split()[-2] |
---|
| | |
---|
| | i += 1 |
---|
| | |
---|
| | if ansname: |
---|
| | hostname = ansname |
---|
| | else: |
---|
| | hostname = authname |
---|
| | |
---|
| | # Get rid of trailing dot, if any |
---|
| | if hostname[-1] == '.': |
---|
| | hostname = hostname[:-1] |
---|
| | |
---|
| | else: |
---|
| | hostname = hostquad |
---|
| | |
---|
| | return hostname |
---|
| | |
---|
| | |
---|
| | #################### |
---|
| | |
---|
| | # Paw through a log record, doing any reverse rosolution needed, |
---|
| | # confirm with user, and return name of the host to notify about |
---|
| | # the instrusion attempt. A null return means the user want to |
---|
| | # skip this record. |
---|
| | |
---|
| | |
---|
| | def ProcessLogRecord(logrecord): |
---|
| | |
---|
| | # Check for each known attack keyword |
---|
| | |
---|
| | sendto = "" |
---|
| | for attackkey in AttackKeys.keys(): |
---|
| | |
---|
| | logfield = logrecord.split() |
---|
| | if logrecord.count(attackkey): |
---|
| | |
---|
| | # Different attack records put the hostquad in different places |
---|
| | hostquad = logfield[AttackKeys[attackkey]] |
---|
| | if hostquad[-1] == ',': |
---|
| | hostquad = hostquad[:-1] # Strip trailing dots |
---|
| | |
---|
| | # Go do a reverse resolution if we need to |
---|
| | hostname = CheckIPReverse(hostquad) |
---|
| | |
---|
| | # Check for the case of getting a PTR record back |
---|
| | hostname = ReversePTR(hostname) |
---|
| | |
---|
| | # Check if we've seen this abuser before |
---|
| | if NameCache.has_key(hostname): |
---|
| | sendto = NameCache[hostname] |
---|
| | |
---|
| | # New one |
---|
| | else: |
---|
| | depth = 2 |
---|
| | DONE=FALSE |
---|
| | while not DONE: |
---|
| | |
---|
| | # Set depth of default response |
---|
| | default = HostDepth(hostname, depth) |
---|
| | |
---|
| | # Ask the user about it |
---|
| | st = raw_input("Who Gets Message for: <%s>? %s [%s] " % |
---|
| | (hostname[-40:], |
---|
| | " " * (40 - len(hostname)), |
---|
| | default)) |
---|
| | |
---|
| | # Parse the response |
---|
| | if st == "!": # Skip this record |
---|
| | sendto = "" |
---|
| | DONE = TRUE |
---|
| | |
---|
| | elif st.lower() == "r": # Less depth in recipient name |
---|
| | if depth > 2: |
---|
| | depth -= 1 |
---|
| | |
---|
| | elif st.lower() == "l": # More depth in recipient name |
---|
| | if depth < len(hostname.split('.')): |
---|
| | depth += 1 |
---|
| | |
---|
| | else: |
---|
| | if st: # User keyed in their own recipient |
---|
| | sendto = st |
---|
| | else: # User accepted the default |
---|
| | sendto = default |
---|
| | DONE = TRUE |
---|
| | |
---|
| | NameCache[hostname] = sendto # Cache it |
---|
| | |
---|
| | return sendto |
---|
| | |
---|
| | #################### |
---|
| | |
---|
| | # Check the passed hostname and see if it looks like a PTR record. |
---|
| | # If so, strip out the address portion, reverse it, and trying |
---|
| | # doing another reverse lookup. If not, just return the original hostname. |
---|
| | |
---|
| | def ReversePTR(hostname): |
---|
| | |
---|
| | tmp = hostname.split("in-addr.arpa") |
---|
| | |
---|
| | if len(tmp) > 1: # Looks like a PTR record |
---|
| | |
---|
| | tmp = tmp[0].split('.') # Get addr components |
---|
| | tmp.reverse() # and reverse their order |
---|
| | # Take the 1st four quads (some PTR records have more) |
---|
| | # and see if we can dig out a reverse. |
---|
| | hostname = CheckIPReverse('.'.join(tmp[1:5])) |
---|
| | |
---|
| | return hostname |
---|
| | |
---|
| | |
---|
| | #------------------------- Program Entry And Mail Loop -----------------------# |
---|
| | |
---|
| | logfile = open(LOG) |
---|
| | abuserfile = open(OUT, "w") |
---|
| | |
---|
| | # Read in the whole log logfileile |
---|
| | for logrecord in logfile.read().splitlines(): |
---|
| | |
---|
| | # Go check the record in no command line constraint given |
---|
| | # or a constraint is given and exits in the record |
---|
| | if not sys.argv[1] or logrecord.count(sys.argv[1]): |
---|
| | |
---|
| | sendto = ProcessLogRecord(logrecord) |
---|
| | if sendto: |
---|
| | abuserfile.write("abnot \"" + logrecord + "\" " + |
---|
| | sendto + "\n") |
---|
| | |
---|
| | logfile.close() |
---|
| | abuserfile.close() |
---|
| | |
---|
| | |