Detection rules › Kusto

Account Creation

Status
available
Severity
medium
Time window
1h
Group by
CreatedOnLocalMachine, CreatedUser, DeviceId, DeviceName, FileName, InitiatingProcessCommandLine, InitiatingProcessFileName, ProcessCommandLine, ProcessId
Source
github.com/Azure/Azure-Sentinel

User accounts may be created to achieve persistence on a machine. Read more here: https://attack.mitre.org/wiki/Technique/T1136. Tags: #CreateAccount. Query #1: Query for users being created using "net user" command. "net user" commands are noisy, so needs to be joined with another signal -. E.g. in this example we look for use of uncommon & undocumented commandline switches (e.g. /ad instead of /add).

MITRE ATT&CK coverage

TacticTechniques
Persistence

Telemetry coverage

Rule body

id: 450f4e56-5bba-4070-b9d9-9204ba9d777d
name: Account Creation
description: |
  User accounts may be created to achieve persistence on a machine.
  Read more here: https://attack.mitre.org/wiki/Technique/T1136.
  Tags: #CreateAccount.
  Query #1: Query for users being created using "net user" command.
  "net user" commands are noisy, so needs to be joined with another signal -.
  E.g. in this example we look for use of uncommon & undocumented commandline switches (e.g. /ad instead of /add).
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: MicrosoftThreatProtection
    dataTypes:
      - DeviceProcessEvents
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Persistence
relevantTechniques:
  - T1136
query: |
  DeviceProcessEvents
  // Pro-tip: 
  // There are many different ways to run a process from a file - e.g. by using full path, env. variables, ~1 annotation, more...
  // So, to find executions of a known filename, better filter on the filename (and possibly on folder path) than on the commandline.
  | where FileName in~ ("net.exe", "net1.exe")
  // Parse the user name from the commandline.
  // To have case-insensitive parsing use the i flag, to have non-greedy match (e.g. CreatedUser as short as possible), specify U flag:
  // "kind=regex flags=i"
  | parse kind=regex flags=iU ProcessCommandLine with * "user " CreatedUser " " * "/ad"
  // Filter rows where user could not be parsed - e.g. because it was not a user command, or the /add commandline switch was not specified.
  | where isnotempty(CreatedUser)
  // Every net.exe executed will run net1.exe with the same commandline.
  // in this where clause we remove such rows, as they duplicate the number of results we have without adding any value.
  | where not (FileName =~ "net1.exe" and InitiatingProcessFileName =~ "net.exe" and replace("net", "net1", InitiatingProcessCommandLine) =~ ProcessCommandLine)
  // If /domain is specified, so the user is created on the domain controller.
  // Also, any prefix that's longer than 1 char will also do the same, e.g. /do, /dom, /doma, ....
  | extend CreatedOnLocalMachine=(ProcessCommandLine !contains "/do")
  | where ProcessCommandLine !contains "/add" or (CreatedOnLocalMachine == 0 and ProcessCommandLine !contains "/domain")
  | summarize MachineCount=dcount(DeviceName) by CreatedUser, CreatedOnLocalMachine, InitiatingProcessFileName, FileName, ProcessId, ProcessCommandLine, InitiatingProcessCommandLine, DeviceId, DeviceName
  | extend HostName = iff(DeviceName has '.', substring(DeviceName, 0, indexof(DeviceName, '.')), DeviceName)
  | extend DnsDomain = iff(DeviceName has '.', substring(DeviceName, indexof(DeviceName, '.') + 1), "")
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: DeviceName
      - identifier: HostName
        columnName: HostName
      - identifier: DnsDomain
        columnName: DnsDomain
  - entityType: Process
    fieldMappings:
      - identifier: ProcessId
        columnName: ProcessId
      - identifier: CommandLine
        columnName: ProcessCommandLine 
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

DeviceProcessEvents

Stage 2: where

| where FileName in~ ("net.exe", "net1.exe")

Stage 3: parse

| parse kind=regex flags=iU ProcessCommandLine with * "user " CreatedUser " " * "/ad"

Stage 4: where

| where isnotempty(CreatedUser)

Stage 5: where

| where not (FileName =~ "net1.exe" and InitiatingProcessFileName =~ "net.exe" and replace("net", "net1", InitiatingProcessCommandLine) =~ ProcessCommandLine)

Stage 6: extend

| extend CreatedOnLocalMachine=(ProcessCommandLine !contains "/do")

Stage 7: where

| where ProcessCommandLine !contains "/add" or (CreatedOnLocalMachine == 0 and ProcessCommandLine !contains "/domain")

Stage 8: summarize

| summarize MachineCount=dcount(DeviceName) by CreatedUser, CreatedOnLocalMachine, InitiatingProcessFileName, FileName, ProcessId, ProcessCommandLine, InitiatingProcessCommandLine, DeviceId, DeviceName

Stage 9: extend

| extend HostName = iff(DeviceName has '.', substring(DeviceName, 0, indexof(DeviceName, '.')), DeviceName)
HostName =
ifDeviceName has "."substring(DeviceName, 0, indexof(DeviceName, '.'))
elseDeviceName

Stage 10: extend

| extend DnsDomain = iff(DeviceName has '.', substring(DeviceName, indexof(DeviceName, '.') + 1), "")
DnsDomain =
ifDeviceName has "."substring(DeviceName, (indexof(DeviceName, '.') + 1))
else""

Exclusions

The rule actively suppresses these predicates.

Indicators

These rows show field, operator, and value matches.

Output fields

These fields are emitted when the rule matches.

FieldSource
CreatedOnLocalMachinesummarize
CreatedUsersummarize
DeviceIdsummarize
DeviceNamesummarize
FileNamesummarize
InitiatingProcessCommandLinesummarize
InitiatingProcessFileNamesummarize
MachineCountsummarize
ProcessCommandLinesummarize
ProcessIdsummarize
HostNameextend
DnsDomainextend