def check_whitelist(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None): phantom.debug('check_whitelist() called') parsed_access_points = json.loads(phantom.get_run_data(key='parsed_access_points')) success, message, whitelist = phantom.get_list(list_name='Example Company WiFi ESSID Whitelist') whitelist_filtered_access_points = [ ap for ap in parsed_access_points if ( # all ESSIDs that aren't in the whitelist are suspicious [ap["ESSID"]] not in whitelist or # all non-WPA2 access points are suspicious 'WPA2' not in ap['security_protocol'] ) ] message = 'out of the {} access points identified by the scan, {} matched the whitelist and are being ignored'.format(len(parsed_access_points), len(parsed_access_points) - len(whitelist_filtered_access_points)) phantom.debug(message) live_comment(message) phantom.save_run_data(value=json.dumps(whitelist_filtered_access_points), key='whitelist_filtered_access_points') check_greylist() return
def L5_CF_Retrieve_List_SOAR53(listName=None, **kwargs): """ This CF retrieves the contents of a list Args: listName Returns a JSON-serializable object that implements the configured data paths: listContents """ ############################ Custom Code Goes Below This Line ################################# import json import phantom.rules as phantom outputs = {} # Write your custom code here... read_list__peer_list = phantom.get_list(listName)[2] phantom.debug("from cf getlist") phantom.debug(read_list__peer_list) outputs = {"listContents": read_list__peer_list} # Return a JSON-serializable object assert json.dumps(outputs) # Will raise an exception if the :outputs: object is not JSON-serializable return outputs
def url_filter_domain_allowlist(url=None, domain_allowlist=None, **kwargs): """ Input a list of urls and the name of a custom_list that contains safe domains. Output urls where the domain is NOT present in the custom_list. Args: url (CEF type: url): Supports any URL format, including FTP, HTTP, HTTPS, SMB. domain_allowlist (CEF type: *): The name of a custom list that will be used as the list of domains to filter the URL against. Only the first column of the custom list will be used. Example: https://web.example.com/ will be parsed as web.example.com and matched to values inside my_custom_list. Returns a JSON-serializable object that implements the configured data paths: *.filtered_url (CEF type: url): Only URLs that are not hosted at domains on the allow list will be returned as output.\ """ ############################ Custom Code Goes Below This Line ################################# import json import phantom.rules as phantom import urlparse outputs = [] custom_list = phantom.get_list(list_name=domain_allowlist)[2] custom_list = [item[0] for item in custom_list] for var in url: if var: parsed_url = urlparse.urlparse(var) if parsed_url.netloc not in custom_list: outputs.append({'filtered_url': var}) phantom.debug("Filtered URLs: {}".format(outputs)) # Write your custom code here... # Return a JSON-serializable object assert json.dumps( outputs ) # Will raise an exception if the :outputs: object is not JSON-serializable return outputs
def check_greylist(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None): phantom.debug('check_greylist() called') edit_distance_threshold = 5 success, message, potentials = phantom.get_list(list_name='Potential Rogue Access Point ESSIDs') whitelist_filtered_access_points = json.loads(phantom.get_run_data(key='whitelist_filtered_access_points')) scanned_ESSIDs = [ap['ESSID'] for ap in whitelist_filtered_access_points] # compare each ESSID against each potential evil twin and escalate those with a sufficiently small edit distance matches = 0 for ap in whitelist_filtered_access_points: ap['is_escalated'] = False ap['matched_rule'] = None for potential in potentials: if edit_distance(ap['ESSID'], potential[0]) < edit_distance_threshold: ap['is_escalated'] = True ap['matched_rule'] = potential matches += 1 break message = '{} out of {} access points fuzzy-matched "Potential Rogue Access Point ESSIDs"'.format( matches, len(whitelist_filtered_access_points)) phantom.debug(message) live_comment(message) phantom.save_run_data(value=json.dumps(whitelist_filtered_access_points), key='fuzzy_matched_access_points') get_pins() return
def ssh_detect_high_sierra(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None): phantom.debug('execute_program_3() called') success, message, macos_endpoints = phantom.get_list("macos_endpoints") phantom.debug("using the following ip addresses from the custom list:") phantom.debug(macos_endpoints) parameters = [] # build parameters list for 'execute_program_1' call for macos_endpoint in macos_endpoints: parameters.append({ 'ip_hostname': macos_endpoint[0], 'command': "sw_vers", 'timeout': "", }) phantom.act("execute program", parameters=parameters, assets=['ssh_macos_administrator'], callback=filter_1, name="ssh_detect_high_sierra") return
def check_internal_addresses(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None): phantom.debug('check_internal_addresses() called') datapaths = [ "geolocate_ip_dst:action_result.parameter.ip", "geolocate_ip_src:action_result.parameter.ip", "geolocate_sourceAddress:action_result.parameter.ip", "geolocate_destAddress:action_result.parameter.ip", "geolocate_source:action_result.parameter.ip", "geolocate_dest:action_result.parameter.ip", "geolocate_url:action_result.parameter.ip", ] ip_parameters = [ phantom.collect2(container=container, datapath=[datapath], action_results=results) for datapath in datapaths ] ip_addresses = [] for block in ip_parameters: for action_run in block: ip_addresses += action_run success, message, internal_network_ranges = phantom.get_list( list_name='internal network ranges') if not success: check_internal_template = "Failed to check IP addresses against internal network because the Custom List 'internal network ranges' does not exist." else: check_internal_template = "Using the Custom List 'internal network ranges', the following internal IP addresses were identified:\n" internal_ip_addresses = [] for row in internal_network_ranges: for ip_address in set(ip_addresses): if phantom.address_in_network(ip_address, str(row[0])): internal_ip_addresses.append(ip_address) check_internal_template += "{} is in internal network range {}\n".format( ip_address, row[0]) for idx, external_address in enumerate( set(ip_addresses) - set(internal_ip_addresses)): if idx == 0: check_internal_template += "\nThe following IP addresses were not found in internal network ranges:\n" check_internal_template += external_address + '\n' template = "{0}\n\n" + check_internal_template parameters = [ "summarize_results:formatted_data", ] phantom.format(container=container, template=template, parameters=parameters, name="check_internal_addresses") add_comment_2(container=container) return
def vault_filter_filetype_allowlist(filetype=None, filetype_allowlist=None, vault_id=None, **kwargs): """ Input a list of file content types, vault ids and the name of a custom_list that contains safe file types. Output vauld ids where the filetype is NOT present in the custom_list. Args: filetype (CEF type: *): Supports Content-Type from Vault Artifact filetype_allowlist (CEF type: *): The name of a custom list that will be used as the list of domains to filter the URL against. Only the first column of the custom list will be used. Example: https://web.example.com/ will be parsed as web.example.com and matched to values inside my_custom_list. vault_id (CEF type: vault id): Vault ID Returns a JSON-serializable object that implements the configured data paths: *.filtered_vault_id (CEF type: vault id): Only vault_id's where the content-type is not found within the custom list *.filtered_content_type """ ############################ Custom Code Goes Below This Line ################################# import json import phantom.rules as phantom import urlparse outputs = [] custom_list = phantom.get_list(list_name=filetype_allowlist)[2] custom_list = [item[0] for item in custom_list] for file,vault in zip(filetype, vault_id): if file and vault: extracted_filetype = file.split(';')[0] if extracted_filetype not in custom_list: outputs.append({'filtered_vault_id': vault, 'filtered_content_type': extracted_filetype}) phantom.debug("Filtered Vault Items: {}".format(outputs)) # Write your custom code here... # Return a JSON-serializable object assert json.dumps(outputs) # Will raise an exception if the :outputs: object is not JSON-serializable return outputs
def custom_list_value_in_strings(custom_list=None, comparison_strings=None, **kwargs): """ Iterates through all items of a custom list to see if any list value (i.e. "sample.com") exists in the input you are comparing it to (i.e "findme.sample.com"). Returns a list of matches, a list of misses, a count of matches, and a count of misses. Args: custom_list: Name of the custom list. Every string in this list will be compared to see if it is a substring of any of the comparison_strings comparison_strings (CEF type: *): String to use for comparison. Returns a JSON-serializable object that implements the configured data paths: matches.*.match (CEF type: *): List of all items from the list that are substrings of any of the comparison strings match_count: Number of matches misses.*.miss (CEF type: *): List of all items from the list that are not substrings of any of the comparison strings miss_count: Number of misses """ ############################ Custom Code Goes Below This Line ################################# import json import phantom.rules as phantom # Get the custom list success, message, this_list = phantom.get_list(list_name=custom_list) # Create the lists to store matches and misses matches = [] misses = [] # Loop through each comparison string for comparison_string in comparison_strings: # Loop through the custom list to see if any list value is found in the comparison string for row in this_list: for cell in row: if comparison_string.find(cell) != -1: matches.append({"match": cell}) else: misses.append({"miss": cell}) # Prepare the outputs match_count = len(matches) miss_count = len(misses) outputs = { 'matches': matches, 'match_count': match_count, 'misses': misses, 'miss_count': miss_count, } # Return a JSON-serializable object assert json.dumps( outputs ) # Will raise an exception if the :outputs: object is not JSON-serializable return outputs
def L5_CF_Get_Query_Results_py3_SOAR53(peer=None, priority=None, count=None, container=None, **kwargs): """ created with SOAR 5.3 Args: peer priority count container (CEF type: phantom container id) Returns a JSON-serializable object that implements the configured data paths: results_list_name """ ############################ Custom Code Goes Below This Line ################################# import json import phantom.rules as phantom outputs = {} # Write your custom code here... phantom.debug(container) phantom.debug(type(container)) list_name = "temp_peer_list_%s" % container # You need the container object in order to update it. update_container = phantom.get_container(container) # Get the data node of the container data = phantom.get_container(container)['data'] data.update({"peer_list": list_name}) phantom.update(update_container, {'data': data}) phantom.remove_list(list_name) for i in range(0, len(peer)): phantom.add_list(list_name, [peer[i], priority[i], count[i]]) # The actual list is in slot 3 of the tuple returned by phantom.get_list() results_list = phantom.get_list(list_name)[2] phantom.debug(results_list) outputs = {'results_list_name': list_name} # Return a JSON-serializable object assert json.dumps( outputs ) # Will raise an exception if the :outputs: object is not JSON-serializable return outputs
def filter_1(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None): result = True phantom.debug('filter_1() called') sucess, message, trusted = phantom.get_list(list_name='Trusted') trusted = [[str(j) for j in i] for i in trusted] trusted = [i[0] for i in trusted] # phantom.debug('Trusted {}'.format(trusted)) artifacts_data_1 = phantom.collect2(container=container, datapath=['artifact:*.cef.src']) for artifacts_item_1 in artifacts_data_1: ip = artifacts_item_1[0] if ip: if ip in trusted: result = False else: for net in trusted[1:]: if phantom.valid_net(net): if phantom.address_in_network(ip, net): phantom.debug( 'ip {} in Trusted net {}: Skipped'.format( ip, net)) result = False # collect filtered artifact ids for 'if' condition 1 matched_artifacts_1, matched_results_1 = phantom.condition( container=container, conditions=[ [result, "==", True], # ["artifact:*.cef.src", "not in", "custom_list:Trusted"], ], name="filter_1:condition_1") # call connected blocks if filtered artifacts or results if matched_artifacts_1 or matched_results_1: filter_2(action=action, success=success, container=container, results=results, handle=handle, filtered_artifacts=matched_artifacts_1, filtered_results=matched_results_1) return
def parse_list(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs): phantom.debug("parse_list() called") playbook_input_list_name = phantom.collect2( container=container, datapath=["playbook_input:list_name"]) playbook_input_list_name_values = [ item[0] for item in playbook_input_list_name ] ################################################################################ ## Custom Code Start ################################################################################ # Write your custom code here... phantom.debug("parse_list input = {}".format(playbook_input_list_name)) sta, msg, read_list__peer_list = phantom.get_list( list_name=playbook_input_list_name_values[0]) for server in read_list__peer_list: if server[2] in ["critical", "high"]: phantom.debug("%s is priority %s" % (server[0], server[2])) status, message, cid = phantom.create_container( name="Possible server malware", label="events") #phantom.set_severity(cid, "high") phantom.add_artifact(container=cid, raw_data={}, cef_data={"sourceAddress": server[0]}, label="infection", name="Possibly infected host", severity="high", artifact_type="host") ################################################################################ ## Custom Code End ################################################################################ return
def format_2(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None): phantom.debug('format_2() called') container_data = phantom.collect2( container=container, datapath=[ 'filtered-data:filter_1:condition_1:artifact:*.cef.emailHeaders.Subject' ]) service = "" for result in container_data: if result[0] and "Service Alert" in result[0]: parts = result[0].split("** PROBLEM Service Alert: ") service = parts[1].split('/')[1].split(" process is CRITICAL")[0] ssh_command = "service {} restart".format(service) success, message, whitelist = phantom.get_list( "nagios_service_monitoring_service_name_whitelist") if [service] in whitelist: phantom.debug("service name whitelist check passed") else: phantom.error("service name whitelist check failed") phantom.comment(container=container, comment="service name whitelist check failed") ssh_command = "service name whitelist check failed" phantom.format(container=container, template=ssh_command, parameters=[""], name="format_2") join_execute_program_1(container=container) return
def read_list(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None): phantom.debug('read_list() called') input_parameter_0 = "" read_list__peer_list = None ################################################################################ ## Custom Code Start ################################################################################ # Write your custom code here... # Fetch list name from container data peer_list_name = phantom.get_container( container['id'])['data']["peer_list"] phantom.debug("peer list value ind data list: <<%s>>" % phantom.get_container(container['id'])['data']["peer_list"]) phantom.debug("peer_list_name = <<%s>>" % peer_list_name) sta, msg, read_list__peer_list = phantom.get_list(list_name=peer_list_name) ################################################################################ ## Custom Code End ################################################################################ phantom.save_run_data(key='read_list:peer_list', value=json.dumps(read_list__peer_list)) create_containers(container=container) return
def filter_1(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None): 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.requestURL", "!=", ""], ], name="filter_1:condition_1") # call connected blocks if filtered artifacts or results if matched_artifacts_1 or matched_results_1: success, message, domains = phantom.get_list(list_name='Domain Whitelist', column_index=0) phantom.debug( 'phantom.get_list results: success: {}, message: {}, Domains: {}'.format(success, message, domains) ) domains = [str(n[0]) for n in domains if n] phantom.debug(domains) matches = [] container_data = phantom.collect2(container=container, datapath=['filtered-data:filter_1:condition_1:artifact:*.cef.requestURL', 'filtered-data:filter_1:condition_1:artifact:*.id']) for container_item in container_data: phantom.debug(container_item) if any(n in container_item[0] for n in domains): phantom.debug(container_item[0]) continue else: matches.append(container_item) phantom.debug(matches) matched_artifacts_1 = matches detonate_url_1(action=action, success=success, container=container, results=results, handle=handle, filtered_artifacts=matched_artifacts_1, filtered_results=matched_results_1) return
def enumerate_hosts(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs): phantom.debug("enumerate_hosts() called") parameters = [] parameters.append({ "input_1": "log4j_hosts", "input_2": None, "input_3": None, "input_4": None, "input_5": None, "input_6": None, "input_7": None, "input_8": None, "input_9": None, "input_10": None, }) ################################################################################ ## Custom Code Start ################################################################################ # use custom code to read a custom list of potential log4j hosts and/or ip addresses # and make a json to create an artifact for each one. # the expected format of the custom list is: # hostname1 | unix # 1.1.1.1 | windows custom_list_name = parameters[0]['input_1'] success, message, rows = phantom.get_list(list_name=custom_list_name) # return early if the list is not found if not success: phantom.debug( "Failed to find the custom list, so only existing artifacts will be used" ) phantom.custom_function(custom_function="community/passthrough", parameters=[], name="enumerate_hosts", callback=create_host_artifacts) return # loop through the rows and create a list of artifact jsons to add # the two columns are expected to be the ip_or_hostname and the operating system family parameters = [] unix_hosts = [] windows_hosts = [] unknown_hosts = [] for row in rows: if row[0]: if row[1] != 'unix' and row[1] != 'windows': os_family = 'unknown' else: os_family = row[1] artifact_dict = { 'cef_data': { 'deviceHostname': row[0], 'operatingSystemFamily': os_family }, 'field_mapping': { 'deviceHostname': ['host name', 'ip'] } } parameters.append({'input_1': artifact_dict}) ################################################################################ ## Custom Code End ################################################################################ phantom.custom_function(custom_function="community/passthrough", parameters=parameters, name="enumerate_hosts", callback=create_host_artifacts) return
def enumerate_files_to_delete(action=None, success=None, container=None, results=None, handle=None, filtered_artifacts=None, filtered_results=None, custom_function=None, **kwargs): phantom.debug("enumerate_files_to_delete() called") parameters = [] parameters.append({ "input_1": "log4j_hosts_and_files", "input_2": None, "input_3": None, "input_4": None, "input_5": None, "input_6": None, "input_7": None, "input_8": None, "input_9": None, "input_10": None, }) ################################################################################ ## Custom Code Start ################################################################################ # use custom code to read a custom list of potential log4j files to delete # and make a json to create an artifact for each one. # the expected format of the custom list is: # hostname1 | unix | /full/path/to/delete/on/hostname_1 # 1.1.1.1 | windows | C:\\Full\Path\To\Delete\On\1_1_1_1 # # the list can either have all rows with files or no rows with files. some rows with files and some without will not work custom_list_name = parameters[0]['input_1'] success, message, rows = phantom.get_list(list_name=custom_list_name) # return early if the list is not found if not success: phantom.debug( "Failed to find the custom list, so only existing artifacts will be used" ) phantom.custom_function(custom_function="community/passthrough", parameters=[], name="enumerate_files_to_delete", callback=create_file_artifacts) return # loop through the rows and create a list of artifact jsons to add # the three columns are expected to be the ip_or_hostname, the operating system family, and the full path to the file to delete parameters = [] unix_hosts = [] windows_hosts = [] unknown_hosts = [] has_files = False if rows[0][2] and ('/' in rows[0][2] or '\\' in rows[0][2]): has_files = True for row in rows: # hostname and operating system are required, but file path is optional. files will not be deleted if file path is missing if row[0] and row[1]: # only windows and unix are supported, and operating system family is required if row[1] == 'unix' or row[1] == 'windows': artifact_dict = { 'cef_data': { 'deviceHostname': row[0], 'operatingSystemFamily': row[1], 'filePath': row[2] }, 'field_mapping': { 'deviceHostname': ['host name', 'ip'], 'filePath': ['file path'] } } # full paths should have at least one slash somewhere in them if row[2] and ('/' in row[2] or '\\' in row[2]): if has_files: artifact_dict['cef_data']['filePath'] = row[2] artifact_dict['field_mapping']['filePath'] = [ 'file path' ] else: phantom.debug( "skipping host {} with file {} because other rows did not have files" .format(row[0], row[2])) else: if has_files: phantom.error( "host {} is missing a file; playbook will be discontinued" .format(row[0])) phantom.discontinue() parameters.append({'input_1': artifact_dict}) ################################################################################ ## Custom Code End ################################################################################ phantom.custom_function(custom_function="community/passthrough", parameters=parameters, name="enumerate_files_to_delete", callback=create_file_artifacts) return
def L5_CF_Read_list_and_Create_Containers_From_List_py3_SOAR53_copy( container_label=None, listName=None, **kwargs): """ From a list of lists, create a set of Phantom containers and add in the appropriate artifacts Args: to_be_containerized: List of Lists for things to be containerized. container_label: This will be the label applied to the container Returns a JSON-serializable object that implements the configured data paths: new_container_ids: List of container id's that have been created by this Custom Function. Args: container_label listName Returns a JSON-serializable object that implements the configured data paths: new_container_ids """ ############################ Custom Code Goes Below This Line ################################# import json import phantom.rules as phantom import re outputs = {} # Write your custom code here... new_container_ids = [] temp_peer_track = [] read_list__peer_list = phantom.get_list(listName) for item in read_list__peer_list[0]: phantom.debug(item) (peer, priority, count) = item if peer not in temp_peer_track: if 'critical' in priority: priority = "high" temp_peer_track.append(peer) sta, msg, cid = phantom.create_container( name="Malware Peer found: %s" % peer, label=container_label) if re.match("^\d+\.\d+\.\d+\.\d+$", peer) is not None: phantom.add_artifact(container=cid, raw_data={}, cef_data={"destinationAddress": peer}, label=container_label, name="Malware IP Peer: %s" % peer, severity=priority, artifact_type="host") else: phantom.add_artifact(container=cid, raw_data={}, cef_data={"destinationHostName": peer}, label=container_label, name="Malware Hostname Peer: %s" % peer, severity=priority, artifact_type="host") new_container_ids.append(cid) outputs = {"new_container_ids": new_container_ids} # Return a JSON-serializable object assert json.dumps( outputs ) # Will raise an exception if the :outputs: object is not JSON-serializable return outputs