Automating with PowerShell: Deploying spoofing warnings

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></div></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></div></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

2 Comments

  1. Julien March 23, 2021 at 12:45 pm

    About the Displayname Spoofing rule, there used to be a size limit for the rule (8KB ?). Is this limit now gone ?
    If you have a huge list of names, you may hit the limit if it still exists. I use a similar script but I split into several rules so that the size limit is never reached…

  2. Pingback: Automating with PowerShell: Deploying External e-mail markers – CyberDrain

Leave a comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.