Featured image of post Automating with PowerShell: Deploying spoofing warnings

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

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
######### 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

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
######### 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

All blogs are posted under AGPL3.0 unless stated otherwise
comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy