def _dt_utils_delete_rows_function(self, event, *args, **kwargs):
        """Function: Function that deletes rows from a Data Table"""

        log = logging.getLogger(__name__)

        try:
            # Instansiate new Resilient API object
            res_client = self.rest_client()
            workflow_id = event.message.get('workflow_instance', {}).get('workflow_instance_id')

            inputs = {
                "incident_id": get_function_input(kwargs, "incident_id"),  # number (required)
                "dt_utils_datatable_api_name": get_function_input(kwargs, "dt_utils_datatable_api_name"),  # text (required)
                "dt_utils_rows_ids": get_function_input(kwargs, "dt_utils_rows_ids", optional=True),  # text (optional)
                "dt_utils_search_column": get_function_input(kwargs, "dt_utils_search_column", optional=True),  # text (optional)
                "dt_utils_search_value": get_function_input(kwargs, "dt_utils_search_value", optional=True), # text (optional)
            }

            log.info("incident_id: {0}".format(inputs["incident_id"]))
            log.info("dt_utils_datatable_api_name: {0}".format(inputs["dt_utils_datatable_api_name"]))
            log.info("dt_utils_rows_ids: {0}".format(inputs["dt_utils_rows_ids"]))
            log.info("dt_utils_search_column: {0}".format(inputs["dt_utils_search_column"]))
            log.info(u"dt_utils_search_value: {0}".format(inputs["dt_utils_search_value"]))

            # Ensure correct search inputs are defined correctly
            valid_search_inputs = validate_search_inputs(rows_ids=inputs["dt_utils_rows_ids"],
                                                         search_column=inputs["dt_utils_search_column"],
                                                         search_value=inputs["dt_utils_search_value"])

            if not valid_search_inputs["valid"]:
                raise ValueError(valid_search_inputs["msg"])

            # Create payload dict with inputs
            payload = FunctionPayload(inputs)

            # Instantiate a new RESDatatable
            datatable = RESDatatable(res_client, payload.inputs["incident_id"],
                                     payload.inputs["dt_utils_datatable_api_name"])
            
            # get datatable row_id if function used on a datatable
            row_id = datatable.get_row_id_from_workflow(workflow_id)
            row_id and log.debug("Current row_id: %s", row_id)

            # Get the data table data
            datatable.get_data()

            deleted_rows = datatable.delete_rows(payload.inputs["dt_utils_rows_ids"], 
                                                 payload.inputs["dt_utils_search_column"], 
                                                 payload.inputs["dt_utils_search_value"],
                                                 row_id,
                                                 workflow_id)

            if not deleted_rows:
                yield StatusMessage("No row(s) found.")
                payload.success = False

            elif "error" in deleted_rows:
                yield StatusMessage(u"Row(s) not deleted. Error: {0}".format(deleted_rows["error"]))
                payload.success = False
                raise FunctionError("Failed to delete a row.")

            else:
                yield StatusMessage("Row(s) {0} in {1} deleted.".format(deleted_rows, datatable.api_name))
                payload.rows_ids = deleted_rows
                payload.success = True

            results = payload.as_dict()

            log.info("Complete")

            # Produce a FunctionResult with the results
            yield FunctionResult(results)
        except Exception:
            yield FunctionError()
    def _isitphishing_url_function(self, event, *args, **kwargs):
        """Function: isitphishing_url
        This function takes URL string as a required parameter.  It
        queries the Vade Secure isiphishing API to analyze the
        URL to determine if it is PHISHING or SPAM.
        The results contain the result of the query in 'contents' and the
        URL input parameter to the function.
        """
        try:
            rp = ResultPayload(CONFIG_DATA_SECTION, **kwargs)

            # Get the function parameters:
            isitphishing_url = kwargs.get("isitphishing_url")  # text

            log = logging.getLogger(__name__)
            log.info("isitphishing_url: %s", isitphishing_url)

            # Form the URL for API request.
            API_URL = u"{0}/url".format(self.options["isitphishing_api_url"])

            # Get the license key to access the API endpoint.
            auth_token = get_license_key(self.options["isitphishing_name"],
                                         self.options["isitphishing_license"])

            # Build the header and the data payload.
            headers = {
                "Authorization": u'Bearer {}'.format(auth_token),
                "Content-type": "application/json",
            }

            payload = {
                "url": isitphishing_url,
                "force": False,
                "smart": True,
                "timeout": 8000
            }

            yield StatusMessage(
                "Query isitPhishing.org endpoint for status of URL {0}.".
                format(isitphishing_url))

            # Make URL request
            rc = RequestsCommon(self.opts, self.options)
            results_analysis = rc.execute_call("post",
                                               API_URL,
                                               payload,
                                               log=log,
                                               headers=headers)

            results = rp.done(True, results_analysis)

            # Send back the results and the input parameter.
            results = {
                "content": results_analysis,
                "inputs": {
                    "URL": isitphishing_url
                }
            }

            # Produce a FunctionResult with the results
            yield FunctionResult(results)
        except Exception as err:
            yield FunctionError(err)
예제 #3
0
    def _fn_calendar_invite_function(self, event, *args, **kwargs):
        """Function: A function to invite people to a meeting via a calendar invite"""
        try:
            # Get the calendar meeting information input
            calendar_invite_datetime = kwargs.get(
                "calendar_invite_datetime")  # datetime picker
            calendar_invite_subject = kwargs.get(
                "calendar_invite_subject")  # text
            calendar_invite_description = kwargs.get(
                "calendar_invite_description")  # text area
            calendar_invite_extra_email_addr = kwargs.get(
                "calendar_invite_extra_email_addr")  # text area
            incident_id = kwargs.get("calendar_invite_incident_id")  # number

            log = logging.getLogger(__name__)
            log.info(u"calendar_invite_datetime: %s", calendar_invite_datetime)
            log.info(u"calendar_invite_subject: %s", calendar_invite_subject)
            log.info(u"calendar_invite_description: %s",
                     calendar_invite_description)
            log.info(u"calendar_invite_extra_email_addr %s",
                     calendar_invite_extra_email_addr)

            # Email sender information
            host = self.email_host
            port = int(self.email_port)
            nickname = self.email_nickname
            e_login = self.email_username
            e_password = self.email_password

            now_utc = datetime.datetime.utcnow()
            meeting_time_utc = datetime.datetime.utcfromtimestamp(
                calendar_invite_datetime / 1000)
            if now_utc > meeting_time_utc:
                log.error("Calendar date and time for meeting is in the past.")
                raise ValueError(
                    "Calendar date and time for meeting is in the past.")

            # Get email addresses of the members and owner of the incident.
            client = self.rest_client()
            attendees = get_email_addresses(client, log, incident_id,
                                            calendar_invite_extra_email_addr)

            yield StatusMessage("Sending Emails to {}".format(attendees))

            # Build the email message string to be sent.
            sender = u"{} <{}>".format(nickname, e_login)
            email_message_string = build_email_message(
                calendar_invite_datetime, calendar_invite_subject,
                calendar_invite_description, nickname, e_login, sender,
                attendees)

            yield StatusMessage("Connecting to Mail Server")

            # Connect to SMTP server and send the message.
            send_email(host, port, sender, e_login, e_password, attendees,
                       email_message_string)

            yield StatusMessage("Send Mail - Complete")

            results = {
                "recipient": attendees,
                "sender": sender,
                "subject": calendar_invite_subject,
                "description": calendar_invite_description
            }

            # Produce a FunctionResult with the results
            yield FunctionResult(results)
        except Exception as e:
            yield FunctionError()
    def _cb_retrieve_process_list_function(self, event, *args, **kwargs):

        results = {}
        results["was_successful"] = False
        results["hostname"] = None
        lock_acquired = False

        try:
            # Get the function parameters:
            incident_id = kwargs.get("incident_id")  # number
            hostname = kwargs.get("hostname")  # text

            log = logging.getLogger(__name__)  # Establish logging

            days_later_timeout_length = datetime.datetime.now(
            ) + datetime.timedelta(
                days=DAYS_UNTIL_TIMEOUT)  # Max duration length before aborting
            hostname = hostname.upper(
            )[:15]  # CB limits hostname to 15 characters
            sensor = cb.select(Sensor).where(
                'hostname:' + hostname)  # Query CB for the hostname's sensor
            timeouts = 0  # Number of timeouts that have occurred

            if len(sensor) <= 0:  # Host does not have CB agent, abort
                yield StatusMessage(
                    "[FATAL ERROR] CB could not find hostname: " +
                    str(hostname))
                yield FunctionResult(results)
                return

            sensor = sensor[0]  # Get the sensor object from the query
            results["hostname"] = str(hostname).upper()

            while timeouts <= MAX_TIMEOUTS:  # Max timeouts before aborting

                try:

                    now = datetime.datetime.now()

                    # Check if the sensor is queued to restart, wait up to 90 seconds before checking online status
                    three_minutes_passed = datetime.datetime.now(
                    ) + datetime.timedelta(minutes=3)
                    while (sensor.restart_queued is
                           True) and (three_minutes_passed >= now):
                        time.sleep(3)  # Give the CPU a break, it works hard!
                        now = datetime.datetime.now()
                        sensor = (cb.select(Sensor).where('hostname:' +
                                                          hostname)
                                  )[0]  # Retrieve the latest sensor vitals

                    # Check online status
                    if sensor.status != "Online":
                        yield StatusMessage('[WARNING] Hostname: ' +
                                            str(hostname) +
                                            ' is offline. Will attempt for ' +
                                            str(DAYS_UNTIL_TIMEOUT) +
                                            ' days...')

                    # Check lock status
                    if os.path.exists(
                            '/home/integrations/.resilient/cb_host_locks/{}.lock'
                            .format(hostname)):
                        yield StatusMessage(
                            '[WARNING] A running action has a lock on  ' +
                            str(hostname) + '. Will attempt for ' +
                            str(DAYS_UNTIL_TIMEOUT) + ' days...')

                    # Wait for offline and locked hosts for days_later_timeout_length
                    while (sensor.status != "Online" or os.path.exists(
                            '/home/integrations/.resilient/cb_host_locks/{}.lock'
                            .format(hostname))) and (days_later_timeout_length
                                                     >= now):
                        time.sleep(3)  # Give the CPU a break, it works hard!
                        now = datetime.datetime.now()
                        sensor = (cb.select(Sensor).where('hostname:' +
                                                          hostname)
                                  )[0]  # Retrieve the latest sensor vitals

                    # Abort after DAYS_UNTIL_TIMEOUT
                    if sensor.status != "Online" or os.path.exists(
                            '/home/integrations/.resilient/cb_host_locks/{}.lock'
                            .format(hostname)):
                        yield StatusMessage('[FATAL ERROR] Hostname: ' +
                                            str(hostname) +
                                            ' is still offline!')
                        yield FunctionResult(results)
                        return

                    # Check if the sensor is queued to restart, wait up to 90 seconds before continuing
                    three_minutes_passed = datetime.datetime.now(
                    ) + datetime.timedelta(minutes=3)
                    while (sensor.restart_queued is True) and (
                            three_minutes_passed >= now
                    ):  # If the sensor is queued to restart, wait up to 90 seconds
                        time.sleep(3)  # Give the CPU a break, it works hard!
                        now = datetime.datetime.now()
                        sensor = (cb.select(Sensor).where('hostname:' +
                                                          hostname)
                                  )[0]  # Retrieve the latest sensor vitals

                    # Verify the incident still exists and is reachable, if not abort
                    try:
                        incident = self.rest_client().get(
                            '/incidents/{0}?text_content_output_format=always_text&handle_format=names'
                            .format(str(incident_id)))
                    except Exception as err:
                        if err.message and "not found" in err.message.lower():
                            log.info('[FATAL ERROR] Incident ID ' +
                                     str(incident_id) + ' no longer exists.')
                            log.info('[FAILURE] Fatal error caused exit!')
                        else:
                            log.info(
                                '[FATAL ERROR] Incident ID ' +
                                str(incident_id) +
                                ' could not be reached, Resilient instance may be down.'
                            )
                            log.info('[FAILURE] Fatal error caused exit!')
                        return

                    # Acquire host lock
                    try:
                        f = os.fdopen(
                            os.open(
                                '/home/integrations/.resilient/cb_host_locks/{}.lock'
                                .format(hostname),
                                os.O_CREAT | os.O_WRONLY | os.O_EXCL), 'w')
                        f.close()
                        lock_acquired = True
                    except OSError:
                        continue

                    # Establish a session to the host sensor
                    yield StatusMessage(
                        '[INFO] Establishing session to CB Sensor #' +
                        str(sensor.id) + ' (' + sensor.hostname + ')')
                    session = cb.live_response.request_session(sensor.id)
                    yield StatusMessage('[SUCCESS] Connected on Session #' +
                                        str(session.session_id) +
                                        ' to CB Sensor #' + str(sensor.id) +
                                        ' (' + sensor.hostname + ')')

                    process_list = session.list_processes(
                    )  # Retrieve process information list
                    with tempfile.NamedTemporaryFile(
                            delete=False
                    ) as temp_file:  # Create temporary temp_file for the CSV file
                        try:
                            with UnicodeWriter(temp_file.name) as csv_writer:
                                csv_writer.writerow([
                                    'Process Name:', 'Path:', 'Command Line:',
                                    'Username:'******'PID:', 'SID:', 'Parent PID:',
                                    'Created Time:', 'Process CB URL:'
                                ])
                                for process in process_list:
                                    process_exe = process['path'].rsplit(
                                        '\\')[-1]
                                    process_path = process['path']
                                    process_commandline = process[
                                        'command_line']
                                    process_username = process['username']
                                    process_pid = str(process['pid'])
                                    process_sid = str(process['sid'])
                                    process_parent_pid = str(process['parent'])
                                    process_created_time = datetime.datetime.fromtimestamp(
                                        process['create_time']).strftime(
                                            '%Y-%m-%d %H:%M:%S')
                                    process_cb_url = str(
                                        cb.url + r'/#/analyze/' +
                                        process['proc_guid'] + r'/' +
                                        str(process['create_time']) +
                                        '?cb.legacy_5x_mode=false')

                                    csv_writer.writerow([
                                        process_exe, process_path,
                                        process_commandline, process_username,
                                        process_pid, process_sid,
                                        process_parent_pid,
                                        process_created_time, process_cb_url
                                    ])

                            yield StatusMessage(
                                '[SUCCESS] Retrieved process data file from Sensor!'
                            )
                            self.rest_client().post_attachment(
                                '/incidents/{0}/attachments'.format(
                                    incident_id), temp_file.name,
                                '{0}-running_processes.csv'.format(
                                    sensor.hostname)
                            )  # Post temp_file to incident
                            yield StatusMessage(
                                '[SUCCESS] Posted CSV data file to the incident as an attachment!'
                            )

                        finally:
                            os.unlink(
                                temp_file.name)  # Delete temporary temp_file

                except TimeoutError:  # Catch TimeoutError and handle
                    timeouts = timeouts + 1
                    if timeouts <= MAX_TIMEOUTS:
                        yield StatusMessage(
                            '[ERROR] TimeoutError was encountered. Reattempting... ('
                            + str(timeouts) + '/' + str(MAX_TIMEOUTS) + ')')
                        try:
                            session.close()
                        except:
                            pass
                        sensor = (cb.select(Sensor).where('hostname:' +
                                                          hostname)
                                  )[0]  # Retrieve the latest sensor vitals
                        sensor.restart_sensor(
                        )  # Restarting the sensor may avoid a timeout from occurring again
                        time.sleep(30)  # Sleep to apply sensor restart
                        sensor = (cb.select(Sensor).where('hostname:' +
                                                          hostname)
                                  )[0]  # Retrieve the latest sensor vitals
                    else:
                        yield StatusMessage(
                            '[FATAL ERROR] TimeoutError was encountered. The maximum number of retries was reached. Aborting!'
                        )
                        yield StatusMessage(
                            '[FAILURE] Fatal error caused exit!')
                    continue

                except (
                        ApiError, ProtocolError, NewConnectionError,
                        ConnectTimeoutError, MaxRetryError
                ) as err:  # Catch urllib3 connection exceptions and handle
                    if 'ApiError' in str(
                            type(err).__name__
                    ) and 'network connection error' not in str(err):
                        raise  # Only handle ApiError involving network connection error
                    timeouts = timeouts + 1
                    if timeouts <= MAX_TIMEOUTS:
                        yield StatusMessage(
                            '[ERROR] Carbon Black was unreachable. Reattempting in 30 minutes... ('
                            + str(timeouts) + '/' + str(MAX_TIMEOUTS) + ')')
                        time.sleep(
                            1800
                        )  # Sleep for 30 minutes, backup service may have been running.
                    else:
                        yield StatusMessage(
                            '[FATAL ERROR] ' + str(type(err).__name__) +
                            ' was encountered. The maximum number of retries was reached. Aborting!'
                        )
                        yield StatusMessage(
                            '[FAILURE] Fatal error caused exit!')
                    continue

                except Exception as err:  # Catch all other exceptions and abort
                    yield StatusMessage('[FATAL ERROR] Encountered: ' +
                                        str(err))
                    yield StatusMessage('[FAILURE] Fatal error caused exit!')

                else:
                    results["was_successful"] = True

                try:
                    session.close()
                except:
                    pass
                yield StatusMessage(
                    '[INFO] Session has been closed to CB Sensor #' +
                    str(sensor.id) + '(' + sensor.hostname + ')')
                break

            # Release the host lock if acquired
            if lock_acquired is True:
                os.remove(
                    '/home/integrations/.resilient/cb_host_locks/{}.lock'.
                    format(hostname))

            # Produce a FunctionResult with the results
            yield FunctionResult(results)
        except Exception:
            yield FunctionError()
