Detection rules › Kusto
Microsoft Entra ID UserAgent OS Missmatch
This query extracts the operating system from the UserAgent header and compares this to the DeviceDetail information present in Microsoft Entra ID.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Stealth |
Telemetry coverage
| Platform | Record / event type |
|---|---|
| Azure | Success: Successful sign-in |
| Entra ID | SigninLogs sign-in event: Successful sign-in |
Rules detecting the same action
These rules filter on the same operation.
- [Entra ID] Suspicious Continuous OAuth Token Usage (Kusto)
- Anomalous sign-in location by user account and authenticating application (Kusto)
- Anomalous Single Factor Signin (Kusto)
- Authentications of Privileged Accounts Outside of Expected Controls (Kusto)
- Azure Portal sign in from another Azure Tenant (Kusto)
- Azure Service Principal Sign-In Followed by Arc Cluster Credential Access (Elastic)
- Azure SignIn via Legacy Authentication Protocol (Panther)
- Cisco - firewall block but success logon to Microsoft Entra ID (Kusto)
Rule body
id: 6a638d80-f6b2-473b-9087-3cac78a84b40
name: Microsoft Entra ID UserAgent OS Missmatch
description: |
This query extracts the operating system from the UserAgent header and compares this to the DeviceDetail information present in Microsoft Entra ID.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- SigninLogs
- connectorId: AzureActiveDirectory
dataTypes:
- AADNonInteractiveUserSignInLogs
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- DefenseEvasion
relevantTechniques:
- T1036
query: |
let timeframe = 1d;
let ExtractOSFromUA=(ua:string) {
case(
ua has "Windows NT 6.0", "Windows Vista/Windows Server 2008",
ua has "Windows NT 6.1", "Windows 7/Windows Server 2008R2",
ua has "Windows NT 6.1", "Windows 7/Windows Server 2008",
ua has "Windows NT 6.2", "Windows 8/Windows Server 2012",
ua has "Windows NT 6.3", "Windows 8.1/Windows Server 2012R2",
ua has "Windows NT 10.0", "Windows 10",
ua has "Windows NT 11.0", "Windows 11",
ua has "Windows Phone", "WindowsPhone",
ua has "Android", "Android",
ua has "iPhone;", "IOS",
ua has "iPad;", "IOS",
ua has "Polycom/", "Polycom",
ua has "Darwin/", "MacOS",
ua has "Mac OS X", "MacOS",
ua has "macOS", "MacOS",
ua has "ubuntu", "Linux",
ua has "Linux", "Linux",
ua has "curl", "CLI",
ua has "python", "CLI",
"Unknown"
)
};
// Query to obtain 'simplified' user agents in a given timespan.
union withsource=tbl_name AADNonInteractiveUserSignInLogs, SigninLogs
| where TimeGenerated >= ago(timeframe)
| extend UserAgentOS=tolower(ExtractOSFromUA(UserAgent))
| where not(isempty(UserAgent))
| where not(isempty(AppId))
| where ResultType == 0
| extend DeviceOS=tolower(DeviceDetail_dynamic.operatingSystem)
| where not(isempty(DeviceOS))
| where not(UserAgentOS == "unknown")
// Look for matches both ways, since sometimes the browser OS is more specific and sometimes the DeviceOS is more specific.
| where not(UserAgentOS contains DeviceOS) and not(DeviceOS contains UserAgentOS)
| where not(DeviceOS == "ios" and UserAgentOS == "macos") // This can happen for 'request desktop site'
| where not(DeviceOS == "android" and UserAgentOS == "linux") // Android and Linux sometimes confused
| summarize count(), arg_min(TimeGenerated,*) by DeviceOS, UserAgentOS, UserPrincipalName
// Begin allow-list.
// End allow-list.
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: UserPrincipalName
version: 1.0.2
kind: Scheduled
Stages and Predicates
Stage 0: let
let timeframe = 1d;
let ExtractOSFromUA=(ua:string) {
case(
ua has "Windows NT 6.0", "Windows Vista/Windows Server 2008",
ua has "Windows NT 6.1", "Windows 7/Windows Server 2008R2",
ua has "Windows NT 6.1", "Windows 7/Windows Server 2008",
ua has "Windows NT 6.2", "Windows 8/Windows Server 2012",
ua has "Windows NT 6.3", "Windows 8.1/Windows Server 2012R2",
ua has "Windows NT 10.0", "Windows 10",
ua has "Windows NT 11.0", "Windows 11",
ua has "Windows Phone", "WindowsPhone",
ua has "Android", "Android",
ua has "iPhone;", "IOS",
ua has "iPad;", "IOS",
ua has "Polycom/", "Polycom",
ua has "Darwin/", "MacOS",
ua has "Mac OS X", "MacOS",
ua has "macOS", "MacOS",
ua has "ubuntu", "Linux",
ua has "Linux", "Linux",
ua has "curl", "CLI",
ua has "python", "CLI",
"Unknown"
)
};
Stage 1: union
union of 2 branches
Stage 2: source
AADNonInteractiveUserSignInLogs
Stage 3: source
SigninLogs
Stage 4: where
where TimeGenerated >= ago(86400s)
Stage 5: extend
extend UserAgentOS
Stage 6: where
where not (isempty(UserAgent))
Stage 7: where
where not (isempty(AppId))
Stage 8: where
where ResultType == 0
Stage 9: extend
extend DeviceOS
Stage 10: where
where not (isempty(DeviceOS))
Stage 11: where
where not (UserAgentOS =~ "unknown")
Stage 12: where
where not (DeviceOS contains UserAgentOS) and not (UserAgentOS contains DeviceOS)
Stage 13: where
where not ((DeviceOS =~ "ios" and UserAgentOS =~ "macos"))
Stage 14: where
where not ((DeviceOS =~ "android" and UserAgentOS =~ "linux"))
Stage 15: summarize
summarize by DeviceOS, UserAgentOS, UserPrincipalName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ResultType | eq |
| field:"ResultType" kind:eq value:"0" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
UserAgent | is_null | excludes:UserAgent | |
AppId | is_null | excludes:AppId | |
DeviceOS | is_null | excludes:DeviceOS | |
UserAgentOS | eq | unknown | excludes:UserAgentOS field:"UserAgentOS" value:"unknown" |
DeviceOS | cross_field_compare | UserAgentOS | excludes:DeviceOS field:"DeviceOS" value:"UserAgentOS" |
UserAgentOS | cross_field_compare | DeviceOS | excludes:UserAgentOS field:"UserAgentOS" value:"DeviceOS" |
DeviceOS | eq | ios | excludes:DeviceOS field:"DeviceOS" value:"ios" |
UserAgentOS | eq | macos | excludes:UserAgentOS field:"UserAgentOS" value:"macos" |
DeviceOS | eq | android | excludes:DeviceOS field:"DeviceOS" value:"android" |
UserAgentOS | eq | linux | excludes:UserAgentOS field:"UserAgentOS" value:"linux" |
Output fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DeviceOS | summarize |
UserAgentOS | summarize |
UserPrincipalName | summarize |