From 21802e0bedb3b53df644b4220c0ce6308a1b86e5 Mon Sep 17 00:00:00 2001 From: Christian Cloutier Date: Fri, 20 Jun 2025 11:42:52 -0400 Subject: [PATCH 1/2] Added Playbooks from the CrowdStrike EDR Pack --- ...owdStrike_OAuth_API_Endpoint_Analysis.json | 795 ++++++++++++++++ ...rowdStrike_OAuth_API_Endpoint_Analysis.png | Bin 0 -> 43811 bytes ...CrowdStrike_OAuth_API_Endpoint_Analysis.py | 830 +++++++++++++++++ ...rowdStrike_OAuth_API_Endpoint_Analysis.yml | 34 + ...rike_OAuth_API_Executable_Denylisting.json | 407 ++++++++ ...trike_OAuth_API_Executable_Denylisting.png | Bin 0 -> 27419 bytes ...Strike_OAuth_API_Executable_Denylisting.py | 283 ++++++ ...trike_OAuth_API_Executable_Denylisting.yml | 31 + ...CrowdStrike_OAuth_API_File_Collection.json | 880 ++++++++++++++++++ .../CrowdStrike_OAuth_API_File_Collection.png | Bin 0 -> 44773 bytes .../CrowdStrike_OAuth_API_File_Collection.py | 606 ++++++++++++ .../CrowdStrike_OAuth_API_File_Collection.yml | 31 + .../CrowdStrike_OAuth_API_File_Eviction.json | 499 ++++++++++ .../CrowdStrike_OAuth_API_File_Eviction.png | Bin 0 -> 42811 bytes .../CrowdStrike_OAuth_API_File_Eviction.py | 365 ++++++++ .../CrowdStrike_OAuth_API_File_Eviction.yml | 31 + .../CrowdStrike_OAuth_API_File_Restore.json | 697 ++++++++++++++ .../CrowdStrike_OAuth_API_File_Restore.png | Bin 0 -> 43677 bytes .../CrowdStrike_OAuth_API_File_Restore.py | 468 ++++++++++ .../CrowdStrike_OAuth_API_File_Restore.yml | 31 + ...CrowdStrike_OAuth_API_Get_Device_Info.json | 300 ++++++ .../CrowdStrike_OAuth_API_Get_Device_Info.png | Bin 0 -> 26210 bytes .../CrowdStrike_OAuth_API_Get_Device_Info.py | 179 ++++ .../CrowdStrike_OAuth_API_Get_Device_Info.yml | 26 + ...owdStrike_OAuth_API_Network_Isolation.json | 362 +++++++ ...rowdStrike_OAuth_API_Network_Isolation.png | Bin 0 -> 26632 bytes ...CrowdStrike_OAuth_API_Network_Isolation.py | 265 ++++++ ...rowdStrike_OAuth_API_Network_Isolation.yml | 30 + ...CrowdStrike_OAuth_API_Network_Restore.json | 361 +++++++ .../CrowdStrike_OAuth_API_Network_Restore.png | Bin 0 -> 26357 bytes .../CrowdStrike_OAuth_API_Network_Restore.py | 265 ++++++ .../CrowdStrike_OAuth_API_Network_Restore.yml | 30 + ...dStrike_OAuth_API_Process_Termination.json | 497 ++++++++++ ...wdStrike_OAuth_API_Process_Termination.png | Bin 0 -> 43961 bytes ...owdStrike_OAuth_API_Process_Termination.py | 368 ++++++++ ...wdStrike_OAuth_API_Process_Termination.yml | 31 + 36 files changed, 8702 insertions(+) create mode 100644 playbooks/CrowdStrike_OAuth_API_Endpoint_Analysis.json create mode 100644 playbooks/CrowdStrike_OAuth_API_Endpoint_Analysis.png create mode 100644 playbooks/CrowdStrike_OAuth_API_Endpoint_Analysis.py create mode 100644 playbooks/CrowdStrike_OAuth_API_Endpoint_Analysis.yml create mode 100644 playbooks/CrowdStrike_OAuth_API_Executable_Denylisting.json create mode 100644 playbooks/CrowdStrike_OAuth_API_Executable_Denylisting.png create mode 100644 playbooks/CrowdStrike_OAuth_API_Executable_Denylisting.py create mode 100644 playbooks/CrowdStrike_OAuth_API_Executable_Denylisting.yml create mode 100644 playbooks/CrowdStrike_OAuth_API_File_Collection.json create mode 100644 playbooks/CrowdStrike_OAuth_API_File_Collection.png create mode 100644 playbooks/CrowdStrike_OAuth_API_File_Collection.py create mode 100644 playbooks/CrowdStrike_OAuth_API_File_Collection.yml create mode 100644 playbooks/CrowdStrike_OAuth_API_File_Eviction.json create mode 100644 playbooks/CrowdStrike_OAuth_API_File_Eviction.png create mode 100644 playbooks/CrowdStrike_OAuth_API_File_Eviction.py create mode 100644 playbooks/CrowdStrike_OAuth_API_File_Eviction.yml create mode 100644 playbooks/CrowdStrike_OAuth_API_File_Restore.json create mode 100644 playbooks/CrowdStrike_OAuth_API_File_Restore.png create mode 100644 playbooks/CrowdStrike_OAuth_API_File_Restore.py create mode 100644 playbooks/CrowdStrike_OAuth_API_File_Restore.yml create mode 100644 playbooks/CrowdStrike_OAuth_API_Get_Device_Info.json create mode 100644 playbooks/CrowdStrike_OAuth_API_Get_Device_Info.png create mode 100644 playbooks/CrowdStrike_OAuth_API_Get_Device_Info.py create mode 100644 playbooks/CrowdStrike_OAuth_API_Get_Device_Info.yml create mode 100644 playbooks/CrowdStrike_OAuth_API_Network_Isolation.json create mode 100644 playbooks/CrowdStrike_OAuth_API_Network_Isolation.png create mode 100644 playbooks/CrowdStrike_OAuth_API_Network_Isolation.py create mode 100644 playbooks/CrowdStrike_OAuth_API_Network_Isolation.yml create mode 100644 playbooks/CrowdStrike_OAuth_API_Network_Restore.json create mode 100644 playbooks/CrowdStrike_OAuth_API_Network_Restore.png create mode 100644 playbooks/CrowdStrike_OAuth_API_Network_Restore.py create mode 100644 playbooks/CrowdStrike_OAuth_API_Network_Restore.yml create mode 100644 playbooks/CrowdStrike_OAuth_API_Process_Termination.json create mode 100644 playbooks/CrowdStrike_OAuth_API_Process_Termination.png create mode 100644 playbooks/CrowdStrike_OAuth_API_Process_Termination.py create mode 100644 playbooks/CrowdStrike_OAuth_API_Process_Termination.yml diff --git a/playbooks/CrowdStrike_OAuth_API_Endpoint_Analysis.json b/playbooks/CrowdStrike_OAuth_API_Endpoint_Analysis.json new file mode 100644 index 0000000000..efa5626ad0 --- /dev/null +++ b/playbooks/CrowdStrike_OAuth_API_Endpoint_Analysis.json @@ -0,0 +1,795 @@ +{ + "blockly": false, + "blockly_xml": "", + "category": "Enrichment", + "coa": { + "data": { + "description": "Accepts a hostname or device id as input and collects running processes, network connections and various system information from the device via Crowdstrike. We then generate an observable report for each. This can be customized based on user preference.", + "edges": [ + { + "id": "port_0_to_port_2", + "sourceNode": "0", + "sourcePort": "0_out", + "targetNode": "2", + "targetPort": "2_in" + }, + { + "conditions": [ + { + "index": 0 + } + ], + "id": "port_2_to_port_7", + "sourceNode": "2", + "sourcePort": "2_out", + "targetNode": "7", + "targetPort": "7_in" + }, + { + "id": "port_7_to_port_8", + "sourceNode": "7", + "sourcePort": "7_out", + "targetNode": "8", + "targetPort": "8_in" + }, + { + "id": "port_8_to_port_11", + "sourceNode": "8", + "sourcePort": "8_out", + "targetNode": "11", + "targetPort": "11_in" + }, + { + "id": "port_11_to_port_12", + "sourceNode": "11", + "sourcePort": "11_out", + "targetNode": "12", + "targetPort": "12_in" + }, + { + "id": "port_12_to_port_17", + "sourceNode": "12", + "sourcePort": "12_out", + "targetNode": "17", + "targetPort": "17_in" + }, + { + "id": "port_11_to_port_18", + "sourceNode": "11", + "sourcePort": "11_out", + "targetNode": "18", + "targetPort": "18_in" + }, + { + "id": "port_18_to_port_19", + "sourceNode": "18", + "sourcePort": "18_out", + "targetNode": "19", + "targetPort": "19_in" + }, + { + "id": "port_8_to_port_20", + "sourceNode": "8", + "sourcePort": "8_out", + "targetNode": "20", + "targetPort": "20_in" + }, + { + "id": "port_20_to_port_21", + "sourceNode": "20", + "sourcePort": "20_out", + "targetNode": "21", + "targetPort": "21_in" + }, + { + "id": "port_21_to_port_1", + "sourceNode": "21", + "sourcePort": "21_out", + "targetNode": "1", + "targetPort": "1_in" + }, + { + "id": "port_19_to_port_23", + "sourceNode": "19", + "sourcePort": "19_out", + "targetNode": "23", + "targetPort": "23_in" + }, + { + "id": "port_17_to_port_23", + "sourceNode": "17", + "sourcePort": "17_out", + "targetNode": "23", + "targetPort": "23_in" + }, + { + "id": "port_23_to_port_1", + "sourceNode": "23", + "sourcePort": "23_out", + "targetNode": "1", + "targetPort": "1_in" + }, + { + "id": "port_11_to_port_24", + "sourceNode": "11", + "sourcePort": "11_out", + "targetNode": "24", + "targetPort": "24_in" + }, + { + "id": "port_24_to_port_25", + "sourceNode": "24", + "sourcePort": "24_out", + "targetNode": "25", + "targetPort": "25_in" + }, + { + "id": "port_25_to_port_23", + "sourceNode": "25", + "sourcePort": "25_out", + "targetNode": "23", + "targetPort": "23_in" + } + ], + "hash": "b056d8c5001ac05e31fe03ff61e1acb72c4b9281", + "nodes": { + "0": { + "data": { + "advanced": { + "join": [] + }, + "functionName": "on_start", + "id": "0", + "type": "start" + }, + "errors": {}, + "id": "0", + "type": "start", + "warnings": {}, + "x": 360, + "y": -3.197442310920451e-13 + }, + "1": { + "data": { + "advanced": { + "join": [] + }, + "functionName": "on_finish", + "id": "1", + "type": "end" + }, + "errors": {}, + "id": "1", + "type": "end", + "warnings": {}, + "x": 440, + "y": 1460 + }, + "11": { + "data": { + "action": "create session", + "actionType": "generic", + "advanced": { + "customName": "create session", + "customNameId": 0, + "description": "Create a Real Time Response (RTR) session on CS Falcon to interact with the endpoint.", + "join": [], + "note": "Create a Real Time Response (RTR) session on CS Falcon to interact with the endpoint." + }, + "connector": "CrowdStrike OAuth API", + "connectorConfigs": [ + "crowdstrike_oauth_api" + ], + "connectorId": "ae971ba5-3117-444a-8ac5-6ce779f3a232", + "connectorVersion": "v1", + "functionId": 1, + "functionName": "create_session", + "id": "11", + "loop": { + "enabled": false, + "exitAfterUnit": "m", + "exitAfterValue": 10, + "exitConditionEnabled": false, + "exitLoopAfter": 2, + "pauseUnit": "m", + "pauseValue": 2 + }, + "parameters": { + "device_id": "query_device:action_result.data.*.device_id" + }, + "requiredParameters": [ + { + "data_type": "string", + "field": "device_id" + } + ], + "tab": "byAction", + "type": "action" + }, + "errors": {}, + "id": "11", + "type": "action", + "warnings": {}, + "x": 170, + "y": 680 + }, + "12": { + "data": { + "action": "run admin command", + "actionType": "generic", + "advanced": { + "customName": "run admin command ps", + "customNameId": 0, + "description": "Gets a list of running processes on the specified endpoint.", + "join": [], + "note": "Gets a list of running processes on the specified endpoint." + }, + "connector": "CrowdStrike OAuth API", + "connectorConfigs": [ + "crowdstrike_oauth_api" + ], + "connectorId": "ae971ba5-3117-444a-8ac5-6ce779f3a232", + "connectorVersion": "v1", + "functionId": 1, + "functionName": "run_admin_command_ps", + "id": "12", + "loop": { + "enabled": false, + "exitAfterUnit": "m", + "exitAfterValue": 10, + "exitConditionEnabled": false, + "exitLoopAfter": 2, + "pauseUnit": "m", + "pauseValue": 2 + }, + "parameters": { + "command": "ps", + "device_id": "query_device:action_result.data.*.device_id", + "session_id": "create_session:action_result.data.*.resources.*.session_id" + }, + "requiredParameters": [ + { + "data_type": "string", + "field": "command" + }, + { + "data_type": "string", + "field": "device_id" + }, + { + "data_type": "string", + "field": "session_id" + } + ], + "tab": "byAction", + "type": "action" + }, + "errors": {}, + "id": "12", + "type": "action", + "warnings": {}, + "x": 340, + "y": 840 + }, + "17": { + "data": { + "advanced": { + "customName": "process process observables", + "customNameId": 0, + "description": "Format a normalized output for each process", + "join": [], + "note": "Format a normalized output for each process" + }, + "functionId": 3, + "functionName": "process_process_observables", + "id": "17", + "inputParameters": [ + "query_device:action_result.data.*.hostname", + "run_admin_command_ps:action_result.parameter", + "run_admin_command_ps:action_result.data", + "run_admin_command_ps:action_result.status", + "run_admin_command_ps:action_result.message" + ], + "outputVariables": [ + "observable_array" + ], + "type": "code" + }, + "errors": {}, + "id": "17", + "type": "code", + "userCode": "\n import re\n process_process_observables__observable_array = []\n i = 0\n \n ################################################################################\n # This returns process information from stdout as text, with lines separated by newlines (\\n).\n # For example:\n #\n # Name Id Start Time (UTC-7) WorkingMemory(kb) CPU(s) HandleCount Path \n # ---- -- ------------------ ----------------- ------ ----------- ---- \n # conhost 2216 4/8/2025 12:14:21 PM 5,612 0.05 93 C:\\Windows\\system32\\conhost.exe \n # conhost 4192 4/8/2025 12:14:21 PM 5,620 0.03 93 C:\\Windows\\system32\\conhost.exe \n # CSFalconService 488 3/27/2025 9:05:13 AM 60,196 312.31 671 \n # csrss 672 3/11/2025 4:21:37 AM 4,476 667.27 500\n # powershell 3768 4/8/2025 12:14:21 PM 60,892 0.66 511 C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe \n # VGAuthService 2412 3/11/2025 4:21:42 AM 10,920 0.05 149 C:\\Program Files\\VMware\\VMware Tools\\VMware \n # VGAuth\\VGAuthService.exe \n # vm3dservice 2440 3/11/2025 4:21:42 AM 6,096 0.02 123 C:\\Windows\\system32\\vm3dservice.exe \n # vm3dservice 2568 3/11/2025 4:21:42 AM 6,564 0.03 110 C:\\Windows\\system32\\vm3dservice.exe \n # vm3dservice 4248 3/11/2025 4:25:31 AM 6,564 0.08 111 C:\\Windows\\system32\\vm3dservice.exe \n \n for system in run_admin_command_ps_result_item_1:\n device_id = run_admin_command_ps_result_item_0[i].get(\"device_id\")\n hostname = query_device_result_item_0[i]\n \n observable = {\n \"source\": \"Crowdstrike OAuth API\",\n \"type\": \"Endpoint\",\n \"activity_name\": \"Process Collection\",\n \"uid\": device_id,\n \"hostname\": hostname,\n \"status\": run_admin_command_ps_result_item_2[i],\n \"status_detail\": run_admin_command_ps_result_message[i],\n \"process_artifacts\": []\n }\n \n stdout = system[0][\"resources\"][0][\"stdout\"]\n \n # Skip header lines and read each one using REGEX (for lack of a better way).\n for line in stdout.split('\\n')[3:]:\n pattern = re.compile('^(?P\\w+)\\s+(?P\\d+)\\s+(?P[\\d\\/:\\s]+(AM|PM))\\s+(?P[\\d,]+)\\s+(?P[\\d,\\.]+)\\s+(?P\\d+)\\s(?P.*)$')\n matches = pattern.match(line)\n orphan_matches = pattern.match(line)\n \n if matches:\n name = matches.group('name')\n pid = matches.group('pid')\n start_time = matches.group('start_time')\n working_memory = matches.group('working_memory')\n cpu = matches.group('cpu')\n handle_count = matches.group('handle_count')\n path = matches.group('path') \n \n # Skip lines we don't care about.\n if line.strip() and name not in ['Idle','System']:\n observable['process_artifacts'].append(\n {\n \"name\": name,\n \"pid\": pid,\n \"path\": path.strip(),\n \"created_time\": start_time,\n \"xattributes\": [\n {\n \"working_memory\": working_memory,\n \"handle_count\": handle_count,\n \"cpu\": cpu\n }\n ]\n }\n )\n else:\n # Some paths are continued on the next line... fold them back into the correct observable.\n orphan_pattern = re.compile('^\\s+(?P.*?)\\s+$')\n orphan_matches = orphan_pattern.match(line)\n \n if orphan_matches:\n orphaned_path = orphan_matches.group('orphaned_path')\n previous_observable = observable['process_artifacts'][-1]\n if previous_observable:\n previous_observable[\"path\"] = previous_observable[\"path\"] + \" \" + orphaned_path\n \n \n #phantom.debug(f\"observable = {observable}\")\n process_process_observables__observable_array.append(observable)\n i+=1\n \n", + "warnings": {}, + "x": 340, + "y": 1020 + }, + "18": { + "data": { + "action": "run admin command", + "actionType": "generic", + "advanced": { + "customName": "run admin command netstat", + "customNameId": 0, + "description": "List the open sockets & network ports on the specified endpoint.", + "join": [], + "note": "List the open sockets & network ports on the specified endpoint." + }, + "connector": "CrowdStrike OAuth API", + "connectorConfigs": [ + "crowdstrike_oauth_api" + ], + "connectorId": "ae971ba5-3117-444a-8ac5-6ce779f3a232", + "connectorVersion": "v1", + "functionId": 2, + "functionName": "run_admin_command_netstat", + "id": "18", + "loop": { + "enabled": false, + "exitAfterUnit": "m", + "exitAfterValue": 10, + "exitConditionEnabled": false, + "exitLoopAfter": 2, + "pauseUnit": "m", + "pauseValue": 2 + }, + "parameters": { + "command": "netstat", + "device_id": "query_device:action_result.data.*.device_id", + "session_id": "create_session:action_result.data.*.resources.*.session_id" + }, + "requiredParameters": [ + { + "data_type": "string", + "field": "command" + }, + { + "data_type": "string", + "field": "device_id" + }, + { + "data_type": "string", + "field": "session_id" + } + ], + "type": "action" + }, + "errors": {}, + "id": "18", + "type": "action", + "warnings": {}, + "x": 0, + "y": 840 + }, + "19": { + "customCode": null, + "data": { + "advanced": { + "customName": "process network observables", + "customNameId": 0, + "description": "Format a normalized output for each network connection", + "join": [], + "note": "Format a normalized output for each network connection" + }, + "functionId": 4, + "functionName": "process_network_observables", + "id": "19", + "inputParameters": [ + "run_admin_command_netstat:action_result.parameter.device_id", + "query_device:action_result.data.*.hostname", + "run_admin_command_netstat:action_result.status", + "run_admin_command_netstat:action_result.message", + "run_admin_command_netstat:action_result.data" + ], + "outputVariables": [ + "observable_array" + ], + "type": "code" + }, + "errors": {}, + "id": "19", + "type": "code", + "userCode": "\n import re\n process_network_observables__observable_array = []\n i = 0\n \n ################################################################################\n # This returns network connection information from stdout as text, with lines separated by newlines (\\n).\n # For example:\n #\n # Proto Local Address Foreign Address State Owning Process Id Owning Process Name Process Start Time (UTC-8)\n # ----- ------------- --------------- ----- ----------------- ------------------- --------------------------\n # TCP :::49689 :::0 Listen 920 C:\\Windows\\system32\\lsass.exe 10/25/2024 10:28:22 AM \n # TCP :::49675 :::0 Listen 2060 C:\\Windows\\system32\\svchost.exe 10/25/2024 10:28:13 AM \n # TCP :::49669 :::0 Listen 2164 C:\\Windows\\System32\\spoolsv.exe 10/25/2024 10:28:13 AM \n # TCP :::49666 :::0 Listen 1056 C:\\Windows\\system32\\svchost.exe 10/25/2024 10:28:13 AM \n # TCP :::49665 :::0 Listen 1116 C:\\Windows\\System32\\svchost.exe 10/25/2024 10:28:12 AM \n # TCP :::49664 :::0 Listen 768 10/25/2024 10:28:12 AM \n # TCP :::3389 :::0 Listen 1048 C:\\Windows\\System32\\svchost.exe 10/25/2024 10:28:13 AM \n # TCP :::445 :::0 Listen 4 10/25/2024 10:28:13 AM \n # TCP :::135 :::0 Listen 352 C:\\Windows\\system32\\svchost.exe 10/25/2024 10:28:12 AM \n # TCP 0.0.0.0:58808 0.0.0.0:0 Bound 1056 C:\\Windows\\system32\\svchost.exe 11/23/2024 9:31:16 PM \n # TCP 10.1.19.4:59929 54.241.197.58:443 Established 4 2/24/2025 2:34:34 PM \n # TCP 10.1.19.4:58808 13.64.180.106:443 Established 1056 C:\\Windows\\system32\\svchost.exe 11/23/2024 9:31:16 PM \n # TCP 0.0.0.0:49689 0.0.0.0:0 Listen 920 C:\\Windows\\system32\\lsass.exe 10/25/2024 10:28:22 AM \n # TCP 0.0.0.0:49679 0.0.0.0:0 Listen 904 10/25/2024 10:28:13 AM \n # TCP 0.0.0.0:49675 0.0.0.0:0 Listen 2060 C:\\Windows\\system32\\svchost.exe 10/25/2024 10:28:13 AM \n # TCP 0.0.0.0:49669 0.0.0.0:0 Listen 2164 C:\\Windows\\System32\\spoolsv.exe 10/25/2024 10:28:13 AM \n # UDP ::1:60661 *:* Listen 5472 C:\\Windows\\system32\\svchost.exe 10/26/2024 9:03:09 AM \n # UDP fe80::9c8b:6e28:cc9:6e2e%2:60660 *:* Listen 5472 C:\\Windows\\system32\\svchost.exe 10/26/2024 9:03:09 AM \n # UDP :::5355 *:* Listen 1488 C:\\Windows\\System32\\svchost.exe 2/25/2025 6:52:43 AM \n # UDP :::5353 *:* Listen 1488 C:\\Windows\\System32\\svchost.exe 2/25/2025 6:52:43 AM \n\n for system in run_admin_command_netstat_result_item_3:\n device_id = run_admin_command_netstat_parameter_device_id[i]\n hostname = query_device_result_item_0[i]\n \n observable = {\n \"source\": \"Crowdstrike OAuth API\",\n \"type\": \"Endpoint\",\n \"activity_name\": \"Network Connections Collection\",\n \"uid\": device_id,\n \"hostname\": hostname,\n \"status\": run_admin_command_netstat_result_item_1[i],\n \"status_detail\": run_admin_command_netstat_result_message[i],\n \"network_artifacts\": []\n }\n\n phantom.debug(f\"system -> {system}\")\n \n stdout = system[0][\"resources\"][0][\"stdout\"]\n if stdout:\n # Skip header lines and read each one, extracting fields via REGEX (for lack of a better way).\n for line in stdout.split('\\n')[3:]: \n line_groups = re.search('^(\\w+)\\s+(.*?)\\s+(.*?)\\s+(\\w+)\\s+(\\d+)\\s(.*?)\\s(\\d.*?)\\s+$', line)\n \n if line_groups:\n proto = line_groups.group(1)\n local_address = line_groups.group(2)\n foreign_address = line_groups.group(3)\n state = line_groups.group(4)\n process_id = line_groups.group(5)\n process_path = line_groups.group(6)\n start_time = line_groups.group(7)\n \n observable['network_artifacts'].append(\n {\n \"protocol_name\": proto,\n \"local_address\": local_address,\n \"foreign_address\": foreign_address,\n \"state\": state,\n \"start_time\": start_time,\n \"process\": [\n {\n \"pid\": process_id,\n \"path\": process_path\n }\n ]\n }\n ) \n\n #phantom.debug(observable)\n process_network_observables__observable_array.append(observable)\n i+=1\n \n", + "warnings": {}, + "x": 1.4210854715202004e-14, + "y": 1020 + }, + "2": { + "data": { + "advanced": { + "customName": "input filter", + "customNameId": 0, + "description": "Determines if the provided inputs are present in the dataset.", + "join": [], + "note": "Determines if the provided inputs are present in the dataset." + }, + "conditions": [ + { + "comparisons": [ + { + "conditionIndex": 0, + "op": "!=", + "param": "playbook_input:device", + "value": "" + } + ], + "conditionIndex": 0, + "customName": "input device present", + "logic": "and" + } + ], + "functionId": 1, + "functionName": "input_filter", + "id": "2", + "type": "filter" + }, + "errors": {}, + "id": "2", + "type": "filter", + "warnings": {}, + "x": 400, + "y": 148 + }, + "20": { + "data": { + "action": "get system info", + "actionType": "investigate", + "advanced": { + "customName": "get system info", + "customNameId": 0, + "description": "Get information from the endpoint.", + "join": [], + "note": "Get information from the endpoint." + }, + "connector": "CrowdStrike OAuth API", + "connectorConfigs": [ + "crowdstrike_oauth_api" + ], + "connectorId": "ae971ba5-3117-444a-8ac5-6ce779f3a232", + "connectorVersion": "v1", + "functionId": 1, + "functionName": "get_system_info", + "id": "20", + "loop": { + "enabled": false, + "exitAfterUnit": "m", + "exitAfterValue": 10, + "exitConditionEnabled": false, + "exitLoopAfter": 2, + "pauseUnit": "m", + "pauseValue": 2 + }, + "parameters": { + "id": "query_device:action_result.data.*.device_id" + }, + "requiredParameters": [ + { + "data_type": "string", + "field": "id" + } + ], + "type": "action" + }, + "errors": {}, + "id": "20", + "type": "action", + "warnings": {}, + "x": 680, + "y": 680 + }, + "21": { + "data": { + "advanced": { + "customName": "process system observables", + "customNameId": 0, + "description": "Format a normalized output for the endpoint.", + "join": [], + "note": "Format a normalized output for the endpoint." + }, + "functionId": 2, + "functionName": "process_system_observables", + "id": "21", + "inputParameters": [ + "get_system_info:action_result.status", + "get_system_info:action_result.message", + "get_system_info:action_result.data" + ], + "outputVariables": [ + "observable_array" + ], + "type": "code" + }, + "errors": {}, + "id": "21", + "type": "code", + "userCode": " process_system_observables__observable_array = []\n i = 0\n \n for system in get_system_info_result_item_2:\n data = system[0]\n if data:\n observable = {\n \"source\": \"Crowdstrike OAuth API\", \n \"type\": \"Endpoint\", \n \"activity_name\": \"System Collection\",\n \"uid\": data['device_id'],\n \"hostname\": data['hostname'],\n \"status\": get_system_info_result_item_0[i],\n \"status_detail\": get_system_info_result_message[i],\n \"endpoint_artifacts\": [\n {\n \"agents\": [\n {\n \"type\": \"Endpoint Detection and Response\",\n \"type_id\": 1,\n \"uid\": data['cid'],\n \"vendor_name\": \"Crowdstrike\",\n \"version\": data['agent_version'] \n }\n ],\n \"ip\": data['local_ip'],\n \"external_ip\": data['external_ip'],\n \"mac\": data['mac_address'],\n \"domain\": data['machine_domain'],\n \"type\": data['product_type_desc'],\n \"last_seen\": data['last_seen'],\n \"last_reboot\": data['last_reboot'],\n \"last_login_user_sid\": data.get('last_login_user_sid', ''),\n \"last_login_timestamp\": data.get('last_login_timestamp', ''),\n \"operating_system\": {\n \"build:\": data['os_build'],\n \"kernel_release\": data['kernel_version'],\n \"name\": data['os_product_name'],\n \"type\": data['platform_name'],\n \"version\": data['os_version']\n },\n \"hw_info\": {\n \"bios_manufacturer\": data['bios_manufacturer'],\n \"bios_version\": data['bios_version'],\n \"serial_number\": data['serial_number'],\n \"chassis\": data['chassis_type_desc']\n }\n }\n ]\n }\n \n policies = []\n for policy in data['policies']:\n policies.append(\n {\n \"name\": policy['policy_type'],\n \"uid\": policy['policy_id'],\n \"is_applied\": policy['applied']\n }\n )\n observable['endpoint_artifacts'][0]['agents'][0]['policies'] = policies\n \n #phantom.debug(f\"---> OBSERVABLE: {observable}\")\n process_system_observables__observable_array.append(observable)\n i+=1\n\n", + "warnings": {}, + "x": 680, + "y": 1020 + }, + "23": { + "data": { + "action": "delete session", + "actionType": "generic", + "advanced": { + "customName": "delete session", + "customNameId": 0, + "description": "Deletes the Real Time Response (RTR) session on CS Falcon.", + "join": [], + "note": "Deletes the Real Time Response (RTR) session on CS Falcon." + }, + "connector": "CrowdStrike OAuth API", + "connectorConfigs": [ + "crowdstrike_oauth_api" + ], + "connectorId": "ae971ba5-3117-444a-8ac5-6ce779f3a232", + "connectorVersion": "v1", + "functionId": 1, + "functionName": "delete_session", + "id": "23", + "loop": { + "enabled": false, + "exitAfterUnit": "m", + "exitAfterValue": 10, + "exitConditionEnabled": false, + "exitLoopAfter": 2, + "pauseUnit": "m", + "pauseValue": 2 + }, + "parameters": { + "session_id": "create_session:action_result.summary.session_id" + }, + "requiredParameters": [ + { + "data_type": "string", + "field": "session_id" + } + ], + "tab": "byAction", + "type": "action" + }, + "errors": {}, + "id": "23", + "type": "action", + "warnings": {}, + "x": 180, + "y": 1260 + }, + "24": { + "data": { + "action": "run admin command", + "actionType": "generic", + "advanced": { + "customName": "run admin command services", + "customNameId": 0, + "description": "Runs a set of Powershell commands to gather details about the services on the endpoint. This requires that the host response policy permits it on the CrowdStrike side.", + "join": [], + "note": "Runs a set of Powershell commands to gather details about the services on the endpoint. This requires that the host response policy permits it on the CrowdStrike side." + }, + "connector": "CrowdStrike OAuth API", + "connectorConfigs": [ + "crowdstrike_oauth_api" + ], + "connectorId": "ae971ba5-3117-444a-8ac5-6ce779f3a232", + "connectorVersion": "v1", + "functionId": 3, + "functionName": "run_admin_command_services", + "id": "24", + "loop": { + "enabled": false, + "exitAfterUnit": "m", + "exitAfterValue": 10, + "exitConditionEnabled": false, + "exitLoopAfter": 2, + "pauseUnit": "m", + "pauseValue": 2 + }, + "parameters": { + "command": "runscript", + "data": "-Raw=```Get-Service | Select-Object -Property Name, DisplayName, Status, ServiceType, StartType | ConvertTo-Json```", + "device_id": "query_device:action_result.data.*.device_id", + "session_id": "create_session:action_result.data.*.resources.*.session_id" + }, + "requiredParameters": [ + { + "data_type": "string", + "field": "command" + }, + { + "data_type": "string", + "field": "device_id" + }, + { + "data_type": "string", + "field": "session_id" + } + ], + "tab": "byAction", + "type": "action" + }, + "errors": {}, + "id": "24", + "type": "action", + "warnings": {}, + "x": -320, + "y": 840 + }, + "25": { + "data": { + "advanced": { + "customName": "process service observables", + "customNameId": 0, + "description": "Format a normalized output for each service", + "join": [], + "note": "Format a normalized output for each service" + }, + "functionId": 1, + "functionName": "process_service_observables", + "id": "25", + "inputParameters": [ + "query_device:action_result.data.*.device_id", + "query_device:action_result.data.*.hostname", + "run_admin_command_services:action_result.data.*.resources", + "run_admin_command_services:action_result.status", + "run_admin_command_services:action_result.message" + ], + "outputVariables": [ + "observable_array" + ], + "type": "code" + }, + "errors": {}, + "id": "25", + "type": "code", + "userCode": "\n import json\n \n # Ref: https://learn.microsoft.com/en-us/dotnet/api/system.serviceprocess.servicestartmode?view=net-9.0-pp\n ocsf_start_type_mapping = {\n \"0\": { \"id\": \"1\", \"type\": \"Boot\" }, # Boot -> Boot\n \"1\": { \"id\": \"2\", \"type\": \"System\" }, # System -> System\n \"2\": { \"id\": \"3\", \"type\": \"Auto\" }, # Automatic -> Auto\n \"3\": { \"id\": \"4\", \"type\": \"Demand\" }, # Manual -> Demand\n \"4\": { \"id\": \"5\", \"type\": \"Disabled\" } # Disabled -> Disabled\n }\n \n # Ref: https://learn.microsoft.com/en-us/dotnet/api/system.serviceprocess.servicetype?view=net-9.0-pp\n ocsf_service_type_mapping = {\n # Unused for now. OCSF doesn't seem to properly support BITWISE ENUMs yet.\n # See \"service_type_id\" at https://schema.ocsf.io/1.4.0/objects/win/win_service\n }\n \n process_service_observables__observable_array = []\n for device_id, hostname, services, status, messsage in zip(query_device_result_item_0, query_device_result_item_1, run_admin_command_services_result_item_0, run_admin_command_services_result_item_1, run_admin_command_services_result_message):\n \n observable = {\n \"source\": \"Crowdstrike OAuth API\",\n \"type\": \"Endpoint\",\n \"activity_name\": \"Services Collection\",\n \"uid\": device_id,\n \"hostname\": hostname,\n \"status\": status,\n \"status_detail\": messsage,\n \"service_artifacts\": []\n }\n \n if services[0] and services[0][\"stdout\"]:\n services_json = json.loads(services[0][\"stdout\"])\n for service in services_json:\n start_type = ocsf_start_type_mapping.get(str(service[\"StartType\"]))\n \n observable[\"service_artifacts\"].append({\n \"type_id\": 3, \n \"type\": \"Service\",\n \"name\": service[\"Name\"],\n \"display_name\": service[\"DisplayName\"],\n \"service_start_type_id\": start_type[\"id\"],\n \"service_start_type\": start_type[\"type\"], \n \"service_type_id\": service[\"ServiceType\"], # No mapping to OCSF yet, see comment above.\n })\n \n process_service_observables__observable_array.append(observable)\n \n #phantom.debug(process_service_observables__observable_array)\n \n", + "warnings": {}, + "x": -320, + "y": 1020 + }, + "7": { + "data": { + "advanced": { + "customName": "format fql", + "customNameId": 0, + "description": "Format the FQL query to get the input device information using its ID or hostname.", + "join": [], + "note": "Format the FQL query to get the input device information using its ID or hostname." + }, + "functionId": 2, + "functionName": "format_fql", + "id": "7", + "parameters": [ + "playbook_input:device" + ], + "template": "%%\nhostname:['{0}'],device_id:['{0}']\n%%", + "type": "format" + }, + "errors": {}, + "id": "7", + "type": "format", + "warnings": {}, + "x": 340, + "y": 328 + }, + "8": { + "data": { + "action": "query device", + "actionType": "investigate", + "advanced": { + "customName": "query device", + "customNameId": 0, + "description": "Get information about the device to unquarantine using its hostname or device id.", + "join": [], + "note": "Get information about the device to unquarantine using its hostname or device id." + }, + "connector": "CrowdStrike OAuth API", + "connectorConfigs": [ + "crowdstrike_oauth_api" + ], + "connectorId": "ae971ba5-3117-444a-8ac5-6ce779f3a232", + "connectorVersion": "v1", + "functionId": 1, + "functionName": "query_device", + "id": "8", + "loop": { + "enabled": false, + "exitAfterUnit": "m", + "exitAfterValue": 10, + "exitConditionEnabled": false, + "exitLoopAfter": 2, + "pauseUnit": "m", + "pauseValue": 2 + }, + "parameters": { + "filter": "format_fql:formatted_data.*", + "limit": 50 + }, + "requiredParameters": [ + { + "data_type": "numeric", + "default": 50, + "field": "limit" + } + ], + "type": "action" + }, + "errors": {}, + "id": "8", + "type": "action", + "warnings": {}, + "x": 340, + "y": 504 + } + }, + "notes": "Inputs: device (CrowdStrike Device ID or Hostname)\nInteractions: CrowdStrike OAuth API\nActions: unquarantine device\nOutputs: observables, markdown report" + }, + "input_spec": [ + { + "contains": [ + "host name" + ], + "description": "Device ID or hostname of the host to quarantine", + "name": "device" + } + ], + "output_spec": [ + { + "contains": [], + "datapaths": [ + "process_process_observables:custom_function:observable_array" + ], + "deduplicate": false, + "description": "An array of process observable dictionaries", + "metadata": {}, + "name": "process_observable" + }, + { + "contains": [], + "datapaths": [ + "process_network_observables:custom_function:observable_array" + ], + "deduplicate": false, + "description": "An array of network connection observable dictionaries", + "metadata": {}, + "name": "network_observable" + }, + { + "contains": [], + "datapaths": [ + "process_system_observables:custom_function:observable_array" + ], + "deduplicate": false, + "description": "An array of endpoint observable dictionaries", + "metadata": {}, + "name": "endpoint_observable" + }, + { + "contains": [], + "datapaths": [ + "process_service_observables:custom_function:observable_array" + ], + "deduplicate": false, + "description": "An array of service observable dictionaries", + "metadata": {}, + "name": "service_observable" + } + ], + "playbook_trigger": "artifact_created", + "playbook_type": "data", + "python_version": "3", + "schema": "5.0.15", + "version": "6.3.1.178" + }, + "create_time": "2025-05-01T17:39:10.288392+00:00", + "draft_mode": false, + "labels": [ + "*" + ], + "tags": [ + "CrowdStrike_OAuth_API", + "host name", + "enrichment", + "D3-NTA", + "D3-PA", + "D3-AI" + ] +} \ No newline at end of file diff --git a/playbooks/CrowdStrike_OAuth_API_Endpoint_Analysis.png b/playbooks/CrowdStrike_OAuth_API_Endpoint_Analysis.png new file mode 100644 index 0000000000000000000000000000000000000000..2ec126e9dbb91e522e76d9cfbfb23a0a9f4a2a93 GIT binary patch literal 43811 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

