InvestigationSummaryParse

Retrieves information from previously run reputation commands and aggregates their results.

python · Malware Investigation and Response

Source

from abc import ABC
from enum import Enum

from CommonServerPython import *

SUPPORTED_HASHES = "SHA256", "SHA512", "SHA1", "MD5"
CONTEXT_FINDING_PREFIX = "InvestigationSummary"


def get(value: dict, key: str) -> Optional[set]:
    """
    Fetches a value, entering lists when necessary.
    NOTE: None values are removed!

    >>> get(value={'foo': {'inner':'value'}}, key='foo.bar') # returns None
    >>> get(value={'foo': {'inner':'value'}}, key='foo.inner')
    {'value'}
    >>> sorted(list(get(value={'foo': [{'inner': 'value2'}, {'inner': 'value1'}, {'bar':'baz'}]}, key='foo.inner')))
    ['value1', 'value2']
    >>> sorted(list(get(value={'foo': [{'inner': ['value1', 'value2']}, {'bar':'baz'}]}, key='foo.inner')))
    ['value1', 'value2']
    """
    parts = key.split(".")
    for i, part in enumerate(parts):
        if isinstance(value, dict):
            if part in value:
                value = value[part]
                if i == len(parts) - 1:  # got to the last key part
                    if isinstance(value, list):
                        return {sub_value for sub_value in value if sub_value is not None}
                    else:
                        return {value}
            else:
                break
        elif isinstance(value, list) and isinstance(value[0], dict) and i != len(parts):
            # NOTE: None values are removed!
            next_values = set()
            for sub_dict in value:
                next_values.update(get(sub_dict, ".".join(parts[i:])) or {})
            return {value for value in next_values if value is not None}
    return None


class Result(Enum):
    SUSPICIOUS = 1
    NOT_DETECTED = 0


class Source(Enum):
    SANDBOX = "Sandbox"
    EDR = "EDR"


class MalwareFinding(ABC):
    name: str
    context: dict
    tactic: Optional[str]
    result: Optional[Result]
    sources: Set[Source]
    additional_attributes: Optional[Dict[str, str]] = {}

    def __init__(self, name: str, context: dict, tactic: Optional[str] = None):
        """
        Represents a query to the context.
        :param name: Name of the query
        :param context: Context to search in.
        :param tactic: The tactic matching this query, to be shown to be user.
        """
        self.name = name
        self.context = context
        self.tactic = tactic
        self.result = Result.NOT_DETECTED  # default
        self.sources = set()
        # make sure you self.parse_context in every inheriting class' __init__!

    def to_context(self) -> dict:
        """
        Creates a context section from the finding.
        Use `False` when creating context for aggregated results (max of findings)
        :return: a dictionary to be used as CommandResult.output.
        """
        context: Dict[str, Any] = {}

        if self.sources:
            # sources are not always defined, e.g. in Verdict.
            context["Sources"] = sorted([source.value for source in self.sources])  # sorting for deterministic results

        if self.tactic:
            context["Tactic"] = self.tactic

        if self.result:
            context["Result"] = self.result.name.title().replace("_", " ")

        if self.additional_attributes:
            context.update(self.additional_attributes)

        return context

    def _process_value(self, value: str, expected_value: Union[str, bool], result: Result, source: Source):
        """
        Compares the value to an expected value.
        If the two are equal, the result and source are set accordingly.
        """
        if isinstance(expected_value, bool):
            try:
                value = argToBoolean(value)
            except ValueError:
                return  # if a value can't be converted to bool, it is definitely not the expected boolean.

        if value == expected_value:
            self.result = result
            self.sources.add(source)  # not using break here, in case there are multiple sources

    def _parse_key(self, key: str, expected_value: Union[str, bool], result: Result, source: Source, context: dict = None):
        if context is None:
            context = self.context

        for value in get(context, key) or {}:
            self._process_value(value, expected_value, result, source)

    @abstractmethod  # noqa: B027
    def to_command_results(self) -> CommandResults:
        pass


class KillChain(MalwareFinding, ABC):
    def __init__(self, name: str, tactic: str, context: dict, search_value: str):
        self.search_value = search_value
        super().__init__(name=name, tactic=tactic, context=context)  # calls self._parse_context
        self._parse_context()

    def _parse_context(self) -> None:
        self._parse_key("csfalconx.resource.sandbox.mitre_attacks.tactic", self.search_value, Result.SUSPICIOUS, Source.SANDBOX)
        self._parse_key("incident.mitretacticname", self.search_value, Result.SUSPICIOUS, Source.EDR)
        self._parse_key("MITREATTACK.value", self.search_value, Result.SUSPICIOUS, Source.EDR)
        self._parse_key("AttackPattern.KillChainPhases", self.search_value, Result.SUSPICIOUS, Source.EDR)

    def to_command_results(self) -> CommandResults:
        return CommandResults(outputs=self.to_context(), outputs_prefix=f"{CONTEXT_FINDING_PREFIX}.{self.name}")


