Skip to content

Instantly share code, notes, and snippets.

@nickadam
Last active January 26, 2023 23:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nickadam/64f8bf5578adc6c38b2af5022480ff50 to your computer and use it in GitHub Desktop.
Save nickadam/64f8bf5578adc6c38b2af5022480ff50 to your computer and use it in GitHub Desktop.
Send breach notifications to AD Users that have not changed their password since a breach was reported
function Find-EnabledADUser(){
<#
.SYNOPSIS
Finds a single enabled AD user for a given email address
.DESCRIPTION
Searches through the default domain or each domain given in ADDomains for a single enabled user. The user could match on EmailAddress ProxyAddresses or SamAccountName. Warns if no results or more that one result per email address.
.EXAMPLE
Find-EnabledADUser -ADDomains "example.com", "child.example.com" -Email "bob@example.com"
#>
Param(
[Parameter(
Mandatory=$True,
HelpMessage='The email address: bob@example.com')]
$Email,
[Parameter(
HelpMessage='Optional list of domains to search: "example.com", "child.example.com"')]
$ADDomains,
[Parameter(
HelpMessage='Optional list of properties to return from AD')]
$Properties = @("PasswordLastSet","EmailAddress"))
$ProxyAddress = "smtp:" + $Email
$Username = ($Email -split "@")[0]
$Filter = {(Enabled -eq $True) -and ((ProxyAddresses -eq $ProxyAddress) -or (SamAccountName -eq $Username) -or (EmailAddress -eq $Email))}
if($ADDomains){
$Results = $ADDomains | ForEach {
$ADDomain = $_
Get-ADUser -Server $ADDomain -Filter $Filter -Properties $Properties
}
}else{
$Results = Get-ADUser -Filter $Filter -Properties $Properties
}
$ResultCount = ($Results | Measure).Count
if($ResultCount -eq 0){
Write-Warning ($Email + " not found")
}
if($ResultCount -gt 1){
Write-Warning ("More than one result for " + $Email + ": " + (($Results).DistinguishedName | ConvertTo-Json -Compress))
}
if(($ResultCount -eq 1) -and (-not($Results.EmailAddress))){
Write-Warning ($Email + " does not have an email address in AD")
}
if(($ResultCount -eq 1) -and $Results.EmailAddress){
$Results
}
}
function Send-BreachNotifications(){
<#
.SYNOPSIS
Sends breach notification emails to enabled AD users that haven't changed their password since the breach
.DESCRIPTION
Accepts a piped list of breach details, finds associated enabled AD accounts, and sends a breach notification email if the password hasn't been changed since the breach date.DESCRIPTION
Input object must at least have the fields: email, breach_added_date, and breach_description. For example:
{
"email": "bob@example.com",
"breach_added_date": "2019-11-22T20:13:04Z",
"breach_description": "Something bad happened",
}
EmailTemplate is a plain text string that can include strings that will be replaced with the values from AD or the breach report. The values that can be substituted are:
_FIRSTNAME_
_LASTNAME_
_BREACHCOUNT_
_BREACHDESCRIPTIONS_
For example:
$EmailTemplate = @"
Dear _FIRSTNAME_ _LASTNAME_,
Your information has been reported in one or more breaches and your password has not changed since the date the breach was reported. Please change your password as soon as possible.
Number of breaches: _BREACHCOUNT_
Breach Descriptions:
_BREACHDESCRIPTIONS_
Please let us know if you have any questions or concerns.
Example Security Team
"@
.EXAMPLE
Import-Csv hibp_report.csv | Send-BreachNotifications -From "security_team@example.com" -Subject "Your password may be compromised" -EmailTemplate $MyTemplate -SmtpServer exchange.example.com -WhatIf
#>
[CmdletBinding(SupportsShouldProcess)]
param(
[Parameter(
Mandatory=$True,
ValueFromPipeline=$true)]
$Breaches,
[Parameter(
Mandatory=$True)]
$From,
[Parameter(
Mandatory=$True)]
$Subject,
[Parameter(
Mandatory=$True)]
$EmailTemplate,
[Parameter(
Mandatory=$True)]
$SmtpServer,
[Parameter(
HelpMessage='Optional list of domains to search: "example.com", "child.example.com"')]
$ADDomains,
[Parameter(
HelpMessage='Optional email priority: {Normal | Low | High}')]
$Priority = "Normal"
)
Begin{
$BreachUsers = @{}
}
Process{
# Create a list of breaches per AD user
$Breaches | ForEach {
$UserBreach = $_
if($ADDomains){
$User = Find-EnabledADUser -Email $UserBreach.email -ADDomains $ADDomains
}else{
$User = Find-EnabledADUser -Email $UserBreach.email
}
# Skip if unique user not found
# Skip if password changed after breach date
if($User -and ((Get-Date $UserBreach.breach_added_date) -gt $User.PasswordLastSet)){
if(-not ($BreachUsers[$User.DistinguishedName])){
$BreachUsers[$User.DistinguishedName] = @{
"User" = $User
"Breaches" = @()
}
}
$BreachUsers[$User.DistinguishedName]["Breaches"] += $UserBreach
}
}
}
End {
$BreachUsers.Keys | Foreach {
$dn = $_
$User = $BreachUsers[$dn]["User"]
$Breaches = $BreachUsers[$dn]["Breaches"]
$FirstName = $User.GivenName
$LastName = $User.Surname
$BreachCount = ($Breaches | Measure).Count
$BreachDescriptions += ($Breaches).breach_description
$Body = $EmailTemplate
$Body = $Body -Replace "_FIRSTNAME_", $FirstName
$Body = $Body -Replace "_LASTNAME_", $LastName
$Body = $Body -Replace "_BREACHCOUNT_", $BreachCount
$Body = $Body -Replace "_BREACHDESCRIPTIONS_", $BreachDescriptions
if($PSCmdlet.ShouldProcess(("To: " + $User.EmailAddress + ", Body: " + $Body), $User.EmailAddress, "Send-MailMessage")){
Send-MailMessage -To $User.EmailAddress -From $From -Subject $Subject -Body $Body -Priority $Priority -SmtpServer $SmtpServer
}
}
}
}
@nickadam
Copy link
Author

