Tag Archive: ExposureGraph

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!