A way to (sort of) approximate DMARC aggregate reports in Office 365

One of the most common questions people ask me is "How do you get Office 365 to send out DMARC aggregate and forensic reports?" This is followed by "When is Office 365 going to send out DMARC aggregate and forensic reports?"

Office 365 doesn't send out DMARC reports, nor is it on our public roadmap. However, there's a hacky way you can kind of approximate aggregate reports. This is how I would do it.

Many people don't know about this, but there's basic reporting for spoofed messages flowing into your organization. This is so that you can see who is sending you spoofed messages and create allow/block lists as required (that is, for a message that doesn't authenticate and is marked as spam by our service because of it, you can allow that domain-pair [1] to go through regular filtering; conversely, if Office 365 thinks a domain-pair is legitimate, you can override it and say it's not).

Anyhow, the Get-PhishFilterPolicy gives you an overview of everything sending into the service that doesn't authenticate, so you can actually extract this information and send it off to a DMARC reporting address. You can do this via the following:

1. Connect to Exchange Online using Powershell

2. Run the following script, updating this value for your own domain:

$domain = "<yourDomain>"
# Get today's date
$date = get-Date -Format "yyyy-MM-dd"
# Create a variable for exporting the results
$filename = "AuthResults-" + $date + ".csv"
# Get the required data and export it to the filename as a CSV file
$spoof = Get-PhishFilterPolicy -SpoofAllowBlockList -Detailed | Select-Object SpoofedUser,Sender,NumberOfMessages,DomainPairsInCategory,LastSeen,SpoofType
$spoof | where { $_.SpoofType -eq "Internal" } | Export-Csv $filename# Lookup the DMARC rua address from DNS
$dmarcDomain = "_dmarc." + $domain
$dmarc = Resolve-DnsName -Type TXT -Name $dmarcDomain# Convert the Object to a string and strip off the irrelevant parts
$dmarcString = $dmarc.Strings
$dmarcString = $dmarcString -replace ".*rua=mailto:",""
$dmarcString = $dmarcString -replace ";.*",""

# Store this result as the recipient to whom you will send the "DMARC" aggregate report
$to = $dmarcString

# Lookup the DMARC rua domain's MX record
$dmarcRuaDomain = $dmarcString -replace ".*\@","" | Out-String
$dmarcRuaDomain = $dmarcRuaDomain -replace "\s",""
$mxRua = Resolve-DnsName -type MX -Name $dmarcRuaDomain
$smtpServer = $mxRua[0].Name | Out-String
$smtpServer -replace "\s",""

# Construct the variables for sending an email
$from = "DMARC report for " + $domain + " <antispoof@" + $domain + ">"
$subject = "DMARC report for " + $domain + " on " + $date
$body = gc $filename

# Strip off the localpart in the email address. You may need to adjust this regex if you have localparts that contain other characters
$regex = "[a-z0-9\-\.\+]+@"
$body = $body -replace $regex,""
$body = [string]$body

# Send the message with the Get-PhishFilterPolicy's contents
Send-MailMessage -From $from -To $to -Subject $subject -Body $body -smtpserver $smtpServer -port 25 -usessl

What this does is get all the spoofed email sending as you into your organization, and then send a copy to the address in the DMARC's DNS record.

There are some things to be aware of:

a) Whoever you send this DMARC "aggregate" report to needs to understand that you are not sending a DMARC aggregate report which is in xml format. Instead, it is the Office 365 phish filter policy for antispoofing reporting and the fields do *not* map 1:1 for a regular DMARC aggregate report. The report has to be parsed and the necessary data extrapolated. And this is the best I can do at the moment. Please don't come back to me saying "But this is not a DMARC report! When are we getting DMARC reports?"

b) This script sends your organization's data to a third party outside of the Office 365 compliance boundary. It includes what we classify as End User Identifiable Information because it contains an email address in clear text. When you run this script, be aware of data that you are sending. The script above strips off the localpart of the email address, although there may be other email addresses it doesn't match. So, beware.

c) When you run this script, make sure you run it from a machine with an IP address that is in your organization's SPF record. If it isn't, you will need to do some customization.

d) I've omitted several fields from the Get-PhishFilterPolicy when sending the "aggregate" report, but IMHO they aren't required

e) This cmdlet gets all of your data, whereas you might want to only pull data every 24 hours. If it were me, I would automate it and add the following line:

$yesterday = (Get-Date).AddHours(-24)

Then replace the corresponding line above with:

$spoof | where { $_.SpoofType -eq "Internal" } | where { $_.LastSeen -gt $yesterday } | Export-Csv $filename

f) I'm not a Powershell expert, there are many of you out there who are much better than me and can better optimize the script above. This script is verbose so you can see what's going on.
So, this script isn't perfect, and it excludes some key information. But it does give some useful data.


[1] A domain-pair is a combination of the From: + the sending infrastructure. If the From domain is internal to your organization (one of your org's accepted domains), the SpoofedUser is the exact email address; if the From domain is external, then the SpoofedUser is only the From: domain. The sending infrastructure (Sender) is the organizational domain of the PTR record of the sending IP. If the sending IP has no PTR record, then the Sender is the /24 range of the sending IP, e.g., would be