Query Details

SigninLogs — Service Account Interactive Browser Sign-In

13 SIGNIN Service Account Interactive Login

Query

// ---- Network Allowlist (exclude trusted IPs / CIDR / ranges) --------------
let _allow = materialize(union isfuzzy=true (print R="" | take 0), (_GetWatchlist('NetworkAllowlist') | project R = tostring(IPOrRange)) | where isnotempty(R));
let _allowCIDR  = toscalar(_allow | where not(R matches regex @'^\d+\.\d+\.\d+\.\d+-\d+\.\d+\.\d+\.\d+$') | extend R = iff(R has '/', R, strcat(R, '/32')) | summarize make_list(R));
let _allowRange = toscalar(_allow | where R matches regex @'^\d+\.\d+\.\d+\.\d+-\d+\.\d+\.\d+\.\d+$' | summarize make_list(R));
let _ExcludeAllowlistedIPs = (T:(IPAddress:string)) {
    T
    | extend IPAddress = tostring(IPAddress)
    | where array_length(_allowCIDR) == 0 or isnull(ipv4_is_in_any_range(IPAddress, _allowCIDR)) or not(ipv4_is_in_any_range(IPAddress, _allowCIDR))
    | mv-apply _r = _allowRange to typeof(string) on (
        extend _lo = tostring(split(_r,'-')[0]), _hi = tostring(split(_r,'-')[1])
        | extend _inRange = ipv4_compare(IPAddress, _lo) >= 0 and ipv4_compare(IPAddress, _hi) <= 0
        | summarize _anyInRange = max(toint(_inRange)))
    | where isnull(_anyInRange) or _anyInRange == 0
    | project-away _anyInRange
};
// ---------------------------------------------------------------------------
let serviceAccountKeywords = dynamic([
    "svc", "svc-", "svc.", "-svc", "_svc",
    "app-", "app_", "api-", "api_",
    "bot-", "bot_", "sys-", "sys_",
    "func-", "func_", "daemon", "automation",
    "robot", "service_", "service-", "noreply",
    "pipeline", "cicd", "ci-", "cd-"
]);
SigninLogs
| invoke _ExcludeAllowlistedIPs()
| where TimeGenerated > ago(4h)
| where ResultType == "0"
| where IsInteractive == true
| where ClientAppUsed == "Browser"
| where UserPrincipalName has_any (serviceAccountKeywords)
| project
    TimeGenerated,
    UserPrincipalName,
    IPAddress,
    Location,
    AppDisplayName,
    RiskLevel = RiskLevelDuringSignIn,
    CAStatus = ConditionalAccessStatus,
    UserAgent

Explanation

This query is designed to detect suspicious activity involving service accounts and non-human identities that are performing interactive sign-ins using a web browser. Here's a simplified breakdown of what the query does:

  1. Purpose: The query aims to identify instances where service accounts, which should typically authenticate using non-interactive methods like client credentials or managed identity flows, are instead signing in interactively via a browser. This behavior could indicate credential theft or an attacker using stolen credentials.

  2. Data Source: It uses data from Azure Active Directory's SigninLogs.

  3. Frequency: The query runs every 4 hours and looks at sign-in logs from the past 4 hours.

  4. Network Allowlist: The query excludes sign-ins from trusted IP addresses or ranges specified in a "Network Allowlist" watchlist.

  5. Service Account Identification: It looks for user principal names (UPNs) that contain keywords typically associated with service accounts, such as "svc", "app", "bot", etc.

  6. Conditions: The query filters for successful sign-ins (ResultType "0") that are interactive (IsInteractive is true) and performed using a browser (ClientAppUsed is "Browser").

  7. Output: It projects relevant details such as the time of the sign-in, user principal name, IP address, location, application name, risk level during sign-in, conditional access status, and user agent.

  8. Alerting: If any such sign-ins are detected, an alert is generated with a high severity level. The alert includes details like the service account name and IP address used for the sign-in, suggesting possible credential theft.

  9. Incident Management: The query is configured to create incidents for detected events, with grouping enabled based on account and IP to manage related alerts efficiently.

Overall, this query is a security measure to monitor and alert on potentially unauthorized or risky use of service accounts in an organization's Azure environment.

Details

David Alonso profile picture

David Alonso

Released: April 20, 2026

Tables

SigninLogs

Keywords

SigninLogsAzureActiveDirectoryAccountIPAddressUserPrincipalNameIPAddressLocationAppDisplayNameRiskLevelConditionalAccessStatusUserAgentNetworkAllowlistServiceAccountKeywordsTimeGeneratedResultTypeIsInteractiveClientAppUsed

Operators

letmaterializeunionisfuzzyprinttakeprojecttostringwhereisnotemptytoscalarmatchesregexextendiffstrcatsummarizemake_listnotipv4_is_in_any_rangemv-applytotypeofonsplitipv4_comparemaxtointproject-awaydynamicSigninLogsinvokeagohas_any

MITRE Techniques

Actions

GitHub