OpenDMARC does not convert a U-label From domain to its A-label, so internationalised domains have DMARC bypassed entirely (OpenDMARC ≤ 1.4.2; latest version tested 2026-07-30)

Author: Weitong Li, Virginia Tech <weitongli@vt.edu>
Date: 2026-07-31
Vendor: The Trusted Domain Project
Software: OpenDMARC
Source code: https://github.com/trusteddomainproject/OpenDMARC
Affected version: OpenDMARC 1.4.2, built from the upstream tarball rel-opendmarc-1-4-2. The finding was originally isolated on the Debian package 1.4.2-1+deb12u1 and re-confirmed against pristine upstream. Earlier 1.4.x and 1.3.x releases are expected to share the behaviour. Latest version tested on 2026-07-30.
Vulnerable component: DMARC Author Domain extraction — the missing IDNA ToASCII conversion before the _dmarc lookup and alignment
Vulnerability type: CWE-172 (Encoding Error) / CWE-290 (Authentication Bypass by Spoofing)
Attack vector: Remote, unauthenticated, no user interaction. One forged, unaligned message.
CVSS v3.1: 7.5 (High) — CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N
Status: Reproduced 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. Reproduced for Latin (münchen, café), CJK (例え) and a Cyrillic homoglyph, under both p=reject and p=quarantine.

Impact. Writing the victim's IDN in Unicode rather than punycode makes OpenDMARC report dmarc=none, defeating p=reject for every IDN sender.

Description

Three consequences, all following from the same missing conversion:

A DMARC policy for an internationalised domain can only exist in DNS at the A-label node, for instance _dmarc.xn--mnchen-3ya.example.com. RFC 7489 section 6.6.1 therefore requires the verifier to convert a UTF-8 From domain to its A-label before querying.

OpenDMARC performs no conversion. It queries using the raw UTF-8 bytes, never finds the policy, and stamps dmarc=none. Every sender using an internationalised domain has its published p=reject or p=quarantine completely unenforced on OpenDMARC receivers.

The victim publishes its policy at the only node it can occupy:

_dmarc.xn--mnchen-3ya.example.com.  IN  TXT  "v=DMARC1; p=reject"

A forged, unaligned message (no aligned DKIM; envelope sender bounce@attacker.invalid; SPF -all) varies only the form of the From domain:

From domain formOpenDMARCRspamdRequired
ceo@münchen.example.com (U-label, UTF-8)dmarc=none — delivereddmarc=fail, policy rejectfail
ceo@xn--mnchen-3ya.example.com (A-label, control)dmarc=faildmarc=failfail
ceo@plainmunich.example.com (ASCII, control)dmarc=faildmarc=failfail

The two controls isolate the single variable: only the U-label form makes OpenDMARC fail open. The header it writes shows the failure directly:

Authentication-Results: receiver.test; dmarc=none (p=none dis=none)
    header.from=münchen.example.com

It both abandons the policy and records a non-A-label domain, contrary to section 6.6.

Publishing the record additionally at the literal raw-UTF-8 node _dmarc.münchen.example.com does not make OpenDMARC find it. That places the defect before the DNS match: OpenDMARC abandons DMARC for a non-ASCII From rather than normalising it.

Proof of Concept

# zone
_dmarc.xn--mnchen-3ya.example.com.  IN  TXT  "v=DMARC1; p=reject"

# forged message, unsigned, unaligned envelope
MAIL FROM:<bounce@attacker.invalid>
RCPT TO:<user@receiver.test>
DATA
From: ceo@münchen.example.com
To: user@receiver.test
Subject: payroll update

...
.

Required result: dmarc=fail, disposition reject. Observed on OpenDMARC: dmarc=none, delivered.

Mitigation

Apply IDNA ToASCII to the extracted Author Domain before the _dmarc query and before alignment, and record the A-label in the Authentication-Results header, as section 6.6.1 requires. See opendmarc-unicode-dot-bypass: the conversion must include UTS-46 mapping, not only punycoding of labels that contain non-ASCII letters.

References