Email security & monitoring
Dark Web Breach Check (HIBP)
Checks every user in a Microsoft 365 tenant against Have I Been Pwned and produces a searchable HTML plus CSV breach report. Needs a paid HIBP API key, and uses the legacy AzureAD module (being retired).
Adapted from: Adapted from CyberDrain (Kelvin Tegelaar) ↗
Replace generic placeholder values (tenant, domain, secrets) for your own environment before running. Read it first and test safely.
<#
.SYNOPSIS
Checks every user in a Microsoft 365 / Entra tenant against Have I Been Pwned
and produces an HTML + CSV "breach logbook".
.DESCRIPTION
Connects to Entra ID (AzureAD module), enumerates user email addresses, queries
the Have I Been Pwned API for each, and writes a searchable HTML report plus a CSV.
Requires:
- The AzureAD PowerShell module (Install-Module AzureAD)
- A paid Have I Been Pwned API key (https://haveibeenpwned.com/API/Key)
.NOTES
ATTRIBUTION: adapted from Kelvin Tegelaar's public breach-monitoring script at
CyberDrain.com. Original author retains copyright; used/adapted with credit.
Check the original's licence/terms before redistributing.
SANITISED FOR PUBLIC RELEASE: the hardcoded HIBP API key was removed. Supply
your own key in the config block below (better: pull it from a secret store).
#>
function Get-BreachInfo {
[CmdletBinding()]
Param(
[Parameter(Mandatory = $true, ValueFromPipeline = $true)]$EmailAddress,
[Parameter(Mandatory = $true)]$HaveIBeenPwnedKey,
[Parameter(Mandatory = $true)]$Outputfile
)
# HTML/JS for a searchable report table
$head = @"
<script>
function myFunction() {
const filter = document.querySelector('#myInput').value.toUpperCase();
const trs = document.querySelectorAll('table tr:not(.header)');
trs.forEach(tr => tr.style.display = [...tr.children].find(td => td.innerHTML.toUpperCase().includes(filter)) ? '' : 'none');
}</script>
<Title>Dark Web / Breach Report</Title>
<style>
body { background-color:#E5E4E2; font-family:Monospace; font-size:10pt; }
td, th { border:0px solid black; border-collapse:collapse; white-space:pre; }
th { color:white; background-color:black; }
table, tr, td, th { padding: 2px; margin: 0px; white-space:pre; }
tr:nth-child(odd) {background-color: lightgray}
table { width:95%;margin-left:5px; margin-bottom:20px; }
h2 { font-family:Tahoma; color:#6D7B8D; }
.footer { color:green; margin-left:10px; font-family:Tahoma; font-size:8pt; font-style:italic; }
#myInput {
background-position: 10px 12px; background-repeat: no-repeat; width: 50%;
font-size: 16px; padding: 12px 20px 12px 40px; border: 1px solid #ddd; margin-bottom: 12px;
}
</style>
"@
$PreContent = @"
<H1> Breach logbook</H1> <br>
This log contains all breaches found for the e-mail addresses in your Microsoft tenant. Use the search to find specific addresses.
<br/><br/>
<input type="text" id="myInput" onkeyup="myFunction()" placeholder="Search...">
"@
# HIBP requires the key in the 'hibp-api-key' header
$HIBPHeader = @{ 'hibp-api-key' = $HaveIBeenPwnedKey }
Write-Host " Retrieving Breach Info" -ForegroundColor Green
$UserList = $EmailAddress
$index = 0
$HIBPList = foreach ($User in $UserList) {
$index++
Write-Progress -Activity "Search in Progress" -Status "$([math]::Round($index/$UserList.count*100))% Complete:" -PercentComplete ($index/$UserList.count*100)
try {
$Breaches = $null
$Breaches = Invoke-RestMethod -Uri "https://haveibeenpwned.com/api/v3/breachedaccount/$($user)?truncateResponse=false" -Headers $HIBPHeader -UserAgent 'PowerShell Breach Script'
} catch {
if ($_.Exception.Response.StatusCode.value__ -eq '404') { } else { Write-Error "$($_.Exception.message)" }
}
Start-Sleep 7 # HIBP rate limit - do not lower without a higher-tier key
foreach ($Breach in $Breaches) {
[PSCustomObject]@{
Username = $user
'Name' = $Breach.name
'Domain name' = $breach.Domain
'Date' = $Breach.Breachdate
'Verified by experts' = if ($Breach.isverified) { 'Yes' } else { 'No' }
'Leaked data' = $Breach.DataClasses -join ', '
'Description' = $Breach.Description
}
}
}
$BreachListHTML = $HIBPList | ConvertTo-Html -Fragment -PreContent '<h2>Breaches</h2><br>A "breach" is an incident where data is inadvertently exposed. HIBP aggregates breaches so people can assess where their data has been exposed.<br>' | Out-String
$HIBPList | Export-Csv -Path "$OutputFile.csv" -NoTypeInformation
$head, $PreContent, [System.Web.HttpUtility]::HtmlDecode($BreachListHTML) | Out-File $Outputfile
Write-Progress -Activity "Search in Progress" -Completed
}
# ---------------------------------------------------------------------------
# Configure for your environment
# ---------------------------------------------------------------------------
$HibpApiKey = '<HAVEIBEENPWNED_API_KEY>' # your paid HIBP API key - do NOT hardcode a real one in a shared script
# Ensure the AzureAD module is present
$Modules = Get-Module -Name AzureAD -ListAvailable
if ($Modules.count -eq 0) {
Write-Host "Please install the AzureAD module (as admin): Install-Module -Name AzureAD" -ForegroundColor Yellow
Exit
}
Add-Type -AssemblyName System.Web
Connect-AzureAD
# All real user UPNs (skip the default onmicrosoft.com addresses)
$userlist = (Get-AzureADUser -All $true).UserPrincipalName | Where-Object { $_ -NotLike '*.onmicrosoft.com' }
$userlist.count
$OutputFile = "$($env:USERPROFILE)\Downloads\Breach Report $((Get-AzureADTenantDetail).DisplayName) $(Get-Date -Format 'yyyy-MM-dd').html"
Get-BreachInfo -EmailAddress $userlist -HaveIBeenPwnedKey $HibpApiKey -Outputfile $OutputFile
Invoke-Item $OutputFile