diff --git a/playbooks/Cisco_Umbrella_DNS_Denylisting.json b/playbooks/Cisco_Umbrella_DNS_Denylisting.json new file mode 100644 index 0000000000..622115d8a9 --- /dev/null +++ b/playbooks/Cisco_Umbrella_DNS_Denylisting.json @@ -0,0 +1,268 @@ +{ + "blockly": false, + "blockly_xml": "", + "category": "DNS Denylisting", + "coa": { + "data": { + "description": "Accepts a domain or list of domains as input. Blocks the given domains in Cisco Umbrella.\n\nhttps://d3fend.mitre.org/technique/d3f:DNSDenylisting/", + "edges": [ + { + "id": "port_0_to_port_2", + "sourceNode": "0", + "sourcePort": "0_out", + "targetNode": "2", + "targetPort": "2_in" + }, + { + "conditions": [ + { + "index": 0 + } + ], + "id": "port_2_to_port_3", + "sourceNode": "2", + "sourcePort": "2_out", + "targetNode": "3", + "targetPort": "3_in" + }, + { + "id": "port_5_to_port_1", + "sourceNode": "5", + "sourcePort": "5_out", + "targetNode": "1", + "targetPort": "1_in" + }, + { + "id": "port_3_to_port_6", + "sourceNode": "3", + "sourcePort": "3_out", + "targetNode": "6", + "targetPort": "6_in" + }, + { + "conditions": [ + { + "index": 0 + } + ], + "id": "port_6_to_port_5", + "sourceNode": "6", + "sourcePort": "6_out", + "targetNode": "5", + "targetPort": "5_in" + } + ], + "hash": "3ef6e0e09a728ad1f2aea6b5c89f41b9f5671f50", + "nodes": { + "0": { + "data": { + "advanced": { + "join": [] + }, + "functionName": "on_start", + "id": "0", + "type": "start" + }, + "errors": {}, + "id": "0", + "type": "start", + "warnings": {}, + "x": 1000, + "y": 419.99999999999955 + }, + "1": { + "data": { + "advanced": { + "join": [] + }, + "functionName": "on_finish", + "id": "1", + "type": "end" + }, + "errors": {}, + "id": "1", + "type": "end", + "warnings": {}, + "x": 1000, + "y": 1200 + }, + "2": { + "data": { + "advanced": { + "customName": "domain input filter", + "customNameId": 0, + "description": "Determine branches based on provided inputs.", + "join": [], + "note": "Determine branches based on provided inputs." + }, + "conditions": [ + { + "comparisons": [ + { + "conditionIndex": 0, + "op": "!=", + "param": "playbook_input:domain", + "value": "None" + } + ], + "conditionIndex": 0, + "customName": "input_url", + "logic": "and" + } + ], + "functionId": 1, + "functionName": "domain_input_filter", + "id": "2", + "type": "filter" + }, + "errors": {}, + "id": "2", + "type": "filter", + "warnings": {}, + "x": 1040, + "y": 540 + }, + "3": { + "data": { + "action": "block domain", + "actionType": "contain", + "advanced": { + "customName": "block domain", + "customNameId": 0, + "description": "Block domains in Cisco Umbrella based on given domains. ", + "join": [], + "note": "Block domains in Cisco Umbrella based on given domains. " + }, + "connector": "Cisco Umbrella", + "connectorConfigs": [ + "cisco_umbrella" + ], + "connectorId": "96f3e021-5396-42d0-97f4-4fab683e9adb", + "connectorVersion": "v1", + "functionId": 1, + "functionName": "block_domain", + "id": "3", + "parameters": { + "domain": "playbook_input:domain" + }, + "requiredParameters": [ + { + "data_type": "string", + "field": "domain" + } + ], + "type": "action" + }, + "errors": {}, + "id": "3", + "type": "action", + "warnings": {}, + "x": 980, + "y": 740 + }, + "5": { + "data": { + "advanced": { + "customName": "build observable", + "customNameId": 0, + "description": "This block uses custom code to generate an observable dictionary to output into the observables data path.", + "join": [], + "note": "This block uses custom code to generate an observable dictionary to output into the observables data path." + }, + "functionId": 1, + "functionName": "build_observable", + "id": "5", + "inputParameters": [ + "filtered-data:success_filter:condition_1:block_domain:action_result.parameter.domain", + "filtered-data:success_filter:condition_1:block_domain:action_result.status" + ], + "outputVariables": [ + "observable_array" + ], + "type": "code" + }, + "errors": {}, + "id": "5", + "type": "code", + "userCode": "\n build_observable__observable_array = list()\n for status, domain in zip(filtered_result_0_status, filtered_result_0_parameter_domain):\n if status == \"success\":\n observable = {\n \"type\": \"domain\",\n \"value\": domain,\n \"source\": \"Cisco Umbrella\",\n \"status\": \"blocked\"\n }\n \n build_observable__observable_array.append(observable)\n\n", + "warnings": {}, + "x": 980, + "y": 1060 + }, + "6": { + "data": { + "advanced": { + "customName": "success filter", + "customNameId": 0, + "description": "Determine if the block domain was successful.", + "join": [], + "note": "Determine if the block domain was successful." + }, + "conditions": [ + { + "comparisons": [ + { + "conditionIndex": 0, + "op": "==", + "param": "block_domain:action_result.status", + "value": "success" + } + ], + "conditionIndex": 0, + "customName": "success", + "logic": "and" + } + ], + "functionId": 2, + "functionName": "success_filter", + "id": "6", + "type": "filter" + }, + "errors": {}, + "id": "6", + "type": "filter", + "warnings": {}, + "x": 1040, + "y": 880 + } + }, + "notes": "Inputs: domain\nInteractions: Cisco Umbrella\nActions: block domain\nOutputs: observables" + }, + "input_spec": [ + { + "contains": [ + "domain" + ], + "description": "Accepts domain and block them", + "name": "domain" + } + ], + "output_spec": [ + { + "contains": [], + "datapaths": [ + "build_observable:custom_function:observable_array" + ], + "deduplicate": false, + "description": "An array of observable dictionaries with value, type and information about the blocked domains.", + "metadata": {}, + "name": "observable" + } + ], + "playbook_type": "data", + "python_version": "3", + "schema": "5.0.10", + "version": "6.1.0.131" + }, + "create_time": "2023-08-02T11:29:36.835550+00:00", + "draft_mode": false, + "labels": [ + "*" + ], + "tags": [ + "Cisco Umbrella", + "denylist", + "D3-DNSDL", + "domain" + ] +} \ No newline at end of file diff --git a/playbooks/Cisco_Umbrella_DNS_Denylisting.png b/playbooks/Cisco_Umbrella_DNS_Denylisting.png new file mode 100644 index 0000000000..5c9865d5af Binary files /dev/null and b/playbooks/Cisco_Umbrella_DNS_Denylisting.png differ diff --git a/playbooks/Cisco_Umbrella_DNS_Denylisting.py b/playbooks/Cisco_Umbrella_DNS_Denylisting.py new file mode 100644 index 0000000000..e989b47e99 --- /dev/null +++ b/playbooks/Cisco_Umbrella_DNS_Denylisting.py @@ -0,0 +1,167 @@ +""" +Accepts a domain or list of domains as input. Blocks the given domains in Cisco Umbrella.\n\nhttps://d3fend.mitre.org/technique/d3f:DNSDenylisting/ +""" + + +import phantom.rules as phantom +import json +from datetime import datetime, timedelta + + +@phantom.playbook_block() +def on_start(container): + phantom.debug('on_start() called') + + # call 'domain_input_filter' block + domain_input_filter(container=container) + + return + +@phantom.playbook_block() +def domain_input_filter(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs): + phantom.debug("domain_input_filter() called") + + ################################################################################ + # Determine branches based on provided inputs. + ################################################################################ + + # collect filtered artifact ids and results for 'if' condition 1 + matched_artifacts_1, matched_results_1 = phantom.condition( + container=container, + conditions=[ + ["playbook_input:domain", "!=", None] + ], + name="domain_input_filter:condition_1", + delimiter=None) + + # call connected blocks if filtered artifacts or results + if matched_artifacts_1 or matched_results_1: + block_domain(action=action, success=success, container=container, results=results, handle=handle, filtered_artifacts=matched_artifacts_1, filtered_results=matched_results_1) + + return + + +@phantom.playbook_block() +def block_domain(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs): + phantom.debug("block_domain() called") + + # phantom.debug('Action: {0} {1}'.format(action['name'], ('SUCCEEDED' if success else 'FAILED'))) + + ################################################################################ + # Block domains in Cisco Umbrella based on given domains. + ################################################################################ + + playbook_input_domain = phantom.collect2(container=container, datapath=["playbook_input:domain"]) + + parameters = [] + + # build parameters list for 'block_domain' call + for playbook_input_domain_item in playbook_input_domain: + if playbook_input_domain_item[0] is not None: + parameters.append({ + "domain": playbook_input_domain_item[0], + }) + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.act("block domain", parameters=parameters, name="block_domain", assets=["cisco_umbrella"], callback=success_filter) + + return + + +@phantom.playbook_block() +def build_observable(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs): + phantom.debug("build_observable() called") + + ################################################################################ + # This block uses custom code to generate an observable dictionary to output into + # the observables data path. + ################################################################################ + + filtered_result_0_data_success_filter = phantom.collect2(container=container, datapath=["filtered-data:success_filter:condition_1:block_domain:action_result.parameter.domain","filtered-data:success_filter:condition_1:block_domain:action_result.status"]) + + filtered_result_0_parameter_domain = [item[0] for item in filtered_result_0_data_success_filter] + filtered_result_0_status = [item[1] for item in filtered_result_0_data_success_filter] + + build_observable__observable_array = None + + ################################################################################ + ## Custom Code Start + ################################################################################ + + build_observable__observable_array = list() + for status, domain in zip(filtered_result_0_status, filtered_result_0_parameter_domain): + if status == "success": + observable = { + "type": "domain", + "value": domain, + "source": "Cisco Umbrella", + "status": "blocked" + } + + build_observable__observable_array.append(observable) + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.save_run_data(key="build_observable:observable_array", value=json.dumps(build_observable__observable_array)) + + return + + +@phantom.playbook_block() +def success_filter(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs): + phantom.debug("success_filter() called") + + ################################################################################ + # Determine if the block domain was successful. + ################################################################################ + + # collect filtered artifact ids and results for 'if' condition 1 + matched_artifacts_1, matched_results_1 = phantom.condition( + container=container, + conditions=[ + ["block_domain:action_result.status", "==", "success"] + ], + name="success_filter:condition_1", + delimiter=None) + + # call connected blocks if filtered artifacts or results + if matched_artifacts_1 or matched_results_1: + build_observable(action=action, success=success, container=container, results=results, handle=handle, filtered_artifacts=matched_artifacts_1, filtered_results=matched_results_1) + + return + + +@phantom.playbook_block() +def on_finish(container, summary): + phantom.debug("on_finish() called") + + build_observable__observable_array = json.loads(_ if (_ := phantom.get_run_data(key="build_observable:observable_array")) != "" else "null") # pylint: disable=used-before-assignment + + output = { + "observable": build_observable__observable_array, + } + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.save_playbook_output_data(output=output) + + return \ No newline at end of file diff --git a/playbooks/Cisco_Umbrella_DNS_Denylisting.yml b/playbooks/Cisco_Umbrella_DNS_Denylisting.yml new file mode 100644 index 0000000000..d60ea29383 --- /dev/null +++ b/playbooks/Cisco_Umbrella_DNS_Denylisting.yml @@ -0,0 +1,24 @@ +name: Cisco Umbrella DNS Denylisting +id: 3705f371-f355-46d7-979a-3bc4c26e2208 +version: 1 +date: '2023-07-14' +author: Patrick Bareiss, Splunk +type: Response +description: Accepts a domain or list of domains and block them in Cisco Umbrella. Generates a list of observables with the blocked domains. +playbook: Cisco_Umbrella_DNS_Denylisting +how_to_implement: This input playbook requires the Cisco Umbrella connector to be configured. + It is designed to work in conjunction with the DNS Denylisting Dispatch playbook or other playbooks in the same style. +references: [] +app_list: + - Cisco Umbrella +tags: + platform_tags: + - url + - D3-DNSDL + - Cisco Umbrella + - denylist + playbook_type: Input + vpe_type: Modern + playbook_fields: [] + product: + - Splunk SOAR diff --git a/playbooks/DNS_Denylisting_Dispatch.json b/playbooks/DNS_Denylisting_Dispatch.json new file mode 100644 index 0000000000..32331bdb86 --- /dev/null +++ b/playbooks/DNS_Denylisting_Dispatch.json @@ -0,0 +1,750 @@ +{ + "blockly": false, + "blockly_xml": "", + "category": "DNS Denylisting", + "coa": { + "data": { + "description": "Detects available indicators and routes them to dispatch DNS denylisting playbooks. These playbooks will block the given domains. The output of the analysis will update any artifacts, tasks, and indicator tags.\n\nhttps://d3fend.mitre.org/technique/d3f:DNSDenylisting/", + "edges": [ + { + "id": "port_0_to_port_2", + "sourceNode": "0", + "sourcePort": "0_out", + "targetNode": "2", + "targetPort": "2_in" + }, + { + "conditions": [ + { + "index": 0 + } + ], + "id": "port_2_to_port_3", + "sourceNode": "2", + "sourcePort": "2_out", + "targetNode": "3", + "targetPort": "3_in" + }, + { + "conditions": [ + { + "index": 1 + } + ], + "id": "port_2_to_port_5", + "sourceNode": "2", + "sourcePort": "2_out", + "targetNode": "5", + "targetPort": "5_in" + }, + { + "conditions": [ + { + "index": 0 + } + ], + "id": "port_3_to_port_6", + "sourceNode": "3", + "sourcePort": "3_out", + "targetNode": "6", + "targetPort": "6_in" + }, + { + "id": "port_6_to_port_7", + "sourceNode": "6", + "sourcePort": "6_out", + "targetNode": "7", + "targetPort": "7_in" + }, + { + "id": "port_8_to_port_9", + "sourceNode": "8", + "sourcePort": "8_out", + "targetNode": "9", + "targetPort": "9_in" + }, + { + "id": "port_9_to_port_10", + "sourceNode": "9", + "sourcePort": "9_out", + "targetNode": "10", + "targetPort": "10_in" + }, + { + "id": "port_10_to_port_1", + "sourceNode": "10", + "sourcePort": "10_out", + "targetNode": "1", + "targetPort": "1_in" + }, + { + "conditions": [ + { + "index": 0 + } + ], + "id": "port_7_to_port_12", + "sourceNode": "7", + "sourcePort": "7_out", + "targetNode": "12", + "targetPort": "12_in" + }, + { + "conditions": [ + { + "index": 1 + } + ], + "id": "port_7_to_port_11", + "sourceNode": "7", + "sourcePort": "7_out", + "targetNode": "11", + "targetPort": "11_in" + }, + { + "conditions": [ + { + "index": 0 + } + ], + "id": "port_12_to_port_8", + "sourceNode": "12", + "sourcePort": "12_out", + "targetNode": "8", + "targetPort": "8_in" + } + ], + "hash": "0c7323b8609eddecd1ded1e9217bafb54dfe2d84", + "nodes": { + "0": { + "data": { + "advanced": { + "join": [] + }, + "functionName": "on_start", + "id": "0", + "type": "start" + }, + "errors": {}, + "id": "0", + "type": "start", + "warnings": {}, + "x": 1000, + "y": 279.9999999999998 + }, + "1": { + "data": { + "advanced": { + "join": [] + }, + "functionName": "on_finish", + "id": "1", + "type": "end" + }, + "errors": {}, + "id": "1", + "type": "end", + "warnings": {}, + "x": 1060, + "y": 1440 + }, + "10": { + "data": { + "advanced": { + "customName": "update isolation task", + "customNameId": 0, + "join": [] + }, + "customFunction": { + "draftMode": false, + "name": "workbook_task_update", + "repoName": "community" + }, + "functionId": 4, + "functionName": "update_isolation_task", + "id": "10", + "selectMore": false, + "type": "utility", + "utilities": { + "workbook_task_update": { + "description": "Update a workbook task by task name or the task where the currently running playbook appears. Requires a task_name, container_id, and a note_title, note_content, owner, or status.", + "fields": [ + { + "dataTypes": [ + "*" + ], + "description": "Name of a workbook task or keyword 'playbook' to update the task where the currently running playbook appears. (Required)", + "inputType": "item", + "label": "task_name", + "name": "task_name", + "placeholder": "my_task", + "renderType": "datapath", + "required": false + }, + { + "dataTypes": [ + "*" + ], + "description": "Note title. (Optional)", + "inputType": "item", + "label": "note_title", + "name": "note_title", + "placeholder": "My Title", + "renderType": "datapath", + "required": false + }, + { + "dataTypes": [ + "*" + ], + "description": "Note content. (Optional)", + "inputType": "item", + "label": "note_content", + "name": "note_content", + "placeholder": "My notes", + "renderType": "datapath", + "required": false + }, + { + "dataTypes": [ + "*" + ], + "description": "Accepts 'incomplete', 'in_progress, or 'complete'. (Optional)", + "inputType": "item", + "label": "status", + "name": "status", + "placeholder": "in_progress", + "renderType": "datapath", + "required": false + }, + { + "dataTypes": [ + "*" + ], + "description": "A user to assign as the task owner or keyword 'current\" to assign the task to the user that launched the playbook. (Optional)", + "inputType": "item", + "label": "owner", + "name": "owner", + "placeholder": "username", + "renderType": "datapath", + "required": false + }, + { + "dataTypes": [ + "phantom container id" + ], + "description": "The ID of a SOAR Container. (Required)", + "inputType": "item", + "label": "container", + "name": "container", + "placeholder": "container:id", + "renderType": "datapath", + "required": false + } + ], + "label": "workbook_task_update", + "name": "workbook_task_update" + } + }, + "utilityType": "custom_function", + "values": { + "workbook_task_update": { + "container": "container:id", + "note_content": "format_note:formatted_data", + "note_title": "DNS Denylisting Dispatch Report", + "owner": null, + "status": "complete", + "task_name": "playbook" + } + } + }, + "errors": {}, + "id": "10", + "type": "utility", + "warnings": {}, + "x": 1040, + "y": 1340 + }, + "11": { + "data": { + "advanced": { + "customName": "comment no observables", + "customNameId": 0, + "description": "Add comment when no observables were generated.", + "join": [], + "note": "Add comment when no observables were generated." + }, + "functionId": 5, + "functionName": "comment_no_observables", + "id": "11", + "selectMore": false, + "tab": "apis", + "type": "utility", + "utilities": { + "comment": { + "description": "", + "fields": [ + { + "description": "", + "label": "comment", + "name": "comment", + "placeholder": "Enter a comment", + "renderType": "datapath", + "required": true + }, + { + "hidden": true, + "name": "container", + "required": false + }, + { + "hidden": true, + "name": "author", + "required": false + }, + { + "hidden": true, + "name": "trace", + "required": false + } + ], + "label": "add comment", + "name": "comment" + } + }, + "utilityType": "api", + "values": { + "comment": { + "_internal": [ + "container", + "author", + "trace" + ], + "comment": "No observable data found from dispatched playbooks." + } + } + }, + "errors": {}, + "id": "11", + "type": "utility", + "warnings": {}, + "x": 680, + "y": 960 + }, + "12": { + "data": { + "advanced": { + "customName": "output filter", + "customNameId": 0, + "description": "Determine if the observable is not None.", + "join": [], + "note": "Determine if the observable is not None." + }, + "conditions": [ + { + "comparisons": [ + { + "conditionIndex": 0, + "op": "!=", + "param": "dispatch_dns_denylisting_playbooks:playbook_output:observable", + "value": "None" + } + ], + "conditionIndex": 0, + "customName": "output exists", + "logic": "and" + } + ], + "functionId": 2, + "functionName": "output_filter", + "id": "12", + "type": "filter" + }, + "errors": {}, + "id": "12", + "type": "filter", + "warnings": {}, + "x": 1100, + "y": 960 + }, + "2": { + "data": { + "advanced": { + "customName": "artifact_exists", + "customNameId": 0, + "description": "Checks if a artifact exists", + "join": [], + "note": "Checks if a artifact exists" + }, + "conditions": [ + { + "comparisons": [ + { + "conditionIndex": 0, + "op": "!=", + "param": "artifact:*.id", + "value": "None" + } + ], + "conditionIndex": 0, + "customName": "artifact exists", + "display": "If", + "logic": "and", + "type": "if" + }, + { + "comparisons": [ + { + "conditionIndex": 1, + "op": "==", + "param": "", + "value": "" + } + ], + "conditionIndex": 1, + "customName": "artifact does not exist", + "display": "Else", + "logic": "and", + "type": "else" + } + ], + "functionId": 1, + "functionName": "artifact_exists", + "id": "2", + "type": "decision" + }, + "errors": {}, + "id": "2", + "type": "decision", + "warnings": {}, + "x": 1060, + "y": 400 + }, + "3": { + "data": { + "advanced": { + "customName": "filter_new_artifacts", + "customNameId": 0, + "description": "Only dispatch playbooks against new artifacts.", + "join": [], + "note": "Only dispatch playbooks against new artifacts." + }, + "conditions": [ + { + "comparisons": [ + { + "conditionIndex": 0, + "op": "!=", + "param": "artifact:*.id", + "value": "None" + } + ], + "conditionIndex": 0, + "customName": "artifact exists", + "logic": "and" + } + ], + "functionId": 1, + "functionName": "filter_new_artifacts", + "id": "3", + "type": "filter" + }, + "errors": {}, + "id": "3", + "type": "filter", + "warnings": {}, + "x": 900, + "y": 540 + }, + "5": { + "data": { + "advanced": { + "customName": "comment no new artifacts", + "customNameId": 0, + "description": "Add comment when no new artifacts exists.", + "join": [], + "note": "Add comment when no new artifacts exists." + }, + "functionId": 2, + "functionName": "comment_no_new_artifacts", + "id": "5", + "selectMore": false, + "tab": "apis", + "type": "utility", + "utilities": { + "comment": { + "description": "", + "fields": [ + { + "description": "", + "label": "comment", + "name": "comment", + "placeholder": "Enter a comment", + "renderType": "datapath", + "required": true + }, + { + "hidden": true, + "name": "container", + "required": false + }, + { + "hidden": true, + "name": "author", + "required": false + }, + { + "hidden": true, + "name": "trace", + "required": false + } + ], + "label": "add comment", + "name": "comment" + } + }, + "utilityType": "api", + "values": { + "comment": { + "_internal": [ + "container", + "author", + "trace" + ], + "comment": "No new artifacts found." + } + } + }, + "errors": {}, + "id": "5", + "type": "utility", + "warnings": {}, + "x": 1180, + "y": 540 + }, + "6": { + "data": { + "advanced": { + "customName": "dispatch dns denylisting playbooks", + "customNameId": 0, + "join": [] + }, + "functionId": 1, + "functionName": "dispatch_dns_denylisting_playbooks", + "id": "6", + "inputs": { + "artifact_ids_include": { + "datapaths": [ + "artifact:*.id" + ], + "deduplicate": false + }, + "indicator_tags_exclude": { + "datapaths": [], + "deduplicate": false + }, + "indicator_tags_include": { + "datapaths": [], + "deduplicate": false + }, + "playbook_repo": { + "datapaths": [], + "deduplicate": false + }, + "playbook_tags": { + "datapaths": [ + "denylist" + ], + "deduplicate": false + } + }, + "playbookName": "dispatch_input_playbooks", + "playbookRepo": 1, + "playbookRepoName": "community", + "playbookType": "data", + "synchronous": true, + "type": "playbook" + }, + "errors": {}, + "id": "6", + "type": "playbook", + "warnings": {}, + "x": 840, + "y": 700 + }, + "7": { + "data": { + "advanced": { + "customName": "outputs decision", + "customNameId": 0, + "description": "Determine if outputs exist.", + "join": [], + "note": "Determine if outputs exist." + }, + "conditions": [ + { + "comparisons": [ + { + "conditionIndex": 0, + "op": "!=", + "param": "dispatch_dns_denylisting_playbooks:playbook_output:observable", + "value": "None" + } + ], + "conditionIndex": 0, + "customName": "observable exists", + "display": "If", + "logic": "and", + "type": "if" + }, + { + "comparisons": [ + { + "conditionIndex": 1, + "op": "==", + "param": "", + "value": "" + } + ], + "conditionIndex": 1, + "customName": "observable does not exist", + "display": "Else", + "logic": "and", + "type": "else" + } + ], + "functionId": 2, + "functionName": "outputs_decision", + "id": "7", + "type": "decision" + }, + "errors": {}, + "id": "7", + "type": "decision", + "warnings": {}, + "x": 920, + "y": 820 + }, + "8": { + "data": { + "advanced": { + "customName": "tag indicators", + "customNameId": 0, + "join": [] + }, + "customFunction": { + "draftMode": false, + "name": "indicator_tag", + "repoName": "community" + }, + "functionId": 3, + "functionName": "tag_indicators", + "id": "8", + "selectMore": false, + "tab": "customFunctions", + "type": "utility", + "utilities": { + "indicator_tag": { + "description": "Tag an existing indicator record. Tags can be overwritten or appended.", + "fields": [ + { + "dataTypes": [ + "*" + ], + "description": "Specifies the indicator which the tag will be added to. Supports a string indicator value or an indicator id.", + "inputType": "item", + "label": "indicator", + "name": "indicator", + "placeholder": "my_indicator", + "renderType": "datapath", + "required": false + }, + { + "dataTypes": [ + "*" + ], + "description": "Comma separated list of tags. Tags should only contain characters Aa-Zz, 0-9, '-', and '_'.", + "inputType": "item", + "label": "tags", + "name": "tags", + "placeholder": "tag1,tag2,...,tagk", + "renderType": "datapath", + "required": false + }, + { + "dataTypes": [], + "description": "Either True or False with default as False. If set to True, existing tags on the indicator record will be replaced by the provided input. If set to False, the new tags will be appended to the existing indicator tags.", + "inputType": "item", + "label": "overwrite", + "name": "overwrite", + "placeholder": "False", + "renderType": "datapath", + "required": false + } + ], + "label": "indicator_tag", + "name": "indicator_tag" + } + }, + "utilityType": "custom_function", + "values": { + "indicator_tag": { + "indicator": "filtered-data:output_filter:condition_1:dispatch_dns_denylisting_playbooks:playbook_output:observable.value", + "overwrite": null, + "tags": "blocked" + } + } + }, + "errors": {}, + "id": "8", + "type": "utility", + "warnings": {}, + "x": 1040, + "y": 1120 + }, + "9": { + "data": { + "advanced": { + "customName": "format note", + "customNameId": 0, + "description": "Format a note that merges together normalized data. ", + "join": [], + "note": "Format a note that merges together normalized data. " + }, + "functionId": 1, + "functionName": "format_note", + "id": "9", + "parameters": [ + "filtered-data:output_filter:condition_1:dispatch_dns_denylisting_playbooks:playbook_output:observable.value", + "filtered-data:output_filter:condition_1:dispatch_dns_denylisting_playbooks:playbook_output:observable.status", + "filtered-data:output_filter:condition_1:dispatch_dns_denylisting_playbooks:playbook_output:observable.source" + ], + "template": "Splunk SOAR blocked the following domains:\n\n| domain | status | source |\n| --- | --- | --- |\n%%\n| {0} | {1} | {2} |\n%%\n", + "type": "format" + }, + "errors": {}, + "id": "9", + "type": "format", + "warnings": {}, + "x": 1040, + "y": 1220 + } + }, + "notes": "Outputs: \n- Concludes task\n- tags indicators" + }, + "input_spec": null, + "output_spec": null, + "playbook_type": "automation", + "python_version": "3", + "schema": "5.0.10", + "version": "6.1.0.131" + }, + "create_time": "2023-08-02T12:03:29.640344+00:00", + "draft_mode": false, + "labels": [ + "*" + ], + "tags": [ + "D3-DNSDLL" + ] +} \ No newline at end of file diff --git a/playbooks/DNS_Denylisting_Dispatch.png b/playbooks/DNS_Denylisting_Dispatch.png new file mode 100644 index 0000000000..9b7afd1982 Binary files /dev/null and b/playbooks/DNS_Denylisting_Dispatch.png differ diff --git a/playbooks/DNS_Denylisting_Dispatch.py b/playbooks/DNS_Denylisting_Dispatch.py new file mode 100644 index 0000000000..c805987e61 --- /dev/null +++ b/playbooks/DNS_Denylisting_Dispatch.py @@ -0,0 +1,312 @@ +""" +Detects available indicators and routes them to dispatch DNS denylisting playbooks. These playbooks will block the given domains. The output of the analysis will update any artifacts, tasks, and indicator tags.\n\nhttps://d3fend.mitre.org/technique/d3f:DNSDenylisting/ +""" + + +import phantom.rules as phantom +import json +from datetime import datetime, timedelta + + +@phantom.playbook_block() +def on_start(container): + phantom.debug('on_start() called') + + # call 'artifact_exists' block + artifact_exists(container=container) + + return + +@phantom.playbook_block() +def artifact_exists(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs): + phantom.debug("artifact_exists() called") + + ################################################################################ + # Checks if a artifact exists + ################################################################################ + + # check for 'if' condition 1 + found_match_1 = phantom.decision( + container=container, + conditions=[ + ["artifact:*.id", "!=", None] + ], + delimiter=None) + + # call connected blocks if condition 1 matched + if found_match_1: + filter_new_artifacts(action=action, success=success, container=container, results=results, handle=handle) + return + + # check for 'else' condition 2 + comment_no_new_artifacts(action=action, success=success, container=container, results=results, handle=handle) + + return + + +@phantom.playbook_block() +def filter_new_artifacts(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs): + phantom.debug("filter_new_artifacts() called") + + ################################################################################ + # Only dispatch playbooks against new artifacts. + ################################################################################ + + # collect filtered artifact ids and results for 'if' condition 1 + matched_artifacts_1, matched_results_1 = phantom.condition( + container=container, + conditions=[ + ["artifact:*.id", "!=", None] + ], + name="filter_new_artifacts:condition_1", + delimiter=None) + + # call connected blocks if filtered artifacts or results + if matched_artifacts_1 or matched_results_1: + dispatch_dns_denylisting_playbooks(action=action, success=success, container=container, results=results, handle=handle, filtered_artifacts=matched_artifacts_1, filtered_results=matched_results_1) + + return + + +@phantom.playbook_block() +def comment_no_new_artifacts(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs): + phantom.debug("comment_no_new_artifacts() called") + + ################################################################################ + # Add comment when no new artifacts exists. + ################################################################################ + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.comment(container=container, comment="No new artifacts found.") + + return + + +@phantom.playbook_block() +def dispatch_dns_denylisting_playbooks(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs): + phantom.debug("dispatch_dns_denylisting_playbooks() called") + + container_artifact_data = phantom.collect2(container=container, datapath=["artifact:*.id"]) + + container_artifact_header_item_0 = [item[0] for item in container_artifact_data] + + inputs = { + "playbook_tags": ["denylist"], + "playbook_repo": [], + "indicator_tags_include": [], + "indicator_tags_exclude": [], + "artifact_ids_include": container_artifact_header_item_0, + } + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + # call playbook "community/dispatch_input_playbooks", returns the playbook_run_id + playbook_run_id = phantom.playbook("community/dispatch_input_playbooks", container=container, name="dispatch_dns_denylisting_playbooks", callback=outputs_decision, inputs=inputs) + + return + + +@phantom.playbook_block() +def outputs_decision(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs): + phantom.debug("outputs_decision() called") + + ################################################################################ + # Determine if outputs exist. + ################################################################################ + + # check for 'if' condition 1 + found_match_1 = phantom.decision( + container=container, + conditions=[ + ["dispatch_dns_denylisting_playbooks:playbook_output:observable", "!=", None] + ], + delimiter=None) + + # call connected blocks if condition 1 matched + if found_match_1: + output_filter(action=action, success=success, container=container, results=results, handle=handle) + return + + # check for 'else' condition 2 + comment_no_observables(action=action, success=success, container=container, results=results, handle=handle) + + return + + +@phantom.playbook_block() +def tag_indicators(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs): + phantom.debug("tag_indicators() called") + + filtered_output_0_dispatch_dns_denylisting_playbooks_output_observable = phantom.collect2(container=container, datapath=["filtered-data:output_filter:condition_1:dispatch_dns_denylisting_playbooks:playbook_output:observable.value"]) + + parameters = [] + + # build parameters list for 'tag_indicators' call + for filtered_output_0_dispatch_dns_denylisting_playbooks_output_observable_item in filtered_output_0_dispatch_dns_denylisting_playbooks_output_observable: + parameters.append({ + "tags": "blocked", + "indicator": filtered_output_0_dispatch_dns_denylisting_playbooks_output_observable_item[0], + "overwrite": None, + }) + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.custom_function(custom_function="community/indicator_tag", parameters=parameters, name="tag_indicators", callback=format_note) + + return + + +@phantom.playbook_block() +def format_note(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs): + phantom.debug("format_note() called") + + ################################################################################ + # Format a note that merges together normalized data. + ################################################################################ + + template = """Splunk SOAR blocked the following domains:\n\n| domain | status | source |\n| --- | --- | --- |\n%%\n| {0} | {1} | {2} |\n%%\n""" + + # parameter list for template variable replacement + parameters = [ + "filtered-data:output_filter:condition_1:dispatch_dns_denylisting_playbooks:playbook_output:observable.value", + "filtered-data:output_filter:condition_1:dispatch_dns_denylisting_playbooks:playbook_output:observable.status", + "filtered-data:output_filter:condition_1:dispatch_dns_denylisting_playbooks:playbook_output:observable.source" + ] + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.format(container=container, template=template, parameters=parameters, name="format_note") + + update_isolation_task(container=container) + + return + + +@phantom.playbook_block() +def update_isolation_task(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs): + phantom.debug("update_isolation_task() called") + + id_value = container.get("id", None) + format_note = phantom.get_format_data(name="format_note") + + parameters = [] + + parameters.append({ + "owner": None, + "status": "complete", + "container": id_value, + "task_name": "playbook", + "note_title": "DNS Denylisting Dispatch Report", + "note_content": format_note, + }) + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.custom_function(custom_function="community/workbook_task_update", parameters=parameters, name="update_isolation_task") + + return + + +@phantom.playbook_block() +def comment_no_observables(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs): + phantom.debug("comment_no_observables() called") + + ################################################################################ + # Add comment when no observables were generated. + ################################################################################ + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.comment(container=container, comment="No observable data found from dispatched playbooks.") + + return + + +@phantom.playbook_block() +def output_filter(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs): + phantom.debug("output_filter() called") + + ################################################################################ + # Determine if the observable is not None. + ################################################################################ + + # collect filtered artifact ids and results for 'if' condition 1 + matched_artifacts_1, matched_results_1 = phantom.condition( + container=container, + conditions=[ + ["dispatch_dns_denylisting_playbooks:playbook_output:observable", "!=", None] + ], + name="output_filter:condition_1", + delimiter=None) + + # call connected blocks if filtered artifacts or results + if matched_artifacts_1 or matched_results_1: + tag_indicators(action=action, success=success, container=container, results=results, handle=handle, filtered_artifacts=matched_artifacts_1, filtered_results=matched_results_1) + + return + + +@phantom.playbook_block() +def on_finish(container, summary): + phantom.debug("on_finish() called") + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + return \ No newline at end of file diff --git a/playbooks/DNS_Denylisting_Dispatch.yml b/playbooks/DNS_Denylisting_Dispatch.yml new file mode 100644 index 0000000000..6840f5382f --- /dev/null +++ b/playbooks/DNS_Denylisting_Dispatch.yml @@ -0,0 +1,20 @@ +name: DNS Denylisting Dispatch +id: 7fd9a82f-517a-4d86-bf24-4d4158719dc1 +version: 1 +date: '2023-07-14' +author: Patrick Bareiss, Splunk +type: Response +description: Accepts a list of domains and blocks them. Generates a global report and list of observables. +playbook: DNS_Denylisting_Dispatch +how_to_implement: This playbook looks for artifacts and then dispatches the community denylisting playbooks. This playbook takes the output of those playbooks and nicely formats them into notes and tags indicators with their results. +references: + - https://d3fend.mitre.org/technique/d3f:DNSDenylisting/ +app_list: [] +tags: + platform_tags: + - D3-DNSDL + playbook_type: Automation + vpe_type: Modern + playbook_fields: [] + product: + - Splunk SOAR \ No newline at end of file