Last active
January 26, 2023 23:18
-
-
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
This file contains 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
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 | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
How-To
Import and test with
-WhatIf
If the output messages look good run without
-WhatIf
to send messagesIf you wanted to exclude email addresses that match a pattern, use pipes
More help