Beispiel #1
0
    def process_IN_CLOSE_WRITE(self, event):
        """
        Handles a file creation.
        @param event: The file event.
        """
        LOG.debug("Callback from event in directory")
        try:
            # First, translate the CSV in HSPL, MSPL sets
            [hsplSet, msplSet] = self.getMSPLsFromFile(
                event.pathname, self.configParser.get("global",
                                                      "landscapeFile"))

            # Then, if extra logging is activated, print HSPL (and/or MSPL)
            # to an external file
            if self.configParser.has_option("global", "hsplsFile"):
                with open(self.configParser.get("global", "hsplsFile"),
                          "w") as f:
                    f.write(
                        etree.tostring(hsplSet, pretty_print=True).decode())
            if self.configParser.has_option("global", "msplsFile"):
                with open(self.configParser.get("global", "msplsFile"),
                          "w") as f:
                    f.write(
                        etree.tostring(msplSet, pretty_print=True).decode())

            # Finally, sends everything to RabbitMQ.
            self.send(hsplSet, msplSet)
        except BaseException as e:
            LOG.critical(str(e))
            if self.channel is not None:
                if not self.channel.is_closed:
                    self.channel.close()
    def getLandscape(self, fileName):
        """
        Creates a landscape map by parsing an XML file.
        @param fileName: the file name of the XML file to parse.
        @return: the landscape map.
        @raise IOError: if the file has an invalid format.
        """
        schema = etree.XMLSchema(etree.parse(getLandscapeXSDFile()))
        parser = etree.XMLParser(schema = schema)

        if not os.path.exists(fileName):
            LOG.critical("The file '%s' does not exist", fileName)
            raise IOError("The file '%s' does not exist", fileName)

        root = etree.parse(fileName, parser).getroot()
        landscape = {}
        for i in root:
            identifier = i.attrib["id"]
            capabilities = set()
            for j in i.findall("{%s}capability" % getLandscapeNamespace()):
                capabilities.add(j.text)
            landscape[identifier] = capabilities

        LOG.info("Landscape with %d IT resources read.", len(landscape))
        return landscape
    def parse(self, fileName, count, line):
        """
        Parses an event line.
        @param fileName: The current file name or None if this is a list.
        @param count: The current line count.
        @param line: The line to parse.
        @return: The attack event or None if this line should be silently ignored.
        @raise IOError: if the line contains something invalid.
        """

        if re.match("\s*#.*", line):
            return None

        parts = re.split("\s*,\s*|\s+", line.rstrip())

        if parts == [""]:
            return None

        try:
            timestamp = parser.parse("%s %s" % (parts[0], parts[1]))
            sourceAddress = ipaddress.ip_address(parts[9])
            destinationAddress = ipaddress.ip_address(parts[10])
            destinationPort = int(parts[11])
            sourcePort = int(parts[12])
            protocol = parts[13]
            inputPackets = int(parts[14])
            inputBytes = int(parts[15])
            outputPackets = int(parts[16])
            outputBytes = int(parts[17])

            attackEvent = AttackEvent(
                timestamp, "%s:%d" % (sourceAddress, sourcePort),
                "%s:%d" % (destinationAddress, destinationPort))
            attackEvent.fields["protocol"] = protocol
            attackEvent.fields["inputPackets"] = inputPackets
            attackEvent.fields["inputBytes"] = inputBytes
            attackEvent.fields["outputPackets"] = outputPackets
            attackEvent.fields["outputBytes"] = outputBytes

            return attackEvent
        except:
            if count == 1:
                return None
            elif fileName is None:
                LOG.critical("The line %d has an invalid format.", count)
                raise IOError("The line %d has an invalid format." % count)
            else:
                LOG.critical(
                    "The line %d in the file '%s' has an invalid format.",
                    count, fileName)
                raise IOError(
                    "The line %d in the file '%s' has an invalid format." %
                    (count, fileName))
    def parse(self, fileName, count, line):
        """
        Parses an event line.
        @param fileName: The current file name or None if this is a list.
        @param count: The current line count.
        @param line: The line to parse.
        @return: The attack event or None if this line should be silently ignored.
        @raise IOError: if the line contains something invalid.
        """

        if re.match("\s*#.*", line):
            return None

        parts = re.split("\s*,\s*|\s+", line.rstrip())

        if parts == [""]:
            return None

        try:
            timestamp = parser.parse(
                "%s %s %s %s" %
                (parts[0], parts[1], parts[2], parts[3].split('.')[0]))
            frameLength = int(parts[6])
            destinationAddress = ipaddress.ip_address(parts[7])
            query = parts[8]
            queryClass = int(parts[9], 16)
            queryType = int(parts[10])
            queryResponseCode = int(parts[11])

            attackEvent = AttackEvent(timestamp, "%s:*" % destinationAddress,
                                      "0.0.0.0/0:53")
            attackEvent.fields["frameLength"] = frameLength
            attackEvent.fields["query"] = query
            attackEvent.fields["queryClass"] = queryClass
            attackEvent.fields["queryType"] = queryType
            attackEvent.fields["queryResponseCode"] = queryResponseCode

            return attackEvent
        except:
            if count == 1:
                return None
            elif fileName is None:
                LOG.critical("The line %d has an invalid format.", count)
                raise IOError("The line %d has an invalid format." % count)
            else:
                LOG.critical(
                    "The line %d in the file '%s' has an invalid format.",
                    count, fileName)
                raise IOError(
                    "The line %d in the file '%s' has an invalid format." %
                    (count, fileName))
    def getAttackFromList(self, identifier, severity, attackType, attackList, anomaly_name):
        """
        Creates an attack object by parsing a CSV list.
        @param identifier: the attack id.
        @param severity: the attack severity.
        @param attackType: the attack type.
        @param attackList: the list to parse.
        @return: the attack object.
        @raise IOError: if the file has an invalid format or if no suitable parser plug-in is available.
        """

        # Finds a suitable parser.
        plugin = None
        for i in self.pluginManager.getPluginsOfCategory("Parser"):
            pluginFileName = i.details.get("Core", "FileName")
            if re.match(pluginFileName, attackType):
                plugin = i
                break
        if plugin is None:
            LOG.critical("No suitable attack event parser found.")
            raise IOError("No suitable attack event parser found")

        # Creates an attack object.
        attackType = plugin.details.get("Core", "Attack")
        attack = Attack(severity, attackType, identifier, anomaly_name)

        # Opens the file and read the events.
        count = 0
        for line in attackList:
            count += 1
            event = plugin.plugin_object.parse(None, count, line)
            print(">>>>>>", event)
            if event is not None:
                attack.events.append(event)

        # Third: checks if there are some events.
        if count == 0:
            LOG.critical("The list is empty")
            raise IOError("The list is empty")

        LOG.info("Parsed an attack of type '%s' with severity %d and containing %d events.", attack.type, attack.severity, len(attack.events))
        return attack
Beispiel #6
0
def retrieve_vnsfr_id(vnsfo_base_url, vnfd_id, attack_name, timeout):
    LOG.info("Request vNSFO API call for vnsfd_id=" + vnfd_id +
             " and attack type=" + attack_name)
    url = vnsfo_base_url + "/vnsf/r4/running"
    LOG.info("VNSFO API call: " + url)

    try:
        response = requests.get(url, verify=False, timeout=timeout)
        LOG.info("VNSFO API response: " + response.text)
        vnsfs = response.json()["vnsf"]

        # search for first running instance which matches the query
        for vnsf in vnsfs:
            target_vnf = vnsf['vnfd_id'][:-5].lower()
            if vnfd_id[:-5].lower() in target_vnf and attack_name.lower(
            ) in target_vnf:
                LOG.info("Found instance=" + vnsf['vnfr_id'] + " for attack=" +
                         attack_name)
                return vnsf['vnfr_id']
        LOG.info("No running instance found from VNSFO API.")
        return None
    except Exception as e:
        LOG.critical("VNSFO API error: " + str(e))
        return None
Beispiel #7
0
    def getMSPLs(self, hsplRecommendations, landscape, anomaly_name):
        """
        Retrieve the HSPLs that can be used to mitigate an attack.
        @param recommendations: The HSPL recommendations to use.
        @param landscape: The landscape.
        @return: The XML MSPL set that can mitigate the attack. It is None if no HSPL is available.
        @raise SyntaxError: When the generated XML is not valid.
        """
        if hsplRecommendations is None:
            return None

        schema = etree.XMLSchema(etree.parse(getMSPLXSDFile()))

        recommendations = etree.Element("{%s}recommendations" %
                                        getMSPLNamespace(),
                                        nsmap={
                                            None: getMSPLNamespace(),
                                            "xsi": getXSINamespace()
                                        })

        for hsplSet in hsplRecommendations:
            msplSet = etree.SubElement(recommendations,
                                       "{%s}mspl-set" % getMSPLNamespace(),
                                       nsmap={
                                           None: getMSPLNamespace(),
                                           "xsi": getXSINamespace()
                                       })

            # Gather some data about the recipe.
            msplSeverity = hsplSet.findtext(
                "{%s}context/{%s}severity" %
                (getHSPLNamespace(), getHSPLNamespace()))
            msplType = hsplSet.findtext(
                "{%s}context/{%s}type" %
                (getHSPLNamespace(), getHSPLNamespace()))
            msplTimestamp = hsplSet.findtext(
                "{%s}context/{%s}timestamp" %
                (getHSPLNamespace(), getHSPLNamespace()))

            # Adds the context.
            context = etree.SubElement(msplSet,
                                       "{%s}context" % getMSPLNamespace())
            etree.SubElement(context, "{%s}severity" %
                             getMSPLNamespace()).text = msplSeverity
            #etree.SubElement(context, "{%s}type" % getMSPLNamespace()).text = msplType
            etree.SubElement(context, "{%s}type" %
                             getMSPLNamespace()).text = anomaly_name
            etree.SubElement(context, "{%s}timestamp" %
                             getMSPLNamespace()).text = msplTimestamp

            # Finds a plug-in that can create a configured IT resource.
            [plugin, identifier] = self.__findLocation(hsplSet, landscape)
            plugin.plugin_object.setup(self.configParser)

            LOG.info("Check if VNSFO API call (experimental) is enabled")

            # Check if VNSFO is integrated to recommendations engine
            vnsfo_integration = self.configParser.getboolean(
                "vnsfo", "enable_vnsfo_api_call")
            if vnsfo_integration:
                # Creates the IT resource.
                LOG.info("Experimental: contact vNSFO API")
                vnsfo_base_url = self.configParser.get("vnsfo",
                                                       "vnsfo_base_url")
                vnsfo_timeout = int(
                    self.configParser.get("vnsfo", "vnsfo_timeout"))
                if not vnsfo_base_url:
                    LOG.info("VNSFO base URL empty. Fallback to stable.")
                else:
                    LOG.info("Retrieving VNSF running ID for: " + identifier)
                    vnfr_id = retrieve_vnsfr_id(vnsfo_base_url, identifier,
                                                anomaly_name, vnsfo_timeout)
                    if vnfr_id:
                        LOG.info("VNSF running ID is: " + vnfr_id)
                        identifier = vnfr_id
            else:
                LOG.info("Stable solution selected.")
            itResource = etree.SubElement(
                msplSet, "{%s}it-resource" % getMSPLNamespace(),
                {"id": identifier})
            # Calls the plug-in to configure the IT resource.
            plugin.plugin_object.configureITResource(itResource, hsplSet)

        if schema.validate(recommendations):
            LOG.debug(
                etree.tostring(recommendations, pretty_print=True).decode())

            return recommendations
        else:
            LOG.critical("Invalid MSPL recommendations generated.")
            raise SyntaxError("Invalid MSPL recommendations generated.")
    def getAttackFromFile(self, fileName):
        """
        Creates an attack object by parsing a CSV file.
        @param fileName: the file name of the CSV file to parse.
        @return: the attack object.
        @raise IOError: if the file has an invalid format or if no suitable parser plug-in is available.
        """

        # First: checks if the file is a regular file.
        if not ntpath.isfile(fileName):
            LOG.critical("The file '%s' is not a regular file.", fileName)
            raise IOError("The file '%s' is not a regular file." % fileName)

        # Second: checks the file name format.
        match = re.match("^(Very Low|Very low|very low|Low|low|High|high|Very High|Very high|high)-(.+)?-(\d+)\.csv$", os.path.basename(fileName))
        if match:
            severity = match.group(1).lower()
            if severity == "very low":
                severity = 1
            elif severity == "low":
                severity = 2
            elif severity == "high":
                severity = 3
            else:
                severity = 4
            attackType = match.group(2)
            identifier = int(match.group(3))
        else:
            severity = 4
            attackType = os.path.splitext(ntpath.basename(fileName))[0]
            identifier = None

        anomaly_name = attackType

        # Finds a suitable parser.
        plugin = None
        for i in self.pluginManager.getPluginsOfCategory("Parser"):
            pluginFileName = i.details.get("Core", "FileName")
            if re.match(pluginFileName, attackType):
                plugin = i
                break
        if plugin is None:
            LOG.critical("No suitable attack event parser found.")
            raise IOError("No suitable attack event parser found")

        # Creates an attack object.
        attackType = plugin.details.get("Core", "Attack")
        attack = Attack(severity, attackType, identifier, anomaly_name)

        # Opens the file and read the events.
        count = 0
        with open(fileName, "rt") as csv:
            for line in csv:
                count += 1
                event = plugin.plugin_object.parse(fileName, count, line)
                if event is not None:
                    attack.events.append(event)

        # Third: checks if there are some events.
        if count <= 1:
            LOG.critical("The file '%s' is empty.", fileName)
            raise IOError("The file '%s' is empty." % fileName)

        LOG.info("Parsed an attack of type '%s' with severity %d and containing %d events.", attack.type, attack.severity, len(attack.events))
        return attack
