RemediationPathRuleEvaluation
For a given alert and remediation path rules that are defined for that alert's attack surface rule, this script takes each remediation path rule and looks at the rule criteria to see if the rule matches for the given alert. If multiple rules match, it will return the most recently created rule. This assumes that the rules passed in are filtered to correlate with the alert's attack surface rule.
python · Cortex Attack Surface Management
Source
import demistomock as demisto # noqa: F401 from CommonServerPython import * # noqa: F401 """ Script for evaluating and finding a remediation path rule that matches for a given alert """ import traceback from typing import Any # should match IncidentSeverity in CommonServerPython SEVERITY_MAP = { "unknown": 0, "info": 0.5, "low": 1, "medium": 2, "high": 3, "critical": 4, } def match_remediation_rule(alert_context: dict[str, Any], rules: list) -> list: """ For the given alert (with the field in alert_context), finds the most recently created remediation path rule where the alert meets the rule's criteria Args: alert_context: dictionary of relevant alert fields to check against rules: list of remediation path rules to evaluate Returns: the matching rule (list of 1) """ matched_rules = [] # loop through remediation rules if isinstance(rules, dict): rules = [rules] for rule in rules: match = True conditions = rule["criteria"] if len(conditions) == 0: # pragma: no cover demisto.info("Invalid Remediation Path Rule - no criteria condition to evaluate.") match = False else: # check criteria conjunction if rule.get("criteria_conjunction") == "AND": for cond in conditions: rule_matched = evaluate_criteria(cond, alert_context) if not rule_matched: match = False else: demisto.info(f"Criteria conjunction {rule.get('criteria_conjunction')} not supported at this time.") match = False if match: matched_rules.append(rule) # sort matched rules by created_at timestamp in descending order # return the most recent one matched_rules_sorted = sorted(matched_rules, key=lambda x: x["created_at"], reverse=True) if len(matched_rules_sorted) > 0: return [matched_rules_sorted[0]] else: return [] def evaluate_criteria(cond: dict, alert_context: dict) -> bool: """ Evaluate whether a criteria condition is met based on the given alert's context fields Args: cond: Condition to evaluation (dictionary with keys: field, operator, value) alert_context: dictionary of relevant alert fields to check against Returns: True if the condition is met or False if not """ field = cond["field"] operator = cond["operator"] value = cond["value"] alert_context_value = alert_context.get(field) # force lowercase for field and operator and value if isinstance(field, str) and isinstance(operator, str) and isinstance(value, str): field = field.lower() operator = operator.lower() value = value.lower() else: demisto.info("Invalid condition: field, operator or value must be strings") return False if operator == "eq": # severity if field == "severity": return SEVERITY_MAP.get(value) == alert_context_value # boolean fields elif field == "development_environment" or field == "cloud_managed" or field == "service_owner_identified": if value == "true" and (alert_context_value is True or alert_context_value): return True elif value == "false" and (alert_context_value is False or not alert_context_value): return True return False # text match fields elif field == "provider": # providers can be a string or a list if alert_context_value: if isinstance(alert_context_value, str): alert_context_value = [alert_context_value] providers_set = {provider.lower() for provider in alert_context_value} if value in providers_set: return True return False elif isinstance(alert_context_value, str): if field == "ip": return value == alert_context_value.lower() else: # pragma: no cover demisto.info(f"Criteria field {field} not supported at this time.") return False # list fields elif isinstance(alert_context_value, list): if field == "tag": # build set of tag values to check against tags = set() for tag in alert_context_value: tags.add(tag.get("value").lower()) tags.add(tag.get("key").lower()) return value in tags else: # pragma: no cover demisto.info(f"Criteria field {field} not supported at this time.") return False else: return False else: # pragma: no cover demisto.info(f"Condition Operator {operator} is not supported at this time.") return False """ MAIN FUNCTION """ def main(): # pragma: no cover """ For a given alert and remediation path rules that are defined for that alert's attack surface rule, this takes each remediation path rule and looks at the rule criteria too see if the rule matches for the given alert. If multiple rules match, it will return the most recently created rule. This assumes that the rules passed in are filtered to correlate with the alert's attack surface rule. Args: alert_context: dictionary of relevant alert fields we are checking against remediation_path_rules: list of remediation path rules for the alert's attack surface rule Returns: asmremediationpathrule: the remediation path rule if there is a match Does not populate this alert field if no match is found. """ try: # parse args args = demisto.args() alert_context = { "severity": args.get("severity"), "ip": args.get("ip"), "tag": args.get("tags"), "provider": args.get("providers"), "development_environment": args.get("development_environment"), "cloud_managed": args.get("cloud_managed"), "service_owner_identified": args.get("service_owner_identified"), } rules = args.get("remediation_path_rules") matched_rule = match_remediation_rule(alert_context, rules) if len(matched_rule) > 0: demisto.executeCommand("setAlert", {"asmremediationpathrule": matched_rule[0]}) return_results(CommandResults(readable_output="Remediation path rule match found")) else: return_results(CommandResults(readable_output="No matched remediation path rules found")) except Exception as ex: demisto.error(traceback.format_exc()) # print the traceback return_error(f"Failed to execute Remediation Path Rule Evaluation. Error: {str(ex)}") """ ENTRY POINT """ if __name__ in ("__main__", "__builtin__", "builtins"): main()
README
For a given alert and remediation path rules that are defined for that alert’s attack surface rule, this script takes each remediation path rule and looks at the rule criteria to see if the rule matches for the given alert. If multiple rules match, it will return the most recently created rule. This assumes that the rules passed in are filtered to correlate with the alert’s attack surface rule.
Script Data
| Name | Description |
|---|---|
| Script Type | python3 |
Inputs
| Argument Name | Description |
|---|---|
| severity | Alert’s Severity. |
| ip | Alert’s Remote IP. |
| development_environment | Is this in a development environment? |
| cloud_managed | Is this cloud managed? |
| service_owner_identified | Has a service owner been identified? |
| tags | Includes Cloud and Xpanse tags |
| providers | Externally Detected Providers |
| remediation_path_rules | List of remediation path rules for the alert’s attack surface rule. |
Outputs
There are no outputs for this script.