diff --git a/playbooks/ServiceNow_Create_Incident.json b/playbooks/ServiceNow_Create_Incident.json new file mode 100644 index 0000000000..8109f86592 --- /dev/null +++ b/playbooks/ServiceNow_Create_Incident.json @@ -0,0 +1,311 @@ +{ + "blockly": false, + "blockly_xml": "", + "category": "Uncategorized", + "coa": { + "data": { + "description": "This Playbook will take the output from automation run upstream in the playbook and send a report of that to a SNOW Incident of your choice. ", + "edges": [ + { + "id": "port_2_to_port_3", + "sourceNode": "2", + "sourcePort": "2_out", + "targetNode": "3", + "targetPort": "3_in" + }, + { + "id": "port_0_to_port_2", + "sourceNode": "0", + "sourcePort": "0_out", + "targetNode": "2", + "targetPort": "2_in" + }, + { + "id": "port_3_to_port_1", + "sourceNode": "3", + "sourcePort": "3_out", + "targetNode": "1", + "targetPort": "1_in" + } + ], + "hash": "d42cbc8cfd5f6d63e69c9550da0cf06269a83103", + "nodes": { + "0": { + "data": { + "advanced": { + "join": [] + }, + "functionName": "on_start", + "id": "0", + "type": "start" + }, + "errors": {}, + "id": "0", + "type": "start", + "warnings": {}, + "x": 1000, + "y": 419.9999999999998 + }, + "1": { + "data": { + "advanced": { + "join": [] + }, + "functionName": "on_finish", + "id": "1", + "type": "end" + }, + "errors": {}, + "id": "1", + "type": "end", + "warnings": {}, + "x": 1000, + "y": 1000 + }, + "2": { + "data": { + "action": "create ticket", + "actionType": "generic", + "advanced": { + "customName": "create snow incident", + "customNameId": 0, + "description": "This will create a Service Now(SNOW) Incident and pass the Container Name as the Title of the incident and Description as the Description in the incident", + "join": [], + "note": "We are using the container:name and container:description to populate the SNOW incident fields but you could select something else if you see fit. " + }, + "connector": "ServiceNow", + "connectorConfigs": [ + "servicenow" + ], + "connectorId": "a590c3bc-ca41-4a0e-b063-8066ca868794", + "connectorVersion": "v1", + "functionId": 1, + "functionName": "create_snow_incident", + "id": "2", + "loop": { + "enabled": false, + "exitAfterUnit": "m", + "exitAfterValue": 10, + "exitConditionEnabled": false, + "exitLoopAfter": 2, + "pauseUnit": "m", + "pauseValue": 2 + }, + "nestedNodeChildren": [], + "nestedNodeParent": null, + "parameters": { + "description": "container:description", + "short_description": "container:name", + "table": "incident" + }, + "requiredParameters": [ + { + "data_type": "string", + "default": "incident", + "field": "table" + } + ], + "type": "action" + }, + "errors": {}, + "id": "2", + "type": "action", + "warnings": {}, + "x": 980, + "y": 600 + }, + "3": { + "data": { + "advanced": { + "customName": "add snow incident number", + "customNameId": 0, + "description": "This will add an artifact for the SNOW Incident in the SOAR Container that can be used later to update the SNOW incident with other actions take. ", + "join": [], + "note": "This will add an artifact for the SNOW Incident in the SOAR Container that can be used later to update the SNOW incident with other actions take. " + }, + "customFunction": { + "draftMode": false, + "name": "artifact_create", + "repoName": "community" + }, + "functionId": 1, + "functionName": "add_snow_incident_number", + "id": "3", + "loop": { + "enabled": false, + "exitAfterUnit": "m", + "exitAfterValue": 10, + "exitConditionEnabled": false, + "exitLoopAfter": 2, + "pauseUnit": "m", + "pauseValue": 2 + }, + "nestedNodeChildren": [], + "nestedNodeParent": null, + "selectMore": false, + "type": "utility", + "utilities": { + "artifact_create": { + "description": "Create a new artifact with the specified attributes. Supports all fields available in /rest/artifact. Add any unlisted inputs as dictionary keys in input_json. Unsupported keys will automatically be dropped.", + "fields": [ + { + "dataTypes": [ + "phantom container id" + ], + "description": "Container which the artifact will be added to.", + "inputType": "item", + "label": "container", + "name": "container", + "placeholder": "container:id", + "renderType": "datapath", + "required": false + }, + { + "dataTypes": [], + "description": "The name of the new artifact, which is optional and defaults to \"artifact\".", + "inputType": "item", + "label": "name", + "name": "name", + "placeholder": "artifact", + "renderType": "datapath", + "required": false + }, + { + "dataTypes": [], + "description": "The label of the new artifact, which is optional and defaults to \"events\"", + "inputType": "item", + "label": "label", + "name": "label", + "placeholder": "events", + "renderType": "datapath", + "required": false + }, + { + "dataTypes": [ + "" + ], + "description": "The severity of the new artifact, which is optional and defaults to \"Medium\". Typically this is either \"High\", \"Medium\", or \"Low\".", + "inputType": "item", + "label": "severity", + "name": "severity", + "placeholder": "Medium", + "renderType": "datapath", + "required": false + }, + { + "dataTypes": [], + "description": "The name of the CEF field to populate in the artifact, such as \"destinationAddress\" or \"sourceDnsDomain\". Required only if cef_value is provided.", + "inputType": "item", + "label": "cef_field", + "name": "cef_field", + "placeholder": "destinationAddress", + "renderType": "datapath", + "required": false + }, + { + "dataTypes": [ + "*" + ], + "description": "The value of the CEF field to populate in the artifact, such as the IP address, domain name, or file hash. Required only if cef_field is provided.", + "inputType": "item", + "label": "cef_value", + "name": "cef_value", + "placeholder": "192.0.2.192", + "renderType": "datapath", + "required": false + }, + { + "dataTypes": [], + "description": "The CEF data type of the data in cef_value. For example, this could be \"ip\", \"hash\", or \"domain\". Optional.", + "inputType": "item", + "label": "cef_data_type", + "name": "cef_data_type", + "placeholder": "ip", + "renderType": "datapath", + "required": false + }, + { + "dataTypes": [], + "description": "A comma-separated list of tags to apply to the created artifact, which is optional.", + "inputType": "item", + "label": "tags", + "name": "tags", + "placeholder": "tag1, tag2, tag3", + "renderType": "datapath", + "required": false + }, + { + "dataTypes": [], + "description": "Either \"true\" or \"false\", depending on whether or not the new artifact should trigger the execution of any playbooks that are set to active on the label of the container the artifact will be added to. Optional and defaults to \"false\".", + "inputType": "item", + "label": "run_automation", + "name": "run_automation", + "placeholder": "false", + "renderType": "datapath", + "required": false + }, + { + "dataTypes": [], + "description": "Optional parameter to modify any extra attributes of the artifact. Input_json will be merged with other inputs. In the event of a conflict, input_json will take precedence.", + "inputType": "item", + "label": "input_json", + "name": "input_json", + "placeholder": "{\"source_data_identifier\": \"1234\", \"data\": \"5678\"}", + "renderType": "datapath", + "required": false + } + ], + "label": "artifact_create", + "name": "artifact_create" + } + }, + "utilityType": "custom_function", + "values": { + "artifact_create": { + "cef_data_type": null, + "cef_field": "snow_incident", + "cef_value": "create_snow_incident:action_result.data.*.number", + "container": "container:id", + "input_json": null, + "label": "create_snow_incident", + "name": "Service Now Incident Create", + "run_automation": null, + "severity": "", + "tags": null + } + } + }, + "errors": {}, + "id": "3", + "type": "utility", + "warnings": {}, + "x": 980, + "y": 760 + } + }, + "notes": "This is an input playbook that is designed to be added to a larger automation playbook you have that does initial triage of a detection.", + "origin": { + "playbook_id": 730, + "playbook_name": "servicenow_create_incident", + "playbook_repo_id": 2, + "playbook_repo_name": "local" + } + }, + "input_spec": null, + "output_spec": null, + "playbook_trigger": "artifact_created", + "playbook_type": "automation", + "python_version": "3.13", + "schema": "5.0.19", + "version": "6.4.1.356" + }, + "create_time": "2025-08-15T16:35:31.659527+00:00", + "draft_mode": false, + "labels": [ + "*" + ], + "tags": [ + "ServiceNow", + "ticket" + ] +} \ No newline at end of file diff --git a/playbooks/ServiceNow_Create_Incident.png b/playbooks/ServiceNow_Create_Incident.png new file mode 100644 index 0000000000..5a8c2e9e9d Binary files /dev/null and b/playbooks/ServiceNow_Create_Incident.png differ diff --git a/playbooks/ServiceNow_Create_Incident.py b/playbooks/ServiceNow_Create_Incident.py new file mode 100644 index 0000000000..b375d17249 --- /dev/null +++ b/playbooks/ServiceNow_Create_Incident.py @@ -0,0 +1,115 @@ +""" +This Playbook will take the output from automation run upstream in the playbook and send a report of that to a SNOW Incident of your choice. +""" + + +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 'create_snow_incident' block + create_snow_incident(container=container) + + return + +@phantom.playbook_block() +def create_snow_incident(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, loop_state_json=None, **kwargs): + phantom.debug("create_snow_incident() called") + + # phantom.debug('Action: {0} {1}'.format(action['name'], ('SUCCEEDED' if success else 'FAILED'))) + + ################################################################################ + # This will create a Service Now(SNOW) Incident and pass the Container Name as + # the Title of the incident and Description as the Description in the incident + ################################################################################ + + description_value = container.get("description", None) + name_value = container.get("name", None) + + parameters = [] + + parameters.append({ + "table": "incident", + "description": description_value, + "short_description": name_value, + }) + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.act("create ticket", parameters=parameters, name="create_snow_incident", assets=["servicenow"], callback=add_snow_incident_number) + + return + + +@phantom.playbook_block() +def add_snow_incident_number(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, loop_state_json=None, **kwargs): + phantom.debug("add_snow_incident_number() called") + + ################################################################################ + # This will add an artifact for the SNOW Incident in the SOAR Container that can + # be used later to update the SNOW incident with other actions take. + ################################################################################ + + id_value = container.get("id", None) + create_snow_incident_result_data = phantom.collect2(container=container, datapath=["create_snow_incident:action_result.data.*.number","create_snow_incident:action_result.parameter.context.artifact_id"], action_results=results) + + parameters = [] + + # build parameters list for 'add_snow_incident_number' call + for create_snow_incident_result_item in create_snow_incident_result_data: + parameters.append({ + "name": "Service Now Incident Create", + "tags": None, + "label": "create_snow_incident", + "severity": None, + "cef_field": "snow_incident", + "cef_value": create_snow_incident_result_item[0], + "container": id_value, + "input_json": None, + "cef_data_type": None, + "run_automation": None, + }) + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.custom_function(custom_function="community/artifact_create", parameters=parameters, name="add_snow_incident_number") + + return + + +@phantom.playbook_block() +def on_finish(container, summary): + phantom.debug("on_finish() called") + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + return \ No newline at end of file diff --git a/playbooks/ServiceNow_Create_Incident.yml b/playbooks/ServiceNow_Create_Incident.yml new file mode 100644 index 0000000000..792b55b0c4 --- /dev/null +++ b/playbooks/ServiceNow_Create_Incident.yml @@ -0,0 +1,24 @@ +name: ServiceNow Create Incident +id: a972fd32-53a0-4e15-ad9f-d6968a465e94 +version: 1 +date: '2025-08-26' +author: Kenneth Bouchard, Christian Cloutier, Splunk +type: Response +description: "This Playbook was designed to be added to a Response Plan inside of Enterprise Security 8.x, to create a correlating SNOW incident and a artifact inside of the SOAR container to continue to track the investigation. You could also run this on demand." +playbook: ServiceNow_Create_Incident +how_to_implement: This Automation playbook requires the ServiceNow connector to be configured. It is designed to work Enterprise Security 8.1 or above to create a ServiceNow Incident, add a artifact and add a general note in the Soar Container. +references: [] +app_list: + - ServiceNow +tags: + platform_tags: + - "ServiceNow" + - "ticket" + playbook_type: Automation + vpe_type: Modern + playbook_fields: [device] + product: + - Splunk SOAR + use_cases: + - Response + defend_technique_id: diff --git a/playbooks/ServiceNow_Create_Incident_Es.json b/playbooks/ServiceNow_Create_Incident_Es.json new file mode 100644 index 0000000000..77b6631d7d --- /dev/null +++ b/playbooks/ServiceNow_Create_Incident_Es.json @@ -0,0 +1,280 @@ +{ + "blockly": false, + "blockly_xml": "", + "category": "Uncategorized", + "coa": { + "data": { + "description": "This Playbook was designed to be added to a Response Plan inside of Enterprise Security 8.x, to create a correlating SNOW incident and a custom field inside of the ES investigation to continue to track the investigation. ", + "edges": [ + { + "id": "port_0_to_port_2", + "sourceNode": "0", + "sourcePort": "0_out", + "targetNode": "2", + "targetPort": "2_in" + }, + { + "id": "port_2_to_port_3", + "sourceNode": "2", + "sourcePort": "2_out", + "targetNode": "3", + "targetPort": "3_in" + }, + { + "id": "port_3_to_port_1", + "sourceNode": "3", + "sourcePort": "3_out", + "targetNode": "1", + "targetPort": "1_in" + }, + { + "id": "port_2_to_port_4", + "sourceNode": "2", + "sourcePort": "2_out", + "targetNode": "4", + "targetPort": "4_in" + }, + { + "id": "port_4_to_port_1", + "sourceNode": "4", + "sourcePort": "4_out", + "targetNode": "1", + "targetPort": "1_in" + } + ], + "hash": "826a7c49983d5f1dd79d3cbbe425f503fe731f75", + "nodes": { + "0": { + "data": { + "advanced": { + "join": [] + }, + "functionName": "on_start", + "id": "0", + "type": "start" + }, + "errors": { + "input_spec": [ + { + "name": "Name is required" + } + ] + }, + "id": "0", + "type": "start", + "warnings": {}, + "x": 200, + "y": -1.9184653865522705e-13 + }, + "1": { + "data": { + "advanced": { + "join": [] + }, + "functionName": "on_finish", + "id": "1", + "type": "end" + }, + "errors": {}, + "id": "1", + "type": "end", + "warnings": {}, + "x": 200, + "y": 479.9999999999998 + }, + "2": { + "data": { + "action": "create ticket", + "actionType": "generic", + "advanced": { + "customName": "create snow ticket", + "customNameId": 0, + "description": "This will create a Service Now ticket to track your Security Investigation in SNOW", + "join": [], + "note": "Typically you will use the \n\nthe Rule Title and Description field in Enterprise Security " + }, + "connector": "ServiceNow", + "connectorConfigs": [ + "servicenow" + ], + "connectorId": "a590c3bc-ca41-4a0e-b063-8066ca868794", + "connectorVersion": "v1", + "functionId": 1, + "functionName": "create_snow_ticket", + "id": "2", + "loop": { + "enabled": false, + "exitAfterUnit": "m", + "exitAfterValue": 10, + "exitConditionEnabled": false, + "exitLoopAfter": 2, + "pauseUnit": "m", + "pauseValue": 2 + }, + "parameters": { + "description": "finding:consolidated_findings.source", + "short_description": "finding:consolidated_findings.rule_title", + "table": "incident" + }, + "requiredParameters": [], + "type": "action" + }, + "errors": {}, + "id": "2", + "type": "action", + "warnings": {}, + "x": 180, + "y": 140 + }, + "3": { + "data": { + "action": "set custom fields", + "actionType": "post", + "advanced": { + "customName": "create snow incident field", + "customNameId": 0, + "description": " We create this so that you can use it downstream with other ServiceNow playbooks to update the Snow Incident as you work thru the investigation. ", + "join": [], + "note": "This can be added as an Automation Rule , as a Phase:Task in a Response Plan or run ad hoc in ES 8.x" + }, + "connector": "Enterprise Security", + "connectorConfigs": [ + "builtin_mc_connector" + ], + "connectorId": "7e971d03-6ae7-4429-b4e1-cf461003e75b", + "connectorVersion": "v1", + "functionId": 1, + "functionName": "create_snow_incident_field", + "id": "3", + "loop": { + "enabled": false, + "exitAfterUnit": "m", + "exitAfterValue": 10, + "exitConditionEnabled": false, + "exitLoopAfter": 2, + "pauseUnit": "m", + "pauseValue": 2 + }, + "parameters": { + "incident_id": "finding:id", + "pairs": [ + { + "name": "snow_incident", + "value": "create_snow_ticket:action_result.data.*.number" + } + ] + }, + "requiredParameters": [ + { + "data_type": "array", + "field": "pairs" + }, + { + "data_type": "string", + "default": "", + "field": "incident_id" + } + ], + "type": "enterpriseSecurity" + }, + "errors": {}, + "id": "3", + "type": "enterpriseSecurity", + "warnings": {}, + "x": 1.4210854715202004e-14, + "y": 320 + }, + "4": { + "data": { + "action": "add finding or investigation note", + "actionType": "post", + "advanced": { + "customName": "snow ticket number note", + "customNameId": 0, + "description": "We add the Snow ticket number as a note in the Investigation (or Finding)", + "join": [], + "note": "We add the Snow ticket number as a note in the Investigation (or Finding)" + }, + "connector": "Enterprise Security", + "connectorConfigs": [ + "builtin_mc_connector" + ], + "connectorId": "7e971d03-6ae7-4429-b4e1-cf461003e75b", + "connectorVersion": "v1", + "functionId": 1, + "functionName": "snow_ticket_number_note", + "id": "4", + "loop": { + "enabled": false, + "exitAfterUnit": "m", + "exitAfterValue": 10, + "exitConditionEnabled": false, + "exitLoopAfter": 2, + "pauseUnit": "m", + "pauseValue": 2 + }, + "nestedNodeChildren": [], + "nestedNodeParent": null, + "parameters": { + "content": { + "functionId": 1, + "parameters": [ + "create_snow_ticket:action_result.data.*.number" + ], + "template": "ServiceNow ticket created: {0}\n" + }, + "id": "finding:id", + "title": "ServiceNow ticket created" + }, + "requiredParameters": [ + { + "data_type": "string", + "field": "id" + }, + { + "data_type": "string", + "default": "", + "field": "title" + }, + { + "data_type": "string", + "default": "", + "field": "content" + } + ], + "type": "enterpriseSecurity" + }, + "errors": {}, + "id": "4", + "type": "enterpriseSecurity", + "warnings": {}, + "x": 340, + "y": 320 + } + }, + "notes": "You could also run this on demand from the automation tab inside ES Investigation and get the same results. ", + "origin": { + "playbook_id": 239, + "playbook_name": "servicenow_create_incident_es", + "playbook_repo_id": 2, + "playbook_repo_name": "local" + } + }, + "input_spec": null, + "output_spec": null, + "playbook_trigger": "artifact_created", + "playbook_type": "es", + "python_version": "3.13", + "schema": "5.0.20", + "version": "6.4.1.361" + }, + "create_time": "2025-08-26T15:21:45.941136+00:00", + "draft_mode": false, + "labels": [ + "es_soar_integration" + ], + "tags": [ + "ServiceNow", + "ticket" + ] +} \ No newline at end of file diff --git a/playbooks/ServiceNow_Create_Incident_Es.png b/playbooks/ServiceNow_Create_Incident_Es.png new file mode 100644 index 0000000000..7e629fd43a Binary files /dev/null and b/playbooks/ServiceNow_Create_Incident_Es.png differ diff --git a/playbooks/ServiceNow_Create_Incident_Es.py b/playbooks/ServiceNow_Create_Incident_Es.py new file mode 100644 index 0000000000..731d8a4d00 --- /dev/null +++ b/playbooks/ServiceNow_Create_Incident_Es.py @@ -0,0 +1,173 @@ +""" +This Playbook was designed to be added to a Response Plan inside of Enterprise Security 8.x, to create a correlating SNOW incident and a custom field inside of the ES investigation to continue to track the investigation. +""" + + +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 'create_snow_ticket' block + create_snow_ticket(container=container) + + return + +@phantom.playbook_block() +def create_snow_ticket(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, loop_state_json=None, **kwargs): + phantom.debug("create_snow_ticket() called") + + # phantom.debug('Action: {0} {1}'.format(action['name'], ('SUCCEEDED' if success else 'FAILED'))) + + ################################################################################ + # This will create a Service Now ticket to track your Security Investigation in + # SNOW + ################################################################################ + + finding_data = phantom.collect2(container=container, datapath=["finding:consolidated_findings.source","finding:consolidated_findings.rule_title"]) + + parameters = [] + + # build parameters list for 'create_snow_ticket' call + for finding_data_item in finding_data: + parameters.append({ + "table": "incident", + "description": finding_data_item[0], + "short_description": finding_data_item[1], + }) + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.act("create ticket", parameters=parameters, name="create_snow_ticket", assets=["servicenow"], callback=create_snow_ticket_callback) + + return + + +@phantom.playbook_block() +def create_snow_ticket_callback(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, loop_state_json=None, **kwargs): + phantom.debug("create_snow_ticket_callback() called") + + + create_snow_incident_field(action=action, success=success, container=container, results=results, handle=handle, filtered_artifacts=filtered_artifacts, filtered_results=filtered_results) + snow_ticket_number_note(action=action, success=success, container=container, results=results, handle=handle, filtered_artifacts=filtered_artifacts, filtered_results=filtered_results) + + + return + + +@phantom.playbook_block() +def create_snow_incident_field(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, loop_state_json=None, **kwargs): + phantom.debug("create_snow_incident_field() called") + + # phantom.debug('Action: {0} {1}'.format(action['name'], ('SUCCEEDED' if success else 'FAILED'))) + + ################################################################################ + # We create this so that you can use it downstream with other ServiceNow playbooks + # to update the Snow Incident as you work thru the investigation. + ################################################################################ + + create_snow_ticket_result_data = phantom.collect2(container=container, datapath=["create_snow_ticket:action_result.data.*.number","create_snow_ticket:action_result.parameter.context.artifact_id"], action_results=results) + finding_data = phantom.collect2(container=container, datapath=["finding:id"]) + + parameters = [] + + # build parameters list for 'create_snow_incident_field' call + for create_snow_ticket_result_item in create_snow_ticket_result_data: + for finding_data_item in finding_data: + if finding_data_item[0] is not None: + parameters.append({ + "pairs": [ + { "name": "snow_incident", "value": create_snow_ticket_result_item[0] }, + ], + "incident_id": finding_data_item[0], + }) + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.act("set custom fields", parameters=parameters, name="create_snow_incident_field", assets=["builtin_mc_connector"]) + + return + + +@phantom.playbook_block() +def snow_ticket_number_note(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, loop_state_json=None, **kwargs): + phantom.debug("snow_ticket_number_note() called") + + # phantom.debug('Action: {0} {1}'.format(action['name'], ('SUCCEEDED' if success else 'FAILED'))) + + content_formatted_string = phantom.format( + container=container, + template="""ServiceNow ticket created: {0}\n""", + parameters=[ + "create_snow_ticket:action_result.data.*.number" + ]) + + ################################################################################ + # We add the Snow ticket number as a note in the Investigation (or Finding) + ################################################################################ + + finding_data = phantom.collect2(container=container, datapath=["finding:id"]) + create_snow_ticket_result_data = phantom.collect2(container=container, datapath=["create_snow_ticket:action_result.data.*.number","create_snow_ticket:action_result.parameter.context.artifact_id"], action_results=results) + + parameters = [] + + # build parameters list for 'snow_ticket_number_note' call + for finding_data_item in finding_data: + for create_snow_ticket_result_item in create_snow_ticket_result_data: + if finding_data_item[0] is not None and content_formatted_string is not None: + parameters.append({ + "id": finding_data_item[0], + "title": "ServiceNow ticket created", + "content": content_formatted_string, + }) + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.act("add finding or investigation note", parameters=parameters, name="snow_ticket_number_note", assets=["builtin_mc_connector"]) + + return + + +@phantom.playbook_block() +def on_finish(container, summary): + phantom.debug("on_finish() called") + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + return \ No newline at end of file diff --git a/playbooks/ServiceNow_Create_Incident_Es.yml b/playbooks/ServiceNow_Create_Incident_Es.yml new file mode 100644 index 0000000000..e0ce52ec2a --- /dev/null +++ b/playbooks/ServiceNow_Create_Incident_Es.yml @@ -0,0 +1,25 @@ +name: ServiceNow Create Incident +id: a972fd32-53a0-4e15-ad9f-d6968a465e94 +version: 1 +date: '2025-08-26' +author: Kenneth Bouchard, Christian Cloutier, Splunk +type: Response +description: "This Playbook was designed to be added to a Response Plan inside of Enterprise Security 8.x, to create a correlating SNOW incident and a custom field inside of the ES investigation to continue to track the investigation. You could also run this on demand from the automation tab inside ES Investigation and get the same results." +playbook: ServiceNow_Create_Incident +how_to_implement: This Enterprise Security playbook requires the ServiceNow connector to be configured. It is designed to work Enterprise Security 8.1 or above to create a ServiceNow Incident, add a custom field and add a general note in the Splunk Enterprise Security Investigation. +references: [] +app_list: + - ServiceNow +tags: + platform_tags: + - "ServiceNow" + - "ticket" + playbook_type: Enterprise Security + vpe_type: Modern + playbook_fields: [device] + product: + - Splunk SOAR + - Splunk Enterprise Security + use_cases: + - Response + defend_technique_id: diff --git a/playbooks/ServiceNow_Query_Incidents.json b/playbooks/ServiceNow_Query_Incidents.json new file mode 100644 index 0000000000..e1566c15ad --- /dev/null +++ b/playbooks/ServiceNow_Query_Incidents.json @@ -0,0 +1,220 @@ +{ + "blockly": false, + "blockly_xml": "", + "category": "Uncategorized", + "coa": { + "data": { + "description": "This will query your SNOW Incident table looking for incidents that have a cmdb_ci entry that matches your affected entity in the detections from ES or SOAR and give a report of the found SNOW Incidents ", + "edges": [ + { + "id": "port_0_to_port_2", + "sourceNode": "0", + "sourcePort": "0_out", + "targetNode": "2", + "targetPort": "2_in" + }, + { + "id": "port_2_to_port_3", + "sourceNode": "2", + "sourcePort": "2_out", + "targetNode": "3", + "targetPort": "3_in" + }, + { + "id": "port_3_to_port_4", + "sourceNode": "3", + "sourcePort": "3_out", + "targetNode": "4", + "targetPort": "4_in" + }, + { + "id": "port_4_to_port_1", + "sourceNode": "4", + "sourcePort": "4_out", + "targetNode": "1", + "targetPort": "1_in" + } + ], + "hash": "37b00d3d544c9742c3186af15aabd1d9b19f064e", + "nodes": { + "0": { + "data": { + "advanced": { + "join": [] + }, + "functionName": "on_start", + "id": "0", + "type": "start" + }, + "errors": {}, + "id": "0", + "type": "start", + "warnings": {}, + "x": 1000, + "y": 419.9999999999993 + }, + "1": { + "data": { + "advanced": { + "join": [] + }, + "functionName": "on_finish", + "id": "1", + "type": "end" + }, + "errors": {}, + "id": "1", + "type": "end", + "warnings": {}, + "x": 1000, + "y": 1020 + }, + "2": { + "data": { + "advanced": { + "customName": "snow format incident query", + "customNameId": 0, + "description": "This will format a query to look for SNOW incidents over the last 7 days for a entity in a detection", + "join": [], + "note": "You will need to define. the entity in the input block that gets passed into the format we are doing. " + }, + "functionId": 1, + "functionName": "snow_format_incident_query", + "id": "2", + "parameters": [ + "playbook_input:entity" + ], + "template": "cmdb_ci.name={0}^sys_created_onONLast%207%20days\n", + "type": "format" + }, + "errors": {}, + "id": "2", + "type": "format", + "warnings": {}, + "x": 980, + "y": 560 + }, + "3": { + "data": { + "action": "list tickets", + "actionType": "investigate", + "advanced": { + "customName": "list snow tickets", + "customNameId": 0, + "description": "this will list the SNOW Incidents relating to affected entity in the detection. ", + "join": [], + "note": "An entity could be a user, device, application. " + }, + "connector": "ServiceNow", + "connectorConfigs": [ + "servicenow" + ], + "connectorId": "a590c3bc-ca41-4a0e-b063-8066ca868794", + "connectorVersion": "v1", + "functionId": 1, + "functionName": "list_snow_tickets", + "id": "3", + "loop": { + "enabled": false, + "exitAfterUnit": "m", + "exitAfterValue": 10, + "exitConditionEnabled": false, + "exitLoopAfter": 2, + "pauseUnit": "m", + "pauseValue": 2 + }, + "parameters": { + "filter": "snow_format_incident_query:formatted_data", + "max_results": 100, + "table": "incident" + }, + "requiredParameters": [], + "type": "action" + }, + "errors": {}, + "id": "3", + "type": "action", + "warnings": {}, + "x": 980, + "y": 700 + }, + "4": { + "customCode": null, + "data": { + "advanced": { + "customName": "snow incident markdown", + "customNameId": 0, + "description": "This will give you a table of the SNOW incidents related to the detection entity affected.", + "join": [], + "note": "This will be used to output to notes in SOAR Workbook or in ES Notes." + }, + "functionId": 2, + "functionName": "snow_incident_markdown", + "id": "4", + "parameters": [ + "list_snow_tickets:action_result.data.*.number", + "list_snow_tickets:action_result.data.*.short_description", + "list_snow_tickets:action_result.data.*.sys_id", + "list_snow_tickets:artifact:*.severity", + "list_snow_tickets:action_result.data.*.priority", + "list_snow_tickets:action_result.data.*.opened_at", + "list_snow_tickets:action_result.data.*.closed_at" + ], + "template": "#### SNOW Related Incidents\n| Ticket Number | Short Description | ID | Severity | Priority | Opened On | Closed On |\n| --- | --- | --- | --- | --- | --- | --- | \n%%\n| {0} | {1} | {2} | {3} | {4} | {5} | {6} | \n%%", + "type": "format" + }, + "errors": {}, + "id": "4", + "type": "format", + "userCode": null, + "warnings": {}, + "x": 980, + "y": 820 + } + }, + "notes": "This will query your SNOW Incident table looking for incidents that have a cmdb_ci entry that matches your affected entity in the detections from ES or SOAR and give a report of the found SNOW Incidents This is an input playbook that is designed to be added to a larger automation playbook you have that does initial triage of a detection", + "origin": { + "playbook_id": 240, + "playbook_name": "servicenow_query_incidents", + "playbook_repo_id": 2, + "playbook_repo_name": "local" + } + }, + "input_spec": [ + { + "contains": [ + "host name", + "user name" + ], + "description": "If ES fields like risk_object, user, src or dest. If SOAR data source sourceHostName, SourceUserName, destinationHostName, destinationAddress", + "name": "entity" + } + ], + "output_spec": [ + { + "contains": [], + "datapaths": [ + "snow_incident_markdown:formatted_data" + ], + "deduplicate": false, + "description": "This tells you about related SNOW incidents", + "metadata": {}, + "name": "snow_tickets" + } + ], + "playbook_trigger": "artifact_created", + "playbook_type": "data", + "python_version": "3.13", + "schema": "5.0.20", + "version": "6.4.1.361" + }, + "create_time": "2025-08-12T17:57:02.113752+00:00", + "draft_mode": false, + "labels": [ + "*" + ], + "tags": [ + "ServiceNow", + "ticket" + ] +} \ No newline at end of file diff --git a/playbooks/ServiceNow_Query_Incidents.png b/playbooks/ServiceNow_Query_Incidents.png new file mode 100644 index 0000000000..80ffc661dd Binary files /dev/null and b/playbooks/ServiceNow_Query_Incidents.png differ diff --git a/playbooks/ServiceNow_Query_Incidents.py b/playbooks/ServiceNow_Query_Incidents.py new file mode 100644 index 0000000000..2d91460f37 --- /dev/null +++ b/playbooks/ServiceNow_Query_Incidents.py @@ -0,0 +1,148 @@ +""" +This will query your SNOW Incident table looking for incidents that have a cmdb_ci entry that matches your affected entity in the detections from ES or SOAR and give a report of the found SNOW Incidents +""" + + +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 'snow_format_incident_query' block + snow_format_incident_query(container=container) + + return + +@phantom.playbook_block() +def snow_format_incident_query(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, loop_state_json=None, **kwargs): + phantom.debug("snow_format_incident_query() called") + + ################################################################################ + # This will format a query to look for SNOW incidents over the last 7 days for + # a entity in a detection + ################################################################################ + + template = """cmdb_ci.name={0}^sys_created_onONLast%207%20days\n""" + + # parameter list for template variable replacement + parameters = [ + "playbook_input:entity" + ] + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.format(container=container, template=template, parameters=parameters, name="snow_format_incident_query") + + list_snow_tickets(container=container) + + return + + +@phantom.playbook_block() +def list_snow_tickets(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, loop_state_json=None, **kwargs): + phantom.debug("list_snow_tickets() called") + + # phantom.debug('Action: {0} {1}'.format(action['name'], ('SUCCEEDED' if success else 'FAILED'))) + + ################################################################################ + # this will list the SNOW Incidents relating to affected entity in the detection. + # + ################################################################################ + + snow_format_incident_query = phantom.get_format_data(name="snow_format_incident_query") + + parameters = [] + + parameters.append({ + "table": "incident", + "filter": snow_format_incident_query, + "max_results": 100, + }) + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.act("list tickets", parameters=parameters, name="list_snow_tickets", assets=["servicenow"], callback=snow_incident_markdown) + + return + + +@phantom.playbook_block() +def snow_incident_markdown(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, loop_state_json=None, **kwargs): + phantom.debug("snow_incident_markdown() called") + + ################################################################################ + # This will give you a table of the SNOW incidents related to the detection entity + # affected. + ################################################################################ + + template = """#### SNOW Related Incidents\n| Ticket Number | Short Description | ID | Severity | Priority | Opened On | Closed On |\n| --- | --- | --- | --- | --- | --- | --- | \n%%\n| {0} | {1} | {2} | {3} | {4} | {5} | {6} | \n%%""" + + # parameter list for template variable replacement + parameters = [ + "list_snow_tickets:action_result.data.*.number", + "list_snow_tickets:action_result.data.*.short_description", + "list_snow_tickets:action_result.data.*.sys_id", + "list_snow_tickets:artifact:*.severity", + "list_snow_tickets:action_result.data.*.priority", + "list_snow_tickets:action_result.data.*.opened_at", + "list_snow_tickets:action_result.data.*.closed_at" + ] + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.format(container=container, template=template, parameters=parameters, name="snow_incident_markdown") + + return + + +@phantom.playbook_block() +def on_finish(container, summary): + phantom.debug("on_finish() called") + + snow_incident_markdown = phantom.get_format_data(name="snow_incident_markdown") + + output = { + "snow_tickets": snow_incident_markdown, + } + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.save_playbook_output_data(output=output) + + return \ No newline at end of file diff --git a/playbooks/ServiceNow_Query_Incidents.yml b/playbooks/ServiceNow_Query_Incidents.yml new file mode 100644 index 0000000000..1c16db7fbe --- /dev/null +++ b/playbooks/ServiceNow_Query_Incidents.yml @@ -0,0 +1,25 @@ +name: ServiceNow Query Incident +id: 831b2957-3c3c-473a-8e75-b51193af8c79 +version: 1 +date: '2025-08-26' +author: Kenneth Bouchard, Christian Cloutier, Splunk +type: Response +description: "This will query your SNOW Incident table looking for incidents that have a cmdb_ci entry that matches your affected entity in the detections from ES or SOAR and give a report of the found SNOW Incidents." +playbook: ServiceNow_Query_Incident +how_to_implement: This input playbook requires the ServiceNow connector to be configured. It is designed to work with Enterprise Security 8.1 or above and SOAR 6.4.1 and above. Use this to query ServiceNow Incident for related incidents to the entity(risk_object, dest, src, srcip) in the finding. +references: [] +app_list: + - ServiceNow +tags: + platform_tags: + - "ServiceNow" + - "ticket" + playbook_type: Enterprise Security + vpe_type: Modern + playbook_fields: [device] + product: + - Splunk SOAR + - Splunk Enterprise Security + use_cases: + - Response + defend_technique_id: diff --git a/playbooks/ServiceNow_Update_Incident.json b/playbooks/ServiceNow_Update_Incident.json new file mode 100644 index 0000000000..d8b7efa4a0 --- /dev/null +++ b/playbooks/ServiceNow_Update_Incident.json @@ -0,0 +1,816 @@ +{ + "blockly": false, + "blockly_xml": "", + "category": "Uncategorized", + "coa": { + "data": { + "description": "This Playbook will take the output from automation run upstream in the playbook and send a report of that to a SNOW Incident of your choice. ", + "edges": [ + { + "id": "port_2_to_port_3", + "sourceNode": "2", + "sourcePort": "2_out", + "targetNode": "3", + "targetPort": "3_in" + }, + { + "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_5_to_port_6", + "sourceNode": "5", + "sourcePort": "5_out", + "targetNode": "6", + "targetPort": "6_in" + }, + { + "id": "port_8_to_port_1", + "sourceNode": "8", + "sourcePort": "8_out", + "targetNode": "1", + "targetPort": "1_in" + }, + { + "id": "port_6_to_port_7", + "sourceNode": "6", + "sourcePort": "6_out", + "targetNode": "7", + "targetPort": "7_in" + }, + { + "id": "port_0_to_port_9", + "sourceNode": "0", + "sourcePort": "0_out", + "targetNode": "9", + "targetPort": "9_in" + }, + { + "id": "port_10_to_port_11", + "sourceNode": "10", + "sourcePort": "10_out", + "targetNode": "11", + "targetPort": "11_in" + }, + { + "conditions": [ + { + "conditionKey": "condition_key_0", + "index": 0 + } + ], + "id": "port_9_to_port_10", + "sourceNode": "9", + "sourcePort": "9_out", + "targetNode": "10", + "targetPort": "10_in" + }, + { + "id": "port_12_to_port_1", + "sourceNode": "12", + "sourcePort": "12_out", + "targetNode": "1", + "targetPort": "1_in" + }, + { + "conditions": [ + { + "conditionKey": "condition_key_3", + "index": 1 + } + ], + "id": "port_9_to_port_2", + "sourceNode": "9", + "sourcePort": "9_out", + "targetNode": "2", + "targetPort": "2_in" + }, + { + "id": "port_7_to_port_13", + "sourceNode": "7", + "sourcePort": "7_out", + "targetNode": "13", + "targetPort": "13_in" + }, + { + "id": "port_11_to_port_14", + "sourceNode": "11", + "sourcePort": "11_out", + "targetNode": "14", + "targetPort": "14_in" + }, + { + "id": "port_13_to_port_8", + "sourceNode": "13", + "sourcePort": "13_out", + "targetNode": "8", + "targetPort": "8_in" + }, + { + "id": "port_14_to_port_12", + "sourceNode": "14", + "sourcePort": "14_out", + "targetNode": "12", + "targetPort": "12_in" + } + ], + "hash": "acc9c7c08d418f145e56d88c105c97380731ccc3", + "nodes": { + "0": { + "data": { + "advanced": { + "join": [] + }, + "functionName": "on_start", + "id": "0", + "type": "start" + }, + "errors": {}, + "id": "0", + "type": "start", + "warnings": {}, + "x": 200, + "y": -20.000000000000384 + }, + "1": { + "data": { + "advanced": { + "join": [] + }, + "functionName": "on_finish", + "id": "1", + "type": "end" + }, + "errors": {}, + "id": "1", + "type": "end", + "warnings": {}, + "x": 180, + "y": 1800 + }, + "10": { + "data": { + "advanced": { + "customName": "convert note to json2", + "customNameId": 0, + "description": "this takes the Observables from SOAR automation and converts to json fromat that SOAR will accept. ", + "join": [], + "note": "this takes the Observables from SOAR automation and converts to json fromat that SOAR will accept. " + }, + "functionId": 4, + "functionName": "convert_note_to_json2", + "id": "10", + "nestedNodeChildren": [], + "nestedNodeParent": null, + "parameters": [ + "playbook_input:note" + ], + "template": "{{\"comments\":\"{0}\"}}\n", + "type": "format" + }, + "errors": {}, + "id": "10", + "type": "format", + "warnings": {}, + "x": 340, + "y": 1080 + }, + "11": { + "data": { + "advanced": { + "customName": "string remove crlf 2", + "customNameId": 0, + "description": "Remove any \\n characters from the input string", + "join": [], + "note": "Remove any \\n characters from the input string" + }, + "customFunction": { + "draftMode": false, + "name": "string_remove_crlf", + "repoName": "local" + }, + "functionId": 2, + "functionName": "string_remove_crlf_2", + "id": "11", + "loop": { + "enabled": false, + "exitAfterUnit": "m", + "exitAfterValue": 10, + "exitConditionEnabled": false, + "exitLoopAfter": 2, + "pauseUnit": "m", + "pauseValue": 2 + }, + "nestedNodeChildren": [], + "nestedNodeParent": null, + "selectMore": false, + "type": "utility", + "utilities": { + "string_remove_crlf": { + "description": "Sanitize the provided string to remove carriage return/line feed characters (CR/LF, \\r \\n).", + "fields": [ + { + "dataTypes": [], + "description": "The string to remove CR/LF characters from.", + "inputType": "item", + "label": "input_string", + "name": "input_string", + "placeholder": "string to sanitize", + "renderType": "datapath", + "required": false + } + ], + "label": "string_remove_crlf", + "name": "string_remove_crlf" + } + }, + "utilityType": "custom_function", + "values": { + "string_remove_crlf": { + "input_string": "convert_note_to_json2:formatted_data" + } + } + }, + "errors": {}, + "id": "11", + "type": "utility", + "warnings": {}, + "x": 340, + "y": 1260 + }, + "12": { + "data": { + "action": "update ticket", + "actionType": "generic", + "advanced": { + "customName": "update servicenow incident 2", + "customNameId": 0, + "description": "This will input the note that was configured to be sent from parent playbook in the start block to the Service Now Incident. ", + "join": [], + "note": "This will input the note that was configured to be sent from parent playbook in the start block to the Service Now Incident. " + }, + "connector": "ServiceNow", + "connectorConfigs": [ + "servicenow" + ], + "connectorId": "a590c3bc-ca41-4a0e-b063-8066ca868794", + "connectorVersion": "v1", + "functionId": 2, + "functionName": "update_servicenow_incident_2", + "id": "12", + "loop": { + "enabled": false, + "exitAfterUnit": "m", + "exitAfterValue": 10, + "exitConditionEnabled": false, + "exitLoopAfter": 2, + "pauseUnit": "m", + "pauseValue": 2 + }, + "nestedNodeChildren": [], + "nestedNodeParent": null, + "parameters": { + "fields": { + "functionId": 2, + "parameters": [ + "stiring_uri_decode2:custom_function_result.data.decoded_string" + ], + "template": "{0}\n" + }, + "id": "playbook_input:snow_incident", + "table": "incident" + }, + "requiredParameters": [ + { + "data_type": "string", + "field": "id" + } + ], + "type": "action" + }, + "errors": {}, + "id": "12", + "type": "action", + "warnings": {}, + "x": 340, + "y": 1580 + }, + "13": { + "data": { + "advanced": { + "customName": "string uri decode", + "customNameId": 0, + "description": "Remove any uri encoding", + "join": [], + "note": "Remove any uri encoding" + }, + "customFunction": { + "draftMode": false, + "name": "string_uri_decode", + "repoName": "local" + }, + "functionId": 3, + "functionName": "string_uri_decode", + "id": "13", + "loop": { + "enabled": false, + "exitAfterUnit": "m", + "exitAfterValue": 10, + "exitConditionEnabled": false, + "exitLoopAfter": 2, + "pauseUnit": "m", + "pauseValue": 2 + }, + "selectMore": false, + "type": "utility", + "utilities": { + "string_uri_decode": { + "description": "Decodes a URI-encoded string to a plain text string.", + "fields": [ + { + "dataTypes": [], + "description": "The URI encoded string to decode", + "inputType": "item", + "label": "input_string", + "name": "input_string", + "placeholder": "The URI encoded string to decode", + "renderType": "datapath", + "required": false + } + ], + "label": "string_uri_decode", + "name": "string_uri_decode" + } + }, + "utilityType": "custom_function", + "values": { + "string_uri_decode": { + "input_string": "string_remove_crlf:custom_function_result.data.sanitized_string" + } + } + }, + "errors": {}, + "id": "13", + "type": "utility", + "warnings": {}, + "x": 1.4210854715202004e-14, + "y": 1460 + }, + "14": { + "data": { + "advanced": { + "customName": "stiring uri decode2", + "customNameId": 0, + "description": "Remove any uri encoding", + "join": [], + "note": "Remove any uri encoding" + }, + "customFunction": { + "draftMode": false, + "name": "string_uri_decode", + "repoName": "local" + }, + "functionId": 4, + "functionName": "stiring_uri_decode2", + "id": "14", + "loop": { + "enabled": false, + "exitAfterUnit": "m", + "exitAfterValue": 10, + "exitConditionEnabled": false, + "exitLoopAfter": 2, + "pauseUnit": "m", + "pauseValue": 2 + }, + "selectMore": false, + "type": "utility", + "utilities": { + "string_uri_decode": { + "description": "Decodes a URI-encoded string to a plain text string.", + "fields": [ + { + "dataTypes": [], + "description": "The URI encoded string to decode", + "inputType": "item", + "label": "input_string", + "name": "input_string", + "placeholder": "The URI encoded string to decode", + "renderType": "datapath", + "required": false + } + ], + "label": "string_uri_decode", + "name": "string_uri_decode" + } + }, + "utilityType": "custom_function", + "values": { + "string_uri_decode": { + "input_string": "string_remove_crlf_2:custom_function_result.data.sanitized_string" + } + } + }, + "errors": {}, + "id": "14", + "type": "utility", + "warnings": {}, + "x": 340, + "y": 1460 + }, + "2": { + "data": { + "advanced": { + "customName": "snow format incident query", + "customNameId": 0, + "description": "This will format a query to look for SNOW incidents over the last 7 days for a entity in a detection", + "join": [], + "note": "You will need to define. the entity in the input block that gets passed into the format we are doing. " + }, + "functionId": 1, + "functionName": "snow_format_incident_query", + "id": "2", + "nestedNodeChildren": [], + "nestedNodeParent": null, + "parameters": [ + "playbook_input:entity" + ], + "template": "cmdb_ci.name={0}^sys_created_onONLast%207%20days\n", + "type": "format" + }, + "errors": {}, + "id": "2", + "type": "format", + "warnings": {}, + "x": 1.4210854715202004e-14, + "y": 320 + }, + "3": { + "data": { + "action": "list tickets", + "actionType": "investigate", + "advanced": { + "customName": "list snow tickets", + "customNameId": 0, + "description": "this will list the SNOW Incidents relating to affected entity in the detection. ", + "join": [], + "note": "An entity could be a user, device, application. " + }, + "connector": "ServiceNow", + "connectorConfigs": [ + "servicenow" + ], + "connectorId": "a590c3bc-ca41-4a0e-b063-8066ca868794", + "connectorVersion": "v1", + "functionId": 1, + "functionName": "list_snow_tickets", + "id": "3", + "loop": { + "enabled": false, + "exitAfterUnit": "m", + "exitAfterValue": 10, + "exitConditionEnabled": false, + "exitLoopAfter": 2, + "pauseUnit": "m", + "pauseValue": 2 + }, + "nestedNodeChildren": [], + "nestedNodeParent": null, + "parameters": { + "filter": "snow_format_incident_query:formatted_data", + "max_results": 100, + "table": "incident" + }, + "requiredParameters": [], + "type": "action" + }, + "errors": {}, + "id": "3", + "type": "action", + "warnings": {}, + "x": 1.4210854715202004e-14, + "y": 520 + }, + "4": { + "data": { + "advanced": { + "customName": "snow incident markdown", + "customNameId": 0, + "description": "This will give you a table of the SNOW incidents related to the detection entity affected.", + "join": [], + "note": "This will be used to output to notes in SOAR Workbook or in ES Notes." + }, + "functionId": 2, + "functionName": "snow_incident_markdown", + "id": "4", + "nestedNodeChildren": [], + "nestedNodeParent": null, + "parameters": [ + "list_snow_tickets:action_result.data.*.number", + "list_snow_tickets:action_result.data.*.short_description", + "list_snow_tickets:action_result.data.*.sys_id", + "list_snow_tickets:artifact:*.severity", + "list_snow_tickets:action_result.data.*.priority", + "list_snow_tickets:action_result.data.*.opened_at", + "list_snow_tickets:action_result.data.*.closed_at" + ], + "template": "#### SNOW Related Incidents\n| Ticket Number | Short Description | ID | Severity | Priority | Opened On | Closed On |\n| --- | --- | --- | --- | --- | --- | --- | \n%%\n| {0} | {1} | {2} | {3} | {4} | {5} | {6} | \n%%", + "type": "format" + }, + "errors": {}, + "id": "4", + "type": "format", + "userCode": null, + "warnings": {}, + "x": -19.999999999999986, + "y": 700 + }, + "5": { + "data": { + "advanced": { + "customName": "input snow incident", + "customNameId": 0, + "description": "this will operate two queries. \n1. Query ES or SOAR for Snow Incident in the investigation or case already documented\n2. Query the SNOW incidents \n\nThen prompt our user that submitted the automation to input the SNOW incident they want to update. ", + "join": [], + "note": "You can only input 1 incident." + }, + "approver": { + "type": "user", + "value": "launching_user" + }, + "functionId": 1, + "functionName": "input_snow_incident", + "id": "5", + "message": "Associated Service Now Incidents\n{0}", + "parameters": [ + "snow_incident_markdown:formatted_data", + "playbook_input:snow_incident" + ], + "responseTime": 30, + "responses": [ + { + "required": true, + "responsePrompt": "Add Snow Incident number", + "responseType": "message" + } + ], + "type": "prompt" + }, + "errors": {}, + "id": "5", + "type": "prompt", + "warnings": {}, + "x": 1.4210854715202004e-14, + "y": 880 + }, + "6": { + "data": { + "advanced": { + "customName": "convert note to json", + "customNameId": 0, + "description": "this takes the Observables from SOAR automation and converts to json fromat that SOAR will accept. ", + "join": [], + "note": "this takes the Observables from SOAR automation and converts to json fromat that SOAR will accept. " + }, + "functionId": 3, + "functionName": "convert_note_to_json", + "id": "6", + "parameters": [ + "playbook_input:note" + ], + "template": "{{\"comments\":\"{0}\"}}\n", + "type": "format" + }, + "errors": {}, + "id": "6", + "type": "format", + "warnings": {}, + "x": 1.4210854715202004e-14, + "y": 1080 + }, + "7": { + "data": { + "advanced": { + "customName": "string remove crlf", + "customNameId": 0, + "description": "Remove any \\n characters from the input string", + "join": [], + "note": "Remove any \\n characters from the input string" + }, + "customFunction": { + "draftMode": false, + "name": "string_remove_crlf", + "repoName": "local" + }, + "functionId": 1, + "functionName": "string_remove_crlf", + "id": "7", + "loop": { + "enabled": false, + "exitAfterUnit": "m", + "exitAfterValue": 10, + "exitConditionEnabled": false, + "exitLoopAfter": 2, + "pauseUnit": "m", + "pauseValue": 2 + }, + "selectMore": false, + "type": "utility", + "utilities": { + "string_remove_crlf": { + "description": "Sanitize the provided string to remove carriage return/line feed characters (CR/LF, \\r \\n).", + "fields": [ + { + "dataTypes": [], + "description": "The string to remove CR/LF characters from.", + "inputType": "item", + "label": "input_string", + "name": "input_string", + "placeholder": "string to sanitize", + "renderType": "datapath", + "required": false + } + ], + "label": "string_remove_crlf", + "name": "string_remove_crlf" + } + }, + "utilityType": "custom_function", + "values": { + "string_remove_crlf": { + "input_string": "convert_note_to_json:formatted_data" + } + } + }, + "errors": {}, + "id": "7", + "type": "utility", + "warnings": {}, + "x": 1.4210854715202004e-14, + "y": 1240 + }, + "8": { + "data": { + "action": "update ticket", + "actionType": "generic", + "advanced": { + "customName": "update servicenow incident", + "customNameId": 0, + "description": "This will input the note that was configured to be sent from parent playbook in the start block to the Service Now Incident. ", + "join": [], + "note": "This will input the note that was configured to be sent from parent playbook in the start block to the Service Now Incident. " + }, + "connector": "ServiceNow", + "connectorConfigs": [ + "servicenow" + ], + "connectorId": "a590c3bc-ca41-4a0e-b063-8066ca868794", + "connectorVersion": "v1", + "functionId": 1, + "functionName": "update_servicenow_incident", + "id": "8", + "loop": { + "enabled": false, + "exitAfterUnit": "m", + "exitAfterValue": 10, + "exitConditionEnabled": false, + "exitLoopAfter": 2, + "pauseUnit": "m", + "pauseValue": 2 + }, + "parameters": { + "fields": { + "functionId": 1, + "parameters": [ + "string_uri_decode:custom_function_result.data.decoded_string" + ], + "template": "{0}\n" + }, + "id": "input_snow_incident:action_result.summary.responses.0", + "table": "incident" + }, + "requiredParameters": [ + { + "data_type": "string", + "field": "id" + } + ], + "type": "action" + }, + "errors": {}, + "id": "8", + "type": "action", + "warnings": {}, + "x": 1.4210854715202004e-14, + "y": 1580 + }, + "9": { + "data": { + "advanced": { + "customName": "check local snow incident", + "customNameId": 0, + "description": "Looks for snow_incident field", + "join": [], + "note": "Looks for snow_incident field" + }, + "conditions": [ + { + "comparisons": [ + { + "conditionIndex": 0, + "op": "!=", + "param": "playbook_input:snow_incident", + "value": "" + } + ], + "conditionIndex": 0, + "conditionKey": "condition_key_0", + "customName": "local snow incident", + "display": "If", + "logic": "and", + "type": "if" + }, + { + "comparisons": [ + { + "conditionIndex": 1, + "op": "==", + "param": "", + "value": "" + } + ], + "conditionIndex": 1, + "conditionKey": "condition_key_3", + "customName": "run snow query", + "display": "Else", + "logic": "and", + "type": "else" + } + ], + "functionId": 1, + "functionName": "check_local_snow_incident", + "id": "9", + "type": "decision" + }, + "errors": {}, + "id": "9", + "type": "decision", + "warnings": {}, + "x": 260, + "y": 140 + } + }, + "notes": "This is an input playbook that is designed to be added to a larger automation playbook you have that does initial triage of a detection.", + "origin": { + "playbook_id": 243, + "playbook_name": "servicenow_update_incident", + "playbook_repo_id": 2, + "playbook_repo_name": "local" + } + }, + "input_spec": [ + { + "contains": [ + "host name", + "user name" + ], + "description": "", + "name": "entity" + }, + { + "contains": [], + "description": "", + "name": "note" + }, + { + "contains": [], + "description": "", + "name": "snow_incident" + } + ], + "output_spec": null, + "playbook_trigger": "artifact_created", + "playbook_type": "data", + "python_version": "3.13", + "schema": "5.0.20", + "version": "6.4.1.361" + }, + "create_time": "2025-08-20T19:07:43.098342+00:00", + "draft_mode": false, + "labels": [ + "*" + ], + "tags": [ + "ServiceNow", + "ticket" + ] +} \ No newline at end of file diff --git a/playbooks/ServiceNow_Update_Incident.png b/playbooks/ServiceNow_Update_Incident.png new file mode 100644 index 0000000000..e5c0ea63a4 Binary files /dev/null and b/playbooks/ServiceNow_Update_Incident.png differ diff --git a/playbooks/ServiceNow_Update_Incident.py b/playbooks/ServiceNow_Update_Incident.py new file mode 100644 index 0000000000..f9cc034252 --- /dev/null +++ b/playbooks/ServiceNow_Update_Incident.py @@ -0,0 +1,505 @@ +""" +This Playbook will take the output from automation run upstream in the playbook and send a report of that to a SNOW Incident of your choice. +""" + + +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 'check_local_snow_incident' block + check_local_snow_incident(container=container) + + return + +@phantom.playbook_block() +def snow_format_incident_query(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, loop_state_json=None, **kwargs): + phantom.debug("snow_format_incident_query() called") + + ################################################################################ + # This will format a query to look for SNOW incidents over the last 7 days for + # a entity in a detection + ################################################################################ + + template = """cmdb_ci.name={0}^sys_created_onONLast%207%20days\n""" + + # parameter list for template variable replacement + parameters = [ + "playbook_input:entity" + ] + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.format(container=container, template=template, parameters=parameters, name="snow_format_incident_query") + + list_snow_tickets(container=container) + + return + + +@phantom.playbook_block() +def list_snow_tickets(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, loop_state_json=None, **kwargs): + phantom.debug("list_snow_tickets() called") + + # phantom.debug('Action: {0} {1}'.format(action['name'], ('SUCCEEDED' if success else 'FAILED'))) + + ################################################################################ + # this will list the SNOW Incidents relating to affected entity in the detection. + # + ################################################################################ + + snow_format_incident_query = phantom.get_format_data(name="snow_format_incident_query") + + parameters = [] + + parameters.append({ + "table": "incident", + "filter": snow_format_incident_query, + "max_results": 100, + }) + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.act("list tickets", parameters=parameters, name="list_snow_tickets", assets=["servicenow"], callback=snow_incident_markdown) + + return + + +@phantom.playbook_block() +def snow_incident_markdown(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, loop_state_json=None, **kwargs): + phantom.debug("snow_incident_markdown() called") + + ################################################################################ + # This will give you a table of the SNOW incidents related to the detection entity + # affected. + ################################################################################ + + template = """#### SNOW Related Incidents\n| Ticket Number | Short Description | ID | Severity | Priority | Opened On | Closed On |\n| --- | --- | --- | --- | --- | --- | --- | \n%%\n| {0} | {1} | {2} | {3} | {4} | {5} | {6} | \n%%""" + + # parameter list for template variable replacement + parameters = [ + "list_snow_tickets:action_result.data.*.number", + "list_snow_tickets:action_result.data.*.short_description", + "list_snow_tickets:action_result.data.*.sys_id", + "list_snow_tickets:artifact:*.severity", + "list_snow_tickets:action_result.data.*.priority", + "list_snow_tickets:action_result.data.*.opened_at", + "list_snow_tickets:action_result.data.*.closed_at" + ] + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.format(container=container, template=template, parameters=parameters, name="snow_incident_markdown") + + input_snow_incident(container=container) + + return + + +@phantom.playbook_block() +def input_snow_incident(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, loop_state_json=None, **kwargs): + phantom.debug("input_snow_incident() called") + + ################################################################################ + # this will operate two queries. + # 1. Query ES or SOAR for Snow Incident in the investigation or case already documented + # 2. Query the SNOW incidents + # + # Then prompt our user that submitted the automation to input the SNOW incident + # they want to update. + ################################################################################ + + # set approver and message variables for phantom.prompt call + + user = phantom.collect2(container=container, datapath=["playbook:launching_user.name"])[0][0] + role = None + message = """Associated Service Now Incidents\n{0}""" + + # parameter list for template variable replacement + parameters = [ + "snow_incident_markdown:formatted_data", + "playbook_input:snow_incident" + ] + + # responses + response_types = [ + { + "prompt": "Add Snow Incident number", + "options": { + "type": "message", + "required": True, + }, + } + ] + + phantom.prompt2(container=container, user=user, role=role, message=message, respond_in_mins=30, name="input_snow_incident", parameters=parameters, response_types=response_types, callback=convert_note_to_json) + + return + + +@phantom.playbook_block() +def convert_note_to_json(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, loop_state_json=None, **kwargs): + phantom.debug("convert_note_to_json() called") + + ################################################################################ + # this takes the Observables from SOAR automation and converts to json fromat + # that SOAR will accept. + ################################################################################ + + template = """{{\"comments\":\"{0}\"}}\n""" + + # parameter list for template variable replacement + parameters = [ + "playbook_input:note" + ] + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.format(container=container, template=template, parameters=parameters, name="convert_note_to_json") + + string_remove_crlf(container=container) + + return + + +@phantom.playbook_block() +def string_remove_crlf(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, loop_state_json=None, **kwargs): + phantom.debug("string_remove_crlf() called") + + ################################################################################ + # Remove any \n characters from the input string + ################################################################################ + + convert_note_to_json = phantom.get_format_data(name="convert_note_to_json") + + parameters = [] + + parameters.append({ + "input_string": convert_note_to_json, + }) + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.custom_function(custom_function="local/string_remove_crlf", parameters=parameters, name="string_remove_crlf", callback=string_uri_decode) + + return + + +@phantom.playbook_block() +def update_servicenow_incident(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, loop_state_json=None, **kwargs): + phantom.debug("update_servicenow_incident() called") + + # phantom.debug('Action: {0} {1}'.format(action['name'], ('SUCCEEDED' if success else 'FAILED'))) + + fields_formatted_string = phantom.format( + container=container, + template="""{0}\n""", + parameters=[ + "string_uri_decode:custom_function_result.data.decoded_string" + ]) + + ################################################################################ + # This will input the note that was configured to be sent from parent playbook + # in the start block to the Service Now Incident. + ################################################################################ + + input_snow_incident_result_data = phantom.collect2(container=container, datapath=["input_snow_incident:action_result.summary.responses.0","input_snow_incident:action_result.parameter.context.artifact_id"], action_results=results) + string_uri_decode__result = phantom.collect2(container=container, datapath=["string_uri_decode:custom_function_result.data.decoded_string"]) + + parameters = [] + + # build parameters list for 'update_servicenow_incident' call + for input_snow_incident_result_item in input_snow_incident_result_data: + for string_uri_decode__result_item in string_uri_decode__result: + if input_snow_incident_result_item[0] is not None: + parameters.append({ + "id": input_snow_incident_result_item[0], + "table": "incident", + "fields": fields_formatted_string, + "context": {'artifact_id': input_snow_incident_result_item[1]}, + }) + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.act("update ticket", parameters=parameters, name="update_servicenow_incident", assets=["servicenow"]) + + return + + +@phantom.playbook_block() +def check_local_snow_incident(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, loop_state_json=None, **kwargs): + phantom.debug("check_local_snow_incident() called") + + ################################################################################ + # Looks for snow_incident field + ################################################################################ + + # check for 'if' condition 1 + found_match_1 = phantom.decision( + container=container, + conditions=[ + ["playbook_input:snow_incident", "!=", ""] + ], + conditions_dps=[ + ["playbook_input:snow_incident", "!=", ""] + ], + name="check_local_snow_incident:condition_1", + delimiter=None) + + # call connected blocks if condition 1 matched + if found_match_1: + convert_note_to_json2(action=action, success=success, container=container, results=results, handle=handle) + return + + # check for 'else' condition 2 + snow_format_incident_query(action=action, success=success, container=container, results=results, handle=handle) + + return + + +@phantom.playbook_block() +def convert_note_to_json2(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, loop_state_json=None, **kwargs): + phantom.debug("convert_note_to_json2() called") + + ################################################################################ + # this takes the Observables from SOAR automation and converts to json fromat + # that SOAR will accept. + ################################################################################ + + template = """{{\"comments\":\"{0}\"}}\n""" + + # parameter list for template variable replacement + parameters = [ + "playbook_input:note" + ] + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.format(container=container, template=template, parameters=parameters, name="convert_note_to_json2") + + string_remove_crlf_2(container=container) + + return + + +@phantom.playbook_block() +def string_remove_crlf_2(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, loop_state_json=None, **kwargs): + phantom.debug("string_remove_crlf_2() called") + + ################################################################################ + # Remove any \n characters from the input string + ################################################################################ + + convert_note_to_json2 = phantom.get_format_data(name="convert_note_to_json2") + + parameters = [] + + parameters.append({ + "input_string": convert_note_to_json2, + }) + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.custom_function(custom_function="local/string_remove_crlf", parameters=parameters, name="string_remove_crlf_2", callback=stiring_uri_decode2) + + return + + +@phantom.playbook_block() +def update_servicenow_incident_2(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, loop_state_json=None, **kwargs): + phantom.debug("update_servicenow_incident_2() called") + + # phantom.debug('Action: {0} {1}'.format(action['name'], ('SUCCEEDED' if success else 'FAILED'))) + + fields_formatted_string = phantom.format( + container=container, + template="""{0}\n""", + parameters=[ + "stiring_uri_decode2:custom_function_result.data.decoded_string" + ]) + + ################################################################################ + # This will input the note that was configured to be sent from parent playbook + # in the start block to the Service Now Incident. + ################################################################################ + + playbook_input_snow_incident = phantom.collect2(container=container, datapath=["playbook_input:snow_incident"]) + stiring_uri_decode2__result = phantom.collect2(container=container, datapath=["stiring_uri_decode2:custom_function_result.data.decoded_string"]) + + parameters = [] + + # build parameters list for 'update_servicenow_incident_2' call + for playbook_input_snow_incident_item in playbook_input_snow_incident: + for stiring_uri_decode2__result_item in stiring_uri_decode2__result: + if playbook_input_snow_incident_item[0] is not None: + parameters.append({ + "id": playbook_input_snow_incident_item[0], + "table": "incident", + "fields": fields_formatted_string, + }) + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.act("update ticket", parameters=parameters, name="update_servicenow_incident_2", assets=["servicenow"]) + + return + + +@phantom.playbook_block() +def string_uri_decode(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, loop_state_json=None, **kwargs): + phantom.debug("string_uri_decode() called") + + ################################################################################ + # Remove any uri encoding + ################################################################################ + + string_remove_crlf__result = phantom.collect2(container=container, datapath=["string_remove_crlf:custom_function_result.data.sanitized_string"]) + + parameters = [] + + # build parameters list for 'string_uri_decode' call + for string_remove_crlf__result_item in string_remove_crlf__result: + parameters.append({ + "input_string": string_remove_crlf__result_item[0], + }) + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.custom_function(custom_function="local/string_uri_decode", parameters=parameters, name="string_uri_decode", callback=update_servicenow_incident) + + return + + +@phantom.playbook_block() +def stiring_uri_decode2(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, loop_state_json=None, **kwargs): + phantom.debug("stiring_uri_decode2() called") + + ################################################################################ + # Remove any uri encoding + ################################################################################ + + string_remove_crlf_2__result = phantom.collect2(container=container, datapath=["string_remove_crlf_2:custom_function_result.data.sanitized_string"]) + + parameters = [] + + # build parameters list for 'stiring_uri_decode2' call + for string_remove_crlf_2__result_item in string_remove_crlf_2__result: + parameters.append({ + "input_string": string_remove_crlf_2__result_item[0], + }) + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.custom_function(custom_function="local/string_uri_decode", parameters=parameters, name="stiring_uri_decode2", callback=update_servicenow_incident_2) + + return + + +@phantom.playbook_block() +def on_finish(container, summary): + phantom.debug("on_finish() called") + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + return \ No newline at end of file diff --git a/playbooks/ServiceNow_Update_Incident.yml b/playbooks/ServiceNow_Update_Incident.yml new file mode 100644 index 0000000000..f9c26ad58f --- /dev/null +++ b/playbooks/ServiceNow_Update_Incident.yml @@ -0,0 +1,25 @@ +name: ServiceNow Update Incident +id: 831b2957-3c3c-473a-8e75-b51193af8c79 +version: 1 +date: '2025-08-26' +author: Kenneth Bouchard, Christian Cloutier, Splunk +type: Response +description: "This Playbook will take the output from automation run upstream in the playbook and send a report of that to a SNOW Incident of your choice." +playbook: ServiceNow_Update_Incident +how_to_implement: This input playbook requires the ServiceNow connector to be configured. It is designed to work with Enterprise Security 8.1 or above and SOAR 6.4.1 and above. Use this to send the output of your playbooks to ServiceNow Incident created during investigation or one queried in ServiceNow. +references: [] +app_list: + - ServiceNow +tags: + platform_tags: + - "ServiceNow" + - "ticket" + playbook_type: Enterprise Security + vpe_type: Modern + playbook_fields: [device] + product: + - Splunk SOAR + - Splunk Enterprise Security + use_cases: + - Response + defend_technique_id: diff --git a/playbooks/ServiceNow_Update_Incident_EsNotes.json b/playbooks/ServiceNow_Update_Incident_EsNotes.json new file mode 100644 index 0000000000..286ea23012 --- /dev/null +++ b/playbooks/ServiceNow_Update_Incident_EsNotes.json @@ -0,0 +1,478 @@ +{ + "blockly": false, + "blockly_xml": "", + "category": "Uncategorized", + "coa": { + "data": { + "description": "This ES Playbook will be added as the last phase in a response plan to send all the notes from an investigation to the SNOW incident created earlier in investigation.", + "edges": [ + { + "id": "port_0_to_port_3", + "sourceNode": "0", + "sourcePort": "0_out", + "targetNode": "3", + "targetPort": "3_in" + }, + { + "id": "port_4_to_port_5", + "sourceNode": "4", + "sourcePort": "4_out", + "targetNode": "5", + "targetPort": "5_in" + }, + { + "id": "port_6_to_port_1", + "sourceNode": "6", + "sourcePort": "6_out", + "targetNode": "1", + "targetPort": "1_in" + }, + { + "id": "port_5_to_port_7", + "sourceNode": "5", + "sourcePort": "5_out", + "targetNode": "7", + "targetPort": "7_in" + }, + { + "id": "port_7_to_port_6", + "sourceNode": "7", + "sourcePort": "7_out", + "targetNode": "6", + "targetPort": "6_in" + }, + { + "id": "port_9_to_port_11", + "sourceNode": "9", + "sourcePort": "9_out", + "targetNode": "11", + "targetPort": "11_in" + }, + { + "id": "port_11_to_port_4", + "sourceNode": "11", + "sourcePort": "11_out", + "targetNode": "4", + "targetPort": "4_in" + }, + { + "id": "port_3_to_port_9", + "sourceNode": "3", + "sourcePort": "3_out", + "targetNode": "9", + "targetPort": "9_in" + } + ], + "hash": "dd470fed9604cfc368f2e7500c17fe8d4fe8051f", + "nodes": { + "0": { + "data": { + "advanced": { + "join": [] + }, + "functionName": "on_start", + "id": "0", + "type": "start" + }, + "errors": { + "input_spec": [ + { + "name": "Name is required" + } + ] + }, + "id": "0", + "type": "start", + "warnings": {}, + "x": 19.999999999999986, + "y": -2.5579538487363607e-13 + }, + "1": { + "data": { + "advanced": { + "join": [] + }, + "functionName": "on_finish", + "id": "1", + "type": "end" + }, + "errors": {}, + "id": "1", + "type": "end", + "warnings": {}, + "x": 19.999999999999986, + "y": 1382 + }, + "11": { + "data": { + "advanced": { + "customName": "loop convert to html", + "customNameId": 0, + "description": "This block loops thru the items in the ES notes and converts the ES data to HTML which for service now looks better. ", + "join": [], + "note": "This block converts the ES data to HTML which for service now looks better. " + }, + "functionId": 3, + "functionName": "loop_convert_to_html", + "id": "11", + "parameters": [ + "list_demux:custom_function_result.data.output.content", + "list_demux:custom_function_result.data.output.title", + "list_demux:custom_function_result.data.output.create_time", + "list_demux:custom_function_result.data.output.author.realname", + "list_demux:custom_function_result.data.output.response_plan_info.response_task.name" + ], + "template": "[code]\n