nickadam commented Nov 30, 2021

How-To

Import and test with -WhatIf

. .\BreachNotificationModule.ps1
$EmailTemplate = @"
Dear _FIRSTNAME_ _LASTNAME_,

Your information has been reported in one or more breaches and your password has not changed since the date the
breach was reported. Please change your password as soon as possible.

Number of breaches: _BREACHCOUNT_
Breach Descriptions:

_BREACHDESCRIPTIONS_

Please let us know if you have any questions or concerns.

Example Security Team
"@
Import-Csv .\breach_report_sample.csv | Send-BreachNotifications -ADDomains "example.com", "child.example.com" -From "securityteam@example.com" -Subject "Your password may be compromised" -EmailTemplate $EmailTemplate -SmtpServer "exchange.example.com" -WhatIf

If the output messages look good run without -WhatIf to send messages

Import-Csv .\breach_report_sample.csv | Send-BreachNotifications -ADDomains "example.com", "child.example.com" -From "securityteam@example.com" -Subject "Your password may be compromised" -EmailTemplate $EmailTemplate -SmtpServer "exchange.example.com"

If you wanted to exclude email addresses that match a pattern, use pipes

# excludes email addresses that are just numbers before the @ symbol
Import-Csv .\breach_report_sample.csv | Where{$_.email -notmatch "^[0-9]+@"} | Send-BreachNotifications -ADDomains "example.com", "child.example.com" -From "securityteam@example.com" -Subject "Your password may be compromised" -EmailTemplate $EmailTemplate -SmtpServer "exchange.example.com"

More help

. .\BreachNotificationModule.ps1
help Find-EnabledADUser
help Send-BreachNotifications

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment