Account Created and Deleted in Short Timeframe
| Id | bb616d82-108f-47d3-9dec-9652ea0d3bf6 |
| Rulename | Account Created and Deleted in Short Timeframe |
| Description | Search for user principal name (UPN) events. Look for accounts created and then deleted in under 24 hours. 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 |
| Tactics | InitialAccess |
| Techniques | T1078.004 |
| Required data connectors | AzureActiveDirectory |
| Kind | Scheduled |
| Query frequency | 1h |
| Query period | 7d |
| Trigger threshold | 0 |
| Trigger operator | gt |
| Source Uri | https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/Microsoft Entra ID/Analytic Rules/AccountCreatedandDeletedinShortTimeframe.yaml |
| Version | 1.1.1 |
| Arm template | bb616d82-108f-47d3-9dec-9652ea0d3bf6.json |
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])
relevantTechniques:
- T1078.004
entityMappings:
- entityType: Account
fieldMappings:
- columnName: TargetUserPrincipalName
identifier: FullName
- columnName: TargetName
identifier: Name
- columnName: TargetUPNSuffix
identifier: UPNSuffix
- entityType: Account
fieldMappings:
- columnName: CreatedByUserPrincipalName
identifier: FullName
- columnName: CreatedByName
identifier: Name
- columnName: CreatedByUPNSuffix
identifier: UPNSuffix
- entityType: Account
fieldMappings:
- columnName: CreatedByAadUserId
identifier: AadUserId
- entityType: Account
fieldMappings:
- columnName: DeletedByUserPrincipalName
identifier: FullName
- columnName: DeletedByName
identifier: Name
- columnName: DeletedByUPNSuffix
identifier: UPNSuffix
- entityType: Account
fieldMappings:
- columnName: DeletedByAadUserId
identifier: AadUserId
- entityType: IP
fieldMappings:
- columnName: CreatedByIPAddress
identifier: Address
- entityType: IP
fieldMappings:
- columnName: DeletedByIPAddress
identifier: Address
version: 1.1.1
id: bb616d82-108f-47d3-9dec-9652ea0d3bf6
severity: High
kind: Scheduled
queryFrequency: 1h
description: |
'Search for user principal name (UPN) events. Look for accounts created and then deleted in under 24 hours. 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'
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- SigninLogs
triggerOperator: gt
name: Account Created and Deleted in Short Timeframe
tactics:
- InitialAccess
tags:
- AADSecOpsGuide
triggerThreshold: 0
queryPeriod: 7d
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])
status: Available
OriginalUri: https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/Microsoft Entra ID/Analytic Rules/AccountCreatedandDeletedinShortTimeframe.yaml