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

Query · kql

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
Raw source Tracking Password Changes · KQL
Esc
Published by Azure/Azure-Sentinel ↗, licensed under MIT ↗. Reproduced here unmodified.
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 (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
version: 1.0.1
metadata:
    source:
        kind: Community
    author:
        name: Shain
    support:
        tier: Community
    categories:
        domains: [ "Security - Other" ]

Detection rules belong to the projects that publish them and remain under their own licenses. This site indexes and links to them; it claims no rights in them.