Created
April 5, 2021 19:49
-
-
Save jhochwald/1178b28a80b4d766531acb934d8abced to your computer and use it in GitHub Desktop.
Deploying Exchange Online security and spoofing warnings rules
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#requires -Version 3.0 -Modules ExchangeOnlineManagement, MSOnline | |
<# | |
.SYNOPSIS | |
Deploying Exchange Online security and spoofing warnings rules | |
.DESCRIPTION | |
Deploying Exchange Online security and spoofing warnings rules | |
.EXAMPLE | |
PS C:\> .\invoke-ExchangeSafetyRules.ps1 | |
.LINK | |
https://www.cyberdrain.com/automating-with-powershell-deploying-spoofing-warnings/ | |
.NOTES | |
Based on the great work of CYBERDRAIN.COM (Kelvin Tegelaar) | |
#> | |
[CmdletBinding(ConfirmImpact = 'None')] | |
param () | |
# Connect to the online services | |
<# | |
Look at the solution of CYBERDRAIN: https://www.cyberdrain.com/automating-with-powershell-deploying-spoofing-warnings/ | |
You can use an Azure App to connect. | |
#> | |
Connect-ExchangeOnline | |
Connect-MsolService | |
# Get the InitialDomain | |
$InitialDomain = (Get-MsolCompanyInformation | Select-Object -ExpandProperty InitialDomain) | |
# Get the TenantID! Could be done with the AzureAD Module or by a complex filetr on the Domains as well | |
$paramInvokeWebRequest = @{ | |
UseBasicParsing = $true | |
Method = 'Get' | |
Uri = ('https://login.windows.net/' + $InitialDomain + '/.well-known/openid-configuration') | |
} | |
$TargetTenantID = ((Invoke-WebRequest @paramInvokeWebRequest | ConvertFrom-Json).token_endpoint.Split('/')[3]) | |
#region Rule setup | |
<# | |
Please take a closer look here | |
#> | |
$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.' | |
} | |
#endregion Rule setup | |
#region HelperFunction | |
function Get-TypoSquatDomain | |
{ | |
<# | |
.SYNOPSIS | |
Get a fuzy naming variation for a given domain | |
.DESCRIPTION | |
Get a fuzy naming variation for a given domain | |
.PARAMETER DomainName | |
Top Level Domain | |
e.g. contoso.com or contoso.net | |
.EXAMPLE | |
PS C:\> Get-TypoSquatDomain -DomainName 'contoso.com' | |
.LINK | |
https://www.cyberdrain.com/automating-with-powershell-deploying-spoofing-warnings/ | |
.NOTES | |
Internal Helper | |
Based on the great work of CYBERDRAIN.COM (Kelvin Tegelaar) | |
I renamed it | |
#> | |
[CmdletBinding(ConfirmImpact = 'None')] | |
[OutputType([string])] | |
param | |
( | |
[Parameter(Mandatory, | |
ValueFromPipeline, | |
ValueFromPipelineByPropertyName, | |
HelpMessage = 'Top Level Domain')] | |
[ValidateNotNullOrEmpty()] | |
[ValidateNotNull()] | |
[Alias('Domain')] | |
[string] | |
$DomainName | |
) | |
begin | |
{ | |
$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 | |
} | |
process | |
{ | |
$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 -Process { | |
"$($_).$($TLD)" | |
} | |
} | |
end | |
{ | |
return ($CombinedDomains | Sort-Object -Unique | Where-Object -FilterScript { | |
$_ -ne $DomainName | |
}) | |
} | |
} | |
#endregion HelperFunction | |
foreach ($Rule in $Rules) | |
{ | |
$Reason = $Rule.RuleWarning | |
# Also something you might want to tweak to fit your CI or security requirements | |
$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> | |
"@ | |
# Try to figure out if the rule exists | |
$ExistingRule = (Get-TransportRule | Where-Object -FilterScript { | |
$_.Identity -contains $Rule.RuleName | |
}) | |
if ($ExistingRule) | |
{ | |
# Modify the existing rule(s)! | |
switch ($Rule.rulename) | |
{ | |
'SPF Fail' | |
{ | |
$paramSetTransportRule = @{ | |
Identity = $Rule.RuleName | |
Priority = 0 | |
FromScope = 'NotInOrganization' | |
ApplyHtmlDisclaimerLocation = 'Prepend' | |
HeaderMatchesMessageHeader = 'Authentication-Results' | |
HeaderMatchesPatterns = 'fail' | |
SenderAddressLocation = 'HeaderOrEnvelope' | |
ApplyHtmlDisclaimerText = $WarningHTML | |
} | |
$null = (Set-TransportRule @paramSetTransportRule) | |
} | |
'Displayname Spoofing' | |
{ | |
$AllNames = ((Get-Mailbox -ResultSize Unlimited).DisplayName) | |
$paramSetTransportRule = @{ | |
Identity = $Rule.RuleName | |
Priority = 0 | |
FromScope = 'NotInOrganization' | |
ApplyHtmlDisclaimerLocation = 'Prepend' | |
HeaderMatchesMessageHeader = 'From' | |
HeaderMatchesPatterns = $AllNames | |
SenderAddressLocation = 'HeaderOrEnvelope' | |
ApplyHtmlDisclaimerText = $WarningHTML | |
} | |
$null = (Set-TransportRule @paramSetTransportRule) | |
} | |
'Typosquatted Domains' | |
{ | |
#TODO: This need some care! The list might get to big and creates an error. | |
<# | |
A plit based on each domain with a dedicated rule will come soon! | |
#> | |
$OwnedDomains = (Get-MsolDomain -TenantId $TargetTenantID) | |
$TypoSquattedDomains = foreach ($Domain in $OwnedDomains) | |
{ | |
Get-TypoSquatDomain -DomainName $Domain.name | |
} | |
$paramSetTransportRule = @{ | |
Identity = $Rule.RuleName | |
Priority = 0 | |
FromScope = 'NotInOrganization' | |
ApplyHtmlDisclaimerLocation = 'Prepend' | |
SenderDomainIs = $TypoSquattedDomains | |
SenderAddressLocation = 'HeaderOrEnvelope' | |
ApplyHtmlDisclaimerText = $WarningHTML | |
} | |
$null = (Set-TransportRule @paramSetTransportRule) | |
} | |
} | |
} | |
else | |
{ | |
# Create the rule(s)! | |
switch ($Rule.rulename) | |
{ | |
'SPF Fail' | |
{ | |
$paramNewTransportRule = @{ | |
Name = $Rule.RuleName | |
Priority = 0 | |
FromScope = 'NotInOrganization' | |
ApplyHtmlDisclaimerLocation = 'Prepend' | |
HeaderMatchesMessageHeader = 'Authentication-Results' | |
HeaderMatchesPatterns = 'fail' | |
SenderAddressLocation = 'HeaderOrEnvelope' | |
ApplyHtmlDisclaimerText = $WarningHTML | |
} | |
$null = (New-TransportRule @paramNewTransportRule) | |
} | |
'Displayname Spoofing' | |
{ | |
$AllNames = (Get-Mailbox -ResultSize Unlimited).DisplayName | |
$paramNewTransportRule = @{ | |
Name = $Rule.RuleName | |
Priority = 0 | |
FromScope = 'NotInOrganization' | |
ApplyHtmlDisclaimerLocation = 'Prepend' | |
HeaderMatchesMessageHeader = 'From' | |
HeaderMatchesPatterns = $AllNames | |
SenderAddressLocation = 'HeaderOrEnvelope' | |
ApplyHtmlDisclaimerText = $WarningHTML | |
} | |
$null = (New-TransportRule @paramNewTransportRule) | |
} | |
'Typosquatted Domains' | |
{ | |
#TODO: This need some care! The list might get to big and creates an error. | |
<# | |
A plit based on each domain with a dedicated rule will come soon! | |
#> | |
$OwnedDomains = (Get-AcceptedDomain) | |
$TypoSquattedDomains = foreach ($Domain in $OwnedDomains) | |
{ | |
Get-TypoSquatDomain -DomainName $Domain.Domainname | |
} | |
$paramNewTransportRule = @{ | |
Name = $Rule.RuleName | |
Priority = 0 | |
FromScope = 'NotInOrganization' | |
ApplyHtmlDisclaimerLocation = 'Prepend' | |
SenderDomainIs = $TypoSquattedDomains | |
SenderAddressLocation = 'HeaderOrEnvelope' | |
ApplyHtmlDisclaimerText = $WarningHTML | |
} | |
$null = (New-TransportRule @paramNewTransportRule) | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment