1

In order to have mail exchanger MX servers to be able to deliver mail to the primary MX, both SPF and DMARC checks needs to be bypassed when the connection is made from a secondary MX. For SPF this is quite straightforward: if the secondary MX performs the SPF check it can be whitelisted.

For DMARC this gets more complicated. OpenDMARC trusts Authentication-Results headers from external DKIM and SPF checks. By default, it will trust Authentication-Results from the same hostname, and any secondary servers would need to be listed here, too. This is not secret information, as it's usually the very same hostname that is advertised in SMTP banner.

##  TrustedAuthservIDs string
##      default HOSTNAME
##
##  Specifies one or more "authserv-id" values to trust as relaying true
##  upstream DKIM and SPF results.  The default is to use the name of
##  the MTA processing the message.  To specify a list, separate each entry
##  with a comma.  The key word "HOSTNAME" will be replaced by the name of
##  the host running the filter as reported by the gethostname(3) function.
#
# TrustedAuthservIDs HOSTNAME

Therefore, OpenDMARC can be easily bypassed by adding a single forged header:

Authentication-Results: <HOSTNAME>;
        dkim=pass (1024-bit key; unprotected) header.d=example.com header.i=@example.com;

My questions: Is there any configuration parameters in OpenDMARC or other methods to mitigate this kind of attacks? Is this solved better in other DMARC implementations?


In this example attack:

  • The _dmarc.example.com has TXT "v=DMARC1; p=reject; aspf=s; adkim=s;.
  • OpenDMARC + OpenDKIM are configured as milters in Postfix.
  • SPF: check_policy_service unix:private/policy-spf
  • OpenDMARC has default configuration with more strict RejectFailures true.

Knowing the hostname (mx.example.com) and using a working SPF domain for envelope sender it's possible to bypass OpenDMARC completely with no more than:

<-- 220 mx.example.com ESMTP Postfix (Debian/GNU)
--> HELO evil.example.net
<-- 250 mx.example.com
--> MAIL FROM:<someone.from@evil.example.net>
<-- 250 2.1.0 Ok
--> RCPT TO:<user@example.com>
<-- 250 2.1.5 Ok
--> DATA
<-- 354 End data with <CR><LF>.<CR><LF>
--> Authentication-Results: mx.example.com; spf=pass
--> Authentication-Results: mx.example.com;
-->         dkim=pass (1024-bit key; unprotected) header.d=example.com header.i=@example.com;
--> From: <user@example.com>
--> To: <user@example.com>
--> Subject: forged Authentication-Results: mx.example.com;

The MX server supposedly checking all DKIM+SPF+DMARC will hapily queue and append:

Authentication-Results: mx.example.com; spf=pass (mailfrom) smtp.mailfrom=evil.example.net ...
Authentication-Results: mx.example.com; dmarc=pass (p=reject dis=none) header.from=example.com
Received: from smtp.evil.example.net (evil.example.net [198.51.100.80])
    by mx.example.com (Postfix) with SMTP id D4AC64048A
    for <user@example.com>;

The only way to distinguish this from genuine Authentication-Results added by mx.example.com is to manually revise the order of the Receiced headers. I suppose we could see a new wave of "you have been hacked" scams with forged Authentication-Results to cover fake From: <self>.

1

You need to harden some other parts in your configuration:

  1. OpenDMARC can and should check the SPF independently. /etc/opendmarc.conf for every MX:

    SPFIgnoreResults true
    SPFSelfValidate true
    
    • Remember that the external SPF will pass for every valid envelope sender, leaving the forged header the one matching for the From header first.

    • Also, the Authentication-Results from a secondary MX could only be trusted if the SMTP connection originates from that server.

  2. When your secondary MX is configured to perform all the checks, it can be excluded:

    IgnoreHosts /etc/opendmarc-ignorehosts.conf
    

    ...and list the IP addresses for the secondary MX server(s) in that new configuration file.

    This allows you to remove the secondary MX form TrustedAuthservIDs, which was the root cause.

  3. OpenDKIM must be configured to add header even when it already exists. /etc/opendkim.conf:

    AlwaysAddARHeader yes
    

Everything works fine when OpenDMARC counts the topmost matching Authentication-Results only.


If this didn't work, there's also a problem in the OpenDMARC implementation: this seems like a known unfixed bug, ticket #223 Able to maliciously pass DMARC check with multiple DKIM signatures:

Example:

  • opendmarc_policy_store_from_domain marketplace.amazon.de
  • opendmarc_policy_store_spf example.org PASS
  • opendmarc_policy_store_dkim amazon.de FAIL
  • opendmarc_policy_store_dkim example.org PASS

This will incorrectly result in a DMARC pass.

The root cause is that second call to opendmarc_policy_store_dkim doesn't match the exact match, nor a subdomain match and because the previous dkim_outcome stored isn't a pass it skips this, passing into the set_final label and sets the dkim_outcome result to be pass for the previously stored dkim_domain, in this case, amazon.de. Allowing it to pass validation.

As per the docs, opendmarc_policy_store_dkim states: "You may feed this function results from multiple DKIM signatures. This function will select the most successful check from among those that align with the header From: domain."

There's a patch in the comments, but as it's not merged to the source code you can't get it from your favourite distribution, but need to build OpenDMARC on your own.

  • The key was that #1 & #3 must be both configured and IgnoreHosts must be used for secondary MX servers instead of TrustedAuthservIDs. Therefore, you need to add #2 if you have multiple MX. Some of my intermediate tests failed because they tested only #1 or #3 at a time. – Esa Jokinen Mar 6 at 16:02

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.