Checking Exchange Online Email Addresses to Make Sure They’re Not Compromised

Exchange Online Office 365

Email Scams for BitCoin

Last month, I posted a description of an email extortion scam to my personal blog. The scammer used my Gmail address, possibly as a result a gigantic leak of millions of In any case, the scam failed with me, even though there’s evidence that it has successfully relieved others of thousands of dollars.

Inevitably, thoughts turned to Office 365. I don’t use Office 365 email addresses to sign into other services, so the only way that a hacker could recover an address/password combination would be to penetrate Azure Active Directory. So far, there’s no evidence that any such attempt has been successful.

Pwned Office 365 Mailboxes

But others use their Office 365 email addresses to sign into other services and those services might be penetrated. The question therefore arises how to check addresses used by Office 365 mailboxes against the Have I been Pwned service maintained by security researcher and MVP Troy Hunt.

Commercial products like Quadrotech Nova include reports for compromised accounts, but a post by Elliott Munro caught my eye because it used PowerShell to check Office 365 tenant accounts.

Using PowerShell to Check the Pwned Database

The beauty of PowerShell is that you can take code and “improve” it. Beauty is in the eye of the beholder, and a PowerShell improvement is in the eye of a coder. I asked Elliott if I could make some changes to his code. He had done all the heavy lifting and I just wanted to tweak it a tad.

