AAD Local Device Join Information and Transport Key Registry Keys Access
Id | a356c8bd-c81d-428b-aa36-83be706be034 |
Rulename | AAD Local Device Join Information and Transport Key Registry Keys Access |
Description | This detection uses Windows security events to detect suspicious access attempts by the same process to registry keys that provide information about an AAD joined or registered devices and Transport keys (tkpub / tkpriv). This information can be used to export the Device Certificate (dkpub / dkpriv) and Transport key (tkpub/tkpriv). These set of keys can be used to impersonate existing Azure AD joined devices. This detection requires an access control entry (ACE) on the system access control list (SACL) of the following securable objects: HKLM:\SYSTEM\CurrentControlSet\Control\CloudDomainJoin (AAD joined devices) HKCU:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\WorkplaceJoin (AAD registered devices) HKLM:\SYSTEM\CurrentControlSet\Control\Cryptography\Ngc\KeyTransportKey (Transport Key) Make sure you set the SACL to propagate to its sub-keys. You can find more information in here https://github.com/OTRF/Set-AuditRule/blob/master/rules/registry/aad_connect_health_service_agent.yml Reference: https://o365blog.com/post/deviceidentity/ |
Severity | Medium |
Tactics | Discovery |
Techniques | T1012 |
Required data connectors | SecurityEvents WindowsSecurityEvents |
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/Windows Security Events/Analytic Rules/LocalDeviceJoinInfoAndTransportKeyRegKeysAccess.yaml |
Version | 1.0.2 |
Arm template | a356c8bd-c81d-428b-aa36-83be706be034.json |
// AADJoined or Register Device Registry Keys
let aadJoinRoot = "\\REGISTRY\\MACHINE\\SYSTEM\\ControlSet001\\Control\\CloudDomainJoin\\JoinInfo\\";
let aadRegisteredRoot = "\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\WorkplaceJoin";
// Transport Key Registry Key
let keyTransportKey = "\\REGISTRY\\MACHINE\\SYSTEM\\ControlSet001\\Control\\Cryptography\\Ngc\\KeyTransportKey\\";
(union isfuzzy=true
(
// Access to Object Requested
SecurityEvent
| where EventID == '4656'
| where EventData has aadJoinRoot or EventData has aadRegisteredRoot
| extend EventData = parse_xml(EventData).EventData.Data
| mv-expand bagexpansion=array EventData
| evaluate bag_unpack(EventData)
| extend Key = tostring(column_ifexists('@Name', "")), Value = column_ifexists('#text', "")
| evaluate pivot(Key, any(Value), TimeGenerated, Computer, EventID)
| where ObjectType == 'Key'
| where ObjectName startswith aadJoinRoot and SubjectLogonId != '0x3e7' //Local System
| extend ProcessId = column_ifexists("ProcessId", ""), Process = split(ProcessName, '\\', -1)[-1],Account = strcat(SubjectDomainName, "\\", SubjectUserName)
| join kind=innerunique (
SecurityEvent
| where EventID == '4656'
| where EventData has keyTransportKey
| extend EventData = parse_xml(EventData).EventData.Data
| mv-expand bagexpansion=array EventData
| evaluate bag_unpack(EventData)
| extend Key = tostring(column_ifexists('@Name', "")), Value = column_ifexists('#text', "")
| evaluate pivot(Key, any(Value), TimeGenerated, Computer, EventID)
| extend ObjectName = column_ifexists("ObjectName", ""),ObjectType = column_ifexists("ObjectType", "")
| where ObjectType == 'Key'
| where ObjectName startswith keyTransportKey and SubjectLogonId != '0x3e7' //Local System
| extend ProcessId = column_ifexists("ProcessId", ""), Process = split(ProcessName, '\\', -1)[-1],Account = strcat(SubjectDomainName, "\\", SubjectUserName)
) on $left.Computer == $right.Computer and $left.SubjectLogonId == $right.SubjectLogonId and $left.ProcessId == $right.ProcessId
| project TimeGenerated, Computer, Account, SubjectDomainName, SubjectUserName, SubjectLogonId, ObjectName, tostring(Process), ProcessName, ProcessId, EventID
),
// Accessing Object
(
SecurityEvent
| where EventID == '4663'
| where ObjectType == 'Key'
| where (ObjectName startswith aadJoinRoot or ObjectName contains aadRegisteredRoot) and SubjectLogonId != '0x3e7' //Local System
| extend Account = SubjectAccount
| join kind=innerunique (
SecurityEvent
| where EventID == '4663'
| where ObjectType == 'Key'
| where ObjectName has keyTransportKey and SubjectLogonId != '0x3e7' //Local System
| extend Account = SubjectAccount
) on $left.Computer == $right.Computer and $left.SubjectLogonId == $right.SubjectLogonId and $left.ProcessId == $right.ProcessId
| project TimeGenerated, Computer, Account, SubjectDomainName, SubjectUserName, SubjectLogonId, ObjectName, Process, ProcessName, ProcessId, EventID
| extend HostName = tostring(split(Computer, '.', 0)[0]), DnsDomain = tostring(strcat_array(array_slice(split(Computer, '.'), 1, -1), '.'))
)
)
severity: Medium
queryFrequency: 1d
relevantTechniques:
- T1012
tactics:
- Discovery
kind: Scheduled
query: |
// AADJoined or Register Device Registry Keys
let aadJoinRoot = "\\REGISTRY\\MACHINE\\SYSTEM\\ControlSet001\\Control\\CloudDomainJoin\\JoinInfo\\";
let aadRegisteredRoot = "\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\WorkplaceJoin";
// Transport Key Registry Key
let keyTransportKey = "\\REGISTRY\\MACHINE\\SYSTEM\\ControlSet001\\Control\\Cryptography\\Ngc\\KeyTransportKey\\";
(union isfuzzy=true
(
// Access to Object Requested
SecurityEvent
| where EventID == '4656'
| where EventData has aadJoinRoot or EventData has aadRegisteredRoot
| extend EventData = parse_xml(EventData).EventData.Data
| mv-expand bagexpansion=array EventData
| evaluate bag_unpack(EventData)
| extend Key = tostring(column_ifexists('@Name', "")), Value = column_ifexists('#text', "")
| evaluate pivot(Key, any(Value), TimeGenerated, Computer, EventID)
| where ObjectType == 'Key'
| where ObjectName startswith aadJoinRoot and SubjectLogonId != '0x3e7' //Local System
| extend ProcessId = column_ifexists("ProcessId", ""), Process = split(ProcessName, '\\', -1)[-1],Account = strcat(SubjectDomainName, "\\", SubjectUserName)
| join kind=innerunique (
SecurityEvent
| where EventID == '4656'
| where EventData has keyTransportKey
| extend EventData = parse_xml(EventData).EventData.Data
| mv-expand bagexpansion=array EventData
| evaluate bag_unpack(EventData)
| extend Key = tostring(column_ifexists('@Name', "")), Value = column_ifexists('#text', "")
| evaluate pivot(Key, any(Value), TimeGenerated, Computer, EventID)
| extend ObjectName = column_ifexists("ObjectName", ""),ObjectType = column_ifexists("ObjectType", "")
| where ObjectType == 'Key'
| where ObjectName startswith keyTransportKey and SubjectLogonId != '0x3e7' //Local System
| extend ProcessId = column_ifexists("ProcessId", ""), Process = split(ProcessName, '\\', -1)[-1],Account = strcat(SubjectDomainName, "\\", SubjectUserName)
) on $left.Computer == $right.Computer and $left.SubjectLogonId == $right.SubjectLogonId and $left.ProcessId == $right.ProcessId
| project TimeGenerated, Computer, Account, SubjectDomainName, SubjectUserName, SubjectLogonId, ObjectName, tostring(Process), ProcessName, ProcessId, EventID
),
// Accessing Object
(
SecurityEvent
| where EventID == '4663'
| where ObjectType == 'Key'
| where (ObjectName startswith aadJoinRoot or ObjectName contains aadRegisteredRoot) and SubjectLogonId != '0x3e7' //Local System
| extend Account = SubjectAccount
| join kind=innerunique (
SecurityEvent
| where EventID == '4663'
| where ObjectType == 'Key'
| where ObjectName has keyTransportKey and SubjectLogonId != '0x3e7' //Local System
| extend Account = SubjectAccount
) on $left.Computer == $right.Computer and $left.SubjectLogonId == $right.SubjectLogonId and $left.ProcessId == $right.ProcessId
| project TimeGenerated, Computer, Account, SubjectDomainName, SubjectUserName, SubjectLogonId, ObjectName, Process, ProcessName, ProcessId, EventID
| extend HostName = tostring(split(Computer, '.', 0)[0]), DnsDomain = tostring(strcat_array(array_slice(split(Computer, '.'), 1, -1), '.'))
)
)
OriginalUri: https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/Windows Security Events/Analytic Rules/LocalDeviceJoinInfoAndTransportKeyRegKeysAccess.yaml
queryPeriod: 1d
status: Available
version: 1.0.2
tags:
- SimuLand
- ATR
- AADInternals
name: AAD Local Device Join Information and Transport Key Registry Keys Access
requiredDataConnectors:
- dataTypes:
- SecurityEvent
connectorId: SecurityEvents
- dataTypes:
- SecurityEvent
connectorId: WindowsSecurityEvents
triggerOperator: gt
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: SubjectUserName
- identifier: NTDomain
columnName: SubjectDomainName
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: HostName
- identifier: DnsDomain
columnName: DnsDomain
id: a356c8bd-c81d-428b-aa36-83be706be034
description: |
'This detection uses Windows security events to detect suspicious access attempts by the same process
to registry keys that provide information about an AAD joined or registered devices and Transport keys (tkpub / tkpriv).
This information can be used to export the Device Certificate (dkpub / dkpriv) and Transport key (tkpub/tkpriv).
These set of keys can be used to impersonate existing Azure AD joined devices.
This detection requires an access control entry (ACE) on the system access control list (SACL) of the following securable objects:
HKLM:\SYSTEM\CurrentControlSet\Control\CloudDomainJoin (AAD joined devices)
HKCU:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\WorkplaceJoin (AAD registered devices)
HKLM:\SYSTEM\CurrentControlSet\Control\Cryptography\Ngc\KeyTransportKey (Transport Key)
Make sure you set the SACL to propagate to its sub-keys. You can find more information in here https://github.com/OTRF/Set-AuditRule/blob/master/rules/registry/aad_connect_health_service_agent.yml
Reference: https://o365blog.com/post/deviceidentity/'
triggerThreshold: 0
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"workspace": {
"type": "String"
}
},
"resources": [
{
"id": "[concat(resourceId('Microsoft.OperationalInsights/workspaces/providers', parameters('workspace'), 'Microsoft.SecurityInsights'),'/alertRules/a356c8bd-c81d-428b-aa36-83be706be034')]",
"name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/a356c8bd-c81d-428b-aa36-83be706be034')]",
"type": "Microsoft.OperationalInsights/workspaces/providers/alertRules",
"kind": "Scheduled",
"apiVersion": "2022-11-01-preview",
"properties": {
"displayName": "AAD Local Device Join Information and Transport Key Registry Keys Access",
"description": "'This detection uses Windows security events to detect suspicious access attempts by the same process\n to registry keys that provide information about an AAD joined or registered devices and Transport keys (tkpub / tkpriv).\n This information can be used to export the Device Certificate (dkpub / dkpriv) and Transport key (tkpub/tkpriv).\n These set of keys can be used to impersonate existing Azure AD joined devices.\n This detection requires an access control entry (ACE) on the system access control list (SACL) of the following securable objects:\n HKLM:\\SYSTEM\\CurrentControlSet\\Control\\CloudDomainJoin (AAD joined devices)\n HKCU:\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\WorkplaceJoin (AAD registered devices)\n HKLM:\\SYSTEM\\CurrentControlSet\\Control\\Cryptography\\Ngc\\KeyTransportKey (Transport Key)\n Make sure you set the SACL to propagate to its sub-keys. You can find more information in here https://github.com/OTRF/Set-AuditRule/blob/master/rules/registry/aad_connect_health_service_agent.yml\n Reference: https://o365blog.com/post/deviceidentity/'\n",
"severity": "Medium",
"enabled": true,
"query": "// AADJoined or Register Device Registry Keys\nlet aadJoinRoot = \"\\\\REGISTRY\\\\MACHINE\\\\SYSTEM\\\\ControlSet001\\\\Control\\\\CloudDomainJoin\\\\JoinInfo\\\\\";\nlet aadRegisteredRoot = \"\\\\SOFTWARE\\\\Microsoft\\\\Windows NT\\\\CurrentVersion\\\\WorkplaceJoin\";\n// Transport Key Registry Key\nlet keyTransportKey = \"\\\\REGISTRY\\\\MACHINE\\\\SYSTEM\\\\ControlSet001\\\\Control\\\\Cryptography\\\\Ngc\\\\KeyTransportKey\\\\\";\n(union isfuzzy=true\n(\n// Access to Object Requested\nSecurityEvent\n| where EventID == '4656'\n| where EventData has aadJoinRoot or EventData has aadRegisteredRoot\n| extend EventData = parse_xml(EventData).EventData.Data\n| mv-expand bagexpansion=array EventData\n| evaluate bag_unpack(EventData)\n| extend Key = tostring(column_ifexists('@Name', \"\")), Value = column_ifexists('#text', \"\")\n| evaluate pivot(Key, any(Value), TimeGenerated, Computer, EventID)\n| where ObjectType == 'Key'\n| where ObjectName startswith aadJoinRoot and SubjectLogonId != '0x3e7' //Local System\n| extend ProcessId = column_ifexists(\"ProcessId\", \"\"), Process = split(ProcessName, '\\\\', -1)[-1],Account = strcat(SubjectDomainName, \"\\\\\", SubjectUserName)\n| join kind=innerunique (\n SecurityEvent\n | where EventID == '4656'\n | where EventData has keyTransportKey\n | extend EventData = parse_xml(EventData).EventData.Data\n | mv-expand bagexpansion=array EventData\n | evaluate bag_unpack(EventData)\n | extend Key = tostring(column_ifexists('@Name', \"\")), Value = column_ifexists('#text', \"\")\n | evaluate pivot(Key, any(Value), TimeGenerated, Computer, EventID)\n | extend ObjectName = column_ifexists(\"ObjectName\", \"\"),ObjectType = column_ifexists(\"ObjectType\", \"\")\n | where ObjectType == 'Key'\n | where ObjectName startswith keyTransportKey and SubjectLogonId != '0x3e7' //Local System\n | extend ProcessId = column_ifexists(\"ProcessId\", \"\"), Process = split(ProcessName, '\\\\', -1)[-1],Account = strcat(SubjectDomainName, \"\\\\\", SubjectUserName)\n) on $left.Computer == $right.Computer and $left.SubjectLogonId == $right.SubjectLogonId and $left.ProcessId == $right.ProcessId\n| project TimeGenerated, Computer, Account, SubjectDomainName, SubjectUserName, SubjectLogonId, ObjectName, tostring(Process), ProcessName, ProcessId, EventID\n),\n// Accessing Object\n(\nSecurityEvent\n| where EventID == '4663'\n| where ObjectType == 'Key'\n| where (ObjectName startswith aadJoinRoot or ObjectName contains aadRegisteredRoot) and SubjectLogonId != '0x3e7' //Local System\n| extend Account = SubjectAccount\n| join kind=innerunique (\n SecurityEvent\n | where EventID == '4663'\n | where ObjectType == 'Key'\n | where ObjectName has keyTransportKey and SubjectLogonId != '0x3e7' //Local System\n | extend Account = SubjectAccount\n) on $left.Computer == $right.Computer and $left.SubjectLogonId == $right.SubjectLogonId and $left.ProcessId == $right.ProcessId\n| project TimeGenerated, Computer, Account, SubjectDomainName, SubjectUserName, SubjectLogonId, ObjectName, Process, ProcessName, ProcessId, EventID\n| extend HostName = tostring(split(Computer, '.', 0)[0]), DnsDomain = tostring(strcat_array(array_slice(split(Computer, '.'), 1, -1), '.'))\n)\n)\n",
"queryFrequency": "P1D",
"queryPeriod": "P1D",
"triggerOperator": "GreaterThan",
"triggerThreshold": 0,
"suppressionDuration": "PT1H",
"suppressionEnabled": false,
"tactics": [
"Discovery"
],
"techniques": [
"T1012"
],
"alertRuleTemplateName": "a356c8bd-c81d-428b-aa36-83be706be034",
"customDetails": null,
"entityMappings": [
{
"fieldMappings": [
{
"columnName": "SubjectUserName",
"identifier": "Name"
},
{
"columnName": "SubjectDomainName",
"identifier": "NTDomain"
}
],
"entityType": "Account"
},
{
"fieldMappings": [
{
"columnName": "HostName",
"identifier": "HostName"
},
{
"columnName": "DnsDomain",
"identifier": "DnsDomain"
}
],
"entityType": "Host"
}
],
"tags": [
"SimuLand",
"ATR",
"AADInternals"
],
"status": "Available",
"templateVersion": "1.0.2",
"OriginalUri": "https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/Windows Security Events/Analytic Rules/LocalDeviceJoinInfoAndTransportKeyRegKeysAccess.yaml"
}
}
]
}