mFomTK%^4^AnNNWCpt6$qF!T-JJQO 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 M86P$(F>sFl9|XyjzyJUM literal 0 HcmV?d00001 diff --git a/playbooks/CrowdStrike_OAuth_API_Endpoint_Analysis.py b/playbooks/CrowdStrike_OAuth_API_Endpoint_Analysis.py new file mode 100644 index 0000000000..0dfc82c0c9 --- /dev/null +++ b/playbooks/CrowdStrike_OAuth_API_Endpoint_Analysis.py @@ -0,0 +1,830 @@ +""" +Accepts a hostname or device id as input and collects running processes, network connections and various system information from the device via Crowdstrike. We then generate an observable report for each. This can be customized based on user preference. +""" + + +import phantom.rules as phantom +import json +from datetime import datetime, timedelta + + +@phantom.playbook_block() +def on_start(container): + phantom.debug('on_start() called') + + # call 'input_filter' block + input_filter(container=container) + + return + +@phantom.playbook_block() +def input_filter(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, loop_state_json=None, **kwargs): + phantom.debug("input_filter() called") + + ################################################################################ + # Determines if the provided inputs are present in the dataset. + ################################################################################ + + # collect filtered artifact ids and results for 'if' condition 1 + matched_artifacts_1, matched_results_1 = phantom.condition( + container=container, + conditions=[ + ["playbook_input:device", "!=", ""] + ], + name="input_filter:condition_1", + delimiter=None) + + # call connected blocks if filtered artifacts or results + if matched_artifacts_1 or matched_results_1: + format_fql(action=action, success=success, container=container, results=results, handle=handle, filtered_artifacts=matched_artifacts_1, filtered_results=matched_results_1) + + return + + +@phantom.playbook_block() +def format_fql(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("format_fql() called") + + ################################################################################ + # Format the FQL query to get the input device information using its ID or hostname. + ################################################################################ + + template = """%%\nhostname:['{0}'],device_id:['{0}']\n%%""" + + # parameter list for template variable replacement + parameters = [ + "playbook_input:device" + ] + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.format(container=container, template=template, parameters=parameters, name="format_fql") + + query_device(container=container) + + return + + +@phantom.playbook_block() +def query_device(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("query_device() called") + + # phantom.debug('Action: {0} {1}'.format(action['name'], ('SUCCEEDED' if success else 'FAILED'))) + + ################################################################################ + # Get information about the device to unquarantine using its hostname or device + # id. + ################################################################################ + + format_fql__as_list = phantom.get_format_data(name="format_fql__as_list") + + parameters = [] + + # build parameters list for 'query_device' call + for format_fql__item in format_fql__as_list: + parameters.append({ + "limit": 50, + "filter": format_fql__item, + }) + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.act("query device", parameters=parameters, name="query_device", assets=["crowdstrike_oauth_api"], callback=query_device_callback) + + return + + +@phantom.playbook_block() +def query_device_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("query_device_callback() called") + + + create_session(action=action, success=success, container=container, results=results, handle=handle, filtered_artifacts=filtered_artifacts, filtered_results=filtered_results) + get_system_info(action=action, success=success, container=container, results=results, handle=handle, filtered_artifacts=filtered_artifacts, filtered_results=filtered_results) + + + return + + +@phantom.playbook_block() +def create_session(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_session() called") + + # phantom.debug('Action: {0} {1}'.format(action['name'], ('SUCCEEDED' if success else 'FAILED'))) + + ################################################################################ + # Create a Real Time Response (RTR) session on CS Falcon to interact with the + # endpoint. + ################################################################################ + + query_device_result_data = phantom.collect2(container=container, datapath=["query_device:action_result.data.*.device_id","query_device:action_result.parameter.context.artifact_id"], action_results=results) + + parameters = [] + + # build parameters list for 'create_session' call + for query_device_result_item in query_device_result_data: + if query_device_result_item[0] is not None: + parameters.append({ + "device_id": query_device_result_item[0], + "context": {'artifact_id': query_device_result_item[1]}, + }) + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.act("create session", parameters=parameters, name="create_session", assets=["crowdstrike_oauth_api"], callback=create_session_callback) + + return + + +@phantom.playbook_block() +def create_session_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_session_callback() called") + + + run_admin_command_ps(action=action, success=success, container=container, results=results, handle=handle, filtered_artifacts=filtered_artifacts, filtered_results=filtered_results) + run_admin_command_netstat(action=action, success=success, container=container, results=results, handle=handle, filtered_artifacts=filtered_artifacts, filtered_results=filtered_results) + run_admin_command_services(action=action, success=success, container=container, results=results, handle=handle, filtered_artifacts=filtered_artifacts, filtered_results=filtered_results) + + + return + + +@phantom.playbook_block() +def run_admin_command_ps(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("run_admin_command_ps() called") + + # phantom.debug('Action: {0} {1}'.format(action['name'], ('SUCCEEDED' if success else 'FAILED'))) + + ################################################################################ + # Gets a list of running processes on the specified endpoint. + ################################################################################ + + query_device_result_data = phantom.collect2(container=container, datapath=["query_device:action_result.data.*.device_id","query_device:action_result.parameter.context.artifact_id"], action_results=results) + create_session_result_data = phantom.collect2(container=container, datapath=["create_session:action_result.data.*.resources.*.session_id","create_session:action_result.parameter.context.artifact_id"], action_results=results) + + parameters = [] + + # build parameters list for 'run_admin_command_ps' call + for query_device_result_item in query_device_result_data: + for create_session_result_item in create_session_result_data: + if query_device_result_item[0] is not None and create_session_result_item[0] is not None: + parameters.append({ + "command": "ps", + "device_id": query_device_result_item[0], + "session_id": create_session_result_item[0], + "context": {'artifact_id': create_session_result_item[1]}, + }) + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.act("run admin command", parameters=parameters, name="run_admin_command_ps", assets=["crowdstrike_oauth_api"], callback=process_process_observables) + + return + + +@phantom.playbook_block() +def process_process_observables(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("process_process_observables() called") + + ################################################################################ + # Format a normalized output for each process + ################################################################################ + + query_device_result_data = phantom.collect2(container=container, datapath=["query_device:action_result.data.*.hostname"], action_results=results) + run_admin_command_ps_result_data = phantom.collect2(container=container, datapath=["run_admin_command_ps:action_result.parameter","run_admin_command_ps:action_result.data","run_admin_command_ps:action_result.status","run_admin_command_ps:action_result.message"], action_results=results) + + query_device_result_item_0 = [item[0] for item in query_device_result_data] + run_admin_command_ps_result_item_0 = [item[0] for item in run_admin_command_ps_result_data] + run_admin_command_ps_result_item_1 = [item[1] for item in run_admin_command_ps_result_data] + run_admin_command_ps_result_item_2 = [item[2] for item in run_admin_command_ps_result_data] + run_admin_command_ps_result_message = [item[3] for item in run_admin_command_ps_result_data] + + process_process_observables__observable_array = None + + ################################################################################ + ## Custom Code Start + ################################################################################ + + import re + process_process_observables__observable_array = [] + i = 0 + + ################################################################################ + # This returns process information from stdout as text, with lines separated by newlines (\n). + # For example: + # + # Name Id Start Time (UTC-7) WorkingMemory(kb) CPU(s) HandleCount Path + # ---- -- ------------------ ----------------- ------ ----------- ---- + # conhost 2216 4/8/2025 12:14:21 PM 5,612 0.05 93 C:\Windows\system32\conhost.exe + # conhost 4192 4/8/2025 12:14:21 PM 5,620 0.03 93 C:\Windows\system32\conhost.exe + # CSFalconService 488 3/27/2025 9:05:13 AM 60,196 312.31 671 + # csrss 672 3/11/2025 4:21:37 AM 4,476 667.27 500 + # powershell 3768 4/8/2025 12:14:21 PM 60,892 0.66 511 C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe + # VGAuthService 2412 3/11/2025 4:21:42 AM 10,920 0.05 149 C:\Program Files\VMware\VMware Tools\VMware + # VGAuth\VGAuthService.exe + # vm3dservice 2440 3/11/2025 4:21:42 AM 6,096 0.02 123 C:\Windows\system32\vm3dservice.exe + # vm3dservice 2568 3/11/2025 4:21:42 AM 6,564 0.03 110 C:\Windows\system32\vm3dservice.exe + # vm3dservice 4248 3/11/2025 4:25:31 AM 6,564 0.08 111 C:\Windows\system32\vm3dservice.exe + + for system in run_admin_command_ps_result_item_1: + device_id = run_admin_command_ps_result_item_0[i].get("device_id") + hostname = query_device_result_item_0[i] + + observable = { + "source": "Crowdstrike OAuth API", + "type": "Endpoint", + "activity_name": "Process Collection", + "uid": device_id, + "hostname": hostname, + "status": run_admin_command_ps_result_item_2[i], + "status_detail": run_admin_command_ps_result_message[i], + "process_artifacts": [] + } + + stdout = system[0]["resources"][0]["stdout"] + + # Skip header lines and read each one using REGEX (for lack of a better way). + for line in stdout.split('\n')[3:]: + pattern = re.compile('^(?P\w+)\s+(?P\d+)\s+(?P[\d\/:\s]+(AM|PM))\s+(?P[\d,]+)\s+(?P[\d,\.]+)\s+(?P\d+)\s(?P.*)$') + matches = pattern.match(line) + orphan_matches = pattern.match(line) + + if matches: + name = matches.group('name') + pid = matches.group('pid') + start_time = matches.group('start_time') + working_memory = matches.group('working_memory') + cpu = matches.group('cpu') + handle_count = matches.group('handle_count') + path = matches.group('path') + + # Skip lines we don't care about. + if line.strip() and name not in ['Idle','System']: + observable['process_artifacts'].append( + { + "name": name, + "pid": pid, + "path": path.strip(), + "created_time": start_time, + "xattributes": [ + { + "working_memory": working_memory, + "handle_count": handle_count, + "cpu": cpu + } + ] + } + ) + else: + # Some paths are continued on the next line... fold them back into the correct observable. + orphan_pattern = re.compile('^\s+(?P.*?)\s+$') + orphan_matches = orphan_pattern.match(line) + + if orphan_matches: + orphaned_path = orphan_matches.group('orphaned_path') + previous_observable = observable['process_artifacts'][-1] + if previous_observable: + previous_observable["path"] = previous_observable["path"] + " " + orphaned_path + + + #phantom.debug(f"observable = {observable}") + process_process_observables__observable_array.append(observable) + i+=1 + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.save_run_data(key="process_process_observables:observable_array", value=json.dumps(process_process_observables__observable_array)) + + join_delete_session(container=container) + + return + + +@phantom.playbook_block() +def run_admin_command_netstat(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("run_admin_command_netstat() called") + + # phantom.debug('Action: {0} {1}'.format(action['name'], ('SUCCEEDED' if success else 'FAILED'))) + + ################################################################################ + # List the open sockets & network ports on the specified endpoint. + ################################################################################ + + query_device_result_data = phantom.collect2(container=container, datapath=["query_device:action_result.data.*.device_id","query_device:action_result.parameter.context.artifact_id"], action_results=results) + create_session_result_data = phantom.collect2(container=container, datapath=["create_session:action_result.data.*.resources.*.session_id","create_session:action_result.parameter.context.artifact_id"], action_results=results) + + parameters = [] + + # build parameters list for 'run_admin_command_netstat' call + for query_device_result_item in query_device_result_data: + for create_session_result_item in create_session_result_data: + if query_device_result_item[0] is not None and create_session_result_item[0] is not None: + parameters.append({ + "command": "netstat", + "device_id": query_device_result_item[0], + "session_id": create_session_result_item[0], + "context": {'artifact_id': create_session_result_item[1]}, + }) + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.act("run admin command", parameters=parameters, name="run_admin_command_netstat", assets=["crowdstrike_oauth_api"], callback=process_network_observables) + + return + + +@phantom.playbook_block() +def process_network_observables(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("process_network_observables() called") + + ################################################################################ + # Format a normalized output for each network connection + ################################################################################ + + run_admin_command_netstat_result_data = phantom.collect2(container=container, datapath=["run_admin_command_netstat:action_result.parameter.device_id","run_admin_command_netstat:action_result.status","run_admin_command_netstat:action_result.message","run_admin_command_netstat:action_result.data"], action_results=results) + query_device_result_data = phantom.collect2(container=container, datapath=["query_device:action_result.data.*.hostname"], action_results=results) + + run_admin_command_netstat_parameter_device_id = [item[0] for item in run_admin_command_netstat_result_data] + run_admin_command_netstat_result_item_1 = [item[1] for item in run_admin_command_netstat_result_data] + run_admin_command_netstat_result_message = [item[2] for item in run_admin_command_netstat_result_data] + run_admin_command_netstat_result_item_3 = [item[3] for item in run_admin_command_netstat_result_data] + query_device_result_item_0 = [item[0] for item in query_device_result_data] + + process_network_observables__observable_array = None + + ################################################################################ + ## Custom Code Start + ################################################################################ + + import re + process_network_observables__observable_array = [] + i = 0 + + ################################################################################ + # This returns network connection information from stdout as text, with lines separated by newlines (\n). + # For example: + # + # Proto Local Address Foreign Address State Owning Process Id Owning Process Name Process Start Time (UTC-8) + # ----- ------------- --------------- ----- ----------------- ------------------- -------------------------- + # TCP :::49689 :::0 Listen 920 C:\Windows\system32\lsass.exe 10/25/2024 10:28:22 AM + # TCP :::49675 :::0 Listen 2060 C:\Windows\system32\svchost.exe 10/25/2024 10:28:13 AM + # TCP :::49669 :::0 Listen 2164 C:\Windows\System32\spoolsv.exe 10/25/2024 10:28:13 AM + # TCP :::49666 :::0 Listen 1056 C:\Windows\system32\svchost.exe 10/25/2024 10:28:13 AM + # TCP :::49665 :::0 Listen 1116 C:\Windows\System32\svchost.exe 10/25/2024 10:28:12 AM + # TCP :::49664 :::0 Listen 768 10/25/2024 10:28:12 AM + # TCP :::3389 :::0 Listen 1048 C:\Windows\System32\svchost.exe 10/25/2024 10:28:13 AM + # TCP :::445 :::0 Listen 4 10/25/2024 10:28:13 AM + # TCP :::135 :::0 Listen 352 C:\Windows\system32\svchost.exe 10/25/2024 10:28:12 AM + # TCP 0.0.0.0:58808 0.0.0.0:0 Bound 1056 C:\Windows\system32\svchost.exe 11/23/2024 9:31:16 PM + # TCP 10.1.19.4:59929 54.241.197.58:443 Established 4 2/24/2025 2:34:34 PM + # TCP 10.1.19.4:58808 13.64.180.106:443 Established 1056 C:\Windows\system32\svchost.exe 11/23/2024 9:31:16 PM + # TCP 0.0.0.0:49689 0.0.0.0:0 Listen 920 C:\Windows\system32\lsass.exe 10/25/2024 10:28:22 AM + # TCP 0.0.0.0:49679 0.0.0.0:0 Listen 904 10/25/2024 10:28:13 AM + # TCP 0.0.0.0:49675 0.0.0.0:0 Listen 2060 C:\Windows\system32\svchost.exe 10/25/2024 10:28:13 AM + # TCP 0.0.0.0:49669 0.0.0.0:0 Listen 2164 C:\Windows\System32\spoolsv.exe 10/25/2024 10:28:13 AM + # UDP ::1:60661 *:* Listen 5472 C:\Windows\system32\svchost.exe 10/26/2024 9:03:09 AM + # UDP fe80::9c8b:6e28:cc9:6e2e%2:60660 *:* Listen 5472 C:\Windows\system32\svchost.exe 10/26/2024 9:03:09 AM + # UDP :::5355 *:* Listen 1488 C:\Windows\System32\svchost.exe 2/25/2025 6:52:43 AM + # UDP :::5353 *:* Listen 1488 C:\Windows\System32\svchost.exe 2/25/2025 6:52:43 AM + + for system in run_admin_command_netstat_result_item_3: + device_id = run_admin_command_netstat_parameter_device_id[i] + hostname = query_device_result_item_0[i] + + observable = { + "source": "Crowdstrike OAuth API", + "type": "Endpoint", + "activity_name": "Network Connections Collection", + "uid": device_id, + "hostname": hostname, + "status": run_admin_command_netstat_result_item_1[i], + "status_detail": run_admin_command_netstat_result_message[i], + "network_artifacts": [] + } + + phantom.debug(f"system -> {system}") + + stdout = system[0]["resources"][0]["stdout"] + if stdout: + # Skip header lines and read each one, extracting fields via REGEX (for lack of a better way). + for line in stdout.split('\n')[3:]: + line_groups = re.search('^(\w+)\s+(.*?)\s+(.*?)\s+(\w+)\s+(\d+)\s(.*?)\s(\d.*?)\s+$', line) + + if line_groups: + proto = line_groups.group(1) + local_address = line_groups.group(2) + foreign_address = line_groups.group(3) + state = line_groups.group(4) + process_id = line_groups.group(5) + process_path = line_groups.group(6) + start_time = line_groups.group(7) + + observable['network_artifacts'].append( + { + "protocol_name": proto, + "local_address": local_address, + "foreign_address": foreign_address, + "state": state, + "start_time": start_time, + "process": [ + { + "pid": process_id, + "path": process_path + } + ] + } + ) + + #phantom.debug(observable) + process_network_observables__observable_array.append(observable) + i+=1 + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.save_run_data(key="process_network_observables:observable_array", value=json.dumps(process_network_observables__observable_array)) + + join_delete_session(container=container) + + return + + +@phantom.playbook_block() +def get_system_info(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("get_system_info() called") + + # phantom.debug('Action: {0} {1}'.format(action['name'], ('SUCCEEDED' if success else 'FAILED'))) + + ################################################################################ + # Get information from the endpoint. + ################################################################################ + + query_device_result_data = phantom.collect2(container=container, datapath=["query_device:action_result.data.*.device_id","query_device:action_result.parameter.context.artifact_id"], action_results=results) + + parameters = [] + + # build parameters list for 'get_system_info' call + for query_device_result_item in query_device_result_data: + if query_device_result_item[0] is not None: + parameters.append({ + "id": query_device_result_item[0], + "context": {'artifact_id': query_device_result_item[1]}, + }) + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.act("get system info", parameters=parameters, name="get_system_info", assets=["crowdstrike_oauth_api"], callback=process_system_observables) + + return + + +@phantom.playbook_block() +def process_system_observables(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("process_system_observables() called") + + ################################################################################ + # Format a normalized output for the endpoint. + ################################################################################ + + get_system_info_result_data = phantom.collect2(container=container, datapath=["get_system_info:action_result.status","get_system_info:action_result.message","get_system_info:action_result.data"], action_results=results) + + get_system_info_result_item_0 = [item[0] for item in get_system_info_result_data] + get_system_info_result_message = [item[1] for item in get_system_info_result_data] + get_system_info_result_item_2 = [item[2] for item in get_system_info_result_data] + + process_system_observables__observable_array = None + + ################################################################################ + ## Custom Code Start + ################################################################################ + process_system_observables__observable_array = [] + i = 0 + + for system in get_system_info_result_item_2: + data = system[0] + if data: + observable = { + "source": "Crowdstrike OAuth API", + "type": "Endpoint", + "activity_name": "System Collection", + "uid": data['device_id'], + "hostname": data['hostname'], + "status": get_system_info_result_item_0[i], + "status_detail": get_system_info_result_message[i], + "endpoint_artifacts": [ + { + "agents": [ + { + "type": "Endpoint Detection and Response", + "type_id": 1, + "uid": data['cid'], + "vendor_name": "Crowdstrike", + "version": data['agent_version'] + } + ], + "ip": data['local_ip'], + "external_ip": data['external_ip'], + "mac": data['mac_address'], + "domain": data['machine_domain'], + "type": data['product_type_desc'], + "last_seen": data['last_seen'], + "last_reboot": data['last_reboot'], + "last_login_user_sid": data.get('last_login_user_sid', ''), + "last_login_timestamp": data.get('last_login_timestamp', ''), + "operating_system": { + "build:": data['os_build'], + "kernel_release": data['kernel_version'], + "name": data['os_product_name'], + "type": data['platform_name'], + "version": data['os_version'] + }, + "hw_info": { + "bios_manufacturer": data['bios_manufacturer'], + "bios_version": data['bios_version'], + "serial_number": data['serial_number'], + "chassis": data['chassis_type_desc'] + } + } + ] + } + + policies = [] + for policy in data['policies']: + policies.append( + { + "name": policy['policy_type'], + "uid": policy['policy_id'], + "is_applied": policy['applied'] + } + ) + observable['endpoint_artifacts'][0]['agents'][0]['policies'] = policies + + #phantom.debug(f"---> OBSERVABLE: {observable}") + process_system_observables__observable_array.append(observable) + i+=1 + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.save_run_data(key="process_system_observables:observable_array", value=json.dumps(process_system_observables__observable_array)) + + return + + +@phantom.playbook_block() +def join_delete_session(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("join_delete_session() called") + + if phantom.completed(action_names=["run_admin_command_ps", "run_admin_command_netstat", "run_admin_command_services"]): + # call connected block "delete_session" + delete_session(container=container, handle=handle) + + return + + +@phantom.playbook_block() +def delete_session(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("delete_session() called") + + # phantom.debug('Action: {0} {1}'.format(action['name'], ('SUCCEEDED' if success else 'FAILED'))) + + ################################################################################ + # Deletes the Real Time Response (RTR) session on CS Falcon. + ################################################################################ + + create_session_result_data = phantom.collect2(container=container, datapath=["create_session:action_result.summary.session_id","create_session:action_result.parameter.context.artifact_id"], action_results=results) + + parameters = [] + + # build parameters list for 'delete_session' call + for create_session_result_item in create_session_result_data: + if create_session_result_item[0] is not None: + parameters.append({ + "session_id": create_session_result_item[0], + "context": {'artifact_id': create_session_result_item[1]}, + }) + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.act("delete session", parameters=parameters, name="delete_session", assets=["crowdstrike_oauth_api"]) + + return + + +@phantom.playbook_block() +def run_admin_command_services(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("run_admin_command_services() called") + + # phantom.debug('Action: {0} {1}'.format(action['name'], ('SUCCEEDED' if success else 'FAILED'))) + + ################################################################################ + # Runs a set of Powershell commands to gather details about the services on the + # endpoint. This requires that the host response policy permits it on the CrowdStrike + # side. + ################################################################################ + + query_device_result_data = phantom.collect2(container=container, datapath=["query_device:action_result.data.*.device_id","query_device:action_result.parameter.context.artifact_id"], action_results=results) + create_session_result_data = phantom.collect2(container=container, datapath=["create_session:action_result.data.*.resources.*.session_id","create_session:action_result.parameter.context.artifact_id"], action_results=results) + + parameters = [] + + # build parameters list for 'run_admin_command_services' call + for query_device_result_item in query_device_result_data: + for create_session_result_item in create_session_result_data: + if query_device_result_item[0] is not None and create_session_result_item[0] is not None: + parameters.append({ + "data": "-Raw=```Get-Service | Select-Object -Property Name, DisplayName, Status, ServiceType, StartType | ConvertTo-Json```", + "command": "runscript", + "device_id": query_device_result_item[0], + "session_id": create_session_result_item[0], + "context": {'artifact_id': create_session_result_item[1]}, + }) + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.act("run admin command", parameters=parameters, name="run_admin_command_services", assets=["crowdstrike_oauth_api"], callback=process_service_observables) + + return + + +@phantom.playbook_block() +def process_service_observables(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("process_service_observables() called") + + ################################################################################ + # Format a normalized output for each service + ################################################################################ + + query_device_result_data = phantom.collect2(container=container, datapath=["query_device:action_result.data.*.device_id","query_device:action_result.data.*.hostname"], action_results=results) + run_admin_command_services_result_data = phantom.collect2(container=container, datapath=["run_admin_command_services:action_result.data.*.resources","run_admin_command_services:action_result.status","run_admin_command_services:action_result.message"], action_results=results) + + query_device_result_item_0 = [item[0] for item in query_device_result_data] + query_device_result_item_1 = [item[1] for item in query_device_result_data] + run_admin_command_services_result_item_0 = [item[0] for item in run_admin_command_services_result_data] + run_admin_command_services_result_item_1 = [item[1] for item in run_admin_command_services_result_data] + run_admin_command_services_result_message = [item[2] for item in run_admin_command_services_result_data] + + process_service_observables__observable_array = None + + ################################################################################ + ## Custom Code Start + ################################################################################ + + import json + + # Ref: https://learn.microsoft.com/en-us/dotnet/api/system.serviceprocess.servicestartmode?view=net-9.0-pp + ocsf_start_type_mapping = { + "0": { "id": "1", "type": "Boot" }, # Boot -> Boot + "1": { "id": "2", "type": "System" }, # System -> System + "2": { "id": "3", "type": "Auto" }, # Automatic -> Auto + "3": { "id": "4", "type": "Demand" }, # Manual -> Demand + "4": { "id": "5", "type": "Disabled" } # Disabled -> Disabled + } + + # Ref: https://learn.microsoft.com/en-us/dotnet/api/system.serviceprocess.servicetype?view=net-9.0-pp + ocsf_service_type_mapping = { + # Unused for now. OCSF doesn't seem to properly support BITWISE ENUMs yet. + # See "service_type_id" at https://schema.ocsf.io/1.4.0/objects/win/win_service + } + + process_service_observables__observable_array = [] + for device_id, hostname, services, status, messsage in zip(query_device_result_item_0, query_device_result_item_1, run_admin_command_services_result_item_0, run_admin_command_services_result_item_1, run_admin_command_services_result_message): + + observable = { + "source": "Crowdstrike OAuth API", + "type": "Endpoint", + "activity_name": "Services Collection", + "uid": device_id, + "hostname": hostname, + "status": status, + "status_detail": messsage, + "service_artifacts": [] + } + + if services[0] and services[0]["stdout"]: + services_json = json.loads(services[0]["stdout"]) + for service in services_json: + start_type = ocsf_start_type_mapping.get(str(service["StartType"])) + + observable["service_artifacts"].append({ + "type_id": 3, + "type": "Service", + "name": service["Name"], + "display_name": service["DisplayName"], + "service_start_type_id": start_type["id"], + "service_start_type": start_type["type"], + "service_type_id": service["ServiceType"], # No mapping to OCSF yet, see comment above. + }) + + process_service_observables__observable_array.append(observable) + + #phantom.debug(process_service_observables__observable_array) + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.save_run_data(key="process_service_observables:observable_array", value=json.dumps(process_service_observables__observable_array)) + + join_delete_session(container=container) + + return + + +@phantom.playbook_block() +def on_finish(container, summary): + phantom.debug("on_finish() called") + + process_process_observables__observable_array = json.loads(_ if (_ := phantom.get_run_data(key="process_process_observables:observable_array")) != "" else "null") # pylint: disable=used-before-assignment + process_network_observables__observable_array = json.loads(_ if (_ := phantom.get_run_data(key="process_network_observables:observable_array")) != "" else "null") # pylint: disable=used-before-assignment + process_system_observables__observable_array = json.loads(_ if (_ := phantom.get_run_data(key="process_system_observables:observable_array")) != "" else "null") # pylint: disable=used-before-assignment + process_service_observables__observable_array = json.loads(_ if (_ := phantom.get_run_data(key="process_service_observables:observable_array")) != "" else "null") # pylint: disable=used-before-assignment + + output = { + "process_observable": process_process_observables__observable_array, + "network_observable": process_network_observables__observable_array, + "endpoint_observable": process_system_observables__observable_array, + "service_observable": process_service_observables__observable_array, + } + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.save_playbook_output_data(output=output) + + return \ No newline at end of file diff --git a/playbooks/CrowdStrike_OAuth_API_Endpoint_Analysis.yml b/playbooks/CrowdStrike_OAuth_API_Endpoint_Analysis.yml new file mode 100644 index 0000000000..30c668dfe0 --- /dev/null +++ b/playbooks/CrowdStrike_OAuth_API_Endpoint_Analysis.yml @@ -0,0 +1,34 @@ +name: CrowdStrike OAuth API Endpoint Analysis +id: +version: 1 +date: '2025-06-09' +author: Christian Cloutier, Splunk +type: Investigation +description: "Accepts a hostname or device id as input and collects running processes, network connections and various system information from the device via Crowdstrike. We then generate an observable report for each. This can be customized based on user preference." +playbook: CrowdStrike_OAuth_API_Endpoint_Analysis +how_to_implement: This input playbook requires the CrowdStrike OAuth API connector to be configured. It is designed to work with an endpoint hostname or device id and collect key information about the system, network connections and running processes for use in automation playbooks. +references: [] +app_list: + - CrowdStrike OAuth API +tags: + platform_tags: + - "host name" + - "device id" + - "enrichment" + - "D3-NTA" + - "D3-PA" + - "D3-AI" + - "CrowdStrike_OAuth_API" + playbook_type: Input + vpe_type: Modern + playbook_fields: [device] + product: + - Splunk SOAR + use_cases: + - Enrichment + - Malware + - Endpoint + defend_technique_id: + - D3-NTA + - D3-PA + - D3-AI diff --git a/playbooks/CrowdStrike_OAuth_API_Executable_Denylisting.json b/playbooks/CrowdStrike_OAuth_API_Executable_Denylisting.json new file mode 100644 index 0000000000..9e94bdc1ae --- /dev/null +++ b/playbooks/CrowdStrike_OAuth_API_Executable_Denylisting.json @@ -0,0 +1,407 @@ +{ + "blockly": false, + "blockly_xml": "", + "category": "Executable Denylisting", + "coa": { + "data": { + "description": "Accepts a hostname or device id as well as a file hash as input and add an indicator (IOC) for a device in Crowdstrike. We then generate an observable report as well as a Markdown formatted report. Both reports can be customized based on user preference.", + "edges": [ + { + "id": "port_0_to_port_2", + "sourceNode": "0", + "sourcePort": "0_out", + "targetNode": "2", + "targetPort": "2_in" + }, + { + "id": "port_5_to_port_1", + "sourceNode": "5", + "sourcePort": "5_out", + "targetNode": "1", + "targetPort": "1_in" + }, + { + "conditions": [ + { + "index": 0 + } + ], + "id": "port_2_to_port_7", + "sourceNode": "2", + "sourcePort": "2_out", + "targetNode": "7", + "targetPort": "7_in" + }, + { + "id": "port_7_to_port_8", + "sourceNode": "7", + "sourcePort": "7_out", + "targetNode": "8", + "targetPort": "8_in" + }, + { + "id": "port_9_to_port_5", + "sourceNode": "9", + "sourcePort": "9_out", + "targetNode": "5", + "targetPort": "5_in" + }, + { + "id": "port_8_to_port_18", + "sourceNode": "8", + "sourcePort": "8_out", + "targetNode": "18", + "targetPort": "18_in" + }, + { + "id": "port_18_to_port_9", + "sourceNode": "18", + "sourcePort": "18_out", + "targetNode": "9", + "targetPort": "9_in" + } + ], + "hash": "7505d40f9e5d889d81302b51e4d2f10f2d245c5f", + "nodes": { + "0": { + "data": { + "advanced": { + "join": [] + }, + "functionName": "on_start", + "id": "0", + "type": "start" + }, + "errors": {}, + "id": "0", + "type": "start", + "warnings": {}, + "x": 19.999999999999986, + "y": -1.9184653865522705e-13 + }, + "1": { + "data": { + "advanced": { + "join": [] + }, + "functionName": "on_finish", + "id": "1", + "type": "end" + }, + "errors": {}, + "id": "1", + "type": "end", + "warnings": {}, + "x": 19.999999999999986, + "y": 1196 + }, + "18": { + "data": { + "action": "upload indicator", + "actionType": "contain", + "advanced": { + "customName": "upload indicator", + "customNameId": 0, + "description": "Upload indicator that we want CrowdStrike to prevent and watch for all platforms.", + "join": [], + "note": "Upload indicator that we want CrowdStrike to prevent and watch for all platforms." + }, + "connector": "CrowdStrike OAuth API", + "connectorConfigs": [ + "crowdstrike_oauth_api" + ], + "connectorId": "ae971ba5-3117-444a-8ac5-6ce779f3a232", + "connectorVersion": "v1", + "functionId": 1, + "functionName": "upload_indicator", + "id": "18", + "loop": { + "enabled": false, + "exitAfterUnit": "m", + "exitAfterValue": 10, + "exitConditionEnabled": false, + "exitLoopAfter": 2, + "pauseUnit": "m", + "pauseValue": 2 + }, + "parameters": { + "action": "prevent", + "description": "File Indicator blocked from Splunk SOAR", + "ioc": "filtered-data:input_filter:condition_1:playbook_input:hash", + "platforms": "linux,mac,windows", + "severity": "MEDIUM", + "source": "IOC uploaded via Splunk SOAR" + }, + "requiredParameters": [ + { + "data_type": "string", + "field": "ioc" + }, + { + "data_type": "string", + "field": "action" + }, + { + "data_type": "string", + "default": "IOC uploaded via Splunk SOAR", + "field": "source" + }, + { + "data_type": "string", + "field": "platforms" + } + ], + "tab": "byAction", + "type": "action" + }, + "errors": {}, + "id": "18", + "type": "action", + "warnings": {}, + "x": 1.4210854715202004e-14, + "y": 680 + }, + "2": { + "data": { + "advanced": { + "customName": "input filter", + "customNameId": 0, + "description": "Determines if the provided inputs are present in the dataset.", + "join": [], + "note": "Determines if the provided inputs are present in the dataset." + }, + "conditions": [ + { + "comparisons": [ + { + "conditionIndex": 0, + "op": "!=", + "param": "playbook_input:device", + "value": "" + }, + { + "conditionIndex": 0, + "op": "!=", + "param": "playbook_input:hash", + "value": "" + } + ], + "conditionIndex": 0, + "customName": "input device and hash present", + "logic": "and" + } + ], + "functionId": 1, + "functionName": "input_filter", + "id": "2", + "type": "filter" + }, + "errors": {}, + "id": "2", + "type": "filter", + "warnings": {}, + "x": 60, + "y": 148 + }, + "5": { + "customCode": null, + "data": { + "advanced": { + "customName": "file observables", + "customNameId": 0, + "description": "Format a normalized output for each host", + "join": [], + "note": "Format a normalized output for each host." + }, + "functionId": 1, + "functionName": "file_observables", + "id": "5", + "inputParameters": [ + "query_device:action_result.data.*.device_id", + "query_device:action_result.data.*.hostname", + "filtered-data:input_filter:condition_1:playbook_input:hash", + "upload_indicator:action_result.status", + "upload_indicator:action_result.message" + ], + "outputVariables": [ + "observable_array" + ], + "type": "code" + }, + "errors": {}, + "id": "5", + "type": "code", + "userCode": " \n file_observables__observable_array = []\n \n for device_id, hostname, file_hash, status, status_message in zip(query_device_result_item_0, query_device_result_item_1, filtered_input_0_hash_values, upload_indicator_result_item_0, upload_indicator_result_message):\n # Initialize the observable dictionary\n observable = {\n \"source\": \"Crowdstrike OAuth API\",\n \"type\": \"Endpoint\",\n \"activity_name\": \"File Execution Prevention\",\n \"uid\": device_id,\n \"hostname\": hostname,\n \"status\": status,\n \"status_detail\": status_message,\n \"file\": {\n \"hashes\": [\n {\n \"algorithm\": \"SHA-256\",\n \"algorithm_id\": 3,\n \"value\": file_hash \n }\n ]\n },\n \"d3fend\": {\n \"d3f_tactic\": \"Isolate\",\n \"d3f_technique\": \"D3-EDL\",\n \"version\": \"1.0.0\"\n }\n } \n\n # Add the observable to the array\n file_observables__observable_array.append(observable)\n \n # Debug output for verification\n phantom.debug(file_observables__observable_array)\n \n", + "warnings": {}, + "x": 1.4210854715202004e-14, + "y": 1020 + }, + "7": { + "data": { + "advanced": { + "customName": "format fql", + "customNameId": 0, + "description": "Format the FQL query to get the input device information using its ID or hostname.", + "join": [], + "note": "Format the FQL query to get the input device information using its ID or hostname." + }, + "functionId": 2, + "functionName": "format_fql", + "id": "7", + "parameters": [ + "playbook_input:device" + ], + "template": "%%\nhostname:['{0}'],device_id:['{0}']\n%%", + "type": "format" + }, + "errors": {}, + "id": "7", + "type": "format", + "warnings": {}, + "x": 1.4210854715202004e-14, + "y": 320 + }, + "8": { + "data": { + "action": "query device", + "actionType": "investigate", + "advanced": { + "customName": "query device", + "customNameId": 0, + "description": "Get information about the device to unquarantine using its hostname or device id.", + "join": [], + "note": "Get information about the device to unquarantine using its hostname or device id." + }, + "connector": "CrowdStrike OAuth API", + "connectorConfigs": [ + "crowdstrike_oauth_api" + ], + "connectorId": "ae971ba5-3117-444a-8ac5-6ce779f3a232", + "connectorVersion": "v1", + "functionId": 1, + "functionName": "query_device", + "id": "8", + "loop": { + "enabled": false, + "exitAfterUnit": "m", + "exitAfterValue": 10, + "exitConditionEnabled": false, + "exitLoopAfter": 2, + "pauseUnit": "m", + "pauseValue": 2 + }, + "parameters": { + "filter": "format_fql:formatted_data.*", + "limit": 50 + }, + "requiredParameters": [ + { + "data_type": "numeric", + "default": 50, + "field": "limit" + } + ], + "type": "action" + }, + "errors": {}, + "id": "8", + "type": "action", + "warnings": {}, + "x": 1.4210854715202004e-14, + "y": 504 + }, + "9": { + "data": { + "advanced": { + "customName": "format executable denylisting report", + "customNameId": 0, + "description": "Format a summary table with the information gathered from the playbook.", + "join": [], + "note": "Format a summary table with the information gathered from the playbook." + }, + "functionId": 3, + "functionName": "format_executable_denylisting_report", + "id": "9", + "parameters": [ + "query_device:action_result.data.*.device_id", + "filtered-data:input_filter:condition_1:playbook_input:hash", + "upload_indicator:action_result.parameter.action", + "upload_indicator:action_result.status", + "upload_indicator:action_result.message" + ], + "template": "Endpoint Files were denylisted by Splunk SOAR. The table below summarizes the information gathered.\n\n| Device ID | Executable Hash | Action | Denylisting Status | Message |\n| --- | --- | --- | --- | --- |\n%%\n| {0} | {1} | {2} | {3} | {4} |\n%%", + "type": "format" + }, + "errors": {}, + "id": "9", + "type": "format", + "warnings": {}, + "x": 1.4210854715202004e-14, + "y": 828 + } + }, + "notes": "Inputs: \ndevice (CrowdStrike Device ID or Hostname)\nhash (SHA-256 File hash)\nInteractions: CrowdStrike OAuth API\nActions: query device, upload indicator\nOutputs: observables, markdown report", + "origin": { + "playbook_id": 232, + "playbook_name": "CrowdStrike_OAuth_API_File_Eviction", + "playbook_repo_id": 2, + "playbook_repo_name": "local" + } + }, + "input_spec": [ + { + "contains": [ + "host name" + ], + "description": "Device ID or hostname of the host to deny a file on", + "name": "device" + }, + { + "contains": [ + "sha256" + ], + "description": "Hash of the executable file on the endpoint to deny", + "name": "hash" + } + ], + "output_spec": [ + { + "contains": [], + "datapaths": [ + "file_observables:custom_function:observable_array" + ], + "deduplicate": false, + "description": "An array of observable dictionaries", + "metadata": {}, + "name": "observable" + }, + { + "contains": [], + "datapaths": [ + "format_executable_denylisting_report:formatted_data" + ], + "deduplicate": false, + "description": "A report of the devices that were isolated via Splunk SOAR.", + "metadata": {}, + "name": "markdown_report" + } + ], + "playbook_trigger": "artifact_created", + "playbook_type": "data", + "python_version": "3", + "schema": "5.0.15", + "version": "6.3.1.178" + }, + "create_time": "2025-04-09T12:54:45.902287+00:00", + "draft_mode": false, + "labels": [ + "*" + ], + "tags": [ + "CrowdStrike_OAuth_API", + "host name", + "D3-EDL", + "file_hash", + "response_option" + ] +} \ No newline at end of file diff --git a/playbooks/CrowdStrike_OAuth_API_Executable_Denylisting.png b/playbooks/CrowdStrike_OAuth_API_Executable_Denylisting.png new file mode 100644 index 0000000000000000000000000000000000000000..95a089c0ac54a79afe74abd28eb76b6a8ba36e63 GIT binary patch literal 27419 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", + "category": "File Collection", + "coa": { + "data": { + "description": "Accepts a hostname or device id as well as a file path as input and collects the file to the event File Vault from a device in Crowdstrike. An artifact is created from the collected file. We then generate an observable report as well as a Markdown formatted report. Both reports can be customized based on user preference.", + "edges": [ + { + "id": "port_0_to_port_2", + "sourceNode": "0", + "sourcePort": "0_out", + "targetNode": "2", + "targetPort": "2_in" + }, + { + "id": "port_5_to_port_1", + "sourceNode": "5", + "sourcePort": "5_out", + "targetNode": "1", + "targetPort": "1_in" + }, + { + "conditions": [ + { + "index": 0 + } + ], + "id": "port_2_to_port_7", + "sourceNode": "2", + "sourcePort": "2_out", + "targetNode": "7", + "targetPort": "7_in" + }, + { + "id": "port_7_to_port_8", + "sourceNode": "7", + "sourcePort": "7_out", + "targetNode": "8", + "targetPort": "8_in" + }, + { + "id": "port_9_to_port_5", + "sourceNode": "9", + "sourcePort": "9_out", + "targetNode": "5", + "targetPort": "5_in" + }, + { + "id": "port_8_to_port_11", + "sourceNode": "8", + "sourcePort": "8_out", + "targetNode": "11", + "targetPort": "11_in" + }, + { + "id": "port_11_to_port_12", + "sourceNode": "11", + "sourcePort": "11_out", + "targetNode": "12", + "targetPort": "12_in" + }, + { + "id": "port_13_to_port_14", + "sourceNode": "13", + "sourcePort": "13_out", + "targetNode": "14", + "targetPort": "14_in" + }, + { + "id": "port_15_to_port_9", + "sourceNode": "15", + "sourcePort": "15_out", + "targetNode": "9", + "targetPort": "9_in" + }, + { + "id": "port_14_to_port_18", + "sourceNode": "14", + "sourcePort": "14_out", + "targetNode": "18", + "targetPort": "18_in" + }, + { + "id": "port_18_to_port_19", + "sourceNode": "18", + "sourcePort": "18_out", + "targetNode": "19", + "targetPort": "19_in" + }, + { + "id": "port_19_to_port_15", + "sourceNode": "19", + "sourcePort": "19_out", + "targetNode": "15", + "targetPort": "15_in" + }, + { + "id": "port_12_to_port_21", + "sourceNode": "12", + "sourcePort": "12_out", + "targetNode": "21", + "targetPort": "21_in" + }, + { + "id": "port_21_to_port_13", + "sourceNode": "21", + "sourcePort": "21_out", + "targetNode": "13", + "targetPort": "13_in" + } + ], + "hash": "73a7f39d0ed6036fcc911933d66ca06fd300d55b", + "nodes": { + "0": { + "data": { + "advanced": { + "join": [] + }, + "functionName": "on_start", + "id": "0", + "type": "start" + }, + "errors": {}, + "id": "0", + "type": "start", + "warnings": {}, + "x": 19.999999999999986, + "y": -1.2789769243681803e-13 + }, + "1": { + "data": { + "advanced": { + "join": [] + }, + "functionName": "on_finish", + "id": "1", + "type": "end" + }, + "errors": {}, + "id": "1", + "type": "end", + "warnings": {}, + "x": 19.999999999999986, + "y": 2456 + }, + "11": { + "data": { + "action": "create session", + "actionType": "generic", + "advanced": { + "customName": "create session", + "customNameId": 0, + "description": "Creates a Real Time Response (RTR) session to interact with the endpoint", + "join": [], + "note": "Creates a Real Time Response (RTR) session to interact with the endpoint" + }, + "connector": "CrowdStrike OAuth API", + "connectorConfigs": [ + "crowdstrike_oauth_api" + ], + "connectorId": "ae971ba5-3117-444a-8ac5-6ce779f3a232", + "connectorVersion": "v1", + "functionId": 1, + "functionName": "create_session", + "id": "11", + "loop": { + "enabled": false, + "exitAfterUnit": "m", + "exitAfterValue": 10, + "exitConditionEnabled": false, + "exitLoopAfter": 2, + "pauseUnit": "m", + "pauseValue": 2 + }, + "parameters": { + "device_id": "query_device:action_result.data.*.device_id" + }, + "requiredParameters": [ + { + "data_type": "string", + "field": "device_id" + } + ], + "tab": "byAction", + "type": "action" + }, + "errors": {}, + "id": "11", + "type": "action", + "warnings": {}, + "x": 1.4210854715202004e-14, + "y": 680 + }, + "12": { + "data": { + "action": "run admin command", + "actionType": "generic", + "advanced": { + "customName": "run admin command get", + "customNameId": 0, + "description": "Gets the specified file based on the playbook input path", + "join": [], + "note": "Gets the specified file based on the playbook input path" + }, + "connector": "CrowdStrike OAuth API", + "connectorConfigs": [ + "crowdstrike_oauth_api" + ], + "connectorId": "ae971ba5-3117-444a-8ac5-6ce779f3a232", + "connectorVersion": "v1", + "functionId": 1, + "functionName": "run_admin_command_get", + "id": "12", + "loop": { + "enabled": false, + "exitAfterUnit": "m", + "exitAfterValue": 10, + "exitConditionEnabled": false, + "exitLoopAfter": 2, + "pauseUnit": "m", + "pauseValue": 2 + }, + "parameters": { + "command": "get", + "data": "playbook_input:path", + "device_id": "query_device:action_result.data.*.device_id", + "session_id": "create_session:action_result.summary.session_id" + }, + "requiredParameters": [ + { + "data_type": "string", + "field": "command" + }, + { + "data_type": "string", + "field": "device_id" + }, + { + "data_type": "string", + "field": "session_id" + } + ], + "tab": "byAction", + "type": "action" + }, + "errors": {}, + "id": "12", + "type": "action", + "warnings": {}, + "x": 1.4210854715202004e-14, + "y": 860 + }, + "13": { + "data": { + "action": "list session files", + "actionType": "investigate", + "advanced": { + "customName": "list session files", + "customNameId": 0, + "description": "Lists the previous file we ran a get command on, which returns its SHA256 hash", + "join": [], + "note": "Lists the previous file we ran a get command on, which returns its SHA256 hash" + }, + "connector": "CrowdStrike OAuth API", + "connectorConfigs": [ + "crowdstrike_oauth_api" + ], + "connectorId": "ae971ba5-3117-444a-8ac5-6ce779f3a232", + "connectorVersion": "v1", + "functionId": 1, + "functionName": "list_session_files", + "id": "13", + "loop": { + "enabled": false, + "exitAfterUnit": "m", + "exitAfterValue": 10, + "exitConditionEnabled": false, + "exitLoopAfter": 2, + "pauseUnit": "m", + "pauseValue": 2 + }, + "parameters": { + "session_id": "create_session:action_result.summary.session_id" + }, + "requiredParameters": [ + { + "data_type": "string", + "field": "session_id" + } + ], + "type": "action" + }, + "errors": {}, + "id": "13", + "type": "action", + "warnings": {}, + "x": 1.4210854715202004e-14, + "y": 1208 + }, + "14": { + "data": { + "action": "get session file", + "actionType": "generic", + "advanced": { + "customName": "get session file", + "customNameId": 0, + "description": "Downloads the RTR session file from the endpoint to the SOAR File Vault", + "join": [], + "note": "Downloads the RTR session file from the endpoint to the SOAR File Vault" + }, + "connector": "CrowdStrike OAuth API", + "connectorConfigs": [ + "crowdstrike_oauth_api" + ], + "connectorId": "ae971ba5-3117-444a-8ac5-6ce779f3a232", + "connectorVersion": "v1", + "functionId": 1, + "functionName": "get_session_file", + "id": "14", + "loop": { + "enabled": false, + "exitAfterUnit": "m", + "exitAfterValue": 10, + "exitConditionEnabled": false, + "exitLoopAfter": 2, + "pauseUnit": "m", + "pauseValue": 2 + }, + "parameters": { + "file_hash": "list_session_files:action_result.data.*.resources.*.sha256", + "file_name": "playbook_input:path", + "session_id": "create_session:action_result.summary.session_id" + }, + "requiredParameters": [ + { + "data_type": "string", + "field": "file_hash" + }, + { + "data_type": "string", + "field": "session_id" + } + ], + "tab": "byAction", + "type": "action" + }, + "errors": {}, + "id": "14", + "type": "action", + "warnings": {}, + "x": 1.4210854715202004e-14, + "y": 1384 + }, + "15": { + "data": { + "action": "delete session", + "actionType": "generic", + "advanced": { + "customName": "delete session", + "customNameId": 0, + "description": "Closes a Real Time Response (RTR) session with the endpoint", + "join": [], + "notRequiredJoins": [], + "note": "Closes a Real Time Response (RTR) session with the endpoint" + }, + "connector": "CrowdStrike OAuth API", + "connectorConfigs": [ + "crowdstrike_oauth_api" + ], + "connectorId": "ae971ba5-3117-444a-8ac5-6ce779f3a232", + "connectorVersion": "v1", + "functionId": 1, + "functionName": "delete_session", + "id": "15", + "loop": { + "enabled": false, + "exitAfterUnit": "m", + "exitAfterValue": 10, + "exitConditionEnabled": false, + "exitLoopAfter": 2, + "pauseUnit": "m", + "pauseValue": 2 + }, + "parameters": { + "session_id": "create_session:action_result.summary.session_id" + }, + "requiredParameters": [ + { + "data_type": "string", + "field": "session_id" + } + ], + "tab": "byAction", + "type": "action" + }, + "errors": {}, + "id": "15", + "type": "action", + "warnings": {}, + "x": 1.4210854715202004e-14, + "y": 1912 + }, + "18": { + "data": { + "advanced": { + "customName": "create file artifact", + "customNameId": 0, + "description": "Create the JSON structure from the collected file metadata to create a SOAR artifact.", + "join": [], + "note": "Create the JSON structure from the collected file metadata to create a SOAR artifact." + }, + "functionId": 2, + "functionName": "create_file_artifact", + "id": "18", + "inputParameters": [ + "query_device:action_result.data.*.device_id", + "query_device:action_result.data.*.hostname", + "list_session_files:action_result.data.*.resources.*.name", + "list_session_files:action_result.data.*.resources.*.sha256", + "get_session_file:action_result.data.*.vault_id", + "run_admin_command_get:action_result.parameter.data" + ], + "outputVariables": [ + "data" + ], + "type": "code" + }, + "errors": {}, + "id": "18", + "type": "code", + "userCode": " import os\n \n create_file_artifact__data = []\n for device_id, hostname, origFilepath, filehash, vault_id, fullpath in zip(query_device_result_item_0, query_device_result_item_1, list_session_files_result_item_0, list_session_files_result_item_1, get_session_file_result_item_0, run_admin_command_get_parameter_data):\n filename = os.path.basename(fullpath.replace('\\\\','/'))\n filepath = os.path.dirname(fullpath.replace('\\\\','/'))\n create_file_artifact__data = {\n \"cef\": {\n \"deviceExternalId\": device_id,\n \"hostname\": hostname,\n \"origFilePath\": origFilepath,\n \"fileName\": filename,\n \"filePath\": os.path.join(filepath.replace('/','\\\\'), ''),\n \"fileHashSha256\": filehash,\n \"vaultId\": vault_id\n }\n }\n\n #phantom.debug(f\"File artifact data: {create_file_artifact__data}\")\n \n", + "warnings": {}, + "x": 1.4210854715202004e-14, + "y": 1560 + }, + "19": { + "data": { + "advanced": { + "customName": "artifact create", + "customNameId": 0, + "description": "Creates a new artifact to store information about the file collected from Crowdstrike.", + "join": [], + "note": "Creates a new artifact to store information about the file collected from Crowdstrike." + }, + "customFunction": { + "draftMode": false, + "name": "artifact_create", + "repoName": "community" + }, + "functionId": 2, + "functionName": "artifact_create", + "id": "19", + "loop": { + "enabled": false, + "exitAfterUnit": "m", + "exitAfterValue": 10, + "exitConditionEnabled": false, + "exitLoopAfter": 2, + "pauseUnit": "m", + "pauseValue": 2 + }, + "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": null, + "cef_value": null, + "container": "container:id", + "input_json": "create_file_artifact:custom_function:data", + "label": "collected_file", + "name": "Collected File Artifact", + "run_automation": null, + "severity": null, + "tags": null + } + } + }, + "errors": {}, + "id": "19", + "type": "utility", + "warnings": {}, + "x": 1.4210854715202004e-14, + "y": 1736 + }, + "2": { + "data": { + "advanced": { + "customName": "input filter", + "customNameId": 0, + "description": "Determines if the provided inputs are present in the dataset.", + "join": [], + "note": "Determines if the provided inputs are present in the dataset." + }, + "conditions": [ + { + "comparisons": [ + { + "conditionIndex": 0, + "op": "!=", + "param": "playbook_input:device", + "value": "" + }, + { + "conditionIndex": 0, + "op": "!=", + "param": "playbook_input:path", + "value": "" + } + ], + "conditionIndex": 0, + "customName": "input device and path present", + "logic": "and" + } + ], + "functionId": 1, + "functionName": "input_filter", + "id": "2", + "type": "filter" + }, + "errors": {}, + "id": "2", + "type": "filter", + "warnings": {}, + "x": 60, + "y": 148 + }, + "21": { + "data": { + "advanced": { + "customName": "determine get status", + "customNameId": 0, + "description": "Check the results of the RTR get command to ensure it was indeed successful. RTR commands are notably unreliable in how they report status, instead proving stdout and stderr information.", + "join": [], + "note": "Check the results of the RTR get command to ensure it was indeed successful. RTR commands are notably unreliable in how they report status, instead proving stdout and stderr information." + }, + "functionId": 3, + "functionName": "determine_get_status", + "id": "21", + "inputParameters": [ + "run_admin_command_get:action_result.status", + "run_admin_command_get:action_result.data.*.resources.*.stdout", + "run_admin_command_get:action_result.data.*.resources.*.stderr", + "run_admin_command_get:action_result.parameter.data", + "run_admin_command_get:action_result.message" + ], + "outputVariables": [ + "file_name", + "status", + "status_detail" + ], + "type": "code" + }, + "errors": {}, + "id": "21", + "type": "code", + "userCode": " \n determine_get_status__file_name = []\n determine_get_status__status = []\n determine_get_status__status_detail = []\n \n for file_name, status, stdout, stderr, message in zip(run_admin_command_get_parameter_data, run_admin_command_get_result_item_0, run_admin_command_get_result_item_1, run_admin_command_get_result_item_2,run_admin_command_get_result_message):\n #phantom.debug(f\"file_name: {file_name}, status: {status}, stdout: {stdout}, stderr: {stderr}\")\n determine_get_status__file_name.append(file_name)\n determine_get_status__status.append(status)\n if status == \"success\" and stdout.strip() and not stderr.strip():\n determine_get_status__status_detail.append(f\"File collected: {stdout.strip()}\")\n else:\n determine_get_status__status_detail.append(stderr.strip() if stderr else message)\n\n", + "warnings": {}, + "x": 1.4210854715202004e-14, + "y": 1032 + }, + "5": { + "customCode": null, + "data": { + "advanced": { + "customName": "host observables", + "customNameId": 0, + "description": "Format a normalized output for each host", + "join": [], + "note": "Format a normalized output for each host." + }, + "functionId": 1, + "functionName": "host_observables", + "id": "5", + "inputParameters": [ + "create_session:action_result.parameter.device_id", + "query_device:action_result.data.*.hostname", + "determine_get_status:custom_function:file_name", + "list_session_files:action_result.data.*.resources.*.sha256", + "list_session_files:action_result.data.*.resources.*.size", + "get_session_file:action_result.data.*.vault_document", + "get_session_file:action_result.summary.vault_id", + "determine_get_status:custom_function:status", + "determine_get_status:custom_function:status_detail" + ], + "outputVariables": [ + "observable_array" + ], + "type": "code" + }, + "errors": {}, + "id": "5", + "type": "code", + "userCode": " \n host_observables__observable_array = []\n \n for device_id, hostname, file_hash, file_size, vault_document, vault_id in zip(create_session_parameter_device_id, query_device_result_item_0, list_session_files_result_item_0, list_session_files_result_item_1, get_session_file_result_item_0, get_session_file_summary_vault_id):\n file_artifacts = []\n for file_name, status, status_message in zip(determine_get_status__file_name, determine_get_status__status, determine_get_status__status_detail):\n # Initialize the observable dictionary \n if status == \"success\":\n file_artifacts.append({\n \"name\": file_name,\n \"status\": status,\n \"status_detail\": status_message,\n \"hashes\": [\n {\n \"value\": file_hash,\n \"algorithm\": \"SHA-256\",\n \"algorithm_id\": 3\n }\n ],\n \"size\": file_size,\n \"vault_document\": vault_document,\n \"vault_id\": vault_id\n })\n else:\n file_artifacts.append({\n \"name\": file_name,\n \"status\": status,\n \"status_detail\": status_message\n })\n observable = {\n \"source\": \"Crowdstrike OAuth API\", \n \"type\": \"Endpoint\", \n \"activity_name\": \"File Collection\",\n \"uid\": device_id,\n \"hostname\": hostname,\n \"file_artifacts\": file_artifacts\n }\n \n # Add the observable to the array\n host_observables__observable_array.append(observable)\n \n # Debug output for verification\n #phantom.debug(host_observables__observable_array)\n \n", + "warnings": {}, + "x": 1.4210854715202004e-14, + "y": 2280 + }, + "7": { + "data": { + "advanced": { + "customName": "format fql", + "customNameId": 0, + "description": "Format the FQL query to get the input device information using its ID or hostname.", + "join": [], + "note": "Format the FQL query to get the input device information using its ID or hostname." + }, + "functionId": 2, + "functionName": "format_fql", + "id": "7", + "parameters": [ + "playbook_input:device" + ], + "template": "%%\nhostname:['{0}'],device_id:['{0}']\n%%", + "type": "format" + }, + "errors": {}, + "id": "7", + "type": "format", + "warnings": {}, + "x": 1.4210854715202004e-14, + "y": 328 + }, + "8": { + "data": { + "action": "query device", + "actionType": "investigate", + "advanced": { + "customName": "query device", + "customNameId": 0, + "description": "Get information about the device using its hostname or device id.", + "join": [], + "note": "Get information about the device using its hostname or device id." + }, + "connector": "CrowdStrike OAuth API", + "connectorConfigs": [ + "crowdstrike_oauth_api" + ], + "connectorId": "ae971ba5-3117-444a-8ac5-6ce779f3a232", + "connectorVersion": "v1", + "functionId": 1, + "functionName": "query_device", + "id": "8", + "loop": { + "enabled": false, + "exitAfterUnit": "m", + "exitAfterValue": 10, + "exitConditionEnabled": false, + "exitLoopAfter": 2, + "pauseUnit": "m", + "pauseValue": 2 + }, + "parameters": { + "filter": "format_fql:formatted_data.*", + "limit": 50 + }, + "requiredParameters": [ + { + "data_type": "numeric", + "default": 50, + "field": "limit" + } + ], + "type": "action" + }, + "errors": {}, + "id": "8", + "type": "action", + "warnings": {}, + "x": 1.4210854715202004e-14, + "y": 500 + }, + "9": { + "data": { + "advanced": { + "customName": "format file collection report", + "customNameId": 0, + "description": "Format a summary table with the information gathered from the playbook.", + "join": [], + "note": "Format a summary table with the information gathered from the playbook." + }, + "functionId": 3, + "functionName": "format_file_collection_report", + "id": "9", + "parameters": [ + "create_session:action_result.parameter.device_id", + "query_device:action_result.data.*.hostname", + "list_session_files:action_result.data.*.resources.*.name", + "list_session_files:action_result.data.*.resources.*.sha256", + "get_session_file:action_result.summary.vault_id", + "artifact_create:custom_function_result.data.artifact_id", + "determine_get_status:custom_function:status", + "determine_get_status:custom_function:status_detail" + ], + "template": "Endpoint Files were collected by Splunk SOAR and an artifact was created. The table below summarizes the information gathered.\n\n| Device ID | Hostname | File Path | File Hash | Vault ID | Artifact ID | Status | Message |\n| --- | --- | --- | --- | --- | --- | --- | --- |\n%%\n| {0} | {1} | `{2}` | {3} | {4} | {5} | {6} | `{7}` |\n%%", + "type": "format" + }, + "errors": {}, + "id": "9", + "type": "format", + "warnings": {}, + "x": 1.4210854715202004e-14, + "y": 2088 + } + }, + "notes": "Inputs: \ndevice (CrowdStrike Device ID or Hostname)\npath (File path)\nInteractions: CrowdStrike OAuth API\nActions: create session, run admin command, list session file, get session file\nOutputs: observables, markdown report" + }, + "input_spec": [ + { + "contains": [ + "host name" + ], + "description": "Device ID or hostname of the host from which to collect files", + "name": "device" + }, + { + "contains": [ + "file path" + ], + "description": "Path of the file on the endpoint to collect to a new artifact", + "name": "path" + } + ], + "output_spec": [ + { + "contains": [], + "datapaths": [ + "host_observables:custom_function:observable_array" + ], + "deduplicate": false, + "description": "An array of observable dictionaries", + "metadata": {}, + "name": "observable" + }, + { + "contains": [], + "datapaths": [ + "format_file_collection_report:formatted_data" + ], + "deduplicate": false, + "description": "A report of the devices that were isolated via Splunk SOAR.", + "metadata": {}, + "name": "markdown_report" + } + ], + "playbook_trigger": "artifact_created", + "playbook_type": "data", + "python_version": "3", + "schema": "5.0.15", + "version": "6.3.1.178" + }, + "create_time": "2025-04-30T17:42:24.612005+00:00", + "draft_mode": false, + "labels": [ + "*" + ], + "tags": [ + "CrowdStrike_OAuth_API", + "host name", + "enrichment", + "D3-FA" + ] +} \ No newline at end of file diff --git a/playbooks/CrowdStrike_OAuth_API_File_Collection.png b/playbooks/CrowdStrike_OAuth_API_File_Collection.png new file mode 100644 index 0000000000000000000000000000000000000000..19831ceff7e392e5dac481a449641e177c90f1a6 GIT binary patch literal 44773 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%0Ak$56 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 A{{R30 literal 0 HcmV?d00001 diff --git a/playbooks/CrowdStrike_OAuth_API_File_Collection.py b/playbooks/CrowdStrike_OAuth_API_File_Collection.py new file mode 100644 index 0000000000..996f27f0d1 --- /dev/null +++ b/playbooks/CrowdStrike_OAuth_API_File_Collection.py @@ -0,0 +1,606 @@ +""" +Accepts a hostname or device id as well as a file path as input and collects the file to the event File Vault from a device in Crowdstrike. An artifact is created from the collected file. We then generate an observable report as well as a Markdown formatted report. Both reports can be customized based on user preference. +""" + + +import phantom.rules as phantom +import json +from datetime import datetime, timedelta + + +@phantom.playbook_block() +def on_start(container): + phantom.debug('on_start() called') + + # call 'input_filter' block + input_filter(container=container) + + return + +@phantom.playbook_block() +def input_filter(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, loop_state_json=None, **kwargs): + phantom.debug("input_filter() called") + + ################################################################################ + # Determines if the provided inputs are present in the dataset. + ################################################################################ + + # collect filtered artifact ids and results for 'if' condition 1 + matched_artifacts_1, matched_results_1 = phantom.condition( + container=container, + logical_operator="and", + conditions=[ + ["playbook_input:device", "!=", ""], + ["playbook_input:path", "!=", ""] + ], + name="input_filter:condition_1", + delimiter=None) + + # call connected blocks if filtered artifacts or results + if matched_artifacts_1 or matched_results_1: + format_fql(action=action, success=success, container=container, results=results, handle=handle, filtered_artifacts=matched_artifacts_1, filtered_results=matched_results_1) + + return + + +@phantom.playbook_block() +def host_observables(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("host_observables() called") + + ################################################################################ + # Format a normalized output for each host + ################################################################################ + + create_session_result_data = phantom.collect2(container=container, datapath=["create_session:action_result.parameter.device_id"], action_results=results) + query_device_result_data = phantom.collect2(container=container, datapath=["query_device:action_result.data.*.hostname"], action_results=results) + list_session_files_result_data = phantom.collect2(container=container, datapath=["list_session_files:action_result.data.*.resources.*.sha256","list_session_files:action_result.data.*.resources.*.size"], action_results=results) + get_session_file_result_data = phantom.collect2(container=container, datapath=["get_session_file:action_result.data.*.vault_document","get_session_file:action_result.summary.vault_id"], action_results=results) + determine_get_status__file_name = json.loads(_ if (_ := phantom.get_run_data(key="determine_get_status:file_name")) != "" else "null") # pylint: disable=used-before-assignment + determine_get_status__status = json.loads(_ if (_ := phantom.get_run_data(key="determine_get_status:status")) != "" else "null") # pylint: disable=used-before-assignment + determine_get_status__status_detail = json.loads(_ if (_ := phantom.get_run_data(key="determine_get_status:status_detail")) != "" else "null") # pylint: disable=used-before-assignment + + create_session_parameter_device_id = [item[0] for item in create_session_result_data] + query_device_result_item_0 = [item[0] for item in query_device_result_data] + list_session_files_result_item_0 = [item[0] for item in list_session_files_result_data] + list_session_files_result_item_1 = [item[1] for item in list_session_files_result_data] + get_session_file_result_item_0 = [item[0] for item in get_session_file_result_data] + get_session_file_summary_vault_id = [item[1] for item in get_session_file_result_data] + + host_observables__observable_array = None + + ################################################################################ + ## Custom Code Start + ################################################################################ + + host_observables__observable_array = [] + + for device_id, hostname, file_hash, file_size, vault_document, vault_id in zip(create_session_parameter_device_id, query_device_result_item_0, list_session_files_result_item_0, list_session_files_result_item_1, get_session_file_result_item_0, get_session_file_summary_vault_id): + file_artifacts = [] + for file_name, status, status_message in zip(determine_get_status__file_name, determine_get_status__status, determine_get_status__status_detail): + # Initialize the observable dictionary + if status == "success": + file_artifacts.append({ + "name": file_name, + "status": status, + "status_detail": status_message, + "hashes": [ + { + "value": file_hash, + "algorithm": "SHA-256", + "algorithm_id": 3 + } + ], + "size": file_size, + "vault_document": vault_document, + "vault_id": vault_id + }) + else: + file_artifacts.append({ + "name": file_name, + "status": status, + "status_detail": status_message + }) + observable = { + "source": "Crowdstrike OAuth API", + "type": "Endpoint", + "activity_name": "File Collection", + "uid": device_id, + "hostname": hostname, + "file_artifacts": file_artifacts + } + + # Add the observable to the array + host_observables__observable_array.append(observable) + + # Debug output for verification + #phantom.debug(host_observables__observable_array) + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.save_run_data(key="host_observables:observable_array", value=json.dumps(host_observables__observable_array)) + + return + + +@phantom.playbook_block() +def format_fql(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("format_fql() called") + + ################################################################################ + # Format the FQL query to get the input device information using its ID or hostname. + ################################################################################ + + template = """%%\nhostname:['{0}'],device_id:['{0}']\n%%""" + + # parameter list for template variable replacement + parameters = [ + "playbook_input:device" + ] + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.format(container=container, template=template, parameters=parameters, name="format_fql") + + query_device(container=container) + + return + + +@phantom.playbook_block() +def query_device(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("query_device() called") + + # phantom.debug('Action: {0} {1}'.format(action['name'], ('SUCCEEDED' if success else 'FAILED'))) + + ################################################################################ + # Get information about the device using its hostname or device id. + ################################################################################ + + format_fql__as_list = phantom.get_format_data(name="format_fql__as_list") + + parameters = [] + + # build parameters list for 'query_device' call + for format_fql__item in format_fql__as_list: + parameters.append({ + "limit": 50, + "filter": format_fql__item, + }) + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.act("query device", parameters=parameters, name="query_device", assets=["crowdstrike_oauth_api"], callback=create_session) + + return + + +@phantom.playbook_block() +def format_file_collection_report(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("format_file_collection_report() called") + + ################################################################################ + # Format a summary table with the information gathered from the playbook. + ################################################################################ + + template = """Endpoint Files were collected by Splunk SOAR and an artifact was created. The table below summarizes the information gathered.\n\n| Device ID | Hostname | File Path | File Hash | Vault ID | Artifact ID | Status | Message |\n| --- | --- | --- | --- | --- | --- | --- | --- |\n%%\n| {0} | {1} | `{2}` | {3} | {4} | {5} | {6} | `{7}` |\n%%""" + + # parameter list for template variable replacement + parameters = [ + "create_session:action_result.parameter.device_id", + "query_device:action_result.data.*.hostname", + "list_session_files:action_result.data.*.resources.*.name", + "list_session_files:action_result.data.*.resources.*.sha256", + "get_session_file:action_result.summary.vault_id", + "artifact_create:custom_function_result.data.artifact_id", + "determine_get_status:custom_function:status", + "determine_get_status:custom_function:status_detail" + ] + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.format(container=container, template=template, parameters=parameters, name="format_file_collection_report") + + host_observables(container=container) + + return + + +@phantom.playbook_block() +def create_session(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_session() called") + + # phantom.debug('Action: {0} {1}'.format(action['name'], ('SUCCEEDED' if success else 'FAILED'))) + + ################################################################################ + # Creates a Real Time Response (RTR) session to interact with the endpoint + ################################################################################ + + query_device_result_data = phantom.collect2(container=container, datapath=["query_device:action_result.data.*.device_id","query_device:action_result.parameter.context.artifact_id"], action_results=results) + + parameters = [] + + # build parameters list for 'create_session' call + for query_device_result_item in query_device_result_data: + if query_device_result_item[0] is not None: + parameters.append({ + "device_id": query_device_result_item[0], + "context": {'artifact_id': query_device_result_item[1]}, + }) + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.act("create session", parameters=parameters, name="create_session", assets=["crowdstrike_oauth_api"], callback=run_admin_command_get) + + return + + +@phantom.playbook_block() +def run_admin_command_get(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("run_admin_command_get() called") + + # phantom.debug('Action: {0} {1}'.format(action['name'], ('SUCCEEDED' if success else 'FAILED'))) + + ################################################################################ + # Gets the specified file based on the playbook input path + ################################################################################ + + playbook_input_path = phantom.collect2(container=container, datapath=["playbook_input:path"]) + query_device_result_data = phantom.collect2(container=container, datapath=["query_device:action_result.data.*.device_id","query_device:action_result.parameter.context.artifact_id"], action_results=results) + create_session_result_data = phantom.collect2(container=container, datapath=["create_session:action_result.summary.session_id","create_session:action_result.parameter.context.artifact_id"], action_results=results) + + parameters = [] + + # build parameters list for 'run_admin_command_get' call + for playbook_input_path_item in playbook_input_path: + for query_device_result_item in query_device_result_data: + for create_session_result_item in create_session_result_data: + if query_device_result_item[0] is not None and create_session_result_item[0] is not None: + parameters.append({ + "data": playbook_input_path_item[0], + "command": "get", + "device_id": query_device_result_item[0], + "session_id": create_session_result_item[0], + "context": {'artifact_id': create_session_result_item[1]}, + }) + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.act("run admin command", parameters=parameters, name="run_admin_command_get", assets=["crowdstrike_oauth_api"], callback=determine_get_status) + + return + + +@phantom.playbook_block() +def list_session_files(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_session_files() called") + + # phantom.debug('Action: {0} {1}'.format(action['name'], ('SUCCEEDED' if success else 'FAILED'))) + + ################################################################################ + # Lists the previous file we ran a get command on, which returns its SHA256 hash + ################################################################################ + + create_session_result_data = phantom.collect2(container=container, datapath=["create_session:action_result.summary.session_id","create_session:action_result.parameter.context.artifact_id"], action_results=results) + + parameters = [] + + # build parameters list for 'list_session_files' call + for create_session_result_item in create_session_result_data: + if create_session_result_item[0] is not None: + parameters.append({ + "session_id": create_session_result_item[0], + "context": {'artifact_id': create_session_result_item[1]}, + }) + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.act("list session files", parameters=parameters, name="list_session_files", assets=["crowdstrike_oauth_api"], callback=get_session_file) + + return + + +@phantom.playbook_block() +def get_session_file(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("get_session_file() called") + + # phantom.debug('Action: {0} {1}'.format(action['name'], ('SUCCEEDED' if success else 'FAILED'))) + + ################################################################################ + # Downloads the RTR session file from the endpoint to the SOAR File Vault + ################################################################################ + + list_session_files_result_data = phantom.collect2(container=container, datapath=["list_session_files:action_result.data.*.resources.*.sha256","list_session_files:action_result.parameter.context.artifact_id"], action_results=results) + playbook_input_path = phantom.collect2(container=container, datapath=["playbook_input:path"]) + create_session_result_data = phantom.collect2(container=container, datapath=["create_session:action_result.summary.session_id","create_session:action_result.parameter.context.artifact_id"], action_results=results) + + parameters = [] + + # build parameters list for 'get_session_file' call + for list_session_files_result_item in list_session_files_result_data: + for playbook_input_path_item in playbook_input_path: + for create_session_result_item in create_session_result_data: + if list_session_files_result_item[0] is not None and create_session_result_item[0] is not None: + parameters.append({ + "file_hash": list_session_files_result_item[0], + "file_name": playbook_input_path_item[0], + "session_id": create_session_result_item[0], + "context": {'artifact_id': create_session_result_item[1]}, + }) + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.act("get session file", parameters=parameters, name="get_session_file", assets=["crowdstrike_oauth_api"], callback=create_file_artifact) + + return + + +@phantom.playbook_block() +def delete_session(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("delete_session() called") + + # phantom.debug('Action: {0} {1}'.format(action['name'], ('SUCCEEDED' if success else 'FAILED'))) + + ################################################################################ + # Closes a Real Time Response (RTR) session with the endpoint + ################################################################################ + + create_session_result_data = phantom.collect2(container=container, datapath=["create_session:action_result.summary.session_id","create_session:action_result.parameter.context.artifact_id"], action_results=results) + + parameters = [] + + # build parameters list for 'delete_session' call + for create_session_result_item in create_session_result_data: + if create_session_result_item[0] is not None: + parameters.append({ + "session_id": create_session_result_item[0], + "context": {'artifact_id': create_session_result_item[1]}, + }) + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.act("delete session", parameters=parameters, name="delete_session", assets=["crowdstrike_oauth_api"], callback=format_file_collection_report) + + return + + +@phantom.playbook_block() +def create_file_artifact(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_file_artifact() called") + + ################################################################################ + # Create the JSON structure from the collected file metadata to create a SOAR + # artifact. + ################################################################################ + + query_device_result_data = phantom.collect2(container=container, datapath=["query_device:action_result.data.*.device_id","query_device:action_result.data.*.hostname"], action_results=results) + list_session_files_result_data = phantom.collect2(container=container, datapath=["list_session_files:action_result.data.*.resources.*.name","list_session_files:action_result.data.*.resources.*.sha256"], action_results=results) + get_session_file_result_data = phantom.collect2(container=container, datapath=["get_session_file:action_result.data.*.vault_id"], action_results=results) + run_admin_command_get_result_data = phantom.collect2(container=container, datapath=["run_admin_command_get:action_result.parameter.data"], action_results=results) + + query_device_result_item_0 = [item[0] for item in query_device_result_data] + query_device_result_item_1 = [item[1] for item in query_device_result_data] + list_session_files_result_item_0 = [item[0] for item in list_session_files_result_data] + list_session_files_result_item_1 = [item[1] for item in list_session_files_result_data] + get_session_file_result_item_0 = [item[0] for item in get_session_file_result_data] + run_admin_command_get_parameter_data = [item[0] for item in run_admin_command_get_result_data] + + create_file_artifact__data = None + + ################################################################################ + ## Custom Code Start + ################################################################################ + import os + + create_file_artifact__data = [] + for device_id, hostname, origFilepath, filehash, vault_id, fullpath in zip(query_device_result_item_0, query_device_result_item_1, list_session_files_result_item_0, list_session_files_result_item_1, get_session_file_result_item_0, run_admin_command_get_parameter_data): + filename = os.path.basename(fullpath.replace('\\','/')) + filepath = os.path.dirname(fullpath.replace('\\','/')) + create_file_artifact__data = { + "cef": { + "deviceExternalId": device_id, + "hostname": hostname, + "origFilePath": origFilepath, + "fileName": filename, + "filePath": os.path.join(filepath.replace('/','\\'), ''), + "fileHashSha256": filehash, + "vaultId": vault_id + } + } + + #phantom.debug(f"File artifact data: {create_file_artifact__data}") + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.save_run_data(key="create_file_artifact:data", value=json.dumps(create_file_artifact__data)) + + artifact_create(container=container) + + return + + +@phantom.playbook_block() +def artifact_create(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("artifact_create() called") + + ################################################################################ + # Creates a new artifact to store information about the file collected from Crowdstrike. + ################################################################################ + + id_value = container.get("id", None) + create_file_artifact__data = json.loads(_ if (_ := phantom.get_run_data(key="create_file_artifact:data")) != "" else "null") # pylint: disable=used-before-assignment + + parameters = [] + + parameters.append({ + "name": "Collected File Artifact", + "tags": None, + "label": "collected_file", + "severity": None, + "cef_field": None, + "cef_value": None, + "container": id_value, + "input_json": create_file_artifact__data, + "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="artifact_create", callback=delete_session) + + return + + +@phantom.playbook_block() +def determine_get_status(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("determine_get_status() called") + + ################################################################################ + # Check the results of the RTR get command to ensure it was indeed successful. + # RTR commands are notably unreliable in how they report status, instead proving + # stdout and stderr information. + ################################################################################ + + run_admin_command_get_result_data = phantom.collect2(container=container, datapath=["run_admin_command_get:action_result.status","run_admin_command_get:action_result.data.*.resources.*.stdout","run_admin_command_get:action_result.data.*.resources.*.stderr","run_admin_command_get:action_result.parameter.data","run_admin_command_get:action_result.message"], action_results=results) + + run_admin_command_get_result_item_0 = [item[0] for item in run_admin_command_get_result_data] + run_admin_command_get_result_item_1 = [item[1] for item in run_admin_command_get_result_data] + run_admin_command_get_result_item_2 = [item[2] for item in run_admin_command_get_result_data] + run_admin_command_get_parameter_data = [item[3] for item in run_admin_command_get_result_data] + run_admin_command_get_result_message = [item[4] for item in run_admin_command_get_result_data] + + determine_get_status__file_name = None + determine_get_status__status = None + determine_get_status__status_detail = None + + ################################################################################ + ## Custom Code Start + ################################################################################ + + determine_get_status__file_name = [] + determine_get_status__status = [] + determine_get_status__status_detail = [] + + for file_name, status, stdout, stderr, message in zip(run_admin_command_get_parameter_data, run_admin_command_get_result_item_0, run_admin_command_get_result_item_1, run_admin_command_get_result_item_2,run_admin_command_get_result_message): + #phantom.debug(f"file_name: {file_name}, status: {status}, stdout: {stdout}, stderr: {stderr}") + determine_get_status__file_name.append(file_name) + determine_get_status__status.append(status) + if status == "success" and stdout.strip() and not stderr.strip(): + determine_get_status__status_detail.append(f"File collected: {stdout.strip()}") + else: + determine_get_status__status_detail.append(stderr.strip() if stderr else message) + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.save_run_data(key="determine_get_status:file_name", value=json.dumps(determine_get_status__file_name)) + phantom.save_run_data(key="determine_get_status:status", value=json.dumps(determine_get_status__status)) + phantom.save_run_data(key="determine_get_status:status_detail", value=json.dumps(determine_get_status__status_detail)) + + list_session_files(container=container) + + return + + +@phantom.playbook_block() +def on_finish(container, summary): + phantom.debug("on_finish() called") + + format_file_collection_report = phantom.get_format_data(name="format_file_collection_report") + host_observables__observable_array = json.loads(_ if (_ := phantom.get_run_data(key="host_observables:observable_array")) != "" else "null") # pylint: disable=used-before-assignment + + output = { + "observable": host_observables__observable_array, + "markdown_report": format_file_collection_report, + } + + ################################################################################ + ## 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/CrowdStrike_OAuth_API_File_Collection.yml b/playbooks/CrowdStrike_OAuth_API_File_Collection.yml new file mode 100644 index 0000000000..0652a64eb1 --- /dev/null +++ b/playbooks/CrowdStrike_OAuth_API_File_Collection.yml @@ -0,0 +1,31 @@ +name: CrowdStrike OAuth API File Collection +id: +version: 1 +date: '2025-06-09' +author: Christian Cloutier, Splunk +type: Investigation +description: "Accepts a hostname or device id as well as a file path as input and collects the file to the event File Vault from a device in Crowdstrike. An artifact is created from the collected file. We then generate an observable report as well as a Markdown formatted report. Both reports can be customized based on user preference." +playbook: CrowdStrike_OAuth_API_File_Collection +how_to_implement: This input playbook requires the CrowdStrike OAuth API connector to be configured. It is designed to work with an endpoint hostname or agent id and collect a specific file from the endpoint (using an absolute path) for forensics or later use in automation playbooks. +references: [] +app_list: + - CrowdStrike OAuth API +tags: + platform_tags: + - "host name" + - "device id" + - "path" + - "File Collection" + - "D3-FA" + - "CrowdStrike_OAuth_API" + playbook_type: Input + vpe_type: Modern + playbook_fields: [device,path] + product: + - Splunk SOAR + use_cases: + - Collection + - Malware + - Endpoint + defend_technique_id: + - D3-FA diff --git a/playbooks/CrowdStrike_OAuth_API_File_Eviction.json b/playbooks/CrowdStrike_OAuth_API_File_Eviction.json new file mode 100644 index 0000000000..e30f1efa49 --- /dev/null +++ b/playbooks/CrowdStrike_OAuth_API_File_Eviction.json @@ -0,0 +1,499 @@ +{ + "blockly": false, + "blockly_xml": "", + "category": "File Eviction", + "coa": { + "data": { + "description": "Accepts a hostname or device id as well as a file path as input and deletes the file from a device in Crowdstrike. We then generate an observable report as well as a Markdown formatted report. Both reports can be customized based on user preference.", + "edges": [ + { + "id": "port_0_to_port_2", + "sourceNode": "0", + "sourcePort": "0_out", + "targetNode": "2", + "targetPort": "2_in" + }, + { + "id": "port_5_to_port_1", + "sourceNode": "5", + "sourcePort": "5_out", + "targetNode": "1", + "targetPort": "1_in" + }, + { + "conditions": [ + { + "index": 0 + } + ], + "id": "port_2_to_port_7", + "sourceNode": "2", + "sourcePort": "2_out", + "targetNode": "7", + "targetPort": "7_in" + }, + { + "id": "port_7_to_port_8", + "sourceNode": "7", + "sourcePort": "7_out", + "targetNode": "8", + "targetPort": "8_in" + }, + { + "id": "port_9_to_port_5", + "sourceNode": "9", + "sourcePort": "9_out", + "targetNode": "5", + "targetPort": "5_in" + }, + { + "id": "port_8_to_port_11", + "sourceNode": "8", + "sourcePort": "8_out", + "targetNode": "11", + "targetPort": "11_in" + }, + { + "id": "port_15_to_port_9", + "sourceNode": "15", + "sourcePort": "15_out", + "targetNode": "9", + "targetPort": "9_in" + }, + { + "id": "port_11_to_port_16", + "sourceNode": "11", + "sourcePort": "11_out", + "targetNode": "16", + "targetPort": "16_in" + }, + { + "id": "port_16_to_port_15", + "sourceNode": "16", + "sourcePort": "16_out", + "targetNode": "15", + "targetPort": "15_in" + } + ], + "hash": "5a2079268f4ee2268aa332c6f8af2f2b76274fba", + "nodes": { + "0": { + "data": { + "advanced": { + "join": [] + }, + "functionName": "on_start", + "id": "0", + "type": "start" + }, + "errors": {}, + "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": 1576 + }, + "11": { + "data": { + "action": "create session", + "actionType": "generic", + "advanced": { + "customName": "create session", + "customNameId": 0, + "description": "Creates a Real Time Response (RTR) session to interact with the endpoint", + "join": [], + "note": "Creates a Real Time Response (RTR) session to interact with the endpoint" + }, + "connector": "CrowdStrike OAuth API", + "connectorConfigs": [ + "crowdstrike_oauth_api" + ], + "connectorId": "ae971ba5-3117-444a-8ac5-6ce779f3a232", + "connectorVersion": "v1", + "functionId": 1, + "functionName": "create_session", + "id": "11", + "loop": { + "enabled": false, + "exitAfterUnit": "m", + "exitAfterValue": 10, + "exitConditionEnabled": false, + "exitLoopAfter": 2, + "pauseUnit": "m", + "pauseValue": 2 + }, + "parameters": { + "device_id": "query_device:action_result.data.*.device_id" + }, + "requiredParameters": [ + { + "data_type": "string", + "field": "device_id" + } + ], + "tab": "byAction", + "type": "action" + }, + "errors": {}, + "id": "11", + "type": "action", + "warnings": {}, + "x": 1.4210854715202004e-14, + "y": 680 + }, + "15": { + "data": { + "action": "delete session", + "actionType": "generic", + "advanced": { + "customName": "delete session", + "customNameId": 0, + "description": "Closes a Real Time Response (RTR) session to interact with the endpoint", + "join": [], + "note": "Closes a Real Time Response (RTR) session to interact with the endpoint" + }, + "connector": "CrowdStrike OAuth API", + "connectorConfigs": [ + "crowdstrike_oauth_api" + ], + "connectorId": "ae971ba5-3117-444a-8ac5-6ce779f3a232", + "connectorVersion": "v1", + "functionId": 1, + "functionName": "delete_session", + "id": "15", + "loop": { + "enabled": false, + "exitAfterUnit": "m", + "exitAfterValue": 10, + "exitConditionEnabled": false, + "exitLoopAfter": 2, + "pauseUnit": "m", + "pauseValue": 2 + }, + "parameters": { + "session_id": "create_session:action_result.summary.session_id" + }, + "requiredParameters": [ + { + "data_type": "string", + "field": "session_id" + } + ], + "tab": "byAction", + "type": "action" + }, + "errors": {}, + "id": "15", + "type": "action", + "warnings": {}, + "x": 1.4210854715202004e-14, + "y": 1032 + }, + "16": { + "data": { + "action": "run admin command", + "actionType": "generic", + "advanced": { + "customName": "run admin command rm", + "customNameId": 0, + "description": "Run the rm admin command via Real Time Response (RTR)", + "join": [], + "note": "Run the rm admin command via Real Time Response (RTR)" + }, + "connector": "CrowdStrike OAuth API", + "connectorConfigs": [ + "crowdstrike_oauth_api" + ], + "connectorId": "ae971ba5-3117-444a-8ac5-6ce779f3a232", + "connectorVersion": "v1", + "functionId": 1, + "functionName": "run_admin_command_rm", + "id": "16", + "loop": { + "enabled": false, + "exitAfterUnit": "m", + "exitAfterValue": 10, + "exitConditionEnabled": false, + "exitLoopAfter": 2, + "pauseUnit": "m", + "pauseValue": 2 + }, + "parameters": { + "command": "rm", + "data": "playbook_input:path", + "device_id": "query_device:action_result.data.*.device_id", + "session_id": "create_session:action_result.summary.session_id" + }, + "requiredParameters": [ + { + "data_type": "string", + "field": "command" + }, + { + "data_type": "string", + "field": "device_id" + }, + { + "data_type": "string", + "field": "session_id" + } + ], + "type": "action" + }, + "errors": {}, + "id": "16", + "type": "action", + "warnings": {}, + "x": 1.4210854715202004e-14, + "y": 856 + }, + "2": { + "data": { + "advanced": { + "customName": "input filter", + "customNameId": 0, + "description": "Determines if the provided inputs are present in the dataset.", + "join": [], + "note": "Determines if the provided inputs are present in the dataset." + }, + "conditions": [ + { + "comparisons": [ + { + "conditionIndex": 0, + "op": "!=", + "param": "playbook_input:device", + "value": "" + }, + { + "conditionIndex": 0, + "op": "!=", + "param": "playbook_input:path", + "value": "" + } + ], + "conditionIndex": 0, + "customName": "input device and path present", + "logic": "and" + } + ], + "functionId": 1, + "functionName": "input_filter", + "id": "2", + "type": "filter" + }, + "errors": {}, + "id": "2", + "type": "filter", + "warnings": {}, + "x": 60, + "y": 148 + }, + "5": { + "customCode": null, + "data": { + "advanced": { + "customName": "host observables", + "customNameId": 0, + "description": "Format a normalized output for each host", + "join": [], + "note": "Format a normalized output for each host." + }, + "functionId": 1, + "functionName": "host_observables", + "id": "5", + "inputParameters": [ + "create_session:action_result.parameter.device_id", + "query_device:action_result.data.*.hostname", + "run_admin_command_rm:action_result.parameter.data", + "run_admin_command_rm:action_result.data" + ], + "outputVariables": [ + "observable_array" + ], + "type": "code" + }, + "errors": {}, + "id": "5", + "type": "code", + "userCode": " \n host_observables__observable_array = []\n i = 0\n \n for system in run_admin_command_rm_result_item_1:\n # Handle case where multiple files are listed on the same host.\n device_id = create_session_parameter_device_id[i] if i < len(create_session_parameter_device_id) else create_session_parameter_device_id[0]\n hostname = query_device_result_item_0[i] if i < len(query_device_result_item_0) else query_device_result_item_0[0]\n \n # Initialize the observable dictionary\n observable = {\n \"source\": \"Crowdstrike OAuth API\",\n \"type\": \"Endpoint\",\n \"activity_name\": \"File Eviction\",\n \"uid\": device_id,\n \"hostname\": hostname,\n \"file_artifacts\": [],\n \"d3fend\": {\n \"d3f_tactic\": \"Evict\",\n \"d3f_technique\": \"D3-FEV\",\n \"version\": \"1.0.0\"\n }\n } \n \n for file in system[0][\"resources\"]:\n file_name = run_admin_command_rm_parameter_data[i]\n status = \"success\" if file[\"stdout\"] and not file[\"stderr\"] else \"failed\"\n status_message = file[\"stderr\"] if file[\"stderr\"] else file[\"stdout\"]\n observable[\"file_artifacts\"].append(\n {\n \"name\": file_name,\n \"status\": status,\n \"status_detail\": status_message\n }\n )\n \n # Add the observable to the array\n host_observables__observable_array.append(observable)\n i+=1\n \n # Debug output for verification\n phantom.debug(host_observables__observable_array)\n \n", + "warnings": {}, + "x": 1.4210854715202004e-14, + "y": 1400 + }, + "7": { + "data": { + "advanced": { + "customName": "format fql", + "customNameId": 0, + "description": "Format the FQL query to get the input device information using its ID or hostname.", + "join": [], + "note": "Format the FQL query to get the input device information using its ID or hostname." + }, + "functionId": 2, + "functionName": "format_fql", + "id": "7", + "parameters": [ + "playbook_input:device" + ], + "template": "%%\nhostname:['{0}'],device_id:['{0}']\n%%", + "type": "format" + }, + "errors": {}, + "id": "7", + "type": "format", + "warnings": {}, + "x": 1.4210854715202004e-14, + "y": 328 + }, + "8": { + "data": { + "action": "query device", + "actionType": "investigate", + "advanced": { + "customName": "query device", + "customNameId": 0, + "description": "Get information about the device using its hostname or device id.", + "join": [], + "note": "Get information about the device using its hostname or device id." + }, + "connector": "CrowdStrike OAuth API", + "connectorConfigs": [ + "crowdstrike_oauth_api" + ], + "connectorId": "ae971ba5-3117-444a-8ac5-6ce779f3a232", + "connectorVersion": "v1", + "functionId": 1, + "functionName": "query_device", + "id": "8", + "loop": { + "enabled": false, + "exitAfterUnit": "m", + "exitAfterValue": 10, + "exitConditionEnabled": false, + "exitLoopAfter": 2, + "pauseUnit": "m", + "pauseValue": 2 + }, + "parameters": { + "filter": "format_fql:formatted_data.*", + "limit": 50 + }, + "requiredParameters": [ + { + "data_type": "numeric", + "default": 50, + "field": "limit" + } + ], + "type": "action" + }, + "errors": {}, + "id": "8", + "type": "action", + "warnings": {}, + "x": 1.4210854715202004e-14, + "y": 504 + }, + "9": { + "data": { + "advanced": { + "customName": "format file eviction report", + "customNameId": 0, + "description": "Format a summary table with the information gathered from the playbook.", + "join": [], + "note": "Format a summary table with the information gathered from the playbook." + }, + "functionId": 3, + "functionName": "format_file_eviction_report", + "id": "9", + "parameters": [ + "query_device:action_result.data.*.device_id", + "run_admin_command_rm:action_result.parameter.data", + "run_admin_command_rm:action_result.data.*.resources.*.stdout", + "run_admin_command_rm:action_result.data.*.resources.*.stderr" + ], + "template": "Endpoint Files were removed by Splunk SOAR. The table below summarizes the information gathered.\n\n| Device ID | File Path | Eviction Status |\n| --- | --- | --- |\n%%\n| {0} | {1} | {2}{3} |\n%%", + "type": "format" + }, + "errors": {}, + "id": "9", + "type": "format", + "warnings": {}, + "x": 1.4210854715202004e-14, + "y": 1208 + } + }, + "notes": "Inputs: \ndevice (CrowdStrike Device ID or Hostname)\npath (File path)\nInteractions: CrowdStrike OAuth API\nActions: create session, run admin command, delete session\nOutputs: observables, markdown report" + }, + "input_spec": [ + { + "contains": [ + "host name" + ], + "description": "Device ID or hostname of the host to delete a file from", + "name": "device" + }, + { + "contains": [ + "file path" + ], + "description": "Path of the file on the endpoint to remove", + "name": "path" + } + ], + "output_spec": [ + { + "contains": [], + "datapaths": [ + "host_observables:custom_function:observable_array" + ], + "deduplicate": false, + "description": "An array of observable dictionaries", + "metadata": {}, + "name": "observable" + }, + { + "contains": [], + "datapaths": [ + "format_file_eviction_report:formatted_data" + ], + "deduplicate": false, + "description": "A report of the devices that were isolated via Splunk SOAR.", + "metadata": {}, + "name": "markdown_report" + } + ], + "playbook_trigger": "artifact_created", + "playbook_type": "data", + "python_version": "3", + "schema": "5.0.15", + "version": "6.3.1.178" + }, + "create_time": "2025-04-09T16:26:45.445744+00:00", + "draft_mode": false, + "labels": [ + "*" + ], + "tags": [ + "CrowdStrike_OAuth_API", + "host name", + "D3-FEV" + ] +} \ No newline at end of file diff --git a/playbooks/CrowdStrike_OAuth_API_File_Eviction.png b/playbooks/CrowdStrike_OAuth_API_File_Eviction.png new file mode 100644 index 0000000000000000000000000000000000000000..e46ebdc072e052d97bbf4770dd5835864aaceb9f GIT binary patch literal 42811 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ŬRhzGZcQgHd4JfY9{Tv!_$zf&gF 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 literal 0 HcmV?d00001 diff --git a/playbooks/CrowdStrike_OAuth_API_File_Eviction.py b/playbooks/CrowdStrike_OAuth_API_File_Eviction.py new file mode 100644 index 0000000000..ee89b9207a --- /dev/null +++ b/playbooks/CrowdStrike_OAuth_API_File_Eviction.py @@ -0,0 +1,365 @@ +""" +Accepts a hostname or device id as well as a file path as input and deletes the file from a device in Crowdstrike. We then generate an observable report as well as a Markdown formatted report. Both reports can be customized based on user preference. +""" + + +import phantom.rules as phantom +import json +from datetime import datetime, timedelta + + +@phantom.playbook_block() +def on_start(container): + phantom.debug('on_start() called') + + # call 'input_filter' block + input_filter(container=container) + + return + +@phantom.playbook_block() +def input_filter(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, loop_state_json=None, **kwargs): + phantom.debug("input_filter() called") + + ################################################################################ + # Determines if the provided inputs are present in the dataset. + ################################################################################ + + # collect filtered artifact ids and results for 'if' condition 1 + matched_artifacts_1, matched_results_1 = phantom.condition( + container=container, + logical_operator="and", + conditions=[ + ["playbook_input:device", "!=", ""], + ["playbook_input:path", "!=", ""] + ], + name="input_filter:condition_1", + delimiter=None) + + # call connected blocks if filtered artifacts or results + if matched_artifacts_1 or matched_results_1: + format_fql(action=action, success=success, container=container, results=results, handle=handle, filtered_artifacts=matched_artifacts_1, filtered_results=matched_results_1) + + return + + +@phantom.playbook_block() +def host_observables(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("host_observables() called") + + ################################################################################ + # Format a normalized output for each host + ################################################################################ + + create_session_result_data = phantom.collect2(container=container, datapath=["create_session:action_result.parameter.device_id"], action_results=results) + query_device_result_data = phantom.collect2(container=container, datapath=["query_device:action_result.data.*.hostname"], action_results=results) + run_admin_command_rm_result_data = phantom.collect2(container=container, datapath=["run_admin_command_rm:action_result.parameter.data","run_admin_command_rm:action_result.data"], action_results=results) + + create_session_parameter_device_id = [item[0] for item in create_session_result_data] + query_device_result_item_0 = [item[0] for item in query_device_result_data] + run_admin_command_rm_parameter_data = [item[0] for item in run_admin_command_rm_result_data] + run_admin_command_rm_result_item_1 = [item[1] for item in run_admin_command_rm_result_data] + + host_observables__observable_array = None + + ################################################################################ + ## Custom Code Start + ################################################################################ + + host_observables__observable_array = [] + i = 0 + + for system in run_admin_command_rm_result_item_1: + # Handle case where multiple files are listed on the same host. + device_id = create_session_parameter_device_id[i] if i < len(create_session_parameter_device_id) else create_session_parameter_device_id[0] + hostname = query_device_result_item_0[i] if i < len(query_device_result_item_0) else query_device_result_item_0[0] + + # Initialize the observable dictionary + observable = { + "source": "Crowdstrike OAuth API", + "type": "Endpoint", + "activity_name": "File Eviction", + "uid": device_id, + "hostname": hostname, + "file_artifacts": [], + "d3fend": { + "d3f_tactic": "Evict", + "d3f_technique": "D3-FEV", + "version": "1.0.0" + } + } + + for file in system[0]["resources"]: + file_name = run_admin_command_rm_parameter_data[i] + status = "success" if file["stdout"] and not file["stderr"] else "failed" + status_message = file["stderr"] if file["stderr"] else file["stdout"] + observable["file_artifacts"].append( + { + "name": file_name, + "status": status, + "status_detail": status_message + } + ) + + # Add the observable to the array + host_observables__observable_array.append(observable) + i+=1 + + # Debug output for verification + phantom.debug(host_observables__observable_array) + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.save_run_data(key="host_observables:observable_array", value=json.dumps(host_observables__observable_array)) + + return + + +@phantom.playbook_block() +def format_fql(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("format_fql() called") + + ################################################################################ + # Format the FQL query to get the input device information using its ID or hostname. + ################################################################################ + + template = """%%\nhostname:['{0}'],device_id:['{0}']\n%%""" + + # parameter list for template variable replacement + parameters = [ + "playbook_input:device" + ] + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.format(container=container, template=template, parameters=parameters, name="format_fql") + + query_device(container=container) + + return + + +@phantom.playbook_block() +def query_device(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("query_device() called") + + # phantom.debug('Action: {0} {1}'.format(action['name'], ('SUCCEEDED' if success else 'FAILED'))) + + ################################################################################ + # Get information about the device using its hostname or device id. + ################################################################################ + + format_fql__as_list = phantom.get_format_data(name="format_fql__as_list") + + parameters = [] + + # build parameters list for 'query_device' call + for format_fql__item in format_fql__as_list: + parameters.append({ + "limit": 50, + "filter": format_fql__item, + }) + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.act("query device", parameters=parameters, name="query_device", assets=["crowdstrike_oauth_api"], callback=create_session) + + return + + +@phantom.playbook_block() +def format_file_eviction_report(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("format_file_eviction_report() called") + + ################################################################################ + # Format a summary table with the information gathered from the playbook. + ################################################################################ + + template = """Endpoint Files were removed by Splunk SOAR. The table below summarizes the information gathered.\n\n| Device ID | File Path | Eviction Status |\n| --- | --- | --- |\n%%\n| {0} | {1} | {2}{3} |\n%%""" + + # parameter list for template variable replacement + parameters = [ + "query_device:action_result.data.*.device_id", + "run_admin_command_rm:action_result.parameter.data", + "run_admin_command_rm:action_result.data.*.resources.*.stdout", + "run_admin_command_rm:action_result.data.*.resources.*.stderr" + ] + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.format(container=container, template=template, parameters=parameters, name="format_file_eviction_report") + + host_observables(container=container) + + return + + +@phantom.playbook_block() +def create_session(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_session() called") + + # phantom.debug('Action: {0} {1}'.format(action['name'], ('SUCCEEDED' if success else 'FAILED'))) + + ################################################################################ + # Creates a Real Time Response (RTR) session to interact with the endpoint + ################################################################################ + + query_device_result_data = phantom.collect2(container=container, datapath=["query_device:action_result.data.*.device_id","query_device:action_result.parameter.context.artifact_id"], action_results=results) + + parameters = [] + + # build parameters list for 'create_session' call + for query_device_result_item in query_device_result_data: + if query_device_result_item[0] is not None: + parameters.append({ + "device_id": query_device_result_item[0], + "context": {'artifact_id': query_device_result_item[1]}, + }) + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.act("create session", parameters=parameters, name="create_session", assets=["crowdstrike_oauth_api"], callback=run_admin_command_rm) + + return + + +@phantom.playbook_block() +def delete_session(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("delete_session() called") + + # phantom.debug('Action: {0} {1}'.format(action['name'], ('SUCCEEDED' if success else 'FAILED'))) + + ################################################################################ + # Closes a Real Time Response (RTR) session to interact with the endpoint + ################################################################################ + + create_session_result_data = phantom.collect2(container=container, datapath=["create_session:action_result.summary.session_id","create_session:action_result.parameter.context.artifact_id"], action_results=results) + + parameters = [] + + # build parameters list for 'delete_session' call + for create_session_result_item in create_session_result_data: + if create_session_result_item[0] is not None: + parameters.append({ + "session_id": create_session_result_item[0], + "context": {'artifact_id': create_session_result_item[1]}, + }) + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.act("delete session", parameters=parameters, name="delete_session", assets=["crowdstrike_oauth_api"], callback=format_file_eviction_report) + + return + + +@phantom.playbook_block() +def run_admin_command_rm(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("run_admin_command_rm() called") + + # phantom.debug('Action: {0} {1}'.format(action['name'], ('SUCCEEDED' if success else 'FAILED'))) + + ################################################################################ + # Run the rm admin command via Real Time Response (RTR) + ################################################################################ + + playbook_input_path = phantom.collect2(container=container, datapath=["playbook_input:path"]) + query_device_result_data = phantom.collect2(container=container, datapath=["query_device:action_result.data.*.device_id","query_device:action_result.parameter.context.artifact_id"], action_results=results) + create_session_result_data = phantom.collect2(container=container, datapath=["create_session:action_result.summary.session_id","create_session:action_result.parameter.context.artifact_id"], action_results=results) + + parameters = [] + + # build parameters list for 'run_admin_command_rm' call + for playbook_input_path_item in playbook_input_path: + for query_device_result_item in query_device_result_data: + for create_session_result_item in create_session_result_data: + if query_device_result_item[0] is not None and create_session_result_item[0] is not None: + parameters.append({ + "data": playbook_input_path_item[0], + "command": "rm", + "device_id": query_device_result_item[0], + "session_id": create_session_result_item[0], + "context": {'artifact_id': create_session_result_item[1]}, + }) + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.act("run admin command", parameters=parameters, name="run_admin_command_rm", assets=["crowdstrike_oauth_api"], callback=delete_session) + + return + + +@phantom.playbook_block() +def on_finish(container, summary): + phantom.debug("on_finish() called") + + format_file_eviction_report = phantom.get_format_data(name="format_file_eviction_report") + host_observables__observable_array = json.loads(_ if (_ := phantom.get_run_data(key="host_observables:observable_array")) != "" else "null") # pylint: disable=used-before-assignment + + output = { + "observable": host_observables__observable_array, + "markdown_report": format_file_eviction_report, + } + + ################################################################################ + ## 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/CrowdStrike_OAuth_API_File_Eviction.yml b/playbooks/CrowdStrike_OAuth_API_File_Eviction.yml new file mode 100644 index 0000000000..dad80502f8 --- /dev/null +++ b/playbooks/CrowdStrike_OAuth_API_File_Eviction.yml @@ -0,0 +1,31 @@ +name: CrowdStrike OAuth API File Eviction +id: +version: 1 +date: '2025-06-09' +author: Christian Cloutier, Splunk +type: Response +description: "Accepts a hostname or device id as well as a file path as input and deletes the file from a device in Crowdstrike. We then generate an observable report as well as a Markdown formatted report. Both reports can be customized based on user preference." +playbook: CrowdStrike_OAuth_API_File_Eviction +how_to_implement: This input playbook requires the CrowdStrike OAuth API connector to be configured. It is designed to work with an endpoint hostname or device id and delete a specific file from the endpoint (using an absolute path) for use in automation playbooks. +references: [] +app_list: + - CrowdStrike OAuth API +tags: + platform_tags: + - "host name" + - "device id" + - "path" + - "File Eviction" + - "D3-FEV" + - "CrowdStrike_OAuth_API" + playbook_type: Input + vpe_type: Modern + playbook_fields: [device,path] + product: + - Splunk SOAR + use_cases: + - Response + - Malware + - Endpoint + defend_technique_id: + - D3-FEV diff --git a/playbooks/CrowdStrike_OAuth_API_File_Restore.json b/playbooks/CrowdStrike_OAuth_API_File_Restore.json new file mode 100644 index 0000000000..92f2b9543a --- /dev/null +++ b/playbooks/CrowdStrike_OAuth_API_File_Restore.json @@ -0,0 +1,697 @@ +{ + "blockly": false, + "blockly_xml": "", + "category": "File Restore", + "coa": { + "data": { + "description": "Accepts a hostname or device id as well as a file path as input and restores the file from the File Vault to a device in Crowdstrike. We then generate an observable report as well as a Markdown formatted report. Both reports can be customized based on user preference.", + "edges": [ + { + "id": "port_0_to_port_2", + "sourceNode": "0", + "sourcePort": "0_out", + "targetNode": "2", + "targetPort": "2_in" + }, + { + "id": "port_5_to_port_1", + "sourceNode": "5", + "sourcePort": "5_out", + "targetNode": "1", + "targetPort": "1_in" + }, + { + "conditions": [ + { + "index": 0 + } + ], + "id": "port_2_to_port_7", + "sourceNode": "2", + "sourcePort": "2_out", + "targetNode": "7", + "targetPort": "7_in" + }, + { + "id": "port_7_to_port_8", + "sourceNode": "7", + "sourcePort": "7_out", + "targetNode": "8", + "targetPort": "8_in" + }, + { + "id": "port_9_to_port_5", + "sourceNode": "9", + "sourcePort": "9_out", + "targetNode": "5", + "targetPort": "5_in" + }, + { + "id": "port_15_to_port_9", + "sourceNode": "15", + "sourcePort": "15_out", + "targetNode": "9", + "targetPort": "9_in" + }, + { + "id": "port_16_to_port_15", + "sourceNode": "16", + "sourcePort": "16_out", + "targetNode": "15", + "targetPort": "15_in" + }, + { + "id": "port_19_to_port_11", + "sourceNode": "19", + "sourcePort": "19_out", + "targetNode": "11", + "targetPort": "11_in" + }, + { + "conditions": [ + { + "index": 0 + } + ], + "id": "port_18_to_port_19", + "sourceNode": "18", + "sourcePort": "18_out", + "targetNode": "19", + "targetPort": "19_in" + }, + { + "id": "port_8_to_port_18", + "sourceNode": "8", + "sourcePort": "8_out", + "targetNode": "18", + "targetPort": "18_in" + }, + { + "id": "port_11_to_port_29", + "sourceNode": "11", + "sourcePort": "11_out", + "targetNode": "29", + "targetPort": "29_in" + }, + { + "id": "port_29_to_port_16", + "sourceNode": "29", + "sourcePort": "29_out", + "targetNode": "16", + "targetPort": "16_in" + } + ], + "hash": "3339fe23d90ca191d7151a1b509f9bc5879ea2da", + "nodes": { + "0": { + "data": { + "advanced": { + "join": [] + }, + "functionName": "on_start", + "id": "0", + "type": "start" + }, + "errors": {}, + "id": "0", + "type": "start", + "warnings": {}, + "x": 19.999999999999986, + "y": -6.394884621840902e-14 + }, + "1": { + "data": { + "advanced": { + "join": [] + }, + "functionName": "on_finish", + "id": "1", + "type": "end" + }, + "errors": {}, + "id": "1", + "type": "end", + "warnings": {}, + "x": 19.999999999999986, + "y": 2080 + }, + "11": { + "data": { + "action": "create session", + "actionType": "generic", + "advanced": { + "customName": "create session", + "customNameId": 0, + "description": "Creates a Real Time Response (RTR) session to interact with the endpoint", + "join": [], + "note": "Creates a Real Time Response (RTR) session to interact with the endpoint" + }, + "connector": "CrowdStrike OAuth API", + "connectorConfigs": [ + "crowdstrike_oauth_api" + ], + "connectorId": "ae971ba5-3117-444a-8ac5-6ce779f3a232", + "connectorVersion": "v1", + "functionId": 1, + "functionName": "create_session", + "id": "11", + "loop": { + "enabled": false, + "exitAfterUnit": "m", + "exitAfterValue": 10, + "exitConditionEnabled": false, + "exitLoopAfter": 2, + "pauseUnit": "m", + "pauseValue": 2 + }, + "parameters": { + "device_id": "query_device:action_result.data.*.device_id" + }, + "requiredParameters": [ + { + "data_type": "string", + "field": "device_id" + } + ], + "tab": "byAction", + "type": "action" + }, + "errors": {}, + "id": "11", + "type": "action", + "warnings": {}, + "x": 1.4210854715202004e-14, + "y": 1036 + }, + "15": { + "data": { + "action": "delete session", + "actionType": "generic", + "advanced": { + "customName": "delete session", + "customNameId": 0, + "description": "Closes a Real Time Response (RTR) session to interact with the endpoint", + "join": [], + "note": "Closes a Real Time Response (RTR) session to interact with the endpoint" + }, + "connector": "CrowdStrike OAuth API", + "connectorConfigs": [ + "crowdstrike_oauth_api" + ], + "connectorId": "ae971ba5-3117-444a-8ac5-6ce779f3a232", + "connectorVersion": "v1", + "functionId": 1, + "functionName": "delete_session", + "id": "15", + "loop": { + "enabled": false, + "exitAfterUnit": "m", + "exitAfterValue": 10, + "exitConditionEnabled": false, + "exitLoopAfter": 2, + "pauseUnit": "m", + "pauseValue": 2 + }, + "parameters": { + "session_id": "create_session:action_result.summary.session_id" + }, + "requiredParameters": [ + { + "data_type": "string", + "field": "session_id" + } + ], + "tab": "byAction", + "type": "action" + }, + "errors": {}, + "id": "15", + "type": "action", + "warnings": {}, + "x": 1.4210854715202004e-14, + "y": 1536 + }, + "16": { + "data": { + "action": "run admin command", + "actionType": "generic", + "advanced": { + "customName": "run admin command put", + "customNameId": 0, + "description": "Run the put admin command via Real Time Response (RTR)", + "join": [], + "note": "Run the put admin command via Real Time Response (RTR)" + }, + "connector": "CrowdStrike OAuth API", + "connectorConfigs": [ + "crowdstrike_oauth_api" + ], + "connectorId": "ae971ba5-3117-444a-8ac5-6ce779f3a232", + "connectorVersion": "v1", + "functionId": 1, + "functionName": "run_admin_command_put", + "id": "16", + "loop": { + "enabled": false, + "exitAfterUnit": "m", + "exitAfterValue": 10, + "exitConditionEnabled": false, + "exitLoopAfter": 2, + "pauseUnit": "m", + "pauseValue": 2 + }, + "parameters": { + "command": "put", + "data": "artifact:*.cef.fileName", + "device_id": "query_device:action_result.data.*.device_id", + "session_id": "create_session:action_result.summary.session_id" + }, + "requiredParameters": [ + { + "data_type": "string", + "field": "command" + }, + { + "data_type": "string", + "field": "device_id" + }, + { + "data_type": "string", + "field": "session_id" + } + ], + "type": "action" + }, + "errors": {}, + "id": "16", + "type": "action", + "warnings": {}, + "x": 1.4210854715202004e-14, + "y": 1360 + }, + "18": { + "data": { + "advanced": { + "customName": "filter file artifacts", + "customNameId": 0, + "description": "Filter to ensure the requested file to restore was found in the vent artifacts. This playbook assumes the file to restore has been previously collected by the CrowdStrike_OAuth_API_File_Collection input playbook.\n", + "join": [], + "note": "Filter to ensure the requested file to restore was found in the vent artifacts. This playbook assumes the file to restore has been previously collected by the CrowdStrike_OAuth_API_File_Collection input playbook.\n" + }, + "conditions": [ + { + "comparisons": [ + { + "conditionIndex": 0, + "op": "==", + "param": "artifact:*.label", + "value": "collected_file" + }, + { + "conditionIndex": 0, + "op": "in", + "param": "playbook_input:file", + "value": "artifact:*.cef.fileName" + } + ], + "conditionIndex": 0, + "customName": "file artifact exists", + "logic": "and" + } + ], + "functionId": 2, + "functionName": "filter_file_artifacts_0", + "id": "18", + "type": "filter" + }, + "errors": {}, + "id": "18", + "type": "filter", + "warnings": {}, + "x": 60, + "y": 680 + }, + "19": { + "data": { + "action": "upload put file", + "actionType": "generic", + "advanced": { + "customName": "upload put file", + "customNameId": 0, + "description": "Upload the file to restore from the SOAR Vault to Crowdstrike.", + "join": [], + "note": "Upload the file to restore from the SOAR Vault to Crowdstrike." + }, + "connector": "CrowdStrike OAuth API", + "connectorConfigs": [ + "crowdstrike_oauth_api" + ], + "connectorId": "ae971ba5-3117-444a-8ac5-6ce779f3a232", + "connectorVersion": "v1", + "functionId": 1, + "functionName": "upload_put_file", + "id": "19", + "loop": { + "enabled": false, + "exitAfterUnit": "m", + "exitAfterValue": 10, + "exitConditionEnabled": false, + "exitLoopAfter": 2, + "pauseUnit": "m", + "pauseValue": 2 + }, + "parameters": { + "comment": "File restored from Splunk SOAR", + "description": "File restored from Splunk SOAR via the CrowdStrike_OAuth_API_File_Restore playbook.", + "file_name": "filtered-data:filter_file_artifacts_0:condition_1:artifact:*.cef.fileName", + "vault_id": "filtered-data:filter_file_artifacts_0:condition_1:artifact:*.cef.vaultId" + }, + "requiredParameters": [ + { + "data_type": "string", + "field": "vault_id" + }, + { + "data_type": "string", + "field": "description" + } + ], + "tab": "byAction", + "type": "action" + }, + "errors": {}, + "id": "19", + "type": "action", + "warnings": {}, + "x": 1.4210854715202004e-14, + "y": 860 + }, + "2": { + "data": { + "advanced": { + "customName": "input filter", + "customNameId": 0, + "description": "Determines if the provided inputs are present in the dataset.", + "join": [], + "note": "Determines if the provided inputs are present in the dataset." + }, + "conditions": [ + { + "comparisons": [ + { + "conditionIndex": 0, + "op": "!=", + "param": "playbook_input:device", + "value": "" + }, + { + "conditionIndex": 0, + "op": "!=", + "param": "playbook_input:file", + "value": "" + } + ], + "conditionIndex": 0, + "customName": "input device and file present", + "logic": "and" + } + ], + "functionId": 1, + "functionName": "input_filter", + "id": "2", + "type": "filter" + }, + "errors": {}, + "id": "2", + "type": "filter", + "warnings": {}, + "x": 60, + "y": 148 + }, + "29": { + "data": { + "action": "run admin command", + "actionType": "generic", + "advanced": { + "customName": "run admin command cd", + "customNameId": 0, + "description": "Run the cd admin command via Real Time Response (RTR) to set the working directory properly for the following put command.", + "join": [], + "note": "Run the cd admin command via Real Time Response (RTR) to set the working directory properly for the following put command." + }, + "connector": "CrowdStrike OAuth API", + "connectorConfigs": [ + "crowdstrike_oauth_api" + ], + "connectorId": "ae971ba5-3117-444a-8ac5-6ce779f3a232", + "connectorVersion": "v1", + "functionId": 2, + "functionName": "run_admin_command_cd", + "id": "29", + "loop": { + "enabled": false, + "exitAfterUnit": "m", + "exitAfterValue": 10, + "exitConditionEnabled": false, + "exitLoopAfter": 2, + "pauseUnit": "m", + "pauseValue": 2 + }, + "parameters": { + "command": "cd", + "data": "filtered-data:filter_file_artifacts_0:condition_1:artifact:*.cef.filePath", + "device_id": "query_device:action_result.data.*.device_id", + "session_id": "create_session:action_result.summary.session_id" + }, + "requiredParameters": [ + { + "data_type": "string", + "field": "command" + }, + { + "data_type": "string", + "field": "device_id" + }, + { + "data_type": "string", + "field": "session_id" + } + ], + "type": "action" + }, + "errors": {}, + "id": "29", + "type": "action", + "warnings": {}, + "x": 1.4210854715202004e-14, + "y": 1212 + }, + "5": { + "customCode": null, + "data": { + "advanced": { + "customName": "host observables", + "customNameId": 0, + "description": "Format a normalized output for each host", + "join": [], + "note": "Format a normalized output for each host." + }, + "functionId": 1, + "functionName": "host_observables", + "id": "5", + "inputParameters": [ + "create_session:action_result.parameter.device_id", + "query_device:action_result.data.*.hostname", + "run_admin_command_put:action_result.parameter.data", + "run_admin_command_cd:action_result.parameter.data", + "run_admin_command_put:action_result.status", + "run_admin_command_put:action_result.message" + ], + "outputVariables": [ + "observable_array" + ], + "type": "code" + }, + "errors": {}, + "id": "5", + "type": "code", + "userCode": " \n host_observables__observable_array = []\n \n for device_id, hostname, file_name, file_path, status, status_message in zip(create_session_parameter_device_id, query_device_result_item_0, run_admin_command_put_parameter_data, run_admin_command_cd_parameter_data, run_admin_command_put_result_item_1, run_admin_command_put_result_message):\n # Initialize the observable dictionary\n observable = {\n \"source\": \"Crowdstrike OAuth API\", \n \"type\": \"Endpoint\", \n \"activity_name\": \"File Restore\",\n \"uid\": device_id,\n \"hostname\": hostname,\n \"status\": status,\n \"status_detail\": status_message,\n \"file_artifacts\": {\n \"name\": file_name,\n \"path\": file_path\n },\n \"d3fend\": {\n \"d3f_tactic\": \"Restore\",\n \"d3f_technique\": \"D3-RF\",\n \"version\": \"1.0.0\"\n }\n }\n\n # Add the observable to the array\n host_observables__observable_array.append(observable)\n \n # Debug output for verification\n phantom.debug(host_observables__observable_array)\n \n", + "warnings": {}, + "x": 1.4210854715202004e-14, + "y": 1900 + }, + "7": { + "data": { + "advanced": { + "customName": "format fql", + "customNameId": 0, + "description": "Format the FQL query to get the input device information using its ID or hostname.", + "join": [], + "note": "Format the FQL query to get the input device information using its ID or hostname." + }, + "functionId": 2, + "functionName": "format_fql", + "id": "7", + "parameters": [ + "playbook_input:device" + ], + "template": "%%\nhostname:['{0}'],device_id:['{0}']\n%%", + "type": "format" + }, + "errors": {}, + "id": "7", + "type": "format", + "warnings": {}, + "x": 1.4210854715202004e-14, + "y": 328 + }, + "8": { + "data": { + "action": "query device", + "actionType": "investigate", + "advanced": { + "customName": "query device", + "customNameId": 0, + "description": "Get information about the device using its hostname or device id.", + "join": [], + "note": "Get information about the device using its hostname or device id." + }, + "connector": "CrowdStrike OAuth API", + "connectorConfigs": [ + "crowdstrike_oauth_api" + ], + "connectorId": "ae971ba5-3117-444a-8ac5-6ce779f3a232", + "connectorVersion": "v1", + "functionId": 1, + "functionName": "query_device", + "id": "8", + "loop": { + "conditions": [ + { + "comparisons": [ + { + "conditionIndex": 0, + "op": "==", + "param": "", + "value": "" + } + ], + "conditionIndex": 0, + "display": "If", + "logic": "and", + "type": "if" + } + ], + "enabled": false, + "exitAfterUnit": "m", + "exitAfterValue": 10, + "exitConditionEnabled": false, + "exitLoopAfter": 2, + "pauseUnit": "m", + "pauseValue": 2 + }, + "parameters": { + "filter": "format_fql:formatted_data.*", + "limit": 50 + }, + "requiredParameters": [ + { + "data_type": "numeric", + "default": 50, + "field": "limit" + } + ], + "type": "action" + }, + "errors": {}, + "id": "8", + "type": "action", + "warnings": {}, + "x": 1.4210854715202004e-14, + "y": 504 + }, + "9": { + "data": { + "advanced": { + "customName": "format file restore report", + "customNameId": 0, + "description": "Format a summary table with the information gathered from the playbook.", + "join": [], + "note": "Format a summary table with the information gathered from the playbook." + }, + "functionId": 3, + "functionName": "format_file_restore_report", + "id": "9", + "parameters": [ + "query_device:action_result.data.*.device_id", + "run_admin_command_put:action_result.parameter.data", + "run_admin_command_put:action_result.status" + ], + "template": "Endpoint Files were restored by Splunk SOAR. The table below summarizes the information gathered.\n\n| Device ID | File Path | Restore Status |\n| --- | --- | --- |\n%%\n| {0} | {1} | {2} |\n%%", + "type": "format" + }, + "errors": {}, + "id": "9", + "type": "format", + "warnings": {}, + "x": 1.4210854715202004e-14, + "y": 1712 + } + }, + "notes": "Inputs: \ndevice (CrowdStrike Device ID or Hostname)\npath (File path)\nInteractions: CrowdStrike OAuth API\nActions: create session, run admin command, delete session\nOutputs: observables, markdown report" + }, + "input_spec": [ + { + "contains": [ + "host name" + ], + "description": "Device ID or hostname of the host where the file should be restored.", + "name": "device" + }, + { + "contains": [ + "file name" + ], + "description": "Name of the file on the endpoint to restore. Assumes a file is present in the File Vault and a matching file_collected artifact exists with the same name from a previous File Collection run.", + "name": "file" + } + ], + "output_spec": [ + { + "contains": [], + "datapaths": [ + "host_observables:custom_function:observable_array" + ], + "deduplicate": false, + "description": "An array of observable dictionaries", + "metadata": {}, + "name": "observable" + }, + { + "contains": [], + "datapaths": [ + "format_file_restore_report:formatted_data" + ], + "deduplicate": false, + "description": "A report of the devices that were isolated via Splunk SOAR.", + "metadata": {}, + "name": "markdown_report" + } + ], + "playbook_trigger": "artifact_created", + "playbook_type": "data", + "python_version": "3", + "schema": "5.0.15", + "version": "6.3.1.178" + }, + "create_time": "2025-04-09T16:44:19.014187+00:00", + "draft_mode": false, + "labels": [ + "*" + ], + "tags": [ + "CrowdStrike_OAuth_API", + "host name", + "D3-RF" + ] +} \ No newline at end of file diff --git a/playbooks/CrowdStrike_OAuth_API_File_Restore.png b/playbooks/CrowdStrike_OAuth_API_File_Restore.png new file mode 100644 index 0000000000000000000000000000000000000000..c897194be8013fa4d926e31e633e342106d0f5a5 GIT binary patch literal 43677 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 CW7*{Z literal 0 HcmV?d00001 diff --git a/playbooks/CrowdStrike_OAuth_API_File_Restore.py b/playbooks/CrowdStrike_OAuth_API_File_Restore.py new file mode 100644 index 0000000000..ad9da8bffe --- /dev/null +++ b/playbooks/CrowdStrike_OAuth_API_File_Restore.py @@ -0,0 +1,468 @@ +""" +Accepts a hostname or device id as well as a file path as input and restores the file from the File Vault to a device in Crowdstrike. We then generate an observable report as well as a Markdown formatted report. Both reports can be customized based on user preference. +""" + + +import phantom.rules as phantom +import json +from datetime import datetime, timedelta + + +@phantom.playbook_block() +def on_start(container): + phantom.debug('on_start() called') + + # call 'input_filter' block + input_filter(container=container) + + return + +@phantom.playbook_block() +def input_filter(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, loop_state_json=None, **kwargs): + phantom.debug("input_filter() called") + + ################################################################################ + # Determines if the provided inputs are present in the dataset. + ################################################################################ + + # collect filtered artifact ids and results for 'if' condition 1 + matched_artifacts_1, matched_results_1 = phantom.condition( + container=container, + logical_operator="and", + conditions=[ + ["playbook_input:device", "!=", ""], + ["playbook_input:file", "!=", ""] + ], + name="input_filter:condition_1", + delimiter=None) + + # call connected blocks if filtered artifacts or results + if matched_artifacts_1 or matched_results_1: + format_fql(action=action, success=success, container=container, results=results, handle=handle, filtered_artifacts=matched_artifacts_1, filtered_results=matched_results_1) + + return + + +@phantom.playbook_block() +def host_observables(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("host_observables() called") + + ################################################################################ + # Format a normalized output for each host + ################################################################################ + + create_session_result_data = phantom.collect2(container=container, datapath=["create_session:action_result.parameter.device_id"], action_results=results) + query_device_result_data = phantom.collect2(container=container, datapath=["query_device:action_result.data.*.hostname"], action_results=results) + run_admin_command_put_result_data = phantom.collect2(container=container, datapath=["run_admin_command_put:action_result.parameter.data","run_admin_command_put:action_result.status","run_admin_command_put:action_result.message"], action_results=results) + run_admin_command_cd_result_data = phantom.collect2(container=container, datapath=["run_admin_command_cd:action_result.parameter.data"], action_results=results) + + create_session_parameter_device_id = [item[0] for item in create_session_result_data] + query_device_result_item_0 = [item[0] for item in query_device_result_data] + run_admin_command_put_parameter_data = [item[0] for item in run_admin_command_put_result_data] + run_admin_command_put_result_item_1 = [item[1] for item in run_admin_command_put_result_data] + run_admin_command_put_result_message = [item[2] for item in run_admin_command_put_result_data] + run_admin_command_cd_parameter_data = [item[0] for item in run_admin_command_cd_result_data] + + host_observables__observable_array = None + + ################################################################################ + ## Custom Code Start + ################################################################################ + + host_observables__observable_array = [] + + for device_id, hostname, file_name, file_path, status, status_message in zip(create_session_parameter_device_id, query_device_result_item_0, run_admin_command_put_parameter_data, run_admin_command_cd_parameter_data, run_admin_command_put_result_item_1, run_admin_command_put_result_message): + # Initialize the observable dictionary + observable = { + "source": "Crowdstrike OAuth API", + "type": "Endpoint", + "activity_name": "File Restore", + "uid": device_id, + "hostname": hostname, + "status": status, + "status_detail": status_message, + "file_artifacts": { + "name": file_name, + "path": file_path + }, + "d3fend": { + "d3f_tactic": "Restore", + "d3f_technique": "D3-RF", + "version": "1.0.0" + } + } + + # Add the observable to the array + host_observables__observable_array.append(observable) + + # Debug output for verification + phantom.debug(host_observables__observable_array) + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.save_run_data(key="host_observables:observable_array", value=json.dumps(host_observables__observable_array)) + + return + + +@phantom.playbook_block() +def format_fql(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("format_fql() called") + + ################################################################################ + # Format the FQL query to get the input device information using its ID or hostname. + ################################################################################ + + template = """%%\nhostname:['{0}'],device_id:['{0}']\n%%""" + + # parameter list for template variable replacement + parameters = [ + "playbook_input:device" + ] + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.format(container=container, template=template, parameters=parameters, name="format_fql") + + query_device(container=container) + + return + + +@phantom.playbook_block() +def query_device(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("query_device() called") + + # phantom.debug('Action: {0} {1}'.format(action['name'], ('SUCCEEDED' if success else 'FAILED'))) + + ################################################################################ + # Get information about the device using its hostname or device id. + ################################################################################ + + format_fql__as_list = phantom.get_format_data(name="format_fql__as_list") + + parameters = [] + + # build parameters list for 'query_device' call + for format_fql__item in format_fql__as_list: + parameters.append({ + "limit": 50, + "filter": format_fql__item, + }) + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.act("query device", parameters=parameters, name="query_device", assets=["crowdstrike_oauth_api"], callback=filter_file_artifacts_0) + + return + + +@phantom.playbook_block() +def format_file_restore_report(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("format_file_restore_report() called") + + ################################################################################ + # Format a summary table with the information gathered from the playbook. + ################################################################################ + + template = """Endpoint Files were restored by Splunk SOAR. The table below summarizes the information gathered.\n\n| Device ID | File Path | Restore Status |\n| --- | --- | --- |\n%%\n| {0} | {1} | {2} |\n%%""" + + # parameter list for template variable replacement + parameters = [ + "query_device:action_result.data.*.device_id", + "run_admin_command_put:action_result.parameter.data", + "run_admin_command_put:action_result.status" + ] + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.format(container=container, template=template, parameters=parameters, name="format_file_restore_report") + + host_observables(container=container) + + return + + +@phantom.playbook_block() +def create_session(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_session() called") + + # phantom.debug('Action: {0} {1}'.format(action['name'], ('SUCCEEDED' if success else 'FAILED'))) + + ################################################################################ + # Creates a Real Time Response (RTR) session to interact with the endpoint + ################################################################################ + + query_device_result_data = phantom.collect2(container=container, datapath=["query_device:action_result.data.*.device_id","query_device:action_result.parameter.context.artifact_id"], action_results=results) + + parameters = [] + + # build parameters list for 'create_session' call + for query_device_result_item in query_device_result_data: + if query_device_result_item[0] is not None: + parameters.append({ + "device_id": query_device_result_item[0], + "context": {'artifact_id': query_device_result_item[1]}, + }) + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.act("create session", parameters=parameters, name="create_session", assets=["crowdstrike_oauth_api"], callback=run_admin_command_cd) + + return + + +@phantom.playbook_block() +def delete_session(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("delete_session() called") + + # phantom.debug('Action: {0} {1}'.format(action['name'], ('SUCCEEDED' if success else 'FAILED'))) + + ################################################################################ + # Closes a Real Time Response (RTR) session to interact with the endpoint + ################################################################################ + + create_session_result_data = phantom.collect2(container=container, datapath=["create_session:action_result.summary.session_id","create_session:action_result.parameter.context.artifact_id"], action_results=results) + + parameters = [] + + # build parameters list for 'delete_session' call + for create_session_result_item in create_session_result_data: + if create_session_result_item[0] is not None: + parameters.append({ + "session_id": create_session_result_item[0], + "context": {'artifact_id': create_session_result_item[1]}, + }) + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.act("delete session", parameters=parameters, name="delete_session", assets=["crowdstrike_oauth_api"], callback=format_file_restore_report) + + return + + +@phantom.playbook_block() +def run_admin_command_put(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("run_admin_command_put() called") + + # phantom.debug('Action: {0} {1}'.format(action['name'], ('SUCCEEDED' if success else 'FAILED'))) + + ################################################################################ + # Run the put admin command via Real Time Response (RTR) + ################################################################################ + + container_artifact_data = phantom.collect2(container=container, datapath=["artifact:*.cef.fileName","artifact:*.id"]) + query_device_result_data = phantom.collect2(container=container, datapath=["query_device:action_result.data.*.device_id","query_device:action_result.parameter.context.artifact_id"], action_results=results) + create_session_result_data = phantom.collect2(container=container, datapath=["create_session:action_result.summary.session_id","create_session:action_result.parameter.context.artifact_id"], action_results=results) + + parameters = [] + + # build parameters list for 'run_admin_command_put' call + for container_artifact_item in container_artifact_data: + for query_device_result_item in query_device_result_data: + for create_session_result_item in create_session_result_data: + if query_device_result_item[0] is not None and create_session_result_item[0] is not None: + parameters.append({ + "data": container_artifact_item[0], + "command": "put", + "device_id": query_device_result_item[0], + "session_id": create_session_result_item[0], + "context": {'artifact_id': create_session_result_item[1]}, + }) + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.act("run admin command", parameters=parameters, name="run_admin_command_put", assets=["crowdstrike_oauth_api"], callback=delete_session) + + return + + +@phantom.playbook_block() +def filter_file_artifacts_0(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("filter_file_artifacts_0() called") + + ################################################################################ + # Filter to ensure the requested file to restore was found in the vent artifacts. + # This playbook assumes the file to restore has been previously collected by + # the CrowdStrike_OAuth_API_File_Collection input playbook. + # + ################################################################################ + + # collect filtered artifact ids and results for 'if' condition 1 + matched_artifacts_1, matched_results_1 = phantom.condition( + container=container, + logical_operator="and", + conditions=[ + ["artifact:*.label", "==", "collected_file"], + ["playbook_input:file", "in", "artifact:*.cef.fileName"] + ], + name="filter_file_artifacts_0:condition_1", + delimiter=None) + + # call connected blocks if filtered artifacts or results + if matched_artifacts_1 or matched_results_1: + upload_put_file(action=action, success=success, container=container, results=results, handle=handle, filtered_artifacts=matched_artifacts_1, filtered_results=matched_results_1) + + return + + +@phantom.playbook_block() +def upload_put_file(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("upload_put_file() called") + + # phantom.debug('Action: {0} {1}'.format(action['name'], ('SUCCEEDED' if success else 'FAILED'))) + + ################################################################################ + # Upload the file to restore from the SOAR Vault to Crowdstrike. + ################################################################################ + + filtered_artifact_0_data_filter_file_artifacts_0 = phantom.collect2(container=container, datapath=["filtered-data:filter_file_artifacts_0:condition_1:artifact:*.cef.vaultId","filtered-data:filter_file_artifacts_0:condition_1:artifact:*.cef.fileName","filtered-data:filter_file_artifacts_0:condition_1:artifact:*.id"]) + + parameters = [] + + # build parameters list for 'upload_put_file' call + for filtered_artifact_0_item_filter_file_artifacts_0 in filtered_artifact_0_data_filter_file_artifacts_0: + if filtered_artifact_0_item_filter_file_artifacts_0[0] is not None: + parameters.append({ + "comment": "File restored from Splunk SOAR", + "vault_id": filtered_artifact_0_item_filter_file_artifacts_0[0], + "file_name": filtered_artifact_0_item_filter_file_artifacts_0[1], + "description": "File restored from Splunk SOAR via the CrowdStrike_OAuth_API_File_Restore playbook.", + "context": {'artifact_id': filtered_artifact_0_item_filter_file_artifacts_0[2]}, + }) + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.act("upload put file", parameters=parameters, name="upload_put_file", assets=["crowdstrike_oauth_api"], callback=create_session) + + return + + +@phantom.playbook_block() +def run_admin_command_cd(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("run_admin_command_cd() called") + + # phantom.debug('Action: {0} {1}'.format(action['name'], ('SUCCEEDED' if success else 'FAILED'))) + + ################################################################################ + # Run the cd admin command via Real Time Response (RTR) to set the working directory + # properly for the following put command. + ################################################################################ + + filtered_artifact_0_data_filter_file_artifacts_0 = phantom.collect2(container=container, datapath=["filtered-data:filter_file_artifacts_0:condition_1:artifact:*.cef.filePath","filtered-data:filter_file_artifacts_0:condition_1:artifact:*.id"]) + query_device_result_data = phantom.collect2(container=container, datapath=["query_device:action_result.data.*.device_id","query_device:action_result.parameter.context.artifact_id"], action_results=results) + create_session_result_data = phantom.collect2(container=container, datapath=["create_session:action_result.summary.session_id","create_session:action_result.parameter.context.artifact_id"], action_results=results) + + parameters = [] + + # build parameters list for 'run_admin_command_cd' call + for filtered_artifact_0_item_filter_file_artifacts_0 in filtered_artifact_0_data_filter_file_artifacts_0: + for query_device_result_item in query_device_result_data: + for create_session_result_item in create_session_result_data: + if query_device_result_item[0] is not None and create_session_result_item[0] is not None: + parameters.append({ + "data": filtered_artifact_0_item_filter_file_artifacts_0[0], + "command": "cd", + "device_id": query_device_result_item[0], + "session_id": create_session_result_item[0], + "context": {'artifact_id': create_session_result_item[1]}, + }) + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.act("run admin command", parameters=parameters, name="run_admin_command_cd", assets=["crowdstrike_oauth_api"], callback=run_admin_command_put) + + return + + +@phantom.playbook_block() +def on_finish(container, summary): + phantom.debug("on_finish() called") + + format_file_restore_report = phantom.get_format_data(name="format_file_restore_report") + host_observables__observable_array = json.loads(_ if (_ := phantom.get_run_data(key="host_observables:observable_array")) != "" else "null") # pylint: disable=used-before-assignment + + output = { + "observable": host_observables__observable_array, + "markdown_report": format_file_restore_report, + } + + ################################################################################ + ## 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/CrowdStrike_OAuth_API_File_Restore.yml b/playbooks/CrowdStrike_OAuth_API_File_Restore.yml new file mode 100644 index 0000000000..90dd513591 --- /dev/null +++ b/playbooks/CrowdStrike_OAuth_API_File_Restore.yml @@ -0,0 +1,31 @@ +name: CrowdStrike OAuth API File Restore +id: +version: 1 +date: '2025-06-09' +author: Christian Cloutier, Splunk +type: Response +description: "Accepts a hostname or device id as well as a file path as input and restores the file from the File Vault to a device in Crowdstrike. We then generate an observable report as well as a Markdown formatted report. Both reports can be customized based on user preference." +playbook: CrowdStrike_OAuth_API_File_Restore +how_to_implement: This input playbook requires the CrowdStrike OAuth API connector to be configured. It is designed to work with an endpoint hostname or device id and restore a specific file to the endpoint (based on a previous run of the CrowdStrike_OAuth_API_File_Collection playbook) for use in automation playbooks. +references: [] +app_list: + - CrowdStrike OAuth API +tags: + platform_tags: + - "host name" + - "device id" + - "file name" + - "File Restore" + - "D3-RF" + - "CrowdStrike_OAuth_API" + playbook_type: Input + vpe_type: Modern + playbook_fields: [device,file] + product: + - Splunk SOAR + use_cases: + - Response + - Malware + - Endpoint + defend_technique_id: + - D3-RF diff --git a/playbooks/CrowdStrike_OAuth_API_Get_Device_Info.json b/playbooks/CrowdStrike_OAuth_API_Get_Device_Info.json new file mode 100644 index 0000000000..7671c0bd6f --- /dev/null +++ b/playbooks/CrowdStrike_OAuth_API_Get_Device_Info.json @@ -0,0 +1,300 @@ +{ + "blockly": false, + "blockly_xml": "", + "category": "Attribute Lookup", + "coa": { + "data": { + "description": "Given either a CrowdStrike device id (agentId) or a hostname, will query the device to get the other missing attribute. This enables finding the hostname from a device id or the device id from a hostname and can be used in front of other CrowdStrike custom playbooks for added flexibility.", + "edges": [ + { + "id": "port_0_to_port_3", + "sourceNode": "0", + "sourcePort": "0_out", + "targetNode": "3", + "targetPort": "3_in" + }, + { + "conditions": [ + { + "index": 0 + } + ], + "id": "port_3_to_port_2", + "sourceNode": "3", + "sourcePort": "3_out", + "targetNode": "2", + "targetPort": "2_in" + }, + { + "id": "port_2_to_port_4", + "sourceNode": "2", + "sourcePort": "2_out", + "targetNode": "4", + "targetPort": "4_in" + }, + { + "id": "port_4_to_port_6", + "sourceNode": "4", + "sourcePort": "4_out", + "targetNode": "6", + "targetPort": "6_in" + }, + { + "id": "port_6_to_port_1", + "sourceNode": "6", + "sourcePort": "6_out", + "targetNode": "1", + "targetPort": "1_in" + } + ], + "hash": "71712736c224e12b9baf5c81a571a973fa95b728", + "nodes": { + "0": { + "data": { + "advanced": { + "join": [] + }, + "functionName": "on_start", + "id": "0", + "type": "start" + }, + "errors": {}, + "id": "0", + "type": "start", + "warnings": {}, + "x": 19.999999999999986, + "y": -6.394884621840902e-14 + }, + "1": { + "data": { + "advanced": { + "join": [] + }, + "functionName": "on_finish", + "id": "1", + "type": "end" + }, + "errors": {}, + "id": "1", + "type": "end", + "warnings": {}, + "x": 20, + "y": 800 + }, + "2": { + "data": { + "advanced": { + "customName": "format fql", + "customNameId": 0, + "description": "Format the FQL query to get the input device information using its Device ID or hostname.", + "join": [], + "note": "Format the FQL query to get the input device information using its Device ID or hostname." + }, + "functionId": 1, + "functionName": "format_fql", + "id": "2", + "parameters": [ + "playbook_input:device" + ], + "template": "%%\nhostname:['{0}'],device_id:['{0}']\n%%", + "type": "format" + }, + "errors": {}, + "id": "2", + "type": "format", + "warnings": {}, + "x": 1.4210854715202004e-14, + "y": 328 + }, + "3": { + "data": { + "advanced": { + "customName": "input filter", + "customNameId": 0, + "description": "Determines if the provided inputs are present in the dataset.", + "join": [], + "note": "Determines if the provided inputs are present in the dataset." + }, + "conditions": [ + { + "comparisons": [ + { + "conditionIndex": 0, + "op": "!=", + "param": "playbook_input:device", + "value": "" + } + ], + "conditionIndex": 0, + "customName": "playbook inputs are present", + "logic": "and" + } + ], + "functionId": 1, + "functionName": "input_filter", + "id": "3", + "type": "filter" + }, + "errors": {}, + "id": "3", + "type": "filter", + "warnings": {}, + "x": 60, + "y": 148 + }, + "4": { + "data": { + "action": "query device", + "actionType": "investigate", + "advanced": { + "customName": "query device", + "customNameId": 0, + "description": "Get information about the CrowdStrike device using its hostname or device id.", + "join": [], + "note": "Get information about the CrowdStrike device using its hostname or device id." + }, + "connector": "CrowdStrike OAuth API", + "connectorConfigs": [ + "crowdstrike_oauth_api" + ], + "connectorId": "ae971ba5-3117-444a-8ac5-6ce779f3a232", + "connectorVersion": "v1", + "functionId": 1, + "functionName": "query_device", + "id": "4", + "loop": { + "enabled": false, + "exitAfterUnit": "m", + "exitAfterValue": 10, + "exitConditionEnabled": false, + "exitLoopAfter": 2, + "pauseUnit": "m", + "pauseValue": 2 + }, + "parameters": { + "filter": "format_fql:formatted_data.*", + "limit": 50 + }, + "requiredParameters": [ + { + "data_type": "numeric", + "default": 50, + "field": "limit" + } + ], + "type": "action" + }, + "errors": {}, + "id": "4", + "type": "action", + "warnings": {}, + "x": 1.4210854715202004e-14, + "y": 504 + }, + "6": { + "data": { + "advanced": { + "customName": "format output data", + "customNameId": 0, + "description": "This code block removes null values returned in some cases for device_id or hostname values. It also provides a default value of \"No results found\" when nothing is returned.", + "join": [], + "note": "This code block removes null values returned in some cases for device_id or hostname values. It also provides a default value of \"No results found\" when nothing is returned." + }, + "functionId": 1, + "functionName": "format_output_data", + "id": "6", + "inputParameters": [ + "query_device:action_result.data.*.device_id", + "query_device:action_result.data.*.hostname" + ], + "outputVariables": [ + "device_ids", + "hostnames" + ], + "type": "code" + }, + "errors": {}, + "id": "6", + "type": "code", + "userCode": "\n format_output_data__device_ids = [x for x in query_device_result_item_0 if x is not None] \n format_output_data__hostnames = [x for x in query_device_result_item_1 if x is not None] \n if not format_output_data__device_ids:\n format_output_data__device_ids = [\"No result found\"]\n if not format_output_data__hostnames:\n format_output_data__hostnames = [\"No result found\"]\n \n", + "warnings": {}, + "x": 1.4210854715202004e-14, + "y": 660 + } + }, + "notes": "This playbook can be customized to return other key attributes as required by customers in the End block." + }, + "input_spec": [ + { + "contains": [ + "host name" + ], + "description": "Hostname or Device ID of the Crowdstrike Endpoint to query", + "name": "device" + } + ], + "output_spec": [ + { + "contains": [ + "crowdstrike device id" + ], + "datapaths": [ + "format_output_data:custom_function:device_ids" + ], + "deduplicate": true, + "description": "The device_id of the CrowdStrike Endpoint", + "metadata": { + "device_id_drop_none:custom_function_result.data.*.item": { + "contains": [ + "*" + ] + }, + "query_device:action_result.data.*.device_id": { + "contains": [ + "crowdstrike device id" + ] + } + }, + "name": "device_id" + }, + { + "contains": [ + "host name" + ], + "datapaths": [ + "format_output_data:custom_function:hostnames" + ], + "deduplicate": true, + "description": "The hostname of the CrowdStrike Endpoint", + "metadata": { + "hostname_drop_none:custom_function_result.data.*.item": { + "contains": [ + "*" + ] + }, + "query_device:action_result.data.*.hostname": { + "contains": [ + "host name" + ] + } + }, + "name": "hostname" + } + ], + "playbook_trigger": "artifact_created", + "playbook_type": "data", + "python_version": "3", + "schema": "5.0.15", + "version": "6.3.1.178" + }, + "create_time": "2025-05-02T17:31:33.721278+00:00", + "draft_mode": false, + "labels": [ + "*" + ], + "tags": [ + "host name", + "device id", + "CrowdStrike_OAuth_API" + ] +} \ No newline at end of file diff --git a/playbooks/CrowdStrike_OAuth_API_Get_Device_Info.png b/playbooks/CrowdStrike_OAuth_API_Get_Device_Info.png new file mode 100644 index 0000000000000000000000000000000000000000..a30c75369f185916f25fd686f23b03f02da0efe5 GIT binary patch literal 26210 zcmc$G1ymi)wkD8+h2ZY)!QI_mf(8f&?uY zd9!AEowK@6^{(oYuByGi{p}N~C@%s39_Kw67#O^iq^L3&7&sXi*gGZ|2q0yQP_qR% zfjcNm2!mCQ;~fA$ER8j!OyuOiXn<=NFc3Hn7~~%j;L8t=`)}9c;8bAm{z?bR2{s1< z{X<3`IR7~$f$yI@f1e?;A^s@=PL}=dpV#l0{)nbL9d!UFSX)U=2QV-=)IVQvu+(%c zFffSs=BgTw8gjDShBj98`bIVe#`LaMwtsSg@w##YmsZA(`b4f)mevm3u6!haNpJ(# zf07wUi2f3BwBRGrkW(ZQv9UKMVy9=MXC&c&Peeq-Yj0%2tt=}3kLdKVUY8+%g*CN3^621aHEW@b7-g3iIs+EL$?&f0vUd1Kvw#LN{JF!xM9;|ZA7wLkHUA%E`*Y{- zY=5=u@5k}}DU4g$*uloq=})abS(`iZGxPrSjQ>9Q-z&-crz~zob5~ zD?c+Aknumf^B?Ba{P&!kOdS83^Iz`#BPTDzpWgbH-uruK{z?VLhW|Y;!@rF)|9h|i zJ_0Z>K`<#%AyrrKlMHB2G~>s?_*FRtc0SNFsyhtmAlp|%5rn%iK2vv;F`_xn^M=vq zzP7kCTh}O35R$!sjE1Y@jvN-gWBAm_`8FeC-TVIIS=)_U=h@Tf z(>Tvn#}O_z`cVKPjIZw#EY^x1NE!?rsT#$1VG>bLkQ@RjO*&iY15ApB2oi9hq3oVb zr0bXa`SXcp7Zg;~bTl+KYofA72ni7%V@XTHMMtpWFs;?jP{?Wv>yR^26<7TD(cYe6 zF47udSf)LbGQiKQF@ytN;fUMN7qm!f@uGoxYgVvaP0lXIN*4hFcLNczyRF2$2Ysf< z_7fAx>lTHhq@R~%7AjIOH8cz`LIe5x_JA}n)26=QVF^qQsy`q%<~24^vIg?o|)*wFC|#D5Cp-nTFePK`{|^ zgb_tb&=;n7UAbyFUl9iZfXxN0L1lMXu%J3wvTCVRrxqe_U=Rh+EAn}McbRo(GOyetGn(P*P zV3r%t&~$s?A!Ly!8x}HMEoDc|LROjm^o*jPff6OeeExlrS z_&&CqnwmP-pwkiF%?b#nrHz)WfXj*Do(n5=iiya*uIr=#X>1$z?l_dp3_pTh^RVZm` zsUvtMs{-CPZ1b3C81`bHopuOcy(d=Fp9Ujwhb|s*ZQAd%lG)!bRcvjMbex@?JFZSW zCivfedz`hg8T8p+E;d`-75|JS8;8cEE3DVoam(a)fFif~`Zbl&4JR45qdmoUwFm`vg}WlB8n;{HN8d}jfg&|B50CdSlG3bvp?#(rz-=1&jO zX}?}nu6k5H_ZsInHm-TEj?)5fu_mD5%nxO>~5GciVcV6j!2BG&?&hpT-{_-t6>rEA$SUoPz9ly-_bE zeF7SxiY(i$CU&~%lLXFhGp9cMNL9z5_7913u~*M-4un3}o3r(-l?hQ%2sq_tiE*vk zPgi3cFFO+)@z48()<08hr_&}=Zg9)%K5Tn!oV!D$V&cvVuS|!GU6NAv zymD;D?PWzqM&7X97m`t`SW8@8Ht`Y&Ku69M`Ya3TuQ}1R!sQzV6fG!e-@N4Um{CMXLAs(9S-IpEoom(+c2{DdoHwr$=)XCr8 z=H}EDm&=3ocbl~Zv6I91w_IJpq}id3uZ!HsTx6`Xg?IN4573a1%dVHn&%^vLZ^_7+5CP)q`VOQI=PW&CBYTSsWRb_@*z75H*PkF3B zD=^%#!STX3jN!u$7f_IR^1IkGSAYD5HIA|jJHu)$qvjP9#D+M$`&5JSqg`-%V$Rmv zFFy;CxVaV9%)n1{cWBqzd593y77f<7>tz*&!z2x6U|6sJCFu_Ni;kr{KXm*&Bj=zz z0Y`qEgs)T0R4ev19M|ARsLAsC-D4_ajs|6Ho9XTflhdPiN$j=uZ{E%_h%93}n&B*$ov!yl; z0r@shC6LGQj^!Iv05Ng5`&UI#qwV85)0;)Zj#m?}PX#xM(%}U$QICf#9gSj)SW8P< zsW_}GxCZi;v&5-sY5Q4?Hy`#6<1_f8n5Wlwk&s}p{Ox<*P?TCyQ3c(Nt1ooZMnmzN(G4^fTRUTh7@WwP=!QTx#! zn69&1tuJvgz0WEMNf2U?8U@fex%PT*2m9db~NcrwfH8_BBIWf!=m*x?ey9nBhGlsxg)Ud`c zvqDCW-L-Yll7?!&x^(zjYEfERj#zs!g$MqTL>eAxY(4Axh;+7PgVLkd=7Uf%ts(iv9pzarvmh%AaLJAr#ImU9gPlShPw zehcr8<*Kcz`?VsZ|)0gC*v18v*kkCPb+2LS) zK~Z23G|0XSst}O|K;uLR!N3!N*3$TiGZBH+p}|BKQlXu1E6 zC>sSlM2>ut3JMUq21im11x6CZ4DB5R67C}yOtC1KfeI{Z7&v$cycZZqmE_OB2pze- z>!eD^1nW8=-rVqE;soZ0b3-amv4sso%2Jv{6>ZnSx4gS=zin+l3fpv*>uzd*)U*d6 zTY(1BC-@cvU9y2?(d(7ic8^|7jz?2;k;<5z#Qy(9&x6H2L>Dx3%Gg z!oV0rf<=iTSs~Yqo#ev0L!e1%C}}FGsI0~poV>A1tK^F=Gm}**<$?%+))mD9i*zz$ z2HdoM=);4Xm%$Vr9_6qitFt>oVeEKgDzrh=7=bB!@G_buOZj5p?>J2ephrxOwv2(w#j_$9#P{_I5#?l&-vWkgZq?N<6h8^$Ei8rR-gUgbBwmO0z*Jz2j;*E1yFrcmv^_5|V{em;B)|&DxYbc)ed)au_s&S3i z<+Su)YDsc&tcqJ-wrs)Mg8c z#UkU+tkS};PZ1F?1e8GU7NvHr3lNdbJ_4KY`g#dS7qcX%GU`xu{^rCfQKdxKb$pJ) zEVT&IW$g4Z>ad-O6M~Me0?^jhv}Nw*J^+*Ya)+Z)#GE%Tr1;cj(3R6OCLV#0GQn+P zU|j8rf4M2E(BPpvh;giVxF}22>e6v{8cw>8Pn!@vk}DscsKu=}BMlyvk^#^QbMGo3j&KuK6GCFcLYOblmlt_VBqpiG9 znNx6I3R&Yx$&iXO@tn?`Ygf5j)COS08Z3E4I zJP$6`Bg#=#928}6oBtN2(bzvoz$b_d)UU|8CuT1Ko6f-H8%yMCT1@CRm(_OfCCQf~ z9ck)=2(6o>p)G&POMHx3GqWB(kSpO`O+}=Lc^M3k9fJ&dUzrfI0Hm+BHGlC7&NYB2 zfpHBPgN%6jOr66fbxMVd8D>O8LlGm*2GO^RGyGbfU}nJ;xaU9k$Q$O(O~gJAHZ2`` zJWNI>JUwJm$9$l5Q94*S^1G%(dA3IIH?vf#?DR&OtvdqK)}=L-`Qge6znt9?N~+Me?+!(B|JKUkb*AMtqq&QB+8t~8NLTQ=YOh$1d7 zMty^jTqd_McV9qGKK+pQPVyW%fYgC)8t%4Ort7zVdI_l<#nVp-2jp~3OgpHk-moJs z;=)*j)2<8LLoE6f zE=A0A@e|?unwzW~Ht0RVR;lDhqN3(iyOiVQsE0{NSd}nc48x$kYX>v7m5G!VCpS5@hKz+a z`-UfFlh4{hNct;wnL-5Tah%Wec3h|R_4dxg%70sDdKKrTASM~3B%RXNJpZaZJ*;HI zkRupXjFR%Z{NfTi>=DU$-Jz*0ujvt`;PUqS4i)=RJ?p%cgV&RKl_IS3us^L)IoDCSGc%Lk1j=x+M8+1W9zZh3h!O=*oBx*p%c3blVkCsoB%cRvux z5@neFdP~7te4LnMhsL3OtV`5R!LMMQZvTEhjH^Ra`#hBBuP-OLylNdXMi+>lF5A-I zQ7LP#;h?uWw3rFjXEGrQPH0Wr2!Y*92Ui~61Ycn`fRsj+>J7#Cy8Rw?$h)B`VjC&# zgV{KRl5?iGOzb$uiaM_`{81TepbM(+!pZ_V+Gpxb%$EbUH*c*7SR(=O5qaw&taN%( zCaY(0VJ2%~PU(t-a~sM#A8O_Mo|;M3nnTWO3E^N3P6c4QT7R6s+I5B<`5c2J?<23+ zKpjG0^macp!Jb*EJ6%n|yQyJTDx*o91EOc*bk&i8NHq|8@eB@MBRc5(*)($P78h;c zBrfg@?9e=9*5~JUh#?_ijv@n<#-Xij^RCqRxNv0Bs7?ulaM7#TtP*s2rG$XSU|pal zZIl)cboUf>B9ehhm=WQFEiJj1sJ%8LyoL2BE;f%0pm`;!zHL$8FN}RMWGru%{)vA< z%|@M?$SHdQf-evULF@_N z3x0A)8YKl8>441Y?G8Z3oj)TMmaACBzBMK#CG)*?HjqNf`}cvSr4$cEdxr`U_H+xH zT9i@Tc0McP7;j=?T4i$Nsq6m6Oc4yLyO4$H+r>VmuSmG>7rjtRHBe}AQwT=bgd0R4 z?NTuY6x@-n)^LY|8tt(=ZC6@c7lQ3o6a<{PVi-V+P|gCApI6Rua|s>K%3i76v(>f| z@z60mY0*0pSlx)|uT9oZAI7uWM(zq9x@@A3ACY6A+On4WzN^HIDNCA2aVYJ5>Us=S z&^!%MI`C~lwckoFMTVCAcKF2W{L5G-EGa)HhEP3mAA4gs#XhVpbq;Wok38g6p*Phm zB@ZlF7Tv@*P#A~Z`Y#;}uu<3xmh?z+_)ygeTNn4bo|?GJbGTd`ZxQ-8zxeeL)B;eU zCUy5}Dzw&kA_=g`RHFGcd7Oc;SV-VXEfKVk(AKbYK`gRX6_H{ZU2CZE;n>d7>tBBYe zmInT;x#dbI4ToSBqQFK^@`ZQrWZDSjh$Pd3Dp@9I;j?(5u>bivjHxv2HKHvA&TmFx zFl|dL^N@HGGBUD)A`@12_WFi~%t_J7uyC{Y1Y z22&doXn<77?s;fbeU$q3cZbvCqpDVuNAn{43w1lK2>8YV^a$;!y2-GgetQ;t2oXC!jIn8{{Y8z$HvncLNGy-@Gs?jbjIrNeYAobG)Y!-P>b&-+ z#ibUTC(hc_Wv8QN2*8$oR52LXxF+b;xhEu-?m_`}C?gfm!KT22&oT1SM7R@lnCsJ~KOftA92@1ZQY$BC~8FTI`^-*)|9V0q+9wab|w(O4eKX9I9=SbWs zYv^LbzJ6te)TUk5QPbwpv}XKXoSnz;!f>08O>7=24Cj;SO03TMmhn7q5m$MGQ#JSmD=wzDRJ)b-ebkFdrtMFW3DrSAdUFE)~8(52(%x~=VTC7>f2rT#791i z;IKb_6YCJ4@e9r@d-XuPA`Ydwucw+(6cn{?_K=dDEh@ z=E2R_{g`lkkGYQf?l~=Nw3tU3Ie&ATr%Iv4V6938Q&E_?i{qo^^=P!>#F|5|z<}4I zyTzxm)3P&y+n?`(PrW1JRO(^%+SA_V!lrjDs7)R-<_GQ~P6p>Z)9&tN&`i4s>9ITq zf6S#OB`5`{-Yvb8_*msFDHLGc_VHwZc1VcO11-UApIXz2=T`Q9x80D?$BlUyKAF&a zS57~a)$ZQw0;ROv?XfR5dd=&+hCEV&ZY=m}C_nb?b+AUEgQ5o#I;vhxt>A5krjqj! z!1dbB5q!+~o!TO&sfId)9`3bVuAimmtod37XmcL?E^0!Pf5Hw+Zt}kL2(u70(~|Lh zkDgJ}=6Gy3&D~9xfA{Ss+GV~dCwnNJ>o`H-)#3vzcf;c{xP!@@<;wKb?B|Q#-FwJ7 zuShk;i>nFji>&@xuW$ivy|!B(<)x4}Uf%O1$cH!Y9~T-2Ih}k2d=3V?7SSB|d}`;f zl%E^E5tdWCy~R&e@efc(UTt@j6d}A0%GF%$p>@=x4=_D~`UXwfuj+j~W!HR?-y6p9 zP%&k)N+HDQNvNRSJmr7$!Lfg$x#UHCMCiD?<`1C$9(>}~?d!yLA zD8c(8K+Z_H)kmG}-If70mGdjtH--)iIpJO{g|q7$me<#4%1FDpoQ{%Xke5Wd)7Rtj z;T3%IQx=U9b>NPo5$K_ompRy+zM_hp8l#iP z!Z>Dr`JL$HQWwIHFVjmoFNV3CZ(Djc$7Yu4=Q}((c|jU4q+`f+h1>i$+)fl%=Mkrz z>#wfAi%wWINiIMI6fp=2&utakbvI}@N#=4+&!ds(+RmR|T3C$CXp#e=el5v$qPIOU zxlq68+`bf+>OhZhSi|D7^`Tjx6ry(?Q|XxEbL<euGAc$UE!4dvTj(xf{%IakrI*z?5FUrD}T8 zNF#ei-t0kv2cJv(d0)mb;=Pr1fti8M?@X3Od`6_tfq~XIH+#w77I4jOHTv~+dSJ>l zV5x6@vS7Dgtzm9yo}B;W2A(UrO(QveVvy-u@bhb*3|T0#!fWprX8YF~7K$IA=56@y zQj<_u4Oe{~Or~XRx*$#M;c(F^>}SSBT==A2s4pB$5K>*BB5{LHJ?OUN~elPZ7NBbm^_D%S>{MM=;(ml-+9Zy+>op-&2O&Pi> z81xC6DlT-qT9`BC@!v0N&l@!oT5X*GAgd^UHyGhj^h1o_mO=?7?k z*e2n8lcRNFrSH#R$zaLmd0T7N#1zL(D~I#dk@+@fJY0z`-+>l7U2&9!pYcqHDNXV% zmsTQ;OeYaNL9(n|G|%FE8y<<%Bbnu@mOh&n5i)^A#Nn>ZJ^{N%*jl)p3lw_|TD- zh)yb3T~bn&xuXh2+Vzd{pkQR>Bs-3992x@BpyfECy(6j?n>#laT~ed8+L}hz&aKJV z6tq8CJtPY>xHtwz5{Uamw>NdSReilCTfMv;d{F3+kKFhAfjT!NBhKC-9R#Mbkf7op zoIhP5aCQJ0C1e*+j4>!3?IxsiPcs>&`p$~r#YsZK;ciBre~1=;je@<(lB^WX+iez3 z3xkE8!Aw+D2p9ALJ7C6GLmIa;Rxtv1BP5pChp+cS#U&*eEaT$lnQWhaN(R5WvwzxUMOi+;za<-2q2>e} z3Md6I0SPopDMj_T zx%If1wHLq+xMMk$fW39G#+rNdo9UGzW=G@@%<4C^JwlCDBN4PLZ%nYY?X4bgw~$(b z2GA6JB1R&j>fF+&LB)W8fc-e|yf6NE5E_vQB61n5o14hP)aFXavF!b^x9=~nO3TVD zpY4V@!5JRB*axUb8UMwvU_Q*k?@jdbLFeE63e_)(T2OMcIZ`4)A8=W#pMu-+409>u zQe#rSw12GCV7FP*^3L^Np_ZcdrkL664R#!o$on+F8rK{oBPofK&J@UUG(yXuUA;kq zi>hA!yd6+5$~i&G6-<{ToCecm6c1JyYxyI5c!JgBnz}U98vWJSzMr(Qimcx_TxqjpyqdlCAyhuDqB1X6e9#XIWtpDS1C3vO``UA z|G0c$EDeh~>AB{5LfPW}UQ|Nd0cwoPrJ>%~VVo~5yo)k6Dq1lbz~y+?K0Uqgxxob9LC_Qr~MFq{h6IXN}*o*tD~ z51fQEbng^wC8-j2J#mQ&C14aW3!+L*rmVRzh&eem#<;UTBE?7uw&c-x`v(9M!g7m4 z-84YSq~S7#7#(JalxS6{fTvkB)BtwXegXrNkU{^wzo0KZ1b|qVVh0dQvdvOSo`F`H zu5AKt51YC*bLRGh-+OEE@bC*L-8-DvL0*Zf5p7UF(1cpqYpmSB_O2=gU+gZ_xp*B! zrN>gjP{{}um>bm(>uI(=tU!DiPz)gSfrW-)lo2GaVbb;)uWnk<5I{v6b)_|5<4r7# z{b<{A{_KW25WPibZf@>%Uo$9S9MyIl2MHB$6Fr{U?jfMnak;W+n#FBJgIQ5qd%5g{ z>wL24HE>Wg!R;k(B{vqYAn+v zw{kCh0}qk^T2g|tp=&{LYOCN>yrHnLaBEQJK+oH|W9mzrW4EU=j+Oes@$pX_HW~}n zLWcI7{Vf%3ZJEs2*jP+m#%Ox33C6nb-e>*^19u6>Oq%H)_viZCW|o%RO1knWJG{ek zZ`rH_$>aExPm%J1$4l*dGe5E%5({)v#Pe!8ayA$D^wM3{(udnwBO46M5H$IOV zvr+X;Is^{-U<85Kx}V?b-Y`5KmY%k{%eExK0V|6`AAq-0fO9|9t2ZXDARu(4Jx3jzBffiwgnnA0tsR2khktCC7v+Nw7 z1AKjbfzHM6Jb64kvI!9c$UoKka~CwsBkqh_T|#r}KTAtneNgBGI*B^7zX0%>Ni_CL zf3ltml9%(zl8T1L?AqF&nUrkUV!dES9{)OJtX298l@JMubmJj*jDbbuC#TDOV?)C% z$Lx#%P89+kr>Kh>ad)@N`o(-G>VS&fa)YMAR1V^*m)FY)x7J~H0kT%jHZLs=&2YT^ z20gTCe!SsUv_Qp^HJ=m*zzpRseNuc!578kcDB0CZic3v*cs5`EoT>M4k87m1{lamN z=;Vn9Mkry{{()mP;G$%SXGy*Mv(o|jRbC5J0%=THFa#X;1(90BmzOYu9mw$ zuR`JV#l;;Q3>;h@EZK0hznK=d(a=WCV%~hkdKYEzJ5V4<)&1zLZK$uai2L>1ehj4P z54<6()%lmJNtAc^gc2Iy_F0WbM=epO%QR_3lnh%2hBUNg4<0O(D#WRnNQze#K8Vql zvsyQ_o#+kUQ*s0C&|H#}G@ z7k=`GYI~yc5fE3Ae$ywpvkbnp8W4Oc-*^Y{9wlaVxE}=oTKd?%N6WsB=6Jfn#=Zxw zNC-MpV#@44K*qiH9=<_Hy;%t&;E-omL82t$4jVC!XYiQbSL^e*0E3>UYr#UJ-Gh#) z&px4N%hay*aKo`aT?g%fTPXiMovZdqXgzo|Y5v!*#X-Ml3K&;!wz$lSDb~1F!aOIR zH{?0WeguJ+PSeqmpPwH)U7FSJ6$HG3()w-}NW&+xIPRi&&UD{i5D{p5JYE4xn03>u z)@j`JuA(KJ&6A9Y$zAvtftTkizkquo_ROpSJOYfgPdXwh_a5xZTKjDW2*`6@4O2Ot zSDE!WyW>0F?#C*sB2HF$3JUW2hI-AgXuD=5>Hug{MWeb!**rA|84E)d zQcR;OtJMvMVTeo5*M9QRq$@KPoOg3j z*}L>kjuQ<}jaDlu18gWKKOVMz&9B&BMK-QDlD6y0WOg``4b)XB}wjidkDbO9m%!|aUX z!^{ADy~O79=$mh>98ATVHQU*uQKk58XWZf5`NN)`B4VMvkASD^YRgTQR|7j8`OTcb zy0A3q_o;C8dgP7EQHGbxvZ@1o$uRLMA{8VnmLD4Bc^K4@?DLc7!c4<_d+Cdx*nR;7 z%R(PA83BRLZ;k8Xq?=1Wvf_!TUvKewhT1I`-`g+yc)z{CEOxc?+^)JD4Yxau-g=yN z#3zVn)|*N^R_i`}*zDy8#}7SuBhWD7GD#duRS;;svbp=HU1NWH%yJM{@3{7W+3~n^ zw4Ld5A!gJ0wALK}J*=am!sC8?&tjU@>ePC3Vl>_LmgFrNMgHdYy(2F-cRyKiKiR!@ zFbbRBlInqs>_e^%&9~(eITjK7Dy{Y)-V=dliL0TYxyp7fpVmEHdzC+RdWcV5pqoBgfC-;L{@85qJSQ1Bze z2i`UDGRuoM1I#a$@3Io(5pWo(DO`xcri?NcFSBmh%k-~XSkB%B@51bBS(fo@iX7%5 zAEZ_BcU}Y?6<;LL32+_dN#!{@*n2?U9qx>d;V1mz|h5hY1FAYwJQQ`>nxf z9>Y1=OXMfV+tXIwQjh&(BK=a2GY{W!qFg$M#YIE$+v6%!ao;pA2yU&& z1ha_Q1f%c*jWvw4KwwHc`RF5W?lS5T>Ydv8Pj)J}7>SbcHG4l4U><;x0PBd1Sm(Y4 zGnUczqG?V`WcKdY{NSuQrs)BRD>x(V--_l5+D>v6Z2+UMMWLroA!=UY_`BJV1bGd< z1=j;eQP2d z0FP2rr;;O;Vgu*)t`}%FUA&Z%{mg=Lb_fopn3~MqrSCnu~N@fxNne)jiO2FW_M2>sc{>hdDj|JugX z;glIW`VFKcC98eRWx7dX5;>6nW&5i8`4|wLw1{Yt-reM8*u2SWoOUcvpM*3>;#x|r zM2`xx{Rn|N21Y6f&|zD=14K0whTuW@fXz7ViS!;K4DdpsiG)T73Z{bfQH(+75M^Tl zo=&EOAs9fYNz5{%-Md+92`tdxcMzs{MQN3!VJ^ue`a-0eXoD3zk*8FK$m%6DUsnifgZVT4M z?FS?_N<*Hly$C8z8i5bv9N=cQYrSe;f+;{tJT%0-g;f=O(p zx8fxGM3G1V%TXLBcRAv4M0)(@Pco66i0vmr_C_Dzbol1d6v;K>% z)l*%j7J8}>65Cf#ls~3bm@g9*RUmbMqqVGxiik!r7bj;^L4om`p9rZ00wPD6RSFu0 zW~Q6-@I~C#ww|u8u40VoKbWp57RiUHhYL;M6nU|8Se=i%tqpSxlan#k)zwi^QF#(q zo%g@fOtan;jNKp?S68NMZQe3(Z`pSb!2;lIN z1}2BU$j6^<<0<^%mXG>d?zO7)PL9lj#4KT%-RfMki1sDlCYWO&aEP!hXCK^bodSa+`Af6;=Euo0rfY$u7au6t@=KCp+CQ4HfPex`%E z02a;v6r=T@&f5P}jsE9ETD)+g_gan6z6vy@es8@8H041{+KP^kM}2Om<*j#5FOLo?h^+lcV$QHUYm0wLR8* z1$J1h0Ds|ydf5rJg8V|$eB=9DDl5RQH>{N?Dk_4!hfe2Ik~gBF;-*P`Evqb@WudbA zgutSKKp6%;r`Va*Y4Aj>MV2}VZjWtV8kMOYAI;*pkwm90{o<_1OYeZjUss;9-)$hcb}`UEhp zfdJV7`It&I8YBx~!wyl~Em}?hbHL_9bAqj+v*%}%@-j%NK^vQcp~aYa+gu@T93adE z3LHBlomT8)&d)td1KiI3QnU;Lx208;KH;Y=LY|NAFNU~J@$aVneMKNDo$dKiYXT{f z#VPrD)K$L6ySK}Y?#=i>hmJ7}RsB$Ehg^AGj=*CT>iQ)=t}gwN_;j%a85>^Q$A{~z z6^IlQlaRQ`-UI_@f&d8O28&FmK8Lf^dzmb05uPt&jZwRpwsYfLrnf0fc_Jd^X=aFo z{00adh#^PbxGCY)0L_LitI}(u!^bxHdUb8BV}DaZPJAYk<8xZdmsQKT>FLW|o-+(| zO^nqyAIJ3W!(Ulet2Hkd5*UOGbY2$%3|Fw5lPus~k(Q(q5gRxjKli874R19~<w0N-GDoE8Q6balQ$NRg<;$mTR@2#MQG~*`z-i zQRn_D8YrZ&9~{ugvYISjn3@t&QNwm43B^R6j9q9dA#HbEnw%7* zR}ft1;%YV=G@(u7vY+7wOvA`DZh)pApPnE(mh_#gwj(Dqh{#^yFjcl?DJfDeGJ%YV zI95&CAL8Axj(cvz=yp+-4}ACXLM6n_?Y(=mX`U3R9KJcz))OK7G?%UEPi{VPuJ1T5 zXKP-05~dkmyVx@d8RE2r^~P6EkpfE`+`|X8GMKhSpXD-nsi~>WcS;qU&(<84N@jM) zG8}fXi*P>#0g;Z? zJNzU70?7vn2&g>!-bV{)ipYeM0cK;QD^PmRbhA0}!(_$k5OQxbTXfUH!j$IRN{9BF zS8Trl^Y@{wq}OEcdf61``BT0*89Wd2z+9U~ZRf9p{e#Pja%r*57eOkPyeABpkvgK8 zkWP?ic;18jsy|JC$yq$TLBS3Wwese=AdLh9h5U)UOY^AcujIwaN zl%YV~Moz0=PykjL&zeK6wfq3d$h8RR;=;c)|=@H1&JweHl2yO*iU8; z@73Y?ot9M9`%J(n>y z4od*lnAh{hz{Yla3qbUGT-16RxfD}Z%Y4q61Q?Q607^#<9oG5Q9YCY~(@BCvU!pT*CTSDp6s9(dI&9`j2rv{zLW_?bLz7gws{r_-2B$1+8H zV3HWLxsA@hT;_AHUEO(fE}!#dAt9*4hGbYzn(`a`iI((A8vGL2AC4J2Ju*6abad3r zFWt#Iv^@MUxua>CX?G!p8H1MWeU8j`@F!H4Wtyeq`S2+0eVKgiU__y86d}V8ji?hK zY}u2(*^&R{H$8RaBM=g=gx6u#fritR>G_*U8AqzDDL>Hp2YuVg97D%*9LY>;@-?4x z`OUX9mvs-Hrzm`Z=jzzksaxC2O@4Fgl08Q&u8Fa+mz#MxJ+B+K2Z4)wQjy>fTtGnH zNVK}rxWenzMdto}7A!=jUZg6tNKcp0tGD;89#CCpS=s9#r}dD;(Tp^~(iDz6UYu?! zoRcB%lSmHpw1e3am3`wa^BGfn^8?FoKvZDt8+x)fCa?3^fjf2_9I7YJ$EU`6;O`?y zt^WKMz$4&DD2Bn|GR^RI{}SUg*cAx7Wz*`l$>!6NY7&%BUdI)oyOBT7Nxk3DxhA!) z&G913y~q4|MaL0qxZH3uzv_AkjXD^OE0I|rd~;kkP6FM2BIN@)LR;S19f=4Bx+Y;K zJ_73m+boze`zE;D$fdBPEFG1UG$#^_V8`D{$p4dT-{U0+zU?VlkAsbsRa`0K}$lC?%CW4 z>B;CKrELhr6LTt9ze6SjN0>n2m|cl*Mj+@46%L+3rCidU`?l z<+W@=U`uzK^6hL+dj|~-Z7Acr*pu~sWhh6qIPRKUR3vVNkRu9ghFVXgaF62nN%cOV z&m%zn@1~<(Y-x_-C-kaM?@+JMl3RCN7hA9DxTWyodA2;}?(@4XirYDBf7xyKX-&&L zS+(H9CTFJzQenh*N6gFGN4sNQ*dpWY{D3H&J zVJvQFcq(d9tI!p3v2fG_JdpA#t>0NH&T7vI!+y37UyS(Z?a?>dtP@{1+FgmoFEwj3 zq^{C=-z#Y?gb*_NO6U0GhKmd)nyzVIfd-7_&pxJYzbR zpY8E#N4VM<`|bPp?+L^wKeo1dyCJ|E8Wx5~e@|niHKn8gq1Ly@EAo6d39#`cqFTCl zS1i+L(le4hth>`nHKcioJHHk;0L9~!(=lzmgZD67q|C<=@i@gR!HebE6i^F3RgViui zBQf?q0l3H7`am)9-`EI*iK_s(2oMB}{UiTBgWnyt3;jmG8v?K{sV<&%q65l=SIVN3 z38~>w_6J+sUQ1qJTRbJoo(>;6WnBHmi}|r>K_6uw2;+67kp~cBQs=ZrOJr#az&$3P zYW4!|kyG>=ph3Xcg09Kg)717@q1qR}bQh$-41JDtjAuJEm2uD8jG9S%EXWhpL7v7~x=m*tHR5NAu& zOp%afTx%HB*us$!Y(@z?NeXttk>zu=xXtLt=Z$3kf&Sp%r53rTjgS618!EgClDPRr znVlCKIY(d~N?7)M_}_Xv%dn`vc8$X@q#!9uBSTArq?EvbG$;sC0*XVobfz5TJNsj2MK_m>C!lyeMb-w5Er?l39S z7ylU5eSDtJG1>faHwZ7uaZu4O5loBqA_!i8Kgc+vIi{KAanvIb*|=Zs5xq)NfS~d@ zYp%}A8#)PH)P>x0d(_wRe4Q~R38lT4Yp{}$kD;GoR)>9 zMR#^>5-Q7QiqU$CyTvLOK^CIKKtqwk0ZKC+L{Pk7LK59CE{QAG$c{z0^aDx5_HQfk z>HU~o%((Th{Q`TC7VSl6pFgLp3H$h5gG2>}K3-Ahp^1r!PAAQ2{-NgjH^+v16{V$; z4kt1HOWv0oP*PDXVdFDauN_Pd=+%Y<1v4@+ImCgM%)uV$*eoT$?Qc`hPK=Lt=+nH6 zfkABCh^I89cO3ns^gS;^b6}@XW*TM=C5a)Dpemi=#EG#d>ZBiuX|W8^^`K-a)l zF#`hw|7b{Xu&6hlVs%&g>_TY?`mT^?R8K7oMOVGo@_ylw ze}@I(b<^&La;x3Ub;9`==$F7kaWHdK)X)p0ibTIW z;s5%;jZkHm{*XSbvkTkqRVx6Oj4@iEmj7Q?Thf89B0HP|0K-#$H;{}z+Xd)-^B!ow zD=2};{FlO@7i>C48#j6gKCvj$h?xfqlKVD#QHuq*uJ7u3^i4p4JDA5%_xTPKj*=X> zv^xs`pPI0MAU%iTtB|vCI)!U5B*~x z(EsckItnQ*$nsqv%uN_C>#xksDFjitUL~dM57-}AF)V`g0V1j|WuPWAF-;%-71Brz zgnvF&v@wOZR7Kf5M*YI?=qnj_j{jPF=`Ex(PbQg&8Id%QM>lJN(=COhZ9Ayr8e1v> zBo&z5c}MU~;KI~x)b3$F(R|*5o3vRIWU6G<$(yFsWxm+Q2<$#RX2kbi^G8kT<1u{g z5d{N>rX?ZMJ*--U+uE6`C=QZjIKFAQ;i z-iQ}qWnwu#E-Crb5*L=h%2A{yq?jPZh=mQZBBb_phzJm{;HZ^wq0@7a=`#E}_S-0G zwD+9u#LLJ02`&I?mT%y%hzZ59)sd9W2b>Q%ov$u#6{=)xZfqze{@&cY_?fH%mJdgP zZB_%#nxsNAZ_hEOvcs2+ysSkNvT}sZW{pv)u&$Hy^Z3{?)r`HJrSqq8@xleMgd%bA z@n%_G@YtU^#?r*6fx#)^;hYf@IIH&1kHF2XmW|8kk_9)No^#<`dCgoC_lPZ&A%+DQ zs6d8GDgf^)zYExpUjMl5;<*qaRT8;mxMXF;8mWyhcC5>nR(e%E*6I=Y0 zMTzRMkOPR~uzQjZgC$^s`M*^*Y#mV1CKZ0}ByAZ@(oT6djloVSAt8{2?-@Y~!{b&1 zrE7F_bm{xXS64b)Waxyp(Ig0SbIT7E);2cUV|aBR;Blj0VX#Q}MT1r~n?}+tXptip z7UriF9fGlCe_FtRF0#OV)jw`U!4&0); zqh%8B8&(?BX}8cSP{3B>jM~-`Ra?P^*xb)fnv5{J4%GAVDod+MxU9jBMm*5VvC0H( zq*-aeLFL=N{4ACLmyKv^_wvM;^TTrU_pO3HBT1`*&#aowc`Z$|u<|IR&~pi*hF*C& zW6<5S>z)F0){U>2TNb&3f?$Hd{3i3QIMgts4P3Ub5f zzr=?#f%Gb{pqfz}$3(Ox_qPc1F#yM0K@1Kw%YCY z8G?~mHI0pVwvCs!lhpv4=M1@bnNzpUTkZ$azs3i(jLxug{@dY29^uJ}G4l;o$ftFp-Ro z#l=-tm_^WG0)W8bc#iP6h@BPpACY zptHrNHCrwof9)~AEst1XFFl-b{pXMMubr@hrcZnHzhtC)qa2*t znV(T6YQA_2Q6$|9nW2F`;uf?aS}u@FT*?3Btj5-0uAE0dnpx%Uh^@GS3mJtzYNYIW zuvOsl#Wu;+@w6CrSJoYNx|``KAS@;I0Q%@rRYn}emwl;*$k9MYg(#R%uS$2=7@qbd zF60RMh@#FCPh4Mm$oV4iJ8unTxonI3i2d5EChHkK=X*k(V7GiYF@O|lKQCFGqIA=-dgi8T z`v~(~gfY@wk&W{Kzhl?YK(s{FN$-P-`u(UN&zxRSki@q8XQ7sDM%Tq^?%>;-(w|f7 zCF1W@vTApZ^zP1kr|H<=D2sPImqz3}7+M$ZqYs5g_A@luJ1k|E*t;Oo8$4rVHVpRi zGx!*J++CZjH)_}UhZ+}iF+KN)+e74>XsXmC{Ls-?3Vq13xB%9a-xed7hJ}s!zK}7V zr6DixgDuZkh<`V~@gn`7xMsGmi)|Fo!WtfsrWurY3e4ZhZ485O#@ER+_Jdml{=87EU%oL-Q6jfY!QfqJRzh?;SBkFY+C&B&%0R;6rjg2 z?>_UDQ?G6t;7;8B)aQiM^_yXmcwVk`UL!8El{;$BuVb&s^*xpcsDwU*M?_q-Cq7?r z?)th*+PPocZ9mwsug!7odjQ|Ahhk!m9A$wD`?rsgm)G~+*9R~9Te93&FAL~4ry4Sp zkv!(e{i9)phc17pOJLS6*~gF9w^DEYGO9XF&hif^`!Zde;4<$OeZ+9Nw|q?2J0l~g zobo7=(IQ}JVxUQQXIIy8?N3gvD8UUxH9j%%R2L{HjW%;(tlp}QPaU!J|fz(AeQzCxL!bss4gyD9t}53xPMt%RD+Z! z78Ml>4-fCzP1dt{>-r%8TpX&|ZGSND!`}Wa1kM)w)Y+$l0K#H_&;W>AtRB9UJ;{ zF(clm{UKfaT=_F2XNJpF5Kvk~8-5vij?8Q~yX<5i{!9eA_=ss&mdN>R*-YIS)1~?K z!YE-+WxnM#;@LB@3^Ln2{@2ko{GOBQd+#%B4^#@Cy?tj4a^O~&i(}qjb{UTxjdcG4 z8;m>B~R%(Fm6+W9&4oRf(1H#bGH zt-C~0_7|6B3$r0JEUJzZw9$swRQM>~80N@yTLlF>W#wZ|reDhu;>{8$Wd)6ct?f6L zvBqZ?r>kjKd%x0(ou(56%pwjK$p^Fgc%*pH!g3auED!W8|)V?S9dkWkmy0NmcTfO4R6RRY?iEq4`RDTC)7YQ)V^y>x{Zg&kGHQ z@SJS=E$@GBtMUhU35u3x3niX+5n^LL?|yFZyBK46dHpEj&rbymPgbE!yX17e6P2rq zrYrJDmgTODxRjLc_Y7SCK+E~T#VG80abo|bAIXNlGiZb9V^U5L9H z(t5hTKhzSFrftKle2FXM3s)GW z6P~>}MeEZcs(-@lZ3j z$$Vhige~cKNwTa@FRZN2UyzQ{2Vnyjt=z3^sNqI3`)S?LvKCKUJ2|O=K8_|`8;sms zNrH0M+esT3Sg*e$^`v$fek&$=t+dwOkWm>Dh7-+`C9J`vtg3WfP*A{Vmc!Cuo~i6& zFR->NZNh3r!slEJ5V++Tv_iY0m&!h>zD`8~RSlmDQ51b!;kD}Z>cmF{cazEX1#xHyA_dL_alAUzahBJn%8-+1617s^{7%1Z#(tI6%}L7; zankmN1Sgb`sz0$h(BlUogDA(-#H$+Z>y}v`p&UchS7#Yk&EmdHgX4Cm!!2rR z)2F_^CZU`~nBZ2Lf0q7a(n$LaF3-Wt=tNg_InF<1QBeHyJqmYC!-~$cMp?Mtyb0I1 zMH^>rH#0H;z7cbvR4ek+b^p`ki;ucvP$x#gGcDdfA>THsHYbdPW<0)PLlc<5nfC{# z!s{=$9UK$U{!HmJ1)tE<#{=?+lseAOw-n66^LO_paJm@*!~_j_MZd@nsw6FwP(lbF z?!7e~D8&==<+{c|zTU?qL_0WtZ_htIlsftBY;__#?7u^L-cQ9unNbM7&v{osBqd`U zo<|va=PI}17gWcIR6Tj4C|S!vgPud*3b|qF09_`#YXO-5w{Oo*&QBj)h~hJyyAw+RPCCx{-j(?END$#-KUzG zqkmC!$5ylziEjyt%F1BN>mJ8{nnpim<9NPWPr-Q&2!&1s8K*S++{OgQvQwwBn%9;R zm;V1}8M5nxyz8 z5$=PV-H9x!t}ZKMViry67yWp>Sh=*+k>6ogomyA*v3n1dw(WiTQz%_cbuj1i^a}~H z+ex_fE)U~=BCD9N2At?EGjaA;BIgC|H|p3kY$&&cl@7k6ztNw&MZnkZFw{ z789dcJ|XXksNwMT+Gc-`xM#m+N6{fqA2FC&;yZ#D5D0{XB+g_R3wnmVL;moSKyo5& zoeo}FTAG`huz)m>n56hIp-|OKfkgbdn$zUfRb~Pmd{P?X*3lLid&KE3WSJ&{0>?2O z2(}_YxUo{%*HHS}D}LGA^ot;JWq?gN6biXy$v&#YF%j!L|A0%PNDrY_xzWx3UGZBt z%JrhaL-Kl&>+ZLA9^y|ypXxJ$Tt~-BGFfbNq8*Udf%{)a(*7GtAI~}sTKl0Iu+mhj zu01u-($&y41^^`okN_vGGgk7+sFnjIBv*b;g1kX{ND91aMhf^3O*)@I-!b{M{AD;H z8W?*WU0mdHT&4H@;UQepZGX$0*qt;_Z)C~pd*E=&J{;xl9R)?Q*V4qaWfd7y8 zoYZ!&_O??E6rNyVBmv!ArOerkMus0EoaM6+ z6!kK2X~9*+oO|2Dx7D7g0`Xj8ML|^+>2*XQmt+cdhZRyv6E3b=9z$447jdfvqn@}Q zbIhz4Wu&>w!l#(z)M3TFeEKk{OtREC9GM?xNjILvu?{)HMf?{$*N_m!<;QHHg~{n@ zYG1WgUPjV@$K&=pdGW9PX6#wovW2P{9M(8<-5LU6?M$&z5$fbX)a0mnTu0E1g~9n} z46{regwyF;g|%{OXp$&)t3P}hhJ(JEbag4;Ph&T|m4j<4haW72lg_CA=5*^MTIt2@ z58~_JK8ACGsAxVepK!ZRf(iWi1cjB4Te*di$~~uRqAF{VWxuQh3W>k$sVI4Rdt@XY z1=4Vk-t#a$SeYrFA+s>HGqo&NtCrtL!78YnZAq=#s+%ZoNBk@d;H>2R9>s$Nb``Wr zwm#AZKVMe#+CNj&NOOS{u&7XD^&D*;tXo-G#bi+9-q2d8Z)O7bKv8%HsarvN4oVOI3$Rg!b-RRyPC zK#WEF{U{Zdrt@u)5JFE6baGM{h7XZ?3fjidRDP+Ljeaf9*P|`5o3jMkQ;@?BQquLT zWcHh}$z>b_mF;?P(5=HG>G8fFZt`DK^NWVFY)F4sNfodkFVgsHObT)JSR58X9|%{O z^j4D8ft${~P9FUAC`gnH?)bgwYNi4xR;SNE74;^|k=AE@*nZA=0xYh)qU?Akj@|NE znTAY)l)YwI9``3=jC>ki6q7%6%Gd#q-PHK3vfH1TFImm7{?*ka)Zk!LU|>i3Y`ml` z&0W&5S(C_MLYzsW9rntpah0jQ5IV1mFNak1k6<3Ejtgo*-+3;er>4`e*LvGT)>)=&Zr(L#9m(6-J%MEWbm-c*46R$PxR|+E~yP|0F zC<>>jkGdXQ6H!pKv2n7n2*F@;Wu5enoFqJm5k$_w`1bm|)p$|tgn>umu#vrssp(T| zYnsc_^5P$Sw$|1e;&0|%7RuiDjpS8#Mv4VdgDrIPuX{$#9wR0Pg^TchHS@`XuFjZ9z*lp|J0mC*d|`l-Q{R5}N%Sm1=E$o{k%yjAQb0~OS!QqNjI2MTIBpNW zd%3*rledKU?Fx#&yN_39_3CWsL`*sw8-7L7qz%wyS%6GbcjiSVlV#*^86gU|D$nFG zgMxzJzOC8M2u_j_J{}vA5VHQA&=$ZSM?zW(ns5vZYb=>32s>Zj=kEqo21NEFs<#{m zduhAeBu<9g5278%<|xI1NNo)|KrBbyPTP6bm28KTFWPL>zgK(azRc|I6~jSzJRB|C zhi@ZZj{DO(b#)MN3B7s-As=Dt&t!`duaE6Ib6@I4?_?*>w zgkE6+y-epW)g=o55)$l0vl4_tY4<6Gf)^P?`(n-0=PV$L_sU|Pi&s9oH`dYJy5I#B zpcgE8RP&>m);C=X)_NwO`H+Ye2RaMdFOEXSD}<0aaAa}9yb1EvGqJR_7C6B?zgJ_= zQmjdua}O8N%zTpq&Y1g=v_rd&Yo=Midyl}+(a3W4x>7CVFpv;TSAuUFnSev{2zSw| zF{)g99lV{$2vIjwMHj)w!7U1>Z!^Rc{Ngsa_``U9CF$GIUan=frFyiT?6>?lEA5?c z-E+2A=zT>OP*J5NXEP7a&a2*?9PC_>7*=f3AxqPqY*Qt&1HbJSBUfrvH+?oI1Svu; zy(|9PtCke>U!cN#i=fX8%mhk0t(YJZk)d_hM!5b>Ji-WMn8)4pTojSMjZ#u{;4-~x g_$_+S{I0Q`Nt3bWu6$a-`&ZDO$f?SfKYr=|KXin00ssI2 literal 0 HcmV?d00001 diff --git a/playbooks/CrowdStrike_OAuth_API_Get_Device_Info.py b/playbooks/CrowdStrike_OAuth_API_Get_Device_Info.py new file mode 100644 index 0000000000..57bbfbfe67 --- /dev/null +++ b/playbooks/CrowdStrike_OAuth_API_Get_Device_Info.py @@ -0,0 +1,179 @@ +""" +Given either a CrowdStrike device id (agentId) or a hostname, will query the device to get the other missing attribute. This enables finding the hostname from a device id or the device id from a hostname and can be used in front of other CrowdStrike custom playbooks for added flexibility. +""" + + +import phantom.rules as phantom +import json +from datetime import datetime, timedelta + + +@phantom.playbook_block() +def on_start(container): + phantom.debug('on_start() called') + + # call 'input_filter' block + input_filter(container=container) + + return + +@phantom.playbook_block() +def format_fql(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("format_fql() called") + + ################################################################################ + # Format the FQL query to get the input device information using its Device ID + # or hostname. + ################################################################################ + + template = """%%\nhostname:['{0}'],device_id:['{0}']\n%%""" + + # parameter list for template variable replacement + parameters = [ + "playbook_input:device" + ] + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.format(container=container, template=template, parameters=parameters, name="format_fql") + + query_device(container=container) + + return + + +@phantom.playbook_block() +def input_filter(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, loop_state_json=None, **kwargs): + phantom.debug("input_filter() called") + + ################################################################################ + # Determines if the provided inputs are present in the dataset. + ################################################################################ + + # collect filtered artifact ids and results for 'if' condition 1 + matched_artifacts_1, matched_results_1 = phantom.condition( + container=container, + conditions=[ + ["playbook_input:device", "!=", ""] + ], + name="input_filter:condition_1", + delimiter=None) + + # call connected blocks if filtered artifacts or results + if matched_artifacts_1 or matched_results_1: + format_fql(action=action, success=success, container=container, results=results, handle=handle, filtered_artifacts=matched_artifacts_1, filtered_results=matched_results_1) + + return + + +@phantom.playbook_block() +def query_device(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("query_device() called") + + # phantom.debug('Action: {0} {1}'.format(action['name'], ('SUCCEEDED' if success else 'FAILED'))) + + ################################################################################ + # Get information about the CrowdStrike device using its hostname or device id. + ################################################################################ + + format_fql__as_list = phantom.get_format_data(name="format_fql__as_list") + + parameters = [] + + # build parameters list for 'query_device' call + for format_fql__item in format_fql__as_list: + parameters.append({ + "limit": 50, + "filter": format_fql__item, + }) + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.act("query device", parameters=parameters, name="query_device", assets=["crowdstrike_oauth_api"], callback=format_output_data) + + return + + +@phantom.playbook_block() +def format_output_data(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("format_output_data() called") + + ################################################################################ + # This code block removes null values returned in some cases for device_id or + # hostname values. It also provides a default value of "No results found" when + # nothing is returned. + ################################################################################ + + query_device_result_data = phantom.collect2(container=container, datapath=["query_device:action_result.data.*.device_id","query_device:action_result.data.*.hostname"], action_results=results) + + query_device_result_item_0 = [item[0] for item in query_device_result_data] + query_device_result_item_1 = [item[1] for item in query_device_result_data] + + format_output_data__device_ids = None + format_output_data__hostnames = None + + ################################################################################ + ## Custom Code Start + ################################################################################ + + format_output_data__device_ids = [x for x in query_device_result_item_0 if x is not None] + format_output_data__hostnames = [x for x in query_device_result_item_1 if x is not None] + if not format_output_data__device_ids: + format_output_data__device_ids = ["No result found"] + if not format_output_data__hostnames: + format_output_data__hostnames = ["No result found"] + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.save_run_data(key="format_output_data:device_ids", value=json.dumps(format_output_data__device_ids)) + phantom.save_run_data(key="format_output_data:hostnames", value=json.dumps(format_output_data__hostnames)) + + return + + +@phantom.playbook_block() +def on_finish(container, summary): + phantom.debug("on_finish() called") + + format_output_data__device_ids = json.loads(_ if (_ := phantom.get_run_data(key="format_output_data:device_ids")) != "" else "null") # pylint: disable=used-before-assignment + format_output_data__hostnames = json.loads(_ if (_ := phantom.get_run_data(key="format_output_data:hostnames")) != "" else "null") # pylint: disable=used-before-assignment + + device_id_combined_value = phantom.concatenate(format_output_data__device_ids, dedup=True) + hostname_combined_value = phantom.concatenate(format_output_data__hostnames, dedup=True) + + output = { + "device_id": device_id_combined_value, + "hostname": hostname_combined_value, + } + + ################################################################################ + ## 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/CrowdStrike_OAuth_API_Get_Device_Info.yml b/playbooks/CrowdStrike_OAuth_API_Get_Device_Info.yml new file mode 100644 index 0000000000..7579eb959d --- /dev/null +++ b/playbooks/CrowdStrike_OAuth_API_Get_Device_Info.yml @@ -0,0 +1,26 @@ +name: CrowdStrike OAuth API File Restore +id: +version: 1 +date: '2025-06-09' +author: Christian Cloutier, Splunk +type: Investigation +description: "Given either a CrowdStrike device id (agentId) or a hostname, will query the device to get the other missing attribute. This enables finding the hostname from a device id or the device id from a hostname and can be used in front of other CrowdStrike custom playbooks for added flexibility." +playbook: CrowdStrike_OAuth_API_Get_Device_Info +how_to_implement: This input playbook requires the CrowdStrike OAuth API connector to be configured. It is designed to work with an endpoint hostname or device id and will provide the correspoding information (hostname for a device id, and vice versa) for use in automation playbooks. +references: [] +app_list: + - CrowdStrike OAuth API +tags: + platform_tags: + - "host name" + - "device id" + - "CrowdStrike_OAuth_API" + playbook_type: Input + vpe_type: Modern + playbook_fields: [device] + product: + - Splunk SOAR + use_cases: + - Utility + - Endpoint + defend_technique_id: diff --git a/playbooks/CrowdStrike_OAuth_API_Network_Isolation.json b/playbooks/CrowdStrike_OAuth_API_Network_Isolation.json new file mode 100644 index 0000000000..d07f406503 --- /dev/null +++ b/playbooks/CrowdStrike_OAuth_API_Network_Isolation.json @@ -0,0 +1,362 @@ +{ + "blockly": false, + "blockly_xml": "", + "category": "Containment", + "coa": { + "data": { + "description": "Accepts a hostname or device id as input and attempts to isolate (quarantine) the device in Crowdstrike. We then generate an observable report as well as a Markdown formatted report from the results. Both reports can be customized based on user preference.", + "edges": [ + { + "id": "port_0_to_port_2", + "sourceNode": "0", + "sourcePort": "0_out", + "targetNode": "2", + "targetPort": "2_in" + }, + { + "id": "port_5_to_port_1", + "sourceNode": "5", + "sourcePort": "5_out", + "targetNode": "1", + "targetPort": "1_in" + }, + { + "conditions": [ + { + "index": 0 + } + ], + "id": "port_2_to_port_7", + "sourceNode": "2", + "sourcePort": "2_out", + "targetNode": "7", + "targetPort": "7_in" + }, + { + "id": "port_7_to_port_8", + "sourceNode": "7", + "sourcePort": "7_out", + "targetNode": "8", + "targetPort": "8_in" + }, + { + "id": "port_8_to_port_3", + "sourceNode": "8", + "sourcePort": "8_out", + "targetNode": "3", + "targetPort": "3_in" + }, + { + "id": "port_9_to_port_5", + "sourceNode": "9", + "sourcePort": "9_out", + "targetNode": "5", + "targetPort": "5_in" + }, + { + "id": "port_3_to_port_9", + "sourceNode": "3", + "sourcePort": "3_out", + "targetNode": "9", + "targetPort": "9_in" + } + ], + "hash": "81702a597569cf52a31eedffe0193bbff4825326", + "nodes": { + "0": { + "data": { + "advanced": { + "join": [] + }, + "functionName": "on_start", + "id": "0", + "type": "start" + }, + "errors": {}, + "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": 1224 + }, + "2": { + "data": { + "advanced": { + "customName": "input filter", + "customNameId": 0, + "description": "Determines if the provided inputs are present in the dataset.", + "join": [], + "note": "Determines if the provided inputs are present in the dataset." + }, + "conditions": [ + { + "comparisons": [ + { + "conditionIndex": 0, + "op": "!=", + "param": "playbook_input:device", + "value": "" + } + ], + "conditionIndex": 0, + "customName": "input device present", + "logic": "and" + } + ], + "functionId": 1, + "functionName": "input_filter", + "id": "2", + "type": "filter" + }, + "errors": {}, + "id": "2", + "type": "filter", + "warnings": {}, + "x": 60, + "y": 148 + }, + "3": { + "data": { + "action": "quarantine device", + "actionType": "contain", + "advanced": { + "customName": "quarantine device", + "customNameId": 0, + "description": "Quarantines device in Crowdstrike given either a Sensor ID (device_id) or hostname.", + "join": [], + "note": "Quarantines device in Crowdstrike given either a Sensor ID (device_id) or hostname." + }, + "connector": "CrowdStrike OAuth API", + "connectorConfigs": [ + "crowdstrike_oauth_api" + ], + "connectorId": "ae971ba5-3117-444a-8ac5-6ce779f3a232", + "connectorVersion": "v1", + "functionId": 1, + "functionName": "quarantine_device", + "id": "3", + "loop": { + "enabled": false, + "exitAfterUnit": "m", + "exitAfterValue": 10, + "exitConditionEnabled": false, + "exitLoopAfter": 2, + "pauseUnit": "m", + "pauseValue": 2 + }, + "parameters": { + "device_id": "query_device:action_result.data.*.device_id", + "hostname": "" + }, + "requiredParameters": [], + "type": "action" + }, + "errors": {}, + "id": "3", + "type": "action", + "warnings": {}, + "x": 1.4210854715202004e-14, + "y": 680 + }, + "5": { + "customCode": null, + "data": { + "advanced": { + "customName": "host observables", + "customNameId": 0, + "description": "Format a normalized output for each host", + "join": [], + "note": "Format a normalized output for each host." + }, + "functionId": 1, + "functionName": "host_observables", + "id": "5", + "inputParameters": [ + "quarantine_device:action_result.parameter.device_id", + "query_device:action_result.data.*.hostname", + "quarantine_device:action_result.status", + "quarantine_device:action_result.message" + ], + "outputVariables": [ + "observable_array" + ], + "type": "code" + }, + "errors": {}, + "id": "5", + "type": "code", + "userCode": " \n host_observables__observable_array = []\n \n for device_id, hostname, status, status_message in zip(quarantine_device_parameter_device_id, query_device_result_item_0, quarantine_device_result_item_1, quarantine_device_result_message):\n # Initialize the observable dictionary\n observable = {\n \"source\": \"Crowdstrike OAuth API\", \n \"type\": \"Endpoint\", \n \"activity_name\": \"Network Isolation\",\n \"uid\": device_id,\n \"hostname\": hostname,\n \"status\": status,\n \"status_detail\": status_message,\n \"d3fend\": {\n \"d3f_tactic\": \"Isolate\",\n \"d3f_technique\": \"D3-NAM\",\n \"version\": \"1.0.0\"\n }\n }\n\n # Add the observable to the array\n host_observables__observable_array.append(observable)\n \n # Debug output for verification\n phantom.debug(host_observables__observable_array)\n \n", + "warnings": {}, + "x": 1.4210854715202004e-14, + "y": 1048 + }, + "7": { + "data": { + "advanced": { + "customName": "format fql", + "customNameId": 0, + "description": "Format the FQL query to get the input device information using its ID or hostname.", + "join": [], + "note": "Format the FQL query to get the input device information using its ID or hostname." + }, + "functionId": 2, + "functionName": "format_fql", + "id": "7", + "parameters": [ + "playbook_input:device" + ], + "template": "%%\nhostname:['{0}'],device_id:['{0}']\n%%", + "type": "format" + }, + "errors": {}, + "id": "7", + "type": "format", + "warnings": {}, + "x": 1.4210854715202004e-14, + "y": 328 + }, + "8": { + "data": { + "action": "query device", + "actionType": "investigate", + "advanced": { + "customName": "query device", + "customNameId": 0, + "description": "Get information about the device to quarantine using its hostname or device id.", + "join": [], + "note": "Get information about the device to quarantine using its hostname or device id." + }, + "connector": "CrowdStrike OAuth API", + "connectorConfigs": [ + "crowdstrike_oauth_api" + ], + "connectorId": "ae971ba5-3117-444a-8ac5-6ce779f3a232", + "connectorVersion": "v1", + "functionId": 1, + "functionName": "query_device", + "id": "8", + "loop": { + "enabled": false, + "exitAfterUnit": "m", + "exitAfterValue": 10, + "exitConditionEnabled": false, + "exitLoopAfter": 2, + "pauseUnit": "m", + "pauseValue": 2 + }, + "parameters": { + "filter": "format_fql:formatted_data.*", + "limit": 50 + }, + "requiredParameters": [ + { + "data_type": "numeric", + "default": 50, + "field": "limit" + } + ], + "type": "action" + }, + "errors": {}, + "id": "8", + "type": "action", + "warnings": {}, + "x": 1.4210854715202004e-14, + "y": 504 + }, + "9": { + "data": { + "advanced": { + "customName": "format quarantine report", + "customNameId": 0, + "description": "Format a summary table with the information gathered from the playbook.", + "join": [], + "note": "Format a summary table with the information gathered from the playbook." + }, + "functionId": 3, + "functionName": "format_quarantine_report", + "id": "9", + "parameters": [ + "quarantine_device:action_result.data.*.id", + "quarantine_device:action_result.parameter.hostname", + "quarantine_device:action_result.data.*.path", + "quarantine_device:action_result.status" + ], + "template": "An attempt to isolate device(s) was performed via Splunk SOAR. The table below summarizes the information gathered.\n\n| Device ID | DNS Name | Device Path | Quarantine Status |\n| --- | --- | --- | --- |\n%%\n| {0} | {1} | {2} | {3} |\n%%", + "type": "format" + }, + "errors": {}, + "id": "9", + "type": "format", + "warnings": {}, + "x": 1.4210854715202004e-14, + "y": 856 + } + }, + "notes": "Inputs: device (CrowdStrike Device ID or Hostname)\nInteractions: CrowdStrike OAuth API\nActions: quarantine device\nOutputs: observables, markdown report" + }, + "input_spec": [ + { + "contains": [ + "host name" + ], + "description": "Device ID or hostname of the host to quarantine", + "name": "device" + } + ], + "output_spec": [ + { + "contains": [], + "datapaths": [ + "host_observables:custom_function:observable_array" + ], + "deduplicate": false, + "description": "An array of observable dictionaries", + "metadata": {}, + "name": "observable" + }, + { + "contains": [], + "datapaths": [ + "format_quarantine_report:formatted_data" + ], + "deduplicate": false, + "description": "A report of the devices that were isolated via Splunk SOAR.", + "metadata": {}, + "name": "markdown_report" + } + ], + "playbook_trigger": "artifact_created", + "playbook_type": "data", + "python_version": "3", + "schema": "5.0.15", + "version": "6.3.1.178" + }, + "create_time": "2025-04-08T14:45:48.561243+00:00", + "draft_mode": false, + "labels": [ + "*" + ], + "tags": [ + "CrowdStrike_OAuth_API", + "host name", + "containment", + "D3-NAM" + ] +} \ No newline at end of file diff --git a/playbooks/CrowdStrike_OAuth_API_Network_Isolation.png b/playbooks/CrowdStrike_OAuth_API_Network_Isolation.png new file mode 100644 index 0000000000000000000000000000000000000000..23833d36dedb47bb1812f946a6e8ad836a3aac7f GIT binary patch literal 26632 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�_5A|L! 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ΠhvCoLD3ex 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 njJ_4ZG0cgER_*dws?U%G=H", + "category": "Network Restore", + "coa": { + "data": { + "description": "Accepts a hostname or device id as input and tries to restore access for (unquarantines) the device in Crowdstrike. We then generate an observable report as well as a Markdown formatted report of the results. Both can be customized based on user preference.", + "edges": [ + { + "id": "port_0_to_port_2", + "sourceNode": "0", + "sourcePort": "0_out", + "targetNode": "2", + "targetPort": "2_in" + }, + { + "id": "port_5_to_port_1", + "sourceNode": "5", + "sourcePort": "5_out", + "targetNode": "1", + "targetPort": "1_in" + }, + { + "conditions": [ + { + "index": 0 + } + ], + "id": "port_2_to_port_7", + "sourceNode": "2", + "sourcePort": "2_out", + "targetNode": "7", + "targetPort": "7_in" + }, + { + "id": "port_7_to_port_8", + "sourceNode": "7", + "sourcePort": "7_out", + "targetNode": "8", + "targetPort": "8_in" + }, + { + "id": "port_9_to_port_5", + "sourceNode": "9", + "sourcePort": "9_out", + "targetNode": "5", + "targetPort": "5_in" + }, + { + "id": "port_8_to_port_10", + "sourceNode": "8", + "sourcePort": "8_out", + "targetNode": "10", + "targetPort": "10_in" + }, + { + "id": "port_10_to_port_9", + "sourceNode": "10", + "sourcePort": "10_out", + "targetNode": "9", + "targetPort": "9_in" + } + ], + "hash": "45988d55dd87c4bf4f04e4989535ed09243af0a4", + "nodes": { + "0": { + "data": { + "advanced": { + "join": [] + }, + "functionName": "on_start", + "id": "0", + "type": "start" + }, + "errors": {}, + "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": 1224 + }, + "10": { + "data": { + "action": "unquarantine device", + "actionType": "correct", + "advanced": { + "customName": "unquarantine device", + "customNameId": 0, + "description": "Remove network isolation from a device in CrowdStrike.", + "join": [], + "note": "Remove network isolation from a device in CrowdStrike." + }, + "connector": "CrowdStrike OAuth API", + "connectorConfigs": [ + "crowdstrike_oauth_api" + ], + "connectorId": "ae971ba5-3117-444a-8ac5-6ce779f3a232", + "connectorVersion": "v1", + "functionId": 1, + "functionName": "unquarantine_device", + "id": "10", + "loop": { + "enabled": false, + "exitAfterUnit": "m", + "exitAfterValue": 10, + "exitConditionEnabled": false, + "exitLoopAfter": 2, + "pauseUnit": "m", + "pauseValue": 2 + }, + "parameters": { + "device_id": "query_device:action_result.data.*.device_id", + "hostname": "" + }, + "requiredParameters": [], + "tab": "byAction", + "type": "action" + }, + "errors": {}, + "id": "10", + "type": "action", + "warnings": {}, + "x": 1.4210854715202004e-14, + "y": 680 + }, + "2": { + "data": { + "advanced": { + "customName": "input filter", + "customNameId": 0, + "description": "Determines if the provided inputs are present in the dataset.", + "join": [], + "note": "Determines if the provided inputs are present in the dataset." + }, + "conditions": [ + { + "comparisons": [ + { + "conditionIndex": 0, + "op": "!=", + "param": "playbook_input:device", + "value": "" + } + ], + "conditionIndex": 0, + "customName": "input device present", + "logic": "and" + } + ], + "functionId": 1, + "functionName": "input_filter", + "id": "2", + "type": "filter" + }, + "errors": {}, + "id": "2", + "type": "filter", + "warnings": {}, + "x": 60, + "y": 148 + }, + "5": { + "customCode": null, + "data": { + "advanced": { + "customName": "host observables", + "customNameId": 0, + "description": "Format a normalized output for each host", + "join": [], + "note": "Format a normalized output for each host." + }, + "functionId": 1, + "functionName": "host_observables", + "id": "5", + "inputParameters": [ + "unquarantine_device:action_result.parameter.device_id", + "query_device:action_result.data.*.hostname", + "unquarantine_device:action_result.status", + "unquarantine_device:action_result.message" + ], + "outputVariables": [ + "observable_array" + ], + "type": "code" + }, + "errors": {}, + "id": "5", + "type": "code", + "userCode": " \n host_observables__observable_array = []\n \n for device_id, hostname, status, status_message in zip(unquarantine_device_parameter_device_id, query_device_result_item_0, unquarantine_device_result_item_1, unquarantine_device_result_message):\n # Initialize the observable dictionary\n observable = {\n \"source\": \"Crowdstrike OAuth API\", \n \"type\": \"Endpoint\",\n \"activity_name\": \"Network Restore\",\n \"uid\": device_id,\n \"hostname\": hostname,\n \"status\": status,\n \"status_detail\": status_message,\n \"d3fend\": {\n \"d3f_tactic\": \"Restore\",\n \"d3f_technique\": \"D3-RNA\",\n \"version\": \"1.0.0\"\n }\n }\n\n # Add the observable to the array\n host_observables__observable_array.append(observable)\n \n # Debug output for verification\n phantom.debug(host_observables__observable_array)\n \n", + "warnings": {}, + "x": 1.4210854715202004e-14, + "y": 1040 + }, + "7": { + "data": { + "advanced": { + "customName": "format fql", + "customNameId": 0, + "description": "Format the FQL query to get the input device information using its ID or hostname.", + "join": [], + "note": "Format the FQL query to get the input device information using its ID or hostname." + }, + "functionId": 2, + "functionName": "format_fql", + "id": "7", + "parameters": [ + "playbook_input:device" + ], + "template": "%%\nhostname:['{0}'],device_id:['{0}']\n%%", + "type": "format" + }, + "errors": {}, + "id": "7", + "type": "format", + "warnings": {}, + "x": 1.4210854715202004e-14, + "y": 328 + }, + "8": { + "data": { + "action": "query device", + "actionType": "investigate", + "advanced": { + "customName": "query device", + "customNameId": 0, + "description": "Get information about the device to unquarantine using its hostname or device id.", + "join": [], + "note": "Get information about the device to unquarantine using its hostname or device id." + }, + "connector": "CrowdStrike OAuth API", + "connectorConfigs": [ + "crowdstrike_oauth_api" + ], + "connectorId": "ae971ba5-3117-444a-8ac5-6ce779f3a232", + "connectorVersion": "v1", + "functionId": 1, + "functionName": "query_device", + "id": "8", + "loop": { + "enabled": false, + "exitAfterUnit": "m", + "exitAfterValue": 10, + "exitConditionEnabled": false, + "exitLoopAfter": 2, + "pauseUnit": "m", + "pauseValue": 2 + }, + "parameters": { + "filter": "format_fql:formatted_data.*", + "limit": 50 + }, + "requiredParameters": [ + { + "data_type": "numeric", + "default": 50, + "field": "limit" + } + ], + "type": "action" + }, + "errors": {}, + "id": "8", + "type": "action", + "warnings": {}, + "x": 1.4210854715202004e-14, + "y": 504 + }, + "9": { + "data": { + "advanced": { + "customName": "format unquarantine report", + "customNameId": 0, + "description": "Format a summary table with the information gathered from the playbook.", + "join": [], + "note": "Format a summary table with the information gathered from the playbook." + }, + "functionId": 3, + "functionName": "format_unquarantine_report", + "id": "9", + "parameters": [ + "unquarantine_device:action_result.parameter.device_id", + "unquarantine_device:action_result.parameter.hostname", + "unquarantine_device:action_result.data.*.path", + "unquarantine_device:action_result.status" + ], + "template": "An attempt to remove device(s) from isolation was performed via Splunk SOAR. The table below summarizes the information gathered.\n\n| Device ID | DNS Name | Device Path | Unquarantine Status |\n| --- | --- | --- | --- |\n%%\n| {0} | {1} | {2} | {3} |\n%%", + "type": "format" + }, + "errors": {}, + "id": "9", + "type": "format", + "warnings": {}, + "x": 1.4210854715202004e-14, + "y": 856 + } + }, + "notes": "Inputs: device (CrowdStrike Device ID or Hostname)\nInteractions: CrowdStrike OAuth API\nActions: unquarantine device\nOutputs: observables, markdown report" + }, + "input_spec": [ + { + "contains": [], + "description": "Device ID or hostname of the host to quarantine", + "name": "device" + } + ], + "output_spec": [ + { + "contains": [], + "datapaths": [ + "host_observables:custom_function:observable_array" + ], + "deduplicate": false, + "description": "An array of observable dictionaries", + "metadata": {}, + "name": "observable" + }, + { + "contains": [], + "datapaths": [ + "format_unquarantine_report:formatted_data" + ], + "deduplicate": false, + "description": "A report of the devices that were isolated via Splunk SOAR.", + "metadata": {}, + "name": "markdown_report" + } + ], + "playbook_trigger": "artifact_created", + "playbook_type": "data", + "python_version": "3", + "schema": "5.0.15", + "version": "6.3.1.178" + }, + "create_time": "2025-04-08T13:59:03.337026+00:00", + "draft_mode": false, + "labels": [ + "*" + ], + "tags": [ + "CrowdStrike_OAuth_API", + "host name", + "restore", + "D3-RNA" + ] +} \ No newline at end of file diff --git a/playbooks/CrowdStrike_OAuth_API_Network_Restore.png b/playbooks/CrowdStrike_OAuth_API_Network_Restore.png new file mode 100644 index 0000000000000000000000000000000000000000..a51676b87f77a68a0fe17f65b5c39af7b920b301 GIT binary patch literal 26357 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 hy}6ezo49?0>EnVDbLSIl%5(V?%{{akre~AD9 literal 0 HcmV?d00001 diff --git a/playbooks/CrowdStrike_OAuth_API_Network_Restore.py b/playbooks/CrowdStrike_OAuth_API_Network_Restore.py new file mode 100644 index 0000000000..3d303d59d5 --- /dev/null +++ b/playbooks/CrowdStrike_OAuth_API_Network_Restore.py @@ -0,0 +1,265 @@ +""" +Accepts a hostname or device id as input and tries to restore access for (unquarantines) the device in Crowdstrike. We then generate an observable report as well as a Markdown formatted report of the results. Both can be customized based on user preference. +""" + + +import phantom.rules as phantom +import json +from datetime import datetime, timedelta + + +@phantom.playbook_block() +def on_start(container): + phantom.debug('on_start() called') + + # call 'input_filter' block + input_filter(container=container) + + return + +@phantom.playbook_block() +def input_filter(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, loop_state_json=None, **kwargs): + phantom.debug("input_filter() called") + + ################################################################################ + # Determines if the provided inputs are present in the dataset. + ################################################################################ + + # collect filtered artifact ids and results for 'if' condition 1 + matched_artifacts_1, matched_results_1 = phantom.condition( + container=container, + conditions=[ + ["playbook_input:device", "!=", ""] + ], + name="input_filter:condition_1", + delimiter=None) + + # call connected blocks if filtered artifacts or results + if matched_artifacts_1 or matched_results_1: + format_fql(action=action, success=success, container=container, results=results, handle=handle, filtered_artifacts=matched_artifacts_1, filtered_results=matched_results_1) + + return + + +@phantom.playbook_block() +def host_observables(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("host_observables() called") + + ################################################################################ + # Format a normalized output for each host + ################################################################################ + + unquarantine_device_result_data = phantom.collect2(container=container, datapath=["unquarantine_device:action_result.parameter.device_id","unquarantine_device:action_result.status","unquarantine_device:action_result.message"], action_results=results) + query_device_result_data = phantom.collect2(container=container, datapath=["query_device:action_result.data.*.hostname"], action_results=results) + + unquarantine_device_parameter_device_id = [item[0] for item in unquarantine_device_result_data] + unquarantine_device_result_item_1 = [item[1] for item in unquarantine_device_result_data] + unquarantine_device_result_message = [item[2] for item in unquarantine_device_result_data] + query_device_result_item_0 = [item[0] for item in query_device_result_data] + + host_observables__observable_array = None + + ################################################################################ + ## Custom Code Start + ################################################################################ + + host_observables__observable_array = [] + + for device_id, hostname, status, status_message in zip(unquarantine_device_parameter_device_id, query_device_result_item_0, unquarantine_device_result_item_1, unquarantine_device_result_message): + # Initialize the observable dictionary + observable = { + "source": "Crowdstrike OAuth API", + "type": "Endpoint", + "activity_name": "Network Restore", + "uid": device_id, + "hostname": hostname, + "status": status, + "status_detail": status_message, + "d3fend": { + "d3f_tactic": "Restore", + "d3f_technique": "D3-RNA", + "version": "1.0.0" + } + } + + # Add the observable to the array + host_observables__observable_array.append(observable) + + # Debug output for verification + phantom.debug(host_observables__observable_array) + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.save_run_data(key="host_observables:observable_array", value=json.dumps(host_observables__observable_array)) + + return + + +@phantom.playbook_block() +def format_fql(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("format_fql() called") + + ################################################################################ + # Format the FQL query to get the input device information using its ID or hostname. + ################################################################################ + + template = """%%\nhostname:['{0}'],device_id:['{0}']\n%%""" + + # parameter list for template variable replacement + parameters = [ + "playbook_input:device" + ] + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.format(container=container, template=template, parameters=parameters, name="format_fql") + + query_device(container=container) + + return + + +@phantom.playbook_block() +def query_device(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("query_device() called") + + # phantom.debug('Action: {0} {1}'.format(action['name'], ('SUCCEEDED' if success else 'FAILED'))) + + ################################################################################ + # Get information about the device to unquarantine using its hostname or device + # id. + ################################################################################ + + format_fql__as_list = phantom.get_format_data(name="format_fql__as_list") + + parameters = [] + + # build parameters list for 'query_device' call + for format_fql__item in format_fql__as_list: + parameters.append({ + "limit": 50, + "filter": format_fql__item, + }) + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.act("query device", parameters=parameters, name="query_device", assets=["crowdstrike_oauth_api"], callback=unquarantine_device) + + return + + +@phantom.playbook_block() +def format_unquarantine_report(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("format_unquarantine_report() called") + + ################################################################################ + # Format a summary table with the information gathered from the playbook. + ################################################################################ + + template = """An attempt to remove device(s) from isolation was performed via Splunk SOAR. The table below summarizes the information gathered.\n\n| Device ID | DNS Name | Device Path | Unquarantine Status |\n| --- | --- | --- | --- |\n%%\n| {0} | {1} | {2} | {3} |\n%%""" + + # parameter list for template variable replacement + parameters = [ + "unquarantine_device:action_result.parameter.device_id", + "unquarantine_device:action_result.parameter.hostname", + "unquarantine_device:action_result.data.*.path", + "unquarantine_device:action_result.status" + ] + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.format(container=container, template=template, parameters=parameters, name="format_unquarantine_report") + + host_observables(container=container) + + return + + +@phantom.playbook_block() +def unquarantine_device(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("unquarantine_device() called") + + # phantom.debug('Action: {0} {1}'.format(action['name'], ('SUCCEEDED' if success else 'FAILED'))) + + ################################################################################ + # Remove network isolation from a device in CrowdStrike. + ################################################################################ + + query_device_result_data = phantom.collect2(container=container, datapath=["query_device:action_result.data.*.device_id","query_device:action_result.parameter.context.artifact_id"], action_results=results) + + parameters = [] + + # build parameters list for 'unquarantine_device' call + for query_device_result_item in query_device_result_data: + parameters.append({ + "hostname": "", + "device_id": query_device_result_item[0], + "context": {'artifact_id': query_device_result_item[1]}, + }) + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.act("unquarantine device", parameters=parameters, name="unquarantine_device", assets=["crowdstrike_oauth_api"], callback=format_unquarantine_report) + + return + + +@phantom.playbook_block() +def on_finish(container, summary): + phantom.debug("on_finish() called") + + format_unquarantine_report = phantom.get_format_data(name="format_unquarantine_report") + host_observables__observable_array = json.loads(_ if (_ := phantom.get_run_data(key="host_observables:observable_array")) != "" else "null") # pylint: disable=used-before-assignment + + output = { + "observable": host_observables__observable_array, + "markdown_report": format_unquarantine_report, + } + + ################################################################################ + ## 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/CrowdStrike_OAuth_API_Network_Restore.yml b/playbooks/CrowdStrike_OAuth_API_Network_Restore.yml new file mode 100644 index 0000000000..d14bc4c4f9 --- /dev/null +++ b/playbooks/CrowdStrike_OAuth_API_Network_Restore.yml @@ -0,0 +1,30 @@ +name: CrowdStrike OAuth API Network Restore +id: +version: 1 +date: '2025-06-09' +author: Christian Cloutier, Splunk +type: Response +description: "Accepts a hostname or device id as input and tries to restore access for (unquarantines) the device in Crowdstrike. We then generate an observable report as well as a Markdown formatted report of the results. Both can be customized based on user preference." +playbook: CrowdStrike_OAuth_API_Network_Restore +how_to_implement: This input playbook requires the CrowdStrike OAuth API connector to be configured. It is designed to work with an endpoint hostname or device id and remove the corresponding endpoint from containment for use in automation playbooks. +references: [] +app_list: + - CrowdStrike OAuth API +tags: + platform_tags: + - "host name" + - "device id" + - "Network Restore" + - "D3-RNA" + - "CrowdStrike_OAuth_API" + playbook_type: Input + vpe_type: Modern + playbook_fields: [device] + product: + - Splunk SOAR + use_cases: + - Response + - Malware + - Endpoint + defend_technique_id: + - D3-RNA diff --git a/playbooks/CrowdStrike_OAuth_API_Process_Termination.json b/playbooks/CrowdStrike_OAuth_API_Process_Termination.json new file mode 100644 index 0000000000..762a7ce35d --- /dev/null +++ b/playbooks/CrowdStrike_OAuth_API_Process_Termination.json @@ -0,0 +1,497 @@ +{ + "blockly": false, + "blockly_xml": "", + "category": "Process Termination", + "coa": { + "data": { + "description": "Accepts a hostname or device id as well as one or more process IDs as input and terminates those process(es) on a device in Crowdstrike. We then generate an observable report as well as a Markdown formatted report. Both reports can be customized based on user preference. Note that the Markdown report can report a status of \"success\" even when a particular PID is not actually killed. Rely on the observable output if you need to reliably check that.", + "edges": [ + { + "id": "port_0_to_port_2", + "sourceNode": "0", + "sourcePort": "0_out", + "targetNode": "2", + "targetPort": "2_in" + }, + { + "id": "port_5_to_port_1", + "sourceNode": "5", + "sourcePort": "5_out", + "targetNode": "1", + "targetPort": "1_in" + }, + { + "conditions": [ + { + "index": 0 + } + ], + "id": "port_2_to_port_7", + "sourceNode": "2", + "sourcePort": "2_out", + "targetNode": "7", + "targetPort": "7_in" + }, + { + "id": "port_7_to_port_8", + "sourceNode": "7", + "sourcePort": "7_out", + "targetNode": "8", + "targetPort": "8_in" + }, + { + "id": "port_9_to_port_5", + "sourceNode": "9", + "sourcePort": "9_out", + "targetNode": "5", + "targetPort": "5_in" + }, + { + "id": "port_8_to_port_14", + "sourceNode": "8", + "sourcePort": "8_out", + "targetNode": "14", + "targetPort": "14_in" + }, + { + "id": "port_14_to_port_15", + "sourceNode": "14", + "sourcePort": "14_out", + "targetNode": "15", + "targetPort": "15_in" + }, + { + "id": "port_16_to_port_9", + "sourceNode": "16", + "sourcePort": "16_out", + "targetNode": "9", + "targetPort": "9_in" + }, + { + "id": "port_15_to_port_16", + "sourceNode": "15", + "sourcePort": "15_out", + "targetNode": "16", + "targetPort": "16_in" + } + ], + "hash": "efab1b66882b7dade8150195f014248b9575d250", + "nodes": { + "0": { + "data": { + "advanced": { + "join": [] + }, + "functionName": "on_start", + "id": "0", + "type": "start" + }, + "errors": {}, + "id": "0", + "type": "start", + "warnings": {}, + "x": 20, + "y": 0 + }, + "1": { + "data": { + "advanced": { + "join": [] + }, + "functionName": "on_finish", + "id": "1", + "type": "end" + }, + "errors": {}, + "id": "1", + "type": "end", + "warnings": {}, + "x": 20, + "y": 1576 + }, + "14": { + "data": { + "action": "create session", + "actionType": "generic", + "advanced": { + "customName": "create session", + "customNameId": 0, + "description": "Create an RTR session on the selected Crowdstrike endpoint.", + "join": [], + "note": "Create an RTR session on the selected Crowdstrike endpoint." + }, + "connector": "CrowdStrike OAuth API", + "connectorConfigs": [ + "crowdstrike_oauth_api" + ], + "connectorId": "ae971ba5-3117-444a-8ac5-6ce779f3a232", + "connectorVersion": "v1", + "functionId": 1, + "functionName": "create_session", + "id": "14", + "loop": { + "enabled": false, + "exitAfterUnit": "m", + "exitAfterValue": 10, + "exitConditionEnabled": false, + "exitLoopAfter": 2, + "pauseUnit": "m", + "pauseValue": 2 + }, + "parameters": { + "device_id": "query_device:action_result.data.*.device_id" + }, + "requiredParameters": [ + { + "data_type": "string", + "field": "device_id" + } + ], + "type": "action" + }, + "errors": {}, + "id": "14", + "type": "action", + "warnings": {}, + "x": 0, + "y": 680 + }, + "15": { + "data": { + "action": "run admin command", + "actionType": "generic", + "advanced": { + "customName": "run admin command", + "customNameId": 0, + "description": "Runs the kill command against a Crowdstrike endpoint via an RTR session to terminate the selected process PID.", + "join": [], + "note": "Runs the kill command against a Crowdstrike endpoint via an RTR session to terminate the selected process PID." + }, + "connector": "CrowdStrike OAuth API", + "connectorConfigs": [ + "crowdstrike_oauth_api" + ], + "connectorId": "ae971ba5-3117-444a-8ac5-6ce779f3a232", + "connectorVersion": "v1", + "functionId": 1, + "functionName": "run_admin_command", + "id": "15", + "loop": { + "enabled": false, + "exitAfterUnit": "m", + "exitAfterValue": 10, + "exitConditionEnabled": false, + "exitLoopAfter": 2, + "pauseUnit": "m", + "pauseValue": 2 + }, + "parameters": { + "command": "kill", + "data": "playbook_input:pid", + "device_id": "query_device:action_result.data.*.device_id", + "session_id": "create_session:action_result.data.*.resources.*.session_id" + }, + "requiredParameters": [ + { + "data_type": "string", + "field": "command" + }, + { + "data_type": "string", + "field": "device_id" + }, + { + "data_type": "string", + "field": "session_id" + } + ], + "tab": "byAction", + "type": "action" + }, + "errors": {}, + "id": "15", + "type": "action", + "warnings": {}, + "x": 0, + "y": 856 + }, + "16": { + "data": { + "action": "delete session", + "actionType": "generic", + "advanced": { + "customName": "delete session", + "customNameId": 0, + "description": "Deletes an RTR session on the selected Crowdstrike endpoint.", + "join": [], + "note": "Deletes an RTR session on the selected Crowdstrike endpoint." + }, + "connector": "CrowdStrike OAuth API", + "connectorConfigs": [ + "crowdstrike_oauth_api" + ], + "connectorId": "ae971ba5-3117-444a-8ac5-6ce779f3a232", + "connectorVersion": "v1", + "functionId": 1, + "functionName": "delete_session", + "id": "16", + "loop": { + "enabled": false, + "exitAfterUnit": "m", + "exitAfterValue": 10, + "exitConditionEnabled": false, + "exitLoopAfter": 2, + "pauseUnit": "m", + "pauseValue": 2 + }, + "parameters": { + "session_id": "create_session:action_result.summary.session_id" + }, + "requiredParameters": [ + { + "data_type": "string", + "field": "session_id" + } + ], + "type": "action" + }, + "errors": {}, + "id": "16", + "type": "action", + "warnings": {}, + "x": 0, + "y": 1032 + }, + "2": { + "data": { + "advanced": { + "customName": "input filter", + "customNameId": 0, + "description": "Determines if the provided inputs are present in the dataset.", + "join": [], + "note": "Determines if the provided inputs are present in the dataset." + }, + "conditions": [ + { + "comparisons": [ + { + "conditionIndex": 0, + "op": "!=", + "param": "playbook_input:device", + "value": "" + }, + { + "op": "!=", + "param": "playbook_input:pid", + "value": "" + } + ], + "conditionIndex": 0, + "customName": "input device present", + "logic": "and" + } + ], + "functionId": 1, + "functionName": "input_filter", + "id": "2", + "type": "filter" + }, + "errors": {}, + "id": "2", + "type": "filter", + "warnings": {}, + "x": 60, + "y": 148 + }, + "5": { + "customCode": null, + "data": { + "advanced": { + "customName": "host observables", + "customNameId": 0, + "description": "Format a normalized output for each host", + "join": [], + "note": "Format a normalized output for each host." + }, + "functionId": 1, + "functionName": "host_observables", + "id": "5", + "inputParameters": [ + "query_device:action_result.data.*.device_id", + "query_device:action_result.data.*.hostname", + "run_admin_command:action_result.parameter.data", + "run_admin_command:action_result.data" + ], + "outputVariables": [ + "observable_array" + ], + "type": "code" + }, + "errors": {}, + "id": "5", + "type": "code", + "userCode": " \n host_observables__observable_array = []\n i = 0\n \n for system in run_admin_command_result_item_1:\n # Handle case where a single host is passed with multiple PIDs to kill.\n device_id = query_device_result_item_0[i] if i < len(query_device_result_item_0) else query_device_result_item_0[0]\n hostname = query_device_result_item_1[i] if i < len(query_device_result_item_1) else query_device_result_item_1[0]\n\n # Initialize the observable dictionary\n observable = {\n \"source\": \"Crowdstrike OAuth API\",\n \"type\": \"Endpoint\",\n \"activity_name\": \"Process Termination\",\n \"uid\": device_id,\n \"hostname\": hostname, \n \"processes\": [],\n \"d3fend\": {\n \"d3f_tactic\": \"Evict\",\n \"d3f_technique\": \"D3-PT\",\n \"version\": \"1.0.0\"\n }\n }\n\n for process in system[0][\"resources\"]:\n pid = run_admin_command_parameter_data[i]\n status = \"success\" if process[\"stdout\"] and not process[\"stderr\"] else \"failed\"\n message = process[\"stdout\"].strip() if process[\"stdout\"] else process[\"stderr\"].rstrip()\n observable[\"processes\"].append(\n {\n \"pid\": pid,\n \"status\": status,\n \"message\": message\n }\n )\n \n # Add the observable to the array\n host_observables__observable_array.append(observable)\n i+=1\n \n # Debug output for verification\n #phantom.debug(host_observables__observable_array)\n \n", + "warnings": {}, + "x": 0, + "y": 1400 + }, + "7": { + "data": { + "advanced": { + "customName": "format fql", + "customNameId": 0, + "description": "Format the FQL query to get the input device information using its ID or hostname.", + "join": [], + "note": "Format the FQL query to get the input device information using its ID or hostname." + }, + "functionId": 2, + "functionName": "format_fql", + "id": "7", + "parameters": [ + "playbook_input:device" + ], + "template": "%%\nhostname:['{0}'],device_id:['{0}']\n%%", + "type": "format" + }, + "errors": {}, + "id": "7", + "type": "format", + "warnings": {}, + "x": 0, + "y": 340 + }, + "8": { + "data": { + "action": "query device", + "actionType": "investigate", + "advanced": { + "customName": "query device", + "customNameId": 0, + "description": "Get information about the device where the process should be terminated using its hostname or device id.", + "join": [], + "note": "Get information about the device where the process should be terminated using its hostname or device id." + }, + "connector": "CrowdStrike OAuth API", + "connectorConfigs": [ + "crowdstrike_oauth_api" + ], + "connectorId": "ae971ba5-3117-444a-8ac5-6ce779f3a232", + "connectorVersion": "v1", + "functionId": 1, + "functionName": "query_device", + "id": "8", + "loop": { + "enabled": false, + "exitAfterUnit": "m", + "exitAfterValue": 10, + "exitConditionEnabled": false, + "exitLoopAfter": 2, + "pauseUnit": "m", + "pauseValue": 2 + }, + "parameters": { + "filter": "format_fql:formatted_data.*", + "limit": 50 + }, + "requiredParameters": [ + { + "data_type": "numeric", + "default": 50, + "field": "limit" + } + ], + "type": "action" + }, + "errors": {}, + "id": "8", + "type": "action", + "warnings": {}, + "x": 0, + "y": 504 + }, + "9": { + "data": { + "advanced": { + "customName": "format process termination report", + "customNameId": 0, + "description": "Format a summary table with the information gathered from the playbook.", + "join": [], + "note": "Format a summary table with the information gathered from the playbook." + }, + "functionId": 3, + "functionName": "format_process_termination_report", + "id": "9", + "parameters": [ + "query_device:action_result.data.*.device_id", + "query_device:action_result.data.*.hostname", + "filtered-data:input_filter:condition_1:playbook_input:pid", + "run_admin_command:action_result.status", + "run_admin_command:action_result.data.*.resources.*.stdout", + "run_admin_command:action_result.data.*.resources.*.stderr" + ], + "template": "Devices processes were terminated via Splunk SOAR. The table below summarizes the information gathered.\n\n%%\n| Device ID | DNS Name | Process ID | Request Status |\n| --- | --- | --- | --- |\n| {0} | {1} | {2} | {3} |\n\n```\n{4}{5}\n```\n%%\n", + "type": "format" + }, + "errors": {}, + "id": "9", + "type": "format", + "warnings": {}, + "x": 0, + "y": 1208 + } + }, + "notes": "Inputs: device (CrowdStrike Device ID or Hostname)\npid (one or more process IDs to terminate)\nInteractions: CrowdStrike OAuth API\nActions: run admin command (kill)\nOutputs: observables, markdown report" + }, + "input_spec": [ + { + "contains": [ + "host name" + ], + "description": "Device ID or hostname of the host to quarantine", + "name": "device" + }, + { + "contains": [], + "description": "Process ID of the process to terminate", + "name": "pid" + } + ], + "output_spec": [ + { + "contains": [], + "datapaths": [ + "host_observables:custom_function:observable_array" + ], + "deduplicate": false, + "description": "An array of observable dictionaries", + "metadata": {}, + "name": "observable" + }, + { + "contains": [], + "datapaths": [ + "format_process_termination_report:formatted_data" + ], + "deduplicate": false, + "description": "A report of the devices that were isolated via Splunk SOAR.", + "metadata": {}, + "name": "markdown_report" + } + ], + "playbook_trigger": "artifact_created", + "playbook_type": "data", + "python_version": "3", + "schema": "5.0.15", + "version": "6.3.1.178" + }, + "create_time": "2025-04-09T14:46:26.643171+00:00", + "draft_mode": false, + "labels": [ + "*" + ], + "tags": [ + "CrowdStrike_OAuth_API", + "host name", + "D3-PT" + ] +} \ No newline at end of file diff --git a/playbooks/CrowdStrike_OAuth_API_Process_Termination.png b/playbooks/CrowdStrike_OAuth_API_Process_Termination.png new file mode 100644 index 0000000000000000000000000000000000000000..4d9a556ab22e62a55a360aae7b378c8db225dbdc GIT binary patch literal 43961 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 literal 0 HcmV?d00001 diff --git a/playbooks/CrowdStrike_OAuth_API_Process_Termination.py b/playbooks/CrowdStrike_OAuth_API_Process_Termination.py new file mode 100644 index 0000000000..e5819616de --- /dev/null +++ b/playbooks/CrowdStrike_OAuth_API_Process_Termination.py @@ -0,0 +1,368 @@ +""" +Accepts a hostname or device id as well as one or more process IDs as input and terminates those process(es) on a device in Crowdstrike. We then generate an observable report as well as a Markdown formatted report. Both reports can be customized based on user preference. Note that the Markdown report can report a status of "success" even when a particular PID is not actually killed. Rely on the observable output if you need to reliably check that. +""" + + +import phantom.rules as phantom +import json +from datetime import datetime, timedelta + + +@phantom.playbook_block() +def on_start(container): + phantom.debug('on_start() called') + + # call 'input_filter' block + input_filter(container=container) + + return + +@phantom.playbook_block() +def input_filter(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, loop_state_json=None, **kwargs): + phantom.debug("input_filter() called") + + ################################################################################ + # Determines if the provided inputs are present in the dataset. + ################################################################################ + + # collect filtered artifact ids and results for 'if' condition 1 + matched_artifacts_1, matched_results_1 = phantom.condition( + container=container, + logical_operator="and", + conditions=[ + ["playbook_input:device", "!=", ""], + ["playbook_input:pid", "!=", ""] + ], + name="input_filter:condition_1", + delimiter=None) + + # call connected blocks if filtered artifacts or results + if matched_artifacts_1 or matched_results_1: + format_fql(action=action, success=success, container=container, results=results, handle=handle, filtered_artifacts=matched_artifacts_1, filtered_results=matched_results_1) + + return + + +@phantom.playbook_block() +def host_observables(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("host_observables() called") + + ################################################################################ + # Format a normalized output for each host + ################################################################################ + + query_device_result_data = phantom.collect2(container=container, datapath=["query_device:action_result.data.*.device_id","query_device:action_result.data.*.hostname"], action_results=results) + run_admin_command_result_data = phantom.collect2(container=container, datapath=["run_admin_command:action_result.parameter.data","run_admin_command:action_result.data"], action_results=results) + + query_device_result_item_0 = [item[0] for item in query_device_result_data] + query_device_result_item_1 = [item[1] for item in query_device_result_data] + run_admin_command_parameter_data = [item[0] for item in run_admin_command_result_data] + run_admin_command_result_item_1 = [item[1] for item in run_admin_command_result_data] + + host_observables__observable_array = None + + ################################################################################ + ## Custom Code Start + ################################################################################ + + host_observables__observable_array = [] + i = 0 + + for system in run_admin_command_result_item_1: + # Handle case where a single host is passed with multiple PIDs to kill. + device_id = query_device_result_item_0[i] if i < len(query_device_result_item_0) else query_device_result_item_0[0] + hostname = query_device_result_item_1[i] if i < len(query_device_result_item_1) else query_device_result_item_1[0] + + # Initialize the observable dictionary + observable = { + "source": "Crowdstrike OAuth API", + "type": "Endpoint", + "activity_name": "Process Termination", + "uid": device_id, + "hostname": hostname, + "processes": [], + "d3fend": { + "d3f_tactic": "Evict", + "d3f_technique": "D3-PT", + "version": "1.0.0" + } + } + + for process in system[0]["resources"]: + pid = run_admin_command_parameter_data[i] + status = "success" if process["stdout"] and not process["stderr"] else "failed" + message = process["stdout"].strip() if process["stdout"] else process["stderr"].rstrip() + observable["processes"].append( + { + "pid": pid, + "status": status, + "message": message + } + ) + + # Add the observable to the array + host_observables__observable_array.append(observable) + i+=1 + + # Debug output for verification + #phantom.debug(host_observables__observable_array) + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.save_run_data(key="host_observables:observable_array", value=json.dumps(host_observables__observable_array)) + + return + + +@phantom.playbook_block() +def format_fql(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("format_fql() called") + + ################################################################################ + # Format the FQL query to get the input device information using its ID or hostname. + ################################################################################ + + template = """%%\nhostname:['{0}'],device_id:['{0}']\n%%""" + + # parameter list for template variable replacement + parameters = [ + "playbook_input:device" + ] + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.format(container=container, template=template, parameters=parameters, name="format_fql") + + query_device(container=container) + + return + + +@phantom.playbook_block() +def query_device(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("query_device() called") + + # phantom.debug('Action: {0} {1}'.format(action['name'], ('SUCCEEDED' if success else 'FAILED'))) + + ################################################################################ + # Get information about the device where the process should be terminated using + # its hostname or device id. + ################################################################################ + + format_fql__as_list = phantom.get_format_data(name="format_fql__as_list") + + parameters = [] + + # build parameters list for 'query_device' call + for format_fql__item in format_fql__as_list: + parameters.append({ + "limit": 50, + "filter": format_fql__item, + }) + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.act("query device", parameters=parameters, name="query_device", assets=["crowdstrike_oauth_api"], callback=create_session) + + return + + +@phantom.playbook_block() +def format_process_termination_report(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("format_process_termination_report() called") + + ################################################################################ + # Format a summary table with the information gathered from the playbook. + ################################################################################ + + template = """Devices processes were terminated via Splunk SOAR. The table below summarizes the information gathered.\n\n%%\n| Device ID | DNS Name | Process ID | Request Status |\n| --- | --- | --- | --- |\n| {0} | {1} | {2} | {3} |\n\n```\n{4}{5}\n```\n%%\n""" + + # parameter list for template variable replacement + parameters = [ + "query_device:action_result.data.*.device_id", + "query_device:action_result.data.*.hostname", + "filtered-data:input_filter:condition_1:playbook_input:pid", + "run_admin_command:action_result.status", + "run_admin_command:action_result.data.*.resources.*.stdout", + "run_admin_command:action_result.data.*.resources.*.stderr" + ] + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.format(container=container, template=template, parameters=parameters, name="format_process_termination_report") + + host_observables(container=container) + + return + + +@phantom.playbook_block() +def create_session(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_session() called") + + # phantom.debug('Action: {0} {1}'.format(action['name'], ('SUCCEEDED' if success else 'FAILED'))) + + ################################################################################ + # Create an RTR session on the selected Crowdstrike endpoint. + ################################################################################ + + query_device_result_data = phantom.collect2(container=container, datapath=["query_device:action_result.data.*.device_id","query_device:action_result.parameter.context.artifact_id"], action_results=results) + + parameters = [] + + # build parameters list for 'create_session' call + for query_device_result_item in query_device_result_data: + if query_device_result_item[0] is not None: + parameters.append({ + "device_id": query_device_result_item[0], + "context": {'artifact_id': query_device_result_item[1]}, + }) + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.act("create session", parameters=parameters, name="create_session", assets=["crowdstrike_oauth_api"], callback=run_admin_command) + + return + + +@phantom.playbook_block() +def run_admin_command(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("run_admin_command() called") + + # phantom.debug('Action: {0} {1}'.format(action['name'], ('SUCCEEDED' if success else 'FAILED'))) + + ################################################################################ + # Runs the kill command against a Crowdstrike endpoint via an RTR session to terminate + # the selected process PID. + ################################################################################ + + playbook_input_pid = phantom.collect2(container=container, datapath=["playbook_input:pid"]) + query_device_result_data = phantom.collect2(container=container, datapath=["query_device:action_result.data.*.device_id","query_device:action_result.parameter.context.artifact_id"], action_results=results) + create_session_result_data = phantom.collect2(container=container, datapath=["create_session:action_result.data.*.resources.*.session_id","create_session:action_result.parameter.context.artifact_id"], action_results=results) + + parameters = [] + + # build parameters list for 'run_admin_command' call + for playbook_input_pid_item in playbook_input_pid: + for query_device_result_item in query_device_result_data: + for create_session_result_item in create_session_result_data: + if query_device_result_item[0] is not None and create_session_result_item[0] is not None: + parameters.append({ + "data": playbook_input_pid_item[0], + "command": "kill", + "device_id": query_device_result_item[0], + "session_id": create_session_result_item[0], + "context": {'artifact_id': create_session_result_item[1]}, + }) + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.act("run admin command", parameters=parameters, name="run_admin_command", assets=["crowdstrike_oauth_api"], callback=delete_session) + + return + + +@phantom.playbook_block() +def delete_session(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("delete_session() called") + + # phantom.debug('Action: {0} {1}'.format(action['name'], ('SUCCEEDED' if success else 'FAILED'))) + + ################################################################################ + # Deletes an RTR session on the selected Crowdstrike endpoint. + ################################################################################ + + create_session_result_data = phantom.collect2(container=container, datapath=["create_session:action_result.summary.session_id","create_session:action_result.parameter.context.artifact_id"], action_results=results) + + parameters = [] + + # build parameters list for 'delete_session' call + for create_session_result_item in create_session_result_data: + if create_session_result_item[0] is not None: + parameters.append({ + "session_id": create_session_result_item[0], + "context": {'artifact_id': create_session_result_item[1]}, + }) + + ################################################################################ + ## Custom Code Start + ################################################################################ + + # Write your custom code here... + + ################################################################################ + ## Custom Code End + ################################################################################ + + phantom.act("delete session", parameters=parameters, name="delete_session", assets=["crowdstrike_oauth_api"], callback=format_process_termination_report) + + return + + +@phantom.playbook_block() +def on_finish(container, summary): + phantom.debug("on_finish() called") + + format_process_termination_report = phantom.get_format_data(name="format_process_termination_report") + host_observables__observable_array = json.loads(_ if (_ := phantom.get_run_data(key="host_observables:observable_array")) != "" else "null") # pylint: disable=used-before-assignment + + output = { + "observable": host_observables__observable_array, + "markdown_report": format_process_termination_report, + } + + ################################################################################ + ## 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/CrowdStrike_OAuth_API_Process_Termination.yml b/playbooks/CrowdStrike_OAuth_API_Process_Termination.yml new file mode 100644 index 0000000000..d6af3acc15 --- /dev/null +++ b/playbooks/CrowdStrike_OAuth_API_Process_Termination.yml @@ -0,0 +1,31 @@ +name: CrowdStrike OAuth API Process Termination +id: +version: 1 +date: '2025-06-09' +author: Christian Cloutier, Splunk +type: Response +description: "Accepts a hostname or device id as well as one or more process IDs as input and terminates those process(es) on a device in CrowdStrike. We then generate an observable report as well as a Markdown formatted report. Both reports can be customized based on user preference. Note that the Markdown report can report a status of "success" even when a particular PID is not actually killed. Rely on the observable output if you need to reliably check that." +playbook: CrowdStrike_OAuth_API_Process_Termination +how_to_implement: This input playbook requires the CrowdStrike OAuth API connector to be configured. It is designed to work with an endpoint hostname or device id and terminate the corresponding process on the endpoint for use in automation playbooks. +references: [] +app_list: + - CrowdStrike OAuth API +tags: + platform_tags: + - "host name" + - "device id" + - "pid" + - "Process Termination" + - "D3-PT" + - "CrowdStrike_OAuth_API" + playbook_type: Input + vpe_type: Modern + playbook_fields: [device,pid] + product: + - Splunk SOAR + use_cases: + - Response + - Malware + - Endpoint + defend_technique_id: + - D3-PT From 8c7650575d608bf2c552225f9622a665d0c38737 Mon Sep 17 00:00:00 2001 From: Christian Cloutier Date: Fri, 20 Jun 2025 14:22:43 -0400 Subject: [PATCH 2/2] Add missing GUIDs --- playbooks/CrowdStrike_OAuth_API_Endpoint_Analysis.yml | 2 +- playbooks/CrowdStrike_OAuth_API_Executable_Denylisting.yml | 2 +- playbooks/CrowdStrike_OAuth_API_File_Collection.yml | 2 +- playbooks/CrowdStrike_OAuth_API_File_Eviction.yml | 2 +- playbooks/CrowdStrike_OAuth_API_File_Restore.yml | 2 +- playbooks/CrowdStrike_OAuth_API_Get_Device_Info.yml | 2 +- playbooks/CrowdStrike_OAuth_API_Network_Isolation.yml | 2 +- playbooks/CrowdStrike_OAuth_API_Network_Restore.yml | 2 +- playbooks/CrowdStrike_OAuth_API_Process_Termination.yml | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/playbooks/CrowdStrike_OAuth_API_Endpoint_Analysis.yml b/playbooks/CrowdStrike_OAuth_API_Endpoint_Analysis.yml index 30c668dfe0..c1adce850f 100644 --- a/playbooks/CrowdStrike_OAuth_API_Endpoint_Analysis.yml +++ b/playbooks/CrowdStrike_OAuth_API_Endpoint_Analysis.yml @@ -1,5 +1,5 @@ name: CrowdStrike OAuth API Endpoint Analysis -id: +id: 1356baeb-9ad4-4d2c-b6ae-55dda6bd9db5 version: 1 date: '2025-06-09' author: Christian Cloutier, Splunk diff --git a/playbooks/CrowdStrike_OAuth_API_Executable_Denylisting.yml b/playbooks/CrowdStrike_OAuth_API_Executable_Denylisting.yml index e08cd3105d..0a985541e8 100644 --- a/playbooks/CrowdStrike_OAuth_API_Executable_Denylisting.yml +++ b/playbooks/CrowdStrike_OAuth_API_Executable_Denylisting.yml @@ -1,5 +1,5 @@ name: CrowdStrike OAuth API Executable Denylisting -id: +id: 9d87f2d5-2578-4f39-9eee-c1a88af658bb version: 1 date: '2025-06-09' author: Christian Cloutier, Splunk diff --git a/playbooks/CrowdStrike_OAuth_API_File_Collection.yml b/playbooks/CrowdStrike_OAuth_API_File_Collection.yml index 0652a64eb1..4094ab98e0 100644 --- a/playbooks/CrowdStrike_OAuth_API_File_Collection.yml +++ b/playbooks/CrowdStrike_OAuth_API_File_Collection.yml @@ -1,5 +1,5 @@ name: CrowdStrike OAuth API File Collection -id: +id: 2296ce3f-171f-467f-8025-f046f5d59133 version: 1 date: '2025-06-09' author: Christian Cloutier, Splunk diff --git a/playbooks/CrowdStrike_OAuth_API_File_Eviction.yml b/playbooks/CrowdStrike_OAuth_API_File_Eviction.yml index dad80502f8..2c8456a6d1 100644 --- a/playbooks/CrowdStrike_OAuth_API_File_Eviction.yml +++ b/playbooks/CrowdStrike_OAuth_API_File_Eviction.yml @@ -1,5 +1,5 @@ name: CrowdStrike OAuth API File Eviction -id: +id: 4750935c-0105-416f-aca4-0d7a4207666d version: 1 date: '2025-06-09' author: Christian Cloutier, Splunk diff --git a/playbooks/CrowdStrike_OAuth_API_File_Restore.yml b/playbooks/CrowdStrike_OAuth_API_File_Restore.yml index 90dd513591..f6d8db71a8 100644 --- a/playbooks/CrowdStrike_OAuth_API_File_Restore.yml +++ b/playbooks/CrowdStrike_OAuth_API_File_Restore.yml @@ -1,5 +1,5 @@ name: CrowdStrike OAuth API File Restore -id: +id: 8afd7816-bab2-41f6-a848-395115c46d1c version: 1 date: '2025-06-09' author: Christian Cloutier, Splunk diff --git a/playbooks/CrowdStrike_OAuth_API_Get_Device_Info.yml b/playbooks/CrowdStrike_OAuth_API_Get_Device_Info.yml index 7579eb959d..ba182e9291 100644 --- a/playbooks/CrowdStrike_OAuth_API_Get_Device_Info.yml +++ b/playbooks/CrowdStrike_OAuth_API_Get_Device_Info.yml @@ -1,5 +1,5 @@ name: CrowdStrike OAuth API File Restore -id: +id: d97f8a59-fbb0-40db-a4ca-a681000c3b6d version: 1 date: '2025-06-09' author: Christian Cloutier, Splunk diff --git a/playbooks/CrowdStrike_OAuth_API_Network_Isolation.yml b/playbooks/CrowdStrike_OAuth_API_Network_Isolation.yml index 8d6cd9bee3..198489ecda 100644 --- a/playbooks/CrowdStrike_OAuth_API_Network_Isolation.yml +++ b/playbooks/CrowdStrike_OAuth_API_Network_Isolation.yml @@ -1,5 +1,5 @@ name: CrowdStrike OAuth API Network Isolation -id: +id: dd7ef79b-2bfd-4844-821d-a9e8db570d0a version: 1 date: '2025-06-09' author: Christian Cloutier, Splunk diff --git a/playbooks/CrowdStrike_OAuth_API_Network_Restore.yml b/playbooks/CrowdStrike_OAuth_API_Network_Restore.yml index d14bc4c4f9..0d5a0d1da5 100644 --- a/playbooks/CrowdStrike_OAuth_API_Network_Restore.yml +++ b/playbooks/CrowdStrike_OAuth_API_Network_Restore.yml @@ -1,5 +1,5 @@ name: CrowdStrike OAuth API Network Restore -id: +id: 92ddd3da-02ac-40d1-84fb-1bc8c9fdc1da version: 1 date: '2025-06-09' author: Christian Cloutier, Splunk diff --git a/playbooks/CrowdStrike_OAuth_API_Process_Termination.yml b/playbooks/CrowdStrike_OAuth_API_Process_Termination.yml index d6af3acc15..c9f78aa193 100644 --- a/playbooks/CrowdStrike_OAuth_API_Process_Termination.yml +++ b/playbooks/CrowdStrike_OAuth_API_Process_Termination.yml @@ -1,5 +1,5 @@ name: CrowdStrike OAuth API Process Termination -id: +id: 4a64ee9b-09fa-42fa-8b43-0de75908ff08 version: 1 date: '2025-06-09' author: Christian Cloutier, Splunk