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

Recent Articles

The return of CyberDrain CTF

CyberDrain CTF returns! (and so do I!)

It’s been since september that I actually picked up a digital pen equivalent and wrote anything down. This was due to me being busy with life but also my side projects like CIPP. I’m trying to get back into the game of scripting and blogging about these scripts. There’s still so much to automate and so little time, right? ;)

Monitoring with PowerShell: Monitoring Acronis Backups

Intro

This is a monitoring script requested via Reddit, One of the reddit r/msp users wondered how they can monitor Acronis a little bit easier. I jumped on this because it happened pretty much at the same time that I was asked to speak at the Acronis CyberSummit so it kinda made sense to script this so I have something to demonstrate at my session there.

Monitoring with PowerShell: Monitoring VSS Snapshots

Intro

Wow! It’s been a while since I’ve blogged. I’ve just been so swamped with CIPP that I’ve just let the blogging go entirely. It’s a shame because I think out of all my hobbies it’s one I enjoy the most. It’s always nice helping others achieve their scripting target. I even got a couple of LinkedIn questions asking if I was done with blogging but I’m not. Writing always gives me some more piece of mind so I’ll try to catch up again. I know I’ve said that before but this time I’ll follow through. I’m sitting down right now and scheduling the release of 5 blogs in one go. No more whining and no more waiting.