Scroll Top
Evotec Services sp. z o.o., ul. Drozdów 6, Mikołów, 43-190, Poland

Creating Visual Indicators for spoofed / external emails with PowerShell

img_5c9935e34c95f

I've been managing mail service for users for a lot of years now. I don't do it daily but I've spent my fair share of time analyzing spam emails. Mail vendors are doing what they can fighting spam, but it's not easy. Each month, each year spam is getting more sophisticated. Spam emails either look like a legit email, or worse someone is targeting your company trying to get them to transfer money into a wrong account. While most of those end up in spam, there are those that come thru. It's even worse if the company you work with has not implemented SPF or their SPF is configured to soft fail which can't be treated as spam.

Visual Indicators

So what can you do? Well, you can help your users distinguishing internal, trusted emails from external emails, coming from outside sources. You can help them by adding visual indicators to emails which will help them asses something came from an external server or not. Of course, users should be always aware of what they do, not send money to wrong accounts but I've seen well-planned scams including proper SPF and email trail but with typos in the domain. Of course, there is a mechanism that can help you with that in Office 365, but it's not available for everyone, and sometimes it may not be enough. What are the visual indicators? Basically a message that is added to email at the top of it.

That's about it. You will either add just one message or both messages depending on whether the user is coming from outside or SPF is non-existent or failing otherwise. And if it weren't for those messages, some emails would surely look like legit internal messages. Just two days after adding those visual indicators one of my Clients received an email that looked like internal email. Domains were a match, and the only thing that didn't fit was a small typo on senders name inside an email, but it looked so real on both Outlook for Mobile and Outlook on Desktop that if it wasn't for the indicator, the problem could have been much worse.

Of course, I've removed Clients data, but you have to trust me on that. It was showing proper domains and all that. Even SPF was correct, but not for Client's domain, just the sending one. That's why you can only see one visual indicator on the screenshot. Boy, I was happy we'd implemented this. It is highly recommended.

Visual Indicators - Added value

I wouldn't be myself if I wouldn't add something from myself. You know, when you talk to your management about implementing something as simple as visual indicators there are (or at least should be) questions on how it will affect our brand, visually impacting how our Customers see us and how we see them. For that, I've prepared a little script. This script below is based on PSWriteHTML PowerShell Module that I've continued working on. While it's quite easy to get the text to be added as a visual indicator from your Manager things get complicated when you ask them about Colors. I, myself spend a lot of times playing with colors and it's real pain for me. Half of the time I am not sure which color will look good with another color making me go crazy. So… below, you get just that. It creates 2 files. One simple file with predefined colors, as below:

One a bit more complicated, with just 21000 colors. You see the visual indicator has four types of colors: Text, TextHeading, BorderColor, BackgroundColor. Initially, I thought it would be a good idea to have all those four types do color matching and see how it would look like, but then I did some math, and my concept died a bit. You see, my little colors library has only 147 named colors if you try to apply 147 colors per each type that gives you 466948881  (466 million of colors) colors.  Even if I would leave PowerShell up and running for a few hours that would give me a large HTML file with output nobody would want to process with their own eyes to decide what is matching what (visually). Trying just three types gives you only 3176523 (3 million options), and while that seemed like I could probably make it, I doubt anyone wants to go thru that. So I only settled for 21609 possibilities. I decided that the border will be White and text color will be Black.  While I can see you complaining, you're welcome to try it yourself and change the colors as you want them!

The file itself is 9MB of size when generated. On my computer, it took about 30 seconds to get this done. You can generate it on your own from the code below or you can use the one I generated on my computer from PSWriteHTML GitHub Examples.

Install-Module PSWriteHTML -Force

Keep in mind you need PSWriteHTML for things to work. While you don't need it overall the generation part I created is using this little project of mine.

