Detection rules › Kusto

Multiple Password Reset by user

Severity
low
Time window
1d
Group by
Account, Type
Author
Microsoft Security Research
Source
github.com/Azure/Azure-Sentinel

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

TacticTechniques
Initial AccessT1078 Valid Accounts
Credential AccessT1110 Brute Force

Event coverage

Rule body kusto

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 isfuzzy=true
(//Password reset events
SecurityEvent
| where EventID in ("4723","4724")
| project TimeGenerated, Computer, AccountType, Account, Type, TargetUserName),
(//Password reset events
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;

Stage 1: source

let PasswordResetMultiDataSource

Stage 2: source

let pwrmd

Stage 3: union

union isfuzzy=true

Stage 4: union

union isfuzzy=true

Stage 5: source time_window=86400s

//Password reset events
SecurityEvent

Stage 6: where

| where EventID in ("4723","4724")

Stage 7: project

| project TimeGenerated, Computer, AccountType, Account, Type, TargetUserName

Stage 8: source

//Password reset events
WindowsEvent

Stage 9: where

| where EventID in ("4723","4724")

Stage 10: extend

| extend SubjectUserSid = tostring(EventData.SubjectUserSid)

Stage 11: extend

| extend TargetUserName = tostring(EventData.TargetUserName)

Stage 12: extend

| extend Account = strcat(tostring(EventData.SubjectDomainName),"\\", tostring(EventData.SubjectUserName))

Stage 13: extend

| 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 14: project

| project TimeGenerated, Computer, AccountType, Account, Type, TargetUserName

Stage 15: source

//Azure Active Directory Password reset events
AuditLogs

Stage 16: where

| where OperationName has_any (pWord) and OperationName has_any (action) and Result =~ "success"

Stage 17: where

| where OperationName !in (selfServicePasswordReset)

Stage 18: 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 19: project

| project TimeGenerated, AccountType, Account, TargetUserName, Computer = "", Type

Stage 20: source

//OfficeActive ActiveDirectory Password reset events
OfficeActivity

Stage 21: where

| where OfficeWorkload == "AzureActiveDirectory"

Stage 22: where

| where (ExtendedProperties has_any (pWord) or ModifiedProperties has_any (pWord)) and (ExtendedProperties has_any (action) or ModifiedProperties has_any (action))

Stage 23: extend

| extend AccountType = UserType, Account = OfficeObjectId

Stage 24: project

| project TimeGenerated, AccountType, Account, Type, Computer = ""

Stage 25: source

// Unix syslog password reset events
Syslog

Stage 26: where

| where Facility in ("auth","authpriv")

Stage 27: where

| where SyslogMessage has_any (pWord) and SyslogMessage has_any (action)

Stage 28: extend

| extend AccountType = iif(SyslogMessage contains "root", "Root", "Non-Root")

Stage 29: where

| where SyslogMessage matches regex ".*password changed for.*"

Stage 30: parse

| parse SyslogMessage with * "password changed for" Account

Stage 31: project

| project TimeGenerated, AccountType, Account, Computer = HostName, Type

Stage 32: project

| project TimeGenerated, Computer, AccountType, Account, Type, TargetUserName

Stage 33: summarize

| 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

Stage 34: where

| where Total > PerUserThreshold

Stage 35: extend

| extend ResetPivot = "PerUserReset"

Stage 36: union

union isfuzzy=true

Stage 37: source

//Password reset events
SecurityEvent

Stage 38: where

| where EventID in ("4723","4724")

Stage 39: project

| project TimeGenerated, Computer, AccountType, Account, Type, TargetUserName

Stage 40: source

//Password reset events
WindowsEvent

Stage 41: where

| where EventID in ("4723","4724")

Stage 42: extend

| extend SubjectUserSid = tostring(EventData.SubjectUserSid)

Stage 43: extend

| extend TargetUserName = tostring(EventData.TargetUserName)

Stage 44: extend

| extend Account = strcat(tostring(EventData.SubjectDomainName),"\\", tostring(EventData.SubjectUserName))

Stage 45: extend

| 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 46: project

| project TimeGenerated, Computer, AccountType, Account, Type, TargetUserName

Stage 47: source

//Azure Active Directory Password reset events
AuditLogs

Stage 48: where

| where OperationName has_any (pWord) and OperationName has_any (action) and Result =~ "success"

Stage 49: where

| where OperationName !in (selfServicePasswordReset)

Stage 50: 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 51: project

| project TimeGenerated, AccountType, Account, TargetUserName, Computer = "", Type

Stage 52: source

//OfficeActive ActiveDirectory Password reset events
OfficeActivity

Stage 53: where

| where OfficeWorkload == "AzureActiveDirectory"

Stage 54: where

| where (ExtendedProperties has_any (pWord) or ModifiedProperties has_any (pWord)) and (ExtendedProperties has_any (action) or ModifiedProperties has_any (action))

Stage 55: extend

| extend AccountType = UserType, Account = OfficeObjectId

Stage 56: project

| project TimeGenerated, AccountType, Account, Type, Computer = ""

Stage 57: source

// Unix syslog password reset events
Syslog

Stage 58: where

| where Facility in ("auth","authpriv")

Stage 59: where

| where SyslogMessage has_any (pWord) and SyslogMessage has_any (action)

Stage 60: extend

| extend AccountType = iif(SyslogMessage contains "root", "Root", "Non-Root")

Stage 61: where

| where SyslogMessage matches regex ".*password changed for.*"

Stage 62: parse

| parse SyslogMessage with * "password changed for" Account

Stage 63: project

| project TimeGenerated, AccountType, Account, Computer = HostName, Type

Stage 64: project

| project TimeGenerated, Computer, AccountType, Account, Type, TargetUserName

Stage 65: summarize

| 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

Stage 66: where

| where Total > TotalThreshold

Stage 67: extend

| extend ResetPivot = "TotalUserReset"

Stage 68: extend

| 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])

