In one of the communities I’m active in someone recently asked if Microsoft has a default method of protecting against stuff like BEC fraud via email spoofing, we’ve all seen stuff like nearly matching domain names, display names that are copied, or just the plain “I am the CEO, plz transfer 1 million dollars now”.
While you have lots of protection against this with stuff like ATP or external products, you can also deploy some regex based filtering. In the script below we prepend each email message with a small warning, the warning depends on the type of risk found. I based this script on something I’ve found, but I am not sure where. We’re tackling the following items;
- Display name spoofing
- This generates the warning “CAUTION: This email was sent by an external user with the same display name. This email might be fraudulent”
- Typosquatting domains
- This generates the warning “CAUTION: This email was sent from a domain that looks like yours, but is not. This email might be fraudulent”
- SPF fails
- This generates the warning “CAUTION: This e-mail is not validated to come from the sender.”
We have two scripts for this, one for a single tenant deployment, the other to tackle this for all your tenants.
Single tenant script
######### Secrets #########
$ApplicationId = 'YourAPPID'
$ApplicationSecret = 'YourAPPSecret' | ConvertTo-SecureString -Force -AsPlainText
$ExchangeRefreshToken = 'yourexchangerefreshtoken'
$UPN = "valid-upn-in-your-tenant"
$customerTenantId = "Bla.onmicrosoft.com"
######### Secrets #########
######### Rule setup ######
$Rules = [PSCustomObject]@{
RuleName = "Displayname spoofing"
RuleWarning = "CAUTION: This email was sent by an external user with the same display name. This email might be fraudulent"
},
[PSCustomObject]@{
RuleName = "Typosquatted Domains"
RuleWarning = "CAUTION: This e-mail was sent from a domain that looks like yours, but is not. This email might be fraudulent"
},
[PSCustomObject]@{
RuleName = "SPF fail"
RuleWarning = "CAUTION: This e-mail is not validated to come from the sender."
}
##################
function New-TypoSquatDomain {
param (
$DomainName
)
$ReplacementGylph = [pscustomobject]@{
0 = 'b', 'd'
1 = 'b', 'lb'
2 = 'c', 'e'
3 = 'd', 'b'
4 = 'd', 'cl'
5 = 'd', 'dl'
6 = 'e', 'c'
7 = 'g', 'q'
8 = 'h', 'lh'
9 = 'i', '1'
10 = 'i', 'l'
11 = 'k', 'lk'
12 = 'k', 'ik'
13 = 'k', 'lc'
14 = 'l', '1'
15 = 'l', 'i'
16 = 'm', 'n'
17 = 'm', 'nn'
18 = 'm', 'rn'
19 = 'm', 'rr'
20 = 'n', 'r'
21 = 'n', 'm'
22 = 'o', '0'
23 = 'o', 'q'
24 = 'q', 'g'
25 = 'u', 'v'
26 = 'v', 'u'
27 = 'w', 'vv'
28 = 'w', 'uu'
29 = 'z', 's'
30 = 'n', 'r'
31 = 'r', 'n'
}
$i = 0
$TLD = $DomainName -split '\.' | Select-Object -last 1
$DomainName = $DomainName -split '\.' | Select-Object -First 1
$HomoGlyph = do {
$NewDomain = $DomainName -replace $ReplacementGylph.$i
$NewDomain
$NewDomain + 's'
$NewDomain + 'a'
$NewDomain + 't'
$NewDomain + 'en'
$i++
} while ($i -lt 29)
$i = 0
$BitSquatAndOmission = do {
$($DomainName[0..($i)] -join '') + $($DomainName[($i + 2)..$DomainName.Length] -join '')
$($DomainName[0..$i] -join '') + $DomainName[$i + 2] + $DomainName[$i + 1] + $($DomainName[($i + 3)..$DomainName.Length] -join '')
$i++
} while ($i -lt $DomainName.Length)
$Plurals = $DomainName + 's'; $DomainName + 'a'; $domainname + 'en' ; ; $DomainName + 't'
$CombinedDomains = $HomoGlyph + $BitSquatAndOmission + $Plurals | ForEach-Object { "$($_).$($TLD)" }
return ( $CombinedDomains | Sort-Object -Unique | Where-Object { $_ -ne $DomainName })
}
$credential = New-Object System.Management.Automation.PSCredential($ApplicationId, $ApplicationSecret)
$token = New-PartnerAccessToken -ApplicationId 'a0c73c16-a7e3-4564-9a95-2bdf47383716'-RefreshToken $ExchangeRefreshToken -Scopes 'https://outlook.office365.com/.default' -Tenant $customertenantID
$tokenValue = ConvertTo-SecureString "Bearer $($token.AccessToken)" -AsPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential($upn, $tokenValue)
$customerId = $customer.DefaultDomainName
$session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri "https://ps.outlook.com/powershell-liveid?DelegatedOrg=$($customerId)&BasicAuthToOAuthConversion=true" -Credential $credential -Authentication Basic -AllowRedirection
Import-PSSession $session
foreach ($Rule in $Rules) {
$Reason = $rule.RuleWarning
$WarningHTML = @"
<table border=0 cellspacing=0 cellpadding=0 align=left width="100%" style='width:100.0%><tr style='mso-yfti-irow:0;mso-yfti-firstrow:yes;mso-yfti-lastrow:yes'><td style='background:#910A19;padding:5.25pt 1.5pt 5.25pt 1.5pt'></td><td width="100%" style='width:100.0%;background:#FDF2F4;padding:5.25pt 3.75pt 5.25pt 11.25pt; word-wrap:break-word' cellpadding="7px 5px 7px 15px" color="#212121"><div><p mso-element-wrap:around;mso-element-anchor-vertical:paragraph;mso-element-anchor-horizontal: column;mso-height-rule:exactly'><span style='font-size:10.0pt;color:#212121'>$Reason<o:p></o:p></span></p></td></tr></table>
"@
$ExistingRule = Get-TransportRule | Where-Object { $_.Identity -contains $rule.RuleName }
if ($ExistingRule) {
switch ($rule.rulename) {
"SPF Fail" {
Set-TransportRule -Identity $rule.RuleName -Priority 0 -FromScope "NotInOrganization" -ApplyHtmlDisclaimerLocation "Prepend" -HeaderMatchesMessageHeader Authentication-Results -HeaderMatchesPatterns 'fail' -ApplyHtmlDisclaimerText $WarningHTML
}
"Displayname Spoofing" {
$AllNames = (Get-Mailbox -ResultSize Unlimited).DisplayName
Set-TransportRule -Identity $rule.RuleName -Priority 0 -FromScope "NotInOrganization" -ApplyHtmlDisclaimerLocation "Prepend" -HeaderMatchesMessageHeader From -HeaderMatchesPatterns $AllNames -ApplyHtmlDisclaimerText $WarningHTML
}
"Typosquatted Domains" {
$OwnedDomains = Get-MsolDomain -TenantId $customertenantID
$TypoSquattedDomains = foreach ($Domain in $OwnedDomains) {
New-TypoSquatDomain -DomainName $domain.name
}
Set-TransportRule -Identity $rule.RuleName -Priority 0 -FromScope "NotInOrganization" -ApplyHtmlDisclaimerLocation "Prepend" -SenderDomainIs $TypoSquattedDomains -ApplyHtmlDisclaimerText $WarningHTML
}
}
}
else {
switch ($rule.rulename) {
"SPF Fail" {
New-TransportRule -Name $rule.RuleName -Priority 0 -FromScope "NotInOrganization" -ApplyHtmlDisclaimerLocation "Prepend" -HeaderMatchesMessageHeader Authentication-Results -HeaderMatchesPatterns 'fail' -ApplyHtmlDisclaimerText $WarningHTML
}
"Displayname Spoofing" {
$AllNames = (Get-Mailbox -ResultSize Unlimited).DisplayName
New-TransportRule -Name $rule.RuleName -Priority 0 -FromScope "NotInOrganization" -ApplyHtmlDisclaimerLocation "Prepend" -HeaderMatchesMessageHeader From -HeaderMatchesPatterns $AllNames -ApplyHtmlDisclaimerText $WarningHTML
}
"Typosquatted Domains" {
$OwnedDomains = Get-AcceptedDomain
$TypoSquattedDomains = foreach ($Domain in $OwnedDomains) {
New-TypoSquatDomain -DomainName $domain.Domainname
}
New-TransportRule -Name $rule.RuleName -Priority 0 -FromScope "NotInOrganization" -ApplyHtmlDisclaimerLocation "Prepend" -SenderDomainIs $TypoSquattedDomains -ApplyHtmlDisclaimerText $WarningHTML
}
}
}
}
Remove-PSSession $Session
All tenant scripts
######### Secrets #########
$ApplicationId = 'YourAPPID'
$ApplicationSecret = 'YourAPPSecret' | ConvertTo-SecureString -Force -AsPlainText
$TenantID = 'YourTENANTID'
$RefreshToken = 'YourRefreshToken'
$ExchangeRefreshToken = 'yourexchangerefreshtoken'
$UPN = "valid-upn-in-your-tenant"
######### Secrets #########
######### Rule setup ######
$Rules = [PSCustomObject]@{
RuleName = "Displayname spoofing"
RuleWarning = "CAUTION: This email was sent by an external user with the same display name. This email might be fraudulent"
},
[PSCustomObject]@{
RuleName = "Typosquatted Domains"
RuleWarning = "CAUTION: This e-mail was sent from a domain that looks like yours, but is not. This email might be fraudulent"
},
[PSCustomObject]@{
RuleName = "SPF fail"
RuleWarning = "CAUTION: This e-mail is not validated to come from the sender."
}
##################
function New-TypoSquatDomain {
param (
$DomainName
)
$ReplacementGylph = [pscustomobject]@{
0 = 'b', 'd'
1 = 'b', 'lb'
2 = 'c', 'e'
3 = 'd', 'b'
4 = 'd', 'cl'
5 = 'd', 'dl'
6 = 'e', 'c'
7 = 'g', 'q'
8 = 'h', 'lh'
9 = 'i', '1'
10 = 'i', 'l'
11 = 'k', 'lk'
12 = 'k', 'ik'
13 = 'k', 'lc'
14 = 'l', '1'
15 = 'l', 'i'
16 = 'm', 'n'
17 = 'm', 'nn'
18 = 'm', 'rn'
19 = 'm', 'rr'
20 = 'n', 'r'
21 = 'n', 'm'
22 = 'o', '0'
23 = 'o', 'q'
24 = 'q', 'g'
25 = 'u', 'v'
26 = 'v', 'u'
27 = 'w', 'vv'
28 = 'w', 'uu'
29 = 'z', 's'
30 = 'n', 'r'
31 = 'r', 'n'
}
$i = 0
$TLD = $DomainName -split '\.' | Select-Object -last 1
$DomainName = $DomainName -split '\.' | Select-Object -First 1
$HomoGlyph = do {
$NewDomain = $DomainName -replace $ReplacementGylph.$i
$NewDomain
$NewDomain + 's'
$NewDomain + 'a'
$NewDomain + 't'
$NewDomain + 'en'
$i++
} while ($i -lt 29)
$i = 0
$BitSquatAndOmission = do {
$($DomainName[0..($i)] -join '') + $($DomainName[($i + 2)..$DomainName.Length] -join '')
$($DomainName[0..$i] -join '') + $DomainName[$i + 2] + $DomainName[$i + 1] + $($DomainName[($i + 3)..$DomainName.Length] -join '')
$i++
} while ($i -lt $DomainName.Length)
$Plurals = $DomainName + 's'; $DomainName + 'a'; $domainname + 'en' ; ; $DomainName + 't'
$CombinedDomains = $HomoGlyph + $BitSquatAndOmission + $Plurals | ForEach-Object { "$($_).$($TLD)" }
return ( $CombinedDomains | Sort-Object -Unique | Where-Object { $_ -ne $DomainName })
}
$credential = New-Object System.Management.Automation.PSCredential($ApplicationId, $ApplicationSecret)
$aadGraphToken = New-PartnerAccessToken -ApplicationId $ApplicationId -Credential $credential -RefreshToken $refreshToken -Scopes 'https://graph.windows.net/.default' -ServicePrincipal -Tenant $tenantID
$graphToken = New-PartnerAccessToken -ApplicationId $ApplicationId -Credential $credential -RefreshToken $refreshToken -Scopes 'https://graph.microsoft.com/.default' -ServicePrincipal -Tenant $tenantID
Connect-MsolService -AdGraphAccessToken $aadGraphToken.AccessToken -MsGraphAccessToken $graphToken.AccessToken
$customers = Get-MsolPartnerContract -All
foreach ($customer in $customers) {
$token = New-PartnerAccessToken -ApplicationId 'a0c73c16-a7e3-4564-9a95-2bdf47383716'-RefreshToken $ExchangeRefreshToken -Scopes 'https://outlook.office365.com/.default' -Tenant $customer.TenantId
$tokenValue = ConvertTo-SecureString "Bearer $($token.AccessToken)" -AsPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential($upn, $tokenValue)
$customerId = $customer.DefaultDomainName
$session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri "https://ps.outlook.com/powershell-liveid?DelegatedOrg=$($customerId)&BasicAuthToOAuthConversion=true" -Credential $credential -Authentication Basic -AllowRedirection
Import-PSSession $session
foreach ($Rule in $Rules) {
$Reason = $rule.RuleWarning
$WarningHTML = @"
<table border=0 cellspacing=0 cellpadding=0 align=left width="100%" style='width:100.0%><tr style='mso-yfti-irow:0;mso-yfti-firstrow:yes;mso-yfti-lastrow:yes'><td style='background:#910A19;padding:5.25pt 1.5pt 5.25pt 1.5pt'></td><td width="100%" style='width:100.0%;background:#FDF2F4;padding:5.25pt 3.75pt 5.25pt 11.25pt; word-wrap:break-word' cellpadding="7px 5px 7px 15px" color="#212121"><div><p mso-element-wrap:around;mso-element-anchor-vertical:paragraph;mso-element-anchor-horizontal: column;mso-height-rule:exactly'><span style='font-size:10.0pt;color:#212121'>$Reason<o:p></o:p></span></p></td></tr></table>
"@
$ExistingRule = Get-TransportRule | Where-Object { $_.Identity -contains $rule.RuleName }
if ($ExistingRule) {
switch ($rule.rulename) {
"SPF Fail" {
Set-TransportRule -Identity $rule.RuleName -Priority 0 -FromScope "NotInOrganization" -ApplyHtmlDisclaimerLocation "Prepend" -HeaderMatchesMessageHeader Authentication-Results -HeaderMatchesPatterns 'fail' -ApplyHtmlDisclaimerText $WarningHTML
}
"Displayname Spoofing" {
$AllNames = (Get-Mailbox -ResultSize Unlimited).DisplayName
Set-TransportRule -Identity $rule.RuleName -Priority 0 -FromScope "NotInOrganization" -ApplyHtmlDisclaimerLocation "Prepend" -HeaderMatchesMessageHeader From -HeaderMatchesPatterns $AllNames -ApplyHtmlDisclaimerText $WarningHTML
}
"Typosquatted Domains" {
$OwnedDomains = Get-MsolDomain -TenantId $customer.tenantid
$TypoSquattedDomains = foreach ($Domain in $OwnedDomains) {
New-TypoSquatDomain -DomainName $domain.name
}
Set-TransportRule -Identity $rule.RuleName -Priority 0 -FromScope "NotInOrganization" -ApplyHtmlDisclaimerLocation "Prepend" -SenderDomainIs $TypoSquattedDomains -ApplyHtmlDisclaimerText $WarningHTML
}
}
}
else {
switch ($rule.rulename) {
"SPF Fail" {
New-TransportRule -Name $rule.RuleName -Priority 0 -FromScope "NotInOrganization" -ApplyHtmlDisclaimerLocation "Prepend" -HeaderMatchesMessageHeader Authentication-Results -HeaderMatchesPatterns 'fail' -ApplyHtmlDisclaimerText $WarningHTML
}
"Displayname Spoofing" {
$AllNames = (Get-Mailbox -ResultSize Unlimited).DisplayName
New-TransportRule -Name $rule.RuleName -Priority 0 -FromScope "NotInOrganization" -ApplyHtmlDisclaimerLocation "Prepend" -HeaderMatchesMessageHeader From -HeaderMatchesPatterns $AllNames -ApplyHtmlDisclaimerText $WarningHTML
}
"Typosquatted Domains" {
$OwnedDomains = Get-MsolDomain -TenantId $customer.tenantid
$TypoSquattedDomains = foreach ($Domain in $OwnedDomains) {
New-TypoSquatDomain -DomainName $domain.name
}
New-TransportRule -Name $rule.RuleName -Priority 0 -FromScope "NotInOrganization" -ApplyHtmlDisclaimerLocation "Prepend" -SenderDomainIs $TypoSquattedDomains -ApplyHtmlDisclaimerText $WarningHTML
}
}
}
}
Remove-PSSession $Session
}
The warnings look like this inside of the email:
and that’s it! as always, Happy PowerShelling