mirror of
https://github.com/splunk/security_content
synced 2026-06-08 17:32:49 +00:00
Merge pull request #2311 from splunk/0729_playbooks
Adding hunting playbook
This commit is contained in:
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
After Width: | Height: | Size: 199 KiB |
@@ -0,0 +1,373 @@
|
||||
"""
|
||||
Hunt for internal sightings of malicious files or connections to malicious domains or IP addresses.
|
||||
"""
|
||||
|
||||
import phantom.rules as phantom
|
||||
import json
|
||||
from datetime import datetime, timedelta
|
||||
##############################
|
||||
# Start - Global Code Block
|
||||
|
||||
"""
|
||||
The hunting Playbook queries a number of internal security technologies in order to determine if any of the artifacts present in your data source have been observed in your environment.
|
||||
"""
|
||||
|
||||
def get_specific_assets(action, include_products=None):
|
||||
|
||||
supported_assets = phantom.get_assets(action=action)
|
||||
# phantom.debug("Action Supported Assets")
|
||||
# phantom.debug(supported_assets)
|
||||
|
||||
if not supported_assets:
|
||||
# no supported products configured
|
||||
return []
|
||||
|
||||
if not include_products:
|
||||
# no product filters, so return whatever we found
|
||||
return [x['name'] for x in supported_assets]
|
||||
|
||||
if include_products:
|
||||
|
||||
if (type(include_products) != list):
|
||||
phantom.debug("Please specify a list for filter_products")
|
||||
return []
|
||||
|
||||
# make the product names sent to this funcion lower
|
||||
include_products = [x.lower() for x in include_products]
|
||||
|
||||
# get products that are configured and asked for
|
||||
assets_matched = [x['name'] for x in supported_assets if x['product_name'].lower() in include_products]
|
||||
# phantom.debug("Action Supported Matches")
|
||||
# phantom.debug(assets_matched)
|
||||
return assets_matched
|
||||
|
||||
# should not reach here
|
||||
return []
|
||||
|
||||
# End - Global Code block
|
||||
##############################
|
||||
|
||||
def on_start(container):
|
||||
phantom.debug('on_start() called')
|
||||
|
||||
# call 'hunt_domain_1' block
|
||||
hunt_domain_1(container=container)
|
||||
|
||||
# call 'hunt_file_1' block
|
||||
hunt_file_1(container=container)
|
||||
|
||||
# call 'run_query_1' block
|
||||
run_query_1(container=container)
|
||||
|
||||
return
|
||||
|
||||
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,
|
||||
action_results=results,
|
||||
conditions=[
|
||||
["hunt_domain_1:action_result.summary.device_count", ">", 0],
|
||||
["artifact:*.cef.destinationDnsDomain", "==", "hunt_domain_1:action_result.parameter.domain"],
|
||||
],
|
||||
logical_operator='and',
|
||||
name="filter_1:condition_1")
|
||||
|
||||
# call connected blocks if filtered artifacts or results
|
||||
if matched_artifacts_1 or matched_results_1:
|
||||
get_system_info_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
|
||||
|
||||
def get_system_info_1(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
|
||||
|
||||
assets = get_specific_assets("get system info", ["Carbon Black"])
|
||||
|
||||
if (not assets):
|
||||
phantom.debug("Carbon Black::get system info not found returning.")
|
||||
|
||||
# collect data for 'get_system_info_1' call
|
||||
filtered_container_data = phantom.collect2(container=container, datapath=['filtered-artifact:*.cef.sourceAddress', 'filtered-artifact:*.id'], filter_artifacts=filtered_artifacts)
|
||||
|
||||
parameters = []
|
||||
|
||||
# build parameters list for 'get_system_info_1' call
|
||||
for filtered_container_item in filtered_container_data:
|
||||
if filtered_container_item[0]:
|
||||
parameters.append({
|
||||
'ip_hostname': filtered_container_item[0],
|
||||
})
|
||||
|
||||
if parameters:
|
||||
phantom.act("get system info", parameters=parameters, assets=assets, name="get_system_info_1")
|
||||
else:
|
||||
phantom.error("'get_system_info_1' will not be executed due to lack of parameters")
|
||||
|
||||
return
|
||||
|
||||
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=[
|
||||
["hunt_file_1:action_result.data.*.binary.total_results", ">", 0],
|
||||
["artifact:*.cef.fileHash", "==", "hunt_file_1:action_result.parameter.hash"],
|
||||
],
|
||||
logical_operator='and',
|
||||
name="filter_4: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
|
||||
|
||||
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=[
|
||||
["action_result.summary.positives", "==", 0],
|
||||
["artifact:*.cef.fileHash", "==", "file_reputation_1:action_result.parameter.hash"],
|
||||
],
|
||||
logical_operator='and',
|
||||
name="filter_2:condition_1")
|
||||
|
||||
# call connected blocks if filtered artifacts or results
|
||||
if matched_artifacts_1 or matched_results_1:
|
||||
get_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
|
||||
|
||||
def hunt_domain_1(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
|
||||
|
||||
assets = get_specific_assets("hunt domain", ["Falcon Host API"])
|
||||
|
||||
if (not assets):
|
||||
phantom.debug("hunt domain/Falcon Host API not found returning.")
|
||||
|
||||
# collect data for 'hunt_domain_1' call
|
||||
container_data = phantom.collect2(container=container, datapath=['artifact:*.cef.destinationDnsDomain', 'artifact:*.id'])
|
||||
|
||||
parameters = []
|
||||
|
||||
# build parameters list for 'hunt_domain_1' call
|
||||
for container_item in container_data:
|
||||
if container_item[0]:
|
||||
parameters.append({
|
||||
'domain': container_item[0],
|
||||
'count_only': True,
|
||||
# context (artifact id) is added to associate results with the artifact
|
||||
'context': {'artifact_id': container_item[1]},
|
||||
})
|
||||
|
||||
if parameters:
|
||||
phantom.act("hunt domain", parameters=parameters, assets=assets, callback=filter_1, name="hunt_domain_1")
|
||||
else:
|
||||
phantom.error("'hunt_domain_1' will not be executed due to lack of parameters")
|
||||
|
||||
return
|
||||
|
||||
def get_file_2(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
|
||||
|
||||
assets = get_specific_assets("get file", ["Carbon Black"])
|
||||
|
||||
if (not assets):
|
||||
phantom.debug("Carbon Black::get file not found returning.")
|
||||
|
||||
# collect data for 'get_file_2' call
|
||||
filtered_container_data = phantom.collect2(container=container, datapath=['filtered-artifact:*.cef.fileHash', 'filtered-artifact:*.id'], filter_artifacts=filtered_artifacts)
|
||||
|
||||
parameters = []
|
||||
|
||||
# build parameters list for 'get_file_2' call
|
||||
for filtered_container_item in filtered_container_data:
|
||||
if filtered_container_item[0]:
|
||||
parameters.append({
|
||||
'hash': filtered_container_item[0],
|
||||
})
|
||||
|
||||
if parameters:
|
||||
phantom.act("get file", parameters=parameters, assets=assets, callback=filter_3, name="get_file_2")
|
||||
else:
|
||||
phantom.error("'get_file_2' will not be executed due to lack of parameters")
|
||||
|
||||
return
|
||||
|
||||
def detonate_file_1(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
|
||||
|
||||
assets = get_specific_assets("detonate file", ["Threat Grid"])
|
||||
|
||||
if (not assets):
|
||||
phantom.debug("Threat Grid::detonate file not found returning.")
|
||||
|
||||
# collect data for 'detonate_file_1' call
|
||||
filtered_results_data_1 = phantom.collect2(container=container, datapath=["get_file_2:filtered-action_result.data.*.vault_id", "get_file_2:filtered-action_result.parameter.context.artifact_id"], action_results=filtered_results)
|
||||
|
||||
parameters = []
|
||||
|
||||
# build parameters list for 'detonate_file_1' call
|
||||
for filtered_results_item_1 in filtered_results_data_1:
|
||||
if filtered_results_item_1[0]:
|
||||
parameters.append({
|
||||
'vault_id': filtered_results_item_1[0],
|
||||
'file_name': "",
|
||||
'vm': "",
|
||||
'force_analysis': "",
|
||||
'private': "",
|
||||
# context (artifact id) is added to associate results with the artifact
|
||||
'context': {'artifact_id': filtered_results_item_1[1]},
|
||||
})
|
||||
|
||||
if parameters:
|
||||
phantom.act("detonate file", parameters=parameters, assets=assets, name="detonate_file_1")
|
||||
else:
|
||||
phantom.error("'detonate_file_1' will not be executed due to lack of parameters")
|
||||
|
||||
return
|
||||
|
||||
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=[
|
||||
["get_file_2:action_result.data.*.vault_id", "!=", ""],
|
||||
],
|
||||
name="filter_3: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
|
||||
|
||||
def file_reputation_1(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
|
||||
|
||||
assets = get_specific_assets("file reputation", ["TitaniumCloud"])
|
||||
|
||||
if (not assets):
|
||||
phantom.debug("ReversingLabs/TitaniumCloud::file reputation not found returning.")
|
||||
|
||||
# collect data for 'file_reputation_1' call
|
||||
container_data = phantom.collect2(container=container, datapath=['filtered-artifact:*.cef.fileHash', 'filtered-artifact:*.id'], filter_artifacts=filtered_artifacts)
|
||||
|
||||
phantom.debug(container_data)
|
||||
|
||||
parameters = []
|
||||
|
||||
# build parameters list for 'file_reputation_1' call
|
||||
for container_item in container_data:
|
||||
if container_item[0]:
|
||||
parameters.append({
|
||||
'hash': container_item[0],
|
||||
# context (artifact id) is added to associate results with the artifact
|
||||
'context': {'artifact_id': container_item[1]},
|
||||
})
|
||||
|
||||
if parameters:
|
||||
phantom.act("file reputation", parameters=parameters, assets=assets, name="file_reputation_1", callback=filter_2)
|
||||
else:
|
||||
phantom.error("'file_reputation_1' will not be executed due to lack of parameters")
|
||||
|
||||
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):
|
||||
|
||||
assets = get_specific_assets("run query", ["Splunk Enterprise", "Carbon Black"])
|
||||
|
||||
if (not assets):
|
||||
phantom.debug("Did not find any asset configured, supporting run query")
|
||||
return
|
||||
|
||||
container_data_src = phantom.collect2(container=container, datapath=['artifact:*.cef.sourceAddress', 'artifact:*.id'])
|
||||
container_data_dst = phantom.collect2(container=container, datapath=['artifact:*.cef.destinationAddress', 'artifact:*.id'])
|
||||
|
||||
parameters = []
|
||||
|
||||
# build parameters list for 'run_query_1' call
|
||||
|
||||
phantom.debug("Got the following assets:")
|
||||
phantom.debug(','.join([x for x in assets]))
|
||||
|
||||
for container_item in container_data_src:
|
||||
if container_item[0]:
|
||||
parameters.append({
|
||||
'query': container_item[0],
|
||||
'display': "",
|
||||
'type': "process",
|
||||
# context (artifact id) is added to associate results with the artifact
|
||||
'context': {'artifact_id': container_item[1]},
|
||||
})
|
||||
|
||||
for container_item in container_data_dst:
|
||||
if container_item[0]:
|
||||
parameters.append({
|
||||
'query': container_item[0],
|
||||
'display': "",
|
||||
'type': "process",
|
||||
# context (artifact id) is added to associate results with the artifact
|
||||
'context': {'artifact_id': container_item[1]},
|
||||
})
|
||||
|
||||
if (parameters):
|
||||
phantom.act("run query", parameters=parameters, assets=assets, name="run_query_1")
|
||||
else:
|
||||
phantom.error("'run_query_1' will not be executed due to lack of parameters")
|
||||
|
||||
return
|
||||
|
||||
def hunt_file_1(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
|
||||
|
||||
assets = get_specific_assets("hunt file", ["Carbon Black"])
|
||||
|
||||
if (not assets):
|
||||
phantom.debug("Carbon Black::hunt file not found returning.")
|
||||
|
||||
# collect data for 'hunt_file_1' call
|
||||
container_data = phantom.collect2(container=container, datapath=['artifact:*.cef.fileHash', 'artifact:*.id'])
|
||||
|
||||
parameters = []
|
||||
|
||||
# build parameters list for 'hunt_file_1' call
|
||||
for container_item in container_data:
|
||||
if container_item[0]:
|
||||
parameters.append({
|
||||
'hash': container_item[0],
|
||||
'range': "",
|
||||
'type': "binary",
|
||||
# context (artifact id) is added to associate results with the artifact
|
||||
'context': {'artifact_id': container_item[1]},
|
||||
})
|
||||
|
||||
if parameters:
|
||||
phantom.act("hunt file", parameters=parameters, assets=assets, name="hunt_file_1", callback=filter_4)
|
||||
else:
|
||||
phantom.error("'hunt_file_1' will not be executed due to lack of parameters")
|
||||
|
||||
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
|
||||
@@ -0,0 +1,24 @@
|
||||
name: Hunting
|
||||
id: fb3edc76-ff2b-48b0-5f6f-63da6351ad63
|
||||
version: 1
|
||||
date: '2021-01-21'
|
||||
author: Philip Royer, Splunk
|
||||
type: Investigation
|
||||
description: The hunting Playbook queries a number of internal security technologies in order to determine if any of the artifacts present in your data source have been observed in your environment.
|
||||
playbook: hunting
|
||||
how_to_implement: "Be sure to update asset naming to reflect the asset names configured in your environment."
|
||||
references: []
|
||||
app_list:
|
||||
- "Splunk"
|
||||
- "Reversing Labs"
|
||||
- "CarbonBlack Response"
|
||||
- "Threat Grid"
|
||||
- "Falcon Host API"
|
||||
tags:
|
||||
platform_tags: []
|
||||
playbook_type: Automation
|
||||
playbook_fields:
|
||||
- fileHash
|
||||
- vault_id
|
||||
product:
|
||||
- Splunk SOAR
|
||||
Reference in New Issue
Block a user