Microsoft Security Exposure Management Graph: unveiling the power
Published May 24 2024 11:28 AM 5,520 Views
Microsoft

Introduction

In the complicated and rapidly evolving realm of cybersecurity, Exposure Management plays a pivotal role in fortifying organization's defenses against potential threats. To empower security teams, Microsoft Security Exposure Management has unveiled two new powerful tables within Advanced Hunting: ExposureGraphNodes and ExposureGraphEdges.

 

The introduction of these tables opens novel capabilities for security teams. It enables efficient investigation of security posture across organizational assets. This is the first in a series of posts where we will present the tables and share investigation scenarios (along with relevant queries) for Advanced Hunting. These queries unlock capabilities that were previously unattainable. We’ll provide screenshots and Kusto Query Language snippets to guide you through your reading.

 

Understanding the tables

As John Lambert's saying that is well-known in the security domain goes, 'Defenders think in lists. Attackers think in graphs. As long as this is true, attackers win.’ By exposing the context around each asset, relations between assets and the graph-based toolset for exploring them, we hope to start changing this.

 

asset map.jpg

 

Context: Beyond Asset Information

Think of assets and entities as points on a graph, and of relations between them as links between these points. Traditionally, when dealing with an asset (such as a server, device, or network component), we have focused on gathering specific information about that asset. With the powerful combination of the Attack Map and Extended Security Posture Management tables, we can gain a better perspective that zooms out and reveals the entire environment surrounding each asset.

 

The Tables:

ExposureGraphNodes

This table represents all the nodes in the Attack Surface Map. According to the table documentation, ExposureGraphNodes contain organizational entities and their properties. These entities include devices, identities, user groups, and cloud assets (such as virtual machines, storage, and containers). Each node corresponds to an individual entity and encapsulates information about its characteristics, attributes, and security-related insights within the organizational structure.

 

Before diving into specific scenarios, we recommend examining the available data first. 

Running the following query will retrieve one node for each type in the environment. This will allow you to see the kinds of nodes present and their properties.

 

 

ExposureGraphNodes
| summarize take_any(*) by NodeLabel

 

 

nodes sample.jpg

 

ExposureGraphEdges

This table represents all the edges in the Attack Surface Map. Each edge describes a pairwise relationship between two of the ExposureGraphNodes we have just reviewed. As stated in Advanced Hunting documentation: “The ExposureGraphEdges schema, along with the complementing ExposureGraphNodes schema, provides visibility into relationships between entities and assets in the graph. Many hunting scenarios require exploration of entity relationships and attack paths. For example, when hunting for devices exposed to a specific critical vulnerability, knowing the relationship between entities, can uncover critical organizational assets.”

 

Similarly, we recommend exploring your data related to edges. Running the following query will retrieve one edge for each edge type in your organization. After running the query, you’ll gain insights into the relations between your organizational entities and the additional data on them.

 

 

ExposureGraphEdges
| summarize take_any(*) by EdgeLabel

 

 

edges sample.jpg

 

Now we would like to describe several security-related scenarios that can be investigated using the security exposure graph.

 

Scenarios

Scenario 1: Nodes with specific properties

Security Exposure Graph displays various assets and entities in your organization (such as storage accounts, devices and users) as nodes in ExposureGraphNodes table, while various properties (such as criticality, sensitive data) appear as node properties.

For proper posture management, it might be interesting to find all nodes filtered by specific types and/or properties. For example, we might want to find all critical assets, or all virtual machines that are exposed to the internet and have vulnerabilities.

 

Example 1A: Critical assets

  • Query:

 

 

ExposureGraphNodes
| project NodeLabel, NodeName, NodeId, Categories
   , criticalityLevel = toint(NodeProperties.rawData.criticalityLevel.criticalityLevel)
| where criticalityLevel > 0
| sort by criticalityLevel desc

 

 

  • Output:

output 1A.jpg

 

Example 1B: Virtual Machines with specific vulnerabilities (RCE and privilege escalation)

  • Query:

 

