Detection rules › Kusto

Microsoft Entra ID UserAgent OS Missmatch

Status
available
Severity
medium
Time window
1d
Group by
DeviceOS, UserAgentOS, UserPrincipalName
Source
github.com/Azure/Azure-Sentinel

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

TacticTechniques
StealthT1036 Masquerading

Rules detecting the same action

Other rules on this platform that filter on the same API call or operation.

Rule body kusto

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

Parameters

let timeframe = 1d;

Let binding: ExtractOSFromUA

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"
    )
};

union withsource=tbl_name (2 sources)

Each leg below queries one source; the rule matches if any leg does. Sources: AADNonInteractiveUserSignInLogs, SigninLogs

Leg 1: AADNonInteractiveUserSignInLogs

Leg 2: SigninLogs

Applied to the combined result

| 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") | where not(UserAgentOS contains DeviceOS) and not(DeviceOS contains UserAgentOS) | where not(DeviceOS == "ios" and UserAgentOS == "macos") | where not(DeviceOS == "android" and UserAgentOS == "linux") | summarize count(), arg_min(TimeGenerated,*) by DeviceOS, UserAgentOS, UserPrincipalName

Exclusions

Top-level NOT(...) conjuncts: predicates this rule actively suppresses.

FieldKindExcluded values
UserAgentis_null(no value, null check)
AppIdis_null(no value, null check)
DeviceOSis_null(no value, null check)
UserAgentOSequnknown
DeviceOScontainsUserAgentOS
UserAgentOScontainsDeviceOS
DeviceOSeqios
UserAgentOSeqmacos
DeviceOSeqandroid
UserAgentOSeqlinux

Indicators

Each row is a field, operator, and value that the rule matches. The corpus column counts how many other rules in the catalog look for the same combination: high numbers point to widely-used, community-vetted indicators. Blank or 1 shows that the indicator is specific to this rule.

FieldKindValues
ResultTypeeq
  • 0 transforms: cased

Output fields

Fields the rule emits when it matches. Chronicle authors list these in the outcome block; they appear on the detection and $risk_score drives alerting. Sentinel / Defender XDR rules build them up through project / summarize / extend stages. Sentinel maps these into alert fields via entityMappings and customDetails; Defender XDR custom detections surface them as alert fields directly.

FieldSource
DeviceOSsummarize
UserAgentOSsummarize
UserPrincipalNamesummarize