function Get-VisualIndicator {
    [CmdLetBinding()]
    param(
        [string] $TextHeading,
        [string] $Text,
        [RGBColors] $Color = [RGBColors]::Black,
        [RGBColors] $ColorBackground = [RGBColors]::DarkOrange,
        [RGBColors] $ColorBorder = [RGBColors]::White,
        [RGBColors] $ColorHeading = [RGBColors]::Black,
        [switch] $TextHeadingBold,
        [switch] $TextBold,
        [switch] $SkipNewLine,
        [switch] $Simplify
    )

    $RGBColorText = ConvertFrom-Color -Color $Color
    $RGBColorBorder = ConvertFrom-Color -Color $ColorBorder
    $RGBColorBackground = ConvertFrom-Color -Color $ColorBackground
    $RGBColorHeading = ConvertFrom-Color -Color $ColorHeading

    [string] $HTMLOutput = @(
        if ($Simplify) {

            $AttributesDiv = @{
                'style' = @{
                    'width'       = '100%'
                    'padding'     = '2pt'
                    'font-size'   = '11pt'
                    'line-height' = '12pt'
                    'font-family' = 'Calibri Light'
                    'color'       = $RGBColorText
                    'text-align'  = 'left'
                    'font-weight' = if ($TextBold) { 'bold' }
                }
            }

            $AttributesSpanHeading = @{
                'style' = @{
                    'color'       = $RGBColorHeading
                    'font-weight' = if ($TextHeadingBold) { 'bold' }
                }
            }

            New-HTMLTag -Tag 'div' -Attributes $AttributesDiv {
                New-HTMLTag -Tag 'span' -Attributes $AttributesSpanHeading {
                    $TextHeading
                }
                $Text
            }
            if (-not $SkipNewLine) {
                New-HTMLTag -Tag 'br' -SelfClosing
            }
        } else {

            $AttributesDiv = @{
                'style' = @{
                    'background-color' = $RGBColorBackground
                    'width'            = '100%'
                    'border-style'     = 'solid'
                    'border-color'     = $RGBColorBorder
                    'border-width'     = '1pt'
                    'padding'          = '2pt'
                    'font-size'        = '11pt'
                    'line-height'      = '12pt'
                    'font-family'      = 'Calibri Light'
                    'color'            = $RGBColorText
                    'text-align'       = 'left'
                    'font-weight'      = if ($TextBold) { 'bold' }
                }
            }

            $AttributesSpanHeading = @{
                'style' = @{
                    'color'       = $RGBColorHeading
                    'font-weight' = if ($TextHeadingBold) { 'bold' }
                }
            }

            New-HTMLTag -Tag 'div' -Attributes $AttributesDiv {
                New-HTMLTag -Tag 'span' -Attributes $AttributesSpanHeading {
                    $TextHeading
                }
                $Text

            }
            if (-not $SkipNewLine) {
                New-HTMLTag -Tag 'br' -SelfClosing
            }
        }
    )
    return $HTMLOutput
}

Import-Module PSWriteHTML

$Output = @(
    Get-VisualIndicator -TextHeading "WARNING:" -Text "The sender of this message could not be fully validated. The message may not be from the sender/domain displayed." -ColorBackground Orange
    Get-VisualIndicator -TextHeading 'Caution:' -Text 'This email originated from outside the organisation. Please be mindful when opening attachments and embedded links.' -ColorBackground Red

    Get-VisualIndicator -TextHeading "WARNING:" -Text "The sender of this message could not be fully validated. The message may not be from the sender/domain displayed." -ColorBackground Orange
    Get-VisualIndicator -TextHeading 'Caution:' -Text 'This email originated from outside the organisation. Please be mindful when opening attachments and embedded links.' -ColorBackground Red

    Get-VisualIndicator -TextHeading "WARNING:" -Text "The sender of this message could not be fully validated. The message may not be from the sender/domain displayed." -Simplify -Color Red -ColorHeading Red
    Get-VisualIndicator -TextHeading 'Caution:' -Text 'This email originated from outside the organisation. Please be mindful when opening attachments and embedded links.' -Simplify -Color Orange -ColorHeading Orange

    Get-VisualIndicator -TextHeading "WARNING:" -Text "The sender of this message could not be fully validated. The message may not be from the sender/domain displayed." -Simplify -Color Red -ColorHeading Red -TextHeadingBold
    Get-VisualIndicator -TextHeading 'Caution:' -Text 'This email originated from outside the organisation. Please be mindful when opening attachments and embedded links.' -Simplify -Color Orange -ColorHeading Orange -TextHeadingBold

    Get-VisualIndicator -TextHeading "WARNING:" -Text "The sender of this message could not be fully validated. The message may not be from the sender/domain displayed." -Simplify -Color Red -ColorHeading Red -TextHeadingBold -TextBold
    Get-VisualIndicator -TextHeading 'Caution:' -Text 'This email originated from outside the organisation. Please be mindful when opening attachments and embedded links.' -Simplify -Color Orange -ColorHeading Orange -TextHeadingBold -TextBold
)
Save-HTML -FilePath $PSScriptRoot\TestColors.html -HTML $Output -ShowHTML

$Output = foreach ($Color1 in [RGBColors].GetEnumNames()) {
    foreach ($Color2 in [RGBColors].GetEnumNames()) {
        $RGB1 = ConvertFrom-Color -Color $Color1
        $RGB2 = ConvertFrom-Color -Color $Color2
        Get-VisualIndicator -SkipNewLine -TextHeading "WARNING: (Color Heading: $RGB1 ($Color1)" -Text "The sender of this message could not be fully validated. The message may not be from the sender/domain displayed. (ColorBackground $RGB2 ($Color2))" -ColorHeading $Color1 -ColorBackground $Color2
    }
}
Save-HTML -FilePath $PSScriptRoot\TestColors147x2.html -HTML $Output -ShowHTML

<# Uncomment below if you dare - 147 x 147 x 147 colors.
$Time = Start-TimeLog
$Output = foreach ($Color1 in [RGBColors].GetEnumNames()) {
    foreach ($Color2 in [RGBColors].GetEnumNames()) {
        foreach ($Color3 in [RGBColors].GetEnumNames()) {
            $RGB1 = ConvertFrom-Color -Color $Color1
            $RGB2 = ConvertFrom-Color -Color $Color2
            $RGB3 = ConvertFrom-Color -Color $Color3
            Get-VisualIndicator -SkipNewLine -TextHeading "WARNING:" -Text "The sender of this message could not be fully validated. The message may not be from the sender/domain displayed. (Color Heading: $RGB1 ($Color1), ColorBackground $RGB2 ($Color2), TextColor $RBG3 ($Color3))" -ColorHeading $RGB1 -ColorBackground $RGB2 -Color $RGB3
        }
    }
}

Save-HTML -FilePath $PSScriptRoot\TestColors147x3.html -HTML $Output #-ShowHTML
Stop-TimeLog -Time $Time -Option OneLiner
#>

For those, brave enough I've left commented out code for the 3 million options. Take your shot!

Visual Indicators - Adding transport rules

Now that you have your colors up and ready, text agreed with management you can add your rules. This is done by modifying Transport Rules in Exchange. We have two rules to use. The first rule is about delivering information to users that emails are sent from outside. This should be a general warning that if the email has this visual indicator, it comes from outside and if someone says he's your CEO you should throw it away. The second rule is checking SPF. It is supposed to add a warning that sender doesn't have an SPF defined or that it's poorly configured and your mail server is unable to tell you if the person sending this email is this person. Keep in mind that it's possible and you will see this frequently where there are two rules triggered, and you get two warnings in the same email. That is expected.

function Get-VisualIndicator {
    [CmdLetBinding()]
    param(
        [string] $TextHeading,
        [string] $Text,
        [RGBColors] $Color = [RGBColors]::Black,
        [RGBColors] $ColorBackground = [RGBColors]::DarkOrange,
        [RGBColors] $ColorBorder = [RGBColors]::White,
        [RGBColors] $ColorHeading = [RGBColors]::Black,
        [switch] $TextHeadingBold,
        [switch] $TextBold,
        [switch] $SkipNewLine,
        [switch] $Simplify
    )

    $RGBColorText = ConvertFrom-Color -Color $Color
    $RGBColorBorder = ConvertFrom-Color -Color $ColorBorder
    $RGBColorBackground = ConvertFrom-Color -Color $ColorBackground
    $RGBColorHeading = ConvertFrom-Color -Color $ColorHeading

    [string] $HTMLOutput = @(
        if ($Simplify) {

            $AttributesDiv = @{
                'style' = @{
                    'width'       = '100%'
                    'padding'     = '2pt'
                    'font-size'   = '11pt'
                    'line-height' = '12pt'
                    'font-family' = 'Calibri Light'
                    'color'       = $RGBColorText
                    'text-align'  = 'left'
                    'font-weight' = if ($TextBold) { 'bold' }
                }
            }

            $AttributesSpanHeading = @{
                'style' = @{
                    'color'       = $RGBColorHeading
                    'font-weight' = if ($TextHeadingBold) { 'bold' }
                }
            }

            New-HTMLTag -Tag 'div' -Attributes $AttributesDiv {
                New-HTMLTag -Tag 'span' -Attributes $AttributesSpanHeading {
                    $TextHeading
                }
                $Text
            }
            if (-not $SkipNewLine) {
                New-HTMLTag -Tag 'br' -SelfClosing
            }
        } else {

            $AttributesDiv = @{
                'style' = @{
                    'background-color' = $RGBColorBackground
                    'width'            = '100%'
                    'border-style'     = 'solid'
                    'border-color'     = $RGBColorBorder
                    'border-width'     = '1pt'
                    'padding'          = '2pt'
                    'font-size'        = '11pt'
                    'line-height'      = '12pt'
                    'font-family'      = 'Calibri Light'
                    'color'            = $RGBColorText
                    'text-align'       = 'left'
                    'font-weight'      = if ($TextBold) { 'bold' }
                }
            }

            $AttributesSpanHeading = @{
                'style' = @{
                    'color'       = $RGBColorHeading
                    'font-weight' = if ($TextHeadingBold) { 'bold' }
                }
            }

            New-HTMLTag -Tag 'div' -Attributes $AttributesDiv {
                New-HTMLTag -Tag 'span' -Attributes $AttributesSpanHeading {
                    $TextHeading
                }
                $Text

            }
            if (-not $SkipNewLine) {
                New-HTMLTag -Tag 'br' -SelfClosing
            }
        }
    )
    return $HTMLOutput
}


