#!/usr/bin/python # # Author: Brian Kellogg # Version: 1.3 # # Intel file MUST have the below header and it MUST be TAB DELIMITED # #fields indicator indicator_type meta.source meta.desc meta.url meta.do_notice meta.if_in # # This script was written for Bro on SecurityOnion # import sys from optparse import OptionParser import re import os import stat # global var to hold file name to be modified intelFile = "" # Location of intel files in SecurityOnion # This directory is replicated to all SecurityOnion sensors intelLoc = "/opt/bro/share/bro/policy/" # regex to match first three octets of IP including trailing "." regIP = re.compile("^(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]?)\.$") # string colourizations # from http://korbinin.blogspot.com/2012/10/color-text-output-from-python.html def hilite(string, status, bold): attr = [] if(sys.stdout.isatty()): if status=='g': # green attr.append('32') elif status=='r': # red attr.append('31') elif status=='y': # yellow attr.append('33') elif status=='b': # blue attr.append('34') elif status=='m': # magenta attr.append('35') if bold: attr.append('1') return '\x1b[%sm%s\x1b[0m' % (';'.join(attr), string) else: return(string) # check if file exists and is writeable # if file is does not exist then ask to create it otherwise exit def EandW(): if os.path.isfile(intelFile): try: f = open(intelFile, 'a+') f.close() except: print(hilite("\nFile is not writeable!\n", "r", True)) exit(4) else: print(hilite("\nFile does not exist!\n", "r", True)) create = raw_input("Create intel file (y/n)? ") if create == 'y': try: f = open(intelFile, 'w+') f.write('#fields\tindicator\tindicator_type\tmeta.source\tmeta.desc\tmeta.url\tmeta.do_notice\tmeta.if_in\n') f.close except: print(hilite("\nCould not create file!\n", "r", True)) exit(4) else: exit(0) # add IP(s) to intel file def addIP(addr, begIP, endIP, source, desc, url, notice, ifIn): # Open file for reading and appending f = open(intelFile,"a+") lines = f.readlines() # Iterate through the defined IP range for lastOctet in range(begIP,endIP + 1): found = False # Create the four octet IP; convert lastOctet to string for concatenation IP = addr + str(lastOctet) # Lets see if the IP already exists in the file for line in lines: # regex to locate IP in string regIP = re.compile(r'' + re.escape(IP) + '\\t') if re.match(regIP, line): found = True print(hilite("%s already exists in file!", "r", True) % (IP)) # if we get a match then lets go to next line in file break # write line to file if not found; file MUST be tab delimited if not found: f.write('%s\t%s\t%s\t%s\t%s\t%s\t%s\n' % (IP,"Intel::ADDR",source,desc,url,notice,ifIn)) print(hilite("Added %s", "y", True) % (IP)) f.close() # remove IP(s) from intel file def remIP(addr, begIP, endIP): # Open Bro intel file for appending f = open(intelFile,"r") lines = f.readlines() f.close() # open intel file for writing f = open(intelFile,"w") for line in lines: found = False # regex to locate IP in string regIP = re.compile(r'' + re.escape(addr)) # write lines back to file that don't match if re.match(regIP, line): # once we find the first occurrence lets loop through and only write non-matching lines for lastOctet in range(begIP,endIP + 1): # Create the four octet IP; convert lastOctet to string for concatenation IP = addr + str(lastOctet) # regex to locate IP in string regIP = re.compile(r'' + re.escape(IP) + '\\t') # if we get a match then lets go to next line in file if re.match(regIP, line): print(hilite("Removed %s!", "y", True) % (IP)) found = True # tell the first for loop we found it so it doesn't writ the line break # write line back to file if not found if not found: f.write(line) f.close() # choose where to look for the intel def getIfIn(): cont = True while cont: print(""" Choose where to look for intel: ******************************** 1. Conn::IN_ORIG 2. Conn::IN_RESP 3. Files::IN_HASH 4. Files::IN_NAME 5. DNS::IN_REQUEST 6. DNS::IN_RESPONSE 7. HTTP::IN_HOST_HEADER 8. HTTP::IN_REFERRER_HEADER 9. HTTP::IN_USER_AGENT_HEADER 10. HTTP::IN_X_FORWARDED_FOR_HEADER 11. HTTP::IN_URL 12. SMTP::IN_MAIL_FROM 13. SMTP::IN_RCPT_TO 14. SMTP::IN_FROM 15. SMTP::IN_TO 16. SMTP::IN_RECEIVED_HEADER 17. SMTP::IN_REPLY_TO 18. SMTP::IN_X_ORIGINATING_IP_HEADER 19. SMTP::IN_MESSAGE 20. SSL::IN_SERVER_CERT 21. SSL::IN_CLIENT_CERT 22. SSL::IN_SERVER_NAME 23. SMTP::IN_HEADER 24. Leave Blank """) ans = raw_input("Choice? ") if ans == "1": return "Conn::IN_ORIG" elif ans == "2": return "Conn::IN_RESP" elif ans == "3": return "Files::IN_HASH" elif ans == "4": return "Files::IN_NAME" elif ans == "5": return "DNS::IN_REQUEST" elif ans == "6": return "DNS::IN_RESPONSE" elif ans == "7": return "HTTP::IN_HOST_HEADER" elif ans == "8": return "HTTP::IN_REFERRER_HEADER" elif ans == "9": return "HTTP::IN_USER_AGENT_HEADER" elif ans == "10": return "HTTP::IN_X_FORWARDED_FOR_HEADER" elif ans == "11": return "HTTP::IN_URL" elif ans == "12": return "SMTP::IN_MAIL_FROM" elif ans == "13": return "SMTP::IN_RCPT_TO" elif ans == "14": return "SMTP::IN_FROM" elif ans == "15": return "SMTP::IN_TO" elif ans == "16": return "SMTP::IN_RECEIVED_HEADER" elif ans == "17": return "SMTP::IN_REPLY_TO" elif ans == "18": return "SMTP::IN_X_ORIGINATING_IP_HEADER" elif ans == "19": return "SMTP::IN_MESSAGE" elif ans == "20": return "SSL::IN_SERVER_CERT" elif ans == "21": return "SSL::IN_CLIENT_CERT" elif ans == "22": return "SSL::IN_SERVER_NAME" elif ans == "23": return "SMTP::IN_HEADER" elif ans == "24": return "-" else: print(hilite("\nInvalid input!", "r", True)) # get all the info needed to add the intel def getInfo(): desc = raw_input("Description? ") if not desc: desc = "-" source = raw_input("Source? ") if not source: source = "-" url = raw_input("URL? ") if not url: url = "-" notice = raw_input("Do notice (T)? ") notice = notice.upper() if notice != "T" or notice != "F": notice = "T" ifIn = getIfIn() return (source, desc, url, notice, ifIn) # add all other types of intel def addMisc(header, type): print("\n%s" % (header)) print("----------------------------") intel = raw_input("Intel to add? ") source, desc, url, notice, ifIn = getInfo() f = open(intelFile,"a+") lines = f.readlines() # Lets see if this intel is already in the file for line in lines: if intel in line: print(hilite("%s already exists in file!", "r", True) % (IP)) # if we get a match then exit return # write line to file if not found; file MUST be tab delimited f.write('%s\t%s\t%s\t%s\t%s\t%s\t%s\n' % (intel,type,source,desc,url,notice,ifIn)) print(hilite("Added %s", "y", True) % (intel)) f.close() # remove misc intel type def remMisc(header): print("\n%s" % (header)) print("----------------------------") intel = raw_input("Intel to remove? ") # Open Bro intel file to find intel to remove f = open(intelFile,"r") lines = f.readlines() f.close() # open intel file for writing f = open(intelFile,"w") for line in lines: # skip matching line we want to remove if intel in line: print(hilite("Removed %s!", "y", True) % (intel)) continue # write line back to file if not a match f.write(line) f.close() # Get user input and run correct function to add or remove intel IP def doIP(header, begText, singleIP, add): print("\n%s" % (header)) print("----------------------------") addr = raw_input("First three octets including the trailing . ? ") # need to convert beginning IP to int for comparison begIP = int(raw_input(begText)) # if singleIP is TRUE then then set endIP = begIP if singleIP: endIP = begIP else: # need to convert ending IP to int for comparison endIP = int(raw_input("Last IP in last octet? ")) # is the IP information valid, if not return to main menu if (begIP < 0 or endIP > 255 or not(re.match(regIP,addr))): print(hilite("\n\nInvalid IP information.", "r", True)) return if add: source, desc, url, notice, ifIn = getInfo() print(hilite("\n------------RESULTS---------------", "y", True)) if add: addIP(addr, begIP, endIP, source, desc, url, notice, ifIn) else: remIP(addr, begIP, endIP) def mainMenu(): cont = True while cont: # triple quotes = multi-line print print(""" Intel Update: ############################## 1. Add single intel IP 2. Add range of intel IPs 3. Remove single intel IP 4. Remove range of intel IPs 5. Add URL 6. Remove URL 7. Add Software 8. Remove Software 9. Add Email 10. Remove Email 11. Add Domain 12. Remove Domain 13. Add Username 14. Remove Username 15. Add File Hash 16. Remove File Hash 17. Add File Name 18. Remove File Name 19. Add Cert Hash 20. Remove Cert Hash q. Quit ############################## """) ans = raw_input("Choice? ") if ans == "1": doIP("\nAdd single intel IP:", "Last octet? ", True, True) elif ans == "2": doIP("\nAdd range of intel IPs:", "First IP in last octet? ", False, True) elif ans == "3": doIP("\nRemove single intel IP:", "Last octet? ", True, False) elif ans == "4": doIP("\nRemove range of intel IPs:", "First IP in last octet? ", False, False) elif ans == "5": addMisc("\nAdd URL:", "Intel::URL") elif ans == "6": remMisc("\nRemove URL:") elif ans == "7": addMisc("\nAdd software:", "Intel::SOFTWARE") elif ans == "8": remMisc("\nRemove software:") elif ans == "9": addMisc("\nAdd Email:", "Intel::EMAIL") elif ans == "10": remMisc("\nRemove Email:") elif ans == "11": addMisc("\nAdd domain:", "Intel::DOMAIN") elif ans == "12": remMisc("\nRemove domain:") elif ans == "13": addMisc("\nAdd username:", "Intel::USER_NAME") elif ans == "14": remMisc("\nRemove username:") elif ans == "15": addMisc("\nAdd file hash:", "Intel::FILE_HASH") elif ans == "16": remMisc("\nRemove file hash:") elif ans == "17": addMisc("\nAdd file name:", "Intel::FILE_NAME") elif ans == "18": remMisc("\nRemove file name:") elif ans == "19": addMisc("\nAdd Cert hash:", "Intel::CERT_HASH") elif ans == "20": remMisc("\nRemove Cert hash:") elif ans == "q": exit(0) else: print(hilite("\nInvalid input!", "r", True)) def main(): usage = """ usage: %prog -f filename All bro intel files must be located in /opt/bro/share/bro/policy to update them with this script. Header of the intel file must be: #fields\tindicator\tindicator_type\tmeta.source\tmeta.desc\tmeta.url\tmeta.do_notice\tmeta.if_in Remember Bro intel files MUST be tab delimited!!! This script will create the file if you direct it to and add the header if the intel file does not exist. Any Bro intel file must be loaded into Bro to be used. Example, add below to /opt/bro/share/bro/site/local.bro in order to load the intel1.txt and intel2.txt custom files: redef Intel::read_files += { "/opt/bro/share/bro/policy/intel1.txt", "/opt/bro/share/bro/policy/intel2.txt", }; Bro adds new intel but does not remove without a restart: sudo broctl install sudo broctl restart """ parser = OptionParser(usage=usage) parser.add_option('-f', '--File', action='store', type='string', dest='filename', help='Bro intel file name to modify') (options, args) = parser.parse_args() if not options.filename: parser.error('Filename not given') # set global intel file variable for update global intelFile # add intel file location to file name user entered intelFile = intelLoc + options.filename # check if file exists and is writeable EandW() # goto main menu mainMenu() if __name__ == '__main__': main()