Internal Network & Active Directory · intermediate · ~11 min
**What you will learn** - Explain what an Active Directory (AD) *attack path* is and why it is a chain of legitimate rights rather than a single bug. - Describe how BloodHound turns AD data into a graph and computes the shortest path from controlled accounts to Domain Admin. - Map the durable, structural defenses that break those paths: tiered administration, ACL and least-privilege hygiene, LAPS, gMSA, Protected Users, and monitoring. - Verify that a fix actually cuts a path (re-run the graph and confirm the edge is gone). - Choose the right AD security events to log for detection, and know what must never be logged. - Write the high-value AD finding: the path plus the single change that removes it, with a retest step.
Security objective. The asset you are protecting is the identity plane of a Windows domain: high-value credentials (Domain Admins, domain controllers) and the trust relationships that let one account act on another. The threat is an attacker who has already gained a low-value foothold (one ordinary user or one workstation) and wants to escalate to full domain control by hopping through legitimate permissions. What you will learn to detect and prevent is the attack path — the specific chain of rights that connects the foothold to Domain Admin — and how to cut it structurally.
An AD compromise is almost never one dramatic exploit. It is a chain of small, individually reasonable permissions that add up to game over. User A can reset User B's password; B happens to be a local admin on a server; a domain admin is logged into that server; the attacker steals that session. Each link is a normal AD feature. The combination is the vulnerability.
BloodHound is the tool that makes these chains visible. It ingests AD enumeration data and models it as a directed graph, then computes the shortest path from "principals I control" to "Domain Admin". Attackers use it to plan; defenders use the exact same tool to find and cut paths before attackers walk them.
How this connects to your prerequisite. In Lateral movement and segmentation you saw how an attacker pivots host-to-host once inside a network, and how segmentation limits blast radius. This lesson is the identity-layer equivalent: instead of network hops between machines, we study permission hops between AD objects. Segmentation limited network reach; tiering and ACL hygiene limit credential reach. Same defensive instinct, different plane.
Everything here is taught for authorized work only: your own lab domain, a training range, or a client engagement with written scope. We never point tools at systems we do not own or are not explicitly permitted to test.
Modern AD assessment is about attack paths, not isolated bugs. A vulnerability scanner that lists 200 findings on 200 hosts is far less useful to a defender than one sentence: "Remove this one ACL and the path to Domain Admin disappears."
In real, authorized engagements this matters because:
The professional habit this builds: always ask "what is the shortest path to the crown jewels, and what one change breaks it?"
Definition. An attack path is an ordered chain of legitimate rights and relationships that leads from a principal an attacker controls to a high-value target (usually Domain Admin or a domain controller).
Plain explanation. Think of AD as a web of "who can do what to whom." Most edges are harmless in isolation. A path is a sequence of edges that composes into escalation.
How it works. Each hop uses a real capability: an ACL right (reset a password, add a member to a group), a session (an admin is logged into a box you control), or membership (being in a group that has rights elsewhere). None of these is a "bug" — they are configuration.
When it matters / when not. It matters whenever many accounts have been granted rights over time without review. In a tiny, freshly-built domain with clean tiering there may be no path — that is the goal.
Pitfall. Treating each edge as a separate low-severity finding. The severity lives in the composition, not the individual link.
Definition. A directed graph where nodes are AD principals and objects (users, groups, computers, GPOs, OUs) and edges are relationships/rights (MemberOf, AdminTo, HasSession, GenericAll, ForceChangePassword, and so on).
Plain explanation. A collector (the SharpHound data collector) reads AD over standard protocols (LDAP for directory data, plus session enumeration) and exports the data. BloodHound loads it into a graph database and lets you query it.
How it works. Because it is a graph, "how do I get from here to Domain Admin?" becomes a classic shortest-path query. The tool highlights the chain visually.
When / when not. Use it to audit your domain or an authorized target. Do not run collectors against a domain you are not scoped for — enumeration itself is intrusive and logged.
Pitfall. Assuming a clean graph means you are secure. The graph only reflects the data you collected, from the vantage point you collected it. Missing sessions or unread ACLs mean missing edges. Never claim a domain is "completely secure" because one BloodHound run was clean.
Definition. A model that partitions identities and assets into tiers by trust, and forbids high-tier credentials from ever being exposed on low-tier hosts.
Plain explanation. Tier 0 = the keys to the kingdom (domain controllers, Domain Admins, AD itself). Tier 1 = servers/applications. Tier 2 = workstations. Credentials only ever log on within or above their tier, never down into a less-trusted one.
How it works. If a Domain Admin never logs into a Tier-2 workstation, then compromising that workstation cannot harvest Tier-0 credentials — the credential simply isn't there to steal. This severs the most common escalation edges (HasSession from a low host to a high account).
When / when not. Essential in any environment with more than a handful of admins. Overkill only in trivial home labs (but the lab is exactly where you should practice it).
Pitfall. "Tiering" that exists on a slide but not in reality because one break-glass admin logs in everywhere. One violated boundary reopens the path.
Pitfall. Enabling LAPS but leaving a legacy shared local-admin password on a subset of "special" machines. The exception becomes the path.
Definition. Systematically removing dangerous rights (GenericAll, WriteDACL, ForceChangePassword, AddMember over privileged groups) and stale privileged-group memberships.
Plain explanation. Most attack-path edges are delegated permissions nobody remembers granting. Auditing and pruning them removes the edges wholesale.
Pitfall. Removing a right that a legitimate automation depends on and causing an outage. Change ACLs through a reviewed process, in a maintenance window, with rollback ready.
TRUST BOUNDARIES (tiers)
Tier 2 (workstations) Tier 1 (servers) Tier 0 (DCs / Domain Admins)
+------------------+ +----------------+ +-------------------------+
| low-value user | | app / file | | DOMAIN ADMIN |
| foothold (X) | | servers | | domain controllers |
+--------+---------+ +--------+-------+ +------------+------------+
| | |
ENTRY POINT: Path edge: Protected asset:
phished user X, X can ForceChangePassword full domain control
controlled workstation on B; B is AdminTo server; (the crown jewels)
admin has HasSession there
ATTACK PATH (edges the graph reveals):
X --ForceChangePassword--> B --AdminTo--> SRV --HasSession--> DomainAdmin
DEFENSE = cut ANY edge:
* Tiering removes HasSession (admin never logs into SRV from low tier)
* ACL hygiene removes ForceChangePassword (revoke X's right over B)
* LAPS/gMSA remove credential-reuse and Kerberoast edges elsewhere
Knowledge check.
HasSession edge dangerous — and why is it only safe to enumerate this path in an authorized lab?BloodHound is not a language; the "syntax" that matters is (a) how data is collected and (b) how you query the graph. Below are lab-safe, annotated shapes. Run collection only against a domain you own or are scoped to test.
# 1. Collect AD data with the SharpHound collector (run inside your lab domain,
# from an authorized, domain-joined test host). '-c' selects collection methods.
SharpHound.exe -c Default,Session,ACL
# ^ produces timestamped .json/.zip files describing nodes and edges
# 2. In the BloodHound UI you don't type raw queries for common tasks — you use
# built-in analytics like "Shortest Paths to Domain Admins". Under the hood
# those are Cypher graph queries. A read-only example (conceptual):
MATCH p = shortestPath(
(n {name:'FOOTHOLD@LAB.LOCAL'})-[*1..]->(m:Group {name:'DOMAIN ADMINS@LAB.LOCAL'})
)
RETURN p
# MATCH = find a subgraph pattern
# shortestPath = the classic escalation question
# [*1..] = one-or-more edges of any allowed type
# RETURN p = give me the chain to visualize
Key defensive commands live on the AD side, e.g. auditing a delegated right (PowerShell, read-only):
# List explicit access-control entries on a user object (who can act on 'targetUser').
(Get-Acl "AD:$((Get-ADUser targetUser).DistinguishedName)").Access |
Where-Object { $_.ActiveDirectoryRights -match 'GenericAll|WriteDacl|ForceChangePassword' }
# -> surfaces the exact ACL edges BloodHound would draw
Nothing above modifies the directory; they read and visualize. Remediation (removing an ACE, moving an account to Protected Users) is a separate, change-controlled step.
An AD compromise is rarely one bug. It is a chain of small rights that lead, step by step, to Domain Admin. Finding and breaking those chains is the core of modern AD work.
BloodHound ingests AD enumeration data: users, groups, sessions, ACLs (access control lists, the rules that say who can do what to an object), and GPO links.
It builds a graph, then computes the shortest path from "what I control" to "Domain Admin".
This surfaces non-obvious chains. For example:
You control user A -> A can reset B's password (an ACL right) -> B is in a group with admin rights on server X -> an admin is logged into X -> steal their token -> Domain Admin.
Each hop is a legitimate right on its own. The combination is the vulnerability.
Defenders use BloodHound too, to find and cut these paths before attackers do.
For AD, the value is the path plus the single change that breaks it.
For example: "Remove this ACL or this group membership, and the path to Domain Admin disappears."
That is far more actionable than a flat list of individual findings.
The "code" for a defensive AD lesson is a repeatable workflow: reveal a path, cut it, then verify it is gone. Everything here is for an isolated lab domain you built yourself (e.g. a couple of VMs named LAB.LOCAL).
A lab misconfiguration that creates a path. This grants a dangerous right so you can learn to see and remove it. Never run against production.
# LAB ONLY. Domain: LAB.LOCAL, isolated VMs, no internet, snapshot taken first.
# Give a low-value helpdesk user the ability to reset a server-admin's password.
# This is the ForceChangePassword edge BloodHound will light up.
$helpdesk = Get-ADUser helpdesk01
$target = Get-ADUser svradmin01
# (Intentionally over-broad delegation — the vulnerability under study.)
dsacls ("CN=svradmin01," + (Get-ADDomain).UsersContainer) `
/G "LAB\helpdesk01:CA;Reset Password"
# Result: helpdesk01 --ForceChangePassword--> svradmin01, an escalation edge.
Collect and view it:
SharpHound.exe -c Default,ACL,Session # from an authorized lab host
# In BloodHound: run "Shortest Paths to Domain Admins" starting at helpdesk01.
# You now SEE the chain: helpdesk01 -> svradmin01 -> ... -> DOMAIN ADMINS.
Remove the dangerous edge and apply the structural controls that stop it recurring.
# LAB ONLY. Revoke the over-broad delegation (cut the ACL edge).
dsacls ("CN=svradmin01," + (Get-ADDomain).UsersContainer) `
/R "LAB\helpdesk01"
# Structural hardening so the path cannot re-form:
# a) Put sensitive admin accounts in Protected Users (less cached credential material).
Add-ADGroupMember -Identity "Protected Users" -Members svradmin01
# b) Enforce tiering: svradmin01 is Tier 1 and must never log into Tier-2 hosts.
# (Enforced via 'Deny log on' rights / authentication policy silos — set per your lab.)
# c) Ensure local-admin reuse is dead: LAPS rotates unique passwords per machine.
# (Deploy the LAPS policy to the lab OU; verify each host has a distinct password.)
# Re-collect and re-query. The edge and the path must be GONE.
SharpHound.exe -c Default,ACL,Session
# In BloodHound: "Shortest Paths to Domain Admins" from helpdesk01
# EXPECTED (fix works): "No paths found" from helpdesk01 to Domain Admins.
# Prove legitimate access still works (no false lockout):
# - helpdesk01 can still do its intended job (e.g. reset ORDINARY user passwords
# it is legitimately delegated), just not svradmin01's.
# Confirm the removed ACE is really gone (read-only check):
(Get-Acl "AD:CN=svradmin01,$((Get-ADDomain).UsersContainer)").Access |
Where-Object { $_.IdentityReference -like '*helpdesk01*' }
# EXPECTED: no rows returned.
Expected outcome. Before the fix, BloodHound draws a path from helpdesk01 to DOMAIN ADMINS. After revoking the ACE (and confirming with the read-only ACL check that returns nothing), the same query returns no path, while helpdesk01 retains its legitimate duties. That before/after is your evidence.
Lab authorization checklist (run before any of this).
Lab cleanup / reset. Revert to the pre-lab VM snapshot, or explicitly undo: dsacls ... /R LAB\helpdesk01, Remove-ADGroupMember "Protected Users" -Members svradmin01, and remove any test LAPS/tiering policy from the lab OU. Confirm a fresh SharpHound run shows the domain back to baseline.
Walking the workflow above, step by step.
| Step | What happens | State change | Why |
|---|---|---|---|
| Snapshot VMs | Save lab state | Rollback point exists | So any mistake is reversible in a lab |
dsacls ... /G Reset Password |
Grant helpdesk01 reset over svradmin01 | New ACL edge ForceChangePassword created |
Deliberately builds the vulnerable path to study |
SharpHound -c Default,ACL,Session |
Collector reads directory + sessions | Data exported to JSON/zip | Turns live AD into graph input |
| Load + "Shortest Paths to Domain Admins" | Graph query runs | Path from helpdesk01 to DA is drawn | Makes the composition of edges visible |
| Inspect the chain | You read each hop | Understanding: which edge is weakest/cheapest to cut | You only need to break ONE edge |
dsacls ... /R helpdesk01 |
Revoke the delegation | The ForceChangePassword edge is deleted |
Cuts the path at its first hop |
| Add to Protected Users / tiering / LAPS | Structural controls applied | Fewer cached creds, no down-tier logon, no reuse | Stops the path re-forming elsewhere |
| Re-run SharpHound + query | Fresh collection, same query | Result: no path found | This is the verification — the fix is proven, not assumed |
Read-only Get-Acl filter |
Check for helpdesk01 ACE | Returns nothing | Independent confirmation the edge is gone |
| Confirm helpdesk01's real job still works | Test legitimate action | Still succeeds | Ensures no false positive / no broken business function |
The crucial move is the re-query after the fix. Decoding a graph once is not enough; a defense is only real when the repeated measurement shows the path removed while legitimate access survives.
| Wrong approach | Why it's wrong | Corrected approach | How to recognise / prevent |
|---|---|---|---|
| Reporting each edge as its own low-severity finding | Misses that the composition is the real (often high) risk | Report the path and the one change that breaks it | If findings never mention Domain Admin reachability, you're listing bugs, not paths |
| Assuming a clean BloodHound run = "secure" | The graph only shows edges you collected, from your vantage point; missing sessions/ACLs hide edges | Collect multiple methods, over time; treat clean as "no path found", never "no path exists" | Never write "completely secure"; re-collect periodically |
| Enabling LAPS but leaving a shared local-admin password on "special" hosts | The exception is exactly the reuse edge attackers want | No exceptions; verify every host has a unique rotated password | Audit for any machine still using the legacy shared secret |
| "Tiering on paper" — one admin still logs in everywhere | A single boundary violation reopens the whole path | Enforce with Deny-logon / authentication policy silos; monitor for violations | Alert on Tier-0 accounts authenticating to Tier-2 hosts |
| Removing an ACL right in production with no testing | Can break legitimate automation and cause outages | Change ACLs through review, in a window, with rollback; test in lab first | Keep a snapshot/rollback; verify legitimate access after the change |
| Treating "I decoded/enumerated the domain" as proof of impact | Enumeration ≠ demonstrated escalation; and running collectors unscoped is itself intrusive | Only enumerate within scope; describe impact via the reachable path, safely | Written authorization on file before any collection |
Common problems and how to work them.
-c Session again at a busy time; confirm the collecting account can query the hosts; remember sessions are transient, so collect repeatedly.DOMAIN ADMINS@LAB.LOCAL? Missing collection methods = missing edges, not absence of risk.Questions to ask when it fails: Which collection methods did I run? From which vantage point/account? Is this the shortest path or one of several? Did I verify by re-collecting, or did I just assume? Did legitimate access survive the change?
Security & safety — detection and logging.
Breaking paths is half the job; detecting attempts to walk them is the other half. In your lab, turn on auditing and watch what BloodHound-style activity and escalation attempts look like.
What to log (and why). For each security-relevant event capture: timestamp, source host/account, target resource, the action, the result (success/deny), the security decision made, and a correlation id so multi-hop activity can be stitched together. Useful AD events:
| Event | Meaning | Why it helps here |
|---|---|---|
| 4624 / 4625 | Logon success / failure | Tier-0 account logging into a low-tier host = tiering violation |
| 4768 / 4769 | Kerberos TGT / service-ticket requests | Bursts of 4769 for many SPNs suggest Kerberoasting |
| 4728 / 4732 / 4756 | Member added to a (privileged) group | The AddMember edge being used for real |
| 4724 / 4723 | Password reset / change | The ForceChangePassword edge being exercised |
| 5136 | Directory object (ACL) modified | Someone editing delegations |
| Heavy LDAP reads | Directory enumeration | Consistent with a collector run |
What to NEVER log. Passwords, password hashes, Kerberos tickets/keys, session cookies, private keys, or full secrets of any kind. Do not dump reusable credential material into logs "for debugging" — that turns your SIEM into the attacker's loot box. Avoid unneeded PII. Use placeholders like API_KEY=<development-placeholder> in any lab config you share.
Which events signal abuse. A low-value account suddenly resetting an admin's password; a Tier-0 credential authenticating to a workstation; a spray of service-ticket requests; a new privileged-group membership outside change windows; large LDAP enumeration from an unusual host.
How false positives arise. Legitimate helpdesk staff do reset passwords; backup/monitoring tools do enumerate LDAP; patch cycles cause logon bursts. That is why you log the decision and context (who, where, expected?) and tune against a known-good baseline — not raw event counts. Correlate across events before alerting.
Concrete authorized use case. A bank hires a red team with written scope covering the corporate domain. The team runs SharpHound from an authorized jump host, loads BloodHound, and finds that a service desk group has ForceChangePassword over a group that is AdminTo the backup servers — where a Domain Admin's scheduled task runs. Three hops, service desk to Domain Admin. The report leads not with 40 individual ACL findings but with that single path and the one delegation to revoke. The blue team removes the ACE, enforces tiering for the backup admin, and the retest shows no path. That retest is the deliverable that closes the finding.
Professional best-practice habits.
| Habit | Beginner | Advanced |
|---|---|---|
| Validation / scope | Confirm you own the lab or have written scope before collecting | Maintain per-engagement scope docs; enforce with jump-host allowlists |
| Least privilege | Remove obvious GenericAll/reset rights you don't need | Model tiering with authentication policy silos; continuous ACL review |
| Secure defaults | Turn on LAPS in the lab so no two machines share a local-admin password | Roll LAPS + gMSA org-wide; alert on any non-compliant host |
| Logging | Enable the AD audit events above in the lab and read them | Feed to a SIEM; build correlation rules; baseline to cut false positives |
| Error handling / verify | Always re-collect after a fix to confirm the path is gone | Automate scheduled BloodHound diffs so new paths are caught as they appear |
| Reporting | State path + single fix | Prioritise fixes by how many paths each cut invalidates |
Misconceptions to keep straight: a clean scanner or a clean BloodHound run does not prove security — it proves nothing was found this time. Nothing in AD is ever "completely secure." And enumerating a domain is intrusive; only do it where you are authorized.
All tasks are lab-only: your own isolated LAB.LOCAL VMs, snapshot first, no internet, no third-party systems. Every task ends defensively — remediate and verify.
Beginner 1 — Read a path.
Beginner 2 — Enable AD auditing and observe.
Intermediate 1 — Create, find, and cut an edge.
Get-Acl check proving the ACE is gone.dsacls /G to create, /R to revoke. Concepts: ACL hygiene, mitigation verification.Intermediate 2 — Tiering violation detection.
Challenge — Path-cut prioritisation report.
Main concepts. An AD compromise is a chain of legitimate rights — an attack path — from a low-value foothold to Domain Admin. BloodHound models AD as a directed graph (nodes = principals/objects, edges = rights/sessions) and computes the shortest path; defenders use the same tool to find and cut paths.
Key defenses (durable, structural). Tiered administration (high-tier credentials never exposed on low-tier hosts), ACL and least-privilege hygiene (prune GenericAll/WriteDACL/ForceChangePassword and stale memberships), LAPS (kills local-admin reuse), gMSA (defeats Kerberoasting), Protected Users/Credential Guard, and monitoring of events 4624/4625/4768/4769/4728/4724/5136 plus unusual LDAP.
Key workflow / commands. Reveal → cut → verify: SharpHound -c Default,ACL,Session, run "Shortest Paths to Domain Admins", cut one edge (e.g. dsacls /R), then re-collect and confirm "no path" while legitimate access survives.
Common mistakes. Listing edges instead of paths; treating a clean run as "secure"; LAPS/tiering with exceptions; changing ACLs in production untested; confusing enumeration with proven impact.
What to remember. The graph only shows edges you collected — never claim "completely secure." Log the decision and context, never secrets. A fix is real only after a re-run proves the path is gone. And do all of it only where you own the system or have written authorization.