BloodHound#

BloodHound is a graph database for Active Directory and Azure AD. A SharpHound or AzureHound collector enumerates objects and their relationships into a JSON dump; the dump loads into Neo4j; the BloodHound UI (or raw Cypher) traces shortest paths between arbitrary principals. Attack paths that take human analysts days of CSV-grepping fall out of a single query.

Red teams use it to plan privilege escalation. Blue teams use it to find the same paths first and remove them. The query patterns below are useful from either side.

Owned and target#

The owned and highvalue flags mark principals already controlled and principals worth controlling. Marking them up front lets later traversal queries treat the graph as a goal- seeking problem.

MATCH (u:User)        WHERE u.owned = TRUE     RETURN u
MATCH (c:Computer)    WHERE c.owned = TRUE     RETURN c
MATCH (g:Group)       WHERE g.owned = TRUE     RETURN g
MATCH (n)             WHERE n.highvalue = TRUE RETURN n

// Group memberships of every already-owned user
MATCH (u:User {owned:true})-[:MemberOf*1..]->(g:Group) RETURN u, g

Kerberoasting#

Accounts with a Service Principal Name registered against them can be Kerberoasted. A stale password makes the offline crack trivial.

// All Kerberoastable users
MATCH (u:User {hasspn:true}) RETURN u

// Kerberoastable with password set more than 5 years ago
MATCH (u:User {hasspn:true})
  WHERE u.pwdlastset < (datetime().epochseconds - (5 * 365 * 86400))
    AND NOT u.pwdlastset IN [-1.0, 0.0]
  RETURN u.name, u.pwdlastset
  ORDER BY u.pwdlastset

// Kerberoastable users with a path to Domain Admins
MATCH (u:User {hasspn:true}),
      (g:Group)             WHERE g.objectid ENDS WITH '-512'
MATCH p = shortestPath((u)-[*1..]->(g))
  RETURN p

// AS-REP roastable accounts (pre-auth disabled)
MATCH (u:User {dontreqpreauth:true}) RETURN u

Lateral movement#

Where can the current toehold actually reach.

// Machines Domain Users (-513) can RDP into
MATCH p = (g:Group)-[:CanRDP]->(c:Computer)
  WHERE g.objectid ENDS WITH '-513' RETURN p

// Groups (and their members) that can RDP anywhere
MATCH p = (g:Group)-[:CanRDP]->(c:Computer) RETURN p

// Local-admin paths from any user
MATCH p = (u:User)-[:AdminTo]->(c:Computer) RETURN p

// Groups that can force-change another user's password
MATCH p = (g:Group)-[:ForceChangePassword]->(u:User) RETURN p

Delegation#

Unconstrained, constrained, and resource-based constrained delegation are the high-yield AD primitives most operators look for first.

// Unconstrained-delegation computers (excluding DCs at -516)
MATCH (dc:Computer)-[:MemberOf*1..]->(g:Group)
  WHERE g.objectid ENDS WITH '-516'
  WITH COLLECT(dc.name) AS domain_controllers
MATCH (c:Computer {unconstraineddelegation:true})
  WHERE NOT c.name IN domain_controllers
  RETURN c

// Constrained delegation paths
MATCH p = (u:User)-[:AllowedToDelegate]->(c:Computer) RETURN p

Sessions#

A session edge means the user’s TGT is sitting on that machine. Find the easiest box to land on that already hosts a Domain Admin session.

MATCH (da:User)-[:MemberOf*1..]->(g:Group)
  WHERE g.objectid ENDS WITH '-512'
MATCH p = (c:Computer)-[:HasSession]->(da)
  RETURN p

Stale and exotic#

// Computers running unsupported Windows versions
MATCH (c:Computer)
  WHERE c.operatingsystem =~ '.*(2000|2003|2008|XP|Vista|7|ME).*'
  RETURN c

// Inactive users still enabled
MATCH (u:User)
  WHERE u.lastlogon < (datetime().epochseconds - (90 * 86400))
    AND NOT u.lastlogon IN [-1.0, 0.0]
    AND u.enabled = true
  RETURN u

// MSSQL service principals
MATCH (c:Computer)
  WHERE ANY (x IN c.serviceprincipalnames WHERE toUpper(x) CONTAINS 'MSSQL')
  RETURN c

// Cross-domain edges (a principal in one domain acts on another)
MATCH p = (n)-[r]->(m) WHERE NOT n.domain = m.domain RETURN p

References#