ExposureGraphNodes
| where NodeLabel == 'microsoft.compute/virtualmachines'
| project NodeLabel, NodeName, NodeId, NodeProperties
    , vulnerableToRCE = isnotnull(NodeProperties.rawData.vulnerableToRCE.type)
    , vulnerableToPrivilegeEscalation = isnotnull(NodeProperties.rawData.highRiskVulnerabilityInsights.vulnerableToPrivilegeEscalation)
| where vulnerableToRCE > 0 or vulnerableToPrivilegeEscalation > 0

 

 

  • Output:

output 1B.jpg

 

Since filtering the assets by specific types and properties can cover various scenarios, it might be useful to wrap such queries in a generic format, that will allow repeated usage with various parameters:

 

 

let XGraph_NodesWithTypesAndProperties = (nodeTypes:dynamic, nodeProperties:dynamic) 
{
    let propertiesFormatted = strcat('(', strcat_array(nodeProperties, '|'), ')');
    ExposureGraphNodes
    | where NodeLabel in (nodeTypes) or nodeTypes == "[\"\"]"
    | project NodeName, NodeLabel, NodeId, Categories
        , propertiesExtracted = iff(nodeProperties != "[\"\"]", extract_all(propertiesFormatted, tostring(NodeProperties)), pack_array(''))
    | mv-apply propertiesExtracted on (
        summarize propertiesExtracted = make_set_if(propertiesExtracted, isnotempty(propertiesExtracted))
    )
    | extend countProperties = coalesce(array_length(propertiesExtracted), 0)
    | where countProperties > 0 or nodeProperties == "[\"\"]"
    | sort by countProperties desc
};

 

 

Sample usage – find all vulnerable Virtual Machines:

 

 

XGraph_NodesWithTypesAndProperties(
nodeTypes=pack_array('microsoft.compute/virtualmachines')
, nodeProperties=pack_array('vulnerableToRCE', 'vulnerableToPrivilegeEscalation'))

 

 

Note that if any of the parameters is an empty array, the function will not filter on it and bring assets of all types or properties. For example, the following will bring vulnerable assets of any type that have RCE or Privilege Escalation vulnerabilities:

 

 

XGraph_NodesWithTypesAndProperties(
nodeTypes=pack_array('')
, nodeProperties=pack_array('vulnerableToRCE', 'vulnerableToPrivilegeEscalation'))

 

 

Alternatively, you can create and save several specific functions for common usage and use them without any additional parameters.

 

 

let XGraph_VulnerableVMs = () {
    let nodeTypesList      = pack_array('microsoft.compute/virtualmachines');
    let nodePropertiesList = pack_array('vulnerableToRCE', 'vulnerableToPrivilegeEscalation');
    XGraph_NodesWithTypesAndProperties(nodeTypes = nodeTypesList, nodeProperties = nodePropertiesList)
};

 

 

Usage - 

 

 

XGraph_FindVulnerableVMs()

 

 

 

Any function (with or without parameters) can be saved for repeated usage as described here: Custom functions in the advanced hunting schema - Microsoft Defender XDR | Microsoft Learn.

 

Scenario 2: Connected nodes with specific properties

The relations between assets appear as edges in ExposureGraphEdges table. For example, users that can access a virtual machine will be connected to it with ‘has permissions to’ edge.

 

We might want to look for pairs of connected assets while filtering by relevant edge types. For example, we might want to find users that have permissions to access keyvaults.

 

Example 2A: Users that have access to keyvaults

  • Query:

 

ExposureGraphEdges
| where EdgeLabel == 'has permissions to' and  SourceNodeLabel == 'user' and TargetNodeLabel == 'microsoft.keyvault/vaults'
| project SourceNodeName, SourceNodeLabel, SourceNodeId, EdgeLabel, TargetNodeName, TargetNodeLabel, TargetNodeId

 

 

  • Output:

output 2A.jpg

 

Alternatively, we might want to filter not only by endpoint types, but also by properties. For this, we need to join the edges table with node table (that contains the node properties) both on source and target. Note that the unique identifier of each asset is the NodeId (and not NodeName).

 

