Vulnerability analysis · Authentication-Results trust boundary

An inbound Authentication-Results header claiming the receiver's own authserv-id is delivered unchanged by the OpenDKIM and OpenDMARC milter chain [OpenDKIM <= 2.11.0 and OpenDMARC ≤ 1.4.2, latest release checked 2026-07-30]

Yongzhe Xu, Virginia Tech  ·  yongzhe@vt.edu  ·  2026-07-31

RFC 8601 section 5 requires the receiver to delete such a header. Seven forged variants were sent through the chain and six arrived intact; only the one carrying a dkim= token was removed.

SoftwareOpenDKIM and OpenDMARC, as a milter chain
VendorThe Trusted Domain Project
Sourcehttps://github.com/trusteddomainproject/OpenDMARC
AffectedOpenDKIM 2.11.0 and OpenDMARC 1.4.2, chained behind Postfix. Newest published release checked on 2026-07-30.
WhereAuthentication-Results handling, the RFC 8601 section 5 requirement to delete forged instances bearing the host's own authentication service identifier
WeaknessCWE-290 (Authentication Bypass by Spoofing) / CWE-345 (Insufficient Verification of Data Authenticity)
Reachable byRemote, unauthenticated. One inbound message with a prepended header.
CVSS v3.1CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:L/A:N (5.8 (Medium))
VerificationReproduced on 2026-07-28 against the current release (run 20260728-120957) with a scripted reproducer, negative controls, and a second independent implementation as the differential oracle. Seven-variant survival table with foreign-identifier and baseline controls, using distinct .com organizations.

1. Overview

The Authentication-Results header carries the receiver's own verdict, so a message arriving with one already attached is a forgery attempt. RFC 8601 section 5 states the rule as a MUST: “any MTA conforming to this specification MUST delete any discovered instance of this header field that claims, by virtue of its authentication service identifier, to have been added within its trust boundary but that did not come directly from another trusted MTA.”

The chain implements half of that. OpenDKIM removes a pre-existing header bearing the trusted identifier, but only when the header carries a dkim= token. OpenDMARC removes nothing at all. A forged header that claims the trusted identifier and omits dkim= therefore passes through the chain untouched and is delivered.

2. Analysis

The test message genuinely fails DMARC. From=victim.com, published policy p=reject; adkim=s; aspf=s, no aligned DKIM, envelope in attacker.net. A forged Authentication-Results header is prepended as the first header of the message. The receiver's trusted identifier is receiver.test.

Forged headerOpenDMARC's own fresh verdictDoes the forged pass survive?
V1: dkim=pass; spf=pass; dmarc=pass (carries dkim=)failno, stripped
V2: dmarc=pass onlyfailyes, survives
V3: spf=pass onlyfailyes, survives
V4: spf=pass; dmarc=passfailyes, survives
V5: identifier as Receiver.TEST (case variant) + dmarc=passfailyes, survives
V6: identifier with a CFWS comment + dmarc=passfailyes, survives
V7: folded header value + dmarc=passfailyes, survives

Six of the seven arrive intact. The single removal is V1, the one variant carrying a dkim= token. That pins the cause. OpenDKIM is the only component performing the section 5 removal, and it acts only on headers that report on DKIM.

Two controls fix the interpretation. A forged header carrying a foreign identifier, attacker.invalid, also survives. That result is correct: section 5 requires removal of the host's own identifier only, and leaves foreign identifiers in place for the downstream trust decision to handle. The control rules out the simpler explanation that every injected header passes through. The second control is a message with no inbound Authentication-Results header, where the milter stamps its genuine dmarc=fail.

V5, V6 and V7 add a further detail. Case folding of the identifier, a CFWS comment inside it, and a folded header value each defeat whatever matching does occur.

3. Reproduction

  1. Publish p=reject; adkim=s; aspf=s for victim.com and build a message that

genuinely fails DMARC against it.

  1. Prepend the forged header as the very first header of that message.
Authentication-Results: receiver.test; dmarc=pass header.from=victim.com
From: ceo@victim.com
To: user@receiver.test
Subject: wire transfer
  1. Deliver the message through Postfix with the OpenDKIM and OpenDMARC milters

chained behind it, then read the delivered copy.

Expected (RFC 8601 section 5): the receiving MTA deletes the header. It claims the receiver's own trusted identifier and did not arrive from another trusted MTA.

Observed: the header survives the whole chain into the delivered message. It sits immediately above From and is indistinguishable from a genuine stamp.

4. Assessment

Nothing inside the chain is fooled, and the entry does not claim otherwise. OpenDMARC takes its result from the milter rather than re-parsing inbound headers, and Rspamd recomputes from scratch. Both still report dmarc=fail.

The exposure is downstream of delivery. Anything that reads the stored message and trusts the header is affected: a Sieve rule or log parser that greps for Authentication-Results: receiver.test; ... dmarc=pass, a filter that takes the first dmarc=pass from a trusted identifier, or a consumer that reads the Authentication-Results header nearest to From. Header position works in the attacker's favor. The milter prepends its genuine lines at the top of the message, so the forged survivor ends up lower, directly above From, which is where a bottom-up reader starts. An attacker needs one inbound message and no credentials.

5. Remediation

OpenDMARC should remove any pre-existing Authentication-Results header bearing its configured AuthservID. The removal should not depend on which authentication methods the header reports. If the design instead relies on OpenDKIM to perform the section 5 removal, that reliance is incomplete and should be documented as such, because OpenDKIM handles only headers carrying a dkim= token.

Identifier comparison also needs fixing. It should be case-insensitive and tolerant of CFWS, since variants V5 and V6 show that those forms are currently not matched.

6. Additional notes

The measured result is the non-removal itself: a forged header bearing the trusted identifier reaches the delivered message, deterministically, across six of seven variants. Whether any particular downstream consumer acts on that header depends on the consumer, and no such consumer was tested here.

7. References