Detection rules › Kusto

User account enabled and disabled within 10 mins

Severity
medium
Time window
25h
Author
Microsoft Security Research
Source
github.com/Azure/Azure-Sentinel

Identifies when a user account is enabled and then disabled within 10 minutes. This can be an indication of compromise and an adversary attempting to hide in the noise.

MITRE ATT&CK coverage

Event coverage

Rule body kusto

id: 3d023f64-8225-41a2-9570-2bd7c2c4535e
name: User account enabled and disabled within 10 mins
description: |
  'Identifies when a user account is enabled and then disabled within 10 minutes. This can be an indication of compromise and an adversary attempting to hide in the noise.'
severity: Medium
requiredDataConnectors:
  - connectorId: SecurityEvents
    dataTypes:
      - SecurityEvent
  - connectorId: WindowsSecurityEvents
    dataTypes: 
      - SecurityEvents 
  - connectorId: WindowsForwardedEvents
    dataTypes: 
      - WindowsEvent  
queryFrequency: 1d
queryPeriod: 25h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Persistence
  - PrivilegeEscalation
relevantTechniques:
  - T1098
  - T1078
query: |
  let timeframe = 1d;
  let spanoftime = 10m;
  let threshold = 0;
    (union isfuzzy=true
      (SecurityEvent
      | where TimeGenerated > ago(timeframe+spanoftime)
      // A user account was enabled
      | where EventID == 4722
      | where AccountType =~ "User"
      | where TargetAccount !endswith "$"
      | project EnableTime = TimeGenerated, EnableEventID = EventID, EnableActivity = Activity, Computer = toupper(Computer), 
      TargetAccount = tolower(TargetAccount), TargetUserName, TargetDomainName, TargetSid, 
      AccountUsedToEnable = SubjectAccount, EnabledBySubjectUserName = SubjectUserName, EnabledBySubjectDomainName = SubjectDomainName, SIDofAccountUsedToEnable = SubjectUserSid, UserPrincipalName
      ),
      (
      WindowsEvent
      | where TimeGenerated > ago(timeframe+spanoftime)
      // A user account was enabled
      | where EventID == 4722
      | extend SubjectUserSid = tostring(EventData.SubjectUserSid)
      | extend AccountType=case(EventData.SubjectUserName endswith "$" or SubjectUserSid in ("S-1-5-18", "S-1-5-19", "S-1-5-20"), "Machine", isempty(SubjectUserSid), "", "User")
      | where AccountType =~ "User"
      | extend SubjectAccount = strcat(tostring(EventData.SubjectDomainName),"\\", tostring(EventData.SubjectUserName))
      | extend SubjectDomainName = tostring(EventData.SubjectDomainName), SubjectUserName = tostring(EventData.SubjectUserName)
      | extend TargetAccount = strcat(EventData.TargetDomainName,"\\", EventData.TargetUserName)
      | where TargetAccount !endswith "$"
      | extend Activity="4722 - A user account was enabled."
      | extend TargetUserName = tostring(EventData.TargetUserName), TargetDomainName = tostring(EventData.TargetDomainName) 
      | extend TargetSid = tostring(EventData.TargetSid)
      | extend UserPrincipalName = tostring(EventData.UserPrincipalName)
      | project EnableTime = TimeGenerated, EnableEventID = EventID, EnableActivity = Activity, Computer = toupper(Computer), 
      TargetAccount = tolower(TargetAccount), TargetUserName, TargetDomainName, TargetSid, 
      AccountUsedToEnable = SubjectAccount, EnabledBySubjectUserName = SubjectUserName, EnabledBySubjectDomainName = SubjectDomainName, SIDofAccountUsedToEnable = SubjectUserSid, UserPrincipalName
      )
    )
  | join kind= inner (
    (union isfuzzy=true
      (SecurityEvent
      | where TimeGenerated > ago(timeframe)
      // A user account was disabled
      | where EventID == 4725
      | where AccountType =~ "User"
      | project DisableTime = TimeGenerated, DisableEventID = EventID, DisableActivity = Activity, Computer = toupper(Computer), 
      TargetAccount = tolower(TargetAccount), TargetUserName, TargetDomainName, TargetSid, 
      AccountUsedToDisable = SubjectAccount, DisabledBySubjectUserName = SubjectUserName, DisabledBySubjectDomainName = SubjectDomainName, SIDofAccountUsedToDisable = SubjectUserSid, UserPrincipalName
      ),
      (WindowsEvent
      | where TimeGenerated > ago(timeframe)
      // A user account was disabled
      | where EventID == 4725
      | extend SubjectUserSid = tostring(EventData.SubjectUserSid)
      | extend AccountType=case(EventData.SubjectUserName endswith "$" or SubjectUserSid in ("S-1-5-18", "S-1-5-19", "S-1-5-20"), "Machine", isempty(SubjectUserSid), "", "User")
      | where AccountType =~ "User"
      | extend SubjectAccount = strcat(tostring(EventData.SubjectDomainName),"\\", tostring(EventData.SubjectUserName))
      | extend SubjectDomainName = tostring(EventData.SubjectDomainName), SubjectUserName = tostring(EventData.SubjectUserName)
      | extend TargetAccount = strcat(EventData.TargetDomainName,"\\", EventData.TargetUserName)
      | extend TargetUserName = tostring(EventData.TargetUserName), TargetDomainName = tostring(EventData.TargetDomainName) 
      | extend TargetSid = tostring(EventData.TargetSid)
      | extend UserPrincipalName = tostring(EventData.UserPrincipalName)
      | extend Activity = "4725 - A user account was disabled."
      | project DisableTime = TimeGenerated, DisableEventID = EventID, DisableActivity = Activity, Computer = toupper(Computer), 
      TargetAccount = tolower(TargetAccount), TargetUserName, TargetDomainName, TargetSid, 
      AccountUsedToDisable = SubjectAccount, DisabledBySubjectUserName = SubjectUserName, DisabledBySubjectDomainName = SubjectDomainName, SIDofAccountUsedToDisable = SubjectUserSid, UserPrincipalName
      )
    )
  ) on Computer, TargetAccount
  | where DisableTime - EnableTime < spanoftime
  | extend TimeDelta = DisableTime - EnableTime
  | where tolong(TimeDelta) >= threshold
  | project TimeDelta, EnableTime, EnableEventID, EnableActivity, Computer, TargetAccount, TargetSid, TargetUserName, TargetDomainName, UserPrincipalName, 
  AccountUsedToEnable, SIDofAccountUsedToEnable, DisableTime, DisableEventID, DisableActivity, AccountUsedToDisable, SIDofAccountUsedToDisable, 
  EnabledBySubjectUserName, EnabledBySubjectDomainName, DisabledBySubjectUserName, DisabledBySubjectDomainName
  | extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
  | extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
  | project-away DomainIndex
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: AccountUsedToEnable
      - identifier: Name
        columnName: EnabledBySubjectUserName
      - identifier: NTDomain
        columnName: EnabledBySubjectDomainName
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: AccountUsedToDisable
      - identifier: Name
        columnName: DisabledBySubjectUserName
      - identifier: NTDomain
        columnName: DisabledBySubjectDomainName
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: TargetAccount
      - identifier: Name
        columnName: TargetUserName
      - identifier: NTDomain
        columnName: TargetDomainName
  - entityType: Account
    fieldMappings:
      - identifier: Sid
        columnName: TargetSid
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: Computer
      - identifier: HostName
        columnName: HostName
      - identifier: NTDomain
        columnName: HostNameDomain