예제 #5
0
    def _ldap_md_utilities_search_function(self, event, *args, **kwargs):
        """Function: Resilient Function to do a search or query against an LDAP server."""

        ##############################################
        def replace_ldap_param(ldap_param_value=None, ldap_search_filter=""):

            re_pattern = "%ldap_param%"

            # if "ldap_param" in ldap_search_filter:

            if re.search(re_pattern, ldap_search_filter) is not None:
                if ldap_param_value is None:
                    raise ValueError("The LDAP Search Filter '{0}' contains the key '%ldap_param%' but no value has been given for ldap_search_param.".format(ldap_search_filter))

                else:
                    # Insert escaped param value in filter, need to escape any backslashes X 2 for regex.
                    ldap_search_filter = re.sub(re_pattern, ldap_param_value.replace('\\', '\\\\'), ldap_search_filter)

            return ldap_search_filter
        ##############################################


        try:

            # Get the wf_instance_id of the workflow this Function was called in
            wf_instance_id = event.message["workflow_instance"]["workflow_instance_id"]

            yield StatusMessage("Starting 'ldap_md_utilities_search' running in workflow '{0}'".format(wf_instance_id))

            # Get the function parameters:
            ldap_md_domain_name = kwargs.get("ldap_md_domain_name")  # text
            ldap_md_search_attributes = kwargs.get("ldap_md_search_attributes")  # text
            ldap_md_search_param = kwargs.get("ldap_md_search_param")  # text
            ldap_md_search_base = kwargs.get("ldap_md_search_base")  # text
            ldap_md_search_filter = self.get_textarea_param(kwargs.get("ldap_md_search_filter"))  # textarea

            log = logging.getLogger(__name__)
            log.info("ldap_md_domain_name: %s", ldap_md_domain_name)
            log.info("ldap_md_search_attributes: %s", ldap_md_search_attributes)
            log.info("ldap_md_search_param: %s", ldap_md_search_param)
            log.info("ldap_md_search_base: %s", ldap_md_search_base)
            log.info("ldap_md_search_filter: %s", ldap_md_search_filter)
            yield StatusMessage("Function Inputs OK")


            # Instansiate helper (which gets appconfigs from file)
            helper = LDAPUtilitiesHelper(self.options, ldap_md_domain_name)
            log.info("[app.config] -ldap_server: %s", helper.LDAP_SERVER)
            #log.info("[app.config] -ldap_pas: %s", helper.LDAP_PAS)
            log.info("[app.config] -ldap_user_dn: %s", helper.LDAP_USER_DN)
            yield StatusMessage("Appconfig Settings OK")


            ##############################################
            # If search_attributes is not specified, request that ALL_ATTRIBUTES for the DN be returned
            if ldap_md_search_attributes is None:
                ldap_md_search_attributes = ALL_ATTRIBUTES
            else:
                ldap_md_search_attributes = [str(attr) for attr in ldap_md_search_attributes.split(',')]

            if ldap_md_search_param is not None:
                # Escape special chars from the search_param
                ldap_md_search_param = escape_filter_chars(ldap_md_search_param)

            ldap_md_search_filter = replace_ldap_param(ldap_md_search_param, ldap_md_search_filter)

            # Instansiate LDAP Server and Connection
            conn = helper.get_ldap_connection()

            try:
                # Bind to the connection
                conn.bind()
            except Exception as err:
                raise ValueError("Cannot connect to LDAP Server. Ensure credentials are correct\n Error: {0}".format(err))

            try:
                # Inform user
                yield StatusMessage("Connected to {0}".format("Active Directory" if helper.LDAP_IS_ACTIVE_DIRECTORY else "LDAP Server"))

                res = None
                entries = []
                success = False

                yield StatusMessage("Attempting to Search")

                res = conn.search(
                    search_base=ldap_md_search_base,
                    search_filter=ldap_md_search_filter,
                    attributes=ldap_md_search_attributes)

                if res and len(conn.entries) > 0:

                    entries = json.loads(conn.response_to_json())["entries"]
                    log.info("Result contains %s entries", len(entries))

                    # Each entry has 'dn' and dict of 'attributes'. Move attributes to the top level for easier processing.
                    for entry in entries:
                        entry.update(entry.pop("attributes", None))

                    yield StatusMessage("{0} entries found".format(len(entries)))
                    success = True

                else:
                    yield StatusMessage("No entries found")
                    success = False

            except LDAPSocketOpenError:
                success = False
                raise ValueError("Invalid Search Base", ldap_md_search_base)

            except Exception as err:
                success = False
                raise ValueError("Could not Search the LDAP Server. Ensure 'ldap_search_base' is valid", err)

            finally:
                # Unbind connection
                conn.unbind()

            ##############################################


            results = {
                "success": success,
                "domain_name": ldap_md_domain_name,
                "entries": entries
            }

            yield StatusMessage("Finished 'ldap_md_utilities_search' that was running in workflow '{0}'".format(wf_instance_id))

            # Produce a FunctionResult with the results
            yield FunctionResult(results)
        except Exception:
            yield FunctionError()
    def _base64_to_artifact_function(self, event, *args, **kwargs):
        """Function: """
        try:
            log = logging.getLogger(__name__)

            # Get the function parameters:
            # artifact_file_type:
            # "Email Attachment", "Malware Sample", "Log File", "X509 Certificate File", "Other File", etc.
            base64content = kwargs.get("base64content")  # text
            incident_id = kwargs.get("incident_id")  # number
            artifact_file_type = self.get_select_param(
                kwargs.get("artifact_file_type")) or "Malware Sample"
            file_name = kwargs.get("file_name")  # text
            content_type = kwargs.get("content_type")  # text
            description = self.get_textarea_param(
                kwargs.get("description"))  # textarea

            content_type = content_type \
                           or mimetypes.guess_type(file_name or "")[0] \
                           or "application/octet-stream"

            log.info("incident_id: %s", incident_id)
            log.info("artifact_file_type: %s", artifact_file_type)
            log.info("file_name: %s", file_name)
            log.info("content_type: %s", content_type)
            log.info("description: %s", description)

            # To post a file artifact, we need to specify the type by id
            # So, look up the id of the type name
            client = self.rest_client()
            artifact_type_id = 16  # Other File (as default)
            for value in client.cached_get(
                    "/types/artifact/fields/type")["values"]:
                if artifact_file_type == value["label"]:
                    artifact_type_id = value["value"]
                    break

            yield StatusMessage("Writing artifact...")
            with tempfile.NamedTemporaryFile(delete=False) as temp_file:
                try:
                    temp_file.write(base64.b64encode(s_to_b(base64content)))
                    temp_file.close()
                    # Create a new artifact
                    client = self.rest_client()
                    artifact_uri = "/incidents/{}/artifacts/files".format(
                        incident_id)
                    new_artifact = client.post_artifact_file(
                        artifact_uri,
                        artifact_type_id,
                        temp_file.name,
                        description=description,
                        value=file_name,
                        mimetype=content_type)
                finally:
                    os.unlink(temp_file.name)

            # Produce a FunctionResult with the return value
            if isinstance(new_artifact, list):
                new_artifact = new_artifact[0]
            log.info(json.dumps(new_artifact))
            yield FunctionResult(new_artifact)
        except Exception:
            yield FunctionError()
    def _exchange_find_emails_function(self, event, *args, **kwargs):
        """Function: """
        try:
            # Get the function parameters:
            exchange_email = kwargs.get("exchange_email")  # text
            exchange_folder_path = kwargs.get("exchange_folder_path")  # text
            exchange_email_ids = kwargs.get("exchange_email_ids")  # text
            exchange_sender = kwargs.get("exchange_sender")  # text
            exchange_message_subject = kwargs.get(
                "exchange_message_subject")  # text
            exchange_message_body = kwargs.get("exchange_message_body")  # text
            exchange_start_date = kwargs.get(
                "exchange_start_date")  # datepicker
            exchange_end_date = kwargs.get("exchange_end_date")  # datepicker
            exchange_has_attachments = kwargs.get(
                "exchange_has_attachments")  # boolean
            exchange_order_by_recency = kwargs.get(
                "exchange_order_by_recency")  # boolean
            exchange_num_emails = kwargs.get("exchange_num_emails")  # int
            exchange_search_subfolders = kwargs.get(
                "exchange_search_subfolders")  # boolean

            log = logging.getLogger(__name__)
            if exchange_folder_path is None:
                exchange_folder_path = self.options.get('default_folder_path')
                log.info(
                    'No folder path was specified, using value from config file'
                )
            log.info("exchange_email: %s" % exchange_email)
            log.info("exchange_folder_path: %s" % exchange_folder_path)
            log.info("exchange_email_ids: %s" % exchange_email_ids)
            log.info("exchange_sender: %s" % exchange_sender)
            log.info("exchange_message_subject: %s" % exchange_message_subject)
            log.info("exchange_message_body: %s" % exchange_message_body)
            log.info("exchange_start_date: %s" % exchange_start_date)
            log.info("exchange_end_date: %s" % exchange_end_date)
            log.info("exchange_has_attachments: %s" % exchange_has_attachments)
            log.info("exchange_order_by_recency: %s" %
                     exchange_order_by_recency)
            log.info("exchange_num_emails: %s" % exchange_num_emails)
            log.info("exchange_search_subfolders: %s" %
                     exchange_search_subfolders)

            # Initialize utils
            utils = exchange_utils(self.options, self.opts)

            # Find emails
            yield StatusMessage("Finding emails")
            emails = utils.get_emails(
                exchange_email, exchange_folder_path, exchange_email_ids,
                exchange_sender, exchange_message_subject,
                exchange_message_body, exchange_start_date, exchange_end_date,
                exchange_has_attachments, exchange_order_by_recency,
                exchange_num_emails, exchange_search_subfolders)
            yield StatusMessage("Done finding emails, %d emails found" %
                                emails.count())

            # Populate results with query data
            results = utils.create_email_function_results(emails)

            # Produce a FunctionResult with the results
            yield FunctionResult(results)
        except Exception:
            yield FunctionError()
    def _exchange_online_write_message_as_attachment_function(
            self, event, *args, **kwargs):
        """Function: This function will get the mime content of an Exchange Online message and write it as an
           attachment."""
        try:
            # Initialize the results payload
            rp = ResultPayload(CONFIG_DATA_SECTION, **kwargs)

            # Validate fields
            validate_fields(
                ['incident_id', 'exo_email_address', 'exo_messages_id'],
                kwargs)

            # Get the function parameters:
            incident_id = kwargs.get("incident_id")  # number
            task_id = kwargs.get("task_id")  # number
            email_address = kwargs.get("exo_email_address")  # text
            message_id = kwargs.get("exo_messages_id")  # text
            attachment_name = kwargs.get("exo_attachment_name")  # text

            LOG.info(u"incident_id: %s", incident_id)
            LOG.info(u"task_id: %s", task_id)
            LOG.info(u"exo_email_address: %s", email_address)
            LOG.info(u"exo_messages_id: %s", message_id)
            LOG.info(u"exo_attachment_name: %s", attachment_name)

            yield StatusMessage(
                u"Starting to get message mime for email address: {}".format(
                    email_address))

            # Get the MS Graph helper class
            MS_graph_helper = MSGraphHelper(
                self.options.get("microsoft_graph_token_url"),
                self.options.get("microsoft_graph_url"),
                self.options.get("tenant_id"), self.options.get("client_id"),
                self.options.get("client_secret"),
                self.options.get("max_messages"),
                self.options.get("max_users"),
                RequestsCommon(self.opts, self.options).get_proxies())

            # Call MS Graph API to get the user profile
            response = MS_graph_helper.get_message_mime(
                email_address, message_id)

            datastream = BytesIO(response.content)

            if attachment_name is None:
                attachment_name = u"message-{}-{}.eml".format(
                    email_address, message_id)
                LOG.info(u"attachment_name: %s", attachment_name)

            # Get the rest client so we can add the attachment to the incident.
            rest_client = self.rest_client()

            # Write the file as attachement: failures will raise an exception
            write_file_attachment(rest_client, attachment_name, datastream,
                                  incident_id, task_id)

            results_data = {"attachment_name": attachment_name}
            results = rp.done(True, results_data)

            yield StatusMessage(
                u"Returning results for get message mime for email address: {0}\n attachment name: {1}"
                .format(email_address, attachment_name))

            # Produce a FunctionResult with the results
            yield FunctionResult(results)
        except Exception as err:
            LOG.error(err)
            yield FunctionError(err)
    def _cb_refresh_av_signatures(self, event, *args, **kwargs):

        results = {}
        results["was_successful"] = False
        results["hostname"] = None
        lock_acquired = False

        try:
            # Get the function parameters:
            incident_id = kwargs.get("incident_id")  # number
            hostname = kwargs.get("hostname")  # text

            log = logging.getLogger(__name__)  # Establish logging

            days_later_timeout_length = datetime.datetime.now(
            ) + datetime.timedelta(
                days=DAYS_UNTIL_TIMEOUT)  # Max duration length before aborting
            hostname = hostname.upper(
            )[:15]  # CB limits hostname to 15 characters
            sensor = cb.select(Sensor).where(
                'hostname:' + hostname)  # Query CB for the hostname's sensor
            timeouts = 0  # Number of timeouts that have occurred

            if len(sensor) <= 0:  # Host does not have CB agent, abort
                yield StatusMessage(
                    "[FATAL ERROR] CB could not find hostname: " +
                    str(hostname))
                yield FunctionResult(results)
                return

            sensor = sensor[0]  # Get the sensor object from the query
            results["hostname"] = str(hostname).upper()

            while timeouts <= MAX_TIMEOUTS:  # Max timeouts before aborting

                try:

                    now = datetime.datetime.now()

                    # Check if the sensor is queued to restart, wait up to 90 seconds before checking online status
                    three_minutes_passed = datetime.datetime.now(
                    ) + datetime.timedelta(minutes=3)
                    while (sensor.restart_queued is
                           True) and (three_minutes_passed >= now):
                        time.sleep(3)  # Give the CPU a break, it works hard!
                        now = datetime.datetime.now()
                        sensor = (cb.select(Sensor).where('hostname:' +
                                                          hostname)
                                  )[0]  # Retrieve the latest sensor vitals

                    # Check online status
                    if sensor.status != "Online":
                        yield StatusMessage('[WARNING] Hostname: ' +
                                            str(hostname) +
                                            ' is offline. Will attempt for ' +
                                            str(DAYS_UNTIL_TIMEOUT) +
                                            ' days...')

                    # Check lock status
                    if os.path.exists(
                            '/home/integrations/.resilient/cb_host_locks/{}.lock'
                            .format(hostname)):
                        yield StatusMessage(
                            '[WARNING] A running action has a lock on  ' +
                            str(hostname) + '. Will attempt for ' +
                            str(DAYS_UNTIL_TIMEOUT) + ' days...')

                    # Wait for offline and locked hosts for days_later_timeout_length
                    while (sensor.status != "Online" or os.path.exists(
                            '/home/integrations/.resilient/cb_host_locks/{}.lock'
                            .format(hostname))) and (days_later_timeout_length
                                                     >= now):
                        time.sleep(3)  # Give the CPU a break, it works hard!
                        now = datetime.datetime.now()
                        sensor = (cb.select(Sensor).where('hostname:' +
                                                          hostname)
                                  )[0]  # Retrieve the latest sensor vitals

                    # Abort after DAYS_UNTIL_TIMEOUT
                    if sensor.status != "Online" or os.path.exists(
                            '/home/integrations/.resilient/cb_host_locks/{}.lock'
                            .format(hostname)):
                        yield StatusMessage('[FATAL ERROR] Hostname: ' +
                                            str(hostname) +
                                            ' is still offline!')
                        yield FunctionResult(results)
                        return

                    # Check if the sensor is queued to restart, wait up to 90 seconds before continuing
                    three_minutes_passed = datetime.datetime.now(
                    ) + datetime.timedelta(minutes=3)
                    while (sensor.restart_queued is True) and (
                            three_minutes_passed >= now
                    ):  # If the sensor is queued to restart, wait up to 90 seconds
                        time.sleep(3)  # Give the CPU a break, it works hard!
                        now = datetime.datetime.now()
                        sensor = (cb.select(Sensor).where('hostname:' +
                                                          hostname)
                                  )[0]  # Retrieve the latest sensor vitals

                    # Verify the incident still exists and is reachable, if not abort
                    try:
                        incident = self.rest_client().get(
                            '/incidents/{0}?text_content_output_format=always_text&handle_format=names'
                            .format(str(incident_id)))
                    except Exception as err:
                        if err.message and "not found" in err.message.lower():
                            log.info('[FATAL ERROR] Incident ID ' +
                                     str(incident_id) + ' no longer exists.')
                            log.info('[FAILURE] Fatal error caused exit!')
                        else:
                            log.info(
                                '[FATAL ERROR] Incident ID ' +
                                str(incident_id) +
                                ' could not be reached, Resilient instance may be down.'
                            )
                            log.info('[FAILURE] Fatal error caused exit!')
                        return

                    # Acquire host lock
                    try:
                        f = os.fdopen(
                            os.open(
                                '/home/integrations/.resilient/cb_host_locks/{}.lock'
                                .format(hostname),
                                os.O_CREAT | os.O_WRONLY | os.O_EXCL), 'w')
                        f.close()
                        lock_acquired = True
                    except OSError:
                        continue

                    # Establish a session to the host sensor
                    yield StatusMessage(
                        '[INFO] Establishing session to CB Sensor #' +
                        str(sensor.id) + ' (' + sensor.hostname + ')')
                    session = cb.live_response.request_session(sensor.id)
                    yield StatusMessage('[SUCCESS] Connected on Session #' +
                                        str(session.session_id) +
                                        ' to CB Sensor #' + str(sensor.id) +
                                        ' (' + sensor.hostname + ')')

                    av_paths = []

                    try:
                        session.list_directory(
                            r'C:\Program Files\Microsoft Security Client\mpcmdrun.exe'
                        )
                        av_paths.append(
                            r'C:\Program Files\Microsoft Security Client\mpcmdrun.exe'
                        )
                    except TimeoutError:
                        raise
                    except Exception:
                        pass

                    try:
                        session.list_directory(
                            r'C:\Program Files\Windows Defender\mpcmdrun.exe')
                        av_paths.append(
                            r'C:\Program Files\Windows Defender\mpcmdrun.exe')
                    except TimeoutError:
                        raise
                    except Exception:
                        pass

                    if len(
                            av_paths
                    ) > 1:  # If both Defender and Microsoft Security Client were found
                        for process in session.list_processes(
                        ):  # Use whichever version is running on the endpoint
                            if ('c:\program files\microsoft security client'
                                    in process['path'].lower()):
                                av_paths.remove(
                                    'C:\Program Files\Windows Defender\mpcmdrun.exe'
                                )
                                break

                            elif ('c:\program files\windows defender'
                                  in process['path'].lower()):
                                av_paths.remove(
                                    'C:\Program Files\Microsoft Security Client\mpcmdrun.exe'
                                )
                                break

                    for detected_av_path in av_paths:
                        o = session.create_process(
                            detected_av_path + ' -RemoveDefinitions -All',
                            True, None, None, 300)
                        yield StatusMessage(
                            '[SUCCESS] Signature removal command sent to ' +
                            detected_av_path)
                        log.info('[INFO] Output was: \n' + str(o))
                        results["remove_definitions_output"] = str(o)

                        o = session.create_process(
                            detected_av_path + ' -SignatureUpdate', True, None,
                            None, 300)
                        log.info(
                            '[SUCCESS] Signature update command sent to ' +
                            detected_av_path)
                        yield StatusMessage(
                            '[SUCCESS] Signature update command sent to ' +
                            detected_av_path)
                        log.info('[INFO] Output was: \n' + str(o))
                        results["signature_update_output"] = str(o)

                    if len(av_paths) == 0:
                        yield StatusMessage(
                            '[ERROR] Neither Windows Defender nor Microsoft Security Client were detected.'
                        )
                        yield StatusMessage(
                            '[FAILURE] Signatures were neither cleaned nor updated!'
                        )
                        results["was_successful"] = False
                        try:
                            session.close()
                        except:
                            pass
                        break

                except TimeoutError:  # Catch TimeoutError and handle
                    timeouts = timeouts + 1
                    if timeouts <= MAX_TIMEOUTS:
                        yield StatusMessage(
                            '[ERROR] TimeoutError was encountered. Reattempting... ('
                            + str(timeouts) + '/' + str(MAX_TIMEOUTS) + ')')
                        try:
                            session.close()
                        except:
                            pass
                        sensor = (cb.select(Sensor).where('hostname:' +
                                                          hostname)
                                  )[0]  # Retrieve the latest sensor vitals
                        sensor.restart_sensor(
                        )  # Restarting the sensor may avoid a timeout from occurring again
                        time.sleep(30)  # Sleep to apply sensor restart
                        sensor = (cb.select(Sensor).where('hostname:' +
                                                          hostname)
                                  )[0]  # Retrieve the latest sensor vitals
                    else:
                        yield StatusMessage(
                            '[FATAL ERROR] TimeoutError was encountered. The maximum number of retries was reached. Aborting!'
                        )
                        yield StatusMessage(
                            '[FAILURE] Fatal error caused exit!')
                    continue

                except (
                        ApiError, ProtocolError, NewConnectionError,
                        ConnectTimeoutError, MaxRetryError
                ) as err:  # Catch urllib3 connection exceptions and handle
                    if 'ApiError' in str(
                            type(err).__name__
                    ) and 'network connection error' not in str(err):
                        raise  # Only handle ApiError involving network connection error
                    timeouts = timeouts + 1
                    if timeouts <= MAX_TIMEOUTS:
                        yield StatusMessage(
                            '[ERROR] Carbon Black was unreachable. Reattempting in 30 minutes... ('
                            + str(timeouts) + '/' + str(MAX_TIMEOUTS) + ')')
                        time.sleep(
                            1800
                        )  # Sleep for 30 minutes, backup service may have been running.
                    else:
                        yield StatusMessage(
                            '[FATAL ERROR] ' + str(type(err).__name__) +
                            ' was encountered. The maximum number of retries was reached. Aborting!'
                        )
                        yield StatusMessage(
                            '[FAILURE] Fatal error caused exit!')
                    continue

                except Exception as err:  # Catch all other exceptions and abort
                    yield StatusMessage('[FATAL ERROR] Encountered: ' +
                                        str(err))
                    yield StatusMessage('[FAILURE] Fatal error caused exit!')

                else:
                    results["was_successful"] = True

                try:
                    session.close()
                except:
                    pass
                yield StatusMessage(
                    '[INFO] Session has been closed to CB Sensor #' +
                    str(sensor.id) + '(' + sensor.hostname + ')')
                break

            # Release the host lock if acquired
            if lock_acquired is True:
                os.remove(
                    '/home/integrations/.resilient/cb_host_locks/{}.lock'.
                    format(hostname))

            # Produce a FunctionResult with the results
            yield FunctionResult(results)
        except Exception:
            yield FunctionError()
    def _ldap_md_utilities_add_to_groups_function(self, event, *args,
                                                  **kwargs):
        """Function: A function that allows adding multiple users to multiple groups"""
        try:

            # Get the wf_instance_id of the workflow this Function was called in
            wf_instance_id = event.message["workflow_instance"][
                "workflow_instance_id"]

            yield StatusMessage(
                "Starting 'ldap_md_utilities_add_to_groups' running in workflow '{0}'"
                .format(wf_instance_id))

            # Get the function parameters:
            ldap_md_domain_name = kwargs.get("ldap_md_domain_name")  # text
            ldap_md_multiple_user_dn = kwargs.get(
                "ldap_md_multiple_user_dn")  # text
            ldap_md_multiple_group_dn = kwargs.get(
                "ldap_md_multiple_group_dn")  # text

            log = logging.getLogger(__name__)
            log.info("ldap_md_domain_name: %s", ldap_md_domain_name)
            log.info("ldap_md_multiple_user_dn: %s", ldap_md_multiple_user_dn)
            log.info("ldap_md_multiple_group_dn: %s",
                     ldap_md_multiple_group_dn)
            yield StatusMessage("Function Inputs OK")

            # Instansiate helper (which gets appconfigs from file)
            helper = LDAPUtilitiesHelper(self.options, ldap_md_domain_name)
            log.info("[app.config] -ldap_server: %s", helper.LDAP_SERVER)
            log.info("[app.config] -ldap_user_dn: %s", helper.LDAP_USER_DN)
            yield StatusMessage("Appconfig Settings OK")

            ##############################################

            if not helper.LDAP_IS_ACTIVE_DIRECTORY:
                raise FunctionError(
                    "This function only supports an Active Directory connection. Make sure ldap_is_active_directory is set to True in the app.config file"
                )

            try:
                # Try converting input to an array
                ldap_md_multiple_user_dn = literal_eval(
                    ldap_md_multiple_user_dn)
                ldap_md_multiple_group_dn = literal_eval(
                    ldap_md_multiple_group_dn)

            except Exception:
                raise ValueError(
                    """ldap_md_multiple_user_dn and ldap_md_multiple_group_dn must be a string repersenation of an array e.g. "['dn=Accounts Group,dc=example,dc=com', 'dn=IT Group,dc=example,dc=com']" """
                )

            # Instansiate LDAP Server and Connection
            c = helper.get_ldap_connection()

            try:
                # Bind to the connection
                c.bind()
            except Exception as err:
                raise ValueError(
                    "Cannot connect to LDAP Server. Ensure credentials are correct\n Error: {0}"
                    .format(err))

            # Inform user
            msg = "Connected to {0}".format("Active Directory")
            yield StatusMessage(msg)

            res = False

            try:
                yield StatusMessage("Attempting to add user(s) to group(s)")
                # perform the removeMermbersFromGroups operation
                res = ad_add_members_to_groups(c, ldap_md_multiple_user_dn,
                                               ldap_md_multiple_group_dn, True)
                # Test: res = 'ad_add_members_to_groups(c, ' + str(ldap_md_multiple_user_dn) + ', ' + str(ldap_md_multiple_group_dn) + ', True)'

            except Exception:
                raise ValueError("Ensure all user and group DNs exist")

            finally:
                # Unbind connection
                c.unbind()

            ##############################################

            results = {
                "success": res,
                "domain_name": ldap_md_domain_name,
                "users_dn": ldap_md_multiple_user_dn,
                "groups_dn": ldap_md_multiple_group_dn
            }

            yield StatusMessage(
                "Finished 'ldap_md_utilities_add_to_groups' that was running in workflow '{0}'"
                .format(wf_instance_id))

            # Produce a FunctionResult with the results
            yield FunctionResult(results)
        except Exception:
            yield FunctionError()
    def _cb_retrieve_usb_history_function(self, event, *args, **kwargs):

        results = {}
        results["was_successful"] = False
        results["hostname"] = None
        lock_acquired = False

        try:
            # Get the function parameters:
            incident_id = kwargs.get("incident_id")  # number
            hostname = kwargs.get("hostname")  # text

            log = logging.getLogger(__name__)  # Establish logging

            days_later_timeout_length = datetime.datetime.now(
            ) + datetime.timedelta(
                days=DAYS_UNTIL_TIMEOUT)  # Max duration length before aborting
            hostname = hostname.upper(
            )[:15]  # CB limits hostname to 15 characters
            sensor = cb.select(Sensor).where(
                'hostname:' + hostname)  # Query CB for the hostname's sensor
            timeouts = 0  # Number of timeouts that have occurred

            if len(sensor) <= 0:  # Host does not have CB agent, abort
                yield StatusMessage(
                    "[FATAL ERROR] CB could not find hostname: " +
                    str(hostname))
                yield FunctionResult(results)
                return

            sensor = sensor[0]  # Get the sensor object from the query
            results["hostname"] = str(hostname).upper()

            while timeouts <= MAX_TIMEOUTS:  # Max timeouts before aborting

                try:

                    now = datetime.datetime.now()

                    # Check if the sensor is queued to restart, wait up to 90 seconds before checking online status
                    three_minutes_passed = datetime.datetime.now(
                    ) + datetime.timedelta(minutes=3)
                    while (sensor.restart_queued is
                           True) and (three_minutes_passed >= now):
                        time.sleep(3)  # Give the CPU a break, it works hard!
                        now = datetime.datetime.now()
                        sensor = (cb.select(Sensor).where('hostname:' +
                                                          hostname)
                                  )[0]  # Retrieve the latest sensor vitals

                    # Check online status
                    if sensor.status != "Online":
                        yield StatusMessage('[WARNING] Hostname: ' +
                                            str(hostname) +
                                            ' is offline. Will attempt for ' +
                                            str(DAYS_UNTIL_TIMEOUT) +
                                            ' days...')

                    # Check lock status
                    if os.path.exists(
                            '/home/integrations/.resilient/cb_host_locks/{}.lock'
                            .format(hostname)):
                        yield StatusMessage(
                            '[WARNING] A running action has a lock on  ' +
                            str(hostname) + '. Will attempt for ' +
                            str(DAYS_UNTIL_TIMEOUT) + ' days...')

                    # Wait for offline and locked hosts for days_later_timeout_length
                    while (sensor.status != "Online" or os.path.exists(
                            '/home/integrations/.resilient/cb_host_locks/{}.lock'
                            .format(hostname))) and (days_later_timeout_length
                                                     >= now):
                        time.sleep(3)  # Give the CPU a break, it works hard!
                        now = datetime.datetime.now()
                        sensor = (cb.select(Sensor).where('hostname:' +
                                                          hostname)
                                  )[0]  # Retrieve the latest sensor vitals

                    # Abort after DAYS_UNTIL_TIMEOUT
                    if sensor.status != "Online" or os.path.exists(
                            '/home/integrations/.resilient/cb_host_locks/{}.lock'
                            .format(hostname)):
                        yield StatusMessage('[FATAL ERROR] Hostname: ' +
                                            str(hostname) +
                                            ' is still offline!')
                        yield FunctionResult(results)
                        return

                    # Check if the sensor is queued to restart, wait up to 90 seconds before continuing
                    three_minutes_passed = datetime.datetime.now(
                    ) + datetime.timedelta(minutes=3)
                    while (sensor.restart_queued is True) and (
                            three_minutes_passed >= now
                    ):  # If the sensor is queued to restart, wait up to 90 seconds
                        time.sleep(3)  # Give the CPU a break, it works hard!
                        now = datetime.datetime.now()
                        sensor = (cb.select(Sensor).where('hostname:' +
                                                          hostname)
                                  )[0]  # Retrieve the latest sensor vitals

                    # Verify the incident still exists and is reachable, if not abort
                    try:
                        incident = self.rest_client().get(
                            '/incidents/{0}?text_content_output_format=always_text&handle_format=names'
                            .format(str(incident_id)))
                    except Exception as err:
                        if err.message and "not found" in err.message.lower():
                            log.info('[FATAL ERROR] Incident ID ' +
                                     str(incident_id) + ' no longer exists.')
                            log.info('[FAILURE] Fatal error caused exit!')
                        else:
                            log.info(
                                '[FATAL ERROR] Incident ID ' +
                                str(incident_id) +
                                ' could not be reached, Resilient instance may be down.'
                            )
                            log.info('[FAILURE] Fatal error caused exit!')
                        return

                    # Acquire host lock
                    try:
                        f = os.fdopen(
                            os.open(
                                '/home/integrations/.resilient/cb_host_locks/{}.lock'
                                .format(hostname),
                                os.O_CREAT | os.O_WRONLY | os.O_EXCL), 'w')
                        f.close()
                        lock_acquired = True
                    except OSError:
                        continue

                    # Establish a session to the host sensor
                    yield StatusMessage(
                        '[INFO] Establishing session to CB Sensor #' +
                        str(sensor.id) + ' (' + sensor.hostname + ')')
                    session = cb.live_response.request_session(sensor.id)
                    yield StatusMessage('[SUCCESS] Connected on Session #' +
                                        str(session.session_id) +
                                        ' to CB Sensor #' + str(sensor.id) +
                                        ' (' + sensor.hostname + ')')

                    try:
                        session.create_directory(
                            'C:\Windows\CarbonBlack\Reports')
                    except TimeoutError:
                        raise
                    except Exception:
                        pass  # Existed already

                    try:
                        session.create_directory(
                            r'C:\Windows\CarbonBlack\Tools')
                    except TimeoutError:
                        raise
                    except Exception:
                        pass  # Existed already

                    try:
                        session.delete_file(
                            r'C:\Windows\CarbonBlack\Tools\USBD.exe')
                    except TimeoutError:
                        raise
                    except Exception:
                        pass  # Didn't exist already

                    try:
                        session.delete_file(
                            r'C:\Windows\CarbonBlack\Tools\DLV.exe')
                    except TimeoutError:
                        raise
                    except Exception:
                        pass  # Didn't exist already

                    session.put_file(open(PATH_TO_UTILITY_1, 'rb'),
                                     r'C:\Windows\CarbonBlack\Tools\USBD.exe'
                                     )  # Place the utility on the endpoint
                    session.put_file(open(PATH_TO_UTILITY_2, 'rb'),
                                     r'C:\Windows\CarbonBlack\Tools\DLV.exe'
                                     )  # Place the utility on the endpoint

                    session.create_process(
                        r'C:\Windows\CarbonBlack\Tools\USBD.exe /shtml "C:\Windows\CarbonBlack\Reports\usb-dump1.html" /sort "Last Plug/Unplug Date"',
                        True)  # Execute the utility
                    yield StatusMessage(
                        '[SUCCESS] Executed USBD.exe on Sensor!')
                    session.create_process(
                        r'C:\Windows\CarbonBlack\Tools\DLV.exe /shtml "C:\Windows\CarbonBlack\Reports\usb-dump2.html" /sort "Drive Letter"',
                        True)  # Execute the utility
                    yield StatusMessage(
                        '[SUCCESS] Executed DLV.exe on Sensor!')

                    with tempfile.NamedTemporaryFile(
                            delete=False
                    ) as temp_file:  # Create temporary temp_file for HTML file
                        try:
                            temp_file.write(
                                session.get_file(
                                    r'C:\Windows\CarbonBlack\Reports\usb-dump1.html'
                                )
                            )  # Write the HTML file from the endpoint to temp_file
                            temp_file.close()
                            yield StatusMessage(
                                '[SUCCESS] Retrieved HTML data file from Sensor!'
                            )
                            self.rest_client().post_attachment(
                                '/incidents/{0}/attachments'.format(
                                    incident_id), temp_file.name,
                                '{0}-USB_drives.html'.format(sensor.hostname)
                            )  # Post temp_file to incident
                            yield StatusMessage(
                                '[SUCCESS] Posted HTML data file to the incident as an attachment!'
                            )

                        finally:
                            os.unlink(
                                temp_file.name)  # Delete temporary temp_file

                    with tempfile.NamedTemporaryFile(
                            delete=False
                    ) as temp_file:  # Create temporary temp_file for HTML file
                        try:
                            temp_file.write(
                                session.get_file(
                                    r'C:\Windows\CarbonBlack\Reports\usb-dump2.html'
                                )
                            )  # Write the HTML file from the endpoint to temp_file
                            temp_file.close()
                            yield StatusMessage(
                                '[SUCCESS] Retrieved HTML data file from Sensor!'
                            )
                            self.rest_client().post_attachment(
                                '/incidents/{0}/attachments'.format(
                                    incident_id), temp_file.name,
                                '{0}-drive_letters.html'.format(
                                    sensor.hostname)
                            )  # Post temp_file to incident
                            yield StatusMessage(
                                '[SUCCESS] Posted HTML data file to the incident as an attachment!'
                            )

                        finally:
                            os.unlink(
                                temp_file.name)  # Delete temporary temp_file

                    session.delete_file(
                        r'C:\Windows\CarbonBlack\Tools\USBD.exe')
                    session.delete_file(
                        r'C:\Windows\CarbonBlack\Tools\DLV.exe')
                    session.delete_file(
                        r'C:\Windows\CarbonBlack\Reports\usb-dump1.html')
                    session.delete_file(
                        r'C:\Windows\CarbonBlack\Reports\usb-dump2.html')

                except TimeoutError:  # Catch TimeoutError and handle
                    timeouts = timeouts + 1
                    if timeouts <= MAX_TIMEOUTS:
                        yield StatusMessage(
                            '[ERROR] TimeoutError was encountered. Reattempting... ('
                            + str(timeouts) + '/' + str(MAX_TIMEOUTS) + ')')
                        try:
                            session.close()
                        except:
                            pass
                        sensor = (cb.select(Sensor).where('hostname:' +
                                                          hostname)
                                  )[0]  # Retrieve the latest sensor vitals
                        sensor.restart_sensor(
                        )  # Restarting the sensor may avoid a timeout from occurring again
                        time.sleep(30)  # Sleep to apply sensor restart
                        sensor = (cb.select(Sensor).where('hostname:' +
                                                          hostname)
                                  )[0]  # Retrieve the latest sensor vitals
                    else:
                        yield StatusMessage(
                            '[FATAL ERROR] TimeoutError was encountered. The maximum number of retries was reached. Aborting!'
                        )
                        yield StatusMessage(
                            '[FAILURE] Fatal error caused exit!')
                    continue

                except (
                        ApiError, ProtocolError, NewConnectionError,
                        ConnectTimeoutError, MaxRetryError
                ) as err:  # Catch urllib3 connection exceptions and handle
                    if 'ApiError' in str(
                            type(err).__name__
                    ) and 'network connection error' not in str(err):
                        raise  # Only handle ApiError involving network connection error
                    timeouts = timeouts + 1
                    if timeouts <= MAX_TIMEOUTS:
                        yield StatusMessage(
                            '[ERROR] Carbon Black was unreachable. Reattempting in 30 minutes... ('
                            + str(timeouts) + '/' + str(MAX_TIMEOUTS) + ')')
                        time.sleep(
                            1800
                        )  # Sleep for 30 minutes, backup service may have been running.
                    else:
                        yield StatusMessage(
                            '[FATAL ERROR] ' + str(type(err).__name__) +
                            ' was encountered. The maximum number of retries was reached. Aborting!'
                        )
                        yield StatusMessage(
                            '[FAILURE] Fatal error caused exit!')
                    continue

                except Exception as err:  # Catch all other exceptions and abort
                    yield StatusMessage('[FATAL ERROR] Encountered: ' +
                                        str(err))
                    yield StatusMessage('[FAILURE] Fatal error caused exit!')

                else:
                    results["was_successful"] = True

                try:
                    session.close()
                except:
                    pass
                yield StatusMessage(
                    '[INFO] Session has been closed to CB Sensor #' +
                    str(sensor.id) + '(' + sensor.hostname + ')')
                break

            # Release the host lock if acquired
            if lock_acquired is True:
                os.remove(
                    '/home/integrations/.resilient/cb_host_locks/{}.lock'.
                    format(hostname))

            # Produce a FunctionResult with the results
            yield FunctionResult(results)
        except Exception:
            yield FunctionError()
    def _cb_retrieve_prefetch_files_function(self, event, *args, **kwargs):

        results = {}
        results["was_successful"] = False
        results["hostname"] = None
        lock_acquired = False

        try:
            # Get the function parameters:
            incident_id = kwargs.get("incident_id")  # number
            hostname = kwargs.get("hostname")  # text

            log = logging.getLogger(__name__)  # Establish logging

            days_later_timeout_length = datetime.datetime.now(
            ) + datetime.timedelta(
                days=DAYS_UNTIL_TIMEOUT)  # Max duration length before aborting
            hostname = hostname.upper(
            )[:15]  # CB limits hostname to 15 characters
            lock_file = '/home/integrations/.resilient/cb_host_locks/{}.lock'.format(
                hostname)
            sensor = cb.select(Sensor).where(
                'hostname:' + hostname)  # Query CB for the hostname's sensor
            timeouts = 0  # Number of timeouts that have occurred

            if len(sensor) <= 0:  # Host does not have CB agent, abort
                yield StatusMessage(
                    "[FATAL ERROR] CB could not find hostname: " +
                    str(hostname))
                yield StatusMessage('[FAILURE] Fatal error caused exit!')
                yield FunctionResult(results)
                return

            sensor = sensor[0]  # Get the sensor object from the query
            results["hostname"] = str(hostname).upper()

            while timeouts <= MAX_TIMEOUTS:  # Max timeouts before aborting

                try:

                    now = datetime.datetime.now()

                    # Check if the sensor is queued to restart, wait up to 90 seconds before checking online status
                    three_minutes_passed = datetime.datetime.now(
                    ) + datetime.timedelta(minutes=3)
                    while (sensor.restart_queued is
                           True) and (three_minutes_passed >= now):
                        time.sleep(3)  # Give the CPU a break, it works hard!
                        now = datetime.datetime.now()
                        sensor = (cb.select(Sensor).where('hostname:' +
                                                          hostname)
                                  )[0]  # Retrieve the latest sensor vitals

                    # Check online status
                    if sensor.status != "Online":
                        yield StatusMessage('[WARNING] Hostname: ' +
                                            str(hostname) +
                                            ' is offline. Will attempt for ' +
                                            str(DAYS_UNTIL_TIMEOUT) +
                                            ' days...')

                    # Check lock status
                    if os.path.exists(lock_file) and lock_acquired is False:
                        yield StatusMessage(
                            '[WARNING] A running action has a lock on  ' +
                            str(hostname) + '. Will attempt for ' +
                            str(DAYS_UNTIL_TIMEOUT) + ' days...')

                    # Wait for offline and locked hosts for days_later_timeout_length
                    while (sensor.status != "Online" or
                           (os.path.exists(lock_file) and lock_acquired is
                            False)) and (days_later_timeout_length >= now):
                        time.sleep(3)  # Give the CPU a break, it works hard!
                        now = datetime.datetime.now()
                        sensor = (cb.select(Sensor).where('hostname:' +
                                                          hostname)
                                  )[0]  # Retrieve the latest sensor vitals

                    # Abort after DAYS_UNTIL_TIMEOUT
                    if sensor.status != "Online" or (os.path.exists(lock_file)
                                                     and
                                                     lock_acquired is False):
                        yield StatusMessage('[FATAL ERROR] Hostname: ' +
                                            str(hostname) +
                                            ' is still offline!')
                        yield StatusMessage(
                            '[FAILURE] Fatal error caused exit!')
                        break

                    # Check if the sensor is queued to restart, wait up to 90 seconds before continuing
                    three_minutes_passed = datetime.datetime.now(
                    ) + datetime.timedelta(minutes=3)
                    while (sensor.restart_queued is True) and (
                            three_minutes_passed >= now
                    ):  # If the sensor is queued to restart, wait up to 90 seconds
                        time.sleep(3)  # Give the CPU a break, it works hard!
                        now = datetime.datetime.now()
                        sensor = (cb.select(Sensor).where('hostname:' +
                                                          hostname)
                                  )[0]  # Retrieve the latest sensor vitals

                    # Verify the incident still exists and is reachable, if not abort
                    try:
                        incident = self.rest_client().get(
                            '/incidents/{0}?text_content_output_format=always_text&handle_format=names'
                            .format(str(incident_id)))
                    except Exception as err:
                        if err.message and "not found" in err.message.lower():
                            log.info('[FATAL ERROR] Incident ID ' +
                                     str(incident_id) + ' no longer exists.')
                            log.info('[FAILURE] Fatal error caused exit!')
                        else:
                            log.info(
                                '[FATAL ERROR] Incident ID ' +
                                str(incident_id) +
                                ' could not be reached, Resilient instance may be down.'
                            )
                            log.info('[FAILURE] Fatal error caused exit!')
                        break

                    # Acquire host lock
                    if lock_acquired is False:
                        try:
                            f = os.fdopen(
                                os.open(lock_file,
                                        os.O_CREAT | os.O_WRONLY | os.O_EXCL),
                                'w')
                            f.close()
                            lock_acquired = True
                        except OSError:
                            continue

                    # Establish a session to the host sensor
                    yield StatusMessage(
                        '[INFO] Establishing session to CB Sensor #' +
                        str(sensor.id) + ' (' + sensor.hostname + ')')
                    session = cb.live_response.request_session(sensor.id)
                    yield StatusMessage('[SUCCESS] Connected on Session #' +
                                        str(session.session_id) +
                                        ' to CB Sensor #' + str(sensor.id) +
                                        ' (' + sensor.hostname + ')')

                    files_to_retrieve = [
                    ]  # Stores log file path located for retrieval

                    try:
                        session.create_directory(
                            r'C:\Windows\CarbonBlack\Reports')
                    except TimeoutError:
                        raise
                    except Exception:
                        pass  # Existed already

                    # Collect the prefetch files list via PowerShell into a CSV, store the CSV in a tempfile, and post it to the incident as an attachment
                    powershell_cmd = (
                        r"gci -path C:\Windows\Prefetch\*.pf -ea 0 | select Name,LastAccessTime,CreationTime | sort LastAccessTime | "
                        r"ConvertTo-csv -NoTypeInformation | out-file C:\Windows\CarbonBlack\Reports\prefetch.csv -Encoding ascii"
                    )

                    session.create_process(
                        r'powershell.exe -ExecutionPolicy Bypass -Command "' +
                        powershell_cmd + '" ', True, None, None, 300, True)
                    with tempfile.NamedTemporaryFile(
                            delete=False
                    ) as temp_file:  # Create temporary temp_file for the CSV file
                        try:
                            temp_file.write(
                                session.get_file(
                                    r'C:\Windows\CarbonBlack\Reports\prefetch.csv'
                                )
                            )  # Write the CSV file from the endpoint to temp_file
                            temp_file.close()
                            yield StatusMessage(
                                '[SUCCESS] Retrieved CSV data file from Sensor!'
                            )
                            self.rest_client().post_attachment(
                                '/incidents/{0}/attachments'.format(
                                    incident_id), temp_file.name,
                                '{0}-prefetch.csv'.format(sensor.hostname)
                            )  # Post temp_file to incident
                            yield StatusMessage(
                                '[SUCCESS] Posted a CSV data file to the incident as an attachment!'
                            )

                        finally:
                            os.unlink(
                                temp_file.name)  # Delete temporary temp_file

                    session.delete_file(
                        r'C:\Windows\CarbonBlack\Reports\prefetch.csv')

                    prefetch_directory = session.list_directory(
                        r'C:\Windows\Prefetch\\')
                    for file in prefetch_directory:  # For each file in the Prefetch directory
                        if file['filename'].endswith('.pf'):
                            files_to_retrieve.append(
                                r'C:\Windows\Prefetch\{}'.format(
                                    file['filename'])
                            )  # Store the file path into files_to_retrieve

                    with tempfile.NamedTemporaryFile(
                            delete=False
                    ) as temp_zip:  # Create temporary temp_zip for creating zip_file
                        try:
                            with zipfile.ZipFile(
                                    temp_zip, 'w'
                            ) as zip_file:  # Establish zip_file from temporary temp_zip for packaging prefetch files into
                                for each_file in files_to_retrieve:  # For each located log file
                                    with tempfile.NamedTemporaryFile(
                                            delete=False
                                    ) as temp_file:  # Create temp_file for log
                                        try:
                                            temp_file.write(
                                                session.get_file(each_file)
                                            )  # Write the log to temp_file
                                            temp_file.close()
                                            zip_file.write(
                                                temp_file.name,
                                                each_file.replace(
                                                    'C:\\Windows\\Prefetch\\',
                                                    '').replace('\\', os.sep),
                                                compress_type=zipfile.
                                                ZIP_DEFLATED
                                            )  # Write temp_file into zip_file

                                        finally:
                                            os.unlink(
                                                temp_file.name
                                            )  # Delete temporary temp_file

                            if os.stat(
                                    temp_zip.name).st_size <= MAX_UPLOAD_SIZE:
                                self.rest_client().post_attachment(
                                    '/incidents/{0}/attachments'.format(
                                        incident_id), temp_zip.name,
                                    '{0}-prefetch_files.zip'.format(
                                        sensor.hostname)
                                )  # Post temp_zip to incident
                                yield StatusMessage(
                                    '[SUCCESS] Posted a ZIP file of the prefetch files to the incident as an attachment!'
                                )
                            else:
                                if not os.path.exists(
                                        os.path.normpath(
                                            '/mnt/cyber-sec-forensics/Resilient/{0}'
                                            .format(incident_id))):
                                    os.makedirs(
                                        '/mnt/cyber-sec-forensics/Resilient/{0}'
                                        .format(incident_id))
                                shutil.copyfile(
                                    temp_zip.name,
                                    '/mnt/cyber-sec-forensics/Resilient/{0}/{1}-prefetch_files-{2}.zip'
                                    .format(incident_id, sensor.hostname,
                                            str(int(time.time())))
                                )  # Post temp_zip to network share
                                yield StatusMessage(
                                    '[SUCCESS] Posted a ZIP file of the prefetch files to the forensics network share!'
                                )

                        finally:
                            os.unlink(
                                temp_zip.name)  # Delete temporary temp_file

                except TimeoutError:  # Catch TimeoutError and handle
                    timeouts = timeouts + 1
                    if timeouts <= MAX_TIMEOUTS:
                        yield StatusMessage(
                            '[ERROR] TimeoutError was encountered. Reattempting... ('
                            + str(timeouts) + '/' + str(MAX_TIMEOUTS) + ')')
                        try:
                            session.close()
                        except:
                            pass
                        sensor = (cb.select(Sensor).where('hostname:' +
                                                          hostname)
                                  )[0]  # Retrieve the latest sensor vitals
                        sensor.restart_sensor(
                        )  # Restarting the sensor may avoid a timeout from occurring again
                        time.sleep(30)  # Sleep to apply sensor restart
                        sensor = (cb.select(Sensor).where('hostname:' +
                                                          hostname)
                                  )[0]  # Retrieve the latest sensor vitals
                    else:
                        yield StatusMessage(
                            '[FATAL ERROR] TimeoutError was encountered. The maximum number of retries was reached. Aborting!'
                        )
                        yield StatusMessage(
                            '[FAILURE] Fatal error caused exit!')
                    continue

                except (
                        ApiError, ProtocolError, NewConnectionError,
                        ConnectTimeoutError, MaxRetryError
                ) as err:  # Catch urllib3 connection exceptions and handle
                    if 'ApiError' in str(
                            type(err).__name__
                    ) and 'network connection error' not in str(err):
                        raise  # Only handle ApiError involving network connection error
                    timeouts = timeouts + 1
                    if timeouts <= MAX_TIMEOUTS:
                        yield StatusMessage(
                            '[ERROR] Carbon Black was unreachable. Reattempting in 30 minutes... ('
                            + str(timeouts) + '/' + str(MAX_TIMEOUTS) + ')')
                        time.sleep(
                            1800
                        )  # Sleep for 30 minutes, backup service may have been running.
                    else:
                        yield StatusMessage(
                            '[FATAL ERROR] ' + str(type(err).__name__) +
                            ' was encountered. The maximum number of retries was reached. Aborting!'
                        )
                        yield StatusMessage(
                            '[FAILURE] Fatal error caused exit!')
                    continue

                except Exception as err:  # Catch all other exceptions and abort
                    yield StatusMessage('[FATAL ERROR] Encountered: ' +
                                        str(err))
                    yield StatusMessage('[FAILURE] Fatal error caused exit!')

                else:
                    results["was_successful"] = True

                try:
                    session.close()
                except:
                    pass
                yield StatusMessage(
                    '[INFO] Session has been closed to CB Sensor #' +
                    str(sensor.id) + '(' + sensor.hostname + ')')
                break

            # Release the host lock if acquired
            if lock_acquired is True:
                os.remove(lock_file)

            # Produce a FunctionResult with the results
            yield FunctionResult(results)
        except Exception:
            yield FunctionError()
