← Back to SOC feed Coverage →

Tracking Password Changes

kql MEDIUM Azure-Sentinel
T1078T1110
AuditLogsOfficeActivitySecurityEventSigninLogsSyslogWindowsEvent
credential-thefthuntingmicrosoftofficial
This rule was pulled from an open-source repository and enriched with AI. Validate in a test environment before deploying to production.
View original rule at Azure-Sentinel →
Retrieved: 2026-06-03T23:00:00Z · Confidence: medium

Hunt Hypothesis

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

Analytic Rule Definition

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

Required Data Sources

Sentinel TableNotes
AuditLogsEnsure this data connector is enabled
OfficeActivityEnsure this data connector is enabled
SecurityEventEnsure this data connector is enabled
SigninLogsEnsure this data connector is enabled
SyslogEnsure this data connector is enabled
WindowsEventEnsure this data connector is enabled

MITRE ATT&CK Context

References

False Positive Guidance

Original source: https://github.com/Azure/Azure-Sentinel/blob/main/Hunting Queries/MultipleDataSources/TrackingPasswordChanges.yaml