Microsoft Defender for Cloud's Cloud Security Explorer provides security teams with an intuitive visual interface to investigate their cloud security posture. It excels at helping users explore relationships between resources, identities, permissions, and vulnerabilities while surfacing potential misconfigurations and risky assets that could be vulnerable to attacks and breaches.
But what happens when you need to go deeper than what the UI can offer? What if you require more sophisticated analysis with interconnected insights for comprehensive research results, or you want complete control over filtering conditions and query logic? Perhaps you need to build a custom library of reusable security queries, or you want to create predefined research queries for triaging security alerts and incidents, either as automated responses or manual investigations during event handling.
The answer lies in leveraging the Exposure Graph directly through Microsoft's XDR portal using Advanced Hunting and Kusto Query Language (KQL). This approach transforms the graph from a visualization tool into a programmable security engine that adapts to your environment, threats, and workflows.
Understanding the Foundation: Exposure Graph Tables
The Enterprise Exposure Graph, is a central tool for exploring and managing attack surface. It exposes its full power through two fundamental data tables accessible via Advanced Hunting.
The ExposureGraphNodes table represents entities in your environment, containing virtual machines, cloud resources, user identities, service principals, databases, storage accounts, vulnerabilities, and more. Each node contains a unique NodeId for identification, a NodeLabel indicating the entity type such as "VirtualMachine", "User", or "Database", and NodeProperties containing rich JSON metadata including region information, tags, risk levels, and exposure details.
The ExposureGraphEdges table captures the relationships between these entities, defining how they connect and interact. These relationships include access permissions where one entity "has permissions to" another, network connections showing how entities "connect to" each other, and security relationships indicating when something "is vulnerable to" or "is exposed via" another entity. Each edge includes SourceNodeId and TargetNodeId to identify the connected entities, an EdgeLabel describing the relationship type, and EdgeProperties containing additional context such as role assignments, port numbers, and protocol details.
Together, these tables form more than just a data model, they create a security reasoning engine. By querying this structure, you can reconstruct attack paths, identify privilege escalation opportunities, map exposure from internet-facing assets to critical data stores, and prioritize remediation based on contextual risk rather than isolated vulnerability scores.
Using KQL instead of the visual query builder
While the Cloud Security Explorer UI excels at quick investigations and guided exploration, it becomes limiting when your investigation requires custom logic, repeatability, or integration with broader security workflows. KQL transforms your approach by enabling the creation of custom query libraries where you can build, save, and maintain reusable queries that can be versioned, documented, and shared across your security team. This eliminates the need to start investigations from scratch and ensures consistent methodologies across different team members.
The advanced query logic capabilities of KQL far exceed what's possible through the UI. You can perform multi-table joins to correlate graph data with alerts, asset inventories, and threat intelligence from other Microsoft security tools. Multi-hop traversal allows you to simulate complete attack paths across your environment, following the breadcrumbs an attacker might leave as they move laterally through your infrastructure. Dynamic field parsing lets you extract and filter complex nested JSON properties, giving you granular control over your analysis criteria.
Perhaps most importantly, KQL enables automation and integration that transforms one-time investigations into operational workflows. You can embed your queries into custom detection rules, create workbooks and automated playbooks, and schedule continuous monitoring for specific security patterns. This shift from reactive investigation to proactive defense represents a fundamental change in how you approach security operations.
Unlike the abstracted view provided by the UI, KQL gives you complete schema access to all node types, edge relationships, and properties, including those not visible in the interface. This comprehensive access ensures that your analysis can leverage every piece of available context and relationship data.
Real-World Scenario
Consider the challenge of identifying high-privilege identities across your organization. While the UI might show you individual role assignments, a KQL query can systematically examine all identities with elevated permissions like Owner or Contributor roles, correlating this information with departmental data to help you assess privilege escalation risks across business units. The query joins the edges table where relationships indicate permission assignments with the nodes table to extract organizational context, providing a comprehensive view that would require multiple UI interactions to achieve.
Attack path analysis becomes particularly powerful when you can trace the complete journey a threat actor might take through your environment. Starting with potentially compromised user identities, you can construct multi-hop queries that follow authentication relationships to intermediate systems, then network connections to critical databases. This type of analysis simulates real attack scenarios and helps you understand not just individual vulnerabilities, but the pathways that connect them into exploitable chains.
The identification of internet-exposed vulnerable assets demonstrates how KQL can combine multiple relationship types to surface your most critical security gaps. By correlating assets that are exposed to the internet with those that have known vulnerabilities, you create a prioritized list for patching and network segmentation efforts. This contextual approach to vulnerability management moves beyond simple severity scores to focus on actual exploitability and exposure.
When investigating potential security incidents, blast radius analysis becomes crucial for understanding the scope of potential impact. KQL enables you to map all entities connected to a critical asset, whether through direct permissions, network paths, or data flows. This comprehensive mapping supports both impact analysis during active incidents and proactive planning for incident response procedures.
Crafting Effective Graph Queries
Writing efficient and maintainable graph queries requires a thoughtful approach to handling the dynamic nature of the graph data. Since both NodeProperties and EdgeProperties are stored as JSON objects, parsing these fields early in your queries improves both readability and performance. Extracting specific attributes like region, criticality, or exposure level at the beginning of your query makes subsequent filtering and joining operations more straightforward.
Many properties within the graph contain multiple values, such as role assignments or IP address ranges. The mv-expand operator becomes essential for flattening these arrays so you can filter or aggregate on individual values. This is particularly useful when analyzing permissions where a single identity might have multiple roles across different resources.
Performance optimization requires careful consideration of when and how you apply filters and joins. Applying restrictive filters early in your query reduces the amount of data processed in subsequent operations. Using the project operator to limit columns before performing joins reduces memory usage and improves execution speed. The order of operations matters significantly when working with large graph datasets.
KQL's specialized graph operators provide powerful capabilities for complex relationship analysis. The make-graph operator builds graph structures directly from your tabular data, while graph-match enables pattern matching across the relationships. These operators are particularly useful for visualizing attack paths or validating the structure of your security graph.
Building and maintaining a query library requires documentation and organization. Adding comments to explain your logic and assumptions makes queries maintainable and shareable. Organizing queries by use case or threat type helps team members find and adapt existing work rather than creating duplicate efforts.
Integration Across the Microsoft Security Ecosystem
The Exposure Graph serves as a unified foundation across multiple Microsoft security products, creating opportunities for correlation and enrichment that extend far beyond individual tool capabilities. Microsoft Defender for Cloud uses this same graph data to power its attack path analysis and cloud security posture insights, while Microsoft Security Exposure Management leverages it for comprehensive risk prioritization. This shared foundation means that insights developed through KQL queries directly complement and enhance the experiences in these other tools.
The real power emerges when you correlate graph-based insights with real-time security events from across the Microsoft XDR ecosystem. You can enrich attack path analysis with live alert data, connecting theoretical vulnerabilities with actual threat activity. This correlation helps distinguish between academic security gaps and actively exploited weaknesses, enabling more targeted and effective response efforts.
Cross-product correlation becomes particularly valuable during incident response. When an alert fires indicating suspicious activity on a particular identity or resource, you can immediately query the graph to understand the potential blast radius, identify related assets that might be at risk, and trace possible attack paths the threat actor might pursue. This context transforms isolated alerts into comprehensive threat intelligence.
The integration capabilities extend to automated workflows where graph insights can trigger protective actions or investigative procedures. When your queries identify new high-risk attack paths or exposure scenarios, these findings can automatically generate tickets, send notifications, or even trigger remediation workflows in other security tools.
Operationalizing Graph Intelligence
Moving from ad-hoc investigations to operational security intelligence requires systematic approaches to query development, execution, and action. Building a comprehensive query library involves more than just saving individual queries—it requires organizing them by threat scenarios, business contexts, and operational procedures. Each query should be documented with its purpose, assumptions, and expected outcomes, making it easier for team members to understand when and how to use different analytical approaches.
Automation transforms your graph insights from periodic investigations into continuous monitoring capabilities. Scheduling queries to run regularly allows you to detect emerging risks before they become active threats. These automated executions can feed into dashboards, generate regular reports, or trigger alerts when specific patterns are detected.
The collaborative aspect of query development multiplies the value of your efforts. When team members share and refine queries, the collective intelligence of the group improves everyone's analytical capabilities. This collaboration also helps ensure that queries remain current as your environment evolves and new threat patterns emerge.
Measuring the impact of your graph-based analysis helps justify the investment in these advanced techniques and identifies areas for further development. Tracking metrics such as the number of security gaps identified, attack paths remediated, or incidents prevented provides concrete evidence of value while highlighting opportunities for additional automation or analysis.
From Reactive to Proactive Security
The Exposure Graph represents a fundamental shift in how security teams can approach threat detection and response. Rather than waiting for alerts to indicate that something has gone wrong, you can proactively identify and remediate the conditions that enable successful attacks. This shift from reactive investigation to proactive defense requires new skills and approaches, but the payoff comes in the form of more effective security operations and reduced risk exposure.
The comprehensive visibility provided by graph analysis enables security teams to think like attackers while defending like architects. By understanding how your infrastructure looks from an adversary's perspective, you can make informed decisions about where to invest in additional controls, which assets require enhanced monitoring, and how to structure your defenses for maximum effectiveness.
As threat landscapes continue to evolve and cloud environments become more complex, the ability to understand and analyze the relationships between security elements becomes increasingly critical. The Exposure Graph provides the foundation for this understanding, while KQL provides the tools to extract actionable intelligence from this rich dataset.
Practical Use Cases with KQL
Now that we understand the structure, let’s explore how to use KQL to extract meaningful insights. These examples demonstrate how to go beyond the Cloud Security Explorer by writing custom, flexible queries that can be saved, shared, and extended.
Use Case 1: Identify High-Privilege Identities Across Subscriptions
This query finds identities with elevated roles like Owner or Contributor, helping you assess potential privilege escalation risks.
ExposureGraphEdges
| where EdgeLabel == "has permissions to"
| extend Roles = parse_json(EdgeProperties).rawData.permissions.roles
| mv-expand Roles
| where Roles.name in ("Owner", "Contributor")
| join kind=inner (
ExposureGraphNodes
| project NodeId, Department = tostring(NodeProperties.department)
) on $left.SourceNodeId == $right.NodeId
Why is this important? This helps prioritize identity-related risks across departments or business units.
Use Case 2: Trace Lateral Movement
This multi-hop query simulates an attacker moving from one compromised resource to another
// Step 1: Identify High-Risk Azure VMs with High-Severity Vulnerabilities
let HighRiskVMs =
ExposureGraphNodes
| where NodeLabel == "microsoft.compute/virtualmachines"
| extend NodeProps = parse_json(NodeProperties)
| extend RawData = parse_json(tostring(NodeProps.rawData)) // Parse rawData as JSON
| extend VulnerabilitiesData = parse_json(tostring(RawData.hasHighSeverityVulnerabilities)) // Extract nested JSON
| where toint(VulnerabilitiesData.data['count']) > 0 // Filter VMs with count > 0
| project VMId = NodeId, VMName = NodeName, VulnerabilityCount = VulnerabilitiesData.data['count'], NodeProperties;
// Step 2: Identify Critical Storage Accounts with Sensitive Data
let CriticalStorageAccounts =
ExposureGraphNodes
| where NodeLabel == "microsoft.storage/storageaccounts"
| extend NodeProps = parse_json(NodeProperties)
| extend RawData = parse_json(tostring(NodeProps.rawData)) // Parse rawData as JSON
| where RawData.containsSensitiveData == "true" // Check for sensitive data
| project StorageAccountId = NodeId, StorageAccountName = NodeName;
// Step 3: Find Lateral Movement Paths from High-Risk VMs to Critical Storage Accounts
let LateralMovementPaths =
ExposureGraphEdges
| where EdgeLabel in ("has role on", "has permissions to", "can authenticate to") // Paths that allow access
| project SourceNodeId, SourceNodeName, SourceNodeLabel, TargetNodeId, TargetNodeName, EdgeLabel;
// Step 4: Correlate High-Risk VMs with Storage Accounts They Can Access
HighRiskVMs
| join kind=inner LateralMovementPaths on $left.VMId == $right.SourceNodeId
| join kind=inner CriticalStorageAccounts on $left.TargetNodeId == $right.StorageAccountId
| project VMName, StorageAccountName = TargetNodeName, EdgeLabel, VulnerabilityCount
| order by VMName asc
Why is this important? This helps visualize potential attack paths and prioritize defenses around critical assets.
Use Case 3: Find Internet-Facing VMs with Known Vulnerabilities
This query identifies virtual machines that are both internet-exposed and linked to known CVEs.
ExposureGraphNodes
| extend rawData = todynamic(NodeProperties).rawData
| where isnotnull(rawData.exposedToInternet)
| where rawData.highRiskVulnerabilityInsights.hasHighOrCritical == true
| project VM_Name = NodeName
Why is this important? This helps prioritize patching and segmentation for high-risk assets.
Use Case 4: Assessing Privileged Access Risks in Cloud Environment
This query help assessing the potential impact of a breach of a Virtual Machine with privileges to access Azure Key Vaults.
let ResourceRiskWeights = datatable(TargetNodeLabel:string, RiskWeight:long)
[
"microsoft.keyvault/vaults", 10,
"microsoft.compute/virtualmachines", 5
];
let RoleRiskWeights = datatable(RoleName:string, RoleWeight:long)
[
"Owner", 20,
"Contributor", 15,
"User Access Administrator", 15,
"Virtual Machine Administrator Login", 8,
"Virtual Machine User Login", 5,
"Key Vault Administrator", 10
];
ExposureGraphEdges
| where EdgeLabel == "has permissions to"
| mv-expand Roles = EdgeProperties.rawData.permissions.roles
| where Roles.name != "Reader" // Exclude low-risk role
| project
SourceNodeId,
SourceNodeName,
SourceNodeLabel,
TargetNodeId,
TargetNodeName,
TargetNodeLabel,
RoleName = tostring(Roles.name)
| distinct SourceNodeId, SourceNodeName, SourceNodeLabel, TargetNodeId, TargetNodeName, TargetNodeLabel, RoleName // Remove duplicates
| join kind=inner ResourceRiskWeights on TargetNodeLabel // Use inner join to keep only matching resources
| join kind=leftouter RoleRiskWeights on RoleName
| extend
WeightedResourceRisk = iif(isnull(RiskWeight), 0, RiskWeight), // Assign resource risk
WeightedRoleRisk = iif(isnull(RoleWeight), 1, RoleWeight) // Assign role risk (default to 1 if missing)
| extend TotalWeightedPoints = WeightedResourceRisk * WeightedRoleRisk // Multiply risks
| summarize TotalRisk = sum(TotalWeightedPoints) by SourceNodeId, SourceNodeName, SourceNodeLabel, TargetNodeId, TargetNodeName, TargetNodeLabel, RoleName
| order by TotalRisk desc
Why is this important? This supports impact analysis and incident response planning.
Use Case 5: List Suggested Owners for Resources when Assigning a Remediation Action
This query helps to find the name of the possible/suggested Owner for a resource when assigning a remediation task.
// --------- 1. Pull & flatten the raw exposure data --------------------------------
let RawExposure = materialize (
ExposureGraphNodes
| where NodeProperties has 'identifiedResourceUsers' // quick filter
| mv-expand Entity = EntityIds // one row / ID
| extend ResourceId = tostring(Entity.id)
| mv-expand User = NodeProperties.rawData.identifiedResourceUsers
| extend UserObjectId = tostring(User.accountObjectId),
LastSeen = todatetime(User.lastSeen),
Score = todouble(User.score),
Confidence = tostring(User.confidence)
);
// --------- 2. (Optional) identity enrichment --------------------------------------
let Identities =
IdentityInfo // or AADSignInLogs, etc.
| project UserObjectId = tolower(AccountObjectId),
AccountDisplayName,
UPN = tolower(AccountUpn);
// Left-outer so we never drop a row if identity data is missing
let Enriched = RawExposure
| join kind=leftouter Identities on UserObjectId
| extend DisplayName = coalesce(AccountDisplayName, UserObjectId); // fallback
// --------- 3. Choose the “best” owner candidate per resource ----------------------
let OwnerPerResource =
Enriched
| summarize arg_max(Score, DisplayName, UPN, Confidence, LastSeen) by ResourceId
| project ResourceId,
LikelyOwner = DisplayName,
LikelyOwnerUPN = UPN,
OwnerScore = Score,
OwnerConfidence = Confidence,
OwnerLastSeen = LastSeen;
// --------- 4. Human-friendly final view -------------------------------------------
Enriched
| extend
SubscriptionId = extract('/subscriptions/([^/]+)', 1, ResourceId),
ResourceGroup = extract('/resourceGroups/([^/]+)', 1, ResourceId),
ResourceName = extract('([^/]+)$', 1, ResourceId)
| join kind=leftouter OwnerPerResource on ResourceId
| project
SubscriptionId,
ResourceGroup,
ResourceName,
UserDisplayName = DisplayName,
UserUPN = UPN,
UserObjectId,
Score,
Confidence,
LastSeen,
// single-row owner summary so you can filter or group later
LikelyOwner,
LikelyOwnerUPN,
OwnerScore,
OwnerConfidence,
OwnerLastSeen
| order by SubscriptionId, ResourceGroup, ResourceName, Score desc
Why is this important? This supports remediation action planning.
These examples are just the beginning. With KQL and the Exposure Graph, you can model virtually any security scenario—tailored to your environment and your priorities.
Conclusion and Next Steps
Mastering the Exposure Graph through KQL transforms Microsoft's security tools from reactive investigation platforms into proactive defense engines. This approach enables sophisticated, reusable security analysis workflows that can perform complex multi-hop reasoning to understand attack paths, integrate graph insights into automated detection and response systems, and bridge the gap between security posture assessment and real-time threat detection.
Whether you're hunting threats, responding to incidents, or architecting cloud security strategies, the Exposure Graph provides unprecedented visibility and control over your security data. The investment in learning KQL and developing graph-based analytical capabilities pays dividends in improved threat detection, more effective incident response, and enhanced overall security posture.
To begin leveraging these capabilities, start by exploring the Exposure Graph documentation and experimenting with sample queries in Microsoft XDR Advanced Hunting. Build your team's custom query library gradually, focusing on the scenarios most relevant to your environment and threat model. As your expertise develops, begin correlating graph insights with your existing security workflows and consider opportunities for automation and integration.
The graph is already capturing the security relationships within your environment—the opportunity lies in unlocking its full potential to transform how your team approaches security operations and threat defense.