예제 #1
0
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
예제 #3
0
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
예제 #4
0
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
예제 #6
0
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
예제 #7
0
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
예제 #10
0
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
예제 #11
0
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
예제 #12
0
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
예제 #13
0
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
예제 #15
0
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
예제 #16
0
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
예제 #17
0
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