Monitoring with PowerShell: Monitoring failed logins for Office365

So this was another request by a reader; he has MFA configured for all his users, but still wants to know when the failed logon count increases. Mostly so he can warn his users that a possible spear-phising attempt might also be imminent. We know that when brute force does not work, focussed bad actors will often try the next avenue of attack.

At his request i’ve made the following scripts, one will monitor all possible locations using your partner credentials. The other will monitor only one tenant. I personally like the latter better as I’ve integrated this into my RMM so it can run and alert per client, Also it’s a little faster.

The Script

The script is designed to run at least every 4 hours, but can be run even on a 5-10 minute basis. It will get all info for the previous 4 hours, If you want to decrease on increase this you can edit line 13. Getting the logs is based on Elliot’s script to get the unified logs here. To connect with MFA, use my other blog here to generate your Secure App Model credentials.

Get Failed Logon information for all tenants

$credential = Get-Credential
Connect-MsolService -Credential $credential
$customers = Get-msolpartnercontract -All
$FilteredLogs = @()
$FailedLogonCount = 0
foreach ($customer in $customers) {
    $InitDomain = $customer.DefaultDomainName
    $DelegatedOrgURL = "https://outlook.office365.com/powershell-liveid?DelegatedOrg=" + $InitDomain
    write-host "Connecting to $($customer.Name) Security Center"
    $s = New-PSSession -ConnectionUri $DelegatedOrgURL -Credential $credential -Authentication Basic -ConfigurationName Microsoft.Exchange -AllowRedirection
    Import-PSSession $s -CommandName Search-UnifiedAuditLog -AllowClobber
    write-host "Getting last 30 minutes of logs for $($customer.Name)"
    $startDate = (Get-Date).addhours(-4)
    $endDate = (Get-Date)
    $Logs = @()
    Write-Host "Retrieving logs for $($customer.name)" -ForegroundColor Blue
    do {
        $logs += Search-unifiedAuditLog -SessionCommand ReturnLargeSet -SessionId $customer.name -ResultSize 5000 -StartDate $startDate -EndDate $endDate -Operations userloginfailed #-SessionId "$($customer.name)"
        Write-Host "Retrieved $($logs.count) logs" -ForegroundColor Yellow
    }while ($Logs.count % 5000 -eq 0 -and $logs.count -ne 0)
   $FilteredLogs +=  $logs.auditdata | convertfrom-json -ErrorAction SilentlyContinue | Select-Object UserID, ClientIP | Group-Object -Property UserID
   Foreach($item in $FilteredLogs){
       if($item.name -ne $null){ $FailedLogonCount += $item.count; $FailedLogon += "$($Item.name) has $($item.count) failed logons from the following IPs: $($Item.group.ClientIP) `n" }
   }
}

if(!$FailedLogonCount){ $FailedLogon = "Healthy"}

Get failed logins for only one tenant

$TenantName = "TenantDomain.onmicrosoft.com"

$credential = Get-Credential
Connect-MsolService -Credential $credential
$FilteredLogs = @()
$FailedLogonCount = 0
$customer = Get-msolpartnercontract | Where-Object {$_.DefaultDomainName -eq $TenantName}

$InitDomain = $customer.DefaultDomainName
    $DelegatedOrgURL = "https://outlook.office365.com/powershell-liveid?DelegatedOrg=" + $InitDomain
    write-host "Connecting to $($customer.Name) Security Center"
    $s = New-PSSession -ConnectionUri $DelegatedOrgURL -Credential $credential -Authentication Basic -ConfigurationName Microsoft.Exchange -AllowRedirection
    Import-PSSession $s -CommandName Search-UnifiedAuditLog -AllowClobber
    write-host "Getting last 30 minutes of logs for $($customer.Name)"
    $startDate = (Get-Date).addhours(-4)
    $endDate = (Get-Date)
    $Logs = @()
    Write-Host "Retrieving logs for $($customer.name)" -ForegroundColor Blue
    do {
        $logs += Search-unifiedAuditLog -SessionCommand ReturnLargeSet -SessionId $customer.name -ResultSize 5000 -StartDate $startDate -EndDate $endDate -Operations userloginfailed #-SessionId "$($customer.name)"
        Write-Host "Retrieved $($logs.count) logs" -ForegroundColor Yellow
    }while ($Logs.count % 5000 -eq 0 -and $logs.count -ne 0)
   $FilteredLogs +=  $logs.auditdata | convertfrom-json -ErrorAction SilentlyContinue | Select-Object UserID, ClientIP | Group-Object -Property UserID
   Foreach($item in $FilteredLogs){
       if($item.name -ne $null){ $FailedLogonCount += $item.count; $FailedLogon += "$($Item.name) has $($item.count) failed logons from the following IPs: $($Item.group.ClientIP) `n" }
   }


if(!$FailedLogonCount){ $FailedLogon = "Healthy"}

You can choose to alert just on the FailedLogon variable, or alert based on the actual count via FailedLogonCount. As always, Happy Powershelling.

6 Comments

  1. array November 23, 2019 at 11:14 pm

    are you gonna make a secure model version of this?

  2. Small November 25, 2019 at 11:17 am

    cutting edge stuff!

  3. Justin November 29, 2019 at 3:50 am

    I am getting errors when attempting to connect to my tenants with this script, any idea what would cause the below error? I am logging in with my partner credentials and I can log into the GUI no problem and modify my tenants (create/delete users etc).

    New-PSSession : [outlook.office365.com] Connecting to remote server outlook.office365.com failed with the following
    error message : Access is denied. For more information, see the about_Remote_Troubleshooting Help topic.
    At line:1 char:10
    + $s = New-PSSession -ConnectionUri $DelegatedOrgURL -Credential $c …
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : OpenError: (System.Manageme….RemoteRunspace:RemoteRunspace) [New-PSSession], PSRemotin
    gTransportException
    + FullyQualifiedErrorId : AccessDenied,PSSessionOpenFailed

    1. Kelvin Tegelaar November 29, 2019 at 8:28 am

      The 403 error is most likely due to MFA being enabled on your account. You’ll need to use the Secure Application Model then.

  4. Manat December 18, 2019 at 6:03 pm

    seriously…life safer. You should have a donate button. its craazzyyy how many blogs you have and how much you are helping us.

  5. OfficerMonkey April 23, 2020 at 7:42 pm

    Sweet blog man, works great. do you have one for just administrators too?

Leave a comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.