From d4fa5f28ca910021162e74956cae8e6e7ccb8583 Mon Sep 17 00:00:00 2001 From: P4T12ICK Date: Fri, 31 Mar 2023 09:05:32 +0200 Subject: [PATCH 1/3] New ZScaler Playbook --- .../Zscaler_Outbound_Traffic_Filtering.json | 280 ++++++++++++++++++ .../Zscaler_Outbound_Traffic_Filtering.png | Bin 0 -> 31415 bytes .../Zscaler_Outbound_Traffic_Filtering.py | 170 +++++++++++ .../Zscaler_Outbound_Traffic_Filtering.yml | 24 ++ 4 files changed, 474 insertions(+) create mode 100755 playbooks/Zscaler_Outbound_Traffic_Filtering.json create mode 100755 playbooks/Zscaler_Outbound_Traffic_Filtering.png create mode 100755 playbooks/Zscaler_Outbound_Traffic_Filtering.py create mode 100644 playbooks/Zscaler_Outbound_Traffic_Filtering.yml diff --git a/playbooks/Zscaler_Outbound_Traffic_Filtering.json b/playbooks/Zscaler_Outbound_Traffic_Filtering.json new file mode 100755 index 0000000000..f77a7624f5 --- /dev/null +++ b/playbooks/Zscaler_Outbound_Traffic_Filtering.json @@ -0,0 +1,280 @@ +{ + "blockly": false, + "blockly_xml": "", + "category": "Isolation", + "coa": { + "data": { + "description": "Accepts a URL or list of URLs as input. Blocks the given URLs in ZScaler.\n\nhttps://d3fend.mitre.org/technique/d3f:OutboundTrafficFiltering/", + "edges": [ + { + "id": "port_0_to_port_4", + "sourceNode": "0", + "sourcePort": "0_out", + "targetNode": "4", + "targetPort": "4_in" + }, + { + "conditions": [ + { + "index": 0 + } + ], + "id": "port_4_to_port_6", + "sourceNode": "4", + "sourcePort": "4_out", + "targetNode": "6", + "targetPort": "6_in" + }, + { + "id": "port_7_to_port_1", + "sourceNode": "7", + "sourcePort": "7_out", + "targetNode": "1", + "targetPort": "1_in" + }, + { + "id": "port_6_to_port_8", + "sourceNode": "6", + "sourcePort": "6_out", + "targetNode": "8", + "targetPort": "8_in" + }, + { + "conditions": [ + { + "index": 0 + } + ], + "id": "port_8_to_port_7", + "sourceNode": "8", + "sourcePort": "8_out", + "targetNode": "7", + "targetPort": "7_in" + } + ], + "hash": "d00eda8b809a9b54fd2a3df75a64dc1538169f7c", + "nodes": { + "0": { + "data": { + "advanced": { + "join": [] + }, + "functionName": "on_start", + "id": "0", + "type": "start" + }, + "errors": {}, + "id": "0", + "type": "start", + "warnings": {}, + "x": 20, + "y": 0 + }, + "1": { + "data": { + "advanced": { + "join": [] + }, + "functionName": "on_finish", + "id": "1", + "type": "end" + }, + "errors": {}, + "id": "1", + "type": "end", + "warnings": {}, + "x": 19.999999999999986, + "y": 864 + }, + "4": { + "data": { + "advanced": { + "customName": "url 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:input_url", + "value": "None" + } + ], + "conditionIndex": 0, + "customName": "input_url", + "logic": "and" + } + ], + "functionId": 1, + "functionName": "url_input_filter", + "id": "4", + "type": "filter" + }, + "errors": {}, + "id": "4", + "type": "filter", + "warnings": { + "config": [ + "Reconfigure invalid datapath." + ] + }, + "x": 60, + "y": 148 + }, + "6": { + "data": { + "action": "block url", + "actionType": "contain", + "advanced": { + "customName": "block url", + "customNameId": 0, + "description": "Block urls in ZScaler based on given urls. ", + "join": [], + "note": "Block urls in ZScaler based on given urls. " + }, + "connector": "Zscaler", + "connectorConfigs": [ + "zscaler" + ], + "connectorId": "ee475421-d2e4-4d8e-addb-a2ce1465d6a3", + "connectorVersion": "v1", + "functionId": 1, + "functionName": "block_url", + "id": "6", + "parameters": { + "url": "playbook_input:input_url" + }, + "requiredParameters": [ + { + "data_type": "string", + "field": "url" + } + ], + "type": "action" + }, + "errors": {}, + "id": "6", + "type": "action", + "warnings": { + "config": [ + "Reconfigure invalid datapath." + ] + }, + "x": 0, + "y": 328 + }, + "7": { + "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": "7", + "inputParameters": [ + "block_url:action_result.summary", + "block_url:action_result.status", + "block_url:action_result.message", + "block_url:action_result.parameter.url" + ], + "outputVariables": [ + "observable_array" + ], + "type": "code" + }, + "errors": {}, + "id": "7", + "type": "code", + "userCode": "\n build_observable__observable_array = list()\n for summary, status, message, url in zip(block_url_result_item_0, block_url_result_item_1, block_url_result_message, block_url_parameter_url):\n observable = {\n \"type\": \"url\",\n \"value\": url,\n \"source\": \"ZScaler\"\n }\n if len(summary[\"ignored\"]) > 0:\n observable[\"status\"] = \"ignored\"\n else:\n observable[\"status\"] = \"updated\"\n \n build_observable__observable_array.append(observable)\n \n\n", + "warnings": {}, + "x": 0, + "y": 700 + }, + "8": { + "data": { + "advanced": { + "customName": "block url success filter", + "customNameId": 0, + "description": "Determine if the block url action was successful or not.", + "join": [], + "note": "Determine if the block url action was successful or not." + }, + "conditions": [ + { + "comparisons": [ + { + "conditionIndex": 0, + "op": "==", + "param": "block_url:action_result.status", + "value": "success" + } + ], + "conditionIndex": 0, + "customName": "success", + "logic": "and" + } + ], + "functionId": 2, + "functionName": "block_url_success_filter", + "id": "8", + "type": "filter" + }, + "errors": {}, + "id": "8", + "type": "filter", + "warnings": {}, + "x": 60, + "y": 506 + } + }, + "notes": "Inputs: url\nInteractions: ZScaler\nActions: block url\nOutputs: observables" + }, + "input_spec": [ + { + "contains": [ + "url" + ], + "description": "Accepts url and block them", + "name": "input_url" + } + ], + "output_spec": [ + { + "contains": [ + "url" + ], + "datapaths": [ + "build_observable:custom_function:observable_array" + ], + "deduplicate": false, + "description": "An array of observable dictionaries with value, type and information about the blocked urls.", + "metadata": {}, + "name": "observable" + } + ], + "playbook_type": "data", + "python_version": "3", + "schema": "5.0.9", + "version": "6.0.0.114895" + }, + "create_time": "2023-03-31T06:50:12.071333+00:00", + "draft_mode": false, + "labels": [ + "*" + ], + "tags": [ + "url", + "D3-OTF", + "ZScaler", + "denylist" + ] +} \ No newline at end of file diff --git a/playbooks/Zscaler_Outbound_Traffic_Filtering.png b/playbooks/Zscaler_Outbound_Traffic_Filtering.png new file mode 100755 index 0000000000000000000000000000000000000000..7f6babff65cdb8d99a3721a29d9b3d5a59a9cf8e GIT binary patch literal 31415 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 literal 0 HcmV?d00001 diff --git a/playbooks/Zscaler_Outbound_Traffic_Filtering.py b/playbooks/Zscaler_Outbound_Traffic_Filtering.py new file mode 100755 index 0000000000..ced11584cd --- /dev/null +++ b/playbooks/Zscaler_Outbound_Traffic_Filtering.py @@ -0,0 +1,170 @@ +""" +Accepts a URL or list of URLs as input. Blocks the given URLs in ZScaler.\n\nhttps://d3fend.mitre.org/technique/d3f:OutboundTrafficFiltering/ +""" + + +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 'url_input_filter' block + url_input_filter(container=container) + + return + +@phantom.playbook_block() +def url_input_filter(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs): + phantom.debug("url_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:input_url", "!=", None] + ], + name="url_input_filter:condition_1") + + # call connected blocks if filtered artifacts or results + if matched_artifacts_1 or matched_results_1: + block_url(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_url(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs): + phantom.debug("block_url() called") + + # phantom.debug('Action: {0} {1}'.format(action['name'], ('SUCCEEDED' if success else 'FAILED'))) + + ################################################################################ + # Block urls in ZScaler based on given urls. + ################################################################################ + + playbook_input_input_url = phantom.collect2(container=container, datapath=["playbook_input:input_url"]) + + parameters = [] + + # build parameters list for 'block_url' call + for playbook_input_input_url_item in playbook_input_input_url: + if playbook_input_input_url_item[0] is not None: + parameters.append({ + "url": playbook_input_input_url_item[0], + }) + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.act("block url", parameters=parameters, name="block_url", assets=["zscaler"], callback=block_url_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. + ################################################################################ + + block_url_result_data = phantom.collect2(container=container, datapath=["block_url:action_result.summary","block_url:action_result.status","block_url:action_result.message","block_url:action_result.parameter.url"], action_results=results) + + block_url_result_item_0 = [item[0] for item in block_url_result_data] + block_url_result_item_1 = [item[1] for item in block_url_result_data] + block_url_result_message = [item[2] for item in block_url_result_data] + block_url_parameter_url = [item[3] for item in block_url_result_data] + + build_observable__observable_array = None + + ################################################################################ + ## Custom Code Start + ################################################################################ + + build_observable__observable_array = list() + for summary, status, message, url in zip(block_url_result_item_0, block_url_result_item_1, block_url_result_message, block_url_parameter_url): + observable = { + "type": "url", + "value": url, + "source": "ZScaler" + } + if len(summary["ignored"]) > 0: + observable["status"] = "ignored" + else: + observable["status"] = "updated" + + 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 block_url_success_filter(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs): + phantom.debug("block_url_success_filter() called") + + ################################################################################ + # Determine if the block url action was successful or not. + ################################################################################ + + # collect filtered artifact ids and results for 'if' condition 1 + matched_artifacts_1, matched_results_1 = phantom.condition( + container=container, + conditions=[ + ["block_url:action_result.status", "==", "success"] + ], + name="block_url_success_filter:condition_1") + + # 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/Zscaler_Outbound_Traffic_Filtering.yml b/playbooks/Zscaler_Outbound_Traffic_Filtering.yml new file mode 100644 index 0000000000..d40a1a03a1 --- /dev/null +++ b/playbooks/Zscaler_Outbound_Traffic_Filtering.yml @@ -0,0 +1,24 @@ +name: ZScaler Outbound Traffic Filtering +id: 3e0df448-0546-4b2b-9143-365161cf40f9 +version: 1 +date: '2023-03-31' +author: Patrick Bareiss, Splunk +type: Investigation +description: Accepts a URL or list of URLs and block them in ZScaler. Generates a list of observables with the blocked URLs. +playbook: ZScaler_Outbound_Traffic_Filtering +how_to_implement: This input playbook requires the ZScaler connector to be configured. It is designed to work in conjunction with the Dynamic URL Outbound Traffic Filtering Analysis playbook or other playbooks in the same style. +references: + - https://d3fend.mitre.org/technique/d3f:OutboundTrafficFiltering/ +app_list: + - ZScaler +tags: + platform_tags: + - denylist + - url + - D3-OTF + - ZScaler + playbook_type: Input + vpe_type: Modern + playbook_fields: [] + product: + - Splunk SOAR \ No newline at end of file From b55a0281ea4e2b2d9aeaa579492039a8d350dd79 Mon Sep 17 00:00:00 2001 From: P4T12ICK Date: Wed, 21 Jun 2023 11:33:05 +0200 Subject: [PATCH 2/3] Improved the data paths --- .../Zscaler_Outbound_Traffic_Filtering.json | 34 ++++++++----------- .../Zscaler_Outbound_Traffic_Filtering.py | 21 +++++++----- 2 files changed, 27 insertions(+), 28 deletions(-) mode change 100755 => 100644 playbooks/Zscaler_Outbound_Traffic_Filtering.json mode change 100755 => 100644 playbooks/Zscaler_Outbound_Traffic_Filtering.py diff --git a/playbooks/Zscaler_Outbound_Traffic_Filtering.json b/playbooks/Zscaler_Outbound_Traffic_Filtering.json old mode 100755 new mode 100644 index f77a7624f5..04a5c3c26f --- a/playbooks/Zscaler_Outbound_Traffic_Filtering.json +++ b/playbooks/Zscaler_Outbound_Traffic_Filtering.json @@ -52,7 +52,7 @@ "targetPort": "7_in" } ], - "hash": "d00eda8b809a9b54fd2a3df75a64dc1538169f7c", + "hash": "ddce2d54e4936120ec7acc772b88f91fab41ec55", "nodes": { "0": { "data": { @@ -67,8 +67,8 @@ "id": "0", "type": "start", "warnings": {}, - "x": 20, - "y": 0 + "x": 19.999999999999986, + "y": -6.394884621840902e-14 }, "1": { "data": { @@ -91,6 +91,8 @@ "advanced": { "customName": "url input filter", "customNameId": 0, + "delimiter": ",", + "delimiter_enabled": true, "description": "Determine branches based on provided inputs.", "join": [], "note": "Determine branches based on provided inputs." @@ -118,11 +120,7 @@ "errors": {}, "id": "4", "type": "filter", - "warnings": { - "config": [ - "Reconfigure invalid datapath." - ] - }, + "warnings": {}, "x": 60, "y": 148 }, @@ -147,7 +145,7 @@ "functionName": "block_url", "id": "6", "parameters": { - "url": "playbook_input:input_url" + "url": "filtered-data:url_input_filter:condition_1:playbook_input:input_url" }, "requiredParameters": [ { @@ -160,11 +158,7 @@ "errors": {}, "id": "6", "type": "action", - "warnings": { - "config": [ - "Reconfigure invalid datapath." - ] - }, + "warnings": {}, "x": 0, "y": 328 }, @@ -184,7 +178,7 @@ "block_url:action_result.summary", "block_url:action_result.status", "block_url:action_result.message", - "block_url:action_result.parameter.url" + "filtered-data:url_input_filter:condition_1:playbook_input:input_url" ], "outputVariables": [ "observable_array" @@ -194,7 +188,7 @@ "errors": {}, "id": "7", "type": "code", - "userCode": "\n build_observable__observable_array = list()\n for summary, status, message, url in zip(block_url_result_item_0, block_url_result_item_1, block_url_result_message, block_url_parameter_url):\n observable = {\n \"type\": \"url\",\n \"value\": url,\n \"source\": \"ZScaler\"\n }\n if len(summary[\"ignored\"]) > 0:\n observable[\"status\"] = \"ignored\"\n else:\n observable[\"status\"] = \"updated\"\n \n build_observable__observable_array.append(observable)\n \n\n", + "userCode": "\n build_observable__observable_array = list()\n for summary, status, message, url in zip(block_url_result_item_0, block_url_result_item_1, block_url_result_message, filtered_input_0_input_url_values):\n observable = {\n \"type\": \"url\",\n \"value\": url,\n \"source\": \"ZScaler\"\n }\n if len(summary[\"ignored\"]) > 0:\n observable[\"status\"] = \"ignored\"\n else:\n observable[\"status\"] = \"updated\"\n \n build_observable__observable_array.append(observable)\n \n\n", "warnings": {}, "x": 0, "y": 700 @@ -204,6 +198,8 @@ "advanced": { "customName": "block url success filter", "customNameId": 0, + "delimiter": ",", + "delimiter_enabled": true, "description": "Determine if the block url action was successful or not.", "join": [], "note": "Determine if the block url action was successful or not." @@ -263,10 +259,10 @@ ], "playbook_type": "data", "python_version": "3", - "schema": "5.0.9", - "version": "6.0.0.114895" + "schema": "5.0.10", + "version": "6.0.1.123902" }, - "create_time": "2023-03-31T06:50:12.071333+00:00", + "create_time": "2023-06-21T09:27:47.392005+00:00", "draft_mode": false, "labels": [ "*" diff --git a/playbooks/Zscaler_Outbound_Traffic_Filtering.py b/playbooks/Zscaler_Outbound_Traffic_Filtering.py old mode 100755 new mode 100644 index ced11584cd..f5e7bef5a4 --- a/playbooks/Zscaler_Outbound_Traffic_Filtering.py +++ b/playbooks/Zscaler_Outbound_Traffic_Filtering.py @@ -31,7 +31,8 @@ def url_input_filter(action=None, success=None, container=None, results=None, ha conditions=[ ["playbook_input:input_url", "!=", None] ], - name="url_input_filter:condition_1") + name="url_input_filter:condition_1", + delimiter=",") # call connected blocks if filtered artifacts or results if matched_artifacts_1 or matched_results_1: @@ -50,15 +51,15 @@ def block_url(action=None, success=None, container=None, results=None, handle=No # Block urls in ZScaler based on given urls. ################################################################################ - playbook_input_input_url = phantom.collect2(container=container, datapath=["playbook_input:input_url"]) + filtered_input_0_input_url = phantom.collect2(container=container, datapath=["filtered-data:url_input_filter:condition_1:playbook_input:input_url"]) parameters = [] # build parameters list for 'block_url' call - for playbook_input_input_url_item in playbook_input_input_url: - if playbook_input_input_url_item[0] is not None: + for filtered_input_0_input_url_item in filtered_input_0_input_url: + if filtered_input_0_input_url_item[0] is not None: parameters.append({ - "url": playbook_input_input_url_item[0], + "url": filtered_input_0_input_url_item[0], }) ################################################################################ @@ -85,12 +86,13 @@ def build_observable(action=None, success=None, container=None, results=None, ha # the observables data path. ################################################################################ - block_url_result_data = phantom.collect2(container=container, datapath=["block_url:action_result.summary","block_url:action_result.status","block_url:action_result.message","block_url:action_result.parameter.url"], action_results=results) + block_url_result_data = phantom.collect2(container=container, datapath=["block_url:action_result.summary","block_url:action_result.status","block_url:action_result.message"], action_results=results) + filtered_input_0_input_url = phantom.collect2(container=container, datapath=["filtered-data:url_input_filter:condition_1:playbook_input:input_url"]) block_url_result_item_0 = [item[0] for item in block_url_result_data] block_url_result_item_1 = [item[1] for item in block_url_result_data] block_url_result_message = [item[2] for item in block_url_result_data] - block_url_parameter_url = [item[3] for item in block_url_result_data] + filtered_input_0_input_url_values = [item[0] for item in filtered_input_0_input_url] build_observable__observable_array = None @@ -99,7 +101,7 @@ def build_observable(action=None, success=None, container=None, results=None, ha ################################################################################ build_observable__observable_array = list() - for summary, status, message, url in zip(block_url_result_item_0, block_url_result_item_1, block_url_result_message, block_url_parameter_url): + for summary, status, message, url in zip(block_url_result_item_0, block_url_result_item_1, block_url_result_message, filtered_input_0_input_url_values): observable = { "type": "url", "value": url, @@ -136,7 +138,8 @@ def block_url_success_filter(action=None, success=None, container=None, results= conditions=[ ["block_url:action_result.status", "==", "success"] ], - name="block_url_success_filter:condition_1") + name="block_url_success_filter:condition_1", + delimiter=",") # call connected blocks if filtered artifacts or results if matched_artifacts_1 or matched_results_1: From 1e0ec0734598933893dc7070943e977ffd8cad56 Mon Sep 17 00:00:00 2001 From: P4T12ICK Date: Wed, 21 Jun 2023 16:22:42 +0200 Subject: [PATCH 3/3] Improvements --- playbooks/Zscaler_Outbound_Traffic_Filtering.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playbooks/Zscaler_Outbound_Traffic_Filtering.yml b/playbooks/Zscaler_Outbound_Traffic_Filtering.yml index d40a1a03a1..f99cf8c988 100644 --- a/playbooks/Zscaler_Outbound_Traffic_Filtering.yml +++ b/playbooks/Zscaler_Outbound_Traffic_Filtering.yml @@ -3,7 +3,7 @@ id: 3e0df448-0546-4b2b-9143-365161cf40f9 version: 1 date: '2023-03-31' author: Patrick Bareiss, Splunk -type: Investigation +type: Response description: Accepts a URL or list of URLs and block them in ZScaler. Generates a list of observables with the blocked URLs. playbook: ZScaler_Outbound_Traffic_Filtering how_to_implement: This input playbook requires the ZScaler connector to be configured. It is designed to work in conjunction with the Dynamic URL Outbound Traffic Filtering Analysis playbook or other playbooks in the same style.