Example 2B: Critical users that can access storage accounts with sensitive data

  • Query:

 

ExposureGraphEdges
| where EdgeLabel == 'has permissions to' and  SourceNodeLabel == 'user' and TargetNodeLabel == 'microsoft.storage/storageaccounts'
| project SourceNodeName, SourceNodeLabel, SourceNodeId, EdgeLabel, TargetNodeName, TargetNodeLabel, TargetNodeId
| join kind = leftouter (ExposureGraphNodes | project SourceNodeId = NodeId, SourceNodeProperties = NodeProperties) on SourceNodeId
| join kind = leftouter (ExposureGraphNodes | project TargetNodeId = NodeId, TargetNodeProperties = NodeProperties) on TargetNodeId
| extend sourceCriticalityLevel = toint(SourceNodeProperties.rawData.criticalityLevel.criticalityLevel)
    , targetSensitiveData = isnotempty(TargetNodeProperties.rawData.containsSensitiveData.type)
| where sourceCriticalityLevel > 0 and targetSensitiveData > 0
| project SourceNodeName, SourceNodeLabel, SourceNodeId, EdgeLabel, TargetNodeName, TargetNodeLabel, TargetNodeId, sourceCriticalityLevel, targetSensitiveData

 

 

  • Output:

output 2B.jpg

We can add (and save) a generic function that looks for edges between nodes with specific types and properties as well.

 

 

let XGraph_EdgesWithTypesAndProperties = (sourceTypes:dynamic, sourceProperties:dynamic, targetTypes:dynamic, targetProperties:dynamic) 
{
    let sourcePropertiesFormatted = strcat('(', strcat_array(sourceProperties, '|'), ')');
    let targetPropertiesFormatted = strcat('(', strcat_array(targetProperties, '|'), ')');
    let edgeTypes = pack_array('has role on', 'has permissions to', 'can authenticate to', 'can authenticate as'
        , 'member of', 'contains');
    ExposureGraphEdges
    | where EdgeLabel in (edgeTypes)
    | where (SourceNodeLabel in (sourceTypes) or sourceTypes == "[\"\"]") and (TargetNodeLabel in (targetTypes) or targetTypes == "[\"\"]")
    | project SourceNodeName, SourceNodeLabel, SourceNodeId, EdgeLabel, TargetNodeName, TargetNodeLabel, TargetNodeId
    | join hint.strategy = shuffle kind = leftouter (ExposureGraphNodes | project SourceNodeId = NodeId, SourceNodeProperties = NodeProperties) on SourceNodeId
    | join hint.strategy = shuffle kind = leftouter (ExposureGraphNodes | project TargetNodeId = NodeId, TargetNodeProperties = NodeProperties) on TargetNodeId
    | extend sourcePropertiesExtracted = iff(sourceProperties != "[\"\"]", extract_all(sourcePropertiesFormatted, tostring(SourceNodeProperties)), pack_array(''))
        , targetPropertiesExtracted = iff(targetProperties != "[\"\"]", extract_all(targetPropertiesFormatted, tostring(TargetNodeProperties)), pack_array(''))
    | mv-apply sourcePropertiesExtracted, targetPropertiesExtracted on (
        summarize sourcePropertiesExtracted = make_set_if(sourcePropertiesExtracted, isnotempty(sourcePropertiesExtracted))
        , targetPropertiesExtracted = make_set_if(targetPropertiesExtracted, isnotempty(targetPropertiesExtracted))
    )
    | extend countSourceProperties = coalesce(array_length(sourcePropertiesExtracted), 0)
        , countTargetProperties = coalesce(array_length(targetPropertiesExtracted), 0)
    | where (countSourceProperties > 0 or sourceProperties == "[\"\"]") and (countTargetProperties > 0 or targetProperties == "[\"\"]")
    | project SourceNodeName, SourceNodeLabel, SourceNodeId, EdgeLabel, TargetNodeName, TargetNodeLabel, TargetNodeId
        , sourcePropertiesExtracted, countSourceProperties, targetPropertiesExtracted, countTargetProperties
    | sort by countSourceProperties desc, countTargetProperties desc
};

 

 

 