예제 #13
0
    def _cb_retrieve_windows_av_events_function(self, event, *args, **kwargs):

        results = {}
        results["was_successful"] = False
        results["hostname"] = None
        lock_acquired = False

        try:
            # Get the function parameters:
            incident_id = kwargs.get("incident_id")  # number
            hostname = kwargs.get("hostname")  # text

            log = logging.getLogger(__name__)  # Establish logging

            days_later_timeout_length = datetime.datetime.now(
            ) + datetime.timedelta(
                days=DAYS_UNTIL_TIMEOUT)  # Max duration length before aborting
            hostname = hostname.upper(
            )[:15]  # CB limits hostname to 15 characters
            sensor = cb.select(Sensor).where(
                'hostname:' + hostname)  # Query CB for the hostname's sensor
            timeouts = 0  # Number of timeouts that have occurred

            if len(sensor) <= 0:  # Host does not have CB agent, abort
                yield StatusMessage(
                    "[FATAL ERROR] CB could not find hostname: " +
                    str(hostname))
                yield FunctionResult(results)
                return

            sensor = sensor[0]  # Get the sensor object from the query
            results["hostname"] = str(hostname).upper()

            while timeouts <= MAX_TIMEOUTS:  # Max timeouts before aborting

                try:

                    now = datetime.datetime.now()

                    # Check if the sensor is queued to restart, wait up to 90 seconds before checking online status
                    three_minutes_passed = datetime.datetime.now(
                    ) + datetime.timedelta(minutes=3)
                    while (sensor.restart_queued is
                           True) and (three_minutes_passed >= now):
                        time.sleep(3)  # Give the CPU a break, it works hard!
                        now = datetime.datetime.now()
                        sensor = (cb.select(Sensor).where('hostname:' +
                                                          hostname)
                                  )[0]  # Retrieve the latest sensor vitals

                    # Check online status
                    if sensor.status != "Online":
                        yield StatusMessage('[WARNING] Hostname: ' +
                                            str(hostname) +
                                            ' is offline. Will attempt for ' +
                                            str(DAYS_UNTIL_TIMEOUT) +
                                            ' days...')

                    # Check lock status
                    if os.path.exists(
                            '/home/integrations/.resilient/cb_host_locks/{}.lock'
                            .format(hostname)):
                        yield StatusMessage(
                            '[WARNING] A running action has a lock on  ' +
                            str(hostname) + '. Will attempt for ' +
                            str(DAYS_UNTIL_TIMEOUT) + ' days...')

                    # Wait for offline and locked hosts for days_later_timeout_length
                    while (sensor.status != "Online" or os.path.exists(
                            '/home/integrations/.resilient/cb_host_locks/{}.lock'
                            .format(hostname))) and (days_later_timeout_length
                                                     >= now):
                        time.sleep(3)  # Give the CPU a break, it works hard!
                        now = datetime.datetime.now()
                        sensor = (cb.select(Sensor).where('hostname:' +
                                                          hostname)
                                  )[0]  # Retrieve the latest sensor vitals

                    # Abort after DAYS_UNTIL_TIMEOUT
                    if sensor.status != "Online" or os.path.exists(
                            '/home/integrations/.resilient/cb_host_locks/{}.lock'
                            .format(hostname)):
                        yield StatusMessage('[FATAL ERROR] Hostname: ' +
                                            str(hostname) +
                                            ' is still offline!')
                        yield FunctionResult(results)
                        return

                    # Check if the sensor is queued to restart, wait up to 90 seconds before continuing
                    three_minutes_passed = datetime.datetime.now(
                    ) + datetime.timedelta(minutes=3)
                    while (sensor.restart_queued is True) and (
                            three_minutes_passed >= now
                    ):  # If the sensor is queued to restart, wait up to 90 seconds
                        time.sleep(3)  # Give the CPU a break, it works hard!
                        now = datetime.datetime.now()
                        sensor = (cb.select(Sensor).where('hostname:' +
                                                          hostname)
                                  )[0]  # Retrieve the latest sensor vitals

                    # Verify the incident still exists and is reachable, if not abort
                    try:
                        incident = self.rest_client().get(
                            '/incidents/{0}?text_content_output_format=always_text&handle_format=names'
                            .format(str(incident_id)))
                    except Exception as err:
                        if err.message and "not found" in err.message.lower():
                            log.info('[FATAL ERROR] Incident ID ' +
                                     str(incident_id) + ' no longer exists.')
                            log.info('[FAILURE] Fatal error caused exit!')
                        else:
                            log.info(
                                '[FATAL ERROR] Incident ID ' +
                                str(incident_id) +
                                ' could not be reached, Resilient instance may be down.'
                            )
                            log.info('[FAILURE] Fatal error caused exit!')
                        return

                    # Acquire host lock
                    try:
                        f = os.fdopen(
                            os.open(
                                '/home/integrations/.resilient/cb_host_locks/{}.lock'
                                .format(hostname),
                                os.O_CREAT | os.O_WRONLY | os.O_EXCL), 'w')
                        f.close()
                        lock_acquired = True
                    except OSError:
                        continue

                    # Establish a session to the host sensor
                    yield StatusMessage(
                        '[INFO] Establishing session to CB Sensor #' +
                        str(sensor.id) + ' (' + sensor.hostname + ')')
                    session = cb.live_response.request_session(sensor.id)
                    yield StatusMessage('[SUCCESS] Connected on Session #' +
                                        str(session.session_id) +
                                        ' to CB Sensor #' + str(sensor.id) +
                                        ' (' + sensor.hostname + ')')

                    try:
                        session.create_directory(
                            'C:\Windows\CarbonBlack\Reports')
                    except TimeoutError:
                        raise
                    except Exception:
                        pass  # Existed already

                    session.create_process(
                        r'''cmd.exe /c wevtutil qe "System" /rd:True /q:"*[System[Provider[@Name='Microsoft Antimalware']]]" /f:Text > C:\Windows\CarbonBlack\Reports\Antimalware_Events.txt''',
                        True, None, None, 300, True)  # Execute the utility
                    yield StatusMessage(
                        '[SUCCESS] Queried all Microsoft Antimalware events on Sensor!'
                    )

                    session.create_process(
                        r'''cmd.exe /c wevtutil qe "Microsoft-Windows-Windows Defender/Operational" /rd:True /f:Text > C:\Windows\CarbonBlack\Reports\Defender_Events.txt''',
                        True, None, None, 300, True)  # Execute the utility
                    yield StatusMessage(
                        '[SUCCESS] Queried all Windows Defender events on Sensor!'
                    )

                    with tempfile.NamedTemporaryFile(
                            delete=False
                    ) as temp_file:  # Create temporary temp_file for TXT file
                        try:
                            file_size = session.list_directory(
                                r'C:\Windows\CarbonBlack\Reports\Antimalware_Events.txt'
                            )[0]['size']  # File size in bytes
                            custom_timeout = int(
                                (file_size / TRANSFER_RATE) + 120
                            )  # The expected timeout duration + 120 seconds for good measure
                            temp_file.write(
                                session.get_file(
                                    r'C:\Windows\CarbonBlack\Reports\Antimalware_Events.txt',
                                    timeout=custom_timeout)
                            )  # Write the HTML file from the endpoint to temp_file
                            temp_file.close()
                            yield StatusMessage(
                                '[SUCCESS] Retrieved Microsoft Antimalware events data file from Sensor!'
                            )
                            if os.stat(temp_file.name).st_size == 0:
                                yield StatusMessage(
                                    '[SUCCESS] Microsoft Antimalware events data file is empty. Skipping...'
                                )  # If file is empty, don't send
                            else:
                                self.rest_client().post_attachment(
                                    '/incidents/{0}/attachments'.format(
                                        incident_id), temp_file.name,
                                    '{0}-Antimalware_Events.txt'.format(
                                        sensor.hostname)
                                )  # Post temp_file to incident
                                yield StatusMessage(
                                    '[SUCCESS] Posted Microsoft Antimalware events data file to the incident as an attachment!'
                                )

                        finally:
                            os.unlink(
                                temp_file.name)  # Delete temporary temp_file

                    with tempfile.NamedTemporaryFile(
                            delete=False
                    ) as temp_file:  # Create temporary temp_file for TXT file
                        try:
                            file_size = session.list_directory(
                                r'C:\Windows\CarbonBlack\Reports\Defender_Events.txt'
                            )[0]['size']  # File size in bytes
                            custom_timeout = int(
                                (file_size / TRANSFER_RATE) + 120
                            )  # The expected timeout duration + 120 seconds for good measure
                            temp_file.write(
                                session.get_file(
                                    r'C:\Windows\CarbonBlack\Reports\Defender_Events.txt',
                                    timeout=custom_timeout)
                            )  # Write the HTML file from the endpoint to temp_file
                            temp_file.close()
                            yield StatusMessage(
                                '[SUCCESS] Retrieved Windows Defender events data file from Sensor!'
                            )

                            if os.stat(temp_file.name).st_size == 0:
                                yield StatusMessage(
                                    '[SUCCESS] Windows Defender events data file is empty. Skipping...'
                                )  # If file is empty, don't send
                            elif os.stat(
                                    temp_file.name).st_size <= MAX_UPLOAD_SIZE:
                                self.rest_client().post_attachment(
                                    '/incidents/{0}/attachments'.format(
                                        incident_id), temp_file.name,
                                    '{0}-Defender_Events.txt'.format(
                                        sensor.hostname)
                                )  # Post temp_file to incident
                                yield StatusMessage(
                                    '[SUCCESS] Posted Windows Defender events data file to the incident as an attachment!'
                                )
                            else:
                                if not os.path.exists(
                                        os.path.normpath(
                                            '/mnt/cyber-sec-forensics/Resilient/{0}'
                                            .format(incident_id))):
                                    os.makedirs(
                                        '/mnt/cyber-sec-forensics/Resilient/{0}'
                                        .format(incident_id))
                                shutil.copyfile(
                                    temp_file.name,
                                    '/mnt/cyber-sec-forensics/Resilient/{0}/{1}-Defender_Events-{2}.txt'
                                    .format(incident_id, sensor.hostname,
                                            str(int(time.time())))
                                )  # Post temp_file to network share
                                yield StatusMessage(
                                    '[SUCCESS] Posted Windows Defender events data file to the forensics network share!'
                                )

                        finally:
                            os.unlink(
                                temp_file.name)  # Delete temporary temp_file

                    session.delete_file(
                        r'C:\Windows\CarbonBlack\Reports\Antimalware_Events.txt'
                    )
                    session.delete_file(
                        r'C:\Windows\CarbonBlack\Reports\Defender_Events.txt')

                except TimeoutError:  # Catch TimeoutError and handle
                    timeouts = timeouts + 1
                    if timeouts <= MAX_TIMEOUTS:
                        yield StatusMessage(
                            '[ERROR] TimeoutError was encountered. Reattempting... ('
                            + str(timeouts) + '/' + str(MAX_TIMEOUTS) + ')')
                        try:
                            session.close()
                        except:
                            pass
                        sensor = (cb.select(Sensor).where('hostname:' +
                                                          hostname)
                                  )[0]  # Retrieve the latest sensor vitals
                        sensor.restart_sensor(
                        )  # Restarting the sensor may avoid a timeout from occurring again
                        time.sleep(30)  # Sleep to apply sensor restart
                        sensor = (cb.select(Sensor).where('hostname:' +
                                                          hostname)
                                  )[0]  # Retrieve the latest sensor vitals
                    else:
                        yield StatusMessage(
                            '[FATAL ERROR] TimeoutError was encountered. The maximum number of retries was reached. Aborting!'
                        )
                        yield StatusMessage(
                            '[FAILURE] Fatal error caused exit!')
                    continue

                except (
                        ApiError, ProtocolError, NewConnectionError,
                        ConnectTimeoutError, MaxRetryError
                ) as err:  # Catch urllib3 connection exceptions and handle
                    if 'ApiError' in str(
                            type(err).__name__
                    ) and 'network connection error' not in str(err):
                        raise  # Only handle ApiError involving network connection error
                    timeouts = timeouts + 1
                    if timeouts <= MAX_TIMEOUTS:
                        yield StatusMessage(
                            '[ERROR] Carbon Black was unreachable. Reattempting in 30 minutes... ('
                            + str(timeouts) + '/' + str(MAX_TIMEOUTS) + ')')
                        time.sleep(
                            1800
                        )  # Sleep for 30 minutes, backup service may have been running.
                    else:
                        yield StatusMessage(
                            '[FATAL ERROR] ' + str(type(err).__name__) +
                            ' was encountered. The maximum number of retries was reached. Aborting!'
                        )
                        yield StatusMessage(
                            '[FAILURE] Fatal error caused exit!')
                    continue

                except Exception as err:  # Catch all other exceptions and abort
                    yield StatusMessage('[FATAL ERROR] Encountered: ' +
                                        str(err))
                    yield StatusMessage('[FAILURE] Fatal error caused exit!')

                else:
                    results["was_successful"] = True

                try:
                    session.close()
                except:
                    pass
                yield StatusMessage(
                    '[INFO] Session has been closed to CB Sensor #' +
                    str(sensor.id) + '(' + sensor.hostname + ')')
                break

            # Release the host lock if acquired
            if lock_acquired is True:
                os.remove(
                    '/home/integrations/.resilient/cb_host_locks/{}.lock'.
                    format(hostname))

            # Produce a FunctionResult with the results
            yield FunctionResult(results)
        except Exception:
            yield FunctionError()