class Persistence(KillChain):
    def __init__(self, context: dict):
        super().__init__(
            name="EvidenceOfPersistence",
            tactic="Persistence",
            context=context,
            search_value=ThreatIntel.KillChainPhases.PERSISTENCE,
        )


class DefenseEvasion(KillChain):
    def __init__(self, context: dict):
        super().__init__(
            name="EvidenceOfDefenseEvasion",
            tactic="Defense Evasion",
            context=context,
            search_value=ThreatIntel.KillChainPhases.DEFENSE_EVASION,
        )


class Execution(KillChain):
    def __init__(self, context: dict):
        super().__init__(
            name="EvidenceOfExecution", tactic="Execution", context=context, search_value=ThreatIntel.KillChainPhases.EXECUTION
        )


class LateralMovement(KillChain):
    def __init__(self, context: dict):
        super().__init__(
            name="EvidenceOfLateralMovement",
            tactic="Lateral Movement",
            context=context,
            search_value=ThreatIntel.KillChainPhases.LATERAL_MOVEMENT,
        )


class PrivilegeEscalation(KillChain):
    def __init__(self, context: dict):
        super().__init__(
            name="EvidenceOfPrivilegeEscalation",
            tactic="Privilege Escalation",
            context=context,
            search_value=ThreatIntel.KillChainPhases.PRIVILEGE_ESCALATION,
        )


class CommandAndControl(KillChain):
    def __init__(self, context: dict):
        super().__init__(
            name="EvidenceOfCommandAndControl",
            tactic="Command and Control",
            context=context,
            search_value=ThreatIntel.KillChainPhases.COMMAND_AND_CONTROL,
        )


def parse_command(context: dict) -> List[CommandResults]:
    return [
        finding.to_command_results()
        for finding in (
            Persistence(context),
            DefenseEvasion(context),
            Execution(context),
            LateralMovement(context),
            PrivilegeEscalation(context),
            CommandAndControl(context),
        )
    ]


def main():
    try:
        context = demisto.callingContext.get("context", {}).get("ExecutionContext", {})
        result = parse_command(context)
        return_results(result)

    except Exception as e:
        demisto.error(traceback.format_exc())  # print the traceback
        return_error(f"Failed to execute FindingsParse. Error: {e!s}")


if __name__ in ["__main__", "__builtin__", "builtins"]:
    main()

README

Retrieves information from previously run reputation commands and aggregates their results.

Script Data


Name Description
Script Type python3
Cortex XSOAR Version 6.2.0

Inputs


There are no inputs for this script.

Outputs


Path Description Type
InvestigationSummary.EvidenceOfPersistence.Tactic The tactic associated with the evidence of persistence finding. String
InvestigationSummary.EvidenceOfPersistence.Result The result of the evidence of persistence finding. String
InvestigationSummary.EvidenceOfPersistence.Sources The sources by which the evidence of persistence value was set. String
InvestigationSummary.EvidenceOfDefenseEvasion.Tactic The tactic associated with the evidence of defense evasion finding. String
InvestigationSummary.EvidenceOfDefenseEvasion.Result The result of the evidence of persistence finding. String
InvestigationSummary.EvidenceOfDefenseEvasion.Sources The sources by which the evidence of defense evasion value was set. String
InvestigationSummary.EvidenceOfExecution.Tactic The tactic associated with the evidence of execution finding. String
InvestigationSummary.EvidenceOfExecution.Result The result of the evidence of execution finding. String
InvestigationSummary.EvidenceOfExecution.Sources The sources by which the evidence of execution value was set. String
InvestigationSummary.EvidenceOfLateralMovement.Tactic The tactic associated with the evidence of lateral movement finding. String
InvestigationSummary.EvidenceOfLateralMovement.Result The Result of the evidence of lateral movement finding. String
InvestigationSummary.EvidenceOfLateralMovement.Sources The sources by which the evidence of lateral movement value was set. String
InvestigationSummary.EvidenceOfPrivilegeEscalation.Tactic The tactic associated with the evidence of privilege escalation finding. String
InvestigationSummary.EvidenceOfPrivilegeEscalation.Result The result of the evidence of privilege escalation finding. String
InvestigationSummary.EvidenceOfPrivilegeEscalation.Sources The sources by which the evidence of privilege escalation value was set. String
InvestigationSummary.EvidenceOfCommandAndControl.Tactic The tactic associated with the evidence of command and control finding. String
InvestigationSummary.EvidenceOfCommandAndControl.Result The result of the evidence of command and control finding. String
InvestigationSummary.EvidenceOfCommandAndControl.Sources The sources by which the evidence of command and control value was set. String