Series · 4 partsInternal Network TriagePart 4 · You are here
  1. 1Kerberoasting Triage: Most Service Tickets Are a Waste of Your Time
  2. 2SMB Signing Is On. That Closed One Edge, Not the Graph.
  3. 3Delegation Triage: You Were Taught the Rare One
  4. 4BloodHound Path Triage: The Shortest Path Is Usually the One That Expires FirstYou are here

BloodHound path triage in 60 seconds

BloodHound turns Active Directory relationships into a graph. Users, computers, groups, sessions, and permissions become nodes and edges; path queries then show how one controlled identity might reach a privileged target. The graph is a map of collected facts, not proof that every route still works.

Everyone runs the same query first. Mark something owned, ask for the shortest path to Domain Admins, screenshot the graph. It is a good query. It is also the one most likely to hand you a path that does not work, and the reason is not bad data.

BloodHound draws every relationship as an identical arrow. The facts behind those arrows have wildly different shelf lives.

Three lifetimes, one picture

ACL edgesGenericWrite, WriteDacl, Owns, AddMember, ForceChangePassword. These come from object security descriptors. They were set during some project years ago and nobody has touched them since. If the edge was there when you collected, it is there now, and it will be there next week.

Membership and rights edgesMemberOf, AdminTo, CanRDP, CanPSRemote. Stable over an engagement, but they do change. A group gets cleaned up, a machine gets rebuilt.

Session edgesHasSession. A user was logged on to that host at the moment of collection. That is all it means. They may have logged off ninety seconds later. On a workstation fleet, a session graph collected in the morning is substantially fiction by the afternoon.

All three render as the same arrow. Shortest-path then does what shortest-path does: it finds the fewest hops. Session edges are shortcuts — a single HasSession can jump you straight from a workstation to a domain admin’s credentials — so the algorithm reaches for them preferentially. The shortest path is systematically biased toward the most perishable facts in the dataset.

That is why the first path so often fails on contact, and why the fifth one you try — longer, duller, entirely made of ACLs — is the one that works every time you run it.

ACL GenericWrite · WriteDacl RIGHTS MemberOf · AdminTo SESSION HasSession MINUTES HOURS DAYS MONTHS YEARS SHORTEST PATH prefers the leftmost bar — a session is one hop, so it wins on length
Every one of these renders as an identical arrow. A session edge is a single hop from a workstation to a domain admin's credentials, so shortest-path reaches for it — which is precisely the fact most likely to have expired before you walk it.

Ask for the durable path instead

The fix is not a better tool. It is constraining the query to edge types that do not decay.

// Deterministic path: ACL edges only. Longer, and it works tomorrow too.
MATCH p = shortestPath(
  (n {owned: true})-[:Owns|GenericAll|GenericWrite|WriteDacl|WriteOwner|
                     AddMember|ForceChangePassword|AllExtendedRights*1..]->
  (g:Group {name: 'DOMAIN [email protected]'})
)
RETURN p

Then ask the opposite question — not “how do I reach DA” but “what does this one thing I already hold actually reach”:

MATCH (u:User {name: '[email protected]'})
MATCH p = (u)-[:MemberOf|AdminTo|GenericAll|GenericWrite|WriteDacl*1..3]->(c:Computer)
RETURN DISTINCT c.name, length(p) AS hops
ORDER BY hops

And make the perishable edges explicit rather than silently trusting them:

// Which of my paths depend on someone still being logged in?
MATCH p = shortestPath((n {owned: true})-[*1..]->(g:Group {name: 'DOMAIN [email protected]'}))
WHERE any(r IN relationships(p) WHERE type(r) = 'HasSession')
RETURN p

If every path you have runs through a session, you do not have a reliable finding yet. You have a screenshot.

The edges worth more than they look

Two relationship types are consistently under-weighted because they are one hop from something boring:

ReadLAPSPassword reads as an information disclosure. It is local administrator on that machine, permanently, for anyone holding it — and LAPS deployments hand the read right to broader groups than people remember approving.

ReadGMSAPassword is the same shape. Worth noting because moving service accounts to gMSA is the correct remediation for Kerberoasting — but it relocates the problem into a DACL rather than deleting it. If the read right is over-granted, the gMSA is a service account with a 240-byte machine-generated password that a helpdesk group can simply retrieve.

Both are ACL-class edges. They are permanent, and they do not appear in the shortest path because they usually need one more hop.

What the graph cannot see

Worth stating plainly in a report, because the picture invites the opposite conclusion: the absence of a path is not evidence that none exists.

BloodHound models Active Directory. It does not model a shared local administrator password that was never in AD, credentials sitting in a deployment script on a share, an application that authenticates to a database with a domain account, or trust that lives in a CI system. Those are real edges. They are simply not the kind of fact the collector reads.

The corollary matters on the offensive side too: when the graph shows nothing, the answer is usually that the next edge is outside AD, not that there is no next edge.

The same graph approach, pointed at cloud trust instead of directory trust, is the cloud IAM note — the deliverable is the path there too, and the edges are trust relationships rather than ACLs.

Evidence matrix

SignalWhat it provesNegative controlDefender verification
ACL or ownership edge survives two collectionsThe path is durable enough to treat as a standing control failureRemove session edges from the graph and confirm the route still existsRe-collect object DACLs and calculate effective rights through nested groups
Session edge has a recent timestamp and reachable hostA transient credential opportunity existed at collection timeLog the user off and collect again; the edge should disappearCorrelate the edge with logon telemetry and the collection window
Group membership connects a low-tier principal to a durable rightThe route depends on identity governance, not an endpoint accidentRemove the test membership and recompute the pathReview nested membership, group owner, purpose, and expiry
The named target is reachable from the tester’s network positionThe graph path is operational rather than merely theoreticalBlock the management path and verify the final hop failsValidate firewall, tiering, and administrative logon restrictions independently of AD data

The pattern I keep seeing

The ACL edges that end up mattering are almost never granted to an individual. They arrive as a group that was given broad rights over an OU for a project, and kept them afterwards because revoking access is nobody’s deliverable. The nesting then does the rest: the group gets added to another group for an unrelated reason, and a permission scoped to one team quietly becomes reachable by three.

Which is why the finding people write — “path from user X to Domain Admin exists” — closes badly. Remove one membership and that specific path disappears while the permission that made it possible stays exactly where it was.

What to hand the defenders

Report the edge, not the path. The path is a demonstration. The finding is the specific permission on the specific object, and who holds it through what nesting. That is the thing that can be revoked.

Separate durable from perishable. If a path depended on a live session, say so. It is still worth reporting — it shows a credential exposure — but it is a different remediation from a DACL, and conflating them gets both handled badly.

Give them the reverse query. Defenders get more value from “what can this group reach” than from any attack path, because it maps to something they can act on: a group they own, and a list of what it currently touches.

The graph is not the finding. It is the argument for the finding.

Sources & freshness

How current is this note?

Sources checkedJune 19, 2025

The latest source-review, content-update, or publication date is shown.

ReviewAuthor review complete

The author completed a technical review. This does not, by itself, claim lab reproduction.