예제 #14
0
    def _umbrella_dns_rr_hist_function(self, event, *args, **kwargs):
        """Function: Resilient Function : Cisco Umbrella Investigate for DNS RR History for a IP, Type and Domain Name"""
        try:
            # Get the function parameters:
            umbinv_resource = kwargs.get("umbinv_resource")  # text
            umbinv_dns_type = self.get_select_param(
                kwargs.get("umbinv_dns_type")
            )  # select, values: "A", "NS", "MX", "TXT", "CNAME"
            incident_id = kwargs.get("incident_id")  # number
            artifact_type = kwargs.get("artifact_type")  # text

            log = logging.getLogger(__name__)
            log.info("umbinv_resource: %s", umbinv_resource)
            log.info("umbinv_dns_type: %s", umbinv_dns_type)
            log.info("incident_id: %s", incident_id)
            log.info("artifact_type: %s", artifact_type)

            if is_none(umbinv_resource):
                raise ValueError(
                    "Required parameter 'umbinv_resource' not set")

            if is_none(umbinv_dns_type):
                raise ValueError(
                    "Required parameter 'umbinv_dns_type' not set")

            if is_none(incident_id):
                raise ValueError("Required parameter 'incident_id' not set")

            if is_none(artifact_type):
                raise ValueError("Required parameter 'artifact_type' not set")

            yield StatusMessage("Starting...")
            res = None
            res_type = None
            process_result = {}
            func_name = event.name

            params = {
                "resource": umbinv_resource.strip(),
                "dns_type": umbinv_dns_type,
                "incident_id": incident_id,
                "artifact_type": artifact_type
            }

            validate_params(params)
            process_params(params, process_result)

            if "_res" not in process_result or "_res_type" not in process_result:
                raise ValueError(
                    "Parameter 'umbinv_resource' was not processed correctly")
            else:
                res = process_result.pop("_res")
                res_type = process_result.pop("_res_type")

            if res_type != "domain_name" and res_type != "ip_address":
                raise ValueError(
                    "Parameter 'umbinv_resource' was an incorrect type '{}', should be a 'domain name', "
                    "or an 'ip address'.".format(res_type))

            api_token = self.options.get("api_token")
            base_url = self.options.get("base_url")
            rinv = ResilientInv(api_token, base_url)

            yield StatusMessage("Running Cisco Investigate query...")
            rtn = rinv.rr_history(res, query_type=umbinv_dns_type)
            query_execution_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
            if ("rrs" in rtn and len(rtn["rrs"]) == 0) \
                    or ("rrs_tf" in rtn and len(rtn["rrs_tf"]) == 0):
                log.debug(json.dumps(rtn))
                yield StatusMessage(
                    "No Results returned for resource '{}' with query type '{}'."
                    .format(res, umbinv_dns_type))
                results = {}
            elif ("rrs" in rtn and len(rtn["rrs"]) > int(self.options.get("results_limit", "200"))) \
                    or ("rrs_tf" in rtn and len(rtn["rrs_tf"]) > int(self.options.get("results_limit", "200"))):

                att_report = create_attachment(self, func_name, res, params,
                                               rtn, query_execution_time)
                # Add in "query_execution_time" and "ip_address" to result to facilitate post-processing.
                results = {
                    "over_limit": True,
                    "resource_name": res,
                    "att_name": att_report["name"],
                    "query_execution_time": query_execution_time
                }
                yield StatusMessage(
                    "Returning 'dns_rr_history' results for resource '{0}' as attachment: {1} ."
                    .format(res, att_report["name"]))
            else:
                # Add in "query_execution_time" and "ip_address" to result to facilitate post-processing.
                results = {
                    "dns_rr_history": json.loads(json.dumps(rtn)),
                    "resource_name": res,
                    "query_execution_time": query_execution_time
                }
                yield StatusMessage(
                    "Returning 'dns_rr_history' results for resource '{}'.".
                    format(res))

            yield StatusMessage("Done...")

            log.debug(json.dumps(results))
            # Produce a FunctionResult with the results
            yield FunctionResult(results)
        except Exception:
            log.exception("Exception in Resilient Function.")
            yield FunctionError()
    def _icdx_find_events_function(self, event, *args, **kwargs):
        """Function: Takes a number of parameters in a search request and attempts to gather events from the ICDx Platform. Returns a response containing a list of events or a response with a 204 status code when no results are found."""
        try:
            rc = ResultPayload(ICDX_SECTION, **kwargs)

            # Get the function parameters:
            icdx_search_request = self.get_textarea_param(
                kwargs.get("icdx_search_request"))  # textarea

            log = logging.getLogger(__name__)
            log.info("icdx_search_request: %s", icdx_search_request)

            if icdx_search_request is None:
                raise FunctionError(
                    "Encountered error: icdx_search_request may not be None")

            helper = ICDXHelper(self.options)
            yield StatusMessage(
                "Attempting to gather config and setup the AMQP Client")
            try:

                # Initialise the AmqpFacade, pass in config values
                amqp_client = AmqpFacade(
                    host=helper.get_config_option("icdx_amqp_host"),
                    port=helper.get_config_option("icdx_amqp_port", True),
                    virtual_host=helper.get_config_option("icdx_amqp_vhost"),
                    username=helper.get_config_option("icdx_amqp_username"),
                    amqp_password=helper.get_config_option(
                        "icdx_amqp_password"))

                yield StatusMessage(
                    "Config options gathered and AMQP client setup.")
            except Exception as e:
                yield StatusMessage(
                    "Encountered error while initialising the AMQP Client; Reason (if any): {0}"
                    .format(str(e)))
                raise FunctionError()
            # Prepare request payload
            try:
                search_payload = json.loads(icdx_search_request)

                # Set the hard limit for displaying in the UI
                """
                if hard limit is not set, it defaults to 100
                if hard limit is set and exceeds 100 that is the new limit
                if hard limit is set and does not exceed 100, 100 is the limit
                Max limit appears to be 500
                """
                search_payload['hard_limit'] = max([
                    helper.safe_cast(val=i, to_type=int) for i in
                    [helper.get_config_option("icdx_search_limit", True), 100]
                    if i is not None
                ])

                # Payload is now a dict -- read 'limit' and replace with app.config value if exceeds it
                if search_payload['limit'] > search_payload['hard_limit']:
                    search_payload['limit'] = search_payload['hard_limit']

                # All okay, request has valid JSON and an ID
                yield StatusMessage(
                    "Request payload validated. Sending request with a where attribute of {0} and a filter attribute of {1}"
                    .format(
                        search_payload.get('where', None)
                        or '"No Where Condition"',
                        search_payload.get('filter', None)
                        or '"No Filter Condition"'))

            except Exception:
                raise FunctionError(
                    "Problem parsing the JSON for search request")

            # Make the call to ICDx and get a handle on any results
            search_result, status = amqp_client.call(
                json.dumps(search_payload))

            yield StatusMessage(
                "ICDX call complete with status: {}".format(status))
            result_set, num_of_results = None, 0

            if not search_result:
                log.info("Received an empty result set for search query.")

            else:
                log.info("Received response. Parsing for information")
                # Get results dict and number of results
                if status == 200:
                    res = json.loads(search_result)

                    result_set, num_of_results = res["result"], len(
                        res["result"])
                elif status == 400:
                    yield StatusMessage(
                        "Received a 400 (Bad Request). Check the formatting of your ICDx Query"
                    )

            execution_time = int(time.time() * 1000)
            success = False if status != 200 else True
            results = rc.done(success=success,
                              content={
                                  "result_set": result_set,
                                  "num_of_results": num_of_results,
                                  "execution_time": execution_time
                              })
            results.update({
                "result_set": result_set,
                "num_of_results": num_of_results,
                "execution_time": execution_time,
                "success": success
            })
            yield StatusMessage(
                "Finishing. Received results: {}. Number of results: {}".
                format(results["success"], num_of_results))
            # Produce a FunctionResult with the results
            yield FunctionResult(results)
        except Exception:
            yield FunctionError()