ES Notes

\n%%\nTask {4}\n
Author {3}\n
Created Time {2}\n
title {1}\n
content {0}\n

\n%%\n[/code]", + "type": "format" + }, + "errors": {}, + "id": "11", + "type": "format", + "warnings": {}, + "x": 1.4210854715202004e-14, + "y": 480 + }, + "3": { + "data": { + "action": "get notes in finding or investigation", + "actionType": "get", + "advanced": { + "customName": "finding and investigation notes", + "customNameId": 0, + "description": "This will retrieve that notes from your investigation. Not only the General notes but any notes in Response plans also. ", + "join": [], + "note": "This will retrieve that notes from your investigation. Not only the General notes but any notes in Response plans also. " + }, + "connector": "Enterprise Security", + "connectorConfigs": [ + "builtin_mc_connector" + ], + "connectorId": "7e971d03-6ae7-4429-b4e1-cf461003e75b", + "connectorVersion": "v1", + "functionId": 3, + "functionName": "finding_and_investigation_notes", + "id": "3", + "loop": { + "enabled": false, + "exitAfterUnit": "m", + "exitAfterValue": 10, + "exitConditionEnabled": false, + "exitLoopAfter": 2, + "pauseUnit": "m", + "pauseValue": 2 + }, + "parameters": { + "id": "finding:id" + }, + "requiredParameters": [ + { + "data_type": "string", + "field": "id" + } + ], + "type": "enterpriseSecurity" + }, + "errors": {}, + "id": "3", + "type": "enterpriseSecurity", + "warnings": {}, + "x": 1.4210854715202004e-14, + "y": 148 + }, + "4": { + "data": { + "advanced": { + "customName": "convert note to json", + "customNameId": 0, + "description": "this takes the Observables from SOAR automation and converts to json fromat that SOAR will accept. ", + "join": [], + "note": "this takes the Observables from SOAR automation and converts to json fromat that SOAR will accept. " + }, + "functionId": 1, + "functionName": "convert_note_to_json", + "id": "4", + "nestedNodeChildren": [], + "nestedNodeParent": null, + "parameters": [ + "loop_convert_to_html:formatted_data.*" + ], + "template": "{{\"comments\":\"{0}\"}}\n", + "type": "format" + }, + "errors": {}, + "id": "4", + "type": "format", + "warnings": {}, + "x": 1.4210854715202004e-14, + "y": 662 + }, + "5": { + "data": { + "advanced": { + "customName": "string remove crlf", + "customNameId": 0, + "description": "Remove any \\n characters from the input string", + "join": [], + "note": "Remove any \\n characters from the input string" + }, + "customFunction": { + "draftMode": false, + "name": "string_remove_crlf", + "repoName": "local" + }, + "functionId": 1, + "functionName": "string_remove_crlf", + "id": "5", + "loop": { + "enabled": false, + "exitAfterUnit": "m", + "exitAfterValue": 10, + "exitConditionEnabled": false, + "exitLoopAfter": 2, + "pauseUnit": "m", + "pauseValue": 2 + }, + "nestedNodeChildren": [], + "nestedNodeParent": null, + "selectMore": false, + "type": "utility", + "utilities": { + "string_remove_crlf": { + "description": "Sanitize the provided string to remove carriage return/line feed characters (CR/LF, \\r \\n).", + "fields": [ + { + "dataTypes": [], + "description": "The string to remove CR/LF characters from.", + "inputType": "item", + "label": "input_string", + "name": "input_string", + "placeholder": "string to sanitize", + "renderType": "datapath", + "required": false + } + ], + "label": "string_remove_crlf", + "name": "string_remove_crlf" + } + }, + "utilityType": "custom_function", + "values": { + "string_remove_crlf": { + "input_string": "convert_note_to_json:formatted_data" + } + } + }, + "errors": {}, + "id": "5", + "type": "utility", + "warnings": {}, + "x": 1.4210854715202004e-14, + "y": 838 + }, + "6": { + "data": { + "action": "update ticket", + "actionType": "generic", + "advanced": { + "customName": "update servicenow incident", + "customNameId": 0, + "description": "This will input the note that was configured to be sent from parent playbook in the start block to the Service Now Incident. ", + "join": [], + "note": "This will input the note that was configured to be sent from parent playbook in the start block to the Service Now Incident. " + }, + "connector": "ServiceNow", + "connectorConfigs": [ + "servicenow" + ], + "connectorId": "a590c3bc-ca41-4a0e-b063-8066ca868794", + "connectorVersion": "v1", + "functionId": 1, + "functionName": "update_servicenow_incident", + "id": "6", + "loop": { + "enabled": false, + "exitAfterUnit": "m", + "exitAfterValue": 10, + "exitConditionEnabled": false, + "exitLoopAfter": 2, + "pauseUnit": "m", + "pauseValue": 2 + }, + "nestedNodeChildren": [], + "nestedNodeParent": null, + "parameters": { + "fields": { + "functionId": 1, + "parameters": [ + "string_uri_decode:custom_function_result.data.decoded_string" + ], + "template": "{0}\n" + }, + "id": "finding:custom_fields.snow_incident", + "table": "incident" + }, + "requiredParameters": [ + { + "data_type": "string", + "field": "id" + } + ], + "type": "action" + }, + "errors": {}, + "id": "6", + "type": "action", + "warnings": {}, + "x": 1.4210854715202004e-14, + "y": 1200 + }, + "7": { + "data": { + "advanced": { + "customName": "string uri decode", + "customNameId": 0, + "description": "Remove any uri encoding", + "join": [], + "note": "Remove any uri encoding" + }, + "customFunction": { + "draftMode": false, + "name": "string_uri_decode", + "repoName": "local" + }, + "functionId": 2, + "functionName": "string_uri_decode", + "id": "7", + "loop": { + "enabled": false, + "exitAfterUnit": "m", + "exitAfterValue": 10, + "exitConditionEnabled": false, + "exitLoopAfter": 2, + "pauseUnit": "m", + "pauseValue": 2 + }, + "selectMore": false, + "type": "utility", + "utilities": { + "string_uri_decode": { + "description": "Decodes a URI-encoded string to a plain text string.", + "fields": [ + { + "dataTypes": [], + "description": "The URI encoded string to decode", + "inputType": "item", + "label": "input_string", + "name": "input_string", + "placeholder": "The URI encoded string to decode", + "renderType": "datapath", + "required": false + } + ], + "label": "string_uri_decode", + "name": "string_uri_decode" + } + }, + "utilityType": "custom_function", + "values": { + "string_uri_decode": { + "input_string": "string_remove_crlf:custom_function_result.data.sanitized_string" + } + } + }, + "errors": {}, + "id": "7", + "type": "utility", + "warnings": {}, + "x": 1.4210854715202004e-14, + "y": 1020 + }, + "9": { + "data": { + "advanced": { + "customName": "list demux", + "customNameId": 0, + "description": "We use to take the multiple notes that may be in a ES investigation and convert them into something we can loop thru and format better. ", + "join": [], + "note": "We use to take the multiple notes that may be in a ES investigation and convert them into something we can loop thru and format better. " + }, + "customFunction": { + "draftMode": false, + "name": "list_demux", + "repoName": "community" + }, + "functionId": 3, + "functionName": "list_demux", + "id": "9", + "loop": { + "enabled": false, + "exitAfterUnit": "m", + "exitAfterValue": 10, + "exitConditionEnabled": false, + "exitLoopAfter": 2, + "pauseUnit": "m", + "pauseValue": 2 + }, + "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": "finding_and_investigation_notes:action_result.data.*.items" + } + } + }, + "errors": {}, + "id": "9", + "type": "utility", + "warnings": {}, + "x": 1.4210854715202004e-14, + "y": 320 + } + }, + "notes": "This ES Playbook will be added as the last phase in a response plan to send all the notes from an investigation to the SNOW incident created earlier in investigation.", + "origin": { + "playbook_id": 264, + "playbook_name": "servicenow_update_incident_esNotes", + "playbook_repo_id": 2, + "playbook_repo_name": "local" + } + }, + "input_spec": null, + "output_spec": null, + "playbook_trigger": "artifact_created", + "playbook_type": "es", + "python_version": "3.13", + "schema": "5.0.20", + "version": "6.4.1.361" + }, + "create_time": "2025-08-26T15:13:05.134048+00:00", + "draft_mode": false, + "labels": [ + "es_soar_integration" + ], + "tags": [ + "ServiceNow" + ] +} \ No newline at end of file diff --git a/playbooks/ServiceNow_Update_Incident_EsNotes.png b/playbooks/ServiceNow_Update_Incident_EsNotes.png new file mode 100644 index 0000000000..7651a8b663 Binary files /dev/null and b/playbooks/ServiceNow_Update_Incident_EsNotes.png differ diff --git a/playbooks/ServiceNow_Update_Incident_EsNotes.py b/playbooks/ServiceNow_Update_Incident_EsNotes.py new file mode 100644 index 0000000000..284751ef3d --- /dev/null +++ b/playbooks/ServiceNow_Update_Incident_EsNotes.py @@ -0,0 +1,287 @@ +""" +This ES Playbook will be added as the last phase in a response plan to send all the notes from an investigation to the SNOW incident created earlier in investigation. +""" + + +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 'finding_and_investigation_notes' block + finding_and_investigation_notes(container=container) + + return + +@phantom.playbook_block() +def finding_and_investigation_notes(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, loop_state_json=None, **kwargs): + phantom.debug("finding_and_investigation_notes() called") + + # phantom.debug('Action: {0} {1}'.format(action['name'], ('SUCCEEDED' if success else 'FAILED'))) + + ################################################################################ + # This will retrieve that notes from your investigation. Not only the General + # notes but any notes in Response plans also. + ################################################################################ + + finding_data = phantom.collect2(container=container, datapath=["finding:id"]) + + parameters = [] + + # build parameters list for 'finding_and_investigation_notes' call + for finding_data_item in finding_data: + if finding_data_item[0] is not None: + parameters.append({ + "id": finding_data_item[0], + }) + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.act("get notes in finding or investigation", parameters=parameters, name="finding_and_investigation_notes", assets=["builtin_mc_connector"], callback=list_demux) + + return + + +@phantom.playbook_block() +def convert_note_to_json(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, loop_state_json=None, **kwargs): + phantom.debug("convert_note_to_json() called") + + ################################################################################ + # this takes the Observables from SOAR automation and converts to json fromat + # that SOAR will accept. + ################################################################################ + + template = """{{\"comments\":\"{0}\"}}\n""" + + # parameter list for template variable replacement + parameters = [ + "loop_convert_to_html:formatted_data.*" + ] + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.format(container=container, template=template, parameters=parameters, name="convert_note_to_json") + + string_remove_crlf(container=container) + + return + + +@phantom.playbook_block() +def string_remove_crlf(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, loop_state_json=None, **kwargs): + phantom.debug("string_remove_crlf() called") + + ################################################################################ + # Remove any \n characters from the input string + ################################################################################ + + convert_note_to_json = phantom.get_format_data(name="convert_note_to_json") + + parameters = [] + + parameters.append({ + "input_string": convert_note_to_json, + }) + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.custom_function(custom_function="local/string_remove_crlf", parameters=parameters, name="string_remove_crlf", callback=string_uri_decode) + + return + + +@phantom.playbook_block() +def update_servicenow_incident(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, loop_state_json=None, **kwargs): + phantom.debug("update_servicenow_incident() called") + + # phantom.debug('Action: {0} {1}'.format(action['name'], ('SUCCEEDED' if success else 'FAILED'))) + + fields_formatted_string = phantom.format( + container=container, + template="""{0}\n""", + parameters=[ + "string_uri_decode:custom_function_result.data.decoded_string" + ]) + + ################################################################################ + # This will input the note that was configured to be sent from parent playbook + # in the start block to the Service Now Incident. + ################################################################################ + + finding_data = phantom.collect2(container=container, datapath=["finding:custom_fields.snow_incident"]) + string_uri_decode__result = phantom.collect2(container=container, datapath=["string_uri_decode:custom_function_result.data.decoded_string"]) + + parameters = [] + + # build parameters list for 'update_servicenow_incident' call + for finding_data_item in finding_data: + for string_uri_decode__result_item in string_uri_decode__result: + if finding_data_item[0] is not None: + parameters.append({ + "id": finding_data_item[0], + "table": "incident", + "fields": fields_formatted_string, + }) + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.act("update ticket", parameters=parameters, name="update_servicenow_incident", assets=["servicenow"]) + + return + + +@phantom.playbook_block() +def string_uri_decode(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, loop_state_json=None, **kwargs): + phantom.debug("string_uri_decode() called") + + ################################################################################ + # Remove any uri encoding + ################################################################################ + + string_remove_crlf__result = phantom.collect2(container=container, datapath=["string_remove_crlf:custom_function_result.data.sanitized_string"]) + + parameters = [] + + # build parameters list for 'string_uri_decode' call + for string_remove_crlf__result_item in string_remove_crlf__result: + parameters.append({ + "input_string": string_remove_crlf__result_item[0], + }) + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.custom_function(custom_function="local/string_uri_decode", parameters=parameters, name="string_uri_decode", callback=update_servicenow_incident) + + return + + +@phantom.playbook_block() +def list_demux(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, loop_state_json=None, **kwargs): + phantom.debug("list_demux() called") + + ################################################################################ + # We use to take the multiple notes that may be in a ES investigation and convert + # them into something we can loop thru and format better. + ################################################################################ + + finding_and_investigation_notes_result_data = phantom.collect2(container=container, datapath=["finding_and_investigation_notes:action_result.data.*.items","finding_and_investigation_notes:action_result.parameter.context.artifact_id"], action_results=results) + + parameters = [] + + # build parameters list for 'list_demux' call + for finding_and_investigation_notes_result_item in finding_and_investigation_notes_result_data: + parameters.append({ + "input_list": finding_and_investigation_notes_result_item[0], + }) + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.custom_function(custom_function="community/list_demux", parameters=parameters, name="list_demux", callback=loop_convert_to_html) + + return + + +@phantom.playbook_block() +def loop_convert_to_html(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, loop_state_json=None, **kwargs): + phantom.debug("loop_convert_to_html() called") + + ################################################################################ + # This block loops thru the items in the ES notes and converts the ES data to + # HTML which for service now looks better. + ################################################################################ + + template = """[code]\n