version: 1.2.3
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 timeframe = 1d;
let spanoftime = 10m;
let threshold = 0;

Stage 1: union

union isfuzzy=true

Stage 2: source time_window=90000s

SecurityEvent

Stage 3: where

| where TimeGenerated > ago(timeframe+spanoftime)

Stage 4: where

| where EventID == 4722

Stage 5: where

| where AccountType =~ "User"

Stage 6: where

| where TargetAccount !endswith "$"

Stage 7: project

| project EnableTime = TimeGenerated, EnableEventID = EventID, EnableActivity = Activity, Computer = toupper(Computer), 
    TargetAccount = tolower(TargetAccount), TargetUserName, TargetDomainName, TargetSid, 
    AccountUsedToEnable = SubjectAccount, EnabledBySubjectUserName = SubjectUserName, EnabledBySubjectDomainName = SubjectDomainName, SIDofAccountUsedToEnable = SubjectUserSid, UserPrincipalName

Stage 8: source

WindowsEvent

Stage 9: where

| where TimeGenerated > ago(timeframe+spanoftime)

Stage 10: where

| where EventID == 4722

Stage 11: extend

| extend SubjectUserSid = tostring(EventData.SubjectUserSid)

Stage 12: extend

| extend AccountType=case(EventData.SubjectUserName endswith "$" or SubjectUserSid in ("S-1-5-18", "S-1-5-19", "S-1-5-20"), "Machine", isempty(SubjectUserSid), "", "User")

Stage 13: where

| where AccountType =~ "User"

Stage 14: extend

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

Stage 15: extend

| extend SubjectDomainName = tostring(EventData.SubjectDomainName), SubjectUserName = tostring(EventData.SubjectUserName)

Stage 16: extend

| extend TargetAccount = strcat(EventData.TargetDomainName,"\\", EventData.TargetUserName)

Stage 17: where

| where TargetAccount !endswith "$"

Stage 18: extend

| extend Activity="4722 - A user account was enabled."

Stage 19: extend

| extend TargetUserName = tostring(EventData.TargetUserName), TargetDomainName = tostring(EventData.TargetDomainName)

Stage 20: extend

| extend TargetSid = tostring(EventData.TargetSid)

Stage 21: extend

| extend UserPrincipalName = tostring(EventData.UserPrincipalName)

