def test_return_translations_from_multiple_tables(self): dicts = Dicts([ExampleTable1(), ExampleTable2()]) self.assertEqual( [ExampleTable1.SPAM_TRANS, ExampleTable2.SPAM_TRANS], dicts.lookup('spam')) self.assertEqual( [ExampleTable1.BREAKFAST_TRANS, ExampleTable2.BREAKFAST_TRANS], dicts.lookup('breakfast'))
def __init__(self, rule): self.dicts = Dicts() self.rule = rule self.header = self.parse_header() self.options = self.parse_options() self.validate_options(self.options) self.data = {"header": self.header, "options": self.options} self.all = self.data
class Parser(object): ''' this will take an array of lines and parse it and hand back a dictionary NOTE: if you pass an invalid rule to the parser, it will a raise ValueError. ''' def __init__(self, rule): self.dicts = Dicts() self.rule = rule self.header = self.parse_header() self.options = self.parse_options() self.validate_options(self.options) self.data = {"header": self.header, "options": self.options} self.all = self.data def __iter__(self): yield self.data def __getitem__(self, key): if key is 'all': return self.data else: return self.data[key] @staticmethod def actions(action: str) -> str: actions = { "alert", "log", "pass", "activate", "dynamic", "drop", "reject", "sdrop" } if action in actions: return action else: msg = "Invalid action specified %s" % action raise ValueError(msg) @staticmethod def proto(proto: str) -> str: protos = {"tcp", "udp", "icmp", "ip"} if proto.lower() in protos: return proto else: msg = "Unsupported Protocol %s " % proto raise ValueError(msg) @staticmethod def __ip_to_tuple(ip: str) -> Tuple: if ip.startswith("!"): ip = ip.lstrip("!") return False, ip else: return True, ip def __form_ip_list(self, ip_list: str) -> List: ip_list = ip_list.split(",") ips = [] for ip in ip_list: ips.append(self.__ip_to_tuple(ip)) return ips def __flatten_ip(self, ip): list_deny = True if ip.startswith("!"): list_deny = False ip = ip.lstrip("!") _ip_list = [] _not_nest = True ip = re.sub(r'^\[|\]$', '', ip) ip = re.sub(r'"', '', ip) if re.search(r"(\[.*\])", ip): _not_nest = False nest = re.split(r",(!?\[.*\])", ip) nest = filter(None, nest) # unnest from _ip_list _return_ips = [] for item in nest: if re.match(r"^\[|^!\[", item): nested = self.__flatten_ip(item) _return_ips.append(nested) continue else: _ip_list = self.__form_ip_list(item) for _ip in _ip_list: _return_ips.append(_ip) return list_deny, _return_ips if _not_nest: _ip_list = self.__form_ip_list(ip) return list_deny, _ip_list def __validate_ip(self, ips): variables = { "$EXTERNAL_NET", "$HTTP_SERVERS", "$INTERNAL_NET", "$SQL_SERVERS", "$SMTP_SERVERS", "$DNS_SERVERS", "$HOME_NET", "HOME_NET", "any" } for item in ips: if isinstance(item, bool): pass if isinstance(item, list): for ip in item: self.__validate_ip(ip) if isinstance(item, str): if item not in variables: if "/" in item: ipaddress.ip_network(item) else: ipaddress.ip_address(item) return True def ip(self, ip): if isinstance(ip, str): ip = ip.strip('"') if re.search(r",", ip): item = self.__flatten_ip(ip) ip = item else: ip = self.__ip_to_tuple(ip) valid = self.__validate_ip(ip) if valid: return ip else: raise ValueError("Unvalid ip or variable: %s" % ip) @staticmethod def port(port): variables = {"any", "$HTTP_PORTS"} # is the source marked as not if port.startswith("!"): if_not = False port = port.strip("!") else: if_not = True # is it a list ? # if it is, then make it a list from the string """ Snort allows for ports marked between square brackets and are used to define lists correct: >> [80:443,!90,8080] >> ![80:443] >> [!80:443] """ if port.startswith("["): if port.endswith("]"): port = port[1:-1].split(",") else: raise ValueError("Port list is malformed") if isinstance(port, list): ports = [] for item in port: not_range = True if ":" in item: # Checking later on if port is [prt:] or [:prt] open_range = False items = item.split(":", 1) message = '' for prt in items: message = "Port range is malformed %s" % item prt = prt.lstrip("!") if not prt: open_range = True continue try: prt = int(prt) except: raise ValueError(message) if prt < 0 or prt > 65535: raise ValueError(message) for index, value in enumerate(items): value = value.lstrip("!") items[index] = value if not open_range: try: a = int(items[-1]) b = int(items[0]) except: raise ValueError(message) if a - b < 0: raise ValueError(message) not_range = False port_not = True if re.search("^!", item): port_not = False item = item.strip("!") if not_range: if item.lower() or item in variables: ports.append((port_not, item)) continue try: prt = int(item) if prt < 0 or prt > 65535: raise ValueError( "Port is out of range {}".format(item)) except ValueError: raise ValueError("Unknown port {}".format(item)) ports.append((port_not, item)) return if_not, ports if isinstance(port, str): """ Parsing ports like: :8080, 80:, 80:443 and passes all variables ex: $HTTP ranges do not accept denial (!) """ if port or port.lower() in variables or re.search(r"^\$+", port): return if_not, port if re.search(":", port): message = "Port is out of range %s" % port ports = port.split(":") for portl in ports: portl.lstrip("!") if not portl: continue if portl or portl.lower() in variables: continue try: portl = int(portl) except ValueError: raise ValueError(message) if portl < 0 or portl > 65535: raise ValueError(message) return if_not, port """ Parsing a single port single port accepts denial. """ try: if not int(port) > 65535 or int(port) < 0: return if_not, port if int(port) > 65535 or int(port) < 0: raise ValueError except: msg = "Unknown port: \"%s\" " % port raise ValueError(msg) else: message = "Unknown port \"%s\"" % port raise ValueError(message) def destination(self, dst): destinations = {"->": "to_dst", "<>": "bi_direct"} if dst in destinations: return dst else: msg = "Invalid destination variable %s" % dst raise ValueError(msg) def get_header(self): if re.match(r'(^[a-z|A-Z].+?)?(\(.+;\)|;\s\))', self.rule.lstrip()): header = self.rule.split('(', 1) return header[0] else: msg = 'Error in syntax, check if rule'\ 'has been closed properly %s ' % self.rule raise SyntaxError(msg) @staticmethod def remove_leading_spaces(string: str) -> str: return string.strip() def get_options(self): options = "{}".format(self.rule.split('(', 1)[-1].lstrip().rstrip()) if not options.endswith(")"): raise ValueError("Snort rule options is not closed properly, " "you have a syntax error") op_list = list() value = "" option = "" last_char = "" for char in options.rstrip(")"): if char != ";": value = value + char option = option + char if char == ";" and last_char != "\\": op_list.append(option.strip()) value = option = "" last_char = char return op_list def parse_header(self): """ OrderedDict([('action', 'alert'), ('proto', 'tcp'), ('source', \ (True, '$HOME_NET')), ('src_port', (True, 'any')), ('arrow', '->'), \ ('destination', (False, '$EXTERNAL_NET')), ('dst_port', (True, 'any'))]) """ if self.get_header(): header = self.get_header() if re.search(r"[,\[\]]\s", header): header = re.sub(r",\s+", ",", header) header = re.sub(r"\s+,", ",", header) header = re.sub(r"\[\s+", "[", header) header = re.sub(r"\s+\]", "]", header) header = header.split() else: raise ValueError("Header is missing, or unparsable") # get rid of empty list elements header = list(filter(None, header)) header_dict = collections.OrderedDict() size = len(header) if not size == 7 and not size == 1: msg = "Snort rule header is malformed %s" % header raise ValueError(msg) for item in header: if "action" not in header_dict: action = self.actions(item) header_dict["action"] = action continue if "proto" not in header_dict: try: proto = self.proto(item) header_dict["proto"] = proto continue except Exception as perror: raise ValueError(perror) if "source" not in header_dict: try: src_ip = self.ip(item) header_dict["source"] = src_ip continue except Exception as serror: raise ValueError(serror) if "src_port" not in header_dict: src_port = self.port(item) header_dict["src_port"] = src_port continue if "arrow" not in header_dict: dst = self.destination(item) header_dict["arrow"] = dst continue if "destination" not in header_dict: dst_ip = self.ip(item) header_dict["destination"] = dst_ip continue if "dst_port" not in header_dict: dst_port = self.port(item) header_dict["dst_port"] = dst_port continue return header_dict def parse_options(self, rule=None): if rule: self.rule = rule opts = self.get_options() options_dict = collections.OrderedDict() for index, option_string in enumerate(opts): if ':' in option_string: option = option_string.split(":", 1) key, value = option if key is not "pcre": value = value.split(",") options_dict[index] = (key, value) else: options_dict[index] = (option_string, "") return options_dict def validate_options(self, options): for index, option in options.items(): key, value = option if len(value) == 1: content_mod = self.dicts.content_modifiers(value[0]) opt = False if content_mod: # An unfinished feature continue gen_option = self.dicts.options(key) if gen_option: opt = True continue pay_option = self.dicts.options(key) if pay_option: opt = True continue non_pay_option = self.dicts.options(key) if non_pay_option: opt = True continue post_detect = self.dicts.options(key) if post_detect: opt = True continue threshold = self.dicts.options(key) if threshold: opt = True continue if not opt: message = "unrecognized option: %s" % key raise ValueError(message) return options
def test_return_expected_translation(self): dicts = Dicts([ExampleTable1()]) self.assertEqual([ExampleTable1.SPAM_TRANS], dicts.lookup('spam')) self.assertEqual([ExampleTable1.BREAKFAST_TRANS], dicts.lookup('breakfast'))
def test_lookup_returns_word_unchanged_without_tables(self): dicts = Dicts(None) self.assertEqual(['gnarble'], dicts.lookup('gnarble'))
def test_lookup_exists(self): dicts = Dicts(None) dicts.lookup('')
import discord import asyncio import random import json import re import collections, itertools from dicts import Dicts import requests from bs4 import BeautifulSoup dicts = Dicts() class Commands: def __init__(self, client): self._client = client #self.botty_id = 599026500200562712 self.botty_id = 350123637535145986 self.invite_link = 'https://discord.gg/NrxCsKG' async def invite(self, message): await message.channel.send(self.invite_link) async def maze(self, message): random.seed() q1 = '*You know exactly what it is. It\'s the maze, the deepest level of this game. You\'re gonna help me find the entrance.*' q2 = '*You can\'t play God without being acquainted with the devil.*' q3 = '*You know why this beats the real world, Lawrence? The world is chaos. It\'s an accident. But in here, every detail adds up to something. Even you, Lawrence.*' q4 = '*Dreams are mainly memories. Can you imagine how f****d we\'d be if these poor assholes ever remembered what the guests do to them?*' q5 = '*It\'s a very special kind of game, Dolores. The goal is to find the center of it. If you can do that, then maybe you can be free.*' q6 = '*It begins with the birth of a new people, and the choices the\'ll have to make and the people they will decide to become.*'
class Parser(object): ''' this will take an array of lines and parse it and hand back a dictionary NOTE: if you pass an invalid rule to the parser, it will a raise ValueError. ''' def __init__(self, rule=None): if rule: self.dicts = Dicts() self.rule = rule self.utils = Utils() self.header = self.parse_header() self.options = self.parse_options() self.validate_options(self.options) self.data = {"header": self.header, "options": self.options} self.all = self.data def __iter__(self): yield self.data def __getitem__(self, key): if key is 'all': return self.data else: return self.data[key] def actions(self, action): actions = {"alert": "alert", "log": "log", "pass": "******", "activate": "activate", "dynamic": "dynamic", "drop": "drop", "reject": "reject", "sdrop": "sdrop" } if action in actions: return actions[action] else: msg = "Invalid action specified %s" % action raise ValueError(msg) def proto(self, proto): protos = {"tcp": "tcp", "udp": "udp", "icmp": "icmp", "ip": "ip" } if proto in protos: return protos[proto] else: msg = "Unsupported Protocol %s " % proto raise ValueError(msg) def __ip_to_tuple(self, ip): if re.match(r"!", ip): ip = ip.lstrip("!") return (False, ip) else: return (True, ip) def __form_ip_list(self, ip_list): ip_list = ip_list.split(",") ips = [] for ip in ip_list: ips.append(self.__ip_to_tuple(ip)) return ips def __flatten_ip(self, ip): list_deny = True if re.match("^!", ip): list_deny = False ip = ip.strip("!") _ip_list = [] _not_nest = True ip = re.sub(r'^\[|\]$', '', ip) ip = re.sub(r'"', '', ip) if re.search(r"(\[.*\])", ip): _not_nest = False nest = re.split(r",(!?\[.*\])", ip) nest = filter(None, nest) # unnest from _ip_list _return_ips = [] for item in nest: if re.match(r"^\[|^!\[", item): nested = self.__flatten_ip(item) _return_ips.append(nested) continue else: _ip_list = self. __form_ip_list(item) for _ip in _ip_list: _return_ips.append(_ip) return (list_deny, _return_ips) if _not_nest: _ip_list = self. __form_ip_list(ip) return (list_deny, _ip_list) def __validate_ip(self, ips): utils = Utils() variables = {"$EXTERNAL_NET": "$EXTERNAL_NET", "$HTTP_SERVERS": "$HTTP_SERVERS", "$INTERNAL_NET": "$INTERNAL_NET", "$SQL_SERVERS": "$SQL_SERVERS", "$SMTP_SERVERS": "$SMTP_SERVERS", "$DNS_SERVERS": "$DNS_SERVERS", "$HOME_NET": "$HOME_NET", "HOME_NET": "HOME_NET", "any": "any"} # deny_flag = None for item in ips: if isinstance(item, bool): pass # deny_flag = item if isinstance(item, list): for ip in item: ip = self.__validate_ip(ip) if isinstance(ip, list): ip = self.__validate_ip(ip) if isinstance(item, basestring): if item in variables: pass elif utils.valid_ip(item): pass else: raise ValueError("Unknown ip or variable %s" % item) return True def ip(self, ip): if isinstance(ip, basestring): ip = ip.strip('"') if re.search(r",", ip): item = self.__flatten_ip(ip) ip = item else: ip = self.__ip_to_tuple(ip) valid = self.__validate_ip(ip) if valid: return ip else: raise ValueError("Unvalid ip or variable: %s" % ip) def port(self, port): ifnot = None # Possibly needs more variables variables = {"any": "any", "$HTTP_PORTS": "$HTTP_PORTS" } # is the source marked as not if re.search("^!.*", port): ifnot = False port = port.strip("!") else: ifnot = True # is it a list ? # if it is, then make it a list from the string """ Snort allows for ports marked between square prackets and are used to define lists correct: >> [80:443,!90,8080] >> ![80:443] >> [!80:443] """ if re.match("^\[", port): port = re.sub(r'\[|\]', '', port) port = port.split(",") if isinstance(port, list): ports = [] for item in port: not_range = True if re.search(r"\:", item): # Checking later on if port is [prt:] or [:prt] open_range = False items = item.split(":", 1) for prt in items: message = "Port range is malformed %s" % item prt = prt.lstrip("!") if not prt: open_range = True continue try: prt = int(prt) except: raise ValueError(message) if prt < 0 or prt > 65535: raise ValueError(message) for index, value in enumerate(items): value = value.lstrip("!") items[index] = value if not open_range: try: a = int(items[-1]) b = int(items[0]) except: raise ValueError(message) if a - b < 0: raise ValueError(message) not_range = False port_not = True if re.search("^!", item): port_not = False item = item.strip("!") if not_range: message1 = "Port is out of range %s" % item message2 = "Unknown port %s" % item if item in variables: ports.append((port_not, item)) continue try: prt = int(item) if prt < 0 or prt > 65535: raise ValueError(message1) except: raise ValueError(message2) ports.append((port_not, item)) return (ifnot, ports) if isinstance(port, basestring): """ Parsing ports like: :8080, 80:, 80:443 and passes all variables ex: $HTTP ranges do not accept denial (!) """ if port in variables or re.search(r"^\$+", port): return (ifnot, port) if re.search(":", port): message = "Port is out of range %s" % port ports = port.split(":") for portl in ports: portl.lstrip("!") if not portl: continue if portl in variables: continue try: portl = int(portl) except: raise ValueError(message) if portl < 0 or portl > 65535: raise ValueError(message) return (ifnot, port) """ Parsing a single port single port accepts denial. """ try: if not int(port) > 65535 or int[port] < 0: return (ifnot, port) if int(port) > 65535 or int[port] < 0: raise ValueError(message) except: msg = "Unknown port: \"%s\" " % port raise ValueError(msg) else: message = "Unknown port \"%s\"" % port raise ValueError(message) def destination(self, dst): destinations = {"->": "to_dst", "<>": "bi_direct"} if dst in destinations: return dst else: msg = "Invalid destination variable %s" % dst raise ValueError(msg) def get_header(self): if re.match(r"(^[a-z|A-Z].+?)?(\(.+;\)|;\s\))", self.rule): header = self.rule.split('(', 1) return header[0] else: msg = 'Error in syntax, check if rule'\ 'has been closed properly %s ' % self.rule raise SyntaxError(msg) def get_options(self): options = self.rule.split('(', 1) return options[1] def parse_header(self): """ >>> from snortparser import Parser >>> rule = ('alert tcp $HOME_NET any -> !$EXTERNAL_NET \ any (msg:\"MALWARE-BACKDOOR - Dagger_1.4.0\"; \ flow:to_client,established; \ content:\"2|00 00 00 06 00 00 00|Drives|24 00|\"; \ depth:16; metadata:ruleset community; \ classtype:misc-activity; sid:105; rev:14;)') >>> parsed = Parser(rule) >>> parsed.header OrderedDict([('action', 'alert'), ('proto', 'tcp'), ('source', \ (True, '$HOME_NET')), ('src_port', (True, 'any')), ('arrow', '->'), \ ('destination', (False, '$EXTERNAL_NET')), ('dst_port', (True, 'any'))]) """ if self.get_header(): header = self.get_header() if re.search(r"[,\[\]]\s", header): header = re.sub(r",\s+", ",", header) header = re.sub(r"\s+,", ",", header) header = re.sub(r"\[\s+", "[", header) header = re.sub(r"\s+\]", "]", header) header = header.split() else: raise ValueError("Header is missing, or unparsable") # get rid of empty list elements header = filter(None, header) header_dict = collections.OrderedDict() size = len(header) if not size == 7 and not size == 1: msg = "Snort rule header is malformed %s" % header raise ValueError(msg) for item in header: if "action" not in header_dict: action = self.actions(item) header_dict["action"] = action continue if "proto" not in header_dict: try: proto = self.proto(item) header_dict["proto"] = proto continue except Exception as perror: raise ValueError(perror) if "source" not in header_dict: try: src_ip = self.ip(item) header_dict["source"] = src_ip continue except Exception as serror: raise ValueError(serror) if "src_port" not in header_dict: src_port = self.port(item) header_dict["src_port"] = src_port continue if "arrow" not in header_dict: dst = self.destination(item) header_dict["arrow"] = dst continue if "destination" not in header_dict: dst_ip = self.ip(item) header_dict["destination"] = dst_ip continue if "dst_port" not in header_dict: dst_port = self.port(item) header_dict["dst_port"] = dst_port continue return header_dict def parse_options(self, rule=None): # TODO: # 1. preprocesor checks # 2. output modules checks if rule: self.rule = rule opts = self.get_options() _pcre_opt = False #if re.search(r'(;\s+pcre:\s+".*";)', opts): if re.search(r'(;\s+pcre:\s+".*";|;\s+pcre:".*";)', opts): _pcre_opt = True if _pcre_opt: #_pcre = re.split(r';(\s+pcre:\s+".*");', opts) _pcre = re.split(r';(\s+pcre:\s+".*"|\s+pcre:".*");', opts) options_l = _pcre[0].split(';') options_l.append(_pcre[1]) options_r = _pcre[2].split(';') options = options_l + options_r else: options = opts.split(';') options = filter(None, options) if options[-1].lstrip().rstrip() == ")": options.pop() else: raise ValueError("Snort rule options is not closed properly, " "you have a syntax error") options_dict = collections.OrderedDict() for index, option in enumerate(options): if ':' in option: split_option = option.split(":", 1) for place, item in enumerate(split_option): item = item.lstrip().rstrip() split_option[place] = item option_dict = {} if isinstance(split_option, list): key = split_option[0] split_option_values = split_option[-1].split(",") option_dict[key] = split_option_values else: option_dict[split_option[0]] = None options_dict[index] = option_dict else: options_dict[index] = {"modifier":[option.lstrip().rstrip()]} return options_dict def validate_options(self, options): for key, value in options.iteritems(): option_dict = value opt = False for key, value in option_dict.iteritems(): content_mod = self.dicts.content_modifiers(value[0]) if content_mod: # An unfinished feature continue gen_option = self.dicts.options(key) if gen_option: opt = True continue pay_option = self.dicts.options(key) if pay_option: opt = True continue non_pay_option = self.dicts.options(key) if non_pay_option: opt = True continue post_detect = self.dicts.options(key) if post_detect: opt = True continue threshold = self.dicts.options(key) if threshold: opt = True continue if not opt: message = "unrecognized option: %s" % key raise ValueError(message) return options
class Parser: """ this will take an array of lines and parse it and hand back a dictionary NOTE: if you pass an invalid rule to the parser, it will a raise ValueError. """ def __init__(self, rule): self.dicts = Dicts() self.rule = rule self.header = self.parse_header() self.options = self.parse_options() self.validate_options(self.options) self.data = {"header": self.header, "options": self.options} self.all = self.data def __iter__(self): yield self.data def __getitem__(self, key): if key == "all": return self.data return self.data[key] @staticmethod def actions(action: str) -> str: actions = { "alert", "log", "pass", "activate", "dynamic", "drop", "reject", "sdrop", } if action in actions: return action msg = f"Invalid action specified {action}" raise ValueError(msg) @staticmethod def proto(proto: str) -> str: protos = {"tcp", "udp", "icmp", "ip"} if proto.lower() in protos: return proto msg = f"Unsupported Protocol {proto} " raise ValueError(msg) @staticmethod def __ip_to_tuple(ip: str) -> Tuple[bool, str]: if ip.startswith("!"): ip = ip.lstrip("!") return False, ip return True, ip def __form_ip_list(self, ip_list: str) -> List[Tuple[bool, str]]: return [self.__ip_to_tuple(ip) for ip in ip_list.split(",")] def __flatten_ip(self, ip): list_deny = True if ip.startswith("!"): list_deny = False ip = ip.lstrip("!") _ip_list = [] _not_nest = True ip = re.sub(r"^\[|\]$", "", ip) ip = re.sub(r'"', "", ip) if re.search(r"(\[.*\])", ip): _not_nest = False nest = re.split(r",(!?\[.*\])", ip) nest = filter(None, nest) # unnest from _ip_list _return_ips = [] for item in nest: if re.match(r"^\[|^!\[", item): nested = self.__flatten_ip(item) _return_ips.append(nested) continue _ip_list = self.__form_ip_list(item) for _ip in _ip_list: _return_ips.append(_ip) return list_deny, _return_ips if _not_nest: _ip_list = self.__form_ip_list(ip) return list_deny, _ip_list return None def __validate_ip(self, ips): variables = { "$EXTERNAL_NET", "$HTTP_SERVERS", "$INTERNAL_NET", "$SQL_SERVERS", "$SMTP_SERVERS", "$DNS_SERVERS", "$HOME_NET", "HOME_NET", "any", } for item in ips: if isinstance(item, bool): pass if isinstance(item, list): for ip in item: self.__validate_ip(ip) if isinstance(item, str): if item not in variables: if "/" in item: ipaddress.ip_network(item) else: ipaddress.ip_address(item) return True def ip(self, ip): if isinstance(ip, str): ip = ip.strip('"') if re.search(r",", ip): item = self.__flatten_ip(ip) ip = item else: ip = self.__ip_to_tuple(ip) valid = self.__validate_ip(ip) if valid: return ip raise ValueError(f"Invalid IP or variable: {ip}") return None # pylint: disable=too-many-branches # pylint: disable=too-many-locals # pylint: disable=too-many-statements @staticmethod def port(port): variables = {"any", "$HTTP_PORTS"} # is the source marked as not if port.startswith("!"): if_not = False port = port.strip("!") else: if_not = True # is it a list ? # if it is, then make it a list from the string # Snort allows for ports marked between # square brackets and are used to define lists # correct: # >> [80:443,!90,8080] # >> ![80:443] # >> [!80:443] if port.startswith("["): if port.endswith("]"): port = port[1:-1].split(",") else: raise ValueError("Port list is malformed") if isinstance(port, list): ports = [] for item in port: not_range = True if ":" in item: # Checking later on if port is [prt:] or [:prt] open_range = False items = item.split(":", 1) message = "" for prt in items: message = f"Port range is malformed {item}" prt = prt.lstrip("!") if not prt: open_range = True continue try: prt = int(prt) except Exception as e: raise ValueError(message) from e if prt < 0 or prt > 65535: raise ValueError(message) for index, value in enumerate(items): value = value.lstrip("!") items[index] = value if not open_range: try: a = int(items[-1]) b = int(items[0]) except Exception as e: raise ValueError(message) from e if a - b < 0: raise ValueError(message) not_range = False port_not = True if re.search("^!", item): port_not = False item = item.strip("!") if not_range: if item.lower() or item in variables: ports.append((port_not, item)) continue try: prt = int(item) if prt < 0 or prt > 65535: raise ValueError(f"Port is out of range {item}") except ValueError as e: raise ValueError(f"Unknown port {item}") from e ports.append((port_not, item)) return if_not, ports if isinstance(port, str): # Parsing ports like: :8080, 80:, 80:443 # and passes all variables ex: $HTTP # ranges do not accept denial (!) if port or port.lower() in variables or re.search(r"^\$+", port): return if_not, port if re.search(":", port): message = f"Port is out of range {port}" ports = port.split(":") for portl in ports: portl.lstrip("!") if not portl: continue if portl or portl.lower() in variables: continue try: portl = int(portl) except ValueError as e: raise ValueError(message) from e if portl < 0 or portl > 65535: raise ValueError(message) return if_not, port # Parsing a single port # single port accepts denial. try: if int(port) <= 65535 or int(port) < 0: return if_not, port if int(port) > 65535 or int(port) < 0: raise ValueError return None # TODO: verify this is intended behavior except Exception as e: msg = f'Unknown port: "{port}" ' raise ValueError(msg) from e else: message = f'Unknown port "{port}"' raise ValueError(message) @staticmethod def destination(dst): destinations = {"->": "to_dst", "<>": "bi_direct"} if dst in destinations: return dst msg = f"Invalid destination variable {dst}" raise ValueError(msg) def get_header(self): if re.match(r"(^[a-z|A-Z].+?)?(\(.+;\)|;\s\))", self.rule.lstrip()): header = self.rule.split("(", 1) return header[0] msg = f"Error in syntax, check if rulehas been closed properly {self.rule} " raise SyntaxError(msg) @staticmethod def remove_leading_spaces(string: str) -> str: return string.strip() def get_options(self): options = f"{self.rule.split('(', 1)[-1].lstrip().rstrip()}" if not options.endswith(")"): raise ValueError("Snort rule options is not closed properly, " "you have a syntax error") op_list = [] value = "" option = "" last_char = "" for char in options.rstrip(")"): if char != ";": value = value + char option = option + char if char == ";" and last_char != "\\": op_list.append(option.strip()) value = option = "" last_char = char return op_list def parse_header(self): # pylint: disable=too-many-statements # OrderedDict( # [ # ("action", "alert"), # ("proto", "tcp"), # ("source", (True, "$HOME_NET")), # ("src_port", (True, "any")), # ("arrow", "->"), # ("destination", (False, "$EXTERNAL_NET")), # ("dst_port", (True, "any")), # ] # ) if self.get_header(): header = self.get_header() if re.search(r"[,\[\]]\s", header): header = re.sub(r",\s+", ",", header) header = re.sub(r"\s+,", ",", header) header = re.sub(r"\[\s+", "[", header) header = re.sub(r"\s+\]", "]", header) header = header.split() else: raise ValueError("Header is missing, or unparsable") # get rid of empty list elements header = list(filter(None, header)) header_dict = collections.OrderedDict() size = len(header) if not size == 7 and not size == 1: msg = f"Snort rule header is malformed {header}" raise ValueError(msg) for item in header: if "action" not in header_dict: action = self.actions(item) header_dict["action"] = action continue if "proto" not in header_dict: try: proto = self.proto(item) header_dict["proto"] = proto continue except Exception as perror: raise ValueError(perror) from perror if "source" not in header_dict: try: src_ip = self.ip(item) header_dict["source"] = src_ip continue except Exception as serror: raise ValueError(serror) from serror if "src_port" not in header_dict: src_port = self.port(item) header_dict["src_port"] = src_port continue if "arrow" not in header_dict: dst = self.destination(item) header_dict["arrow"] = dst continue if "destination" not in header_dict: dst_ip = self.ip(item) header_dict["destination"] = dst_ip continue if "dst_port" not in header_dict: dst_port = self.port(item) header_dict["dst_port"] = dst_port continue return header_dict def parse_options(self, rule=None): if rule: self.rule = rule opts = self.get_options() options_dict = collections.OrderedDict() for index, option_string in enumerate(opts): if ":" in option_string: option = option_string.split(":", 1) key, value = option if key != "pcre": value = value.split(",") options_dict[index] = (key, value) else: options_dict[index] = (option_string, "") return options_dict def validate_options(self, options): for _index, option in options.items(): key, value = option if len(value) == 1: content_mod = self.dicts.content_modifiers(value[0]) opt = False if content_mod: # TODO: implement this feature continue gen_option = self.dicts.options(key) if gen_option: opt = True continue pay_option = self.dicts.options(key) if pay_option: opt = True continue non_pay_option = self.dicts.options(key) if non_pay_option: opt = True continue post_detect = self.dicts.options(key) if post_detect: opt = True continue threshold = self.dicts.options(key) if threshold: opt = True continue if not opt: message = f"unrecognized option: {key}" raise ValueError(message) return options