Query Details

Hunt Privilege Escalation Paths With High AC Ls

Query

# *Hunt for privilege escalation paths with high ACLs*

## Query Information

#### MITRE ATT&CK Technique(s)

| Technique ID | Title    | Link    |
| ---  | --- | --- |
| T1078 | Valid Accounts | https://attack.mitre.org/techniques/T1078/ |

#### Description
When an adversary establishes data collection of an Active Directory domain, they regularly search for interesting accounts with privilege escalation paths using the genericWrite and genericAll ACL permissions on objects. When using BloodHound, it is very easy to get a visual overview of these paths in an Active Directory domain. This query tries to establish the same using Defender XDR Exposure Management. 

#### Risk
By knowing these paths you can effectivly remediate and lower the risk of privilege escalation paths in AD DS. 

#### Author <Optional>
- **Name:** Robbe Van den Daele
- **Github:** https://github.com/RobbeVandenDaele
- **Twitter:** https://x.com/RobbeVdDaele
- **LinkedIn:** https://www.linkedin.com/in/robbe-van-den-daele-677986190/
- **Website:** https://hybridbrothers.com/

#### References
- https://www.hackingarticles.in/genericwrite-active-directory-abuse/
- https://bloodhound.specterops.io/resources/edges/generic-write
- https://bloodhound.specterops.io/resources/edges/generic-all

## Defender XDR
```KQL
let high_permissions = dynamic(["genericWrite", "genericAll"]);
let edge_labels = dynamic(["member of", "has permissions to", "can authenticate to", "can authenticate as", "has credentials of", "can impersonate as"]);
// Get users and groups with high ACL permissions on other objects
let HighPermissionLinks = (ExposureGraphEdges
    // Get edges related to roles
    | where EdgeLabel == "has role on"
    // Get edges containing high permission ACLs
    | extend Permissions = todynamic(EdgeProperties).rawData.acl.controlTypes
    | where Permissions has_any (high_permissions)
    // Exclude Domain and Enterprise Administrators as source node
    | where not(SourceNodeLabel == "group" and SourceNodeName in ("Domain Admins", "Enterprise Admins"))
    // Exclude Built-in administrator account
    | where not(SourceNodeLabel == "user" and SourceNodeName == "Administrator")
    | summarize TargetNodes = make_set(TargetNodeName), TargetNodeCount = count() by SourceNodeName, SourceNodeLabel, tostring(Permissions), TargetNodeLabel, SourceNodeId
);
let HighPermissionNodes = toscalar(
    HighPermissionLinks
    | summarize SourceNodes = make_set(SourceNodeName)
);
// Get edges for links to the high ACL permissions
ExposureGraphEdges
| where TargetNodeName in (HighPermissionNodes)
| make-graph SourceNodeId --> TargetNodeId with ExposureGraphNodes on NodeId
// Get between one and three relations
| graph-match (SourceNode)-[anyEdge*1..3]->(TargetNode)
    project IncomingNodeName = SourceNode.NodeName, 
    IncomingNodeLabel = SourceNode.NodeLabel,
    Edges = anyEdge.EdgeLabel, 
    OutgoingNodeName = TargetNode.NodeName,
    OutgoingNodeId = TargetNode.NodeId
// Filter for interesting edges
| where Edges has_any (edge_labels)
// Join the high permission ACLs
| join kind=inner HighPermissionLinks on $left.OutgoingNodeId == $right.SourceNodeId
// Exclude Domain and Enterprise Administrators as source node
| where not(IncomingNodeLabel == "group" and IncomingNodeName in ("Domain Admins", "Enterprise Admins"))
// Exclude Built-in administrator account
| where not(IncomingNodeLabel == "user" and IncomingNodeName == "Administrator")
| distinct IncomingNodeName, IncomingNodeLabel, tostring(Edges), OutgoingNodeName, OutgoingNodeLabel = SourceNodeLabel, tostring(Permissions), TargetNodeLabel, tostring(TargetNodes), TargetNodeCount
```

Explanation

This query is designed to identify potential privilege escalation paths within an Active Directory (AD) environment by examining Access Control List (ACL) permissions. Here's a simplified breakdown of what the query does:

  1. Define High Permissions: It starts by defining two types of high permissions, genericWrite and genericAll, which are considered risky if granted to certain accounts.

  2. Identify High Permission Links: The query looks for relationships (or "edges") in the AD exposure graph where these high permissions are granted. It specifically focuses on edges labeled "has role on" and checks if they include the high permissions defined earlier.

  3. Exclude Certain Accounts: It excludes well-known high-privilege groups and accounts like "Domain Admins", "Enterprise Admins", and the built-in "Administrator" account from being considered as sources of these permissions. This is because these accounts typically have high privileges by design.

  4. Summarize High Permission Sources: It summarizes the findings by listing which accounts or groups have these high permissions on other objects, along with the count of such target objects.

  5. Graph Relationships: The query then constructs a graph of relationships involving these high-permission nodes, looking for paths that involve one to three relationships.

  6. Filter for Interesting Paths: It filters these paths to include only those with specific types of relationships, such as "member of", "has permissions to", etc., which are considered interesting from a security perspective.

  7. Join and Exclude Again: It joins this information back with the high permission links and again excludes the well-known high-privilege accounts from being considered as sources.

  8. Output: Finally, it provides a distinct list of nodes (accounts or groups) that have high permissions, the types of permissions they have, and the target nodes they can potentially affect.

Overall, this query helps security analysts identify and visualize potential privilege escalation paths in an AD environment, allowing them to take steps to mitigate these risks.

Details

Robbe Van den Daele profile picture

Robbe Van den Daele

Released: May 5, 2025

Tables

ExposureGraphEdgesExposureGraphNodes

Keywords

PermissionsRolesUsersGroupsObjectsNodesAdministratorsAccountDomainEnterpriseEdgesLabelsGraphExposure

Operators

letdynamicwhereextendtodynamichas_anynotinsummarizemake_settostringtoscalarprojectjoinkind=innerdistinct-->withongraph-match

Actions