Stage 22: project

| project EnableTime = TimeGenerated, EnableEventID = EventID, EnableActivity = Activity, Computer = toupper(Computer), 
    TargetAccount = tolower(TargetAccount), TargetUserName, TargetDomainName, TargetSid, 
    AccountUsedToEnable = SubjectAccount, EnabledBySubjectUserName = SubjectUserName, EnabledBySubjectDomainName = SubjectDomainName, SIDofAccountUsedToEnable = SubjectUserSid, UserPrincipalName

Stage 23: join

| join kind= inner (
  (union isfuzzy=true
    (SecurityEvent
    | where TimeGenerated > ago(timeframe)
    | where EventID == 4725
    | where AccountType =~ "User"
    | project DisableTime = TimeGenerated, DisableEventID = EventID, DisableActivity = Activity, Computer = toupper(Computer), 
    TargetAccount = tolower(TargetAccount), TargetUserName, TargetDomainName, TargetSid, 
    AccountUsedToDisable = SubjectAccount, DisabledBySubjectUserName = SubjectUserName, DisabledBySubjectDomainName = SubjectDomainName, SIDofAccountUsedToDisable = SubjectUserSid, UserPrincipalName
    ),
    (WindowsEvent
    | where TimeGenerated > ago(timeframe)
    | where EventID == 4725
    | extend SubjectUserSid = tostring(EventData.SubjectUserSid)
    | extend AccountType=case(EventData.SubjectUserName endswith "$" or SubjectUserSid in ("S-1-5-18", "S-1-5-19", "S-1-5-20"), "Machine", isempty(SubjectUserSid), "", "User")
    | where AccountType =~ "User"
    | extend SubjectAccount = strcat(tostring(EventData.SubjectDomainName),"\\", tostring(EventData.SubjectUserName))
    | extend SubjectDomainName = tostring(EventData.SubjectDomainName), SubjectUserName = tostring(EventData.SubjectUserName)
    | extend TargetAccount = strcat(EventData.TargetDomainName,"\\", EventData.TargetUserName)
    | extend TargetUserName = tostring(EventData.TargetUserName), TargetDomainName = tostring(EventData.TargetDomainName) 
    | extend TargetSid = tostring(EventData.TargetSid)
    | extend UserPrincipalName = tostring(EventData.UserPrincipalName)
    | extend Activity = "4725 - A user account was disabled."
    | project DisableTime = TimeGenerated, DisableEventID = EventID, DisableActivity = Activity, Computer = toupper(Computer), 
    TargetAccount = tolower(TargetAccount), TargetUserName, TargetDomainName, TargetSid, 
    AccountUsedToDisable = SubjectAccount, DisabledBySubjectUserName = SubjectUserName, DisabledBySubjectDomainName = SubjectDomainName, SIDofAccountUsedToDisable = SubjectUserSid, UserPrincipalName
    )
  )
) on Computer, TargetAccount

Stage 24: where where DisableTime - EnableTime < 10m

| where DisableTime - EnableTime < spanoftime

Stage 25: extend

| extend TimeDelta = DisableTime - EnableTime

Stage 26: where

| where tolong(TimeDelta) >= threshold

Stage 27: project

| project TimeDelta, EnableTime, EnableEventID, EnableActivity, Computer, TargetAccount, TargetSid, TargetUserName, TargetDomainName, UserPrincipalName, 
AccountUsedToEnable, SIDofAccountUsedToEnable, DisableTime, DisableEventID, DisableActivity, AccountUsedToDisable, SIDofAccountUsedToDisable, 
EnabledBySubjectUserName, EnabledBySubjectDomainName, DisabledBySubjectUserName, DisabledBySubjectDomainName

Stage 28: extend

| extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))

Stage 29: extend

| extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)

Stage 30: project-away

| project-away DomainIndex

Exclusions

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

StageFieldKindExcluded values
6TargetAccountends_with$
17TargetAccountends_with$

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
AccountTypeeq
  • User corpus 9 (kusto 9)
EventIDeq
  • 4722 transforms: cased corpus 2 (kusto 2)
  • 4725 transforms: cased corpus 2 (splunk 1, kusto 1)
TimeDeltage
  • 0 transforms: tolong, cased corpus 2 (kusto 2)

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
AccountUsedToDisableproject
AccountUsedToEnableproject
Computerproject
DisableActivityproject
DisableEventIDproject
DisableTimeproject
DisabledBySubjectDomainNameproject
DisabledBySubjectUserNameproject
EnableActivityproject
EnableEventIDproject
EnableTimeproject
EnabledBySubjectDomainNameproject
EnabledBySubjectUserNameproject
SIDofAccountUsedToDisableproject
SIDofAccountUsedToEnableproject
TargetAccountproject
TargetDomainNameproject
TargetSidproject
TargetUserNameproject
TimeDeltaproject
UserPrincipalNameproject
HostNameextend
HostNameDomainextend