Beispiel #9
0
    def __init__(self,
                 configurationFileName=None,
                 logConfigurationFileName=None):
        """
        Constructor.
        @param configurationFileName: the name of the configuration file to
                                      parse.
        @param logConfigurationFileName: the name of the log configuration file
                                         to use.
        """
        # Configures the logging.
        log.load_settings(logConfigurationFileName)

        # Configures the configuration file parser.
        self.configParser = ConfigParser()
        if configurationFileName is None:
            c = self.configParser.read(getConfigurationFile())
        else:
            c = self.configParser.read(configurationFileName)
        if len(c) > 0:
            LOG.debug("Configuration file '%s' read." % c[0])
        else:
            LOG.critical("Cannot read the configuration file from '%s'." %
                         configurationFileName)
            raise IOError("Cannot read the configuration file from '%s'" %
                          configurationFileName)

        # Configures the plug-ins.
        self.pluginManager = PluginManager()
        self.pluginManager.setPluginPlaces([getPluginDirectory()])
        self.pluginManager.setCategoriesFilter({
            "Action": ActionPlugin,
            "Parser": ParserPlugin,
            "Filter": FilterPlugin
        })
        self.pluginManager.collectPlugins()
        pluginsCount = len(self.pluginManager.getPluginsOfCategory("Parser"))
        if pluginsCount > 1:
            LOG.info("Found %d attack event parser plug-ins.", pluginsCount)
        else:
            LOG.info("Found %d attack event parser plug-in.", pluginsCount)
        pluginsCount = len(self.pluginManager.getPluginsOfCategory("Filter"))
        if pluginsCount > 1:
            LOG.info("Found %d attack event filter plug-ins.", pluginsCount)
        else:
            LOG.info("Found %d attack event filter plug-in.", pluginsCount)
        pluginsCount = len(self.pluginManager.getPluginsOfCategory("Action"))
        if pluginsCount > 1:
            LOG.info("Found %d action plug-ins.", pluginsCount)
        else:
            LOG.info("Found %d action plug-in.", pluginsCount)

        # Loads all the sub-modules.
        self.parser = Parser(self.configParser, self.pluginManager)
        self.recipesReasoner = RecipesReasoner(self.configParser,
                                               self.pluginManager)
        self.hsplReasoner = HSPLReasoner(self.configParser, self.pluginManager)
        self.msplReasoner = MSPLReasoner(self.configParser, self.pluginManager)
        # Starts with no attack info.
        self.attacks = {}
        # Connection to the DARE rabbitMQ queue
        self.r_connection = None
        self.r_channel = None
        self.r_closingConnection = False
        LOG.info("CyberSecurity Topologies initialized.")
