Dormant user accounts that have MFA updated or added before logging in may indicate an adversary attempting to re-activate a compromised account to evade detection. SOC teams should proactively hunt for this behavior in Azure Sentinel to identify potential re-use of dormant accounts by threat actors.
KQL Query
let starttime = todatetime('{{StartTimeISO}}');
let endtime = todatetime('{{EndTimeISO}}');
let lookback = endtime - 14d;
let active_users = (
SigninLogs
| where TimeGenerated between(lookback..starttime)
| where ResultType == 0
| extend UserPrincipalName == tolower(UserPrincipalName)
| summarize by UserId);
AuditLogs
| where TimeGenerated between(starttime..endtime)
// Get users where they added MFA
| where OperationName =~ "User registered security info"
| extend TargetUser = tolower(tostring(TargetResources[0].userPrincipalName))
| extend UserId = tostring(TargetResources[0].id)
// Check and see if this activity was from a user who is considered not active
| where UserId !in (active_users)
// Further reduce FP by just looking at users who have successfully logged in recently as well (avoiding hits for users adding MFA but not actually logging in)
| join kind=inner (SigninLogs | where TimeGenerated between(starttime..endtime) | where ResultType == 0 | summarize max(TimeGenerated), make_set(IPAddress), make_set(UserAgent), make_set(LocationDetails) by UserPrincipalName, UserId
) on UserId
| extend LogonLocation = set_LocationDetails[0], LogonUserAgent = set_UserAgent[0], LogonIP = set_IPAddress[0]
| project-rename MostRecentLogon = max_TimeGenerated
| project-reorder TimeGenerated, TargetUser, OperationName, ResultDescription, MostRecentLogon, LogonUserAgent, LogonLocation, LogonIP
| extend AccountCustomEntity = TargetUser, IPCustomEntity = LogonIP
id: a67834b0-3359-40be-bf11-71faac93b509
name: Dormant User Update MFA and Logs In
description: |
'This querys look for users accounts that have not been successfully logged into recently, who then have a MFA method added or updated before logging in.
Threat actors may look to re-activate dormant accounts and use them for access by adding MFA methods in the hope that changes to such dormant accounts may go un-noticed.'
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- SigninLogs
- AuditLogs
tactics:
- Persistence
relevantTechniques:
- T1098
query: |
let starttime = todatetime('{{StartTimeISO}}');
let endtime = todatetime('{{EndTimeISO}}');
let lookback = endtime - 14d;
let active_users = (
SigninLogs
| where TimeGenerated between(lookback..starttime)
| where ResultType == 0
| extend UserPrincipalName == tolower(UserPrincipalName)
| summarize by UserId);
AuditLogs
| where TimeGenerated between(starttime..endtime)
// Get users where they added MFA
| where OperationName =~ "User registered security info"
| extend TargetUser = tolower(tostring(TargetResources[0].userPrincipalName))
| extend UserId = tostring(TargetResources[0].id)
// Check and see if this activity was from a user who is considered not active
| where UserId !in (active_users)
// Further reduce FP by just looking at users who have successfully logged in recently as well (avoiding hits for users adding MFA but not actually logging in)
| join kind=inner (SigninLogs | where TimeGenerated between(starttime..endtime) | where ResultType == 0 | summarize max(TimeGenerated), make_set(IPAddress), make_set(UserAgent), make_set(LocationDetails) by UserPrincipalName, UserId
) on UserId
| extend LogonLocation = set_LocationDetails[0], LogonUserAgent = set_UserAgent[0], LogonIP = set_IPAddress[0]
| project-rename MostRecentLogon = max_TimeGenerated
| project-reorder TimeGenerated, TargetUser, OperationName, ResultDescription, MostRecentLogon, LogonUserAgent, LogonLocation, LogonIP
| extend AccountCustomEntity = TargetUser, IPCustomEntity = LogonIP
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: AccountCustomEntity
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPCustomEntity
| Sentinel Table | Notes |
|---|---|
AuditLogs | Ensure this data connector is enabled |
SigninLogs | Ensure this data connector is enabled |
Scenario: Scheduled Job Updates MFA for a Dormant User
Description: A system maintenance job or automation script updates MFA settings for a user account that hasn’t been active in months.
Filter/Exclusion: process.name != "scheduled_job.exe" OR process.name != "systemd" OR process.name != "cron" (depending on OS)
Example Tools: Ansible, Puppet, Jenkins, or native OS task scheduler
Scenario: Admin Task to Enable MFA for a Dormant User
Description: An administrator manually enables MFA for a user who hasn’t logged in recently as part of a security policy update.
Filter/Exclusion: user.role = "admin" OR user.action = "enable_mfa"
Example Tools: Azure AD Admin Center, AWS IAM Console, Okta Admin Panel
Scenario: User-Initiated MFA Update via Self-Service Portal
Description: A legitimate user updates their MFA method through a self-service portal after a long period of inactivity.
Filter/Exclusion: user.action = "self_service_mfa_update" OR user.device = "company_issued_device"
Example Tools: Microsoft Authenticator, Google Authenticator, Okta, Ping Identity
Scenario: Legacy System Synchronization Updates MFA
Description: A legacy system or directory synchronization tool (e.g., Azure AD Connect) updates MFA settings for a dormant user during a sync cycle.
Filter/Exclusion: process.name = "AzureADConnect.exe" OR process.name = "adconnect.exe"
Example Tools: Azure AD Connect, LDAP Sync Tools, Microsoft Identity Manager
Scenario: User Logs In After Being Marked as Dormant by a Security Tool
Description: A security tool (e.g