Branch was auto-updated.

This commit is contained in:
srv-rr-gh-researchbt
2023-03-06 11:15:42 -08:00
committed by GitHub
12 changed files with 2903 additions and 0 deletions
@@ -0,0 +1,735 @@
{
"blockly": false,
"blockly_xml": "<xml></xml>",
"category": "Dynamic Related Ticket Search",
"coa": {
"data": {
"description": "Detects available indicators and routes them to dynamic related ticket search playbooks. The output of the analysis will update any artifacts, tasks, and indicator tags.",
"edges": [
{
"conditions": [
{
"index": 0
}
],
"id": "port_3_to_port_6",
"sourceNode": "3",
"sourcePort": "3_out",
"targetNode": "6",
"targetPort": "6_in"
},
{
"id": "port_0_to_port_7",
"sourceNode": "0",
"sourcePort": "0_out",
"targetNode": "7",
"targetPort": "7_in"
},
{
"conditions": [
{
"index": 0
}
],
"id": "port_7_to_port_3",
"sourceNode": "7",
"sourcePort": "7_out",
"targetNode": "3",
"targetPort": "3_in"
},
{
"conditions": [
{
"index": 1
}
],
"id": "port_7_to_port_5",
"sourceNode": "7",
"sourcePort": "7_out",
"targetNode": "5",
"targetPort": "5_in"
},
{
"id": "port_6_to_port_8",
"sourceNode": "6",
"sourcePort": "6_out",
"targetNode": "8",
"targetPort": "8_in"
},
{
"conditions": [
{
"index": 1
}
],
"id": "port_8_to_port_9",
"sourceNode": "8",
"sourcePort": "8_out",
"targetNode": "9",
"targetPort": "9_in"
},
{
"id": "port_10_to_port_11",
"sourceNode": "10",
"sourcePort": "10_out",
"targetNode": "11",
"targetPort": "11_in"
},
{
"id": "port_11_to_port_1",
"sourceNode": "11",
"sourcePort": "11_out",
"targetNode": "1",
"targetPort": "1_in"
},
{
"conditions": [
{
"index": 0
}
],
"id": "port_8_to_port_14",
"sourceNode": "8",
"sourcePort": "8_out",
"targetNode": "14",
"targetPort": "14_in"
},
{
"conditions": [
{
"index": 0
}
],
"id": "port_14_to_port_10",
"sourceNode": "14",
"sourcePort": "14_out",
"targetNode": "10",
"targetPort": "10_in"
}
],
"hash": "a6c7d1370562fa8c2c59b56f47c3fd52f27f8b37",
"nodes": {
"0": {
"data": {
"advanced": {
"join": []
},
"functionName": "on_start",
"id": "0",
"type": "start"
},
"errors": {},
"id": "0",
"type": "start",
"warnings": {},
"x": 300,
"y": 0
},
"1": {
"data": {
"advanced": {
"join": []
},
"functionName": "on_finish",
"id": "1",
"type": "end"
},
"errors": {},
"id": "1",
"type": "end",
"warnings": {},
"x": 300,
"y": 1358
},
"10": {
"data": {
"advanced": {
"customName": "merge reports",
"customNameId": 0,
"description": "Format a note that merges together normalized data. The data will come from the playbooks launched by the Dispatch Ticketing Playbooks block.",
"join": [],
"note": "Format a note that merges together normalized data. The data will come from the playbooks launched by the Dispatch Ticketing Playbooks block."
},
"customDatapaths": {
"dispatch_filter_1": {
"condition_1:dispatch_ticketing_playbooks:outputs:observable.matched_fields": {
"contains": [],
"isCustomDatapath": true,
"label": "condition_1:dispatch_ticketing_playbooks:outputs:observable.matched_fields",
"value": "filtered-data:dispatch_filter_1:condition_1:dispatch_ticketing_playbooks:playbook_output:observable.matched_fields"
},
"condition_1:dispatch_ticketing_playbooks:outputs:observable.source": {
"contains": [],
"isCustomDatapath": true,
"label": "condition_1:dispatch_ticketing_playbooks:outputs:observable.source",
"value": "filtered-data:dispatch_filter_1:condition_1:dispatch_ticketing_playbooks:playbook_output:observable.source"
},
"condition_1:dispatch_ticketing_playbooks:outputs:observable.source_link": {
"contains": [],
"isCustomDatapath": true,
"label": "condition_1:dispatch_ticketing_playbooks:outputs:observable.source_link",
"value": "filtered-data:dispatch_filter_1:condition_1:dispatch_ticketing_playbooks:playbook_output:observable.source_link"
},
"condition_1:dispatch_ticketing_playbooks:outputs:observable.ticket.assignee": {
"contains": [],
"isCustomDatapath": true,
"label": "condition_1:dispatch_ticketing_playbooks:outputs:observable.ticket.assignee",
"value": "filtered-data:dispatch_filter_1:condition_1:dispatch_ticketing_playbooks:playbook_output:observable.ticket.assignee"
},
"condition_1:dispatch_ticketing_playbooks:outputs:observable.ticket.creator_name": {
"contains": [],
"isCustomDatapath": true,
"label": "condition_1:dispatch_ticketing_playbooks:outputs:observable.ticket.creator_name",
"value": "filtered-data:dispatch_filter_1:condition_1:dispatch_ticketing_playbooks:playbook_output:observable.ticket.creator_name"
},
"condition_1:dispatch_ticketing_playbooks:outputs:observable.ticket.end_time": {
"contains": [],
"isCustomDatapath": true,
"label": "condition_1:dispatch_ticketing_playbooks:outputs:observable.ticket.end_time",
"value": "filtered-data:dispatch_filter_1:condition_1:dispatch_ticketing_playbooks:playbook_output:observable.ticket.end_time"
},
"condition_1:dispatch_ticketing_playbooks:outputs:observable.ticket.message": {
"contains": [],
"isCustomDatapath": true,
"label": "condition_1:dispatch_ticketing_playbooks:outputs:observable.ticket.message",
"value": "filtered-data:dispatch_filter_1:condition_1:dispatch_ticketing_playbooks:playbook_output:observable.ticket.message"
},
"condition_1:dispatch_ticketing_playbooks:outputs:observable.ticket.name": {
"contains": [],
"isCustomDatapath": true,
"label": "condition_1:dispatch_ticketing_playbooks:outputs:observable.ticket.name",
"value": "filtered-data:dispatch_filter_1:condition_1:dispatch_ticketing_playbooks:playbook_output:observable.ticket.name"
},
"condition_1:dispatch_ticketing_playbooks:outputs:observable.ticket.number": {
"contains": [],
"isCustomDatapath": true,
"label": "condition_1:dispatch_ticketing_playbooks:outputs:observable.ticket.number",
"value": "filtered-data:dispatch_filter_1:condition_1:dispatch_ticketing_playbooks:playbook_output:observable.ticket.number"
},
"condition_1:dispatch_ticketing_playbooks:outputs:observable.ticket.start_time": {
"contains": [],
"isCustomDatapath": true,
"label": "condition_1:dispatch_ticketing_playbooks:outputs:observable.ticket.start_time",
"value": "filtered-data:dispatch_filter_1:condition_1:dispatch_ticketing_playbooks:playbook_output:observable.ticket.start_time"
},
"condition_1:dispatch_ticketing_playbooks:outputs:observable.ticket.state": {
"contains": [],
"isCustomDatapath": true,
"label": "condition_1:dispatch_ticketing_playbooks:outputs:observable.ticket.state",
"value": "filtered-data:dispatch_filter_1:condition_1:dispatch_ticketing_playbooks:playbook_output:observable.ticket.state"
}
}
},
"functionId": 1,
"functionName": "merge_reports",
"id": "10",
"parameters": [
"filtered-data:dispatch_filter_1:condition_1:dispatch_ticketing_playbooks:playbook_output:observable.ticket.name",
"filtered-data:dispatch_filter_1:condition_1:dispatch_ticketing_playbooks:playbook_output:observable.ticket.number",
"filtered-data:dispatch_filter_1:condition_1:dispatch_ticketing_playbooks:playbook_output:observable.ticket.message",
"filtered-data:dispatch_filter_1:condition_1:dispatch_ticketing_playbooks:playbook_output:observable.ticket.start_time",
"filtered-data:dispatch_filter_1:condition_1:dispatch_ticketing_playbooks:playbook_output:observable.ticket.end_time",
"filtered-data:dispatch_filter_1:condition_1:dispatch_ticketing_playbooks:playbook_output:observable.ticket.assignee",
"filtered-data:dispatch_filter_1:condition_1:dispatch_ticketing_playbooks:playbook_output:observable.ticket.creator_name",
"filtered-data:dispatch_filter_1:condition_1:dispatch_ticketing_playbooks:playbook_output:observable.ticket.state",
"filtered-data:dispatch_filter_1:condition_1:dispatch_ticketing_playbooks:playbook_output:observable.matched_fields",
"filtered-data:dispatch_filter_1:condition_1:dispatch_ticketing_playbooks:playbook_output:observable.source",
"filtered-data:dispatch_filter_1:condition_1:dispatch_ticketing_playbooks:playbook_output:observable.source_link"
],
"template": "SOAR retrieved tickets from Splunk. The table below shows a summary of the information gathered.\n\n| Name | Number | Message | Start Time | End Time | Assignee | Creator Name | State | Matched Fields | Source | Source Link |\n| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |\n%%\n| {0} | {1} | {2} | {3} | {4} | {5} | {6} | {7} | {8} | {9} | {10} |\n%%\n",
"type": "format"
},
"errors": {},
"id": "10",
"type": "format",
"warnings": {},
"x": 280,
"y": 1032
},
"11": {
"data": {
"advanced": {
"customName": "ticketing update",
"customNameId": 0,
"join": []
},
"customFunction": {
"draftMode": false,
"name": "workbook_task_update",
"repoName": "community"
},
"functionId": 4,
"functionName": "ticketing_update",
"id": "11",
"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": "merge_reports:formatted_data",
"note_title": "Dynamic Related Ticket Search Report",
"owner": null,
"status": "complete",
"task_name": "playbook"
}
}
},
"errors": {},
"id": "11",
"type": "utility",
"warnings": {},
"x": 280,
"y": 1210
},
"14": {
"data": {
"advanced": {
"customName": "dispatch filter",
"customNameId": 1,
"description": "Create a dataset with the output of the dispatch playbooks that is not None",
"join": [],
"note": "Create a dataset with the output of the dispatch playbooks that is not None"
},
"conditions": [
{
"comparisons": [
{
"conditionIndex": 0,
"op": "!=",
"param": "dispatch_ticketing_playbooks:playbook_output:observable",
"value": "None"
}
],
"conditionIndex": 0,
"customName": "outputs exist",
"logic": "and"
}
],
"functionId": 3,
"functionName": "dispatch_filter_1",
"id": "14",
"type": "filter"
},
"errors": {},
"id": "14",
"type": "filter",
"warnings": {},
"x": 340,
"y": 852
},
"3": {
"data": {
"advanced": {
"customName": "filter new artifacts",
"customNameId": 0,
"description": "Only dispatch rplaybooks against new artifacts.",
"join": [],
"note": "Only dispatch rplaybooks against new artifacts.",
"scope": "default"
},
"conditions": [
{
"comparisons": [
{
"conditionIndex": 0,
"op": "!=",
"param": "artifact:*.id",
"value": "None"
}
],
"conditionIndex": 0,
"customName": "artifacts",
"logic": "and"
}
],
"functionId": 2,
"functionName": "filter_new_artifacts",
"id": "3",
"type": "filter"
},
"errors": {},
"id": "3",
"type": "filter",
"warnings": {},
"x": 200,
"y": 328
},
"5": {
"data": {
"advanced": {
"join": []
},
"functionId": 2,
"functionName": "add_comment_2",
"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 artifact data found in event."
}
}
},
"errors": {},
"id": "5",
"type": "utility",
"warnings": {},
"x": 420,
"y": 344
},
"6": {
"data": {
"advanced": {
"customName": "Dispatch Ticketing Playbooks",
"customNameId": 0,
"join": []
},
"functionId": 1,
"functionName": "dispatch_ticketing_playbooks",
"id": "6",
"inputs": {
"artifact_ids_include": {
"datapaths": [
"filtered-data:filter_new_artifacts:condition_1:artifact:*.id"
],
"deduplicate": false
},
"indicator_tags_exclude": {
"datapaths": [],
"deduplicate": false
},
"indicator_tags_include": {
"datapaths": [],
"deduplicate": false
},
"playbook_repo": {
"datapaths": [],
"deduplicate": false
},
"playbook_tags": {
"datapaths": [
"ticket"
],
"deduplicate": false
}
},
"playbookName": "dispatch_input_playbooks",
"playbookRepo": 3,
"playbookRepoName": "community",
"playbookType": "data",
"synchronous": true,
"type": "playbook"
},
"errors": {},
"id": "6",
"type": "playbook",
"warnings": {},
"x": 140,
"y": 508
},
"7": {
"data": {
"advanced": {
"customName": "artifact decision",
"customNameId": 0,
"description": "Determine if artifacts exist to run through the playbook.",
"join": [],
"note": "Determine if artifacts exist to run through the playbook."
},
"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_decision",
"id": "7",
"type": "decision"
},
"errors": {},
"id": "7",
"type": "decision",
"warnings": {},
"x": 360,
"y": 148
},
"8": {
"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_ticketing_playbooks:playbook_output:observable",
"value": "None"
}
],
"conditionIndex": 0,
"customName": "output exists",
"display": "If",
"logic": "and",
"type": "if"
},
{
"comparisons": [
{
"conditionIndex": 1,
"op": "==",
"param": "",
"value": ""
}
],
"conditionIndex": 1,
"customName": "outputs do not exist",
"display": "Else",
"logic": "and",
"type": "else"
}
],
"functionId": 2,
"functionName": "outputs_decision",
"id": "8",
"type": "decision"
},
"errors": {},
"id": "8",
"type": "decision",
"warnings": {},
"x": 220,
"y": 672
},
"9": {
"data": {
"advanced": {
"join": []
},
"functionId": 3,
"functionName": "add_comment_3",
"id": "9",
"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": "9",
"type": "utility",
"warnings": {},
"x": 0,
"y": 868
}
},
"notes": "Outputs:\ntags indicators\nadd relevant tickets"
},
"input_spec": null,
"output_spec": null,
"playbook_type": "automation",
"python_version": "3",
"schema": "5.0.9",
"version": "6.0.0.114895"
},
"create_time": "2023-02-27T20:44:02.427087+00:00",
"draft_mode": false,
"labels": [
"*"
],
"tags": []
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