Beispiel #10
0
    def getHSPLs(self, attack, recipes, landscape):
        """
        Retrieve the HSPLs that can be used to mitigate an attack.
        @param attack: The attack to mitigate.
        @param recipes: The recipes to use.
        @param landscape: The landscape.
        @return: The XML HSPL set that can mitigate the attack. It is None if no recipe is available.
        @raise SyntaxError: When the generated XML is not valid.
        """
        if recipes is None:
            return None

        schema = etree.XMLSchema(etree.parse(getHSPLXSDFile()))

        recommendations = etree.Element("{%s}recommendations" %
                                        getHSPLNamespace(),
                                        nsmap={
                                            None: getHSPLNamespace(),
                                            "xsi": getXSINamespace()
                                        })

        for recipe in recipes:
            hsplSet = etree.SubElement(recommendations,
                                       "{%s}hspl-set" % getHSPLNamespace(),
                                       nsmap={
                                           None: getHSPLNamespace(),
                                           "xsi": getXSINamespace()
                                       })

            # Gather some data about the recipe.
            recipeName = recipe.findtext("{%s}name" % getRecipeNamespace())
            recipeAction = recipe.findtext("{%s}action" % getRecipeNamespace())
            recipeSubjectAnyAddress = recipe.findtext(
                "{%s}subject-constraints/{%s}any-address" %
                (getRecipeNamespace(), getRecipeNamespace()))
            recipeSubjectAnyPort = recipe.findtext(
                "{%s}subject-constraints/{%s}any-port" %
                (getRecipeNamespace(), getRecipeNamespace()))
            recipeObjectAnyAddress = recipe.findtext(
                "{%s}object-constraints/{%s}any-address" %
                (getRecipeNamespace(), getRecipeNamespace()))
            recipeObjectAnyPort = recipe.findtext(
                "{%s}object-constraints/{%s}any-port" %
                (getRecipeNamespace(), getRecipeNamespace()))
            recipeType = recipe.findtext(
                "{%s}traffic-constraints/{%s}type" %
                (getRecipeNamespace(), getRecipeNamespace()))
            recipeMaxConnections = recipe.findtext(
                "{%s}traffic-constraints/{%s}max-connections" %
                (getRecipeNamespace(), getRecipeNamespace()))
            recipeRateLimit = recipe.findtext(
                "{%s}traffic-constraints/{%s}rate-limit" %
                (getRecipeNamespace(), getRecipeNamespace()))

            # Adds the context.
            context = etree.SubElement(hsplSet,
                                       "{%s}context" % getHSPLNamespace())
            etree.SubElement(context, "{%s}severity" %
                             getHSPLNamespace()).text = str(attack.severity)
            etree.SubElement(context, "{%s}type" %
                             getHSPLNamespace()).text = attack.type
            etree.SubElement(
                context, "{%s}timestamp" %
                getHSPLNamespace()).text = attack.getTimestamp().isoformat()

            # Filters the events.
            events = []
            recipeFilters = recipe.find("{%s}filters" % getRecipeNamespace())
            evaluation = "or"
            if recipeFilters is None:
                events = attack.events
            else:
                if "evaluation" in recipeFilters.attrib.keys():
                    evaluation = recipeFilters.attrib["evaluation"]
                for i in attack.events:
                    if evaluation == "or":
                        test = False
                    else:
                        test = True
                    for j in self.pluginManager.getPluginsOfCategory("Filter"):
                        pluginTag = j.details.get("Core", "Tag")
                        filterValues = recipeFilters.findall(
                            "{%s}%s" % (getRecipeNamespace(), pluginTag))
                        for k in filterValues:
                            t = j.plugin_object.filter(k.text, i)
                            if evaluation == "or":
                                test = test or t
                            else:
                                test = test and t
                    if not test:
                        events.append(i)

            # Adds an HSPL for each event.
            count = 0
            for i in events:
                count += 1
                hspl = etree.SubElement(hsplSet,
                                        "{%s}hspl" % getHSPLNamespace())
                etree.SubElement(
                    hspl, "{%s}name" %
                    getHSPLNamespace()).text = "%s #%d" % (recipeName, count)
                m = re.match("(\d+\.\d+\.\d+\.\d+(/\d+)?)(:(\d+|\*|any))?",
                             i.target)
                targetAddress = m.group(1)
                targetPort = m.group(4)
                if recipeSubjectAnyAddress is not None:
                    targetAddress = "*"
                if recipeSubjectAnyPort is not None:
                    targetPort = "*"
                etree.SubElement(
                    hspl, "{%s}subject" %
                    getHSPLNamespace()).text = "%s:%s" % (targetAddress,
                                                          targetPort)
                etree.SubElement(hspl, "{%s}action" %
                                 getHSPLNamespace()).text = recipeAction
                m = re.match("(\d+\.\d+\.\d+\.\d+(/\d+)?)(:(\d+|\*|any))?",
                             i.attacker)
                attackerAddress = m.group(1)
                attackerPort = m.group(4)
                if recipeObjectAnyAddress is not None:
                    attackerAddress = "*"
                if recipeObjectAnyPort is not None:
                    attackerPort = "*"
                etree.SubElement(
                    hspl, "{%s}object" %
                    getHSPLNamespace()).text = "%s:%s" % (attackerAddress,
                                                          attackerPort)
                trafficConstraints = etree.SubElement(
                    hspl, "{%s}traffic-constraints" % getHSPLNamespace())
                if recipeType is not None:
                    eventType = recipeType
                else:
                    eventType = i.fields["protocol"]
                etree.SubElement(trafficConstraints, "{%s}type" %
                                 getHSPLNamespace()).text = eventType
                if eventType == "TCP" and recipeMaxConnections is not None:
                    etree.SubElement(
                        trafficConstraints, "{%s}max-connections" %
                        getHSPLNamespace()).text = recipeMaxConnections
                if recipeRateLimit is not None:
                    etree.SubElement(trafficConstraints, "{%s}rate-limit" %
                                     getHSPLNamespace()).text = recipeRateLimit

        LOG.debug(etree.tostring(recommendations, pretty_print=True).decode())

        if schema.validate(recommendations):
            return self.__cleanAndMerge(recommendations)
        else:
            LOG.critical("Invalid HSPL recommendations generated.")
            raise SyntaxError("Invalid HSPL recommendations generated.")