← Back to SOC feed Coverage →

Same User - Successful logon for a given App and failure on another App within 1m and low distribution

kql MEDIUM Azure-Sentinel
T1087T1021
SigninLogs
huntinglateral-movementmicrosoftofficial
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-06-04T11:00:00Z · Confidence: medium

Hunt Hypothesis

A user successfully logs into one application and fails to log into another within one minute, potentially indicating an adversary attempting to access restricted applications through credential reuse or brute force. SOC teams should proactively hunt for this behavior in Azure Sentinel to identify potential credential compromise or unauthorized access attempts to sensitive applications.

KQL Query


let logonDiff = 1m;
let Success = SigninLogs
| where ResultType == "0"
| where AppDisplayName !in ("Office 365 Exchange Online", "Skype for Business Online", "Office 365 SharePoint Online")
| project SuccessLogonTime = TimeGenerated, UserPrincipalName, IPAddress , SuccessAppDisplayName = AppDisplayName;
let Fail = SigninLogs
| where ResultType !in ("0", "50140")
| where ResultDescription !~ "Other"
| where AppDisplayName !in ("Office 365 Exchange Online", "Skype for Business Online", "Office 365 SharePoint Online")
| project FailedLogonTime = TimeGenerated, UserPrincipalName, IPAddress , FailedAppDisplayName = AppDisplayName, ResultType, ResultDescription;
let InitialDataSet =
Success | join kind= inner (
Fail
) on UserPrincipalName, IPAddress
| where isnotempty(FailedAppDisplayName)
| where SuccessLogonTime < FailedLogonTime and FailedLogonTime - SuccessLogonTime <= logonDiff and SuccessAppDisplayName != FailedAppDisplayName;
let InitialHits =
InitialDataSet
| summarize FailedLogonTime = min(FailedLogonTime), SuccessLogonTime = min(SuccessLogonTime)
by UserPrincipalName, SuccessAppDisplayName, FailedAppDisplayName, IPAddress, ResultType, ResultDescription;
// Only take hits where there is 5 or less distinct AppDisplayNames on the success side as this limits highly active applications where failures occur more regularly
let Distribution =
InitialDataSet
| summarize count_SuccessAppDisplayName = count() by SuccessAppDisplayName, ResultType
| where count_SuccessAppDisplayName <= 5;
InitialHits | join (
   Distribution
) on SuccessAppDisplayName, ResultType
| project UserPrincipalName, SuccessLogonTime, IPAddress, SuccessAppDisplayName, FailedLogonTime, FailedAppDisplayName, ResultType, ResultDescription
| extend timestamp = SuccessLogonTime, AccountCustomEntity = UserPrincipalName, IPCustomEntity = IPAddress

Analytic Rule Definition

id: bc17381e-07ee-48a2-931f-06a3d9e149c9
name: Same User - Successful logon for a given App and failure on another App within 1m and low distribution
description: |
  'This identifies when a user account successfully logs onto a given App and within 1 minute fails to logon to a different App.
  This may indicate a malicious attempt at accessing disallowed Apps for discovery or potential lateral movement'
requiredDataConnectors:
  - connectorId: AzureActiveDirectory
    dataTypes:
      - SigninLogs
tactics:
  - Discovery
  - LateralMovement
relevantTechniques:
  - T1087
  - T1021
query: |

  let logonDiff = 1m;
  let Success = SigninLogs
  | where ResultType == "0"
  | where AppDisplayName !in ("Office 365 Exchange Online", "Skype for Business Online", "Office 365 SharePoint Online")
  | project SuccessLogonTime = TimeGenerated, UserPrincipalName, IPAddress , SuccessAppDisplayName = AppDisplayName;
  let Fail = SigninLogs
  | where ResultType !in ("0", "50140")
  | where ResultDescription !~ "Other"
  | where AppDisplayName !in ("Office 365 Exchange Online", "Skype for Business Online", "Office 365 SharePoint Online")
  | project FailedLogonTime = TimeGenerated, UserPrincipalName, IPAddress , FailedAppDisplayName = AppDisplayName, ResultType, ResultDescription;
  let InitialDataSet =
  Success | join kind= inner (
  Fail
  ) on UserPrincipalName, IPAddress
  | where isnotempty(FailedAppDisplayName)
  | where SuccessLogonTime < FailedLogonTime and FailedLogonTime - SuccessLogonTime <= logonDiff and SuccessAppDisplayName != FailedAppDisplayName;
  let InitialHits =
  InitialDataSet
  | summarize FailedLogonTime = min(FailedLogonTime), SuccessLogonTime = min(SuccessLogonTime)
  by UserPrincipalName, SuccessAppDisplayName, FailedAppDisplayName, IPAddress, ResultType, ResultDescription;
  // Only take hits where there is 5 or less distinct AppDisplayNames on the success side as this limits highly active applications where failures occur more regularly
  let Distribution =
  InitialDataSet
  | summarize count_SuccessAppDisplayName = count() by SuccessAppDisplayName, ResultType
  | where count_SuccessAppDisplayName <= 5;
  InitialHits | join (
     Distribution
  ) on SuccessAppDisplayName, ResultType
  | project UserPrincipalName, SuccessLogonTime, IPAddress, SuccessAppDisplayName, FailedLogonTime, FailedAppDisplayName, ResultType, ResultDescription
  | extend timestamp = SuccessLogonTime, AccountCustomEntity = UserPrincipalName, IPCustomEntity = IPAddress

entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: AccountCustomEntity
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPCustomEntity
version: 1.0.1
metadata:
    source:
        kind: Community
    author:
        name: Shain
    support:
        tier: Community
    categories:
        domains: [ "Security - Other", "Identity" ]

Required Data Sources

Sentinel TableNotes
SigninLogsEnsure this data connector is enabled

MITRE ATT&CK Context

References

False Positive Guidance

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