Branch was auto-updated.
@@ -91,7 +91,7 @@
|
||||
"targetPort": "1_in"
|
||||
}
|
||||
],
|
||||
"hash": "b2bce4ea7e2dbaefba3a2b8403baf8d50fe9c488",
|
||||
"hash": "57ccd374d540a47cc227de541a38f31c0f455394",
|
||||
"nodes": {
|
||||
"0": {
|
||||
"data": {
|
||||
@@ -106,7 +106,7 @@
|
||||
"id": "0",
|
||||
"type": "start",
|
||||
"x": 530,
|
||||
"y": 0
|
||||
"y": -3.943512183468556e-13
|
||||
},
|
||||
"1": {
|
||||
"data": {
|
||||
@@ -123,7 +123,7 @@
|
||||
"type": "end",
|
||||
"userCode": "\n # This function is called after all actions are completed.\n # summary of all the action and/or all details of actions\n # can be collected here.\n\n # summary_json = phantom.get_summary()\n # if 'result' in summary_json:\n # for action_result in summary_json['result']:\n # if 'action_run_id' in action_result:\n # action_results = phantom.get_action_results(action_run_id=action_result['action_run_id'], result_data=False, flatten=False)\n # phantom.debug(action_results)\n\n",
|
||||
"x": 530,
|
||||
"y": 481
|
||||
"y": 480.9999999999994
|
||||
},
|
||||
"10": {
|
||||
"data": {
|
||||
@@ -146,13 +146,15 @@
|
||||
"id": "10",
|
||||
"type": "format",
|
||||
"x": 1020,
|
||||
"y": 150
|
||||
"y": 149.99999999999991
|
||||
},
|
||||
"11": {
|
||||
"data": {
|
||||
"action": "run query",
|
||||
"actionType": "investigate",
|
||||
"advanced": {
|
||||
"customName": "run Get Notable History",
|
||||
"customNameId": 0,
|
||||
"join": []
|
||||
},
|
||||
"connector": "Splunk",
|
||||
@@ -162,7 +164,7 @@
|
||||
"connectorId": "91883aa8-9c81-470b-97a1-5d8f7995f560",
|
||||
"connectorVersion": "v1",
|
||||
"functionId": 1,
|
||||
"functionName": "run_query_1",
|
||||
"functionName": "run_get_notable_history",
|
||||
"id": "11",
|
||||
"parameters": {
|
||||
"command": "search",
|
||||
@@ -181,10 +183,13 @@
|
||||
"y": 331
|
||||
},
|
||||
"12": {
|
||||
"customCode": null,
|
||||
"data": {
|
||||
"action": "run query",
|
||||
"actionType": "investigate",
|
||||
"advanced": {
|
||||
"customName": "run Get Process Info",
|
||||
"customNameId": 0,
|
||||
"join": []
|
||||
},
|
||||
"connector": "Splunk",
|
||||
@@ -194,7 +199,7 @@
|
||||
"connectorId": "91883aa8-9c81-470b-97a1-5d8f7995f560",
|
||||
"connectorVersion": "v1",
|
||||
"functionId": 2,
|
||||
"functionName": "run_query_2",
|
||||
"functionName": "run_get_process_info",
|
||||
"id": "12",
|
||||
"parameters": {
|
||||
"command": "tstats",
|
||||
@@ -208,15 +213,17 @@
|
||||
"errors": {},
|
||||
"id": "12",
|
||||
"type": "action",
|
||||
"userCode": "\n parameters = []\n phantom.debug(get_process_info__as_list)\n for formatted_item in get_process_info__as_list:\n parameters.append({\n \"query\": formatted_item,\n \"command\": \"tstats\",\n })\n \n",
|
||||
"userCode": "\n parameters = []\n phantom.debug(get_process_info__as_list)\n for formatted_item in get_process_info__as_list:\n parameters.append({\n \"query\": formatted_item,\n \"command\": \"| tstats\",\n })\n \n",
|
||||
"x": 340,
|
||||
"y": 331
|
||||
"y": 340
|
||||
},
|
||||
"13": {
|
||||
"data": {
|
||||
"action": "run query",
|
||||
"actionType": "investigate",
|
||||
"advanced": {
|
||||
"customName": "run Get Children of Java",
|
||||
"customNameId": 0,
|
||||
"join": []
|
||||
},
|
||||
"connector": "Splunk",
|
||||
@@ -226,7 +233,7 @@
|
||||
"connectorId": "91883aa8-9c81-470b-97a1-5d8f7995f560",
|
||||
"connectorVersion": "v1",
|
||||
"functionId": 3,
|
||||
"functionName": "run_query_3",
|
||||
"functionName": "run_get_children_of_java",
|
||||
"id": "13",
|
||||
"parameters": {
|
||||
"command": "tstats",
|
||||
@@ -240,15 +247,17 @@
|
||||
"errors": {},
|
||||
"id": "13",
|
||||
"type": "action",
|
||||
"userCode": "\n\n parameters = []\n phantom.debug(get_children_of_java__as_list)\n for formatted_item in get_children_of_java__as_list:\n parameters.append({\n \"query\": formatted_item,\n \"command\": \"tstats\",\n })\n \n",
|
||||
"userCode": "\n\n parameters = []\n phantom.debug(get_children_of_java__as_list)\n for formatted_item in get_children_of_java__as_list:\n parameters.append({\n \"query\": formatted_item,\n \"command\": \"| tstats\",\n })\n \n",
|
||||
"x": 680,
|
||||
"y": 331
|
||||
"y": 330.9999999999998
|
||||
},
|
||||
"14": {
|
||||
"data": {
|
||||
"action": "run query",
|
||||
"actionType": "investigate",
|
||||
"advanced": {
|
||||
"customName": "Fetch ES Assets",
|
||||
"customNameId": 0,
|
||||
"join": []
|
||||
},
|
||||
"connector": "Splunk",
|
||||
@@ -258,7 +267,7 @@
|
||||
"connectorId": "91883aa8-9c81-470b-97a1-5d8f7995f560",
|
||||
"connectorVersion": "v1",
|
||||
"functionId": 4,
|
||||
"functionName": "run_query_4",
|
||||
"functionName": "fetch_es_assets",
|
||||
"id": "14",
|
||||
"parameters": {
|
||||
"command": "| inputlookup",
|
||||
@@ -274,7 +283,7 @@
|
||||
"type": "action",
|
||||
"userCode": "\n parameters = []\n phantom.debug(es_assets__as_list)\n for formatted_item in es_assets__as_list:\n parameters.append({\n \"query\": formatted_item,\n \"command\": \"| inputlookup\",\n })\n \n",
|
||||
"x": 1020,
|
||||
"y": 331
|
||||
"y": 340
|
||||
},
|
||||
"6": {
|
||||
"data": {
|
||||
@@ -299,7 +308,7 @@
|
||||
"type": "format",
|
||||
"userCode": "\n # Write your custom code here...\n\n",
|
||||
"x": 0,
|
||||
"y": 150
|
||||
"y": 149.99999999999991
|
||||
},
|
||||
"7": {
|
||||
"data": {
|
||||
@@ -316,14 +325,14 @@
|
||||
"parameters": [
|
||||
"playbook_input:ip_or_hostname"
|
||||
],
|
||||
"template": "%%\n`security_content_summariesonly` count values(Processes.process)\n as process min(_time) as firstTime max(_time) as lastTime FROM datamodel=Endpoint.Processes\n by Processes.user Processes.parent_process_name Processes.process_name Processes.dest\n | `drop_dm_object_name(\"Processes\")` | search process_name= \"*java*\" | search\n dest = {0} | `security_content_ctime(firstTime)` | `security_content_ctime(lastTime)`\n%%",
|
||||
"template": "%%\n`security_content_summariesonly` count values(Processes.process)\n as process min(_time) as firstTime max(_time) as lastTime FROM datamodel=Endpoint.Processes\n where earliest=-7d by Processes.user Processes.parent_process_name Processes.process_name Processes.dest\n | `drop_dm_object_name(\"Processes\")` | search process_name= \"*java*\" | search\n dest = {0} | `security_content_ctime(firstTime)` | `security_content_ctime(lastTime)`\n%%",
|
||||
"type": "format"
|
||||
},
|
||||
"errors": {},
|
||||
"id": "7",
|
||||
"type": "format",
|
||||
"x": 340,
|
||||
"y": 150
|
||||
"y": 140
|
||||
},
|
||||
"8": {
|
||||
"data": {
|
||||
@@ -339,17 +348,17 @@
|
||||
"parameters": [
|
||||
"playbook_input:ip_or_hostname"
|
||||
],
|
||||
"template": "%%\n`security_content_summariesonly` count values(Processes.process)\n as process min(_time) as firstTime max(_time) as lastTime FROM datamodel=Endpoint.Processes\n by Processes.user Processes.parent_process_name Processes.process_name Processes.dest\n | `drop_dm_object_name(\"Processes\")` | search parent_process_name= \"*java*\" | search dest = {0} |`security_content_ctime(firstTime)` | `security_content_ctime(lastTime)`\n%%",
|
||||
"template": "%%\n`security_content_summariesonly` count values(Processes.process)\n as process min(_time) as firstTime max(_time) as lastTime FROM datamodel=Endpoint.Processes\n where earliest=-7d by Processes.user Processes.parent_process_name Processes.process_name Processes.dest\n | `drop_dm_object_name(\"Processes\")` | search parent_process_name= \"*java*\" | search dest = {0} |`security_content_ctime(firstTime)` | `security_content_ctime(lastTime)`\n%%",
|
||||
"type": "format"
|
||||
},
|
||||
"errors": {},
|
||||
"id": "8",
|
||||
"type": "format",
|
||||
"x": 680,
|
||||
"y": 150
|
||||
"y": 140
|
||||
}
|
||||
},
|
||||
"notes": "Deployment Notes: This playbook presumes you have Enterprise Security and have configured Assets and Identities, as well as the Endpoint.Processes datamodel."
|
||||
"notes": "This playbook uses the following Apps:\n - Splunk (run query) [asset name = splunk] - query for evidence of log4j exploitation\n\nDeployment Notes:\n - The first and fourth searches require Splunk ES to use the Notable and Asset frameworks, respectively\n - The second and third searches require ESCU to use the security_content macros, and the Endpoint.Processes datamodel to find process activity"
|
||||
},
|
||||
"input_spec": [
|
||||
{
|
||||
@@ -367,12 +376,15 @@
|
||||
"schema": "5.0.4",
|
||||
"version": "5.1.0.70187"
|
||||
},
|
||||
"create_time": "2021-12-15T00:13:52.864984+00:00",
|
||||
"create_time": "2021-12-15T15:45:06.239972+00:00",
|
||||
"draft_mode": false,
|
||||
"labels": [
|
||||
"*"
|
||||
],
|
||||
"tags": [
|
||||
"investigate"
|
||||
"investigate",
|
||||
"log4j",
|
||||
"splunk",
|
||||
"internal_host"
|
||||
]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 201 KiB |
@@ -44,7 +44,7 @@ def get_notable_history(action=None, success=None, container=None, results=None,
|
||||
|
||||
phantom.format(container=container, template=template, parameters=parameters, name="get_notable_history")
|
||||
|
||||
run_query_1(container=container)
|
||||
run_get_notable_history(container=container)
|
||||
|
||||
return
|
||||
|
||||
@@ -52,7 +52,7 @@ def get_notable_history(action=None, success=None, container=None, results=None,
|
||||
def get_process_info(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
|
||||
phantom.debug("get_process_info() called")
|
||||
|
||||
template = """%%\n`security_content_summariesonly` count values(Processes.process)\n as process min(_time) as firstTime max(_time) as lastTime FROM datamodel=Endpoint.Processes\n by Processes.user Processes.parent_process_name Processes.process_name Processes.dest\n | `drop_dm_object_name(\"Processes\")` | search process_name= \"*java*\" | search\n dest = {0} | `security_content_ctime(firstTime)` | `security_content_ctime(lastTime)`\n%%"""
|
||||
template = """%%\n`security_content_summariesonly` count values(Processes.process)\n as process min(_time) as firstTime max(_time) as lastTime FROM datamodel=Endpoint.Processes\n where earliest=-7d by Processes.user Processes.parent_process_name Processes.process_name Processes.dest\n | `drop_dm_object_name(\"Processes\")` | search process_name= \"*java*\" | search\n dest = {0} | `security_content_ctime(firstTime)` | `security_content_ctime(lastTime)`\n%%"""
|
||||
|
||||
# parameter list for template variable replacement
|
||||
parameters = [
|
||||
@@ -71,7 +71,7 @@ def get_process_info(action=None, success=None, container=None, results=None, ha
|
||||
|
||||
phantom.format(container=container, template=template, parameters=parameters, name="get_process_info")
|
||||
|
||||
run_query_2(container=container)
|
||||
run_get_process_info(container=container)
|
||||
|
||||
return
|
||||
|
||||
@@ -79,7 +79,7 @@ def get_process_info(action=None, success=None, container=None, results=None, ha
|
||||
def get_children_of_java(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
|
||||
phantom.debug("get_children_of_java() called")
|
||||
|
||||
template = """%%\n`security_content_summariesonly` count values(Processes.process)\n as process min(_time) as firstTime max(_time) as lastTime FROM datamodel=Endpoint.Processes\n by Processes.user Processes.parent_process_name Processes.process_name Processes.dest\n | `drop_dm_object_name(\"Processes\")` | search parent_process_name= \"*java*\" | search dest = {0} |`security_content_ctime(firstTime)` | `security_content_ctime(lastTime)`\n%%"""
|
||||
template = """%%\n`security_content_summariesonly` count values(Processes.process)\n as process min(_time) as firstTime max(_time) as lastTime FROM datamodel=Endpoint.Processes\n where earliest=-7d by Processes.user Processes.parent_process_name Processes.process_name Processes.dest\n | `drop_dm_object_name(\"Processes\")` | search parent_process_name= \"*java*\" | search dest = {0} |`security_content_ctime(firstTime)` | `security_content_ctime(lastTime)`\n%%"""
|
||||
|
||||
# parameter list for template variable replacement
|
||||
parameters = [
|
||||
@@ -98,7 +98,7 @@ def get_children_of_java(action=None, success=None, container=None, results=None
|
||||
|
||||
phantom.format(container=container, template=template, parameters=parameters, name="get_children_of_java")
|
||||
|
||||
run_query_3(container=container)
|
||||
run_get_children_of_java(container=container)
|
||||
|
||||
return
|
||||
|
||||
@@ -125,13 +125,13 @@ def es_assets(action=None, success=None, container=None, results=None, handle=No
|
||||
|
||||
phantom.format(container=container, template=template, parameters=parameters, name="es_assets")
|
||||
|
||||
run_query_4(container=container)
|
||||
fetch_es_assets(container=container)
|
||||
|
||||
return
|
||||
|
||||
|
||||
def run_query_1(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
|
||||
phantom.debug("run_query_1() called")
|
||||
def run_get_notable_history(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
|
||||
phantom.debug("run_get_notable_history() called")
|
||||
|
||||
# phantom.debug('Action: {0} {1}'.format(action['name'], ('SUCCEEDED' if success else 'FAILED')))
|
||||
|
||||
@@ -161,13 +161,13 @@ def run_query_1(action=None, success=None, container=None, results=None, handle=
|
||||
## Custom Code End
|
||||
################################################################################
|
||||
|
||||
phantom.act("run query", parameters=parameters, name="run_query_1", assets=["splunk"])
|
||||
phantom.act("run query", parameters=parameters, name="run_get_notable_history", assets=["splunk"])
|
||||
|
||||
return
|
||||
|
||||
|
||||
def run_query_2(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
|
||||
phantom.debug("run_query_2() called")
|
||||
def run_get_process_info(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
|
||||
phantom.debug("run_get_process_info() called")
|
||||
|
||||
# phantom.debug('Action: {0} {1}'.format(action['name'], ('SUCCEEDED' if success else 'FAILED')))
|
||||
|
||||
@@ -190,20 +190,20 @@ def run_query_2(action=None, success=None, container=None, results=None, handle=
|
||||
for formatted_item in get_process_info__as_list:
|
||||
parameters.append({
|
||||
"query": formatted_item,
|
||||
"command": "tstats",
|
||||
"command": "| tstats",
|
||||
})
|
||||
|
||||
################################################################################
|
||||
## Custom Code End
|
||||
################################################################################
|
||||
|
||||
phantom.act("run query", parameters=parameters, name="run_query_2", assets=["splunk"])
|
||||
phantom.act("run query", parameters=parameters, name="run_get_process_info", assets=["splunk"])
|
||||
|
||||
return
|
||||
|
||||
|
||||
def run_query_3(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
|
||||
phantom.debug("run_query_3() called")
|
||||
def run_get_children_of_java(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
|
||||
phantom.debug("run_get_children_of_java() called")
|
||||
|
||||
# phantom.debug('Action: {0} {1}'.format(action['name'], ('SUCCEEDED' if success else 'FAILED')))
|
||||
|
||||
@@ -227,20 +227,20 @@ def run_query_3(action=None, success=None, container=None, results=None, handle=
|
||||
for formatted_item in get_children_of_java__as_list:
|
||||
parameters.append({
|
||||
"query": formatted_item,
|
||||
"command": "tstats",
|
||||
"command": "| tstats",
|
||||
})
|
||||
|
||||
################################################################################
|
||||
## Custom Code End
|
||||
################################################################################
|
||||
|
||||
phantom.act("run query", parameters=parameters, name="run_query_3", assets=["splunk"])
|
||||
phantom.act("run query", parameters=parameters, name="run_get_children_of_java", assets=["splunk"])
|
||||
|
||||
return
|
||||
|
||||
|
||||
def run_query_4(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
|
||||
phantom.debug("run_query_4() called")
|
||||
def fetch_es_assets(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
|
||||
phantom.debug("fetch_es_assets() called")
|
||||
|
||||
# phantom.debug('Action: {0} {1}'.format(action['name'], ('SUCCEEDED' if success else 'FAILED')))
|
||||
|
||||
@@ -270,7 +270,7 @@ def run_query_4(action=None, success=None, container=None, results=None, handle=
|
||||
## Custom Code End
|
||||
################################################################################
|
||||
|
||||
phantom.act("run query", parameters=parameters, name="run_query_4", assets=["splunk"])
|
||||
phantom.act("run query", parameters=parameters, name="fetch_es_assets", assets=["splunk"])
|
||||
|
||||
return
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Log4j Splunk Investigate And Respond
|
||||
name: Log4j Splunk Investigation
|
||||
id: fc0adc66-ff2b-48b0-9a6f-63da6783fd63
|
||||
version: 1
|
||||
date: '2021-12-14'
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"blockly": false,
|
||||
"blockly_xml": "<xml></xml>",
|
||||
"category": "Use Cases",
|
||||
"category": "Threat Response",
|
||||
"coa": {
|
||||
"data": {
|
||||
"description": "Investigate an internal unix host using SSH. This pushes a bash script to the endpoint and runs it, collecting generic information about the processes, user activity, and network activity. This includes the process list, login history, cron jobs, and open sockets. The results are zipped up in .csv files and added to the vault for an analyst to review.",
|
||||
"description": "Published in response to CVE-2021-44228, this playbook investigates an internal unix host using SSH. This pushes a bash script to the endpoint and runs it, collecting generic information about the processes, user activity, and network activity. This includes the process list, login history, cron jobs, and open sockets. The results are zipped up in .csv files and added to the vault for an analyst to review.",
|
||||
"edges": [
|
||||
{
|
||||
"id": "port_0_to_port_2",
|
||||
@@ -84,7 +84,7 @@
|
||||
"targetPort": "1_in"
|
||||
}
|
||||
],
|
||||
"hash": "0da2cbd02719d0f1db566ed2b4c158466d774b17",
|
||||
"hash": "9fb5a54460b6307f046b6fff0272919a4532a39d",
|
||||
"nodes": {
|
||||
"0": {
|
||||
"data": {
|
||||
@@ -99,7 +99,7 @@
|
||||
"id": "0",
|
||||
"type": "start",
|
||||
"x": 500,
|
||||
"y": 0
|
||||
"y": -3.659295089164516e-13
|
||||
},
|
||||
"1": {
|
||||
"data": {
|
||||
@@ -146,7 +146,7 @@
|
||||
"id": "2",
|
||||
"type": "action",
|
||||
"x": 1080,
|
||||
"y": 120
|
||||
"y": 119.99999999999994
|
||||
},
|
||||
"3": {
|
||||
"data": {
|
||||
@@ -176,7 +176,7 @@
|
||||
"id": "3",
|
||||
"type": "action",
|
||||
"x": 800,
|
||||
"y": 120
|
||||
"y": 119.99999999999994
|
||||
},
|
||||
"5": {
|
||||
"data": {
|
||||
@@ -206,7 +206,7 @@
|
||||
"id": "5",
|
||||
"type": "action",
|
||||
"x": 160,
|
||||
"y": 120
|
||||
"y": 119.99999999999994
|
||||
},
|
||||
"6": {
|
||||
"data": {
|
||||
@@ -373,9 +373,9 @@
|
||||
"errors": {},
|
||||
"id": "6",
|
||||
"type": "utility",
|
||||
"userCode": "\n bash_script = r\"\"\"\n#!/bin/bash\n\n# This script is part of the Splunk SOAR playbook called internal_host_ssh_log4j_investigate. It gathers system information as part of a unix endpoint investigation. The output is a human-readable log and a set of .csv files\n\necho \"##############################################################\"\necho \"splunk_soar_internal_host_ssh_investigate.sh\"\necho \"##############################################################\"\necho \"\"\necho \"[+] Basic system configuration:\"\n\necho \"key,value\" > basic_system_configuration.csv\n\necho \"hostname: $(uname -n | tr -d \"\\n\")\"\necho \"hostname,$(uname -n | tr -d \"\\n\")\" >> basic_system_configuration.csv\n\necho \"current time: $(date +%F_%T)\"\necho \"current time,$(date +%F_%T)\" >> basic_system_configuration.csv\n\necho \"IP address: $(ifconfig | grep -Eo 'inet (addr:)?([0-9]*\\.){3}[0-9]*' | grep -Eo '([0-9]*\\.){3}[0-9]*' | grep -v '127.0.0.1' | tr '\\n' ' ')\"\necho \"IP address,$(ifconfig | grep -Eo 'inet (addr:)?([0-9]*\\.){3}[0-9]*' | grep -Eo '([0-9]*\\.){3}[0-9]*' | grep -v '127.0.0.1' | tr '\\n' ' ')\" >> basic_system_configuration.csv\n\necho \"OS release: $(cat /etc/*release | sort -u | tr \"\\n\" \";\")\"\necho \"OS release,$(cat /etc/*release | sort -u | tr \"\\n\" \";\")\" >> basic_system_configuration.csv\n\necho \"OS issue: $(cat /etc/issue)\"\necho \"OS issue,$(cat /etc/issue)\" >> basic_system_configuration.csv\n\necho \"OS kernel: $(uname -a)\"\necho \"OS kernel,$(uname -a)\" >> basic_system_configuration.csv\n\necho \"\"\necho \"USER,PID,%CPU,%MEM,VSZ,RSS,TTY,STAT,START,TIME,COMMAND\" > process_list.csv\necho \"$(ps aux)\" >> process_list.csv\necho \"[+] Process list:\"\necho \"$(ps aux)\"\n\necho \"\"\necho \"UNIT,LOAD,ACTIVE,SUB,DESCRIPTION\" > service_list.csv\necho \"$(systemctl)\" >> service_list.csv\necho \"[+] Service list:\"\necho \"$(systemctl)\"\n\necho \"\"\necho \"$(ss -tunapl)\" > open_sockets.csv\necho \"[+] Open sockets:\"\necho \"$(ss -tunapl)\"\n\necho \"\"\necho \"cron_job\" > cron_jobs.csv\necho \"$(for user in $(cut -f1 -d: /etc/passwd); do crontab -u $user -l 2>/dev/null | grep -v '^#'; done)\" >> cron_jobs.csv\necho \"[+] Cron jobs:\"\necho \"$(for user in $(cut -f1 -d: /etc/passwd); do crontab -u $user -l 2>/dev/null | grep -v '^#'; done)\"\n\necho \"[+] Zip up the outputs ...\"\nhostname=$1\nzip $(hostname)_ssh_output.zip basic_system_configuration.csv process_list.csv service_list.csv open_sockets.csv cron_jobs.csv\n\"\"\"\n\n file_name = 'splunk_soar_internal_host_ssh_investigate.sh'\n file_path = '/opt/phantom/vault/tmp/{}'.format(file_name)\n with open(file_path, 'w') as bash_script_file:\n bash_script_file.write(bash_script)\n \n success, message, vault_id = phantom.vault_add(file_location=file_path, file_name=file_name)\n parameters = [{'input_1': vault_id}]\n \n",
|
||||
"userCode": "\n bash_script = r\"\"\"\n#!/bin/bash\n\n# This script is part of the Splunk SOAR playbook called internal_host_ssh_log4j_investigate. It gathers \n# system information as part of a unix endpoint investigation. The output is a human-readable log and a \n# set of .csv files to be copied back to SOAR\n\necho \"##############################################################\"\necho \"splunk_soar_internal_host_ssh_investigate.sh\"\necho \"##############################################################\"\necho \"\"\necho \"[+] Basic system configuration:\"\n\necho \"key,value\" > /tmp/basic_system_configuration.csv\n\necho \"hostname: $(uname -n | tr -d \"\\n\")\"\necho \"hostname,$(uname -n | tr -d \"\\n\")\" >> /tmp/basic_system_configuration.csv\n\necho \"current time: $(date +%F_%T)\"\necho \"current time,$(date +%F_%T)\" >> /tmp/basic_system_configuration.csv\n\necho \"IP address: $(ifconfig | grep -Eo 'inet (addr:)?([0-9]*\\.){3}[0-9]*' | grep -Eo '([0-9]*\\.){3}[0-9]*' | grep -v '127.0.0.1' | tr '\\n' ' ')\"\necho \"IP address,$(ifconfig | grep -Eo 'inet (addr:)?([0-9]*\\.){3}[0-9]*' | grep -Eo '([0-9]*\\.){3}[0-9]*' | grep -v '127.0.0.1' | tr '\\n' ' ')\" >> /tmp/basic_system_configuration.csv\n\necho \"OS release: $(cat /etc/*release | sort -u | tr \"\\n\" \";\")\"\necho \"OS release,$(cat /etc/*release | sort -u | tr \"\\n\" \";\")\" >> /tmp/basic_system_configuration.csv\n\necho \"OS issue: $(cat /etc/issue)\"\necho \"OS issue,$(cat /etc/issue)\" >> /tmp/basic_system_configuration.csv\n\necho \"OS kernel: $(uname -a)\"\necho \"OS kernel,$(uname -a)\" >> /tmp/basic_system_configuration.csv\n\necho \"\"\necho \"USER,PID,%CPU,%MEM,VSZ,RSS,TTY,STAT,START,TIME,COMMAND\" > /tmp/process_list.csv\necho \"$(ps aux)\" >> /tmp/process_list.csv\necho \"[+] Process list:\"\necho \"$(ps aux)\"\n\necho \"\"\necho \"UNIT,LOAD,ACTIVE,SUB,DESCRIPTION\" > /tmp/service_list.csv\necho \"$(systemctl)\" >> /tmp/service_list.csv\necho \"[+] Service list:\"\necho \"$(systemctl)\"\n\necho \"\"\necho \"$(last -a)\" > /tmp/login_history.csv\necho \"[+] login history:\"\necho \"$(last -a)\"\n\necho \"\"\necho \"$(ss -tunapl)\" > /tmp/open_sockets.csv\necho \"[+] Open sockets:\"\necho \"$(ss -tunapl)\"\n\necho \"\"\necho \"cron_job\" > /tmp/cron_jobs.csv\necho \"$(for user in $(cut -f1 -d: /etc/passwd); do crontab -u $user -l 2>/dev/null | grep -v '^#'; done)\" >> /tmp/cron_jobs.csv\necho \"[+] Cron jobs:\"\necho \"$(for user in $(cut -f1 -d: /etc/passwd); do crontab -u $user -l 2>/dev/null | grep -v '^#'; done)\"\n\necho \"\"\necho \"[+] Zip up the outputs ...\"\nzip -j /tmp/$1_ssh_output.zip /tmp/basic_system_configuration.csv /tmp/process_list.csv /tmp/service_list.csv /tmp/login_history.csv /tmp/open_sockets.csv /tmp/cron_jobs.csv\necho \"wrote zip file to /tmp/$1_ssh_output.zip; next we will copy it back to SOAR\"\n\"\"\"\n\n file_name = 'splunk_soar_internal_host_ssh_investigate.sh'\n file_path = '/opt/phantom/vault/tmp/{}'.format(file_name)\n with open(file_path, 'w') as bash_script_file:\n bash_script_file.write(bash_script)\n \n success, message, vault_id = phantom.vault_add(file_location=file_path, file_name=file_name)\n parameters = [{'input_1': vault_id}]\n \n",
|
||||
"x": 480,
|
||||
"y": 120
|
||||
"y": 119.99999999999977
|
||||
},
|
||||
"7": {
|
||||
"data": {
|
||||
@@ -395,6 +395,11 @@
|
||||
"functionId": 1,
|
||||
"functionName": "upload_bash_script",
|
||||
"id": "7",
|
||||
"parameters": {
|
||||
"file_destination": "/tmp/",
|
||||
"ip_hostname": "playbook_input:ip_or_hostname",
|
||||
"vault_id": "write_embedded_bash_script_to_vault:custom_function_result.data.*.item"
|
||||
},
|
||||
"requiredParameters": [
|
||||
"vault_id",
|
||||
"ip_hostname",
|
||||
@@ -406,7 +411,7 @@
|
||||
"id": "7",
|
||||
"type": "action",
|
||||
"x": 480,
|
||||
"y": 220
|
||||
"y": 219.99999999999994
|
||||
},
|
||||
"8": {
|
||||
"data": {
|
||||
@@ -426,6 +431,10 @@
|
||||
"functionId": 1,
|
||||
"functionName": "run_bash_script",
|
||||
"id": "8",
|
||||
"parameters": {
|
||||
"command": "bash /tmp/splunk_soar_internal_host_ssh_investigate.sh",
|
||||
"ip_hostname": "playbook_input:ip_or_hostname"
|
||||
},
|
||||
"requiredParameters": [
|
||||
"ip_hostname"
|
||||
],
|
||||
@@ -434,6 +443,7 @@
|
||||
"errors": {},
|
||||
"id": "8",
|
||||
"type": "action",
|
||||
"userCode": "\n # pass the ip_hostname as an argument so it can be used in the output zip file name\n for parameter in parameters:\n parameter['command'] = parameter['command'] + ' ' + parameter['ip_hostname']\n\n",
|
||||
"x": 480,
|
||||
"y": 320
|
||||
},
|
||||
@@ -455,6 +465,10 @@
|
||||
"functionId": 1,
|
||||
"functionName": "get_output_zip_file",
|
||||
"id": "9",
|
||||
"parameters": {
|
||||
"file_path": "playbook_input:ip_or_hostname",
|
||||
"ip_hostname": "playbook_input:ip_or_hostname"
|
||||
},
|
||||
"requiredParameters": [
|
||||
"file_path",
|
||||
"ip_hostname"
|
||||
@@ -464,11 +478,12 @@
|
||||
"errors": {},
|
||||
"id": "9",
|
||||
"type": "action",
|
||||
"userCode": "\n for parameter in parameters:\n parameter['file_path'] = '/tmp/' + parameter['file_path'] + '_ssh_output.zip'\n\n",
|
||||
"x": 480,
|
||||
"y": 420
|
||||
}
|
||||
},
|
||||
"notes": "TODO: last -a login history"
|
||||
"notes": "This playbook uses the following Apps:\n - SSH (execute program, list firewall rules, list connections, list processes, get file, put file) [asset name = ssh] - investigate behavior on the host\nDeployment Notes:\n - The ssh asset requires sudo access to view the processes with open sockets\n - The command line argument to the script is the ip_or_hostname, which will be used to name the zip file as output\n - To add a new data collection, pipe the output to a file and include it in the zip command"
|
||||
},
|
||||
"input_spec": [
|
||||
{
|
||||
@@ -486,13 +501,15 @@
|
||||
"schema": "5.0.4",
|
||||
"version": "5.1.0.70187"
|
||||
},
|
||||
"create_time": "2021-12-15T01:02:29.241695+00:00",
|
||||
"create_time": "2021-12-15T22:32:03.384311+00:00",
|
||||
"draft_mode": false,
|
||||
"labels": [
|
||||
"*"
|
||||
],
|
||||
"tags": [
|
||||
"unix",
|
||||
"internal_host"
|
||||
"internal_host",
|
||||
"log4j",
|
||||
"investigate"
|
||||
]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 254 KiB After Width: | Height: | Size: 263 KiB |
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
Investigate an internal unix host using SSH. This pushes a bash script to the endpoint and runs it, collecting generic information about the processes, user activity, and network activity. This includes the process list, login history, cron jobs, and open sockets. The results are zipped up in .csv files and added to the vault for an analyst to review.
|
||||
Published in response to CVE-2021-44228, this playbook investigates an internal unix host using SSH. This pushes a bash script to the endpoint and runs it, collecting generic information about the processes, user activity, and network activity. This includes the process list, login history, cron jobs, and open sockets. The results are zipped up in .csv files and added to the vault for an analyst to review.
|
||||
"""
|
||||
|
||||
|
||||
@@ -140,7 +140,9 @@ def write_embedded_bash_script_to_vault(action=None, success=None, container=Non
|
||||
bash_script = r"""
|
||||
#!/bin/bash
|
||||
|
||||
# This script is part of the Splunk SOAR playbook called internal_host_ssh_log4j_investigate. It gathers system information as part of a unix endpoint investigation. The output is a human-readable log and a set of .csv files
|
||||
# This script is part of the Splunk SOAR playbook called internal_host_ssh_log4j_investigate. It gathers
|
||||
# system information as part of a unix endpoint investigation. The output is a human-readable log and a
|
||||
# set of .csv files to be copied back to SOAR
|
||||
|
||||
echo "##############################################################"
|
||||
echo "splunk_soar_internal_host_ssh_investigate.sh"
|
||||
@@ -148,52 +150,58 @@ echo "##############################################################"
|
||||
echo ""
|
||||
echo "[+] Basic system configuration:"
|
||||
|
||||
echo "key,value" > basic_system_configuration.csv
|
||||
echo "key,value" > /tmp/basic_system_configuration.csv
|
||||
|
||||
echo "hostname: $(uname -n | tr -d "\n")"
|
||||
echo "hostname,$(uname -n | tr -d "\n")" >> basic_system_configuration.csv
|
||||
echo "hostname,$(uname -n | tr -d "\n")" >> /tmp/basic_system_configuration.csv
|
||||
|
||||
echo "current time: $(date +%F_%T)"
|
||||
echo "current time,$(date +%F_%T)" >> basic_system_configuration.csv
|
||||
echo "current time,$(date +%F_%T)" >> /tmp/basic_system_configuration.csv
|
||||
|
||||
echo "IP address: $(ifconfig | grep -Eo 'inet (addr:)?([0-9]*\.){3}[0-9]*' | grep -Eo '([0-9]*\.){3}[0-9]*' | grep -v '127.0.0.1' | tr '\n' ' ')"
|
||||
echo "IP address,$(ifconfig | grep -Eo 'inet (addr:)?([0-9]*\.){3}[0-9]*' | grep -Eo '([0-9]*\.){3}[0-9]*' | grep -v '127.0.0.1' | tr '\n' ' ')" >> basic_system_configuration.csv
|
||||
echo "IP address,$(ifconfig | grep -Eo 'inet (addr:)?([0-9]*\.){3}[0-9]*' | grep -Eo '([0-9]*\.){3}[0-9]*' | grep -v '127.0.0.1' | tr '\n' ' ')" >> /tmp/basic_system_configuration.csv
|
||||
|
||||
echo "OS release: $(cat /etc/*release | sort -u | tr "\n" ";")"
|
||||
echo "OS release,$(cat /etc/*release | sort -u | tr "\n" ";")" >> basic_system_configuration.csv
|
||||
echo "OS release,$(cat /etc/*release | sort -u | tr "\n" ";")" >> /tmp/basic_system_configuration.csv
|
||||
|
||||
echo "OS issue: $(cat /etc/issue)"
|
||||
echo "OS issue,$(cat /etc/issue)" >> basic_system_configuration.csv
|
||||
echo "OS issue,$(cat /etc/issue)" >> /tmp/basic_system_configuration.csv
|
||||
|
||||
echo "OS kernel: $(uname -a)"
|
||||
echo "OS kernel,$(uname -a)" >> basic_system_configuration.csv
|
||||
echo "OS kernel,$(uname -a)" >> /tmp/basic_system_configuration.csv
|
||||
|
||||
echo ""
|
||||
echo "USER,PID,%CPU,%MEM,VSZ,RSS,TTY,STAT,START,TIME,COMMAND" > process_list.csv
|
||||
echo "$(ps aux)" >> process_list.csv
|
||||
echo "USER,PID,%CPU,%MEM,VSZ,RSS,TTY,STAT,START,TIME,COMMAND" > /tmp/process_list.csv
|
||||
echo "$(ps aux)" >> /tmp/process_list.csv
|
||||
echo "[+] Process list:"
|
||||
echo "$(ps aux)"
|
||||
|
||||
echo ""
|
||||
echo "UNIT,LOAD,ACTIVE,SUB,DESCRIPTION" > service_list.csv
|
||||
echo "$(systemctl)" >> service_list.csv
|
||||
echo "UNIT,LOAD,ACTIVE,SUB,DESCRIPTION" > /tmp/service_list.csv
|
||||
echo "$(systemctl)" >> /tmp/service_list.csv
|
||||
echo "[+] Service list:"
|
||||
echo "$(systemctl)"
|
||||
|
||||
echo ""
|
||||
echo "$(ss -tunapl)" > open_sockets.csv
|
||||
echo "$(last -a)" > /tmp/login_history.csv
|
||||
echo "[+] login history:"
|
||||
echo "$(last -a)"
|
||||
|
||||
echo ""
|
||||
echo "$(ss -tunapl)" > /tmp/open_sockets.csv
|
||||
echo "[+] Open sockets:"
|
||||
echo "$(ss -tunapl)"
|
||||
|
||||
echo ""
|
||||
echo "cron_job" > cron_jobs.csv
|
||||
echo "$(for user in $(cut -f1 -d: /etc/passwd); do crontab -u $user -l 2>/dev/null | grep -v '^#'; done)" >> cron_jobs.csv
|
||||
echo "cron_job" > /tmp/cron_jobs.csv
|
||||
echo "$(for user in $(cut -f1 -d: /etc/passwd); do crontab -u $user -l 2>/dev/null | grep -v '^#'; done)" >> /tmp/cron_jobs.csv
|
||||
echo "[+] Cron jobs:"
|
||||
echo "$(for user in $(cut -f1 -d: /etc/passwd); do crontab -u $user -l 2>/dev/null | grep -v '^#'; done)"
|
||||
|
||||
echo ""
|
||||
echo "[+] Zip up the outputs ..."
|
||||
hostname=$1
|
||||
zip $(hostname)_ssh_output.zip basic_system_configuration.csv process_list.csv service_list.csv open_sockets.csv cron_jobs.csv
|
||||
zip -j /tmp/$1_ssh_output.zip /tmp/basic_system_configuration.csv /tmp/process_list.csv /tmp/service_list.csv /tmp/login_history.csv /tmp/open_sockets.csv /tmp/cron_jobs.csv
|
||||
echo "wrote zip file to /tmp/$1_ssh_output.zip; next we will copy it back to SOAR"
|
||||
"""
|
||||
|
||||
file_name = 'splunk_soar_internal_host_ssh_investigate.sh'
|
||||
@@ -218,8 +226,21 @@ def upload_bash_script(action=None, success=None, container=None, results=None,
|
||||
|
||||
# phantom.debug('Action: {0} {1}'.format(action['name'], ('SUCCEEDED' if success else 'FAILED')))
|
||||
|
||||
write_embedded_bash_script_to_vault_data = phantom.collect2(container=container, datapath=["write_embedded_bash_script_to_vault:custom_function_result.data.*.item"])
|
||||
playbook_input_ip_or_hostname = phantom.collect2(container=container, datapath=["playbook_input:ip_or_hostname"])
|
||||
|
||||
parameters = []
|
||||
|
||||
# build parameters list for 'upload_bash_script' call
|
||||
for write_embedded_bash_script_to_vault_data_item in write_embedded_bash_script_to_vault_data:
|
||||
for playbook_input_ip_or_hostname_item in playbook_input_ip_or_hostname:
|
||||
if write_embedded_bash_script_to_vault_data_item[0] is not None and playbook_input_ip_or_hostname_item[0] is not None:
|
||||
parameters.append({
|
||||
"vault_id": write_embedded_bash_script_to_vault_data_item[0],
|
||||
"ip_hostname": playbook_input_ip_or_hostname_item[0],
|
||||
"file_destination": "/tmp/",
|
||||
})
|
||||
|
||||
################################################################################
|
||||
## Custom Code Start
|
||||
################################################################################
|
||||
@@ -240,13 +261,25 @@ def run_bash_script(action=None, success=None, container=None, results=None, han
|
||||
|
||||
# phantom.debug('Action: {0} {1}'.format(action['name'], ('SUCCEEDED' if success else 'FAILED')))
|
||||
|
||||
playbook_input_ip_or_hostname = phantom.collect2(container=container, datapath=["playbook_input:ip_or_hostname"])
|
||||
|
||||
parameters = []
|
||||
|
||||
# build parameters list for 'run_bash_script' call
|
||||
for playbook_input_ip_or_hostname_item in playbook_input_ip_or_hostname:
|
||||
if playbook_input_ip_or_hostname_item[0] is not None:
|
||||
parameters.append({
|
||||
"command": "bash /tmp/splunk_soar_internal_host_ssh_investigate.sh",
|
||||
"ip_hostname": playbook_input_ip_or_hostname_item[0],
|
||||
})
|
||||
|
||||
################################################################################
|
||||
## Custom Code Start
|
||||
################################################################################
|
||||
|
||||
# Write your custom code here...
|
||||
# pass the ip_hostname as an argument so it can be used in the output zip file name
|
||||
for parameter in parameters:
|
||||
parameter['command'] = parameter['command'] + ' ' + parameter['ip_hostname']
|
||||
|
||||
################################################################################
|
||||
## Custom Code End
|
||||
@@ -262,13 +295,24 @@ def get_output_zip_file(action=None, success=None, container=None, results=None,
|
||||
|
||||
# phantom.debug('Action: {0} {1}'.format(action['name'], ('SUCCEEDED' if success else 'FAILED')))
|
||||
|
||||
playbook_input_ip_or_hostname = phantom.collect2(container=container, datapath=["playbook_input:ip_or_hostname"])
|
||||
|
||||
parameters = []
|
||||
|
||||
# build parameters list for 'get_output_zip_file' call
|
||||
for playbook_input_ip_or_hostname_item in playbook_input_ip_or_hostname:
|
||||
if playbook_input_ip_or_hostname_item[0] is not None:
|
||||
parameters.append({
|
||||
"file_path": playbook_input_ip_or_hostname_item[0],
|
||||
"ip_hostname": playbook_input_ip_or_hostname_item[0],
|
||||
})
|
||||
|
||||
################################################################################
|
||||
## Custom Code Start
|
||||
################################################################################
|
||||
|
||||
# Write your custom code here...
|
||||
for parameter in parameters:
|
||||
parameter['file_path'] = '/tmp/' + parameter['file_path'] + '_ssh_output.zip'
|
||||
|
||||
################################################################################
|
||||
## Custom Code End
|
||||
|
||||
@@ -3,7 +3,7 @@ id: fdb65816-6688-41d8-8698-755b7b4ec44e
|
||||
version: 1
|
||||
date: '2021-12-14'
|
||||
author: Philip Royer, Splunk
|
||||
type: Investigate
|
||||
type: Investigation
|
||||
description: Investigate an internal unix host using SSH. This pushes a bash script to the endpoint and runs it, collecting generic information about the processes, user activity, and network activity. This includes the process list, login history, cron jobs, and open sockets. The results are zipped up in .csv files and added to the vault for an analyst to review.
|
||||
playbook: internal_host_ssh_investigate
|
||||
how_to_implement: The ssh asset requires sudo access to view the processes with open sockets.
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"blockly": false,
|
||||
"blockly_xml": "<xml></xml>",
|
||||
"category": "Use Cases",
|
||||
"category": "Threat Response",
|
||||
"coa": {
|
||||
"data": {
|
||||
"description": "Investigate an internal unix host using SSH. This pushes a bash script to the endpoint and runs it, collecting information specific to the December 2021 log4j vulnerability disclosure. This includes the java version installed on the host, any running java processes, and the results of a scan for the affected JndiLookup.class file or log4j .jar files. ",
|
||||
"description": "Published in response to CVE-2021-44228, \n this playbook investigates an internal unix host using SSH. This pushes a bash script to the endpoint and runs it, collecting information specific to the December 2021 log4j vulnerability disclosure. This includes the java version installed on the host, any running java processes, and the results of a scan for the affected JndiLookup.class file or log4j .jar files.",
|
||||
"edges": [
|
||||
{
|
||||
"id": "port_0_to_port_5",
|
||||
@@ -42,7 +42,7 @@
|
||||
"targetPort": "1_in"
|
||||
}
|
||||
],
|
||||
"hash": "f0f51d60c1f03e0566b642644ddb96be1979fb2e",
|
||||
"hash": "836df07909a97930becce0ff97153dcc05232fc6",
|
||||
"nodes": {
|
||||
"0": {
|
||||
"data": {
|
||||
@@ -56,8 +56,8 @@
|
||||
"errors": {},
|
||||
"id": "0",
|
||||
"type": "start",
|
||||
"x": 1000,
|
||||
"y": 419.99999999999966
|
||||
"x": 19.999999999999986,
|
||||
"y": -1.5987211554602254e-13
|
||||
},
|
||||
"1": {
|
||||
"data": {
|
||||
@@ -73,13 +73,13 @@
|
||||
"id": "1",
|
||||
"type": "end",
|
||||
"userCode": "\n # This function is called after all actions are completed.\n # summary of all the action and/or all details of actions\n # can be collected here.\n\n # summary_json = phantom.get_summary()\n # if 'result' in summary_json:\n # for action_result in summary_json['result']:\n # if 'action_run_id' in action_result:\n # action_results = phantom.get_action_results(action_run_id=action_result['action_run_id'], result_data=False, flatten=False)\n # phantom.debug(action_results)\n\n",
|
||||
"x": 1000,
|
||||
"y": 1000
|
||||
"x": 19.999999999999986,
|
||||
"y": 756
|
||||
},
|
||||
"5": {
|
||||
"data": {
|
||||
"advanced": {
|
||||
"customName": "write embedded bash script to vault",
|
||||
"customName": "add embedded bash script to vault",
|
||||
"customNameId": 0,
|
||||
"join": []
|
||||
},
|
||||
@@ -89,7 +89,7 @@
|
||||
"repoName": "community"
|
||||
},
|
||||
"functionId": 3,
|
||||
"functionName": "write_embedded_bash_script_to_vault",
|
||||
"functionName": "add_embedded_bash_script_to_vault",
|
||||
"id": "5",
|
||||
"selectMore": false,
|
||||
"type": "utility",
|
||||
@@ -241,9 +241,9 @@
|
||||
"errors": {},
|
||||
"id": "5",
|
||||
"type": "utility",
|
||||
"userCode": "\n bash_script = r\"\"\"\n#!/bin/bash\n \n# This script is part of the Splunk SOAR playbook called internal_host_ssh_log4j_investigate. It shows\n# the installed java version, lists any running java processes, performs a search for the JndiLookup.class\n# file in any .jar files found on disk, and searches any .war files for a log4j jar.\n\necho \"##############################################################\"\necho \"splunk_soar_internal_host_ssh_log4j_investigate.sh\"\necho \"##############################################################\"\necho \"\"\n\necho \"java environment configuration\" > java_environment.csv\necho \"[+] Checking Java version:\"\necho \"$(java -version)\"\necho \"java version:\" >> java_environment.csv\njava -version 2>> java_environment.csv\n\necho \"\"\necho \"[+] Checking running Java processes with ps:\"\necho \"$(ps aux | grep java)\"\necho \"ps java processes:\" >> java_environment.csv\necho \"$(ps aux | grep java)\" >> java_environment.csv\n\necho \"\"\necho \"[+] Checking running Java processes with jps:\"\necho \"$(jps -v)\"\necho \"jps java processes:\" >> java_environment.csv\necho \"$(jps -v)\" >> java_environment.csv\n\necho \"[+] Search .jar files for JndiLookup.class files ...\"\necho \"jar_file\" > jars_with_jndi.csv\nfind / 2>/dev/null -name '*.jar' -type f -print0 | xargs -0 grep JndiLookup.class | awk '{print $3}' | while read -r file\ndo\n if [ -f \"$file\" ]; then\n echo \"JndiLookup.class found in .jar file: $file\"\n echo \"$file\" >> jars_with_jndi.csv\n fi\ndone\n\necho \"\"\necho \"[+] Search .war files for log4j .jar files ...\"\necho \"war_file,jar_size,jar_time_modified,jar_file\" > wars_with_jars.csv\nfind / 2>/dev/null -name '*.war' -type f -print0 | xargs -0 grep log4j | awk '{print $3}' | while read -r war_file\ndo\n if [ -f \"$war_file\" ]; then\n unzip -l \"$war_file\" | grep log4j | awk '{print $1\",\" $2\" \"$3\",\"$4}' | while read -r jar_file\n do\n echo \".war file $war_file was found containing the file $jar_file\"\n echo \"$war_file,$jar_file\" >> wars_with_jars.csv\n done\n fi\ndone\n\necho \"[+] Zip up the outputs ...\"\nhostname=$1\nzip $(hostname)_ssh_log4j_output.zip java_environment.csv jars_with_jndi.csv wars_with_jars.csv\n\"\"\"\n \n file_name = 'splunk_soar_internal_host_ssh_log4j_investigate.sh'\n file_path = '/opt/phantom/vault/tmp/{}'.format(file_name)\n with open(file_path, 'w') as bash_script_file:\n bash_script_file.write(bash_script)\n \n success, message, vault_id = phantom.vault_add(file_location=file_path, file_name=file_name)\n parameters = [{'input_1': vault_id}]\n\n",
|
||||
"x": 980,
|
||||
"y": 524.5
|
||||
"userCode": "\n bash_script = r\"\"\"\n#!/bin/bash\n \n# This script is part of the Splunk SOAR playbook called internal_host_ssh_log4j_investigate. It shows\n# the installed java version, lists any running java processes, performs a search for the JndiLookup.class\n# file in any .jar files found on disk, and searches any .war files for a log4j jar. The output is a human-readable\n# log and a set of .csv files to be copied back to SOAR\n\necho \"##############################################################\"\necho \"splunk_soar_internal_host_ssh_log4j_investigate.sh\"\necho \"##############################################################\"\necho \"\"\n\necho \"java environment configuration\" > /tmp/java_environment.csv\necho \"[+] Checking Java version:\"\necho \"$(java -version)\"\necho \"java version:\" >> /tmp/java_environment.csv\njava -version 2>> /tmp/java_environment.csv\n\necho \"\"\necho \"[+] Checking running Java processes with ps:\"\necho \"$(ps aux | grep java)\"\necho \"ps java processes:\" >> /tmp/java_environment.csv\necho \"$(ps aux | grep java)\" >> /tmp/java_environment.csv\n\necho \"\"\necho \"[+] Checking running Java processes with jps:\"\necho \"$(jps -v)\"\necho \"jps java processes:\" >> /tmp/java_environment.csv\necho \"$(jps -v)\" >> /tmp/java_environment.csv\n\necho \"[+] Search .jar files for JndiLookup.class files ...\"\necho \"jar_files\" > /tmp/jars_with_jndi.csv\nfind / 2>/dev/null -name '*.jar' -type f -print0 | xargs -0 grep JndiLookup.class | awk '{print $3}' | while read -r file\ndo\n if [ -f \"$file\" ]; then\n echo \"JndiLookup.class found in .jar file: $file\"\n echo \"$file\" >> /tmp/jars_with_jndi.csv\n fi\ndone\n\necho \"\"\necho \"[+] Search .war files for log4j .jar files ...\"\necho \"war_file,jar_size,jar_time_modified,jar_file\" > /tmp/wars_with_jars.csv\nfind / 2>/dev/null -name '*.war' -type f -print0 | xargs -0 grep log4j | awk '{print $3}' | while read -r war_file\ndo\n if [ -f \"$war_file\" ]; then\n unzip -l \"$war_file\" | grep log4j | awk '{print $1\",\" $2\" \"$3\",\"$4}' | while read -r jar_file\n do\n echo \".war file $war_file was found containing the file $jar_file\"\n echo \"$war_file,$jar_file\" >> /tmp/wars_with_jars.csv\n done\n fi\ndone\n\necho \"[+] Zip up the outputs ...\"\nzip -j /tmp/$1_ssh_log4j_output.zip /tmp/java_environment.csv /tmp/jars_with_jndi.csv /tmp/wars_with_jars.csv\necho \"wrote zip file to /tmp/$1_ssh_log4j_output.zip; next we will copy it back to SOAR\"\n\"\"\"\n \n file_name = 'splunk_soar_internal_host_ssh_log4j_investigate.sh'\n file_path = '/opt/phantom/vault/tmp/{}'.format(file_name)\n with open(file_path, 'w') as bash_script_file:\n bash_script_file.write(bash_script)\n \n success, message, vault_id = phantom.vault_add(file_location=file_path, file_name=file_name)\n parameters = [{'input_1': vault_id}]\n\n",
|
||||
"x": -1.4210854715202004e-14,
|
||||
"y": 147.99999999999991
|
||||
},
|
||||
"6": {
|
||||
"data": {
|
||||
@@ -276,8 +276,8 @@
|
||||
"id": "6",
|
||||
"type": "action",
|
||||
"userCode": "\n # append the ip_hostname as an argument so it can be used in the output zip file name\n for parameter in parameters:\n parameter['command'] = parameter['command'] + ' ' + parameter['ip_hostname']\n\n",
|
||||
"x": 980,
|
||||
"y": 740
|
||||
"x": 0,
|
||||
"y": 460
|
||||
},
|
||||
"7": {
|
||||
"data": {
|
||||
@@ -300,7 +300,7 @@
|
||||
"parameters": {
|
||||
"file_destination": "/tmp/",
|
||||
"ip_hostname": "playbook_input:ip_or_hostname",
|
||||
"vault_id": "write_embedded_bash_script_to_vault:custom_function_result.data.*.item"
|
||||
"vault_id": "add_embedded_bash_script_to_vault:custom_function_result.data.*.item"
|
||||
},
|
||||
"requiredParameters": [
|
||||
"vault_id",
|
||||
@@ -312,8 +312,8 @@
|
||||
"errors": {},
|
||||
"id": "7",
|
||||
"type": "action",
|
||||
"x": 980,
|
||||
"y": 640
|
||||
"x": 0,
|
||||
"y": 312
|
||||
},
|
||||
"8": {
|
||||
"data": {
|
||||
@@ -346,12 +346,12 @@
|
||||
"errors": {},
|
||||
"id": "8",
|
||||
"type": "action",
|
||||
"userCode": "\n import re\n for parameter in parameters:\n parameter['file_path'] = '/tmp/' + parameter['file_path'] + '_ssh_log4j_output.zip'\n\n",
|
||||
"x": 980,
|
||||
"y": 864.5
|
||||
"userCode": "\n for parameter in parameters:\n parameter['file_path'] = '/tmp/' + parameter['file_path'] + '_ssh_log4j_output.zip'\n\n",
|
||||
"x": 0,
|
||||
"y": 608
|
||||
}
|
||||
},
|
||||
"notes": ""
|
||||
"notes": "This playbook uses the following Apps:\n - SSH (execute program, get file, put file) [asset name = ssh] - investigate behavior on the host\nDeployment Notes:\n - The ssh asset may require sudo access depending on permissions on the file system\n - The command line argument to the script is the ip_or_hostname, which will be used to name the zip file as output\n - To add a new data collection, pipe the output to a file and include it in the zip command"
|
||||
},
|
||||
"input_spec": [
|
||||
{
|
||||
@@ -369,7 +369,7 @@
|
||||
"schema": "5.0.4",
|
||||
"version": "5.1.0.70187"
|
||||
},
|
||||
"create_time": "2021-12-15T00:25:26.575505+00:00",
|
||||
"create_time": "2021-12-15T21:17:41.411838+00:00",
|
||||
"draft_mode": false,
|
||||
"labels": [
|
||||
"*"
|
||||
@@ -377,6 +377,7 @@
|
||||
"tags": [
|
||||
"internal_host",
|
||||
"unix",
|
||||
"log4j"
|
||||
"log4j",
|
||||
"investigate"
|
||||
]
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
Investigate an internal unix host using SSH. This pushes a bash script to the endpoint and runs it, collecting information specific to the December 2021 log4j vulnerability disclosure. This includes the java version installed on the host, any running java processes, and the results of a scan for the affected JndiLookup.class file or log4j .jar files.
|
||||
Published in response to CVE-2021-44228, \n this playbook investigates an internal unix host using SSH. This pushes a bash script to the endpoint and runs it, collecting information specific to the December 2021 log4j vulnerability disclosure. This includes the java version installed on the host, any running java processes, and the results of a scan for the affected JndiLookup.class file or log4j .jar files.
|
||||
"""
|
||||
|
||||
|
||||
@@ -11,13 +11,13 @@ from datetime import datetime, timedelta
|
||||
def on_start(container):
|
||||
phantom.debug('on_start() called')
|
||||
|
||||
# call 'write_embedded_bash_script_to_vault' block
|
||||
write_embedded_bash_script_to_vault(container=container)
|
||||
# call 'add_embedded_bash_script_to_vault' block
|
||||
add_embedded_bash_script_to_vault(container=container)
|
||||
|
||||
return
|
||||
|
||||
def write_embedded_bash_script_to_vault(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
|
||||
phantom.debug("write_embedded_bash_script_to_vault() called")
|
||||
def add_embedded_bash_script_to_vault(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
|
||||
phantom.debug("add_embedded_bash_script_to_vault() called")
|
||||
|
||||
parameters = []
|
||||
|
||||
@@ -43,58 +43,59 @@ def write_embedded_bash_script_to_vault(action=None, success=None, container=Non
|
||||
|
||||
# This script is part of the Splunk SOAR playbook called internal_host_ssh_log4j_investigate. It shows
|
||||
# the installed java version, lists any running java processes, performs a search for the JndiLookup.class
|
||||
# file in any .jar files found on disk, and searches any .war files for a log4j jar.
|
||||
# file in any .jar files found on disk, and searches any .war files for a log4j jar. The output is a human-readable
|
||||
# log and a set of .csv files to be copied back to SOAR
|
||||
|
||||
echo "##############################################################"
|
||||
echo "splunk_soar_internal_host_ssh_log4j_investigate.sh"
|
||||
echo "##############################################################"
|
||||
echo ""
|
||||
|
||||
echo "java environment configuration" > java_environment.csv
|
||||
echo "java environment configuration" > /tmp/java_environment.csv
|
||||
echo "[+] Checking Java version:"
|
||||
echo "$(java -version)"
|
||||
echo "java version:" >> java_environment.csv
|
||||
java -version 2>> java_environment.csv
|
||||
echo "java version:" >> /tmp/java_environment.csv
|
||||
java -version 2>> /tmp/java_environment.csv
|
||||
|
||||
echo ""
|
||||
echo "[+] Checking running Java processes with ps:"
|
||||
echo "$(ps aux | grep java)"
|
||||
echo "ps java processes:" >> java_environment.csv
|
||||
echo "$(ps aux | grep java)" >> java_environment.csv
|
||||
echo "ps java processes:" >> /tmp/java_environment.csv
|
||||
echo "$(ps aux | grep java)" >> /tmp/java_environment.csv
|
||||
|
||||
echo ""
|
||||
echo "[+] Checking running Java processes with jps:"
|
||||
echo "$(jps -v)"
|
||||
echo "jps java processes:" >> java_environment.csv
|
||||
echo "$(jps -v)" >> java_environment.csv
|
||||
echo "jps java processes:" >> /tmp/java_environment.csv
|
||||
echo "$(jps -v)" >> /tmp/java_environment.csv
|
||||
|
||||
echo "[+] Search .jar files for JndiLookup.class files ..."
|
||||
echo "jar_file" > jars_with_jndi.csv
|
||||
echo "jar_files" > /tmp/jars_with_jndi.csv
|
||||
find / 2>/dev/null -name '*.jar' -type f -print0 | xargs -0 grep JndiLookup.class | awk '{print $3}' | while read -r file
|
||||
do
|
||||
if [ -f "$file" ]; then
|
||||
echo "JndiLookup.class found in .jar file: $file"
|
||||
echo "$file" >> jars_with_jndi.csv
|
||||
echo "$file" >> /tmp/jars_with_jndi.csv
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "[+] Search .war files for log4j .jar files ..."
|
||||
echo "war_file,jar_size,jar_time_modified,jar_file" > wars_with_jars.csv
|
||||
echo "war_file,jar_size,jar_time_modified,jar_file" > /tmp/wars_with_jars.csv
|
||||
find / 2>/dev/null -name '*.war' -type f -print0 | xargs -0 grep log4j | awk '{print $3}' | while read -r war_file
|
||||
do
|
||||
if [ -f "$war_file" ]; then
|
||||
unzip -l "$war_file" | grep log4j | awk '{print $1"," $2" "$3","$4}' | while read -r jar_file
|
||||
do
|
||||
echo ".war file $war_file was found containing the file $jar_file"
|
||||
echo "$war_file,$jar_file" >> wars_with_jars.csv
|
||||
echo "$war_file,$jar_file" >> /tmp/wars_with_jars.csv
|
||||
done
|
||||
fi
|
||||
done
|
||||
|
||||
echo "[+] Zip up the outputs ..."
|
||||
hostname=$1
|
||||
zip $(hostname)_ssh_log4j_output.zip java_environment.csv jars_with_jndi.csv wars_with_jars.csv
|
||||
zip -j /tmp/$1_ssh_log4j_output.zip /tmp/java_environment.csv /tmp/jars_with_jndi.csv /tmp/wars_with_jars.csv
|
||||
echo "wrote zip file to /tmp/$1_ssh_log4j_output.zip; next we will copy it back to SOAR"
|
||||
"""
|
||||
|
||||
file_name = 'splunk_soar_internal_host_ssh_log4j_investigate.sh'
|
||||
@@ -109,7 +110,7 @@ zip $(hostname)_ssh_log4j_output.zip java_environment.csv jars_with_jndi.csv war
|
||||
## Custom Code End
|
||||
################################################################################
|
||||
|
||||
phantom.custom_function(custom_function="community/passthrough", parameters=parameters, name="write_embedded_bash_script_to_vault", callback=upload_bash_script)
|
||||
phantom.custom_function(custom_function="community/passthrough", parameters=parameters, name="add_embedded_bash_script_to_vault", callback=upload_bash_script)
|
||||
|
||||
return
|
||||
|
||||
@@ -153,17 +154,17 @@ def upload_bash_script(action=None, success=None, container=None, results=None,
|
||||
|
||||
# phantom.debug('Action: {0} {1}'.format(action['name'], ('SUCCEEDED' if success else 'FAILED')))
|
||||
|
||||
write_embedded_bash_script_to_vault_data = phantom.collect2(container=container, datapath=["write_embedded_bash_script_to_vault:custom_function_result.data.*.item"])
|
||||
add_embedded_bash_script_to_vault_data = phantom.collect2(container=container, datapath=["add_embedded_bash_script_to_vault:custom_function_result.data.*.item"])
|
||||
playbook_input_ip_or_hostname = phantom.collect2(container=container, datapath=["playbook_input:ip_or_hostname"])
|
||||
|
||||
parameters = []
|
||||
|
||||
# build parameters list for 'upload_bash_script' call
|
||||
for write_embedded_bash_script_to_vault_data_item in write_embedded_bash_script_to_vault_data:
|
||||
for add_embedded_bash_script_to_vault_data_item in add_embedded_bash_script_to_vault_data:
|
||||
for playbook_input_ip_or_hostname_item in playbook_input_ip_or_hostname:
|
||||
if write_embedded_bash_script_to_vault_data_item[0] is not None and playbook_input_ip_or_hostname_item[0] is not None:
|
||||
if add_embedded_bash_script_to_vault_data_item[0] is not None and playbook_input_ip_or_hostname_item[0] is not None:
|
||||
parameters.append({
|
||||
"vault_id": write_embedded_bash_script_to_vault_data_item[0],
|
||||
"vault_id": add_embedded_bash_script_to_vault_data_item[0],
|
||||
"ip_hostname": playbook_input_ip_or_hostname_item[0],
|
||||
"file_destination": "/tmp/",
|
||||
})
|
||||
@@ -196,15 +197,14 @@ def get_output_zip_file(action=None, success=None, container=None, results=None,
|
||||
for playbook_input_ip_or_hostname_item in playbook_input_ip_or_hostname:
|
||||
if playbook_input_ip_or_hostname_item[0] is not None:
|
||||
parameters.append({
|
||||
"ip_hostname": playbook_input_ip_or_hostname_item[0],
|
||||
"file_path": playbook_input_ip_or_hostname_item[0],
|
||||
"ip_hostname": playbook_input_ip_or_hostname_item[0],
|
||||
})
|
||||
|
||||
################################################################################
|
||||
## Custom Code Start
|
||||
################################################################################
|
||||
|
||||
import re
|
||||
for parameter in parameters:
|
||||
parameter['file_path'] = '/tmp/' + parameter['file_path'] + '_ssh_log4j_output.zip'
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ id: 49b2b88c-8e22-48a6-8808-ace1efcb194b
|
||||
version: 1
|
||||
date: '2021-12-14'
|
||||
author: Philip Royer, Splunk
|
||||
type: Investigate
|
||||
type: Investigation
|
||||
description: Investigate an internal unix host using SSH. This pushes a bash script to the endpoint and runs it, collecting information specific to the December 2021 log4j vulnerability disclosure. This includes the java version installed on the host, any running java processes, and the results of a scan for the affected JndiLookup.class file or log4j .jar files.
|
||||
playbook: internal_host_ssh_log4j_investigate
|
||||
how_to_implement: The ssh asset requires sudo access to scan the whole file system.
|
||||
|
||||
@@ -1,18 +1,11 @@
|
||||
{
|
||||
"blockly": false,
|
||||
"blockly_xml": "<xml></xml>",
|
||||
"category": "Use Cases",
|
||||
"category": "Threat Response",
|
||||
"coa": {
|
||||
"data": {
|
||||
"description": "Published in response to CVE-2021-44228, this playbook accepts a list of hosts and filenames to remediate on the endpoint. If filenames are provided, the endpoints will be searched and then the user can approve deletion. Then the user is prompted to quarantine the endpoint.",
|
||||
"edges": [
|
||||
{
|
||||
"id": "port_25_to_port_26",
|
||||
"sourceNode": "25",
|
||||
"sourcePort": "25_out",
|
||||
"targetNode": "26",
|
||||
"targetPort": "26_in"
|
||||
},
|
||||
{
|
||||
"conditions": [
|
||||
{
|
||||
@@ -225,7 +218,7 @@
|
||||
"targetPort": "9_in"
|
||||
}
|
||||
],
|
||||
"hash": "2a0cfc5974aa54de43e828455cf51d6e6b67031a",
|
||||
"hash": "9a556cf16898c397fac9fa7ecce677e29ea0ad8a",
|
||||
"nodes": {
|
||||
"0": {
|
||||
"data": {
|
||||
@@ -239,8 +232,8 @@
|
||||
"errors": {},
|
||||
"id": "0",
|
||||
"type": "start",
|
||||
"x": 370,
|
||||
"y": -8.526512829121202e-14
|
||||
"x": 530,
|
||||
"y": 0
|
||||
},
|
||||
"1": {
|
||||
"data": {
|
||||
@@ -256,8 +249,8 @@
|
||||
"id": "1",
|
||||
"type": "end",
|
||||
"userCode": "\n # This function is called after all actions are completed.\n # summary of all the action and/or all details of actions\n # can be collected here.\n\n # summary_json = phantom.get_summary()\n # if 'result' in summary_json:\n # for action_result in summary_json['result']:\n # if 'action_run_id' in action_result:\n # action_results = phantom.get_action_results(action_run_id=action_result['action_run_id'], result_data=False, flatten=False)\n # phantom.debug(action_results)\n\n",
|
||||
"x": 450,
|
||||
"y": 2845
|
||||
"x": 530,
|
||||
"y": 2730
|
||||
},
|
||||
"10": {
|
||||
"data": {
|
||||
@@ -286,8 +279,8 @@
|
||||
"id": "10",
|
||||
"type": "code",
|
||||
"userCode": " \n generate_deletion_commands__prompt_content = \"\"\n generate_deletion_commands__host = []\n generate_deletion_commands__deletion_command = []\n \n # Retrieve previously saved key\n host_dictionary = json.loads(phantom.get_run_data(key=\"host_dictionary\"))\n # Iterate through the paired host and locate files action result\n for hostname, action_result in zip(locate_files_parameter_ip_hostname, locate_files_result_item_1):\n\n # Ensure at least one file was found for that host\n if \"true\" in action_result.split(' '):\n \n # Attach this host to list of deletion hosts\n generate_deletion_commands__host.append(hostname)\n \n # Begin building deletion powershell script and prompt message\n deletion_string = f'''for i in '''\n generate_deletion_commands__prompt_content += f\"### {hostname}\\n\\n\"\n for filepath, result in zip(host_dictionary[hostname], action_result.split(' ')):\n if result == \"true\":\n generate_deletion_commands__prompt_content += f\"- {filepath}\\n\"\n deletion_string += f'\"{filepath}\" '\n \n # Remove trailing comma and attach one deletion command\n deletion_string = deletion_string.rstrip(',')\n deletion_string += '; do rm -v $i; done;'\n generate_deletion_commands__deletion_command.append(deletion_string)\n\n",
|
||||
"x": 260,
|
||||
"y": 691
|
||||
"x": 330,
|
||||
"y": 703
|
||||
},
|
||||
"12": {
|
||||
"data": {
|
||||
@@ -298,11 +291,11 @@
|
||||
"join": [],
|
||||
"note": "Prompt the user to confirm deletion"
|
||||
},
|
||||
"approver": "admin",
|
||||
"approver": "Incident Commander",
|
||||
"functionId": 3,
|
||||
"functionName": "deletion_confirmation",
|
||||
"id": "12",
|
||||
"message": "SOAR found results for the following files. Please review the returned list and confirm if they should be deleted.\n\n{0}",
|
||||
"message": "SOAR found results for the following files. Please review the returned list and confirm if they should be deleted.\n\n \n{0}",
|
||||
"parameters": [
|
||||
"generate_deletion_commands:custom_function:prompt_content"
|
||||
],
|
||||
@@ -319,8 +312,8 @@
|
||||
"errors": {},
|
||||
"id": "12",
|
||||
"type": "prompt",
|
||||
"x": 340,
|
||||
"y": 889
|
||||
"x": 410,
|
||||
"y": 911
|
||||
},
|
||||
"13": {
|
||||
"data": {
|
||||
@@ -371,8 +364,8 @@
|
||||
"errors": {},
|
||||
"id": "13",
|
||||
"type": "decision",
|
||||
"x": 340,
|
||||
"y": 1069
|
||||
"x": 410,
|
||||
"y": 1091
|
||||
},
|
||||
"14": {
|
||||
"data": {
|
||||
@@ -407,8 +400,8 @@
|
||||
"id": "14",
|
||||
"type": "action",
|
||||
"userCode": "\n parameters = []\n for del_cmd, host in zip(generate_deletion_commands__deletion_command, generate_deletion_commands__host):\n parameters.append({\n \"command\": del_cmd,\n \"ip_hostname\": host,\n })\n \n",
|
||||
"x": 170,
|
||||
"y": 1249
|
||||
"x": 230,
|
||||
"y": 1271
|
||||
},
|
||||
"15": {
|
||||
"data": {
|
||||
@@ -459,8 +452,8 @@
|
||||
"errors": {},
|
||||
"id": "15",
|
||||
"type": "decision",
|
||||
"x": 430,
|
||||
"y": 150
|
||||
"x": 590,
|
||||
"y": 154
|
||||
},
|
||||
"17": {
|
||||
"data": {
|
||||
@@ -488,8 +481,8 @@
|
||||
"errors": {},
|
||||
"id": "17",
|
||||
"type": "format",
|
||||
"x": 360,
|
||||
"y": 1580
|
||||
"x": 510,
|
||||
"y": 1614
|
||||
},
|
||||
"20": {
|
||||
"data": {
|
||||
@@ -504,11 +497,11 @@
|
||||
],
|
||||
"note": "Offer the user options to quarantine the affected endpoints"
|
||||
},
|
||||
"approver": "admin",
|
||||
"approver": "Incident Commander",
|
||||
"functionId": 2,
|
||||
"functionName": "quarantine_prompt",
|
||||
"id": "20",
|
||||
"message": "Choose an action you would like to take on the following hosts and then type confirm. The same selected action will be performed on every host.\n\n### Available Actions:\n- Restrict Outbound Traffic\n(Sets a firewall policy to prevent all outbound traffic. This may disrupt domain authentication for all but cached credentials.)\n- Shutdown Host (This is a forced shutdown)\n\n### Target Hosts\n{0}",
|
||||
"message": "Choose an action you would like to take on the following hosts and then type confirm. The same selected action will be performed on every host.\n\n \n### Available Actions:\n- Restrict Outbound Traffic\n(Sets a firewall policy to prevent all outbound traffic. This may disrupt domain authentication for all but cached credentials.)\n- Shutdown Host (This is a forced shutdown)\n\n \n### Target Hosts\n{0}",
|
||||
"parameters": [
|
||||
"format_host_list_prompt:formatted_data"
|
||||
],
|
||||
@@ -534,8 +527,8 @@
|
||||
"errors": {},
|
||||
"id": "20",
|
||||
"type": "prompt",
|
||||
"x": 440,
|
||||
"y": 1761
|
||||
"x": 590,
|
||||
"y": 1803
|
||||
},
|
||||
"23": {
|
||||
"data": {
|
||||
@@ -634,8 +627,8 @@
|
||||
"errors": {},
|
||||
"id": "23",
|
||||
"type": "decision",
|
||||
"x": 440,
|
||||
"y": 1941
|
||||
"x": 590,
|
||||
"y": 1983
|
||||
},
|
||||
"24": {
|
||||
"data": {
|
||||
@@ -669,8 +662,8 @@
|
||||
"errors": {},
|
||||
"id": "24",
|
||||
"type": "action",
|
||||
"x": 520,
|
||||
"y": 2302
|
||||
"x": 680,
|
||||
"y": 2163
|
||||
},
|
||||
"25": {
|
||||
"data": {
|
||||
@@ -705,7 +698,7 @@
|
||||
"id": "25",
|
||||
"type": "action",
|
||||
"x": 0,
|
||||
"y": 2121
|
||||
"y": 2163
|
||||
},
|
||||
"26": {
|
||||
"data": {
|
||||
@@ -738,8 +731,8 @@
|
||||
"errors": {},
|
||||
"id": "26",
|
||||
"type": "action",
|
||||
"x": 180,
|
||||
"y": 2317.5
|
||||
"x": 340,
|
||||
"y": 2180.5
|
||||
},
|
||||
"27": {
|
||||
"data": {
|
||||
@@ -845,7 +838,7 @@
|
||||
"content": "format_custom_note:custom_function:output",
|
||||
"note_format": "markdown",
|
||||
"note_type": "general",
|
||||
"title": "WinRm Log4j Response"
|
||||
"title": "SSH Log4j Response"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -853,8 +846,8 @@
|
||||
"id": "27",
|
||||
"type": "utility",
|
||||
"userCode": "\n\n",
|
||||
"x": 430,
|
||||
"y": 2664
|
||||
"x": 510,
|
||||
"y": 2541
|
||||
},
|
||||
"28": {
|
||||
"data": {
|
||||
@@ -909,8 +902,8 @@
|
||||
"errors": {},
|
||||
"id": "28",
|
||||
"type": "utility",
|
||||
"x": 360,
|
||||
"y": 1430
|
||||
"x": 510,
|
||||
"y": 1460
|
||||
},
|
||||
"32": {
|
||||
"data": {
|
||||
@@ -940,8 +933,8 @@
|
||||
"id": "32",
|
||||
"type": "code",
|
||||
"userCode": " format_custom_note__output = \"\"\n playbook_summary = phantom.get_summary()\n if 'result' in playbook_summary.keys() and playbook_summary['result']:\n for result_item in playbook_summary['result']:\n format_custom_note__output += f\"#### Action - {result_item['name']}: {result_item['message']}\\n\"\n # Generate app run summary for each action\n if 'app_runs' in result_item.keys() and result_item['app_runs']:\n for app_run_item in result_item['app_runs']:\n format_custom_note__output += f\"- app_run_id: {app_run_item['app_run_id']}\\n\"\n for k,v in app_run_item['parameter'].items():\n if k != 'context':\n format_custom_note__output += f\" - {k}: {v}\\n\"\n format_custom_note__output += f\"- summary: {app_run_item['summary']}\\n\"\n format_custom_note__output += \"\\n\"\n\n",
|
||||
"x": 430,
|
||||
"y": 2483
|
||||
"x": 510,
|
||||
"y": 2352
|
||||
},
|
||||
"4": {
|
||||
"data": {
|
||||
@@ -976,8 +969,8 @@
|
||||
"id": "4",
|
||||
"type": "action",
|
||||
"userCode": "\n parameters = []\n host_search = {}\n \n # Treat ip_or_hostname and filePath as a paired unit and iterate through them,\n # then create a dictionary for each host with a list of its filepaths\n for playbook_input_filepath_item, playbook_input_ip_or_hostname_item in zip(playbook_input_filepath, playbook_input_ip_or_hostname):\n if playbook_input_ip_or_hostname_item[0] in host_search.keys():\n host_search[playbook_input_ip_or_hostname_item[0]].append(playbook_input_filepath_item[0])\n else:\n host_search[playbook_input_ip_or_hostname_item[0]] = [playbook_input_filepath_item[0]]\n \n # Iterate through the host dictionary and generation one search string that checks all filepaths per host.\n # This ensures that we are only connecting to each host once.\n for k,v in host_search.items():\n script_str = f'''RESULT=\"\"; for i in \"{'\" \"'.join(v)}\"; do if [ -f \"$i\" ]; then RESULT=\"${{RESULT}} true\"; else RESULT=\"${{RESULT}} false\"; fi; done; echo $RESULT'''\n parameters.append({\n \"ip_hostname\": k,\n \"command\": script_str\n })\n \n # Save the host_search dictionary into temporary data to access downstream.\n # This makes it easier to compare the results of locate files with the filepaths and hosts\n phantom.save_run_data(value=json.dumps(host_search), key=\"host_dictionary\")\n \n",
|
||||
"x": 260,
|
||||
"y": 330
|
||||
"x": 420,
|
||||
"y": 334
|
||||
},
|
||||
"9": {
|
||||
"data": {
|
||||
@@ -1028,11 +1021,11 @@
|
||||
"errors": {},
|
||||
"id": "9",
|
||||
"type": "decision",
|
||||
"x": 340,
|
||||
"y": 511
|
||||
"x": 500,
|
||||
"y": 523
|
||||
}
|
||||
},
|
||||
"notes": ""
|
||||
"notes": "This playbook uses the following Apps:\n - SSH (execute program) [asset name = ssh] - respond on the host\nDeployment Notes:\n - The ssh asset may require sudo access depending on permissions on the file system\n - The prompts are assigned to Incident Commanders, but can be changed if needed"
|
||||
},
|
||||
"input_spec": [
|
||||
{
|
||||
@@ -1057,7 +1050,7 @@
|
||||
"schema": "5.0.4",
|
||||
"version": "5.1.0.70187"
|
||||
},
|
||||
"create_time": "2021-12-15T00:28:06.769849+00:00",
|
||||
"create_time": "2021-12-15T22:19:47.085179+00:00",
|
||||
"draft_mode": false,
|
||||
"labels": [
|
||||
"*"
|
||||
@@ -1065,6 +1058,7 @@
|
||||
"tags": [
|
||||
"unix",
|
||||
"internal_host",
|
||||
"log4j"
|
||||
"log4j",
|
||||
"respond"
|
||||
]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 183 KiB After Width: | Height: | Size: 206 KiB |
@@ -171,8 +171,8 @@ def deletion_confirmation(action=None, success=None, container=None, results=Non
|
||||
|
||||
# set user and message variables for phantom.prompt call
|
||||
|
||||
user = "admin"
|
||||
message = """SOAR found results for the following files. Please review the returned list and confirm if they should be deleted.\n\n{0}"""
|
||||
user = "Incident Commander"
|
||||
message = """SOAR found results for the following files. Please review the returned list and confirm if they should be deleted.\n\n \n{0}"""
|
||||
|
||||
# parameter list for template variable replacement
|
||||
parameters = [
|
||||
@@ -325,8 +325,8 @@ def quarantine_prompt(action=None, success=None, container=None, results=None, h
|
||||
|
||||
# set user and message variables for phantom.prompt call
|
||||
|
||||
user = "admin"
|
||||
message = """Choose an action you would like to take on the following hosts and then type confirm. The same selected action will be performed on every host.\n\n### Available Actions:\n- Restrict Outbound Traffic\n(Sets a firewall policy to prevent all outbound traffic. This may disrupt domain authentication for all but cached credentials.)\n- Shutdown Host (This is a forced shutdown)\n\n### Target Hosts\n{0}"""
|
||||
user = "Incident Commander"
|
||||
message = """Choose an action you would like to take on the following hosts and then type confirm. The same selected action will be performed on every host.\n\n \n### Available Actions:\n- Restrict Outbound Traffic\n(Sets a firewall policy to prevent all outbound traffic. This may disrupt domain authentication for all but cached credentials.)\n- Shutdown Host (This is a forced shutdown)\n\n \n### Target Hosts\n{0}"""
|
||||
|
||||
# parameter list for template variable replacement
|
||||
parameters = [
|
||||
@@ -406,7 +406,7 @@ def quarantine_decision(action=None, success=None, container=None, results=None,
|
||||
|
||||
# call connected blocks if condition 3 matched
|
||||
if found_match_3:
|
||||
join_block_and_shutdown(action=action, success=success, container=container, results=results, handle=handle)
|
||||
block_and_shutdown(action=action, success=success, container=container, results=results, handle=handle)
|
||||
return
|
||||
|
||||
# check for 'else' condition 4
|
||||
@@ -482,28 +482,7 @@ def shutdown(action=None, success=None, container=None, results=None, handle=Non
|
||||
## Custom Code End
|
||||
################################################################################
|
||||
|
||||
phantom.act("execute program", parameters=parameters, name="shutdown", assets=["ssh"], callback=shutdown_callback)
|
||||
|
||||
return
|
||||
|
||||
|
||||
def shutdown_callback(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
|
||||
phantom.debug("shutdown_callback() called")
|
||||
|
||||
|
||||
join_block_and_shutdown(action=action, success=success, container=container, results=results, handle=handle, filtered_artifacts=filtered_artifacts, filtered_results=filtered_results)
|
||||
join_format_custom_note(action=action, success=success, container=container, results=results, handle=handle, filtered_artifacts=filtered_artifacts, filtered_results=filtered_results)
|
||||
|
||||
|
||||
return
|
||||
|
||||
|
||||
def join_block_and_shutdown(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
|
||||
phantom.debug("join_block_and_shutdown() called")
|
||||
|
||||
if phantom.completed(action_names=["quarantine_prompt", "shutdown"]):
|
||||
# call connected block "block_and_shutdown"
|
||||
block_and_shutdown(container=container, handle=handle)
|
||||
phantom.act("execute program", parameters=parameters, name="shutdown", assets=["ssh"], callback=join_format_custom_note)
|
||||
|
||||
return
|
||||
|
||||
@@ -562,7 +541,7 @@ def summary_note(action=None, success=None, container=None, results=None, handle
|
||||
## Custom Code End
|
||||
################################################################################
|
||||
|
||||
phantom.add_note(container=container, content=format_custom_note__output, note_format="markdown", note_type="general", title="WinRm Log4j Response")
|
||||
phantom.add_note(container=container, content=format_custom_note__output, note_format="markdown", note_type="general", title="SSH Log4j Response")
|
||||
|
||||
return
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
name: Internal Host SSH Log4j Respond
|
||||
name: Internal Host SSH Log4j Response
|
||||
id: 6ea2007c-8ef8-4647-a4a4-7825cfee3866
|
||||
version: 1
|
||||
date: '2021-12-14'
|
||||
author: Kelby Shelton, Splunk
|
||||
type: Respond
|
||||
type: Response
|
||||
description: Published in response to CVE-2021-44228, this playbook accepts a list of hosts and filenames to remediate on the endpoint. If filenames are provided, the endpoints will be searched and then the user can approve deletion. Then the user is prompted to quarantine the endpoint.
|
||||
playbook: internal_host_ssh_log4j_respond
|
||||
how_to_implement: The ssh asset may require ssh access to delete some files depending on their permissions.
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"blockly": false,
|
||||
"blockly_xml": "<xml></xml>",
|
||||
"category": "Use Cases",
|
||||
"category": "Threat Response",
|
||||
"coa": {
|
||||
"data": {
|
||||
"description": "Performs a general investigation on key aspects of a windows device using windows remote management. Important files related to the endpoint are generated, bundled into a zip, and copied to the container vault.",
|
||||
"description": "Published in response to CVE-2021-44228, this playbook performs a general investigation on key aspects of a windows device using windows remote management. Important files related to the endpoint are generated, bundled into a zip, and copied to the container vault.",
|
||||
"edges": [
|
||||
{
|
||||
"id": "port_6_to_port_1",
|
||||
@@ -91,8 +91,8 @@
|
||||
"errors": {},
|
||||
"id": "0",
|
||||
"type": "start",
|
||||
"x": 20,
|
||||
"y": -1.1368683772161603e-13
|
||||
"x": 19.999999999999986,
|
||||
"y": -2.3803181647963356e-13
|
||||
},
|
||||
"1": {
|
||||
"data": {
|
||||
@@ -108,7 +108,7 @@
|
||||
"id": "1",
|
||||
"type": "end",
|
||||
"userCode": "\n # This function is called after all actions are completed.\n # summary of all the action and/or all details of actions\n # can be collected here.\n\n # summary_json = phantom.get_summary()\n # if 'result' in summary_json:\n # for action_result in summary_json['result']:\n # if 'action_run_id' in action_result:\n # action_results = phantom.get_action_results(action_run_id=action_result['action_run_id'], result_data=False, flatten=False)\n # phantom.debug(action_results)\n\n",
|
||||
"x": 20,
|
||||
"x": 19.999999999999986,
|
||||
"y": 1360
|
||||
},
|
||||
"10": {
|
||||
@@ -293,7 +293,7 @@
|
||||
"id": "6",
|
||||
"type": "action",
|
||||
"userCode": "\n parameters = []\n\n # build parameters list for 'remove_data_capture_files' call\n for playbook_input_ip_or_hostname_item, formatted_part in zip(playbook_input_ip_or_hostname, format_file_removal__as_list):\n parameters.append({\n \"script_str\": formatted_part,\n \"ip_hostname\": playbook_input_ip_or_hostname_item[0],\n })\n\n",
|
||||
"x": -9.947598300641403e-14,
|
||||
"x": -1.2789769243681803e-13,
|
||||
"y": 1200
|
||||
},
|
||||
"7": {
|
||||
@@ -348,7 +348,7 @@
|
||||
"id": "8",
|
||||
"type": "action",
|
||||
"x": -5.684341886080802e-14,
|
||||
"y": 99.99999999999994
|
||||
"y": 99.99999999999989
|
||||
},
|
||||
"9": {
|
||||
"data": {
|
||||
@@ -383,7 +383,7 @@
|
||||
"y": 240
|
||||
}
|
||||
},
|
||||
"notes": ""
|
||||
"notes": "This playbook uses the following Apps:\n - Windows Remote Management (list_processes, list_connections, list_sessions, run_script, get_file) [asset name = winrm] - Used to collect details on a windows host\n\nDeployment Notes:\n- Playbook should receive a list of IPs and/or Hostnames\n- Ensure Splunk SOAR has access to the target windows hosts using the credentials in the configured winrm asset\n- Many of the collection queries can be adapted to query EDR vendors using their app"
|
||||
},
|
||||
"input_spec": [
|
||||
{
|
||||
@@ -401,13 +401,15 @@
|
||||
"schema": "5.0.4",
|
||||
"version": "5.1.0.70187"
|
||||
},
|
||||
"create_time": "2021-12-15T00:19:14.002156+00:00",
|
||||
"create_time": "2021-12-15T21:16:58.966416+00:00",
|
||||
"draft_mode": false,
|
||||
"labels": [
|
||||
"*"
|
||||
],
|
||||
"tags": [
|
||||
"windows",
|
||||
"internal_host"
|
||||
"internal_host",
|
||||
"investigate",
|
||||
"log4j"
|
||||
]
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
Performs a general investigation on key aspects of a windows device using windows remote management. Important files related to the endpoint are generated, bundled into a zip, and copied to the container vault.
|
||||
Published in response to CVE-2021-44228, this playbook performs a general investigation on key aspects of a windows device using windows remote management. Important files related to the endpoint are generated, bundled into a zip, and copied to the container vault.
|
||||
"""
|
||||
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ id: 32fd9db5-5201-4a2f-b2c2-9299c7b3495d
|
||||
version: 1
|
||||
date: '2021-12-14'
|
||||
author: Kelby Shelton, Splunk
|
||||
type: Investigate
|
||||
type: Investigation
|
||||
description: Performs a general investigation on key aspects of a windows device using windows remote management. Important files related to the endpoint are generated, bundled into a zip, and copied to the container vault.
|
||||
playbook: internal_host_winrm_investigate
|
||||
how_to_implement: The winrm asset requires Administrator access to gather certain files.
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"blockly": false,
|
||||
"blockly_xml": "<xml></xml>",
|
||||
"category": "Use Cases",
|
||||
"category": "Threat Response",
|
||||
"coa": {
|
||||
"data": {
|
||||
"description": "Published in response to CVE-2021-44228, this playbook scans the endpoint for the presence of \"jndilookup.class\" in all .jar files. The presence of that string could indicate a log4j vulnerability.",
|
||||
"description": "Published in response to CVE-2021-44228, this playbook scans the endpoint for the presence of \"jndilookup.class\" in all .jar, .jsp, and .jspx files. The presence of that string could indicate a log4j vulnerability.",
|
||||
"edges": [
|
||||
{
|
||||
"id": "port_0_to_port_3",
|
||||
@@ -47,7 +47,7 @@
|
||||
"targetPort": "5_in"
|
||||
}
|
||||
],
|
||||
"hash": "6be22bd4339e81de35e935fd531dd37b71c5e6a6",
|
||||
"hash": "dc9b961c3c246a0016bdc7d5b58d593b06e97eac",
|
||||
"nodes": {
|
||||
"0": {
|
||||
"data": {
|
||||
@@ -61,8 +61,8 @@
|
||||
"errors": {},
|
||||
"id": "0",
|
||||
"type": "start",
|
||||
"x": 840,
|
||||
"y": 359.99999999999955
|
||||
"x": 19.999999999999986,
|
||||
"y": -1.5987211554602254e-13
|
||||
},
|
||||
"1": {
|
||||
"data": {
|
||||
@@ -78,8 +78,8 @@
|
||||
"id": "1",
|
||||
"type": "end",
|
||||
"userCode": "\n # This function is called after all actions are completed.\n # summary of all the action and/or all details of actions\n # can be collected here.\n\n # summary_json = phantom.get_summary()\n # if 'result' in summary_json:\n # for action_result in summary_json['result']:\n # if 'action_run_id' in action_result:\n # action_results = phantom.get_action_results(action_run_id=action_result['action_run_id'], result_data=False, flatten=False)\n # phantom.debug(action_results)\n\n",
|
||||
"x": 840,
|
||||
"y": 1000
|
||||
"x": 19.999999999999986,
|
||||
"y": 832
|
||||
},
|
||||
"3": {
|
||||
"data": {
|
||||
@@ -103,7 +103,7 @@
|
||||
"id": "3",
|
||||
"parameters": {
|
||||
"ip_hostname": "playbook_input:ip_or_hostname",
|
||||
"script_str": "$ProgressPreference = 'SilentlyContinue'; Get-PSDrive -PSProvider FileSystem | foreach {(gci ($_.Root) -rec -force -include ('*.war', '*.jsp', '*.jspx', '*.jar') -ea 0 | foreach {select-string \"JndiLookup.class\" $_} | Select-Object -Property LineNumber, Path )} | ConvertTo-Json"
|
||||
"script_str": "$ProgressPreference = 'SilentlyContinue'; Get-PSDrive -PSProvider FileSystem | foreach {(gci ($_.Root) -rec -force -include ('*.jsp', '*.jspx', '*.jar') -ea 0 | foreach {select-string \"JndiLookup.class\" $_} | Select-Object -Property LineNumber, Path )} | ConvertTo-Json"
|
||||
},
|
||||
"requiredParameters": [],
|
||||
"type": "action"
|
||||
@@ -111,8 +111,8 @@
|
||||
"errors": {},
|
||||
"id": "3",
|
||||
"type": "action",
|
||||
"x": 820,
|
||||
"y": 480
|
||||
"x": 0,
|
||||
"y": 148
|
||||
},
|
||||
"5": {
|
||||
"data": {
|
||||
@@ -139,8 +139,8 @@
|
||||
"id": "5",
|
||||
"type": "code",
|
||||
"userCode": " custom_format__note_content = \"\"\n for script_result_item, ip_hostname in zip(run_traversal_script_result_item_0, playbook_input_ip_or_hostname_values):\n try:\n custom_format__note_content += f\"### Device - {ip_hostname}\\n\"\n custom_format__note_content += \" | Path | LineNumber |\\n\"\n custom_format__note_content += \"| --- | --- |\\n\"\n result_to_json = json.loads(script_result_item)\n for json_result in result_to_json:\n custom_format__note_content += f\"| {json_result['Path']} | {json_result['LineNumber']} |\\n\"\n custom_format__note_content += \"\\n \"\n except:\n phantom.error(\"Unable to parse JSON\")\n",
|
||||
"x": 820,
|
||||
"y": 780
|
||||
"x": 0,
|
||||
"y": 506
|
||||
},
|
||||
"6": {
|
||||
"data": {
|
||||
@@ -243,8 +243,8 @@
|
||||
"errors": {},
|
||||
"id": "6",
|
||||
"type": "utility",
|
||||
"x": 820,
|
||||
"y": 904.5
|
||||
"x": 0,
|
||||
"y": 684
|
||||
},
|
||||
"7": {
|
||||
"data": {
|
||||
@@ -280,11 +280,11 @@
|
||||
"errors": {},
|
||||
"id": "7",
|
||||
"type": "decision",
|
||||
"x": 900,
|
||||
"y": 620
|
||||
"x": 80,
|
||||
"y": 326
|
||||
}
|
||||
},
|
||||
"notes": "Based on \"https://twitter.com/CyberRaiju/status/1469505677580124160\""
|
||||
"notes": "This playbook uses the following Apps:\n - Windows Remote Management (run_script) [asset name = winrm] - Used to traverse files on a windows host for the presence of a specific string\n\nDeployment Notes:\n- Playbook should receive a list of IPs and/or Hostnames\n- Ensure Splunk SOAR has administrator access to the target windows hosts in order to have access to all files\n- The traversal string can be adapted to query EDR vendors\n- Based on https://twitter.com/CyberRaiju/status/1469505677580124160"
|
||||
},
|
||||
"input_spec": [
|
||||
{
|
||||
@@ -302,7 +302,7 @@
|
||||
"schema": "5.0.4",
|
||||
"version": "5.1.0.70187"
|
||||
},
|
||||
"create_time": "2021-12-15T00:24:05.504834+00:00",
|
||||
"create_time": "2021-12-15T15:17:57.452281+00:00",
|
||||
"draft_mode": false,
|
||||
"labels": [
|
||||
"*"
|
||||
@@ -310,6 +310,7 @@
|
||||
"tags": [
|
||||
"windows",
|
||||
"internal_host",
|
||||
"log4j"
|
||||
"log4j",
|
||||
"investigate"
|
||||
]
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
Published in response to CVE-2021-44228, this playbook scans the endpoint for the presence of "jndilookup.class" in all .jar files. The presence of that string could indicate a log4j vulnerability.
|
||||
Published in response to CVE-2021-44228, this playbook scans the endpoint for the presence of "jndilookup.class" in all .jar, .jsp, and .jspx files. The presence of that string could indicate a log4j vulnerability.
|
||||
"""
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ def run_traversal_script(action=None, success=None, container=None, results=None
|
||||
# build parameters list for 'run_traversal_script' call
|
||||
for playbook_input_ip_or_hostname_item in playbook_input_ip_or_hostname:
|
||||
parameters.append({
|
||||
"script_str": "$ProgressPreference = 'SilentlyContinue'; Get-PSDrive -PSProvider FileSystem | foreach {(gci ($_.Root) -rec -force -include ('*.war', '*.jsp', '*.jspx', '*.jar') -ea 0 | foreach {select-string \"JndiLookup.class\" $_} | Select-Object -Property LineNumber, Path )} | ConvertTo-Json",
|
||||
"script_str": "$ProgressPreference = 'SilentlyContinue'; Get-PSDrive -PSProvider FileSystem | foreach {(gci ($_.Root) -rec -force -include ('*.jsp', '*.jspx', '*.jar') -ea 0 | foreach {select-string \"JndiLookup.class\" $_} | Select-Object -Property LineNumber, Path )} | ConvertTo-Json",
|
||||
"ip_hostname": playbook_input_ip_or_hostname_item[0],
|
||||
})
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ id: 2cf7c9f4-b273-44f6-a27c-e0db668ff05a
|
||||
version: 1
|
||||
date: '2021-12-14'
|
||||
author: Kelby Shelton, Splunk
|
||||
type: Investigate
|
||||
type: Investigation
|
||||
description: Published in response to CVE-2021-44228, this playbook uses WinRM to scan Windows endpoints for the presence of "jndilookup.class" in all .jar files. The presence of that string could indicate a log4j vulnerability.
|
||||
playbook: internal_host_winrm_log4j_investigate
|
||||
how_to_implement: The winrm asset requires Administrator access to scan the whole file system.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"blockly": false,
|
||||
"blockly_xml": "<xml></xml>",
|
||||
"category": "Use Cases",
|
||||
"category": "Threat Response",
|
||||
"coa": {
|
||||
"data": {
|
||||
"description": "Published in response to CVE-2021-44228, this playbook accepts a list of hosts and filenames to remediate on the endpoint. If filenames are provided, the endpoints will be searched and then the user can approve deletion. Then the user is prompted to quarantine the endpoint.",
|
||||
@@ -218,7 +218,7 @@
|
||||
"targetPort": "32_in"
|
||||
}
|
||||
],
|
||||
"hash": "b66aad637ff2a4e0d549f8b7d1b646cb01dbdff4",
|
||||
"hash": "a88fc0fd29ee20bd765f76240ad874b0b64198bf",
|
||||
"nodes": {
|
||||
"0": {
|
||||
"data": {
|
||||
@@ -233,7 +233,7 @@
|
||||
"id": "0",
|
||||
"type": "start",
|
||||
"x": 530,
|
||||
"y": -1.2789769243681803e-13
|
||||
"y": -3.836930773104541e-13
|
||||
},
|
||||
"1": {
|
||||
"data": {
|
||||
@@ -278,7 +278,7 @@
|
||||
"errors": {},
|
||||
"id": "10",
|
||||
"type": "code",
|
||||
"userCode": " \n generate_deletion_commands__prompt_content = \"\"\n generate_deletion_commands__host = []\n generate_deletion_commands__deletion_command = []\n \n # Retrieve previously saved key\n host_dictionary = json.loads(phantom.get_run_data(key=\"host_dictionary\"))\n \n # Iterate through the paired host and locate files action result\n for hostname, action_result in zip(locate_files_parameter_ip_hostname, locate_files_result_item_1):\n \n # Ensure at least one file was found for that host\n if True in json.loads(action_result):\n \n # Attach this host to list of deletion hosts\n generate_deletion_commands__host.append(hostname)\n \n # Begin building deletion powershell script and prompt message\n deletion_string = \"Remove-Item \"\n generate_deletion_commands__prompt_content += f\"### {hostname}\\n\\n\"\n for filepath, result in zip(host_dictionary[hostname], json.loads(action_result)):\n if result == True:\n generate_deletion_commands__prompt_content += f\"- {filepath}\\n\"\n deletion_string += f\"{filepath},\"\n \n # Remove trailing comma and attach one deletion command\n deletion_string = deletion_string.rstrip(',')\n deletion_string += ' | Write-Output \"Files Deleted\"'\n generate_deletion_commands__deletion_command.append(deletion_string)\n\n",
|
||||
"userCode": " \n generate_deletion_commands__prompt_content = \"\"\n generate_deletion_commands__host = []\n generate_deletion_commands__deletion_command = []\n \n # Retrieve previously saved key\n host_dictionary = json.loads(phantom.get_run_data(key=\"host_dictionary\"))\n \n # Iterate through the paired host and locate files action result\n for hostname, action_result in zip(locate_files_parameter_ip_hostname, locate_files_result_item_1):\n \n json_action_result = json.loads(action_result)\n \n if isinstance(json_action_result, bool):\n json_action_result = [json_action_result]\n \n # Ensure at least one file was found for that host\n if any(json_action_result):\n \n # Attach this host to list of deletion hosts\n generate_deletion_commands__host.append(hostname)\n \n # Begin building deletion powershell script and prompt message\n deletion_string = \"$ProgressPreference = 'SilentlyContinue'; Remove-Item \"\n generate_deletion_commands__prompt_content += f\"### {hostname}\\n\\n\"\n for filepath, result in zip(host_dictionary[hostname], json_action_result):\n if result == True:\n generate_deletion_commands__prompt_content += f\"- {filepath}\\n\"\n deletion_string += f\"{filepath},\"\n \n # Remove trailing comma and attach one deletion command\n deletion_string = deletion_string.rstrip(',')\n deletion_string += ' | Write-Output \"Files Deleted\"'\n generate_deletion_commands__deletion_command.append(deletion_string)\n\n",
|
||||
"x": 330,
|
||||
"y": 686
|
||||
},
|
||||
@@ -291,11 +291,11 @@
|
||||
"join": [],
|
||||
"note": "Prompt the user to confirm deletion"
|
||||
},
|
||||
"approver": "admin",
|
||||
"approver": "Incident Commander",
|
||||
"functionId": 3,
|
||||
"functionName": "deletion_confirmation",
|
||||
"id": "12",
|
||||
"message": "SOAR found results for the following files. Please review the returned list and confirm if they should be deleted.\n\n{0}",
|
||||
"message": "SOAR found results for the following files. Please review the returned list and confirm if they should be deleted.\n\n \n{0}",
|
||||
"parameters": [
|
||||
"generate_deletion_commands:custom_function:prompt_content"
|
||||
],
|
||||
@@ -495,11 +495,11 @@
|
||||
],
|
||||
"note": "Offer the user options to quarantine the affected endpoints"
|
||||
},
|
||||
"approver": "admin",
|
||||
"approver": "Incident Commander",
|
||||
"functionId": 2,
|
||||
"functionName": "quarantine_prompt",
|
||||
"id": "20",
|
||||
"message": "Choose an action you would like to take on the following hosts and then type confirm. The same selected action will be performed on every host.\n\n### Available Actions:\n- Restrict Outbound Traffic\n(Sets a firewall policy to prevent all outbound traffic. This may disrupt domain authentication for all but cached credentials.)\n- Shutdown Host (This is a forced shutdown)\n\n### Target Hosts\n{0}",
|
||||
"message": "Choose an action you would like to take on the following hosts and then type confirm. The same selected action will be performed on every host.\n\n \n### Available Actions:\n- Restrict Outbound Traffic\n(Sets a firewall policy to prevent all outbound traffic. This may disrupt domain authentication for all but cached credentials.)\n- Shutdown Host (This is a forced shutdown)\n\n \n### Target Hosts\n{0}",
|
||||
"parameters": [
|
||||
"format_host_list_prompt:formatted_data"
|
||||
],
|
||||
@@ -650,7 +650,7 @@
|
||||
"id": "24",
|
||||
"parameters": {
|
||||
"ip_hostname": "dedup_hostnames:custom_function_result.data.*.item",
|
||||
"script_str": "New-NetFirewallRule -DisplayName Splunk_SOAR_Quarantine -Direction Outbound -Enabled True -LocalPort Any -RemoteAddress Any -Action Block"
|
||||
"script_str": "$ProgressPreference = 'SilentlyContinue'; New-NetFirewallRule -DisplayName Splunk_SOAR_Quarantine -Direction Outbound -Enabled True -LocalPort Any -RemoteAddress Any -Action Block"
|
||||
},
|
||||
"requiredParameters": [],
|
||||
"type": "action"
|
||||
@@ -683,7 +683,7 @@
|
||||
"id": "25",
|
||||
"parameters": {
|
||||
"ip_hostname": "dedup_hostnames:custom_function_result.data.*.item",
|
||||
"script_str": "shutdown /f"
|
||||
"script_str": "$ProgressPreference = 'SilentlyContinue'; shutdown /f"
|
||||
},
|
||||
"requiredParameters": [],
|
||||
"type": "action"
|
||||
@@ -715,7 +715,7 @@
|
||||
"id": "26",
|
||||
"parameters": {
|
||||
"ip_hostname": "dedup_hostnames:custom_function_result.data.*.item",
|
||||
"script_str": "New-NetFirewallRule -DisplayName Splunk_SOAR_Quarantine -Direction Outbound -Enabled True -LocalPort Any -RemoteAddress Any -Action Block; shutdown /f"
|
||||
"script_str": "$ProgressPreference = 'SilentlyContinue'; New-NetFirewallRule -DisplayName Splunk_SOAR_Quarantine -Direction Outbound -Enabled True -LocalPort Any -RemoteAddress Any -Action Block; shutdown /f"
|
||||
},
|
||||
"requiredParameters": [],
|
||||
"type": "action"
|
||||
@@ -958,7 +958,7 @@
|
||||
"errors": {},
|
||||
"id": "4",
|
||||
"type": "action",
|
||||
"userCode": "\n parameters = []\n host_search = {}\n \n # Treat ip_or_hostname and filePath as a paired unit and iterate through them,\n # then create a dictionary for each host with a list of its filepaths\n for playbook_input_filepath_item, playbook_input_ip_or_hostname_item in zip(playbook_input_filepath, playbook_input_ip_or_hostname):\n if playbook_input_ip_or_hostname_item[0] in host_search.keys():\n host_search[playbook_input_ip_or_hostname_item[0]].append(playbook_input_filepath_item[0])\n else:\n host_search[playbook_input_ip_or_hostname_item[0]] = [playbook_input_filepath_item[0]]\n \n # Iterate through the host dictionary and generation one search string that checks all filepaths per host.\n # This ensures that we are only connecting to each host once.\n for k,v in host_search.items():\n script_str = f\"Test-Path {','.join(v)} | ConvertTo-Json\"\n parameters.append({\n \"ip_hostname\": k,\n \"script_str\": script_str\n })\n \n # Save the host_search dictionary into temporary data to access downstream.\n # This makes it easier to compare the results of locate files with the filepaths and hosts\n phantom.save_run_data(value=json.dumps(host_search), key=\"host_dictionary\")\n \n",
|
||||
"userCode": "\n parameters = []\n host_search = {}\n \n # Treat ip_or_hostname and filePath as a paired unit and iterate through them,\n # then create a dictionary for each host with a list of its filepaths\n for playbook_input_filepath_item, playbook_input_ip_or_hostname_item in zip(playbook_input_filepath, playbook_input_ip_or_hostname):\n if playbook_input_ip_or_hostname_item[0] in host_search.keys():\n host_search[playbook_input_ip_or_hostname_item[0]].append(playbook_input_filepath_item[0])\n else:\n host_search[playbook_input_ip_or_hostname_item[0]] = [playbook_input_filepath_item[0]]\n \n # Iterate through the host dictionary and generation one search string that checks all filepaths per host.\n # This ensures that we are only connecting to each host once.\n for k,v in host_search.items():\n script_str = f\"$ProgressPreference = 'SilentlyContinue'; Test-Path {','.join(v)} | ConvertTo-Json\"\n parameters.append({\n \"ip_hostname\": k,\n \"script_str\": script_str\n })\n \n # Save the host_search dictionary into temporary data to access downstream.\n # This makes it easier to compare the results of locate files with the filepaths and hosts\n phantom.save_run_data(value=json.dumps(host_search), key=\"host_dictionary\")\n \n",
|
||||
"x": 420,
|
||||
"y": 328
|
||||
},
|
||||
@@ -1015,7 +1015,7 @@
|
||||
"y": 506
|
||||
}
|
||||
},
|
||||
"notes": ""
|
||||
"notes": "This playbook uses the following Apps:\n - Windows Remote Management (run_script) [asset name = winrm] - Used to query for files, delete files, block network access, and shutdown host.\n\nDeployment Notes:\n- Playbook should receive a list of IPs and/or Hostnames. An optional list of file paths should be provided. \n- If multiple file paths are provided per host, there must be multiples of the hostname to ensure both inputs are of equal length.\n- Ensure Splunk SOAR has administrator access to the target windows hosts in order to perform all of the actions\n- Search and delete functionality, along with quarantine functionality can be adapted to similar actions from EDR vendors"
|
||||
},
|
||||
"input_spec": [
|
||||
{
|
||||
@@ -1040,7 +1040,7 @@
|
||||
"schema": "5.0.4",
|
||||
"version": "5.1.0.70187"
|
||||
},
|
||||
"create_time": "2021-12-15T00:26:33.188209+00:00",
|
||||
"create_time": "2021-12-15T20:17:50.689218+00:00",
|
||||
"draft_mode": false,
|
||||
"labels": [
|
||||
"*"
|
||||
@@ -1048,6 +1048,7 @@
|
||||
"tags": [
|
||||
"internal_host",
|
||||
"windows",
|
||||
"log4j"
|
||||
"log4j",
|
||||
"respond"
|
||||
]
|
||||
}
|
||||
@@ -56,7 +56,7 @@ def locate_files(action=None, success=None, container=None, results=None, handle
|
||||
# Iterate through the host dictionary and generation one search string that checks all filepaths per host.
|
||||
# This ensures that we are only connecting to each host once.
|
||||
for k,v in host_search.items():
|
||||
script_str = f"Test-Path {','.join(v)} | ConvertTo-Json"
|
||||
script_str = f"$ProgressPreference = 'SilentlyContinue'; Test-Path {','.join(v)} | ConvertTo-Json"
|
||||
parameters.append({
|
||||
"ip_hostname": k,
|
||||
"script_str": script_str
|
||||
@@ -130,16 +130,21 @@ def generate_deletion_commands(action=None, success=None, container=None, result
|
||||
# Iterate through the paired host and locate files action result
|
||||
for hostname, action_result in zip(locate_files_parameter_ip_hostname, locate_files_result_item_1):
|
||||
|
||||
# Ensure at least one file was found for that host
|
||||
if True in json.loads(action_result):
|
||||
json_action_result = json.loads(action_result)
|
||||
|
||||
if isinstance(json_action_result, bool):
|
||||
json_action_result = [json_action_result]
|
||||
|
||||
# Ensure at least one file was found for that host
|
||||
if any(json_action_result):
|
||||
|
||||
# Attach this host to list of deletion hosts
|
||||
generate_deletion_commands__host.append(hostname)
|
||||
|
||||
# Begin building deletion powershell script and prompt message
|
||||
deletion_string = "Remove-Item "
|
||||
deletion_string = "$ProgressPreference = 'SilentlyContinue'; Remove-Item "
|
||||
generate_deletion_commands__prompt_content += f"### {hostname}\n\n"
|
||||
for filepath, result in zip(host_dictionary[hostname], json.loads(action_result)):
|
||||
for filepath, result in zip(host_dictionary[hostname], json_action_result):
|
||||
if result == True:
|
||||
generate_deletion_commands__prompt_content += f"- {filepath}\n"
|
||||
deletion_string += f"{filepath},"
|
||||
@@ -171,8 +176,8 @@ def deletion_confirmation(action=None, success=None, container=None, results=Non
|
||||
|
||||
# set user and message variables for phantom.prompt call
|
||||
|
||||
user = "admin"
|
||||
message = """SOAR found results for the following files. Please review the returned list and confirm if they should be deleted.\n\n{0}"""
|
||||
user = "Incident Commander"
|
||||
message = """SOAR found results for the following files. Please review the returned list and confirm if they should be deleted.\n\n \n{0}"""
|
||||
|
||||
# parameter list for template variable replacement
|
||||
parameters = [
|
||||
@@ -324,8 +329,8 @@ def quarantine_prompt(action=None, success=None, container=None, results=None, h
|
||||
|
||||
# set user and message variables for phantom.prompt call
|
||||
|
||||
user = "admin"
|
||||
message = """Choose an action you would like to take on the following hosts and then type confirm. The same selected action will be performed on every host.\n\n### Available Actions:\n- Restrict Outbound Traffic\n(Sets a firewall policy to prevent all outbound traffic. This may disrupt domain authentication for all but cached credentials.)\n- Shutdown Host (This is a forced shutdown)\n\n### Target Hosts\n{0}"""
|
||||
user = "Incident Commander"
|
||||
message = """Choose an action you would like to take on the following hosts and then type confirm. The same selected action will be performed on every host.\n\n \n### Available Actions:\n- Restrict Outbound Traffic\n(Sets a firewall policy to prevent all outbound traffic. This may disrupt domain authentication for all but cached credentials.)\n- Shutdown Host (This is a forced shutdown)\n\n \n### Target Hosts\n{0}"""
|
||||
|
||||
# parameter list for template variable replacement
|
||||
parameters = [
|
||||
@@ -430,7 +435,7 @@ def block_outbound_traffic(action=None, success=None, container=None, results=No
|
||||
# build parameters list for 'block_outbound_traffic' call
|
||||
for dedup_hostnames_data_item in dedup_hostnames_data:
|
||||
parameters.append({
|
||||
"script_str": "New-NetFirewallRule -DisplayName Splunk_SOAR_Quarantine -Direction Outbound -Enabled True -LocalPort Any -RemoteAddress Any -Action Block",
|
||||
"script_str": "$ProgressPreference = 'SilentlyContinue'; New-NetFirewallRule -DisplayName Splunk_SOAR_Quarantine -Direction Outbound -Enabled True -LocalPort Any -RemoteAddress Any -Action Block",
|
||||
"ip_hostname": dedup_hostnames_data_item[0],
|
||||
})
|
||||
|
||||
@@ -465,7 +470,7 @@ def shutdown(action=None, success=None, container=None, results=None, handle=Non
|
||||
# build parameters list for 'shutdown' call
|
||||
for dedup_hostnames_data_item in dedup_hostnames_data:
|
||||
parameters.append({
|
||||
"script_str": "shutdown /f",
|
||||
"script_str": "$ProgressPreference = 'SilentlyContinue'; shutdown /f",
|
||||
"ip_hostname": dedup_hostnames_data_item[0],
|
||||
})
|
||||
|
||||
@@ -500,7 +505,7 @@ def block_and_shutdown(action=None, success=None, container=None, results=None,
|
||||
# build parameters list for 'block_and_shutdown' call
|
||||
for dedup_hostnames_data_item in dedup_hostnames_data:
|
||||
parameters.append({
|
||||
"script_str": "New-NetFirewallRule -DisplayName Splunk_SOAR_Quarantine -Direction Outbound -Enabled True -LocalPort Any -RemoteAddress Any -Action Block; shutdown /f",
|
||||
"script_str": "$ProgressPreference = 'SilentlyContinue'; New-NetFirewallRule -DisplayName Splunk_SOAR_Quarantine -Direction Outbound -Enabled True -LocalPort Any -RemoteAddress Any -Action Block; shutdown /f",
|
||||
"ip_hostname": dedup_hostnames_data_item[0],
|
||||
})
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ id: 32fd9db5-5201-4b2f-b2c2-9299c7b3495d
|
||||
version: 1
|
||||
date: '2021-12-14'
|
||||
author: Kelby Shelton, Splunk
|
||||
type: Investigate
|
||||
type: Investigation
|
||||
description: Published in response to CVE-2021-44228, this playbook accepts a list of hosts and filenames to remediate on the endpoint. If filenames are provided, the endpoints will be searched and then the user can approve deletion. Then the user is prompted to quarantine the endpoint.
|
||||
playbook: internal_host_winrm_log4j_respond
|
||||
how_to_implement: The winrm asset requires Administrator access to gather certain files.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"blockly": false,
|
||||
"blockly_xml": "<xml></xml>",
|
||||
"category": "Use Cases",
|
||||
"category": "Threat Response",
|
||||
"coa": {
|
||||
"data": {
|
||||
"description": "Published in response to CVE-2021-44228, this playbook and its sub-playbooks can be used to investigate and respond to attacks against hosts running vulnerable Java applications which use log4j.",
|
||||
@@ -21,69 +21,116 @@
|
||||
"targetPort": "19_in"
|
||||
},
|
||||
{
|
||||
"conditions": [
|
||||
{
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
"id": "port_20_to_port_21",
|
||||
"sourceNode": "20",
|
||||
"sourcePort": "20_out",
|
||||
"targetNode": "21",
|
||||
"targetPort": "21_in"
|
||||
},
|
||||
{
|
||||
"conditions": [
|
||||
{
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
"id": "port_20_to_port_22",
|
||||
"sourceNode": "20",
|
||||
"sourcePort": "20_out",
|
||||
"targetNode": "22",
|
||||
"targetPort": "22_in"
|
||||
},
|
||||
{
|
||||
"conditions": [
|
||||
{
|
||||
"index": 1
|
||||
}
|
||||
],
|
||||
"id": "port_20_to_port_23",
|
||||
"sourceNode": "20",
|
||||
"sourcePort": "20_out",
|
||||
"targetNode": "23",
|
||||
"targetPort": "23_in"
|
||||
},
|
||||
{
|
||||
"conditions": [
|
||||
{
|
||||
"index": 1
|
||||
}
|
||||
],
|
||||
"id": "port_20_to_port_24",
|
||||
"sourceNode": "20",
|
||||
"sourcePort": "20_out",
|
||||
"targetNode": "24",
|
||||
"targetPort": "24_in"
|
||||
},
|
||||
{
|
||||
"id": "port_19_to_port_25",
|
||||
"id": "port_19_to_port_26",
|
||||
"sourceNode": "19",
|
||||
"sourcePort": "19_out",
|
||||
"targetNode": "25",
|
||||
"targetPort": "25_in"
|
||||
"targetNode": "26",
|
||||
"targetPort": "26_in"
|
||||
},
|
||||
{
|
||||
"id": "port_25_to_port_20",
|
||||
"sourceNode": "25",
|
||||
"sourcePort": "25_out",
|
||||
"id": "port_30_to_port_1",
|
||||
"sourceNode": "30",
|
||||
"sourcePort": "30_out",
|
||||
"targetNode": "1",
|
||||
"targetPort": "1_in"
|
||||
},
|
||||
{
|
||||
"conditions": [
|
||||
{
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
"id": "port_26_to_port_31",
|
||||
"sourceNode": "26",
|
||||
"sourcePort": "26_out",
|
||||
"targetNode": "31",
|
||||
"targetPort": "31_in"
|
||||
},
|
||||
{
|
||||
"id": "port_31_to_port_20",
|
||||
"sourceNode": "31",
|
||||
"sourcePort": "31_out",
|
||||
"targetNode": "20",
|
||||
"targetPort": "20_in"
|
||||
},
|
||||
{
|
||||
"conditions": [
|
||||
{
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
"id": "port_20_to_port_32",
|
||||
"sourceNode": "20",
|
||||
"sourcePort": "20_out",
|
||||
"targetNode": "32",
|
||||
"targetPort": "32_in"
|
||||
},
|
||||
{
|
||||
"conditions": [
|
||||
{
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
"id": "port_20_to_port_33",
|
||||
"sourceNode": "20",
|
||||
"sourcePort": "20_out",
|
||||
"targetNode": "33",
|
||||
"targetPort": "33_in"
|
||||
},
|
||||
{
|
||||
"conditions": [
|
||||
{
|
||||
"index": 1
|
||||
}
|
||||
],
|
||||
"id": "port_20_to_port_34",
|
||||
"sourceNode": "20",
|
||||
"sourcePort": "20_out",
|
||||
"targetNode": "34",
|
||||
"targetPort": "34_in"
|
||||
},
|
||||
{
|
||||
"conditions": [
|
||||
{
|
||||
"index": 1
|
||||
}
|
||||
],
|
||||
"id": "port_20_to_port_35",
|
||||
"sourceNode": "20",
|
||||
"sourcePort": "20_out",
|
||||
"targetNode": "35",
|
||||
"targetPort": "35_in"
|
||||
},
|
||||
{
|
||||
"id": "port_32_to_port_30",
|
||||
"sourceNode": "32",
|
||||
"sourcePort": "32_out",
|
||||
"targetNode": "30",
|
||||
"targetPort": "30_in"
|
||||
},
|
||||
{
|
||||
"id": "port_33_to_port_30",
|
||||
"sourceNode": "33",
|
||||
"sourcePort": "33_out",
|
||||
"targetNode": "30",
|
||||
"targetPort": "30_in"
|
||||
},
|
||||
{
|
||||
"id": "port_35_to_port_30",
|
||||
"sourceNode": "35",
|
||||
"sourcePort": "35_out",
|
||||
"targetNode": "30",
|
||||
"targetPort": "30_in"
|
||||
},
|
||||
{
|
||||
"id": "port_34_to_port_30",
|
||||
"sourceNode": "34",
|
||||
"sourcePort": "34_out",
|
||||
"targetNode": "30",
|
||||
"targetPort": "30_in"
|
||||
}
|
||||
],
|
||||
"hash": "78e9b912032aa57c87befaa5f816a31d048407ed",
|
||||
"hash": "3032f16dc2f5fabcbb60995a4ce913641ef73af7",
|
||||
"nodes": {
|
||||
"0": {
|
||||
"data": {
|
||||
@@ -97,8 +144,8 @@
|
||||
"errors": {},
|
||||
"id": "0",
|
||||
"type": "start",
|
||||
"x": 530,
|
||||
"y": -7.815970093361102e-14
|
||||
"x": 540,
|
||||
"y": 100
|
||||
},
|
||||
"1": {
|
||||
"data": {
|
||||
@@ -114,8 +161,8 @@
|
||||
"id": "1",
|
||||
"type": "end",
|
||||
"userCode": "\n # This function is called after all actions are completed.\n # summary of all the action and/or all details of actions\n # can be collected here.\n\n # summary_json = phantom.get_summary()\n # if 'result' in summary_json:\n # for action_result in summary_json['result']:\n # if 'action_run_id' in action_result:\n # action_results = phantom.get_action_results(action_run_id=action_result['action_run_id'], result_data=False, flatten=False)\n # phantom.debug(action_results)\n\n",
|
||||
"x": 830,
|
||||
"y": -7.815970093361102e-14
|
||||
"x": 540,
|
||||
"y": 1120
|
||||
},
|
||||
"15": {
|
||||
"data": {
|
||||
@@ -282,16 +329,17 @@
|
||||
"errors": {},
|
||||
"id": "15",
|
||||
"type": "utility",
|
||||
"userCode": "\n # use custom code to read a custom list of potential log4j hosts and/or ip addresses\n # and make a json to create an artifact for each one.\n # the expected format of the custom list is:\n # hostname1 | unix\n # 1.1.1.1 | windows\n \n # TODO remove this testing piece which deletes existing artifacts\n container_artifact_data = phantom.collect2(container=container, datapath=[\"artifact:*.id\",\"artifact:*.id\"])\n container_artifact_header_item_0 = [item[0] for item in container_artifact_data]\n for artifact_id in container_artifact_header_item_0:\n phantom.delete_artifact(artifact_id=artifact_id)\n \n \n # TODO: bring in a test splunk notable and clean up the fields in that as well\n\n custom_list_name = parameters[0]['input_1']\n \n success, message, rows = phantom.get_list(list_name=custom_list_name)\n \n # loop through the rows and create a list of artifact jsons to add\n # the two columns are expected to be the ip_or_hostname and the operating system family\n parameters = []\n unix_hosts = []\n windows_hosts = []\n unknown_hosts = []\n for row in rows:\n if row[0]:\n if row[1] != 'unix' and row[1] != 'windows':\n os_family = 'unknown'\n else:\n os_family = row[1]\n \n parameters.append({'input_1': {'cef_data': {'deviceHostname': row[0], 'operatingSystemFamily': os_family}}})\n \n\n",
|
||||
"x": 510,
|
||||
"y": 152
|
||||
"userCode": "\n # use custom code to read a custom list of potential log4j hosts and/or ip addresses\n # and make a json to create an artifact for each one.\n # the expected format of the custom list is:\n # hostname1 | unix\n # 1.1.1.1 | windows\n\n custom_list_name = parameters[0]['input_1']\n \n success, message, rows = phantom.get_list(list_name=custom_list_name)\n \n # return early if the list is not found\n if not success:\n phantom.debug(\"Failed to find the custom list, so only existing artifacts will be used\")\n phantom.custom_function(custom_function=\"community/passthrough\", parameters=[], name=\"enumerate_hosts\", callback=create_host_artifacts)\n return\n \n # loop through the rows and create a list of artifact jsons to add\n # the two columns are expected to be the ip_or_hostname and the operating system family\n parameters = []\n unix_hosts = []\n windows_hosts = []\n unknown_hosts = []\n for row in rows:\n if row[0]:\n if row[1] != 'unix' and row[1] != 'windows':\n os_family = 'unknown'\n else:\n os_family = row[1]\n \n artifact_dict = {\n 'cef_data': {\n 'deviceHostname': row[0],\n 'operatingSystemFamily': os_family},\n 'field_mapping': {\n 'deviceHostname': ['host name', 'ip']}}\n parameters.append({'input_1': artifact_dict})\n\n",
|
||||
"x": 520,
|
||||
"y": 220
|
||||
},
|
||||
"19": {
|
||||
"data": {
|
||||
"advanced": {
|
||||
"customName": "create artifacts",
|
||||
"customName": "create host artifacts",
|
||||
"customNameId": 0,
|
||||
"join": []
|
||||
"join": [],
|
||||
"scope": "all"
|
||||
},
|
||||
"customFunction": {
|
||||
"draftMode": false,
|
||||
@@ -299,7 +347,7 @@
|
||||
"repoName": "community"
|
||||
},
|
||||
"functionId": 9,
|
||||
"functionName": "create_artifacts",
|
||||
"functionName": "create_host_artifacts",
|
||||
"id": "19",
|
||||
"selectMore": false,
|
||||
"type": "utility",
|
||||
@@ -427,7 +475,7 @@
|
||||
"container": "container:id",
|
||||
"input_json": "enumerate_hosts:custom_function_result.data.*.item",
|
||||
"label": null,
|
||||
"name": "Potential log4j Host",
|
||||
"name": "potential log4j host",
|
||||
"run_automation": null,
|
||||
"severity": "high",
|
||||
"tags": null
|
||||
@@ -437,9 +485,9 @@
|
||||
"errors": {},
|
||||
"id": "19",
|
||||
"type": "utility",
|
||||
"userCode": "\n",
|
||||
"x": 510,
|
||||
"y": 304
|
||||
"userCode": "\n # only operate on parameters with input_json, because thats where the data should be\n parameters = [parameter for parameter in parameters if parameter['input_json']]\n if parameters == []:\n \n # if there are no new artifacts and no existing artifacts with deviceHostname, then stop the playbook\n relevant_artifacts = phantom.collect2(container=container, datapath=[\"artifact:*.cef.deviceHostname\"])\n if relevant_artifacts == []:\n phantom.comment(comment=\"No ip_or_hostname values to operate on; playbook discontinued\")\n phantom.error(\"No ip_or_hostname values to operate on; playbook discontinued\")\n phantom.discontinue()\n \n",
|
||||
"x": 520,
|
||||
"y": 340
|
||||
},
|
||||
"20": {
|
||||
"data": {
|
||||
@@ -508,137 +556,75 @@
|
||||
"errors": {},
|
||||
"id": "20",
|
||||
"type": "filter",
|
||||
"x": 570,
|
||||
"y": 625
|
||||
"x": 580,
|
||||
"y": 720
|
||||
},
|
||||
"21": {
|
||||
"26": {
|
||||
"data": {
|
||||
"advanced": {
|
||||
"customName": "if hosts exist",
|
||||
"customNameId": 0,
|
||||
"description": "Only proceed if ip_or_hostname values exist in artifact deviceHostname fields. Without these potential log4j hosts, this playbook cannot do anything.",
|
||||
"join": [],
|
||||
"scope": "all"
|
||||
},
|
||||
"functionId": 2,
|
||||
"functionName": "playbook_internal_host_ssh_investigate_2",
|
||||
"id": "21",
|
||||
"inputs": {
|
||||
"ip_or_hostname": {
|
||||
"datapaths": [
|
||||
"filtered-data:os_filter:condition_1:artifact:*.cef.deviceHostname"
|
||||
"conditions": [
|
||||
{
|
||||
"comparisons": [
|
||||
{
|
||||
"conditionIndex": 0,
|
||||
"op": "!=",
|
||||
"param": "artifact:*.cef.deviceHostname",
|
||||
"value": ""
|
||||
}
|
||||
],
|
||||
"deduplicate": true
|
||||
"conditionIndex": 0,
|
||||
"customName": "hosts_exist",
|
||||
"logic": "and"
|
||||
}
|
||||
},
|
||||
"playbookName": "internal_host_ssh_investigate",
|
||||
"playbookRepo": 2,
|
||||
"playbookRepoName": "local",
|
||||
"playbookType": "data",
|
||||
"synchronous": true,
|
||||
"type": "playbook"
|
||||
],
|
||||
"functionId": 2,
|
||||
"functionName": "if_hosts_exist",
|
||||
"id": "26",
|
||||
"type": "filter"
|
||||
},
|
||||
"errors": {},
|
||||
"id": "21",
|
||||
"type": "playbook",
|
||||
"x": 340,
|
||||
"y": 805
|
||||
"id": "26",
|
||||
"type": "filter",
|
||||
"x": 580,
|
||||
"y": 440
|
||||
},
|
||||
"22": {
|
||||
"data": {
|
||||
"advanced": {
|
||||
"join": [],
|
||||
"scope": "all"
|
||||
},
|
||||
"functionId": 2,
|
||||
"functionName": "playbook_internal_host_ssh_log4j_investigate_2",
|
||||
"id": "22",
|
||||
"inputs": {
|
||||
"ip_or_hostname": {
|
||||
"datapaths": [
|
||||
"filtered-data:os_filter:condition_1:artifact:*.cef.deviceHostname"
|
||||
],
|
||||
"deduplicate": true
|
||||
}
|
||||
},
|
||||
"playbookName": "internal_host_ssh_log4j_investigate",
|
||||
"playbookRepo": 2,
|
||||
"playbookRepoName": "local",
|
||||
"playbookType": "data",
|
||||
"synchronous": true,
|
||||
"type": "playbook"
|
||||
},
|
||||
"errors": {},
|
||||
"id": "22",
|
||||
"type": "playbook",
|
||||
"x": 0,
|
||||
"y": 805
|
||||
},
|
||||
"23": {
|
||||
"data": {
|
||||
"advanced": {
|
||||
"join": [],
|
||||
"scope": "all"
|
||||
},
|
||||
"functionId": 2,
|
||||
"functionName": "playbook_internal_host_winrm_investigate_2",
|
||||
"id": "23",
|
||||
"inputs": {
|
||||
"ip_or_hostname": {
|
||||
"datapaths": [
|
||||
"filtered-data:os_filter:condition_2:artifact:*.cef.deviceHostname"
|
||||
],
|
||||
"deduplicate": true
|
||||
}
|
||||
},
|
||||
"playbookName": "internal_host_winrm_investigate",
|
||||
"playbookRepo": 2,
|
||||
"playbookRepoName": "local",
|
||||
"playbookType": "data",
|
||||
"synchronous": true,
|
||||
"type": "playbook"
|
||||
},
|
||||
"errors": {},
|
||||
"id": "23",
|
||||
"type": "playbook",
|
||||
"x": 680,
|
||||
"y": 805
|
||||
},
|
||||
"24": {
|
||||
"data": {
|
||||
"advanced": {
|
||||
"join": [],
|
||||
"scope": "all"
|
||||
},
|
||||
"functionId": 2,
|
||||
"functionName": "playbook_internal_host_winrm_log4j_investigate_2",
|
||||
"id": "24",
|
||||
"inputs": {
|
||||
"ip_or_hostname": {
|
||||
"datapaths": [
|
||||
"filtered-data:os_filter:condition_2:artifact:*.cef.deviceHostname"
|
||||
],
|
||||
"deduplicate": true
|
||||
}
|
||||
},
|
||||
"playbookName": "internal_host_winrm_log4j_investigate",
|
||||
"playbookRepo": 2,
|
||||
"playbookRepoName": "local",
|
||||
"playbookType": "data",
|
||||
"synchronous": true,
|
||||
"type": "playbook"
|
||||
},
|
||||
"errors": {},
|
||||
"id": "24",
|
||||
"type": "playbook",
|
||||
"x": 1020,
|
||||
"y": 805
|
||||
},
|
||||
"25": {
|
||||
"30": {
|
||||
"data": {
|
||||
"advanced": {
|
||||
"join": []
|
||||
},
|
||||
"functionId": 1,
|
||||
"functionName": "playbook_internal_host_splunk_investigate_log4j_1",
|
||||
"id": "25",
|
||||
"functionName": "playbook_log4j_respond_1",
|
||||
"id": "30",
|
||||
"inputs": {},
|
||||
"playbookName": "log4j_respond",
|
||||
"playbookRepo": 1,
|
||||
"playbookRepoName": "community",
|
||||
"playbookType": "automation",
|
||||
"synchronous": false,
|
||||
"type": "playbook"
|
||||
},
|
||||
"errors": {},
|
||||
"id": "30",
|
||||
"type": "playbook",
|
||||
"x": 520,
|
||||
"y": 1020
|
||||
},
|
||||
"31": {
|
||||
"data": {
|
||||
"advanced": {
|
||||
"join": [],
|
||||
"scope": "all"
|
||||
},
|
||||
"functionId": 2,
|
||||
"functionName": "playbook_internal_host_splunk_investigate_log4j_2",
|
||||
"id": "31",
|
||||
"inputs": {
|
||||
"ip_or_hostname": {
|
||||
"datapaths": [
|
||||
@@ -648,20 +634,140 @@
|
||||
}
|
||||
},
|
||||
"playbookName": "internal_host_splunk_investigate_log4j",
|
||||
"playbookRepo": 2,
|
||||
"playbookRepoName": "local",
|
||||
"playbookRepo": 1,
|
||||
"playbookRepoName": "community",
|
||||
"playbookType": "data",
|
||||
"synchronous": true,
|
||||
"type": "playbook"
|
||||
},
|
||||
"errors": {},
|
||||
"id": "25",
|
||||
"id": "31",
|
||||
"type": "playbook",
|
||||
"x": 520,
|
||||
"y": 404.5
|
||||
"y": 600
|
||||
},
|
||||
"32": {
|
||||
"data": {
|
||||
"advanced": {
|
||||
"join": [],
|
||||
"scope": "all"
|
||||
},
|
||||
"functionId": 1,
|
||||
"functionName": "playbook_internal_host_ssh_log4j_investigate_1",
|
||||
"id": "32",
|
||||
"inputs": {
|
||||
"ip_or_hostname": {
|
||||
"datapaths": [
|
||||
"filtered-data:os_filter:condition_1:artifact:*.cef.deviceHostname"
|
||||
],
|
||||
"deduplicate": true
|
||||
}
|
||||
},
|
||||
"playbookName": "internal_host_ssh_log4j_investigate",
|
||||
"playbookRepo": 1,
|
||||
"playbookRepoName": "community",
|
||||
"playbookType": "data",
|
||||
"synchronous": true,
|
||||
"type": "playbook"
|
||||
},
|
||||
"errors": {},
|
||||
"id": "32",
|
||||
"type": "playbook",
|
||||
"x": 120,
|
||||
"y": 860
|
||||
},
|
||||
"33": {
|
||||
"data": {
|
||||
"advanced": {
|
||||
"join": [],
|
||||
"scope": "all"
|
||||
},
|
||||
"functionId": 1,
|
||||
"functionName": "playbook_internal_host_ssh_investigate_1",
|
||||
"id": "33",
|
||||
"inputs": {
|
||||
"ip_or_hostname": {
|
||||
"datapaths": [
|
||||
"filtered-data:os_filter:condition_1:artifact:*.cef.deviceHostname"
|
||||
],
|
||||
"deduplicate": true
|
||||
}
|
||||
},
|
||||
"playbookName": "internal_host_ssh_investigate",
|
||||
"playbookRepo": 1,
|
||||
"playbookRepoName": "community",
|
||||
"playbookType": "data",
|
||||
"synchronous": true,
|
||||
"type": "playbook"
|
||||
},
|
||||
"errors": {},
|
||||
"id": "33",
|
||||
"type": "playbook",
|
||||
"x": 380,
|
||||
"y": 880
|
||||
},
|
||||
"34": {
|
||||
"data": {
|
||||
"advanced": {
|
||||
"join": [],
|
||||
"scope": "all"
|
||||
},
|
||||
"functionId": 1,
|
||||
"functionName": "playbook_internal_host_winrm_log4j_investigate_1",
|
||||
"id": "34",
|
||||
"inputs": {
|
||||
"ip_or_hostname": {
|
||||
"datapaths": [
|
||||
"filtered-data:os_filter:condition_2:artifact:*.cef.deviceHostname"
|
||||
],
|
||||
"deduplicate": true
|
||||
}
|
||||
},
|
||||
"playbookName": "internal_host_winrm_log4j_investigate",
|
||||
"playbookRepo": 1,
|
||||
"playbookRepoName": "community",
|
||||
"playbookType": "data",
|
||||
"synchronous": true,
|
||||
"type": "playbook"
|
||||
},
|
||||
"errors": {},
|
||||
"id": "34",
|
||||
"type": "playbook",
|
||||
"x": 960,
|
||||
"y": 860
|
||||
},
|
||||
"35": {
|
||||
"data": {
|
||||
"advanced": {
|
||||
"join": [],
|
||||
"scope": "all"
|
||||
},
|
||||
"functionId": 1,
|
||||
"functionName": "playbook_internal_host_winrm_investigate_1",
|
||||
"id": "35",
|
||||
"inputs": {
|
||||
"ip_or_hostname": {
|
||||
"datapaths": [
|
||||
"filtered-data:os_filter:condition_2:artifact:*.cef.deviceHostname"
|
||||
],
|
||||
"deduplicate": true
|
||||
}
|
||||
},
|
||||
"playbookName": "internal_host_winrm_investigate",
|
||||
"playbookRepo": 1,
|
||||
"playbookRepoName": "community",
|
||||
"playbookType": "data",
|
||||
"synchronous": true,
|
||||
"type": "playbook"
|
||||
},
|
||||
"errors": {},
|
||||
"id": "35",
|
||||
"type": "playbook",
|
||||
"x": 680,
|
||||
"y": 880
|
||||
}
|
||||
},
|
||||
"notes": "TODO: bring in a test splunk notable and add a host from that to the custom list in enumerate_hosts\n\n\n* In the block called \"fetch_hosts_from_custom_list\", change the custom list name from \"log4j_hosts\" if needed \n* If the operating system family (\"windows\" or \"unix\") is not known, both ssh and winrm will be attempted\n* If ssh and/or winrm are not the preferred endpoint management methods, these playbooks could be ported to use Google's GRR, osquery, CrowdStrike's RTR, Carbon Black's EDR API, or similar tools.\n\n* The artifact scope \"all\" is used throughout this playbook because the artifact list can be added to as the playbook progresses."
|
||||
"notes": "* To use this playbook, create a custom list called \"log4j_hosts\" with a format like:\n\n| hostname1 | unix |\n| hostname2 | windows |\n\n* The first column should be an IP or hostname of a potentially affected log4j host, and the second should be the operating system family (either unix or windows). If the operating system is unknown it can be left blank.\n* In the block called \"fetch_hosts_from_custom_list\", change the custom list name from \"log4j_hosts\" if needed \n* If the operating system family (\"windows\" or \"unix\") is not known, both ssh and winrm will be attempted\n* If ssh and/or winrm are not the preferred endpoint management methods, these playbooks could be ported to use Google's GRR, osquery, CrowdStrike's RTR, Carbon Black's EDR API, or similar tools.\n* The artifact scope \"all\" is used throughout this playbook because the artifact list can be added to as the playbook progresses.\n * If a sub-playbook needs to be changed it should be copied from the community repository to a local repository and the reference in the parent playbook should be changed to the new repository"
|
||||
},
|
||||
"input_spec": null,
|
||||
"output_spec": null,
|
||||
@@ -670,10 +776,13 @@
|
||||
"schema": "5.0.4",
|
||||
"version": "5.1.0.70187"
|
||||
},
|
||||
"create_time": "2021-12-14T20:29:14.791691+00:00",
|
||||
"create_time": "2021-12-15T22:50:34.136664+00:00",
|
||||
"draft_mode": false,
|
||||
"labels": [
|
||||
"*"
|
||||
"events"
|
||||
],
|
||||
"tags": []
|
||||
"tags": [
|
||||
"log4j",
|
||||
"investigate"
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 252 KiB |
@@ -43,20 +43,17 @@ def enumerate_hosts(action=None, success=None, container=None, results=None, han
|
||||
# the expected format of the custom list is:
|
||||
# hostname1 | unix
|
||||
# 1.1.1.1 | windows
|
||||
|
||||
# TODO remove this testing piece which deletes existing artifacts
|
||||
container_artifact_data = phantom.collect2(container=container, datapath=["artifact:*.id","artifact:*.id"])
|
||||
container_artifact_header_item_0 = [item[0] for item in container_artifact_data]
|
||||
for artifact_id in container_artifact_header_item_0:
|
||||
phantom.delete_artifact(artifact_id=artifact_id)
|
||||
|
||||
|
||||
# TODO: bring in a test splunk notable and clean up the fields in that as well
|
||||
|
||||
custom_list_name = parameters[0]['input_1']
|
||||
|
||||
success, message, rows = phantom.get_list(list_name=custom_list_name)
|
||||
|
||||
# return early if the list is not found
|
||||
if not success:
|
||||
phantom.debug("Failed to find the custom list, so only existing artifacts will be used")
|
||||
phantom.custom_function(custom_function="community/passthrough", parameters=[], name="enumerate_hosts", callback=create_host_artifacts)
|
||||
return
|
||||
|
||||
# loop through the rows and create a list of artifact jsons to add
|
||||
# the two columns are expected to be the ip_or_hostname and the operating system family
|
||||
parameters = []
|
||||
@@ -70,30 +67,35 @@ def enumerate_hosts(action=None, success=None, container=None, results=None, han
|
||||
else:
|
||||
os_family = row[1]
|
||||
|
||||
parameters.append({'input_1': {'cef_data': {'deviceHostname': row[0], 'operatingSystemFamily': os_family}}})
|
||||
|
||||
artifact_dict = {
|
||||
'cef_data': {
|
||||
'deviceHostname': row[0],
|
||||
'operatingSystemFamily': os_family},
|
||||
'field_mapping': {
|
||||
'deviceHostname': ['host name', 'ip']}}
|
||||
parameters.append({'input_1': artifact_dict})
|
||||
|
||||
################################################################################
|
||||
## Custom Code End
|
||||
################################################################################
|
||||
|
||||
phantom.custom_function(custom_function="community/passthrough", parameters=parameters, name="enumerate_hosts", callback=create_artifacts)
|
||||
phantom.custom_function(custom_function="community/passthrough", parameters=parameters, name="enumerate_hosts", callback=create_host_artifacts)
|
||||
|
||||
return
|
||||
|
||||
|
||||
def create_artifacts(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
|
||||
phantom.debug("create_artifacts() called")
|
||||
def create_host_artifacts(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
|
||||
phantom.debug("create_host_artifacts() called")
|
||||
|
||||
id_value = container.get("id", None)
|
||||
enumerate_hosts_data = phantom.collect2(container=container, datapath=["enumerate_hosts:custom_function_result.data.*.item"])
|
||||
enumerate_hosts_data = phantom.collect2(container=container, datapath=["enumerate_hosts:custom_function_result.data.*.item"], scope="all")
|
||||
|
||||
parameters = []
|
||||
|
||||
# build parameters list for 'create_artifacts' call
|
||||
# build parameters list for 'create_host_artifacts' call
|
||||
for enumerate_hosts_data_item in enumerate_hosts_data:
|
||||
parameters.append({
|
||||
"name": "Potential log4j Host",
|
||||
"name": "potential log4j host",
|
||||
"tags": None,
|
||||
"label": None,
|
||||
"severity": "high",
|
||||
@@ -109,11 +111,22 @@ def create_artifacts(action=None, success=None, container=None, results=None, ha
|
||||
## Custom Code Start
|
||||
################################################################################
|
||||
|
||||
# only operate on parameters with input_json, because thats where the data should be
|
||||
parameters = [parameter for parameter in parameters if parameter['input_json']]
|
||||
if parameters == []:
|
||||
|
||||
# if there are no new artifacts and no existing artifacts with deviceHostname, then stop the playbook
|
||||
relevant_artifacts = phantom.collect2(container=container, datapath=["artifact:*.cef.deviceHostname"])
|
||||
if relevant_artifacts == []:
|
||||
phantom.comment(comment="No ip_or_hostname values to operate on; playbook discontinued")
|
||||
phantom.error("No ip_or_hostname values to operate on; playbook discontinued")
|
||||
phantom.discontinue()
|
||||
|
||||
################################################################################
|
||||
## Custom Code End
|
||||
################################################################################
|
||||
|
||||
phantom.custom_function(custom_function="community/artifact_create", parameters=parameters, name="create_artifacts", callback=playbook_internal_host_splunk_investigate_log4j_1)
|
||||
phantom.custom_function(custom_function="community/artifact_create", parameters=parameters, name="create_host_artifacts", callback=if_hosts_exist)
|
||||
|
||||
return
|
||||
|
||||
@@ -139,8 +152,8 @@ def os_filter(action=None, success=None, container=None, results=None, handle=No
|
||||
|
||||
# call connected blocks if filtered artifacts or results
|
||||
if matched_artifacts_1 or matched_results_1:
|
||||
playbook_internal_host_ssh_investigate_2(action=action, success=success, container=container, results=results, handle=handle, filtered_artifacts=matched_artifacts_1, filtered_results=matched_results_1)
|
||||
playbook_internal_host_ssh_log4j_investigate_2(action=action, success=success, container=container, results=results, handle=handle, filtered_artifacts=matched_artifacts_1, filtered_results=matched_results_1)
|
||||
playbook_internal_host_ssh_log4j_investigate_1(action=action, success=success, container=container, results=results, handle=handle, filtered_artifacts=matched_artifacts_1, filtered_results=matched_results_1)
|
||||
playbook_internal_host_ssh_investigate_1(action=action, success=success, container=container, results=results, handle=handle, filtered_artifacts=matched_artifacts_1, filtered_results=matched_results_1)
|
||||
|
||||
# collect filtered artifact ids and results for 'if' condition 2
|
||||
matched_artifacts_2, matched_results_2 = phantom.condition(
|
||||
@@ -155,24 +168,48 @@ def os_filter(action=None, success=None, container=None, results=None, handle=No
|
||||
|
||||
# call connected blocks if filtered artifacts or results
|
||||
if matched_artifacts_2 or matched_results_2:
|
||||
playbook_internal_host_winrm_investigate_2(action=action, success=success, container=container, results=results, handle=handle, filtered_artifacts=matched_artifacts_2, filtered_results=matched_results_2)
|
||||
playbook_internal_host_winrm_log4j_investigate_2(action=action, success=success, container=container, results=results, handle=handle, filtered_artifacts=matched_artifacts_2, filtered_results=matched_results_2)
|
||||
playbook_internal_host_winrm_log4j_investigate_1(action=action, success=success, container=container, results=results, handle=handle, filtered_artifacts=matched_artifacts_2, filtered_results=matched_results_2)
|
||||
playbook_internal_host_winrm_investigate_1(action=action, success=success, container=container, results=results, handle=handle, filtered_artifacts=matched_artifacts_2, filtered_results=matched_results_2)
|
||||
|
||||
return
|
||||
|
||||
|
||||
def playbook_internal_host_ssh_investigate_2(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
|
||||
phantom.debug("playbook_internal_host_ssh_investigate_2() called")
|
||||
def if_hosts_exist(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
|
||||
phantom.debug("if_hosts_exist() called")
|
||||
|
||||
filtered_artifact_0_data_os_filter = phantom.collect2(container=container, datapath=["filtered-data:os_filter:condition_1:artifact:*.cef.deviceHostname"], scope="all")
|
||||
################################################################################
|
||||
# Only proceed if ip_or_hostname values exist in artifact deviceHostname fields.
|
||||
# Without these potential log4j hosts, this playbook cannot do anything.
|
||||
################################################################################
|
||||
|
||||
filtered_artifact_0__cef_devicehostname = [item[0] for item in filtered_artifact_0_data_os_filter]
|
||||
# collect filtered artifact ids and results for 'if' condition 1
|
||||
matched_artifacts_1, matched_results_1 = phantom.condition(
|
||||
container=container,
|
||||
conditions=[
|
||||
["artifact:*.cef.deviceHostname", "!=", ""]
|
||||
],
|
||||
name="if_hosts_exist:condition_1",
|
||||
scope="all")
|
||||
|
||||
ip_or_hostname_combined_value = phantom.concatenate(filtered_artifact_0__cef_devicehostname, dedup=True)
|
||||
# call connected blocks if filtered artifacts or results
|
||||
if matched_artifacts_1 or matched_results_1:
|
||||
playbook_internal_host_splunk_investigate_log4j_2(action=action, success=success, container=container, results=results, handle=handle, filtered_artifacts=matched_artifacts_1, filtered_results=matched_results_1)
|
||||
|
||||
inputs = {
|
||||
"ip_or_hostname": ip_or_hostname_combined_value,
|
||||
}
|
||||
return
|
||||
|
||||
|
||||
def join_playbook_log4j_respond_1(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
|
||||
phantom.debug("join_playbook_log4j_respond_1() called")
|
||||
|
||||
if phantom.completed(playbook_names=["playbook_internal_host_ssh_log4j_investigate_1", "playbook_internal_host_ssh_investigate_1", "playbook_internal_host_winrm_log4j_investigate_1", "playbook_internal_host_winrm_investigate_1"]):
|
||||
# call connected block "playbook_log4j_respond_1"
|
||||
playbook_log4j_respond_1(container=container, handle=handle)
|
||||
|
||||
return
|
||||
|
||||
|
||||
def playbook_log4j_respond_1(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
|
||||
phantom.debug("playbook_log4j_respond_1() called")
|
||||
|
||||
################################################################################
|
||||
## Custom Code Start
|
||||
@@ -184,103 +221,16 @@ def playbook_internal_host_ssh_investigate_2(action=None, success=None, containe
|
||||
## Custom Code End
|
||||
################################################################################
|
||||
|
||||
# call playbook "local/internal_host_ssh_investigate", returns the playbook_run_id
|
||||
playbook_run_id = phantom.playbook("local/internal_host_ssh_investigate", container=container, name="playbook_internal_host_ssh_investigate_2", inputs=inputs)
|
||||
# call playbook "community/log4j_respond", returns the playbook_run_id
|
||||
playbook_run_id = phantom.playbook("community/log4j_respond", container=container)
|
||||
|
||||
return
|
||||
|
||||
|
||||
def playbook_internal_host_ssh_log4j_investigate_2(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
|
||||
phantom.debug("playbook_internal_host_ssh_log4j_investigate_2() called")
|
||||
def playbook_internal_host_splunk_investigate_log4j_2(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
|
||||
phantom.debug("playbook_internal_host_splunk_investigate_log4j_2() called")
|
||||
|
||||
filtered_artifact_0_data_os_filter = phantom.collect2(container=container, datapath=["filtered-data:os_filter:condition_1:artifact:*.cef.deviceHostname"], scope="all")
|
||||
|
||||
filtered_artifact_0__cef_devicehostname = [item[0] for item in filtered_artifact_0_data_os_filter]
|
||||
|
||||
ip_or_hostname_combined_value = phantom.concatenate(filtered_artifact_0__cef_devicehostname, dedup=True)
|
||||
|
||||
inputs = {
|
||||
"ip_or_hostname": ip_or_hostname_combined_value,
|
||||
}
|
||||
|
||||
################################################################################
|
||||
## Custom Code Start
|
||||
################################################################################
|
||||
|
||||
# Write your custom code here...
|
||||
|
||||
################################################################################
|
||||
## Custom Code End
|
||||
################################################################################
|
||||
|
||||
# call playbook "local/internal_host_ssh_log4j_investigate", returns the playbook_run_id
|
||||
playbook_run_id = phantom.playbook("local/internal_host_ssh_log4j_investigate", container=container, name="playbook_internal_host_ssh_log4j_investigate_2", inputs=inputs)
|
||||
|
||||
return
|
||||
|
||||
|
||||
def playbook_internal_host_winrm_investigate_2(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
|
||||
phantom.debug("playbook_internal_host_winrm_investigate_2() called")
|
||||
|
||||
filtered_artifact_0_data_os_filter = phantom.collect2(container=container, datapath=["filtered-data:os_filter:condition_2:artifact:*.cef.deviceHostname"], scope="all")
|
||||
|
||||
filtered_artifact_0__cef_devicehostname = [item[0] for item in filtered_artifact_0_data_os_filter]
|
||||
|
||||
ip_or_hostname_combined_value = phantom.concatenate(filtered_artifact_0__cef_devicehostname, dedup=True)
|
||||
|
||||
inputs = {
|
||||
"ip_or_hostname": ip_or_hostname_combined_value,
|
||||
}
|
||||
|
||||
################################################################################
|
||||
## Custom Code Start
|
||||
################################################################################
|
||||
|
||||
# Write your custom code here...
|
||||
|
||||
################################################################################
|
||||
## Custom Code End
|
||||
################################################################################
|
||||
|
||||
# call playbook "local/internal_host_winrm_investigate", returns the playbook_run_id
|
||||
playbook_run_id = phantom.playbook("local/internal_host_winrm_investigate", container=container, name="playbook_internal_host_winrm_investigate_2", inputs=inputs)
|
||||
|
||||
return
|
||||
|
||||
|
||||
def playbook_internal_host_winrm_log4j_investigate_2(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
|
||||
phantom.debug("playbook_internal_host_winrm_log4j_investigate_2() called")
|
||||
|
||||
filtered_artifact_0_data_os_filter = phantom.collect2(container=container, datapath=["filtered-data:os_filter:condition_2:artifact:*.cef.deviceHostname"], scope="all")
|
||||
|
||||
filtered_artifact_0__cef_devicehostname = [item[0] for item in filtered_artifact_0_data_os_filter]
|
||||
|
||||
ip_or_hostname_combined_value = phantom.concatenate(filtered_artifact_0__cef_devicehostname, dedup=True)
|
||||
|
||||
inputs = {
|
||||
"ip_or_hostname": ip_or_hostname_combined_value,
|
||||
}
|
||||
|
||||
################################################################################
|
||||
## Custom Code Start
|
||||
################################################################################
|
||||
|
||||
# Write your custom code here...
|
||||
|
||||
################################################################################
|
||||
## Custom Code End
|
||||
################################################################################
|
||||
|
||||
# call playbook "local/internal_host_winrm_log4j_investigate", returns the playbook_run_id
|
||||
playbook_run_id = phantom.playbook("local/internal_host_winrm_log4j_investigate", container=container, name="playbook_internal_host_winrm_log4j_investigate_2", inputs=inputs)
|
||||
|
||||
return
|
||||
|
||||
|
||||
def playbook_internal_host_splunk_investigate_log4j_1(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
|
||||
phantom.debug("playbook_internal_host_splunk_investigate_log4j_1() called")
|
||||
|
||||
container_artifact_data = phantom.collect2(container=container, datapath=["artifact:*.cef.deviceHostname"])
|
||||
container_artifact_data = phantom.collect2(container=container, datapath=["artifact:*.cef.deviceHostname"], scope="all")
|
||||
|
||||
container_artifact_cef_item_0 = [item[0] for item in container_artifact_data]
|
||||
|
||||
@@ -300,8 +250,124 @@ def playbook_internal_host_splunk_investigate_log4j_1(action=None, success=None,
|
||||
## Custom Code End
|
||||
################################################################################
|
||||
|
||||
# call playbook "local/internal_host_splunk_investigate_log4j", returns the playbook_run_id
|
||||
playbook_run_id = phantom.playbook("local/internal_host_splunk_investigate_log4j", container=container, name="playbook_internal_host_splunk_investigate_log4j_1", callback=os_filter, inputs=inputs)
|
||||
# call playbook "community/internal_host_splunk_investigate_log4j", returns the playbook_run_id
|
||||
playbook_run_id = phantom.playbook("community/internal_host_splunk_investigate_log4j", container=container, name="playbook_internal_host_splunk_investigate_log4j_2", callback=os_filter, inputs=inputs)
|
||||
|
||||
return
|
||||
|
||||
|
||||
def playbook_internal_host_ssh_log4j_investigate_1(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
|
||||
phantom.debug("playbook_internal_host_ssh_log4j_investigate_1() called")
|
||||
|
||||
filtered_artifact_0_data_os_filter = phantom.collect2(container=container, datapath=["filtered-data:os_filter:condition_1:artifact:*.cef.deviceHostname"], scope="all")
|
||||
|
||||
filtered_artifact_0__cef_devicehostname = [item[0] for item in filtered_artifact_0_data_os_filter]
|
||||
|
||||
ip_or_hostname_combined_value = phantom.concatenate(filtered_artifact_0__cef_devicehostname, dedup=True)
|
||||
|
||||
inputs = {
|
||||
"ip_or_hostname": ip_or_hostname_combined_value,
|
||||
}
|
||||
|
||||
################################################################################
|
||||
## Custom Code Start
|
||||
################################################################################
|
||||
|
||||
# Write your custom code here...
|
||||
|
||||
################################################################################
|
||||
## Custom Code End
|
||||
################################################################################
|
||||
|
||||
# call playbook "community/internal_host_ssh_log4j_investigate", returns the playbook_run_id
|
||||
playbook_run_id = phantom.playbook("community/internal_host_ssh_log4j_investigate", container=container, name="playbook_internal_host_ssh_log4j_investigate_1", callback=join_playbook_log4j_respond_1, inputs=inputs)
|
||||
|
||||
return
|
||||
|
||||
|
||||
def playbook_internal_host_ssh_investigate_1(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
|
||||
phantom.debug("playbook_internal_host_ssh_investigate_1() called")
|
||||
|
||||
filtered_artifact_0_data_os_filter = phantom.collect2(container=container, datapath=["filtered-data:os_filter:condition_1:artifact:*.cef.deviceHostname"], scope="all")
|
||||
|
||||
filtered_artifact_0__cef_devicehostname = [item[0] for item in filtered_artifact_0_data_os_filter]
|
||||
|
||||
ip_or_hostname_combined_value = phantom.concatenate(filtered_artifact_0__cef_devicehostname, dedup=True)
|
||||
|
||||
inputs = {
|
||||
"ip_or_hostname": ip_or_hostname_combined_value,
|
||||
}
|
||||
|
||||
################################################################################
|
||||
## Custom Code Start
|
||||
################################################################################
|
||||
|
||||
# Write your custom code here...
|
||||
|
||||
################################################################################
|
||||
## Custom Code End
|
||||
################################################################################
|
||||
|
||||
# call playbook "community/internal_host_ssh_investigate", returns the playbook_run_id
|
||||
playbook_run_id = phantom.playbook("community/internal_host_ssh_investigate", container=container, name="playbook_internal_host_ssh_investigate_1", callback=join_playbook_log4j_respond_1, inputs=inputs)
|
||||
|
||||
return
|
||||
|
||||
|
||||
def playbook_internal_host_winrm_log4j_investigate_1(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
|
||||
phantom.debug("playbook_internal_host_winrm_log4j_investigate_1() called")
|
||||
|
||||
filtered_artifact_0_data_os_filter = phantom.collect2(container=container, datapath=["filtered-data:os_filter:condition_2:artifact:*.cef.deviceHostname"], scope="all")
|
||||
|
||||
filtered_artifact_0__cef_devicehostname = [item[0] for item in filtered_artifact_0_data_os_filter]
|
||||
|
||||
ip_or_hostname_combined_value = phantom.concatenate(filtered_artifact_0__cef_devicehostname, dedup=True)
|
||||
|
||||
inputs = {
|
||||
"ip_or_hostname": ip_or_hostname_combined_value,
|
||||
}
|
||||
|
||||
################################################################################
|
||||
## Custom Code Start
|
||||
################################################################################
|
||||
|
||||
# Write your custom code here...
|
||||
|
||||
################################################################################
|
||||
## Custom Code End
|
||||
################################################################################
|
||||
|
||||
# call playbook "community/internal_host_winrm_log4j_investigate", returns the playbook_run_id
|
||||
playbook_run_id = phantom.playbook("community/internal_host_winrm_log4j_investigate", container=container, name="playbook_internal_host_winrm_log4j_investigate_1", callback=join_playbook_log4j_respond_1, inputs=inputs)
|
||||
|
||||
return
|
||||
|
||||
|
||||
def playbook_internal_host_winrm_investigate_1(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
|
||||
phantom.debug("playbook_internal_host_winrm_investigate_1() called")
|
||||
|
||||
filtered_artifact_0_data_os_filter = phantom.collect2(container=container, datapath=["filtered-data:os_filter:condition_2:artifact:*.cef.deviceHostname"], scope="all")
|
||||
|
||||
filtered_artifact_0__cef_devicehostname = [item[0] for item in filtered_artifact_0_data_os_filter]
|
||||
|
||||
ip_or_hostname_combined_value = phantom.concatenate(filtered_artifact_0__cef_devicehostname, dedup=True)
|
||||
|
||||
inputs = {
|
||||
"ip_or_hostname": ip_or_hostname_combined_value,
|
||||
}
|
||||
|
||||
################################################################################
|
||||
## Custom Code Start
|
||||
################################################################################
|
||||
|
||||
# Write your custom code here...
|
||||
|
||||
################################################################################
|
||||
## Custom Code End
|
||||
################################################################################
|
||||
|
||||
# call playbook "community/internal_host_winrm_investigate", returns the playbook_run_id
|
||||
playbook_run_id = phantom.playbook("community/internal_host_winrm_investigate", container=container, name="playbook_internal_host_winrm_investigate_1", callback=join_playbook_log4j_respond_1, inputs=inputs)
|
||||
|
||||
return
|
||||
|
||||
@@ -1,18 +1,13 @@
|
||||
name: Log4j Investigate and Respond
|
||||
id: e609d729-0076-421a-b8f7-9e545d000381
|
||||
version: 1
|
||||
version: 2
|
||||
date: '2021-12-14'
|
||||
author: Philip Royer, Splunk
|
||||
type: Investigate
|
||||
description: Published in response to CVE-2021-44228, this playbook and its sub-playbooks can be used to investigate and respond to attacks against hosts running vulnerable Java applications which use log4j. Between the parent playbook and seven sub-playbooks, each potentially compromised host found in Splunk Enteprise can be investigated and the risk can be mitigated using SSH for unix systems and WinRM for Windows systems.
|
||||
playbook: log4j_investigate_and_respond
|
||||
playbook: log4j_investigate
|
||||
how_to_implement: >
|
||||
To start this playbook, create a custom list called "log4j_hosts" with a format as shown below
|
||||
|
||||
| hostname1 | unix |
|
||||
| hostname2 | windows |
|
||||
|
||||
The first column should be an IP or hostname of a potentially affected log4j host, and the second should be the operating system family (either unix or windows). If the operating system is unknown it can be left blank. In the block called "fetch_hosts_from_custom_list", change the custom list name from "log4j_hosts" if needed. If the operating system family ("windows" or "unix") is not known, both ssh and winrm will be attempted. If ssh and/or winrm are not the preferred endpoint management methods, these playbooks could be ported to use Google's GRR, osquery, CrowdStrike's RTR, Carbon Black's EDR API, or similar tools. The artifact scope "all" is used throughout this playbook because the artifact list can be added to as the playbook progresses.
|
||||
To start this playbook, create a custom list called "log4j_hosts" with a format in which the first column should be an IP or hostname of a potentially affected log4j host, the second should be the operating system family (either unix or windows). If the operating system is unknown it can be left blank. In the block called "fetch_hosts_from_custom_list", change the custom list name from "log4j_hosts" if needed. If the operating system family ("windows" or "unix") is not known, both ssh and winrm will be attempted. If ssh and/or winrm are not the preferred endpoint management methods, these playbooks could be ported to use Google's GRR, osquery, CrowdStrike's RTR, Carbon Black's EDR API, or similar tools. The artifact scope "all" is used throughout this playbook because the artifact list can be added to as the playbook progresses.
|
||||
references:
|
||||
- "https://github.com/Neo23x0/Fenrir/blob/master/fenrir.sh"
|
||||
- "https://isc.sans.edu/diary/Log4j++Log4Shell+Followup%3A+What+we+see+and+how+to+defend+%28and+how+to+access+our+data%29/28122"
|
||||
@@ -22,6 +17,7 @@ app_list: []
|
||||
tags:
|
||||
platform_tags:
|
||||
- Response
|
||||
- log4j
|
||||
analytic_story:
|
||||
- Log4Shell CVE-2021-44228
|
||||
detections:
|
||||
|
Before Width: | Height: | Size: 184 KiB |
@@ -0,0 +1,598 @@
|
||||
{
|
||||
"blockly": false,
|
||||
"blockly_xml": "<xml></xml>",
|
||||
"category": "Threat Response",
|
||||
"coa": {
|
||||
"data": {
|
||||
"description": "Published in response to CVE-2021-44228, this playbook is meant to be launched by log4j_investigate. In this playbook, the risk from an exploited host can be mitigated by optionally deleting malicious files from the hosts, blocking outbound network connections from the hosts, and/or shutting down the hosts.",
|
||||
"edges": [
|
||||
{
|
||||
"id": "port_0_to_port_2",
|
||||
"sourceNode": "0",
|
||||
"sourcePort": "0_out",
|
||||
"targetNode": "2",
|
||||
"targetPort": "2_in"
|
||||
},
|
||||
{
|
||||
"id": "port_2_to_port_3",
|
||||
"sourceNode": "2",
|
||||
"sourcePort": "2_out",
|
||||
"targetNode": "3",
|
||||
"targetPort": "3_in"
|
||||
},
|
||||
{
|
||||
"id": "port_3_to_port_7",
|
||||
"sourceNode": "3",
|
||||
"sourcePort": "3_out",
|
||||
"targetNode": "7",
|
||||
"targetPort": "7_in"
|
||||
},
|
||||
{
|
||||
"conditions": [
|
||||
{
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
"id": "port_7_to_port_8",
|
||||
"sourceNode": "7",
|
||||
"sourcePort": "7_out",
|
||||
"targetNode": "8",
|
||||
"targetPort": "8_in"
|
||||
},
|
||||
{
|
||||
"conditions": [
|
||||
{
|
||||
"index": 1
|
||||
}
|
||||
],
|
||||
"id": "port_7_to_port_9",
|
||||
"sourceNode": "7",
|
||||
"sourcePort": "7_out",
|
||||
"targetNode": "9",
|
||||
"targetPort": "9_in"
|
||||
},
|
||||
{
|
||||
"id": "port_8_to_port_1",
|
||||
"sourceNode": "8",
|
||||
"sourcePort": "8_out",
|
||||
"targetNode": "1",
|
||||
"targetPort": "1_in"
|
||||
},
|
||||
{
|
||||
"id": "port_9_to_port_1",
|
||||
"sourceNode": "9",
|
||||
"sourcePort": "9_out",
|
||||
"targetNode": "1",
|
||||
"targetPort": "1_in"
|
||||
}
|
||||
],
|
||||
"hash": "9a3b34f19ae7ca8907f49ee4c143f0e0e9d04caf",
|
||||
"nodes": {
|
||||
"0": {
|
||||
"data": {
|
||||
"advanced": {
|
||||
"join": []
|
||||
},
|
||||
"functionName": "on_start",
|
||||
"id": "0",
|
||||
"type": "start"
|
||||
},
|
||||
"errors": {},
|
||||
"id": "0",
|
||||
"type": "start",
|
||||
"x": 200,
|
||||
"y": 40
|
||||
},
|
||||
"1": {
|
||||
"data": {
|
||||
"advanced": {
|
||||
"join": []
|
||||
},
|
||||
"functionId": 1,
|
||||
"functionName": "on_finish",
|
||||
"id": "1",
|
||||
"type": "end"
|
||||
},
|
||||
"errors": {},
|
||||
"id": "1",
|
||||
"type": "end",
|
||||
"userCode": "\n # This function is called after all actions are completed.\n # summary of all the action and/or all details of actions\n # can be collected here.\n\n # summary_json = phantom.get_summary()\n # if 'result' in summary_json:\n # for action_result in summary_json['result']:\n # if 'action_run_id' in action_result:\n # action_results = phantom.get_action_results(action_run_id=action_result['action_run_id'], result_data=False, flatten=False)\n # phantom.debug(action_results)\n\n",
|
||||
"x": 200,
|
||||
"y": 640
|
||||
},
|
||||
"2": {
|
||||
"data": {
|
||||
"advanced": {
|
||||
"customName": "enumerate files to delete",
|
||||
"customNameId": 0,
|
||||
"join": []
|
||||
},
|
||||
"customFunction": {
|
||||
"draftMode": false,
|
||||
"name": "passthrough",
|
||||
"repoName": "community"
|
||||
},
|
||||
"functionId": 1,
|
||||
"functionName": "enumerate_files_to_delete",
|
||||
"id": "2",
|
||||
"selectMore": false,
|
||||
"type": "utility",
|
||||
"utilities": {
|
||||
"passthrough": {
|
||||
"description": "Return the inputs as outputs. This is useful for publishing pieces of data for other blocks in the playbook to use.",
|
||||
"fields": [
|
||||
{
|
||||
"dataTypes": [
|
||||
"*"
|
||||
],
|
||||
"description": "",
|
||||
"inputType": "list",
|
||||
"label": "input_1",
|
||||
"name": "input_1",
|
||||
"placeholder": "",
|
||||
"renderType": "datapath",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"dataTypes": [
|
||||
"*"
|
||||
],
|
||||
"description": "",
|
||||
"inputType": "list",
|
||||
"label": "input_2",
|
||||
"name": "input_2",
|
||||
"placeholder": "",
|
||||
"renderType": "datapath",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"dataTypes": [
|
||||
"*"
|
||||
],
|
||||
"description": "",
|
||||
"inputType": "list",
|
||||
"label": "input_3",
|
||||
"name": "input_3",
|
||||
"placeholder": "",
|
||||
"renderType": "datapath",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"dataTypes": [
|
||||
"*"
|
||||
],
|
||||
"description": "",
|
||||
"inputType": "list",
|
||||
"label": "input_4",
|
||||
"name": "input_4",
|
||||
"placeholder": "",
|
||||
"renderType": "datapath",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"dataTypes": [
|
||||
"*"
|
||||
],
|
||||
"description": "",
|
||||
"inputType": "list",
|
||||
"label": "input_5",
|
||||
"name": "input_5",
|
||||
"placeholder": "",
|
||||
"renderType": "datapath",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"dataTypes": [
|
||||
"*"
|
||||
],
|
||||
"description": "",
|
||||
"inputType": "list",
|
||||
"label": "input_6",
|
||||
"name": "input_6",
|
||||
"placeholder": "",
|
||||
"renderType": "datapath",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"dataTypes": [
|
||||
"*"
|
||||
],
|
||||
"description": "",
|
||||
"inputType": "list",
|
||||
"label": "input_7",
|
||||
"name": "input_7",
|
||||
"placeholder": "",
|
||||
"renderType": "datapath",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"dataTypes": [
|
||||
"*"
|
||||
],
|
||||
"description": "",
|
||||
"inputType": "list",
|
||||
"label": "input_8",
|
||||
"name": "input_8",
|
||||
"placeholder": "",
|
||||
"renderType": "datapath",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"dataTypes": [
|
||||
"*"
|
||||
],
|
||||
"description": "",
|
||||
"inputType": "list",
|
||||
"label": "input_9",
|
||||
"name": "input_9",
|
||||
"placeholder": "",
|
||||
"renderType": "datapath",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"dataTypes": [
|
||||
"*"
|
||||
],
|
||||
"description": "",
|
||||
"inputType": "list",
|
||||
"label": "input_10",
|
||||
"name": "input_10",
|
||||
"placeholder": "",
|
||||
"renderType": "datapath",
|
||||
"required": false
|
||||
}
|
||||
],
|
||||
"label": "passthrough",
|
||||
"name": "passthrough"
|
||||
}
|
||||
},
|
||||
"utilityType": "custom_function",
|
||||
"values": {
|
||||
"passthrough": {
|
||||
"input_1": "log4j_hosts_and_files",
|
||||
"input_10": null,
|
||||
"input_2": null,
|
||||
"input_3": null,
|
||||
"input_4": null,
|
||||
"input_5": null,
|
||||
"input_6": null,
|
||||
"input_7": null,
|
||||
"input_8": null,
|
||||
"input_9": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"errors": {},
|
||||
"id": "2",
|
||||
"type": "utility",
|
||||
"userCode": "\n # use custom code to read a custom list of potential log4j files to delete\n # and make a json to create an artifact for each one.\n # the expected format of the custom list is:\n # hostname1 | unix | /full/path/to/delete/on/hostname_1\n # 1.1.1.1 | windows | C:\\\\Full\\Path\\To\\Delete\\On\\1_1_1_1\n #\n # the list can either have all rows with files or no rows with files. some rows with files and some without will not work\n\n custom_list_name = parameters[0]['input_1']\n \n success, message, rows = phantom.get_list(list_name=custom_list_name)\n \n # return early if the list is not found\n if not success:\n phantom.debug(\"Failed to find the custom list, so only existing artifacts will be used\")\n phantom.custom_function(custom_function=\"community/passthrough\", parameters=[], name=\"enumerate_files_to_delete\", callback=create_file_artifacts)\n return\n \n # loop through the rows and create a list of artifact jsons to add\n # the three columns are expected to be the ip_or_hostname, the operating system family, and the full path to the file to delete\n parameters = []\n unix_hosts = []\n windows_hosts = []\n unknown_hosts = []\n has_files = False\n if rows[0][2] and ('/' in rows[0][2] or '\\\\' in rows[0][2]):\n has_files = True\n for row in rows:\n # hostname and operating system are required, but file path is optional. files will not be deleted if file path is missing\n if row[0] and row[1]:\n # only windows and unix are supported, and operating system family is required\n if row[1] == 'unix' or row[1] == 'windows':\n artifact_dict = {\n 'cef_data': {\n 'deviceHostname': row[0],\n 'operatingSystemFamily': row[1],\n 'filePath': row[2]},\n 'field_mapping': {\n 'deviceHostname': ['host name', 'ip'],\n 'filePath': ['file path']}}\n # full paths should have at least one slash somewhere in them\n if row[2] and ('/' in row[2] or '\\\\' in row[2]):\n if has_files:\n artifact_dict['cef_data']['filePath'] = row[2]\n artifact_dict['field_mapping']['filePath'] = ['file path']\n else:\n phantom.debug(\"skipping host {} with file {} because other rows did not have files\".format(row[0], row[2]))\n else:\n if has_files:\n phantom.error(\"host {} is missing a file; playbook will be discontinued\".format(row[0]))\n phantom.discontinue()\n parameters.append({'input_1': artifact_dict})\n\n",
|
||||
"x": 180,
|
||||
"y": 160
|
||||
},
|
||||
"3": {
|
||||
"data": {
|
||||
"advanced": {
|
||||
"customName": "create file artifacts",
|
||||
"customNameId": 0,
|
||||
"join": []
|
||||
},
|
||||
"customFunction": {
|
||||
"draftMode": false,
|
||||
"name": "artifact_create",
|
||||
"repoName": "community"
|
||||
},
|
||||
"functionId": 2,
|
||||
"functionName": "create_file_artifacts",
|
||||
"id": "3",
|
||||
"selectMore": false,
|
||||
"tab": "customFunctions",
|
||||
"type": "utility",
|
||||
"utilities": {
|
||||
"artifact_create": {
|
||||
"description": "Create a new artifact with the specified attributes.",
|
||||
"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": "enumerate_files_to_delete:custom_function_result.data.*.item",
|
||||
"label": null,
|
||||
"name": "potential log4j file",
|
||||
"run_automation": null,
|
||||
"severity": "high",
|
||||
"tags": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"errors": {},
|
||||
"id": "3",
|
||||
"type": "utility",
|
||||
"x": 180,
|
||||
"y": 280
|
||||
},
|
||||
"7": {
|
||||
"data": {
|
||||
"advanced": {
|
||||
"customName": "if hosts exist",
|
||||
"customNameId": 0,
|
||||
"join": [],
|
||||
"scope": "all"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"comparisons": [
|
||||
{
|
||||
"conditionIndex": 0,
|
||||
"op": "!=",
|
||||
"param": "artifact:*.cef.deviceHostname",
|
||||
"value": ""
|
||||
},
|
||||
{
|
||||
"conditionIndex": 0,
|
||||
"op": "==",
|
||||
"param": "artifact:*.cef.operatingSystemFamily",
|
||||
"value": "unix"
|
||||
},
|
||||
{
|
||||
"conditionIndex": 0,
|
||||
"op": "==",
|
||||
"param": "artifact:*.name",
|
||||
"value": "potential log4j file"
|
||||
}
|
||||
],
|
||||
"conditionIndex": 0,
|
||||
"customName": "unix",
|
||||
"logic": "and"
|
||||
},
|
||||
{
|
||||
"comparisons": [
|
||||
{
|
||||
"conditionIndex": 1,
|
||||
"op": "!=",
|
||||
"param": "artifact:*.cef.deviceHostname",
|
||||
"value": ""
|
||||
},
|
||||
{
|
||||
"conditionIndex": 1,
|
||||
"op": "==",
|
||||
"param": "artifact:*.cef.operatingSystemFamily",
|
||||
"value": "windows"
|
||||
},
|
||||
{
|
||||
"conditionIndex": 1,
|
||||
"op": "==",
|
||||
"param": "artifact:*.name",
|
||||
"value": "potential log4j file"
|
||||
}
|
||||
],
|
||||
"conditionIndex": 1,
|
||||
"customName": "windows",
|
||||
"logic": "and"
|
||||
}
|
||||
],
|
||||
"customDatapaths": {
|
||||
"artifacts": {
|
||||
"artifact:*.cef.operatingSystemFamily": {
|
||||
"contains": [],
|
||||
"isCustomDatapath": true,
|
||||
"label": "artifact:*.cef.operatingSystemFamily",
|
||||
"value": "artifact:*.cef.operatingSystemFamily"
|
||||
}
|
||||
}
|
||||
},
|
||||
"functionId": 1,
|
||||
"functionName": "if_hosts_exist",
|
||||
"id": "7",
|
||||
"type": "filter"
|
||||
},
|
||||
"errors": {},
|
||||
"id": "7",
|
||||
"type": "filter",
|
||||
"x": 240,
|
||||
"y": 380
|
||||
},
|
||||
"8": {
|
||||
"data": {
|
||||
"advanced": {
|
||||
"join": [],
|
||||
"scope": "all"
|
||||
},
|
||||
"functionId": 2,
|
||||
"functionName": "playbook_internal_host_ssh_log4j_respond_2",
|
||||
"id": "8",
|
||||
"inputs": {
|
||||
"filePath": {
|
||||
"datapaths": [
|
||||
"filtered-data:if_hosts_exist:condition_1:artifact:*.cef.filePath"
|
||||
],
|
||||
"deduplicate": false
|
||||
},
|
||||
"ip_or_hostname": {
|
||||
"datapaths": [
|
||||
"filtered-data:if_hosts_exist:condition_1:artifact:*.cef.deviceHostname"
|
||||
],
|
||||
"deduplicate": false
|
||||
}
|
||||
},
|
||||
"playbookName": "internal_host_ssh_log4j_respond",
|
||||
"playbookRepo": 1,
|
||||
"playbookRepoName": "community",
|
||||
"playbookType": "data",
|
||||
"synchronous": false,
|
||||
"type": "playbook"
|
||||
},
|
||||
"errors": {},
|
||||
"id": "8",
|
||||
"type": "playbook",
|
||||
"x": 40,
|
||||
"y": 520
|
||||
},
|
||||
"9": {
|
||||
"data": {
|
||||
"advanced": {
|
||||
"join": []
|
||||
},
|
||||
"functionId": 2,
|
||||
"functionName": "playbook_internal_host_winrm_log4j_respond_2",
|
||||
"id": "9",
|
||||
"inputs": {
|
||||
"filePath": {
|
||||
"datapaths": [
|
||||
"filtered-data:if_hosts_exist:condition_2:artifact:*.cef.filePath"
|
||||
],
|
||||
"deduplicate": false
|
||||
},
|
||||
"ip_or_hostname": {
|
||||
"datapaths": [
|
||||
"filtered-data:if_hosts_exist:condition_2:artifact:*.cef.deviceHostname"
|
||||
],
|
||||
"deduplicate": false
|
||||
}
|
||||
},
|
||||
"playbookName": "internal_host_winrm_log4j_respond",
|
||||
"playbookRepo": 1,
|
||||
"playbookRepoName": "community",
|
||||
"playbookType": "data",
|
||||
"synchronous": false,
|
||||
"type": "playbook"
|
||||
},
|
||||
"errors": {},
|
||||
"id": "9",
|
||||
"type": "playbook",
|
||||
"x": 320,
|
||||
"y": 520
|
||||
}
|
||||
},
|
||||
"notes": "* To use this playbook, create a custom list called \"log4j_hosts_and_files\" with a format like:\n\n hostname1 | unix | /full/path/to/delete/on/hostname_1\n 1.1.1.1 | windows | C:\\\\Full\\Path\\To\\Delete\\On\\1_1_1_1\n\n* In other words, the first column should be an IP or hostname of a potentially affected log4j host, the second should be the operating system family (either unix or windows), and the third should be a full path to a file to delete if there are any. The first two are mandatory and the file is optional.\n\n* In the block called \"enumerate_files_to_delete\", change the custom list name from \"log4j_hosts_and_files\" if needed \n* If ssh and/or winrm are not the preferred endpoint management methods, these playbooks could be ported to use Google's GRR, osquery, CrowdStrike's RTR, Carbon Black's EDR API, or similar tools.\n* The artifact scope \"all\" is used throughout this playbook because the artifact list can be added to as the playbook progresses."
|
||||
},
|
||||
"input_spec": null,
|
||||
"output_spec": null,
|
||||
"playbook_type": "automation",
|
||||
"python_version": "3",
|
||||
"schema": "5.0.4",
|
||||
"version": "5.1.0.70187"
|
||||
},
|
||||
"create_time": "2021-12-15T22:02:08.116265+00:00",
|
||||
"draft_mode": false,
|
||||
"labels": [
|
||||
"events"
|
||||
],
|
||||
"tags": [
|
||||
"log4j",
|
||||
"respond"
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 187 KiB |
@@ -0,0 +1,259 @@
|
||||
"""
|
||||
Published in response to CVE-2021-44228, this playbook is meant to be launched by log4j_investigate. In this playbook, the risk from an exploited host can be mitigated by optionally deleting malicious files from the hosts, blocking outbound network connections from the hosts, and/or shutting down the hosts.
|
||||
"""
|
||||
|
||||
|
||||
import phantom.rules as phantom
|
||||
import json
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
|
||||
def on_start(container):
|
||||
phantom.debug('on_start() called')
|
||||
|
||||
# call 'enumerate_files_to_delete' block
|
||||
enumerate_files_to_delete(container=container)
|
||||
|
||||
return
|
||||
|
||||
def enumerate_files_to_delete(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
|
||||
phantom.debug("enumerate_files_to_delete() called")
|
||||
|
||||
parameters = []
|
||||
|
||||
parameters.append({
|
||||
"input_1": "log4j_hosts_and_files",
|
||||
"input_2": None,
|
||||
"input_3": None,
|
||||
"input_4": None,
|
||||
"input_5": None,
|
||||
"input_6": None,
|
||||
"input_7": None,
|
||||
"input_8": None,
|
||||
"input_9": None,
|
||||
"input_10": None,
|
||||
})
|
||||
|
||||
################################################################################
|
||||
## Custom Code Start
|
||||
################################################################################
|
||||
|
||||
# use custom code to read a custom list of potential log4j files to delete
|
||||
# and make a json to create an artifact for each one.
|
||||
# the expected format of the custom list is:
|
||||
# hostname1 | unix | /full/path/to/delete/on/hostname_1
|
||||
# 1.1.1.1 | windows | C:\\Full\Path\To\Delete\On\1_1_1_1
|
||||
#
|
||||
# the list can either have all rows with files or no rows with files. some rows with files and some without will not work
|
||||
|
||||
custom_list_name = parameters[0]['input_1']
|
||||
|
||||
success, message, rows = phantom.get_list(list_name=custom_list_name)
|
||||
|
||||
# return early if the list is not found
|
||||
if not success:
|
||||
phantom.debug("Failed to find the custom list, so only existing artifacts will be used")
|
||||
phantom.custom_function(custom_function="community/passthrough", parameters=[], name="enumerate_files_to_delete", callback=create_file_artifacts)
|
||||
return
|
||||
|
||||
# loop through the rows and create a list of artifact jsons to add
|
||||
# the three columns are expected to be the ip_or_hostname, the operating system family, and the full path to the file to delete
|
||||
parameters = []
|
||||
unix_hosts = []
|
||||
windows_hosts = []
|
||||
unknown_hosts = []
|
||||
has_files = False
|
||||
if rows[0][2] and ('/' in rows[0][2] or '\\' in rows[0][2]):
|
||||
has_files = True
|
||||
for row in rows:
|
||||
# hostname and operating system are required, but file path is optional. files will not be deleted if file path is missing
|
||||
if row[0] and row[1]:
|
||||
# only windows and unix are supported, and operating system family is required
|
||||
if row[1] == 'unix' or row[1] == 'windows':
|
||||
artifact_dict = {
|
||||
'cef_data': {
|
||||
'deviceHostname': row[0],
|
||||
'operatingSystemFamily': row[1],
|
||||
'filePath': row[2]},
|
||||
'field_mapping': {
|
||||
'deviceHostname': ['host name', 'ip'],
|
||||
'filePath': ['file path']}}
|
||||
# full paths should have at least one slash somewhere in them
|
||||
if row[2] and ('/' in row[2] or '\\' in row[2]):
|
||||
if has_files:
|
||||
artifact_dict['cef_data']['filePath'] = row[2]
|
||||
artifact_dict['field_mapping']['filePath'] = ['file path']
|
||||
else:
|
||||
phantom.debug("skipping host {} with file {} because other rows did not have files".format(row[0], row[2]))
|
||||
else:
|
||||
if has_files:
|
||||
phantom.error("host {} is missing a file; playbook will be discontinued".format(row[0]))
|
||||
phantom.discontinue()
|
||||
parameters.append({'input_1': artifact_dict})
|
||||
|
||||
################################################################################
|
||||
## Custom Code End
|
||||
################################################################################
|
||||
|
||||
phantom.custom_function(custom_function="community/passthrough", parameters=parameters, name="enumerate_files_to_delete", callback=create_file_artifacts)
|
||||
|
||||
return
|
||||
|
||||
|
||||
def create_file_artifacts(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
|
||||
phantom.debug("create_file_artifacts() called")
|
||||
|
||||
id_value = container.get("id", None)
|
||||
enumerate_files_to_delete_data = phantom.collect2(container=container, datapath=["enumerate_files_to_delete:custom_function_result.data.*.item"])
|
||||
|
||||
parameters = []
|
||||
|
||||
# build parameters list for 'create_file_artifacts' call
|
||||
for enumerate_files_to_delete_data_item in enumerate_files_to_delete_data:
|
||||
parameters.append({
|
||||
"name": "potential log4j file",
|
||||
"tags": None,
|
||||
"label": None,
|
||||
"severity": "high",
|
||||
"cef_field": None,
|
||||
"cef_value": None,
|
||||
"container": id_value,
|
||||
"input_json": enumerate_files_to_delete_data_item[0],
|
||||
"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="create_file_artifacts", callback=if_hosts_exist)
|
||||
|
||||
return
|
||||
|
||||
|
||||
def if_hosts_exist(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
|
||||
phantom.debug("if_hosts_exist() called")
|
||||
|
||||
# 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:*.cef.deviceHostname", "!=", ""],
|
||||
["artifact:*.cef.operatingSystemFamily", "==", "unix"],
|
||||
["artifact:*.name", "==", "potential log4j file"]
|
||||
],
|
||||
name="if_hosts_exist:condition_1",
|
||||
scope="all")
|
||||
|
||||
# call connected blocks if filtered artifacts or results
|
||||
if matched_artifacts_1 or matched_results_1:
|
||||
playbook_internal_host_ssh_log4j_respond_2(action=action, success=success, container=container, results=results, handle=handle, filtered_artifacts=matched_artifacts_1, filtered_results=matched_results_1)
|
||||
|
||||
# collect filtered artifact ids and results for 'if' condition 2
|
||||
matched_artifacts_2, matched_results_2 = phantom.condition(
|
||||
container=container,
|
||||
logical_operator="and",
|
||||
conditions=[
|
||||
["artifact:*.cef.deviceHostname", "!=", ""],
|
||||
["artifact:*.cef.operatingSystemFamily", "==", "windows"],
|
||||
["artifact:*.name", "==", "potential log4j file"]
|
||||
],
|
||||
name="if_hosts_exist:condition_2",
|
||||
scope="all")
|
||||
|
||||
# call connected blocks if filtered artifacts or results
|
||||
if matched_artifacts_2 or matched_results_2:
|
||||
playbook_internal_host_winrm_log4j_respond_2(action=action, success=success, container=container, results=results, handle=handle, filtered_artifacts=matched_artifacts_2, filtered_results=matched_results_2)
|
||||
|
||||
return
|
||||
|
||||
|
||||
def playbook_internal_host_ssh_log4j_respond_2(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
|
||||
phantom.debug("playbook_internal_host_ssh_log4j_respond_2() called")
|
||||
|
||||
filtered_artifact_0_data_if_hosts_exist = phantom.collect2(container=container, datapath=["filtered-data:if_hosts_exist:condition_1:artifact:*.cef.deviceHostname","filtered-data:if_hosts_exist:condition_1:artifact:*.cef.filePath"], scope="all")
|
||||
|
||||
filtered_artifact_0__cef_devicehostname = [item[0] for item in filtered_artifact_0_data_if_hosts_exist]
|
||||
filtered_artifact_0__cef_filepath = [item[1] for item in filtered_artifact_0_data_if_hosts_exist]
|
||||
|
||||
inputs = {
|
||||
"ip_or_hostname": filtered_artifact_0__cef_devicehostname,
|
||||
"filepath": filtered_artifact_0__cef_filepath,
|
||||
}
|
||||
|
||||
################################################################################
|
||||
## Custom Code Start
|
||||
################################################################################
|
||||
|
||||
# Write your custom code here...
|
||||
|
||||
################################################################################
|
||||
## Custom Code End
|
||||
################################################################################
|
||||
|
||||
# call playbook "community/internal_host_ssh_log4j_respond", returns the playbook_run_id
|
||||
playbook_run_id = phantom.playbook("community/internal_host_ssh_log4j_respond", container=container, inputs=inputs)
|
||||
|
||||
return
|
||||
|
||||
|
||||
def playbook_internal_host_winrm_log4j_respond_2(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
|
||||
phantom.debug("playbook_internal_host_winrm_log4j_respond_2() called")
|
||||
|
||||
filtered_artifact_0_data_if_hosts_exist = phantom.collect2(container=container, datapath=["filtered-data:if_hosts_exist:condition_2:artifact:*.cef.deviceHostname","filtered-data:if_hosts_exist:condition_2:artifact:*.cef.filePath"])
|
||||
|
||||
filtered_artifact_0__cef_devicehostname = [item[0] for item in filtered_artifact_0_data_if_hosts_exist]
|
||||
filtered_artifact_0__cef_filepath = [item[1] for item in filtered_artifact_0_data_if_hosts_exist]
|
||||
|
||||
inputs = {
|
||||
"ip_or_hostname": filtered_artifact_0__cef_devicehostname,
|
||||
"filepath": filtered_artifact_0__cef_filepath,
|
||||
}
|
||||
|
||||
################################################################################
|
||||
## Custom Code Start
|
||||
################################################################################
|
||||
|
||||
# Write your custom code here...
|
||||
|
||||
################################################################################
|
||||
## Custom Code End
|
||||
################################################################################
|
||||
|
||||
# call playbook "community/internal_host_winrm_log4j_respond", returns the playbook_run_id
|
||||
playbook_run_id = phantom.playbook("community/internal_host_winrm_log4j_respond", container=container, inputs=inputs)
|
||||
|
||||
return
|
||||
|
||||
|
||||
def on_finish(container, summary):
|
||||
phantom.debug("on_finish() called")
|
||||
|
||||
################################################################################
|
||||
## Custom Code Start
|
||||
################################################################################
|
||||
|
||||
# This function is called after all actions are completed.
|
||||
# summary of all the action and/or all details of actions
|
||||
# can be collected here.
|
||||
|
||||
# summary_json = phantom.get_summary()
|
||||
# if 'result' in summary_json:
|
||||
# for action_result in summary_json['result']:
|
||||
# if 'action_run_id' in action_result:
|
||||
# action_results = phantom.get_action_results(action_run_id=action_result['action_run_id'], result_data=False, flatten=False)
|
||||
# phantom.debug(action_results)
|
||||
|
||||
################################################################################
|
||||
## Custom Code End
|
||||
################################################################################
|
||||
|
||||
return
|
||||
@@ -0,0 +1,35 @@
|
||||
name: Log4j Respond
|
||||
id: e609d729-4076-421a-b8f7-9e545d000381
|
||||
version: 1
|
||||
date: '2021-12-14'
|
||||
author: Philip Royer, Splunk
|
||||
type: Response
|
||||
description: Published in response to CVE-2021-44228, this playbook is meant to be launched after log4j_investigate. In this playbook, the risk from exploited hosts can be mitigated by optionally deleting malicious files from the hosts, blocking outbound network connections from the hosts, and/or shutting down the hosts
|
||||
playbook: log4j_respond
|
||||
how_to_implement: >
|
||||
To use this playbook, create a custom list called "log4j_hosts_and_files" with a format in which the first column should be an IP or hostname of a potentially affected log4j host, the second should be the operating system family (either unix or windows), and the third should be a full path to a file to delete if there are any. The first two are mandatory and the file is optional. In the block called "enumerate_files_to_delete", change the custom list name from "log4j_hosts_and_files" if needed. If ssh and/or winrm are not the preferred endpoint management methods, these playbooks could be ported to use Google's GRR, osquery, CrowdStrike's RTR, Carbon Black's EDR API, or similar tools. The artifact scope "all" is used throughout this playbook because the artifact list can be added to as the playbook progresses.
|
||||
references:
|
||||
- "https://github.com/Neo23x0/Fenrir/blob/master/fenrir.sh"
|
||||
- "https://isc.sans.edu/diary/Log4j++Log4Shell+Followup%3A+What+we+see+and+how+to+defend+%28and+how+to+access+our+data%29/28122"
|
||||
- "https://twitter.com/ElektroWolle/status/1469962895849140224?ref_src=twsrc%5Etfw%7Ctwcamp%5Etweetembed%7Ctwterm%5E1469962895849140224%7Ctwgr%5E%7Ctwcon%5Es1_c10&ref_url=https%3A%2F%2Fpublish.twitter.com%2F%3Fquery%3Dhttps3A2F2Ftwitter.com2FElektroWolle2Fstatus2F1469962895849140224widget%3DTweet"
|
||||
- "https://blog.cloudflare.com/cve-2021-44228-log4j-rce-0-day-mitigation/"
|
||||
app_list: []
|
||||
tags:
|
||||
platform_tags:
|
||||
- Response
|
||||
- log4j
|
||||
analytic_story:
|
||||
- Log4Shell CVE-2021-44228
|
||||
detections:
|
||||
- Curl Download and Bash Execution
|
||||
- Wget Download and Bash Execution
|
||||
- Linux Java Spawning Shell
|
||||
- Windows Java Spawning Shell
|
||||
- Java Class File download by Java User Agent
|
||||
- Outbound Network Connection from Java Using Default Ports
|
||||
- Log4Shell JNDI Payload Injection Attempt
|
||||
- Log4Shell JNDI Payload Injection with Outbound Connection
|
||||
- Detect Outbound LDAP Traffic
|
||||
playbook_fields: []
|
||||
product:
|
||||
- Splunk SOAR
|
||||