Sample usage – critical users that have access to containers or storage accounts that are either critical or have sensitive data:

 

 

XGraph_EdgesWithTypesAndProperties(
    sourceTypes = pack_array('user')
    , sourceProperties = pack_array('criticalityLevel')
    , targetTypes = pack_array('container', 'microsoft.storage/storageaccounts')
    , targetProperties = pack_array('containsSensitiveData', 'criticalityLevel'))

 

 

This function can also be wrapped and used as is for common scenarios. For example, we can create the following function to cover the scenario above:

 

 

let XGraph_CriticalUsersToCriticalOrSensitiveStorage = () {
    let sourceTypesList      = pack_array('user');
    let sourcePropertiesList = pack_array('criticalityLevel');
    let targetTypesList      = pack_array('container', 'microsoft.storage/storageaccounts');
    let targetPropertiesList = pack_array('containsSensitiveData', 'criticalityLevel');
    XGraph_EdgesWithTypesAndProperties(sourceTypes = sourceTypesList, sourceProperties = sourcePropertiesList
            , targetTypes = targetTypesList, targetProperties = targetPropertiesList)
};

 

 

Usage - 

 

 

FindCriticalUsersToCriticalOrSensitiveStorage()

 

 

 

Scenario 3: Paths between nodes with specific properties

Sometimes nodes can be connected in a non-direct way. For example, a virtual machine can have access to a keyvault using SSH key or managed identity. Alternatively, user can have permissions to a subscription containing storage accounts – thus gaining access to all of them.

A great way to explore such connections and find the multi-step paths is using Kusto graph capabilities – namely the make-graph and graph-match operators (you can learn more about Kusto graph semantics here). These operators allow to build paths between endpoints (source and target nodes) according to conditions on endpoints or any of the steps.

 

Example 3A: Users that have access to storage accounts with sensitive data

  • Query:

 

ExposureGraphEdges
| where EdgeLabel in ('has role on', 'has permissions to', 'can authenticate to', 'can authenticate as', 'member of', 'contains')
| make-graph SourceNodeId --> TargetNodeId with (ExposureGraphNodes | project NodeId, NodeName, NodeLabel, NodeProperties) on NodeId
// Look for existing paths between source nodes and target nodes with less than predefined number of hops
| graph-match (s)-[e*1..4]->(t)
    where (s.NodeLabel == 'user'
        and t.NodeLabel == 'microsoft.storage/storageaccounts' and isnotnull(t.NodeProperties.rawData.containsSensitiveData.type))
    project       SourceName            = s.NodeName
                , SourceType            = s.NodeLabel
                , SourceId              = s.NodeId
                , SourceExposedToInternet = s.NodeProperties.rawData.exposedToInternet.type
                , TargetName            = t.NodeName
                , TargetType            = t.NodeLabel
                , TargetId              = t.NodeId
                , TargetcontainsSensitiveData = t.NodeProperties.rawData.containsSensitiveData.type
                , edgeIds               = e.EdgeId
                , edgeLabels            = e.EdgeLabel
| extend pathLength = array_length(edgeIds) + 1

 

 

  • Output:

output 3A.jpg

 

Example 3B: SQL servers or managed instances with basic authentication that have access to keyvaults

  • Query:

 

ExposureGraphEdges
| where EdgeLabel in ('has role on', 'has permissions to', 'can authenticate to', 'can authenticate as', 'member of', 'contains')
| make-graph SourceNodeId --> TargetNodeId with (ExposureGraphNodes | project NodeId, NodeName, NodeLabel, NodeProperties) on NodeId
// Look for existing paths between source nodes and target nodes with less than predefined number of hops
| graph-match (s)-[e*1..6]->(t)
    where (s.NodeLabel in ('microsoft.sql/servers', 'microsoft.sql/managedinstances')
        and isnotnull(s.NodeProperties.rawData.allowsBasicAuth)
        and t.NodeLabel == 'microsoft.keyvault/vaults')
    project       SourceName            = s.NodeName
                , SourceType            = s.NodeLabel
                , SourceId              = s.NodeId
                , SourceExposedToInternet = s.NodeProperties.rawData.exposedToInternet.type
                , TargetName            = t.NodeName
                , TargetType            = t.NodeLabel
                , TargetId              = t.NodeId
                , TargetcontainsSensitiveData = t.NodeProperties.rawData.containsSensitiveData.type
                , edgeIds               = e.EdgeId
                , edgeLabels            = e.EdgeLabel
