← Back to SOC feed Coverage →

Privileged identities authenticating via legacy protocols

kql MEDIUM Azure-Sentinel
T1078.004T1110.003
IdentityInfoSigninLogs
evasionhuntingmicrosoftofficial
This rule was pulled from an open-source repository and enriched with AI. Validate in a test environment before deploying to production.
View original rule at Azure-Sentinel →
Retrieved: 2026-08-13T11:00:01Z · Confidence: medium

Hunt Hypothesis

This hunt detects adversaries leveraging compromised credentials to authenticate privileged identities via legacy protocols that often bypass modern security controls like Conditional Access policies. Proactively hunting for these events in Azure Sentinel is critical because legacy authentication methods lack robust security features, creating a high-risk entry point for attackers aiming to establish persistence or escalate privileges within the environment.

KQL Query

let timeframe = 30d;
let LegacyProtocols = dynamic([
    "Exchange ActiveSync",
    "IMAP4",
    "MAPI over HTTP",
    "POP3",
    "SMTP Auth",
    "Authenticated SMTP",
    "Other clients"
]);
let PrivilegedUsers = IdentityInfo
| where isnotempty(AssignedRoles) or isnotempty(PrivilegedEntraPimRoles)
| summarize arg_max(Timestamp, AccountUpn) by AccountObjectId;
SigninLogs
| where TimeGenerated > ago(timeframe)
| where ResultType == "0"
| where ClientAppUsed in~ (LegacyProtocols)
| join kind=inner PrivilegedUsers on $left.UserId == $right.AccountObjectId
| extend
    AccountName = tostring(split(UserPrincipalName, "@")[0]),
    AccountUPNSuffix = tostring(split(UserPrincipalName, "@")[1])
| summarize
    SignInAttempts = count(),
    FirstSeen = min(TimeGenerated),
    LastSeen = max(TimeGenerated),
    ClientApps = make_set(ClientAppUsed),
    AuthRequirements = make_set(AuthenticationRequirement),
    LastIP = tostring(arg_max(TimeGenerated, IPAddress))
    by UserPrincipalName, AccountName, AccountUPNSuffix, UserId, AccountUpn, AppDisplayName
| project
    UserPrincipalName, AccountName, AccountUPNSuffix, AccountUpn,
    SignInAttempts, FirstSeen, LastSeen, ClientApps, AuthRequirements,
    LastIP, AppDisplayName
| sort by SignInAttempts desc

Analytic Rule Definition

id: 06184800-6d66-46ce-aabd-7e17e9cf3fb0
name: Privileged identities authenticating via legacy protocols
description: |
  Identifies successful sign-ins by privileged accounts using legacy clients
  such as Exchange ActiveSync, IMAP4, POP3, SMTP Auth, MAPI over HTTP, or other
  legacy clients. These protocols may bypass modern Conditional Access and
  MFA controls.
description-detailed: |
  Joins privileged accounts from IdentityInfo (AssignedRoles /
  PrivilegedEntraPimRoles) against SigninLogs where ClientAppUsed matches
  a legacy protocol and the sign-in succeeded. Review each hit against
  Conditional Access 'legacy authentication' blocking policies and
  block-legacy-auth settings in Exchange Online; disable the legacy
  protocol for any account that does not require it. Protocol names here
  follow the SigninLogs ClientAppUsed values used by existing repo
  legacy-auth detection patterns.
  References:
  - https://learn.microsoft.com/entra/identity/conditional-access/block-legacy-authentication
  - https://attack.mitre.org/techniques/T1078/004/
  - https://attack.mitre.org/techniques/T1110/003/
requiredDataConnectors:
  - connectorId: MicrosoftThreatProtection
    dataTypes:
      - IdentityInfo
  - connectorId: AzureActiveDirectory
    dataTypes:
      - SigninLogs
tactics:
  - InitialAccess
  - CredentialAccess
relevantTechniques:
  - T1078.004
  - T1110.003
query: |
  let timeframe = 30d;
  let LegacyProtocols = dynamic([
      "Exchange ActiveSync",
      "IMAP4",
      "MAPI over HTTP",
      "POP3",
      "SMTP Auth",
      "Authenticated SMTP",
      "Other clients"
  ]);
  let PrivilegedUsers = IdentityInfo
  | where isnotempty(AssignedRoles) or isnotempty(PrivilegedEntraPimRoles)
  | summarize arg_max(Timestamp, AccountUpn) by AccountObjectId;
  SigninLogs
  | where TimeGenerated > ago(timeframe)
  | where ResultType == "0"
  | where ClientAppUsed in~ (LegacyProtocols)
  | join kind=inner PrivilegedUsers on $left.UserId == $right.AccountObjectId
  | extend
      AccountName = tostring(split(UserPrincipalName, "@")[0]),
      AccountUPNSuffix = tostring(split(UserPrincipalName, "@")[1])
  | summarize
      SignInAttempts = count(),
      FirstSeen = min(TimeGenerated),
      LastSeen = max(TimeGenerated),
      ClientApps = make_set(ClientAppUsed),
      AuthRequirements = make_set(AuthenticationRequirement),
      LastIP = tostring(arg_max(TimeGenerated, IPAddress))
      by UserPrincipalName, AccountName, AccountUPNSuffix, UserId, AccountUpn, AppDisplayName
  | project
      UserPrincipalName, AccountName, AccountUPNSuffix, AccountUpn,
      SignInAttempts, FirstSeen, LastSeen, ClientApps, AuthRequirements,
      LastIP, AppDisplayName
  | sort by SignInAttempts desc
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: UserPrincipalName
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: AccountUPNSuffix
      - identifie

Required Data Sources

Sentinel TableNotes
IdentityInfoEnsure this data connector is enabled
SigninLogsEnsure this data connector is enabled

MITRE ATT&CK Context

References

False Positive Guidance

Here are 5 specific false positive scenarios for the “Privileged identities authenticating via legacy protocols” rule, along with targeted filtering strategies:

Original source: https://github.com/Azure/Azure-Sentinel/blob/main/Hunting Queries/SigninLogs/PrivilegedAccountsUsingLegacyAuthentication.yaml