CheckEmailAuthenticity
Checks the authenticity of an email based on the email's SPF, DMARC, and DKIM.
python · Phishing
Source
import re import traceback import demistomock as demisto from CommonServerPython import * from CommonServerUserPython import * """HELPER FUNCTIONS""" def get_spf(auth, spf): """ Get SPF validation information :param auth: authentication header value (if exist), contains the validation result and sender ip. :param spf: spf header value (if exist), contains the validation result and sender ip. :return: SPF validation information """ spf_context = {"Validation-Result": "Unspecified", "Sender-IP": "Unspecified", "Reason": "Unspecified"} if auth is None: spf_context["Validation-Result"] = spf.split(" ")[0].lower() sender_ip = re.findall(r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}", spf) else: result = re.search(r"spf=(\w+)", auth) if result is not None: spf_context["Validation-Result"] = result.group(1).lower() sender_ip = re.findall(r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}", auth) if sender_ip: spf_context["Sender-IP"] = sender_ip[0] if spf is not None: if reason := re.findall(r"\((.+)\)", spf): spf_context["Reason"] = reason[0] else: reason = spf.split(" ", 1) spf_context["Reason"] = reason[1] if len(reason) > 1 else "" return spf_context def get_dkim(auth): """ Get DKIM validation information :param auth: authentication header value (if exist), contains the validation result. :return: DKIM validation information """ dkim_context = {"Validation-Result": "Unspecified", "Signing-Domain": "Unspecified"} if auth is not None: result = re.search(r"dkim=(\w+)", auth) if result is not None: dkim_context["Validation-Result"] = result.group(1).lower() reason = re.search(r"dkim=\w+ \((.+?)\)", auth) if reason is not None: dkim_context["Reason"] = reason.group(1) domain = re.findall(r"dkim=[\w\W]+?[=@](\w+\.[^ ]+)", auth) if domain: dkim_context["Signing-Domain"] = domain[0] return dkim_context def get_dmarc(auth): """ Get DMARC validation information :param auth: authentication header value (if exist), contains the validation result and sender ip. :return: DMARC validation information """ dmarc_context = {"Validation-Result": "Unspecified", "Tags": {"Unspecified": "Unspecified"}, "Signing-Domain": "Unspecified"} if auth is not None: result = re.search(r"dmarc=(\w+)", auth) if result is not None: dmarc_context["Validation-Result"] = result.group(1).lower() reason = re.findall(r"dmarc=\w+ \((.+?)\)", auth) if reason: tags = reason[0] tags_data = {} for tag in tags.split(" "): values = tag.split("=") tags_data[values[0]] = values[1] dmarc_context["Tags"] = tags_data domain = re.findall(r"dmarc=.+header.from=([\w-]+\.[^; ]+)", auth) if domain: dmarc_context["Signing-Domain"] = domain[0] return dmarc_context def auth_check(spf_data, dkim_data, dmarc_data, override_dict): spf = spf_data.get("Validation-Result") dmarc = dmarc_data.get("Validation-Result") dkim = dkim_data.get("Validation-Result") if f"spf-{spf}" in override_dict: return override_dict.get(f"spf-{spf}") if f"dkim-{dkim}" in override_dict: return override_dict.get(f"dkim-{dkim}") if f"dmarc-{dmarc}" in override_dict: return override_dict.get(f"dmarc-{dmarc}") if "fail" in [spf, dkim, dmarc]: return "Fail" if spf == "softfail" or dkim == "policy": return "Suspicious" undetermined = [None, "none", "temperror", "permerror"] if dmarc in undetermined or spf in undetermined or dkim in undetermined or dkim == "neutral": return "Undetermined" return "Pass" def get_authentication_value(headers, original_authentication_header): """ Handles the case where the authentication header is given under a different header. This header is represented by the 'original_authentication_header' argument. This can happen when an intermediate server changes the email and holds the original value of the header in a different header. For more info, see issue #46364. Args: headers: The headers dict argument given by the user original_authentication_header: The name of a header which holds the original value of the Authentication-Results header. Returns: The suitable authenticator header. """ header_dict = {str(header.get("name")).lower(): header.get("value") for header in headers if isinstance(header, dict)} if original_authentication_header and original_authentication_header in header_dict: authentication_value = header_dict[original_authentication_header] else: authentication_value = header_dict.get("authentication-results") return authentication_value """MAIN FUNCTION""" def main(): try: args = demisto.args() headers = argToList(demisto.args().get("headers")) original_authentication_header = args.get("original_authentication_header", "").lower() auth = get_authentication_value(headers, original_authentication_header) spf = None message_id = "" # getting override options from user override_dict = {} override_options = ["fail", "suspicious", "undetermined", "pass", "Fail", "Suspicious", "Undetermined", "Pass"] override_fields = { "SPF_override_none": "spf-none", "SPF_override_neutral": "spf-neutral", "SPF_override_pass": "spf-pass", "SPF_override_fail": "spf-fail", "SPF_override_softfail": "spf-softfail", # disable-secrets-detection "SPF_override_temperror": "spf-temperror", "SPF_override_perm": "spf-permerror", "DKIM_override_none": "dkim-none", "DKIM_override_pass": "dkim-pass", "DKIM_override_fail": "dkim-fail", "DKIM_override_policy": "dkim-policy", "DKIM_override_neutral": "dkim-neutral", # disable-secrets-detection "DKIM_override_temperror": "dkim-temperror", "DKIM_override_permerror": "dkim-permerror", "DMARC_override_none": "dmarc-none", "DMARC_override_pass": "dmarc-pass", "DMARC_override_fail": "dmarc-fail", "DMARC_override_temperror": "dmarc-temperror", "DMARC_override_permerror": "dmarc-permerror", } for field, value in override_fields.items(): override = args.get(field) if override in override_options: override_dict[value] = override.lower() else: if override is not None: return_error( f"Invalid override input for argument {field}: got {override}, expected one of {override_options}." ) for header in headers: if isinstance(header, dict): if str(header.get("name")).lower() == "received-spf": spf = header.get("value") if str(header.get("name")).lower() == "message-id": message_id = header.get("value") # type: ignore email_key = ( "Email(val.Headers.filter(function(header) { return header && header.name === 'Message-ID' && " f"header.value === '{message_id}';}}))" ) if not auth and not spf: context = {f"{email_key}.AuthenticityCheck": "undetermined"} return_outputs("No header information was found.", context) sys.exit(0) spf_data = get_spf(auth, spf) dkim_data = get_dkim(auth) dmarc_data = get_dmarc(auth) authenticity = auth_check(spf_data, dkim_data, dmarc_data, override_dict) md = f"Email's authenticity is: **{authenticity}**\n" md += tableToMarkdown("SPF", spf_data, ["Validation-Result", "Reason", "Sender-IP"]) md += tableToMarkdown("DKIM", dkim_data, ["Validation-Result", "Reason", "Signing-Domain"]) md += tableToMarkdown("DMARC", dmarc_data, ["Validation-Result", "Tags", "Signing-Domain"]) ec = { f"{email_key}.SPF": spf_data, f"{email_key}.DMARC": dmarc_data, f"{email_key}.DKIM": dkim_data, f"{email_key}.AuthenticityCheck": authenticity, } return_outputs(md, ec) except Exception as ex: demisto.error(str(ex) + "\n\nTrace:\n" + traceback.format_exc()) return_error(str(ex)) # python2 uses __builtin__ python3 uses builtins if __name__ == "__builtin__" or __name__ == "builtins": main()
README
Checks the authenticity of an email based on the email’s SPF, DMARC, and DKIM.
Script Data
| Name | Description |
|---|---|
| Script Type | python3 |
| Tags | phishing, ews, email |
| Cortex XSOAR Version | 5.0.0 |
Used In
This script is used in the following playbooks and scripts.
- Agari Message Remediation - Agari Phishing Defense
- Email Headers Check - Generic
- Phishing - Generic v3
- Phishing Investigation - Generic v2
- Report Categorization - Cofense Triage v3
Inputs
| Argument Name | Description |
|---|---|
| headers | A list of dictionaries of headers in the form of “Header name”:”Header value”. |
| original_authentication_header | The header that holds the original Authentication-Results header value. This can be used when an intermediate server changes the original email and holds the original header value in a different header. Note - Use this only if you trust the server creating this header. |
| SPF_override_none | Override value for SPF=None. |
| SPF_override_neutral | Override value for SPF=neutral. |
| SPF_override_pass | Override value for SPF=pass. |
| SPF_override_fail | Override value for SPF=fail. |
| SPF_override_softfail | Override value for SPF=softfail. |
| SPF_override_temperror | Override value for SPF=temperror. |
| SPF_override_permerror | Override value for SPF=permerror. |
| DKIM_override_none | Override value for DKIM=none. |
| DKIM_override_pass | Override value for DKIM=pass. |
| DKIM_override_fail | Override value for DKIM=fail. |
| DKIM_override_policy | Override value for DKIM=policy. |
| DKIM_override_neutral | Override value for DKIM=neutral. |
| DKIM_override_temperror | Override value for DKIM=temperror. |
| DKIM_override_permerror | Override value for DKIM=permerror. |
| DMARC_override_none | Override value for DMARC=none. |
| DMARC_override_pass | Override value for DMARC=pass. |
| DMARC_override_fail | Override value for DMARC=fail. |
| DMARC_override_temperror | Override value for DMARC=temperror. |
| DMARC_override_permerror | Override value for DMARC=permerror. |
Outputs
| Path | Description | Type |
|---|---|---|
| Email.SPF.MessageID | SPF ID | String |
| Email.SPF.Validation-Result | Validation Result. Possible values are “None”, “Neutral”, “Pass”, “Fail”, “SoftFail”, “TempError”, and “PermError”. | String |
| Email.SPF.Reason | Reason for the SPF result, which is located in the headers of the email. | String |
| Email.SPF.Sender-IP | Email sender IP address. | String |
| Email.DKIM.Message-ID | DKIM ID. | String |
| Email.DKIM.Reason | DKIM reason (if found). | String |
| Email.DMARC.Message-ID | DMARC ID. | String |
| Email.DMARC.Validation-Result | DMARC reason. Possible values are “None”, “Pass”, “Fail”, “Temperror”, and “Permerror”. | String |
| Email.DMARC.Tags | DMARC Tags (if found) | String |
| Email.DMARC.From-Domain | Sender’s Domain | String |
| Email.DKIM.Signing-Domain | Sender’s Domain | String |
| Email.AuthenticityCheck | Possible values are be: Fail / Suspicious / Undetermined / Pass | Unknown |
| Email.DKIM | DKIM information extracted from the email. | Unknown |
| Email.SPF | SPF information extracted from the email. | Unknown |
| Email.DMARC | DMARC information extracted from the email. | Unknown |
| Email.DKIM.Validation-Result | Validation result. Possible values are “None”, “Pass”, “Fail”, “Policy”, “Neutral”, “Temperror”, and “Permerror”. | Unknown |