Files
splunk-security_content/playbooks/internal_host_ssh_log4j_respond.py
2021-12-15 16:58:31 -06:00

669 lines
29 KiB
Python

"""
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.
"""
import phantom.rules as phantom
import json
from datetime import datetime, timedelta
def on_start(container):
phantom.debug('on_start() called')
# call 'filepath_decision' block
filepath_decision(container=container)
return
def locate_files(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
phantom.debug("locate_files() called")
# phantom.debug('Action: {0} {1}'.format(action['name'], ('SUCCEEDED' if success else 'FAILED')))
################################################################################
# Run a search to locate files. Contains custom code.
################################################################################
playbook_input_filepath = phantom.collect2(container=container, datapath=["playbook_input:filepath"])
playbook_input_ip_or_hostname = phantom.collect2(container=container, datapath=["playbook_input:ip_or_hostname"])
parameters = []
# build parameters list for 'locate_files' call
for playbook_input_filepath_item in playbook_input_filepath:
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": playbook_input_filepath_item[0],
"ip_hostname": playbook_input_ip_or_hostname_item[0],
})
################################################################################
## Custom Code Start
################################################################################
parameters = []
host_search = {}
# Treat ip_or_hostname and filePath as a paired unit and iterate through them,
# then create a dictionary for each host with a list of its filepaths
for playbook_input_filepath_item, playbook_input_ip_or_hostname_item in zip(playbook_input_filepath, playbook_input_ip_or_hostname):
if playbook_input_ip_or_hostname_item[0] in host_search.keys():
host_search[playbook_input_ip_or_hostname_item[0]].append(playbook_input_filepath_item[0])
else:
host_search[playbook_input_ip_or_hostname_item[0]] = [playbook_input_filepath_item[0]]
# 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'''RESULT=""; for i in "{'" "'.join(v)}"; do if [ -f "$i" ]; then RESULT="${{RESULT}} true"; else RESULT="${{RESULT}} false"; fi; done; echo $RESULT'''
parameters.append({
"ip_hostname": k,
"command": script_str
})
# Save the host_search dictionary into temporary data to access downstream.
# This makes it easier to compare the results of locate files with the filepaths and hosts
phantom.save_run_data(value=json.dumps(host_search), key="host_dictionary")
################################################################################
## Custom Code End
################################################################################
phantom.act("execute program", parameters=parameters, name="locate_files", assets=["ssh"], callback=file_search_decision)
return
def file_search_decision(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
phantom.debug("file_search_decision() called")
################################################################################
# Determine if at least one file was found
################################################################################
# check for 'if' condition 1
found_match_1 = phantom.decision(
container=container,
conditions=[
["true", "in", "locate_files:action_result.data.*.output"]
])
# call connected blocks if condition 1 matched
if found_match_1:
generate_deletion_commands(action=action, success=success, container=container, results=results, handle=handle)
return
# check for 'else' condition 2
join_dedup_hostnames(action=action, success=success, container=container, results=results, handle=handle)
return
def generate_deletion_commands(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
phantom.debug("generate_deletion_commands() called")
################################################################################
# Generate a prompt along with one deletion command per host.
################################################################################
locate_files_result_data = phantom.collect2(container=container, datapath=["locate_files:action_result.parameter.ip_hostname","locate_files:action_result.data.*.output"], action_results=results)
locate_files_parameter_ip_hostname = [item[0] for item in locate_files_result_data]
locate_files_result_item_1 = [item[1] for item in locate_files_result_data]
generate_deletion_commands__prompt_content = None
generate_deletion_commands__deletion_command = None
generate_deletion_commands__host = None
################################################################################
## Custom Code Start
################################################################################
generate_deletion_commands__prompt_content = ""
generate_deletion_commands__host = []
generate_deletion_commands__deletion_command = []
# Retrieve previously saved key
host_dictionary = json.loads(phantom.get_run_data(key="host_dictionary"))
# 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 action_result.split(' '):
# Attach this host to list of deletion hosts
generate_deletion_commands__host.append(hostname)
# Begin building deletion powershell script and prompt message
deletion_string = f'''for i in '''
generate_deletion_commands__prompt_content += f"### {hostname}\n\n"
for filepath, result in zip(host_dictionary[hostname], action_result.split(' ')):
if result == "true":
generate_deletion_commands__prompt_content += f"- {filepath}\n"
deletion_string += f'"{filepath}" '
# Remove trailing comma and attach one deletion command
deletion_string = deletion_string.rstrip(',')
deletion_string += '; do rm -v $i; done;'
generate_deletion_commands__deletion_command.append(deletion_string)
################################################################################
## Custom Code End
################################################################################
phantom.save_run_data(key="generate_deletion_commands:prompt_content", value=json.dumps(generate_deletion_commands__prompt_content))
phantom.save_run_data(key="generate_deletion_commands:deletion_command", value=json.dumps(generate_deletion_commands__deletion_command))
phantom.save_run_data(key="generate_deletion_commands:host", value=json.dumps(generate_deletion_commands__host))
deletion_confirmation(container=container)
return
def deletion_confirmation(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
phantom.debug("deletion_confirmation() called")
################################################################################
# Prompt the user to confirm deletion
################################################################################
# set user and message variables for phantom.prompt call
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 = [
"generate_deletion_commands:custom_function:prompt_content"
]
# responses
response_types = [
{
"prompt": "Type 'confirm' to delete",
"options": {
"type": "message",
},
}
]
phantom.prompt2(container=container, user=user, message=message, respond_in_mins=30, name="deletion_confirmation", parameters=parameters, response_types=response_types, callback=deletion_decision)
return
def deletion_decision(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
phantom.debug("deletion_decision() called")
################################################################################
# Determine if user wants to delete files
################################################################################
# check for 'if' condition 1
found_match_1 = phantom.decision(
container=container,
conditions=[
["deletion_confirmation:action_result.summary.responses.0", "==", "confirm"]
])
# call connected blocks if condition 1 matched
if found_match_1:
delete_files(action=action, success=success, container=container, results=results, handle=handle)
return
# check for 'else' condition 2
join_dedup_hostnames(action=action, success=success, container=container, results=results, handle=handle)
return
def delete_files(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
phantom.debug("delete_files() called")
# phantom.debug('Action: {0} {1}'.format(action['name'], ('SUCCEEDED' if success else 'FAILED')))
################################################################################
# Iterate through host and deletion command. Contains custom code.
################################################################################
generate_deletion_commands__deletion_command = json.loads(phantom.get_run_data(key="generate_deletion_commands:deletion_command"))
generate_deletion_commands__host = json.loads(phantom.get_run_data(key="generate_deletion_commands:host"))
parameters = []
if generate_deletion_commands__host is not None:
parameters.append({
"command": generate_deletion_commands__deletion_command,
"ip_hostname": generate_deletion_commands__host,
})
################################################################################
## Custom Code Start
################################################################################
parameters = []
for del_cmd, host in zip(generate_deletion_commands__deletion_command, generate_deletion_commands__host):
parameters.append({
"command": del_cmd,
"ip_hostname": host,
})
################################################################################
## Custom Code End
################################################################################
phantom.act("execute program", parameters=parameters, name="delete_files", assets=["ssh"], callback=join_dedup_hostnames)
return
def filepath_decision(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
phantom.debug("filepath_decision() called")
################################################################################
# Determine if filepath is present in playbook inputs
################################################################################
# check for 'if' condition 1
found_match_1 = phantom.decision(
container=container,
conditions=[
["playbook_input:filepath", "!=", ""]
])
# call connected blocks if condition 1 matched
if found_match_1:
locate_files(action=action, success=success, container=container, results=results, handle=handle)
return
# check for 'else' condition 2
join_dedup_hostnames(action=action, success=success, container=container, results=results, handle=handle)
return
def format_host_list_prompt(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
phantom.debug("format_host_list_prompt() called")
################################################################################
# Format a list of the hosts. This will feed both env var prompt and shut down
# prompts.
################################################################################
template = """%%\n- {0}\n%%"""
# parameter list for template variable replacement
parameters = [
"dedup_hostnames:custom_function_result.data.*.item"
]
################################################################################
## Custom Code Start
################################################################################
# Write your custom code here...
################################################################################
## Custom Code End
################################################################################
phantom.format(container=container, template=template, parameters=parameters, name="format_host_list_prompt")
quarantine_prompt(container=container)
return
def quarantine_prompt(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
phantom.debug("quarantine_prompt() called")
################################################################################
# Offer the user options to quarantine the affected endpoints
################################################################################
# set user and message variables for phantom.prompt call
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 = [
"format_host_list_prompt:formatted_data"
]
# responses
response_types = [
{
"prompt": "Available Actions",
"options": {
"type": "list",
"choices": [
"Restrict Outbound Traffic",
"Shutdown",
"Restrict Outbound Traffic and Shutdown",
"Do Nothing"
],
},
},
{
"prompt": "Type \"confirm\"",
"options": {
"type": "message",
},
}
]
phantom.prompt2(container=container, user=user, message=message, respond_in_mins=30, name="quarantine_prompt", parameters=parameters, response_types=response_types, callback=quarantine_decision)
return
def quarantine_decision(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
phantom.debug("quarantine_decision() called")
################################################################################
# Determine which action the user selected
################################################################################
# check for 'if' condition 1
found_match_1 = phantom.decision(
container=container,
logical_operator="and",
conditions=[
["quarantine_prompt:action_result.summary.responses.0", "==", "Restrict Outbound Traffic"],
["quarantine_prompt:action_result.summary.responses.1", "==", "confirm"]
])
# call connected blocks if condition 1 matched
if found_match_1:
block_outbound_traffic(action=action, success=success, container=container, results=results, handle=handle)
return
# check for 'elif' condition 2
found_match_2 = phantom.decision(
container=container,
logical_operator="and",
conditions=[
["quarantine_prompt:action_result.summary.responses.0", "==", "Shutdown"],
["quarantine_prompt:action_result.summary.responses.1", "==", "confirm"]
])
# call connected blocks if condition 2 matched
if found_match_2:
shutdown(action=action, success=success, container=container, results=results, handle=handle)
return
# check for 'elif' condition 3
found_match_3 = phantom.decision(
container=container,
logical_operator="and",
conditions=[
["quarantine_prompt:action_result.summary.responses.0", "==", "Restrict Outbound Traffic and Shutdown"],
["quarantine_prompt:action_result.summary.responses.1", "==", "confirm"]
])
# call connected blocks if condition 3 matched
if found_match_3:
block_and_shutdown(action=action, success=success, container=container, results=results, handle=handle)
return
# check for 'else' condition 4
join_format_custom_note(action=action, success=success, container=container, results=results, handle=handle)
return
def block_outbound_traffic(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
phantom.debug("block_outbound_traffic() called")
# phantom.debug('Action: {0} {1}'.format(action['name'], ('SUCCEEDED' if success else 'FAILED')))
################################################################################
# Add firewall rule blocking outbound traffic
################################################################################
dedup_hostnames_data = phantom.collect2(container=container, datapath=["dedup_hostnames:custom_function_result.data.*.item"])
parameters = []
# build parameters list for 'block_outbound_traffic' call
for dedup_hostnames_data_item in dedup_hostnames_data:
if dedup_hostnames_data_item[0] is not None:
parameters.append({
"command": "sudo -S iptables -I OUTPUT -p all 0.0.0.0/0 -j DROP SPLUNK_SOAR_BLOCK",
"ip_hostname": dedup_hostnames_data_item[0],
})
################################################################################
## Custom Code Start
################################################################################
# Write your custom code here...
################################################################################
## Custom Code End
################################################################################
phantom.act("execute program", parameters=parameters, name="block_outbound_traffic", assets=["ssh"], callback=join_format_custom_note)
return
def shutdown(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
phantom.debug("shutdown() called")
# phantom.debug('Action: {0} {1}'.format(action['name'], ('SUCCEEDED' if success else 'FAILED')))
################################################################################
# Shutdown machine
################################################################################
dedup_hostnames_data = phantom.collect2(container=container, datapath=["dedup_hostnames:custom_function_result.data.*.item"])
parameters = []
# build parameters list for 'shutdown' call
for dedup_hostnames_data_item in dedup_hostnames_data:
if dedup_hostnames_data_item[0] is not None:
parameters.append({
"command": "shutdown now",
"ip_hostname": dedup_hostnames_data_item[0],
})
################################################################################
## Custom Code Start
################################################################################
# Write your custom code here...
################################################################################
## Custom Code End
################################################################################
phantom.act("execute program", parameters=parameters, name="shutdown", assets=["ssh"], callback=join_format_custom_note)
return
def 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("block_and_shutdown() called")
# phantom.debug('Action: {0} {1}'.format(action['name'], ('SUCCEEDED' if success else 'FAILED')))
################################################################################
# Add firewall rule and then shutdown machine
################################################################################
dedup_hostnames_data = phantom.collect2(container=container, datapath=["dedup_hostnames:custom_function_result.data.*.item"])
parameters = []
# build parameters list for 'block_and_shutdown' call
for dedup_hostnames_data_item in dedup_hostnames_data:
if dedup_hostnames_data_item[0] is not None:
parameters.append({
"command": "sudo -S iptables -I OUTPUT -p all 0.0.0.0/0 -j DROP SPLUNK_SOAR_BLOCK && shutdown now",
"ip_hostname": dedup_hostnames_data_item[0],
})
################################################################################
## Custom Code Start
################################################################################
# Write your custom code here...
################################################################################
## Custom Code End
################################################################################
phantom.act("execute program", parameters=parameters, name="block_and_shutdown", assets=["ssh"], callback=join_format_custom_note)
return
def summary_note(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
phantom.debug("summary_note() called")
################################################################################
# Leave a summary note
################################################################################
format_custom_note__output = json.loads(phantom.get_run_data(key="format_custom_note:output"))
################################################################################
## Custom Code Start
################################################################################
################################################################################
## Custom Code End
################################################################################
phantom.add_note(container=container, content=format_custom_note__output, note_format="markdown", note_type="general", title="SSH Log4j Response")
return
def join_dedup_hostnames(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
phantom.debug("join_dedup_hostnames() called")
# if the joined function has already been called, do nothing
if phantom.get_run_data(key="join_dedup_hostnames_called"):
return
# save the state that the joined function has now been called
phantom.save_run_data(key="join_dedup_hostnames_called", value="dedup_hostnames")
# call connected block "dedup_hostnames"
dedup_hostnames(container=container, handle=handle)
return
def dedup_hostnames(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
phantom.debug("dedup_hostnames() called")
playbook_input_ip_or_hostname = phantom.collect2(container=container, datapath=["playbook_input:ip_or_hostname"])
playbook_input_ip_or_hostname_values = [item[0] for item in playbook_input_ip_or_hostname]
parameters = []
parameters.append({
"input_list": playbook_input_ip_or_hostname_values,
})
################################################################################
## Custom Code Start
################################################################################
# Write your custom code here...
################################################################################
## Custom Code End
################################################################################
phantom.custom_function(custom_function="community/list_deduplicate", parameters=parameters, name="dedup_hostnames", callback=format_host_list_prompt)
return
def join_format_custom_note(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
phantom.debug("join_format_custom_note() called")
# if the joined function has already been called, do nothing
if phantom.get_run_data(key="join_format_custom_note_called"):
return
# save the state that the joined function has now been called
phantom.save_run_data(key="join_format_custom_note_called", value="format_custom_note")
# call connected block "format_custom_note"
format_custom_note(container=container, handle=handle)
return
def format_custom_note(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
phantom.debug("format_custom_note() called")
################################################################################
# Format a dynamic summary note from playbook.get_summary()
################################################################################
format_custom_note__output = None
################################################################################
## Custom Code Start
################################################################################
format_custom_note__output = ""
playbook_summary = phantom.get_summary()
if 'result' in playbook_summary.keys() and playbook_summary['result']:
for result_item in playbook_summary['result']:
format_custom_note__output += f"#### Action - {result_item['name']}: {result_item['message']}\n"
# Generate app run summary for each action
if 'app_runs' in result_item.keys() and result_item['app_runs']:
for app_run_item in result_item['app_runs']:
format_custom_note__output += f"- app_run_id: {app_run_item['app_run_id']}\n"
for k,v in app_run_item['parameter'].items():
if k != 'context':
format_custom_note__output += f" - {k}: {v}\n"
format_custom_note__output += f"- summary: {app_run_item['summary']}\n"
format_custom_note__output += "\n"
################################################################################
## Custom Code End
################################################################################
phantom.save_run_data(key="format_custom_note:output", value=json.dumps(format_custom_note__output))
summary_note(container=container)
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