Skip to content

Latest commit

 

History

History
1495 lines (1285 loc) · 96.8 KB

readmeold.md

File metadata and controls

1495 lines (1285 loc) · 96.8 KB

Azure AD Conditional Access Policies

Author: Chad Cox (Microsoft)
Created: January 2023
Updated: February 2023

Disclaimer This Sample Code is provided for the purpose of illustration only and is not intended to be used in a production environment. THIS SAMPLE CODE AND ANY RELATED INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. We grant You a nonexclusive, royalty-free right to use and modify the Sample Code and to reproduce and distribute the object code form of the Sample Code, provided that You agree: (i) to not use Our name, logo, or trademarks to market Your software product in which the Sample Code is embedded; (ii) to include a valid copyright notice on Your software product in which the Sample Code is embedded; and (iii) to indemnify, hold harmless, and defend Us and Our suppliers from and against any claims or lawsuits, including attorneys` fees, that arise or result from the use or distribution of the Sample Code..


Deploy the query pack that contains all the queries from this solution into the Log Analytics Workspace that contains the Azure AD Audit / Signin logs

Deploy to Azure


How to use this guide

  • Below is a list of Conditional Access Policies that Microsoft recommends in an Azure AD Tenant.
  • Each link in the table of content contains information about a policy with notes about how to evaluate the impact of the policy.
  • Use this method to shorten the amount of time it takes to deploy Conditional Access Policies in Azure AD, by proactively leveraging existing signinlogs and filtering to show the users that could be impacted.
  • Yes it is posible and every organization should still use conditional access policies even if they have a third party IDP. More information in the requirements section.

Table of Content


Conditional Access Policy Matrix

Conditional Access Policy
Start (Pick 1) Always require MFA from untrusted networks
Always require MFA or Trusted Device or Compliant Device from untrusted networks
Always require MFA
Always require MFA or Trusted Device or Compliant Device
Entry Block Exchange ActiveSync
Require MFA when sign-in risk is high
Require password change when user risk is high
Require privileged role member to MFA
Intermediate Block when sign-in risk is high
Block when user risk is high
Block privileged role member from legacy authentication
Require MFA for Microsoft Graph PowerShell and Explorer
Require MFA for Microsoft Azure Management
Require privileged role member to MFA with Auth Strengths (Fido2,CBA, Microsoft Authenticator password-less)
Require privileged role member to use compliant device
Require Compliant Device for Office 365
Block Guest for Medium and High Sign-in Risk
Block Guest from Azure Management
Mature Always require MFA with Auth Strengths (Fido2,CBA, Microsoft Authenticator password-less or Windows Hello for Business)
Limit session to 12 Hours
Block Legacy Authentication
Block the Directory Sync Account from non-trusted locations
Require guest to MFA
Require Compliant Device for All Apps
No Persistent Browser and 1 Hour Session for Unmanaged Devices
Block when user risk is high
Block when sign-in risk is high
Require MFA when sign-in risk is low, medium, or high
Block when privileged role member user risk is low medium high
Block when privileged role member sign in risk is low medium high
Block when Directory Sync Account sign in risk is low medium high
Block Guest for Low, Medium and High Sign-in Risk
Restrict guest to approved apps
Restrict Office 365 service accounts to Office 365 API's
Restrict Azure service accounts to Azure Management API's endpoints

Goals

  • Protect Privileged Credentials
  • Require trusted devices
  • Do not depend on trusted networks / locations
  • Always require multifactor
  • Minimize the use of filters
  • Know the potential impact of a policy.

Requirements

  • The best way to do this is sending the Azure AD Sign In Logs to Azure Monitor (LogAnalytics).
  • Azure AD Premium 1 License are required for:
    • Conditional Access Policies
    • Sign in Logs to be sent to Log Analytics
    • Ability to query Sign in logs via microsoft graph
  • If a third party IDP or ADFS is used to federate the tenant and mfa is being performed there instead of AAD, it must send the multiauthn claim when it performs mfa, so that Azure AD knows a mfa was performed and is reflcted in the logs and bypasses MFA. Here is more info about the settings that needs to be done for this: Set federatedIdpMfaBehavior to enforceMfaByFederatedIdp. Without this data the queries will not provide to much value and Azure AD will have no idea
  • Third Party IDP notes for MFA and Conditional Access Policies
  • Risk Policies require P2 License.
  • Workload Identity License is required to view those risk.

Introduction

While working with organizations to determine impact using read only and the built-in reporting, I found this was taking longer than needed and could be done another way. I have thrown together a few PowerShell Scripts and Log Analytics Queries (KQL) in addition to notes about each policy that will help identify potential impact before a particular policy is created or applied. The idea is to use the results to determine impact of a policy and put in the exclusions or policy adjustments needed to minimize impact and get the desired affect of the policy.

Not everything here is perfect and is being updated as I learn new things or new guidance becomes published. Also do not hesitate to comment and let me know what is not working or is working.


How to run a Log Analytics Query

  • In the Azure AD Portal
  • Navigate to the Log Analytics Tab
  • Copy the example code from the section you want to review the possible impact
  • Replace the existing text in the query window or open a new query tab and paste in the new one.
  • Then select Run and wait for the results.

Untitled

Or Deploy the query pack that contains all the queries from this solution into the Log Analytics Workspace that contains the Azure AD Audit / Signin logs

Deploy to Azure

  • After the query pack is deployed
  • In the Azure AD Portal
  • Navigate to the Log Analytics Tab
  • Select the Queries and change the group by to Label

Untitled


Import the policies from templates

I have put together a script that will import all of the policies from this github.
The scipt can be found here click here

Instructions

  • Copy the contents of the script locally onto a machine.
  • Run the script in PowerShell
  • Select the number of the policy you want to import.
  • Review the results They are always in read-only and have a prefix

Import menu
Untitled

finished policy
Untitled


Create list of privileged users for the kql designed to search for privileged user impact

  • Run this in PowerShell
Connect-MgGraph
Select-MgProfile -Name beta
$roles = @("Application Administrator","Authentication Administrator","Cloud Application Administrator","Conditional Access Administrator","Exchange Administrator","Global Administrator","Helpdesk Administrator","Hybrid Identity Administrator","Password Administrator","Privileged Authentication Administrator","Privileged Role Administrator","Security Administrator","SharePoint Administrator","User Administrator")
(Get-MgDirectoryRole -ExpandProperty members -all | where {$_.displayname -In $roles} | select -ExpandProperty members).id  -join('","') | out-file .\privuser.txt
  • The results of this will be in a file called privuser.txt
  • Open the txt file. Should look something like this
8f47d5a6-a36b-4d99-b6bc-c023cf23ae9b","8f47d5a6-a36b-4d99-b6bc-c023cf23ae9b","8f47d5a6-a36b-4d99-b6bc-c023cf23ae9b","8f47d5a6-a36b-4d99-b6bc-c023cf23ae9b","8f47d5a6-a36b-4d99-b6bc-c023cf23ae9b","8f47d5a6-a36b-4d99-b6bc-c023cf23ae9b
  • Next in the sections titled Log Analytics AAD SigninLogs Query (KQL) needs results from the PowerShell script from the section you are reviewing. Will want to copy the kql statement, and paste in Log Analytics.
  • on line 1 replace the phrase replace this with the results from the privuser.txt found from the powershell cmdlets
let privusers = pack_array("**replace this with the results from the privuser.txt found from the powershell cmdlets**");
  • to look like
let privusers = pack_array("8f47d5a6-a36b-4d99-b6bc-c023cf23ae9b","8f47d5a6-a36b-4d99-b6bc-c023cf23ae9b","8f47d5a6-a36b-4d99-b6bc-c023cf23ae9b","8f47d5a6-a36b-4d99-b6bc-c023cf23ae9b","8f47d5a6-a36b-4d99-b6bc-c023cf23ae9b","8f47d5a6-a36b-4d99-b6bc-c023cf23ae9b");

Find IPAddress not defined as trusted

Log Analytics AAD SigninLogs Query (KQL)

SigninLogs
| where TimeGenerated > ago(30d)
| where ResultType == "0"
| where HomeTenantId == ResourceTenantId
| where NetworkLocationDetails !contains "trustedNamedLocation"
| extend TrustedLocation = tostring(iff(NetworkLocationDetails contains 'trustedNamedLocation', 'trustedNamedLocation',''))
| extend isIPv6 = tostring(iff(IPAddress matches regex @"(([\d|\w]{1,4}\:){7}[\d|\w]{1,4})",'Yes','No'))
| distinct IPAddress, TrustedLocation, UserPrincipalName, isIPv6
| summarize uniqueusercountbyip = count() by IPAddress, TrustedLocation, isIPv6
| where uniqueusercountbyip >= 4
| sort by uniqueusercountbyip desc 

Comment

This query returns IP addresses where 4 or more unique users have authenticated against Azure AD. You will want to research each IP and determine if they are owned by the organization or if they belong to something like a public proxy cloud solution like zscaler or umbrella. Legit ones will need to be defined as a trusted network in Azure AD to make sure any location filtered policy works correctly and to help remediate false positives in Azure Identity Protection

Instructions on how to create named locations can be viewed here Named locations

The field uniqueusercountbyip is count of unique list of users. It is possible to see ipv6 addresses which usually comes from Azure Networks and will be normal in the near future from the internet.

Untitled


Applications not being protected by Conditional Access Policies

Log Analytics AAD SigninLogs Query (KQL)

//https://github.com/reprise99/Sentinel-Queries/blob/main/Azure%20Active%20Directory/Identity-Top20AppswithnoCA.kql
//This query shows applications that are not protected by conditional access policies.
let apps=
    SigninLogs
    | where TimeGenerated > ago (30d)
    | project TimeGenerated, ConditionalAccessPolicies, AppDisplayName
//Exclude native Microsoft apps that you can't enforce policy on or that are covered natively in Office 365
    | where AppDisplayName !in ("Microsoft Office Web Apps Service", "Microsoft App Access Panel", "Office Online Core SSO", "Microsoft Authentication Broker", "Microsoft Account Controls V2", "Microsoft 365 Support Service","Office Online Maker SSO","My Apps","My Profile")
    | mv-expand ConditionalAccessPolicies
    | extend CAResult = tostring(ConditionalAccessPolicies.result)
    | summarize ResultSet=make_set(CAResult) by AppDisplayName
    | where ResultSet !has "success" or ResultSet !has "failure"
    | project AppDisplayName;
SigninLogs
| where TimeGenerated > ago(30d)
| where ResultType == 0
| where AppDisplayName in (apps)
| summarize Count=count()by AppDisplayName
| top 20 by Count

Comment
The image below, shows the applications and the logon count of those apps that is not being protected by some sort of Conditional Access Policy. Ideally every application will have a mfa requirement or a trusted/compliant policy requirement.

Untitled

Back to Matrix


Percentage of MFA / Compliant Device / Trusted Device / Trusted Location / Conditional Access Policies by Applications

Log Analytics AAD SigninLogs Query (KQL)

SigninLogs
| where TimeGenerated > ago(30d)
| where ResultType == 0 and AppDisplayName <> 'Windows Sign In' and UserType <> "Guest"
| where ResourceTenantId == HomeTenantId and AADTenantId == HomeTenantId
| extend trustType = tostring(DeviceDetail.trustType)
| extend isCompliant = tostring(DeviceDetail.isCompliant)
| extend TrustedLocation = tostring(iff(NetworkLocationDetails contains 'trustedNamedLocation', 'trustedNamedLocation',''))
| summarize
    ['Total Signin Count']=count(),
    ['Total MFA Count']=countif(AuthenticationRequirement == "multiFactorAuthentication"),
    ['Total non MFA Count']=countif(AuthenticationRequirement == "singleFactorAuthentication"),
    ['Total Trusted device']=countif(trustType == "Hybrid Azure AD joined"),
    ['Total Compliant device']=countif(isCompliant == 'true'),
    ['Total Trusted Location']=countif(TrustedLocation == 'trustedNamedLocation'),
    ['Total CAP Applied']=countif(ConditionalAccessStatus == 'success')
    by AppDisplayName
| project
    AppDisplayName,TotalSigninCount = ['Total Signin Count'],
    MFAPercentage=(todouble(['Total MFA Count']) * 100 / todouble(['Total Signin Count'])),
    TrustedDevicePercentage=(todouble(['Total Trusted device']) * 100 / todouble(['Total Signin Count'])),
    CompliantDevicePercentage=(todouble(['Total Compliant device']) * 100 / todouble(['Total Signin Count'])),
    TrustedLocationPercentage=(todouble(['Total Trusted Location']) * 100 / todouble(['Total Signin Count'])),
    ConditionalPolicyAppliedPercentage=(todouble(['Total CAP Applied']) * 100 / todouble(['Total Signin Count']))
| where MFAPercentage <> 100
| sort by MFAPercentage desc  

Comment
My lab has no good data for this. This query will show the percentage of each of the major things we are looking for. The idea here is to look and make sure applications actually have the desired protections whether its from a compliant device or MFA.

This is what I would expect to see.

  • Every Application is 100% convered by Conditional Access Policies.
  • At minimum the MFAPercentage + TrustedLocationPercentage should equal 100%
  • And in a true zero trust envrionment the CompliantDevicePercentage should be 100%
  • Im most cases if MFAPercentage + TrustedDevicePercentage + CompliantDevicePercentage = 100%, then a Org is doing a decent job.

Untitled

Back to Matrix


Always require MFA

Conditional Access Policy Setup

  • Create Conditional Access Policy:
  • Users
    • Include: All Users
    • Exclude: Breakglass, Exclusion Group, Directory Role (Directory Sync Accounts), Guest
  • Cloud Apps or Actions
    • Select what this policy applies to: Cloud apps
    • Include: All Cloud Apps
    • Exclude: Windows Store
  • Conditions
  • Grant
    • Grant Access
    • Require Multi-Factor Authentication
    • Require all the selected controls

Note: this policy will more than likely break on premise sync accounts, make sure the Directory Sync Accounts Role is in the exclusion group.

Log Analytics AAD SigninLogs Query (KQL)

let thisTenantId = SigninLogs | take 1 | distinct AADTenantId;
let guests = AADNonInteractiveUserSignInLogs | union SigninLogs | where TimeGenerated > ago(14d) | where HomeTenantId !in (thisTenantId) and HomeTenantId <> '' | distinct UserId;
let excludeapps = pack_array("Windows Sign In","Microsoft Authentication Broker","Microsoft Account Controls V2","Microsoft Intune Company Portal","Microsoft Mobile Application Management");
AADNonInteractiveUserSignInLogs 
| where TimeGenerated > ago(14d)
| where Status !contains "MFA requirement satisfied by claim in the token"
| union SigninLogs 
| where TimeGenerated > ago(14d) 
| where UserType <> "Guest" and UserId !in (guests)
| where HomeTenantId == ResourceTenantId
| where ResultType == 0 and AuthenticationRequirement == "singleFactorAuthentication" 
| where AppDisplayName  !in (excludeapps) and AppDisplayName <> ''
| distinct AppDisplayName,UserPrincipalName,ConditionalAccessStatus,AuthenticationRequirement, Category 
| summarize apps=make_list(AppDisplayName) by UserPrincipalName,ConditionalAccessStatus,AuthenticationRequirement,Category

Comment
This policy is a harder policy to implement. This query will return a unique list of users and applications that are not hitting up against a conditional access policy and not providing multifactor authentication. Things to look for in the KQL results are applications that might have problems like the Windows Store and accounts that need to be excluded such as faceless user objects or "service accounts".

Expect to see most of the users in a org in this list. The goal is to find the users and applications that need to be excluded because it would cause impact.

Looking at the image below. I would make sure to exclude the breakglass account and the sync account as those are accounts that should not have this policy applied to it.

Untitled

Back to Matrix


Always require MFA from untrusted networks

Conditional Access Policy Setup

  • Create Conditional Access Policy:
  • Users
    • Include: All Users
    • Exclude: Breakglass, Exclusion Group, Guest
  • Cloud Apps or Actions
    • Select what this policy applies to: Cloud apps
    • Include: All Cloud Apps
    • Exclude: Windows Store
  • Conditions
    • Locations
    • Include: Any Location
    • Exclude: All trusted locations
  • Grant
    • Grant Access
    • Require Multi-Factor Authentication
    • Require all the selected controls

Log Analytics AAD SigninLogs Query (KQL)

let thisTenantId = SigninLogs | take 1 | distinct AADTenantId;
let guests = AADNonInteractiveUserSignInLogs | union SigninLogs | where TimeGenerated > ago(14d) | where HomeTenantId !in (thisTenantId) and HomeTenantId <> '' | distinct UserId;
let excludeapps = pack_array("Windows Sign In","Microsoft Authentication Broker","Microsoft Account Controls V2","Microsoft Intune Company Portal","Microsoft Mobile Application Management");
AADNonInteractiveUserSignInLogs 
| where TimeGenerated > ago(14d)
| where Status !contains "MFA requirement satisfied by claim in the token"
| where NetworkLocationDetails !contains "trustedNamedLocation"
| extend TrustedLocation = tostring(iff(NetworkLocationDetails contains 'trustedNamedLocation', 'trustedNamedLocation','')) 
| union SigninLogs 
| where TimeGenerated > ago(14d) 
| where UserType <> "Guest" and UserId !in (guests)
| where HomeTenantId == ResourceTenantId
| where NetworkLocationDetails !contains "trustedNamedLocation"
| where ResultType == 0 and AuthenticationRequirement == "singleFactorAuthentication" 
| extend TrustedLocation = tostring(iff(NetworkLocationDetails contains 'trustedNamedLocation', 'trustedNamedLocation','')) 
| where AppDisplayName  !in (excludeapps) and AppDisplayName <> ''
| distinct AppDisplayName,UserPrincipalName,ConditionalAccessStatus,AuthenticationRequirement,TrustedLocation
| summarize apps=make_list(AppDisplayName) by UserPrincipalName,ConditionalAccessStatus,AuthenticationRequirement,TrustedLocation

Comment
This policy is not required if you were able to implement: Always require MFA
Also this policy has a network based filter which means if someone was able to "trick" the ip they would bypass important protections.
This query will return a unique list of users and applications that are not hitting up against a conditional access policy and not providing multifactor authentication. Things to look for in the KQL results are applications that might have problems like the Windows Store and accounts that need to be excluded such as faceless user objects or "service accounts".

The goal is to find the users and applications that need to be excluded because it would cause impact. Also note if users are in this list that never access outside of the org then there is a good chance the IP that user is coming from is not trusted.

Looking at the image below. I would make sure to exclude the breakglass account from the policy and I would research the sync account to figure out why its being used outside a trusted network.

Untitled

Back to Matrix


Always require MFA or Trusted Device or Compliant Device

Conditional Access Policy Setup

  • Create Conditional Access Policy:
  • Users
    • Include: All Users
    • Exclude: Breakglass, Exclusion Group, Directory Role (Directory Sync Accounts), Guest
  • Cloud Apps or Actions
    • Select what this policy applies to: Cloud apps
    • Include: All Cloud Apps
    • Exclude: Windows Store
  • Conditions
  • Grant
    • Grant Access
    • Require Multi-Factor Authentication, Require Hybrid Azure AD joined device, and Require device to be marked as compliant
    • Require one of the selected controls

Log Analytics AAD SigninLogs Query (KQL)

let thisTenantId = SigninLogs | take 1 | distinct AADTenantId;
let guests = AADNonInteractiveUserSignInLogs | union SigninLogs | where TimeGenerated > ago(14d) | where HomeTenantId !in (thisTenantId) and HomeTenantId <> '' | distinct UserId;
let excludeapps = pack_array("Windows Sign In","Microsoft Authentication Broker","Microsoft Account Controls V2","Microsoft Intune Company Portal","Microsoft Mobile Application Management");
//query the non interactive logs
let AADNon = AADNonInteractiveUserSignInLogs
| where TimeGenerated > ago(14d) and ResultType == 0 and AuthenticationRequirement == "singleFactorAuthentication" 
| where Status !contains "MFA requirement satisfied by claim in the token"
| where UserId !in (guests)
| where AppDisplayName  !in (excludeapps)
| extend trustType = tostring(parse_json(DeviceDetail).trustType) 
| extend isCompliant = tostring(parse_json(DeviceDetail).isCompliant) 
| extend TrustedLocation = tostring(iff(NetworkLocationDetails contains 'trustedNamedLocation', 'trustedNamedLocation',''))
| extend os = tostring(parse_json(DeviceDetail).operatingSystem) 
| where isCompliant <> 'true' and trustType <> "Hybrid Azure AD joined"  
| distinct AppDisplayName,UserPrincipalName,ConditionalAccessStatus,AuthenticationRequirement, TrustedLocation, trustType,isCompliant,os, Category;
//query the interactive logs
let AAD = SigninLogs 
| where TimeGenerated > ago(14d) and UserType <> "Guest" and ResultType == 0 and AuthenticationRequirement == "singleFactorAuthentication" 
| where AppDisplayName  !in (excludeapps) 
| where UserType <> "Guest" and UserId !in (guests)
| where HomeTenantId == ResourceTenantId
| extend trustType = tostring(DeviceDetail.trustType) 
| extend isCompliant = tostring(DeviceDetail.isCompliant) 
| extend os = tostring(DeviceDetail.operatingSystem) 
| extend TrustedLocation = tostring(iff(NetworkLocationDetails contains 'trustedNamedLocation', 'trustedNamedLocation',''))
| where isCompliant <> 'true' and trustType <> "Hybrid Azure AD joined"  
| distinct AppDisplayName,UserPrincipalName,ConditionalAccessStatus,AuthenticationRequirement, TrustedLocation, trustType,isCompliant,os,Category;
//combine the results
AADNon
| union AAD
| where AppDisplayName <> ''
| summarize apps=make_set(AppDisplayName),ostypes=make_set(os) by UserPrincipalName,ConditionalAccessStatus,AuthenticationRequirement, TrustedLocation,trustType,isCompliant

Comment
This policy is not required if you were able to implement: Always require MFA

Untitled

Back to Matrix


Always require MFA or Trusted Device or Compliant Device from untrusted networks

Conditional Access Policy Setup

  • Create Conditional Access Policy:
  • Users
    • Include: All Users
    • Exclude: Breakglass, Exclusion Group, Directory Role (Directory Sync Accounts), Guest
  • Cloud Apps or Actions
    • Select what this policy applies to: Cloud apps
    • Include: All Cloud Apps
    • Exclude: Windows Store
  • Conditions
    • Locations
    • Include: Any Location
    • Exclude: All trusted locations
  • Grant
    • Grant Access
    • Require Multi-Factor Authentication, Require Hybrid Azure AD joined device, and Require device to be marked as compliant
    • Require one of the selected controls

Log Analytics AAD SigninLogs Query (KQL)

let thisTenantId = SigninLogs | take 1 | distinct AADTenantId;
let guests = AADNonInteractiveUserSignInLogs | union SigninLogs | where TimeGenerated > ago(14d) | where HomeTenantId !in (thisTenantId) and HomeTenantId <> '' | distinct UserId;
let excludeapps = pack_array("Windows Sign In","Microsoft Authentication Broker","Microsoft Account Controls V2","Microsoft Intune Company Portal","Microsoft Mobile Application Management");
let AADNon = AADNonInteractiveUserSignInLogs
| where TimeGenerated > ago(14d) and ResultType == 0 and AuthenticationRequirement == "singleFactorAuthentication" 
| where AppDisplayName  !in (excludeapps)
| where UserId !in (guests)
| where Status !contains "MFA requirement satisfied by claim in the token"
| where NetworkLocationDetails !contains "trustedNamedLocation"
| extend trustType = tostring(parse_json(DeviceDetail).trustType) 
| extend isCompliant = tostring(parse_json(DeviceDetail).isCompliant) 
| extend TrustedLocation = tostring(iff(NetworkLocationDetails contains 'trustedNamedLocation', 'trustedNamedLocation',''))
| extend os = tostring(parse_json(DeviceDetail).operatingSystem) 
| where isCompliant <> 'true' and trustType <> "Hybrid Azure AD joined"  
| distinct AppDisplayName,UserPrincipalName,ConditionalAccessStatus,AuthenticationRequirement, TrustedLocation, trustType,isCompliant,os, Category;
//query the interactive logs
let AAD = SigninLogs 
| where TimeGenerated > ago(14d) and UserType <> "Guest" and ResultType == 0 and AuthenticationRequirement == "singleFactorAuthentication" 
| where AppDisplayName  !in (excludeapps) 
| where NetworkLocationDetails !contains "trustedNamedLocation"
| where UserType <> "Guest" and UserId !in (guests)
| where HomeTenantId == ResourceTenantId
| extend trustType = tostring(DeviceDetail.trustType) 
| extend isCompliant = tostring(DeviceDetail.isCompliant) 
| extend os = tostring(DeviceDetail.operatingSystem) 
| extend TrustedLocation = tostring(iff(NetworkLocationDetails contains 'trustedNamedLocation', 'trustedNamedLocation',''))
| where isCompliant <> 'true' and trustType <> "Hybrid Azure AD joined"  
| distinct AppDisplayName,UserPrincipalName,ConditionalAccessStatus,AuthenticationRequirement, TrustedLocation, trustType,isCompliant,os,Category;
//combine the results
AADNon
| union AAD
| where AppDisplayName <> ''
| summarize apps=make_set(AppDisplayName),ostypes=make_set(os) by UserPrincipalName,ConditionalAccessStatus,AuthenticationRequirement, TrustedLocation,trustType,isCompliant

Comment
This policy is not required if you were able to implement: Always require MFA

Untitled

Back to Matrix


Require MFA for Microsoft Graph PowerShell and Explorer

Conditional Access Policy Setup

  • Create Conditional Access Policy:
  • Users
    • Include: All Users
    • Exclude: Breakglass, Exclusion Group
  • Cloud Apps or Actions
    • Select what this policy applies to: Cloud Apps
    • Include: Microsoft Graph PowerShell, Graph Explorer
  • Conditions
  • Grant
    • Grant Access
    • Require Multi-Factor Authentication
    • Require all the selected controls

Log Analytics AAD SigninLogs Query (KQL)

let includeapps = pack_array("Graph Explorer","Microsoft Graph PowerShell");
AADNonInteractiveUserSignInLogs
| where TimeGenerated > ago(14d) and ResultType == 0 and AuthenticationRequirement == "singleFactorAuthentication"
| where AppDisplayName in (includeapps) 
| union SigninLogs
| where TimeGenerated > ago(14d) and ResultType == 0 and AuthenticationRequirement == "singleFactorAuthentication" 
| where AppDisplayName in (includeapps)
| distinct AppDisplayName, UserPrincipalName, ConditionalAccessStatus, AuthenticationRequirement

Comment
This policy is great for organizations that have trusted network based filters on the base conditional access policy. This will make sure users that use tools that can be used to perform queries or changes agaisnt the tenant must require MFA from both trusted and non trusted networks.

Revew the list of users in the results. in the example image below, the breakglass account is the only account being used to signin to those end points. That particular account should be excluded from the policy. But also shouldnt be used. Any other account such as possible service accounts used for azure ad automation will need to be excluded from the policy and should eventually transition to using a service principal instead.

Untitled

Back to Matrix


Require MFA for Microsoft Azure Management

Conditional Access Policy Setup

  • Create Conditional Access Policy:
  • Users
    • Include: All Users
    • Exclude: Breakglass, Exclusion Group
  • Cloud Apps or Actions
    • Select what this policy applies to: Cloud Apps
    • Include: Microsoft Azure Management / (Gov Tenant) Azure Government Cloud Management API
  • Conditions
  • Grant
    • Grant Access
    • Require Multi-Factor Authentication
    • Require all the selected controls

Log Analytics AAD SigninLogs Query (KQL)

//https://learn.microsoft.com/en-us/azure/active-directory/conditional-access/howto-conditional-access-policy-azure-management
//Common Conditional Access policy: Require MFA for Azure management
let includeapps = pack_array("Windows Azure Service Management API","Azure Resource Manager","Azure portal","Azure Data Lake","Application Insights API","Log Analytics API");
AADNonInteractiveUserSignInLogs
| union SigninLogs
| where TimeGenerated > ago(14d) and ResultType == 0 and AuthenticationRequirement == "singleFactorAuthentication" 
| where AADTenantId == ResourceTenantId
| where  ResourceDisplayName in (includeapps) or AppDisplayName in (includeapps)
| distinct AppDisplayName, UserPrincipalName, ConditionalAccessStatus, AuthenticationRequirement, ResourceDisplayName

Comment
This policy is great for organizations that have trusted network based filters on the base conditional access policy. This will make sure users that use tools that can be used to perform queries or changes against Azure subscriptions must require MFA from both trusted and non trusted networks.

Revew the list of users in the results. in the example image below, the breakglass account is the only account being used to signin to those end points. That particular account should be excluded from the policy. But also shouldnt be used. Any other account such as possible service accounts used for azure automation will need to be excluded from the policy and should eventually transition to using a service principal instead.

Untitled

Back to Matrix


Block Legacy Authentication

Conditional Access Policy Setup

  • Create Conditional Access Policy:
  • Users
    • Include: All Users
    • Exclude: Breakglass, Exclusion Group
  • Cloud Apps or Actions
    • Select what this policy applies to: Cloud Apps
    • Include: All Cloud Apps
  • Conditions
    • Client apps
    • Exchange ActiveSync clients
    • Other clients
  • Grant
    • Block Access

Log Analytics AAD SigninLogs Query (KQL)

AADNonInteractiveUserSignInLogs
| union SigninLogs
| where TimeGenerated > ago(14d) and ResultType == 0
| extend ClientAppUsed = iff(isempty(ClientAppUsed) == true, "Unknown", ClientAppUsed)  
| extend isLegacyAuth = case(ClientAppUsed contains "Browser", "No", ClientAppUsed contains "Mobile Apps and Desktop clients", "No", ClientAppUsed contains "Exchange ActiveSync", "Yes", ClientAppUsed contains "Exchange Online PowerShell","Yes", ClientAppUsed contains "Unknown", "Unknown", "Yes") 
| where isLegacyAuth == "Yes"
| distinct UserDisplayName, UserPrincipalName, AppDisplayName, ClientAppUsed, isLegacyAuth, UserAgent, Category

Comment
No example image to show what these results look like. Review the results from the query which pulls from both the interactive and non interactive logs. work with the users to remove the dependancy. The sooner this policy is in place the better.

Back to Matrix


Require privileged user to MFA

Conditional Access Policy Setup

  • Create Conditional Access Policy:
  • Users
    • Include: Directory Roles (Application Administrator,Authentication Administrator,Cloud Application Administrator,Conditional Access Administrator,Exchange Administrator,Global Administrator,Helpdesk Administrator,Hybrid Identity Administrator,Password Administrator,Privileged Authentication Administrator,Privileged Role Administrator,Security Administrator,SharePoint Administrator,User Administrator)
    • Exclude: Breakglass, Exclusion Group
  • Cloud Apps or Actions
    • Select what this policy applies to: Cloud apps
    • Include: All Cloud Apps
    • Exclude: None
  • Conditions
  • Grant
    • Grant Access
    • Require Multi-Factor Authentication
    • Require all the selected controls
  • Session
    • Sign-in frequency 2 Hours

Get list of Privileged Users using PowerShell to use in the kql below

Connect-MgGraph
Select-MgProfile -Name beta
$roles = @("Application Administrator","Authentication Administrator","Cloud Application Administrator","Conditional Access Administrator","Exchange Administrator","Global Administrator","Helpdesk Administrator","Hybrid Identity Administrator","Password Administrator","Privileged Authentication Administrator","Privileged Role Administrator","Security Administrator","SharePoint Administrator","User Administrator")
(Get-MgDirectoryRole -ExpandProperty members -all | where {$_.displayname -In $roles} | select -ExpandProperty members).id  -join('","') | out-file .\privuser.txt

Log Analytics AAD SigninLogs Query (KQL) needs results from the PowerShell script

let privusers = pack_array("**replace this with the results from the privuser.txt found from the powershell cmdlets**");
AADNonInteractiveUserSignInLogs
| union SigninLogs
| where TimeGenerated > ago(14d) and UserId  in~ (privusers) and ResultType == 0 and AuthenticationRequirement == "singleFactorAuthentication" 
| where AppDisplayName  <> "Windows Sign In" and AppDisplayName <> "Microsoft Authentication Broker" and AppDisplayName <> 'Microsoft Account Controls V2' 
| distinct AppDisplayName,UserPrincipalName,ConditionalAccessStatus,AuthenticationRequirement, Category

Log Analytics AAD SigninLogs and AuditLogs PIM Query (KQL)

let privroles = pack_array("Application Administrator","Authentication Administrator","Cloud Application Administrator","Conditional Access Administrator","Exchange Administrator","Global Administrator","Helpdesk Administrator","Hybrid Identity Administrator","Password Administrator","Privileged Authentication Administrator","Privileged Role Administrator","Security Administrator","SharePoint Administrator","User Administrator");
let privusers = AuditLogs 
| where TimeGenerated > ago(60d) and ActivityDisplayName == 'Add member to role completed (PIM activation)' and Category == "RoleManagement" 
| extend Caller = tostring(InitiatedBy.user.userPrincipalName) 
| extend Role = tostring(TargetResources[0].displayName) 
| where Role in (privroles) 
| distinct Caller;
AADNonInteractiveUserSignInLogs
| union SigninLogs
| where TimeGenerated > ago(14d) and UserPrincipalName in~ (privusers) and ResultType == 0 and AuthenticationRequirement == "singleFactorAuthentication" 
| where AppDisplayName  <> "Windows Sign In" and AppDisplayName <> "Microsoft Authentication Broker" and AppDisplayName <> 'Microsoft Account Controls V2' 
| distinct AppDisplayName,UserPrincipalName,ConditionalAccessStatus,AuthenticationRequirement, Category

Sentinel AAD SigninLogs Query (KQL) requires UEBA turned on

let privroles = pack_array("Application Administrator","Authentication Administrator","Cloud Application Administrator","Conditional Access Administrator","Exchange Administrator","Global Administrator","Helpdesk Administrator","Hybrid Identity Administrator","Password Administrator","Privileged Authentication Administrator","Privileged Role Administrator","Security Administrator","SharePoint Administrator","User Administrator");
let privusers = IdentityInfo | where TimeGenerated > ago(60d) and AssignedRoles != "[]" | mv-expand AssignedRoles | extend Roles = tostring(AssignedRoles) | where Roles in (privroles) | distinct AccountUPN;
AADNonInteractiveUserSignInLogs
| union SigninLogs
| where TimeGenerated > ago(14d) and UserPrincipalName in~ (privusers) and ResultType == 0 and AuthenticationRequirement == "singleFactorAuthentication" 
| where AppDisplayName  <> "Windows Sign In" and AppDisplayName <> "Microsoft Authentication Broker" and AppDisplayName <> 'Microsoft Account Controls V2' 
| distinct AppDisplayName,UserPrincipalName,ConditionalAccessStatus,AuthenticationRequirement, Category

Comment
The results of these queries will show privileged users that are not using MFA when signing in. Review the results and look for for user based service accounts that will be affected by this policy and exclude them.

Back to Matrix


Block privileged user from legacy authentication

Conditional Access Policy Setup

  • Create Conditional Access Policy:
  • Users
    • Include: Include: Directory Roles (Application Administrator,Authentication Administrator,Cloud Application Administrator,Conditional Access Administrator,Exchange Administrator,Global Administrator,Helpdesk Administrator,Hybrid Identity Administrator,Password Administrator,Privileged Authentication Administrator,Privileged Role Administrator,Security Administrator,SharePoint Administrator,User Administrator)
    • Exclude: Breakglass, Exclusion Group
  • Cloud Apps or Actions
    • Select what this policy applies to: Cloud apps
    • Include: All Cloud Apps
    • Exclude: None
  • Conditions
    • Client apps
      • Exchange ActiveSync clients
      • Other clients
  • Grant
    • Block Access

Get list of Privileged Users using PowerShell to use in the kql below

Connect-MgGraph
Select-MgProfile -Name beta
$roles = @("Application Administrator","Authentication Administrator","Cloud Application Administrator","Conditional Access Administrator","Exchange Administrator","Global Administrator","Helpdesk Administrator","Hybrid Identity Administrator","Password Administrator","Privileged Authentication Administrator","Privileged Role Administrator","Security Administrator","SharePoint Administrator","User Administrator")
(Get-MgDirectoryRole -ExpandProperty members -all | where {$_.displayname -In $roles} | select -ExpandProperty members).id  -join('","') | out-file .\privuser.txt

Log Analytics AAD SigninLogs Query (KQL) needs results from the PowerShell script

let privusers = pack_array("**replace this with the results from the privuser.txt found from the powershell cmdlets**");
AADNonInteractiveUserSignInLogs
| union SigninLogs
| where TimeGenerated > ago(14d) and UserId  in~ (privusers) and ResultType == 0 
| extend ClientAppUsed = iff(isempty(ClientAppUsed) == true, "Unknown", ClientAppUsed)  
| extend isLegacyAuth = case(ClientAppUsed contains "Browser", "No", ClientAppUsed contains "Mobile Apps and Desktop clients", "No", ClientAppUsed contains "Exchange ActiveSync", "Yes", ClientAppUsed contains "Exchange Online PowerShell","Yes", ClientAppUsed contains "Unknown", "Unknown", "Yes") 
| where isLegacyAuth == "Yes"
| distinct AppDisplayName,UserPrincipalName,ConditionalAccessStatus,AuthenticationRequirement, isLegacyAuth

Log Analytics AAD SigninLogs and AuditLogs PIM Query (KQL)

let privroles = pack_array("Application Administrator","Authentication Administrator","Cloud Application Administrator","Conditional Access Administrator","Exchange Administrator","Global Administrator","Helpdesk Administrator","Hybrid Identity Administrator","Password Administrator","Privileged Authentication Administrator","Privileged Role Administrator","Security Administrator","SharePoint Administrator","User Administrator");
let privusers = AuditLogs 
| where TimeGenerated > ago(60d) and ActivityDisplayName == 'Add member to role completed (PIM activation)' and Category == "RoleManagement" 
| extend Caller = tostring(InitiatedBy.user.userPrincipalName) | extend Role = tostring(TargetResources[0].displayName) | where Role in (privroles) | distinct Caller;
AADNonInteractiveUserSignInLogs 
| union SigninLogs 
| where TimeGenerated > ago(14d) and UserPrincipalName in~ (privusers) and ResultType == 0 
| extend ClientAppUsed = iff(isempty(ClientAppUsed) == true, "Unknown", ClientAppUsed)  
| extend isLegacyAuth = case(ClientAppUsed contains "Browser", "No", ClientAppUsed contains "Mobile Apps and Desktop clients", "No", ClientAppUsed contains "Exchange ActiveSync", "Yes", ClientAppUsed contains "Exchange Online PowerShell","Yes", ClientAppUsed contains "Unknown", "Unknown", "Yes") 
| where isLegacyAuth == "Yes"
| distinct AppDisplayName,UserPrincipalName,ConditionalAccessStatus,AuthenticationRequirement, isLegacyAuth

Sentinel AAD SigninLogs Query (KQL) requires UEBA turned on

let privroles = pack_array("Application Administrator","Authentication Administrator","Cloud Application Administrator","Conditional Access Administrator","Exchange Administrator","Global Administrator","Helpdesk Administrator","Hybrid Identity Administrator","Password Administrator","Privileged Authentication Administrator","Privileged Role Administrator","Security Administrator","SharePoint Administrator","User Administrator");
let privusers = IdentityInfo | where TimeGenerated > ago(60d) and AssignedRoles != "[]" | mv-expand AssignedRoles | extend Roles = tostring(AssignedRoles) | where Roles in (privroles) | distinct AccountUPN
AADNonInteractiveUserSignInLogs 
| union SigninLogs 
| where TimeGenerated > ago(14d) and UserPrincipalName in~ (privusers) and ResultType == 0 
| extend ClientAppUsed = iff(isempty(ClientAppUsed) == true, "Unknown", ClientAppUsed)  
| extend isLegacyAuth = case(ClientAppUsed contains "Browser", "No", ClientAppUsed contains "Mobile Apps and Desktop clients", "No", ClientAppUsed contains "Exchange ActiveSync", "Yes", ClientAppUsed contains "Exchange Online PowerShell","Yes", ClientAppUsed contains "Unknown", "Unknown", "Yes") 
| where isLegacyAuth == "Yes"
| distinct AppDisplayName,UserPrincipalName,ConditionalAccessStatus,AuthenticationRequirement, isLegacyAuth

Comment
The list from the results are privileged users that would be affected by this policy. I have found it easier for orgs to apply these restrictions to privileged users before applying to all users. The goal of this policy is to make sure no privileged user is actively using basic authentication.

Back to Matrix


Block the Directory Sync Account from non trusted locations

  • Link to Microsoft Documentation: Named locations
  • Requires Named Locations to be created and trusted

Conditional Access Policy Setup

  • Create Conditional Access Policy:
  • Users
    • Include: Directory Role (Directory Sync Account)
    • Exclude: Breakglass, Exclusion Group
  • Cloud Apps or Actions
    • Select what this policy applies to: Cloud apps
    • Include: All Cloud Apps
    • Exclude: None
  • Conditions
    • Locations
    • Include: Any Location
    • Exclude: All trusted locations
  • Grant
    • Block Access

Log Analytics AAD SigninLogs Query (KQL)

AADNonInteractiveUserSignInLogs 
| union SigninLogs 
| where TimeGenerated > ago(14d) 
| where UserPrincipalName startswith "Sync_" 
| extend TrustedLocation = tostring(iff(NetworkLocationDetails contains 'trustedNamedLocation', 'trustedNamedLocation','')) 
| distinct IPAddress, TrustedLocation, UserPrincipalName

Comment
This account if compromised is limited to a particular endpoint, however it is possible to compromise the account. The goal is to make sure it is limited to trusted networks. This query will show if any of the accounts are being used from untrusted networks. the idea is to review the ip address and define it if it is trusted.

Untitled

Back to Matrix


Block Guest from Azure Management

Conditional Access Policy Setup

  • Create Conditional Access Policy:
  • Users
    • Include: All guest and external users
    • Exclude: Breakglass, Exclusion Group
  • Cloud Apps or Actions
    • Select what this policy applies to: Cloud apps
    • Include: Microsoft Azure Management / (Gov Tenant) Azure Government Cloud Management API
    • Exclude: None
  • Conditions
  • Grant
    • Block Access

Log Analytics AAD SigninLogs Query (KQL)

let includeapps = pack_array("Windows Azure Service Management API","Azure Resource Manager","Azure portal","Azure Data Lake","Application Insights API","Log Analytics API");
SigninLogs
| where TimeGenerated > ago(14d) and ResultType == 0 and AuthenticationRequirement == "singleFactorAuthentication"  and UserType == "Guest"  
| where  ResourceDisplayName in (includeapps)  or AppDisplayName in (includeapps)
| where AADTenantId == ResourceTenantId
| distinct AppDisplayName, UserPrincipalName, ConditionalAccessStatus, AuthenticationRequirement, ResourceDisplayName

Comment
This policy is designed to stop guest accounts from using management portals. The idea is guest should be restricted from doing admin type work.

The results from this query are straight forward. The goal is to provide a list of users that might be affected by applying this policy. Review the results of this and put in exclusions as needed.

Back to Matrix


Require guest to MFA

Conditional Access Policy Setup

  • Create Conditional Access Policy:
  • Users
    • Include: Guest
    • Exclude: Breakglass, Exclusion Group
  • Cloud Apps or Actions
    • Select what this policy applies to: Cloud apps
    • Include: All Cloud Apps
    • Exclude: None
  • Conditions
  • Grant
    • Grant Access
    • Require Multi-Factor Authentication
    • Require all the selected controls

Log Analytics AAD SigninLogs Query (KQL)

// URL: https://learn.microsoft.com/en-us/azure/active-directory/external-identities/b2b-tutorial-require-mfa
let excludeapps = pack_array("Windows Sign In","Microsoft Authentication Broker","Microsoft Account Controls V2","Microsoft Intune Company Portal","Microsoft Mobile Application Management");
SigninLogs 
| where TimeGenerated > ago(14d) and UserType == "Guest" and AppDisplayName !in (excludeapps)
| where ResultType == 0 and AuthenticationRequirement == "singleFactorAuthentication"
| where AADTenantId == ResourceTenantId
| distinct AppDisplayName,UserPrincipalName,ConditionalAccessStatus,AuthenticationRequirement,Category, UserType
| summarize apps=make_list(AppDisplayName) by UserPrincipalName,ConditionalAccessStatus,AuthenticationRequirement, Category, UserType

Comment
Guest users should be treated no differently than regular users. Having a guest register and use mfa adds an additional layer of security as there is no way to know if the guest's email is as secure. The results from this query show users that did not get prompted for MFA when logging into the tenant, review the list and exclude guest or applications that need to be excluded. One thing to note is the guest user may need a way outside of self service to reset their MFA.

Back to Matrix


Require Compliant Device for Office 365 or All Apps

Conditional Access Policy Setup

  • Create Conditional Access Policy:
  • Users
    • Include: All Users
    • Exclude: Breakglass, Exclusion Group, Directory Role (Directory Sync Accounts), Guest
  • Cloud Apps or Actions
    • Select what this policy applies to: Cloud apps
    • Include: All Cloud Apps (or at minimum Office 365)
  • Conditions
  • Grant
    • Grant Access
    • Require device to be marked as compliant
    • Require one of the selected controls

Log Analytics AAD SigninLogs Query (KQL)

let includeapps = pack_array("Exchange Online","Microsoft 365 Search Service","Microsoft Forms","Microsoft Planner","Microsoft Stream","Microsoft Teams","Microsoft To-Do","Microsoft Flow","Microsoft Office 365 Portal","Microsoft Office client application","Microsoft Stream","Microsoft To-Do WebApp","Microsoft Whiteboard Services","Office Delve","Office Online","OneDrive","Power Apps","Power Automate","Security & compliance portal","SharePoint Online","Skype for Business Online","Skype and Teams Tenant Admin API","Sway","Yammer");
AADNonInteractiveUserSignInLogs
| extend trustType = tostring(parse_json(DeviceDetail).trustType) 
| extend isCompliant = tostring(parse_json(DeviceDetail).isCompliant) 
| project-away DeviceDetail
| union SigninLogs
| where TimeGenerated > ago(14d) and ResultType == 0
| extend os = tostring(parse_json(DeviceDetail).operatingSystem) 
| extend trustType = tostring(parse_json(DeviceDetail).trustType) 
| extend isCompliant = tostring(parse_json(DeviceDetail).isCompliant) 
| where AADTenantId == ResourceTenantId
| where  ResourceDisplayName in (includeapps) or AppDisplayName in (includeapps)
| where isCompliant <> 'true' and trustType <> "Hybrid Azure AD joined"
| extend os = tostring(parse_json(DeviceDetail).operatingSystem) 
| distinct AppDisplayName,UserPrincipalName,ConditionalAccessStatus,AuthenticationRequirement, trustType,isCompliant,os,Category;

Comment
These results show users logging in with non compliant devices. The initial goal is to have it only apply to users using office 365, ultimately requiring it for all applications. Will want to review these results and determine the impact of this policy.

Back to Matrix


No Persistent Browser and 1 Hour Session for Unmanaged Devices

Conditional Access Policy Setup

  • Create Conditional Access Policy:
  • Users
    • Include: All Users
    • Exclude: Breakglass, Exclusion Group,
  • Cloud Apps or Actions
    • Select what this policy applies to: Cloud apps
    • Include: All cloud apps
    • Exclude: None
  • Conditions
    • Filter for device
    • device.isCompliant -ne True -or device.trustType -ne "ServerAD"
  • Session
    • Sign-in frequency: 1 Hour
    • Persistent browser session: Never persistent

Log Analytics AAD SigninLogs Query (KQL)

SigninLogs 
| where TimeGenerated > ago(14d) and ResultType == 0 and UserType <> "Guest" 
| extend trustType = tostring(DeviceDetail.trustType) 
| extend isCompliant = tostring(DeviceDetail.isCompliant) 
| extend deviceName = tostring(DeviceDetail.displayName) 
| extend os = tostring(DeviceDetail.operatingSystem) 
| extend TrustedLocation = tostring(iff(NetworkLocationDetails contains 'trustedNamedLocation', 'trustedNamedLocation','')) 
| where isCompliant <> 'true' and trustType <> "Hybrid Azure AD joined" and ClientAppUsed == "Browser" 
| distinct UserPrincipalName, os, deviceName, trustType, isCompliant, TrustedLocation

Comment
This policy is to make sure that token session are limited on non trusted devices. This is to help prevent tokens from being compromised and replayed. The results show users that will be affected by this policy. Do not put this in if the device / compliance journey is still under works as users will be prompted frequently.

Back to Matrix


Block clients that do not support modern authentication

Conditional Access Policy Setup

  • Create Conditional Access Policy:
  • Users
    • Include: All Users
    • Exclude: Breakglass, Exclusion Group
  • Cloud Apps or Actions
    • Select what this policy applies to: Cloud Apps
    • Include: All Cloud Apps
  • Conditions
    • Client apps
    • Exchange ActiveSync clients
    • Other clients
  • Grant
    • Block Access

Log Analytics AAD SigninLogs Query (KQL)

AADNonInteractiveUserSignInLogs
| union SigninLogs
| where TimeGenerated > ago(14d) and ResultType == 0
| extend ClientAppUsed = iff(isempty(ClientAppUsed) == true, "Unknown", ClientAppUsed)  
| extend isLegacyAuth = case(ClientAppUsed contains "Browser", "No", ClientAppUsed contains "Mobile Apps and Desktop clients", "No", ClientAppUsed contains "Exchange ActiveSync", "Yes", ClientAppUsed contains "Exchange Online PowerShell","Yes", ClientAppUsed contains "Unknown", "Unknown", "Yes") 
| where isLegacyAuth == "Yes"
| distinct UserDisplayName, UserPrincipalName, AppDisplayName, ClientAppUsed, isLegacyAuth, UserAgent, Category

Comment
These results show users using basic auth. Find users being called out in this and put them in as an exception while working with the account owners to stop using basic authentication.

Back to Matrix


Require privileged user to use compliant device

Conditional Access Policy Setup

  • Create Conditional Access Policy:
  • Users
    • Include: Directory Roles (Application Administrator,Authentication Administrator,Cloud Application Administrator,Conditional Access Administrator,Exchange Administrator,Global Administrator,Helpdesk Administrator,Hybrid Identity Administrator,Password Administrator,Privileged Authentication Administrator,Privileged Role Administrator,Security Administrator,SharePoint Administrator,User Administrator)
    • Exclude: Breakglass, Exclusion Group
  • Cloud Apps or Actions
    • Select what this policy applies to: Cloud apps
    • Include: All Cloud Apps
    • Exclude: None
  • Conditions
  • Grant
    • Grant Access
    • Require device to be marked as compliant
    • Require Hybrid Azure AD joined device
    • Require one of the selected controls Get list of Privileged Users using PowerShell to use in the kql below
Connect-MgGraph
Select-MgProfile -Name beta
$roles = @("Application Administrator","Authentication Administrator","Cloud Application Administrator","Conditional Access Administrator","Exchange Administrator","Global Administrator","Helpdesk Administrator","Hybrid Identity Administrator","Password Administrator","Privileged Authentication Administrator","Privileged Role Administrator","Security Administrator","SharePoint Administrator","User Administrator")
(Get-MgDirectoryRole -ExpandProperty members -all | where {$_.displayname -In $roles} | select -ExpandProperty members).id  -join('","') | out-file .\privuser.txt

Log Analytics AAD SigninLogs Query (KQL) needs results from the PowerShell script

let privusers = pack_array("**replace this with the results from the privuser.txt found from the powershell cmdlets**");
SigninLogs 
| where TimeGenerated > ago(14d) and UserId  in~ (privusers) and ResultType == 0 
| extend trustType = tostring(parse_json(DeviceDetail).trustType) 
| extend isCompliant = tostring(parse_json(DeviceDetail).isCompliant) 
| extend TrustedLocation = tostring(iff(NetworkLocationDetails contains 'trustedNamedLocation', 'trustedNamedLocation',''))
| extend os = tostring(parse_json(DeviceDetail).operatingSystem) 
| where isCompliant <> 'true' and trustType <> "Hybrid Azure AD joined"  
| distinct AppDisplayName,UserPrincipalName,ConditionalAccessStatus,AuthenticationRequirement, TrustedLocation, trustType,isCompliant,os, Category

Log Analytics AAD SigninLogs and AuditLogs PIM Query (KQL)

let privroles = pack_array("Application Administrator","Authentication Administrator","Cloud Application Administrator","Conditional Access Administrator","Exchange Administrator","Global Administrator","Helpdesk Administrator","Hybrid Identity Administrator","Password Administrator","Privileged Authentication Administrator","Privileged Role Administrator","Security Administrator","SharePoint Administrator","User Administrator");
let privusers = AuditLogs 
| where TimeGenerated > ago(60d) and ActivityDisplayName == 'Add member to role completed (PIM activation)' and Category == "RoleManagement" 
| extend Caller = tostring(InitiatedBy.user.userPrincipalName) | extend Role = tostring(TargetResources[0].displayName) | where Role in (privroles) | distinct Caller;
SigninLogs 
| where TimeGenerated > ago(14d) and UserPrincipalName in~ (privusers) and ResultType == 0 
| extend trustType = tostring(parse_json(DeviceDetail).trustType) 
| extend isCompliant = tostring(parse_json(DeviceDetail).isCompliant) 
| extend TrustedLocation = tostring(iff(NetworkLocationDetails contains 'trustedNamedLocation', 'trustedNamedLocation',''))
| extend os = tostring(parse_json(DeviceDetail).operatingSystem) 
| where isCompliant <> 'true' and trustType <> "Hybrid Azure AD joined"  
| distinct AppDisplayName,UserPrincipalName,ConditionalAccessStatus,AuthenticationRequirement, TrustedLocation, trustType,isCompliant,os, Category

Sentinel AAD SigninLogs Query (KQL) requires UEBA turned on

let privroles = pack_array("Application Administrator","Authentication Administrator","Cloud Application Administrator","Conditional Access Administrator","Exchange Administrator","Global Administrator","Helpdesk Administrator","Hybrid Identity Administrator","Password Administrator","Privileged Authentication Administrator","Privileged Role Administrator","Security Administrator","SharePoint Administrator","User Administrator");
let privusers = IdentityInfo | where TimeGenerated > ago(60d) and AssignedRoles != "[]" | mv-expand AssignedRoles | extend Roles = tostring(AssignedRoles) | where Roles in (privroles) | distinct AccountUPN
SigninLogs 
| where TimeGenerated > ago(14d) and UserPrincipalName in~ (privusers) and ResultType == 0 
| extend trustType = tostring(parse_json(DeviceDetail).trustType) 
| extend isCompliant = tostring(parse_json(DeviceDetail).isCompliant) 
| extend TrustedLocation = tostring(iff(NetworkLocationDetails contains 'trustedNamedLocation', 'trustedNamedLocation',''))
| extend os = tostring(parse_json(DeviceDetail).operatingSystem) 
| where isCompliant <> 'true' and trustType <> "Hybrid Azure AD joined"  
| distinct AppDisplayName,UserPrincipalName,ConditionalAccessStatus,AuthenticationRequirement, TrustedLocation, trustType,isCompliant,os, Category

Comment
This shows privileged users that are logging in without a compliant devices. I have found most orgs are not to a point to be able to require this for all users. the goal is to focus on the privileged accounts to make sure an admin is not logging into random machines where tokens could be exploited.

Back to Matrix


Block when user risk is high

Conditional Access Policy Setup

  • Create Conditional Access Policy:
  • Users
    • Include: All Users
    • Exclude: Guests, Breakglass, Exclusion Group
  • Cloud Apps or Actions
    • Select what this policy applies to: Cloud apps
    • Include: All Cloud Apps
    • Exclude: None
  • Conditions
    • User risk: High
  • Grant
    • Block Access
  • Session
    • Sign-in frequency
    • Every time

Log Analytics AAD SigninLogs Query (KQL)

SigninLogs 
| where TimeGenerated > ago(14d) 
| where RiskState == "atRisk" and RiskLevelAggregated == "high"
| project AppDisplayName, UserPrincipalName, RiskLevelAggregated, RiskLevelDuringSignIn, RiskState, RiskDetail,IsRisky, RiskEventTypes_V2, MfaDetail, ConditionalAccessStatus, AuthenticationRequirement, ResultType

Comment
Having two conditional access policies one blocking high sign-in risk and one blocking high user risk is really important. The image below shows the importance of this. I took a user and logged into a tenant with a tor browser, This action alone only had a sign in risk of medium. Next I went to add a MFA into my profile and immediately the user risk went up to high and would have instantly blocked me if the policy was enabled preventing the "bad actor" from registering their own MFA creds.

Untitled

Back to Matrix


Block when sign-in risk is high

Conditional Access Policy Setup

  • Create Conditional Access Policy:
  • Users
    • Include: All Users
    • Exclude: Guest, Breakglass, Exclusion Group
  • Cloud Apps or Actions
    • Select what this policy applies to: Cloud apps
    • Include: All Cloud Apps
    • Exclude: None
  • Conditions
    • Sign-in risk: High
  • Grant
    • Block Access
  • Session
    • Sign-in frequency
    • Every time

Log Analytics AAD SigninLogs Query (KQL)

SigninLogs 
| where TimeGenerated > ago(14d) and ResultType == 0 and UserType <> "Guest"
| where RiskLevelDuringSignIn in ("high") 
| project ResultType, ResultDescription,AppDisplayName, UserPrincipalName, RiskLevelAggregated, RiskLevelDuringSignIn, RiskState, RiskDetail, RiskEventTypes_V2, ConditionalAccessStatus, AuthenticationRequirement

Comment
Microsoft documents say to require the user to MFA, But all of Microsoft actual security documents say that this should be a block instead. From personal experience and the guidance from the lapsus incidents is that this needs be a block action. Specially if spammable MFA is present and who knows what admins could be easily convinced to accept a mfa prompt for a price.

No image available, the results to this will be very similiar to the results from the Require MFA when sign-in risk is low, medium, or high query. The users with a high sign-in risk will be blocked. The goal will be to have a user adjust how they are logging in to make sure it does not come in as high. An orgonization should not want to leverage any exclusion on this and I find that most orgs have no issues putting this one in place.

Back to Matrix


Require MFA when sign-in risk is low, medium, or high

  • Link to Microsoft Documentation: change me
  • This policy will require
  • Ideally use a block over Change Password.

Conditional Access Policy Setup

  • Create Conditional Access Policy:
  • Users
    • Include: All Users
    • Exclude: Guest, Breakglass, Exclusion Group
  • Cloud Apps or Actions
    • Select what this policy applies to: Cloud apps
    • Include: All Cloud Apps
    • Exclude: None
  • Conditions
    • Sign-in risk: High, Medium, Low
  • Grant
    • Require MFA
  • Session
    • Sign-in frequency
    • Every time

Log Analytics AAD SigninLogs Query (KQL)

SigninLogs 
| where TimeGenerated > ago(14d) and ResultType == 0 and UserType <> "Guest"
| where RiskLevelDuringSignIn in ("high","medium","low") 
| distinct AppDisplayName, UserPrincipalName, RiskLevelAggregated, RiskLevelDuringSignIn, RiskState, RiskDetail, RiskEventTypes_V2, ConditionalAccessStatus, AuthenticationRequirement

Comment
This policy will require anyone with a risky sign-in to have to provide mfa, The High will be blocked due to the other policy so the mfa will only occur for low and medium. The image below is an example of the results, It will more than likely be a lot more users in an actual production tenant.

Untitled

Back to Matrix


Block when privileged role member user risk is low medium high

  • Link to Microsoft Documentation: change me
  • This policy will require
  • Ideally use a block over MFA

Conditional Access Policy Setup

  • Create Conditional Access Policy:
  • Users
    • Include: Directory Roles (Application Administrator,Authentication Administrator,Cloud Application Administrator,Conditional Access Administrator,Exchange Administrator,Global Administrator,Helpdesk Administrator,Hybrid Identity Administrator,Password Administrator,Privileged Authentication Administrator,Privileged Role Administrator,Security Administrator,SharePoint Administrator,User Administrator)
    • Exclude: Breakglass, Exclusion Group
  • Cloud Apps or Actions
    • Select what this policy applies to: Cloud apps
    • Include: All Cloud Apps
    • Exclude: None
  • Conditions
    • User risk: High, Medium, Low
  • Grant
    • Block Access
  • Session
    • Sign-in frequency
    • Every time

Get list of Privileged Users using PowerShell to use in the kql below

Connect-MgGraph
Select-MgProfile -Name beta
$roles = @("Application Administrator","Authentication Administrator","Cloud Application Administrator","Conditional Access Administrator","Exchange Administrator","Global Administrator","Helpdesk Administrator","Hybrid Identity Administrator","Password Administrator","Privileged Authentication Administrator","Privileged Role Administrator","Security Administrator","SharePoint Administrator","User Administrator")
(Get-MgDirectoryRole -ExpandProperty members -all | where {$_.displayname -In $roles} | select -ExpandProperty members).id  -join('","') | out-file .\privuser.txt

Log Analytics AAD SigninLogs Query (KQL) needs results from the PowerShell script

let privusers = pack_array("**replace this with the results from the privuser.txt found from the powershell cmdlets**");
SigninLogs 
| where TimeGenerated > ago(14d) and UserId  in~ (privusers) and RiskLevelAggregated in ("high","medium","low") 
| project AppDisplayName, UserPrincipalName, RiskLevelAggregated, RiskLevelDuringSignIn, RiskState, RiskDetail, RiskEventTypes_V2, ConditionalAccessStatus, AuthenticationRequirement, Category

Log Analytics AAD SigninLogs and AuditLogs PIM Query (KQL)

let privroles = pack_array("Application Administrator","Authentication Administrator","Cloud Application Administrator","Conditional Access Administrator","Exchange Administrator","Global Administrator","Helpdesk Administrator","Hybrid Identity Administrator","Password Administrator","Privileged Authentication Administrator","Privileged Role Administrator","Security Administrator","SharePoint Administrator","User Administrator");
let privusers = AuditLogs 
| where TimeGenerated > ago(60d) and ActivityDisplayName == 'Add member to role completed (PIM activation)' and Category == "RoleManagement" 
| extend Caller = tostring(InitiatedBy.user.userPrincipalName) 
| extend Role = tostring(TargetResources[0].displayName) 
| where Role in (privroles) 
| distinct Caller;
SigninLogs 
| where TimeGenerated > ago(14d) and UserPrincipalName in~ (privusers) and RiskLevelAggregated in ("high","medium","low") 
| project AppDisplayName, UserPrincipalName, RiskLevelAggregated, RiskLevelDuringSignIn, RiskState, RiskDetail, RiskEventTypes_V2, ConditionalAccessStatus, AuthenticationRequirement, Category

Sentinel AAD SigninLogs Query (KQL) requires UEBA turned on

let privroles = pack_array("Application Administrator","Authentication Administrator","Cloud Application Administrator","Conditional Access Administrator","Exchange Administrator","Global Administrator","Helpdesk Administrator","Hybrid Identity Administrator","Password Administrator","Privileged Authentication Administrator","Privileged Role Administrator","Security Administrator","SharePoint Administrator","User Administrator");
let privusers = IdentityInfo | where TimeGenerated > ago(60d) and AssignedRoles != "[]" | mv-expand AssignedRoles | extend Roles = tostring(AssignedRoles) | where Roles in (privroles) | distinct AccountUPN
SigninLogs 
| where TimeGenerated > ago(14d) and UserPrincipalName in~ (privusers) and RiskLevelAggregated in ("high","medium","low") 
| project AppDisplayName, UserPrincipalName, RiskLevelAggregated, RiskLevelDuringSignIn, RiskState, RiskDetail, RiskEventTypes_V2, ConditionalAccessStatus, AuthenticationRequirement, Category

Comment
Privileged Role Members should not be allowed to log in when Identity Protection finds the user to be risky. These accounts are highly sensitive and there should be no question if they are secure or not.

The results from thw query finds users that would have triggered this issue. This is a harder query as every company is not using pim or sentinal. may need to use the all users query and research which ones are in the roles.

Back to Matrix


Block when privileged role member sign in risk is low medium high

  • Ideally use a block over MFA

Conditional Access Policy Setup

  • Create Conditional Access Policy:
  • Users
    • Include: Directory Roles (Application Administrator,Authentication Administrator,Cloud Application Administrator,Conditional Access Administrator,Exchange Administrator,Global Administrator,Helpdesk Administrator,Hybrid Identity Administrator,Password Administrator,Privileged Authentication Administrator,Privileged Role Administrator,Security Administrator,SharePoint Administrator,User Administrator)
    • Exclude: Breakglass, Exclusion Group
  • Cloud Apps or Actions
    • Select what this policy applies to: Cloud apps
    • Include: All Cloud Apps
    • Exclude: None
  • Conditions
    • Sign-in risk: High, Medium, Low
  • Grant
    • Block Access
  • Session
    • Sign-in frequency
      • Every time

Get list of Privileged Users using PowerShell to use in the kql below

Connect-MgGraph
Select-MgProfile -Name beta
$roles = @("Application Administrator","Authentication Administrator","Cloud Application Administrator","Conditional Access Administrator","Exchange Administrator","Global Administrator","Helpdesk Administrator","Hybrid Identity Administrator","Password Administrator","Privileged Authentication Administrator","Privileged Role Administrator","Security Administrator","SharePoint Administrator","User Administrator")
(Get-MgDirectoryRole -ExpandProperty members -all | where {$_.displayname -In $roles} | select -ExpandProperty members).id  -join('","') | out-file .\privuser.txt

Log Analytics AAD SigninLogs Query (KQL) needs results from the PowerShell script

let privusers = pack_array("**replace this with the results from the privuser.txt found from the powershell cmdlets**");
SigninLogs 
| where TimeGenerated > ago(14d) and UserId  in~ (privusers) and RiskLevelDuringSignIn in ("high","medium","low") 
| project AppDisplayName, UserPrincipalName, RiskLevelAggregated, RiskLevelDuringSignIn, RiskState, RiskDetail, RiskEventTypes_V2, ConditionalAccessStatus, AuthenticationRequirement, Category

Log Analytics AAD SigninLogs and AuditLogs PIM Query (KQL)

//https://github.com/chadmcox/Azure_Active_Directory/blob/master/Log%20Analytics/Conditional%20Access%20Policy%20Impact%20KQL/Possible%20impact%20of%20Block%20privileged%20user%20if%20sign-in%20risk%20is%20low%20medium%20or%20high.kql
let privroles = pack_array("Application Administrator","Authentication Administrator","Cloud Application Administrator","Conditional Access Administrator","Exchange Administrator","Global Administrator","Helpdesk Administrator","Hybrid Identity Administrator","Password Administrator","Privileged Authentication Administrator","Privileged Role Administrator","Security Administrator","SharePoint Administrator","User Administrator");
let privusers = AuditLogs 
| where TimeGenerated > ago(60d) and ActivityDisplayName == 'Add member to role completed (PIM activation)' and Category == "RoleManagement" 
| extend Caller = tostring(InitiatedBy.user.userPrincipalName) | extend Role = tostring(TargetResources[0].displayName) | where Role in (privroles) | distinct Caller;
SigninLogs 
| where TimeGenerated > ago(14d) and UserPrincipalName in~ (privusers) and RiskLevelDuringSignIn in ("high","medium","low") 
| project AppDisplayName, UserPrincipalName, RiskLevelAggregated, RiskLevelDuringSignIn, RiskState, RiskDetail, RiskEventTypes_V2, ConditionalAccessStatus, AuthenticationRequirement, Category

Sentinel AAD SigninLogs Query (KQL) requires UEBA turned on

let privroles = pack_array("Application Administrator","Authentication Administrator","Cloud Application Administrator","Conditional Access Administrator","Exchange Administrator","Global Administrator","Helpdesk Administrator","Hybrid Identity Administrator","Password Administrator","Privileged Authentication Administrator","Privileged Role Administrator","Security Administrator","SharePoint Administrator","User Administrator");
let privusers = IdentityInfo | where TimeGenerated > ago(60d) and AssignedRoles != "[]" | mv-expand AssignedRoles | extend Roles = tostring(AssignedRoles) | where Roles in (privroles) | distinct AccountUPN
SigninLogs 
| where TimeGenerated > ago(14d) and UserPrincipalName in~ (privusers) and RiskLevelDuringSignIn in ("high","medium","low") 
| project AppDisplayName, UserPrincipalName, RiskLevelAggregated, RiskLevelDuringSignIn, RiskState, RiskDetail, RiskEventTypes_V2, ConditionalAccessStatus, AuthenticationRequirement, Category

Comment
Privileged Role Members should not be allowed to log in when Identity Protection finds the sign-in to be risky. These accounts are highly sensitive and there should be no question if they are secure or not.

The results from thw query finds users that would have triggered this issue. This is a harder query as every company is not using pim or sentinal. may need to use the all users query and research which ones are in the roles.

Back to Matrix


Block when Directory Sync Account sign in risk is low medium high

  • Link to Microsoft Documentation: NA
  • This policy will require P2 License

Conditional Access Policy Setup

  • Create Conditional Access Policy:
  • Users
    • Include: Directory Roles (Directory Sync Account)
    • Exclude: Breakglass, Exclusion Group
  • Cloud Apps or Actions
    • Select what this policy applies to: Cloud apps
    • Include: All Cloud Apps
    • Exclude: None
  • Conditions
    • Sign-in risk: High, Medium, Low
  • Grant
    • Block Access
  • Session
    • Sign-in frequency
      • Every time

Log Analytics AAD SigninLogs Query (KQL)

//if an account is used other than the default one created by azure ad connect you will need to
//update the syncaccount variable with the other account name instead of sync_
let syncaccount = "sync_";
AADNonInteractiveUserSignInLogs 
| union SigninLogs
| where TimeGenerated > ago(14d) 
| where UserPrincipalName startswith syncaccount
| where RiskLevelDuringSignIn in ("high","medium","low") 
| project AppDisplayName,UserPrincipalName,ConditionalAccessStatus,AuthenticationRequirement,Category,RiskLevelDuringSignIn,RiskDetail 

Comment
This query looks in the logs to see if the Azure AD Connect Sync Account is experiencing any sign in risk. Hopefully it is not.
No example to show with this one.

Back to Matrix


Block guest for Low, Medium and High Sign-in Risk

  • Link to Microsoft Documentation: NA
  • This policy will require P2 License
  • Ideally use a block over MFA

Conditional Access Policy Setup

  • Create Conditional Access Policy:
  • Users
    • Include: All guest and external users
    • Exclude: Breakglass, Exclusion Group
  • Cloud Apps or Actions
    • Select what this policy applies to: Cloud apps
    • Include: All Cloud Apps
    • Exclude: None
  • Conditions
    • Sign-in risk: High, Medium, Low
  • Grant
    • Block Access
      Ideally use a block over MFA, but MFA can be used if non spammable MFA is used

Log Analytics AAD SigninLogs Query (KQL)

SigninLogs | where TimeGenerated > ago(14d) and UserType == "Guest" and ResultType == 0 
| where AADTenantId <> HomeTenantId
| where RiskLevelDuringSignIn in ("high","medium") 
| distinct AppDisplayName,UserPrincipalName,ConditionalAccessStatus,AuthenticationRequirement,Category,RiskLevelDuringSignIn,RiskDetail 
| summarize apps=make_list(AppDisplayName) by UserPrincipalName,ConditionalAccessStatus,AuthenticationRequirement, RiskLevelDuringSignIn,RiskDetail

Comment
The results of this query show guest from other tenants that may be impacted by this policy. The goal is if there is any chance an external guest account is trying to access a resource with any kind of risk that they need to bbe blocked. The guest user should be able to change how they are logging in or from and try again. This policy only looks at the risk during signin. This particular sign in risk was due to this guest account using a tor browser.

The results below show a guest account trying to sign into the Azure Portal with a signin risk of medium. Review the results and determine if this policy is going to cause any problems.

Untitled

Back to Matrix


Block Service Principal from Non Trusted Networks

  • Link to Microsoft Documentation: NA

Conditional Access Policy Setup

  • Create Conditional Access Policy:
  • Users or Workload identities
    • What does this apply to? Workload identities
    • Include: All owned service principals
    • Exclude:
  • Cloud Apps or Actions
    • Select what this policy applies to: Cloud Apps
    • Include: All cloud apps
  • Conditions
    • Locations
    • Include: Any Location
    • Exclude: All trusted locations
  • Grant
    • Block Access

Log Analytics AAD SigninLogs Query (KQL)

let trustedNamedLocations = SigninLogs | where TimeGenerated > ago(30d) | where ResultType == "0" | extend TrustedLocation = tostring(iff(NetworkLocationDetails contains 'trustedNamedLocation', 'trustedNamedLocation','')) | where TrustedLocation == "trustedNamedLocation" | distinct IPAddress;
AADServicePrincipalSignInLogs  
| where TimeGenerated > ago(30d)
| where ResultType == 0
| extend TrustedLocation = tostring(iff(IPAddress in (trustedNamedLocations), 'trustedNamedLocation',''))
| extend City = tostring(parse_json(LocationDetails).city)
| extend State = tostring(parse_json(LocationDetails).state)
| extend Country = tostring(parse_json(LocationDetails).countryOrRegion)
| distinct IPAddress, ServicePrincipalName, City, State, Country, TrustedLocation
| summarize spcountbyip = count() by IPAddress, City, State, Country, TrustedLocation

Comment
the AADServicePrincipalSignInLogs only have a subset of the useful properties provided unlike the user signinlogs.

In order to get the current list of trusted location, Had to pull in a unique list of IP's from the user Signinlogs. Then compare them to the list returned from the serviceprincipal logs. The results do very and some of the ip not showing as trusted could actually be trusted so you will want to research and confirm.

The goal is to look at the ones that are showing that they are coming from outside the trusted network and determine impact if they where blocked.

If trustedlocation column is empty that means the query was unable to find a matching ip from the user signin logs that were marked as trusted.

Untitled

Block Service Principal with High Medium Low Risk

Conditional Access Policy Setup

  • Create Conditional Access Policy:
  • Users or Workload identities
    • What does this apply to? Workload identities
    • Include: All owned service principals
    • Exclude:
  • Cloud Apps or Actions
    • Select what this policy applies to: Cloud Apps
    • Include: All cloud apps
  • Conditions
    • Service principal risk
    • Include: High, Medium, Low
  • Grant
    • Block Access

Log Analytics AAD SigninLogs Query (KQL)

//nothing has been written yet to look into these logs
//ServicePrincipalRiskEvents
//RiskyServicePrincipals

References