Branch was auto-updated.

This commit is contained in:
srv-rr-gh-researchbt
2023-06-21 07:33:22 -07:00
committed by GitHub
12 changed files with 1757 additions and 0 deletions
@@ -0,0 +1,380 @@
{
"blockly": false,
"blockly_xml": "<xml></xml>",
"category": "Message Identifier Activity Analysis",
"coa": {
"data": {
"description": "Accepts an internet message id, and asks Gmail for a list of mailboxes to search, and then searches each one to look for records that have a matching internet message id. It then produces a normalized output and summary table.\n\nThis may not work in the intended fashion if your organization has more than 500 mailboxes.\n\nRef: D3-IAA: https://d3fend.mitre.org/technique/d3f:IdentifierActivityAnalysis/",
"edges": [
{
"conditions": [
{
"index": 0
}
],
"id": "port_3_to_port_4",
"sourceNode": "3",
"sourcePort": "3_out",
"targetNode": "4",
"targetPort": "4_in"
},
{
"id": "port_4_to_port_5",
"sourceNode": "4",
"sourcePort": "4_out",
"targetNode": "5",
"targetPort": "5_in"
},
{
"id": "port_6_to_port_7",
"sourceNode": "6",
"sourcePort": "6_out",
"targetNode": "7",
"targetPort": "7_in"
},
{
"id": "port_7_to_port_1",
"sourceNode": "7",
"sourcePort": "7_out",
"targetNode": "1",
"targetPort": "1_in"
},
{
"id": "port_5_to_port_9",
"sourceNode": "5",
"sourcePort": "5_out",
"targetNode": "9",
"targetPort": "9_in"
},
{
"id": "port_0_to_port_3",
"sourceNode": "0",
"sourcePort": "0_out",
"targetNode": "3",
"targetPort": "3_in"
},
{
"conditions": [
{
"index": 0
}
],
"id": "port_9_to_port_6",
"sourceNode": "9",
"sourcePort": "9_out",
"targetNode": "6",
"targetPort": "6_in"
}
],
"hash": "c253dd1339538806313a6f387f6df7a39d537dff",
"nodes": {
"0": {
"data": {
"advanced": {
"join": []
},
"functionName": "on_start",
"id": "0",
"type": "start"
},
"errors": {},
"id": "0",
"type": "start",
"warnings": {},
"x": 19.999999999999986,
"y": -1.2789769243681803e-13
},
"1": {
"data": {
"advanced": {
"join": []
},
"functionName": "on_finish",
"id": "1",
"type": "end"
},
"errors": {},
"id": "1",
"type": "end",
"warnings": {},
"x": 19.999999999999986,
"y": 1220
},
"3": {
"data": {
"advanced": {
"customName": "artifact filter",
"customNameId": 0,
"delimiter": ",",
"delimiter_enabled": true,
"join": [],
"note": "Ensure we have an input"
},
"conditions": [
{
"comparisons": [
{
"conditionIndex": 0,
"op": "!=",
"param": "playbook_input:message_id",
"value": "None"
}
],
"conditionIndex": 0,
"customName": "Input Valid",
"logic": "and"
}
],
"functionId": 1,
"functionName": "artifact_filter",
"id": "3",
"type": "filter"
},
"errors": {},
"id": "3",
"type": "filter",
"warnings": {},
"x": 60,
"y": 148
},
"4": {
"data": {
"action": "list users",
"actionType": "investigate",
"advanced": {
"customName": "get mailboxes",
"customNameId": 0,
"description": "Required step in order to search \"All\" of a Gsuite organization. This receives a list of mailboxes that are passed to the next action.",
"join": [],
"note": "Gets a list of mailboxes from GMail to search"
},
"connector": "G Suite for GMail",
"connectorConfigs": [
"g_suite_for_gmail"
],
"connectorId": "9c73f233-2c4a-406a-855e-41d8d2497d0e",
"connectorVersion": "v1",
"functionId": 1,
"functionName": "get_mailboxes",
"id": "4",
"parameters": {
"max_items": "500",
"page_token": ""
},
"requiredParameters": [],
"type": "action"
},
"errors": {},
"id": "4",
"type": "action",
"warnings": {},
"x": 0,
"y": 328
},
"5": {
"data": {
"action": "run query",
"actionType": "investigate",
"advanced": {
"customName": "search mailboxes",
"customNameId": 0,
"join": [],
"note": "Searches mailboxes returned by the previous action for messages with an internet message ID matching the input"
},
"connector": "G Suite for GMail",
"connectorConfigs": [
"g_suite_for_gmail"
],
"connectorId": "9c73f233-2c4a-406a-855e-41d8d2497d0e",
"connectorVersion": "v1",
"functionId": 1,
"functionName": "search_mailboxes",
"id": "5",
"parameters": {
"email": "get_mailboxes:action_result.data.*.emails.*.address",
"internet_message_id": "filtered-data:artifact_filter:condition_1:playbook_input:message_id",
"label": "Inbox",
"max_results": 100,
"query": ""
},
"requiredParameters": [
{
"data_type": "string",
"field": "email"
},
{
"data_type": "string",
"default": "Inbox",
"field": "label"
},
{
"data_type": "numeric",
"default": 100,
"field": "max_results"
}
],
"type": "action"
},
"errors": {},
"id": "5",
"type": "action",
"warnings": {},
"x": 0,
"y": 506
},
"6": {
"data": {
"advanced": {
"customName": "format message report",
"customNameId": 0,
"join": [],
"note": "Format the markdown table summary of this playbook execution"
},
"functionId": 1,
"functionName": "format_message_report",
"id": "6",
"parameters": [
"filtered-data:results_filter:condition_1:search_mailboxes:action_result.parameter.internet_message_id",
"filtered-data:results_filter:condition_1:search_mailboxes:action_result.data.*.delivered_to",
"filtered-data:results_filter:condition_1:search_mailboxes:action_result.data.*.to",
"filtered-data:results_filter:condition_1:search_mailboxes:action_result.data.*.subject",
"filtered-data:results_filter:condition_1:search_mailboxes:action_result.data.*.from"
],
"template": "SOAR searched for occurrences of `{0}` within your environment using GSuite for GMail. The table below shows a summary of the information gathered.\n\n| Recipient | Addressed To | Subject | Sender |\n| --- | --- | --- | --- |\n%%\n| {1} | {2} | {3} | {4} |\n%%\n",
"type": "format"
},
"errors": {},
"id": "6",
"type": "format",
"userCode": " \n \n",
"warnings": {},
"x": 0,
"y": 864
},
"7": {
"data": {
"advanced": {
"customName": "build message output",
"customNameId": 0,
"description": "Logic regarding observable construction goes here",
"join": [],
"note": "Format the observable output object produced by this playbook"
},
"functionId": 1,
"functionName": "build_message_output",
"id": "7",
"inputParameters": [
"filtered-data:results_filter:condition_1:search_mailboxes:action_result.parameter.internet_message_id",
"filtered-data:results_filter:condition_1:search_mailboxes:action_result.data.*.delivered_to",
"filtered-data:results_filter:condition_1:search_mailboxes:action_result.data.*.to",
"filtered-data:results_filter:condition_1:search_mailboxes:action_result.data.*.subject",
"filtered-data:results_filter:condition_1:search_mailboxes:action_result.data.*.from"
],
"outputVariables": [
"observable_array"
],
"type": "code"
},
"errors": {},
"id": "7",
"type": "code",
"userCode": "\n # Variable renaming for convenince\n messageID = filtered_result_0_parameter_internet_message_id\n recipients = filtered_result_0_data___delivered_to\n addressees = filtered_result_0_data___to\n subjects = filtered_result_0_data___subject\n senders = filtered_result_0_data___from\n \n build_message_output__observable_array = []\n recordList = []\n \n for message_id in messageID:\n \n # construct iterables for records\n for recipient, addressee, subject, sender in zip(recipients, addressees, subjects, senders):\n record = {\n \"recipient\": recipient,\n \"addressee\": addressee,\n \"subject\": subject,\n \"sender\": sender\n }\n recordList.append(record)\n \n # Create observable body\n observable = {\n \"value\": message_id,\n \"type\": \"internet message ID\",\n \"count\": len(recordList),\n \"source\": \"GSuite for GMail\",\n \"message_identifier_activity\": recordList\n }\n \n build_message_output__observable_array.append(observable)\n \n\n",
"warnings": {},
"x": 0,
"y": 1040
},
"9": {
"data": {
"advanced": {
"customName": "results filter",
"customNameId": 0,
"delimiter": ",",
"delimiter_enabled": true,
"description": "Filter results from mailbox search",
"join": [],
"note": "Remove mailboxes that couldn't be searched or where no results were found"
},
"conditions": [
{
"comparisons": [
{
"conditionIndex": 0,
"op": "==",
"param": "search_mailboxes:action_result.status",
"value": "success"
},
{
"conditionIndex": 0,
"op": ">",
"param": "search_mailboxes:action_result.summary.total_messages_returned",
"value": "0"
}
],
"conditionIndex": 0,
"customName": "Results found",
"logic": "and"
}
],
"functionId": 2,
"functionName": "results_filter",
"id": "9",
"type": "filter"
},
"errors": {},
"id": "9",
"type": "filter",
"warnings": {},
"x": 60,
"y": 684
}
},
"notes": "Inputs: internet message id\nInteractions: GSuite For GMail\nActions: get users, run query\nOutputs: observable, markdown report"
},
"input_spec": [
{
"contains": [
"internet message id"
],
"description": "An Internet Message ID to look for within a GMail environment",
"name": "message_id"
}
],
"output_spec": [
{
"contains": [],
"datapaths": [
"build_message_output:custom_function:observable_array"
],
"deduplicate": false,
"description": "An array of Observable objects ",
"metadata": {},
"name": "observable"
},
{
"contains": [],
"datapaths": [
"format_message_report:formatted_data"
],
"deduplicate": false,
"description": "An array of markdown reports",
"metadata": {},
"name": "markdown_report"
}
],
"playbook_type": "data",
"python_version": "3",
"schema": "5.0.10",
"version": "6.0.1.123902"
},
"create_time": "2023-06-16T15:48:09.633490+00:00",
"draft_mode": false,
"labels": [
"*"
],
"tags": [
"message_identifier_activity",
"internet_message_id",
"gsuite_for_gmail",
"D3-IAA"
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

@@ -0,0 +1,258 @@
"""
Accepts an internet message id, and asks Gmail for a list of mailboxes to search, and then searches each one to look for records that have a matching internet message id. It then produces a normalized output and summary table.\n\nThis may not work in the intended fashion if your organization has more than 500 mailboxes.\n\nRef: D3-IAA: https://d3fend.mitre.org/technique/d3f:IdentifierActivityAnalysis/
"""
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_filter' block
artifact_filter(container=container)
return
@phantom.playbook_block()
def artifact_filter(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
phantom.debug("artifact_filter() called")
# collect filtered artifact ids and results for 'if' condition 1
matched_artifacts_1, matched_results_1 = phantom.condition(
container=container,
conditions=[
["playbook_input:message_id", "!=", None]
],
name="artifact_filter:condition_1",
delimiter=",")
# call connected blocks if filtered artifacts or results
if matched_artifacts_1 or matched_results_1:
get_mailboxes(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 get_mailboxes(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
phantom.debug("get_mailboxes() called")
# phantom.debug('Action: {0} {1}'.format(action['name'], ('SUCCEEDED' if success else 'FAILED')))
################################################################################
# Required step in order to search "All" of a Gsuite organization. This receives
# a list of mailboxes that are passed to the next action.
################################################################################
parameters = []
parameters.append({
"max_items": 500,
"page_token": "",
})
################################################################################
## Custom Code Start
################################################################################
# Write your custom code here...
################################################################################
## Custom Code End
################################################################################
phantom.act("list users", parameters=parameters, name="get_mailboxes", assets=["g_suite_for_gmail"], callback=search_mailboxes)
return
@phantom.playbook_block()
def search_mailboxes(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
phantom.debug("search_mailboxes() called")
# phantom.debug('Action: {0} {1}'.format(action['name'], ('SUCCEEDED' if success else 'FAILED')))
get_mailboxes_result_data = phantom.collect2(container=container, datapath=["get_mailboxes:action_result.data.*.emails.*.address","get_mailboxes:action_result.parameter.context.artifact_id"], action_results=results)
filtered_input_0_message_id = phantom.collect2(container=container, datapath=["filtered-data:artifact_filter:condition_1:playbook_input:message_id"])
parameters = []
# build parameters list for 'search_mailboxes' call
for get_mailboxes_result_item in get_mailboxes_result_data:
for filtered_input_0_message_id_item in filtered_input_0_message_id:
if get_mailboxes_result_item[0] is not None:
parameters.append({
"email": get_mailboxes_result_item[0],
"label": "Inbox",
"query": "",
"max_results": 100,
"internet_message_id": filtered_input_0_message_id_item[0],
"context": {'artifact_id': get_mailboxes_result_item[1]},
})
################################################################################
## Custom Code Start
################################################################################
# Write your custom code here...
################################################################################
## Custom Code End
################################################################################
phantom.act("run query", parameters=parameters, name="search_mailboxes", assets=["g_suite_for_gmail"], callback=results_filter)
return
@phantom.playbook_block()
def format_message_report(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
phantom.debug("format_message_report() called")
template = """SOAR searched for occurrences of `{0}` within your environment using GSuite for GMail. The table below shows a summary of the information gathered.\n\n| Recipient | Addressed To | Subject | Sender |\n| --- | --- | --- | --- |\n%%\n| {1} | {2} | {3} | {4} |\n%%\n"""
# parameter list for template variable replacement
parameters = [
"filtered-data:results_filter:condition_1:search_mailboxes:action_result.parameter.internet_message_id",
"filtered-data:results_filter:condition_1:search_mailboxes:action_result.data.*.delivered_to",
"filtered-data:results_filter:condition_1:search_mailboxes:action_result.data.*.to",
"filtered-data:results_filter:condition_1:search_mailboxes:action_result.data.*.subject",
"filtered-data:results_filter:condition_1:search_mailboxes:action_result.data.*.from"
]
################################################################################
## Custom Code Start
################################################################################
################################################################################
## Custom Code End
################################################################################
phantom.format(container=container, template=template, parameters=parameters, name="format_message_report")
build_message_output(container=container)
return
@phantom.playbook_block()
def build_message_output(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
phantom.debug("build_message_output() called")
################################################################################
# Logic regarding observable construction goes here
################################################################################
filtered_result_0_data_results_filter = phantom.collect2(container=container, datapath=["filtered-data:results_filter:condition_1:search_mailboxes:action_result.parameter.internet_message_id","filtered-data:results_filter:condition_1:search_mailboxes:action_result.data.*.delivered_to","filtered-data:results_filter:condition_1:search_mailboxes:action_result.data.*.to","filtered-data:results_filter:condition_1:search_mailboxes:action_result.data.*.subject","filtered-data:results_filter:condition_1:search_mailboxes:action_result.data.*.from"])
filtered_result_0_parameter_internet_message_id = [item[0] for item in filtered_result_0_data_results_filter]
filtered_result_0_data___delivered_to = [item[1] for item in filtered_result_0_data_results_filter]
filtered_result_0_data___to = [item[2] for item in filtered_result_0_data_results_filter]
filtered_result_0_data___subject = [item[3] for item in filtered_result_0_data_results_filter]
filtered_result_0_data___from = [item[4] for item in filtered_result_0_data_results_filter]
build_message_output__observable_array = None
################################################################################
## Custom Code Start
################################################################################
# Variable renaming for convenince
messageID = filtered_result_0_parameter_internet_message_id
recipients = filtered_result_0_data___delivered_to
addressees = filtered_result_0_data___to
subjects = filtered_result_0_data___subject
senders = filtered_result_0_data___from
build_message_output__observable_array = []
recordList = []
for message_id in messageID:
# construct iterables for records
for recipient, addressee, subject, sender in zip(recipients, addressees, subjects, senders):
record = {
"recipient": recipient,
"addressee": addressee,
"subject": subject,
"sender": sender
}
recordList.append(record)
# Create observable body
observable = {
"value": message_id,
"type": "internet message ID",
"count": len(recordList),
"source": "GSuite for GMail",
"message_identifier_activity": recordList
}
build_message_output__observable_array.append(observable)
################################################################################
## Custom Code End
################################################################################
phantom.save_run_data(key="build_message_output:observable_array", value=json.dumps(build_message_output__observable_array))
return
@phantom.playbook_block()
def results_filter(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
phantom.debug("results_filter() called")
################################################################################
# Filter results from mailbox search
################################################################################
# collect filtered artifact ids and results for 'if' condition 1
matched_artifacts_1, matched_results_1 = phantom.condition(
container=container,
logical_operator="and",
conditions=[
["search_mailboxes:action_result.status", "==", "success"],
["search_mailboxes:action_result.summary.total_messages_returned", ">", 0]
],
name="results_filter:condition_1",
delimiter=",")
# call connected blocks if filtered artifacts or results
if matched_artifacts_1 or matched_results_1:
format_message_report(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")
format_message_report = phantom.get_format_data(name="format_message_report")
build_message_output__observable_array = json.loads(_ if (_ := phantom.get_run_data(key="build_message_output:observable_array")) != "" else "null") # pylint: disable=used-before-assignment
output = {
"observable": build_message_output__observable_array,
"markdown_report": format_message_report,
}
################################################################################
## Custom Code Start
################################################################################
# Write your custom code here...
################################################################################
## Custom Code End
################################################################################
phantom.save_playbook_output_data(output=output)
return
@@ -0,0 +1,22 @@
name: G Suite for GMail Message Identifier Activity Analysis
id: 5299d6dd-e9c4-4afa-b051-928ace0ff816d
version: 1
date: '2023-05-12'
author: Lou Stella, Splunk
type: Investigation
description: "Accepts an internet message id, and asks Gmail for a list of mailboxes to search, and then searches each one to look for records that have a matching internet message id. It then produces a normalized output and summary table."
playbook: G_Suite_for_GMail_Message_Identifier_Acitivity_Analysis
how_to_implement: This input playbook requires the G Suite for GMail connector to be configured. It is designed to work in environments that posess a maximum of 500 mailboxes at this time, due to a limitation in the G Suite for GMail connector.
references: []
app_list:
- G Suite for GMail
tags:
platform_tags:
- message_identifier_activity
- internet_message_id
- gsuite_for_gmail
playbook_type: Input
vpe_type: Modern
playbook_fields: []
product:
- Splunk SOAR
@@ -0,0 +1,361 @@
{
"blockly": false,
"blockly_xml": "<xml></xml>",
"category": "Message Identifier Activity Analysis",
"coa": {
"data": {
"description": "Accepts an internet message id, and asks Splunk \n to look for records that have a matching internet message id. It then produces a normalized output and summary table.\n\nRef: D3-IAA https://d3fend.mitre.org/technique/d3f:IdentifierActivityAnalysis/",
"edges": [
{
"conditions": [
{
"index": 0
}
],
"id": "port_3_to_port_5",
"sourceNode": "3",
"sourcePort": "3_out",
"targetNode": "5",
"targetPort": "5_in"
},
{
"id": "port_5_to_port_6",
"sourceNode": "5",
"sourcePort": "5_out",
"targetNode": "6",
"targetPort": "6_in"
},
{
"id": "port_7_to_port_8",
"sourceNode": "7",
"sourcePort": "7_out",
"targetNode": "8",
"targetPort": "8_in"
},
{
"id": "port_8_to_port_1",
"sourceNode": "8",
"sourcePort": "8_out",
"targetNode": "1",
"targetPort": "1_in"
},
{
"id": "port_6_to_port_9",
"sourceNode": "6",
"sourcePort": "6_out",
"targetNode": "9",
"targetPort": "9_in"
},
{
"conditions": [
{
"index": 0
}
],
"id": "port_9_to_port_7",
"sourceNode": "9",
"sourcePort": "9_out",
"targetNode": "7",
"targetPort": "7_in"
},
{
"id": "port_0_to_port_3",
"sourceNode": "0",
"sourcePort": "0_out",
"targetNode": "3",
"targetPort": "3_in"
}
],
"hash": "6bbfd0d0e879b5ebab46c26404cb193313b5ea56",
"nodes": {
"0": {
"data": {
"advanced": {
"join": []
},
"functionName": "on_start",
"id": "0",
"type": "start"
},
"errors": {},
"id": "0",
"type": "start",
"warnings": {},
"x": 19.999999999999986,
"y": -1.9184653865522705e-13
},
"1": {
"data": {
"advanced": {
"join": []
},
"functionName": "on_finish",
"id": "1",
"type": "end"
},
"errors": {},
"id": "1",
"type": "end",
"warnings": {},
"x": 19.999999999999986,
"y": 1220
},
"3": {
"data": {
"advanced": {
"customName": "input filter",
"customNameId": 0,
"delimiter": ",",
"delimiter_enabled": true,
"join": [],
"note": "Ensure there is an input"
},
"conditions": [
{
"comparisons": [
{
"conditionIndex": 0,
"op": "!=",
"param": "playbook_input:message_id",
"value": "None"
}
],
"conditionIndex": 0,
"customName": "Input Valid",
"logic": "and"
}
],
"functionId": 1,
"functionName": "input_filter",
"id": "3",
"type": "filter"
},
"errors": {},
"id": "3",
"type": "filter",
"warnings": {},
"x": 60,
"y": 148
},
"5": {
"data": {
"advanced": {
"customName": "format message query",
"customNameId": 0,
"join": [],
"note": "construct Splunk query with substitution of input"
},
"functionId": 1,
"functionName": "format_message_query",
"id": "5",
"parameters": [
"filtered-data:input_filter:condition_1:playbook_input:message_id"
],
"template": "summariesonly=false fillnull_value=\"Unknown\" count from datamodel=Email.All_Email where All_Email.message_id=\"{0}\" by All_Email.orig_recipient, All_Email.recipient, All_Email.src_user, All_Email.subject | `drop_dm_object_name(\"All_Email\")` | rename orig_recipient as Addressee, recipient as Recipient, src_user as Sender, subject as Subject | fields Addressee, Recipient, Sender, Subject | fillnull value=\"Unknown\"",
"type": "format"
},
"errors": {},
"id": "5",
"type": "format",
"warnings": {},
"x": 0,
"y": 328
},
"6": {
"data": {
"action": "run query",
"actionType": "investigate",
"advanced": {
"customName": "run message query",
"customNameId": 0,
"join": [],
"note": "Run Splunk query created in prior block"
},
"connector": "Splunk",
"connectorConfigs": [
"splunk"
],
"connectorId": "91883aa8-9c81-470b-97a1-5d8f7995f560",
"connectorVersion": "v1",
"functionId": 1,
"functionName": "run_message_query",
"id": "6",
"parameters": {
"command": "tstats",
"display": "Addressee,Recipient,Sender,Subject",
"query": "format_message_query:formatted_data",
"search_mode": "smart"
},
"requiredParameters": [
{
"data_type": "string",
"field": "query"
},
{
"data_type": "string",
"default": "search",
"field": "command"
},
{
"data_type": "string",
"default": "smart",
"field": "search_mode"
}
],
"type": "action"
},
"errors": {},
"id": "6",
"type": "action",
"warnings": {},
"x": 0,
"y": 506
},
"7": {
"data": {
"advanced": {
"customName": "format message report",
"customNameId": 0,
"join": [],
"note": "Format the markdown table summary of this playbook execution"
},
"functionId": 2,
"functionName": "format_message_report",
"id": "7",
"parameters": [
"filtered-data:input_filter:condition_1:playbook_input:message_id",
"filtered-data:results_filter:condition_1:run_message_query:action_result.data.*.Recipient",
"filtered-data:results_filter:condition_1:run_message_query:action_result.data.*.Addressee",
"filtered-data:results_filter:condition_1:run_message_query:action_result.data.*.Subject",
"filtered-data:results_filter:condition_1:run_message_query:action_result.data.*.Sender"
],
"template": "SOAR searched for occurrences of `{0}` within your environment using Splunk's Email datamodel. The table below shows a summary of the information gathered.\n\n\n| Recipient | Addressed To | Subject | Sender |\n| --- | --- | --- | --- |\n%%\n| {1} | {2} | {3} | {4} |\n%%\n\n",
"type": "format"
},
"errors": {},
"id": "7",
"type": "format",
"warnings": {},
"x": 0,
"y": 864
},
"8": {
"data": {
"advanced": {
"customName": "build message output",
"customNameId": 0,
"description": "Logic regarding observable construction goes here",
"join": [],
"note": "Format the observable output object produced by this playbook"
},
"functionId": 1,
"functionName": "build_message_output",
"id": "8",
"inputParameters": [
"filtered-data:input_filter:condition_1:playbook_input:message_id",
"filtered-data:results_filter:condition_1:run_message_query:action_result.data.*.Recipient",
"filtered-data:results_filter:condition_1:run_message_query:action_result.data.*.Addressee",
"filtered-data:results_filter:condition_1:run_message_query:action_result.data.*.Subject",
"filtered-data:results_filter:condition_1:run_message_query:action_result.data.*.Sender"
],
"outputVariables": [
"observable_array"
],
"type": "code"
},
"errors": {},
"id": "8",
"type": "code",
"userCode": "\n # Variable renames for convenience\n message_id = filtered_input_0_message_id_values\n recipients = filtered_result_0_data___recipient\n addressees = filtered_result_0_data___addressee\n subjects = filtered_result_0_data___subject\n senders = filtered_result_0_data___sender\n \n build_message_output__observable_array = []\n recordList = []\n \n # unwind records\n for message_identifier in message_id:\n for recipient, addressee, subject, sender in zip(recipients, addressees, subjects, senders):\n record = {\n \"recipient\": recipient,\n \"addressee\": addressee,\n \"subject\": subject,\n \"sender\": sender\n }\n recordList.append(record)\n \n observable = {\n \"value\": message_identifier,\n \"type\": \"internet message ID\",\n \"count\": len(recordList),\n \"source\": \"Splunk\",\n \"message_identifier_activity\": recordList\n }\n\n build_message_output__observable_array.append(observable)\n \n",
"warnings": {},
"x": 0,
"y": 1042
},
"9": {
"data": {
"advanced": {
"customName": "Results filter",
"customNameId": 0,
"delimiter": ",",
"delimiter_enabled": true,
"join": [],
"note": "Ensure there are results"
},
"conditions": [
{
"comparisons": [
{
"conditionIndex": 0,
"op": ">",
"param": "run_message_query:action_result.summary.total_events",
"value": "0"
}
],
"conditionIndex": 0,
"customName": "Results exist",
"logic": "and"
}
],
"functionId": 2,
"functionName": "results_filter",
"id": "9",
"type": "filter"
},
"errors": {},
"id": "9",
"type": "filter",
"warnings": {},
"x": 60,
"y": 684
}
},
"notes": "Inputs: internet message id\nInteractions: Splunk\nActions: run query\nOutputs: observable, markdown report"
},
"input_spec": [
{
"contains": [
"internet message id"
],
"description": "An Internet Message ID to look for within message logs in Splunk",
"name": "message_id"
}
],
"output_spec": [
{
"contains": [],
"datapaths": [
"build_message_output:custom_function:observable_array"
],
"deduplicate": false,
"description": "An array of Observable objects ",
"metadata": {},
"name": "observable"
},
{
"contains": [],
"datapaths": [
"format_message_report:formatted_data"
],
"deduplicate": false,
"description": "An array of markdown reports",
"metadata": {},
"name": "markdown_report"
}
],
"playbook_type": "data",
"python_version": "3",
"schema": "5.0.10",
"version": "6.0.1.123902"
},
"create_time": "2023-06-20T13:25:49.471837+00:00",
"draft_mode": false,
"labels": [
"*"
],
"tags": [
"message_identifier_activity",
"internet_message_id",
"splunk",
"D3-IAA"
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

@@ -0,0 +1,240 @@
"""
Accepts an internet message id, and asks Splunk \n to look for records that have a matching internet message id. It then produces a normalized output and summary table.\n\nRef: D3-IAA https://d3fend.mitre.org/technique/d3f:IdentifierActivityAnalysis/
"""
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 'input_filter' block
input_filter(container=container)
return
@phantom.playbook_block()
def input_filter(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
phantom.debug("input_filter() called")
# collect filtered artifact ids and results for 'if' condition 1
matched_artifacts_1, matched_results_1 = phantom.condition(
container=container,
conditions=[
["playbook_input:message_id", "!=", None]
],
name="input_filter:condition_1",
delimiter=",")
# call connected blocks if filtered artifacts or results
if matched_artifacts_1 or matched_results_1:
format_message_query(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 format_message_query(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
phantom.debug("format_message_query() called")
template = """summariesonly=false fillnull_value=\"Unknown\" count from datamodel=Email.All_Email where All_Email.message_id=\"{0}\" by All_Email.orig_recipient, All_Email.recipient, All_Email.src_user, All_Email.subject | `drop_dm_object_name(\"All_Email\")` | rename orig_recipient as Addressee, recipient as Recipient, src_user as Sender, subject as Subject | fields Addressee, Recipient, Sender, Subject | fillnull value=\"Unknown\""""
# parameter list for template variable replacement
parameters = [
"filtered-data:input_filter:condition_1:playbook_input:message_id"
]
################################################################################
## Custom Code Start
################################################################################
# Write your custom code here...
################################################################################
## Custom Code End
################################################################################
phantom.format(container=container, template=template, parameters=parameters, name="format_message_query")
run_message_query(container=container)
return
@phantom.playbook_block()
def run_message_query(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
phantom.debug("run_message_query() called")
# phantom.debug('Action: {0} {1}'.format(action['name'], ('SUCCEEDED' if success else 'FAILED')))
format_message_query = phantom.get_format_data(name="format_message_query")
parameters = []
if format_message_query is not None:
parameters.append({
"query": format_message_query,
"command": "tstats",
"display": "Addressee,Recipient,Sender,Subject",
"search_mode": "smart",
})
################################################################################
## Custom Code Start
################################################################################
# Write your custom code here...
################################################################################
## Custom Code End
################################################################################
phantom.act("run query", parameters=parameters, name="run_message_query", assets=["splunk"], callback=results_filter)
return
@phantom.playbook_block()
def format_message_report(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
phantom.debug("format_message_report() called")
template = """SOAR searched for occurrences of `{0}` within your environment using Splunk's Email datamodel. The table below shows a summary of the information gathered.\n\n\n| Recipient | Addressed To | Subject | Sender |\n| --- | --- | --- | --- |\n%%\n| {1} | {2} | {3} | {4} |\n%%\n\n"""
# parameter list for template variable replacement
parameters = [
"filtered-data:input_filter:condition_1:playbook_input:message_id",
"filtered-data:results_filter:condition_1:run_message_query:action_result.data.*.Recipient",
"filtered-data:results_filter:condition_1:run_message_query:action_result.data.*.Addressee",
"filtered-data:results_filter:condition_1:run_message_query:action_result.data.*.Subject",
"filtered-data:results_filter:condition_1:run_message_query:action_result.data.*.Sender"
]
################################################################################
## Custom Code Start
################################################################################
# Write your custom code here...
################################################################################
## Custom Code End
################################################################################
phantom.format(container=container, template=template, parameters=parameters, name="format_message_report")
build_message_output(container=container)
return
@phantom.playbook_block()
def build_message_output(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
phantom.debug("build_message_output() called")
################################################################################
# Logic regarding observable construction goes here
################################################################################
filtered_input_0_message_id = phantom.collect2(container=container, datapath=["filtered-data:input_filter:condition_1:playbook_input:message_id"])
filtered_result_0_data_results_filter = phantom.collect2(container=container, datapath=["filtered-data:results_filter:condition_1:run_message_query:action_result.data.*.Recipient","filtered-data:results_filter:condition_1:run_message_query:action_result.data.*.Addressee","filtered-data:results_filter:condition_1:run_message_query:action_result.data.*.Subject","filtered-data:results_filter:condition_1:run_message_query:action_result.data.*.Sender"])
filtered_input_0_message_id_values = [item[0] for item in filtered_input_0_message_id]
filtered_result_0_data___recipient = [item[0] for item in filtered_result_0_data_results_filter]
filtered_result_0_data___addressee = [item[1] for item in filtered_result_0_data_results_filter]
filtered_result_0_data___subject = [item[2] for item in filtered_result_0_data_results_filter]
filtered_result_0_data___sender = [item[3] for item in filtered_result_0_data_results_filter]
build_message_output__observable_array = None
################################################################################
## Custom Code Start
################################################################################
# Variable renames for convenience
message_id = filtered_input_0_message_id_values
recipients = filtered_result_0_data___recipient
addressees = filtered_result_0_data___addressee
subjects = filtered_result_0_data___subject
senders = filtered_result_0_data___sender
build_message_output__observable_array = []
recordList = []
# unwind records
for message_identifier in message_id:
for recipient, addressee, subject, sender in zip(recipients, addressees, subjects, senders):
record = {
"recipient": recipient,
"addressee": addressee,
"subject": subject,
"sender": sender
}
recordList.append(record)
observable = {
"value": message_identifier,
"type": "internet message ID",
"count": len(recordList),
"source": "Splunk",
"message_identifier_activity": recordList
}
build_message_output__observable_array.append(observable)
################################################################################
## Custom Code End
################################################################################
phantom.save_run_data(key="build_message_output:observable_array", value=json.dumps(build_message_output__observable_array))
return
@phantom.playbook_block()
def results_filter(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
phantom.debug("results_filter() called")
# collect filtered artifact ids and results for 'if' condition 1
matched_artifacts_1, matched_results_1 = phantom.condition(
container=container,
conditions=[
["run_message_query:action_result.summary.total_events", ">", 0]
],
name="results_filter:condition_1",
delimiter=",")
# call connected blocks if filtered artifacts or results
if matched_artifacts_1 or matched_results_1:
format_message_report(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")
format_message_report = phantom.get_format_data(name="format_message_report")
build_message_output__observable_array = json.loads(_ if (_ := phantom.get_run_data(key="build_message_output:observable_array")) != "" else "null") # pylint: disable=used-before-assignment
output = {
"observable": build_message_output__observable_array,
"markdown_report": format_message_report,
}
################################################################################
## Custom Code Start
################################################################################
# Write your custom code here...
################################################################################
## Custom Code End
################################################################################
phantom.save_playbook_output_data(output=output)
return
@@ -0,0 +1,23 @@
name: Splunk Message Identifier Activity Analysis
id: 5299b9dc-e8c4-46ba-d942-98dae0fa816d
version: 1
date: '2023-05-12'
author: Lou Stella, Splunk
type: Investigation
description: "Accepts an internet message id, and asks Splunk
to look for records that have a matching internet message id. It then produces a normalized output and summary table."
playbook: Splunk_Message_Identifier_Activity_Analysis
how_to_implement: This input playbook requires the Splunk connector to be configured. You will also need data populating the Email.All_Email datamodel in the out-of-the-box configuration of this playbook.
references: []
app_list:
- Splunk
tags:
platform_tags:
- message_identifier_activity
- internet_message_id
- splunk
playbook_type: Input
vpe_type: Modern
playbook_fields: []
product:
- Splunk SOAR
@@ -0,0 +1,276 @@
{
"blockly": false,
"blockly_xml": "<xml></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": "ddce2d54e4936120ec7acc772b88f91fab41ec55",
"nodes": {
"0": {
"data": {
"advanced": {
"join": []
},
"functionName": "on_start",
"id": "0",
"type": "start"
},
"errors": {},
"id": "0",
"type": "start",
"warnings": {},
"x": 19.999999999999986,
"y": -6.394884621840902e-14
},
"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,
"delimiter": ",",
"delimiter_enabled": true,
"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": {},
"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": "filtered-data:url_input_filter:condition_1:playbook_input:input_url"
},
"requiredParameters": [
{
"data_type": "string",
"field": "url"
}
],
"type": "action"
},
"errors": {},
"id": "6",
"type": "action",
"warnings": {},
"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",
"filtered-data:url_input_filter:condition_1:playbook_input:input_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, 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
},
"8": {
"data": {
"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."
},
"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.10",
"version": "6.0.1.123902"
},
"create_time": "2023-06-21T09:27:47.392005+00:00",
"draft_mode": false,
"labels": [
"*"
],
"tags": [
"url",
"D3-OTF",
"ZScaler",
"denylist"
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

@@ -0,0 +1,173 @@
"""
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",
delimiter=",")
# 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.
################################################################################
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 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": filtered_input_0_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"], 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]
filtered_input_0_input_url_values = [item[0] for item in filtered_input_0_input_url]
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, filtered_input_0_input_url_values):
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",
delimiter=",")
# 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
@@ -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: 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.
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