Skip to content

AD enumeration

PingCastle

Quickly identify misconfigurations, vulnerabilities, and risks within Active Directory and Entra ID environments using PingCastle.

  1. Download the Syslifters compiled version of PingCastle.exe
  2. Add license into appsettings.console.json file next to the PingCastle.exe file to run the program with the correct license. Retrieve the file or license key from your password manager / internal vault.
  3. Run pingcastle.exe in a terminal.

Bloodhound

Map and assess Active Directory related attack paths in Active Directory and Entra ID environments.

  1. Download the Syslifters compiled Sharphound.exe collector

  2. Collect data

  • Cheatsheet: SharpHound cheat sheet (external link)

  • For non-admin users:
    ./SharpHound.exe

  • With domain admin, we will also want to collect sessions:
    ./SharpHound.exe --c All

  • Collect data with all attributes:
    sharphound.exe -c all --collectallproperties

  1. Import the .zip file 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.

  1. Download pyldap
  2. Run an LDAP query, e.g.:
    .\--main--.exe operation\user:<password> '(objectClass=*)'
  3. 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

powershell
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

powershell
MATCH p=(c:User)
WHERE c.name = "COMPUTER@DOMAIN.LOCAL"
detach delete p

Get all sensitive users

powershell
match (u:User) where u.sensitive = True return u

Get all certificates with modification rights other than DA / EA / Built-In Administrators

powershell
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 1000

Get owned users

powershell
MATCH (u:User)
WHERE u.owned = True
RETURN u.samaccountname, u.pw, u.distinguishedname
ORDER BY u.name

Get outbound object control for OU (transitive)

powershell
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 DESC

Get outbound object control for user list (transitive)

powershell
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 DESC

Get outbound object control count per group

powershell
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 1000

Get AdminTo count per group

powershell
MATCH (m:Group)-[:AdminTo]->(n:Computer)
RETURN m.name AS GroupName, COUNT(n) AS ComputerCount
ORDER BY ComputerCount DESC
LIMIT 1000

Get outbound object control and AdminTo for groups (non-transitive)

powershell
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 1000

Get outbound object control and AdminTo for users (non-transitive)

powershell
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 1000

Get all owners sorted by count

powershell
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) DESC

Get all groups with owner permission on computer objects

powershell
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) DESC

Shortest path from owned users to DA (GUI functional)

powershell
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 p

Shortest path from owned computers to DA (GUI functional)

powershell
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 p

ADRecon

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.

  1. Execute .\ADRecon.ps1 in PowerShell. An Excel report will be generated automatically if Microsoft Office is installed.
  2. 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.

powershell
# Kerberoast and write hashes to file
Rubeus.exe kerberoast /outfile:hashes.txt /nowrap

AS-Rep Roasting

Find all user accounts with pre-authentication disabled, making them vulnerable to AS-Rep Roasting.

powershell
Rubeus.exe asreproast /outfile:asrephashes.txt /format:hashcat /nowrap