Vulnerability analysis · DMARC
Spell the victim's IDN in Unicode rather than punycode and OpenDMARC reports dmarc=none. The published p=reject is never found, and every IDN sender is unprotected.
| Software | OpenDMARC |
|---|---|
| Vendor | The Trusted Domain Project |
| Source | https://github.com/trusteddomainproject/OpenDMARC |
| Affected | 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 behavior. Newest published release checked on 2026-07-30. |
| Where | DMARC Author Domain extraction, the missing IDNA ToASCII conversion before the _dmarc lookup and alignment |
| Weakness | CWE-172 (Encoding Error) / CWE-290 (Authentication Bypass by Spoofing) |
| Reachable by | Remote, unauthenticated, no user interaction. One forged, unaligned message. |
| CVSS v3.1 | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N (7.5 (High)) |
| Verification | 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. |
DNS carries no Unicode. A DMARC policy for an internationalized domain can live at one node only, the A-label node, such as _dmarc.xn--mnchen-3ya.example.com. RFC 7489 section 6.6.1 therefore tells the verifier to convert a UTF-8 From domain to its A-label before it queries.
OpenDMARC converts nothing. It sends the query with the raw UTF-8 bytes, gets no answer, and stamps dmarc=none. Any sender on an internationalized domain has its published p=reject or p=quarantine ignored on every OpenDMARC receiver.
The victim publishes its policy at the only node it can occupy.
_dmarc.xn--mnchen-3ya.example.com. IN TXT "v=DMARC1; p=reject"
Each test sends the same forged, unaligned message: no aligned DKIM, envelope sender bounce@attacker.invalid, SPF -all. Only the spelling of the From domain changes.
| From domain form | OpenDMARC | Rspamd | Required |
|---|---|---|---|
ceo@münchen.example.com (U-label, UTF-8) | dmarc=none, delivered | dmarc=fail, policy reject | fail |
ceo@xn--mnchen-3ya.example.com (A-label, control) | dmarc=fail | dmarc=fail | fail |
ceo@plainmunich.example.com (ASCII, control) | dmarc=fail | dmarc=fail | fail |
Both controls fail correctly, so the U-label spelling is the only variable that flips the verdict. The header OpenDMARC writes shows what happened.
Authentication-Results: receiver.test; dmarc=none (p=none dis=none)
header.from=münchen.example.com
Two things are wrong in that one header. The policy was abandoned, and the recorded domain is not an A-label, which section 6.6 requires.
One more test locates the defect precisely. Publish the record a second time at the literal raw-UTF-8 node _dmarc.münchen.example.com. OpenDMARC still does not find it. The failure is therefore not a DNS mismatch. OpenDMARC gives up on DMARC for a non-ASCII From rather than normalizing it first.
_dmarc.xn--mnchen-3ya.example.com. IN TXT "v=DMARC1; p=reject"
MAIL FROM:<bounce@attacker.invalid>
RCPT TO:<user@receiver.test>
DATA
From: ceo@münchen.example.com
To: user@receiver.test
Subject: payroll update
...
.
Expected: dmarc=fail, disposition reject.
Observed on OpenDMARC: dmarc=none. The message is delivered.
One missing conversion produces three separate harms.
p=reject or p=quarantine bypassed on OpenDMARC receivers. The attacker pays nothing for it: one forged message, no interaction, no credentials.dmarc=none whatever policy that domain publishes.dmarc=none on OpenDMARC, where Rspamd returns dmarc=pass.Run IDNA ToASCII on the extracted Author Domain. Do it before the _dmarc query and before alignment, and write the A-label into the Authentication-Results header, as section 6.6.1 requires.
The conversion has to include UTS-46 mapping, not just punycoding of labels that hold non-ASCII letters. See opendmarc-unicode-dot-bypass for what the narrower version misses.