[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

$headers = @{
    "User-Agent"  = "$((Get-MsolCompanyInformation).DisplayName) Account Check"
    "api-version" = 2 }

$baseUri = ""

# To check for admin status
$RoleId = (Get-MsolRole -RoleName "Company Administrator").ObjectId
$Admins = (Get-MsolRoleMember -RoleObjectId $RoleId | Select EmailAddress)
$Report = @()

Write-Host "Fetching mailboxes to check..."
$Users = (Get-Mailbox -RecipientTypeDetails UserMailbox -ResultSize Unlimited | Select UserPrincipalName, EmailAddresses, DisplayName)
Write-Host "Processing" $Users.count "mailboxes..."
ForEach ($user in $users) {
    $Emails = $User.emailaddresses | Where-Object {$_ -match "smtp:" -and $_ -notmatch ""}
    $IsAdmin = $False
    $MFAUsed = $False
    $emails | ForEach-Object {
        $Email = ($_ -split ":")[1]
        $uriEncodeEmail = [uri]::EscapeDataString($Email)
        $uri = "$baseUri/breachedaccount/$uriEncodeEmail"
        $BreachResult = $null
        Try {
            [array]$breachResult = Invoke-RestMethod -Uri $uri -Headers $headers -ErrorAction SilentlyContinue
        Catch {
            if($error[0].Exception.response.StatusCode -match "NotFound"){
                Write-Host "No Breach detected for $email" 
                Write-Host "Cannot retrieve results due to rate limiting or suspect IP. You may need to try a different computer"
        if ($BreachResult) {
            $MSOUser = Get-MsolUser -UserPrincipalName $User.UserPrincipalName 
            If ($Admins -Match $User.UserPrincipalName) {$IsAdmin = $True} 
            If ($MSOUser.StrongAuthenticationMethods -ne $Null) {$MFAUsed = $True}
            ForEach ($Breach in $BreachResult) {
                 $ReportLine = [PSCustomObject][ordered]@{
                    Email              = $email
                    UserPrincipalName  = $User.UserPrincipalName
                    Name               = $User.DisplayName
                    LastPasswordChange = $MSOUser.LastPasswordChangeTimestamp
                    BreachName         = $breach.Name
                    BreachTitle        = $breach.Title
                    BreachDate         = $breach.BreachDate
                    BreachAdded        = $breach.AddedDate
                    BreachDescription  = $breach.Description
                    BreachDataClasses  = ($breach.dataclasses -join ", ")
                    IsVerified         = $breach.IsVerified
                    IsFabricated       = $breach.IsFabricated
                    IsActive           = $breach.IsActive
                    IsRetired          = $breach.IsRetired
                    IsSpamList         = $breach.IsSpamList
                    IsTenantAdmin      = $IsAdmin
                    MFAUsed            = $MFAUsed
                $Report += $ReportLine
                Write-Host "Breach detected for $email - $($" -ForegroundColor Red
                If ($IsAdmin -eq $True) {Write-Host "This is a tenant administrator account" -ForeGroundColor DarkRed}
                Write-Host $breach.Description -ForegroundColor Yellow
        Start-sleep -Milliseconds 2000
If ($Breaches -gt 0) {
    $Report | Export-CSV c:\temp\Breaches.csv -NoTypeInformation
    Write-Host "Total breaches found: " $Breaches " You can find a report in c:\temp\Breaches.csv" }
  { Write-Host "Hurray - no breaches found for your Office 365 mailboxes" }

What the Code Does

The code uses cmdlets in the Exchange Online and Microsoft Online Services modules to do the following.

  • Grabs a list of user mailboxes. Originally, the code used Azure Active Directory accounts, but unless you want to check for spam lists, there’s no real point including email addresses for shared mailboxes as they are never logged into. The same is true for guest accounts, service accounts, and so on. If you disagree, amend the Get-Mailbox command.
  • Checks the email addresses for each mailbox against the Have I been Pwned database to detect any breach.
  • If a breach is found, capture the details in an ordered array. I also added some checks to report whether the compromised account is a tenant administrator and if it’s enabled for multi-factor authentication (MFA). All Office 365 administrative accounts should be protected with MFA, which is easy to set up. All user accounts should be protected with MFA too, even if users don’t like it.
  • Write the results out to a CSV file and report the number of breaches found.

You could improve the code further. For instance, it would be easy to add a check for the password age and flag accounts that haven’t changed their password in years.

Whoops! One Compromised Account

When I ran the code, I only account in my tenant that was highlighted was my own. In this case, the problem was due to the 711 million record spambot incident from August 2017 (Figure 1).

Exchange Online email pwned
Figure 1: PowerShell finds a pwned email address (image credit: Tony Redmond)

After reading Troy Hunt’s analysis of spambot, I concluded that I didn’t have a problem. Someone included my email address in one of the sources that form the data; it’s only an email address and my account is protected by multi-factor authentication. I’ll leave Exchange Online Protection to deal with the spam sent to my address.

A Pwned Flow

For the record, I should point out that Microsoft has a Flow template to check an email address against HIBP. Apart from the fact that the flow relies on an RSS feed to tell it about new breaches (which might not affect your tenant), I prefer PowerShell in this instance. PowerShell can process thousands of mailboxes, each of which might have several proxy addresses, and can deal with other checks like those for administrator status and multi-factor authentication enablement. But if Flow’s your thing, you can do it that way too.

The Ongoing Struggle

The ongoing popularity of email makes it a continuing target for hackers and scammers. If it’s not Business Email Compromise attacks, it’ll be another attempt to make people do something they shouldn’t, like pay money after a threat. Given that is used by many scammers, I asked Microsoft if they could do something to make their consumer email platform a little less friendly to attackers. According to Greg Taylor, Director of Marketing for Exchange, Microsoft is aware of the new threats and is looking into how to suppress them. It would be good if Microsoft can do something to repel scammers, but given that the years-old Nigerian 419 fraud (you know, send me your details and I’ll send you $10 million I have in this account) continues to flourish, it’s a challenge to find a mechanism to stop scams while allowing legitimate business email to flow.

Going back to the point of this article, scripts like Elliott’s help the community by showing administrators how they can use publicly-available sources to check whether their tenants are compromised. It also underlines the value of PowerShell in filling gaps that Microsoft can’t ever hope to close in a suite like Office 365.

Follow Tony on Twitter @12Knocksinna.

Want to know more about how to manage Office 365? Find what you need to know in “Office 365 for IT Pros”, the most comprehensive eBook covering all aspects of Office 365. Available in PDF and EPUB formats (suitable for iBooks) or for Amazon Kindle.