Detection rules › Kusto
Multiple Password Reset by user
This query will determine multiple password resets by user across multiple data sources. Account manipulation including password reset may aid adversaries in maintaining access to credentials and certain permission levels within an environment.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Credential Access |
Telemetry coverage
Rule body
id: 0b9ae89d-8cad-461c-808f-0494f70ad5c4
name: Multiple Password Reset by user
description: |
'This query will determine multiple password resets by user across multiple data sources.
Account manipulation including password reset may aid adversaries in maintaining access to credentials and certain permission levels within an environment.'
severity: Low
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- AuditLogs
- connectorId: SecurityEvents
dataTypes:
- SecurityEvent
- connectorId: Syslog
dataTypes:
- Syslog
- connectorId: Office365
dataTypes:
- OfficeActivity
- connectorId: WindowsSecurityEvents
dataTypes:
- SecurityEvents
- connectorId: WindowsForwardedEvents
dataTypes:
- WindowsEvent
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
- CredentialAccess
relevantTechniques:
- T1078
- T1110
query: |
let selfServicePasswordReset = dynamic(["Self-service password reset flow activity progress", "Change password (self-service)", "Reset password (self-service)"]);
//Self-service password reset flow activity progress is typically caused by a password policy which requires users to rotate passwords. This operation already implies the user has signed in successfully and therefore the password reset is non-malicious.
let PerUserThreshold = 5;
let TotalThreshold = 100;
let action = dynamic(["change", "changed", "reset"]);
let pWord = dynamic(["password", "credentials"]);
let PasswordResetMultiDataSource =
(union isfuzzy=true
(//Password reset events
//4723: An attempt was made to change an account's password
//4724: An attempt was made to reset an accounts password
SecurityEvent
| where EventID in ("4723","4724")
| project TimeGenerated, Computer, AccountType, Account, Type, TargetUserName),
(//Password reset events
//4723: An attempt was made to change an account's password
//4724: An attempt was made to reset an accounts password
WindowsEvent
| where EventID in ("4723","4724")
| extend SubjectUserSid = tostring(EventData.SubjectUserSid)
| extend TargetUserName = tostring(EventData.TargetUserName)
| extend Account = strcat(tostring(EventData.SubjectDomainName),"\\", tostring(EventData.SubjectUserName))
| extend AccountType=case(Account endswith "$" or SubjectUserSid in ("S-1-5-18", "S-1-5-19", "S-1-5-20"), "Machine", isempty(SubjectUserSid), "", "User")
| project TimeGenerated, Computer, AccountType, Account, Type, TargetUserName),
(//Azure Active Directory Password reset events
AuditLogs
| where OperationName has_any (pWord) and OperationName has_any (action) and Result =~ "success"
| where OperationName !in (selfServicePasswordReset)
| mv-apply TargetResource = TargetResources on
(
where TargetResource.type =~ "User"
| extend AccountType = tostring(TargetResource.type),
Account = tostring(InitiatedBy.user.userPrincipalName),
TargetUserName = tolower(tostring(TargetResource.userPrincipalName))
)
| project TimeGenerated, AccountType, Account, TargetUserName, Computer = "", Type),
(//OfficeActive ActiveDirectory Password reset events
OfficeActivity
| where OfficeWorkload == "AzureActiveDirectory"
| where (ExtendedProperties has_any (pWord) or ModifiedProperties has_any (pWord)) and (ExtendedProperties has_any (action) or ModifiedProperties has_any (action))
| extend AccountType = UserType, Account = OfficeObjectId
| project TimeGenerated, AccountType, Account, Type, Computer = ""),
(// Unix syslog password reset events
Syslog
| where Facility in ("auth","authpriv")
| where SyslogMessage has_any (pWord) and SyslogMessage has_any (action)
| extend AccountType = iif(SyslogMessage contains "root", "Root", "Non-Root")
| where SyslogMessage matches regex ".*password changed for.*"
| parse SyslogMessage with * "password changed for" Account
| project TimeGenerated, AccountType, Account, Computer = HostName, Type)
);
let pwrmd = PasswordResetMultiDataSource
| project TimeGenerated, Computer, AccountType, Account, Type, TargetUserName;
(union isfuzzy=true
(pwrmd
| summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), Computerlist = make_set(Computer, 25), AccountType = make_set(AccountType, 25), Computer = arg_max(Computer , TimeGenerated), TargetUserList = make_set(TargetUserName, 25), TargetUserName = arg_max(TargetUserName, TimeGenerated), Total=count() by Account, Type
| where Total > PerUserThreshold
| extend ResetPivot = "PerUserReset"),
(pwrmd
| summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), ComputerList = make_set(Computer, 25), AccountList = make_set(Account, 25), AccountType = make_set(AccountType, 25), Computer = arg_max(Computer , TimeGenerated), TargetUserList = make_set(TargetUserName, 25), TargetUserName = arg_max(TargetUserName, TimeGenerated), Total=count() by Type
| where Total > TotalThreshold
| extend ResetPivot = "TotalUserReset")
)
| extend timestamp = StartTimeUtc, HostName = tostring(split(Computer, '.', 0)[0]), DnsDomain = tostring(strcat_array(array_slice(split(Computer, '.'), 1, -1), '.')), Name = tostring(split(Account, '@', 0)[0]), UPNSuffix = tostring(split(Account, '@', 1)[0]), TargetName = tostring(split(TargetUserName,'@',0)[0]), TargetUPNSuffix = tostring(split(TargetUserName,'@',1)[0])
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: Account
- identifier: Name
columnName: Name
- identifier: UPNSuffix
columnName: UPNSuffix
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: Computer
- identifier: HostName
columnName: HostName
- identifier: DnsDomain
columnName: DnsDomain
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: TargetUserName
- identifier: Name
columnName: TargetName
- identifier: UPNSuffix
columnName: TargetUPNSuffix
version: 2.1.7
kind: Scheduled
metadata:
source:
kind: Community
author:
name: Microsoft Security Research
support:
tier: Community
categories:
domains: [ "Security - Others", "Identity" ]
Stages and Predicates
Stage 0: let
let selfServicePasswordReset = dynamic(["Self-service password reset flow activity progress", "Change password (self-service)", "Reset password (self-service)"]);
let PerUserThreshold = 5;
let TotalThreshold = 100;
let action = dynamic(["change", "changed", "reset"]);
let pWord = dynamic(["password", "credentials"]);
let PasswordResetMultiDataSource = (union <inlined as stages below>;
let pwrmd = PasswordResetMultiDataSource <inlined as stages below>;
Stage 1: source
let PasswordResetMultiDataSource
Stage 2: source
let pwrmd
Stage 3: union
union of 2 branches
Stage 4: union
union of 5 branches
Stage 5: source
SecurityEvent
Stage 6: where
| where EventID in ("4723","4724")
Stage 7: project
| project TimeGenerated, Computer, AccountType, Account, Type, TargetUserName
Stage 8: source
WindowsEvent
Stage 9: where
| where EventID in ("4723","4724")
Stage 10: extend (4 consecutive steps)
| extend SubjectUserSid = tostring(EventData.SubjectUserSid)
| extend TargetUserName = tostring(EventData.TargetUserName)
| extend Account = strcat(tostring(EventData.SubjectDomainName),"\\", tostring(EventData.SubjectUserName))
| extend AccountType=case(Account endswith "$" or SubjectUserSid in ("S-1-5-18", "S-1-5-19", "S-1-5-20"), "Machine", isempty(SubjectUserSid), "", "User")
Stage 11: project
| project TimeGenerated, Computer, AccountType, Account, Type, TargetUserName
Stage 12: source
AuditLogs
Stage 13: where
| where OperationName has_any (pWord) and OperationName has_any (action) and Result =~ "success"
Stage 14: where
| where OperationName !in (selfServicePasswordReset)
Stage 15: kusto:mv-apply
| mv-apply TargetResource = TargetResources on
(
where TargetResource.type =~ "User"
| extend AccountType = tostring(TargetResource.type),
Account = tostring(InitiatedBy.user.userPrincipalName),
TargetUserName = tolower(tostring(TargetResource.userPrincipalName))
)
Stage 16: project
| project TimeGenerated, AccountType, Account, TargetUserName, Computer = "", Type
Stage 17: source
OfficeActivity
Stage 18: where
| where OfficeWorkload == "AzureActiveDirectory"
Stage 19: where
| where (ExtendedProperties has_any (pWord) or ModifiedProperties has_any (pWord)) and (ExtendedProperties has_any (action) or ModifiedProperties has_any (action))
Stage 20: extend
| extend AccountType = UserType, Account = OfficeObjectId
Stage 21: project
| project TimeGenerated, AccountType, Account, Type, Computer = ""
Stage 22: source
Syslog
Stage 23: where
| where Facility in ("auth","authpriv")
Stage 24: where
| where SyslogMessage has_any (pWord) and SyslogMessage has_any (action)
Stage 25: extend
| extend AccountType = iif(SyslogMessage contains "root", "Root", "Non-Root")
Stage 26: where
| where SyslogMessage matches regex ".*password changed for.*"
Stage 27: parse
| parse SyslogMessage with * "password changed for" Account
Stage 28: project
| project TimeGenerated, AccountType, Account, Computer = HostName, Type
Stage 29: project
project Account, AccountType, Computer, TargetUserName, TimeGenerated, Type
Stage 30: summarize
summarize AccountType, Computer, Computerlist, EndTimeUtc, StartTimeUtc, TargetUserList, TargetUserName, Total by Account, Type
Stage 31: where
where Total > 5
Stage 32: extend
extend ResetPivot
Stage 33: union
union of 5 branches
Stage 34: source
SecurityEvent
Stage 35: where
| where EventID in ("4723","4724")
Stage 36: project
| project TimeGenerated, Computer, AccountType, Account, Type, TargetUserName
Stage 37: source
WindowsEvent
Stage 38: where
| where EventID in ("4723","4724")
Stage 39: extend (4 consecutive steps)
| extend SubjectUserSid = tostring(EventData.SubjectUserSid)
| extend TargetUserName = tostring(EventData.TargetUserName)
| extend Account = strcat(tostring(EventData.SubjectDomainName),"\\", tostring(EventData.SubjectUserName))
| extend AccountType=case(Account endswith "$" or SubjectUserSid in ("S-1-5-18", "S-1-5-19", "S-1-5-20"), "Machine", isempty(SubjectUserSid), "", "User")
Stage 40: project
| project TimeGenerated, Computer, AccountType, Account, Type, TargetUserName
Stage 41: source
AuditLogs
Stage 42: where
| where OperationName has_any (pWord) and OperationName has_any (action) and Result =~ "success"
Stage 43: where
| where OperationName !in (selfServicePasswordReset)
Stage 44: kusto:mv-apply
| mv-apply TargetResource = TargetResources on
(
where TargetResource.type =~ "User"
| extend AccountType = tostring(TargetResource.type),
Account = tostring(InitiatedBy.user.userPrincipalName),
TargetUserName = tolower(tostring(TargetResource.userPrincipalName))
)
Stage 45: project
| project TimeGenerated, AccountType, Account, TargetUserName, Computer = "", Type
Stage 46: source
OfficeActivity
Stage 47: where
| where OfficeWorkload == "AzureActiveDirectory"
Stage 48: where
| where (ExtendedProperties has_any (pWord) or ModifiedProperties has_any (pWord)) and (ExtendedProperties has_any (action) or ModifiedProperties has_any (action))
Stage 49: extend
| extend AccountType = UserType, Account = OfficeObjectId
Stage 50: project
| project TimeGenerated, AccountType, Account, Type, Computer = ""
Stage 51: source
Syslog
Stage 52: where
| where Facility in ("auth","authpriv")
Stage 53: where
| where SyslogMessage has_any (pWord) and SyslogMessage has_any (action)
Stage 54: extend
| extend AccountType = iif(SyslogMessage contains "root", "Root", "Non-Root")
Stage 55: where
| where SyslogMessage matches regex ".*password changed for.*"
Stage 56: parse
| parse SyslogMessage with * "password changed for" Account
Stage 57: project
| project TimeGenerated, AccountType, Account, Computer = HostName, Type
Stage 58: project
project Account, AccountType, Computer, TargetUserName, TimeGenerated, Type
Stage 59: summarize
summarize AccountList, AccountType, Computer, ComputerList, EndTimeUtc, StartTimeUtc, TargetUserList, TargetUserName, Total by Type
Stage 60: where
where Total > 100
Stage 61: extend
extend ResetPivot
Stage 62: extend
extend DnsDomain, HostName, Name, TargetName, TargetUPNSuffix, UPNSuffix, timestamp
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventID | in |
| field:"EventID" kind:in |
ExtendedProperties | match |
| field:"ExtendedProperties" kind:match |
Facility | in |
| field:"Facility" kind:in |
ModifiedProperties | match |
| field:"ModifiedProperties" kind:match |
OfficeWorkload | eq |
| field:"m365::Workload" kind:eq value:"AzureActiveDirectory" |
OperationName | match |
| field:"OperationName" kind:match |
Result | eq |
| field:"Result" kind:eq value:"success" |
SyslogMessage | match |
| field:"SyslogMessage" kind:match |
SyslogMessage | regex_match |
| field:"SyslogMessage" kind:regex_match value:".*password changed for.*" |
Total | gt |
| field:"Total" kind:gt |
type | eq |
| field:"type" kind:eq value:"User" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
OperationName | in | Change password (self-service), Reset password (self-service), Self-service password reset flow activity progress | excludes:OperationName field:"OperationName" value:"Change password (self-service)" field:"OperationName" value:"Reset password (self-service)" field:"OperationName" value:"Self-service password reset flow activity progress" |
Output fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountList | summarize |
AccountType | summarize |
Computer | summarize |
ComputerList | summarize |
EndTimeUtc | summarize |
StartTimeUtc | summarize |
TargetUserList | summarize |
TargetUserName | summarize |
Total | summarize |
Type | summarize |
ResetPivot | extend |
DnsDomain | extend |
HostName | extend |
Name | extend |
TargetName | extend |
TargetUPNSuffix | extend |
UPNSuffix | extend |
timestamp | extend |