예제 #16
0
    def _ldap_utilities_remove_from_groups_function(self, event, *args,
                                                    **kwargs):
        """Function: A function that allows you to remove multiple from multiple groups"""
        log = logging.getLogger(__name__)

        try:
            yield StatusMessage("Starting ldap_utilities_remove_from_groups")

            # Instansiate helper (which gets appconfigs from file)
            helper = LDAPUtilitiesHelper(self.options)
            yield StatusMessage("Appconfig Settings OK")

            # Get function inputs
            input_ldap_multiple_user_dn_asString = helper.get_function_input(
                kwargs, "ldap_multiple_user_dn"
            )  # text (required) [string repersentation of an array]
            input_ldap_multiple_group_dn_asString = helper.get_function_input(
                kwargs, "ldap_multiple_group_dn"
            )  # text (required) [string repersentation of an array]
            yield StatusMessage("Function Inputs OK")

            if not helper.LDAP_IS_ACTIVE_DIRECTORY:
                raise FunctionError(
                    "This function only supports an Active Directory connection. Make sure ldap_is_active_directory is set to True in the app.config file"
                )

            try:
                # Try converting input to an array
                input_ldap_multiple_user_dn = literal_eval(
                    input_ldap_multiple_user_dn_asString)
                input_ldap_multiple_group_dn = literal_eval(
                    input_ldap_multiple_group_dn_asString)

            except Exception:
                raise ValueError(
                    """input_ldap_multiple_user_dn and input_ldap_multiple_group_dn must be a string repersenation of an array e.g. "['dn=Accounts Group,dc=example,dc=com', 'dn=IT Group,dc=example,dc=com']" """
                )

            # Instansiate LDAP Server and Connection
            c = helper.get_ldap_connection()

            try:
                # Bind to the connection
                c.bind()
            except:
                raise ValueError(
                    "Cannot connect to LDAP Server. Ensure credentials are correct"
                )

            # Inform user
            msg = "Connected to {0}".format("Active Directory")
            yield StatusMessage(msg)

            res = False
            users_dn = []

            try:
                yield StatusMessage(
                    "Attempting to remove user(s) from group(s)")
                # perform the removeMermbersFromGroups operation
                res = ad_remove_members_from_groups(
                    c, input_ldap_multiple_user_dn,
                    input_ldap_multiple_group_dn, True)

                # Return list of users that were removed, and ignore users that do not exist, not valid, or not member of group
                if res and "changes" in c.request:
                    users_dn = c.request["changes"][0]["attribute"]["value"]

            except Exception:
                raise ValueError("Ensure all group DNs exist")

            finally:
                # Unbind connection
                c.unbind()

            results = {
                "success": res,
                "users_dn": users_dn if len(users_dn) > 0 else None,
                "groups_dn": input_ldap_multiple_group_dn
            }

            log.info("Completed")

            # Produce a FunctionResult with the results
            yield FunctionResult(results)
        except Exception:
            yield FunctionError()