| extend pathLength = array_length(edgeIds) + 1

 

 

  • Output:

output 3B.jpg

 

We can wrap up this logic in a generic function XGraph_PathExploration that allows to find and explore all relevant paths between source and target nodes, filtered by relevant types and properties.

 

This is done by changing the following required parameters in array format: sourceTypes, sourceProperties, targetTypes, targetProperties.

 

The following parameters have default values and are optional: maxPathLength controls the maximum length of found paths (default value 6) and resultCountLimit controls that maximum number of output (default value 50000).

 

The function XGraph_PathExploration goes over edges defined in non-exposed edgeTypes parameter (which you can also change) and creates paths between relevant endpoint, from single hops up to length defined by maxPathLength parameter.

 

After creating the paths, the function exposes the endpoints and their properties, shows the full paths in FullPath field and adds the path length metric.

 

 

let XGraph_PathExploration = (sourceTypes:dynamic, sourceProperties:dynamic
    , targetTypes:dynamic, targetProperties:dynamic
    , maxPathLength:long = 6, resultCountLimit:long = 10000) 
{
let edgeTypes               = pack_array('has permissions to', 'contains', 'can authenticate as', 'can authenticate to', 'can remote interactive logon to'
                                , 'can interactive logon to', 'can logon over the network to', 'contains', 'has role on', 'member of');
let sourceNodePropertiesFormatted = strcat('(', strcat_array(sourceProperties, '|'), ')');
let targetNodePropertiesFormatted = strcat('(', strcat_array(targetProperties, '|'), ')');
let nodes = (
    ExposureGraphNodes
    | project NodeId, NodeName, NodeLabel
        , SourcePropertiesExtracted = iff(sourceProperties != "[\"\"]", extract_all(sourceNodePropertiesFormatted, tostring(NodeProperties)), pack_array(''))
        , TargetPropertiesExtracted = iff(targetProperties != "[\"\"]", extract_all(targetNodePropertiesFormatted, tostring(NodeProperties)), pack_array(''))
       , criticalityLevel = toint(NodeProperties.rawData.criticalityLevel.criticalityLevel)
    | mv-apply SourcePropertiesExtracted, TargetPropertiesExtracted on (
        summarize SourcePropertiesExtracted = make_set_if(SourcePropertiesExtracted, isnotempty(SourcePropertiesExtracted))
                , TargetPropertiesExtracted = make_set_if(TargetPropertiesExtracted, isnotempty(TargetPropertiesExtracted))
    )
    | extend CountSourceProperties = coalesce(array_length(SourcePropertiesExtracted), 0)
            , CountTargetProperties = coalesce(array_length(TargetPropertiesExtracted), 0)
    | extend SourceRelevancyByLabel = iff(NodeLabel in (sourceTypes) or sourceTypes == "[\"\"]", 1, 0)
            , TargetRelevancyByLabel = iff(NodeLabel in (targetTypes) or targetTypes == "[\"\"]", 1, 0)
            , SourceRelevancyByProperties = iff(CountSourceProperties > 0 or sourceProperties == "[\"\"]", 1, 0)
            , TargetRelevancyByProperties = iff(CountTargetProperties > 0 or targetProperties == "[\"\"]", 1, 0)
    | extend SourceRelevancy = iff(SourceRelevancyByLabel == 1 and SourceRelevancyByProperties == 1, 1, 0)
            , TargetRelevancy = iff(TargetRelevancyByLabel == 1 and TargetRelevancyByProperties == 1, 1, 0)
);
let edges = (
    ExposureGraphEdges
    | where EdgeLabel in (edgeTypes)
    | project EdgeId, EdgeLabel, SourceNodeId, SourceNodeName, SourceNodeLabel, TargetNodeId, TargetNodeName, TargetNodeLabel
);
let paths = (
    edges
    // Build the graph from all the nodes and edges and enrich it with node data (properties)
    | make-graph SourceNodeId --> TargetNodeId with nodes on NodeId
    // Look for existing paths between source nodes and target nodes with up to predefined number of hops
    | graph-match (s)-[e*1..maxPathLength]->(t)
        // Filter only by paths with relevant sources and targets - filtered by node types and properties
        where (s.SourceRelevancy == 1 and t.TargetRelevancy == 1)
        project   SourceName                = s.NodeName
                , SourceType                = s.NodeLabel
                , SourceId                  = s.NodeId
                , SourceProperties          = s.SourcePropertiesExtracted
                , CountSourceProperties     = s.CountSourceProperties
                , SourceRelevancy           = s.SourceRelevancy
                , TargetName                = t.NodeName
                , TargetType                = t.NodeLabel
                , TargetId                  = t.NodeId
                , TargetProperties          = t.TargetPropertiesExtracted
                , CountTargetProperties     = t.CountTargetProperties
                , TargetRelevancy           = t.TargetRelevancy
                , EdgeLabels                = e.EdgeLabel
                , EdgeIds                   = e.EdgeId
                , EdgeAllTargetIds          = e.TargetNodeId
                , EdgeAllTargetNames        = e.TargetNodeId
                , EdgeAllTargetTypes        = e.TargetNodeLabel
    | extend  PathLength                    = array_length(EdgeIds) + 1
            , PathId                        = hash_md5(strcat(SourceId, strcat(EdgeIds), TargetId))
);
let relevantPaths = (
    paths
    | extend NodesInPath = array_concat(pack_array(SourceId), EdgeAllTargetIds), NodeLabelsInPath = array_concat(pack_array(SourceType), EdgeAllTargetTypes)
    | extend NodesInPathList = NodesInPath
    // Wrap the path into meaningful format (can be tweaked as needed)
    | mv-expand with_itemindex = SortIndex EdgeIds to typeof(string), EdgeLabels to typeof(string)
        , NodesInPath to typeof(string), NodeLabelsInPath to typeof(string)
    | sort by PathId, SortIndex asc
    | extend step = strcat(
          iff(isnotempty(NodesInPath), strcat('(', NodeLabelsInPath, ':', NodesInPath, ')'), '')
        , iff(isnotempty(SourceProperties) and NodesInPath == SourceId, SourceProperties, '')
        , iff(isnotempty(TargetProperties) and NodesInPath == TargetId, TargetProperties, '')
        , iff(isnotempty(EdgeLabels), strcat('-', EdgeLabels, '->'), ''))
    | summarize StepSequence = make_list(step), take_any(*) by PathId
    // Project relevant fields
    | project SourceName, SourceType, SourceId, SourceProperties, CountSourceProperties, SourceRelevancy
            , TargetName, TargetType, TargetId, TargetProperties, CountTargetProperties, TargetRelevancy
            , PathId, PathLength, Path = StepSequence
    | top resultCountLimit by PathLength asc
);
relevantPaths
};

 

 

