HackBlue

Tools · Email security · 05 / 18

Reading DMARC aggregate reports to find every sender using your domain

Parse DMARC rua XML reports, group results by source IP and alignment, identify legitimate senders that need fixing, and spot spoofing attempts.

9 min read · practice only with authorization

What an aggregate report contains

Receivers that honor rua send daily XML files, usually compressed, listing for each source IP the message count, the DKIM and SPF results, the domains used, and the policy applied. They contain no message content. The volume quickly outgrows reading by hand, so parse them into a table.

Snippet
<record>
  <row><source_ip>198.51.100.25</source_ip><count>412</count>
    <policy_evaluated><disposition>none</disposition><dkim>fail</dkim><spf>pass</spf></policy_evaluated></row>
  <identifiers><header_from>example.org</header_from></identifiers>
  <auth_results><dkim><domain>vendor.example</domain><result>pass</result></dkim>
    <spf><domain>example.org</domain><result>pass</result></spf></auth_results>
</record>

A small parser

Python’s standard library is enough to flatten reports into CSV. Decompress the files first; most arrive as .zip or .gz attachments.

Snippet
import csv, glob, sys, xml.etree.ElementTree as ET
w = csv.writer(sys.stdout)
w.writerow(['org', 'source_ip', 'count', 'header_from', 'dkim', 'spf', 'disposition'])
for path in glob.glob('reports/*.xml'):
    root = ET.parse(path).getroot()
    org = root.findtext('report_metadata/org_name')
    for rec in root.iter('record'):
        pe = rec.find('row/policy_evaluated')
        w.writerow([org, rec.findtext('row/source_ip'), rec.findtext('row/count'),
                    rec.findtext('identifiers/header_from'),
                    pe.findtext('dkim'), pe.findtext('spf'), pe.findtext('disposition')])

Lab: classify sources

Run the parser over a week of reports for your domain, or over the sample reports published by open-source DMARC tools. Group by source IP and look up each IP’s owner with reverse DNS or WHOIS. Classify each source as your platform, a known vendor that needs alignment fixes, forwarding, or unknown. Unknown sources failing both checks are likely spoofing and will be blocked once you reach reject.

Snippet
python3 parse_dmarc.py > dmarc.csv
cut -d, -f2 dmarc.csv | sort | uniq -c | sort -rn | head
dig +short -x 198.51.100.25
whois 198.51.100.25 | grep -iE 'orgname|netname'

At scale

Open-source tools such as parsedmarc ingest reports from a mailbox into Elasticsearch or Splunk with dashboards, and many commercial services do the same. Whatever the tool, the questions stay the same: who sends as us, do they align, and what fails.

Diagram

Typical source classes in aggregate reports

Each class leads to a different action.

  • Your mail platform

    Should pass and align; investigate any failures.

  • Known vendor

    Passes but does not align; configure custom DKIM.

  • Forwarder or list

    SPF fails after forwarding; DKIM may survive; consider ARC.

  • Unknown

    Fails both; likely spoofing that enforcement will block.

Check yourself

1. Do aggregate reports include message content?

2. A vendor’s IP passes SPF and DKIM but neither aligns. What is it?

3. An unknown IP fails both checks with your From domain. What does it likely indicate?