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 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.

FieldKindValuesSearch
EventIDin
  • 4723 corpus 4 (splunk 3, kusto 1)
  • 4724 corpus 2 (splunk 2)
field:"EventID" kind:in
ExtendedPropertiesmatch
  • change transforms: term
  • changed transforms: term
  • credentials transforms: term
  • password transforms: term
  • reset transforms: term
field:"ExtendedProperties" kind:match
Facilityin
  • auth
  • authpriv
field:"Facility" kind:in
ModifiedPropertiesmatch
  • change transforms: term
  • changed transforms: term
  • credentials transforms: term
  • password transforms: term
  • reset transforms: term
field:"ModifiedProperties" kind:match
OfficeWorkloadeq
  • AzureActiveDirectory
field:"m365::Workload" kind:eq value:"AzureActiveDirectory"
OperationNamematch
  • change transforms: term
  • changed transforms: term
  • credentials transforms: term
  • password transforms: term
  • reset transforms: term
field:"OperationName" kind:match
Resulteq
  • success corpus 31 (kusto 31)
field:"Result" kind:eq value:"success"
SyslogMessagematch
  • change transforms: term
  • changed transforms: term
  • credentials transforms: term
  • password transforms: term
  • reset transforms: term
field:"SyslogMessage" kind:match
SyslogMessageregex_match
  • .*password changed for.*
field:"SyslogMessage" kind:regex_match value:".*password changed for.*"
Totalgt
  • 100
  • 5 corpus 2 (kusto 2)
field:"Total" kind:gt
typeeq
  • User corpus 16 (kusto 16)
field:"type" kind:eq value:"User"

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
OperationNameinChange password (self-service), Reset password (self-service), Self-service password reset flow activity progressexcludes: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.

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