HuntCONSTRUCTED

Hunt: successful sign-ins from an address the user has never used

For each account, the successful sign-ins from an IP address not seen for that account in the previous 30 days. The first question in any unfamiliar sign-in alert, asked across the whole tenant at once.

Initial AccessT1078.004checked 2026-09-26

Needs

  • Entra ID sign-in logs (interactive) exported to your SIEM, with at least 30 days of retention
  • For the Splunk and Elastic variants, the Azure add-on or integration that ships the sign-in category

Will also return

  • Travel, new mobile carriers and home broadband that changes address: the commonest result by far.
  • VPN and secure web gateway products that move users between exit addresses.
  • IPv6 addresses, which change often. Consider grouping IPv6 by /64 before comparing.

Reading the results

On a tenant of any size this returns a lot, and most of it is people living their lives. The hunt is not the result list; it is what you sort it by. Three columns separate the rows worth opening:

  • AuthenticationRequirement. A new address that satisfied only a single factor is worth more of your time than one that did MFA.
  • The provider behind the address. Enrich IPAddress with its ASN. Hosting providers and anonymising VPN exits are rare for real users.
  • The app. A first-seen address going straight to Exchange Online or to the Azure portal is a different story from one opening Teams.

When a row looks wrong, the first-15-minutes checklist takes it from there.

What it misses

A stolen session replayed from the attacker's infrastructure appears as a non-interactive sign-in if it is refreshing tokens, and those live in a different table. Run the same logic against AADNonInteractiveUserSignInLogs before concluding that nothing new appeared.

KQL

Microsoft SentinelUntestedwindow: 30-day baseline, last 1 day checked

ResultType is a string in SigninLogs, so success is "0" in quotes. Non-interactive sign-ins, such as token refreshes, are in AADNonInteractiveUserSignInLogs, not here.

let baseline = SigninLogs
    | where TimeGenerated between (ago(30d) .. ago(1d))
    | where ResultType == "0"
    | distinct UserPrincipalName, IPAddress;
SigninLogs
| where TimeGenerated > ago(1d)
| where ResultType == "0"
| join kind=leftanti baseline on UserPrincipalName, IPAddress
| project TimeGenerated, UserPrincipalName, IPAddress, Location, AppDisplayName,
          ClientAppUsed, AuthenticationRequirement, ConditionalAccessStatus, UserAgent
| order by TimeGenerated desc

SPL

Splunk with the Microsoft Azure add-onUntestedwindow: 30 days searched, first seen in the last day kept

Field paths under properties depend on the add-on version and how the data was onboarded. Open one sign-in event and adjust the spath and rename lines before trusting an empty result.

index=azure sourcetype="azure:monitor:aad" category=SignInLogs earliest=-30d@d
| spath path=properties.status.errorCode output=error_code
| where error_code=0
| rename properties.userPrincipalName as user, properties.ipAddress as src_ip
| stats earliest(_time) as first_seen latest(_time) as last_seen count by user src_ip
| where first_seen >= relative_time(now(), "-1d@d")
| convert ctime(first_seen) ctime(last_seen)
| sort - first_seen

Elastic

ES|QL, Elastic Agent Azure sign-in logs integrationUntestedwindow: 30 days, set in the query

ES|QL rather than EQL, because first-seen needs an aggregation. Check the index pattern and the user field name against one document from your integration.

FROM logs-azure.signinlogs-*
| WHERE @timestamp > NOW() - 30 days AND event.outcome == "success"
| STATS first_seen = MIN(@timestamp), last_seen = MAX(@timestamp), sign_ins = COUNT(*)
    BY azure.signinlogs.properties.user_principal_name, source.ip
| WHERE first_seen > NOW() - 1 day
| SORT first_seen DESC

sources

  1. Microsoft Learn: SigninLogs table reference · primary
  2. Microsoft Learn: AADNonInteractiveUserSignInLogs table reference
  3. Elastic documentation: ES|QL
  4. MITRE ATT&CK T1078.004, Valid Accounts: Cloud Accounts

Tags: hunting · identity · entra-id · sign-in · sentinel · splunk · elastic · T1078.004