def build_attachments(self, attachment_names, incident_id, resilient_client): """ Get attachment data from Resilient and format it for Exchange Online. :param attachment_name: name of the attachment in Resilient :param incident_id: ID of Resilient incident :param resilient_client: Resilient REST API client :return: formatted attachment data """ attachments = [] failed_attached = [] attachment_names = [item.strip() for item in attachment_names.split(",")] for attachment_name in attachment_names: base64content, attachment_id = get_incident_file_attachment(resilient_client, incident_id, attachment_name) if not attachment_id: failed_attached.append(attachment_name) LOG.info(u"Failed to attach %s. No matching incident attachment found.", attachment_name) continue contentType = get_file_attachment_metadata(resilient_client, incident_id, attachment_id=attachment_id)["content_type"] attachment = { "@odata.type": "#microsoft.graph.fileAttachment", "name": attachment_name, "contentType": contentType, "contentBytes": base64content } attachments.append(attachment) LOG.info(u"Successfully attached %s", attachment_name) return attachments , failed_attached
def _attachment_to_base64_function(self, event, *args, **kwargs): """Function: Produce base64 content of a file attachment.""" 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 artifact_id = kwargs.get("artifact_id") # number log.info("incident_id: %s", incident_id) log.info("task_id: %s", task_id) log.info("attachment_id: %s", attachment_id) log.info("artifact_id: %s", artifact_id) if incident_id is None: raise FunctionError("Error: incident_id must be specified.") elif attachment_id is None and artifact_id is None: raise FunctionError( "Error: attachment_id or artifact_id must be specified.") else: yield StatusMessage("> Function inputs OK") yield StatusMessage("> Reading attachment...") client = self.rest_client() data = get_file_attachment(client, incident_id, artifact_id=artifact_id, task_id=task_id, attachment_id=attachment_id) metadata = get_file_attachment_metadata( client, incident_id, artifact_id=artifact_id, task_id=task_id, attachment_id=attachment_id) results = { "filename": metadata["name"], "content_type": metadata["content_type"], "size": metadata["size"], "created": metadata["created"], "content": b_to_s(base64.b64encode(data)), } yield StatusMessage("> Complete...") # Produce a FunctionResult with the return value log.debug(json.dumps(results, indent=2)) yield FunctionResult(results) except Exception: yield FunctionError()
def _email_parse_function(self, event, *args, **kwargs): """Function: Extract message headers and body parts from an email message (.eml or .msg). Any attachments found are added to the Incident as Artifacts if 'utilities_parse_email_attachments' is set to True""" try: log = logging.getLogger(__name__) # Set variables parsed_email = path_tmp_file = path_tmp_dir = reason = results = None # Get the function inputs: fn_inputs = validate_fields(["incident_id"], kwargs) # Instansiate ResultPayload rp = ResultPayload(CONFIG_DATA_SECTION, **kwargs) # If its just base64content as input, use parse_from_string if fn_inputs.get("base64content"): yield StatusMessage("Processing provided base64content") parsed_email = mailparser.parse_from_string( b_to_s(base64.b64decode(fn_inputs.get("base64content")))) yield StatusMessage("Provided base64content processed") else: # Validate that either: (incident_id AND attachment_id OR artifact_id) OR (task_id AND attachment_id) is defined if not (fn_inputs.get("incident_id") and (fn_inputs.get("attachment_id") or fn_inputs.get("artifact_id"))) and \ not (fn_inputs.get("task_id") and fn_inputs.get("attachment_id")): raise FunctionError( "You must define either: (incident_id AND attachment_id OR artifact_id) OR (task_id AND attachment_id)" ) # Instansiate new Resilient API object res_client = self.rest_client() # Get attachment metadata attachment_metadata = get_file_attachment_metadata( res_client=res_client, incident_id=fn_inputs.get("incident_id"), artifact_id=fn_inputs.get("artifact_id"), task_id=fn_inputs.get("task_id"), attachment_id=fn_inputs.get("attachment_id")) # Get attachment content attachment_contents = get_file_attachment( res_client=res_client, incident_id=fn_inputs.get("incident_id"), artifact_id=fn_inputs.get("artifact_id"), task_id=fn_inputs.get("task_id"), attachment_id=fn_inputs.get("attachment_id")) # Write the attachment_contents to a temp file path_tmp_file, path_tmp_dir = write_to_tmp_file( attachment_contents, tmp_file_name=attachment_metadata.get("name")) # Get the file_extension file_extension = os.path.splitext(path_tmp_file)[1] if file_extension == ".msg": yield StatusMessage("Processing MSG File") try: parsed_email = mailparser.parse_from_file_msg( path_tmp_file) yield StatusMessage("MSG File processed") except Exception as err: reason = u"Could not parse {0} MSG File".format( attachment_metadata.get("name")) yield StatusMessage(reason) results = rp.done(success=False, content=None, reason=reason) log.error(err) else: yield StatusMessage("Processing Raw Email File") try: parsed_email = mailparser.parse_from_file( path_tmp_file) yield StatusMessage("Raw Email File processed") except Exception as err: reason = u"Could not parse {0} Email File".format( attachment_metadata.get("name")) yield StatusMessage(reason) results = rp.done(success=False, content=None, reason=reason) log.error(err) if parsed_email is not None: if not parsed_email.mail: reason = u"Raw email in unsupported format. Failed to parse {0}".format( u"provided base64content" if fn_inputs. get("base64content" ) else attachment_metadata.get("name")) yield StatusMessage(reason) results = rp.done(success=False, content=None, reason=reason) else: # Load all parsed email attributes into a Python Dict parsed_email_dict = json.loads(parsed_email.mail_json, encoding="utf-8") parsed_email_dict[ "plain_body"] = parsed_email.text_plain_json parsed_email_dict[ "html_body"] = parsed_email.text_html_json yield StatusMessage("Email parsed") # If the input 'utilities_parse_email_attachments' is true and some attachments were found if fn_inputs.get("utilities_parse_email_attachments" ) and parsed_email_dict.get( "attachments"): yield StatusMessage( "Attachments found in email message") attachments_found = parsed_email_dict.get( "attachments") # Loop attachments found for attachment in attachments_found: yield StatusMessage( u"Attempting to add {0} to Incident: {1}". format(attachment.get("filename"), fn_inputs.get("incident_id"))) # Write the attachment.payload to a temp file path_tmp_file, path_tmp_dir = write_to_tmp_file( data=s_to_b(attachment.get("payload")), tmp_file_name=attachment.get("filename"), path_tmp_dir=path_tmp_dir) artifact_description = u"This email attachment was found in the parsed email message from: '{0}'".format( u"provided base64content" if fn_inputs. get("base64content" ) else attachment_metadata.get("name")) # POST the artifact to Resilient as an 'Email Attachment' Artifact res_client.post_artifact_file( uri=ARTIFACT_URI.format( fn_inputs.get("incident_id")), artifact_type=EMAIL_ATTACHMENT_ARTIFACT_ID, artifact_filepath=path_tmp_file, description=artifact_description, value=attachment.get("filename"), mimetype=attachment.get("mail_content_type")) results = rp.done(True, parsed_email_dict) else: reason = u"Raw email in unsupported format. Failed to parse {0}".format( u"provided base64content" if fn_inputs. get("base64content") else attachment_metadata.get("name")) yield StatusMessage(reason) results = rp.done(success=False, content=None, reason=reason) log.info("Done") yield FunctionResult(results) except Exception: yield FunctionError() finally: # Remove the tmp directory if path_tmp_dir and os.path.isdir(path_tmp_dir): shutil.rmtree(path_tmp_dir)
def _attachment_hash_function(self, event, *args, **kwargs): """Function: Calculate hashes for a file attachment.""" 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 log.info("incident_id: %s", incident_id) log.info("task_id: %s", task_id) log.info("attachment_id: %s", attachment_id) 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.") yield StatusMessage("Reading attachment...") client = self.rest_client() data = get_file_attachment(client, incident_id, task_id=task_id, attachment_id=attachment_id) metadata = get_file_attachment_metadata( client, incident_id, task_id=task_id, attachment_id=attachment_id) results = { "filename": metadata["name"], "content_type": metadata["content_type"], "size": metadata["size"], "created": metadata["created"] } # Hashlib provides a list of all "algorithms_available", but there's duplication, so # use the standard list: ('md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512') # Hashlib in 3.2 and above does not supports hashlib.algorithms_guaranteed this contains a longer # list but will be used instead of hashlib.algorithms if sys.version_info.major >= 3: algorithms = hashlib.algorithms_guaranteed else: algorithms = hashlib.algorithms for algo in algorithms: impl = hashlib.new(algo) impl.update(data) # shake algorithms require a 'length' parameter if algo.startswith("shake_"): length_list = algo.split('_') results[algo] = impl.hexdigest(int(length_list[-1])) else: results[algo] = impl.hexdigest() log.info(u"{} sha1={}".format(metadata["name"], results["sha1"])) # Produce a FunctionResult with the return value log.debug(json.dumps(results)) yield FunctionResult(results) except Exception: yield FunctionError()