Exclusions

Top-level NOT(...) conjuncts: predicates this rule actively suppresses.

StageFieldKindExcluded values
17OperationNameinChange password (self-service), Reset password (self-service), Self-service password reset flow activity progress
49OperationNameinChange password (self-service), Reset password (self-service), Self-service password reset flow activity progress

Indicators

Each row is a field, operator, and value that the rule matches. The corpus column counts how many other rules in the catalog look for the same combination: high numbers point to widely-used, community-vetted indicators. Blank or 1 shows that the indicator is specific to this rule.

FieldKindValues
EventIDin
  • 4723 corpus 2 (splunk 1, kusto 1)
  • 4724
ExtendedPropertiesmatch
  • change
  • changed
  • credentials
  • password
  • reset
Facilityin
  • auth
  • authpriv
ModifiedPropertiesmatch
  • change
  • changed
  • credentials
  • password
  • reset
OfficeWorkloadeq
  • AzureActiveDirectory transforms: cased
OperationNamematch
  • change
  • changed
  • credentials
  • password
  • reset
Resulteq
  • success
SyslogMessagematch
  • change
  • changed
  • credentials
  • password
  • reset
SyslogMessageregex_match
  • .*password changed for.*
Totalgt
  • 100 transforms: cased
  • 5 transforms: cased corpus 2 (kusto 2)
typeeq
  • User

Output fields

Fields the rule emits when it matches. Chronicle authors list these in the outcome block; they appear on the detection and $risk_score drives alerting. Sentinel / Defender XDR rules build them up through project / summarize / extend stages. Sentinel maps these into alert fields via entityMappings and customDetails; Defender XDR custom detections surface them as alert fields directly.

FieldSource
AccountListsummarize
AccountTypesummarize
Computersummarize
ComputerListsummarize
EndTimeUtcsummarize
StartTimeUtcsummarize
TargetUserListsummarize
TargetUserNamesummarize
Totalsummarize
Typesummarize
ResetPivotextend
DnsDomainextend
HostNameextend
Nameextend
TargetNameextend
TargetUPNSuffixextend
UPNSuffixextend
timestampextend