def _exchange_online_delete_email_function(self, event, *args, **kwargs): """Function: Delete a message in the specified user's mailbox.""" try: # Initialize the results payload rp = ResultPayload(CONFIG_DATA_SECTION, **kwargs) # Validate fields validate_fields(['exo_email_address', 'exo_messages_id'], kwargs) # Get the function parameters email_address = kwargs.get('exo_email_address') # text mailfolders_id = kwargs.get('exo_mailfolders_id') # text messages_id = kwargs.get('exo_messages_id') # text LOG.info(u"exo_email_address: %s", email_address) LOG.info(u"exo_mailfolders_id: %s", mailfolders_id) LOG.info(u"exo_messages_id: %s", messages_id) yield StatusMessage(u"Starting delete message 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"), self.options.get("max_retries_total", MAX_RETRIES_TOTAL), self.options.get("max_retries_backoff_factor", MAX_RETRIES_BACKOFF_FACTOR), self.options.get("max_batched_requests", MAX_BATCHED_REQUESTS), RequestsCommon(self.opts, self.options).get_proxies()) # Call MS Graph API to get the user profile response = MS_graph_helper.delete_message(email_address, mailfolders_id, messages_id) # If message was deleted a 204 code is returned. if response.status_code == 204: success = True response_json = {'value': success} else: success = False response_json = response.json() results = rp.done(success, response_json) yield StatusMessage(u"Returning delete results for email address: {}".format(email_address)) # Produce a FunctionResult with the results yield FunctionResult(results) except Exception as err: LOG.error(err) yield FunctionError(err)
def _data_feeder_sync_incidents_function(self, event, *args, **kwargs): """Function: Synchronize Incident(s) and their associated tasks, notes, attachments, artifacts, milestones and associated datatables""" try: # Get the wf_instance_id of the workflow this Function was called in wf_instance_id = event.message["workflow_instance"][ "workflow_instance_id"] result = ResultPayload("data_feeder", **kwargs) # Get the function parameters: df_min_incident_id = kwargs.get("df_min_incident_id") # number df_max_incident_id = kwargs.get("df_max_incident_id", df_min_incident_id) # number df_query_api_method = kwargs.get("df_query_api_method", False) # boolean log = logging.getLogger(__name__) log.info("df_min_incident_id: %s", df_min_incident_id) log.info("df_max_incident_id: %s", df_max_incident_id) if not df_max_incident_id: df_max_incident_id = df_min_incident_id if df_min_incident_id > df_max_incident_id: raise ValueError( "Min value {} greater than max value {}".format( df_min_incident_id, df_max_incident_id)) # select all incidents as max if df_max_incident_id == 0: df_max_incident_id = sys.maxsize yield StatusMessage("starting...") rest_client_helper = RestClientHelper(self.rest_client) feed_outputs = build_feed_outputs( rest_client_helper, self.opts, self.options.get("feed_names", None)) df = Reload(rest_client_helper, feed_outputs, query_api_method=df_query_api_method) reloaded_incidents = df.reload_all(min_inc_id=df_min_incident_id, max_inc_id=df_max_incident_id) result_payload = result.done( True, {"num_of_sync_incidents": reloaded_incidents}) yield StatusMessage("done...") # Produce a FunctionResult with the results yield FunctionResult(result_payload) except Exception: yield FunctionError()
def _teams_post_message_function(self, event, *args, **kwargs): """Function: Post a message to a Microsoft Teams channel""" try: validate_fields(['incident_id', 'teams_channel', 'teams_payload'], kwargs) # Get the function parameters: incident_id = kwargs.get("incident_id") # number task_id = kwargs.get("task_id") # number teams_channel = kwargs.get("teams_channel") # text teams_payload = kwargs.get("teams_payload") # text teams_mrkdown = kwargs.get("teams_mrkdown", False) # boolean log = logging.getLogger(__name__) log.info("incident_id: %s", incident_id) log.info("task_id: %s", task_id) log.info("teams_channel: %s", teams_channel) log.info("teams_payload: %s", teams_payload) log.info("teams_mrkdown: %s", teams_mrkdown) yield StatusMessage("starting...") result_payload = ResultPayload(SECTION_NAME, **kwargs) # get the webhook for the channel webhook = self.options.get(teams_channel) if not webhook: raise ValueError("Unable to find channel: %s in app.config", teams_channel) request_common = RequestsCommon(self.opts) payload_json = json.loads( teams_payload.replace("\n", "").replace("None", "null")) proxies = request_common.get_proxies() card = pymsteams.connectorcard( webhook, http_proxy=proxies['http'] if proxies else None, https_proxy=proxies['https'] if proxies else None, http_timeout=TIMEOUT) self.build_conversation(card, incident_id, task_id, payload_json, teams_mrkdown) card.send() yield StatusMessage("done...") results = result_payload.done(True, None) # Produce a FunctionResult with the results yield FunctionResult(results) except Exception: yield FunctionError()
def _funct_zia_remove_from_allowlist_function(self, event, *args, **kwargs): """Function: Remove URLs or IP addresses from the allowlist. See following for URL guidelines https://help.zscaler.com/zia/url-format-guidelines""" try: rp = ResultPayload(PACKAGE_NAME, **kwargs) # 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 '{0}' running in workflow '{1}'".format( FN_NAME, wf_instance_id)) # Get and validate required function inputs: fn_inputs = validate_fields(["zia_allowlisturls", "zia_activate"], kwargs) LOG.info("'{0}' inputs: %s", fn_inputs) yield StatusMessage( "Validations complete. Starting business logic") allowlisturls = fn_inputs.get("zia_allowlisturls") activate = fn_inputs.get("zia_activate") ziacli = ZiaClient(self.opts, self.fn_options) result = { "response": ziacli.allowlist_action(allowlisturls, "REMOVE_FROM_LIST") } result["activation"] = ziacli.activate(activate) yield StatusMessage( "Finished '{0}' that was running in workflow '{1}'".format( FN_NAME, wf_instance_id)) results = rp.done(True, result) LOG.info("'%s' complete", FN_NAME) yield StatusMessage( "Returning results for function '{}' with parameters '{}'.". format( FN_NAME, ", ".join("{!s}={!r}".format(k, v) for (k, v) in fn_inputs.items()))) # Produce a FunctionResult with the results yield FunctionResult(results) except Exception as e: yield FunctionError(e)
def _exchange_online_move_message_to_folder_function(self, event, *args, **kwargs): """Function: This function will move an Exchange Online message to the specified folder in the users mailbox.""" try: # Initialize the results payload rp = ResultPayload(CONFIG_DATA_SECTION, **kwargs) # Validate fields validate_fields(['exo_email_address', 'exo_messages_id', 'exo_destination_mailfolder_id'], kwargs) # Get the function parameters: email_address = kwargs.get("exo_email_address") # text message_id = kwargs.get("exo_messages_id") # text mailfolders_id = kwargs.get("exo_mailfolders_id") # text destination_id = kwargs.get("exo_destination_mailfolder_id") # text LOG.info(u"exo_email_address: %s", email_address) LOG.info(u"exo_messages_id: %s", message_id) LOG.info(u"exo_mailfolders_id: %s", mailfolders_id) LOG.info(u"exo_destination_id: %s", destination_id) yield StatusMessage(u"Starting move message for email address: {} to mail folder {}".format(email_address, destination_id)) # 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.move_message(email_address, mailfolders_id, message_id, destination_id) # If message was deleted a 201 code is returned. if response.status_code == 201: success = True new_message_id = response.json().get('id') response_json = {'new_message_id': new_message_id} else: success = False response_json = response.json() results = rp.done(success, response_json) yield StatusMessage(u"Returning delete results for email address: {}".format(email_address)) # Produce a FunctionResult with the results yield FunctionResult(results) except Exception as err: LOG.error(err) yield FunctionError(err)
def _incident_utils_close_incident_function(self, event, *args, **kwargs): """Function: Function that takes a JSON String of field and value pairs to close an Incident.""" try: # Get the function parameters: incident_id = kwargs.get("incident_id") # number close_fields = kwargs.get("close_fields") # text log = logging.getLogger(__name__) rp = ResultPayload(PACKAGE_NAME, **kwargs) # Check JSON string and convert it to dict if close_fields is None: close_fields = {} else: try: close_fields = json.loads(close_fields) except ValueError as jerr: reason = "Failure parsing 'close_fields': {}".format(str(jerr)) log.error(reason) yield FunctionResult(rp.done(False, None, reason=reason)) return log.info("incident_id: %s", incident_id) log.info("close_fields: %s", close_fields) yield StatusMessage("starting...") # Instansiate new Resilient API object res_client = self.rest_client() # API call to Close an Incident response = close_incident(res_client, incident_id, close_fields) results = rp.done(True, response.json()) yield StatusMessage("done...") # Produce a FunctionResult with the results yield FunctionResult(results) except Exception: yield FunctionError()
def _fn_sep_add_fingerprint_list_function(self, event, *args, **kwargs): """Function: Add a hash to a new fingerprint list.""" try: params = transform_kwargs(kwargs) if kwargs else {} # Instantiate result payload object rp = ResultPayload(CONFIG_DATA_SECTION, **kwargs) # Get the function parameters: sep_fingerprintlist_name = kwargs.get( "sep_fingerprintlist_name") # text sep_description = kwargs.get("sep_description") # text sep_domainid = kwargs.get("sep_domainid") # text sep_hash_value = kwargs.get("sep_hash_value") # text LOG.info("sep_fingerprintlist_name: %s", sep_fingerprintlist_name) LOG.info("sep_description: %s", sep_description) LOG.info("sep_domainid: %s", sep_domainid) LOG.info("sep_hash_value: %s", sep_hash_value) validate_fields([ "sep_fingerprintlist_name", "sep_description", "sep_domainid", "sep_hash_value" ], kwargs) yield StatusMessage( "Running Symantec SEP Add Fingerprint List action ...") sep = Sepclient(self.options, params) rtn = sep.add_fingerprint_list(**params) if "errors" in rtn and rtn["errors"][0]["error_code"] == 409: # If this error was trapped user probably tried to re-add a hash to a fingerprint list. yield StatusMessage( u"Got a 409 error while attempting to get a fingerprint list for fingerprint name '{0}' " "because of a possible invalid or deleted id.".format( sep_fingerprintlist_name)) else: yield StatusMessage( u"Returning 'Symantec SEP Add Fingerprint List' results for fingerprint name '{}'." .format(sep_fingerprintlist_name)) results = rp.done(True, rtn) LOG.debug(json.dumps(results["content"])) # Produce a FunctionResult with the results yield FunctionResult(results) except Exception: LOG.exception("Exception in Resilient Function for Symantec SEP.") yield FunctionError()
def _exchange_online_send_message_function(self, event, *args, **kwargs): """Function: This function will create a message and send to the specified recipients.""" try: rp = ResultPayload(CONFIG_DATA_SECTION, **kwargs) # Validate fields validate_fields(['exo_email_address', 'exo_recipients'], kwargs) # Get the function parameters: email_address = kwargs.get("exo_email_address") # text recipients = kwargs.get("exo_recipients") # text message_subject = kwargs.get("exo_message_subject") # text message_body = kwargs.get("exo_message_body") # text log = logging.getLogger(__name__) log.info(u"exo_email_address: %s", email_address) log.info(u"exo_recipients: %s", recipients) log.info(u"exo_message_subject: %s", message_subject) log.info(u"exo_message_body: %s", message_body) yield StatusMessage(u"Starting send message from 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 send the message response = MS_graph_helper.send_message(email_address, recipients, message_subject, message_body) # If message was sent a 202 code is returned...nothing is returned in the response. if response.status_code == 202: success = True response_json = {'value': success} else: success = False response_json = response.json() results = rp.done(success, response_json) yield StatusMessage(u"Returning send mail results by email address: {}".format(email_address)) # Produce a FunctionResult with the results yield FunctionResult(results) except Exception as err: LOG.error(err) yield FunctionError(err)
def _secureworks_ctp_close_ticket_function(self, event, *args, **kwargs): """Function: Close a Secureworks CTP in an incident that has a Secureworks CTP ticket associated with it.""" try: # Initialize the results payload rp = ResultPayload(CONFIG_DATA_SECTION, **kwargs) # Get the function parameters: incident_id = kwargs.get("incident_id") # number log = logging.getLogger(__name__) log.info("incident_id: %s", incident_id) yield StatusMessage(u"Starting Close Secureworks CTP ticket.") # Get the incident uri = u"/incidents/{}?handle_format=names".format(incident_id) incident = self.rest_client().get(uri) if not incident: IntegrationError( "Secureworks CTP close ticket: Incident {0} not found". format(incident_id)) # Make sure there is an SecureWorks CTP ticket associated with this incident ticket_id = incident.get('properties', {}).get('scwx_ctp_ticket_id', None) if not ticket_id: raise IntegrationError( "Secureworks CTP close ticket: Incident {0} does not contain a ticketId" .format(incident_id)) resolution_summary = clean_html(incident.get('resolution_summary')) close_code = incident.get('properties', {}).get('scwx_ctp_close_code', None) response = self.scwx_client.post_tickets_close( ticket_id, resolution_summary, close_code) success = bool(response.get('code') == 'SUCCESS') results = rp.done(success, response) yield StatusMessage( u"Returning results for Close Secureworks CTP ticketId: {0} incident ID: {1}" .format(ticket_id, incident_id)) # Produce a FunctionResult with the results yield FunctionResult(results) except Exception as err: log.error(err) yield FunctionError()
def _exchange_online_get_email_user_profile_function( self, event, *args, **kwargs): """Function: This function will get Exchange Online user profile for a given email address.""" try: # Initialize the results payload rp = ResultPayload(CONFIG_DATA_SECTION, **kwargs) # Validate fields validate_fields(['exo_email_address'], kwargs) # Get the function parameters email_address = kwargs.get('exo_email_address') # text LOG.info(u"exo_email_address: %s", email_address) yield StatusMessage( u"Starting user profile query for email address: {}".format( email_address)) # Get the MS Graph helper class # 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_user_profile(email_address) response_json = response.json() results = rp.done(True, response_json) # Add pretty printed string for easier to read output text in note. pretty_string = json.dumps(response_json, ensure_ascii=False, sort_keys=True, indent=4, separators=(',', ': ')) results['pretty_string'] = pretty_string yield StatusMessage( u"Returning user profile results for email address: {}".format( email_address)) # Produce a FunctionResult with the results yield FunctionResult(results) except Exception as err: LOG.error(err) yield FunctionError(err)
def _fn_aws_iam_deactivate_mfa_devices_function(self, event, *args, **kwargs): """Function: Deactivate an MFA device and remove it from association with the user name for which it was originally enabled. param aws_iam_user_name: An IAM user name. param aws_iam_mfa_serial_numbers: A comma separated list of IAM MFA serial numbers or arns. """ try: params = transform_kwargs(kwargs) if kwargs else {} # Instantiate result payload object rp = ResultPayload(CONFIG_DATA_SECTION, **kwargs) aws_iam_user_name = kwargs.get("aws_iam_user_name") # text aws_iam_mfa_serial_nums = kwargs.get( "aws_iam_mfa_serial_nums") # text LOG.info("aws_iam_user_name: %s", aws_iam_user_name) LOG.info("aws_iam_mfa_serial_nums: %s", aws_iam_mfa_serial_nums) validate_fields(["aws_iam_user_name", "aws_iam_mfa_serial_nums"], kwargs) iam_cli = AwsIamClient(self.options) # Delete 'MfaSerialNums' parameter from params. if "MfaSerialNums" in params: del params["MfaSerialNums"] rtn = [] # Iterate over mfa serial numbers in the comma separated list in parameter # 'param aws_iam_mfa_serial_numbers'. Add each in turn to the 'params' dict then attempt to deactivate each # mfa for the user in parameter 'aws_iam_user_name'. Include the status of each attempt in the returned # result. for mfa_ser_num in re.split(r"\s*,\s*", aws_iam_mfa_serial_nums): params.update({"SerialNumber": mfa_ser_num}) rtn.append({ "SerialNumber": mfa_ser_num, "Status": iam_cli.post("deactivate_mfa_device", **params) }) results = rp.done(True, rtn) # Produce a FunctionResult with the results yield FunctionResult(results) except Exception as aws_err: LOG.exception( "ERROR with Exception '%s' in Resilient Function for AWS IAM.", aws_err.__repr__()) yield FunctionError()
def _fn_sep_scan_endpoints_function(self, event, *args, **kwargs): """Function: Run a Evidence of Compromise (EOC) scan on Symantec Endpoint Protection endpoints.""" try: params = transform_kwargs(kwargs) if kwargs else {} # Instantiate result payload object. rp = ResultPayload(CONFIG_DATA_SECTION, **kwargs) # Get the function parameters: sep_group_ids = kwargs.get("sep_group_ids") # text sep_computer_ids = kwargs.get("sep_computer_ids") # text sep_scan_type = self.get_select_param(kwargs.get( "sep_scan_type")) # select, values: "QUICK_SCAN", "FULL_SCAN" sep_file_name = kwargs.get("sep_file_path") # text sep_sha256 = kwargs.get("sep_sha256") # text sep_sha1 = kwargs.get("sep_sha1") # text sep_md5 = kwargs.get("sep_md5") # text sep_description = kwargs.get("sep_description") # text sep_scan_action = self.get_select_param( kwargs.get( "sep_scan_action")) # select, values: "scan", "remediate" LOG.info("sep_group_ids: %s", sep_group_ids) LOG.info("sep_computer_ids: %s", sep_computer_ids) LOG.info("sep_scan_type: %s", sep_scan_type) LOG.info("sep_file_path: %s", sep_file_name) LOG.info("sep_sha256: %s", sep_sha256) LOG.info("sep_sha1: %s", sep_sha1) LOG.info("sep_md5: %s", sep_md5) LOG.info("sep_description: %s", sep_description) LOG.info("sep_scan_action: %s", sep_scan_action) validate_fields(["sep_scan_type", "sep_description"], kwargs) yield StatusMessage( "Running Symantec SEP Scan Endpoints command...") sep = Sepclient(self.options, params) rtn = sep.scan_endpoints(**params) results = rp.done(True, rtn) yield StatusMessage( "Returning 'Symantec SEP Scan Endpoints' results") LOG.debug(json.dumps(results["content"])) # Produce a FunctionResult with the results yield FunctionResult(results) except Exception: LOG.exception("Exception in Resilient Function for Symantec SEP.") yield FunctionError()
def _mitre_technique_information_function(self, event, *args, **kwargs): """Function: Get ATT&CK information about MITRE ATT&CK technique""" try: # Get the function parameters: mitre_technique_name = kwargs.get("mitre_technique_name") # text mitre_technique_id = kwargs.get("mitre_technique_id") # text mitre_technique_mitigation_only = kwargs.get("mitre_technique_mitigation_only") # boolean log = logging.getLogger(__name__) log.info("mitre_technique_name: %s", mitre_technique_name) log.info("mitre_technique_id: %s", mitre_technique_id) log.info("mitre_technique_mitigation_only: %s", mitre_technique_mitigation_only) if not mitre_technique_name and not mitre_technique_id: raise ValueError("Neither name nor id is provided for getting technique information.") result_payload = ResultPayload("fn_mitre_integration", mitre_technique_name=mitre_technique_name, mitre_technique_id=mitre_technique_id, mitre_technique_mitigation_only=mitre_technique_mitigation_only) yield StatusMessage("starting...") yield StatusMessage("querying MITRE STIX TAXII server. It might take several minutes...") mitre_conn = mitre_attack.MitreAttackConnection() techniques = mitre_attack.MitreAttackTechnique.get(mitre_conn, name=mitre_technique_name, id=mitre_technique_id) if not techniques: raise ValueError( "Technique with name/id {}/{} can't be found".format(mitre_technique_name, mitre_technique_id)) techs = [] for technique in techniques: tactics = technique.get_tactic(mitre_conn) tech_dict = technique.dict_form() tech_dict.update( { "tactic": ",".join(x.name for x in tactics) if tactics else None, # there should be 1 tactic per technique "mitre_mitigations": [x.dict_form() for x in technique.get_mitigations(mitre_conn)] } ) techs.append(tech_dict) yield StatusMessage("done...") results = { "mitre_techniques": techs } # Produce a FunctionResult with the results yield FunctionResult(result_payload.done(True, results)) except Exception as e: log.exception(str(e)) yield FunctionError()
def _wiki_lookup_function(self, event, *args, **kwargs): """Function: """ try: validate_fields(["wiki_path"], kwargs) # Get the function parameters: wiki_path = kwargs.get("wiki_path") # text wiki_search_term = kwargs.get("wiki_search_term") # text log = logging.getLogger(__name__) log.info("wiki_path: %s", wiki_path) log.info(u"wiki_search_term: %s", wiki_search_term) # Setup Resilient rest client helper = WikiHelper(self.rest_client()) rp = ResultPayload(PACKAGE_NAME, **kwargs) # separate the target wiki from it's parent path wiki_list = wiki_path.strip().split("/") wiki_title = wiki_list.pop() reason = matching_wiki_content = None content = helper.get_wiki_contents(wiki_title, wiki_list) if not content: reason = u"Can't find the wiki with path: '{}'".format( wiki_path) yield StatusMessage(reason) else: log.debug(content) matching_wiki_content = do_lookup(wiki_search_term, content) # Handle no matches if not matching_wiki_content: reason = u"No matches found for {} in the Wiki {}".format( wiki_search_term, wiki_title) yield StatusMessage(reason) yield StatusMessage("Found {} matching entries".format( len(matching_wiki_content))) results = rp.done(not bool(reason), matching_wiki_content, reason=reason) # add the title of the wiki page results['title'] = content.get('title') if content else None # Produce a FunctionResult with the results yield FunctionResult(results) except Exception: yield FunctionError()
def _mitre_groups_using_technique_function(self, event, *args, **kwargs): """Function: Get a list of groups that are using the given technique(s).""" try: mitre_technique_name = kwargs.get("mitre_technique_name") # text mitre_technique_id = kwargs.get("mitre_technique_id") # text log = logging.getLogger(__name__) log.info("mitre_technique_name: %s", mitre_technique_name) log.info("mitre_technique_id: %s", mitre_technique_id) result_payload = ResultPayload( "fn_mitre_integration", mitre_technique_name=mitre_technique_name, mitre_technique_id=mitre_technique_id) if not mitre_technique_id and not mitre_technique_name: raise ValueError( "At least one of the inputs(mitre_technique_name or mitre_technique_id) " "should be provided.") yield StatusMessage("Getting technique information...") mitre_conn = mitre_attack.MitreAttackConnection() techniques = mitre_attack_utils.get_multiple_techniques( mitre_conn, mitre_technique_ids=mitre_technique_id, mitre_technique_names=mitre_technique_name) yield StatusMessage("Getting group information...") groups = [] for technique in techniques: groups.extend( mitre_attack.MitreAttackGroup.get_by_technique( mitre_conn, technique)) if len(groups) == 0: yield StatusMessage( "No groups were found using any of the given techniques. Done." ) else: yield StatusMessage("Done. Returning results.") groups = [x.dict_form() for x in groups] # prepare the data for viewing results = {"mitre_groups": groups} # Produce a FunctionResult with the results yield FunctionResult(result_payload.done(True, results)) except Exception as e: log.exception(str(e)) yield FunctionError()
def _have_i_been_pwned_get_pastes_function(self, event, *args, **kwargs): """Function: Get all pastes of an email account from Have I Been Pwned.""" try: yield StatusMessage("starting...") HAVE_I_BEEN_PWNED_PASTES_URL = "https://haveibeenpwned.com/api/v3/pasteaccount/" result_payload = ResultPayload("hibp", **kwargs) # Get the function parameters: email_address = kwargs.get("email_address") # text log = logging.getLogger(__name__) if email_address is not None: log.info("email_address: %s", email_address) else: raise ValueError("email_address is required to run this function") hibp_api_key = self.get_config_option("hibp_api_key") headers={ 'User-Agent': 'Resilient HIBP/2.0', 'hibp-api-key': hibp_api_key } breach_url = "{0}{1}".format(HAVE_I_BEEN_PWNED_PASTES_URL, email_address) pastes_response = requests.get(breach_url, headers=headers,proxies=self.PROXIES) pastes = None # Good response if pastes_response.status_code == 200: pastes = pastes_response.json() # 404 is returned when an email was not found elif pastes_response.status_code == 404: yield StatusMessage("No pastes found on email address: {}".format(email_address)) pastes = None # Rate limit was hit, wait 2 seconds and try again elif pastes_response.status_code == 429: time.sleep(2) else: log.warn("Have I Been Pwned returned " + str(pastes_response.status_code) + " unexpected status code") yield FunctionError("Have I Been Pwned returned " + str(pastes_response.status_code) + " status code") results = { "Pastes": pastes } yield StatusMessage("Lookup complete") # Produce a FunctionResult with the results yield FunctionResult(result_payload.done(True, results)) except Exception as e: yield FunctionError(e)
def _icdx_get_archive_list_function(self, event, *args, **kwargs): """Function: The Get Archive List API is used to return a list of archives in the ICDx system. The response is an unsorted list of archive metadata objects which can then be searched by a user.""" log = logging.getLogger(__name__) try: rc = ResultPayload(ICDX_SECTION, **kwargs) 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: raise FunctionError( "Encountered error while initialising the AMQP Client") # Prepare request payload request = {"id": GET_ARCHIVE_LIST_CODE} # Make the call to ICDx and get a handle on any results archives, status = amqp_client.call(json.dumps(request)) yield StatusMessage( "ICDX call complete with status: {}".format(status)) # If status code in the message header is 200 we have results, 204 is empty response. results = rc.done( success=False if status != 200 else True, content={ "archives": (json.loads(archives) if archives is not None else None) }) results.update({ "archives": (json.loads(archives) if archives is not None else None) }) # Produce a FunctionResult with the results yield FunctionResult(results) log.info("Complete") except Exception: yield FunctionError()
def _exchange_online_get_message_function(self, event, *args, **kwargs): """Function: This function returns the contents of an Exchange Online message.""" try: # Initialize the results payload rp = ResultPayload(CONFIG_DATA_SECTION, **kwargs) # Validate fields validate_fields(['exo_email_address', 'exo_messages_id'], kwargs) # Get the function parameters: email_address = kwargs.get("exo_email_address") # text message_id = kwargs.get("exo_messages_id") # text LOG.info(u"exo_email_address: %s", email_address) LOG.info(u"exo_messages_id: %s", message_id) yield StatusMessage(u"Starting get message 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"), self.options.get("max_retries_total", MAX_RETRIES_TOTAL), self.options.get("max_retries_backoff_factor", MAX_RETRIES_BACKOFF_FACTOR), self.options.get("max_batched_requests", MAX_BATCHED_REQUESTS), RequestsCommon(self.opts, self.options).get_proxies()) # Call MS Graph API to get the user profile response = MS_graph_helper.get_message(email_address, message_id) response_json = response.json() results = rp.done(True, response_json) # Add pretty printed string for easier to read output text in note. pretty_string = json.dumps(response_json, ensure_ascii=False, sort_keys=True, indent=4, separators=(',', ': ')) results['pretty_string'] = pretty_string yield StatusMessage(u"Returning results for get message for email address: {}".format(email_address)) LOG.debug(json.dumps(pretty_string)) # Produce a FunctionResult with the results yield FunctionResult(results) except Exception as err: LOG.error(err) yield FunctionError(err)
def _sentinel_get_incident_entities_function(self, event, *args, **kwargs): """Function: Get the Entities associated with a Sentinel Incident""" try: validate_fields(["sentinel_profile", "sentinel_incident_id"], kwargs) yield StatusMessage("Starting 'sentinel_get_incident_entities'") rc = ResultPayload(PACKAGE_NAME, **kwargs) # Get the function parameters: sentinel_incident_id = kwargs.get("sentinel_incident_id") # text sentinel_profile = kwargs.get("sentinel_profile") # text log = logging.getLogger(__name__) log.info("sentinel_incident_id: %s", sentinel_incident_id) log.info("sentinel_profile: %s", sentinel_profile) sentinel_api = SentinelAPI(self.options['tenant_id'], self.options['client_id'], self.options['app_secret'], self.opts, self.options) profile_data = self.sentinel_profiles.get_profile(sentinel_profile) # read all entities associated with a Sentinel incident result, status, reason = sentinel_api.get_incident_entities( profile_data, sentinel_incident_id) log.debug(result) # iterate over the alerts and get all the entities entities = {} if status: for alert in result['value']: log.debug("Alert: %s", alert['name']) entity_result, entity_status, entity_reason = \ sentinel_api.get_incident_alert_entities(alert['properties']['relatedResourceId']) # organize entities using the key of the alert_id if entity_status: entities[ alert['name']] = entity_result['value']['entities'] else: reason = entity_reason yield StatusMessage("Finished 'sentinel_get_incident_entities'") results = rc.done(status, entities, reason=reason) # Produce a FunctionResult with the results yield FunctionResult(results) except Exception: yield FunctionError()
def _fn_ansible_function(self, event, *args, **kwargs): """Function: Ansible is simple IT engine for automation, it is designed for manage many systems, rather than just one at a time.""" try: # Get the function parameters: ansible_playbook = kwargs.get("ansible_playbook_name") # text ansible_parameters = kwargs.get("ansible_parameters") # text log = logging.getLogger(__name__) log.info(u"playbook_name: %s", ansible_playbook) log.info(u"ansible_parameters: %s", ansible_parameters) # use the workflow_id to identify the ansible process workflow_id = event.message['workflow_instance'][ 'workflow_instance_id'] result = ResultPayload(SECTION_HDR, **kwargs) # Prepare playbook vars extra_vars = {} if ansible_parameters: for item in ansible_parameters.split(u";"): if len(item.strip(u' ')) > 0: k, v = item.split(u"=") extra_vars[k.strip(u' ')] = v.strip(u' ') # prepare playbook arg playbook_extension = ansible_playbook.split(u".")[-1] if playbook_extension != "yml": ansible_playbook = u"{}.yml".format( ansible_playbook.strip(u' ')) playbook_results = run_playbook(id=workflow_id, private_data_dir=self.runner_dir, artifact_dir=self.artifact_dir, playbook_name=ansible_playbook, playbook_args=extra_vars) result_payload = result.done(True, playbook_results) # Produce a FunctionResult with the results yield FunctionResult(result_payload) except Exception: yield FunctionError() finally: log.info( "Running cleanup_artifact_dir for {} previous runs".format( self.artifact_rentention_num)) cleanup_artifact_dir( self.artifact_dir if self.artifact_dir else self.runner_dir, self.artifact_rentention_num)
def _remove_a_scheduled_job(self, event, *args, **kwargs): try: scheduler_label = kwargs.get("scheduler_label") # text log.info(u"scheduler_label: %s", scheduler_label) validate_fields(["scheduler_label"], kwargs) rc = ResultPayload(SECTION_SCHEDULER, **kwargs) scheduler = ResilientScheduler.get_scheduler() try: scheduler.remove_job(scheduler_label) log.info(u"Rule '{}' deleted".format(scheduler_label)) yield StatusMessage("Scheduled rule removed") result = rc.done(True, None) except JobLookupError: yield StatusMessage("Scheduled rule not found") result = rc.done(False, None) yield FunctionResult(result) except Exception: yield FunctionError()
def _qradar_reference_table_update_item_function(self, event, *args, **kwargs): """Function: Update an item in a given QRadar reference table""" try: # Get the wf_instance_id of the workflow this Function was called in, if not found return a backup string wf_instance_id = event.message.get("workflow_instance", {}).get("workflow_instance_id", "no instance id found") yield StatusMessage("Starting 'qradar_reference_table_update_item' running in workflow '{0}'".format(wf_instance_id)) rp = ResultPayload(PACKAGE_NAME, **kwargs) # Get the function parameters: qradar_reference_table_name = kwargs.get("qradar_reference_table_name") # text qradar_reference_table_item_value = kwargs.get("qradar_reference_table_item_value") # text qradar_reference_table_item_inner_key = kwargs.get("qradar_reference_table_item_inner_key") # text qradar_reference_table_item_outer_key = kwargs.get("qradar_reference_table_item_outer_key") # text LOG.info("qradar_reference_table_name: %s", qradar_reference_table_name) LOG.info("qradar_reference_table_item_value: %s", qradar_reference_table_item_value) LOG.info("qradar_reference_table_item_inner_key: %s", qradar_reference_table_item_inner_key) LOG.info("qradar_reference_table_item_outer_key: %s", qradar_reference_table_item_outer_key) qradar_verify_cert = True if "verify_cert" in self.options and self.options["verify_cert"].lower() == "false": qradar_verify_cert = False LOG.debug("Connecting to QRadar instance @ {}".format(self.options["host"])) 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.update_ref_table_element(qradar_reference_table_name, qradar_reference_table_item_inner_key, qradar_reference_table_item_outer_key, qradar_reference_table_item_value) status_code = bool(result.get('status_code', False) < 300) reason = None if status_code else result['content'].get('message', None) results = rp.done(success=status_code, content=result, reason=reason) yield StatusMessage("Call made to QRadar and response code returned: {}".format(result.get('status_code', 'no response code found'))) yield StatusMessage("Finished 'qradar_reference_table_update_item' that was running in workflow '{0}'".format(wf_instance_id)) # Produce a FunctionResult with the results yield FunctionResult(results) except Exception: yield FunctionError()
def _fn_api_void_request_function(self, event, *args, **kwargs): """Function: Use the APIVoid API to make an APIVoid API request. Results are written to an incident note.""" try: yield StatusMessage("APIVoid API request function started...") log = logging.getLogger(__name__) rp = ResultPayload("fn_api_void", **kwargs) # Add support for Requests Common rc = RequestsCommon(self.opts, self.options) # Get app.config parameters apivoid_base_url = self.options.get("apivoid_base_url") apivoid_sub_url = self.options.get("apivoid_sub_url") apivoid_api_key = self.options.get("apivoid_api_key") # Get the function parameters: validate_fields([ "api_void_request_type", "api_void_artifact_type", "api_void_artifact_value" ], kwargs) api_void_request_type = self.get_select_param( kwargs.get("api_void_request_type")) # select api_void_artifact_type = kwargs.get( "api_void_artifact_type") # text api_void_artifact_value = kwargs.get( "api_void_artifact_value") # text log.info("api_void_artifact_value: %s", api_void_artifact_value) yield StatusMessage(u"Getting Intelligence for {0}: {1}".format( api_void_artifact_type, api_void_artifact_value)) # Execute APIVoid API call response = make_apivoid_api_call(base_url=apivoid_base_url, sub_url=apivoid_sub_url, query_type=api_void_request_type, value=api_void_artifact_value, api_key=apivoid_api_key, rc=rc) response_json = response.json() yield StatusMessage( "APIVoid request function completed successfully...") results = rp.done(True, response_json) # Produce a FunctionResult with the results yield FunctionResult(results) except Exception as e: yield FunctionError(e)
def _fn_sep_assign_fingerprint_list_to_group_function( self, event, *args, **kwargs): """Function: Assign a fingerprint list to a group for lock-down.""" try: params = transform_kwargs(kwargs) if kwargs else {} # Instantiate result payload object rp = ResultPayload(CONFIG_DATA_SECTION, **kwargs) # Get the function parameters: sep_fingerprintlist_id = kwargs.get( "sep_fingerprintlist_id") # text sep_groupid = kwargs.get("sep_groupid") # text LOG.info("sep_fingerprintlist_id: %s", sep_fingerprintlist_id) LOG.info("sep_groupid: %s", sep_groupid) validate_fields(["sep_fingerprintlist_id", "sep_groupid"], kwargs) yield StatusMessage( "Running Symantec SEP Assign Fingerprint List to Group for Lock-down action ..." ) sep = Sepclient(self.options, params) rtn = sep.assign_fingerprint_list_to_group(**params) results = rp.done(True, rtn) if "errorCode" in rtn and int(rtn["errorCode"]) == 400: # If this error was trapped user probably tried to get an invalid fingerprint list. yield StatusMessage( "Symantec SEP Assign Fingerprint List to Group for Lock-down: Got a 400 error " "while attempting to assign a fingerprint list for fingerprint id '{0}' because of a " "possible invalid or deleted fingerprintlist id.".format( sep_fingerprintlist_id)) else: yield StatusMessage( "Returning 'Symantec SEP Assign Fingerprint List to Group for Lock-down' results for " "fingerprintlist_id '{0}' and groupid '{1}'".format( sep_fingerprintlist_id, sep_groupid)) LOG.debug(json.dumps(results["content"])) # Produce a FunctionResult with the results yield FunctionResult(results) except Exception: LOG.exception("Exception in Resilient Function for Symantec SEP.") yield FunctionError()
def _func_aws_guardduty_archive_finding_function(self, event, *args, **kwargs): """Function: Archive an AWS GuardDuty finding. :param aws_gd_finding_id: An AWS GuardDuty finding ID. :param aws_gd_detector_id: An AWS GuardDuty detector ID. :param aws_gd_region: An AWS GuardDuty region ID. """ 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 'func_aws_guardduty_archive_finding' running in workflow '{0}'" .format(wf_instance_id)) rp = ResultPayload(PACKAGE_NAME, **kwargs) # Get the function parameters: aws_gd_region = kwargs.get("aws_gd_region") # text aws_gd_finding_id = kwargs.get("aws_gd_finding_id") # text aws_gd_detector_id = kwargs.get("aws_gd_detector_id") # text validate_fields(REQUIRED_FIELDS, kwargs) log = logging.getLogger(__name__) log.info("aws_gd_region: %s", aws_gd_region) log.info("aws_gd_finding_id: %s", aws_gd_finding_id) log.info("aws_gd_detector_id: %s", aws_gd_detector_id) # Instantiate AWS GuardDuty client object. aws_gd = AwsGdClient(self.opts, self.options, region=aws_gd_region) result = aws_gd.post("archive_findings", DetectorId=aws_gd_detector_id, FindingIds=[aws_gd_finding_id]) results = rp.done(True, result) yield StatusMessage( "Finished 'func_aws_guardduty_archive_finding' that was running in workflow '{0}'" .format(wf_instance_id)) # Produce a FunctionResult with the results yield FunctionResult(results) except Exception: yield FunctionError()
def _fn_aws_iam_delete_ssh_keys_function(self, event, *args, **kwargs): """Function: Delete Secure Shell (SSH) public keys associated with the specified IAM user. param aws_iam_user_name: An IAM user name. aws_iam_ssh_key_ids: A comma separated list of SSH key ids credential ids. """ try: params = transform_kwargs(kwargs) if kwargs else {} # Instantiate result payload object rp = ResultPayload(CONFIG_DATA_SECTION, **kwargs) aws_iam_user_name = kwargs.get("aws_iam_user_name") # text aws_iam_ssh_key_ids = kwargs.get("aws_iam_ssh_key_ids") # text LOG.info("aws_iam_user_name: %s", aws_iam_user_name) LOG.info("aws_iam_ssh_key_ids: %s", aws_iam_ssh_key_ids) validate_fields(["aws_iam_user_name", "aws_iam_ssh_key_ids"], kwargs) iam_cli = AwsIamClient(self.options) # Delete 'SshKeyIds' parameter from params. if "SshKeyIds" in params: del params["SshKeyIds"] rtn = [] # Iterate over SSH public key ids in the comma separated list in parameter 'aws_iam_ssh_key_ids'. # Add each in turn to the 'params' dict then attempt to delete each key for the user in parameter # 'aws_iam_user_name'. Include the status of each attempt in the returned result. for ssh_key_id in re.split(r"\s*,\s*", aws_iam_ssh_key_ids): params.update({"SSHPublicKeyId": ssh_key_id}) rtn.append({ "SSHPublicKeyId": ssh_key_id, "Status": iam_cli.post("delete_ssh_public_key", **params) }) results = rp.done(True, rtn) # Produce a FunctionResult with the results yield FunctionResult(results) except Exception as aws_err: LOG.exception( "ERROR with Exception '%s' in Resilient Function for AWS IAM.", aws_err.__repr__()) yield FunctionError()
def _ansible_tower_list_job_templates_function(self, event, *args, **kwargs): """Function: Run an ansible module outside of the job template""" try: validate_fields(("url"), self.options) # validate key app.config settings # Get the function parameters: tower_hosts = kwargs.get("tower_hosts") # text tower_module = self.get_select_param(kwargs.get("tower_module")) # text tower_arguments = kwargs.get("tower_arguments") # text tower_inventory = kwargs.get("tower_inventory") # number tower_credential = kwargs.get("tower_credential") # number log = logging.getLogger(__name__) log.info("tower_hosts: %s", tower_hosts) log.info("tower_module: %s", tower_module) log.info("tower_arguments: %s", tower_arguments) log.info("tower_inventory: %s", tower_inventory) log.info("tower_credential: %s", tower_credential) result = ResultPayload(SECTION_HDR, **kwargs) rc = RequestsCommon(self.opts, self.options) # PUT YOUR FUNCTION IMPLEMENTATION CODE HERE yield StatusMessage("starting...") url = "/".join((clean_url(self.options['url']), TOWER_API_BASE, AD_HOC_URL)) # common basic_auth, cafile = get_common_request_items(self.options) arguments = { "module_name": tower_module, "limit": tower_hosts, "module_args": tower_arguments, "inventory": tower_inventory, "credential": tower_credential } rc = RequestsCommon(self.opts, self.options) results = rc.execute_call_v2("post", url, auth=basic_auth, json=arguments, headers=JSON_HEADERS, verify=cafile) result_payload = result.done(True, results.json()) yield StatusMessage("done...") # Produce a FunctionResult with the results yield FunctionResult(result_payload) except Exception: yield FunctionError()
def _netwitness_query_function(self, event, *args, **kwargs): """Function: Queries NetWitness and returns back a list of session \ IDs based on the provided query""" try: yield StatusMessage("Querying NetWitness...") # Get the function parameters: nw_query = self.get_textarea_param( kwargs.get("nw_query")) # textarea nw_results_size = str(kwargs.get("nw_results_size", '')) # number # Fail if query is not set if len(nw_query) < 1: raise FunctionError( "nw_query must be set in order to run this function.") # Initialize resilient_lib objects results_payload = ResultPayload("fn_rsa_netwitness", **{"nw_query": nw_query, \ "nw_results_size": nw_results_size}) req_common = RequestsCommon(self.opts) log.info("nw_query: %s", nw_query) log.info("nw_results_size: %s", nw_results_size) # Query Netwitness nw_query_results = query_netwitness( self.options.get("nw_packet_server_url"), self.options.get("nw_packet_server_user"), self.options.get("nw_packet_server_password"), self.options.get("nw_packet_server_verify"), query=nw_query, req_common=req_common, size=nw_results_size) log.debug(nw_query_results) if nw_query_results: StatusMessage("Query results found") else: StatusMessage("No query results found") yield StatusMessage("Complete...") results = results_payload.done(True, \ nw_query_results.json() if nw_query_results else None) log.debug("RESULTS: %s", results) # Produce a FunctionResult with the results yield FunctionResult(results) except Exception as error: yield FunctionError(error)
def _fn_get_wiki_contents_function(self, event, *args, **kwargs): """Function: None""" try: validate_fields(["wiki_path"], kwargs) # 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 'fn_get_wiki_contents' running in workflow '{0}'".format(wf_instance_id)) # Get the function parameters: wiki_contents_as_json = str_to_bool( kwargs.get("wiki_contents_as_json", "False")) # boolean wiki_path = kwargs.get("wiki_path") # text log = logging.getLogger(__name__) log.info("wiki_contents_as_json: %s", wiki_contents_as_json) log.info(u"wiki_path: %s", wiki_path) ############################################## # PUT YOUR FUNCTION IMPLEMENTATION CODE HERE # ############################################## rp = ResultPayload(PACKAGE_NAME, **kwargs) helper = WikiHelper(self.rest_client()) # separate the target wiki from it's parent path wiki_list = wiki_path.strip().split("/") wiki_title = wiki_list.pop() # find the wiki page content = helper.get_wiki_contents(wiki_title, wiki_list) log.debug(content) content_text = reason = None if content: content_text = content['text'] if wiki_contents_as_json: content['json'] = json.loads(content_text.replace( '\n', '')) else: reason = u"Unable to find wiki by path: {}".format(wiki_path) yield StatusMessage(reason) results = rp.done(not bool(reason), content, reason=reason) # add the title of the wiki page results['title'] = content.get('title') if content else None # Produce a FunctionResult with the results yield FunctionResult(results) except Exception: yield FunctionError()
def _funct_zia_get_blocklist_function(self, event, *args, **kwargs): """Function: Get a list of block-listed URLs. """ try: rp = ResultPayload(PACKAGE_NAME, **kwargs) # 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 '{0}' running in workflow '{1}'".format( FN_NAME, wf_instance_id)) # Get and validate required function inputs: fn_inputs = validate_fields([], kwargs) LOG.info("'{0}' inputs: %s", fn_inputs) url_filter_patt = fn_inputs.get("zia_url_filter") if url_filter_patt and not is_regex(url_filter_patt): raise ValueError( "The url query filter '{}' does not have a valid regular expression." .format("zia_url_filter")) yield StatusMessage( "Validations complete. Starting business logic") ziacli = ZiaClient(self.opts, self.fn_options) result = ziacli.get_blocklist_urls(url_filter=url_filter_patt) yield StatusMessage( "Finished '{0}' that was running in workflow '{1}'".format( FN_NAME, wf_instance_id)) results = rp.done(True, result) LOG.info("'%s' complete", FN_NAME) yield StatusMessage( "Returning results for function '{}' with parameters '{}'.". format( FN_NAME, ", ".join("{!s}={!r}".format(k, v) for (k, v) in fn_inputs.items()))) # Produce a FunctionResult with the results yield FunctionResult(results) except Exception as e: yield FunctionError(e)