The “Ensure DMARC Records for all Exchange Online domains are published” finding means a mail domain has no DMARC policy at _dmarc.<domain>, or has one that only monitors. Fix it by publishing a TXT record such as v=DMARC1; p=none; rua=mailto:dmarc-reports@example.com, reading the aggregate reports, fixing SPF and DKIM alignment, then moving to p=quarantine and p=reject.
What the scanner is actually detecting
DMARC is a TXT record published at _dmarc under the domain in the visible From header. It tells receivers what to do with mail that fails both aligned SPF and aligned DKIM, and where to send reports. The current specification is RFC 9989 (May 2026), which obsoletes RFC 7489. Two tools commonly raise this finding:
| Scanner | Finding title | What triggers it |
|---|---|---|
| Tenable (CIS Microsoft 365 Foundations v4.0.0 L1 E5 audit) | 2.1.10 (L1) Ensure DMARC Records for all Exchange Online domains are published | The benchmark expects every Exchange Online accepted domain, plus the tenant’s *.onmicrosoft.com (MOERA) domain, to end at p=quarantine or p=reject with pct=100 and both rua and ruf addresses. Tenable ships it as a WARNING item that states Nessus has not performed the check. |
| Nuclei (dmarc-detect) | DNS DMARC – Detect | An info-level presence check: it queries TXT at _dmarc.{{FQDN}} and matches any answer containing the string DMARC. “No DMARC record found” is the absence of a match. |
So the Tenable item appears whether or not you comply and must be verified by hand, while the Nuclei template only proves some record exists. A p=none record satisfies Nuclei but not the CIS item.
Real-world risk
SPF validates the envelope sender, not the From address people read. Without DMARC, anyone can send mail with your exact domain in From and the receiver falls back on its own spoofing heuristics. Microsoft 365, for instance, stamps dmarc=bestguesspass when a sender domain publishes no record, meaning it inferred a result rather than applying your policy. The CIS rationale adds that p=none gives receivers no instruction to act on spear phishing that fails DMARC, which matters for business email compromise.
Keep the scope honest: DMARC stops exact-domain spoofing at receivers that honor it. It does nothing against lookalike domains or a compromised mailbox, and nothing on your servers is exposed. Unused domains without a record are the easiest spoofing targets because nobody watches them.
How to confirm it
On Linux or macOS:
# Public answer
dig +short TXT _dmarc.example.com
# Ask an authoritative server directly, bypassing caches
dig +short NS example.com
dig @ns1.example-dns.net TXT _dmarc.example.com +norecurse
On Windows:
Resolve-DnsName -Name "_dmarc.example.com" -Type TXT | Select-Object -ExpandProperty Strings
nslookup -type=TXT _dmarc.example.com
For Microsoft 365, check every accepted domain in one pass from Exchange Online PowerShell:
Connect-ExchangeOnline
Get-AcceptedDomain | ForEach-Object {
$d = "$($_.DomainName)"
$recs = @(Resolve-DnsName -Name "_dmarc.$d" -Type TXT -ErrorAction SilentlyContinue |
Where-Object { $_.Type -eq 'TXT' } |
ForEach-Object { $_.Strings -join '' } |
Where-Object { $_ -like 'v=DMARC1*' })
[pscustomobject]@{ Domain = $d; Records = $recs.Count; Value = $recs -join ' || ' }
}
Zero records means missing. More than one is also broken: RFC 9989 says multiple DMARC records for one name are all discarded. A single record with p=none is monitoring only and still fails the CIS item.
How to fix it
Step 1: make SPF and DKIM pass and align
A message passes DMARC if SPF passes for a MAIL FROM domain that aligns with the From domain, or DKIM passes with a d= domain that aligns. Google says to allow 48 hours after setting up SPF and DKIM before setting up DMARC. In Microsoft 365, enable DKIM signing with each custom domain; Microsoft’s alignment table shows a signature from contoso.onmicrosoft.com does not align with contoso.com. Third-party senders (marketing, ticketing, CRM) must sign with your domain or send from a dedicated subdomain.
Step 2: publish a monitoring record
Create a TXT record with host _dmarc and a value like v=DMARC1; p=none; rua=mailto:dmarc-reports@example.com. Microsoft recommends a dedicated shared mailbox or Microsoft 365 Group for reports, not a personal inbox.
BIND zone file (increment the SOA serial):
_dmarc 3600 IN TXT "v=DMARC1; p=none; rua=mailto:dmarc-reports@example.com"
named-checkzone example.com /path/to/db.example.com
rndc reload example.com
Windows DNS Server (use the zone that answers the Internet):
Add-DnsServerResourceRecord -ZoneName "example.com" -Name "_dmarc" -Txt `
-DescriptiveText "v=DMARC1; p=none; rua=mailto:dmarc-reports@example.com" `
-TimeToLive 01:00:00
Hosted DNS for Microsoft 365 or Google Workspace domains: add a TXT record at your DNS host with host _dmarc (some providers want _dmarc.example.com). Microsoft 365 has no portal or cmdlet for DMARC on custom domains. The exception is the MOERA domain, which you set in the admin center: Settings > Domains, select contoso.onmicrosoft.com, open DNS records, choose Add record, type TXT, name _dmarc.
If rua points to another domain, such as a reporting service, that domain must publish example.com._report._dmarc TXT v=DMARC1; or receivers will not deliver reports. CIS also asks for ruf; Microsoft notes that Microsoft 365 does not send failure reports and suggests starting with rua only, because failure reports can be high volume and carry message details.
Step 3: read the reports and fix alignment
Aggregate reports arrive as compressed XML, typically daily. The key pattern is auth_results showing pass while policy_evaluated shows fail: the sender is authorized but not aligned. Fix it at the sender with custom DKIM (d=example.com) or a MAIL FROM in your domain. The CIS remediation says one week of data is enough to be useful. RFC 9989 is more cautious for domains whose users post to mailing lists: at least a month at p=none, then an equally long period at p=quarantine.
Step 4: move to enforcement
v=DMARC1; p=quarantine; pct=100; rua=mailto:dmarc-reports@example.com
v=DMARC1; p=reject; pct=100; rua=mailto:dmarc-reports@example.com; ruf=mailto:dmarc-failures@example.com
Microsoft suggests starting with low-volume subdomains, leaving the parent domain for last, and optionally stepping pct through 10, 25, 50, 75 and 100. Note that RFC 9989 removed pct because values other than 0 and 100 were applied inconsistently, and added t=y as a testing flag. Receivers must ignore unknown tags, so pct=100 is harmless and still what CIS and Microsoft document, but do not count on partial values for a precise ramp.
Parked domains and subdomains
For domains that never send mail, Microsoft recommends v=DMARC1; p=reject;, including the onmicrosoft.com domain if you do not send from it. Subdomains inherit the parent policy unless they publish their own record, and sp= sets a separate subdomain policy. Registered domains nobody remembers are where this finding hides; our guide to auditing a sprawling domain portfolio covers finding them.
How to verify the fix and rescan
- Query the authoritative servers, then a public resolver. Confirm exactly one line starting v=DMARC1.
- Send a test message from each sender to an external mailbox and read Authentication-Results. You want dmarc=pass. In Microsoft 365, failures under enforcement show action=quarantine or action=oreject.
- Confirm aggregate reports are arriving at the rua mailbox.
- Rerun the scanners. nuclei -u example.com -id dmarc-detect should match and extract your record. The Tenable CIS item stays a WARNING because Nessus does not perform it, so keep the per-domain query output from the PowerShell loop above as evidence for the auditor.
What can break and how to roll back
- Unaligned legitimate senders. Under quarantine or reject, a forgotten SaaS sender lands in junk or gets rejected. The reports should reveal these before you enforce.
- Mailing lists and forwarding. Forwarding breaks SPF and list modifications break DKIM. RFC 9989 says domains whose users post to mailing lists SHOULD NOT publish p=reject without first measuring the impact.
- Microsoft 365 outbound. When your policy is quarantine or reject, Microsoft routes outbound messages that fail DMARC at the destination through its high-risk delivery pool, with no override.
- Duplicate records. Two _dmarc TXT records cancel each other out.
To roll back, lower the policy to p=none and keep rua; do not delete the record, which loses reporting. On Windows DNS:
Remove-DnsServerResourceRecord -ZoneName "example.com" -RRType Txt -Name "_dmarc" `
-RecordData "v=DMARC1; p=reject; pct=100; rua=mailto:dmarc-reports@example.com" -Force
Add-DnsServerResourceRecord -ZoneName "example.com" -Name "_dmarc" -Txt `
-DescriptiveText "v=DMARC1; p=none; rua=mailto:dmarc-reports@example.com"
On BIND, restore the previous line, increment the serial and run rndc reload. Caches keep the old answer for up to the TTL.
Common false positive reasons
- The Tenable item is manual. It reports a WARNING regardless of your DNS, so a compliant tenant still shows it.
- Wrong name scanned. Nuclei queries _dmarc under the exact host you scanned, so www.example.com checks _dmarc.www.example.com. Receivers fall back to the organizational domain’s record, so the parent policy already covers that host.
- Split-horizon DNS. An internal scanner resolved an internal zone without the public record.
- Query failure. A timeout or SERVFAIL looks the same as “no match” to a presence check.
FAQ
Is p=none enough to close the finding?
For the Nuclei presence check, yes. For the CIS item, no: it expects p=quarantine or p=reject with pct=100.
Does every subdomain need its own _dmarc record?
No. Subdomains inherit the parent domain’s policy unless they publish their own record. Use sp= if subdomains need a different policy.
Should I still include pct=100?
It is optional: 100 was the default under RFC 7489, and RFC 9989 removed the tag. Including it satisfies auditors reading the CIS text and changes nothing for receivers.
Why am I not receiving aggregate reports?
Check that the rua mailbox accepts external mail, that an external report domain publishes the _report._dmarc authorization record, and remember that not every receiver sends reports.
Tracking this finding across many domains
DMARC gaps usually span many domains and get reported by more than one tool. SITEY, a self-hosted vulnerability management platform, imports findings from 16 scanners (Nessus results arrive by uploading an exported .nessus file). It merges duplicates within each scanner but not across scanners, so a domain flagged by two tools remains two items to close.
Sources
- RFC 9989: Domain-Based Message Authentication, Reporting, and Conformance (DMARC)
- Microsoft Learn: Set up DMARC to validate email in Microsoft 365
- Google Workspace Admin Help: Set up DMARC
- Tenable: 2.1.10 (L1) Ensure DMARC Records for all Exchange Online domains are published
- ProjectDiscovery nuclei-templates: dmarc-detect