UPDATE: I have posted the script to check against haveibeenpwned.com at the bottom in the TechNet Gallery. http://aka.ms/pwncheck
Yesterday, I participated in an escalation for a customer where one or more users had been successfully phished and had given up their credentials. While we were walking through some remediation steps, we started a discussion about data exfiltration attempts.
Many moons ago, I put together a few scripts that can be used to check mailbox forwarding and transport rule forwarding configurations, specifically looking for actions that send mail (forward, redirect, bcc) to recipients outside of the domains verified in your tenant. You can see those here:
- Audit Mailbox Rules (https://gallery.technet.microsoft.com/Audit-Mailbox-Rules-to-60710f28): The idea of this script is to check your user’s mailbox rules for actions that can relay mail to external users. It produces a report.
- Audit Transport Rules (https://gallery.technet.microsoft.com/Audit-Transport-Rules-to-1dd8acee): This does a similar thing, only checking transport rules for similar types of activities. And, what would an audit be without a report?
Those can be great for looking at the current state of things. One of the drawbacks of the Get-InboxRules cmdlet is that it doesn’t reveal when a rule was created.
If you have turned on all of your tenant auditing (which I definitely recommend you do), I’d recommend scouring the audit logs for entries regarding new rules as well. This may help you in pinpointing new activity or identifying further compromised accounts.
$RuleLogs = Search-UnifiedAuditLog -StartDate (Get-Date).AddDays(-90) -EndDate (Get-Date) -Operations @('New-InboxRule', 'Set-InboxRule') [array]$entries = @() foreach ($entry in $RuleLogs) { $entry | ` Select CreationDate, UserIds, Operations, @{ l = 'Rule'; ` e = { (($entry.AuditData | ConvertFrom-Json).Parameters | ? { $_.Name -eq "Name" }).Value } } | ` Export-Csv .\90DayRules.csv -append -notypeinformation }
The output is a simple CSV showing you the user date, user ID, Operation (New-InboxRule, Set-InboxRule) and what the name of the rule is.
I also put together another script that I’m still tidying up before I put it on the gallery. It uses the haveibeenpwned.com API to get a list of accounts whose addresses have shown up in various breach notifications. It’s a little rough at the moment, but you can use it against both Office 365 accounts and local Active Directory.
<# .SYNOPSIS Check accounts in Active Directory and Office 365 against haveibeenpwned.com database .PARAMETER ActiveDirectory Choose to run against Active Directory .PARAMETER BreachedAccountOutput CSV filename for any potentially breached accounts .PARAMETER IncludeGuests If querying Office 365, choose if you want to include external guests. Otherwise only objects with type MEMBER are selected. .PARAMETER InstallModules Choose if you want to install MSOnline and supporting modules. .PARAMETER Logfile Output log file name. .PARAMETER Office 365 Choose to run against Office 365. #> param ( # Credentials [System.Management.Automation.PSCredential]$Credential, [switch]$ActiveDirectory, [string]$BreachedAccountOutput = (Get-Date -Format yyyy-MM-dd) + "_BreachedAccounts.csv", [switch]$IncludeGuests, [switch]$InstallModules, [string]$Logfile = (Get-Date -Format yyyy-MM-dd) + "_pwncheck.txt", [switch]$Office365 ) ## Functions # Logging function function Write-Log([string[]]$Message, [string]$LogFile = $Script:LogFile, [switch]$ConsoleOutput, [ValidateSet("SUCCESS", "INFO", "WARN", "ERROR", "DEBUG")][string]$LogLevel) { $Message = $Message + $Input If (!$LogLevel) { $LogLevel = "INFO" } switch ($LogLevel) { SUCCESS { $Color = "Green" } INFO { $Color = "White" } WARN { $Color = "Yellow" } ERROR { $Color = "Red" } DEBUG { $Color = "Gray" } } if ($Message -ne $null -and $Message.Length -gt 0) { $TimeStamp = [System.DateTime]::Now.ToString("yyyy-MM-dd HH:mm:ss") if ($LogFile -ne $null -and $LogFile -ne [System.String]::Empty) { Out-File -Append -FilePath $LogFile -InputObject "[$TimeStamp] [$LogLevel] $Message" } if ($ConsoleOutput -eq $true) { Write-Host "[$TimeStamp] [$LogLevel] :: $Message" -ForegroundColor $Color } } } function MSOnline { Write-Log -LogFile $Logfile -LogLevel INFO -Message "Checking Microsoft Online Services Module." If (!(Get-Module -ListAvailable MSOnline -ea silentlycontinue) -and $InstallModules) { # Check if Elevated $wid = [system.security.principal.windowsidentity]::GetCurrent() $prp = New-Object System.Security.Principal.WindowsPrincipal($wid) $adm = [System.Security.Principal.WindowsBuiltInRole]::Administrator if ($prp.IsInRole($adm)) { Write-Log -LogFile $Logfile -LogLevel SUCCESS -ConsoleOutput -Message "Elevated PowerShell session detected. Continuing." } else { Write-Log -LogFile $Logfile -LogLevel ERROR -ConsoleOutput -Message "This application/script must be run in an elevated PowerShell window. Please launch an elevated session and try again." $ErrorCount++ Break } Write-Log -LogFile $Logfile -LogLevel INFO -ConsoleOutput -Message "This requires the Microsoft Online Services Module. Attempting to download and install." wget https://download.microsoft.com/download/5/0/1/5017D39B-8E29-48C8-91A8-8D0E4968E6D4/en/msoidcli_64.msi -OutFile $env:TEMP\msoidcli_64.msi If (!(Get-Command Install-Module)) { wget https://download.microsoft.com/download/C/4/1/C41378D4-7F41-4BBE-9D0D-0E4F98585C61/PackageManagement_x64.msi -OutFile $env:TEMP\PackageManagement_x64.msi } If ($DebugLogging) { Write-Log -LogFile $Logfile -LogLevel DEBUG -Message "Installing Sign-On Assistant." } msiexec /i $env:TEMP\msoidcli_64.msi /quiet /passive If ($DebugLogging) { Write-Log -LogFile $Logfile -LogLevel DEBUG -Message "Installing PowerShell Get Supporting Libraries." } msiexec /i $env:TEMP\PackageManagement_x64.msi /qn If ($DebugLogging) { Write-Log -LogFile $Logfile -LogLevel DEBUG -Message "Installing PowerShell Get Supporting Libraries (NuGet)." } Install-PackageProvider -Name Nuget -MinimumVersion 2.8.5.201 -Force -Confirm:$false If ($DebugLogging) { Write-Log -LogFile $Logfile -LogLevel DEBUG -Message "Installing Microsoft Online Services Module." } Install-Module MSOnline -Confirm:$false -Force If (!(Get-Module -ListAvailable MSOnline)) { Write-Log -LogFile $Logfile -LogLevel ERROR -ConsoleOutput -Message "This Configuration requires the MSOnline Module. Please download from https://connect.microsoft.com/site1164/Downloads/DownloadDetails.aspx?DownloadID=59185 and try again." $ErrorCount++ Break } } If (Get-Module -ListAvailable MSOnline) { Import-Module MSOnline -Force } Else { Write-Log -LogFile $Logfile -LogLevel ERROR -ConsoleOutput -Message "This Configuration requires the MSOnline Module. Please download from https://connect.microsoft.com/site1164/Downloads/DownloadDetails.aspx?DownloadID=59185 and try again." } Write-Log -LogFile $Logfile -LogLevel INFO -Message "Finished Microsoft Online Service Module check." } # End Function MSOnline [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 $ForestFQDN = (Get-ChildItem Env:\USERDNSDOMAIN).Value.ToString() # Build header and parameter functions $Excluded = @( 'Credential', 'ForestFQDN', 'InstallModules', 'IncludeGuests', 'Logfile,' 'BreachedAccountOutput') [regex]$ParametersToExclude = '(?i)^(\b' + (($Excluded | foreach { [regex]::escape($_) }) –join "\b|\b") + '\b)$' $Params = $PSBoundParameters.Keys | ? { $_ -notmatch $ParametersToExclude } [System.Text.StringBuilder]$UserAgentString = "Compromised User Account Check -" # If no parameters are listed, assume Office 365 If (!($Params -match "ActiveDirectory|Office365" )) { $Params = "Office365"} # Collect users [array]$global:users = @() [array]$ADUsers = @() [array]$MSOLUsers = @() switch ($Params) { # Get users from Active Directory ActiveDirectory { $UserAgentString.Append(" Active Directory Forest $($ForestFQDN)") | Out-Null [array]$ADusers = Get-ADUser -prop guid, enabled, displayname, userprincipalname, proxyAddresses, PasswordNeverExpires, PasswordLastSet, whenCreated | select @{ N = "ObjectId"; E = { $_.Guid.ToString() } }, DisplayName, UserPrincipalName, @{ N = "LogonStatus"; E = { if ($_.Enabled -eq $True) { "Enabled" } else { "Disabled" } } }, @{ N = "LastPasswordChange"; E = { $_.PasswordLastSet } }, @{ N = "StsRefreshTokensValidFrom"; E= { "NotValidForADUsers" } },proxyAddresses, PasswordNeverExpires, WhenCreated } # Get users from Office 365 Office365 { Try { Get-MsolCompanyInformation -ea Stop | Out-Null } Catch { # Check for MSOnline Module If (!(Get-Module -ListAvailable MSOnline)) { # If ($InstallModules) { MSOnline } If (Get-Module -List MSOnline) { Import-Module MSOnline Connect-MsolService -Credential $Credential } Else { Write-Log -LogFile $Logfile -Message "You must install the MSOnline module to continue." -LogLevel ERROR -ConsoleOutput Break } # Check for credential If (!($Credential)) { $Credential = Get-Credential } Import-Module MSOnline -Force Connect-MsolService -Credential $Credential } } If (Get-Module -List MSOnline) { Import-Module MSOnline -Force If (!($Credential)) { $Credential = Get-Credential } Connect-MsolService -Credential $Credential $TenantDisplay = (Get-MsolCompanyInformation).DisplayName $UserAgentString.Append(" Office 365 Tenant - $($DisplayName)") | Out-Null [array]$MSOLUsers = Get-MsolUser -All | select ObjectId, DisplayName, UserPrincipalName, ProxyAddresses, StsRefreshTokensValidFrom, @{N = "PasswordNeverExpires"; e= { $_.PasswordNeverExpires.ToString() } }, @{ N = "LastPasswordChange"; e = { $_.LastPasswordChangeTimestamp } }, LastDirSyncTime, WhenCreated, UserType If (!($IncludeGuests)) { $MSOLUsers = $MSOLusers | ? { $_.UserType -eq "Member" } } } } } $headers = @{ "User-Agent" = $UserAgentString.ToString() "api-version" = 2 } $baseUri = "https://haveibeenpwned.com/api" $users += $ADUsers $users += $MSOLUsers if ($users.count -ge 1) { foreach ($user in $users) { # get all proxy addresses for users and add to an array [array]$addresses = $user.proxyaddresses | Where-Object { $_ -imatch "smtp:" } # trim smtp: and SMTP: from proxy array $addresses = $addresses.SubString(5) # add the user UPNs. This can potentially be important if: # - query is being done against Active Directory and only UPNs were gathered # - customer is using alternate ID to log on to Office 365 and accounts may $addresses += $user.userprincipalname # if guest accounts were selected, their email addresses will show up as # under proxyAddresses, but their UPNs will have #EXT# in them, so we're # going to take those out [array]$global:Errors = @() $addresses = $addresses -notmatch "\#EXT\#\@" | Sort -Unique foreach ($mail in $addresses) { #$addresses | ForEach-Object { #$mail = $_ $uriEncodeEmail = [uri]::EscapeDataString($mail) $uri = "$baseUri/breachedaccount/$uriEncodeEmail" $Result = $null try { [array]$Result = Invoke-RestMethod -Uri $uri -Headers $headers -ErrorAction SilentlyContinue } catch { $global:Errors += $error if ($error[0].Exception.response.StatusCode -match "NotFound" -or $error[0].Exception -match "404") { Write-Log -LogFile $Logfile -LogLevel INFO -Message "No Breach detected for $mail" -ConsoleOutput } if ($error[0].Exception -match "429" -or $error[0].Exception -match "503") { Write-Log -LogFile $Logfile -LogLevel ERROR -Message "Rate limiting is in effect. See https://haveibeenpwned.com/API/v2 for rate limit details." } } if ($Result) { foreach ($obj in $Result) { $RecordData = [ordered]@{ EmailAddress = $mail UserPrincipalName = $user.UserPrincipalName LastPasswordChange = $user.LastPasswordChange StsRefreshTokensValidFrom = $user.StsRefreshTokensValidFrom PasswordNeverExpires = $user.PasswordNeverExpires UserAccountEnabled = $user.LogonStatus BreachName = $obj.Name BreachTitle = $obj.Title BreachDate = $obj.BreachDate BreachAdded = $obj.AddedDate BreachDescription = $obj.Description BreachDataClasses = ($obj.dataclasses -join ", ") IsVerified = $obj.IsVerified IsFabricated = $obj.IsFabricated IsActive = $obj.IsActive IsRetired = $obj.IsRetired IsSpamList = $obj.IsSpamList } $Record = New-Object PSobject -Property $RecordData $Record | Export-csv $BreachedAccountOutput -NoTypeInformation -Append Write-Log -LogFile $Logfile -Message "Possible breach detected for $mail - $($obj.Name) on $($obj.BreachDate)" -LogLevel WARN -ConsoleOutput } } Sleep -Seconds 2 } } }
Happy hunting!
2 Replies to “Checking for compromised email accounts”
Comments are closed.