# Define rules
$TransportRulesNames = "Visual Cue - External to Organization", "Visual Cue - No SPF Validation"

# Get current rules and if those above exists remove them (useful for rerunning script)
$Rules = foreach ($Rule in $TransportRulesNames) {
    $Output = Get-TransportRule -Identity $Rule
    if ($null -ne $Output) {
        Remove-TransportRule -Identity $Rule -Confirm:$false #-WhatIf
    }
}
# Definitions of texts - first one is standard outside of organization
$TextCaution = 'This email originated from outside the organisation. Please be mindful when opening attachments and embedded links.'
$HeadingCaution = 'Caution:'
$TrustDomains = 'evotec.xyz', 'evotec.pl'

$MessageExternalOrganization = Get-VisualIndicator -TextHeading $HeadingCaution -Text $TextCaution -Simplify -Color Orange -ColorHeading Orange -TextHeadingBold

New-TransportRule -Name "Visual Cue - External to Organization" -Priority 0 `
    -FromScope "NotInOrganization" `
    -ApplyHtmlDisclaimerLocation "Prepend" `
    -ApplyHtmlDisclaimerText $MessageExternalOrganization -ExceptIfSenderDomainIs $TrustDomains #-WhatIf


# Definitions of texts - second one is lack of SPF or SPF is bad
$TextWarning = 'The sender of this message could not be fully validated. The message may not be from the sender/domain displayed.'
$HeadingWarning = 'WARNING:'

$MessageSPFNotValidated = Get-VisualIndicator -TextHeading $HeadingWarning -Text $TextWarning -Simplify -Color Red -ColorHeading Red -TextHeadingBold

New-TransportRule -Name "Visual Cue - No SPF Validation" -Priority 1 `
    -HeaderContainsMessageHeader "Authentication-Results" `
    -HeaderContainsWords "spf=TempError", "spf=PermError", "spf=None", "spf=Neutral", "spf=SoftFail", "spf=Fail" `
    -ApplyHtmlDisclaimerLocation "Prepend" `
    -ApplyHtmlDisclaimerText $MessageSPFNotValidated #-WhatIf

Please notice that I've also added a variable TrustedDomains. I'm using this variable since you may want to whitelist some of your close customers, your helpdesk and some other domains you feel safe with. At the same time, you should warn users that just because there is no notification about being sent from outside doesn't mean it's good email. You should only do this if you trust that sender and it has proper SPF in place. If someone is pretending to be your partner they will most likely be caught by SPF rule; therefore even thou first rule whitelisted email, there is no whitelist for the second rule. While you can always add that exception, I would recommend against it.

Visual Indicators - Adding transport rules

You can always confirm added rules, change them or modify when needed.  You can also apply exceptions, disable or remove those rules if required. Be careful thou. Enabling, editing and deleting takes time. It may take 15-60 minutes for things to get applied.

This is how Visual Cue – External to Organization rule looks like when created. Please notice I've added two domain exceptions. Adding domains here is risky. After all, someone not only could spoof your domain but the domain of your Clients. While it's nice not to get warnings people may get too trustful if a warning isn't there.

The Visual Cue – No SPF Validation is a bit different. It focuses mostly on delivering information about missing or bad SPF.

Related Posts