Add hunting queries: break-glass account hunting pack (3 queries) - #14948
Add hunting queries: break-glass account hunting pack (3 queries)#14948Descambiado (descambiado) wants to merge 7 commits into
Conversation
- Sign-in to a designated emergency access account (T1078.004) - Credential or MFA modification on a designated emergency access account (T1098, T1556.006) Both queries key off a BreakGlassAccounts watchlist (AccountUPN column) since break-glass accounts cannot be reliably inferred from log data alone.
Adds the third query to the break-glass account pack, closing the last gap in the account's lifecycle: privilege changes made through direct role assignment or group membership, alongside the sign-in and credential queries already covering usage and secrets. - Break-glass account role or group membership changed (T1098.003)
The sign-in and credentials queries still pointed to a single companion query from before the pack had its third member. Updated both to point readers to the role/group membership query as well.
Two real gaps found on a second pass, cross-checked against this repo's own already-merged content rather than assumption: Credentials/MFA query: password reset and change operation names are not consistent across tenants and Entra ID service versions, this repo alone has three different exact-string lists for the same event in other hunting queries (BulkPasswordResetByActor, PasswordResetThenPrivileged Operation, and this one as originally written). Switched to the same has_any word-pair match the official "Multiple Password Reset by user" analytic rule uses instead of a fourth incomplete exact list. Also stopped assuming the target account sits at TargetResources[0]; the two existing password-reset queries in this repo both read it via mv-apply across all TargetResources entries instead, for good reason. Role/group membership query: the Role.DisplayName and Group.DisplayName lookups used mv-expand followed by a where clause, which silently drops the whole row if that property isn't present in the exact shape expected. Since the watchlist match against a break-glass account is already the real detection signal and the property lookup is only enrichment, a lookup miss should not cost the detection. Split each branch into a base query and a leftouter-joined enrichment query so a row always survives with a placeholder label if the name can't be read.
Both are referenced twice (once to build the name lookup, once as the left side of the join back to it). Without materialize(), Kusto re-evaluates a let expression at each reference instead of caching it, so the new_guid() call inside each would run independently per reference. The RowId used for the join would never match itself, and the leftouter join added in the previous commit to avoid dropping rows would have quietly enriched nothing, every row would have shown "(role name unavailable)" or "(group name unavailable)" instead of the real value, even on a clean match. This repo's own hunting query instructions call out materialize() for exactly this situation (let statements referenced more than once), and three other files in this repo already use it for similar reasons.
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Adds a small “break-glass account” hunting pack to detect sign-in, credential/MFA modifications, and privilege path changes involving designated emergency access accounts via a shared BreakGlassAccounts watchlist.
Changes:
- Adds a SigninLogs hunting query to flag any break-glass account sign-in activity.
- Adds an AuditLogs hunting query to flag break-glass account credential and security-info changes (password/MFA).
- Adds an AuditLogs hunting query to flag direct role assignment events and group-mediated membership changes for break-glass accounts.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| Hunting Queries/SigninLogs/BreakGlassAccountSignIn.yaml | New SigninLogs query to alert on any break-glass account sign-in within the time window. |
| Hunting Queries/AuditLogs/BreakGlassAccountCredentialsModified.yaml | New AuditLogs query to detect password/credential and security-info (MFA) changes affecting break-glass accounts. |
| Hunting Queries/AuditLogs/BreakGlassAccountRoleOrGroupMembershipChanged.yaml | New AuditLogs query combining role-management and group-management changes affecting break-glass accounts, including group-mediated privilege paths. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
The three queries projected AccountUPN directly out of
_GetWatchlist('BreakGlassAccounts'), which fails validation with KS142
because a user-defined watchlist column cannot be resolved against a
known schema.
SearchKey is the standard indexed lookup column on a Sentinel watchlist
and is the column this lookup should use anyway. Documentation in all
three files updated to match.
|
Hi Descambiado (@descambiado), |
Role changes now read the target user and the role name from whichever TargetResources entry describes the user, matching what the group path already did. The previous fixed index could miss detections when the user object was not first, which the comments already acknowledged for groups but not for roles. Group name lookup uses coalesce(newValue, oldValue) so removals, which carry the prior value in oldValue, keep the group display name instead of falling back to the unavailable placeholder. Added the dotted "Remove member from role." variant so both add and remove are normalised the same way. Credential matching includes the singular "credential" alongside "credentials", since has_any is term-based and would otherwise miss singular operation-name variants. Description updated so it describes the extraction that both paths now use rather than only the group path.
|
Thanks for the review. All four points addressed in Role target extraction. Fair catch, and the inconsistency was mine: the group path already read the user from whichever Group name on removals. Right, Singular credential term. Added Dotted remove variant. Added |
Summary
Three hunting queries for monitoring designated emergency break-glass accounts, which should show no sign-in, credential, or membership activity outside a documented recovery test.
BreakGlassAccountSignIn (SigninLogs)
Identifies any sign-in from a designated break-glass account. These accounts are deliberately excluded from Conditional Access and day-to-day MFA, so a successful sign-in bypasses the same controls protecting every other identity in the tenant.
MITRE: T1078.004, InitialAccess, Persistence
BreakGlassAccountCredentialsModified (AuditLogs)
Identifies password resets, security-info registrations, and MFA changes on a break-glass account. Matches official Microsoft phrasing for password-reset detection (noun+verb match) rather than an exact-string list, since operation names vary across tenants and service versions.
MITRE: T1098, T1556.006, Persistence, DefenseEvasion
BreakGlassAccountRoleOrGroupMembershipChanged (AuditLogs)
Identifies direct role assignment and group-mediated privilege changes on a break-glass account. Covers both paths since a group-mediated addition to a role-assignable group grants the inherited role without ever generating a direct role-assignment event.
MITRE: T1098.003, PrivilegeEscalation, Persistence
Validation
BreakGlassAccountswatchlist dependency