예제 #17
0
    def _umbrella_classifiers_function(self, event, *args, **kwargs):
        """Function: Resilient Function : Cisco Umbrella Investigate for  Classifiers."""
        try:
            # Get the function parameters:
            umbinv_domain = kwargs.get("umbinv_domain")  # text

            log = logging.getLogger(__name__)

            log.info("umbinv_domain: %s", umbinv_domain)

            if is_none(umbinv_domain):
                raise ValueError("Required parameter 'umbinv_domain' not set.")

            yield StatusMessage("Starting...")
            domain = None
            process_result = {}
            params = {"domain": umbinv_domain.strip()}

            validate_params(params)
            process_params(params, process_result)

            if "_domain" not in process_result:
                raise ValueError(
                    "Parameter 'umbinv_domain' was not processed correctly")
            else:
                domain = process_result.pop("_domain")

            api_token = self.options.get("api_token")
            base_url = self.options.get("base_url")
            rinv = ResilientInv(api_token, base_url, proxies=self.proxies)

            yield StatusMessage("Running Cisco Investigate query...")
            classifiers_res = True
            info_res = True
            # Run against 'classifiers' endpoint
            rtn_classifiers = rinv.classifiers_classifiers(domain)
            # Run against 'info' endpoint
            rtn_info = rinv.classifiers_info(domain)
            query_execution_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')

            if ("securityCategories" in rtn_classifiers and not rtn_classifiers["securityCategories"]) and \
                    ("attacks" in rtn_classifiers and not rtn_classifiers["attacks"]) and \
                    ("threatTypes" in rtn_classifiers and not rtn_classifiers["threatTypes"]):
                classifiers_res = False

            if "firstQueried" in rtn_info and rtn_info["firstQueried"] is None:
                info_res = False
            else:
                # Make 'firstQueried' more readable
                fq = rtn_info["firstQueried"]
                try:
                    secs = int(fq) / 1000
                    fq_readable = datetime.fromtimestamp(secs).strftime(
                        '%Y-%m-%d %H:%M:%S')
                    rtn_info["first_queried_converted"] = fq_readable
                except ValueError:
                    yield FunctionError(
                        'timestamp value incorrectly specified')

            if not classifiers_res and not info_res:
                log.debug(json.dumps(rtn_classifiers))
                log.debug(json.dumps(rtn_info))
                results = {}
                yield StatusMessage(
                    "No Results returned for domain '{}'.".format(domain))
            else:
                # Add "query_execution_time" and "domain_name" to result to facilitate post-processing.
                results = {
                    "classifiers_classifiers":
                    json.loads(json.dumps(rtn_classifiers)),
                    "classifiers_info":
                    json.loads(json.dumps(rtn_info)),
                    "domain_name":
                    domain,
                    "query_execution_time":
                    query_execution_time
                }
                yield StatusMessage(
                    "Returning 'classifiers and info' results for domain '{}'."
                    .format(domain))

            yield StatusMessage("done...")

            log.debug(json.dumps(results))
            # Produce a FunctionResult with the results
            yield FunctionResult(results)
        except Exception:
            log.exception("Exception in Resilient Function.")
            yield FunctionError()
    def _fn_odbc_query_function(self, event, *args, **kwargs):
        """Resilient Function: A function that makes ODBC queries

        Using prepared SQL statements, where parameters are passed to the database separately,
        protecting against SQL injection attacks.

        Inputs:
        Inputs: sql_query: a SQL query with set parameters using a question mark as a place holder,
            SQL statements SELECT, INSERT, UPDATE and DELETE are supported
        sql_condition_value1: value for the question mark - condition value 1
        sql_condition_value2: value for the question mark - condition value 2
        sql_condition_value3: value for the question mark - condition value 3

        """
        odbc_connection = None

        try:
            # Get the function parameters:
            if "sql_query" not in kwargs or kwargs.get("sql_query") == '':
                LOG.error(u"Required field sql_query is missing or empty")
                raise ValueError("Required field sql_query is missing or empty")

            sql_query = self.get_textarea_param(kwargs.get("sql_query"))  # textarea

            LOG.info(u"sql_query: %s", sql_query)

            # ------------------------------------------------------
            # When adding more condition input fields to the function, you need to load them here
            # and pass the new variable/s to the function_utils.prepare_sql_parameters().
            # ------------------------------------------------------
            sql_condition_value1 = kwargs.get("sql_condition_value1")  # text
            sql_condition_value2 = kwargs.get("sql_condition_value2")  # text
            sql_condition_value3 = kwargs.get("sql_condition_value3")  # text

            LOG.info(u"sql_condition_value1: %s", sql_condition_value1)
            LOG.info(u"sql_condition_value2: %s", sql_condition_value2)
            LOG.info(u"sql_condition_value3: %s", sql_condition_value3)

            sql_params = function_utils.prepare_sql_parameters(sql_condition_value1, sql_condition_value2,
                                                               sql_condition_value3)

            # Read configuration settings:
            if "sql_connection_string" in self.options:
                sql_connection_string = self.options["sql_connection_string"]
            else:
                LOG.error(u"Mandatory config setting 'sql_connection_string' not set.")
                raise ValueError("Mandatory config setting 'sql_connection_string' not set.")

            sql_restricted_sql_statements = self.options["sql_restricted_sql_statements"] \
                if "sql_restricted_sql_statements" in self.options else None

            sql_autocommit = function_utils.str_to_bool(self.options["sql_autocommit"]) \
                if "sql_autocommit" in self.options else False

            sql_query_timeout = int(self.options["sql_query_timeout"]) \
                if "sql_query_timeout" in self.options else None

            sql_database_type = self.options["sql_database_type"].lower() \
                if "sql_database_type" in self.options else None

            sql_number_of_records_returned = int(self.options["sql_number_of_records_returned"]) \
                if "sql_number_of_records_returned" in self.options else None

            yield StatusMessage("Starting...")

            yield StatusMessage("Validating...")
            function_utils.validate_data(sql_restricted_sql_statements, sql_query)

            yield StatusMessage("Opening ODBC connection...")
            odbc_connection = odbc_utils.OdbcConnection(sql_connection_string, sql_autocommit, sql_query_timeout)
            odbc_connection.configure_unicode_settings(sql_database_type)
            odbc_connection.create_cursor()

            yield StatusMessage("Executing an ODBC query...")
            # Check what SQL statement is executed, get the first word in sql_query
            sql_statement = function_utils.get_type_sql_statement(sql_query)

            if sql_statement == 'select':

                LOG.debug(u"Query: %s. Params: %s. Fetching %s records.",
                          sql_query, sql_params, sql_number_of_records_returned)

                rows = odbc_connection.execute_select_statement(sql_query, sql_params, sql_number_of_records_returned)
                results = function_utils.prepare_results(odbc_connection.get_cursor_description(), rows)
                LOG.info(json.dumps(str(results)))

                if results.get("entries") is None:
                    yield StatusMessage("No query results returned...")
                else:
                    yield StatusMessage("Result contains {} entries...".format(len(results.get("entries"))))

            elif sql_statement == 'update' or sql_statement == 'delete' \
                    or sql_statement == 'insert':

                LOG.debug(u"Query: %s. Params: %s.", sql_query, sql_params)

                # Return row count and set results to empty list
                row_count = odbc_connection.execute_odbc_query(sql_query, sql_params)
                results = function_utils.prepare_results(None, None)

                LOG.info(u"%s rows processed", row_count)
                yield StatusMessage("{} rows processed".format(row_count))

            else:
                LOG.error(u"SQL statement '%s' is not supported", sql_statement)
                raise ValueError("SQL statement '{}' is not supported".format(sql_statement))

            yield StatusMessage("Done...")
            yield FunctionResult(results)

        except Exception as err:
            LOG.error(str(err))
            raise FunctionError(str(err))

        # Commit changes and tear down connection
        finally:
            yield StatusMessage("Closing ODBC connection...")

            if odbc_connection:
                odbc_connection.close_connections()
예제 #19
0
    def _cb_force_reboot_with_message_function(self, event, *args, **kwargs):

        results = {}
        results["was_successful"] = False
        results["hostname"] = None
        lock_acquired = False

        try:
            # Get the function parameters:
            incident_id = kwargs.get("incident_id")  # number
            hostname = kwargs.get("hostname")  # text
            minutes = kwargs.get("minutes")  # number
            custom_message = kwargs.get("custom_message")  # text

            log = logging.getLogger(__name__)  # Establish logging

            days_later_timeout_length = datetime.datetime.now(
            ) + datetime.timedelta(
                days=DAYS_UNTIL_TIMEOUT)  # Max duration length before aborting
            hostname = hostname.upper(
            )[:15]  # CB limits hostname to 15 characters
            lock_file = '/home/integrations/.resilient/cb_host_locks/{}.lock'.format(
                hostname)
            sensor = cb.select(Sensor).where(
                'hostname:' + hostname)  # Query CB for the hostname's sensor
            if minutes is None: minutes = 5  # Default to a 5 minutes
            if custom_message is None:
                custom_message = 'System restarting for cyber security reasons.'
            timeouts = 0  # Number of timeouts that have occurred

            if len(sensor) <= 0:  # Host does not have CB agent, abort
                yield StatusMessage(
                    "[FATAL ERROR] CB could not find hostname: " +
                    str(hostname))
                yield StatusMessage('[FAILURE] Fatal error caused exit!')
                yield FunctionResult(results)
                return

            sensor = sensor[0]  # Get the sensor object from the query
            results["hostname"] = str(hostname).upper()

            while timeouts <= MAX_TIMEOUTS:  # Max timeouts before aborting

                try:

                    now = datetime.datetime.now()

                    # Check if the sensor is queued to restart, wait up to 90 seconds before checking online status
                    three_minutes_passed = datetime.datetime.now(
                    ) + datetime.timedelta(minutes=3)
                    while (sensor.restart_queued is
                           True) and (three_minutes_passed >= now):
                        time.sleep(3)  # Give the CPU a break, it works hard!
                        now = datetime.datetime.now()
                        sensor = (cb.select(Sensor).where('hostname:' +
                                                          hostname)
                                  )[0]  # Retrieve the latest sensor vitals

                    # Check online status
                    if sensor.status != "Online":
                        yield StatusMessage('[WARNING] Hostname: ' +
                                            str(hostname) +
                                            ' is offline. Will attempt for ' +
                                            str(DAYS_UNTIL_TIMEOUT) +
                                            ' days...')

                    # Check lock status
                    if os.path.exists(lock_file) and lock_acquired is False:
                        yield StatusMessage(
                            '[WARNING] A running action has a lock on  ' +
                            str(hostname) + '. Will attempt for ' +
                            str(DAYS_UNTIL_TIMEOUT) + ' days...')

                    # Wait for offline and locked hosts for days_later_timeout_length
                    while (sensor.status != "Online" or
                           (os.path.exists(lock_file) and lock_acquired is
                            False)) and (days_later_timeout_length >= now):
                        time.sleep(3)  # Give the CPU a break, it works hard!
                        now = datetime.datetime.now()
                        sensor = (cb.select(Sensor).where('hostname:' +
                                                          hostname)
                                  )[0]  # Retrieve the latest sensor vitals

                    # Abort after DAYS_UNTIL_TIMEOUT
                    if sensor.status != "Online" or (os.path.exists(lock_file)
                                                     and
                                                     lock_acquired is False):
                        yield StatusMessage('[FATAL ERROR] Hostname: ' +
                                            str(hostname) +
                                            ' is still offline!')
                        yield StatusMessage(
                            '[FAILURE] Fatal error caused exit!')
                        break

                    # Check if the sensor is queued to restart, wait up to 90 seconds before continuing
                    three_minutes_passed = datetime.datetime.now(
                    ) + datetime.timedelta(minutes=3)
                    while (sensor.restart_queued is True) and (
                            three_minutes_passed >= now
                    ):  # If the sensor is queued to restart, wait up to 90 seconds
                        time.sleep(3)  # Give the CPU a break, it works hard!
                        now = datetime.datetime.now()
                        sensor = (cb.select(Sensor).where('hostname:' +
                                                          hostname)
                                  )[0]  # Retrieve the latest sensor vitals

                    # Verify the incident still exists and is reachable, if not abort
                    try:
                        incident = self.rest_client().get(
                            '/incidents/{0}?text_content_output_format=always_text&handle_format=names'
                            .format(str(incident_id)))
                    except Exception as err:
                        if err.message and "not found" in err.message.lower():
                            log.info('[FATAL ERROR] Incident ID ' +
                                     str(incident_id) + ' no longer exists.')
                            log.info('[FAILURE] Fatal error caused exit!')
                        else:
                            log.info(
                                '[FATAL ERROR] Incident ID ' +
                                str(incident_id) +
                                ' could not be reached, Resilient instance may be down.'
                            )
                            log.info('[FAILURE] Fatal error caused exit!')
                        break

                    # Acquire host lock
                    if lock_acquired is False:
                        try:
                            f = os.fdopen(
                                os.open(lock_file,
                                        os.O_CREAT | os.O_WRONLY | os.O_EXCL),
                                'w')
                            f.close()
                            lock_acquired = True
                        except OSError:
                            continue

                    # Establish a session to the host sensor
                    yield StatusMessage(
                        '[INFO] Establishing session to CB Sensor #' +
                        str(sensor.id) + ' (' + sensor.hostname + ')')
                    session = cb.live_response.request_session(sensor.id)
                    yield StatusMessage('[SUCCESS] Connected on Session #' +
                                        str(session.session_id) +
                                        ' to CB Sensor #' + str(sensor.id) +
                                        ' (' + sensor.hostname + ')')

                    session.create_process(
                        'shutdown -r -f -t ' + str(minutes * 60) +
                        ' -d p:5:19 -c "' + str(custom_message) +
                        ' Restart will occur in ' + str(minutes) +
                        ' minutes. Contact CTS Security and Compliance at x3199 option 5 with any questions."',
                        True, None, None, 300, True)
                    yield StatusMessage("[SUCCESS] Reboot has been scheduled!")

                except TimeoutError:  # Catch TimeoutError and handle
                    timeouts = timeouts + 1
                    if timeouts <= MAX_TIMEOUTS:
                        yield StatusMessage(
                            '[ERROR] TimeoutError was encountered. Reattempting... ('
                            + str(timeouts) + '/' + str(MAX_TIMEOUTS) + ')')
                        try:
                            session.close()
                        except:
                            pass
                        sensor = (cb.select(Sensor).where('hostname:' +
                                                          hostname)
                                  )[0]  # Retrieve the latest sensor vitals
                        sensor.restart_sensor(
                        )  # Restarting the sensor may avoid a timeout from occurring again
                        time.sleep(30)  # Sleep to apply sensor restart
                        sensor = (cb.select(Sensor).where('hostname:' +
                                                          hostname)
                                  )[0]  # Retrieve the latest sensor vitals
                    else:
                        yield StatusMessage(
                            '[FATAL ERROR] TimeoutError was encountered. The maximum number of retries was reached. Aborting!'
                        )
                        yield StatusMessage(
                            '[FAILURE] Fatal error caused exit!')
                    continue

                except (
                        ApiError, ProtocolError, NewConnectionError,
                        ConnectTimeoutError, MaxRetryError
                ) as err:  # Catch urllib3 connection exceptions and handle
                    if 'ApiError' in str(
                            type(err).__name__
                    ) and 'network connection error' not in str(err):
                        raise  # Only handle ApiError involving network connection error
                    timeouts = timeouts + 1
                    if timeouts <= MAX_TIMEOUTS:
                        yield StatusMessage(
                            '[ERROR] Carbon Black was unreachable. Reattempting in 30 minutes... ('
                            + str(timeouts) + '/' + str(MAX_TIMEOUTS) + ')')
                        time.sleep(
                            1800
                        )  # Sleep for 30 minutes, backup service may have been running.
                    else:
                        yield StatusMessage(
                            '[FATAL ERROR] ' + str(type(err).__name__) +
                            ' was encountered. The maximum number of retries was reached. Aborting!'
                        )
                        yield StatusMessage(
                            '[FAILURE] Fatal error caused exit!')
                    continue

                except Exception as err:  # Catch all other exceptions and abort
                    yield StatusMessage('[FATAL ERROR] Encountered: ' +
                                        str(err))
                    yield StatusMessage('[FAILURE] Fatal error caused exit!')

                else:
                    results["was_successful"] = True

                try:
                    session.close()
                except:
                    pass
                yield StatusMessage(
                    '[INFO] Session has been closed to CB Sensor #' +
                    str(sensor.id) + '(' + sensor.hostname + ')')
                break

            # Release the host lock if acquired
            if lock_acquired is True:
                os.remove(lock_file)

            # Produce a FunctionResult with the results
            yield FunctionResult(results)
        except Exception:
            yield FunctionError()
