Featured image of post Monitoring with PowerShell: O365 location alerts

Monitoring with PowerShell: O365 location alerts

A while back someone asked me to convert a script they had to the Secure Application Model. This specific script checked the Office 365 audit log IPs against a online database of locations. I declined at first and suggested it to look into Microsoft 365, a P1 or P2 subscription which allows you to do this native.

The reader came back to me recently asking once more, and giving a explanation on why P1/P2 or M365 was not possible in her case. I understand that sometimes you might have to make due with what you have. I also figured others might be in the same boat.

So this script is made to monitor the O365 unified audit log and to compare the IP addresses to a database online. The database she used previously was very rate-limited and as such not really suitable. Not a lot of people know that there actually is a great 100% free online lookup service for IPs at https://ip2c.org/. The script uses that database as there are no API limitations. 🙂

As always, I’d like to note that this should just be one layer in your entire security model and you should not put all your faith in this. Enable MFA, and keep good security hygiene.

The Script

You’ll need the Secure Application Model for this script. The script currently checks all your tenants except the ones you state in “$Skiplist”.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
######### Secrets #########
$ApplicationId = 'ApplicationID'
$ApplicationSecret = 'ApplicationSecret' | ConvertTo-SecureString -Force -AsPlainText
$TenantID = 'TenantID'
$RefreshToken = 'VeryLongRefreshToken'
$ExchangeRefreshToken = 'LongExchangeToken'
$UPN = "UPN-User-To-Generate-IDs"
######### Secrets #########

$AllowedCountries = @('Belgium', 'Netherlands', 'Germany', 'United Kingdom')
$Skiplist = @("bla1.onmicrosoft.com", "bla2.onmicrosoft.com", "bla2.onmicrosoft.com")

$credential = New-Object System.Management.Automation.PSCredential($ApplicationId, $ApplicationSecret)
$aadGraphToken = New-PartnerAccessToken -ApplicationId $ApplicationId -Credential $credential -RefreshToken $refreshToken -Scopes 'https://graph.windows.net/.default' -ServicePrincipal -Tenant $tenantID
$graphToken = New-PartnerAccessToken -ApplicationId $ApplicationId -Credential $credential -RefreshToken $refreshToken -Scopes 'https://graph.microsoft.com/.default' -ServicePrincipal -Tenant $tenantID
Connect-MsolService -AdGraphAccessToken $aadGraphToken.AccessToken -MsGraphAccessToken $graphToken.AccessToken

$customers = Get-MsolPartnerContract -All | Where-Object { $\_.DefaultDomainName -notin $SkipList }

$StrangeLocations = foreach ($customer in $customers) {
    Write-Host "Getting logon location details for $($customer.Name)" -ForegroundColor Green
$token = New-PartnerAccessToken -ApplicationId 'a0c73c16-a7e3-4564-9a95-2bdf47383716'-RefreshToken $ExchangeRefreshToken -Scopes 'https://outlook.office365.com/.default' -Tenant $customer.TenantId
    $tokenValue = ConvertTo-SecureString "Bearer $($token.AccessToken)" -AsPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential($upn, $tokenValue)
    $customerId = $customer.DefaultDomainName
    $session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri "https://ps.outlook.com/powershell-liveid?DelegatedOrg=$($customerId)&BasicAuthToOAuthConversion=true" -Credential $credential -Authentication Basic -AllowRedirection
$null = Import-PSSession $session -CommandName Search-UnifiedAuditLog -AllowClobber

    $startDate = (Get-Date).AddDays(-1)
    $endDate = (Get-Date)
    Write-Host "Retrieving logs for $($customer.name)" -ForegroundColor Blue
    $logs = do {
        $log = Search-unifiedAuditLog -SessionCommand ReturnLargeSet -SessionId $customer.name -ResultSize 5000 -StartDate $startDate -EndDate $endDate -Operations UserLoggedIn
        Write-Host "Retrieved $($log.count) logs" -ForegroundColor Yellow
        $log
    } while ($Log.count % 5000 -eq 0 -and $log.count -ne 0)
    Write-Host "Finished Retrieving logs" -ForegroundColor Green

    $userIds = $logs.userIds | Sort-Object -Unique

    $LocationMonitoring = foreach ($userId in $userIds) {

        $searchResult = ($logs | Where-Object { $_.userIds -contains $userId }).auditdata | ConvertFrom-Json -ErrorAction SilentlyContinue
        $ips = $searchResult.clientip | Sort-Object -Unique
        foreach ($ip in $ips) {
            $IsIp = ($ip -as [ipaddress]) -as [bool]
            if ($IsIp) { $ipresult = (Invoke-restmethod -method get -uri "https://ip2c.org/$($ip)") -split ';' }
            [PSCustomObject]@{
                user              = $userId
                IP                = $ip
                Country           = ($ipresult | Select-Object -index 3)
                CountryCode       = ($ipresult | Select-Object -Index 1)
                Company           = $customer.Name
                TenantID          = $customer.tenantID
                DefaultDomainName = $customer.DefaultDomainName
            }

        }

    }
    foreach ($Location in $LocationMonitoring) {
        if ($Location.country -notin $AllowedCountries) { $Location }
    }

}
if (!$StrangeLocations) {
$StrangeLocations = 'Healthy'
}

$StrangeLocations

And that’s it! Now you’re monitoring the locations from where users are logging on. As always, Happy PowerShelling!

All blogs are posted under AGPL3.0 unless stated otherwise
comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy