Adversaries may use password changes or resets to maintain persistence and access to compromised accounts. SOC teams should proactively hunt for this behavior in Azure Sentinel to detect potential account compromise and lateral movement attempts.
KQL Query
let action = dynamic(["change ", "changed ", "reset "]);
let pWord = dynamic(["password ", "credentials "]);
(union isfuzzy=true
(SecurityEvent
| where EventID in (4723,4724)
| summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), ResultDescriptions = makeset(Activity), ActionCount = count() by Resource = Computer, OperationName = strcat("TargetAccount: ", TargetUserName), UserId = Account, Type
),
(WindowsEvent
| where EventID in (4723,4724)
| extend Activity=iff(EventID=='4723',"4723 - An attempt was made to change an account","4724 - An attempt was made to reset an account")
| extend TargetUserName = tostring(EventData.TargetUserName)
| extend Account = strcat(tostring(EventData.SubjectDomainName),"\\", tostring(EventData.SubjectUserName))
| summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), ResultDescriptions = makeset(Activity), ActionCount = count() by Resource = Computer, OperationName = strcat("TargetAccount: ", TargetUserName), UserId = Account, Type
),
(AuditLogs
| where OperationName has_any (pWord) and OperationName has_any (action)
| extend InitiatedBy = tostring(parse_json(tostring(InitiatedBy.user)).userPrincipalName)
| extend TargetUserPrincipalName = tostring(TargetResources[0].userPrincipalName)
| where ResultDescription != "None"
| summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), ResultDescriptions = makeset(ResultDescription), CorrelationIds = makeset(CorrelationId), ActionCount = count() by OperationName = strcat(Category, " - ", OperationName, " - ", Result), Resource, UserId = TargetUserPrincipalName, Type
| extend ResultDescriptions = tostring(ResultDescriptions)
),
(OfficeActivity
| where (ExtendedProperties has_any (pWord) or ModifiedProperties has_any (pWord)) and (ExtendedProperties has_any (action) or ModifiedProperties has_any (action))
| extend ResultDescriptions = case(
OfficeWorkload =~ "AzureActiveDirectory", tostring(ExtendedProperties),
OfficeWorkload has_any ("Exchange","OneDrive"), OfficeObjectId,
RecordType)
| summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), ResultDescriptions = makeset(ResultDescriptions), ActionCount = count() by Resource = OfficeWorkload, OperationName = strcat(Operation, " - ", ResultStatus), IPAddress = ClientIP, UserId, Type
),
(Syslog
| where SyslogMessage has_any (pWord) and SyslogMessage has_any (action)
| summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), ResultDescriptions = makeset(SyslogMessage), ActionCount = count() by Resource = HostName, OperationName = Facility , IPAddress = HostIP, ProcessName, Type
),
(SigninLogs
| where OperationName =~ "Sign-in activity" and ResultType has_any ("50125", "50133")
| summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), ResultDescriptions = makeset(ResultDescription), CorrelationIds = makeset(CorrelationId), ActionCount = count() by Resource, OperationName = strcat(OperationName, " - ", ResultType), IPAddress, UserId = UserPrincipalName, Type
)
)
| extend timestamp = StartTimeUtc, AccountCustomEntity = UserId, IPCustomEntity = IPAddress
id: bac44fe4-c0bc-4e90-aa48-2e346fda803f
name: Tracking Password Changes
description: |
'This script identifies password changes or resets across multiple host and cloud sources. Account manipulation, including password changes and resets, may help adversaries maintain access to credentials and permission levels.'
description_detailed: |
'Identifies when a password change or reset occurs across multiple host and cloud based sources.
Account manipulation including password changes and resets may aid adversaries in maintaining access to credentials
and certain permission levels within an environment.'
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- AuditLogs
- connectorId: SecurityEvents
dataTypes:
- SecurityEvent
- connectorId: Syslog
dataTypes:
- Syslog
- connectorId: Office365
dataTypes:
- OfficeActivity
- connectorId: AzureActiveDirectory
dataTypes:
- SigninLogs
- connectorId: WindowsSecurityEvents
dataTypes:
- SecurityEvents
- connectorId: WindowsForwardedEvents
dataTypes:
- WindowsEvent
tactics:
- InitialAccess
- CredentialAccess
relevantTechniques:
- T1078
- T1110
query: |
let action = dynamic(["change ", "changed ", "reset "]);
let pWord = dynamic(["password ", "credentials "]);
(union isfuzzy=true
(SecurityEvent
| where EventID in (4723,4724)
| summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), ResultDescriptions = makeset(Activity), ActionCount = count() by Resource = Computer, OperationName = strcat("TargetAccount: ", TargetUserName), UserId = Account, Type
),
(WindowsEvent
| where EventID in (4723,4724)
| extend Activity=iff(EventID=='4723',"4723 - An attempt was made to change an account","4724 - An attempt was made to reset an account")
| extend TargetUserName = tostring(EventData.TargetUserName)
| extend Account = strcat(tostring(EventData.SubjectDomainName),"\\", tostring(EventData.SubjectUserName))
| summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), ResultDescriptions = makeset(Activity), ActionCount = count() by Resource = Computer, OperationName = strcat("TargetAccount: ", TargetUserName), UserId = Account, Type
),
(AuditLogs
| where OperationName has_any (pWord) and OperationName has_any (action)
| extend InitiatedBy = tostring(parse_json(tostring(InitiatedBy.user)).userPrincipalName)
| extend TargetUserPrincipalName = tostring(TargetResources[0].userPrincipalName)
| where ResultDescription != "None"
| summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), ResultDescriptions = makeset(ResultDescription), CorrelationIds = makeset(CorrelationId), ActionCount = count() by OperationName = strcat(Category, " - ", OperationName, " - ", Result), Resource, UserId = TargetUserPrincipalName, Type
| extend ResultDescriptions = tostring(ResultDescriptions)
),
(OfficeActivity
| where
| Sentinel Table | Notes |
|---|---|
AuditLogs | Ensure this data connector is enabled |
OfficeActivity | Ensure this data connector is enabled |
SecurityEvent | Ensure this data connector is enabled |
SigninLogs | Ensure this data connector is enabled |
Syslog | Ensure this data connector is enabled |
WindowsEvent | Ensure this data connector is enabled |
Scenario: Scheduled Password Rotation via Identity Management Tool
Description: A legitimate scheduled job using Microsoft Azure AD Password Protection or AWS IAM password policy automatically rotates passwords for user accounts.
Filter/Exclusion: Exclude events where the password change is initiated by a known identity management tool or scheduled task with a specific naming convention (e.g., PasswordRotationJob_2024).
Scenario: Admin Task to Reset User Passwords
Description: An administrator manually resets passwords for multiple users via Active Directory Users and Computers (ADUC) or PowerShell scripts.
Filter/Exclusion: Exclude events where the source is a known admin account (e.g., [email protected]) and the action is associated with a documented password reset task.
Scenario: Password Reset via Self-Service Portal
Description: Users reset their own passwords through a self-service password reset (SSPR) portal, such as Microsoft Azure AD Self-Service Password Reset or Okta Password Reset.
Filter/Exclusion: Exclude events where the user agent or IP address matches the known internal or corporate network, or where the user is identified as a regular employee.
Scenario: System-Wide Password Policy Enforcement
Description: A system-wide password policy enforces periodic password changes, such as via Windows Server Group Policy or Linux PAM modules.
Filter/Exclusion: Exclude events where the password change is triggered by a system policy and occurs at regular intervals (e.g., every 90 days).
Scenario: Password Change for Service Accounts During Maintenance
Description: A service account password is changed as part of routine maintenance or security hardening, such as via Ansible or Chef automation scripts.
Filter/Exclusion: Exclude events where the