1. Home
  2. Lab library
  3. IB-DETECT

Hunt Identity Attacks in Sign-in Logs with KQL

Entra sign-in logs record every authentication, and KQL turns them into detections. In this lab you route sign-in logs to a Log Analytics workspace, then write five queries: a baseline, password spray, legacy authentication, repeated MFA denials and…

Level Intermediate Time about 90 minutes Updated
Quick answer

Entra sign-in logs record every authentication, and KQL turns them into detections. In this lab you route sign-in logs to a Log Analytics workspace, then write five queries: a baseline, password spray, legacy authentication, repeated MFA denials and first-time countries. You finish by saving one query as a scheduled alert.

Key takeaways

  • Sign-in logs must be exported to Log Analytics before you can query them with KQL
  • ResultType is the error code. 0 means success and 50126 means a wrong password
  • Password spray looks like one IP address failing against many different users
  • Save your best query as an alert rule so it runs without you

Why this lab matters

The Entra admin centre is fine for looking up one sign-in. It is not built for questions such as "which IP address failed against forty users in an hour?". Kusto Query Language (KQL) in Log Analytics answers that in seconds, and the same query can become an alert in Microsoft Sentinel or Azure Monitor.

From raw sign-in events to a scheduled detection01Export logsDiagnostic settings to a workspace02Baseline queryResults by app and error code03Hunt patternsSpray, legacy, MFA denial04Tune thresholdsRemove known noise05Schedule alertRule and action group
From raw sign-in events to a scheduled detection

What you need

  • A test tenant with Microsoft Entra ID P1 or P2, which is required to export sign-in logs.
  • An Azure subscription and a Log Analytics workspace.
  • The Security Administrator role in Entra, and Log Analytics Contributor on the workspace.

Step 1: Export the logs

  1. In the Entra admin centre go to Identity, Monitoring and health, Diagnostic settings, Add diagnostic setting.
  2. Tick SignInLogs, NonInteractiveUserSignInLogs and AuditLogs.
  3. Choose Send to Log Analytics workspace, pick your workspace and save.
  4. Generate some data: sign in successfully a few times, and enter a wrong password for two or three test users.
Note

New data can take 15 to 30 minutes to appear the first time. If a query returns nothing, check the time range before you change the query.

Step 2: Build a baseline

Open the workspace, select Logs and run:

SigninLogs
| where TimeGenerated > ago(24h)
| summarize Attempts = count(), Users = dcount(UserPrincipalName) by ResultType, ResultDescription
| order by Attempts desc

Learn the common codes in your tenant. The ones you will meet most:

ResultTypeMeaning
0Success
50126Invalid username or password
50053Account locked by smart lockout
50074Strong authentication required
500121MFA failed or was denied
53003Blocked by Conditional Access

Step 3: Hunt for four patterns

Password spray

SigninLogs
| where TimeGenerated > ago(24h)
| where ResultType in ("50126", "50053")
| summarize Failures = count(), TargetUsers = dcount(UserPrincipalName),
            Sample = make_set(UserPrincipalName, 5)
    by IPAddress, bin(TimeGenerated, 1h)
| where TargetUsers >= 10
| order by TargetUsers desc

One address failing against many accounts is the signature. Lower the threshold to 3 in a small test tenant so you can see your own test data.

Legacy authentication

union SigninLogs, AADNonInteractiveUserSignInLogs
| where TimeGenerated > ago(7d)
| where ClientAppUsed !in ("Browser", "Mobile Apps and Desktop clients", "")
| summarize Attempts = count(), Successes = countif(ResultType == "0")
    by UserPrincipalName, ClientAppUsed
| order by Successes desc

Repeated MFA denials

SigninLogs
| where TimeGenerated > ago(24h)
| where ResultType == "500121"
| summarize Denials = count(), IPs = make_set(IPAddress, 5)
    by UserPrincipalName, bin(TimeGenerated, 30m)
| where Denials >= 5

Many denied prompts in a short window can mean an attacker has the password and is hoping the user approves by mistake.

First sign-in from a new country

let known = SigninLogs
    | where TimeGenerated between (ago(30d) .. ago(1d))
    | where ResultType == "0"
    | distinct UserPrincipalName, Location;
SigninLogs
| where TimeGenerated > ago(1d)
| where ResultType == "0" and isnotempty(Location)
| join kind = leftanti known on UserPrincipalName, Location
| project TimeGenerated, UserPrincipalName, Location, IPAddress, AppDisplayName

Step 4: Tune and schedule

  1. Pick the spray query. Add a line to exclude addresses you trust, for example your office or VPN egress: | where IPAddress !in ("203.0.113.10").
  2. Select New alert rule. Measure table rows, threshold greater than 0, evaluate every hour over the last hour.
  3. Attach an action group that emails your test mailbox.
  4. Trigger it with wrong passwords against several test users and confirm the alert arrives.
Verify

You are finished when all five queries return sensible results on your own test data and one scheduled alert has fired and reached you.

Evidence to keep

  • A screenshot of the diagnostic setting.
  • The saved queries, exported or stored in a query pack.
  • The alert rule and one fired alert.

Clean up

Delete the alert rule and action group. Remove the diagnostic setting if the workspace was created only for this lab, because stored logs are billed by volume.

Frequently asked questions

Which licence do I need to query Entra sign-in logs with KQL?

Exporting sign-in logs through diagnostic settings needs Microsoft Entra ID P1 or P2. You also need an Azure subscription with a Log Analytics workspace, which is billed by the amount of data stored.

What does ResultType 50126 mean in SigninLogs?

It means the username or password was invalid. A high count of 50126 from one IP address against many users is a common sign of password spray.

What is the difference between SigninLogs and AADNonInteractiveUserSignInLogs?

SigninLogs holds interactive sign-ins where a user enters credentials or responds to a prompt. AADNonInteractiveUserSignInLogs holds token refreshes and background sign-ins made by clients on the user's behalf, and is usually much larger.

Can these KQL queries be used in Microsoft Sentinel?

Yes. Sentinel runs on a Log Analytics workspace, so the same queries work as hunting queries or scheduled analytics rules.

Built and published by Muhammad Rizwan Afzal for Identity Bytes. Run every lab against a disposable test tenant, never production.