Adversaries may create user accounts as part of establishing persistence or maintaining access within an environment. SOC teams should proactively hunt for this behavior in Azure Sentinel to identify potential long-term compromise and unauthorized access attempts.
KQL 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") and Timestamp > ago(3d)
// 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, ProcessCommandLine, InitiatingProcessCommandLine
id: ae177dc6-a3ba-474d-87a9-28ff7efb7b21
name: Create account
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).
requiredDataConnectors:
- connectorId: MicrosoftThreatProtection
dataTypes:
- DeviceProcessEvents
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") and Timestamp > ago(3d)
// 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, ProcessCommandLine, InitiatingProcessCommandLine
| Sentinel Table | Notes |
|---|---|
DeviceProcessEvents | Ensure this data connector is enabled |
Scenario: Legitimate user account creation via Active Directory (AD) tools
Description: An administrator creates a new user account using tools like adsiedit.msc or PowerShell’s New-ADUser cmdlet as part of routine user provisioning.
Filter/Exclusion: Check for userAccountControl flags indicating standard user accounts (e.g., UserAccountControl: 512), or filter by known admin tools and AD-related event IDs (e.g., Event ID 472, 473).
Scenario: Scheduled job or automation task creating temporary user accounts
Description: A system or application (e.g., Jenkins, Ansible, or a custom script) creates temporary user accounts for automated tasks or testing.
Filter/Exclusion: Filter by process name (e.g., jenkins.exe, ansible, or powershell.exe), or check for presence of a known automation tool or script path in the command line.
Scenario: User account creation via Microsoft Intune or Azure AD join
Description: A user account is created when a device joins the domain via Microsoft Intune or Azure AD, which automatically provisions a user account for device management.
Filter/Exclusion: Filter by event source (e.g., Microsoft Intune, Azure AD, or Device Registration), or check for device-related event IDs (e.g., Event ID 6006, 6008).
Scenario: System account creation during OS installation or patching
Description: A system account (e.g., LocalService, NetworkService, or LocalSystem) is created during OS installation, updates, or service configuration.
Filter/Exclusion: Filter by account name (e.g., LocalService, NetworkService), or check for event