Ejemplo n.º 1
0
 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'))
Ejemplo n.º 2
0
 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
Ejemplo n.º 3
0
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
Ejemplo n.º 4
0
 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'))
Ejemplo n.º 5
0
 def test_lookup_returns_word_unchanged_without_tables(self):
     dicts = Dicts(None)
     self.assertEqual(['gnarble'], dicts.lookup('gnarble'))
Ejemplo n.º 6
0
 def test_lookup_exists(self):
     dicts = Dicts(None)
     dicts.lookup('')
Ejemplo n.º 7
0
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.*'
Ejemplo n.º 8
0
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
Ejemplo n.º 9
0
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