diff --git a/Packs/CortexXDR/Integrations/CortexXDRIR/CortexXDRIR.py b/Packs/CortexXDR/Integrations/CortexXDRIR/CortexXDRIR.py index 954616acfd25..8cb99d50e5ae 100644 --- a/Packs/CortexXDR/Integrations/CortexXDRIR/CortexXDRIR.py +++ b/Packs/CortexXDR/Integrations/CortexXDRIR/CortexXDRIR.py @@ -50,6 +50,14 @@ XDR_OPEN_STATUS_TO_XSOAR = ["under_investigation", "new"] +BIOC_AND_CR_SEVERITY_MAPPING = { + "info": "SEV_010_INFO", + "low": "SEV_020_LOW", + "medium": "SEV_030_MEDIUM", + "high": "SEV_040_HIGH", + "critical": "SEV_050_CRITICAL", +} + def convert_epoch_to_milli(timestamp): if timestamp is None: @@ -563,6 +571,54 @@ def update_alerts_in_xdr_request(self, alerts_ids, severity, status, comment) -> raise DemistoException(f"Parse Error. Response not in format, can't find reply key. The response {response}.") return response["reply"]["alerts_ids"] + def get_biocs(self, request_data: dict): + reply = self._http_request( + method="POST", + url_suffix="/bioc/get", + json_data=request_data, + ) + return reply + + def insert_or_update_biocs(self, request_data): + reply = self._http_request( + method="POST", + url_suffix="/bioc/insert", + json_data=request_data, + ) + return reply + + def delete_biocs(self, request_data: dict): + reply = self._http_request( + method="POST", + url_suffix="/bioc/delete", + json_data=request_data, + ) + return reply + + def get_correlation_rules(self, request_data: dict): + reply = self._http_request( + method="POST", + url_suffix="/correlations/get", + json_data=request_data, + ) + return reply + + def create_or_update_correlation_rules(self, request_data: dict): + reply = self._http_request( + method="POST", + url_suffix="/correlations/insert", + json_data=request_data, + ) + return reply + + def delete_correlation_rules(self, request_data: dict): + reply = self._http_request( + method="POST", + url_suffix="/correlations/delete", + json_data=request_data, + ) + return reply + def get_asset(self, asset_id: str): try: res = self._http_request( @@ -1529,6 +1585,422 @@ def update_alerts_in_xdr_command(client: Client, args: Dict) -> CommandResults: return CommandResults(readable_output="Alerts with IDs {} have been updated successfully.".format(",".join(array_of_all_ids))) +def create_filters_for_bioc_and_correlation_rules(args: dict) -> list: + """ + Creates a list of filters for BIOC and correlation rules based on the provided arguments. + + Args: + args (dict): The command arguments containing filter criteria. + + Returns: + list: A list of filter dictionaries compatible with the Cortex XDR API. + """ + filters = [] + if name := args.get("name"): + filters.append({"field": "name", "operator": "EQ", "value": name}) + if severity := args.get("severity"): + filters.append({"field": "severity", "operator": "EQ", "value": BIOC_AND_CR_SEVERITY_MAPPING.get(severity, severity)}) + if bioc_type := args.get("type"): + filters.append({"field": "type", "operator": "EQ", "value": bioc_type}) + if is_xql := args.get("is_xql"): + filters.append({"field": "is_xql", "operator": "EQ", "value": argToBoolean(is_xql)}) + if comment := args.get("comment"): + filters.append({"field": "comment", "operator": "EQ", "value": comment}) + if status := args.get("status"): + filters.append({"field": "status", "operator": "EQ", "value": status}) + if indicator := args.get("indicator"): + filters.append({"field": "indicator", "operator": "EQ", "value": indicator}) + if mitre_technique := argToList(args.get("mitre_technique_id_and_name")): + filters.append({"field": "mitre_technique_id_and_name", "operator": "EQ", "value": mitre_technique}) + if mitre_tactic := argToList(args.get("mitre_tactic_id_and_name")): + filters.append({"field": "mitre_tactic_id_and_name", "operator": "EQ", "value": mitre_tactic}) + if xql_query := args.get("xql_query"): + filters.append({"field": "xql_query", "operator": "EQ", "value": xql_query}) + if is_enabled := args.get("is_enabled"): + filters.append({"field": "is_enabled", "operator": "EQ", "value": argToBoolean(is_enabled)}) + if description := args.get("description"): + filters.append({"field": "description", "operator": "EQ", "value": description}) + if alert_name := args.get("alert_name"): + filters.append({"field": "alert_name", "operator": "EQ", "value": alert_name}) + if alert_category := args.get("alert_category"): + filters.append({"field": "alert_category", "operator": "EQ", "value": alert_category}) + if alert_description := args.get("alert_description"): + filters.append({"field": "alert_description", "operator": "EQ", "value": alert_description}) + if alert_fields := argToList(args.get("alert_fields")): + filters.append({"field": "alert_fields", "operator": "EQ", "value": alert_fields}) + if execution_mode := args.get("execution_mode"): + filters.append({"field": "execution_mode", "operator": "EQ", "value": execution_mode}) + if search_window := args.get("search_window"): + filters.append({"field": "search_window", "operator": "EQ", "value": search_window}) + if simple_schedule := args.get("schedule"): + filters.append({"field": "schedule", "operator": "EQ", "value": simple_schedule}) + if timezone := args.get("timezone"): + filters.append({"field": "timezone", "operator": "EQ", "value": timezone}) + if crontab := args.get("schedule_linux"): + filters.append({"field": "schedule_linux", "operator": "EQ", "value": crontab}) + if suppression_enabled := args.get("suppression_enabled"): + filters.append({"field": "suppression_enabled", "operator": "EQ", "value": argToBoolean(suppression_enabled)}) + if suppression_duration := args.get("suppression_duration"): + filters.append({"field": "suppression_duration", "operator": "EQ", "value": suppression_duration}) + if suppression_fields := args.get("suppression_fields"): + filters.append({"field": "suppression_fields", "operator": "EQ", "value": suppression_fields}) + if dataset := args.get("dataset"): + filters.append({"field": "dataset", "operator": "EQ", "value": dataset}) + if user_defined_severity := args.get("user_defined_severity"): + filters.append({"field": "user_defined_severity", "operator": "EQ", "value": user_defined_severity}) + if user_defined_category := args.get("user_defined_category"): + filters.append({"field": "user_defined_category", "operator": "EQ", "value": user_defined_category}) + if mitre_defs := args.get("mitre_defs_json"): + try: + mitre_defs_json = json.loads(mitre_defs) + except ValueError: + raise DemistoException("Unable to parse 'mitre_defs'. Please use the JSON format.") + filters.append({"field": "mitre_defs", "operator": "EQ", "value": mitre_defs_json}) + if investigation_query_link := args.get("investigation_query_link"): + filters.append({"field": "investigation_query_link", "operator": "EQ", "value": investigation_query_link}) + if drilldown_query_timeframe := args.get("drilldown_query_timeframe"): + filters.append({"field": "drilldown_query_timeframe", "operator": "EQ", "value": drilldown_query_timeframe}) + if mapping_strategy := args.get("mapping_strategy"): + filters.append({"field": "mapping_strategy", "operator": "EQ", "value": mapping_strategy}) + if alert_domain := args.get("alert_domain"): + filters.append({"field": "alert_domain", "operator": "EQ", "value": alert_domain}) + return filters + + +def bioc_list_command(client: Client, args: Dict) -> CommandResults: + """ + API Docs https://docs-cortex.paloaltonetworks.com/r/Cortex-XDR-Platform-APIs/Get-BIOCs + Returns a list of BIOCs. + Args: + client (Client): The client to use. + args (Dict): The command arguments. + Returns: + CommandResults: The command results. + """ + filters = create_filters_for_bioc_and_correlation_rules(args) + page = arg_to_number(args.get("page")) or 0 + limit = arg_to_number(args.get("limit")) or 50 + page_size = arg_to_number(args.get("page_size")) or limit + extended_view = argToBoolean(args.get("extra_data", False)) + + request_data = assign_params( + extended_view=extended_view, + search_from=page * page_size, + search_to=(page + 1) * page_size, + ) + request_data["filters"] = filters + reply = client.get_biocs({"request_data": request_data}) + biocs = reply.get("objects", []) + readable_output = tableToMarkdown( + name="BIOCs List", + t=biocs, + headerTransform=string_to_table_header, + headers=["rule_id", "name", "type", "severity", "status"], + removeNull=True, + ) + return CommandResults( + readable_output=readable_output, + outputs_prefix=f"{INTEGRATION_CONTEXT_BRAND}.BIOC", + outputs_key_field="rule_id", + outputs=biocs, + raw_response=reply, + ) + + +def bioc_create_or_update_helper(client: Client, args: Dict) -> dict: + """ + Creates or updates BIOC indicators based on provided arguments. + Args: + client (Client): The client to use. + args (Dict): The command arguments containing rule configuration. + Returns: + dict: The raw response from the client containing the update/creation results. + """ + bioc_data = { + # required for updating + "rule_id": args.get("rule_id"), + # required fields + "name": args.get("name"), + "severity": BIOC_AND_CR_SEVERITY_MAPPING.get(args.get("severity", "")), + # not required but need to be null + "type": args.get("type"), + "is_xql": argToBoolean(args.get("is_xql")) if args.get("is_xql") else None, + "comment": args.get("comment"), + "status": args.get("status"), + "mitre_technique_id_and_name": args.get("mitre_technique_id_and_name") or [], + "mitre_tactic_id_and_name": args.get("mitre_tactic_id_and_name") or [], + } + indicator = args.get("indicator") # required + if indicator: + try: + indicator = json.loads(indicator) + bioc_data["indicator"] = indicator + except ValueError: + raise DemistoException("Unable to parse 'indicator'. Please use the JSON format.") + + return client.insert_or_update_biocs({"request_data": [bioc_data]}) + + +def bioc_create_command(client: Client, args: Dict) -> CommandResults: + """ + API Docs https://docs-cortex.paloaltonetworks.com/r/Cortex-XDR-Platform-APIs/Insert-or-update-BIOCs + Creates a new BIOC. + Args: + client (Client): The client to use. + args (Dict): The command arguments. + Returns: + CommandResults: The command results. + """ + reply = bioc_create_or_update_helper(client, args) + if added_objects := reply.get("added_objects", []): + message = added_objects[0].get("status") + outputs = {"rule_id": added_objects[0].get("id")} + else: + message = "No BIOCs created." + outputs = {} + + return CommandResults( + readable_output=message, + outputs_prefix=f"{INTEGRATION_CONTEXT_BRAND}.BIOC", + outputs_key_field="rule_id", + outputs=outputs, + raw_response=reply, + ) + + +def bioc_update_command(client: Client, args: Dict) -> CommandResults: + """ + API Docs https://docs-cortex.paloaltonetworks.com/r/Cortex-XDR-Platform-APIs/Insert-or-update-BIOCs + Updates an existing BIOC. + Args: + client (Client): The client to use. + args (Dict): The command arguments. + Returns: + CommandResults: The command results. + """ + reply = bioc_create_or_update_helper(client, args) + if updated_objects := reply.get("updated_objects"): + message = updated_objects[0].get("status") + outputs = {"rule_id": updated_objects[0].get("id")} + else: + message = "No BIOCs updated." + outputs = {} + + return CommandResults( + readable_output=message, + outputs_prefix=f"{INTEGRATION_CONTEXT_BRAND}.BIOC", + outputs_key_field="rule_id", + outputs=outputs, + raw_response=reply, + ) + + +def bioc_delete_command(client: Client, args: Dict) -> CommandResults: + """ + API Docs https://docs-cortex.paloaltonetworks.com/r/Cortex-XDR-Platform-APIs/Delete-BIOCs + Deletes a BIOC. + Args: + client (Client): The client to use. + args (Dict): The command arguments. + Returns: + str: Success message. + """ + filters: list = create_filters_for_bioc_and_correlation_rules(args) + request_data = {"request_data": {"filters": filters}} + res = client.delete_biocs(request_data) + rule_ids = res.get("objects", []) + count = len(rule_ids) + + if count == 1: + return CommandResults(readable_output=f"BIOC with id {rule_ids[0]} deleted successfully.") + + elif count > 1: + ids_str = ", ".join(map(str, rule_ids)) + return CommandResults(readable_output=f"BIOCs with ids {ids_str} deleted successfully.") + + else: + return CommandResults(readable_output="No BIOCs were found to delete.") + + +def correlation_rule_list_command(client: Client, args: Dict) -> CommandResults: + """ + Returns a list of correlation rules. + Args: + client (Client): The client to use. + args (Dict): The command arguments. + Returns: + CommandResults: The command results. + """ + filters = create_filters_for_bioc_and_correlation_rules(args) + if filter_json := args.get("filter_json"): + try: + filter_json = json.loads(filter_json) + filters.extend(filter_json) + except ValueError: + raise DemistoException("Unable to parse 'filter_json'. Please use the JSON format.") + + page = arg_to_number(args.get("page")) or 0 + limit = arg_to_number(args.get("limit")) or 50 + page_size = arg_to_number(args.get("page_size")) or limit + + request_data = assign_params( + extended_view=argToBoolean(args.get("extra_data", False)), + search_from=page * page_size, + search_to=(page + 1) * page_size, + ) + request_data["filters"] = filters + reply = client.get_correlation_rules({"request_data": request_data}) + rules = reply.get("objects", []) + readable_output = tableToMarkdown( + name="Correlation Rules List", + t=rules, + headers=["rule_id", "name", "severity", "description", "is_enabled"], + removeNull=True, + headerTransform=string_to_table_header, + ) + return CommandResults( + readable_output=readable_output, + outputs_prefix=f"{INTEGRATION_CONTEXT_BRAND}.CorrelationRule", + outputs_key_field="rule_id", + outputs=rules, + raw_response=reply, + ) + + +def correlation_rule_create_or_update_helper(client: Client, args: Dict) -> dict: + """ + Creates or updates correlation rules based on provided arguments. + Args: + client (Client): The client to use. + args (Dict): The command arguments containing rule configuration. + Returns: + dict: The raw response from the client containing the update/creation results. + """ + severity_arg = args.get("severity") + severity_mapped = BIOC_AND_CR_SEVERITY_MAPPING.get(severity_arg) if severity_arg else None + + rule_data = { + # Required fields + "rule_id": args.get("rule_id"), + "name": args.get("name"), + "severity": severity_mapped, + "xql_query": args.get("xql_query"), + "is_enabled": argToBoolean(args.get("is_enabled")) if args.get("is_enabled") else None, + "action": "ALERTS", + "timezone": args.get("timezone"), + "dataset": args.get("dataset"), + "alert_category": args.get("alert_category", "").upper() if args.get("alert_category") else None, + "execution_mode": args.get("execution_mode", "").upper() if args.get("execution_mode") else None, + "mapping_strategy": args.get("mapping_strategy", "").upper() if args.get("mapping_strategy") else None, + # Use 'or None' to convert empty strings ("") into JSON null. We must put a value for those fields. + "description": args.get("description") or None, + "alert_name": args.get("alert_name") or None, + "alert_description": args.get("alert_description") or None, + "search_window": args.get("search_window") or None, + "crontab": args.get("schedule_linux") or None, + "simple_schedule": args.get("schedule") or None, + "drilldown_query_timeframe": args.get("drilldown_query_timeframe") or None, + "investigation_query_link": args.get("investigation_query_link") or None, + "suppression_enabled": argToBoolean(args.get("suppression_enabled")) if args.get("suppression_enabled") else None, + "suppression_duration": args.get("suppression_duration") or None, + "suppression_fields": args.get("suppression_fields") or None, + # User Defined Severity (Must be None unless Severity is "User Defined") + "user_defined_severity": args.get("user_defined_severity") or None, + "user_defined_category": args.get("user_defined_category") or None, + # Defaults to "{}" string if missing so json.loads doesn't fail + "mitre_defs": json.loads(args.get("mitre_defs_json") or "{}"), + "alert_fields": json.loads(args.get("alert_fields") or "{}"), + } + + return client.create_or_update_correlation_rules({"request_data": [rule_data]}) + + +def correlation_rule_create_command(client: Client, args: Dict) -> CommandResults: + """ + API Docs https://docs-cortex.paloaltonetworks.com/r/Cortex-XDR-Platform-APIs/Insert-or-update-Correlation-Rules + Creates a new correlation rule. + Args: + client (Client): The client to use. + args (Dict): The command arguments. + Returns: + CommandResults: The command results. + """ + reply = correlation_rule_create_or_update_helper(client, args) + if added_objects := reply.get("added_objects", []): + message = added_objects[0].get("status") + outputs = {"rule_id": added_objects[0].get("id")} + else: + message = "No Correlation Rules created." + outputs = {} + return CommandResults( + readable_output=message, + outputs_prefix=f"{INTEGRATION_CONTEXT_BRAND}.CorrelationRule", + outputs_key_field="rule_id", + outputs=outputs, + raw_response=reply, + ) + + +def correlation_rule_update_command(client: Client, args: Dict) -> CommandResults: + """ + API Docs https://docs-cortex.paloaltonetworks.com/r/Cortex-XDR-Platform-APIs/Insert-or-update-Correlation-Rules + Updates an existing correlation rule. + Args: + client (Client): The client to use. + args (Dict): The command arguments. + Returns: + CommandResults: The command results. + """ + reply = correlation_rule_create_or_update_helper(client, args) + if updated_objects := reply.get("updated_objects"): + message = updated_objects[0].get("status") + outputs = {"rule_id": updated_objects[0].get("id")} + else: + message = "No Correlation Rules updated." + outputs = {} + return CommandResults( + readable_output=message, + outputs_prefix=f"{INTEGRATION_CONTEXT_BRAND}.CorrelationRule", + outputs_key_field="rule_id", + outputs=outputs, + raw_response=reply, + ) + + +def correlation_rule_delete_command(client: Client, args: Dict) -> CommandResults: + """ + API Docs https://docs-cortex.paloaltonetworks.com/r/Cortex-XDR-Platform-APIs/Delete-Correlation-Rules + Deletes correlation rules. + Args: + client (Client): The client to use. + args (Dict): The command arguments. + Returns: + str: Success message. + """ + rule_ids = argToList(args.get("rule_id")) + rule_id_list = [] + for rule_id in rule_ids: + try: + rule_id_list.append({"field": "rule_id", "operator": "EQ", "value": int(rule_id)}) + except (ValueError, TypeError): + # If rule_id is None, "abc", or "", skip it safely + continue + + reply = client.delete_correlation_rules({"request_data": {"filters": rule_id_list}}) + deleted_ids = reply.get("objects", []) + objects_count = reply.get("objects_count") + + if objects_count == 0: + status = "Could not find any correlation rules to delete." + elif objects_count == 1: + status = f"Correlation Rule {deleted_ids[0]} was deleted." + else: + ids_str = ", ".join(map(str, deleted_ids)) + status = f"Correlation Rules {ids_str} were deleted." + + return CommandResults(readable_output=status, raw_response=reply) + + def api_key_list_command(client: Client, args: Dict[str, Any]) -> CommandResults: """ API Docs: https://docs-cortex.paloaltonetworks.com/r/Cortex-XDR-Platform-APIs/Get-existing-API-keys @@ -2369,6 +2841,30 @@ def main(): # pragma: no cover elif command == "xdr-update-alert": return_results(update_alerts_in_xdr_command(client, args)) + elif command == "xdr-bioc-list": + return_results(bioc_list_command(client, args)) + + elif command == "xdr-bioc-create": + return_results(bioc_create_command(client, args)) + + elif command == "xdr-bioc-update": + return_results(bioc_update_command(client, args)) + + elif command == "xdr-bioc-delete": + return_results(bioc_delete_command(client, args)) + + elif command == "xdr-correlation-rule-list": + return_results(correlation_rule_list_command(client, args)) + + elif command == "xdr-correlation-rule-create": + return_results(correlation_rule_create_command(client, args)) + + elif command == "xdr-correlation-rule-update": + return_results(correlation_rule_update_command(client, args)) + + elif command == "xdr-correlation-rule-delete": + return_results(correlation_rule_delete_command(client, args)) + elif command == "xdr-api-key-list": return_results(api_key_list_command(client, args)) diff --git a/Packs/CortexXDR/Integrations/CortexXDRIR/CortexXDRIR.yml b/Packs/CortexXDR/Integrations/CortexXDRIR/CortexXDRIR.yml index a3d31dddb5df..48fc8cbe56ea 100644 --- a/Packs/CortexXDR/Integrations/CortexXDRIR/CortexXDRIR.yml +++ b/Packs/CortexXDR/Integrations/CortexXDRIR/CortexXDRIR.yml @@ -4132,6 +4132,565 @@ script: name: membership_predicate_json description: Updates an asset group. name: xdr-asset-group-update + - arguments: + - description: 'The BIOC name to filter by. Can filter only by one name.' + name: name + - auto: PREDEFINED + description: 'The BIOC severity to filter by.' + name: severity + predefined: + - info + - low + - medium + - high + - critical + - auto: PREDEFINED + description: 'The BIOC type to filter by.' + name: type + predefined: + - other + - persistence + - evasion + - tampering + - file_type_obfuscation + - privilege_escalation + - credential_access + - lateral_movement + - execution + - collection + - exfiltration + - infiltration + - dropper + - file_privilege_manipulation + - reconnaissance + - discovery + - auto: PREDEFINED + description: 'Whether the BIOC is XQL.' + name: is_xql + predefined: + - 'true' + - 'false' + - description: 'The BIOC comment to filter by.' + name: comment + - auto: PREDEFINED + description: 'The BIOC status to filter by. Can be one of the following: enabled, disabled.' + name: status + predefined: + - enabled + - disabled + - description: 'The BIOC indicator to filter by.' + name: indicator + - description: "The MITRE technique ID and name. Must be in format 'ID - Name', for example: ['T1566 - Phishing']." + isArray: true + name: mitre_technique_id_and_name + - description: "The MITRE tactic ID and name. Must be in format 'ID - Name', for example: ['T1566 - Phishing']." + isArray: true + name: mitre_tactic_id_and_name + - auto: PREDEFINED + description: Whether to return extended data. + name: extra_data + predefined: + - 'true' + - 'false' + - description: Maximum number of results to return. + name: limit + - description: Page size. + name: page_size + - description: Page number. + name: page + description: Returns a list of BIOCs. + name: xdr-bioc-list + outputs: + - contextPath: PaloAltoNetworksXDR.BIOC.rule_id + description: BIOC rule id. + type: String + - contextPath: PaloAltoNetworksXDR.BIOC.name + description: BIOC name. + type: String + - contextPath: PaloAltoNetworksXDR.BIOC.type + description: BIOC type. + type: String + - contextPath: PaloAltoNetworksXDR.BIOC.severity + description: BIOC severity. + type: String + - contextPath: PaloAltoNetworksXDR.BIOC.status + description: BIOC status. + type: String + - contextPath: PaloAltoNetworksXDR.BIOC.is_xql + description: Whether the BIOC is XQL. + type: Boolean + - contextPath: PaloAltoNetworksXDR.BIOC.comment + description: The BIOC comment. + type: String + - contextPath: PaloAltoNetworksXDR.BIOC.indicator + description: The BIOC indicator. + type: String + - arguments: + - description: The BIOC name. + name: name + required: true + - auto: PREDEFINED + description: The BIOC severity. + name: severity + required: true + predefined: + - info + - low + - medium + - high + - critical + - auto: PREDEFINED + description: The BIOC type. + name: type + predefined: + - other + - persistence + - evasion + - tampering + - file_type_obfuscation + - privilege_escalation + - credential_access + - lateral_movement + - execution + - collection + - exfiltration + - infiltration + - dropper + - file_privilege_manipulation + - reconnaissance + - discovery + - auto: PREDEFINED + description: Whether the new BIOC is XQL. + name: is_xql + predefined: + - 'true' + - 'false' + - description: The BIOC comment. + name: comment + - auto: PREDEFINED + description: The BIOC status. + name: status + predefined: + - enabled + - disabled + - description: |- + The BIOC indicator, + for example: '{\"runOnCGO\":true,\"investigationType\":\"FILE_EVENT\",\"investigation\":{\"FILE_EVENT\":{\"filter\":{\"AND\":[{\"SEARCH_FIELD\":\"action_file_name\",\"SEARCH_TYPE\":\"EQ\",\"SEARCH_VALUE\":\"testfile.exe\"}]}}}}'. + For more information please refer to the documentation https://docs-cortex.paloaltonetworks.com/r/Cortex-XDR-Platform-APIs/Insert-or-update-BIOCs. + isArray: true + name: indicator + required: true + - description: "The MITRE technique ID and name. Must be in format 'ID - Name', for example: ['T1566 - Phishing']." + isArray: true + name: mitre_technique_id_and_name + - description: "The MITRE tactic ID and name. Must be in format 'ID - Name', for example: ['TA0001 - Initial Access']." + isArray: true + name: mitre_tactic_id_and_name + description: Creates a new BIOC. + name: xdr-bioc-create + outputs: + - contextPath: PaloAltoNetworksXDR.BIOC.rule_id + description: BIOC ID. + type: String + - arguments: + - description: BIOC rule ID. + name: rule_id + required: true + - description: BIOC name. + name: name + required: true + - auto: PREDEFINED + description: BIOC severity. + name: severity + required: true + predefined: + - info + - low + - medium + - high + - critical + - auto: PREDEFINED + description: BIOC type. + name: type + predefined: + - other + - persistence + - evasion + - tampering + - file_type_obfuscation + - privilege_escalation + - credential_access + - lateral_movement + - execution + - collection + - exfiltration + - infiltration + - dropper + - file_privilege_manipulation + - reconnaissance + - discovery + - auto: PREDEFINED + description: Whether the BIOC is XQL. + name: is_xql + predefined: + - 'true' + - 'false' + - description: BIOC comment. + name: comment + - auto: PREDEFINED + description: BIOC status. + name: status + predefined: + - enabled + - disabled + - description: |- + The BIOC indicator, + for example: '{\"runOnCGO\":true,\"investigationType\":\"FILE_EVENT\",\"investigation\":{\"FILE_EVENT\":{\"filter\":{\"AND\":[{\"SEARCH_FIELD\":\"action_file_name\",\"SEARCH_TYPE\":\"EQ\",\"SEARCH_VALUE\":\"testfile.exe\"}]}}}}'. + For more information please refer to the documentation https://docs-cortex.paloaltonetworks.com/r/Cortex-XDR-Platform-APIs/Insert-or-update-BIOCs. + isArray: true + name: indicator + required: true + - description: "The MITRE technique ID and name. Must be in format 'ID - Name', for example: ['T1566 - Phishing']." + isArray: true + name: mitre_technique_id_and_name + - description: "The MITRE tactic ID and name. Must be in format 'ID - Name', for example: ['TA0001 - Initial Access']." + isArray: true + name: mitre_tactic_id_and_name + description: Updates an existing BIOC. + name: xdr-bioc-update + outputs: + - contextPath: PaloAltoNetworksXDR.BIOC.rule_id + description: BIOC ID. + type: String + - arguments: + - description: BIOC name. + name: name + - auto: PREDEFINED + description: BIOC severity. + name: severity + predefined: + - info + - low + - medium + - high + - critical + - auto: PREDEFINED + description: BIOC type. + name: type + predefined: + - other + - persistence + - evasion + - tampering + - file_type_obfuscation + - privilege_escalation + - credential_access + - lateral_movement + - execution + - collection + - exfiltration + - infiltration + - dropper + - file_privilege_manipulation + - reconnaissance + - discovery + - auto: PREDEFINED + description: Whether the BIOC is XQL. + name: is_xql + predefined: + - 'true' + - 'false' + - description: BIOC comment. + name: comment + - description: BIOC indicator. + isArray: true + name: indicator + - description: "The MITRE technique ID and name. Must be in format 'ID - Name', for example: ['T1566 - Phishing']." + isArray: true + name: mitre_technique_id_and_name + - description: "The MITRE tactic ID and name. Must be in format 'ID - Name', for example: ['TA0001 - Initial Access']." + isArray: true + name: mitre_tactic_id_and_name + description: Deletes a BIOC. + name: xdr-bioc-delete + - arguments: + - description: Correlation rule name. + name: name + - auto: PREDEFINED + description: Correlation rule severity. + name: severity + predefined: + - info + - low + - medium + - high + - critical + - description: Correlation rule XQL query. + name: xql_query + - auto: PREDEFINED + description: Whether the correlation rule is XQL. + name: is_xql + predefined: + - 'true' + - 'false' + - description: Correlation rule dataset. + name: dataset + - description: Alert name. + name: alert_name + - description: Alert category. + name: alert_category + - description: Alert fields. Can be a string or a dictionary. + name: alert_fields + - description: Alert domain. + name: alert_domain + - description: Filter JSON. + name: filter_json + - auto: PREDEFINED + description: Whether to return extended view. + name: extra_data + predefined: + - 'true' + - 'false' + - description: Maximum number of results to return. + name: limit + - description: Page size. + name: page_size + - description: Page number. + name: page + description: Returns a list of correlation rules. + name: xdr-correlation-rule-list + outputs: + - contextPath: PaloAltoNetworksXDR.CorrelationRule.rule_id + description: Correlation rule ID. + type: String + - contextPath: PaloAltoNetworksXDR.CorrelationRule.name + description: Correlation rule name. + type: String + - contextPath: PaloAltoNetworksXDR.CorrelationRule.description + description: Correlation rule description. + type: String + - contextPath: PaloAltoNetworksXDR.CorrelationRule.is_enabled + description: Whether the correlation rule is enabled. + type: Boolean + - arguments: + - description: 'The correlation rule name. Example: name="This is a CR test 6"' + name: name + required: true + - auto: PREDEFINED + description: 'The correlation rule severity. Example: severity=low' + name: severity + required: true + predefined: + - info + - low + - medium + - high + - critical + - description: 'The correlation rule XQL query. Example: xql_query="dataset = xdr_data | limit 1"' + name: xql_query + required: true + - auto: PREDEFINED + description: 'Whether the rule is enabled. Example: is_enabled=true' + name: is_enabled + required: true + predefined: + - 'true' + - 'false' + - auto: PREDEFINED + description: 'The alert category. Example: alert_category=dropper' + name: alert_category + required: true + predefined: + - other + - persistence + - evasion + - tampering + - file_type_obfuscation + - privilege_escalation + - credential_access + - lateral_movement + - execution + - collection + - exfiltration + - infiltration + - dropper + - file_privilege_manipulation + - reconnaissance + - discovery + - auto: PREDEFINED + description: 'The rule execution mode. Example: execution_mode=scheduled' + name: execution_mode + required: true + predefined: + - scheduled + - real_time + - description: 'The correlation rule timezone. Example: timezone="Asia/Jerusalem"' + name: timezone + required: true + - auto: PREDEFINED + description: 'The rule mapping strategy. Example: mapping_strategy=auto' + name: mapping_strategy + required: true + predefined: + - auto + - custom + - description: The correlation rule description. + name: description + - description: The alert name. + name: alert_name + - description: The alert description. + name: alert_description + - description: Alert fields (string or dictionary). + name: alert_fields + - description: 'The search window timeframe. Example: search_window="1 hours"' + name: search_window + - description: 'The correlation rule schedule. Example: schedule="10 minutes"' + name: schedule + - description: 'Linux scheduling for the rule. Example: schedule_linux="*/10 * * * *"' + name: schedule_linux + - auto: PREDEFINED + description: Whether suppression is enabled. + name: suppression_enabled + predefined: + - 'true' + - 'false' + - description: Duration of correlation rule suppression. + name: suppression_duration + - description: Suppression fields. + name: suppression_fields + - description: 'The correlation rule dataset. Example: dataset=alerts' + name: dataset + required: true + - description: User-defined severity. + name: user_defined_severity + - description: User-defined category. + name: user_defined_category + - description: MITRE definitions. + name: mitre_defs_json + - description: Investigation query link. + name: investigation_query_link + - description: 'The drilldown query timeframe. Example: drilldown_query_timeframe="ALERT"' + name: drilldown_query_timeframe + description: Creates a new correlation rule. + name: xdr-correlation-rule-create + outputs: + - contextPath: PaloAltoNetworksXDR.CorrelationRule.rule_id + description: Correlation rule ID. + type: String + - arguments: + - description: Correlation rule ID. + name: rule_id + required: true + - description: 'The correlation rule name. Example: name="This is a CR test 6"' + name: name + required: true + - auto: PREDEFINED + description: 'The correlation rule severity. Example: severity=low' + name: severity + required: true + predefined: + - info + - low + - medium + - high + - critical + - description: 'The correlation rule XQL query. Example: xql_query="dataset = xdr_data | limit 1"' + name: xql_query + required: true + - auto: PREDEFINED + description: 'Whether the rule is enabled. Example: is_enabled=true' + name: is_enabled + required: true + predefined: + - 'true' + - 'false' + - auto: PREDEFINED + description: 'The alert category. Example: alert_category=dropper' + name: alert_category + required: true + predefined: + - other + - persistence + - evasion + - tampering + - file_type_obfuscation + - privilege_escalation + - credential_access + - lateral_movement + - execution + - collection + - exfiltration + - infiltration + - dropper + - file_privilege_manipulation + - reconnaissance + - discovery + - description: 'The rule execution mode. Example: execution_mode=scheduled' + name: execution_mode + required: true + predefined: + - scheduled + - real_time + - description: 'The correlation rule timezone. Example: timezone="Asia/Jerusalem"' + name: timezone + required: true + - auto: PREDEFINED + description: 'The rule mapping strategy. Example: mapping_strategy=auto' + name: mapping_strategy + required: true + predefined: + - auto + - custom + - description: The correlation rule description. + name: description + - description: The alert name. + name: alert_name + - description: The alert description. + name: alert_description + - description: Alert fields (string or dictionary). + name: alert_fields + - description: 'The search window timeframe. Example: search_window="1 hours"' + name: search_window + - description: 'The correlation rule schedule. Example: schedule="10 minutes"' + name: schedule + - description: 'Linux scheduling for the rule. Example: schedule_linux="*/10 * * * *"' + name: schedule_linux + - auto: PREDEFINED + description: Whether suppression is enabled. + name: suppression_enabled + predefined: + - 'true' + - 'false' + - description: Duration of correlation rule suppression. + name: suppression_duration + - description: Suppression fields. + name: suppression_fields + - description: 'The correlation rule dataset. Example: dataset=alerts' + name: dataset + required: true + - description: User-defined severity. + name: user_defined_severity + - description: User-defined category. + name: user_defined_category + - description: MITRE definitions. + name: mitre_defs_json + - description: Investigation query link. + name: investigation_query_link + - description: 'The drilldown query timeframe. Example: drilldown_query_timeframe="ALERT"' + name: drilldown_query_timeframe + description: Updates an existing correlation rule. + name: xdr-correlation-rule-update + outputs: + - contextPath: PaloAltoNetworksXDR.CorrelationRule.id + description: Correlation rule ID. + type: String + - arguments: + - description: Correlation rule ID. + isArray: true + name: rule_id + required: true + description: Deletes correlation rules. + name: xdr-correlation-rule-delete dockerimage: demisto/python3:3.12.12.5490952 isfetch: true isfetch:xpanse: false diff --git a/Packs/CortexXDR/Integrations/CortexXDRIR/CortexXDRIR_test.py b/Packs/CortexXDR/Integrations/CortexXDRIR/CortexXDRIR_test.py index c019b9c6ef0d..64aefe45bab6 100644 --- a/Packs/CortexXDR/Integrations/CortexXDRIR/CortexXDRIR_test.py +++ b/Packs/CortexXDR/Integrations/CortexXDRIR/CortexXDRIR_test.py @@ -2785,3 +2785,523 @@ def test_replace_dots_in_keys(): # Test with non-dict/list input assert replace_dots_in_keys("string.with.dots") == "string.with.dots" assert replace_dots_in_keys(123) == 123 + + +@pytest.mark.parametrize( + "args, expected_request_data", + [ + ( + {"name": "test_bioc", "severity": "high", "page": "1", "limit": "10", "extra_data": "true"}, + { + "request_data": { + "filters": [ + {"field": "name", "operator": "EQ", "value": "test_bioc"}, + {"field": "severity", "operator": "EQ", "value": "SEV_040_HIGH"}, + ], + "extended_view": True, + "search_from": 10, + "search_to": 20, + } + }, + ), + ( + {}, + { + "request_data": { + "extended_view": False, + "search_from": 0, + "search_to": 50, + "filters": [], + } + }, + ), + ], +) +def test_bioc_list_command(mocker, args, expected_request_data): + """ + Given: + - Arguments for filtering BIOCs. + When: + - Running bioc_list_command. + Then: + - Verify the client.get_biocs is called with correct parameters. + """ + from CortexXDRIR import Client, bioc_list_command + + client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False) + mock_reply = {"objects": [{"rule_id": "1", "name": "test_bioc", "type": "host", "severity": "high", "status": "enabled"}]} + mocker.patch.object(Client, "get_biocs", return_value=mock_reply) + + res = bioc_list_command(client, args) + + assert res.outputs == mock_reply["objects"] + Client.get_biocs.assert_called_with(expected_request_data) + + +@pytest.mark.parametrize( + "args, expected_request_data, expected_output, expected_error", + [ + ( + { + "name": "test_bioc", + "severity": "high", + "type": "bioc_type", + "is_xql": "true", + "comment": "test_comment", + "status": "enabled", + "mitre_technique_id_and_name": "T1000", + "mitre_tactic_id_and_name": "TA0001", + "indicator": '{"field": "value"}', + }, + { + "request_data": [ + { + "rule_id": None, + "name": "test_bioc", + "severity": "SEV_040_HIGH", + "type": "bioc_type", + "is_xql": True, + "comment": "test_comment", + "status": "enabled", + "mitre_technique_id_and_name": "T1000", + "mitre_tactic_id_and_name": "TA0001", + "indicator": {"field": "value"}, + } + ] + }, + {"rule_id": "1", "readable_output": "BIOC created successfully"}, + None, + ), + ( + {"name": "test_bioc", "severity": "high", "indicator": "invalid_json"}, + None, + None, + "Unable to parse 'indicator'. Please use the JSON format.", + ), + ], +) +def test_bioc_create_command(mocker, args, expected_request_data, expected_output, expected_error): + """ + Given: + - Arguments for creating a BIOC. + When: + - Running bioc_create_command. + Then: + - Verify the client.insert_or_update_biocs is called and results are correct. + """ + from CortexXDRIR import Client, bioc_create_command + + client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False) + mock_reply = {"added_objects": [{"id": "1", "status": "BIOC created successfully"}]} + mocker.patch.object(Client, "insert_or_update_biocs", return_value=mock_reply) + + if expected_error: + with pytest.raises(DemistoException) as e: + bioc_create_command(client, args) + assert expected_error in str(e.value) + else: + res = bioc_create_command(client, args) + assert res.outputs == {"rule_id": expected_output["rule_id"]} + assert res.readable_output == expected_output["readable_output"] + Client.insert_or_update_biocs.assert_called_with(expected_request_data) + + +@pytest.mark.parametrize( + "mock_reply, expected_output, expected_readable_output", + [ + ( + {"updated_objects": [{"id": "1", "status": "BIOC updated successfully"}]}, + {"rule_id": "1"}, + "BIOC updated successfully", + ), + ( + {"updated_objects": []}, + {}, + "No BIOCs updated.", + ), + ], +) +def test_bioc_update_command(mocker, mock_reply, expected_output, expected_readable_output): + """ + Given: + - Arguments for updating a BIOC. + When: + - Running bioc_update_command. + Then: + - Verify the client.insert_or_update_biocs is called and results are correct. + """ + from CortexXDRIR import Client, bioc_update_command + + client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False) + mocker.patch.object(Client, "insert_or_update_biocs", return_value=mock_reply) + + args = {"rule_id": "1", "name": "test_bioc", "severity": "high"} + res = bioc_update_command(client, args) + + assert res.outputs == expected_output + assert res.readable_output == expected_readable_output + + +@pytest.mark.parametrize( + "mock_reply, expected_output", + [ + ({"objects": ["1"]}, "BIOC with id 1 deleted successfully."), + ({"objects": ["1", "2"]}, "BIOCs with ids 1, 2 deleted successfully."), + ({"objects": []}, "No BIOCs were found to delete."), + ], +) +def test_bioc_delete_command(mocker, mock_reply, expected_output): + """ + Given: + - Arguments for deleting a BIOC. + When: + - Running bioc_delete_command. + Then: + - Verify the client.delete_biocs is called and results are correct. + """ + from CortexXDRIR import Client, bioc_delete_command + + client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False) + mocker.patch.object(Client, "delete_biocs", return_value=mock_reply) + + args = {"name": "test_bioc"} + res = bioc_delete_command(client, args) + + assert res.readable_output == expected_output + + +@pytest.mark.parametrize( + "args, expected_request_data", + [ + ( + {"name": "test_rule", "page": "1", "limit": "10", "extra_data": "true"}, + { + "request_data": { + "filters": [{"field": "name", "operator": "EQ", "value": "test_rule"}], + "extended_view": True, + "search_from": 10, + "search_to": 20, + } + }, + ), + ( + {"filter_json": '[{"field": "name", "operator": "EQ", "value": "test_rule"}]'}, + { + "request_data": { + "filters": [{"field": "name", "operator": "EQ", "value": "test_rule"}], + "extended_view": False, + "search_from": 0, + "search_to": 50, + } + }, + ), + ], +) +def test_correlation_rule_list_command(mocker, args, expected_request_data): + """ + Given: + - Arguments for filtering correlation rules. + When: + - Running correlation_rule_list_command. + Then: + - Verify the client.get_correlation_rules is called and results are correct. + """ + from CortexXDRIR import Client, correlation_rule_list_command + + client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False) + mock_reply = {"objects": [{"rule_id": "1", "name": "test_rule", "description": "desc", "is_enabled": True}]} + mocker.patch.object(Client, "get_correlation_rules", return_value=mock_reply) + + res = correlation_rule_list_command(client, args) + + assert res.outputs == mock_reply["objects"] + Client.get_correlation_rules.assert_called_with(expected_request_data) + + +@pytest.mark.parametrize( + "args, expected_request_data, expected_output", + [ + ( + { + "name": "test_rule", + "severity": "high", + "xql_query": "dataset = xdr_data", + "is_enabled": "true", + "timezone": "UTC", + "dataset": "xdr_data", + "alert_category": "category", + "execution_mode": "real_time", + "mapping_strategy": "strategy", + "description": "desc", + "alert_name": "alert", + "alert_description": "alert_desc", + "search_window": "1h", + "schedule_linux": "* * * * *", + "schedule": "daily", + "drilldown_query_timeframe": "1h", + "investigation_query_link": "link", + "suppression_enabled": "true", + "suppression_duration": "1h", + "suppression_fields": "field1", + "user_defined_severity": "medium", + "user_defined_category": "cat", + "mitre_defs_json": '{"tactic": "test"}', + "alert_fields": '{"field": "value"}', + }, + { + "request_data": [ + { + "rule_id": None, + "name": "test_rule", + "severity": "SEV_040_HIGH", + "xql_query": "dataset = xdr_data", + "is_enabled": True, + "action": "ALERTS", + "timezone": "UTC", + "dataset": "xdr_data", + "alert_category": "CATEGORY", + "execution_mode": "REAL_TIME", + "mapping_strategy": "STRATEGY", + "description": "desc", + "alert_name": "alert", + "alert_description": "alert_desc", + "search_window": "1h", + "crontab": "* * * * *", + "simple_schedule": "daily", + "drilldown_query_timeframe": "1h", + "investigation_query_link": "link", + "suppression_enabled": True, + "suppression_duration": "1h", + "suppression_fields": "field1", + "user_defined_severity": "medium", + "user_defined_category": "cat", + "mitre_defs": {"tactic": "test"}, + "alert_fields": {"field": "value"}, + } + ] + }, + {"rule_id": "1", "readable_output": "Rule created successfully"}, + ) + ], +) +def test_correlation_rule_create_command(mocker, args, expected_request_data, expected_output): + """ + Given: + - Arguments for creating a correlation rule. + When: + - Running correlation_rule_create_command. + Then: + - Verify the client.create_or_update_correlation_rules is called and results are correct. + """ + from CortexXDRIR import Client, correlation_rule_create_command + + client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False) + mock_reply = {"added_objects": [{"id": "1", "status": "Rule created successfully"}]} + mocker.patch.object(Client, "create_or_update_correlation_rules", return_value=mock_reply) + + res = correlation_rule_create_command(client, args) + + assert res.outputs == {"rule_id": expected_output["rule_id"]} + assert res.readable_output == expected_output["readable_output"] + Client.create_or_update_correlation_rules.assert_called_with(expected_request_data) + + +@pytest.mark.parametrize( + "args, expected_request_data, expected_output", + [ + ( + {"rule_id": "1", "name": "test_rule", "severity": "high", "is_enabled": "true"}, + { + "request_data": [ + { + "rule_id": "1", + "name": "test_rule", + "severity": "SEV_040_HIGH", + "xql_query": None, + "is_enabled": True, + "action": "ALERTS", + "timezone": None, + "dataset": None, + "alert_category": None, + "execution_mode": None, + "mapping_strategy": None, + "description": None, + "alert_name": None, + "alert_description": None, + "search_window": None, + "crontab": None, + "simple_schedule": None, + "drilldown_query_timeframe": None, + "investigation_query_link": None, + "suppression_enabled": None, + "suppression_duration": None, + "suppression_fields": None, + "user_defined_severity": None, + "user_defined_category": None, + "mitre_defs": {}, + "alert_fields": {}, + } + ] + }, + {"rule_id": "1", "readable_output": "Rule updated successfully"}, + ) + ], +) +def test_correlation_rule_update_command(mocker, args, expected_request_data, expected_output): + """ + Given: + - Arguments for updating a correlation rule. + When: + - Running correlation_rule_update_command. + Then: + - Verify the client.create_or_update_correlation_rules is called and results are correct. + """ + from CortexXDRIR import Client, correlation_rule_update_command + + client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False) + mock_reply = {"updated_objects": [{"id": "1", "status": "Rule updated successfully"}]} + mocker.patch.object(Client, "create_or_update_correlation_rules", return_value=mock_reply) + + res = correlation_rule_update_command(client, args) + + assert res.outputs == {"rule_id": expected_output["rule_id"]} + assert res.readable_output == expected_output["readable_output"] + Client.create_or_update_correlation_rules.assert_called_with(expected_request_data) + + +@pytest.mark.parametrize( + "mock_reply, args, expected_output, expected_filters", + [ + ( + {"objects": ["1"], "objects_count": 1}, + {"rule_id": "1"}, + "Correlation Rule 1 was deleted.", + {"request_data": {"filters": [{"field": "rule_id", "operator": "EQ", "value": 1}]}}, + ), + ( + {"objects": ["1", "2"], "objects_count": 2}, + {"rule_id": "1,2"}, + "Correlation Rules 1, 2 were deleted.", + { + "request_data": { + "filters": [ + {"field": "rule_id", "operator": "EQ", "value": 1}, + {"field": "rule_id", "operator": "EQ", "value": 2}, + ] + } + }, + ), + ( + {"objects": [], "objects_count": 0}, + {"rule_id": "1"}, + "Could not find any correlation rules to delete.", + {"request_data": {"filters": [{"field": "rule_id", "operator": "EQ", "value": 1}]}}, + ), + ( + {"objects": ["1"], "objects_count": 1}, + {"rule_id": "1,invalid"}, + "Correlation Rule 1 was deleted.", + {"request_data": {"filters": [{"field": "rule_id", "operator": "EQ", "value": 1}]}}, + ), + ], +) +def test_correlation_rule_delete_command(mocker, mock_reply, args, expected_output, expected_filters): + """ + Given: + - Arguments for deleting correlation rules. + When: + - Running correlation_rule_delete_command. + Then: + - Verify the client.delete_correlation_rules is called and results are correct. + """ + from CortexXDRIR import Client, correlation_rule_delete_command + + client = Client(base_url=f"{XDR_URL}/public_api/v1", verify=False, timeout=120, proxy=False) + mocker.patch.object(Client, "delete_correlation_rules", return_value=mock_reply) + + res = correlation_rule_delete_command(client, args) + + assert res.readable_output == expected_output + Client.delete_correlation_rules.assert_called_with(expected_filters) + + +def test_create_filters_for_bioc_and_correlation_rules_all_fields(): + """ + Given: + - All possible arguments for creating filters. + When: + - Running create_filters_for_bioc_and_correlation_rules. + Then: + - Verify the returned filters list contains all expected filters. + """ + from CortexXDRIR import create_filters_for_bioc_and_correlation_rules + + args = { + "name": "test_name", + "severity": "high", + "type": "bioc_type", + "is_xql": "true", + "comment": "test_comment", + "status": "enabled", + "indicator": "test_indicator", + "mitre_technique_id_and_name": "T1000,T1001", + "mitre_tactic_id_and_name": "TA0001,TA0002", + "xql_query": "test_query", + "is_enabled": "true", + "description": "test_description", + "alert_name": "test_alert_name", + "alert_category": "test_alert_category", + "alert_description": "test_alert_description", + "alert_fields": "field1,field2", + "execution_mode": "real_time", + "search_window": "1h", + "schedule": "daily", + "timezone": "UTC", + "schedule_linux": "* * * * *", + "suppression_enabled": "true", + "suppression_duration": "1h", + "suppression_fields": "field1", + "dataset": "xdr_data", + "user_defined_severity": "medium", + "user_defined_category": "category", + "mitre_defs_json": '{"tactic": "test"}', + "investigation_query_link": "link", + "drilldown_query_timeframe": "1h", + "mapping_strategy": "strategy", + "alert_domain": "domain", + } + + filters = create_filters_for_bioc_and_correlation_rules(args) + + assert {"field": "name", "operator": "EQ", "value": "test_name"} in filters + assert {"field": "severity", "operator": "EQ", "value": "SEV_040_HIGH"} in filters + assert {"field": "type", "operator": "EQ", "value": "bioc_type"} in filters + assert {"field": "is_xql", "operator": "EQ", "value": True} in filters + assert {"field": "comment", "operator": "EQ", "value": "test_comment"} in filters + assert {"field": "status", "operator": "EQ", "value": "enabled"} in filters + assert {"field": "indicator", "operator": "EQ", "value": "test_indicator"} in filters + assert {"field": "mitre_technique_id_and_name", "operator": "EQ", "value": ["T1000", "T1001"]} in filters + assert {"field": "mitre_tactic_id_and_name", "operator": "EQ", "value": ["TA0001", "TA0002"]} in filters + assert {"field": "xql_query", "operator": "EQ", "value": "test_query"} in filters + assert {"field": "is_enabled", "operator": "EQ", "value": True} in filters + assert {"field": "description", "operator": "EQ", "value": "test_description"} in filters + assert {"field": "alert_name", "operator": "EQ", "value": "test_alert_name"} in filters + assert {"field": "alert_category", "operator": "EQ", "value": "test_alert_category"} in filters + assert {"field": "alert_description", "operator": "EQ", "value": "test_alert_description"} in filters + assert {"field": "alert_fields", "operator": "EQ", "value": ["field1", "field2"]} in filters + assert {"field": "execution_mode", "operator": "EQ", "value": "real_time"} in filters + assert {"field": "search_window", "operator": "EQ", "value": "1h"} in filters + assert {"field": "schedule", "operator": "EQ", "value": "daily"} in filters + assert {"field": "timezone", "operator": "EQ", "value": "UTC"} in filters + assert {"field": "schedule_linux", "operator": "EQ", "value": "* * * * *"} in filters + assert {"field": "suppression_enabled", "operator": "EQ", "value": True} in filters + assert {"field": "suppression_duration", "operator": "EQ", "value": "1h"} in filters + assert {"field": "suppression_fields", "operator": "EQ", "value": "field1"} in filters + assert {"field": "dataset", "operator": "EQ", "value": "xdr_data"} in filters + assert {"field": "user_defined_severity", "operator": "EQ", "value": "medium"} in filters + assert {"field": "user_defined_category", "operator": "EQ", "value": "category"} in filters + assert {"field": "mitre_defs", "operator": "EQ", "value": {"tactic": "test"}} in filters + assert {"field": "investigation_query_link", "operator": "EQ", "value": "link"} in filters + assert {"field": "drilldown_query_timeframe", "operator": "EQ", "value": "1h"} in filters + assert {"field": "mapping_strategy", "operator": "EQ", "value": "strategy"} in filters + assert {"field": "alert_domain", "operator": "EQ", "value": "domain"} in filters diff --git a/Packs/CortexXDR/Integrations/CortexXDRIR/README.md b/Packs/CortexXDR/Integrations/CortexXDRIR/README.md index 90f71626678c..ea27ec036a88 100644 --- a/Packs/CortexXDR/Integrations/CortexXDRIR/README.md +++ b/Packs/CortexXDR/Integrations/CortexXDRIR/README.md @@ -4122,3 +4122,285 @@ Gets a list of existing API keys. | PaloAltoNetworksXDR.APIKeyData.id | String | The API key ID. | | PaloAltoNetworksXDR.APIKeyData.roles | String | The roles associated with the API key. | | PaloAltoNetworksXDR.APIKeyData.expiration | Date | The expiration date of the API key. | + +### xdr-bioc-list + +*** +Returns a list of BIOCs. + +#### Base Command + +`xdr-bioc-list` + +#### Input + +| **Argument Name** | **Description** | **Required** | +| --- | --- | --- | +| name | The BIOC name to filter by. Can filter only by one name. | Optional | +| severity | The BIOC severity to filter by. Possible values are: info, low, medium, high. | Optional | +| type | The BIOC type to filter by. Possible values are: other, persistence, evasion, tampering, file_type_obfuscation, privilege_escalation, credential_access, lateral_movement, execution, collection, exfiltration, infiltration, dropper, file_privilege_manipulation, reconnaissance, discovery. | Optional | +| is_xql | Whether the BIOC is XQL. Possible values are: true, false. | Optional | +| comment | The BIOC comment to filter by. | Optional | +| status | The BIOC status to filter by. Can be one of the following: enabled, disabled. Possible values are: enabled, disabled. | Optional | +| indicator | The BIOC indicator to filter by. | Optional | +| mitre_technique_id_and_name | The MITRE technique ID and name. Must be in format 'ID - Name', for example: ['T1566 - Phishing']. | Optional | +| mitre_tactic_id_and_name | The MITRE technique ID and name. Must be in format 'ID - Name', for example: ['T1566 - Phishing']. | Optional | +| extra_data | Whether to return extended data. Possible values are: true, false. | Optional | +| limit | Maximum number of results to return. | Optional | +| page_size | Page size. | Optional | +| page | Page number. | Optional | + +#### Context Output + +| **Path** | **Type** | **Description** | +| --- | --- | --- | +| PaloAltoNetworksXDR.BIOC.rule_id | String | BIOC rule id. | +| PaloAltoNetworksXDR.BIOC.name | String | BIOC name. | +| PaloAltoNetworksXDR.BIOC.type | String | BIOC type. | +| PaloAltoNetworksXDR.BIOC.severity | String | BIOC severity. | +| PaloAltoNetworksXDR.BIOC.status | String | BIOC status. | +| PaloAltoNetworksXDR.BIOC.is_xql | Bool | Whether the BIOC is XQL. | +| PaloAltoNetworksXDR.BIOC.comment | String | The BIOC comment. | +| PaloAltoNetworksXDR.BIOC.indicator | Unknown | The BIOC indicator. | + +### xdr-bioc-create + +*** +Creates a new BIOC. + +#### Base Command + +`xdr-bioc-create` + +#### Input + +| **Argument Name** | **Description** | **Required** | +| --- | --- | --- | +| name | The BIOC name. | Required | +| severity | The BIOC severity. Possible values are: info, low, medium, high. | Required | +| type | The BIOC type. Possible values are: other, persistence, evasion, tampering, file_type_obfuscation, privilege_escalation, credential_access, lateral_movement, execution, collection, exfiltration, infiltration, dropper, file_privilege_manipulation, reconnaissance, discovery. | Required | +| is_xql | Whether the new BIOC is XQL. Possible values are: true, false. | Optional | +| comment | The BIOC comment. | Required | +| status | The BIOC status. Possible values are: enabled, disabled. | Required | +| indicator | The BIOC indicator,
for example: '{\"runOnCGO\":true,\"investigationType\":\"FILE_EVENT\",\"investigation\":{\"FILE_EVENT\":{\"filter\":{\"AND\":[{\"SEARCH_FIELD\":\"action_file_name\",\"SEARCH_TYPE\":\"EQ\",\"SEARCH_VALUE\":\"testfile.exe\"}]}}}}'.
For more information please refer to the documentation https://docs-cortex.paloaltonetworks.com/r/Cortex-XDR-Platform-APIs/Insert-or-update-BIOCs. | Required | +| mitre_technique_id_and_name | The MITRE technique ID and name. Must be in format 'ID - Name', for example: ['T1566 - Phishing']. | Optional | +| mitre_tactic_id_and_name | The MITRE tactic ID and name. Must be in format 'ID - Name', for example: ['TA0001 - Initial Access']. | Optional | + +#### Context Output + +| **Path** | **Type** | **Description** | +| --- | --- | --- | +| PaloAltoNetworksXDR.BIOC.name | String | BIOC name. | +| PaloAltoNetworksXDR.BIOC.type | String | BIOC type. | +| PaloAltoNetworksXDR.BIOC.severity | String | BIOC severity. | +| PaloAltoNetworksXDR.BIOC.status | String | BIOC status. | + +### xdr-bioc-update + +*** +Updates an existing BIOC. + +#### Base Command + +`xdr-bioc-update` + +#### Input + +| **Argument Name** | **Description** | **Required** | +| --- | --- | --- | +| rule_id | BIOC rule ID. | Required | +| name | BIOC name. | Required | +| severity | BIOC severity. Possible values are: info, low, medium, high. | Required | +| type | BIOC type. Possible values are: other, persistence, evasion, tampering, file_type_obfuscation, privilege_escalation, credential_access, lateral_movement, execution, collection, exfiltration, infiltration, dropper, file_privilege_manipulation, reconnaissance, discovery. | Required | +| is_xql | Whether the BIOC is XQL. Possible values are: true, false. | Optional | +| comment | BIOC comment. | Required | +| status | BIOC status. Possible values are: enabled, disabled. | Required | +| indicator | The BIOC indicator,
for example: '{\"runOnCGO\":true,\"investigationType\":\"FILE_EVENT\",\"investigation\":{\"FILE_EVENT\":{\"filter\":{\"AND\":[{\"SEARCH_FIELD\":\"action_file_name\",\"SEARCH_TYPE\":\"EQ\",\"SEARCH_VALUE\":\"testfile.exe\"}]}}}}'.
For more information please refer to the documentation https://docs-cortex.paloaltonetworks.com/r/Cortex-XDR-Platform-APIs/Insert-or-update-BIOCs. | Required | +| mitre_technique_id_and_name | The MITRE technique ID and name. Must be in format 'ID - Name', for example: ['T1566 - Phishing']. | Optional | +| mitre_tactic_id_and_name | The MITRE tactic ID and name. Must be in format 'ID - Name', for example: ['TA0001 - Initial Access']. | Optional | + +#### Context Output + +| **Path** | **Type** | **Description** | +| --- | --- | --- | +| PaloAltoNetworksXDR.BIOC.name | String | BIOC name. | +| PaloAltoNetworksXDR.BIOC.type | String | BIOC type. | +| PaloAltoNetworksXDR.BIOC.severity | String | BIOC severity. | +| PaloAltoNetworksXDR.BIOC.status | String | BIOC status. | + +### xdr-bioc-delete + +*** +Deletes a BIOC. + +#### Base Command + +`xdr-bioc-delete` + +#### Input + +| **Argument Name** | **Description** | **Required** | +| --- | --- | --- | +| name | BIOC name. | Optional | +| severity | BIOC severity. Possible values are: info, low, medium, high. | Optional | +| type | BIOC type. Possible values are: other, persistence, evasion, tampering, file_type_obfuscation, privilege_escalation, credential_access, lateral_movement, execution, collection, exfiltration, infiltration, dropper, file_privilege_manipulation, reconnaissance, discovery. | Optional | +| is_xql | Whether the BIOC is XQL. Possible values are: true, false. | Optional | +| comment | BIOC comment. | Optional | +| indicator | BIOC indicator. | Optional | +| mitre_technique_id_and_name | The MITRE technique ID and name. Must be in format 'ID - Name', for example: ['T1566 - Phishing']. | Optional | +| mitre_tactic_id_and_name | The MITRE tactic ID and name. Must be in format 'ID - Name', for example: ['TA0001 - Initial Access']. | Optional | + +#### Context Output + +There is no context output for this command. + +### xdr-correlation-rule-list + +*** +Returns a list of correlation rules. + +#### Base Command + +`xdr-correlation-rule-list` + +#### Input + +| **Argument Name** | **Description** | **Required** | +| --- | --- | --- | +| name | Correlation rule name. | Optional | +| severity | Correlation rule severity. Possible values are: info, low, medium, high. | Optional | +| xql_query | Correlation rule XQL query. | Optional | +| is_xql | Whether the correlation rule is XQL. Possible values are: true, false. | Optional | +| dataset | Correlation rule dataset. | Optional | +| alert_name | Alert name. | Optional | +| alert_category | Alert category. | Optional | +| alert_fields | Alert fields. Can be a string or a dictionary. | Optional | +| alert_domain | Alert domain. | Optional | +| filter_json | Filter JSON. | Optional | +| extra_data | Whether to return extended view. Possible values are: true, false. | Optional | +| limit | Maximum number of results to return. | Optional | +| page_size | Page size. | Optional | +| page | Page number. | Optional | + +#### Context Output + +| **Path** | **Type** | **Description** | +| --- | --- | --- | +| PaloAltoNetworksXDR.CorrelationRule.id | String | Correlation rule ID. | +| PaloAltoNetworksXDR.CorrelationRule.name | String | Correlation rule name. | +| PaloAltoNetworksXDR.CorrelationRule.description | String | Correlation rule description. | +| PaloAltoNetworksXDR.CorrelationRule.is_enabled | Boolean | Whether the correlation rule is enabled. | + +### xdr-correlation-rule-create + +*** +Creates a new correlation rule. + +#### Base Command + +`xdr-correlation-rule-create` + +#### Input + +| **Argument Name** | **Description** | **Required** | +| --- | --- | --- | +| name | The correlation rule name. Example: name="This is a CR test 6". | Required | +| severity | The correlation rule severity. Example: severity=low. Possible values are: info, low, medium, high. | Required | +| xql_query | The correlation rule XQL query. Example: xql_query="dataset = xdr_data \| limit 1". | Required | +| is_enabled | Whether the rule is enabled. Example: is_enabled=true. Possible values are: true, false. | Required | +| alert_category | The alert category. Example: alert_category=dropper. Possible values are: other, persistence, evasion, tampering, file_type_obfuscation, privilege_escalation, credential_access, lateral_movement, execution, collection, exfiltration, infiltration, dropper, file_privilege_manipulation, reconnaissance, discovery. | Required | +| execution_mode | The rule execution mode. Example: execution_mode=scheduled. Possible values are: scheduled, real_time. | Required | +| timezone | The correlation rule timezone. Example: timezone="Asia/Jerusalem". | Required | +| mapping_strategy | The rule mapping strategy. Example: mapping_strategy=auto. Possible values are: auto, custom. | Required | +| description | The correlation rule description. | Optional | +| alert_name | The alert name. | Optional | +| alert_description | The alert description. | Optional | +| alert_fields | Alert fields (string or dictionary). | Optional | +| search_window | The search window timeframe. Example: search_window="1 hours". | Optional | +| schedule | The correlation rule schedule. Example: schedule="10 minutes". | Optional | +| schedule_linux | Linux scheduling for the rule. Example: schedule_linux="*/10* ** *". | Optional | +| suppression_enabled | Whether suppression is enabled. Possible values are: true, false. | Optional | +| suppression_duration | Duration of correlation rule suppression. | Optional | +| suppression_fields | Suppression fields. | Optional | +| dataset | The correlation rule dataset. Example: dataset=alerts. | Optional | +| user_defined_severity | User-defined severity. | Optional | +| user_defined_category | User-defined category. | Optional | +| mitre_defs_json | MITRE definitions. | Optional | +| investigation_query_link | Investigation query link. | Optional | +| drilldown_query_timeframe | The drilldown query timeframe. Example: drilldown_query_timeframe="ALERT". | Optional | + +#### Context Output + +| **Path** | **Type** | **Description** | +| --- | --- | --- | +| PaloAltoNetworksXDR.CorrelationRule.id | String | Correlation rule ID. | +| PaloAltoNetworksXDR.CorrelationRule.name | String | Correlation rule name. | +| PaloAltoNetworksXDR.CorrelationRule.description | String | Correlation rule description. | +| PaloAltoNetworksXDR.CorrelationRule.is_enabled | Boolean | Whether the correlation rule is enabled. | + +### xdr-correlation-rule-update + +*** +Updates an existing correlation rule. + +#### Base Command + +`xdr-correlation-rule-update` + +#### Input + +| **Argument Name** | **Description** | **Required** | +| --- | --- | --- | +| rule_id | Correlation rule ID. | Required | +| name | The correlation rule name. Example: name="This is a CR test 6". | Required | +| severity | The correlation rule severity. Example: severity=low. Possible values are: info, low, medium, high. | Required | +| xql_query | The correlation rule XQL query. Example: xql_query="dataset = xdr_data \| limit 1". | Required | +| is_enabled | Whether the rule is enabled. Example: is_enabled=true. Possible values are: true, false. | Required | +| alert_category | The alert category. Example: alert_category=dropper. Possible values are: other, persistence, evasion, tampering, file_type_obfuscation, privilege_escalation, credential_access, lateral_movement, execution, collection, exfiltration, infiltration, dropper, file_privilege_manipulation, reconnaissance, discovery. | Required | +| execution_mode | The rule execution mode. Example: execution_mode=scheduled. Possible values are: scheduled, real_time. | Required | +| timezone | The correlation rule timezone. Example: timezone="Asia/Jerusalem". | Required | +| mapping_strategy | The rule mapping strategy. Example: mapping_strategy=auto. Possible values are: auto, custom. | Required | +| description | The correlation rule description. | Optional | +| alert_name | The alert name. | Optional | +| alert_description | The alert description. | Optional | +| alert_fields | Alert fields (string or dictionary). | Optional | +| search_window | The search window timeframe. Example: search_window="1 hours". | Optional | +| schedule | The correlation rule schedule. Example: schedule="10 minutes". | Optional | +| schedule_linux | Linux scheduling for the rule. Example: schedule_linux="*/10* ** *". | Optional | +| suppression_enabled | Whether suppression is enabled. Possible values are: true, false. | Optional | +| suppression_duration | Duration of correlation rule suppression. | Optional | +| suppression_fields | Suppression fields. | Optional | +| dataset | The correlation rule dataset. Example: dataset=alerts. | Required | +| user_defined_severity | User-defined severity. | Optional | +| user_defined_category | User-defined category. | Optional | +| mitre_defs_json | MITRE definitions. | Optional | +| investigation_query_link | Investigation query link. | Optional | +| drilldown_query_timeframe | The drilldown query timeframe. Example: drilldown_query_timeframe="ALERT". | Optional | + +#### Context Output + +| **Path** | **Type** | **Description** | +| --- | --- | --- | +| PaloAltoNetworksXDR.CorrelationRule.id | String | Correlation rule ID. | +| PaloAltoNetworksXDR.CorrelationRule.name | String | Correlation rule name. | +| PaloAltoNetworksXDR.CorrelationRule.description | String | Correlation rule description. | +| PaloAltoNetworksXDR.CorrelationRule.is_enabled | Boolean | Whether the correlation rule is enabled. | + +### xdr-correlation-rule-delete + +*** +Deletes correlation rules. + +#### Base Command + +`xdr-correlation-rule-delete` + +#### Input + +| **Argument Name** | **Description** | **Required** | +| --- | --- | --- | +| rule_id | Correlation rule ID. | Required | + +#### Context Output + +There is no context output for this command. diff --git a/Packs/CortexXDR/Integrations/CortexXDRIR/command_examples.txt b/Packs/CortexXDR/Integrations/CortexXDRIR/command_examples.txt index 015bfe520045..6266f168b98d 100644 --- a/Packs/CortexXDR/Integrations/CortexXDRIR/command_examples.txt +++ b/Packs/CortexXDR/Integrations/CortexXDRIR/command_examples.txt @@ -61,4 +61,14 @@ !xdr-asset-group-list sort_field="XDM.ASSET_GROUP.LAST_UPDATE_TIME" page=1 page_size=3 !xdr-asset-group-create group_name="assetGroupTest" group_type=Dynamic membership_predicate_json="{\"AND\":[{\"SEARCH_FIELD\":\"xdm.asset.type.class\",\"SEARCH_TYPE\":\"NEQ\",\"SEARCH_VALUE\":\"Other\"}]}" !xdr-asset-group-update group_id=1 group_type=Dynamic group_description=test group_name="Test" membership_predicate_json="{\"AND\":[{\"SEARCH_FIELD\":\"xdm.asset.provider\",\"SEARCH_TYPE\":\"NEQ\",\"SEARCH_VALUE\":\"Other\"}]}" -!xdr-asset-group-delete group_id=1 \ No newline at end of file +!xdr-asset-group-delete group_id=1 +!xdr-bioc-list severity=high type=collection +!xdr-bioc-create name="TestBIOC" severity=high mitre_tactic_id_and_name="['TA0002 - Execution']" mitre_technique_id_and_name="['T1059 - Command and Scripting Interpreter']" indicator="{\"runOnCGO\":true,\"investigationType\":\"FILE_EVENT\",\"investigation\":{\"FILE_EVENT\":{\"filter\":{\"AND\":[{\"SEARCH_FIELD\":\"action_file_name\",\"SEARCH_TYPE\":\"EQ\",\"SEARCH_VALUE\":\"testfile.exe\"}]}}}}" type=collection status=enabled comment="Test" +!xdr-bioc-update name="TestBIOC" severity=high mitre_tactic_id_and_name="['TA0002 - Execution']" mitre_technique_id_and_name="['T1059 - Command and Scripting Interpreter']" indicator="{\"runOnCGO\":true,\"investigationType\":\"FILE_EVENT\",\"investigation\":{\"FILE_EVENT\":{\"filter\":{\"AND\":[{\"SEARCH_FIELD\":\"action_file_name\",\"SEARCH_TYPE\":\"EQ\",\"SEARCH_VALUE\":\"testfile.exe\"}]}}}}" type=collection status=enabled comment="Test" +!xdr-bioc-delete type=collection name="TestBIOC" +!xdr-correlation-rule-list extra_data=true +!xdr-correlation-rule-create name="CR test" severity=low alert_category=dropper execution_mode=scheduled mapping_strategy=auto xql_query="dataset = xdr_data | limit 1" dataset=alerts alert_name="Test" is_enabled=true drilldown_query_timeframe="ALERT" investigation_query_link="dataset = xdr_data | limit 1" schedule="10 minutes" schedule_linux="*/10 * * * *" search_window="1 hours" suppression_duration="1 hours" suppression_enabled=true suppression_fields="event_type" timezone="Asia/Jerusalem" +!xdr-correlation-rule-update name="CR test" severity=low alert_category=dropper execution_mode=scheduled mapping_strategy=auto xql_query="dataset = xdr_data | limit 1" dataset=alerts alert_name="Test" is_enabled=true drilldown_query_timeframe="ALERT" investigation_query_link="dataset = xdr_data | limit 1" schedule="10 minutes" schedule_linux="*/10 * * * *" search_window="1 hours" suppression_duration="1 hours" suppression_enabled=true suppression_fields="event_type" timezone="Asia/Jerusalem" +!xdr-correlation-rule-delete rule_id=14,15 + + diff --git a/Packs/CortexXDR/ReleaseNotes/6_3_5.md b/Packs/CortexXDR/ReleaseNotes/6_3_5.md new file mode 100644 index 000000000000..9b18ea6cd9cf --- /dev/null +++ b/Packs/CortexXDR/ReleaseNotes/6_3_5.md @@ -0,0 +1,13 @@ + +#### Integrations + +##### Palo Alto Networks Cortex XDR - Investigation and Response + +- Added support for the **xdr-bioc-create** command which creates a new BIOC. +- Added support for the **xdr-bioc-update** command which updates an existing BIOC. +- Added support for the **xdr-bioc-delete** command which deletes a BIOC. +- Added support for the **xdr-bioc-list** command which returns a list of BIOCs. +- Added support for the **xdr-correlation-rule-list** command which returns a list of correlation rules. +- Added support for the **xdr-correlation-rule-update** command which updates an existing correlation rule. +- Added support for the **xdr-correlation-rule-delete** command which deletes correlation rules. +- Added support for the **xdr-correlation-rule-create** command which creates a new correlation rule. diff --git a/Packs/CortexXDR/pack_metadata.json b/Packs/CortexXDR/pack_metadata.json index 8c79f76bf836..bc4010a16d6b 100644 --- a/Packs/CortexXDR/pack_metadata.json +++ b/Packs/CortexXDR/pack_metadata.json @@ -2,7 +2,7 @@ "name": "Cortex XDR by Palo Alto Networks", "description": "Automates Cortex XDR incident response, and includes custom Cortex XDR incident views and layouts to aid analyst investigations.", "support": "xsoar", - "currentVersion": "6.3.4", + "currentVersion": "6.3.5", "author": "Cortex XSOAR", "url": "https://www.paloaltonetworks.com/cortex", "email": "",