예제 #20
0
    def _qradar_search_function(self, event, *args, **kwargs):
        """Function: Search QRadar"""
        try:
            required_fields = ["qradar_query", "qradar_query_all_results"]
            validate_fields(required_fields, kwargs)
            # Get the function parameters:
            qradar_query = self.get_textarea_param(kwargs.get("qradar_query"))  # textarea
            qradar_query_param1 = kwargs.get("qradar_query_param1")  # text
            qradar_query_param2 = kwargs.get("qradar_query_param2")  # text
            qradar_query_param3 = kwargs.get("qradar_query_param3")  # text
            qradar_query_param4 = kwargs.get("qradar_query_param4")  # text
            qradar_query_param5 = kwargs.get("qradar_query_param5")  # text
            qradar_query_range_start = kwargs.get("qradar_query_range_start")  # number
            qradar_query_range_end = kwargs.get("qradar_query_range_end")  # number

            qradar_query_all_results = False
            if "Yes" in kwargs.get("qradar_query_all_results")["name"]:
                qradar_query_all_results = True

            LOG.info("qradar_query: %s", qradar_query)
            LOG.info("qradar_query_param1: %s", qradar_query_param1)
            LOG.info("qradar_query_param2: %s", qradar_query_param2)
            LOG.info("qradar_query_param3: %s", qradar_query_param3)
            LOG.info("qradar_query_param4: %s", qradar_query_param4)
            LOG.info("qradar_query_param5: %s", qradar_query_param5)
            LOG.info("qradar_query_range_start: %s", qradar_query_range_start)
            LOG.info("qradar_query_range_end: %s", qradar_query_range_end)
            LOG.info("qradar_query_all_results: %s", qradar_query_all_results)

            qradar_verify_cert = True
            if "verify_cert" in self.options and self.options["verify_cert"].lower() == "false":
                qradar_verify_cert = False

            timeout = None
            try:
                if "search_timeout" in self.options:
                    timeout = float(self.options["search_timeout"])
            except Exception:
                LOG.debug("Failed to read search_timeout: {}".format(self.options["search_timeout"]))

            LOG.debug("Connection to {} using {}".format(self.options["host"],
                                                         self.options.get("username") or "service token"))

            query_string = function_utils.make_query_string(qradar_query,
                                                            [qradar_query_param1,
                                                             qradar_query_param2,
                                                             qradar_query_param3,
                                                             qradar_query_param4,
                                                             qradar_query_param5])

            LOG.info("Running query: " + query_string)

            yield StatusMessage("starting...")
            qradar_client = QRadarClient(host=self.options["host"],
                                         username=self.options.get("username", None),
                                         password=self.options.get("qradarpassword", None),
                                         token=self.options.get("qradartoken", None),
                                         cafile=qradar_verify_cert,
                                         opts=self.opts, function_opts=self.options)

            result = qradar_client.ariel_search(query_string,
                                                qradar_query_all_results,
                                                range_start=qradar_query_range_start,
                                                range_end=qradar_query_range_end,
                                                timeout=timeout)

            yield StatusMessage("done...")
            yield FunctionResult(result)
        except Exception as e:
            LOG.error(str(e))
            yield FunctionError()
    def _qradar_advisor_offense_analysis_function(self, event, *args,
                                                  **kwargs):
        """Function:
        This function performs two tasks:
        1. call the QRadar Advisor REST API to retrieve insights for a given QRadar offense
        2. call the QRadar Advisor REST API to perform analysis on the QRadar offense
        The input is qradar_offense_id in the input.
        The reply from QRadar Advisor analysis is in stix format. This function then
        1. extract the observables from the stix objects
        2. generate a html representation for the stix
        The return to Resilient server includes the above two, together with the raw replies for
        offense insights and offense analysis."""
        try:
            # Get the function parameters:
            qradar_offense_id = kwargs.get("qradar_offense_id")  # text
            qradar_advisor_result_stage = self.get_select_param(
                kwargs.get("qradar_advisor_result_stage")
            )  # select, values: "stage1", "stage2", "stage3"
            qradar_analysis_restart_if_existed = kwargs.get(
                "qradar_analysis_restart_if_existed")  # boolean

            log = logging.getLogger(__name__)
            log.info("qradar_offense_id: %s", qradar_offense_id)
            log.info("qradar_advisor_result_stage: %s",
                     qradar_advisor_result_stage)
            log.info("qradar_analysis_restart_if_existed: %s",
                     qradar_analysis_restart_if_existed)

            qradar_verify_cert = True
            if "verify_cert" in self.options and self.options[
                    "verify_cert"] == "false":
                qradar_verify_cert = False

            yield StatusMessage("starting...")

            if qradar_analysis_restart_if_existed:
                # User wants restart a new analysis. Warn him/her it could take some time
                yield StatusMessage(
                    "Restarting a new analysis. It could take up to 15 minutes..."
                )

            offense_analysis_timeout = int(
                self.options.get("offense_analysis_timeout", 1200))
            offense_analysis_period = int(
                self.options.get("offense_analysis_period", 5))

            log.debug("Using timeout: {}".format(
                str(offense_analysis_timeout)))
            log.debug("Using period: {}".format(str(offense_analysis_period)))

            client = QRadarAdvisorClient(
                qradar_host=self.options["qradar_host"],
                qradar_token=self.options["qradar_advisor_token"],
                advisor_app_id=self.options["qradar_advisor_app_id"],
                cafile=qradar_verify_cert,
                log=log)
            stix_json = client.offense_analysis(
                offense_id=qradar_offense_id,
                restart_if_existed=qradar_analysis_restart_if_existed,
                return_stage=qradar_advisor_result_stage,
                timeout=offense_analysis_timeout,
                period=offense_analysis_period)
            #
            # extract list of observables from this stix bundle
            #
            observables = stix_utils.get_observables(stix_json=stix_json,
                                                     log=log)
            #
            # generate a folder-tree like structure in html for this stix bundle
            #
            html_str = stix_tree.get_html(stix_json, log)

            #
            # get the insights for this offense
            #
            insights = client.offense_insights(offense_id=qradar_offense_id)

            yield StatusMessage("done...")
            yield StatusMessage("Returning {} observables".format(
                str(len(observables))))

            results = {
                "observables": observables,
                "note": html_str,
                "insights": insights,  # Return the raw insights dict
                "stix": stix_json  # Return the raw stix2 dict
            }

            # Produce a FunctionResult with the results
            yield FunctionResult(results)
        except Exception as e:
            log.error(e.message)
            yield FunctionError(e.message)
예제 #22
0
    def _cb_remove_system_isolation_function(self, event, *args, **kwargs):

        results = {}
        results["was_successful"] = False
        results["hostname"] = None

        try:
            # Get the function parameters:
            incident_id = kwargs.get("incident_id")  # number
            hostname = kwargs.get("hostname")  # text

            log = logging.getLogger(__name__)

            hostname = hostname.upper(
            )[:15]  # CB limits hostname to 15 characters
            sensor = cb.select(Sensor).where(
                'hostname:' + hostname)  # Query CB for the hostname's sensor

            if len(sensor) <= 0:  # Host does not have CB agent, abort
                yield StatusMessage(
                    "[FATAL ERROR] CB could not find hostname: " +
                    str(hostname))
                yield StatusMessage('[FAILURE] Fatal error caused exit!')
                yield FunctionResult(results)
                return

            sensor = sensor[0]  # Get the sensor object from the query
            results["hostname"] = str(hostname).upper()

            try:

                if sensor.group.id in protected_sensor_group_ids:
                    yield StatusMessage(
                        '[FAILURE] Hostname ' + str(hostname) +
                        ' is in a protected group, isolation removal not allowed via Resilient!'
                    )
                    yield FunctionResult(results)
                    return

                elif sensor.network_isolation_enabled is False:
                    yield StatusMessage('[FAILURE] Hostname ' + str(hostname) +
                                        ' is not isolated!')
                    yield FunctionResult(results)
                    return

                else:
                    yield StatusMessage(
                        '[INFO] Attempting to remove isolation from hostname '
                        + str(hostname) + '...')
                    is_successful = sensor.unisolate(
                    )  # Isolate the sensor, wait indefinitely until the sensor acknowledges the isolation. Store the 'True' return value to is_successful

            except Exception as err:  # Catch all exceptions and abort
                yield StatusMessage('[FATAL ERROR] Encountered: ' + str(err))
                yield StatusMessage('[FAILURE] Fatal error caused exit!')

            if is_successful is True:
                results["was_successful"] = True
                yield StatusMessage('[SUCCESS] The CB agent on hostname ' +
                                    str(hostname) +
                                    ' acknowledged the isolation removal.')

            else:  # This can only occur if there was an exception
                yield StatusMessage('[FAILURE] Hostname ' + str(hostname) +
                                    ' was NOT removed from isolation!')

            # Produce a FunctionResult with the results
            yield FunctionResult(results)
        except Exception:
            yield FunctionError()
    def _attachment_zip_extract_function(self, event, *args, **kwargs):
        """Function: Extract a file from a zipfile attachment, producing a base64 string."""
        try:
            log = logging.getLogger(__name__)

            # Get the function parameters:
            incident_id = kwargs.get("incident_id")  # number
            task_id = kwargs.get("task_id")  # number
            attachment_id = kwargs.get("attachment_id")  # number
            file_path = kwargs.get("file_path")  # text
            zipfile_password = kwargs.get("zipfile_password")  # text

            if incident_id is None and task_id is None:
                raise FunctionError(
                    "Error: incident_id or task_id must be specified.")
            if attachment_id is None:
                raise FunctionError("Error: attachment_id must be specified.")
            if file_path is None:
                raise FunctionError("Error: file_path must be specified.")

            log.info("incident_id: %s", incident_id)
            log.info("task_id: %s", task_id)
            log.info("attachment_id: %s", attachment_id)
            log.info("file_path: %s", file_path)

            yield StatusMessage("Reading attachment...")
            if task_id:
                metadata_uri = "/tasks/{}/attachments/{}".format(
                    task_id, attachment_id)
                data_uri = "/tasks/{}/attachments/{}/contents".format(
                    task_id, attachment_id)
            else:
                metadata_uri = "/incidents/{}/attachments/{}".format(
                    incident_id, attachment_id)
                data_uri = "/incidents/{}/attachments/{}/contents".format(
                    incident_id, attachment_id)

            client = self.rest_client()
            metadata = client.get(metadata_uri)
            data = client.get_content(data_uri)

            results = {}
            with tempfile.NamedTemporaryFile(delete=False) as temp_file:
                try:
                    temp_file.write(data)
                    temp_file.close()
                    # Examine with zip
                    zfile = zipfile.ZipFile(temp_file.name, "r")
                    # Read the metadata, since it may be useful
                    zinfo = zfile.getinfo(file_path)
                    # Don't include zinfo.extra since it's not a string
                    results["info"] = {
                        "filename":
                        zinfo.filename,
                        "date_time":
                        epoch_millis(zinfo.date_time),
                        "compress_type":
                        zinfo.compress_type,
                        "comment":
                        zinfo.comment.decode("utf-8") if isinstance(
                            zinfo.comment, bytes) else zinfo.comment,
                        "create_system":
                        zinfo.create_system,
                        "create_version":
                        zinfo.create_version,
                        "extract_version":
                        zinfo.extract_version,
                        "flag_bits":
                        zinfo.flag_bits,
                        "volume":
                        zinfo.volume,
                        "internal_attr":
                        zinfo.internal_attr,
                        "external_attr":
                        zinfo.external_attr,
                        "header_offset":
                        zinfo.header_offset,
                        "CRC":
                        zinfo.CRC,
                        "compress_size":
                        zinfo.compress_size,
                        "file_size":
                        zinfo.file_size
                    }
                    # Extract the file we want
                    b64data = base64.b64encode(
                        zfile.read(file_path, zipfile_password))
                    results["content"] = b64data.decode("utf-8") if isinstance(
                        b64data, bytes) else b64data,
                except (KeyError, zipfile.LargeZipFile,
                        zipfile.BadZipfile) as exc:
                    # results["error"] = str(exc)
                    # To help debug, list the contents
                    log.info(zfile.namelist())
                    raise
                finally:
                    os.unlink(temp_file.name)
            # Produce a FunctionResult with the return value
            yield FunctionResult(results)
        except Exception:
            yield FunctionError()
