Detection rules › Kusto

AD user enabled and password not set within 48 hours

Status
available
Severity
low
Time window
3d
Group by
TargetAccount, TargetSid
Source
github.com/Azure/Azure-Sentinel

Identifies when an account is enabled with a default password and the password is not set by the user within 48 hours. Effectively, there is an event 4722 indicating an account was enabled and within 48 hours, no event 4723 occurs which indicates there was no attempt by the user to set the password. This will show any attempts (success or fail) that occur after 48 hours, which can indicate too long of a time period in setting the password to something that only the user knows. It is recommended that this time period is adjusted per your internal company policy.

MITRE ATT&CK coverage

TacticTechniques
Persistence

Telemetry coverage

Rule body

id: 62085097-d113-459f-9ea7-30216f2ee6af
name: AD user enabled and password not set within 48 hours
description: |
  'Identifies when an account is enabled with a default password and the password is not set by the user within 48 hours.
  Effectively, there is an event 4722 indicating an account was enabled and within 48 hours, no event 4723 occurs which
  indicates there was no attempt by the user to set the password. This will show any attempts (success or fail) that occur
  after 48 hours, which can indicate too long of a time period in setting the password to something that only the user knows.
  It is recommended that this time period is adjusted per your internal company policy.'
severity: Low
requiredDataConnectors:
  - connectorId: SecurityEvents
    dataTypes:
      - SecurityEvent
  - connectorId: WindowsSecurityEvents
    dataTypes:
      - SecurityEvent
queryFrequency: 1d
queryPeriod: 3d
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
  - Persistence
relevantTechniques:
  - T1098
query: |
  let starttime = 3d;
  let SecEvents = materialize ( SecurityEvent | where TimeGenerated >= ago(starttime)
  | where EventID in (4722,4723) | where TargetUserName !endswith "$"
  | project TimeGenerated, EventID, Activity, Computer, TargetAccount, TargetSid, SubjectAccount, SubjectUserSid);
  let userEnable = SecEvents
  | extend EventID4722Time = TimeGenerated
  // 4722: User Account Enabled
  | where EventID == 4722
  | project Time_Event4722 = TimeGenerated, TargetAccount, TargetSid, SubjectAccount_Event4722 = SubjectAccount, SubjectUserSid_Event4722 = SubjectUserSid, Activity_4722 = Activity, Computer_4722 = Computer;
  let userPwdSet = SecEvents
  // 4723: Attempt made by user to set password
  | where EventID == 4723
  | project Time_Event4723 = TimeGenerated, TargetAccount, TargetSid, SubjectAccount_Event4723 = SubjectAccount, SubjectUserSid_Event4723 = SubjectUserSid, Activity_4723 = Activity, Computer_4723 = Computer;
  userEnable | join kind=leftouter userPwdSet on TargetAccount, TargetSid
  | extend PasswordSetAttemptDelta_Min = datetime_diff('minute', Time_Event4723, Time_Event4722)
  | where PasswordSetAttemptDelta_Min > 2880 or isempty(PasswordSetAttemptDelta_Min)
  | project-away TargetAccount1, TargetSid1
  | extend Reason = @"User either has not yet attempted to set the initial password after account was enabled or it occurred after 48 hours"
  | order by Time_Event4722 asc
  | project-reorder Time_Event4722, Time_Event4723, PasswordSetAttemptDelta_Min, TargetAccount, TargetSid
  | extend Computer = coalesce(Computer_4723, Computer_4722)
  | extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
  | extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
  | extend AccountName = tostring(split(TargetAccount, "\\")[1]), AccountNTDomain = tostring(split(TargetAccount, "\\")[0])
  | project-away DomainIndex
entityMappings:
  - entityType: Account
    fieldMappings: 
      - identifier: FullName
        columnName: TargetAccount
      - identifier: Name
        columnName: AccountName
      - identifier: NTDomain
        columnName: AccountNTDomain
  - entityType: Account
    fieldMappings:
      - identifier: Sid
        columnName: TargetSid
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: Computer
      - identifier: HostName
        columnName: HostName
      - identifier: DnsDomain
        columnName: HostNameDomain
