Monitoring with PowerShell: Monitoring the used MFA type for O365/Azure.

We all know it’s key to have your security hygiene in order, a large part of that is your multi factor authentication deployment. Having all users use MFA these days is a no-brainer, but not all types of MFA are made equal. For example; MFA via text-message is generally considered unsafe.

But even the entered OTP codes are somewhat unsafe when compared to physical tokens or push-messages. Spear-phishers are now using proxy solutions or simply social engineering to extract the OTP code from users.

I have the philosophy that everything should be secure by design. So I like to offer my clients the most secure solutions. This does mean we also keep an eye on how they configure multi-factor authentication. When we do this for them we enforce that they use the push message option, and don’t use unsafe alternatives. We disable these alternatives in the portal too so that they can’t be setup directly.

This doesn’t always work out. sometimes the client sets up their own MFA, we take over a client or sometimes people just make mistakes. This is why we monitor the actual MFA type they’ve set up and try to assist the client in choosing the more secure alternative. To do that, we use the following script I hope it helps you in securing your environment too.

All Tenants Script

So this script checks if the primary option is set to an unsafe one, and alerts if it is. You could also use it to detect if MFA is configured, and what methods are used.

As always we use the Secure Application Model so we can run this script headless.

$ApplicationId = 'xxxx-xxxx-xxxx-xxxx-xxx'
$ApplicationSecret     = 'YOURSECRET' | Convertto-SecureString -AsPlainText -Force
$TenantID              = 'xxxxxx-xxxx-xxx-xxxx--xxx'
$RefreshToken          = 'LongResourcetoken'
$ExchangeRefreshToken  = 'LongExchangeToken'
$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
$MFAType = foreach ($customer in $customers) {
$users = Get-MsolUser -TenantId $customer.tenantid -all

    foreach ($user in $users) {
        $primaryMFA = if ($null -ne $user.StrongAuthenticationUserDetails) { ($user.StrongAuthenticationMethods | Where-Object { $_.IsDefault -eq $true }).methodType } else { "MFA Disabled" }
        $SecondaryMFA = if ($null -ne $user.StrongAuthenticationUserDetails) { ($user.StrongAuthenticationMethods | Where-Object { $_.IsDefault -eq $false }).methodType } else { "No Secondary Option enabled" }
        [PSCustomObject]@{
            "DisplayName"   = $user.DisplayName
            "user"          = $user.UserPrincipalName
            "Primary MFA"   = $primaryMFA
            "Secondary MFA" = $SecondaryMFA
        }
    }

}

$UnSafeMFAUsers = $MFAType | Where-Object { $_.'Primary MFA' -like "*SMS*" -or $_.'Primary MFA' -like "_voice_" -or $\_.'Primary MFA' -like "_OTP_" }

if (!$UnSafeMFAUsers) {
$UnSafeMFAUsers = "Healthy"
}

Single Tenant Script

This script pretty much does the same as above, but for a single tenant.

$ApplicationId = 'xxxx-xxxx-xxxx-xxxx-xxx'
$ApplicationSecret     = 'YOURSECRET' | Convertto-SecureString -AsPlainText -Force
$TenantID              = 'xxxxxx-xxxx-xxx-xxxx--xxx'
$RefreshToken          = 'LongResourcetoken'
$ExchangeRefreshToken  = 'LongExchangeToken'
$TenantToCheck         = 'tenant.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 -eq $TenantToCheck}
$MFAType = foreach ($customer in $customers) {
    $users = Get-MsolUser -TenantId $customer.tenantid -all

    foreach ($user in $users) {
        $primaryMFA = if ($null -ne $user.StrongAuthenticationUserDetails) { ($user.StrongAuthenticationMethods | Where-Object { $_.IsDefault -eq $true }).methodType } else { "MFA Disabled" }
        $SecondaryMFA = if ($null -ne $user.StrongAuthenticationUserDetails) { ($user.StrongAuthenticationMethods | Where-Object { $_.IsDefault -eq $false }).methodType } else { "No Secondary Option enabled" }
        [PSCustomObject]@{
            "DisplayName"   = $user.DisplayName
            "user"          = $user.UserPrincipalName
            "Primary MFA"   = $primaryMFA
            "Secondary MFA" = $SecondaryMFA
        }
    }
}

$UnSafeMFAUsers = $MFAType | Where-Object { $_.'Primary MFA' -like "*SMS*" -or $_.'Primary MFA' -like "*voice*" -or $_.'Primary MFA' -like "*OTP*" }

if (!$UnSafeMFAUsers) {
    $UnSafeMFAUsers = "Healthy"
}

Non Secure App Model script

So if you aren’t a Microsoft Partner but still want to execute this script, just use the following script.

Connect-MsolService
$customers = Get-MsolPartnerContract -All | where-object {$_.DefaultDomainName -eq $TenantToCheck}
$MFAType = foreach ($customer in $customers) {
    $users = Get-MsolUser -TenantId $customer.tenantid -all

    foreach ($user in $users) {
        $primaryMFA = if ($null -ne $user.StrongAuthenticationUserDetails) { ($user.StrongAuthenticationMethods | Where-Object { $_.IsDefault -eq $true }).methodType } else { "MFA Disabled" }
        $SecondaryMFA = if ($null -ne $user.StrongAuthenticationUserDetails) { ($user.StrongAuthenticationMethods | Where-Object { $_.IsDefault -eq $false }).methodType } else { "No Secondary Option enabled" }
        [PSCustomObject]@{
            "DisplayName"   = $user.DisplayName
            "user"          = $user.UserPrincipalName
            "Primary MFA"   = $primaryMFA
            "Secondary MFA" = $SecondaryMFA
        }
    }

}

$UnSafeMFAUsers = $MFAType | Where-Object { $_.'Primary MFA' -like "*SMS*" -or $_.'Primary MFA' -like "_voice_" -or $\_.'Primary MFA' -like "_OTP_" }

if (!$UnSafeMFAUsers) {
$UnSafeMFAUsers = "Healthy"
}

And that’s it! as always, Happy PowerShelling!

Recent Articles

The return of CyberDrain CTF

CyberDrain CTF returns! (and so do I!)

It’s been since september that I actually picked up a digital pen equivalent and wrote anything down. This was due to me being busy with life but also my side projects like CIPP. I’m trying to get back into the game of scripting and blogging about these scripts. There’s still so much to automate and so little time, right? ;)

Monitoring with PowerShell: Monitoring Acronis Backups

Intro

This is a monitoring script requested via Reddit, One of the reddit r/msp users wondered how they can monitor Acronis a little bit easier. I jumped on this because it happened pretty much at the same time that I was asked to speak at the Acronis CyberSummit so it kinda made sense to script this so I have something to demonstrate at my session there.

Monitoring with PowerShell: Monitoring VSS Snapshots

Intro

Wow! It’s been a while since I’ve blogged. I’ve just been so swamped with CIPP that I’ve just let the blogging go entirely. It’s a shame because I think out of all my hobbies it’s one I enjoy the most. It’s always nice helping others achieve their scripting target. I even got a couple of LinkedIn questions asking if I was done with blogging but I’m not. Writing always gives me some more piece of mind so I’ll try to catch up again. I know I’ve said that before but this time I’ll follow through. I’m sitting down right now and scheduling the release of 5 blogs in one go. No more whining and no more waiting.