Neo4j Schema and Attack Paths - krazybeastx1/omenhound GitHub Wiki
Neo4j Schema & Attack Paths
OmenHound writes Neo4j using BloodHound CE's label and relationship model, so existing BloodHound Cypher, tooling and mental models keep working — and imported (BloodHound.py/CE) objects merge with collected ones by SID.
Labels
Every node has an id (the AD SID, or the objectGUID for non-SID objects) and exactly
one label:
User · Group · Computer · Domain · OU · GPO · Container · Trust
Base is used for placeholder nodes — an endpoint referenced by an edge but not
collected. Placeholders let the graph stay referentially valid without inventing data.
Uniqueness constraints are created automatically at startup:
CREATE CONSTRAINT User_id IF NOT EXISTS FOR (n:User) REQUIRE n.id IS UNIQUE
-- … one per label
Relationship types
GenericAll GenericWrite WriteDacl WriteOwner
ForceChangePassword AddMember AddSelf
Owns AllExtendedRights WriteSPN WriteAccountRestrictions
WriteGPLink WriteAltSecurityIdentities
AddKeyCredentialLink AddAllowedToAct
AllowedToDelegate AllowedToAct AdminTo CanPSRemote
ExecuteDCOM CanRDP HasSession DCSync
GetChanges GetChangesAll GetChangesInFilteredSet
ReadLAPSPassword ReadGMSAPassword DumpSMSAPassword
MemberOf Contains GPLink TrustedBy
Every relationship is a whitelisted string (injection-proof) and every one of them has an abuse template.
Directions
OmenHound follows BloodHound CE directions exactly:
| Relationship | Direction | Cypher | Meaning |
|---|---|---|---|
MemberOf |
member → group | (u:User)-[:MemberOf]->(g:Group) |
u is a member of g |
AdminTo |
principal → computer | (u)-[:AdminTo]->(c:Computer) |
u is local admin on c |
HasSession |
user → computer | (u)-[:HasSession]->(c) |
u has a session on c |
DCSync |
principal → domain | (u)-[:DCSync]->(d:Domain) |
u can replicate from d |
GPLink |
GPO → container | (g:GPO)-[:GPLink]->(ou:OU) |
g is linked at ou/domain/site |
Contains |
container → child | (d:Domain)-[:Contains]->(u) |
structural containment |
TrustedBy |
trusted → trusting | (a:Domain)-[:TrustedBy]->(b:Domain) |
a is trusted by b |
AllowedToDelegate |
computer → target | (c:Computer)-[:AllowedToDelegate]->(t) |
constrained delegation to t |
AllowedToAct |
principal → computer | (u)-[:AllowedToAct]->(c) |
RBCD: u may act for c |
GPLink carries enforced |
— | props.enforced |
GPO link is enforced |
Example Cypher
Shortest path from a compromised user to Domain Admins:
MATCH p = shortestPath(
(u:User {sam_account_name:'jdoe'})-[*1..]->(g:Group)
)
WHERE g.name = 'DOMAIN [email protected]'
RETURN p
Everything a principal can reach in ≤ 3 hops:
MATCH p = (u:User {id:$sid})-[*1..3]->(n)
RETURN p
Computers where a user is local admin and has a session:
MATCH (u:User {sam_account_name:'jdoe'})-[:AdminTo]->(c:Computer),
(u)-[:HasSession]->(c)
RETURN c.name
Unconstrained-delegation style paths (AllowedToDelegate) into the DC:
MATCH p = (c:Computer)-[:AllowedToDelegate*1..]->(d:Domain)
RETURN p
GPOs that grant full control of an OU:
MATCH (g:GPO)-[l:GPLink]->(ou:OU)
RETURN g.name, ou.name, l.enforced
Kerberoastable / AS-REP-roastable users (if collected in props):
MATCH (u:User)
WHERE u.serviceprincipalnames <> '' AND u.enabled = 'true'
RETURN u.sam_account_name, u.serviceprincipalnames
Node properties
Node fields (id, kind, name, domain, sam_account_name,
distinguished_name) are written as first-class Neo4j properties, and props may carry
collector-specific attributes (enabled, admincount, pwdlastset, serviceprincipalnames,
description, whencreated, lastlogon, …).
Attack-path reference
docs/ATTACK-PATHS.md
is the full per-edge catalogue: meaning, primary command, alternates, OPSEC, event IDs,
and preconditions. GET /api/v1/abuse/supported returns the edge list the renderer
supports.
Next: Coming from BloodHound.py →