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.
are you gonna make a secure model version of this?
cutting edge stuff!
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
The 403 error is most likely due to MFA being enabled on your account. You’ll need to use the Secure Application Model then.
seriously…life safer. You should have a donate button. its craazzyyy how many blogs you have and how much you are helping us.
Sweet blog man, works great. do you have one for just administrators too?