Suspicious Entra ID Joined Device Update
Id | 3a3c6835-0086-40ca-b033-a93bf26d878f |
Rulename | Suspicious Entra ID Joined Device Update |
Description | This query looks for suspicious updates to an Microsoft Entra ID joined device where the device name is changed and the device falls out of compliance. This could occur when a threat actor updates the details of an Autopilot provisioned device using a stolen device ticket, in order to access certificates and keys. Ref: https://dirkjanm.io/assets/raw/Insomnihack%20Breaking%20and%20fixing%20Azure%20AD%20device%20identity%20security.pdf |
Severity | Medium |
Tactics | CredentialAccess |
Techniques | T1528 |
Required data connectors | AzureActiveDirectory |
Kind | Scheduled |
Query frequency | 1d |
Query period | 1d |
Trigger threshold | 0 |
Trigger operator | gt |
Source Uri | https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/Microsoft Entra ID/Analytic Rules/SuspiciousAADJoinedDeviceUpdate.yaml |
Version | 1.0.4 |
Arm template | 3a3c6835-0086-40ca-b033-a93bf26d878f.json |
AuditLogs
| where OperationName =~ "Update device"
| mv-apply TargetResource=TargetResources on (
where TargetResource.type =~ "Device"
| extend ModifiedProperties = TargetResource.modifiedProperties
| extend DeviceId = TargetResource.id)
| mv-apply Prop=ModifiedProperties on (
where Prop.displayName =~ "CloudDisplayName"
| extend OldName = Prop.oldValue
| extend NewName = Prop.newValue)
| mv-apply Prop=ModifiedProperties on (
where Prop.displayName =~ "IsCompliant"
| extend OldComplianceState = Prop.oldValue
| extend NewComplianceState = Prop.newValue)
| mv-apply Prop=ModifiedProperties on (
where Prop.displayName =~ "TargetId.DeviceTrustType"
| extend OldTrustType = Prop.oldValue
| extend NewTrustType = Prop.newValue)
| mv-apply Prop=ModifiedProperties on (
where Prop.displayName =~ "Included Updated Properties"
| extend UpdatedProperties = Prop.newValue)
| extend OldDeviceName = tostring(parse_json(tostring(OldName))[0])
| extend NewDeviceName = tostring(parse_json(tostring(NewName))[0])
| extend OldComplianceState = tostring(parse_json(tostring(OldComplianceState))[0])
| extend NewComplianceState = tostring(parse_json(tostring(NewComplianceState))[0])
| extend InitiatingAppName = tostring(InitiatedBy.app.displayName)
| extend InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
| extend InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
| extend InitiatingAadUserId = tostring(InitiatedBy.user.id)
| extend InitiatingIpAddress = tostring(iff(isnotempty(InitiatedBy.user.ipAddress), InitiatedBy.user.ipAddress, InitiatedBy.app.ipAddress))
| extend UpdatedPropertiesCount = array_length(split(UpdatedProperties, ','))
| where OldDeviceName != NewDeviceName
| where OldComplianceState =~ 'true' and NewComplianceState =~ 'false'
// Most common is transferring from AAD Registered to AAD Joined - we just want AAD Joined devices
| where NewTrustType == '"AzureAd"' and OldTrustType != '"Workplace"'
// We can modify this value to tune FPs - more properties changed about the device beyond its name the more suspicious it could be
| where UpdatedPropertiesCount > 1
| project-reorder TimeGenerated, DeviceId, NewDeviceName, OldDeviceName, NewComplianceState, InitiatingUserPrincipalName, InitiatingAadUserId, InitiatingAppName, InitiatingAppServicePrincipalId, InitiatingIpAddress, AADOperationType, OldTrustType, NewTrustType, UpdatedProperties, UpdatedPropertiesCount
| extend InitiatedByName = tostring(split(InitiatingUserPrincipalName,'@',0)[0]), InitiatedByUPNSuffix = tostring(split(InitiatingUserPrincipalName,'@',1)[0])
relevantTechniques:
- T1528
name: Suspicious Entra ID Joined Device Update
requiredDataConnectors:
- dataTypes:
- AuditLogs
connectorId: AzureActiveDirectory
entityMappings:
- fieldMappings:
- identifier: HostName
columnName: NewDeviceName
entityType: Host
- fieldMappings:
- identifier: HostName
columnName: OldDeviceName
entityType: Host
- fieldMappings:
- identifier: AzureID
columnName: DeviceId
entityType: Host
- fieldMappings:
- identifier: FullName
columnName: InitiatingUserPrincipalName
- identifier: Name
columnName: InitiatedByName
- identifier: UPNSuffix
columnName: InitiatedByUPNSuffix
entityType: Account
- fieldMappings:
- identifier: AadUserId
columnName: InitiatingAadUserId
entityType: Account
- fieldMappings:
- identifier: AadUserId
columnName: InitiatingAppServicePrincipalId
entityType: Account
- fieldMappings:
- identifier: Address
columnName: InitiatingIpAddress
entityType: IP
triggerThreshold: 0
id: 3a3c6835-0086-40ca-b033-a93bf26d878f
tactics:
- CredentialAccess
version: 1.0.4
alertDetailsOverride:
alertDisplayNameFormat: Suspicious Entra ID Joined Device Update {{OldDeviceName}} renamed to {{NewDeviceName}} and {{UpdatedPropertiesCount}} properties changed
alertDescriptionFormat: |
This query looks for suspicious updates to an Microsoft Entra ID joined device where the device name is changed and the device falls out of compliance.
In this case {{OldDeviceName}} was renamed to {{NewDeviceName}} and {{UpdatedPropertiesCount}} properties were changed.
This could occur when a threat actor updates the details of an Autopilot provisioned device using a stolen device ticket, in order to access certificates and keys.
Ref: https://dirkjanm.io/assets/raw/Insomnihack%20Breaking%20and%20fixing%20Azure%20AD%20device%20identity%20security.pdf
queryPeriod: 1d
kind: Scheduled
OriginalUri: https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/Microsoft Entra ID/Analytic Rules/SuspiciousAADJoinedDeviceUpdate.yaml
queryFrequency: 1d
severity: Medium
status: Available
description: |
'This query looks for suspicious updates to an Microsoft Entra ID joined device where the device name is changed and the device falls out of compliance.
This could occur when a threat actor updates the details of an Autopilot provisioned device using a stolen device ticket, in order to access certificates and keys.
Ref: https://dirkjanm.io/assets/raw/Insomnihack%20Breaking%20and%20fixing%20Azure%20AD%20device%20identity%20security.pdf'
query: |
AuditLogs
| where OperationName =~ "Update device"
| mv-apply TargetResource=TargetResources on (
where TargetResource.type =~ "Device"
| extend ModifiedProperties = TargetResource.modifiedProperties
| extend DeviceId = TargetResource.id)
| mv-apply Prop=ModifiedProperties on (
where Prop.displayName =~ "CloudDisplayName"
| extend OldName = Prop.oldValue
| extend NewName = Prop.newValue)
| mv-apply Prop=ModifiedProperties on (
where Prop.displayName =~ "IsCompliant"
| extend OldComplianceState = Prop.oldValue
| extend NewComplianceState = Prop.newValue)
| mv-apply Prop=ModifiedProperties on (
where Prop.displayName =~ "TargetId.DeviceTrustType"
| extend OldTrustType = Prop.oldValue
| extend NewTrustType = Prop.newValue)
| mv-apply Prop=ModifiedProperties on (
where Prop.displayName =~ "Included Updated Properties"
| extend UpdatedProperties = Prop.newValue)
| extend OldDeviceName = tostring(parse_json(tostring(OldName))[0])
| extend NewDeviceName = tostring(parse_json(tostring(NewName))[0])
| extend OldComplianceState = tostring(parse_json(tostring(OldComplianceState))[0])
| extend NewComplianceState = tostring(parse_json(tostring(NewComplianceState))[0])
| extend InitiatingAppName = tostring(InitiatedBy.app.displayName)
| extend InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
| extend InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
| extend InitiatingAadUserId = tostring(InitiatedBy.user.id)
| extend InitiatingIpAddress = tostring(iff(isnotempty(InitiatedBy.user.ipAddress), InitiatedBy.user.ipAddress, InitiatedBy.app.ipAddress))
| extend UpdatedPropertiesCount = array_length(split(UpdatedProperties, ','))
| where OldDeviceName != NewDeviceName
| where OldComplianceState =~ 'true' and NewComplianceState =~ 'false'
// Most common is transferring from AAD Registered to AAD Joined - we just want AAD Joined devices
| where NewTrustType == '"AzureAd"' and OldTrustType != '"Workplace"'
// We can modify this value to tune FPs - more properties changed about the device beyond its name the more suspicious it could be
| where UpdatedPropertiesCount > 1
| project-reorder TimeGenerated, DeviceId, NewDeviceName, OldDeviceName, NewComplianceState, InitiatingUserPrincipalName, InitiatingAadUserId, InitiatingAppName, InitiatingAppServicePrincipalId, InitiatingIpAddress, AADOperationType, OldTrustType, NewTrustType, UpdatedProperties, UpdatedPropertiesCount
| extend InitiatedByName = tostring(split(InitiatingUserPrincipalName,'@',0)[0]), InitiatedByUPNSuffix = tostring(split(InitiatingUserPrincipalName,'@',1)[0])
triggerOperator: gt
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"workspace": {
"type": "String"
}
},
"resources": [
{
"apiVersion": "2024-01-01-preview",
"id": "[concat(resourceId('Microsoft.OperationalInsights/workspaces/providers', parameters('workspace'), 'Microsoft.SecurityInsights'),'/alertRules/3a3c6835-0086-40ca-b033-a93bf26d878f')]",
"kind": "Scheduled",
"name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/3a3c6835-0086-40ca-b033-a93bf26d878f')]",
"properties": {
"alertDetailsOverride": {
"alertDescriptionFormat": "This query looks for suspicious updates to an Microsoft Entra ID joined device where the device name is changed and the device falls out of compliance.\nIn this case {{OldDeviceName}} was renamed to {{NewDeviceName}} and {{UpdatedPropertiesCount}} properties were changed.\nThis could occur when a threat actor updates the details of an Autopilot provisioned device using a stolen device ticket, in order to access certificates and keys.\nRef: https://dirkjanm.io/assets/raw/Insomnihack%20Breaking%20and%20fixing%20Azure%20AD%20device%20identity%20security.pdf\n",
"alertDisplayNameFormat": "Suspicious Entra ID Joined Device Update {{OldDeviceName}} renamed to {{NewDeviceName}} and {{UpdatedPropertiesCount}} properties changed"
},
"alertRuleTemplateName": "3a3c6835-0086-40ca-b033-a93bf26d878f",
"customDetails": null,
"description": "'This query looks for suspicious updates to an Microsoft Entra ID joined device where the device name is changed and the device falls out of compliance.\nThis could occur when a threat actor updates the details of an Autopilot provisioned device using a stolen device ticket, in order to access certificates and keys.\nRef: https://dirkjanm.io/assets/raw/Insomnihack%20Breaking%20and%20fixing%20Azure%20AD%20device%20identity%20security.pdf'\n",
"displayName": "Suspicious Entra ID Joined Device Update",
"enabled": true,
"entityMappings": [
{
"entityType": "Host",
"fieldMappings": [
{
"columnName": "NewDeviceName",
"identifier": "HostName"
}
]
},
{
"entityType": "Host",
"fieldMappings": [
{
"columnName": "OldDeviceName",
"identifier": "HostName"
}
]
},
{
"entityType": "Host",
"fieldMappings": [
{
"columnName": "DeviceId",
"identifier": "AzureID"
}
]
},
{
"entityType": "Account",
"fieldMappings": [
{
"columnName": "InitiatingUserPrincipalName",
"identifier": "FullName"
},
{
"columnName": "InitiatedByName",
"identifier": "Name"
},
{
"columnName": "InitiatedByUPNSuffix",
"identifier": "UPNSuffix"
}
]
},
{
"entityType": "Account",
"fieldMappings": [
{
"columnName": "InitiatingAadUserId",
"identifier": "AadUserId"
}
]
},
{
"entityType": "Account",
"fieldMappings": [
{
"columnName": "InitiatingAppServicePrincipalId",
"identifier": "AadUserId"
}
]
},
{
"entityType": "IP",
"fieldMappings": [
{
"columnName": "InitiatingIpAddress",
"identifier": "Address"
}
]
}
],
"OriginalUri": "https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/Microsoft Entra ID/Analytic Rules/SuspiciousAADJoinedDeviceUpdate.yaml",
"query": "AuditLogs\n| where OperationName =~ \"Update device\"\n| mv-apply TargetResource=TargetResources on (\n where TargetResource.type =~ \"Device\"\n | extend ModifiedProperties = TargetResource.modifiedProperties\n | extend DeviceId = TargetResource.id)\n| mv-apply Prop=ModifiedProperties on ( \n where Prop.displayName =~ \"CloudDisplayName\"\n | extend OldName = Prop.oldValue \n | extend NewName = Prop.newValue)\n| mv-apply Prop=ModifiedProperties on ( \n where Prop.displayName =~ \"IsCompliant\"\n | extend OldComplianceState = Prop.oldValue \n | extend NewComplianceState = Prop.newValue)\n| mv-apply Prop=ModifiedProperties on ( \n where Prop.displayName =~ \"TargetId.DeviceTrustType\"\n | extend OldTrustType = Prop.oldValue \n | extend NewTrustType = Prop.newValue)\n| mv-apply Prop=ModifiedProperties on ( \n where Prop.displayName =~ \"Included Updated Properties\" \n | extend UpdatedProperties = Prop.newValue)\n| extend OldDeviceName = tostring(parse_json(tostring(OldName))[0])\n| extend NewDeviceName = tostring(parse_json(tostring(NewName))[0])\n| extend OldComplianceState = tostring(parse_json(tostring(OldComplianceState))[0])\n| extend NewComplianceState = tostring(parse_json(tostring(NewComplianceState))[0])\n| extend InitiatingAppName = tostring(InitiatedBy.app.displayName)\n| extend InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)\n| extend InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)\n| extend InitiatingAadUserId = tostring(InitiatedBy.user.id)\n| extend InitiatingIpAddress = tostring(iff(isnotempty(InitiatedBy.user.ipAddress), InitiatedBy.user.ipAddress, InitiatedBy.app.ipAddress))\n| extend UpdatedPropertiesCount = array_length(split(UpdatedProperties, ','))\n| where OldDeviceName != NewDeviceName\n| where OldComplianceState =~ 'true' and NewComplianceState =~ 'false'\n// Most common is transferring from AAD Registered to AAD Joined - we just want AAD Joined devices\n| where NewTrustType == '\"AzureAd\"' and OldTrustType != '\"Workplace\"'\n// We can modify this value to tune FPs - more properties changed about the device beyond its name the more suspicious it could be\n| where UpdatedPropertiesCount > 1\n| project-reorder TimeGenerated, DeviceId, NewDeviceName, OldDeviceName, NewComplianceState, InitiatingUserPrincipalName, InitiatingAadUserId, InitiatingAppName, InitiatingAppServicePrincipalId, InitiatingIpAddress, AADOperationType, OldTrustType, NewTrustType, UpdatedProperties, UpdatedPropertiesCount\n| extend InitiatedByName = tostring(split(InitiatingUserPrincipalName,'@',0)[0]), InitiatedByUPNSuffix = tostring(split(InitiatingUserPrincipalName,'@',1)[0])\n",
"queryFrequency": "P1D",
"queryPeriod": "P1D",
"severity": "Medium",
"status": "Available",
"subTechniques": [],
"suppressionDuration": "PT1H",
"suppressionEnabled": false,
"tactics": [
"CredentialAccess"
],
"techniques": [
"T1528"
],
"templateVersion": "1.0.4",
"triggerOperator": "GreaterThan",
"triggerThreshold": 0
},
"type": "Microsoft.OperationalInsights/workspaces/providers/alertRules"
}
]
}