Detection rules › Kusto
Account Created and Deleted in Short Timeframe
Search for user principal name (UPN) events. Look for accounts created and then deleted within 7 days. Attackers may create an account for their use, and then remove the account when no longer needed. Ref : https://docs.microsoft.com/azure/active-directory/fundamentals/security-operations-user-accounts#short-lived-account
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Telemetry coverage
| Platform | Record / event type |
|---|---|
| Azure | Add user |
| Azure | Delete user |
| Entra ID | SigninLogs sign-in event: Sign-in record (SigninLogs, any result) |
Rules detecting the same action
These rules filter on the same operation.
- [Entra ID] Authentication Method Changed for Privileged Account (Kusto)
- Account Created And Deleted Within A Close Time Frame (Sigma)
- Account created from non-approved sources (Kusto)
- Account created or deleted by non-approved user (Kusto)
- Addition of a Temporary Access Pass to a Privileged Account (Kusto)
- Authentication Method Changed for Privileged Account (Kusto)
- Authentication Methods Changed for Privileged Account (Kusto)
- Azure AD External Guest User Invited (Splunk)
Rule body
id: bb616d82-108f-47d3-9dec-9652ea0d3bf6
name: Account Created and Deleted in Short Timeframe
description: |
'Search for user principal name (UPN) events. Look for accounts created and then deleted within 7 days. Attackers may create an account for their use, and then remove the account when no longer needed.
Ref : https://docs.microsoft.com/azure/active-directory/fundamentals/security-operations-user-accounts#short-lived-account'
severity: High
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- SigninLogs
queryFrequency: 1h
queryPeriod: 7d
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
- InitialAccess
relevantTechniques:
- T1078.004
tags:
- AADSecOpsGuide
query: |
let queryfrequency = 1h;
let queryperiod = 7d; // Increased queryperiod to 7 days to mitigate timing-based bypasses
AuditLogs
| where TimeGenerated > ago(queryfrequency)
| where OperationName =~ "Delete user"
| mv-apply TargetResource = TargetResources on
(
where TargetResource.type == "User"
// Normalize TargetUserPrincipalName by removing optional 32-char hex prefixes and underscores
// Making extraction case-insensitive, and converting the result to lowercase for consistency
| extend TargetUserPrincipalName = tolower(extract(@"(?i)([a-f0-9]{32})?_?(.*)", 2, tostring(TargetResource.userPrincipalName)))
// Standardize UserId to ensure consistent, immutable identification
| extend UserId = tostring(TargetResource.id)
)
| extend DeletedByApp = tostring(InitiatedBy.app.displayName),
DeletedByAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId),
DeletedByUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName),
DeletedByAadUserId = tostring(InitiatedBy.user.id),
DeletedByIPAddress = tostring(InitiatedBy.user.ipAddress)
| project Deletion_TimeGenerated = TimeGenerated, TargetUserPrincipalName, UserId, DeletedByApp, DeletedByAppServicePrincipalId, DeletedByUserPrincipalName, DeletedByAadUserId, DeletedByIPAddress,
Deletion_AdditionalDetails = AdditionalDetails, Deletion_InitiatedBy = InitiatedBy, Deletion_TargetResources = TargetResources
| join kind=inner (
AuditLogs
| where TimeGenerated > ago(queryperiod)
| where OperationName =~ "Add user"
| mv-apply TargetResource = TargetResources on
(
where TargetResource.type == "User"
// Normalize TargetUserPrincipalName by removing optional 32-char hex prefixes and underscores
// Making extraction case-insensitive, and converting the result to lowercase for consistency
| extend TargetUserPrincipalName = tolower(extract(@"(?i)([a-f0-9]{32})?_?(.*)", 2, tostring(TargetResource.userPrincipalName)))
// Standardize UserId to ensure consistent, immutable identification
| extend UserId = tostring(TargetResource.id)
)
| project-rename Creation_TimeGenerated = TimeGenerated
) on UserId
| extend TimeDelta = Deletion_TimeGenerated - Creation_TimeGenerated
| where TimeDelta between (time(0s) .. queryperiod)
| extend CreatedByApp = tostring(InitiatedBy.app.displayName),
CreatedByAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId),
CreatedByUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName),
CreatedByAadUserId = tostring(InitiatedBy.user.id),
CreatedByIPAddress = tostring(InitiatedBy.user.ipAddress)
| project Creation_TimeGenerated, Deletion_TimeGenerated, TimeDelta, UserId, TargetUserPrincipalName, DeletedByApp, DeletedByAppServicePrincipalId, DeletedByUserPrincipalName, DeletedByAadUserId, DeletedByIPAddress,
CreatedByApp, CreatedByAppServicePrincipalId, CreatedByUserPrincipalName, CreatedByAadUserId, CreatedByIPAddress, Creation_AdditionalDetails = AdditionalDetails, Creation_InitiatedBy = InitiatedBy, Creation_TargetResources = TargetResources, Deletion_AdditionalDetails, Deletion_InitiatedBy, Deletion_TargetResources
| extend TargetName = tostring(split(TargetUserPrincipalName,'@',0)[0]), TargetUPNSuffix = tostring(split(TargetUserPrincipalName,'@',1)[0])
| extend CreatedByName = tostring(split(CreatedByUserPrincipalName,'@',0)[0]), CreatedByUPNSuffix = tostring(split(CreatedByUserPrincipalName,'@',1)[0])
| extend DeletedByName = tostring(split(DeletedByUserPrincipalName,'@',0)[0]), DeletedByUPNSuffix = tostring(split(DeletedByUserPrincipalName,'@',1)[0])
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: TargetUserPrincipalName
- identifier: Name
columnName: TargetName
- identifier: UPNSuffix
columnName: TargetUPNSuffix
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: CreatedByUserPrincipalName
- identifier: Name
columnName: CreatedByName
- identifier: UPNSuffix
columnName: CreatedByUPNSuffix
- entityType: Account
fieldMappings:
- identifier: AadUserId
columnName: CreatedByAadUserId
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: DeletedByUserPrincipalName
- identifier: Name
columnName: DeletedByName
- identifier: UPNSuffix
columnName: DeletedByUPNSuffix
- entityType: Account
fieldMappings:
- identifier: AadUserId
columnName: DeletedByAadUserId
- entityType: IP
fieldMappings:
- identifier: Address
columnName: CreatedByIPAddress
- entityType: IP
fieldMappings:
- identifier: Address
columnName: DeletedByIPAddress
version: 1.1.2
kind: Scheduled
Stages and Predicates
Parameters
let queryfrequency = 1h;
let queryperiod = 7d;
Stage 1: source
AuditLogs
Stage 2: where
| where TimeGenerated > ago(queryfrequency)
Stage 3: where
| where OperationName =~ "Delete user"
Stage 4: kusto:mv-apply
| mv-apply TargetResource = TargetResources on
(
where TargetResource.type == "User"
| extend TargetUserPrincipalName = tolower(extract(@"(?i)([a-f0-9]{32})?_?(.*)", 2, tostring(TargetResource.userPrincipalName)))
| extend UserId = tostring(TargetResource.id)
)
Stage 5: extend
| extend DeletedByApp = tostring(InitiatedBy.app.displayName),
DeletedByAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId),
DeletedByUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName),
DeletedByAadUserId = tostring(InitiatedBy.user.id),
DeletedByIPAddress = tostring(InitiatedBy.user.ipAddress)
Stage 6: project
| project Deletion_TimeGenerated = TimeGenerated, TargetUserPrincipalName, UserId, DeletedByApp, DeletedByAppServicePrincipalId, DeletedByUserPrincipalName, DeletedByAadUserId, DeletedByIPAddress,
Deletion_AdditionalDetails = AdditionalDetails, Deletion_InitiatedBy = InitiatedBy, Deletion_TargetResources = TargetResources
Stage 7: join
| join kind=inner (
AuditLogs
| where TimeGenerated > ago(queryperiod)
| where OperationName =~ "Add user"
| mv-apply TargetResource = TargetResources on
(
where TargetResource.type == "User"
| extend TargetUserPrincipalName = tolower(extract(@"(?i)([a-f0-9]{32})?_?(.*)", 2, tostring(TargetResource.userPrincipalName)))
| extend UserId = tostring(TargetResource.id)
)
| project-rename Creation_TimeGenerated = TimeGenerated
) on UserId
Stage 8: extend
| extend TimeDelta = Deletion_TimeGenerated - Creation_TimeGenerated
Stage 9: where
| where TimeDelta between (time(0s) .. queryperiod)
Stage 10: extend
| extend CreatedByApp = tostring(InitiatedBy.app.displayName),
CreatedByAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId),
CreatedByUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName),
CreatedByAadUserId = tostring(InitiatedBy.user.id),
CreatedByIPAddress = tostring(InitiatedBy.user.ipAddress)
Stage 11: project
| project Creation_TimeGenerated, Deletion_TimeGenerated, TimeDelta, UserId, TargetUserPrincipalName, DeletedByApp, DeletedByAppServicePrincipalId, DeletedByUserPrincipalName, DeletedByAadUserId, DeletedByIPAddress,
CreatedByApp, CreatedByAppServicePrincipalId, CreatedByUserPrincipalName, CreatedByAadUserId, CreatedByIPAddress, Creation_AdditionalDetails = AdditionalDetails, Creation_InitiatedBy = InitiatedBy, Creation_TargetResources = TargetResources, Deletion_AdditionalDetails, Deletion_InitiatedBy, Deletion_TargetResources
Stage 12: extend (3 consecutive steps)
| extend TargetName = tostring(split(TargetUserPrincipalName,'@',0)[0]), TargetUPNSuffix = tostring(split(TargetUserPrincipalName,'@',1)[0])
| extend CreatedByName = tostring(split(CreatedByUserPrincipalName,'@',0)[0]), CreatedByUPNSuffix = tostring(split(CreatedByUserPrincipalName,'@',1)[0])
| extend DeletedByName = tostring(split(DeletedByUserPrincipalName,'@',0)[0]), DeletedByUPNSuffix = tostring(split(DeletedByUserPrincipalName,'@',1)[0])
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
OperationName | eq |
| field:"OperationName" kind:eq |
type | eq |
| field:"type" kind:eq value:"User" |
Output fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
CreatedByAadUserId | project |
CreatedByApp | project |
CreatedByAppServicePrincipalId | project |
CreatedByIPAddress | project |
CreatedByUserPrincipalName | project |
Creation_AdditionalDetails | project |
Creation_InitiatedBy | project |
Creation_TargetResources | project |
Creation_TimeGenerated | project |
DeletedByAadUserId | project |
DeletedByApp | project |
DeletedByAppServicePrincipalId | project |
DeletedByIPAddress | project |
DeletedByUserPrincipalName | project |
Deletion_AdditionalDetails | project |
Deletion_InitiatedBy | project |
Deletion_TargetResources | project |
Deletion_TimeGenerated | project |
TargetUserPrincipalName | project |
TimeDelta | project |
UserId | project |
TargetName | extend |
TargetUPNSuffix | extend |
CreatedByName | extend |
CreatedByUPNSuffix | extend |
DeletedByName | extend |
DeletedByUPNSuffix | extend |