+278
View File
@@ -0,0 +1,278 @@
"""
Detects available indicators and routes them to dynamic related ticket search playbooks. The output of the analysis will update any artifacts, tasks, and indicator tags.
"""
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_decision' block
artifact_decision(container=container)
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 rplaybooks 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")
# call connected blocks if filtered artifacts or results
if matched_artifacts_1 or matched_results_1:
dispatch_ticketing_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 add_comment_2(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
phantom.debug("add_comment_2() called")
################################################################################
## Custom Code Start
################################################################################
# Write your custom code here...
################################################################################
## Custom Code End
################################################################################
phantom.comment(container=container, comment="No new artifact data found in event.")
return
@phantom.playbook_block()
def dispatch_ticketing_playbooks(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
phantom.debug("dispatch_ticketing_playbooks() called")
filtered_artifact_0_data_filter_new_artifacts = phantom.collect2(container=container, datapath=["filtered-data:filter_new_artifacts:condition_1:artifact:*.id"])
filtered_artifact_0__id = [item[0] for item in filtered_artifact_0_data_filter_new_artifacts]
inputs = {
"playbook_tags": ["ticket"],
"playbook_repo": [],
"indicator_tags_include": [],
"indicator_tags_exclude": [],
"artifact_ids_include": filtered_artifact_0__id,
}
################################################################################
## 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_ticketing_playbooks", callback=outputs_decision, inputs=inputs)
return
@phantom.playbook_block()
def artifact_decision(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
phantom.debug("artifact_decision() called")
################################################################################
# Determine if artifacts exist to run through the playbook.
################################################################################
# check for 'if' condition 1
found_match_1 = phantom.decision(
container=container,
conditions=[
["artifact:*.id", "!=", 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
add_comment_2(action=action, success=success, container=container, results=results, handle=handle)
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_ticketing_playbooks:playbook_output:observable", "!=", None]
])
# call connected blocks if condition 1 matched
if found_match_1:
dispatch_filter_1(action=action, success=success, container=container, results=results, handle=handle)
return
# check for 'else' condition 2
add_comment_3(action=action, success=success, container=container, results=results, handle=handle)
return
@phantom.playbook_block()
def add_comment_3(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
phantom.debug("add_comment_3() called")
################################################################################
## 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 merge_reports(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
phantom.debug("merge_reports() called")
################################################################################
# Format a note that merges together normalized data. The data will come from
# the playbooks launched by the Dispatch Ticketing Playbooks block.
################################################################################
template = """SOAR retrieved tickets from Splunk. The table below shows a summary of the information gathered.\n\n| Name | Number | Message | Start Time | End Time | Assignee | Creator Name | State | Matched Fields | Source | Source Link |\n| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |\n%%\n| {0} | {1} | {2} | {3} | {4} | {5} | {6} | {7} | {8} | {9} | {10} |\n%%\n"""
# parameter list for template variable replacement
parameters = [
"filtered-data:dispatch_filter_1:condition_1:dispatch_ticketing_playbooks:playbook_output:observable.ticket.name",
"filtered-data:dispatch_filter_1:condition_1:dispatch_ticketing_playbooks:playbook_output:observable.ticket.number",
"filtered-data:dispatch_filter_1:condition_1:dispatch_ticketing_playbooks:playbook_output:observable.ticket.message",
"filtered-data:dispatch_filter_1:condition_1:dispatch_ticketing_playbooks:playbook_output:observable.ticket.start_time",
"filtered-data:dispatch_filter_1:condition_1:dispatch_ticketing_playbooks:playbook_output:observable.ticket.end_time",
"filtered-data:dispatch_filter_1:condition_1:dispatch_ticketing_playbooks:playbook_output:observable.ticket.assignee",
"filtered-data:dispatch_filter_1:condition_1:dispatch_ticketing_playbooks:playbook_output:observable.ticket.creator_name",
"filtered-data:dispatch_filter_1:condition_1:dispatch_ticketing_playbooks:playbook_output:observable.ticket.state",
"filtered-data:dispatch_filter_1:condition_1:dispatch_ticketing_playbooks:playbook_output:observable.matched_fields",
"filtered-data:dispatch_filter_1:condition_1:dispatch_ticketing_playbooks:playbook_output:observable.source",
"filtered-data:dispatch_filter_1:condition_1:dispatch_ticketing_playbooks:playbook_output:observable.source_link"
]
################################################################################
## Custom Code Start
################################################################################
# Write your custom code here...
################################################################################
## Custom Code End
################################################################################
phantom.format(container=container, template=template, parameters=parameters, name="merge_reports")
ticketing_update(container=container)
return
@phantom.playbook_block()
def ticketing_update(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
phantom.debug("ticketing_update() called")
id_value = container.get("id", None)
merge_reports = phantom.get_format_data(name="merge_reports")
parameters = []
parameters.append({
"owner": None,
"status": "complete",
"container": id_value,
"task_name": "playbook",
"note_title": "Dynamic Related Ticket Search Report",
"note_content": merge_reports,
})
################################################################################
## Custom Code Start
################################################################################
# Write your custom code here...
################################################################################
## Custom Code End
################################################################################
phantom.custom_function(custom_function="community/workbook_task_update", parameters=parameters, name="ticketing_update")
return
@phantom.playbook_block()
def dispatch_filter_1(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
phantom.debug("dispatch_filter_1() called")
################################################################################
# Create a dataset with the output of the dispatch playbooks that 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_ticketing_playbooks:playbook_output:observable", "!=", None]
],
name="dispatch_filter_1:condition_1")
# call connected blocks if filtered artifacts or results
if matched_artifacts_1 or matched_results_1:
merge_reports(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
@@ -0,0 +1,19 @@
name: Dynamic Related Tickets Search
id: fc0edc96-ab1f-48b9-9b4d-63da61bafe74
version: 1
date: '2023-02-28'
author: Patrick Bareiss, Splunk
type: Investigation
description: "Detects available indicators and routes them to dynamic related ticket search playbooks. The output of the analysis will update any artifacts, tasks, and indicator tags."
playbook: Dynamic_Related_Tickets_Search
how_to_implement: This playbook looks for artifacts and then dispatches the community Related Tickets 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:IdentifierReputationAnalysis/
app_list: []
tags:
platform_tags: []
playbook_type: Automation
vpe_type: Modern
playbook_fields: []
product:
- Splunk SOAR
@@ -0,0 +1,576 @@
{
"blockly": false,
"blockly_xml": "<xml></xml>",
"category": "Dynamic Related Ticket Search",
"coa": {
"data": {
"description": "Accepts a user or device and identifies if related tickets exists in a timeframe of last 30 days. Generates a global report and list of observables.",
"edges": [
{
"id": "port_6_to_port_8",
"sourceNode": "6",
"sourcePort": "6_out",
"targetNode": "8",
"targetPort": "8_in"
},
{
"id": "port_0_to_port_6",
"sourceNode": "0",
"sourcePort": "0_out",
"targetNode": "6",
"targetPort": "6_in"
},
{
"conditions": [
{
"index": 0
}
],
"id": "port_10_to_port_9",
"sourceNode": "10",
"sourcePort": "10_out",
"targetNode": "9",
"targetPort": "9_in"
},
{
"id": "port_8_to_port_17",
"sourceNode": "8",
"sourcePort": "8_out",
"targetNode": "17",
"targetPort": "17_in"
},
{
"id": "port_17_to_port_10",
"sourceNode": "17",
"sourcePort": "17_out",
"targetNode": "10",
"targetPort": "10_in"
},
{
"id": "port_9_to_port_19",
"sourceNode": "9",
"sourcePort": "9_out",
"targetNode": "19",
"targetPort": "19_in"
},
{
"id": "port_19_to_port_14",
"sourceNode": "19",
"sourcePort": "19_out",
"targetNode": "14",
"targetPort": "14_in"
},
{
"id": "port_14_to_port_16",
"sourceNode": "14",
"sourcePort": "14_out",
"targetNode": "16",
"targetPort": "16_in"
},
{
"id": "port_16_to_port_25",
"sourceNode": "16",
"sourcePort": "16_out",
"targetNode": "25",
"targetPort": "25_in"
},
{
"id": "port_25_to_port_1",
"sourceNode": "25",
"sourcePort": "25_out",
"targetNode": "1",
"targetPort": "1_in"
}
],
"hash": "e7ead2bdc6a802950dd37c47b3f4af40d61c676c",
"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": 1708
},
"10": {
"data": {
"advanced": {
"customName": "input filter",
"customNameId": 0,
"description": "Creates a dataset without None values.",
"join": [],
"note": "Creates a dataset without None values."
},
"conditions": [
{
"comparisons": [
{
"conditionIndex": 0,
"op": "!=",
"param": "playbook_input:search_term",
"value": "None"
}
],
"conditionIndex": 0,
"customName": "search term exists",
"logic": "and"
}
],
"functionId": 1,
"functionName": "input_filter",
"id": "10",
"type": "filter"
},
"errors": {},
"id": "10",
"type": "filter",
"warnings": {
"config": [
"Reconfigure invalid datapath."
]
},
"x": 60,
"y": 622
},
"14": {
"data": {
"advanced": {
"customName": "process results",
"customNameId": 0,
"description": "Iterates through the results of the run ticket query to link playbook input search term to their associated tickets.",
"join": [],
"note": "Iterates through the results of the run ticket query to link playbook input search term to their associated tickets."
},
"functionId": 3,
"functionName": "process_results",
"id": "14",
"inputParameters": [
"filtered-data:input_filter:condition_1:playbook_input:search_term",
"run_ticket_query:action_result.data",
"run_ticket_query:action_result.parameter.query_table"
],
"outputVariables": [
"output"
],
"type": "code"
},
"errors": {},
"id": "14",
"type": "code",
"userCode": " process_results__output = {}\n for search_term in filtered_input_0_search_term_values:\n process_results__output[search_term] = []\n for result, query_table in zip(run_ticket_query_result_item_0, run_ticket_query_parameter_query_table):\n if not isinstance(result, list):\n list_result = [result]\n else:\n list_result = result\n for result_item in list_result:\n result_item_values = [item.lower() for item in result_item.values() if isinstance(item, str)]\n match = False\n for string_value in result_item_values:\n if search_term.lower() in string_value:\n match = True\n break\n if match:\n process_results__output[search_term].append({**result_item, **{\"ticket_type\": query_table}})\n\n",
"warnings": {},
"x": 0,
"y": 1158
},
"16": {
"customCode": null,
"data": {
"advanced": {
"customName": "build output",
"customNameId": 0,
"description": "Extract relevant data and add them to an observable array.",
"join": [],
"note": "Extract relevant data and add them to an observable array."
},
"functionId": 5,
"functionName": "build_output",
"id": "16",
"inputParameters": [
"process_results:custom_function:output"
],
"outputVariables": [
"observable_array",
"name",
"number",
"message",
"start_time",
"end_time",
"assignee",
"creator_name",
"state",
"matched_fields",
"source_link",
"source"
],
"type": "code"
},
"errors": {},
"id": "16",
"type": "code",
"userCode": " import re \n \n build_output__observable_array = []\n build_output__name = []\n build_output__number = []\n build_output__message = []\n build_output__start_time = []\n build_output__end_time = []\n build_output__assignee = []\n build_output__creator_name = []\n build_output__state = []\n build_output__matched_fields = []\n build_output__source_link = []\n build_output__source = []\n \n def generate_ticket_link(sample_url, ticket_type, sys_id):\n extract_host = re.search(r\"https*:\\/\\/[^\\/]+\", sample_url).group(0)\n extract_host += f\"/nav_to.do?uri={ticket_type}.do?sys_id={sys_id}\"\n return extract_host\n \n for key in process_results__output.keys():\n \n for value in process_results__output[key]:\n assigned_to = None\n caller_id = None\n matched_fields = []\n if value.get(\"assigned_to\"):\n assigned_to = value[\"assigned_to\"][\"display_value\"]\n if value.get(\"caller_id\"):\n caller_id = value[\"caller_id\"][\"display_value\"]\n \n for k, v in value.items():\n # generate matched fields where the searched entity appears\n if isinstance(v, str) and key.lower() in v.lower():\n matched_fields.append(k)\n # search for any link sample:\n if isinstance(v, dict):\n sample_link = v.get('link')\n \n source_link = generate_ticket_link(sample_link, value['ticket_type'], value['sys_id'])\n observable_object = {\n \"value\": key,\n \"ticket\": {\n \"name\": value[\"short_description\"],\n \"id\": value[\"sys_id\"],\n \"number\": value[\"number\"],\n \"message\": json.dumps(value[\"description\"]),\n \"start_time\": value[\"sys_created_on\"],\n \"end_time\": value[\"closed_at\"],\n \"assigned_to\": assigned_to,\n \"creator_name\": caller_id,\n \"state\": value[\"state\"],\n \"notes\": [value[\"work_notes\"]],\n \"comments\": [value[\"comments\"]]\n },\n \"matched_fields\": matched_fields,\n \"source\": \"ServiceNow\",\n \"source_link\": source_link\n }\n build_output__observable_array.append(observable_object)\n build_output__name.append(value[\"short_description\"])\n build_output__number.append(value[\"number\"])\n build_output__message.append(json.dumps(value[\"description\"])) # eliminate new line issues\n build_output__start_time.append(value[\"sys_created_on\"])\n build_output__end_time.append(value[\"closed_at\"])\n build_output__assignee.append(assigned_to)\n build_output__creator_name.append(caller_id)\n build_output__state.append(value[\"state\"])\n build_output__matched_fields.append(matched_fields)\n build_output__source.append(\"ServiceNow\")\n build_output__source_link.append(source_link)\n #phantom.debug(observable_object)\n\n",
"warnings": {},
"x": 0,
"y": 1352
},
"17": {
"data": {
"advanced": {
"customName": "calculate earliest time",
"customNameId": 0,
"join": []
},
"customFunction": {
"draftMode": false,
"name": "datetime_modify",
"repoName": "community"
},
"functionId": 2,
"functionName": "calculate_earliest_time",
"id": "17",
"selectMore": false,
"type": "utility",
"utilities": {
"datetime_modify": {
"description": "Change a timestamp by adding or subtracting minutes, hours, or days.",
"fields": [
{
"dataTypes": [
""
],
"description": "The datetime to modify, which should be provided in a string format determined by input_format_string",
"inputType": "item",
"label": "input_datetime",
"name": "input_datetime",
"placeholder": "2020-06-27T14:53:08.219016Z",
"renderType": "datapath",
"required": false
},
{
"dataTypes": [],
"description": "The format string to use for the input according to the Python's datetime.strptime() formatting rules. If none is provided the default will be '%Y-%m-%dT%H:%M:%S.%fZ'. In addition to strptime() formats, the special format \"epoch\" can be used to accept unix epoch timestamps.",
"inputType": "item",
"label": "input_format_string",
"name": "input_format_string",
"placeholder": "%Y-%m-%dT%H:%M:%S.%fZ",
"renderType": "datapath",
"required": false
},
{
"dataTypes": [
""
],
"description": "Choose a unit to modify the date by, which must be either seconds, minutes, hours, or days. If none is provided the default will be 'minutes'",
"inputType": "item",
"label": "modification_unit",
"name": "modification_unit",
"placeholder": "minutes",
"renderType": "datapath",
"required": false
},
{
"dataTypes": [],
"description": "The number of seconds, minutes, hours, or days to add or subtract. Use a negative number such as -1.5 to subtract time. Defaults to zero.",
"inputType": "item",
"label": "amount_to_modify",
"name": "amount_to_modify",
"placeholder": "0",
"renderType": "datapath",
"required": false
},
{
"dataTypes": [],
"description": "The format string to use for the output according to the Python's datetime.strftime() formatting rules. If none is provided the default will be '%Y-%m-%dT%H:%M:%S.%fZ'.",
"inputType": "item",
"label": "output_format_string",
"name": "output_format_string",
"placeholder": "%Y-%m-%dT%H:%M:%S.%fZ",
"renderType": "datapath",
"required": false
}
],
"label": "datetime_modify",
"name": "datetime_modify"
}
},
"utilityType": "custom_function",
"values": {
"datetime_modify": {
"amount_to_modify": "-30",
"input_datetime": "container:create_time",
"input_format_string": "%Y-%m-%d %H:%M:%S.%f+00",
"modification_unit": "days",
"output_format_string": "'%Y-%m-%d','%H:%M:%S'"
}
}
},
"errors": {},
"id": "17",
"type": "utility",
"warnings": {},
"x": 0,
"y": 474
},
"19": {
"data": {
"action": "run query",
"actionType": "investigate",
"advanced": {
"customName": "run ticket query",
"customNameId": 0,
"description": "Perform a text search match within ServiceNow.",
"join": [],
"note": "Perform a text search match within ServiceNow."
},
"connector": "ServiceNow",
"connectorConfigs": [
"servicenow"
],
"connectorId": "a590c3bc-ca41-4a0e-b063-8066ca868794",
"connectorVersion": "v1",
"functionId": 3,
"functionName": "run_ticket_query",
"id": "19",
"parameters": {
"max_results": "100",
"query": {
"functionId": 3,
"parameters": [
"space_delimiter_input:formatted_data",
"calculate_earliest_time:custom_function_result.data.datetime_string"
],
"template": "sysparm_query=active=true^IR_OR_QUERY={0}^opened_at>javascript:gs.dateGenerate({1})&sysparm_display_value=true \n\n"
},
"query_table": "convert_table_list:custom_function_result.data.output"
},
"requiredParameters": [
{
"data_type": "string",
"field": "query"
},
{
"data_type": "numeric",
"default": 100,
"field": "max_results"
},
{
"data_type": "string",
"field": "query_table"
}
],
"type": "action"
},
"errors": {},
"id": "19",
"type": "action",
"warnings": {},
"x": 0,
"y": 980
},
"25": {
"data": {
"advanced": {
"customName": "format report",
"customNameId": 0,
"description": "Format a summary table with the information gathered from the playbook.",
"join": [],
"note": "Format a summary table with the information gathered from the playbook."
},
"functionId": 3,
"functionName": "format_report",
"id": "25",
"parameters": [
"build_output:custom_function:name",
"build_output:custom_function:number",
"build_output:custom_function:message",
"build_output:custom_function:start_time",
"build_output:custom_function:end_time",
"build_output:custom_function:assignee",
"build_output:custom_function:creator_name",
"build_output:custom_function:state",
"build_output:custom_function:matched_fields",
"build_output:custom_function:source",
"build_output:custom_function:source_link"
],
"template": "SOAR retrieved tickets from Service Now. The table below shows a summary of the information gathered.\n\n| Name | Number | Message | Start Time | End Time | Assignee | Creator Name | State | Matched Fields | Source | Source Link |\n| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |\n%%\n| {0} | {1} | {2} | {3} | {4} | {5} | {6} | {7} | {8} | {9} | {10} |\n%%\n",
"type": "format"
},
"errors": {},
"id": "25",
"type": "format",
"warnings": {},
"x": 0,
"y": 1530
},
"6": {
"data": {
"advanced": {
"customName": "default table list",
"customNameId": 0,
"description": "Adjust the table list variable to change which tables should be searched.",
"join": [],
"note": "Adjust the table list variable to change which tables should be searched."
},
"functionId": 2,
"functionName": "default_table_list",
"id": "6",
"inputParameters": [],
"outputVariables": [
"output"
],
"type": "code"
},
"errors": {},
"id": "6",
"type": "code",
"userCode": " \n # Default tables list to find related tickets. Adjust as needed.\n default_table_list = [\n 'incident', \n 'change_request', \n 'change_task', \n 'problem',\n 'sc_request', \n 'sc_task', \n 'sc_req_item',\n ]\n default_table_list__output = default_table_list\n \n",
"warnings": {},
"x": 0,
"y": 148
},
"8": {
"data": {
"advanced": {
"customName": "convert table list",
"customNameId": 0,
"join": [],
"refreshNotableData": false
},
"customFunction": {
"draftMode": false,
"name": "list_demux",
"repoName": "community"
},
"functionId": 1,
"functionName": "convert_table_list",
"id": "8",
"selectMore": false,
"type": "utility",
"utilities": {
"list_demux": {
"description": "Accepts a single list and converts it into multiple custom function output results. All output will be placed in the \"output\" datapath. Sub-items and sub-item variable names are dependent on the input.",
"fields": [
{
"dataTypes": [
"*"
],
"description": "A list of objects. Nested lists are not unpacked.",
"inputType": "item",
"label": "input_list",
"name": "input_list",
"placeholder": "[\"list_item_1\", \"list_item_2\", \"list_item_3\"]",
"renderType": "datapath",
"required": false
}
],
"label": "list_demux",
"name": "list_demux"
}
},
"utilityType": "custom_function",
"values": {
"list_demux": {
"input_list": "default_table_list:custom_function:output"
}
}
},
"errors": {},
"id": "8",
"type": "utility",
"userCode": "\n",
"warnings": {},
"x": 0,
"y": 326
},
"9": {
"data": {
"advanced": {
"customName": "space delimiter input",
"customNameId": 0,
"description": "Convert playbook input into space delimiter string for ServiceNow query.",
"drop_none": true,
"join": [],
"note": "Convert playbook input into space delimiter string for ServiceNow query.",
"separator": " "
},
"functionId": 1,
"functionName": "space_delimiter_input",
"id": "9",
"parameters": [
"filtered-data:input_filter:condition_1:playbook_input:search_term"
],
"template": "{0}\n",
"type": "format"
},
"errors": {},
"id": "9",
"type": "format",
"warnings": {},
"x": 0,
"y": 802
}
},
"notes": "Inputs: user, device\nInteractions: ServiceNow\nActions: run query\nOutputs: report, observables"
},
"input_spec": [
{
"contains": [
"user name",
"host name"
],
"description": "Find tickets in ServiceNow that have mentioned this search term..",
"name": "search_term"
}
],
"output_spec": [
{
"contains": [],
"datapaths": [
"build_output:custom_function:observable_array"
],
"deduplicate": false,
"description": "An array of observable dictionaries with value, type, score, score_id, and categories.",
"metadata": {},
"name": "observable"
},
{
"contains": [],
"datapaths": [
"format_report:formatted_data"
],
"deduplicate": false,
"description": "An array of reports. One report per reputation category.",
"metadata": {},
"name": "markdown_report"
}
],
"playbook_type": "data",
"python_version": "3",
"schema": "5.0.9",
"version": "6.0.0.114895"
},
"create_time": "2023-03-03T21:21:48.729159+00:00",
"draft_mode": false,
"labels": [
"*"
],
"tags": [
"user",
"device",
"ServiceNow",
"ticket"
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

@@ -0,0 +1,452 @@
"""
Accepts a user or device and identifies if related tickets exists in a timeframe of last 30 days. Generates a global report and list of observables.
"""
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 'default_table_list' block
default_table_list(container=container)
return
@phantom.playbook_block()
def default_table_list(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
phantom.debug("default_table_list() called")
################################################################################
# Adjust the table list variable to change which tables should be searched.
################################################################################
default_table_list__output = None
################################################################################
## Custom Code Start
################################################################################
# Default tables list to find related tickets. Adjust as needed.
default_table_list = [
'incident',
'change_request',
'change_task',
'problem',
'sc_request',
'sc_task',
'sc_req_item',
]
default_table_list__output = default_table_list
################################################################################
## Custom Code End
################################################################################
phantom.save_run_data(key="default_table_list:output", value=json.dumps(default_table_list__output))
convert_table_list(container=container)
return
@phantom.playbook_block()
def convert_table_list(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
phantom.debug("convert_table_list() called")
default_table_list__output = json.loads(_ if (_ := phantom.get_run_data(key="default_table_list:output")) != "" else "null") # pylint: disable=used-before-assignment
parameters = []
parameters.append({
"input_list": default_table_list__output,
})
################################################################################
## Custom Code Start
################################################################################
################################################################################
## Custom Code End
################################################################################
phantom.custom_function(custom_function="community/list_demux", parameters=parameters, name="convert_table_list", callback=calculate_earliest_time)
return
@phantom.playbook_block()
def space_delimiter_input(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
phantom.debug("space_delimiter_input() called")
################################################################################
# Convert playbook input into space delimiter string for ServiceNow query.
################################################################################
template = """{0}\n"""
# parameter list for template variable replacement
parameters = [
"filtered-data:input_filter:condition_1:playbook_input:search_term"
]
################################################################################
## Custom Code Start
################################################################################
# Write your custom code here...
################################################################################
## Custom Code End
################################################################################
phantom.format(container=container, template=template, parameters=parameters, name="space_delimiter_input", separator=" ", drop_none=True)
run_ticket_query(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")
################################################################################
# Creates a dataset without None values.
################################################################################
# collect filtered artifact ids and results for 'if' condition 1
matched_artifacts_1, matched_results_1 = phantom.condition(
container=container,
conditions=[
["playbook_input:search_term", "!=", None]
],
name="input_filter:condition_1")
# call connected blocks if filtered artifacts or results
if matched_artifacts_1 or matched_results_1:
space_delimiter_input(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 process_results(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
phantom.debug("process_results() called")
################################################################################
# Iterates through the results of the run ticket query to link playbook input
# search term to their associated tickets.
################################################################################
filtered_input_0_search_term = phantom.collect2(container=container, datapath=["filtered-data:input_filter:condition_1:playbook_input:search_term"])
run_ticket_query_result_data = phantom.collect2(container=container, datapath=["run_ticket_query:action_result.data","run_ticket_query:action_result.parameter.query_table"], action_results=results)
filtered_input_0_search_term_values = [item[0] for item in filtered_input_0_search_term]
run_ticket_query_result_item_0 = [item[0] for item in run_ticket_query_result_data]
run_ticket_query_parameter_query_table = [item[1] for item in run_ticket_query_result_data]
process_results__output = None
################################################################################
## Custom Code Start
################################################################################
process_results__output = {}
for search_term in filtered_input_0_search_term_values:
process_results__output[search_term] = []
for result, query_table in zip(run_ticket_query_result_item_0, run_ticket_query_parameter_query_table):
if not isinstance(result, list):
list_result = [result]
else:
list_result = result
for result_item in list_result:
result_item_values = [item.lower() for item in result_item.values() if isinstance(item, str)]
match = False
for string_value in result_item_values:
if search_term.lower() in string_value:
match = True
break
if match:
process_results__output[search_term].append({**result_item, **{"ticket_type": query_table}})
################################################################################
## Custom Code End
################################################################################
phantom.save_run_data(key="process_results:output", value=json.dumps(process_results__output))
build_output(container=container)
return
@phantom.playbook_block()
def build_output(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
phantom.debug("build_output() called")
################################################################################
# Extract relevant data and add them to an observable array.
################################################################################
process_results__output = json.loads(_ if (_ := phantom.get_run_data(key="process_results:output")) != "" else "null") # pylint: disable=used-before-assignment
build_output__observable_array = None
build_output__name = None
build_output__number = None
build_output__message = None
build_output__start_time = None
build_output__end_time = None
build_output__assignee = None
build_output__creator_name = None
build_output__state = None
build_output__matched_fields = None
build_output__source_link = None
build_output__source = None
################################################################################
## Custom Code Start
################################################################################
import re
build_output__observable_array = []
build_output__name = []
build_output__number = []
build_output__message = []
build_output__start_time = []
build_output__end_time = []
build_output__assignee = []
build_output__creator_name = []
build_output__state = []
build_output__matched_fields = []
build_output__source_link = []
build_output__source = []
def generate_ticket_link(sample_url, ticket_type, sys_id):
extract_host = re.search(r"https*:\/\/[^\/]+", sample_url).group(0)
extract_host += f"/nav_to.do?uri={ticket_type}.do?sys_id={sys_id}"
return extract_host
for key in process_results__output.keys():
for value in process_results__output[key]:
assigned_to = None
caller_id = None
matched_fields = []
if value.get("assigned_to"):
assigned_to = value["assigned_to"]["display_value"]
if value.get("caller_id"):
caller_id = value["caller_id"]["display_value"]
for k, v in value.items():
# generate matched fields where the searched entity appears
if isinstance(v, str) and key.lower() in v.lower():
matched_fields.append(k)
# search for any link sample:
if isinstance(v, dict):
sample_link = v.get('link')
source_link = generate_ticket_link(sample_link, value['ticket_type'], value['sys_id'])
observable_object = {
"value": key,
"ticket": {
"name": value["short_description"],
"id": value["sys_id"],
"number": value["number"],
"message": json.dumps(value["description"]),
"start_time": value["sys_created_on"],
"end_time": value["closed_at"],
"assigned_to": assigned_to,
"creator_name": caller_id,
"state": value["state"],
"notes": [value["work_notes"]],
"comments": [value["comments"]]
},
"matched_fields": matched_fields,
"source": "ServiceNow",
"source_link": source_link
}
build_output__observable_array.append(observable_object)
build_output__name.append(value["short_description"])
build_output__number.append(value["number"])
build_output__message.append(json.dumps(value["description"])) # eliminate new line issues
build_output__start_time.append(value["sys_created_on"])
build_output__end_time.append(value["closed_at"])
build_output__assignee.append(assigned_to)
build_output__creator_name.append(caller_id)
build_output__state.append(value["state"])
build_output__matched_fields.append(matched_fields)
build_output__source.append("ServiceNow")
build_output__source_link.append(source_link)
#phantom.debug(observable_object)
################################################################################
## Custom Code End
################################################################################
phantom.save_run_data(key="build_output:observable_array", value=json.dumps(build_output__observable_array))
phantom.save_run_data(key="build_output:name", value=json.dumps(build_output__name))
phantom.save_run_data(key="build_output:number", value=json.dumps(build_output__number))
phantom.save_run_data(key="build_output:message", value=json.dumps(build_output__message))
phantom.save_run_data(key="build_output:start_time", value=json.dumps(build_output__start_time))
phantom.save_run_data(key="build_output:end_time", value=json.dumps(build_output__end_time))
phantom.save_run_data(key="build_output:assignee", value=json.dumps(build_output__assignee))
phantom.save_run_data(key="build_output:creator_name", value=json.dumps(build_output__creator_name))
phantom.save_run_data(key="build_output:state", value=json.dumps(build_output__state))
phantom.save_run_data(key="build_output:matched_fields", value=json.dumps(build_output__matched_fields))
phantom.save_run_data(key="build_output:source_link", value=json.dumps(build_output__source_link))
phantom.save_run_data(key="build_output:source", value=json.dumps(build_output__source))
format_report(container=container)
return
@phantom.playbook_block()
def calculate_earliest_time(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
phantom.debug("calculate_earliest_time() called")
create_time_value = container.get("create_time", None)
parameters = []
parameters.append({
"input_datetime": create_time_value,
"amount_to_modify": -30,
"modification_unit": "days",
"input_format_string": "%Y-%m-%d %H:%M:%S.%f+00",
"output_format_string": "'%Y-%m-%d','%H:%M:%S'",
})
################################################################################
## Custom Code Start
################################################################################
# Write your custom code here...
################################################################################
## Custom Code End
################################################################################
phantom.custom_function(custom_function="community/datetime_modify", parameters=parameters, name="calculate_earliest_time", callback=input_filter)
return
@phantom.playbook_block()
def run_ticket_query(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
phantom.debug("run_ticket_query() called")
# phantom.debug('Action: {0} {1}'.format(action['name'], ('SUCCEEDED' if success else 'FAILED')))
query_formatted_string = phantom.format(
container=container,
template="""sysparm_query=active=true^IR_OR_QUERY={0}^opened_at>javascript:gs.dateGenerate({1})&sysparm_display_value=true \n\n""",
parameters=[
"space_delimiter_input:formatted_data",
"calculate_earliest_time:custom_function_result.data.datetime_string"
])
################################################################################
# Perform a text search match within ServiceNow.
################################################################################
calculate_earliest_time__result = phantom.collect2(container=container, datapath=["calculate_earliest_time:custom_function_result.data.datetime_string"])
convert_table_list__result = phantom.collect2(container=container, datapath=["convert_table_list:custom_function_result.data.output"])
space_delimiter_input = phantom.get_format_data(name="space_delimiter_input")
parameters = []
# build parameters list for 'run_ticket_query' call
for calculate_earliest_time__result_item in calculate_earliest_time__result:
for convert_table_list__result_item in convert_table_list__result:
if query_formatted_string is not None and convert_table_list__result_item[0] is not None:
parameters.append({
"query": query_formatted_string,
"max_results": 100,
"query_table": convert_table_list__result_item[0],
})
################################################################################
## Custom Code Start
################################################################################
# Write your custom code here...
################################################################################
## Custom Code End
################################################################################
phantom.act("run query", parameters=parameters, name="run_ticket_query", assets=["servicenow"], callback=process_results)
return
@phantom.playbook_block()
def format_report(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
phantom.debug("format_report() called")
################################################################################
# Format a summary table with the information gathered from the playbook.
################################################################################
template = """SOAR retrieved tickets from Service Now. The table below shows a summary of the information gathered.\n\n| Name | Number | Message | Start Time | End Time | Assignee | Creator Name | State | Matched Fields | Source | Source Link |\n| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |\n%%\n| {0} | {1} | {2} | {3} | {4} | {5} | {6} | {7} | {8} | {9} | {10} |\n%%\n"""
# parameter list for template variable replacement
parameters = [
"build_output:custom_function:name",
"build_output:custom_function:number",
"build_output:custom_function:message",
"build_output:custom_function:start_time",
"build_output:custom_function:end_time",
"build_output:custom_function:assignee",
"build_output:custom_function:creator_name",
"build_output:custom_function:state",
"build_output:custom_function:matched_fields",
"build_output:custom_function:source",
"build_output:custom_function:source_link"
]
################################################################################
## Custom Code Start
################################################################################
# Write your custom code here...
################################################################################
## Custom Code End
################################################################################
phantom.format(container=container, template=template, parameters=parameters, name="format_report")
return
@phantom.playbook_block()
def on_finish(container, summary):
phantom.debug("on_finish() called")
format_report = phantom.get_format_data(name="format_report")
build_output__observable_array = json.loads(_ if (_ := phantom.get_run_data(key="build_output:observable_array")) != "" else "null") # pylint: disable=used-before-assignment
output = {
"observable": build_output__observable_array,
"markdown_report": format_report,
}
################################################################################
## Custom Code Start
################################################################################
# Write your custom code here...
################################################################################
## Custom Code End
################################################################################
phantom.save_playbook_output_data(output=output)
return
@@ -0,0 +1,24 @@
name: ServiceNow Related Tickets Search
id: fc0edc96-ff2b-48b0-9b4d-63da61bafe74
version: 1
date: '2023-02-28'
author: Patrick Bareiss, Splunk
type: Investigation
description: "Accepts a user or device and identifies if related tickets exists in a timeframe of last 30 days. Generates a global report and list of observables."
playbook: ServiceNow_Related_Tickets_Search
how_to_implement: This input playbook requires the ServiceNow connector to be configured. It is designed to work in conjunction with the Dynamic Related Tickets Seach playbook or other playbooks in the same style.
references:
- https://d3fend.mitre.org/technique/d3f:IdentifierReputationAnalysis/
app_list:
- Splunk
tags:
platform_tags:
- user
- device
- ServiceNow
- ticket
playbook_type: Input
vpe_type: Modern
playbook_fields: []
product:
- Splunk SOAR
@@ -0,0 +1,428 @@
{
"blockly": false,
"blockly_xml": "<xml></xml>",
"category": "Dynamic Related Ticket Search",
"coa": {
"data": {
"description": "Accepts a user or device and identifies if related notables exists in a timeframe of last 24 hours.. Generates a global report and list of observables.",
"edges": [
{
"id": "port_0_to_port_2",
"sourceNode": "0",
"sourcePort": "0_out",
"targetNode": "2",
"targetPort": "2_in"
},
{
"id": "port_10_to_port_3",
"sourceNode": "10",
"sourcePort": "10_out",
"targetNode": "3",
"targetPort": "3_in"
},
{
"conditions": [
{
"index": 0
}
],
"id": "port_2_to_port_10",
"sourceNode": "2",
"sourcePort": "2_out",
"targetNode": "10",
"targetPort": "10_in"
},
{
"id": "port_7_to_port_12",
"sourceNode": "7",
"sourcePort": "7_out",
"targetNode": "12",
"targetPort": "12_in"
},
{
"id": "port_12_to_port_1",
"sourceNode": "12",
"sourcePort": "12_out",
"targetNode": "1",
"targetPort": "1_in"
},
{
"id": "port_15_to_port_7",
"sourceNode": "15",
"sourcePort": "15_out",
"targetNode": "7",
"targetPort": "7_in"
},
{
"id": "port_3_to_port_17",
"sourceNode": "3",
"sourcePort": "3_out",
"targetNode": "17",
"targetPort": "17_in"
},
{
"conditions": [
{
"index": 0
}
],
"id": "port_17_to_port_15",
"sourceNode": "17",
"sourcePort": "17_out",
"targetNode": "15",
"targetPort": "15_in"
}
],
"hash": "97d4448db53d4f9f36897f9da6cc35bd6c97e460",
"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": 1398
},
"10": {
"data": {
"advanced": {
"customName": "comma separated user",
"customNameId": 0,
"description": "Convert playbook user input list into comma separated string for Splunk query.",
"drop_none": true,
"join": [],
"note": "Convert playbook user input list into comma separated string for Splunk query.",
"separator": "*,*"
},
"functionId": 2,
"functionName": "comma_separated_user",
"id": "10",
"parameters": [
"filtered-data:input_filter:condition_1:playbook_input:search_term"
],
"template": "*{0}*",
"type": "format"
},
"errors": {},
"id": "10",
"type": "format",
"warnings": {},
"x": 0,
"y": 328
},
"12": {
"data": {
"advanced": {
"customName": "format report",
"customNameId": 0,
"description": "Format a summary table with the information gathered from the playbook.",
"join": [],
"note": "Format a summary table with the information gathered from the playbook."
},
"functionId": 4,
"functionName": "format_report",
"id": "12",
"parameters": [
"build_output:custom_function:name",
"build_output:custom_function:number",
"build_output:custom_function:message",
"build_output:custom_function:start_time",
"build_output:custom_function:end_time",
"build_output:custom_function:assignee",
"build_output:custom_function:creator_name",
"build_output:custom_function:state"
],
"template": "SOAR retrieved tickets from Splunk. The table below shows a summary of the information gathered.\n\n| Name | Number | Message | Start Time | End Time | Assignee | Creator Name | State | Source |\n| --- | --- | --- | --- | --- | --- | --- | --- | --- |\n%%\n| {0} | {1} | {2} | {3} | {4} | {5} | {6} | {7} | Splunk Enterprise Security |\n%%\n",
"type": "format"
},
"errors": {},
"id": "12",
"type": "format",
"warnings": {},
"x": 0,
"y": 1220
},
"15": {
"data": {
"advanced": {
"customName": "process results",
"customNameId": 0,
"description": "Iterates through the results of the search notable query to link playbook input search term to their associated notables.",
"join": [],
"note": "Iterates through the results of the search notable query to link playbook input search term to their associated notables."
},
"functionId": 1,
"functionName": "process_results",
"id": "15",
"inputParameters": [
"filtered-data:input_filter:condition_1:playbook_input:search_term",
"filtered-data:search_results_filter:condition_1:search_notables:action_result.data"
],
"outputVariables": [
"output"
],
"type": "code"
},
"errors": {},
"id": "15",
"type": "code",
"userCode": "\n process_results__output = {}\n for search_term in filtered_input_0_search_term_values:\n process_results__output[search_term] = []\n for result in filtered_result_0_data:\n if not isinstance(result, list):\n list_result = [result]\n else:\n list_result = result\n for result_item in list_result:\n result_item_values = [item.lower() for item in result_item.values() if isinstance(item, str)]\n match = False\n for string_value in result_item_values:\n if search_term.lower() in string_value:\n match = True\n break\n if match:\n process_results__output[search_term].append({**result_item})\n\n",
"warnings": {},
"x": 0,
"y": 864
},
"17": {
"data": {
"advanced": {
"customName": "search results filter",
"customNameId": 0,
"description": "Determine if search results exist from the previous query.",
"join": [],
"note": "Determine if search results exist from the previous query."
},
"conditions": [
{
"comparisons": [
{
"conditionIndex": 0,
"op": ">",
"param": "search_notables:action_result.summary.total_events",
"value": "0"
}
],
"conditionIndex": 0,
"customName": "results_exist",
"logic": "and"
}
],
"functionId": 2,
"functionName": "search_results_filter",
"id": "17",
"type": "filter"
},
"errors": {},
"id": "17",
"type": "filter",
"warnings": {},
"x": 60,
"y": 684
},
"2": {
"data": {
"advanced": {
"customName": "input_filter",
"customNameId": 0,
"description": "Creates a dataset without None values.",
"join": [],
"note": "Creates a dataset without None values."
},
"conditions": [
{
"comparisons": [
{
"conditionIndex": 0,
"op": "!=",
"param": "playbook_input:search_term",
"value": "None"
}
],
"conditionIndex": 0,
"customName": "output_exists",
"logic": "and"
}
],
"functionId": 1,
"functionName": "input_filter",
"id": "2",
"type": "filter"
},
"errors": {},
"id": "2",
"type": "filter",
"warnings": {
"config": [
"Reconfigure invalid datapath."
]
},
"x": 60,
"y": 148
},
"3": {
"customCode": null,
"data": {
"action": "run query",
"actionType": "investigate",
"advanced": {
"customName": "search notables",
"customNameId": 0,
"description": "Retrieve a list of notables which matched the given search input.",
"join": [],
"note": "Retrieve a list of notables which matched the given search input."
},
"connector": "Splunk",
"connectorConfigs": [
"splunk"
],
"connectorId": "91883aa8-9c81-470b-97a1-5d8f7995f560",
"connectorVersion": "v1",
"functionId": 1,
"functionName": "search_notables",
"id": "3",
"parameters": {
"attach_result": true,
"command": "search",
"display": "",
"end_time": "now",
"query": {
"functionId": 1,
"parameters": [
"comma_separated_user:formatted_data"
],
"template": "`notable` | search _raw IN ({0})\n"
},
"search_mode": "smart",
"start_time": "-24h"
},
"requiredParameters": [
{
"data_type": "string",
"field": "query"
},
{
"data_type": "string",
"default": "search",
"field": "command"
},
{
"data_type": "string",
"default": "smart",
"field": "search_mode"
}
],
"tab": "byConnector",
"type": "action"
},
"errors": {},
"id": "3",
"type": "action",
"userCode": null,
"warnings": {},
"x": 0,
"y": 506
},
"7": {
"customCode": null,
"data": {
"advanced": {
"customName": "build output",
"customNameId": 0,
"description": "Extract relevant data and add them to an observable array.",
"join": [],
"note": "Extract relevant data and add them to an observable array."
},
"functionId": 2,
"functionName": "build_output",
"id": "7",
"inputParameters": [
"process_results:custom_function:output"
],
"outputVariables": [
"observable_array",
"name",
"id",
"number",
"message",
"start_time",
"end_time",
"assignee",
"creator_name",
"state",
"notes",
"comments"
],
"type": "code"
},
"errors": {},
"id": "7",
"type": "code",
"userCode": " build_output__observable_array = []\n build_output__name = []\n build_output__id = []\n build_output__number = []\n build_output__message = []\n build_output__start_time = []\n build_output__end_time = []\n build_output__assignee = []\n build_output__creator_name = []\n build_output__state = []\n build_output__notes = []\n build_output__comments = []\n \n \n for key in process_results__output.keys():\n \n for result in process_results__output[key]:\n if \"comment\" in result:\n if isinstance(result[\"comment\"], str):\n comments = [result[\"comment\"]]\n else:\n comments = result[\"comment\"]\n else:\n comments = []\n \n matched_fields = []\n for k, v in result.items():\n # generate matched fields where the searched entity appears\n if isinstance(v, str) and key.lower() in v.lower():\n matched_fields.append(k)\n\n observable_object = {\n \"value\": key,\n \"ticket\": {\n \"name\": result['rule_title'] if result.get('rule_title') else result.get(\"search_name\"),\n \"id\": result.get(\"event_id\"),\n \"number\": result.get(\"notable_xref_id\"),\n \"message\": result['rule_description'] if result.get(\"rule_description\") else result.get(\"savedsearch_description\"),\n \"start_time\": result.get(\"_time\"),\n \"end_time\": \"\",\n \"assigned_to\": result.get(\"owner\"),\n \"creator_name\": \"\",\n \"state\": result.get(\"status_label\"),\n \"notes\": [],\n \"comments\": comments\n },\n \"matched_fields\": matched_fields,\n \"source\": \"Splunk Enterprise Security\"\n }\n build_output__observable_array.append(observable_object)\n build_output__name.append(result.get(\"search_name\"))\n build_output__id.append(result.get(\"event_id\"))\n build_output__number.append(result.get(\"notable_xref_id\"))\n build_output__message.append(result.get(\"savedsearch_description\"))\n build_output__start_time.append(result.get(\"_time\"))\n build_output__end_time.append(\"\")\n build_output__assignee.append(result.get(\"owner\"))\n build_output__creator_name.append(\"\")\n build_output__state.append(result.get(\"status_label\"))\n build_output__notes.append([])\n build_output__comments.append(comments)\n \n \n",
"warnings": {},
"x": 0,
"y": 1042
}
},
"notes": "Inputs: user, device\nInteractions: Splunk\nActions: run query\nOutputs: report, observables"
},
"input_spec": [
{
"contains": [
"user name",
"host name"
],
"description": "Find notables in Splunk that contains the given search_term.",
"name": "search_term"
}
],
"output_spec": [
{
"contains": [],
"datapaths": [
"build_output:custom_function:observable_array"
],
"deduplicate": false,
"description": "An array of observable dictionaries with value, type and information about the retrieved notables.",
"metadata": {},
"name": "observable"
},
{
"contains": [],
"datapaths": [
"format_report:formatted_data"
],
"deduplicate": false,
"description": "An array of reports.",
"metadata": {},
"name": "markdown_report"
}
],
"playbook_type": "data",
"python_version": "3",
"schema": "5.0.9",
"version": "6.0.0.114895"
},
"create_time": "2023-03-03T21:14:02.937956+00:00",
"draft_mode": false,
"labels": [
"*"
],
"tags": [
"user",
"device",
"splunk",
"ticket"
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

@@ -0,0 +1,367 @@
"""
Accepts a user or device and identifies if related notables exists in a timeframe of last 24 hours.. Generates a global report and list of observables.
"""
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")
################################################################################
# Creates a dataset without None values.
################################################################################
# collect filtered artifact ids and results for 'if' condition 1
matched_artifacts_1, matched_results_1 = phantom.condition(
container=container,
conditions=[
["playbook_input:search_term", "!=", None]
],
name="input_filter:condition_1")
# call connected blocks if filtered artifacts or results
if matched_artifacts_1 or matched_results_1:
comma_separated_user(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 search_notables(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
phantom.debug("search_notables() called")
# phantom.debug('Action: {0} {1}'.format(action['name'], ('SUCCEEDED' if success else 'FAILED')))
query_formatted_string = phantom.format(
container=container,
template="""`notable` | search _raw IN ({0})\n""",
parameters=[
"comma_separated_user:formatted_data"
])
################################################################################
# Retrieve a list of notables which matched the given search input.
################################################################################
comma_separated_user = phantom.get_format_data(name="comma_separated_user")
parameters = []
if query_formatted_string is not None:
parameters.append({
"query": query_formatted_string,
"command": "search",
"display": "",
"end_time": "now",
"start_time": "-24h",
"search_mode": "smart",
"attach_result": True,
})
################################################################################
## Custom Code Start
################################################################################
# Write your custom code here...
################################################################################
## Custom Code End
################################################################################
phantom.act("run query", parameters=parameters, name="search_notables", assets=["splunk"], callback=search_results_filter)
return
@phantom.playbook_block()
def build_output(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
phantom.debug("build_output() called")
################################################################################
# Extract relevant data and add them to an observable array.
################################################################################
process_results__output = json.loads(_ if (_ := phantom.get_run_data(key="process_results:output")) != "" else "null") # pylint: disable=used-before-assignment
build_output__observable_array = None
build_output__name = None
build_output__id = None
build_output__number = None
build_output__message = None
build_output__start_time = None
build_output__end_time = None
build_output__assignee = None
build_output__creator_name = None
build_output__state = None
build_output__notes = None
build_output__comments = None
################################################################################
## Custom Code Start
################################################################################
build_output__observable_array = []
build_output__name = []
build_output__id = []
build_output__number = []
build_output__message = []
build_output__start_time = []
build_output__end_time = []
build_output__assignee = []
build_output__creator_name = []
build_output__state = []
build_output__notes = []
build_output__comments = []
for key in process_results__output.keys():
for result in process_results__output[key]:
if "comment" in result:
if isinstance(result["comment"], str):
comments = [result["comment"]]
else:
comments = result["comment"]
else:
comments = []
matched_fields = []
for k, v in result.items():
# generate matched fields where the searched entity appears
if isinstance(v, str) and key.lower() in v.lower():
matched_fields.append(k)
observable_object = {
"value": key,
"ticket": {
"name": result['rule_title'] if result.get('rule_title') else result.get("search_name"),
"id": result.get("event_id"),
"number": result.get("notable_xref_id"),
"message": result['rule_description'] if result.get("rule_description") else result.get("savedsearch_description"),
"start_time": result.get("_time"),
"end_time": "",
"assigned_to": result.get("owner"),
"creator_name": "",
"state": result.get("status_label"),
"notes": [],
"comments": comments
},
"matched_fields": matched_fields,
"source": "Splunk Enterprise Security"
}
build_output__observable_array.append(observable_object)
build_output__name.append(result.get("search_name"))
build_output__id.append(result.get("event_id"))
build_output__number.append(result.get("notable_xref_id"))
build_output__message.append(result.get("savedsearch_description"))
build_output__start_time.append(result.get("_time"))
build_output__end_time.append("")
build_output__assignee.append(result.get("owner"))
build_output__creator_name.append("")
build_output__state.append(result.get("status_label"))
build_output__notes.append([])
build_output__comments.append(comments)
################################################################################
## Custom Code End
################################################################################
phantom.save_run_data(key="build_output:observable_array", value=json.dumps(build_output__observable_array))
phantom.save_run_data(key="build_output:name", value=json.dumps(build_output__name))
phantom.save_run_data(key="build_output:id", value=json.dumps(build_output__id))
phantom.save_run_data(key="build_output:number", value=json.dumps(build_output__number))
phantom.save_run_data(key="build_output:message", value=json.dumps(build_output__message))
phantom.save_run_data(key="build_output:start_time", value=json.dumps(build_output__start_time))
phantom.save_run_data(key="build_output:end_time", value=json.dumps(build_output__end_time))
phantom.save_run_data(key="build_output:assignee", value=json.dumps(build_output__assignee))
phantom.save_run_data(key="build_output:creator_name", value=json.dumps(build_output__creator_name))
phantom.save_run_data(key="build_output:state", value=json.dumps(build_output__state))
phantom.save_run_data(key="build_output:notes", value=json.dumps(build_output__notes))
phantom.save_run_data(key="build_output:comments", value=json.dumps(build_output__comments))
format_report(container=container)
return
@phantom.playbook_block()
def comma_separated_user(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
phantom.debug("comma_separated_user() called")
################################################################################
# Convert playbook user input list into comma separated string for Splunk query.
################################################################################
template = """*{0}*"""
# parameter list for template variable replacement
parameters = [
"filtered-data:input_filter:condition_1:playbook_input:search_term"
]
################################################################################
## Custom Code Start
################################################################################
# Write your custom code here...
################################################################################
## Custom Code End
################################################################################
phantom.format(container=container, template=template, parameters=parameters, name="comma_separated_user", separator="*,*", drop_none=True)
search_notables(container=container)
return
@phantom.playbook_block()
def format_report(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
phantom.debug("format_report() called")
################################################################################
# Format a summary table with the information gathered from the playbook.
################################################################################
template = """SOAR retrieved tickets from Splunk. The table below shows a summary of the information gathered.\n\n| Name | Number | Message | Start Time | End Time | Assignee | Creator Name | State | Source |\n| --- | --- | --- | --- | --- | --- | --- | --- | --- |\n%%\n| {0} | {1} | {2} | {3} | {4} | {5} | {6} | {7} | Splunk Enterprise Security |\n%%\n"""
# parameter list for template variable replacement
parameters = [
"build_output:custom_function:name",
"build_output:custom_function:number",
"build_output:custom_function:message",
"build_output:custom_function:start_time",
"build_output:custom_function:end_time",
"build_output:custom_function:assignee",
"build_output:custom_function:creator_name",
"build_output:custom_function:state"
]
################################################################################
## Custom Code Start
################################################################################
# Write your custom code here...
################################################################################
## Custom Code End
################################################################################
phantom.format(container=container, template=template, parameters=parameters, name="format_report")
return
@phantom.playbook_block()
def process_results(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
phantom.debug("process_results() called")
################################################################################
# Iterates through the results of the search notable query to link playbook input
# search term to their associated notables.
################################################################################
filtered_input_0_search_term = phantom.collect2(container=container, datapath=["filtered-data:input_filter:condition_1:playbook_input:search_term"])
filtered_result_0_data_search_results_filter = phantom.collect2(container=container, datapath=["filtered-data:search_results_filter:condition_1:search_notables:action_result.data"])
filtered_input_0_search_term_values = [item[0] for item in filtered_input_0_search_term]
filtered_result_0_data = [item[0] for item in filtered_result_0_data_search_results_filter]
process_results__output = None
################################################################################
## Custom Code Start
################################################################################
process_results__output = {}
for search_term in filtered_input_0_search_term_values:
process_results__output[search_term] = []
for result in filtered_result_0_data:
if not isinstance(result, list):
list_result = [result]
else:
list_result = result
for result_item in list_result:
result_item_values = [item.lower() for item in result_item.values() if isinstance(item, str)]
match = False
for string_value in result_item_values:
if search_term.lower() in string_value:
match = True
break
if match:
process_results__output[search_term].append({**result_item})
################################################################################
## Custom Code End
################################################################################
phantom.save_run_data(key="process_results:output", value=json.dumps(process_results__output))
build_output(container=container)
return
@phantom.playbook_block()
def search_results_filter(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
phantom.debug("search_results_filter() called")
################################################################################
# Determine if search results exist from the previous query.
################################################################################
# collect filtered artifact ids and results for 'if' condition 1
matched_artifacts_1, matched_results_1 = phantom.condition(
container=container,
conditions=[
["search_notables:action_result.summary.total_events", ">", 0]
],
name="search_results_filter:condition_1")
# call connected blocks if filtered artifacts or results
if matched_artifacts_1 or matched_results_1:
process_results(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_report = phantom.get_format_data(name="format_report")
build_output__observable_array = json.loads(_ if (_ := phantom.get_run_data(key="build_output:observable_array")) != "" else "null") # pylint: disable=used-before-assignment
output = {
"observable": build_output__observable_array,
"markdown_report": format_report,
}
################################################################################
## Custom Code Start
################################################################################
# Write your custom code here...
################################################################################
## Custom Code End
################################################################################
phantom.save_playbook_output_data(output=output)
return
@@ -0,0 +1,24 @@
name: Splunk Notable Related Tickets Search
id: fc0edc96-ff2b-58b0-9b4d-43bc61bafe74
version: 1
date: '2023-02-28'
author: Patrick Bareiss, Splunk
type: Investigation
description: "Accepts a user or device and identifies if related notables exists in a timeframe of last 24 hours. Generates a global report and list of observables."
playbook: Splunk_Notable_Related_Tickets_Search
how_to_implement: This input playbook requires the Splunk connector to be configured. It is designed to work in conjunction with the Dynamic Related Tickets Seach playbook or other playbooks in the same style.
references:
- https://d3fend.mitre.org/technique/d3f:IdentifierReputationAnalysis/
app_list:
- Splunk
tags:
platform_tags:
- user
- device
- splunk
- ticket
playbook_type: Input
vpe_type: Modern
playbook_fields: []
product:
- Splunk SOAR