version: 1.0.4
kind: Scheduled

Stages and Predicates

Parameters

let starttime = 3d;

let SecEvents and let userEnable are inlined into the numbered stages below.

Let binding: userPwdSet used in Stages 3, 12

let userPwdSet = SecEvents
| where EventID == 4723
| project Time_Event4723 = TimeGenerated, TargetAccount, TargetSid, SubjectAccount_Event4723 = SubjectAccount, SubjectUserSid_Event4723 = SubjectUserSid, Activity_4723 = Activity, Computer_4723 = Computer;

Stages 1 to 11 define let userEnable (the rule's main pipeline source); stages 12 to 20 run on it.

Stage 1: source

let SecEvents

Stage 2: source

let userEnable

Stage 3: source

let userPwdSet

Stage 4: source

SecurityEvent

Stage 5: where

| where TimeGenerated >= ago(starttime)

Stage 6: where

| where EventID in (4722,4723)

Stage 7: where

| where TargetUserName !endswith "$"

Stage 8: project

| project TimeGenerated, EventID, Activity, Computer, TargetAccount, TargetSid, SubjectAccount, SubjectUserSid

Stage 9: extend

| extend EventID4722Time = TimeGenerated

Stage 10: where

| where EventID == 4722

Stage 11: project

| project Time_Event4722 = TimeGenerated, TargetAccount, TargetSid, SubjectAccount_Event4722 = SubjectAccount, SubjectUserSid_Event4722 = SubjectUserSid, Activity_4722 = Activity, Computer_4722 = Computer

Stage 12: join

userEnable
| join kind=leftouter userPwdSet on TargetAccount, TargetSid

Stage 13: extend

| extend PasswordSetAttemptDelta_Min = datetime_diff('minute', Time_Event4723, Time_Event4722)

Stage 14: where

| where PasswordSetAttemptDelta_Min > 2880 or isempty(PasswordSetAttemptDelta_Min)

Stage 15: project-away

| project-away TargetAccount1, TargetSid1

Stage 16: extend

| extend Reason = @"User either has not yet attempted to set the initial password after account was enabled or it occurred after 48 hours"

Stage 17: sort

| order by Time_Event4722 asc

Stage 18: project-reorder

| project-reorder Time_Event4722, Time_Event4723, PasswordSetAttemptDelta_Min, TargetAccount, TargetSid

Stage 19: extend (4 consecutive steps)

| extend Computer = coalesce(Computer_4723, Computer_4722)
| extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
| extend AccountName = tostring(split(TargetAccount, "\\")[1]), AccountNTDomain = tostring(split(TargetAccount, "\\")[0])

Stage 20: project-away

| project-away DomainIndex

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
TargetUserNameends_with$excludes:TargetUserName field:"TargetUserName" value:"$"

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
EventIDeq
  • 4722 corpus 2 (kusto 2)
  • 4723 corpus 4 (splunk 3, kusto 1)
field:"EventID" kind:eq
EventIDin
  • 4722 corpus 2 (kusto 2)
  • 4723 corpus 4 (splunk 3, kusto 1)
field:"EventID" kind:in
PasswordSetAttemptDelta_Mingt
  • 2880
field:"PasswordSetAttemptDelta_Min" kind:gt value:"2880"
PasswordSetAttemptDelta_Minis_null
  • (no value, null check)
field:"PasswordSetAttemptDelta_Min" kind:is_null

Output fields

These fields are emitted when the rule matches.

FieldSource
Activity_4722project
Computer_4722project
SubjectAccount_Event4722project
SubjectUserSid_Event4722project
TargetAccountproject
TargetSidproject
Time_Event4722project
PasswordSetAttemptDelta_Minextend
Reasonextend
Computerextend
HostNameextend
HostNameDomainextend
AccountNTDomainextend
AccountNameextend