Using Exposure Graph Data to get Domain Admins and Domain Controllers dynamically
When working with both Threat Hunting and Detection Engineering it has been a challenge to dynamically get domain administrators and domain controllers. Sometimes, a valid guess was the best approach.
With the Exposure Graph data, we can dynamically retrieve these entities.

Another thing which will simplify your queries is to save this as a function


Example code for Domain Admins
//Get Domain Admin accounts (member of "Domain Admins")
ExposureGraphNodes
| extend json = (parse_json(NodeProperties)).rawData
| where json.nestedAdGroupNames has "Domain Admins"
| where NodeLabel == "user"
| mv-apply EntityIds on (
summarize Identifiers = make_bag(
pack(
tostring(EntityIds.type),
EntityIds.id
)
)
)
| project accountName = json.accountName,
Identifiers,
accountEnabled=json.accountEnabled,
distinguishedName=json.distinguishedName,
adminCount=json.adminCount,
AccountControl=json.userAccountControl,
passwordUpdateTime=json.passwordUpdateTime,
createdDateTime = json.createdDateTime,
sidHistory=json.sidHistory,
accountDomain=json.accountDomain,
nestedAdGroupNames = json.nestedAdGroupNames
Example code of usage
//One usage of DAs
let DAs =
ExposureGraphNodes
| extend json = (parse_json(NodeProperties)).rawData
| where json.nestedAdGroupNames has "Domain Admins"
| where NodeLabel == "user"
| distinct tostring(json.accountName);
IdentityLogonEvents
| where AccountName has_any (DAs)
//Another example
let DAs =
ExposureGraphNodes
| extend json = (parse_json(NodeProperties)).rawData
| where json.nestedAdGroupNames has "Domain Admins"
| where NodeLabel == "user"
| distinct tostring(json.accountName);
DeviceLogonEvents
| where AccountName has_any (DAs)
| summarize DistinctDevices = dcount(DeviceName),
NetworkSuccessLogons = countif(ActionType == "LogonSuccess" and LogonType == "Network"),
RemoteInteractiveSuccessLogons = countif(ActionType=="LogonSuccess" and LogonType =="RemoteInteractive"),
TotalRemoteDeviceNames = dcount(RemoteDeviceName),
RemoteIPs = dcount(RemoteIP),
FailedLogins =countif(ActionType == "LogonFailed"),
AttemptedLogins = countif(ActionType=="LogonAttempted"),
ProtocolsUsed = dcount(Protocol)
by AccountName
Domain controllers
ExposureGraphNodes
| extend _jsonBlob = (parse_json(NodeProperties)).rawData
| extend DeviceName = tolower((_jsonBlob.deviceName)),
Roles = _jsonBlob.deviceRole,
OSVersionBuild = strcat(tostring(_jsonBlob.osVersionFriendlyName),"-",tostring(_jsonBlob.osReleaseId),"-",tostring(_jsonBlob.osBuild)),
OSVersion = tostring(_jsonBlob.osVersionFriendlyName),
ExposureScore = tostring(_jsonBlob.exposureScore),
SenseClientVersion = tostring(_jsonBlob.senseClientVersion),
isHybridAzureADJoined = _jsonBlob.isHybridAzureADJoined,
isAzureADJoined = _jsonBlob.isAzureADJoined,
SensorHealthState = tostring(_jsonBlob.sensorHealthState),
DeviceRiskScore = tostring(_jsonBlob.riskScore),
isInternetFacing = _jsonBlob.isInternetFacing,
RDPStatus = _jsonBlob.rdpStatus,
RemoteServices = _jsonBlob.remoteServicesInfo,
TPMInfo = _jsonBlob.tpmData,
VulnHasHighOrCritical = _jsonBlob.highRiskVulnerabilityInsights.hasHighOrCritical,
VulnMaxCvssScore = _jsonBlob.highRiskVulnerabilityInsights.maxCvssScore
| extend RDPServiceStatus = RDPStatus.serviceRunning,
RDPAllowConnections = RDPStatus.allowConnections
| where Roles has "DomainController"
| mv-apply EntityIds on (
summarize Identifiers = make_bag(
pack(
tostring(EntityIds.type),
EntityIds.id
)
)
)
| project-away NodeId,NodeLabel,NodeName,NodeProperties,_jsonBlob, Categories, Roles,RDPStatus
//Another example
let DCs = ExposureGraphNodes
| extend _jsonBlob = (parse_json(NodeProperties)).rawData
| extend DeviceName = tolower((_jsonBlob.deviceName)),
Roles = _jsonBlob.deviceRole,
OSVersionBuild = strcat(tostring(_jsonBlob.osVersionFriendlyName),"-",tostring(_jsonBlob.osReleaseId),"-",tostring(_jsonBlob.osBuild)),
OSVersion = tostring(_jsonBlob.osVersionFriendlyName),
ExposureScore = tostring(_jsonBlob.exposureScore),
SenseClientVersion = tostring(_jsonBlob.senseClientVersion),
isHybridAzureADJoined = _jsonBlob.isHybridAzureADJoined,
isAzureADJoined = _jsonBlob.isAzureADJoined,
SensorHealthState = tostring(_jsonBlob.sensorHealthState),
DeviceRiskScore = tostring(_jsonBlob.riskScore),
isInternetFacing = _jsonBlob.isInternetFacing,
RDPStatus = _jsonBlob.rdpStatus,
RemoteServices = _jsonBlob.remoteServicesInfo,
TPMInfo = _jsonBlob.tpmData,
VulnHasHighOrCritical = _jsonBlob.highRiskVulnerabilityInsights.hasHighOrCritical,
VulnMaxCvssScore = _jsonBlob.highRiskVulnerabilityInsights.maxCvssScore
| extend RDPServiceStatus = RDPStatus.serviceRunning,
RDPAllowConnections = RDPStatus.allowConnections
| where Roles has "DomainController"
| mv-apply EntityIds on (
summarize Identifiers = make_bag(
pack(
tostring(EntityIds.type),
EntityIds.id
)
)
)
| project-away NodeId,NodeLabel,NodeName,NodeProperties,_jsonBlob, Categories, Roles,RDPStatus
| distinct _DeviceName;
DeviceProcessEvents
| where DeviceName in(DCs)
Happy Hunting!