CheckEmailAuthenticity

Checks the authenticity of an email based on the email's SPF, DMARC, and DKIM.

python · Phishing

Details

IDCheckEmailAuthenticity
Languagepython
From Version5.0.0
Docker Imagedemisto/python3:3.12.13.10404775
Tagsphishing ews email

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
import demistomock as demisto
from CheckEmailAuthenticity import get_authentication_value, get_spf, main

MOCK_HEADERS = [
    {"name": "Message-ID", "value": "test_message_id"},
    {
        "name": "received-spf",
        "value": "Pass (test.com: domain of test.com designates 8.8.8.8 as permitted sender)"
        "receiver=test.com; client-ip=8.8.8.8; helo=test.com;",
    },
    {
        "name": "Authentication-Results",
        "value": "spf=pass (sender IP is 8.8.8.8) smtp.mailfrom=test.com; dkim=fail (body hash did not verify) "
        "header.d=test.com; dmarc=pass action=none header.from=test.com;compauth=pass reason=100",
    },
]

MOCK_HEADERS_DIFFERENT_AUTH_HEADER = [
    {"name": "Message-ID", "value": "test_message_id"},
    {
        "name": "received-spf",
        "value": "Pass (test.com: domain of test.com designates 8.8.8.8 as permitted sender)"
        "receiver=test.com; client-ip=8.8.8.8; helo=test.com;",
    },
    {"name": "Authentication-Results", "value": "mock_different_value"},
]

EMAIL_KEY = (
    "Email(val.Headers.filter(function(header) { return header && header.name === 'Message-ID' && "
    "header.value === 'test_message_id';}))"
)


def test_check_email_auth(mocker):
    mocker.patch.object(demisto, "args", return_value={"headers": MOCK_HEADERS})
    mocker.patch.object(demisto, "results")

    main()

    results = demisto.results.call_args[0]

    # assert (str(results[0]['EntryContext'])) == '3'

    dmarc = results[0]["EntryContext"][f"{EMAIL_KEY}.DMARC"]
    assert dmarc["Validation-Result"] == "pass"
    assert dmarc["Signing-Domain"] == "test.com"

    spf = results[0]["EntryContext"][f"{EMAIL_KEY}.SPF"]
    assert spf["Validation-Result"] == "pass"
    assert spf["Sender-IP"] == "8.8.8.8"

    dkim = results[0]["EntryContext"][f"{EMAIL_KEY}.DKIM"]
    assert dkim["Validation-Result"] == "fail"
    assert dkim["Reason"] == "body hash did not verify"

    # AuthenticityCheck fails because DKIM failed
    assert results[0]["EntryContext"][f"{EMAIL_KEY}.AuthenticityCheck"] == "Fail"


def test_get_authentication_value():
    """
    Given:
        an authenticator header that is not a part of the given headers array.
    When:
        there is an intermediate server which changes the email and holds the original value of the header in a
        different header.
    Then:
        override the given authenticator headers in the headers array and use the original one.
    """

    original_authentication_header_included_in_headers = "Authentication-Results"
    original_authentication_header_not_included_in_headers = "Authentication-Results-Not-Included"

    assert (
        get_authentication_value(MOCK_HEADERS_DIFFERENT_AUTH_HEADER, original_authentication_header_not_included_in_headers)
        == "mock_different_value"
    )
    assert (
        get_authentication_value(MOCK_HEADERS, original_authentication_header_included_in_headers)
        == "spf=pass (sender IP is 8.8.8.8) smtp.mailfrom=test.com; dkim=fail (body hash did not verify) "
        "header.d=test.com; dmarc=pass action=none header.from=test.com;compauth=pass reason=100"
    )


def test_get_spf_formats():
    spf_with_parentheses = "Pass (test.com: domain of test.com designates 8.8.8.8 as permitted sender)"
    spf_without_parentheses = "Pass test.com: domain of test.com designates 8.8.8.8 as permitted sender"

    spf_data = get_spf(auth=None, spf=spf_with_parentheses)
    assert spf_data["Validation-Result"] == "pass"
    assert spf_data["Sender-IP"] == "8.8.8.8"

    spf_data = get_spf(auth=None, spf=spf_without_parentheses)
    assert spf_data["Validation-Result"] == "pass"
    assert spf_data["Sender-IP"] == "8.8.8.8"