예제 #24
0
    def _umbrella_timeline_function(self, event, *args, **kwargs):
        """Function: Resilient Function : Cisco Umbrella Investigate Investigate for  Timeline."""
        try:
            # Get the function parameters:
            umbinv_resource = kwargs.get("umbinv_resource")  # text

            log = logging.getLogger(__name__)

            log.info("resource: %s", umbinv_resource)

            if is_none(umbinv_resource):
                raise ValueError(
                    "Required parameter 'umbinv_resource' not set")

            yield StatusMessage("Starting...")
            res = None
            res_type = None
            process_result = {}
            params = {"resource": umbinv_resource.strip()}

            validate_params(params)
            process_params(params, process_result)

            if "_res" not in process_result or "_res_type" not in process_result:
                raise ValueError(
                    "Parameter 'umbinv_resource' was not processed correctly")
            else:
                res = process_result.pop("_res")
                res_type = process_result.pop("_res_type")

            if res_type != "domain_name" and res_type != "ip_address" and res_type != "url":
                raise ValueError(
                    "Parameter 'umbinv_resource' was an incorrect type '{}', should be a 'domain name', "
                    "an 'ip address' or a 'url'.".format(res_type))

            api_token = self.options.get("api_token")
            base_url = self.options.get("base_url")
            rinv = ResilientInv(api_token, base_url, proxies=self.proxies)

            yield StatusMessage("Running Cisco Investigate query...")
            rtn = rinv.timeline(res)
            query_execution_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
            if len(rtn) == 0:
                log.debug(json.dumps(rtn))
                yield StatusMessage(
                    "No Results returned for resource '{}'.".format(res))
                results = {}
            else:
                # Make timestamp more readable
                for x in range(len(rtn)):
                    try:
                        secs = int(rtn[x]['timestamp']) / 1000
                        ts_readable = datetime.fromtimestamp(secs).strftime(
                            '%Y-%m-%d %H:%M:%S')
                        rtn[x]['timestamp_converted'] = ts_readable
                    except ValueError:
                        yield FunctionError(
                            'timestamp value incorrectly specified')
                # Add  "query_execution_time" to result to facilitate post-processing.
                results = {
                    "timeline": json.loads(json.dumps(rtn)),
                    "resource_name": res,
                    "query_execution_time": query_execution_time
                }
                yield StatusMessage(
                    "Returning 'thread_grid_samples' results for resource '{}'."
                    .format(res))

            yield StatusMessage("done...")

            log.debug(json.dumps(results))
            # Produce a FunctionResult with the results
            yield FunctionResult(results)
        except Exception:
            log.exception("Exception in Resilient Function.")
            yield FunctionError()
    def _pulsedive_query_id_function(self, event, *args, **kwargs):
        """Function: Query Pulsedive for information on an indicator ID, threat ID, or feed ID.
        This function gets input values from the action rule activity fields and
        sends them to Pulsedive's Query endpoint
        Results: a summary will be written to an incident note and
                 full details will be written to an incident attachment.
        """
        try:
            log = logging.getLogger(__name__)

            log.info("config params: %s", self.options)

            # get query type: Indicator, Threat, or Feed
            pulsedive_id = kwargs.get("pulsedive_id")
            pulsedive_query_type = self.get_select_param(kwargs.get("pulsedive_query_type"))
            pulsedive_id_report_type = self.get_select_param(kwargs.get(
                "pulsedive_id_report_type"))

            # === get url parameters based on report type
            mapping = self._get_mapping(pulsedive_query_type, **kwargs)

            # eliminate empty/null vars
            pulsedive_data = {}
            for k, v in mapping.items():
                if v is not None and v != "":
                    pulsedive_data[k] = v
            # add key
            pulsedive_data["key"] = self.options["pulsedive_api_key"]
            log.info("%s parameters: %s", pulsedive_query_type, pulsedive_data)

            # === set incident parameters
            incident_id = kwargs.get("incident_id")     # integer
            if kwargs.get("attachment_name") is None:
                attachment_name = u"pulsedive_{}_id{}_{}.txt".format(
                    pulsedive_query_type, pulsedive_id, pulsedive_id_report_type)
            else:
                attachment_name = kwargs.get("attachment_name").replace(u" ", u"_")

            log.info("function params: pulsedive id = %s, type = %s, report = %s,\
                     incident='%s', attachment='%s'",
                     pulsedive_id, pulsedive_query_type, pulsedive_id_report_type,
                     incident_id, attachment_name)

            yield StatusMessage("starting...")

            # form the url request
            api_url = "{}/info.php?".format(self.options["pulsedive_api_url"])

            # === make the api call
            rp = ResultPayload(CONFIG_SECTION, **kwargs)
            rc = RequestsCommon(self.opts, self.options)    # initialize
            resp = rc.execute_call_v2("get",
                                      url=api_url,
                                      params=pulsedive_data
                                      )

            # === Get the rest client so we can add the attachment to the incident
            client = self.rest_client()

            # prepare datastream to output to attachment
            if pulsedive_data["pretty"] == "Yes":
                # Pulsedive returns pp format if requested. Convert to bytestream for file handling.
                datastream = BytesIO(resp.content)
            else:
                # Convert dict to string first, then convert to bytestream for file handling.
                ds = resp.text
                datastream = BytesIO(ds.encode("utf-8"))

            # Write the file as attachment: failures will raise an exception
            write_file_attachment(client, attachment_name, datastream=datastream,
                                  incident_id=incident_id, task_id=None)

            # === prepare results
            resp_json = rp.done(True, resp.json())
            results = {
                "resp_json": resp_json,
                "att_name": attachment_name
            }

            # Produce a FunctionResult with the results
            yield FunctionResult(results)

            yield StatusMessage("done...")
        except Exception as err:
            yield FunctionError(err)
    def _fn_tor_function(self, event, *args, **kwargs):
        """Function: This TOR function searches for the given IP Address or host names in TOR exit node Network by using RESTful API"""
        try:
            # Get the function parameters:
            tor_search_data = kwargs.get("tor_search_data")  # text

            log = logging.getLogger(__name__)
            log.info("tor_search_data: %s", tor_search_data)

            yield StatusMessage("starting...")

            __params_data = {
                'flag': self.options.get('flag'),
                'fields': self.options.get('data_fields'),
                'search': tor_search_data
            }
            __params_data = "&".join("%s=%s" % (k, v)
                                     for k, v in __params_data.items())
            __response_data = requests.get(self.options.get('base_url'),
                                           params=__params_data)
            __MATCH_FLAG = False
            if __response_data.status_code == 200:
                __response_data_json_obj = __response_data.json()
                __relays_data_list = __response_data_json_obj.get('relays')
                search_field_list = self.options.get('data_fields').split(',')
                if not __relays_data_list:
                    log.info("Given Search Artifact is Not Matched...!")
                    __MATCH_FLAG = False
                else:
                    for relay_data in __relays_data_list:
                        for field in search_field_list:
                            data = relay_data.get(field)
                            if data is not None:
                                if isinstance(data, list):
                                    for element in data:
                                        if element.find(tor_search_data) != -1:
                                            log.info(
                                                'Given Search Artifact matched..!'
                                            )
                                            __MATCH_FLAG = True
                                else:
                                    if data.find(tor_search_data) != -1:
                                        log.info(
                                            'Given Search Artifact matched..!')
                                        __MATCH_FLAG = True
            else:
                __MATCH_FLAG = False
                log.info(__response_data.text)

            if __MATCH_FLAG:
                results = {
                    'status': 'success',
                    'value': True,
                    'data': __response_data.text
                }
            else:
                log.info("Given Search Artifact is Not Matched...!")
                results = {
                    'status': 'failed',
                    'value': False,
                    'data': __response_data.text
                }

            yield StatusMessage("done...")

            # Produce a FunctionResult with the results
            yield FunctionResult(results)
        except Exception as e:
            yield FunctionError(e)
    def _mcafee_publish_to_dxl_function(self, event, *args, **kwargs):
        """Function: A function which takes 3 inputs:

        mcafee_topic_name: String of the topic name. ie: /mcafee/service/epo/remote/epo1.
        mcafee_dxl_payload: The text of the payload to publish to the topic.
        mcafee_return_request: Specify whether or not to wait for and return the response.


        The function will publish the provided payload to the provided topic.
        Indicate whether acknowledgment response should be returned."""
        try:
            yield StatusMessage("Starting...")
            # Get the function parameters:
            mcafee_topic_name = kwargs.get("mcafee_topic_name")  # text
            if not mcafee_topic_name:
                yield FunctionError("mcafee_topic_name is required")
            mcafee_dxl_payload = kwargs.get("mcafee_dxl_payload")  # text
            if not mcafee_dxl_payload:
                yield FunctionError("mcafee_dxl_payload is required")
            mcafee_publish_method = self.get_select_param(
                kwargs.get("mcafee_publish_method")
            )  # select, values: "Event", "Service"
            if not mcafee_publish_method:
                yield FunctionError("mcafee_publish_method is required")
            mcafee_wait_for_response = self.get_select_param(
                kwargs.get(
                    "mcafee_wait_for_response"))  # select, values: "Yes", "No"

            log.info("mcafee_topic_name: %s", mcafee_topic_name)
            log.info("mcafee_dxl_payload: %s", mcafee_dxl_payload)
            log.info("mcafee_publish_method: %s", mcafee_publish_method)
            log.info("mcafee_wait_for_response: %s", mcafee_wait_for_response)

            response = None

            # Publish Event
            if mcafee_publish_method == "Event":
                event = Event(mcafee_topic_name)
                event.payload = mcafee_dxl_payload
                yield StatusMessage("Publishing Event...")
                self.client.send_event(event)

            # Invoke Service
            else:
                req = Request(mcafee_topic_name)
                req.payload = mcafee_dxl_payload
                yield StatusMessage("Invoking Service...")

                if mcafee_wait_for_response == "No":
                    self.client.async_request(req)
                else:
                    response = Response(
                        self.client.sync_request(req, timeout=300))

            yield StatusMessage("Done...")
            r = {
                "mcafee_topic_name": mcafee_topic_name,
                "mcafee_dxl_payload": mcafee_dxl_payload,
                "mcafee_publish_method": mcafee_publish_method,
                "mcafee_wait_for_response": mcafee_wait_for_response
            }

            # Initialize the results payload
            rp = ResultPayload(PACKAGE_NAME, **kwargs)

            # Return response from publishing to topic
            if response is not None:
                # Convert response object to dict
                response_dict = vars(response)
                # Convert dict bytes fields to string for python 3
                response_dict_str = convert(response_dict)
                r["response"] = response_dict_str

            results = rp.done(True, r)
            # Include for backward compatibility
            results["mcafee_topic_name"] = mcafee_topic_name
            results["mcafee_dxl_payload"] = mcafee_dxl_payload
            results["mcafee_publish_method"] = mcafee_publish_method
            results["mcafee_wait_for_response"] = mcafee_wait_for_response
            yield FunctionResult(results)
        except Exception as e:
            yield FunctionError(e)
예제 #28
0
    def _qradar_search_function(self, event, *args, **kwargs):
        """Function: Search QRadar"""
        try:
            # Get the function parameters:
            qradar_query = self.get_textarea_param(
                kwargs.get("qradar_query"))  # textarea
            qradar_query_param1 = kwargs.get("qradar_query_param1")  # text
            qradar_query_param2 = kwargs.get("qradar_query_param2")  # text
            qradar_query_param3 = kwargs.get("qradar_query_param3")  # text
            qradar_query_param4 = kwargs.get("qradar_query_param4")  # text
            qradar_query_param5 = kwargs.get("qradar_query_param5")  # text
            qradar_query_range_start = kwargs.get(
                "qradar_query_range_start")  # number
            qradar_query_range_end = kwargs.get(
                "qradar_query_range_end")  # number

            log = logging.getLogger(__name__)
            log.info("qradar_query: %s", qradar_query)
            log.info("qradar_query_param1: %s", qradar_query_param1)
            log.info("qradar_query_param2: %s", qradar_query_param2)
            log.info("qradar_query_param3: %s", qradar_query_param3)
            log.info("qradar_query_param4: %s", qradar_query_param4)
            log.info("qradar_query_param5: %s", qradar_query_param5)
            log.info("qradar_query_range_start: %s", qradar_query_range_start)
            log.info("qradar_query_range_end: %s", qradar_query_range_end)

            qradar_verify_cert = True
            if "verify_cert" in self.options and self.options[
                    "verify_cert"] == "false":
                qradar_verify_cert = False

            timeout = None
            try:
                if "search_timeout" in self.options:
                    timeout = float(self.options["search_timeout"])
            except:
                log.debug("Failed to read search_timeout: {}".format(
                    self.options["search_timeout"]))

            log.debug("Connection to {} using {}".format(
                self.options["host"], self.options["username"]))

            query_string = function_utils.make_query_string(
                qradar_query, [
                    qradar_query_param1, qradar_query_param2,
                    qradar_query_param3, qradar_query_param4,
                    qradar_query_param5
                ])

            log.info("Running query: " + query_string)

            yield StatusMessage("starting...")
            qradar_client = QRadarClient(
                host=self.options["host"],
                username=self.options.get("username", None),
                password=self.options.get("qradarpassword", None),
                token=self.options.get("qradartoken", None),
                cafile=qradar_verify_cert)

            result = qradar_client.ariel_search(
                query_string,
                range_start=qradar_query_range_start,
                range_end=qradar_query_range_end,
                timeout=timeout)

            yield StatusMessage("done...")
            yield FunctionResult(result)
        except Exception as e:
            log.error(str(e))
            yield FunctionError()
    def _mcafee_tie_set_file_reputation_function(self, event, *args, **kwargs):
        """Function: Set a file's reputation. This works on MD5, SH1 and SHA256 hashes."""
        try:
            yield StatusMessage("Starting")

            # Get the function parameters:
            mcafee_tie_hash_type = kwargs.get("mcafee_tie_hash_type")  # text
            mcafee_tie_comment = kwargs.get("mcafee_tie_comment")  # text
            mcafee_tie_reputation_type = self.get_select_param(
                kwargs.get("mcafee_tie_reputation_type")
            )  # select, values: "Enterprise", "External"
            mcafee_tie_trust_level = self.get_select_param(
                kwargs.get("mcafee_tie_trust_level")
            )  # select, values: "KNOWN_TRUSTED_INSTALLED", "KNOWN_TRUSTED", "MOST_LIKELY_TRUSTED", "MIGHT_BE_TRUSTED", "UNKNOWN", "MIGHT_BE_MALICIOUS", "MOST_LIKELY_MALICIOUS", "KNOWN_MALICIOUS", "NOT SET"
            mcafee_tie_hash = kwargs.get("mcafee_tie_hash")  # text
            mcafee_tie_filename = kwargs.get("mcafee_tie_filename")  # text

            log = logging.getLogger(__name__)
            log.info("mcafee_tie_reputation_type: %s",
                     mcafee_tie_reputation_type)
            log.info("mcafee_tie_hash_type: %s", mcafee_tie_hash_type)
            log.info("mcafee_tie_comment: %s", mcafee_tie_comment)
            log.info("mcafee_tie_trust_level: %s", mcafee_tie_trust_level)
            log.info("mcafee_tie_hash: %s", mcafee_tie_hash)
            log.info("mcafee_tie_filename: %s", mcafee_tie_filename)

            validate_fields([
                "mcafee_tie_reputation_type", "mcafee_tie_hash_type",
                "mcafee_tie_hash", "mcafee_tie_trust_level"
            ], kwargs)

            result_payload = ResultPayload(PACKAGE_NAME, **kwargs)

            tie_client = get_tie_client(self.client)

            hash_type = get_mcafee_hash_type(mcafee_tie_hash_type)
            trust_level = get_trust_level_value(mcafee_tie_trust_level)
            hash_payload = {hash_type: mcafee_tie_hash}
            LOG.debug("Trust Level: %s, payload: %s", trust_level,
                      hash_payload)

            # set the call back routine to catch changes or errors
            result_callback = MyReputationChangeCallback()
            tie_client.add_file_reputation_change_callback(result_callback)

            if mcafee_tie_reputation_type == "Enterprise":
                # Set the Enterprise reputation for notepad.exe to Known Trusted
                tie_client.set_file_reputation(trust_level,
                                               hash_payload,
                                               comment=mcafee_tie_comment,
                                               filename=mcafee_tie_filename)
            elif mcafee_tie_reputation_type == "External":
                # Set the Enterprise reputation for notepad.exe to Known Trusted
                tie_client.set_external_file_reputation(
                    trust_level,
                    hash_payload,
                    comment=mcafee_tie_comment,
                    filename=mcafee_tie_filename)
            else:
                raise ValueError("Unknown reputation type: %s",
                                 mcafee_tie_reputation_type)

            # wait for result or a timeout
            wait_iter = 4
            sleep_time = 5
            # sleep while waiting for a call back. Sleep time progesses: 5, 10, 15, ...
            while wait_iter > 0 and not result_callback.result:
                time.sleep(sleep_time)
                sleep_time += 5
                wait_iter -= 1

            yield StatusMessage("Finished" if result_callback.result else
                                "No result received. Check McAfee console")
            results = result_payload.done(
                True if result_callback.result else False,
                result_callback.result)

            # Produce a FunctionResult with the results
            yield FunctionResult(results)
        except Exception:
            yield FunctionError()
    def _function_cve_search_function(self, event, *args, **kwargs):
        """Function: A Function to Search  Common Vulnerability Exposures Data from https://cve.circl.lu Data Base."""
        try:
            # Get the function parameters:
            cve_id = kwargs.get("cve_id")  # text
            if cve_id:
                cve_id = cve_id.strip()

            cve_vendor = kwargs.get("cve_vendor")  # text
            if cve_vendor:
                cve_vendor = cve_vendor.strip()

            cve_product = kwargs.get("cve_product")  # text
            if cve_product:
                cve_product = cve_product.strip()

            cve_published_date_from = kwargs.get(
                "cve_published_date_from")  # datepicker
            cve_published_date_to = kwargs.get(
                "cve_published_date_to")  # datepicker

            # Getting Max Result Display Flag from app.config
            MAX_RESULTS_RETURN = int(self.options.get('max_results_display'))

            # Variables to Store Parsed CVE API Data
            _result_search_data = dict()

            log = logging.getLogger(__name__)
            log.info("cve_id: %s", cve_id)
            log.info("cve_vendor: %s", cve_vendor)
            log.info("cve_product: %s", cve_product)
            log.info("cve_published_date_from: %s", cve_published_date_from)
            log.info("cve_published_date_to: %s", cve_published_date_to)

            yield StatusMessage(
                "Searching the CVE Database. ID:{}, Vendor:{}, Product:{}".
                format(cve_id, cve_vendor, cve_product))

            if cve_id is None and ((cve_vendor is None and cve_product) or
                                   (cve_vendor and cve_product is None)):
                raise ValueError(
                    "Specify both cve_vendor and cve_product or cve-id")
            if cve_id and (cve_product or cve_vendor):
                raise ValueError(
                    "Specify both cve_vendor and cve_product or cve-id")

            if cve_id:
                _browse_data = self._get_specific_cve_data(cve_id=cve_id)

            elif cve_vendor and cve_product:
                _browse_data = self._search_cve_api(vendor_name=cve_vendor,
                                                    product=cve_product)
            else:
                _browse_data = self._last_30_cves()

            # Defining a key in result dictionary to store parsed data
            _result_search_data['content'] = []

            # Type Of the rest api call like browse,search,specific cve,last, db info
            api_call_type = _browse_data['api_call']

            # Rest Api Response Data
            _browse_data_content = _browse_data['content']
            if _browse_data_content:
                if api_call_type == 'search':
                    _result_search_data['api_call'] = 'search'
                    search_data_list = self._parse_search_results(
                        _browse_data_content, cve_published_date_from,
                        cve_published_date_to, MAX_RESULTS_RETURN)
                    _result_search_data['content'].extend(search_data_list)
                elif api_call_type == 'cve':
                    _result_search_data['api_call'] = 'cve'
                    cve_data_list = self._parse_cve_results(
                        _browse_data_content)
                    _result_search_data['content'].extend(cve_data_list)
                elif api_call_type == 'last':
                    _result_search_data['api_call'] = 'last'
                    last_data_list = self._parse_last_cve_results(
                        _browse_data_content, MAX_RESULTS_RETURN)
                    _result_search_data['content'].extend(last_data_list)
            log.debug("The Data Received from CVE DB : {}".format(
                _result_search_data))
            yield StatusMessage("Successfully searched the CVE Database.")

            # Produce a FunctionResult with the results
            yield FunctionResult(_result_search_data)
        except Exception as er:
            yield FunctionError(er)