ES Notes

\n%%\nTask {4}\n
Author {3}\n
Created Time {2}\n
title {1}\n
content {0}\n

\n%%\n[/code]""" + + # parameter list for template variable replacement + parameters = [ + "list_demux:custom_function_result.data.output.content", + "list_demux:custom_function_result.data.output.title", + "list_demux:custom_function_result.data.output.create_time", + "list_demux:custom_function_result.data.output.author.realname", + "list_demux:custom_function_result.data.output.response_plan_info.response_task.name" + ] + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.format(container=container, template=template, parameters=parameters, name="loop_convert_to_html") + + convert_note_to_json(container=container) + + return + + +@phantom.playbook_block() +def on_finish(container, summary): + phantom.debug("on_finish() called") + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + return \ No newline at end of file diff --git a/playbooks/ServiceNow_Update_Incident_EsNotes.yml b/playbooks/ServiceNow_Update_Incident_EsNotes.yml new file mode 100644 index 0000000000..8a1bc33b5c --- /dev/null +++ b/playbooks/ServiceNow_Update_Incident_EsNotes.yml @@ -0,0 +1,24 @@ +name: ServiceNow Update Incident EsNotes +id: 34ad8269-b17b-40a2-abbe-37da655ce837 +version: 1 +date: '2025-08-26' +author: Kenneth Bouchard, Christian Cloutier, Splunk +type: Response +description: "This ES Playbook will be added as the last phase in a response plan to send all the notes from an investigation to the SNOW incident created earlier in investigation. You could also run this on demand from the automation tab inside ES Investigation and get the same results." +playbook: ServiceNow_Update_Incident_EsNotes +how_to_implement: This Enterprise Security playbook requires the ServiceNow connector to be configured. It is designed to work Enterprise Security 8.1 or above to communicate the notes from an investigation to your ServiceNow Incident. +references: [] +app_list: + - ServiceNow +tags: + platform_tags: + - "ServiceNow" + playbook_type: Enterprise Security + vpe_type: Modern + playbook_fields: [device] + product: + - Splunk SOAR + - Splunk Enterprise Security + use_cases: + - Response + defend_technique_id: diff --git a/playbooks/custom_functions/string_remove_crlf.json b/playbooks/custom_functions/string_remove_crlf.json new file mode 100644 index 0000000000..96fca02868 --- /dev/null +++ b/playbooks/custom_functions/string_remove_crlf.json @@ -0,0 +1,25 @@ +{ + "create_time": "2025-06-27T16:58:50.260630+00:00", + "custom_function_id": "2ef5fc852a4c91328480c3d373481fbe91cfcc39", + "description": "Sanitize the provided string to remove carriage return/line feed characters (LF, \\n).", + "draft_mode": false, + "inputs": [ + { + "contains_type": [], + "description": "The string to replace LF characters from.", + "input_type": "item", + "name": "input_string", + "placeholder": "string to sanitize" + } + ], + "outputs": [ + { + "contains_type": [], + "data_path": "sanitized_string", + "description": "The sanitized string." + } + ], + "outputs_type": "item", + "platform_version": "6.4.1.342", + "python_version": "3.9" +} \ No newline at end of file diff --git a/playbooks/custom_functions/string_remove_crlf.py b/playbooks/custom_functions/string_remove_crlf.py new file mode 100644 index 0000000000..a56baf19cc --- /dev/null +++ b/playbooks/custom_functions/string_remove_crlf.py @@ -0,0 +1,28 @@ +def string_remove_crlf(input_string=None, **kwargs): + """ + Sanitize the provided string to remove carriage return/line feed characters (LF, \n). + + Args: + input_string: The string to replace LF characters from. + + Returns a JSON-serializable object that implements the configured data paths: + sanitized_string: The sanitized string. + """ + ############################ Custom Code Goes Below This Line ################################# + import json + import phantom.rules as phantom + + outputs = {} + + try: + sanitized_string = input_string.replace('\n', '') + except AttributeError: + raise ValueError('input_string must be a string or unicode') + + outputs = {"sanitized_string": sanitized_string.strip()} + + # Return a JSON-serializable object + assert json.dumps(outputs) # Will raise an exception if the :outputs: object is not JSON-serializable + return outputs + + diff --git a/playbooks/custom_functions/string_uri_decode.json b/playbooks/custom_functions/string_uri_decode.json new file mode 100644 index 0000000000..1f1b9778c5 --- /dev/null +++ b/playbooks/custom_functions/string_uri_decode.json @@ -0,0 +1,25 @@ +{ + "create_time": "2025-07-30T18:18:16.991915+00:00", + "custom_function_id": "0db183c85d111765993faa406edbf00873fda5b5", + "description": "Decodes a URI-encoded string to a plain text string.", + "draft_mode": false, + "inputs": [ + { + "contains_type": [], + "description": "The URI encoded string to decode", + "input_type": "item", + "name": "input_string", + "placeholder": "The URI encoded string to decode" + } + ], + "outputs": [ + { + "contains_type": [], + "data_path": "decoded_string", + "description": "The decoded, plain text string" + } + ], + "outputs_type": "item", + "platform_version": "6.4.1.356", + "python_version": "3.9" +} \ No newline at end of file diff --git a/playbooks/custom_functions/string_uri_decode.py b/playbooks/custom_functions/string_uri_decode.py new file mode 100644 index 0000000000..9ed06fd1d6 --- /dev/null +++ b/playbooks/custom_functions/string_uri_decode.py @@ -0,0 +1,27 @@ +def string_uri_decode(input_string=None, **kwargs): + """ + Decodes a URI-encoded string to a plain text string. + + Args: + input_string: The URI encoded string to decode + + Returns a JSON-serializable object that implements the configured data paths: + decoded_string: The decoded, plain text string + """ + ############################ Custom Code Goes Below This Line ################################# + import json + import urllib.parse + import phantom.rules as phantom + + outputs = {} + + try: + decoded_string = urllib.parse.unquote(input_string) + except TypeError: + raise ValueError('input_string must be a string or bytes') + + outputs = {"decoded_string": decoded_string} + + # Return a JSON-serializable object + assert json.dumps(outputs) # Will raise an exception if the :outputs: object is not JSON-serializable + return outputs