mirror of
https://github.com/splunk/security_content
synced 2026-06-08 17:32:49 +00:00
1085 lines
52 KiB
Python
1085 lines
52 KiB
Python
"""
|
|
Accepts a URL, IP, Domain, or File_Hash and does reputation analysis on the objects. Generates a global report and a per observable sub-report and normalized score. The score can be customized based on a variety of factors.\n\nRef: https://d3fend.mitre.org/technique/d3f:IdentifierReputationAnalysis/
|
|
"""
|
|
|
|
|
|
import phantom.rules as phantom
|
|
import json
|
|
from datetime import datetime, timedelta
|
|
|
|
|
|
################################################################################
|
|
## Global Custom Code Start
|
|
################################################################################
|
|
from math import log
|
|
################################################################################
|
|
## Global Custom Code End
|
|
################################################################################
|
|
|
|
@phantom.playbook_block()
|
|
def on_start(container):
|
|
phantom.debug('on_start() called')
|
|
|
|
# call 'inputs_filter' block
|
|
inputs_filter(container=container)
|
|
|
|
return
|
|
|
|
@phantom.playbook_block()
|
|
def url_reputation(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
|
|
phantom.debug("url_reputation() called")
|
|
|
|
# phantom.debug('Action: {0} {1}'.format(action['name'], ('SUCCEEDED' if success else 'FAILED')))
|
|
|
|
################################################################################
|
|
# Queries VirusTotal for information about the provided URL(s)
|
|
################################################################################
|
|
|
|
filtered_input_0_url = phantom.collect2(container=container, datapath=["filtered-data:inputs_filter:condition_1:playbook_input:url"])
|
|
|
|
parameters = []
|
|
|
|
# build parameters list for 'url_reputation' call
|
|
for filtered_input_0_url_item in filtered_input_0_url:
|
|
if filtered_input_0_url_item[0] is not None:
|
|
parameters.append({
|
|
"url": filtered_input_0_url_item[0],
|
|
})
|
|
|
|
################################################################################
|
|
## Custom Code Start
|
|
################################################################################
|
|
|
|
# Write your custom code here...
|
|
|
|
################################################################################
|
|
## Custom Code End
|
|
################################################################################
|
|
|
|
phantom.act("url reputation", parameters=parameters, name="url_reputation", assets=["virustotal_v3"], callback=url_result_filter)
|
|
|
|
return
|
|
|
|
|
|
@phantom.playbook_block()
|
|
def format_report_url(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
|
|
phantom.debug("format_report_url() called")
|
|
|
|
################################################################################
|
|
# Format a summary table with the information gathered from the playbook.
|
|
################################################################################
|
|
|
|
template = """SOAR analyzed URL(s) using VirusTotal. The table below shows a summary of the information gathered.\n\n| URL | Normalized Score | Categories | Report Link | Source |\n| --- | --- | --- | --- | --- |\n%%\n| `{0}` | {1} | {2} | https://www.virustotal.com/gui/url/{3} | VirusTotal v3 |\n%%"""
|
|
|
|
# parameter list for template variable replacement
|
|
parameters = [
|
|
"filtered-data:url_result_filter:condition_1:url_reputation:action_result.parameter.url",
|
|
"normalize_score_url:custom_function:score",
|
|
"normalize_score_url:custom_function:categories",
|
|
"filtered-data:url_result_filter:condition_1:url_reputation:action_result.data.*.id"
|
|
]
|
|
|
|
################################################################################
|
|
## Custom Code Start
|
|
################################################################################
|
|
|
|
# Write your custom code here...
|
|
|
|
################################################################################
|
|
## Custom Code End
|
|
################################################################################
|
|
|
|
phantom.format(container=container, template=template, parameters=parameters, name="format_report_url")
|
|
|
|
build_url_output(container=container)
|
|
|
|
return
|
|
|
|
|
|
@phantom.playbook_block()
|
|
def build_url_output(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
|
|
phantom.debug("build_url_output() called")
|
|
|
|
################################################################################
|
|
# Generate an observable dictionary to output into the observables data path.
|
|
################################################################################
|
|
|
|
filtered_result_0_data_url_result_filter = phantom.collect2(container=container, datapath=["filtered-data:url_result_filter:condition_1:url_reputation:action_result.parameter.url","filtered-data:url_result_filter:condition_1:url_reputation:action_result.data.*.id"])
|
|
normalize_score_url__url_score_object = json.loads(_ if (_ := phantom.get_run_data(key="normalize_score_url:url_score_object")) != "" else "null") # pylint: disable=used-before-assignment
|
|
|
|
filtered_result_0_parameter_url = [item[0] for item in filtered_result_0_data_url_result_filter]
|
|
filtered_result_0_data___id = [item[1] for item in filtered_result_0_data_url_result_filter]
|
|
|
|
build_url_output__observable_array = None
|
|
|
|
################################################################################
|
|
## Custom Code Start
|
|
################################################################################
|
|
from urllib.parse import urlparse
|
|
build_url_output__observable_array = []
|
|
|
|
# Build URL
|
|
for url, external_id, url_object in zip(filtered_result_0_parameter_url, filtered_result_0_data___id, normalize_score_url__url_score_object):
|
|
parsed_url = urlparse(url)
|
|
observable_object = {
|
|
"value": url,
|
|
"type": "url",
|
|
"reputation": {
|
|
"score_id": url_object['score_id'],
|
|
"score": url_object['score'],
|
|
"confidence": url_object['confidence']
|
|
},
|
|
"attributes": {
|
|
"hostname": parsed_url.hostname,
|
|
"scheme": parsed_url.scheme
|
|
},
|
|
"categories": url_object['categories'],
|
|
"source": "VirusTotal v3",
|
|
"source_link": f"https://www.virustotal.com/gui/url/{external_id}"
|
|
}
|
|
if parsed_url.path:
|
|
observable_object['attributes']['path'] = parsed_url.path
|
|
if parsed_url.query:
|
|
observable_object['attributes']['query'] = parsed_url.query
|
|
if parsed_url.port:
|
|
observable_object['attributes']['port'] = parsed_url.port
|
|
|
|
build_url_output__observable_array.append(observable_object)
|
|
|
|
################################################################################
|
|
## Custom Code End
|
|
################################################################################
|
|
|
|
phantom.save_run_data(key="build_url_output:observable_array", value=json.dumps(build_url_output__observable_array))
|
|
|
|
return
|
|
|
|
|
|
@phantom.playbook_block()
|
|
def normalize_score_url(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
|
|
phantom.debug("normalize_score_url() called")
|
|
|
|
################################################################################
|
|
# Contains custom code for normalizing score. Adjust the logic as desired in the
|
|
# documented sections.
|
|
################################################################################
|
|
|
|
filtered_result_0_data_url_result_filter = phantom.collect2(container=container, datapath=["filtered-data:url_result_filter:condition_1:url_reputation:action_result.data.*.attributes.categories","filtered-data:url_result_filter:condition_1:url_reputation:action_result.summary"])
|
|
|
|
filtered_result_0_data___attributes_categories = [item[0] for item in filtered_result_0_data_url_result_filter]
|
|
filtered_result_0_summary = [item[1] for item in filtered_result_0_data_url_result_filter]
|
|
|
|
normalize_score_url__url_score_object = None
|
|
normalize_score_url__score = None
|
|
normalize_score_url__categories = None
|
|
|
|
################################################################################
|
|
## Custom Code Start
|
|
################################################################################
|
|
|
|
# Reference for scores: https://schema.ocsf.io/objects/reputation
|
|
score_table = {
|
|
"0":"Unknown",
|
|
"1":"Very_Safe",
|
|
"2":"Safe",
|
|
"3":"Probably_Safe",
|
|
"4":"Leans_Safe",
|
|
"5":"May_not_be_Safe",
|
|
"6":"Exercise_Caution",
|
|
"7":"Suspicious_or_Risky",
|
|
"8":"Possibly_Malicious",
|
|
"9":"Probably_Malicious",
|
|
"10":"Malicious"
|
|
}
|
|
|
|
# Assign Variables
|
|
url_categories_list = filtered_result_0_data___attributes_categories
|
|
url_summary_list = filtered_result_0_summary
|
|
normalize_score_url__url_score_object = []
|
|
normalize_score_url__score = []
|
|
normalize_score_url__categories = []
|
|
|
|
# VirusTotal v3 URL Data
|
|
# Adjust logic as desired
|
|
for category, summary_data in zip(url_categories_list, url_summary_list):
|
|
|
|
# Set confidence based on percentage of vendors undetected
|
|
# Reduce the confidence by percentage of vendors undetected.
|
|
vendors = summary_data['harmless'] + summary_data['undetected'] + summary_data['malicious'] + summary_data['suspicious']
|
|
confidence = 100 - int((summary_data['undetected']/vendors) * 100)
|
|
|
|
# Normalize reputation on a 10 point scale based on number of malicious and suspicious divided by harmless vendors
|
|
# This can be adjusted to include whatever logic is desired.
|
|
suspect = summary_data['malicious'] + summary_data['suspicious']
|
|
# If there are only harmless verdicts and no suspicious entries, set score_id to 1.
|
|
if summary_data['harmless'] and not suspect:
|
|
score_id = 1
|
|
else:
|
|
if suspect and vendors:
|
|
# customize score calculation as desired
|
|
log_result = log((suspect/vendors) * 100, 100) # log imported from math in global code block
|
|
score_id = int(log_result * 10) + 3
|
|
|
|
if score_id > 10:
|
|
score_id = 10
|
|
|
|
elif suspect == 0:
|
|
score_id = 0
|
|
|
|
categories = [cat.lower() for cat in category.values()]
|
|
categories = list(set(categories))
|
|
|
|
score = score_table[str(score_id)]
|
|
|
|
# Attach final object
|
|
normalize_score_url__url_score_object.append({'score': score, 'score_id': score_id, 'confidence': confidence, 'categories': categories})
|
|
normalize_score_url__score.append(score)
|
|
normalize_score_url__categories.append(categories)
|
|
|
|
################################################################################
|
|
## Custom Code End
|
|
################################################################################
|
|
|
|
phantom.save_run_data(key="normalize_score_url:url_score_object", value=json.dumps(normalize_score_url__url_score_object))
|
|
phantom.save_run_data(key="normalize_score_url:score", value=json.dumps(normalize_score_url__score))
|
|
phantom.save_run_data(key="normalize_score_url:categories", value=json.dumps(normalize_score_url__categories))
|
|
|
|
format_report_url(container=container)
|
|
|
|
return
|
|
|
|
|
|
@phantom.playbook_block()
|
|
def domain_reputation(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
|
|
phantom.debug("domain_reputation() called")
|
|
|
|
# phantom.debug('Action: {0} {1}'.format(action['name'], ('SUCCEEDED' if success else 'FAILED')))
|
|
|
|
################################################################################
|
|
# Queries VirusTotal for information about the provided Domain(s)
|
|
################################################################################
|
|
|
|
filtered_input_0_domain = phantom.collect2(container=container, datapath=["filtered-data:inputs_filter:condition_2:playbook_input:domain"])
|
|
|
|
parameters = []
|
|
|
|
# build parameters list for 'domain_reputation' call
|
|
for filtered_input_0_domain_item in filtered_input_0_domain:
|
|
if filtered_input_0_domain_item[0] is not None:
|
|
parameters.append({
|
|
"domain": filtered_input_0_domain_item[0],
|
|
})
|
|
|
|
################################################################################
|
|
## Custom Code Start
|
|
################################################################################
|
|
|
|
# Write your custom code here...
|
|
|
|
################################################################################
|
|
## Custom Code End
|
|
################################################################################
|
|
|
|
phantom.act("domain reputation", parameters=parameters, name="domain_reputation", assets=["virustotal_v3"], callback=domain_result_filter)
|
|
|
|
return
|
|
|
|
|
|
@phantom.playbook_block()
|
|
def ip_reputation(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
|
|
phantom.debug("ip_reputation() called")
|
|
|
|
# phantom.debug('Action: {0} {1}'.format(action['name'], ('SUCCEEDED' if success else 'FAILED')))
|
|
|
|
################################################################################
|
|
# Queries VirusTotal for information about the provided IP(s)
|
|
################################################################################
|
|
|
|
filtered_input_0_ip = phantom.collect2(container=container, datapath=["filtered-data:inputs_filter:condition_3:playbook_input:ip"])
|
|
|
|
parameters = []
|
|
|
|
# build parameters list for 'ip_reputation' call
|
|
for filtered_input_0_ip_item in filtered_input_0_ip:
|
|
if filtered_input_0_ip_item[0] is not None:
|
|
parameters.append({
|
|
"ip": filtered_input_0_ip_item[0],
|
|
})
|
|
|
|
################################################################################
|
|
## Custom Code Start
|
|
################################################################################
|
|
|
|
# Write your custom code here...
|
|
|
|
################################################################################
|
|
## Custom Code End
|
|
################################################################################
|
|
|
|
phantom.act("ip reputation", parameters=parameters, name="ip_reputation", assets=["virustotal_v3"], callback=ip_result_filter)
|
|
|
|
return
|
|
|
|
|
|
@phantom.playbook_block()
|
|
def file_hash_reputation(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
|
|
phantom.debug("file_hash_reputation() called")
|
|
|
|
# phantom.debug('Action: {0} {1}'.format(action['name'], ('SUCCEEDED' if success else 'FAILED')))
|
|
|
|
################################################################################
|
|
# Queries VirusTotal for information about the provided File Hash(es)
|
|
################################################################################
|
|
|
|
filtered_input_0_file_hash = phantom.collect2(container=container, datapath=["filtered-data:inputs_filter:condition_4:playbook_input:file_hash"])
|
|
|
|
parameters = []
|
|
|
|
# build parameters list for 'file_hash_reputation' call
|
|
for filtered_input_0_file_hash_item in filtered_input_0_file_hash:
|
|
if filtered_input_0_file_hash_item[0] is not None:
|
|
parameters.append({
|
|
"hash": filtered_input_0_file_hash_item[0],
|
|
})
|
|
|
|
################################################################################
|
|
## Custom Code Start
|
|
################################################################################
|
|
|
|
# Write your custom code here...
|
|
|
|
################################################################################
|
|
## Custom Code End
|
|
################################################################################
|
|
|
|
phantom.act("file reputation", parameters=parameters, name="file_hash_reputation", assets=["virustotal_v3"], callback=file_result_filter)
|
|
|
|
return
|
|
|
|
|
|
@phantom.playbook_block()
|
|
def normalize_score_domain(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
|
|
phantom.debug("normalize_score_domain() called")
|
|
|
|
################################################################################
|
|
# Contains custom code for normalizing score. Adjust the logic as desired in the
|
|
# documented sections.
|
|
################################################################################
|
|
|
|
filtered_result_0_data_domain_result_filter = phantom.collect2(container=container, datapath=["filtered-data:domain_result_filter:condition_1:domain_reputation:action_result.data.*.attributes.categories","filtered-data:domain_result_filter:condition_1:domain_reputation:action_result.summary"])
|
|
|
|
filtered_result_0_data___attributes_categories = [item[0] for item in filtered_result_0_data_domain_result_filter]
|
|
filtered_result_0_summary = [item[1] for item in filtered_result_0_data_domain_result_filter]
|
|
|
|
normalize_score_domain__domain_score_object = None
|
|
normalize_score_domain__score = None
|
|
normalize_score_domain__categories = None
|
|
|
|
################################################################################
|
|
## Custom Code Start
|
|
################################################################################
|
|
|
|
# Reference for scores: https://schema.ocsf.io/objects/reputation
|
|
score_table = {
|
|
"0":"Unknown",
|
|
"1":"Very_Safe",
|
|
"2":"Safe",
|
|
"3":"Probably_Safe",
|
|
"4":"Leans_Safe",
|
|
"5":"May_not_be_Safe",
|
|
"6":"Exercise_Caution",
|
|
"7":"Suspicious_or_Risky",
|
|
"8":"Possibly_Malicious",
|
|
"9":"Probably_Malicious",
|
|
"10":"Malicious"
|
|
}
|
|
|
|
# Assign Variables
|
|
domain_categories_list = filtered_result_0_data___attributes_categories
|
|
domain_summary_list = filtered_result_0_summary
|
|
normalize_score_domain__domain_score_object = []
|
|
normalize_score_domain__score = []
|
|
normalize_score_domain__categories = []
|
|
|
|
# VirusTotal v3 URL Data
|
|
# Adjust logic as desired
|
|
for category, summary_data in zip(domain_categories_list, domain_summary_list):
|
|
|
|
# Set confidence based on percentage of vendors undetected
|
|
# Reduce the confidence by percentage of vendors undetected.
|
|
vendors = summary_data['harmless'] + summary_data['undetected'] + summary_data['malicious'] + summary_data['suspicious']
|
|
confidence = 100 - int((summary_data['undetected']/vendors) * 100)
|
|
|
|
# Normalize reputation on a 10 point scale based on number of malicious and suspicious divided by harmless vendors
|
|
# This can be adjusted to include whatever logic is desired.
|
|
suspect = summary_data['malicious'] + summary_data['suspicious']
|
|
# If there are only harmless verdicts and no suspicious entries, set score_id to 1.
|
|
if summary_data['harmless'] and not suspect:
|
|
score_id = 1
|
|
else:
|
|
if suspect and vendors:
|
|
# customize score calculation as desired
|
|
log_result = log((suspect/vendors) * 100, 100) # log imported from math in global code block
|
|
score_id = int(log_result * 10) + 3
|
|
|
|
if score_id > 10:
|
|
score_id = 10
|
|
|
|
elif suspect == 0:
|
|
score_id = 0
|
|
|
|
categories = [cat.lower() for cat in category.values()]
|
|
categories = list(set(categories))
|
|
score = score_table[str(score_id)]
|
|
|
|
# Attach final object
|
|
normalize_score_domain__domain_score_object.append({'score': score, 'score_id': score_id, 'confidence': confidence, 'categories': categories})
|
|
normalize_score_domain__score.append(score)
|
|
normalize_score_domain__categories.append(categories)
|
|
|
|
|
|
################################################################################
|
|
## Custom Code End
|
|
################################################################################
|
|
|
|
phantom.save_run_data(key="normalize_score_domain:domain_score_object", value=json.dumps(normalize_score_domain__domain_score_object))
|
|
phantom.save_run_data(key="normalize_score_domain:score", value=json.dumps(normalize_score_domain__score))
|
|
phantom.save_run_data(key="normalize_score_domain:categories", value=json.dumps(normalize_score_domain__categories))
|
|
|
|
format_report_domain(container=container)
|
|
|
|
return
|
|
|
|
|
|
@phantom.playbook_block()
|
|
def normalize_score_ip(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
|
|
phantom.debug("normalize_score_ip() called")
|
|
|
|
################################################################################
|
|
# Contains custom code for normalizing score. Adjust the logic as desired in the
|
|
# documented sections.
|
|
################################################################################
|
|
|
|
filtered_result_0_data_ip_result_filter = phantom.collect2(container=container, datapath=["filtered-data:ip_result_filter:condition_1:ip_reputation:action_result.summary"])
|
|
|
|
filtered_result_0_summary = [item[0] for item in filtered_result_0_data_ip_result_filter]
|
|
|
|
normalize_score_ip__ip_score_object = None
|
|
normalize_score_ip__scores = None
|
|
|
|
################################################################################
|
|
## Custom Code Start
|
|
################################################################################
|
|
|
|
# Reference for scores: https://schema.ocsf.io/objects/reputation
|
|
score_table = {
|
|
"0":"Unknown",
|
|
"1":"Very_Safe",
|
|
"2":"Safe",
|
|
"3":"Probably_Safe",
|
|
"4":"Leans_Safe",
|
|
"5":"May_not_be_Safe",
|
|
"6":"Exercise_Caution",
|
|
"7":"Suspicious_or_Risky",
|
|
"8":"Possibly_Malicious",
|
|
"9":"Probably_Malicious",
|
|
"10":"Malicious"
|
|
}
|
|
|
|
ip_summary_list = filtered_result_0_summary
|
|
normalize_score_ip__ip_score_object = []
|
|
normalize_score_ip__scores = []
|
|
|
|
for summary_data in ip_summary_list:
|
|
# Set confidence based on percentage of vendors undetected
|
|
# Reduce the confidence by percentage of vendors undetected.
|
|
vendors = summary_data['harmless'] + summary_data['undetected'] + summary_data['malicious'] + summary_data['suspicious']
|
|
confidence = 100 - int((summary_data['undetected']/vendors) * 100)
|
|
|
|
# Normalize reputation on a 10 point scale based on number of malicious and suspicious divided by harmless vendors
|
|
# This can be adjusted to include whatever logic is desired.
|
|
suspect = summary_data['malicious'] + summary_data['suspicious']
|
|
# If there are only harmless verdicts and no suspicious entries, set score_id to 1.
|
|
if summary_data['harmless'] and not suspect:
|
|
score_id = 1
|
|
else:
|
|
if suspect and vendors:
|
|
# customize score calculation as desired
|
|
log_result = log((suspect/vendors) * 100, 100) # log imported from math in global code block
|
|
score_id = int(log_result * 10) + 3
|
|
|
|
if score_id > 10:
|
|
score_id = 10
|
|
|
|
elif suspect == 0:
|
|
score_id = 0
|
|
|
|
score = score_table[str(score_id)]
|
|
|
|
normalize_score_ip__ip_score_object.append({'score': score, 'score_id': score_id, 'confidence': confidence})
|
|
normalize_score_ip__scores.append(score)
|
|
|
|
################################################################################
|
|
## Custom Code End
|
|
################################################################################
|
|
|
|
phantom.save_run_data(key="normalize_score_ip:ip_score_object", value=json.dumps(normalize_score_ip__ip_score_object))
|
|
phantom.save_run_data(key="normalize_score_ip:scores", value=json.dumps(normalize_score_ip__scores))
|
|
|
|
format_report_ip(container=container)
|
|
|
|
return
|
|
|
|
|
|
@phantom.playbook_block()
|
|
def normalize_score_file(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
|
|
phantom.debug("normalize_score_file() called")
|
|
|
|
################################################################################
|
|
# Contains custom code for normalizing score. Adjust the logic as desired in the
|
|
# documented sections.
|
|
################################################################################
|
|
|
|
filtered_result_0_data_file_result_filter = phantom.collect2(container=container, datapath=["filtered-data:file_result_filter:condition_1:file_hash_reputation:action_result.summary"])
|
|
|
|
filtered_result_0_summary = [item[0] for item in filtered_result_0_data_file_result_filter]
|
|
|
|
normalize_score_file__file_score_object = None
|
|
normalize_score_file__scores = None
|
|
|
|
################################################################################
|
|
## Custom Code Start
|
|
################################################################################
|
|
|
|
# Reference for scores: https://schema.ocsf.io/objects/reputation
|
|
score_table = {
|
|
"0":"Unknown",
|
|
"1":"Very_Safe",
|
|
"2":"Safe",
|
|
"3":"Probably_Safe",
|
|
"4":"Leans_Safe",
|
|
"5":"May_not_be_Safe",
|
|
"6":"Exercise_Caution",
|
|
"7":"Suspicious_or_Risky",
|
|
"8":"Possibly_Malicious",
|
|
"9":"Probably_Malicious",
|
|
"10":"Malicious"
|
|
}
|
|
|
|
file_summary_list = filtered_result_0_summary
|
|
normalize_score_file__file_score_object = []
|
|
normalize_score_file__scores = []
|
|
|
|
for summary_data in file_summary_list:
|
|
# Set confidence based on percentage of vendors undetected
|
|
# Reduce the confidence by percentage of vendors undetected.
|
|
vendors = summary_data['harmless'] + summary_data['undetected'] + summary_data['malicious'] + summary_data['suspicious']
|
|
confidence = 100 - int((summary_data['undetected']/vendors) * 100)
|
|
|
|
# Normalize reputation on a 10 point scale based on number of malicious and suspicious divided by harmless vendors
|
|
# This can be adjusted to include whatever logic is desired.
|
|
suspect = summary_data['malicious'] + summary_data['suspicious']
|
|
# If there are only harmless verdicts and no suspicious entries, set score_id to 1.
|
|
if summary_data['harmless'] and not suspect:
|
|
score_id = 1
|
|
else:
|
|
if suspect and vendors:
|
|
# customize score calculation as desired
|
|
log_result = log((suspect/vendors) * 100, 100) # log imported from math in global code block
|
|
score_id = int(log_result * 10) + 3
|
|
|
|
if score_id > 10:
|
|
score_id = 10
|
|
|
|
elif suspect == 0:
|
|
score_id = 0
|
|
|
|
score = score_table[str(score_id)]
|
|
|
|
normalize_score_file__file_score_object.append({'score': score, 'score_id': score_id, 'confidence': confidence})
|
|
normalize_score_file__scores.append(score)
|
|
################################################################################
|
|
## Custom Code End
|
|
################################################################################
|
|
|
|
phantom.save_run_data(key="normalize_score_file:file_score_object", value=json.dumps(normalize_score_file__file_score_object))
|
|
phantom.save_run_data(key="normalize_score_file:scores", value=json.dumps(normalize_score_file__scores))
|
|
|
|
format_report_file(container=container)
|
|
|
|
return
|
|
|
|
|
|
@phantom.playbook_block()
|
|
def format_report_domain(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
|
|
phantom.debug("format_report_domain() called")
|
|
|
|
################################################################################
|
|
# Format a summary table with the information gathered from the playbook.
|
|
################################################################################
|
|
|
|
template = """SOAR analyzed Domain(s) using VirusTotal. The table below shows a summary of the information gathered.\n\n| Domain | Normalized Data | Categories | Report Link | Source |\n| --- | --- | --- | --- | --- |\n%%\n| `{0}` | {1} | {2} | https://www.virustotal.com/gui/domain/{0} | VirusTotal v3 |\n%%"""
|
|
|
|
# parameter list for template variable replacement
|
|
parameters = [
|
|
"filtered-data:domain_result_filter:condition_1:domain_reputation:action_result.parameter.domain",
|
|
"normalize_score_domain:custom_function:score",
|
|
"normalize_score_domain:custom_function:categories"
|
|
]
|
|
|
|
################################################################################
|
|
## Custom Code Start
|
|
################################################################################
|
|
|
|
# Write your custom code here...
|
|
|
|
################################################################################
|
|
## Custom Code End
|
|
################################################################################
|
|
|
|
phantom.format(container=container, template=template, parameters=parameters, name="format_report_domain")
|
|
|
|
build_domain_output(container=container)
|
|
|
|
return
|
|
|
|
|
|
@phantom.playbook_block()
|
|
def format_report_ip(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
|
|
phantom.debug("format_report_ip() called")
|
|
|
|
################################################################################
|
|
# Format a summary table with the information gathered from the playbook.
|
|
################################################################################
|
|
|
|
template = """SOAR analyzed IP(s) using VirusTotal. The table below shows a summary of the information gathered.\n\n| IP Address | Normalized Score | Report Link | Source |\n| --- | --- | --- | --- |\n%%\n| `{0}` | {1} | https://www.virustotal.com/gui/ip-address/{0} | VirusTotal v3 |\n%%"""
|
|
|
|
# parameter list for template variable replacement
|
|
parameters = [
|
|
"filtered-data:ip_result_filter:condition_1:ip_reputation:action_result.parameter.ip",
|
|
"normalize_score_ip:custom_function:scores"
|
|
]
|
|
|
|
################################################################################
|
|
## Custom Code Start
|
|
################################################################################
|
|
|
|
# Write your custom code here...
|
|
|
|
################################################################################
|
|
## Custom Code End
|
|
################################################################################
|
|
|
|
phantom.format(container=container, template=template, parameters=parameters, name="format_report_ip")
|
|
|
|
build_ip_output(container=container)
|
|
|
|
return
|
|
|
|
|
|
@phantom.playbook_block()
|
|
def format_report_file(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
|
|
phantom.debug("format_report_file() called")
|
|
|
|
################################################################################
|
|
# Format a summary table with the information gathered from the playbook.
|
|
################################################################################
|
|
|
|
template = """SOAR analyzed file(s) using VirusTotal. The table below shows a summary of the information gathered.\n\n| File | VT Name | VT Decription | Normalized Score | Report Link | Source |\n| --- | --- | --- | --- | --- | --- |\n%%\n| `{0}` | {2} | {3} | {1} | https://www.virustotal.com/gui/file/{0} | VirusTotal v3 |\n%%\n\n"""
|
|
|
|
# parameter list for template variable replacement
|
|
parameters = [
|
|
"filtered-data:file_result_filter:condition_1:file_hash_reputation:action_result.parameter.hash",
|
|
"normalize_score_file:custom_function:scores",
|
|
"filtered-data:file_result_filter:condition_1:file_hash_reputation:action_result.data.*.attributes.meaningful_name",
|
|
"filtered-data:file_result_filter:condition_1:file_hash_reputation:action_result.data.*.attributes.magic"
|
|
]
|
|
|
|
################################################################################
|
|
## Custom Code Start
|
|
################################################################################
|
|
|
|
# Write your custom code here...
|
|
|
|
################################################################################
|
|
## Custom Code End
|
|
################################################################################
|
|
|
|
phantom.format(container=container, template=template, parameters=parameters, name="format_report_file")
|
|
|
|
build_file_hash_output(container=container)
|
|
|
|
return
|
|
|
|
|
|
@phantom.playbook_block()
|
|
def inputs_filter(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
|
|
phantom.debug("inputs_filter() called")
|
|
|
|
################################################################################
|
|
# Determine branches based on provided inputs.
|
|
################################################################################
|
|
|
|
# collect filtered artifact ids and results for 'if' condition 1
|
|
matched_artifacts_1, matched_results_1 = phantom.condition(
|
|
container=container,
|
|
conditions=[
|
|
["playbook_input:url", "!=", ""]
|
|
],
|
|
name="inputs_filter:condition_1",
|
|
delimiter=",")
|
|
|
|
# call connected blocks if filtered artifacts or results
|
|
if matched_artifacts_1 or matched_results_1:
|
|
url_reputation(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,
|
|
conditions=[
|
|
["playbook_input:domain", "!=", ""]
|
|
],
|
|
name="inputs_filter:condition_2",
|
|
delimiter=",")
|
|
|
|
# call connected blocks if filtered artifacts or results
|
|
if matched_artifacts_2 or matched_results_2:
|
|
domain_reputation(action=action, success=success, container=container, results=results, handle=handle, filtered_artifacts=matched_artifacts_2, filtered_results=matched_results_2)
|
|
|
|
# collect filtered artifact ids and results for 'if' condition 3
|
|
matched_artifacts_3, matched_results_3 = phantom.condition(
|
|
container=container,
|
|
conditions=[
|
|
["playbook_input:ip", "!=", ""]
|
|
],
|
|
name="inputs_filter:condition_3",
|
|
delimiter=",")
|
|
|
|
# call connected blocks if filtered artifacts or results
|
|
if matched_artifacts_3 or matched_results_3:
|
|
ip_reputation(action=action, success=success, container=container, results=results, handle=handle, filtered_artifacts=matched_artifacts_3, filtered_results=matched_results_3)
|
|
|
|
# collect filtered artifact ids and results for 'if' condition 4
|
|
matched_artifacts_4, matched_results_4 = phantom.condition(
|
|
container=container,
|
|
conditions=[
|
|
["playbook_input:file_hash", "!=", ""]
|
|
],
|
|
name="inputs_filter:condition_4",
|
|
delimiter=",")
|
|
|
|
# call connected blocks if filtered artifacts or results
|
|
if matched_artifacts_4 or matched_results_4:
|
|
file_hash_reputation(action=action, success=success, container=container, results=results, handle=handle, filtered_artifacts=matched_artifacts_4, filtered_results=matched_results_4)
|
|
|
|
return
|
|
|
|
|
|
@phantom.playbook_block()
|
|
def build_domain_output(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
|
|
phantom.debug("build_domain_output() called")
|
|
|
|
################################################################################
|
|
# Generate an observable dictionary to output into the observables data path.
|
|
################################################################################
|
|
|
|
filtered_result_0_data_domain_result_filter = phantom.collect2(container=container, datapath=["filtered-data:domain_result_filter:condition_1:domain_reputation:action_result.parameter.domain"])
|
|
normalize_score_domain__domain_score_object = json.loads(_ if (_ := phantom.get_run_data(key="normalize_score_domain:domain_score_object")) != "" else "null") # pylint: disable=used-before-assignment
|
|
|
|
filtered_result_0_parameter_domain = [item[0] for item in filtered_result_0_data_domain_result_filter]
|
|
|
|
build_domain_output__observable_array = None
|
|
|
|
################################################################################
|
|
## Custom Code Start
|
|
################################################################################
|
|
|
|
build_domain_output__observable_array = []
|
|
|
|
# Build URL
|
|
for domain, domain_object in zip(filtered_result_0_parameter_domain, normalize_score_domain__domain_score_object):
|
|
observable_object = {
|
|
"value": domain,
|
|
"type": "domain",
|
|
"reputation": {
|
|
"score_id": domain_object['score_id'],
|
|
"score": domain_object['score'],
|
|
"confidence": domain_object['confidence']
|
|
},
|
|
"categories": domain_object['categories'],
|
|
"source": "VirusTotal v3",
|
|
"source_link": f"https://www.virustotal.com/gui/domain/{domain}"
|
|
}
|
|
build_domain_output__observable_array.append(observable_object)
|
|
|
|
|
|
################################################################################
|
|
## Custom Code End
|
|
################################################################################
|
|
|
|
phantom.save_run_data(key="build_domain_output:observable_array", value=json.dumps(build_domain_output__observable_array))
|
|
|
|
return
|
|
|
|
|
|
@phantom.playbook_block()
|
|
def build_ip_output(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
|
|
phantom.debug("build_ip_output() called")
|
|
|
|
################################################################################
|
|
# Generate an observable dictionary to output into the observables data path.
|
|
################################################################################
|
|
|
|
filtered_result_0_data_ip_result_filter = phantom.collect2(container=container, datapath=["filtered-data:ip_result_filter:condition_1:ip_reputation:action_result.parameter.ip","filtered-data:ip_result_filter:condition_1:ip_reputation:action_result.data.*.attributes.country","filtered-data:ip_result_filter:condition_1:ip_reputation:action_result.data.*.attributes.network","filtered-data:ip_result_filter:condition_1:ip_reputation:action_result.data.*.attributes.as_owner","filtered-data:ip_result_filter:condition_1:ip_reputation:action_result.data.*.attributes.continent"])
|
|
normalize_score_ip__ip_score_object = json.loads(_ if (_ := phantom.get_run_data(key="normalize_score_ip:ip_score_object")) != "" else "null") # pylint: disable=used-before-assignment
|
|
|
|
filtered_result_0_parameter_ip = [item[0] for item in filtered_result_0_data_ip_result_filter]
|
|
filtered_result_0_data___attributes_country = [item[1] for item in filtered_result_0_data_ip_result_filter]
|
|
filtered_result_0_data___attributes_network = [item[2] for item in filtered_result_0_data_ip_result_filter]
|
|
filtered_result_0_data___attributes_as_owner = [item[3] for item in filtered_result_0_data_ip_result_filter]
|
|
filtered_result_0_data___attributes_continent = [item[4] for item in filtered_result_0_data_ip_result_filter]
|
|
|
|
build_ip_output__observable_array = None
|
|
|
|
################################################################################
|
|
## Custom Code Start
|
|
################################################################################
|
|
|
|
build_ip_output__observable_array = []
|
|
|
|
# Build IP
|
|
for ip, country, network, as_owner, continent, ip_object in zip(
|
|
filtered_result_0_parameter_ip,
|
|
filtered_result_0_data___attributes_country,
|
|
filtered_result_0_data___attributes_network,
|
|
filtered_result_0_data___attributes_as_owner,
|
|
filtered_result_0_data___attributes_continent,
|
|
normalize_score_ip__ip_score_object
|
|
):
|
|
observable_object = {
|
|
"value": ip,
|
|
"type": "ip",
|
|
"reputation": {
|
|
"score_id": ip_object['score_id'],
|
|
"score": ip_object['score'],
|
|
"confidence": ip_object['confidence']
|
|
},
|
|
"enrichment": {
|
|
"provider": "VirusTotal v3",
|
|
"type": "whois",
|
|
"data": {
|
|
"country": country,
|
|
"network": network,
|
|
"as_owner": as_owner,
|
|
"continent": continent
|
|
}
|
|
},
|
|
"source": "VirusTotal v3",
|
|
"source_link": f"https://www.virustotal.com/gui/ip-address/{ip}"
|
|
}
|
|
build_ip_output__observable_array.append(observable_object)
|
|
|
|
|
|
################################################################################
|
|
## Custom Code End
|
|
################################################################################
|
|
|
|
phantom.save_run_data(key="build_ip_output:observable_array", value=json.dumps(build_ip_output__observable_array))
|
|
|
|
return
|
|
|
|
|
|
@phantom.playbook_block()
|
|
def build_file_hash_output(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
|
|
phantom.debug("build_file_hash_output() called")
|
|
|
|
################################################################################
|
|
# Generate an observable dictionary to output into the observables data path.
|
|
################################################################################
|
|
|
|
filtered_result_0_data_file_result_filter = phantom.collect2(container=container, datapath=["filtered-data:file_result_filter:condition_1:file_hash_reputation:action_result.parameter.hash","filtered-data:file_result_filter:condition_1:file_hash_reputation:action_result.data.*.attributes.meaningful_name","filtered-data:file_result_filter:condition_1:file_hash_reputation:action_result.data.*.attributes.type_description"])
|
|
normalize_score_file__file_score_object = json.loads(_ if (_ := phantom.get_run_data(key="normalize_score_file:file_score_object")) != "" else "null") # pylint: disable=used-before-assignment
|
|
|
|
filtered_result_0_parameter_hash = [item[0] for item in filtered_result_0_data_file_result_filter]
|
|
filtered_result_0_data___attributes_meaningful_name = [item[1] for item in filtered_result_0_data_file_result_filter]
|
|
filtered_result_0_data___attributes_type_description = [item[2] for item in filtered_result_0_data_file_result_filter]
|
|
|
|
build_file_hash_output__observable_array = None
|
|
|
|
################################################################################
|
|
## Custom Code Start
|
|
################################################################################
|
|
|
|
build_file_hash_output__observable_array = []
|
|
# Build file_hash
|
|
for file_hash, file_name, file_type, file_object in zip(filtered_result_0_parameter_hash, filtered_result_0_data___attributes_meaningful_name, filtered_result_0_data___attributes_type_description, normalize_score_file__file_score_object):
|
|
observable_object = {
|
|
"value": file_hash,
|
|
"type": "hash",
|
|
"reputation": {
|
|
"score_id": file_object['score_id'],
|
|
"score": file_object['score'],
|
|
"confidence": file_object['confidence']
|
|
},
|
|
"enrichment": {
|
|
"provider": "VirusTotal v3",
|
|
"type": "file",
|
|
"data": {
|
|
"meaningful_name": file_name,
|
|
"magic": file_type
|
|
}
|
|
},
|
|
"source": "VirusTotal v3",
|
|
"source_link": f"https://www.virustotal.com/gui/file/{file_hash}"
|
|
}
|
|
|
|
build_file_hash_output__observable_array.append(observable_object)
|
|
|
|
################################################################################
|
|
## Custom Code End
|
|
################################################################################
|
|
|
|
phantom.save_run_data(key="build_file_hash_output:observable_array", value=json.dumps(build_file_hash_output__observable_array))
|
|
|
|
return
|
|
|
|
|
|
@phantom.playbook_block()
|
|
def url_result_filter(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
|
|
phantom.debug("url_result_filter() called")
|
|
|
|
################################################################################
|
|
# Filters successful url reputation results.
|
|
################################################################################
|
|
|
|
# collect filtered artifact ids and results for 'if' condition 1
|
|
matched_artifacts_1, matched_results_1 = phantom.condition(
|
|
container=container,
|
|
conditions=[
|
|
["url_reputation:action_result.status", "==", "success"]
|
|
],
|
|
name="url_result_filter:condition_1",
|
|
delimiter=",")
|
|
|
|
# call connected blocks if filtered artifacts or results
|
|
if matched_artifacts_1 or matched_results_1:
|
|
normalize_score_url(action=action, success=success, container=container, results=results, handle=handle, filtered_artifacts=matched_artifacts_1, filtered_results=matched_results_1)
|
|
|
|
return
|
|
|
|
|
|
@phantom.playbook_block()
|
|
def domain_result_filter(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
|
|
phantom.debug("domain_result_filter() called")
|
|
|
|
################################################################################
|
|
# Filters successful domain reputation results.
|
|
################################################################################
|
|
|
|
# collect filtered artifact ids and results for 'if' condition 1
|
|
matched_artifacts_1, matched_results_1 = phantom.condition(
|
|
container=container,
|
|
conditions=[
|
|
["domain_reputation:action_result.status", "==", "success"]
|
|
],
|
|
name="domain_result_filter:condition_1",
|
|
delimiter=",")
|
|
|
|
# call connected blocks if filtered artifacts or results
|
|
if matched_artifacts_1 or matched_results_1:
|
|
normalize_score_domain(action=action, success=success, container=container, results=results, handle=handle, filtered_artifacts=matched_artifacts_1, filtered_results=matched_results_1)
|
|
|
|
return
|
|
|
|
|
|
@phantom.playbook_block()
|
|
def ip_result_filter(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
|
|
phantom.debug("ip_result_filter() called")
|
|
|
|
################################################################################
|
|
# Filters successful ip reputation results.
|
|
################################################################################
|
|
|
|
# collect filtered artifact ids and results for 'if' condition 1
|
|
matched_artifacts_1, matched_results_1 = phantom.condition(
|
|
container=container,
|
|
conditions=[
|
|
["ip_reputation:action_result.status", "==", "success"]
|
|
],
|
|
name="ip_result_filter:condition_1",
|
|
delimiter=",")
|
|
|
|
# call connected blocks if filtered artifacts or results
|
|
if matched_artifacts_1 or matched_results_1:
|
|
normalize_score_ip(action=action, success=success, container=container, results=results, handle=handle, filtered_artifacts=matched_artifacts_1, filtered_results=matched_results_1)
|
|
|
|
return
|
|
|
|
|
|
@phantom.playbook_block()
|
|
def file_result_filter(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs):
|
|
phantom.debug("file_result_filter() called")
|
|
|
|
################################################################################
|
|
# Filters successful file reputation results.
|
|
################################################################################
|
|
|
|
# collect filtered artifact ids and results for 'if' condition 1
|
|
matched_artifacts_1, matched_results_1 = phantom.condition(
|
|
container=container,
|
|
conditions=[
|
|
["file_hash_reputation:action_result.status", "==", "success"]
|
|
],
|
|
name="file_result_filter:condition_1",
|
|
delimiter=",")
|
|
|
|
# call connected blocks if filtered artifacts or results
|
|
if matched_artifacts_1 or matched_results_1:
|
|
normalize_score_file(action=action, success=success, container=container, results=results, handle=handle, filtered_artifacts=matched_artifacts_1, filtered_results=matched_results_1)
|
|
|
|
return
|
|
|
|
|
|
@phantom.playbook_block()
|
|
def on_finish(container, summary):
|
|
phantom.debug("on_finish() called")
|
|
|
|
format_report_url = phantom.get_format_data(name="format_report_url")
|
|
format_report_domain = phantom.get_format_data(name="format_report_domain")
|
|
format_report_ip = phantom.get_format_data(name="format_report_ip")
|
|
format_report_file = phantom.get_format_data(name="format_report_file")
|
|
build_url_output__observable_array = json.loads(_ if (_ := phantom.get_run_data(key="build_url_output:observable_array")) != "" else "null") # pylint: disable=used-before-assignment
|
|
build_domain_output__observable_array = json.loads(_ if (_ := phantom.get_run_data(key="build_domain_output:observable_array")) != "" else "null") # pylint: disable=used-before-assignment
|
|
build_ip_output__observable_array = json.loads(_ if (_ := phantom.get_run_data(key="build_ip_output:observable_array")) != "" else "null") # pylint: disable=used-before-assignment
|
|
build_file_hash_output__observable_array = json.loads(_ if (_ := phantom.get_run_data(key="build_file_hash_output:observable_array")) != "" else "null") # pylint: disable=used-before-assignment
|
|
|
|
observable_combined_value = phantom.concatenate(build_url_output__observable_array, build_domain_output__observable_array, build_ip_output__observable_array, build_file_hash_output__observable_array)
|
|
markdown_report_combined_value = phantom.concatenate(format_report_url, format_report_domain, format_report_ip, format_report_file)
|
|
|
|
output = {
|
|
"observable": observable_combined_value,
|
|
"markdown_report": markdown_report_combined_value,
|
|
}
|
|
|
|
################################################################################
|
|
## 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
|
|
################################################################################
|
|
|
|
phantom.save_playbook_output_data(output=output)
|
|
|
|
return |