After defining this function, we can use it by providing the lists of relevant source types, source properties, target types and target properties as well as giving other values to optional parameters. If any of the required parameters is an empty array, no filtering will be applies.

 

For example, we can look for all paths between different compute resources that have various vulnerabilities or are exposed to the internet, to various storage assets that are either critical or contain sensitive data:

 

 

let sourceTypesList         = pack_array('microsoft.compute/virtualmachines', 'compute.instances', 'ec2.instance');
let sourcePropertiesList    = pack_array('vulnerableToPrivilegeEscalation', 'vulnerableToRCE', 'hasHighSeverityVulnerabilities', 'exposedToInternet');
let targetTypesList         = pack_array('microsoft.sql/servers', 's3.bucket', 'rds.db', 'storage.buckets', 'microsoft.storage/storageaccounts', 'rds.snapshot', 'microsoft.documentdb/databaseaccounts');
let targetPropertiesList    = pack_array('criticalityLevel', 'containsSensitiveData');
XGraph_PathExploration(sourceTypes=sourceTypesList, sourceProperties=sourcePropertiesList
                , targetTypes=targetTypesList, targetProperties=targetPropertiesList)

 

 

Output:

path wrapper output.jpg

 

Note that the FullPath field contains the full description of the path, with node and edge types and properties, for example:

 

