Files
splunk-security_content/playbooks/email_notification_for_malware.py
2022-01-05 13:28:38 -06:00

338 lines
15 KiB
Python

"""
This playbook tries to determine if a file is malware and whether or not the file is present on any managed machines. VirusTotal "file reputation" and PAN WildFire "detonate file" are used to determine if a file is malware, and CarbonBlack Response "hunt file" is used to search managed machines for the file. The results of these investigations are summarized in an email to the incident response team.
"""
import phantom.rules as phantom
import json
from datetime import datetime, timedelta
def on_start(container):
phantom.debug('on_start() called')
# call 'filter_1' block
filter_1(container=container)
return
"""
Run a reputation lookup on the fileHash to determine how many antivirus engines recognize it as malware.
"""
def file_reputation_1(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
phantom.debug('file_reputation_1() called')
# collect data for 'file_reputation_1' call
filtered_artifacts_data_1 = phantom.collect2(container=container, datapath=['filtered-data:filter_1:condition_1:artifact:*.cef.fileHash', 'filtered-data:filter_1:condition_1:artifact:*.id'])
parameters = []
# build parameters list for 'file_reputation_1' call
for filtered_artifacts_item_1 in filtered_artifacts_data_1:
if filtered_artifacts_item_1[0]:
parameters.append({
'hash': filtered_artifacts_item_1[0],
# context (artifact id) is added to associate results with the artifact
'context': {'artifact_id': filtered_artifacts_item_1[1]},
})
phantom.act(action="file reputation", parameters=parameters, assets=['virustotal'], callback=filter_2, name="file_reputation_1")
return
"""
Hunt for binaries with the malicious fileHash across endpoints.
"""
def hunt_file_2(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
phantom.debug('hunt_file_2() called')
#phantom.debug('Action: {0} {1}'.format(action['name'], ('SUCCEEDED' if success else 'FAILED')))
# collect data for 'hunt_file_2' call
filtered_results_data_1 = phantom.collect2(container=container, datapath=["filtered-data:filter_5:condition_1:detonate_file_1:action_result.data.*.file_info.md5", "filtered-data:filter_5:condition_1:detonate_file_1:action_result.parameter.context.artifact_id"])
parameters = []
# build parameters list for 'hunt_file_2' call
for filtered_results_item_1 in filtered_results_data_1:
if filtered_results_item_1[0]:
parameters.append({
'hash': filtered_results_item_1[0],
'type': "",
'range': "",
# context (artifact id) is added to associate results with the artifact
'context': {'artifact_id': filtered_results_item_1[1]},
})
phantom.act(action="hunt file", parameters=parameters, assets=['carbonblack'], callback=join_format_for_emailer, name="hunt_file_2")
return
"""
Detonate file requires a Vault file, so only proceed if vaultId is not null.
"""
def filter_4(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
phantom.debug('filter_4() called')
# collect filtered artifact ids for 'if' condition 1
matched_artifacts_1, matched_results_1 = phantom.condition(
container=container,
action_results=results,
conditions=[
["filtered-data:filter_3:condition_1:artifact:*.cef.vaultId", "!=", ""],
],
name="filter_4:condition_1")
# call connected blocks if filtered artifacts or results
if matched_artifacts_1 or matched_results_1:
detonate_file_1(action=action, success=success, container=container, results=results, handle=handle, custom_function=custom_function, filtered_artifacts=matched_artifacts_1, filtered_results=matched_results_1)
return
"""
Only hunt files that are considered malware per the sandbox (malware == yes).
"""
def filter_5(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
phantom.debug('filter_5() called')
# collect filtered artifact ids for 'if' condition 1
matched_artifacts_1, matched_results_1 = phantom.condition(
container=container,
action_results=results,
conditions=[
["detonate_file_1:action_result.summary.malware", "==", "yes"],
],
name="filter_5:condition_1")
# call connected blocks if filtered artifacts or results
if matched_artifacts_1 or matched_results_1:
hunt_file_2(action=action, success=success, container=container, results=results, handle=handle, custom_function=custom_function, filtered_artifacts=matched_artifacts_1, filtered_results=matched_results_1)
return
"""
Match hashes with less than 10 positives to artifacts to identify filtered_artifacts.
"""
def filter_3(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
phantom.debug('filter_3() called')
# collect filtered artifact ids for 'if' condition 1
matched_artifacts_1, matched_results_1 = phantom.condition(
container=container,
action_results=results,
conditions=[
["filtered-data:filter_2:condition_2:file_reputation_1:action_result.parameter.hash", "==", "artifact:*.cef.fileHash"],
],
name="filter_3:condition_1")
# call connected blocks if filtered artifacts or results
if matched_artifacts_1 or matched_results_1:
filter_4(action=action, success=success, container=container, results=results, handle=handle, custom_function=custom_function, filtered_artifacts=matched_artifacts_1, filtered_results=matched_results_1)
return
"""
If 10 or more antivirus engines flagged the hash, proceed directly to hunt for the file. Else, use a sandbox to detonate the executable first.
"""
def filter_2(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
phantom.debug('filter_2() called')
# collect filtered artifact ids for 'if' condition 1
matched_artifacts_1, matched_results_1 = phantom.condition(
container=container,
action_results=results,
conditions=[
["file_reputation_1:action_result.summary.positives", ">=", 10],
],
name="filter_2:condition_1")
# call connected blocks if filtered artifacts or results
if matched_artifacts_1 or matched_results_1:
hunt_file_1(action=action, success=success, container=container, results=results, handle=handle, custom_function=custom_function, filtered_artifacts=matched_artifacts_1, filtered_results=matched_results_1)
# collect filtered artifact ids for 'if' condition 2
matched_artifacts_2, matched_results_2 = phantom.condition(
container=container,
action_results=results,
conditions=[
["file_reputation_1:action_result.summary.positives", "<", 10],
],
name="filter_2:condition_2")
# call connected blocks if filtered artifacts or results
if matched_artifacts_2 or matched_results_2:
filter_3(action=action, success=success, container=container, results=results, handle=handle, custom_function=custom_function, filtered_artifacts=matched_artifacts_2, filtered_results=matched_results_2)
return
"""
Hunt for binaries with the malicious fileHash across endpoints.
"""
def hunt_file_1(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
phantom.debug('hunt_file_1() called')
#phantom.debug('Action: {0} {1}'.format(action['name'], ('SUCCEEDED' if success else 'FAILED')))
# collect data for 'hunt_file_1' call
filtered_results_data_1 = phantom.collect2(container=container, datapath=["filtered-data:filter_2:condition_1:file_reputation_1:action_result.parameter.hash", "filtered-data:filter_2:condition_1:file_reputation_1:action_result.parameter.context.artifact_id"])
parameters = []
# build parameters list for 'hunt_file_1' call
for filtered_results_item_1 in filtered_results_data_1:
if filtered_results_item_1[0]:
parameters.append({
'hash': filtered_results_item_1[0],
'type': "",
'range': "",
# context (artifact id) is added to associate results with the artifact
'context': {'artifact_id': filtered_results_item_1[1]},
})
phantom.act(action="hunt file", parameters=parameters, assets=['carbonblack'], callback=join_format_for_emailer, name="hunt_file_1")
return
"""
Only process artifacts that have a CEF fileHash.
"""
def filter_1(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
phantom.debug('filter_1() called')
# collect filtered artifact ids for 'if' condition 1
matched_artifacts_1, matched_results_1 = phantom.condition(
container=container,
conditions=[
["artifact:*.cef.fileHash", "!=", ""],
],
name="filter_1:condition_1")
# call connected blocks if filtered artifacts or results
if matched_artifacts_1 or matched_results_1:
file_reputation_1(action=action, success=success, container=container, results=results, handle=handle, custom_function=custom_function, filtered_artifacts=matched_artifacts_1, filtered_results=matched_results_1)
return
"""
Detonate the file(s) in the vault.
"""
def detonate_file_1(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
phantom.debug('detonate_file_1() called')
#phantom.debug('Action: {0} {1}'.format(action['name'], ('SUCCEEDED' if success else 'FAILED')))
# collect data for 'detonate_file_1' call
filtered_artifacts_data_1 = phantom.collect2(container=container, datapath=['filtered-data:filter_4:condition_1:artifact:*.cef.vaultId', 'filtered-data:filter_4:condition_1:artifact:*.id'])
parameters = []
# build parameters list for 'detonate_file_1' call
for filtered_artifacts_item_1 in filtered_artifacts_data_1:
if filtered_artifacts_item_1[0]:
parameters.append({
'vault_id': filtered_artifacts_item_1[0],
'file_name': "",
# context (artifact id) is added to associate results with the artifact
'context': {'artifact_id': filtered_artifacts_item_1[1]},
})
phantom.act(action="detonate file", parameters=parameters, assets=['wildfire'], callback=filter_5, name="detonate_file_1")
return
"""
Send the formatted string as an email.
"""
def send_email_1(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
phantom.debug('send_email_1() called')
#phantom.debug('Action: {0} {1}'.format(action['name'], ('SUCCEEDED' if success else 'FAILED')))
# collect data for 'send_email_1' call
formatted_data_1 = phantom.get_format_data(name='format_for_emailer')
parameters = []
# build parameters list for 'send_email_1' call
parameters.append({
'cc': "",
'to': "recipient@phantom.localhost",
'bcc': "",
'body': formatted_data_1,
'from': "automation@phantom.localhost",
'headers': "",
'subject': "Malware event confirmed",
'attachments': "",
})
phantom.act(action="send email", parameters=parameters, assets=['smtp'], name="send_email_1")
return
"""
Format all results for an email.
"""
def format_for_emailer(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
phantom.debug('format_for_emailer() called')
template = """Malware in a security event has been confirmed using file reputation and/or file detonation services.
Reputation Results:
Using file reputation services, the following file hashes have been identified as malware:
{0}
EDR tool detects that the above file hashes are present on the following endpoints:
{1}
Detonation Results:
Using sandboxing services, the following file hashes have been identified as malware:
{2}
The Phantom Vault ID for the malicious files, as determined by the sandbox service, are as follows:
{3}
EDR tool detects that the file hashes indicated as positives per the sandbox service are present on the following endpoints:
{4}
Container id: {5}
[EOM]"""
# parameter list for template variable replacement
parameters = [
"hunt_file_1:action_result.parameter.hash",
"hunt_file_1:action_result.data.*.process.results.*.hostname",
"filtered-data:filter_5:condition_1:detonate_file_1:action_result.data.*.file_info.md5",
"filtered-data:filter_5:condition_1:detonate_file_1:action_result.parameter.vault_id",
"hunt_file_2:action_result.data.*.process.results.*.hostname",
"container:id",
]
phantom.format(container=container, template=template, parameters=parameters, name="format_for_emailer")
send_email_1(container=container)
return
def join_format_for_emailer(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None):
phantom.debug('join_format_for_emailer() called')
# check if all connected incoming playbooks, actions, or custom functions are done i.e. have succeeded or failed
if phantom.completed(action_names=['hunt_file_1', 'hunt_file_2']):
# call connected block "format_for_emailer"
format_for_emailer(container=container, handle=handle)
return
def on_finish(container, summary):
phantom.debug('on_finish() called')
# 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)
return