(microsoft.compute/virtualmachines:ffcbc)[exposedToInternet]-can authenticate as->(managedidentity:23e7)-has role on->(microsoft.sql/servers:3d5c)[criticalityLevel]

 

This shows how the endpoints are connected, and can be used to find the proper disruption method (e.g., removing Managed Identity connecting exposed VM and critical SQL server).

Alternatively, we can look for all assets that allow public access or exposed to Internet (without specifying source type) to all keyvaults (without specifying target properties):

 

 

let sourceTypesList         = pack_array('');
let sourcePropertiesList    = pack_array('allowsPublicAccess', 'exposedToInternet');
let targetTypesList         = pack_array('microsoft.keyvault/vaults');
let targetPropertiesList    = pack_array('');
XGraph_PathExploration(sourceTypes=sourceTypesList, sourceProperties=sourcePropertiesList
                , targetTypes=targetTypesList, targetProperties=targetPropertiesList)

 

 

You can also wrap the XGraph_PathExploration function in a specific function with predefined parameters and use it directly for commonly used scenarios. For example, the first scenario in this section can be covered by the following function:

 

 

let XGraph_VulnerableOrExposedVMsToCriticalOrSensitiveStorage = ()
{
    let sourceTypesList         = pack_array('microsoft.compute/virtualmachines', 'compute.instances', 'ec2.instance');
    let sourcePropertiesList    = pack_array('vulnerableToPrivilegeEscalation', 'vulnerableToRCE', 'hasHighSeverityVulnerabilities', 'exposedToInternet');
    let targetTypesList         = pack_array('microsoft.sql/servers', 's3.bucket', 'rds.db', 'storage.buckets', 'microsoft.storage/storageaccounts', 'rds.snapshot', 'microsoft.documentdb/databaseaccounts');
    let targetPropertiesList    = pack_array('criticalityLevel', 'containsSensitiveData');
    XGraph_PathExploration(sourceTypes=sourceTypesList, sourceProperties=sourcePropertiesList
                , targetTypes=targetTypesList, targetProperties=targetPropertiesList)
};

 

 

Usage - 

 

 

vulnerableOrExposedVMsToCriticalOrSensitiveStorage()

 

 

 

Mastering Security Posture with Microsoft’s Advanced Exposure Management Tables

In this post, we delve into the core components of Microsoft Security Exposure Management - the tables ExposureGraphNodes and ExposureGraphEdges and the graph toolset for exploring them. We explain the schemas and illustrate how these tables improve the investigation of security posture by several real-world scenarios. We also present several generic queries that can be adapted to your usage by specifying the parameters.

 

This is more than just an introduction; it’s an invitation to master the fundamental elements of these tables. We hope this will be the first step in your ‘thinking in graphs’ transformation in the security domain.

 

If you are having trouble accessing Advanced Hunting, please start with this guide.

 

Note: For full Security Exposure Management access, user roles need access to all Defender for Endpoint device groups. Users who have access restricted to specific device groups can access the Security Exposure Management attack surface map and advanced hunting schemas (ExposureGraphNodes and ExposureGraphEdges) for the device groups to which they have access.

 

We hope you will start exploring your Security Exposure Management Graph and integrating it into your security practice. Stay tuned for more content, as in our upcoming posts will delve even deeper, uncovering more fascinating insights and applications.

Version history
Last update:
‎May 24 2024 11:28 AM
Updated by: