├── tests ├── grammar │ ├── __init__.py │ └── cypher │ │ ├── Cypher.tokens │ │ └── CypherLexer.tokens ├── schema.py ├── templates │ └── githubsummary.md.j2 ├── conftest.py └── test_cypher_syntax.py ├── queries.specterops.io.png ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ └── query-issue-report.yml └── workflows │ └── syntax.yml ├── requirements.txt ├── queries ├── Map OU structure.yml ├── Map domain trusts.yml ├── ESC8-vulnerable Enterprise CAs.yml ├── Entra Users with Entra Admin Role approval (direct).yml ├── Entra Users with Entra Admin Role direct eligibility.yml ├── Accounts with SID History.yml ├── Domain migration groups.yml ├── Computers not requiring inbound SMB signing.yml ├── Computers with the WebClient running.yml ├── All Global Administrators.yml ├── AdminSDHolder to protected objects relationship.yml ├── All DNSAdmins.yml ├── Principals with DCSync privileges.yml ├── All Domain Admins.yml ├── CA administrators and CA managers.yml ├── CA Administrators and CA Managers (ESC7).yml ├── Entra Users with Entra Admin Role approval (group delegated).yml ├── Entra Users with Entra Admin Roles group delegated eligibility.yml ├── PKI hierarchy.yml ├── Principals with DES-only Kerberos authentication.yml ├── Users which do not require password to authenticate.yml ├── All Azure VMs with a tied Managed Identity.yml ├── Computers with membership in Protected Users.yml ├── Computers with the outgoing NTLM setting set to Deny all.yml ├── Location of AdminSDHolder Protected objects.yml ├── Principals with passwords stored using reversible encryption.yml ├── Principals with foreign domain group membership.yml ├── Shortest paths to Domain Admins.yml ├── Enabled built-in guest user accounts.yml ├── All Schema Admins.yml ├── Computers where Domain Users are local administrators.yml ├── Users with non-expiring passwords.yml ├── Dangerous privileges for Domain Users groups.yml ├── Disabled Tier Zero High Value principals - AZ.yml ├── Locations of Owned objects - AZ.yml ├── Servers where Domain Users can RDP.yml ├── AS-REP Roastable users (DontReqPreAuth).yml ├── Accounts with SID History to a same-domain account.yml ├── Computers with unsupported operating systems.yml ├── Members of Allowed RODC Password Replication Group.yml ├── Synced Entra Users with Entra Admin Role approval (direct).yml ├── Synced Entra Users with Entra Admin Role direct eligibility.yml ├── All service principals with Microsoft Graph privilege to grant arbitrary App Roles.yml ├── Tier Zero High Value external Entra ID users.yml ├── Computers where Domain Users can read LAPS passwords.yml ├── Locations of Owned objects - AD.yml ├── Locations of Tier Zero High Value objects.yml ├── On-Prem Users synced to Entra Users that Own Entra Objects.yml ├── Owners of Azure Applications.yml ├── Shortest paths from Owned objects.yml ├── Workstations where Domain Users can RDP.yml ├── Cross-forest trusts with abusable configuration.yml ├── Enrollment rights on published certificate templates.yml ├── Public Key Services container.yml ├── Unresolved SID with outbound control.yml ├── On-Prem Users synced to Entra Users with Entra Group Membership.yml ├── Shortest Paths from Azure Users to Azure VMs.yml ├── On-Prem Users synced to Entra Users with Entra Admin Roles (direct).yml ├── Shortest paths to systems trusted for unconstrained delegation.yml ├── Tier Zero computers not owned by Tier Zero.yml ├── Domains with functional level not the latest version.yml ├── Tier Zero principals without AdminSDHolder protection.yml ├── Map Azure Management structure.yml ├── Synced Entra Users with Entra Admin Role approval (group delegated).yml ├── Shortest Paths from Azure Users to Azure Keyvaults.yml ├── Shortest paths to Azure Subscriptions.yml ├── Shortest paths to Tier Zero High Value targets.yml ├── Synced Entra Users with Entra Admin Roles group delegated eligibility.yml ├── Enrollment rights on CertTemplates with OIDGroupLink.yml ├── Domain Controllers allowing NTLMv1 or LM authentication.yml ├── Tier Zero computers not requiring inbound SMB signing.yml ├── Entra Users synced from On-Prem Users added to Domain Admins group.yml ├── Tier Zero computers at risk of constrained delegation.yml ├── Tier Zero computers with the WebClient running.yml ├── Domains with a minimum default password policy length less than 15 characters.yml ├── Non-Tier Zero account with unconstrained delegation.yml ├── Tier Zero High Value users with non-expiring passwords.yml ├── Users with passwords not rotated in over 1 year.yml ├── Accounts with SID History to a non-existent domain.yml ├── Accounts with clear-text password attributes.yml ├── All direct Controllers of MS Graph.yml ├── Domain controllers with weak certificate binding enabled.yml ├── Domains with a single-point-of-failure Domain Controller.yml ├── Domains without Microsoft LAPS computers.yml ├── On-Prem Users synced to Entra Users with Entra Admin Roles (group delegated).yml ├── Principals with weak supported Kerberos encryption types.yml ├── All coerce and NTLM relay edges.yml ├── Non-default permissions on IssuancePolicy nodes.yml ├── Owners of Azure Subscriptions.yml ├── Domains without Protected Users group.yml ├── Shortest Paths from Owned Azure Users to Azure VMs.yml ├── All Kerberoastable users.yml ├── Tier Zero computers at risk of resource-based constrained delegation.yml ├── Smart card accounts with passwords not rotated in over 1 year.yml ├── AS-REP Roastable Tier Zero users (DontReqPreAuth).yml ├── Domains with List Object mode enabled.yml ├── Domains with more than 50 Tier Zero accounts.yml ├── Domains with smart card accounts where smart account passwords do not expire.yml ├── Domains allowing authenticated domain enumeration.yml ├── Paths from Domain Users to Tier Zero High Value targets.yml ├── Shortest Paths from Owned Azure Users to Azure Keyvaults.yml ├── Foreign principals in Tier Zero High Value targets.yml ├── Accounts with smart card required in domains where smart account passwords do not expire.yml ├── Domains affected by AdPrep privilege escalation risk.yml ├── Enrollment rights on published certificate templates with no security extension.yml ├── Sessions across trusts.yml ├── Shortest paths from Domain Users to Tier Zero High Value targets.yml ├── Domains affected by Exchange privilege escalation risk.yml ├── Domains allowing unauthenticated NSPI RPC binds.yml ├── Computers without Windows LAPS.yml ├── Domain Admins logons to non-Domain Controllers.yml ├── Domains not mitigating CVE-2021-42291.yml ├── Domains not verifying UPN and SPN uniqueness.yml ├── Nested groups within Tier Zero High Value.yml ├── Non-default delegation on MicrosoftDNS container.yml ├── Tier Zero computers with unsupported operating systems.yml ├── DCs vulnerable to NTLM relay to LDAP attacks.yml ├── All members of high privileged roles.yml ├── Domains allowing unauthenticated rootDSE searches and binds.yml ├── KRBTGT accounts with passwords not rotated in over 1 year.yml ├── Domains allowing unauthenticated domain enumeration.yml ├── Non-Tier Zero accounts with SID History of Tier Zero accounts.yml ├── ACEs across trusts.yml ├── Domains exempting privileged groups from AdminSDHolder protections.yml ├── Shortest paths from Entra Users to Tier Zero High Value targets.yml ├── Enrollment rights on certificate templates published to Enterprise CA with User Specified SAN enabled.yml ├── Enrollment rights on certificate templates published to Enterprise CA with User Specified SAN enabled (ESC6).yml ├── Enrollment rights on certificate templates published to Enterprise CA with vulnerable HTTP(S) endpoint (ESC8).yml ├── Disabled Tier Zero High Value principals - AD.yml ├── Tier Zero AD principals synchronized with Entra ID.yml ├── Shortest paths from Azure Applications to Tier Zero High Value targets.yml ├── Users with logon scripts stored in a trusted domain.yml ├── All service principals with Microsoft Graph App Role assignments.yml ├── Enrollment rights on published enrollment agent certificate templates.yml ├── Shortest paths to Domain Admins from Kerberoastable users.yml ├── Devices with unsupported operating systems.yml ├── Kerberos-enabled service account member of built-in Admins groups.yml ├── Tier Zero users not member of Protected Users.yml ├── Tier Zero users with passwords not rotated in over 1 year.yml ├── Circular AZ group memberships.yml ├── Compromising permissions on ADCS nodes (ESC5).yml ├── Kerberoastable members of Tier Zero High Value groups.yml ├── Circular AD group memberships.yml ├── Enrollment rights on published ESC1 certificate templates.yml ├── Domains where any user can join a computer to the domain.yml ├── Non-default members in Pre-Windows 2000 Compatible Access.yml ├── Computer owners who can obtain LAPS passwords.yml ├── Tier Zero computers with passwords older than the default maximum password age.yml ├── Accounts related to AAD Entra Connect.yml ├── On-Prem Users synced to Entra Users with Azure RM Roles (direct).yml ├── Principal with SPN keyword.yml ├── Tier Zero accounts that can be delegated.yml ├── All paths crossing a specific trust.yml ├── Shortest paths from Owned objects to Tier Zero.yml ├── Kerberoastable users with most admin privileges.yml ├── Shortest paths to privileged roles.yml ├── Non-Tier Zero account with excessive control.yml ├── Large default group added to computer-local group.yml ├── On-Prem Users synced to Entra Users with Azure RM Roles (group delegated).yml ├── Accounts with weak password storage encryption.yml ├── Usage of built-in domain Administrator account.yml ├── Object name conflict.yml ├── Overprivileged Microsoft Entra Connect accounts.yml ├── Large default groups with outbound control of OUs.yml ├── Domain controllers with UPN certificate mapping enabled.yml ├── Users with non-default Primary Group membership.yml ├── Non-Tier Zero principals with BadSuccessor rights (no prerequisites check).yml ├── All incoming and local paths for a specific computer.yml ├── Tier Zero High Value enabled users not requiring smart card authentication.yml ├── Entra ID SSO accounts not rolling Kerberos decryption key.yml ├── Foreign Service Principals With an EntraID Admin Role.yml ├── Foreign Service Principals With Group Memberships.yml ├── Enrollment rights on published ESC2 certificate templates.yml ├── Enabled users inactive for 180 days.yml ├── All Operators.yml ├── All privileged Azure Service Principals.yml ├── Microsoft Entra Connect accounts with passwords not rotated in over 90 days.yml ├── Computers with non-default Primary Group membership.yml ├── Enrollment rights on published ESC15 certificate templates.yml ├── Enabled computers inactive for 180 days.yml ├── Kerberos-enabled service accounts without AES encryption support.yml ├── All GPOs applied to a specific computer.yml ├── Collection health of DC Registry Data.yml ├── Foreign Service Principals With any Abusable MS Graph App Role Assignment.yml ├── Tier Zero accounts not members of Denied RODC Password Replication Group.yml ├── All ADCS ESC privilege escalation edges.yml ├── Non-Tier Zero principals with control of AdminSDHolder.yml ├── Enabled Tier Zero High Value principals inactive for 60 days.yml ├── Non-Tier Zero principals with BadSuccessor rights (with prerequisites check).yml ├── Tier Zero users with email.yml ├── Potential GPO 'Apply' misconfiguration.yml ├── Collection health of CA Registry Data.yml ├── AdminSDHolder protected Accounts and Groups.yml ├── Enabled computers inactive for 180 days - MSSQL Failover Cluster.yml ├── Large default groups with outbound control.yml ├── Computers with passwords older than the default maximum password age.yml ├── Direct Principal Rights Assignment.yml ├── Non-Tier Zero account with 'Admin Count' flag.yml └── Uncommon permission on containers.yml ├── LICENSE ├── utilities └── python │ ├── convert.py │ └── schema.py ├── docs ├── query-structure.yml └── security-assessment-mapping.md ├── .gitignore ├── CODE_OF_CONDUCT.md └── README.md /tests/grammar/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /queries.specterops.io.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpecterOps/BloodHoundQueryLibrary/HEAD/queries.specterops.io.png -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | /.github/workflows/ @d3vzer0 2 | /.github/actions/ @d3vzer0 3 | **/action.yml @d3vzer0 4 | **/action.yaml @d3vzer0 -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pytest==8.3.5 2 | PyYAML==6.0.2 3 | Jinja2==3.1.6 4 | pydantic==2.11.4 5 | typer==0.15.4 6 | antlr4-python3-runtime==4.13.2 -------------------------------------------------------------------------------- /queries/Map OU structure.yml: -------------------------------------------------------------------------------- 1 | name: Map OU structure 2 | guid: 8f14084b-5065-43d8-865a-a6ac52da25d1 3 | prebuilt: true 4 | platforms: Active Directory 5 | category: Domain Information 6 | description: 7 | query: |- 8 | MATCH p = (:Domain)-[:Contains*1..]->(:OU) 9 | RETURN p 10 | LIMIT 1000 11 | revision: 1 12 | resources: 13 | acknowledgements: 14 | 15 | -------------------------------------------------------------------------------- /queries/Map domain trusts.yml: -------------------------------------------------------------------------------- 1 | name: Map domain trusts 2 | guid: 268d3d26-5bc2-4820-a6ed-09d20f3d5413 3 | prebuilt: true 4 | platforms: Active Directory 5 | category: Domain Information 6 | description: 7 | query: |- 8 | MATCH p = (:Domain)-[:SameForestTrust|CrossForestTrust]->(:Domain) 9 | RETURN p 10 | LIMIT 1000 11 | revision: 1 12 | resources: 13 | acknowledgements: 14 | 15 | -------------------------------------------------------------------------------- /queries/ESC8-vulnerable Enterprise CAs.yml: -------------------------------------------------------------------------------- 1 | name: ESC8-vulnerable Enterprise CAs 2 | guid: 60881923-296c-4702-adf7-a4f059dc9bb8 3 | prebuilt: true 4 | platforms: Active Directory 5 | category: NTLM Relay Attacks 6 | description: 7 | query: |- 8 | MATCH (n:EnterpriseCA) 9 | WHERE n.hasvulnerableendpoint=true 10 | RETURN n 11 | revision: 1 12 | resources: 13 | acknowledgements: 14 | 15 | -------------------------------------------------------------------------------- /queries/Entra Users with Entra Admin Role approval (direct).yml: -------------------------------------------------------------------------------- 1 | name: Entra Users with Entra Admin Role approval (direct) 2 | guid: 74d7993c-24af-4df7-8402-5c6fb22d088c 3 | prebuilt: true 4 | platforms: Azure 5 | category: General 6 | description: 7 | query: |- 8 | MATCH p = (:AZUser)-[:AZRoleApprover]->(:AZRole) 9 | RETURN p LIMIT 100 10 | revision: 1 11 | resources: 12 | acknowledgements: 13 | -------------------------------------------------------------------------------- /queries/Entra Users with Entra Admin Role direct eligibility.yml: -------------------------------------------------------------------------------- 1 | name: Entra Users with Entra Admin Role direct eligibility 2 | guid: b87899ce-3a51-401a-ae39-ef271b566e3d 3 | prebuilt: true 4 | platforms: Azure 5 | category: General 6 | description: 7 | query: |- 8 | MATCH p = (:AZUser)-[:AZRoleEligible]->(:AZRole) 9 | RETURN p LIMIT 100 10 | revision: 1 11 | resources: 12 | acknowledgements: 13 | -------------------------------------------------------------------------------- /queries/Accounts with SID History.yml: -------------------------------------------------------------------------------- 1 | name: Accounts with SID History 2 | guid: 8172d52c-a975-49bd-9180-5b6efc59c9ab 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Active Directory Hygiene 6 | description: 7 | query: |- 8 | MATCH p=(:Base)-[:HasSIDHistory]->(:Base) 9 | RETURN p 10 | revision: 1 11 | resources: 12 | acknowledgements: Martin Sohn Christensen, @martinsohndk 13 | 14 | -------------------------------------------------------------------------------- /queries/Domain migration groups.yml: -------------------------------------------------------------------------------- 1 | name: Domain migration groups 2 | guid: f39c4953-ae92-4d67-bb50-eb1a161d4d3f 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Domain Information 6 | description: 7 | query: |- 8 | MATCH (n:Group) 9 | WHERE n.name CONTAINS "$$$@" 10 | RETURN n 11 | revision: 1 12 | resources: 13 | acknowledgements: Martin Sohn Christensen, @martinsohndk 14 | 15 | -------------------------------------------------------------------------------- /queries/Computers not requiring inbound SMB signing.yml: -------------------------------------------------------------------------------- 1 | name: Computers not requiring inbound SMB signing 2 | guid: 6b1fcfb6-b010-41a2-9d31-f9872fe994ff 3 | prebuilt: true 4 | platforms: Active Directory 5 | category: NTLM Relay Attacks 6 | description: 7 | query: |- 8 | MATCH (n:Computer) 9 | WHERE n.smbsigning = False 10 | RETURN n 11 | revision: 1 12 | resources: 13 | acknowledgements: 14 | 15 | -------------------------------------------------------------------------------- /queries/Computers with the WebClient running.yml: -------------------------------------------------------------------------------- 1 | name: Computers with the WebClient running 2 | guid: 51107ad1-f0bc-43d3-a561-5cee471ca196 3 | prebuilt: true 4 | platforms: Active Directory 5 | category: NTLM Relay Attacks 6 | description: 7 | query: |- 8 | MATCH (c:Computer) 9 | WHERE c.webclientrunning = True 10 | RETURN c LIMIT 1000 11 | revision: 1 12 | resources: 13 | acknowledgements: 14 | 15 | -------------------------------------------------------------------------------- /queries/All Global Administrators.yml: -------------------------------------------------------------------------------- 1 | name: All Global Administrators 2 | guid: 94d7d765-6837-4eb8-aa33-e1c9ef262cdc 3 | prebuilt: true 4 | platforms: Azure 5 | category: General 6 | description: 7 | query: |- 8 | MATCH p=(:AZBase)-[:AZHasRole*1..]->(t:AZRole) 9 | WHERE t.name =~ '(?i)Global Administrator.*' 10 | RETURN p 11 | LIMIT 1000 12 | revision: 2 13 | resources: 14 | acknowledgements: 15 | 16 | -------------------------------------------------------------------------------- /queries/AdminSDHolder to protected objects relationship.yml: -------------------------------------------------------------------------------- 1 | name: AdminSDHolder to protected objects relationship 2 | guid: c751f95c-8bb0-4be4-b027-84f5709c91d2 3 | prebuilt: true 4 | platforms: Active Directory 5 | category: Active Directory Hygiene 6 | description: 7 | query: |- 8 | MATCH p=(n)-[:ProtectAdminGroups]->(m) 9 | RETURN p 10 | LIMIT 1000 11 | revision: 1 12 | resources: 13 | acknowledgements: 14 | -------------------------------------------------------------------------------- /queries/All DNSAdmins.yml: -------------------------------------------------------------------------------- 1 | name: All DNSAdmins 2 | guid: 183fb320-f3ae-4ab3-a090-3f9a7db692e1 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Domain Information 6 | description: 7 | query: |- 8 | MATCH p=(n:Base)-[:MemberOf]->(g:Group) 9 | WHERE n.name STARTS WITH "DNSADMINS@" 10 | RETURN p 11 | revision: 1 12 | resources: 13 | acknowledgements: Martin Sohn Christensen, @martinsohndk 14 | 15 | -------------------------------------------------------------------------------- /queries/Principals with DCSync privileges.yml: -------------------------------------------------------------------------------- 1 | name: Principals with DCSync privileges 2 | guid: 6e9beb8a-ad14-43de-bda1-644d174a5906 3 | prebuilt: true 4 | platforms: Active Directory 5 | category: Dangerous Privileges 6 | description: 7 | query: |- 8 | MATCH p=(:Base)-[:DCSync|AllExtendedRights|GenericAll]->(:Domain) 9 | RETURN p 10 | LIMIT 1000 11 | revision: 1 12 | resources: 13 | acknowledgements: 14 | 15 | -------------------------------------------------------------------------------- /queries/All Domain Admins.yml: -------------------------------------------------------------------------------- 1 | name: All Domain Admins 2 | guid: 0596dba7-9180-49a0-aa54-00243240037c 3 | prebuilt: true 4 | platforms: Active Directory 5 | category: Domain Information 6 | description: 7 | query: |- 8 | MATCH p = (t:Group)<-[:MemberOf*1..]-(a) 9 | WHERE (a:User or a:Computer) and t.objectid ENDS WITH '-512' 10 | RETURN p 11 | LIMIT 1000 12 | revision: 1 13 | resources: 14 | acknowledgements: 15 | 16 | -------------------------------------------------------------------------------- /queries/CA administrators and CA managers.yml: -------------------------------------------------------------------------------- 1 | name: CA administrators and CA managers 2 | guid: fd35e3d8-0c74-4b5a-a847-c0dd1f1c9f19 3 | prebuilt: true 4 | platforms: Active Directory 5 | category: Active Directory Certificate Services 6 | description: 7 | query: |- 8 | MATCH p = (:Base)-[:ManageCertificates|ManageCA]->(:EnterpriseCA) 9 | RETURN p 10 | LIMIT 1000 11 | revision: 1 12 | resources: 13 | acknowledgements: 14 | 15 | -------------------------------------------------------------------------------- /queries/CA Administrators and CA Managers (ESC7).yml: -------------------------------------------------------------------------------- 1 | name: CA Administrators and CA Managers (ESC7) 2 | guid: 77a708b8-962e-4c3d-ad70-e994126aaeba 3 | prebuilt: true 4 | platforms: Active Directory 5 | category: Active Directory Certificate Services 6 | description: 7 | query: |- 8 | MATCH p = (:Base)-[:ManageCertificates|ManageCA]->(:EnterpriseCA) 9 | RETURN p 10 | LIMIT 1000 11 | revision: 1 12 | resources: 13 | acknowledgements: 14 | -------------------------------------------------------------------------------- /queries/Entra Users with Entra Admin Role approval (group delegated).yml: -------------------------------------------------------------------------------- 1 | name: Entra Users with Entra Admin Role approval (group delegated) 2 | guid: b70a6512-21e1-4d6e-926a-fba44646085d 3 | prebuilt: true 4 | platforms: Azure 5 | category: General 6 | description: 7 | query: |- 8 | MATCH p = (:AZUser)-[:AZMemberOf]->(:AZGroup)-[:AZRoleApprover]->(:AZRole) 9 | RETURN p LIMIT 100 10 | revision: 1 11 | resources: 12 | acknowledgements: 13 | -------------------------------------------------------------------------------- /queries/Entra Users with Entra Admin Roles group delegated eligibility.yml: -------------------------------------------------------------------------------- 1 | name: Entra Users with Entra Admin Roles group delegated eligibility 2 | guid: 2e36c81b-25ed-40ba-afec-5f5f6443e095 3 | prebuilt: true 4 | platforms: Azure 5 | category: General 6 | description: 7 | query: |- 8 | MATCH p = (:AZUser)-[:AZMemberOf]->(:AZGroup)-[:AZRoleEligible]->(:AZRole) 9 | RETURN p LIMIT 100 10 | revision: 1 11 | resources: 12 | acknowledgements: 13 | -------------------------------------------------------------------------------- /queries/PKI hierarchy.yml: -------------------------------------------------------------------------------- 1 | name: PKI hierarchy 2 | guid: 928acc23-ee4c-40a5-bde7-64c05cc1491d 3 | prebuilt: true 4 | platforms: Active Directory 5 | category: Active Directory Certificate Services 6 | description: 7 | query: |- 8 | MATCH p=()-[:HostsCAService|IssuedSignedBy|EnterpriseCAFor|RootCAFor|TrustedForNTAuth|NTAuthStoreFor*..]->(:Domain) 9 | RETURN p 10 | LIMIT 1000 11 | revision: 1 12 | resources: 13 | acknowledgements: 14 | 15 | -------------------------------------------------------------------------------- /queries/Principals with DES-only Kerberos authentication.yml: -------------------------------------------------------------------------------- 1 | name: Principals with DES-only Kerberos authentication 2 | guid: d03ea1ef-70f0-439b-b1ef-d7f94ceb2af3 3 | prebuilt: true 4 | platforms: Active Directory 5 | category: Active Directory Hygiene 6 | description: 7 | query: |- 8 | MATCH (n:Base) 9 | WHERE n.enabled = true 10 | AND n.usedeskeyonly = true 11 | RETURN n 12 | revision: 1 13 | resources: 14 | acknowledgements: 15 | 16 | -------------------------------------------------------------------------------- /queries/Users which do not require password to authenticate.yml: -------------------------------------------------------------------------------- 1 | name: Users which do not require password to authenticate 2 | guid: 23bdc2ad-6739-4b2b-85d3-258e3f424eb2 3 | prebuilt: true 4 | platforms: Active Directory 5 | category: Active Directory Hygiene 6 | description: 7 | query: |- 8 | MATCH (u:User) 9 | WHERE u.passwordnotreqd = true 10 | RETURN u 11 | LIMIT 100 12 | revision: 1 13 | resources: 14 | acknowledgements: 15 | 16 | -------------------------------------------------------------------------------- /queries/All Azure VMs with a tied Managed Identity.yml: -------------------------------------------------------------------------------- 1 | name: All Azure VMs with a tied Managed Identity 2 | guid: 3ceca01a-226e-4e61-8692-a4b4611f2af0 3 | prebuilt: false 4 | platforms: 5 | - Azure 6 | category: General 7 | description: Return all Azure VMs with a tied Managed Identity. 8 | query: |- 9 | MATCH p=(:AZVM)-[:AZManagedIdentity]->(n) 10 | RETURN p 11 | revision: 1 12 | resources: - 13 | acknowledgements: Daniel Scheidt, @theluemmel 14 | -------------------------------------------------------------------------------- /queries/Computers with membership in Protected Users.yml: -------------------------------------------------------------------------------- 1 | name: Computers with membership in Protected Users 2 | guid: a26372f4-2e92-49f6-8993-6657fbc1569a 3 | prebuilt: true 4 | platforms: Active Directory 5 | category: NTLM Relay Attacks 6 | description: 7 | query: |- 8 | MATCH p = (:Base)-[:MemberOf*1..]->(g:Group) 9 | WHERE g.objectid ENDS WITH '-525' 10 | RETURN p LIMIT 1000 11 | revision: 2 12 | resources: 13 | acknowledgements: 14 | 15 | -------------------------------------------------------------------------------- /queries/Computers with the outgoing NTLM setting set to Deny all.yml: -------------------------------------------------------------------------------- 1 | name: Computers with the outgoing NTLM setting set to Deny all 2 | guid: a9ddca74-feeb-4dbf-8b0f-de08b3cfa8a6 3 | prebuilt: true 4 | platforms: Active Directory 5 | category: NTLM Relay Attacks 6 | description: 7 | query: |- 8 | MATCH (c:Computer) 9 | WHERE c.restrictoutboundntlm = True 10 | RETURN c LIMIT 1000 11 | revision: 1 12 | resources: 13 | acknowledgements: 14 | 15 | -------------------------------------------------------------------------------- /queries/Location of AdminSDHolder Protected objects.yml: -------------------------------------------------------------------------------- 1 | name: Location of AdminSDHolder Protected objects 2 | guid: 3408ccaf-1f42-4c10-b09a-e986661f84d7 3 | prebuilt: true 4 | platforms: Active Directory 5 | category: Domain Information 6 | description: 7 | query: |- 8 | MATCH p = (n:Base)<-[:Contains*1..]-(:Domain) 9 | WHERE n.adminsdholderprotected = True 10 | RETURN p 11 | LIMIT 1000 12 | revision: 1 13 | resources: 14 | acknowledgements: 15 | -------------------------------------------------------------------------------- /queries/Principals with passwords stored using reversible encryption.yml: -------------------------------------------------------------------------------- 1 | name: Principals with passwords stored using reversible encryption 2 | guid: ab900835-b2b8-4674-87b4-8b5141e80439 3 | prebuilt: true 4 | platforms: Active Directory 5 | category: Active Directory Hygiene 6 | description: 7 | query: |- 8 | MATCH (n:Base) 9 | WHERE n.encryptedtextpwdallowed = true 10 | RETURN n 11 | revision: 1 12 | resources: 13 | acknowledgements: 14 | 15 | -------------------------------------------------------------------------------- /queries/Principals with foreign domain group membership.yml: -------------------------------------------------------------------------------- 1 | name: Principals with foreign domain group membership 2 | guid: 8fb3214a-5a75-4ecd-b293-c121abd94b4b 3 | prebuilt: true 4 | platforms: Active Directory 5 | category: Dangerous Privileges 6 | description: 7 | query: |- 8 | MATCH p=(s:Base)-[:MemberOf]->(t:Group) 9 | WHERE s.domainsid<>t.domainsid 10 | RETURN p 11 | LIMIT 1000 12 | revision: 1 13 | resources: 14 | acknowledgements: 15 | 16 | -------------------------------------------------------------------------------- /queries/Shortest paths to Domain Admins.yml: -------------------------------------------------------------------------------- 1 | name: Shortest paths to Domain Admins 2 | guid: f40cb34b-5ec7-44bc-9aa8-a200a4a41f22 3 | prebuilt: true 4 | platforms: Active Directory 5 | category: Shortest Paths 6 | description: 7 | query: |- 8 | MATCH p=shortestPath((t:Group)<-[:AD_ATTACK_PATHS*1..]-(s:Base)) 9 | WHERE t.objectid ENDS WITH '-512' AND s<>t 10 | RETURN p 11 | LIMIT 1000 12 | revision: 2 13 | resources: 14 | acknowledgements: 15 | 16 | -------------------------------------------------------------------------------- /queries/Enabled built-in guest user accounts.yml: -------------------------------------------------------------------------------- 1 | name: Enabled built-in guest user accounts 2 | guid: bb0f620d-6a55-4413-ac74-4c82905e8598 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Active Directory Hygiene 6 | description: 7 | query: |- 8 | MATCH (n:User) 9 | WHERE n.objectid ENDS WITH "-501" 10 | AND n.enabled = true 11 | RETURN n 12 | revision: 1 13 | resources: 14 | acknowledgements: Martin Sohn Christensen, @martinsohndk 15 | 16 | -------------------------------------------------------------------------------- /queries/All Schema Admins.yml: -------------------------------------------------------------------------------- 1 | name: All Schema Admins 2 | guid: 76d8e61d-7a86-40ff-8a85-fd37f1e2563f 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Domain Information 6 | description: 7 | query: |- 8 | MATCH p=(n:Base)-[:MemberOf*1..]->(m:Group) 9 | WHERE (n:User OR n:Computer) 10 | AND m.objectid ENDS WITH "-518" // Schema Admins 11 | RETURN p 12 | revision: 1 13 | resources: 14 | acknowledgements: Martin Sohn Christensen, @martinsohndk 15 | 16 | -------------------------------------------------------------------------------- /queries/Computers where Domain Users are local administrators.yml: -------------------------------------------------------------------------------- 1 | name: Computers where Domain Users are local administrators 2 | guid: d43a7bdc-33c6-4a39-a3bb-24115749e595 3 | prebuilt: true 4 | platforms: Active Directory 5 | category: Dangerous Privileges 6 | description: 7 | query: |- 8 | MATCH p=(s:Group)-[:AdminTo]->(:Computer) 9 | WHERE s.objectid ENDS WITH '-513' 10 | RETURN p 11 | LIMIT 1000 12 | revision: 1 13 | resources: 14 | acknowledgements: 15 | 16 | -------------------------------------------------------------------------------- /queries/Users with non-expiring passwords.yml: -------------------------------------------------------------------------------- 1 | name: Users with non-expiring passwords 2 | guid: 212c2a98-53d9-4dfa-b177-42c601452dd1 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Active Directory Hygiene 6 | description: 7 | query: |- 8 | MATCH (u:User) 9 | WHERE u.enabled = true 10 | AND u.pwdneverexpires = true 11 | RETURN u 12 | LIMIT 100 13 | revision: 1 14 | resources: 15 | acknowledgements: Martin Sohn Christensen, @martinsohndk 16 | 17 | -------------------------------------------------------------------------------- /queries/Dangerous privileges for Domain Users groups.yml: -------------------------------------------------------------------------------- 1 | name: Dangerous privileges for Domain Users groups 2 | guid: 9b8b9c18-f8c6-4c54-a20f-de0f7a7edbe0 3 | prebuilt: true 4 | platforms: Active Directory 5 | category: Dangerous Privileges 6 | description: 7 | query: |- 8 | MATCH p=(s:Group)-[r:AD_ATTACK_PATHS]->(:Base) 9 | WHERE s.objectid ENDS WITH '-513' 10 | AND NOT r:MemberOf 11 | RETURN p 12 | LIMIT 1000 13 | revision: 3 14 | resources: 15 | acknowledgements: 16 | 17 | -------------------------------------------------------------------------------- /queries/Disabled Tier Zero High Value principals - AZ.yml: -------------------------------------------------------------------------------- 1 | name: Disabled Tier Zero / High Value principals 2 | guid: 860d5c2d-84fe-4c85-80de-e0a9badbd0e7 3 | prebuilt: true 4 | platforms: Azure 5 | category: Azure Hygiene 6 | description: 7 | query: |- 8 | MATCH (n:AZBase) 9 | WHERE ((n:Tag_Tier_Zero) OR COALESCE(n.system_tags, '') CONTAINS 'admin_tier_0') 10 | AND n.enabled = false 11 | RETURN n 12 | LIMIT 100 13 | revision: 1 14 | resources: 15 | acknowledgements: 16 | 17 | -------------------------------------------------------------------------------- /queries/Locations of Owned objects - AZ.yml: -------------------------------------------------------------------------------- 1 | name: Locations of Owned objects 2 | guid: 350b8b8a-ea4c-44f3-874b-c9316de6c41b 3 | prebuilt: false 4 | platforms: Azure 5 | category: General 6 | description: 7 | query: |- 8 | MATCH p = (t:AZBase)<-[:AZContains*1..]-(:AZTenant) 9 | WHERE ((t:Tag_Owned) OR COALESCE(t.system_tags, '') CONTAINS 'owned') 10 | RETURN p 11 | LIMIT 1000 12 | revision: 1 13 | resources: 14 | acknowledgements: Martin Sohn Christensen, @martinsohndk 15 | 16 | -------------------------------------------------------------------------------- /queries/Servers where Domain Users can RDP.yml: -------------------------------------------------------------------------------- 1 | name: Servers where Domain Users can RDP 2 | guid: b9a330ae-1d89-44d4-8f74-9ca18e93eb92 3 | prebuilt: true 4 | platforms: Active Directory 5 | category: Dangerous Privileges 6 | description: 7 | query: |- 8 | MATCH p=(s:Group)-[:CanRDP]->(t:Computer) 9 | WHERE s.objectid ENDS WITH '-513' AND toUpper(t.operatingsystem) CONTAINS 'SERVER' 10 | RETURN p 11 | LIMIT 1000 12 | revision: 1 13 | resources: 14 | acknowledgements: 15 | 16 | -------------------------------------------------------------------------------- /queries/AS-REP Roastable users (DontReqPreAuth).yml: -------------------------------------------------------------------------------- 1 | name: AS-REP Roastable users (DontReqPreAuth) 2 | guid: 2570e359-dec1-419d-b0dc-a204bd64ee42 3 | prebuilt: true 4 | platforms: Active Directory 5 | category: Kerberos Interaction 6 | description: 7 | query: |- 8 | MATCH (u:User) 9 | WHERE u.dontreqpreauth = true 10 | AND u.enabled = true 11 | RETURN u 12 | LIMIT 100 13 | revision: 1 14 | resources: https://attack.mitre.org/techniques/T1558/004/ 15 | acknowledgements: 16 | 17 | -------------------------------------------------------------------------------- /queries/Accounts with SID History to a same-domain account.yml: -------------------------------------------------------------------------------- 1 | name: Accounts with SID History to a same-domain account 2 | guid: 275d2d58-0cad-4cad-8103-e0874cece666 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Dangerous Privileges 6 | description: 7 | query: |- 8 | MATCH p=(n:Base)-[:HasSIDHistory]->(m:Base) 9 | WHERE n.domainsid = m.domainsid 10 | RETURN p 11 | revision: 1 12 | resources: 13 | acknowledgements: Martin Sohn Christensen, @martinsohndk 14 | 15 | -------------------------------------------------------------------------------- /queries/Computers with unsupported operating systems.yml: -------------------------------------------------------------------------------- 1 | name: Computers with unsupported operating systems 2 | guid: d06d3b14-0318-4fa9-9639-4b79ccaf3c2c 3 | prebuilt: true 4 | platforms: Active Directory 5 | category: Active Directory Hygiene 6 | description: 7 | query: |- 8 | MATCH (c:Computer) 9 | WHERE c.operatingsystem =~ '(?i).*Windows.* (2000|2003|2008|2012|xp|vista|7|8|me|nt).*' 10 | RETURN c 11 | LIMIT 100 12 | revision: 1 13 | resources: 14 | acknowledgements: 15 | 16 | -------------------------------------------------------------------------------- /queries/Members of Allowed RODC Password Replication Group.yml: -------------------------------------------------------------------------------- 1 | name: Members of Allowed RODC Password Replication Group 2 | guid: 19fc5acd-e30a-4038-a5b5-2e0494f93373 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Domain Information 6 | description: 7 | query: |- 8 | MATCH p=(:Base)-[:MemberOf*1..]->(m:Group) 9 | WHERE m.objectid ENDS WITH "-571" 10 | RETURN p 11 | revision: 2 12 | resources: 13 | acknowledgements: Martin Sohn Christensen, @martinsohndk 14 | 15 | -------------------------------------------------------------------------------- /queries/Synced Entra Users with Entra Admin Role approval (direct).yml: -------------------------------------------------------------------------------- 1 | name: Synced Entra Users with Entra Admin Role approval (direct) 2 | guid: 1bfe2d75-0a51-4dbe-abc6-178cc0e4476f 3 | prebuilt: true 4 | platforms: 5 | - Active Directory 6 | - Azure 7 | category: Cross Platform Attack Paths 8 | description: 9 | query: |- 10 | MATCH p = (:User)-[:SyncedToEntraUser]->(:AZUser)-[:AZRoleApprover]->(:AZRole) 11 | RETURN p LIMIT 100 12 | revision: 1 13 | resources: 14 | acknowledgements: 15 | -------------------------------------------------------------------------------- /queries/Synced Entra Users with Entra Admin Role direct eligibility.yml: -------------------------------------------------------------------------------- 1 | name: Synced Entra Users with Entra Admin Role direct eligibility 2 | guid: ea82e359-725c-4881-83e9-35007e859cf5 3 | prebuilt: true 4 | platforms: 5 | - Active Directory 6 | - Azure 7 | category: Cross Platform Attack Paths 8 | description: 9 | query: |- 10 | MATCH p = (:User)-[:SyncedToEntraUser]->(:AZUser)-[:AZRoleEligible]->(:AZRole) 11 | RETURN p LIMIT 100 12 | revision: 1 13 | resources: 14 | acknowledgements: 15 | -------------------------------------------------------------------------------- /queries/All service principals with Microsoft Graph privilege to grant arbitrary App Roles.yml: -------------------------------------------------------------------------------- 1 | name: All service principals with Microsoft Graph privilege to grant arbitrary App Roles 2 | guid: e6d6b5da-89da-4514-a409-2d6e368397da 3 | prebuilt: true 4 | platforms: Azure 5 | category: Microsoft Graph 6 | description: 7 | query: |- 8 | MATCH p=(:AZServicePrincipal)-[:AZMGGrantAppRoles]->(:AZTenant) 9 | RETURN p 10 | LIMIT 1000 11 | revision: 1 12 | resources: 13 | acknowledgements: 14 | 15 | -------------------------------------------------------------------------------- /queries/Tier Zero High Value external Entra ID users.yml: -------------------------------------------------------------------------------- 1 | name: Tier Zero / High Value external Entra ID users 2 | guid: 20e07417-d286-4dca-a962-568f2b262f65 3 | prebuilt: true 4 | platforms: Azure 5 | category: Azure Hygiene 6 | description: 7 | query: |- 8 | MATCH (n:AZUser) 9 | WHERE ((n:Tag_Tier_Zero) OR COALESCE(n.system_tags, '') CONTAINS 'admin_tier_0') 10 | AND n.name CONTAINS '#EXT#@' 11 | RETURN n 12 | LIMIT 100 13 | revision: 1 14 | resources: 15 | acknowledgements: 16 | 17 | -------------------------------------------------------------------------------- /queries/Computers where Domain Users can read LAPS passwords.yml: -------------------------------------------------------------------------------- 1 | name: Computers where Domain Users can read LAPS passwords 2 | guid: aa4bfa95-e7b9-4d56-8f35-f34f04d7b6f4 3 | prebuilt: true 4 | platforms: Active Directory 5 | category: Dangerous Privileges 6 | description: 7 | query: |- 8 | MATCH p=(s:Group)-[:AllExtendedRights|ReadLAPSPassword]->(:Computer) 9 | WHERE s.objectid ENDS WITH '-513' 10 | RETURN p 11 | LIMIT 1000 12 | revision: 1 13 | resources: 14 | acknowledgements: 15 | 16 | -------------------------------------------------------------------------------- /queries/Locations of Owned objects - AD.yml: -------------------------------------------------------------------------------- 1 | name: Locations of Owned objects 2 | guid: c88bfab4-3da0-4b36-b71d-7b324ebd2243 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Domain Information 6 | description: 7 | query: |- 8 | MATCH p = (t:Base)<-[:Contains*1..]-(:Domain) 9 | WHERE ((t:Tag_Owned) OR COALESCE(t.system_tags, '') CONTAINS 'owned') 10 | RETURN p 11 | LIMIT 1000 12 | revision: 1 13 | resources: 14 | acknowledgements: Martin Sohn Christensen, @martinsohndk 15 | 16 | -------------------------------------------------------------------------------- /queries/Locations of Tier Zero High Value objects.yml: -------------------------------------------------------------------------------- 1 | name: Locations of Tier Zero / High Value objects 2 | guid: 18a83a17-b451-4343-acfe-7620516e2968 3 | prebuilt: true 4 | platforms: Active Directory 5 | category: Domain Information 6 | description: 7 | query: |- 8 | MATCH p = (t:Base)<-[:Contains*1..]-(:Domain) 9 | WHERE ((t:Tag_Tier_Zero) OR COALESCE(t.system_tags, '') CONTAINS 'admin_tier_0') 10 | RETURN p 11 | LIMIT 1000 12 | revision: 1 13 | resources: 14 | acknowledgements: 15 | 16 | -------------------------------------------------------------------------------- /queries/On-Prem Users synced to Entra Users that Own Entra Objects.yml: -------------------------------------------------------------------------------- 1 | name: On-Prem Users synced to Entra Users that Own Entra Objects 2 | guid: 4baf1026-e64c-4e31-afeb-2090b8090130 3 | prebuilt: true 4 | platforms: 5 | - Active Directory 6 | - Azure 7 | category: Cross Platform Attack Paths 8 | description: 9 | query: |- 10 | MATCH p = (:User)-[:SyncedToEntraUser]->(:AZUser)-[:AZOwns]->(:AZBase) 11 | RETURN p 12 | LIMIT 1000 13 | revision: 1 14 | resources: 15 | acknowledgements: 16 | 17 | -------------------------------------------------------------------------------- /queries/Owners of Azure Applications.yml: -------------------------------------------------------------------------------- 1 | name: Owners of Azure Applications 2 | guid: 3beb1260-61ad-42b5-819f-e1b619d28e22 3 | prebuilt: false 4 | platforms: 5 | - Azure 6 | category: General 7 | description: Return all Owners of Azure Applications to search for possible attack paths. Low privileged users should not be owners of applications. 8 | query: |- 9 | MATCH p = (n)-[r:AZOwns]->(g:AZApp) 10 | RETURN p 11 | revision: 1 12 | resources: - 13 | acknowledgements: Daniel Scheidt, @theluemmel 14 | -------------------------------------------------------------------------------- /queries/Shortest paths from Owned objects.yml: -------------------------------------------------------------------------------- 1 | name: Shortest paths from Owned objects 2 | guid: e370a01d-c129-4f19-b88d-9479cbe00028 3 | prebuilt: true 4 | platforms: Active Directory 5 | category: Shortest Paths 6 | description: 7 | query: |- 8 | MATCH p=shortestPath((s:Base)-[:AD_ATTACK_PATHS*1..]->(t:Base)) 9 | WHERE ((s:Tag_Owned) OR COALESCE(s.system_tags, '') CONTAINS 'owned') 10 | AND s<>t 11 | RETURN p 12 | LIMIT 1000 13 | revision: 3 14 | resources: 15 | acknowledgements: 16 | 17 | -------------------------------------------------------------------------------- /queries/Workstations where Domain Users can RDP.yml: -------------------------------------------------------------------------------- 1 | name: Workstations where Domain Users can RDP 2 | guid: 9486e0e6-2617-4595-b969-cf57ca21fc86 3 | prebuilt: true 4 | platforms: Active Directory 5 | category: Dangerous Privileges 6 | description: 7 | query: |- 8 | MATCH p=(s:Group)-[:CanRDP]->(t:Computer) 9 | WHERE s.objectid ENDS WITH '-513' AND NOT toUpper(t.operatingsystem) CONTAINS 'SERVER' 10 | RETURN p 11 | LIMIT 1000 12 | revision: 1 13 | resources: 14 | acknowledgements: 15 | 16 | -------------------------------------------------------------------------------- /queries/Cross-forest trusts with abusable configuration.yml: -------------------------------------------------------------------------------- 1 | name: Cross-forest trusts with abusable configuration 2 | guid: 5cf1f354-80d4-420e-bc4b-424fabc21a56 3 | prebuilt: true 4 | platforms: Active Directory 5 | category: Active Directory Hygiene 6 | description: 7 | query: |- 8 | MATCH p=(n:Domain)-[:CrossForestTrust|SpoofSIDHistory|AbuseTGTDelegation]-(m:Domain) 9 | WHERE (n)-[:SpoofSIDHistory|AbuseTGTDelegation]-(m) 10 | RETURN p 11 | revision: 1 12 | resources: 13 | acknowledgements: 14 | 15 | -------------------------------------------------------------------------------- /queries/Enrollment rights on published certificate templates.yml: -------------------------------------------------------------------------------- 1 | name: Enrollment rights on published certificate templates 2 | guid: a4ae2e54-aad3-4bfd-a12d-90cb8a9cbc86 3 | prebuilt: true 4 | platforms: Active Directory 5 | category: Active Directory Certificate Services 6 | description: 7 | query: |- 8 | MATCH p = (:Base)-[:Enroll|GenericAll|AllExtendedRights]->(:CertTemplate)-[:PublishedTo]->(:EnterpriseCA) 9 | RETURN p 10 | LIMIT 1000 11 | revision: 1 12 | resources: 13 | acknowledgements: 14 | 15 | -------------------------------------------------------------------------------- /queries/Public Key Services container.yml: -------------------------------------------------------------------------------- 1 | name: Public Key Services container 2 | guid: 07e94492-71aa-4665-ab8c-e7aec25906cd 3 | prebuilt: true 4 | platforms: Active Directory 5 | category: Active Directory Certificate Services 6 | description: 7 | query: |- 8 | MATCH p = (c:Container)-[:Contains*..]->(:Base) 9 | WHERE c.distinguishedname starts with 'CN=PUBLIC KEY SERVICES,CN=SERVICES,CN=CONFIGURATION,DC=' 10 | RETURN p 11 | LIMIT 1000 12 | revision: 1 13 | resources: 14 | acknowledgements: 15 | 16 | -------------------------------------------------------------------------------- /queries/Unresolved SID with outbound control.yml: -------------------------------------------------------------------------------- 1 | name: Unresolved SID with outbound control 2 | guid: 4e8429f9-cba2-41e9-bac6-0c42f96b2c57 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Active Directory Hygiene 6 | description: 7 | query: |- 8 | MATCH p=(n:Base)-[r]->(:Base) 9 | WHERE r.isacl 10 | AND n.name CONTAINS "S-1-5-21-" // Unresolved SID 11 | RETURN p 12 | LIMIT 1000 13 | revision: 1 14 | resources: 15 | acknowledgements: Martin Sohn Christensen, @martinsohndk 16 | 17 | -------------------------------------------------------------------------------- /queries/On-Prem Users synced to Entra Users with Entra Group Membership.yml: -------------------------------------------------------------------------------- 1 | name: On-Prem Users synced to Entra Users with Entra Group Membership 2 | guid: edb575df-2048-4ef0-a0e4-168544a496e9 3 | prebuilt: true 4 | platforms: 5 | - Active Directory 6 | - Azure 7 | category: Cross Platform Attack Paths 8 | description: 9 | query: |- 10 | MATCH p = (:User)-[:SyncedToEntraUser]->(:AZUser)-[:AZMemberOf]->(:AZGroup) 11 | RETURN p 12 | LIMIT 1000 13 | revision: 1 14 | resources: 15 | acknowledgements: 16 | 17 | -------------------------------------------------------------------------------- /queries/Shortest Paths from Azure Users to Azure VMs.yml: -------------------------------------------------------------------------------- 1 | name: Shortest Paths from Azure Users to Azure VMs 2 | guid: 912454f1-75a3-4813-b3df-7bddac0ff00d 3 | prebuilt: false 4 | platforms: 5 | - Azure 6 | category: Shortest Paths 7 | description: Return shortest paths from Azure Users to Azure VMs to check for attack vectors. 8 | query: |- 9 | MATCH p = shortestPath((m:AZUser)-[:AZ_ATTACK_PATHS*..]->(n:AZVM)) 10 | RETURN p 11 | revision: 1 12 | resources: - 13 | acknowledgements: Daniel Scheidt, @theluemmel 14 | -------------------------------------------------------------------------------- /queries/On-Prem Users synced to Entra Users with Entra Admin Roles (direct).yml: -------------------------------------------------------------------------------- 1 | name: On-Prem Users synced to Entra Users with Entra Admin Roles (direct) 2 | guid: de717635-d31f-4fbd-930b-b4dac0f22118 3 | prebuilt: true 4 | platforms: 5 | - Active Directory 6 | - Azure 7 | category: Cross Platform Attack Paths 8 | description: 9 | query: |- 10 | MATCH p = (:User)-[:SyncedToEntraUser]->(:AZUser)-[:AZHasRole]->(:AZRole) 11 | RETURN p 12 | LIMIT 1000 13 | revision: 1 14 | resources: 15 | acknowledgements: 16 | 17 | -------------------------------------------------------------------------------- /queries/Shortest paths to systems trusted for unconstrained delegation.yml: -------------------------------------------------------------------------------- 1 | name: Shortest paths to systems trusted for unconstrained delegation 2 | guid: 16a9e47b-45f8-4514-b409-771bb5186142 3 | prebuilt: true 4 | platforms: Active Directory 5 | category: Shortest Paths 6 | description: 7 | query: |- 8 | MATCH p=shortestPath((s)-[:AD_ATTACK_PATHS*1..]->(t:Computer)) 9 | WHERE t.unconstraineddelegation = true AND s<>t 10 | RETURN p 11 | LIMIT 1000 12 | revision: 2 13 | resources: 14 | acknowledgements: 15 | 16 | -------------------------------------------------------------------------------- /queries/Tier Zero computers not owned by Tier Zero.yml: -------------------------------------------------------------------------------- 1 | name: Tier Zero computers not owned by Tier Zero 2 | guid: 99d29ded-223a-442b-a0e0-f8b5694c6441 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Dangerous Privileges 6 | description: 7 | query: |- 8 | MATCH p=(n:Base)-[:Owns]->(:Computer) 9 | WHERE NOT ((n:Tag_Tier_Zero) OR COALESCE(n.system_tags, '') CONTAINS 'admin_tier_0') 10 | RETURN p 11 | revision: 2 12 | resources: 13 | acknowledgements: Martin Sohn Christensen, @martinsohndk 14 | 15 | -------------------------------------------------------------------------------- /queries/Domains with functional level not the latest version.yml: -------------------------------------------------------------------------------- 1 | name: Domains with functional level not the latest version 2 | guid: 3da9d14a-f1cb-4df7-b3da-8d73ff5c401b 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Active Directory Hygiene 6 | description: 7 | query: |- 8 | MATCH (n:Domain) 9 | WHERE toString(n.functionallevel) IN ['2008','2003','2003 Interim','2000 Mixed/Native'] 10 | RETURN n 11 | revision: 1 12 | resources: 13 | acknowledgements: Martin Sohn Christensen, @martinsohndk 14 | 15 | -------------------------------------------------------------------------------- /queries/Tier Zero principals without AdminSDHolder protection.yml: -------------------------------------------------------------------------------- 1 | name: Tier Zero principals without AdminSDHolder protection 2 | guid: 82ce5e2e-415b-489d-b891-304e8bb25998 3 | prebuilt: true 4 | platforms: Active Directory 5 | category: Active Directory Hygiene 6 | description: 7 | query: |- 8 | MATCH (n:Base) 9 | WHERE ((n:Tag_Tier_Zero) OR COALESCE(n.system_tags, '') CONTAINS 'admin_tier_0') 10 | AND n.adminsdholderprotected = false 11 | RETURN n 12 | LIMIT 500 13 | revision: 1 14 | resources: 15 | acknowledgements: 16 | -------------------------------------------------------------------------------- /queries/Map Azure Management structure.yml: -------------------------------------------------------------------------------- 1 | name: Map Azure Management structure 2 | guid: c1bb109e-e6a4-4c91-864f-f78e1e42615e 3 | prebuilt: false 4 | platforms: Azure 5 | category: General 6 | description: Maps the structure of Azure Management 7 | query: |- 8 | MATCH p = (:AZTenant)-[:AZContains*1..]->(:AZResourceGroup) 9 | RETURN p 10 | LIMIT 1000 11 | revision: 2 12 | resources: https://learn.microsoft.com/en-us/azure/governance/management-groups/overview 13 | acknowledgements: Martin Sohn Christensen, @martinsohndk 14 | 15 | -------------------------------------------------------------------------------- /queries/Synced Entra Users with Entra Admin Role approval (group delegated).yml: -------------------------------------------------------------------------------- 1 | name: Synced Entra Users with Entra Admin Role approval (group delegated) 2 | guid: ead56ecb-fb88-427c-8f39-75e774bb9a0a 3 | prebuilt: true 4 | platforms: 5 | - Active Directory 6 | - Azure 7 | category: Cross Platform Attack Paths 8 | description: 9 | query: |- 10 | MATCH p = (:User)-[:SyncedToEntraUser]->(:AZUser)-[:AZMemberOf]->(:AZGroup)-[:AZRoleApprover]->(:AZRole) 11 | RETURN p LIMIT 100 12 | revision: 1 13 | resources: 14 | acknowledgements: 15 | -------------------------------------------------------------------------------- /queries/Shortest Paths from Azure Users to Azure Keyvaults.yml: -------------------------------------------------------------------------------- 1 | name: Shortest Paths from Azure Users to Azure Keyvaults 2 | guid: 6395428d-2deb-404b-85b5-edbac3a6e05d 3 | prebuilt: false 4 | platforms: 5 | - Azure 6 | category: Shortest Paths 7 | description: Return shortest paths from Azure Users to Azure Keyvaults to check for attack vectors. 8 | query: |- 9 | MATCH p = shortestPath((n:AZUser)-[:AZ_ATTACK_PATHS*..]->(g:AZKeyVault)) 10 | RETURN p 11 | revision: 1 12 | resources: - 13 | acknowledgements: Daniel Scheidt, @theluemmel 14 | -------------------------------------------------------------------------------- /queries/Shortest paths to Azure Subscriptions.yml: -------------------------------------------------------------------------------- 1 | name: Shortest paths to Azure Subscriptions 2 | guid: 4785b305-c101-461c-80fc-3fb3ff67a8ce 3 | prebuilt: true 4 | platforms: Azure 5 | category: Shortest Paths 6 | description: WARNING! MANY-TO-MANY SHORTEST PATH QUERIES USE EXCESSIVE SYSTEM RESOURCES AND TYPICALLY WILL NOT COMPLETE 7 | query: |- 8 | MATCH p=shortestPath((s:AZBase)-[:AZ_ATTACK_PATHS*1..]->(t:AZSubscription)) 9 | WHERE s<>t 10 | RETURN p 11 | LIMIT 1000 12 | revision: 3 13 | resources: 14 | acknowledgements: 15 | 16 | -------------------------------------------------------------------------------- /queries/Shortest paths to Tier Zero High Value targets.yml: -------------------------------------------------------------------------------- 1 | name: Shortest paths to Tier Zero / High Value targets 2 | guid: 237aac58-8641-4703-a9f7-001d69546fd8 3 | prebuilt: true 4 | platforms: Active Directory 5 | category: Shortest Paths 6 | description: 7 | query: |- 8 | MATCH p=shortestPath((s)-[:AD_ATTACK_PATHS*1..]->(t:Base)) 9 | WHERE ((t:Tag_Tier_Zero) OR (COALESCE(t.system_tags, '') CONTAINS 'admin_tier_0')) 10 | AND s<>t 11 | RETURN p 12 | LIMIT 1000 13 | revision: 3 14 | resources: 15 | acknowledgements: 16 | 17 | -------------------------------------------------------------------------------- /queries/Synced Entra Users with Entra Admin Roles group delegated eligibility.yml: -------------------------------------------------------------------------------- 1 | name: Synced Entra Users with Entra Admin Roles group delegated eligibility 2 | guid: bc610e20-e5c0-41f3-9e8e-7378f87a3f71 3 | prebuilt: true 4 | platforms: 5 | - Active Directory 6 | - Azure 7 | category: Cross Platform Attack Paths 8 | description: 9 | query: |- 10 | MATCH p = (:User)-[:SyncedToEntraUser]->(:AZUser)-[:AZMemberOf]->(:AZGroup)-[:AZRoleEligible]->(:AZRole) 11 | RETURN p LIMIT 100 12 | revision: 1 13 | resources: 14 | acknowledgements: 15 | -------------------------------------------------------------------------------- /queries/Enrollment rights on CertTemplates with OIDGroupLink.yml: -------------------------------------------------------------------------------- 1 | name: Enrollment rights on CertTemplates with OIDGroupLink 2 | guid: 140a68eb-d21c-4b75-971f-309225fb2d75 3 | prebuilt: true 4 | platforms: Active Directory 5 | category: Active Directory Certificate Services 6 | description: 7 | query: |- 8 | MATCH p = (:Base)-[:Enroll|GenericAll|AllExtendedRights]->(:CertTemplate)-[:ExtendedByPolicy]->(:IssuancePolicy)-[:OIDGroupLink]->(:Group) 9 | RETURN p 10 | LIMIT 1000 11 | revision: 1 12 | resources: 13 | acknowledgements: 14 | 15 | -------------------------------------------------------------------------------- /queries/Domain Controllers allowing NTLMv1 or LM authentication.yml: -------------------------------------------------------------------------------- 1 | name: Domain Controllers allowing NTLMv1 or LM authentication 2 | guid: 4b42513c-f89d-47ff-8d98-908af49d2b48 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: NTLM Relay Attacks 6 | description: 7 | query: |- 8 | MATCH (dc:Computer) 9 | WHERE dc.isdc = true 10 | AND (dc.lmcompatibilitylevel IS NOT NULL AND NOT dc.lmcompatibilitylevel = 5) 11 | RETURN dc 12 | revision: 1 13 | resources: 14 | acknowledgements: Martin Sohn Christensen, @martinsohndk 15 | 16 | -------------------------------------------------------------------------------- /queries/Tier Zero computers not requiring inbound SMB signing.yml: -------------------------------------------------------------------------------- 1 | name: Tier Zero computers not requiring inbound SMB signing 2 | guid: 13485477-f026-4b1f-906d-4f2e37364ba4 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: NTLM Relay Attacks 6 | description: 7 | query: |- 8 | MATCH (n:Computer) 9 | WHERE n.smbsigning = False 10 | AND ((n:Tag_Tier_Zero) OR COALESCE(n.system_tags, '') CONTAINS 'admin_tier_0') 11 | RETURN n 12 | revision: 1 13 | resources: 14 | acknowledgements: Martin Sohn Christensen, @martinsohndk 15 | 16 | -------------------------------------------------------------------------------- /queries/Entra Users synced from On-Prem Users added to Domain Admins group.yml: -------------------------------------------------------------------------------- 1 | name: Entra Users synced from On-Prem Users added to Domain Admins group 2 | guid: 62722d5f-bd93-4d11-beeb-9be261827e4e 3 | prebuilt: true 4 | platforms: 5 | - Active Directory 6 | - Azure 7 | category: Cross Platform Attack Paths 8 | description: 9 | query: |- 10 | MATCH p = (:AZUser)-[:SyncedToADUser]->(:User)-[:MemberOf]->(t:Group) 11 | WHERE t.objectid ENDS WITH '-512' 12 | RETURN p 13 | LIMIT 1000 14 | revision: 1 15 | resources: 16 | acknowledgements: 17 | 18 | -------------------------------------------------------------------------------- /queries/Tier Zero computers at risk of constrained delegation.yml: -------------------------------------------------------------------------------- 1 | name: Tier Zero computers at risk of constrained delegation 2 | guid: 8641e593-f2f2-48ba-bd45-fbc86e9f632a 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Dangerous Privileges 6 | description: 7 | query: |- 8 | MATCH p = (n:Computer)<-[:AllowedToDelegate]-(:Base) 9 | WHERE ((n:Tag_Tier_Zero) OR COALESCE(n.system_tags, '') CONTAINS 'admin_tier_0') 10 | RETURN p 11 | revision: 1 12 | resources: 13 | acknowledgements: Martin Sohn Christensen, @martinsohndk 14 | 15 | -------------------------------------------------------------------------------- /queries/Tier Zero computers with the WebClient running.yml: -------------------------------------------------------------------------------- 1 | name: Tier Zero computers with the WebClient running 2 | guid: 27a6f917-8ed4-4e2e-9b38-41a4b6de1b14 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Active Directory Hygiene 6 | description: 7 | query: |- 8 | MATCH (c:Computer) 9 | WHERE c.webclientrunning = True 10 | AND ((c:Tag_Tier_Zero) OR COALESCE(c.system_tags, '') CONTAINS 'admin_tier_0') 11 | RETURN c LIMIT 1000 12 | revision: 1 13 | resources: 14 | acknowledgements: Martin Sohn Christensen, @martinsohndk 15 | 16 | -------------------------------------------------------------------------------- /queries/Domains with a minimum default password policy length less than 15 characters.yml: -------------------------------------------------------------------------------- 1 | name: Domains with a minimum default password policy length less than 15 characters 2 | guid: 7d258d2d-a43d-4a90-85d7-71c946ae5fd7 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Active Directory Hygiene 6 | description: 7 | query: |- 8 | MATCH (n:Domain) 9 | WHERE n.minpwdlength < 15 10 | RETURN n 11 | revision: 1 12 | resources: https://pages.nist.gov/800-63-3/sp800-63b.html 13 | acknowledgements: Martin Sohn Christensen, @martinsohndk 14 | 15 | -------------------------------------------------------------------------------- /queries/Non-Tier Zero account with unconstrained delegation.yml: -------------------------------------------------------------------------------- 1 | name: Non-Tier Zero account with unconstrained delegation 2 | guid: e7e9a927-3f34-42c7-b921-d8bcf626011e 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Dangerous Privileges 6 | description: 7 | query: |- 8 | MATCH (n:Base) 9 | WHERE n.unconstraineddelegation = true 10 | AND NOT ((n:Tag_Tier_Zero) OR COALESCE(n.system_tags, '') CONTAINS 'admin_tier_0') 11 | RETURN n 12 | revision: 1 13 | resources: 14 | acknowledgements: Martin Sohn Christensen, @martinsohndk 15 | 16 | -------------------------------------------------------------------------------- /queries/Tier Zero High Value users with non-expiring passwords.yml: -------------------------------------------------------------------------------- 1 | name: Tier Zero / High Value users with non-expiring passwords 2 | guid: 4eca1b69-00a2-48a0-abb3-b94ea647cf6b 3 | prebuilt: true 4 | platforms: Active Directory 5 | category: Active Directory Hygiene 6 | description: 7 | query: |- 8 | MATCH (u:User) 9 | WHERE ((u:Tag_Tier_Zero) OR COALESCE(u.system_tags, '') CONTAINS 'admin_tier_0') AND u.enabled = true 10 | AND u.pwdneverexpires = true 11 | RETURN u 12 | LIMIT 100 13 | revision: 1 14 | resources: 15 | acknowledgements: 16 | 17 | -------------------------------------------------------------------------------- /queries/Users with passwords not rotated in over 1 year.yml: -------------------------------------------------------------------------------- 1 | name: Users with passwords not rotated in over 1 year 2 | guid: be70d1bd-b7eb-40b0-971c-eefc50eca032 3 | prebuilt: true 4 | platforms: Active Directory 5 | category: Active Directory Hygiene 6 | description: 7 | query: |- 8 | WITH 365 as days_since_change 9 | MATCH (u:User) 10 | WHERE u.pwdlastset < (datetime().epochseconds - (days_since_change * 86400)) 11 | AND NOT u.pwdlastset IN [-1.0, 0.0] 12 | RETURN u 13 | LIMIT 100 14 | revision: 1 15 | resources: 16 | acknowledgements: 17 | 18 | -------------------------------------------------------------------------------- /queries/Accounts with SID History to a non-existent domain.yml: -------------------------------------------------------------------------------- 1 | name: Accounts with SID History to a non-existent domain 2 | guid: 2710401a-c4c2-4d2c-9edb-d7625045f2e8 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Active Directory Hygiene 6 | description: 7 | query: |- 8 | MATCH (d:Domain) 9 | WITH collect(d.objectid) AS domainSIDs 10 | MATCH p=(n:Base)-[:HasSIDHistory]->(m:Base) 11 | WHERE NOT n.domainsid IN domainSIDs 12 | RETURN p 13 | revision: 1 14 | resources: 15 | acknowledgements: Martin Sohn Christensen, @martinsohndk 16 | 17 | -------------------------------------------------------------------------------- /queries/Accounts with clear-text password attributes.yml: -------------------------------------------------------------------------------- 1 | name: Accounts with clear-text password attributes 2 | guid: e303498f-e3d4-489d-8a34-b68e187bc4e7 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Active Directory Hygiene 6 | description: 7 | query: |- 8 | MATCH (n:Base) 9 | WHERE n.userpassword IS NOT NULL 10 | OR n.unixpassword IS NOT NULL 11 | OR n.unicodepwd IS NOT NULL 12 | OR n.msSFU30Password IS NOT NULL 13 | RETURN n 14 | revision: 1 15 | resources: 16 | acknowledgements: Martin Sohn Christensen, @martinsohndk 17 | 18 | -------------------------------------------------------------------------------- /queries/All direct Controllers of MS Graph.yml: -------------------------------------------------------------------------------- 1 | name: All direct Controllers of MS Graph 2 | guid: 45f949ca-ab69-43a4-adb2-796f9548beff 3 | prebuilt: false 4 | platforms: 5 | - Azure 6 | category: Microsoft Graph 7 | description: Return all direct Controllers of MS Graph. 8 | query: |- 9 | MATCH p = (n)-[r:AZAddOwner|AZAddSecret|AZAppAdmin|AZCloudAppAdmin|AZMGAddOwner|AZMGAddSecret|AZOwns]->(g:AZServicePrincipal) 10 | WHERE g.displayname = "MICROSOFT GRAPH" 11 | RETURN p 12 | revision: 1 13 | resources: - 14 | acknowledgements: Daniel Scheidt, @theluemmel 15 | -------------------------------------------------------------------------------- /queries/Domain controllers with weak certificate binding enabled.yml: -------------------------------------------------------------------------------- 1 | name: Domain controllers with weak certificate binding enabled 2 | guid: a2444d99-10b5-412d-8fea-4b063cfddd2c 3 | prebuilt: true 4 | platforms: Active Directory 5 | category: Active Directory Certificate Services 6 | description: 7 | query: |- 8 | MATCH p = (s:Computer)-[:DCFor]->(:Domain) 9 | WHERE s.strongcertificatebindingenforcementraw = 0 OR s.strongcertificatebindingenforcementraw = 1 10 | RETURN p 11 | LIMIT 1000 12 | revision: 1 13 | resources: 14 | acknowledgements: 15 | 16 | -------------------------------------------------------------------------------- /queries/Domains with a single-point-of-failure Domain Controller.yml: -------------------------------------------------------------------------------- 1 | name: Domains with a single-point-of-failure Domain Controller 2 | guid: 3359a295-7cfd-491f-976b-c5a68647431c 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Active Directory Hygiene 6 | description: 7 | query: |- 8 | MATCH (n:Group)<-[:MemberOf]-(:Computer) 9 | WHERE n.objectid ENDS WITH '-516' 10 | WITH n, COUNT(n) AS dcCount 11 | WHERE dcCount = 1 12 | RETURN n 13 | revision: 1 14 | resources: 15 | acknowledgements: Martin Sohn Christensen, @martinsohndk 16 | 17 | -------------------------------------------------------------------------------- /queries/Domains without Microsoft LAPS computers.yml: -------------------------------------------------------------------------------- 1 | name: Domains without Microsoft LAPS computers 2 | guid: f9b440b5-732c-4ed3-b6d2-83857db17e1a 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Domain Information 6 | description: 7 | query: |- 8 | MATCH (d:Domain) 9 | OPTIONAL MATCH (c:Computer) 10 | WHERE c.domainsid = d.objectid AND c.haslaps = true 11 | WITH d, COLLECT(c) AS computers 12 | WHERE SIZE(computers) = 0 13 | RETURN d 14 | revision: 1 15 | resources: 16 | acknowledgements: Martin Sohn Christensen, @martinsohndk 17 | 18 | -------------------------------------------------------------------------------- /queries/On-Prem Users synced to Entra Users with Entra Admin Roles (group delegated).yml: -------------------------------------------------------------------------------- 1 | name: On-Prem Users synced to Entra Users with Entra Admin Roles (group delegated) 2 | guid: 609d648f-7fb8-42d3-ad99-626f9ce1f121 3 | prebuilt: true 4 | platforms: 5 | - Active Directory 6 | - Azure 7 | category: Cross Platform Attack Paths 8 | description: 9 | query: |- 10 | MATCH p = (:User)-[:SyncedToEntraUser]->(:AZUser)-[:AZMemberOf]->(:AZGroup)-[:AZHasRole]->(:AZRole) 11 | RETURN p 12 | LIMIT 1000 13 | revision: 1 14 | resources: 15 | acknowledgements: 16 | 17 | -------------------------------------------------------------------------------- /queries/Principals with weak supported Kerberos encryption types.yml: -------------------------------------------------------------------------------- 1 | name: Principals with weak supported Kerberos encryption types 2 | guid: ca329573-2157-41da-ab17-4d122c54b11d 3 | prebuilt: true 4 | platforms: Active Directory 5 | category: Active Directory Hygiene 6 | description: 7 | query: |- 8 | MATCH (u:Base) 9 | WHERE 'DES-CBC-CRC' IN u.supportedencryptiontypes 10 | OR 'DES-CBC-MD5' IN u.supportedencryptiontypes 11 | OR 'RC4-HMAC-MD5' IN u.supportedencryptiontypes 12 | RETURN u 13 | revision: 1 14 | resources: 15 | acknowledgements: 16 | 17 | -------------------------------------------------------------------------------- /queries/All coerce and NTLM relay edges.yml: -------------------------------------------------------------------------------- 1 | name: All coerce and NTLM relay edges 2 | guid: 15c5ff3b-856c-44d1-a731-a8cb72512dd1 3 | prebuilt: true 4 | platforms: Active Directory 5 | category: NTLM Relay Attacks 6 | description: 7 | query: |- 8 | MATCH p = (n:Base)-[:CoerceAndRelayNTLMToLDAP|CoerceAndRelayNTLMToLDAPS|CoerceAndRelayNTLMToADCS|CoerceAndRelayNTLMToSMB]->(:Base) 9 | RETURN p LIMIT 500 10 | revision: 1 11 | resources: https://specterops.io/blog/2025/04/08/the-renaissance-of-ntlm-relay-attacks-everything-you-need-to-know/ 12 | acknowledgements: 13 | 14 | -------------------------------------------------------------------------------- /queries/Non-default permissions on IssuancePolicy nodes.yml: -------------------------------------------------------------------------------- 1 | name: Non-default permissions on IssuancePolicy nodes 2 | guid: b2280665-c91b-448c-8c0f-97d1f38b6f59 3 | prebuilt: true 4 | platforms: Active Directory 5 | category: Active Directory Certificate Services 6 | description: 7 | query: |- 8 | MATCH p = (s:Base)-[:GenericAll|GenericWrite|Owns|WriteOwner|WriteDacl]->(:IssuancePolicy) 9 | WHERE NOT s.objectid ENDS WITH '-512' AND NOT s.objectid ENDS WITH '-519' 10 | RETURN p 11 | LIMIT 1000 12 | revision: 1 13 | resources: 14 | acknowledgements: 15 | 16 | -------------------------------------------------------------------------------- /queries/Owners of Azure Subscriptions.yml: -------------------------------------------------------------------------------- 1 | name: Owners of Azure Subscriptions 2 | guid: bd0daafd-4256-4ce6-9b7f-a9e38509d81c 3 | prebuilt: false 4 | platforms: 5 | - Azure 6 | category: General 7 | description: Return all Owners of Azure Subscriptions to search for possible attack paths. Low privileged users should not be owners of Subscriptions. 8 | query: |- 9 | MATCH p=shortestPath((s:AZBase)-[:AZOwner*1..]->(t:AZSubscription)) 10 | WHERE s<>t 11 | RETURN p 12 | LIMIT 1000 13 | revision: 1 14 | resources: - 15 | acknowledgements: Daniel Scheidt, @theluemmel 16 | -------------------------------------------------------------------------------- /queries/Domains without Protected Users group.yml: -------------------------------------------------------------------------------- 1 | name: Domains without Protected Users group 2 | guid: 8c3e0811-a31b-45b4-a29d-1dce80fa2c5f 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Domain Information 6 | description: 7 | query: |- 8 | MATCH (n:Domain) 9 | WHERE n.collected = true 10 | OPTIONAL MATCH (m:Group) 11 | WHERE m.name ENDS WITH n.name 12 | AND m.objectid ENDS WITH '-525' 13 | WITH n, m 14 | WHERE m IS NULL 15 | RETURN n 16 | revision: 1 17 | resources: 18 | acknowledgements: Martin Sohn Christensen, @martinsohndk 19 | 20 | -------------------------------------------------------------------------------- /queries/Shortest Paths from Owned Azure Users to Azure VMs.yml: -------------------------------------------------------------------------------- 1 | name: Shortest Paths from Owned Azure Users to Azure VMs 2 | guid: bab9fbec-7a46-4c1e-902e-a1b53a454610 3 | prebuilt: false 4 | platforms: 5 | - Azure 6 | category: Shortest Paths 7 | description: Return shortest paths from Owned Azure Users to Azure VMs to check for attack vectors. 8 | query: |- 9 | MATCH p = shortestPath((m:AZUser)-[:AZ_ATTACK_PATHS*..]->(n:AZVM)) 10 | WHERE m.system_tags CONTAINS 'owned' 11 | RETURN p 12 | revision: 1 13 | resources: - 14 | acknowledgements: Daniel Scheidt, @theluemmel 15 | -------------------------------------------------------------------------------- /queries/All Kerberoastable users.yml: -------------------------------------------------------------------------------- 1 | name: All Kerberoastable users 2 | guid: 14ab4eaa-b73b-49c4-b2d1-1e020757c995 3 | prebuilt: true 4 | platforms: Active Directory 5 | category: Kerberos Interaction 6 | description: 7 | query: |- 8 | MATCH (u:User) 9 | WHERE u.hasspn=true 10 | AND u.enabled = true 11 | AND NOT u.objectid ENDS WITH '-502' 12 | AND NOT COALESCE(u.gmsa, false) = true 13 | AND NOT COALESCE(u.msa, false) = true 14 | RETURN u 15 | LIMIT 100 16 | revision: 1 17 | resources: https://attack.mitre.org/techniques/T1558/003/ 18 | acknowledgements: 19 | 20 | -------------------------------------------------------------------------------- /queries/Tier Zero computers at risk of resource-based constrained delegation.yml: -------------------------------------------------------------------------------- 1 | name: Tier Zero computers at risk of resource-based constrained delegation 2 | guid: 4dc97cf4-3c03-4fe6-8a8b-4f665c67e1e5 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Dangerous Privileges 6 | description: 7 | query: |- 8 | MATCH p = (n:Computer)<-[:AllowedToAct]-(:Base) 9 | WHERE ((n:Tag_Tier_Zero) OR COALESCE(n.system_tags, '') CONTAINS 'admin_tier_0') 10 | RETURN p 11 | revision: 1 12 | resources: 13 | acknowledgements: Martin Sohn Christensen, @martinsohndk 14 | 15 | -------------------------------------------------------------------------------- /queries/Smart card accounts with passwords not rotated in over 1 year.yml: -------------------------------------------------------------------------------- 1 | name: Smart card accounts with passwords not rotated in over 1 year 2 | guid: 7e56f2e7-79c3-4f0d-aa3e-14cf3de7ab73 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Active Directory Hygiene 6 | description: 7 | query: |- 8 | MATCH (n:Base) 9 | WHERE n.pwdlastset < (datetime().epochseconds - (365 * 86400)) 10 | AND n.enabled = true 11 | AND n.smartcardrequired = true 12 | RETURN n 13 | revision: 1 14 | resources: 15 | acknowledgements: Martin Sohn Christensen, @martinsohndk 16 | 17 | -------------------------------------------------------------------------------- /queries/AS-REP Roastable Tier Zero users (DontReqPreAuth).yml: -------------------------------------------------------------------------------- 1 | name: AS-REP Roastable Tier Zero users (DontReqPreAuth) 2 | guid: 6d51e4dc-e1ad-477a-b6c6-324f18f03120 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Active Directory Hygiene 6 | description: 7 | query: |- 8 | MATCH (n:Base) 9 | WHERE ((n:Tag_Tier_Zero) OR COALESCE(n.system_tags, '') CONTAINS 'admin_tier_0') 10 | AND n.dontreqpreauth = true 11 | RETURN n 12 | revision: 1 13 | resources: https://attack.mitre.org/techniques/T1558/004/ 14 | acknowledgements: Martin Sohn Christensen, @martinsohndk 15 | 16 | -------------------------------------------------------------------------------- /queries/Domains with List Object mode enabled.yml: -------------------------------------------------------------------------------- 1 | name: Domains with List Object mode enabled 2 | guid: 05e2a94b-5ee6-47ec-b715-3982f30af01b 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Domain Information 6 | description: Checks the fDoListObject flag of dSHeuristics. 7 | query: |- 8 | MATCH (n:Domain) 9 | WHERE n.dsheuristics =~ ".{2}[^0].*" 10 | RETURN n 11 | revision: 1 12 | resources: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/e5899be4-862e-496f-9a38-33950617d2c5 13 | acknowledgements: Martin Sohn Christensen, @martinsohndk 14 | 15 | -------------------------------------------------------------------------------- /queries/Domains with more than 50 Tier Zero accounts.yml: -------------------------------------------------------------------------------- 1 | name: Domains with more than 50 Tier Zero accounts 2 | guid: f046e95a-5f84-4e83-bcda-6e83f3d8e21a 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Active Directory Hygiene 6 | description: 7 | query: |- 8 | MATCH (d:Domain)-[:Contains*1..]->(n:Base) 9 | WHERE ((n:Tag_Tier_Zero) OR COALESCE(n.system_tags, '') CONTAINS 'admin_tier_0') 10 | WITH d, COUNT(n) AS adminCount 11 | WHERE adminCount > 50 12 | RETURN d 13 | revision: 1 14 | resources: 15 | acknowledgements: Martin Sohn Christensen, @martinsohndk 16 | 17 | -------------------------------------------------------------------------------- /queries/Domains with smart card accounts where smart account passwords do not expire.yml: -------------------------------------------------------------------------------- 1 | name: Domains with smart card accounts where smart account passwords do not expire 2 | guid: 97e05e67-5961-4aba-a8e7-fe5f92334035 3 | prebuilt: true 4 | platforms: Active Directory 5 | category: Active Directory Hygiene 6 | description: 7 | query: |- 8 | MATCH (s:Domain)-[:Contains*1..]->(t:Base) 9 | WHERE s.expirepasswordsonsmartcardonlyaccounts = false 10 | AND t.enabled = true 11 | AND t.smartcardrequired = true 12 | RETURN s 13 | revision: 1 14 | resources: 15 | acknowledgements: 16 | 17 | -------------------------------------------------------------------------------- /queries/Domains allowing authenticated domain enumeration.yml: -------------------------------------------------------------------------------- 1 | name: Domains allowing authenticated domain enumeration 2 | guid: 1e1e6fdd-6973-4547-906c-a494b5fbdcba 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Active Directory Hygiene 6 | description: 7 | query: |- 8 | MATCH p=(n:Group)-[:MemberOf]->(m:Group) 9 | WHERE n.objectid ENDS WITH "S-1-5-11" // Authenticated Users 10 | AND m.objectid ENDS WITH "S-1-5-32-554" // Pre-Windows 2000 Compatible Access 11 | RETURN p 12 | revision: 1 13 | resources: 14 | acknowledgements: Martin Sohn Christensen, @martinsohndk 15 | 16 | -------------------------------------------------------------------------------- /queries/Paths from Domain Users to Tier Zero High Value targets.yml: -------------------------------------------------------------------------------- 1 | name: Paths from Domain Users to Tier Zero / High Value targets 2 | guid: 977bec40-565c-40b8-90c8-e3e122c291cd 3 | prebuilt: true 4 | platforms: Active Directory 5 | category: Dangerous Privileges 6 | description: 7 | query: |- 8 | MATCH p=shortestPath((s:Group)-[:AD_ATTACK_PATHS*1..]->(t:Base)) 9 | WHERE s.objectid ENDS WITH '-513' AND s<>t 10 | AND ((t:Tag_Tier_Zero) OR COALESCE(t.system_tags, '') CONTAINS 'admin_tier_0') 11 | RETURN p 12 | LIMIT 1000 13 | revision: 2 14 | resources: 15 | acknowledgements: 16 | 17 | -------------------------------------------------------------------------------- /queries/Shortest Paths from Owned Azure Users to Azure Keyvaults.yml: -------------------------------------------------------------------------------- 1 | name: Shortest Paths from Owned Azure Users to Azure Keyvaults 2 | guid: 53e73ae0-985e-4508-a82e-696d654f9538 3 | prebuilt: false 4 | platforms: 5 | - Azure 6 | category: Shortest Paths 7 | description: Return shortest paths from Owned Azure Users to Azure Keyvaults to check for attack vectors. 8 | query: |- 9 | MATCH p = shortestPath((n:AZUser)-[:AZ_ATTACK_PATHS*..]->(g:AZKeyVault)) 10 | WHERE m.system_tags CONTAINS 'owned' 11 | RETURN p 12 | revision: 1 13 | resources: - 14 | acknowledgements: Daniel Scheidt, @theluemmel 15 | -------------------------------------------------------------------------------- /queries/Foreign principals in Tier Zero High Value targets.yml: -------------------------------------------------------------------------------- 1 | name: Foreign principals in Tier Zero / High Value targets 2 | guid: 95bec736-86ef-4017-8465-9b9b66548b17 3 | prebuilt: true 4 | platforms: Azure 5 | category: Azure Hygiene 6 | description: 7 | query: |- 8 | MATCH (n:AZServicePrincipal) 9 | WHERE ((n:Tag_Tier_Zero) OR COALESCE(n.system_tags, '') CONTAINS 'admin_tier_0') 10 | AND NOT toUpper(n.appownerorganizationid) = toUpper(n.tenantid) 11 | AND n.appownerorganizationid CONTAINS '-' 12 | RETURN n 13 | LIMIT 100 14 | revision: 1 15 | resources: 16 | acknowledgements: 17 | 18 | -------------------------------------------------------------------------------- /queries/Accounts with smart card required in domains where smart account passwords do not expire.yml: -------------------------------------------------------------------------------- 1 | name: Accounts with smart card required in domains where smart account passwords do not expire 2 | guid: bba7985e-f32a-4c62-b1b0-0365bf1455e6 3 | prebuilt: true 4 | platforms: Active Directory 5 | category: Active Directory Hygiene 6 | description: 7 | query: |- 8 | MATCH p=(s:Domain)-[:Contains*1..]->(t:Base) 9 | WHERE s.expirepasswordsonsmartcardonlyaccounts = false 10 | AND t.enabled = true 11 | AND t.smartcardrequired = true 12 | RETURN p 13 | revision: 1 14 | resources: 15 | acknowledgements: 16 | -------------------------------------------------------------------------------- /queries/Domains affected by AdPrep privilege escalation risk.yml: -------------------------------------------------------------------------------- 1 | name: Domains affected by AdPrep privilege escalation risk 2 | guid: 815ff190-f6f3-4757-a516-2f4bf589b705 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Dangerous Privileges 6 | description: 7 | query: |- 8 | MATCH p=(n:Group)-[r:GenericAll]->(m:Domain) 9 | WHERE n.objectid ENDS WITH "-527" // Enterprise Key Admins 10 | AND NOT ((n:Tag_Tier_Zero) OR COALESCE(n.system_tags, '') CONTAINS 'admin_tier_0') 11 | RETURN p 12 | revision: 1 13 | resources: 14 | acknowledgements: Martin Sohn Christensen, @martinsohndk 15 | 16 | -------------------------------------------------------------------------------- /queries/Enrollment rights on published certificate templates with no security extension.yml: -------------------------------------------------------------------------------- 1 | name: Enrollment rights on published certificate templates with no security extension 2 | guid: 0677b70c-4e04-4e89-a6a2-f5764604a6a7 3 | prebuilt: true 4 | platforms: Active Directory 5 | category: Active Directory Certificate Services 6 | description: 7 | query: |- 8 | MATCH p = (:Base)-[:Enroll|GenericAll|AllExtendedRights]->(ct:CertTemplate)-[:PublishedTo]->(:EnterpriseCA) 9 | WHERE ct.nosecurityextension = true 10 | RETURN p 11 | LIMIT 1000 12 | revision: 1 13 | resources: 14 | acknowledgements: 15 | 16 | -------------------------------------------------------------------------------- /queries/Sessions across trusts.yml: -------------------------------------------------------------------------------- 1 | name: Sessions across trusts 2 | guid: aea7ac64-1f51-407b-b0ee-19fd30075794 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Domain Information 6 | description: Users logging on across a trust, the users originate from trusted domains. 7 | query: |- 8 | MATCH p=(trustedDomainPrincipal:Computer)-[r:HasSession]->(trustingDomainPrincipal:User) 9 | WHERE trustedDomainPrincipal.domainsid <> trustingDomainPrincipal.domainsid 10 | RETURN p 11 | LIMIT 1000 12 | revision: 1 13 | resources: 14 | acknowledgements: Martin Sohn Christensen, @martinsohndk 15 | 16 | -------------------------------------------------------------------------------- /queries/Shortest paths from Domain Users to Tier Zero High Value targets.yml: -------------------------------------------------------------------------------- 1 | name: Shortest paths from Domain Users to Tier Zero / High Value targets 2 | guid: 469dc0f3-71b8-41b0-a03b-b4af7874665d 3 | prebuilt: true 4 | platforms: Active Directory 5 | category: Shortest Paths 6 | description: 7 | query: |- 8 | MATCH p=shortestPath((s:Group)-[:AD_ATTACK_PATHS*1..]->(t:Base)) 9 | WHERE s.objectid ENDS WITH '-513' AND s<>t 10 | AND ((t:Tag_Tier_Zero) OR COALESCE(t.system_tags, '') CONTAINS 'admin_tier_0') 11 | RETURN p 12 | LIMIT 1000 13 | revision: 2 14 | resources: 15 | acknowledgements: 16 | 17 | -------------------------------------------------------------------------------- /queries/Domains affected by Exchange privilege escalation risk.yml: -------------------------------------------------------------------------------- 1 | name: Domains affected by Exchange privilege escalation risk 2 | guid: f2d09c94-b6f2-4901-9a2d-f8bacd61edc7 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Dangerous Privileges 6 | description: 7 | query: |- 8 | MATCH p=(n:Group)-[r:WriteDacl|ForceChangePassword|AddMember]->(m:Base) 9 | WHERE n.name STARTS WITH "EXCHANGE " 10 | AND ((m:Tag_Tier_Zero) OR COALESCE(m.system_tags, '') CONTAINS 'admin_tier_0') 11 | RETURN p 12 | revision: 1 13 | resources: 14 | acknowledgements: Martin Sohn Christensen, @martinsohndk 15 | 16 | -------------------------------------------------------------------------------- /queries/Domains allowing unauthenticated NSPI RPC binds.yml: -------------------------------------------------------------------------------- 1 | name: Domains allowing unauthenticated NSPI RPC binds 2 | guid: a950fdab-5934-4c69-a88b-e2e0e3da9d52 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Active Directory Hygiene 6 | description: Checks the fAllowAnonNSPI flag of dSHeuristics. 7 | query: |- 8 | MATCH (n:Domain) 9 | WHERE n.dsheuristics =~ ".{7}[^0].*" 10 | RETURN n 11 | revision: 1 12 | resources: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/e5899be4-862e-496f-9a38-33950617d2c5 13 | acknowledgements: Martin Sohn Christensen, @martinsohndk 14 | 15 | -------------------------------------------------------------------------------- /queries/Computers without Windows LAPS.yml: -------------------------------------------------------------------------------- 1 | name: Computers without Windows LAPS 2 | guid: 7c50f724-c467-4005-8e3f-9a6ce1461db0 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Active Directory Hygiene 6 | description: 7 | query: |- 8 | MATCH (c:Computer) 9 | WHERE c.operatingsystem =~ '(?i).*WINDOWS (SERVER)? ?(10|11|2019|2022|2025).*' 10 | AND c.haslaps = false 11 | AND c.enabled = true 12 | RETURN c 13 | LIMIT 100 14 | revision: 1 15 | resources: https://learn.microsoft.com/en-us/windows-server/identity/laps/laps-overview 16 | acknowledgements: Martin Sohn Christensen, @martinsohndk 17 | 18 | -------------------------------------------------------------------------------- /queries/Domain Admins logons to non-Domain Controllers.yml: -------------------------------------------------------------------------------- 1 | name: Domain Admins logons to non-Domain Controllers 2 | guid: e2f3fd0a-1df2-4089-b0a4-272ad6e369a9 3 | prebuilt: true 4 | platforms: Active Directory 5 | category: Dangerous Privileges 6 | description: 7 | query: |- 8 | MATCH (s)-[:MemberOf*0..]->(g:Group) 9 | WHERE g.objectid ENDS WITH '-516' 10 | WITH COLLECT(s) AS exclude 11 | MATCH p = (c:Computer)-[:HasSession]->(:User)-[:MemberOf*1..]->(g:Group) 12 | WHERE g.objectid ENDS WITH '-512' AND NOT c IN exclude 13 | RETURN p 14 | LIMIT 1000 15 | revision: 1 16 | resources: 17 | acknowledgements: 18 | 19 | -------------------------------------------------------------------------------- /queries/Domains not mitigating CVE-2021-42291.yml: -------------------------------------------------------------------------------- 1 | name: Domains not mitigating CVE-2021-42291 2 | guid: 02202726-d86d-46c2-891c-9770c635f76f 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Active Directory Hygiene 6 | description: Checks the AttributeAuthorizationOnLDAPAdd flag of dSHeuristics. 7 | query: |- 8 | MATCH (n:Domain) 9 | WHERE n.dsheuristics =~ "^(.{0,27}|.{27}[^1].*)$" 10 | RETURN n 11 | revision: 1 12 | resources: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/e5899be4-862e-496f-9a38-33950617d2c5 13 | acknowledgements: Martin Sohn Christensen, @martinsohndk 14 | 15 | -------------------------------------------------------------------------------- /queries/Domains not verifying UPN and SPN uniqueness.yml: -------------------------------------------------------------------------------- 1 | name: Domains not verifying UPN and SPN uniqueness 2 | guid: cb0b1591-5c3e-45f1-afb7-984e5ad865d0 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Active Directory Hygiene 6 | description: Checks the DoNotVerifyUPNAndOrSPNUniqueness flag of dSHeuristics. 7 | query: |- 8 | MATCH (n:Domain) 9 | WHERE n.dsheuristics =~ ".{20}[^0].*" 10 | RETURN n 11 | revision: 1 12 | resources: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/e5899be4-862e-496f-9a38-33950617d2c5 13 | acknowledgements: Martin Sohn Christensen, @martinsohndk 14 | 15 | -------------------------------------------------------------------------------- /queries/Nested groups within Tier Zero High Value.yml: -------------------------------------------------------------------------------- 1 | name: Nested groups within Tier Zero / High Value 2 | guid: 8e541e75-df1d-423f-b429-4bbf0403a338 3 | prebuilt: true 4 | platforms: Active Directory 5 | category: Active Directory Hygiene 6 | description: 7 | query: |- 8 | MATCH p=(t:Group)<-[:MemberOf*..]-(s:Group) 9 | WHERE ((t:Tag_Tier_Zero) OR COALESCE(t.system_tags, '') CONTAINS 'admin_tier_0') 10 | AND NOT s.objectid ENDS WITH '-512' // Domain Admins 11 | AND NOT s.objectid ENDS WITH '-519' // Enterprise Admins 12 | RETURN p 13 | LIMIT 1000 14 | revision: 1 15 | resources: 16 | acknowledgements: 17 | 18 | -------------------------------------------------------------------------------- /queries/Non-default delegation on MicrosoftDNS container.yml: -------------------------------------------------------------------------------- 1 | name: Non-default delegation on MicrosoftDNS container 2 | guid: 008792c0-4458-46a1-a10d-50cdaf95af1e 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Active Directory Hygiene 6 | description: 7 | query: |- 8 | MATCH p=(n:Base)-[r]->(m:Container) 9 | WHERE m.distinguishedname STARTS WITH "CN=MICROSOFTDNS,CN=SYSTEM,DC=" 10 | AND NOT n.name STARTS WITH "DNSADMINS@" 11 | AND NOT n.objectid =~ "-(512|544|519|9)$" 12 | AND r.isacl 13 | RETURN p 14 | revision: 1 15 | resources: 16 | acknowledgements: Martin Sohn Christensen, @martinsohndk 17 | 18 | -------------------------------------------------------------------------------- /queries/Tier Zero computers with unsupported operating systems.yml: -------------------------------------------------------------------------------- 1 | name: Tier Zero computers with unsupported operating systems 2 | guid: a87b558c-5746-4a90-9f83-c86e7b924a52 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Active Directory Hygiene 6 | description: 7 | query: |- 8 | MATCH (c:Computer) 9 | WHERE c.operatingsystem =~ '(?i).*Windows.* (2000|2003|2008|2012|xp|vista|7|8|me|nt).*' 10 | AND ((c:Tag_Tier_Zero) OR COALESCE(c.system_tags, '') CONTAINS 'admin_tier_0') 11 | RETURN c 12 | LIMIT 100 13 | revision: 1 14 | resources: 15 | acknowledgements: Martin Sohn Christensen, @martinsohndk 16 | 17 | -------------------------------------------------------------------------------- /queries/DCs vulnerable to NTLM relay to LDAP attacks.yml: -------------------------------------------------------------------------------- 1 | name: DCs vulnerable to NTLM relay to LDAP attacks 2 | guid: 3f87e0b0-fc06-4986-a94c-e08781253dc8 3 | prebuilt: true 4 | platforms: Active Directory 5 | category: NTLM Relay Attacks 6 | description: 7 | query: |- 8 | MATCH p = (dc:Computer)-[:DCFor]->(:Domain) 9 | WHERE (dc.ldapavailable = True AND dc.ldapsigning = False) 10 | OR (dc.ldapsavailable = True AND dc.ldapsepa = False) 11 | OR (dc.ldapavailable = True AND dc.ldapsavailable = True AND dc.ldapsigning = False and dc.ldapsepa = True) 12 | RETURN p 13 | revision: 1 14 | resources: 15 | acknowledgements: 16 | 17 | -------------------------------------------------------------------------------- /queries/All members of high privileged roles.yml: -------------------------------------------------------------------------------- 1 | name: All members of high privileged roles 2 | guid: 3df24d92-dd12-4125-811b-e696b098f60e 3 | prebuilt: true 4 | platforms: Azure 5 | category: General 6 | description: 7 | query: |- 8 | MATCH p=(t:AZRole)<-[:AZHasRole|AZMemberOf*1..2]-(:AZBase) 9 | WHERE t.name =~ '(?i)Global Administrator|User Administrator|Cloud Application Administrator|Authentication Policy Administrator|Exchange Administrator|Helpdesk Administrator|Privileged Authentication Administrator|Privileged Role Administrator' 10 | RETURN p 11 | LIMIT 1000 12 | revision: 2 13 | resources: 14 | acknowledgements: 15 | 16 | -------------------------------------------------------------------------------- /queries/Domains allowing unauthenticated rootDSE searches and binds.yml: -------------------------------------------------------------------------------- 1 | name: Domains allowing unauthenticated rootDSE searches and binds 2 | guid: ebc79aa4-e816-4be8-93fe-a0b30dbc771d 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Active Directory Hygiene 6 | description: Checks the fLDAPBlockAnonOps flag of dSHeuristics. 7 | query: |- 8 | MATCH (n:Domain) 9 | WHERE n.dsheuristics =~ ".{6}[^2].*" 10 | RETURN n 11 | revision: 1 12 | resources: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/e5899be4-862e-496f-9a38-33950617d2c5 13 | acknowledgements: Martin Sohn Christensen, @martinsohndk 14 | 15 | -------------------------------------------------------------------------------- /queries/KRBTGT accounts with passwords not rotated in over 1 year.yml: -------------------------------------------------------------------------------- 1 | name: KRBTGT accounts with passwords not rotated in over 1 year 2 | guid: 1b3ae310-ffa7-4ce5-a37f-6111aef600c8 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Active Directory Hygiene 6 | description: 7 | query: |- 8 | MATCH (n:User) 9 | WHERE (n.objectid ENDS WITH '-502' 10 | OR n.name STARTS WITH 'AZUREADKERBEROS.' 11 | OR n.name STARTS WITH 'KRBTGT_AZUREAD@') 12 | AND n.pwdlastset < (datetime().epochseconds - (365 * 86400)) 13 | RETURN n 14 | revision: 1 15 | resources: 16 | acknowledgements: Martin Sohn Christensen, @martinsohndk 17 | 18 | -------------------------------------------------------------------------------- /queries/Domains allowing unauthenticated domain enumeration.yml: -------------------------------------------------------------------------------- 1 | name: Domains allowing unauthenticated domain enumeration 2 | guid: 41a08d76-f8a5-4296-ad19-464c4c5c69fe 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Active Directory Hygiene 6 | description: 7 | query: |- 8 | MATCH p=(n:Group)-[:MemberOf]->(m:Group) 9 | WHERE (n.objectid ENDS WITH "S-1-5-7" // Anonymous 10 | OR n.objectid ENDS WITH "S-1-1-0") // Everyone 11 | AND m.objectid ENDS WITH "S-1-5-32-554" // Pre-Windows 2000 Compatible Access 12 | RETURN p 13 | revision: 1 14 | resources: 15 | acknowledgements: Martin Sohn Christensen, @martinsohndk 16 | 17 | -------------------------------------------------------------------------------- /queries/Non-Tier Zero accounts with SID History of Tier Zero accounts.yml: -------------------------------------------------------------------------------- 1 | name: Non-Tier Zero accounts with SID History of Tier Zero accounts 2 | guid: 59744dfe-9411-4daf-b342-1203dc62acd4 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Dangerous Privileges 6 | description: 7 | query: |- 8 | MATCH p=(n:Base)-[:HasSIDHistory]->(m:Base) 9 | WHERE ((m:Tag_Tier_Zero) OR COALESCE(m.system_tags, '') CONTAINS 'admin_tier_0') 10 | AND NOT ((n:Tag_Tier_Zero) OR COALESCE(n.system_tags, '') CONTAINS 'admin_tier_0') 11 | RETURN p 12 | revision: 1 13 | resources: 14 | acknowledgements: Martin Sohn Christensen, @martinsohndk 15 | 16 | -------------------------------------------------------------------------------- /queries/ACEs across trusts.yml: -------------------------------------------------------------------------------- 1 | name: ACEs across trusts 2 | guid: c902d3b4-1a75-4335-acd7-28246dab746d 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Domain Information 6 | description: ACEs granted across a trust, the ACEs are set on trusting objects and the rights are granted to objects from trusted domains. 7 | query: |- 8 | MATCH p=(trustedDomainPrincipal:Base)-[r]->(trustingDomainPrincipal:Base) 9 | WHERE trustedDomainPrincipal.domainsid <> trustingDomainPrincipal.domainsid 10 | AND r.isacl 11 | RETURN p 12 | LIMIT 1000 13 | revision: 1 14 | resources: 15 | acknowledgements: Martin Sohn Christensen, @martinsohndk 16 | 17 | -------------------------------------------------------------------------------- /queries/Domains exempting privileged groups from AdminSDHolder protections.yml: -------------------------------------------------------------------------------- 1 | name: Domains exempting privileged groups from AdminSDHolder protections 2 | guid: 79f8d8f9-8291-4bf7-a13a-15989018075f 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Active Directory Hygiene 6 | description: Checks the dwAdminSDExMask flag of dSHeuristics. 7 | query: |- 8 | MATCH (n:Domain) 9 | WHERE n.dsheuristics =~ ".{15}[^0].*" 10 | RETURN n 11 | revision: 1 12 | resources: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/e5899be4-862e-496f-9a38-33950617d2c5 13 | acknowledgements: Martin Sohn Christensen, @martinsohndk 14 | 15 | -------------------------------------------------------------------------------- /queries/Shortest paths from Entra Users to Tier Zero High Value targets.yml: -------------------------------------------------------------------------------- 1 | name: Shortest paths from Entra Users to Tier Zero / High Value targets 2 | guid: 58089b28-54e0-4fd2-bf66-3db480b00e2f 3 | prebuilt: true 4 | platforms: Azure 5 | category: Shortest Paths 6 | description: WARNING! MANY-TO-MANY SHORTEST PATH QUERIES USE EXCESSIVE SYSTEM RESOURCES AND TYPICALLY WILL NOT COMPLETE 7 | query: |- 8 | MATCH p=shortestPath((s:AZUser)-[:AZ_ATTACK_PATHS*1..]->(t:AZBase)) 9 | WHERE ((t:Tag_Tier_Zero) OR COALESCE(t.system_tags, '') CONTAINS 'admin_tier_0') 10 | RETURN p 11 | LIMIT 1000 12 | revision: 3 13 | resources: 14 | acknowledgements: 15 | 16 | -------------------------------------------------------------------------------- /queries/Enrollment rights on certificate templates published to Enterprise CA with User Specified SAN enabled.yml: -------------------------------------------------------------------------------- 1 | name: Enrollment rights on certificate templates published to Enterprise CA with User Specified SAN enabled 2 | guid: 96e70597-2d74-4503-a624-f1e30b642894 3 | prebuilt: true 4 | platforms: Active Directory 5 | category: Active Directory Certificate Services 6 | description: 7 | query: |- 8 | MATCH p = (:Base)-[:Enroll|GenericAll|AllExtendedRights]->(ct:CertTemplate)-[:PublishedTo]->(eca:EnterpriseCA) 9 | WHERE eca.isuserspecifiessanenabled = True 10 | RETURN p 11 | LIMIT 1000 12 | revision: 1 13 | resources: 14 | acknowledgements: 15 | 16 | -------------------------------------------------------------------------------- /queries/Enrollment rights on certificate templates published to Enterprise CA with User Specified SAN enabled (ESC6).yml: -------------------------------------------------------------------------------- 1 | name: Enrollment rights on certificate templates published to Enterprise CA with User Specified SAN enabled (ESC6) 2 | guid: ab14e9dc-996c-4737-878c-583c19cdbf5a 3 | prebuilt: true 4 | platforms: Active Directory 5 | category: Active Directory Certificate Services 6 | description: 7 | query: |- 8 | MATCH p = (:Base)-[:Enroll|GenericAll|AllExtendedRights]->(ct:CertTemplate)-[:PublishedTo]->(eca:EnterpriseCA) 9 | WHERE eca.isuserspecifiessanenabled = True 10 | RETURN p 11 | LIMIT 1000 12 | revision: 1 13 | resources: 14 | acknowledgements: 15 | -------------------------------------------------------------------------------- /queries/Enrollment rights on certificate templates published to Enterprise CA with vulnerable HTTP(S) endpoint (ESC8).yml: -------------------------------------------------------------------------------- 1 | name: Enrollment rights on certificate templates published to Enterprise CA with vulnerable HTTP(S) endpoint (ESC8) 2 | guid: 1c1435b1-bad0-49f2-ba7d-932e047c0af4 3 | prebuilt: true 4 | platforms: Active Directory 5 | category: Active Directory Certificate Services 6 | description: 7 | query: |- 8 | MATCH p = (:Base)-[:Enroll|GenericAll|AllExtendedRights]->(ct:CertTemplate)-[:PublishedTo]->(eca:EnterpriseCA) 9 | WHERE eca.hasvulnerableendpoint = True 10 | RETURN p 11 | LIMIT 1000 12 | revision: 1 13 | resources: 14 | acknowledgements: 15 | -------------------------------------------------------------------------------- /queries/Disabled Tier Zero High Value principals - AD.yml: -------------------------------------------------------------------------------- 1 | name: Disabled Tier Zero / High Value principals 2 | guid: d65a801f-d3ef-4b7e-8030-99ebfd6dad12 3 | prebuilt: true 4 | platforms: Active Directory 5 | category: Active Directory Hygiene 6 | description: 7 | query: |- 8 | MATCH (n:Base) 9 | WHERE ((n:Tag_Tier_Zero) OR COALESCE(n.system_tags, '') CONTAINS 'admin_tier_0') 10 | AND n.enabled = false 11 | AND NOT n.objectid ENDS WITH '-502' // Removes false positive, KRBTGT 12 | AND NOT n.objectid ENDS WITH '-500' // Removes false positive, built-in Administrator 13 | RETURN n 14 | LIMIT 100 15 | revision: 1 16 | resources: 17 | acknowledgements: 18 | 19 | -------------------------------------------------------------------------------- /queries/Tier Zero AD principals synchronized with Entra ID.yml: -------------------------------------------------------------------------------- 1 | name: Tier Zero AD principals synchronized with Entra ID 2 | guid: a8b6ec67-21aa-4dd2-8906-47bb81bf5262 3 | prebuilt: true 4 | platforms: Azure 5 | category: Azure Hygiene 6 | description: 7 | query: |- 8 | MATCH (ENTRA:AZBase) 9 | MATCH (AD:Base) 10 | WHERE ((AD:Tag_Tier_Zero) OR COALESCE(AD.system_tags, '') CONTAINS 'admin_tier_0') 11 | AND ENTRA.onpremsyncenabled = true 12 | AND ENTRA.onpremid = AD.objectid 13 | RETURN ENTRA 14 | // Replace 'RETURN ENTRA' with 'RETURN AD' to see the corresponding AD principals 15 | LIMIT 100 16 | revision: 1 17 | resources: 18 | acknowledgements: 19 | 20 | -------------------------------------------------------------------------------- /queries/Shortest paths from Azure Applications to Tier Zero High Value targets.yml: -------------------------------------------------------------------------------- 1 | name: Shortest paths from Azure Applications to Tier Zero / High Value targets 2 | guid: 60ff7c58-a98e-4bc1-9e32-8378d2db0c43 3 | prebuilt: true 4 | platforms: Azure 5 | category: Shortest Paths 6 | description: WARNING! MANY-TO-MANY SHORTEST PATH QUERIES USE EXCESSIVE SYSTEM RESOURCES AND TYPICALLY WILL NOT COMPLETE 7 | query: |- 8 | MATCH p=shortestPath((s:AZApp)-[:AZ_ATTACK_PATHS*1..]->(t:AZBase)) 9 | WHERE ((t:Tag_Tier_Zero) OR COALESCE(t.system_tags, '') CONTAINS 'admin_tier_0') AND s<>t 10 | RETURN p 11 | LIMIT 1000 12 | revision: 3 13 | resources: 14 | acknowledgements: 15 | 16 | -------------------------------------------------------------------------------- /queries/Users with logon scripts stored in a trusted domain.yml: -------------------------------------------------------------------------------- 1 | name: Users with logon scripts stored in a trusted domain 2 | guid: 8d94d3f3-3d53-4939-a206-3c0a4dd3f646 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Active Directory Hygiene 6 | description: 7 | query: |- 8 | MATCH (n:User) 9 | WHERE n.logonscript IS NOT NULL 10 | MATCH (d:Domain)<-[:SameForestTrust|CrossForestTrust]-(:Domain)-[:Contains*1..]->(n) 11 | WITH n,last(split(d.name, '@')) AS domain 12 | WHERE toUpper(n.logonscript) STARTS WITH ("\\\\" + domain + "\\") 13 | RETURN n 14 | revision: 2 15 | resources: 16 | acknowledgements: Martin Sohn Christensen, @martinsohndk 17 | 18 | -------------------------------------------------------------------------------- /queries/All service principals with Microsoft Graph App Role assignments.yml: -------------------------------------------------------------------------------- 1 | name: All service principals with Microsoft Graph App Role assignments 2 | guid: 74440269-eb41-476b-8dec-b4095569b029 3 | prebuilt: true 4 | platforms: Azure 5 | category: Microsoft Graph 6 | description: 7 | query: |- 8 | MATCH p=(:AZServicePrincipal)-[:AZMGAppRoleAssignment_ReadWrite_All|AZMGApplication_ReadWrite_All|AZMGDirectory_ReadWrite_All|AZMGGroupMember_ReadWrite_All|AZMGGroup_ReadWrite_All|AZMGRoleManagement_ReadWrite_Directory|AZMGServicePrincipalEndpoint_ReadWrite_All]->(:AZServicePrincipal) 9 | RETURN p 10 | LIMIT 1000 11 | revision: 1 12 | resources: 13 | acknowledgements: 14 | 15 | -------------------------------------------------------------------------------- /queries/Enrollment rights on published enrollment agent certificate templates.yml: -------------------------------------------------------------------------------- 1 | name: Enrollment rights on published enrollment agent certificate templates 2 | guid: 8483bf5b-89f1-4723-abb2-c48295f6393e 3 | prebuilt: true 4 | platforms: Active Directory 5 | category: Active Directory Certificate Services 6 | description: 7 | query: |- 8 | MATCH p = (:Base)-[:Enroll|GenericAll|AllExtendedRights]->(ct:CertTemplate)-[:PublishedTo]->(:EnterpriseCA) 9 | WHERE '1.3.6.1.4.1.311.20.2.1' IN ct.effectiveekus 10 | OR '2.5.29.37.0' IN ct.effectiveekus 11 | OR SIZE(ct.effectiveekus) = 0 12 | RETURN p 13 | LIMIT 1000 14 | revision: 1 15 | resources: 16 | acknowledgements: 17 | 18 | -------------------------------------------------------------------------------- /queries/Shortest paths to Domain Admins from Kerberoastable users.yml: -------------------------------------------------------------------------------- 1 | name: Shortest paths to Domain Admins from Kerberoastable users 2 | guid: bd163361-1e05-47c7-908b-962aef251535 3 | prebuilt: true 4 | platforms: Active Directory 5 | category: Shortest Paths 6 | description: 7 | query: |- 8 | MATCH p=shortestPath((s:User)-[:AD_ATTACK_PATHS*1..]->(t:Group)) 9 | WHERE s.hasspn=true 10 | AND s.enabled = true 11 | AND NOT s.objectid ENDS WITH '-502' 12 | AND NOT COALESCE(s.gmsa, false) = true 13 | AND NOT COALESCE(s.msa, false) = true 14 | AND t.objectid ENDS WITH '-512' 15 | RETURN p 16 | LIMIT 1000 17 | revision: 2 18 | resources: 19 | acknowledgements: 20 | 21 | -------------------------------------------------------------------------------- /queries/Devices with unsupported operating systems.yml: -------------------------------------------------------------------------------- 1 | name: Devices with unsupported operating systems 2 | guid: e3f2b53a-7ce6-4e52-9c74-68b69338288b 3 | prebuilt: true 4 | platforms: Azure 5 | category: Azure Hygiene 6 | description: 7 | query: |- 8 | MATCH (n:AZDevice) 9 | WHERE n.operatingsystem CONTAINS 'WINDOWS' 10 | AND n.operatingsystemversion =~ '(10.0.19044|10.0.22000|10.0.19043|10.0.19042|10.0.19041|10.0.18363|10.0.18362|10.0.17763|10.0.17134|10.0.16299|10.0.15063|10.0.14393|10.0.10586|10.0.10240|6.3.9600|6.2.9200|6.1.7601|6.0.6200|5.1.2600|6.0.6003|5.2.3790|5.0.2195).?.*' 11 | RETURN n 12 | LIMIT 100 13 | revision: 1 14 | resources: 15 | acknowledgements: 16 | 17 | -------------------------------------------------------------------------------- /queries/Kerberos-enabled service account member of built-in Admins groups.yml: -------------------------------------------------------------------------------- 1 | name: Kerberos-enabled service account member of built-in Admins groups 2 | guid: 42a856fc-257a-4142-9592-ca95fd49e579 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Active Directory Hygiene 6 | description: 7 | query: |- 8 | MATCH p=(n:Base)-[:MemberOf*1..]->(g:Group) 9 | WHERE ( 10 | g.objectid ENDS WITH '-512' // Domain Admins 11 | OR g.objectid ENDS WITH '-519' // Enterprise Admins 12 | OR g.objectid ENDS WITH '-518' // Schema Admins 13 | ) 14 | AND n.hasspn = true 15 | RETURN p 16 | revision: 1 17 | resources: 18 | acknowledgements: Martin Sohn Christensen, @martinsohndk 19 | 20 | -------------------------------------------------------------------------------- /queries/Tier Zero users not member of Protected Users.yml: -------------------------------------------------------------------------------- 1 | name: Tier Zero users not member of Protected Users 2 | guid: 543eb01d-9fa3-4b8f-a936-b46bbfdaa2ae 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Active Directory Hygiene 6 | description: 7 | query: |- 8 | MATCH (m:User) 9 | WHERE ((m:Tag_Tier_Zero) OR COALESCE(m.system_tags, '') CONTAINS 'admin_tier_0') 10 | OPTIONAL MATCH (g:Group)<-[:MemberOf*1..]-(n:Base) 11 | WHERE g.objectid ENDS WITH '-525' 12 | WITH m, COLLECT(n) AS matchingNs 13 | WHERE NONE(n IN matchingNs WHERE n.objectid = m.objectid) 14 | RETURN m 15 | revision: 1 16 | resources: 17 | acknowledgements: Martin Sohn Christensen, @martinsohndk 18 | 19 | -------------------------------------------------------------------------------- /queries/Tier Zero users with passwords not rotated in over 1 year.yml: -------------------------------------------------------------------------------- 1 | name: Tier Zero users with passwords not rotated in over 1 year 2 | guid: 5e0d69b1-37d1-43ae-ac5d-f297f312fab5 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Active Directory Hygiene 6 | description: 7 | query: |- 8 | WITH 365 as days_since_change 9 | MATCH (u:User) 10 | WHERE ((u:Tag_Tier_Zero) OR COALESCE(u.system_tags, '') CONTAINS 'admin_tier_0') 11 | AND u.pwdlastset < (datetime().epochseconds - (days_since_change * 86400)) 12 | AND NOT u.pwdlastset IN [-1.0, 0.0] 13 | RETURN u 14 | LIMIT 100 15 | revision: 1 16 | resources: 17 | acknowledgements: Martin Sohn Christensen, @martinsohndk 18 | 19 | -------------------------------------------------------------------------------- /queries/Circular AZ group memberships.yml: -------------------------------------------------------------------------------- 1 | name: Circular AZ group memberships 2 | guid: b005669c-d8af-47ae-a0f1-4f36cd5334ab 3 | prebuilt: false 4 | platforms: Azure 5 | category: Azure Hygiene 6 | description: Detects circular group membership chains where groups are members of themselves through one or more intermediate groups. This causes an administrative complexity. 7 | query: |- 8 | MATCH p=(x:AZGroup)-[:AZMemberOf*2..]->(y:AZGroup) 9 | WHERE x.objectid=y.objectid 10 | RETURN p 11 | LIMIT 100 12 | revision: 1 13 | resources: https://softwareengineering.stackexchange.com/questions/11856/whats-wrong-with-circular-references 14 | acknowledgements: Martin Sohn Christensen, @martinsohndk 15 | 16 | -------------------------------------------------------------------------------- /queries/Compromising permissions on ADCS nodes (ESC5).yml: -------------------------------------------------------------------------------- 1 | name: Compromising permissions on ADCS nodes (ESC5) 2 | guid: 396c7b67-fb5d-4c04-bb13-8007f0dfc9b1 3 | prebuilt: true 4 | platforms: Active Directory 5 | category: Active Directory Certificate Services 6 | description: 7 | query: |- 8 | MATCH p = (n:Base)-[:Owns|WriteOwner|WriteDacl|GenericAll|GenericWrite]->(m:Base) 9 | WHERE m.distinguishedname CONTAINS "PUBLIC KEY SERVICES" 10 | AND NOT n.objectid ENDS WITH "-512" // Domain Admins 11 | AND NOT n.objectid ENDS WITH "-519" // Enterprise Admins 12 | AND NOT n.objectid ENDS WITH "-544" // Administrators 13 | RETURN p 14 | LIMIT 1000 15 | revision: 1 16 | resources: 17 | acknowledgements: 18 | -------------------------------------------------------------------------------- /queries/Kerberoastable members of Tier Zero High Value groups.yml: -------------------------------------------------------------------------------- 1 | name: Kerberoastable members of Tier Zero / High Value groups 2 | guid: e6da7800-ae06-41cb-80a6-d5421ab2143a 3 | prebuilt: true 4 | platforms: Active Directory 5 | category: Kerberos Interaction 6 | description: 7 | query: |- 8 | MATCH (u:User) 9 | WHERE ((u:Tag_Tier_Zero) OR COALESCE(u.system_tags, '') CONTAINS 'admin_tier_0') AND u.hasspn=true 10 | AND u.enabled = true 11 | AND NOT u.objectid ENDS WITH '-502' 12 | AND NOT COALESCE(u.gmsa, false) = true 13 | AND NOT COALESCE(u.msa, false) = true 14 | RETURN u 15 | LIMIT 100 16 | revision: 2 17 | resources: https://attack.mitre.org/techniques/T1558/003/ 18 | acknowledgements: 19 | 20 | -------------------------------------------------------------------------------- /queries/Circular AD group memberships.yml: -------------------------------------------------------------------------------- 1 | name: Circular AD group memberships 2 | guid: fcaa5ffc-3d22-481f-a2a2-18a4eec30058 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Active Directory Hygiene 6 | description: Detects circular group membership chains where groups are members of themselves through one or more intermediate groups. This causes an administrative complexity. 7 | query: |- 8 | MATCH p=(x:Group)-[:MemberOf*2..]->(y:Group) 9 | WHERE x.objectid=y.objectid 10 | RETURN p 11 | LIMIT 100 12 | revision: 1 13 | resources: https://softwareengineering.stackexchange.com/questions/11856/whats-wrong-with-circular-references 14 | acknowledgements: Martin Sohn Christensen, @martinsohndk 15 | 16 | -------------------------------------------------------------------------------- /queries/Enrollment rights on published ESC1 certificate templates.yml: -------------------------------------------------------------------------------- 1 | name: Enrollment rights on published ESC1 certificate templates 2 | guid: 2af855bc-f48f-4b22-9839-627d8231e425 3 | prebuilt: true 4 | platforms: Active Directory 5 | category: Active Directory Certificate Services 6 | description: 7 | query: |- 8 | MATCH p = (:Base)-[:Enroll|GenericAll|AllExtendedRights]->(ct:CertTemplate)-[:PublishedTo]->(:EnterpriseCA) 9 | WHERE ct.enrolleesuppliessubject = True 10 | AND ct.authenticationenabled = True 11 | AND ct.requiresmanagerapproval = False 12 | AND (ct.authorizedsignatures = 0 OR ct.schemaversion = 1) 13 | RETURN p 14 | LIMIT 1000 15 | revision: 1 16 | resources: 17 | acknowledgements: 18 | 19 | -------------------------------------------------------------------------------- /queries/Domains where any user can join a computer to the domain.yml: -------------------------------------------------------------------------------- 1 | name: Domains where any user can join a computer to the domain 2 | guid: 421921fa-bc0f-4659-9680-b7481adcb132 3 | prebuilt: true 4 | platforms: Active Directory 5 | category: Active Directory Hygiene 6 | description: 7 | query: |- 8 | MATCH (d:Domain) 9 | WHERE d.machineaccountquota > 0 10 | RETURN d 11 | revision: 2 12 | resources: 13 | - https://learn.microsoft.com/en-us/troubleshoot/windows-server/active-directory/default-workstation-numbers-join-domain 14 | - https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-10/security/threat-protection/security-policy-settings/add-workstations-to-domain 15 | acknowledgements: 16 | 17 | -------------------------------------------------------------------------------- /queries/Non-default members in Pre-Windows 2000 Compatible Access.yml: -------------------------------------------------------------------------------- 1 | name: Non-default members in Pre-Windows 2000 Compatible Access 2 | guid: 091995b9-7254-473a-996f-6b8368d20431 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Active Directory Hygiene 6 | description: 7 | query: |- 8 | MATCH p=(n:Group)-[:MemberOf]->(m:Group) 9 | WHERE NOT n.objectid ENDS WITH "S-1-5-11" // Authenticated Users 10 | AND NOT (n.objectid ENDS WITH "S-1-5-7" // Anonymous 11 | AND NOT n.objectid ENDS WITH "S-1-1-0") // Everyone 12 | AND m.objectid ENDS WITH "S-1-5-32-554" // Pre-Windows 2000 Compatible Access 13 | RETURN p 14 | revision: 1 15 | resources: 16 | acknowledgements: Martin Sohn Christensen, @martinsohndk 17 | 18 | -------------------------------------------------------------------------------- /queries/Computer owners who can obtain LAPS passwords.yml: -------------------------------------------------------------------------------- 1 | name: Computer owners who can obtain LAPS passwords 2 | guid: 92aa81d6-b08e-4abb-ae39-ecbe5735a74c 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Dangerous Privileges 6 | description: Creators of computer objects get abusable rights on the computer object. If the owner is not explicitly granted ReadLAPSPassword they can still compromise the computer with the abusable owner rights. 7 | query: |- 8 | MATCH p = (c:Computer)<-[:GenericAll|Owns|WriteDacl|WriteOwner|AllExtendedRights]-(n:User) 9 | WHERE c.haslaps = true AND c.ownersid = n.objectid 10 | RETURN p 11 | revision: 1 12 | resources: 13 | acknowledgements: Martin Sohn Christensen, @martinsohndk 14 | 15 | -------------------------------------------------------------------------------- /queries/Tier Zero computers with passwords older than the default maximum password age.yml: -------------------------------------------------------------------------------- 1 | name: Tier Zero computers with passwords older than the default maximum password age 2 | guid: b6d6d0bf-130e-4719-996b-adc29bba36e9 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Active Directory Hygiene 6 | description: 7 | query: |- 8 | MATCH (n:Computer) 9 | WHERE n.enabled = true 10 | AND n.whencreated < (datetime().epochseconds - (60 * 3 * 86400)) 11 | AND n.pwdlastset < (datetime().epochseconds - (60 * 3 * 86400)) 12 | AND ((n:Tag_Tier_Zero) OR COALESCE(n.system_tags, '') CONTAINS 'admin_tier_0') 13 | RETURN n 14 | revision: 2 15 | resources: 16 | acknowledgements: Martin Sohn Christensen, @martinsohndk 17 | 18 | -------------------------------------------------------------------------------- /queries/Accounts related to AAD Entra Connect.yml: -------------------------------------------------------------------------------- 1 | name: Accounts related to AAD Entra Connect 2 | guid: 5993208e-6189-40e6-be03-c23c872d0ca4 3 | prebuilt: false 4 | platforms: 5 | - Active Directory 6 | - Azure 7 | category: Active Directory Hygiene 8 | description: Query to start reconnaissance about AADConnect / Entra Connect related accounts 9 | query: |- 10 | MATCH (u) 11 | WHERE (u:User OR u:AZUser) 12 | AND (u.name =~ '(?i)^MSOL_|.*AADConnect.*|.*ADSyncMSA.*|.*AAD_.*|.*PROVAGENTGMSA.*' 13 | OR u.userprincipalname =~ '(?i)^sync_.*') 14 | RETURN u 15 | revision: 1 16 | resources: https://learn.microsoft.com/en-us/entra/identity/hybrid/connect/concept-adsync-service-account 17 | acknowledgements: Daniel Scheidt, @theluemmel 18 | -------------------------------------------------------------------------------- /queries/On-Prem Users synced to Entra Users with Azure RM Roles (direct).yml: -------------------------------------------------------------------------------- 1 | name: On-Prem Users synced to Entra Users with Azure RM Roles (direct) 2 | guid: 8569113b-e42e-49b0-a968-53bcf0ccd970 3 | prebuilt: true 4 | platforms: 5 | - Active Directory 6 | - Azure 7 | category: Cross Platform Attack Paths 8 | description: 9 | query: |- 10 | MATCH p = (:User)-[:SyncedToEntraUser]->(:AZUser)-[:AZOwner|AZUserAccessAdministrator|AZGetCertificates|AZGetKeys|AZGetSecrets|AZAvereContributor|AZKeyVaultContributor|AZContributor|AZVMAdminLogin|AZVMContributor|AZAKSContributor|AZAutomationContributor|AZLogicAppContributor|AZWebsiteContributor]->(:AZBase) 11 | RETURN p 12 | LIMIT 1000 13 | revision: 1 14 | resources: 15 | acknowledgements: 16 | 17 | -------------------------------------------------------------------------------- /queries/Principal with SPN keyword.yml: -------------------------------------------------------------------------------- 1 | name: Principal with SPN keyword 2 | guid: 38a9c4c9-3d70-453f-a017-cbfd35ed9917 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Kerberos Interaction 6 | description: Finds service accounts used with a specific Kerberos-enabled service or all service accounts running on a Kerberos-enabled service on a specific server. 7 | query: |- 8 | // Replace keyword with a service type or server name (not FQDN) 9 | WITH "KEYWORD" as SPNKeyword 10 | MATCH (n:User) 11 | WHERE ANY(keyword IN n.serviceprincipalnames WHERE toUpper(keyword) CONTAINS toUpper(SPNKeyword)) 12 | RETURN n 13 | revision: 1 14 | resources: https://adsecurity.org/?page_id=183 15 | acknowledgements: Ryan, @haus3c 16 | 17 | -------------------------------------------------------------------------------- /queries/Tier Zero accounts that can be delegated.yml: -------------------------------------------------------------------------------- 1 | name: Tier Zero accounts that can be delegated 2 | guid: 4316eaf1-6af0-4879-8f55-ac2633a711c3 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Kerberos Interaction 6 | description: 7 | query: |- 8 | MATCH (m:Base) 9 | WHERE ((m:Tag_Tier_Zero) OR COALESCE(m.system_tags, '') CONTAINS 'admin_tier_0') 10 | AND m.enabled = true 11 | AND m.sensitive = false 12 | OPTIONAL MATCH (g:Group)<-[:MemberOf*1..]-(n:Base) 13 | WHERE g.objectid ENDS WITH '-525' 14 | WITH m, COLLECT(n) AS matchingNs 15 | WHERE NONE(n IN matchingNs WHERE n.objectid = m.objectid) 16 | RETURN m 17 | revision: 1 18 | resources: 19 | acknowledgements: Martin Sohn Christensen, @martinsohndk 20 | 21 | -------------------------------------------------------------------------------- /queries/All paths crossing a specific trust.yml: -------------------------------------------------------------------------------- 1 | name: All paths crossing a specific trust 2 | guid: 251fc893-7a6b-4a0a-8650-9d5408d38c3c 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Domain Information 6 | description: All paths crossing a specific trust from a trusted to a trusting domain. 7 | query: |- 8 | // Replace the TRUSTED domain SID 9 | // Replace the TRUSTING domain SID 10 | MATCH p=(Trusted:Base)-[:AD_ATTACK_PATHS]->(Trusting:Base) 11 | WHERE Trusted.domainsid = 'S-1-5-21-1111111111-1111111111-1111111111' 12 | AND Trusting.domainsid = 'S-1-5-21-2222222222-2222222222-2222222222' 13 | RETURN p 14 | LIMIT 1000 15 | revision: 2 16 | resources: 17 | acknowledgements: Martin Sohn Christensen, @martinsohndk 18 | 19 | -------------------------------------------------------------------------------- /queries/Shortest paths from Owned objects to Tier Zero.yml: -------------------------------------------------------------------------------- 1 | name: Shortest paths from Owned objects to Tier Zero 2 | guid: dfaa8e8f-2c79-4e92-a291-b1347f6e83b0 3 | prebuilt: true 4 | platforms: Active Directory 5 | category: Shortest Paths 6 | description: WARNING! MANY-TO-MANY SHORTEST PATH QUERIES USE EXCESSIVE SYSTEM RESOURCES AND TYPICALLY WILL NOT COMPLETE 7 | query: |- 8 | // MANY TO MANY SHORTEST PATH QUERIES USE EXCESSIVE SYSTEM RESOURCES AND TYPICALLY WILL NOT COMPLETE 9 | MATCH p=shortestPath((s:Tag_Owned)-[:AD_ATTACK_PATHS*1..]->(t:Base)) 10 | WHERE s<>t 11 | AND ((t:Tag_Tier_Zero) OR COALESCE(t.system_tags, '') CONTAINS 'admin_tier_0') 12 | RETURN p 13 | LIMIT 1000 14 | revision: 4 15 | resources: 16 | acknowledgements: 17 | 18 | -------------------------------------------------------------------------------- /queries/Kerberoastable users with most admin privileges.yml: -------------------------------------------------------------------------------- 1 | name: Kerberoastable users with most admin privileges 2 | guid: 9907b208-494c-4ba6-846d-485e6de14e17 3 | prebuilt: true 4 | platforms: Active Directory 5 | category: Kerberos Interaction 6 | description: 7 | query: |- 8 | MATCH (u:User) 9 | WHERE u.hasspn = true 10 | AND u.enabled = true 11 | AND NOT u.objectid ENDS WITH '-502' 12 | AND NOT COALESCE(u.gmsa, false) = true 13 | AND NOT COALESCE(u.msa, false) = true 14 | MATCH (u)-[:MemberOf|AdminTo*1..]->(c:Computer) 15 | WITH DISTINCT u, COUNT(c) AS adminCount 16 | RETURN u 17 | ORDER BY adminCount DESC 18 | LIMIT 100 19 | revision: 1 20 | resources: https://attack.mitre.org/techniques/T1558/003/ 21 | acknowledgements: 22 | 23 | -------------------------------------------------------------------------------- /queries/Shortest paths to privileged roles.yml: -------------------------------------------------------------------------------- 1 | name: Shortest paths to privileged roles 2 | guid: 3dc73dd8-4873-4aeb-a88f-56a58c77f512 3 | prebuilt: true 4 | platforms: Azure 5 | category: Shortest Paths 6 | description: WARNING! MANY-TO-MANY SHORTEST PATH QUERIES USE EXCESSIVE SYSTEM RESOURCES AND TYPICALLY WILL NOT COMPLETE 7 | query: |- 8 | MATCH p=shortestPath((s:AZBase)-[:AZ_ATTACK_PATHS*1..]->(t:AZRole)) 9 | WHERE t.name =~ '(?i)Global Administrator|User Administrator|Cloud Application Administrator|Authentication Policy Administrator|Exchange Administrator|Helpdesk Administrator|Privileged Authentication Administrator|Privileged Role Administrator' AND s<>t 10 | RETURN p 11 | LIMIT 1000 12 | revision: 3 13 | resources: 14 | acknowledgements: 15 | 16 | -------------------------------------------------------------------------------- /queries/Non-Tier Zero account with excessive control.yml: -------------------------------------------------------------------------------- 1 | name: Non-Tier Zero object with excessive control 2 | guid: 944cecfe-519b-4318-b226-e8520161b454 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Dangerous Privileges 6 | description: Returns non-Tier Zero principals with >= 1000 direct rights to other principals. This does not include rights from group memberships. 7 | query: |- 8 | MATCH (n:Base)-[r:AD_ATTACK_PATHS]->(m:Base) 9 | WHERE NOT r:MemberOf 10 | AND NOT ((n:Tag_Tier_Zero) OR COALESCE(n.system_tags, '') CONTAINS 'admin_tier_0') 11 | WITH n, COLLECT(DISTINCT(m)) AS endNodes 12 | WHERE SIZE(endNodes) >= 1000 13 | RETURN n 14 | revision: 4 15 | resources: 16 | acknowledgements: Martin Sohn Christensen, @martinsohndk 17 | 18 | -------------------------------------------------------------------------------- /queries/Large default group added to computer-local group.yml: -------------------------------------------------------------------------------- 1 | name: Large default group added to computer-local group 2 | guid: dde133d2-b4d2-4de9-a656-905f3bf066f3 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Dangerous Privileges 6 | description: 7 | query: |- 8 | MATCH p=(n:Group)-[:MemberOfLocalGroup]->(m:ADLocalGroup)-[:LocalToComputer]->(:Computer) 9 | WHERE n.objectid =~ ".*-(S-1-5-11|S-1-1-0|S-1-5-32-545|S-1-5-7|-513|-515)$" // Authenticated Users, Everyone, Users, Anonymous, Domain Users, Domain Computers 10 | AND NOT m.objectid =~ ".*-(545|574|554)$" // Users, Certificate Service DCOM Access, Pre-Windows 2000 Compatible Access 11 | RETURN p 12 | revision: 1 13 | resources: 14 | acknowledgements: Martin Sohn Christensen, @martinsohndk 15 | 16 | -------------------------------------------------------------------------------- /queries/On-Prem Users synced to Entra Users with Azure RM Roles (group delegated).yml: -------------------------------------------------------------------------------- 1 | name: On-Prem Users synced to Entra Users with Azure RM Roles (group delegated) 2 | guid: e4f2eada-8a89-4ba9-89eb-abbee4efbc7a 3 | prebuilt: true 4 | platforms: 5 | - Active Directory 6 | - Azure 7 | category: Cross Platform Attack Paths 8 | description: 9 | query: |- 10 | MATCH p = (:User)-[:SyncedToEntraUser]->(:AZUser)-[:AZMemberOf]->(:AZGroup)-[:AZOwner|AZUserAccessAdministrator|AZGetCertificates|AZGetKeys|AZGetSecrets|AZAvereContributor|AZKeyVaultContributor|AZContributor|AZVMAdminLogin|AZVMContributor|AZAKSContributor|AZAutomationContributor|AZLogicAppContributor|AZWebsiteContributor]->(:AZBase) 11 | RETURN p 12 | LIMIT 1000 13 | revision: 1 14 | resources: 15 | acknowledgements: 16 | 17 | -------------------------------------------------------------------------------- /queries/Accounts with weak password storage encryption.yml: -------------------------------------------------------------------------------- 1 | name: Accounts with weak password storage encryption 2 | guid: 8bd6fcf2-3f3c-414c-857a-4caf28e49def 3 | prebuilt: true 4 | platforms: Active Directory 5 | category: Active Directory Hygiene 6 | description: Accounts with passwords set before the existence of Windows Server 2008 Domain Controller which therefore lack AES encryption keys. 7 | query: |- 8 | MATCH (n:Base) 9 | WHERE n.pwdlastset < 1204070400 // Password Last Set before Windows Server 2008 release 10 | RETURN n 11 | LIMIT 100 12 | revision: 2 13 | resources: https://techcommunity.microsoft.com/blog/coreinfrastructureandsecurityblog/decrypting-the-selection-of-supported-kerberos-encryption-types/1628797 14 | acknowledgements: Martin Sohn Christensen, @martinsohndk 15 | 16 | -------------------------------------------------------------------------------- /queries/Usage of built-in domain Administrator account.yml: -------------------------------------------------------------------------------- 1 | name: Usage of built-in domain Administrator account 2 | guid: 35b1206f-871b-44aa-a601-c5258060dfcf 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Active Directory Hygiene 6 | description: Usage of Active Directory's built-in Administrator account is a sign that the account is not only used for break-glass purposes. 7 | query: |- 8 | MATCH (n:User) 9 | WHERE n.objectid ENDS WITH "-500" 10 | AND ( 11 | n.lastlogontimestamp > (datetime().epochseconds - (60 * 86400)) OR 12 | n.lastlogon > (datetime().epochseconds - (60 * 86400)) 13 | ) 14 | AND NOT n.whencreated > (datetime().epochseconds - (60 * 86400)) 15 | RETURN n 16 | revision: 1 17 | resources: 18 | acknowledgements: Martin Sohn Christensen, @martinsohndk 19 | 20 | -------------------------------------------------------------------------------- /queries/Object name conflict.yml: -------------------------------------------------------------------------------- 1 | name: Object name conflict 2 | guid: c561c4f8-ea45-453f-85a2-3fc2e20e7f8c 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Active Directory Hygiene 6 | description: When two objects are created with the same Relative Distinguished Name (RDN) in the same parent Organizational Unit or container, the conflict is recognized by the system when one of the new objects replicates to another domain controller. When this happens, one of the objects is renamed with 'CNF' 7 | query: |- 8 | MATCH (n:Base) 9 | WHERE n.distinguishedname CONTAINS 'CNF:' 10 | RETURN n 11 | revision: 1 12 | resources: https://learn.microsoft.com/en-us/archive/technet-wiki/15435.active-directory-duplicate-object-name-resolution 13 | acknowledgements: Martin Sohn Christensen, @martinsohndk 14 | 15 | -------------------------------------------------------------------------------- /queries/Overprivileged Microsoft Entra Connect accounts.yml: -------------------------------------------------------------------------------- 1 | name: Overprivileged Microsoft Entra Connect accounts 2 | guid: 9e6e75b4-9ecc-45d4-a39b-b6427b813f0a 3 | prebuilt: false 4 | platforms: 5 | - Active Directory 6 | - Azure 7 | category: Active Directory Hygiene 8 | description: Legacy MSOL accounts were by default deployed with Domain Admins or Enterprise Admins membership. 9 | query: |- 10 | MATCH p=(n:User)-[:MemberOf*1..]->(g:Group) 11 | WHERE n.name STARTS WITH "MSOL_" 12 | AND (g.objectid ENDS WITH "-512" // Domain Admins 13 | OR g.objectid ENDS WITH "-519") // Entterprise Admins 14 | RETURN p 15 | revision: 1 16 | resources: https://learn.microsoft.com/en-us/entra/identity/hybrid/connect/reference-connect-accounts-permissions 17 | acknowledgements: Martin Sohn Christensen, @martinsohndk 18 | 19 | -------------------------------------------------------------------------------- /queries/Large default groups with outbound control of OUs.yml: -------------------------------------------------------------------------------- 1 | name: Large default groups with outbound control of OUs 2 | guid: 310b3626-f8e6-4ab0-832c-72df6048597f 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Dangerous Privileges 6 | description: 7 | query: |- 8 | MATCH p=(n:Group)-[]->(:OU) 9 | WHERE n.objectid ENDS WITH "-513" // DOMAIN USERS 10 | OR n.objectid ENDS WITH "-515" // DOMAIN COMPUTERS 11 | OR n.objectid ENDS WITH "-S-1-5-11" // AUTHENTICATED USERS 12 | OR n.objectid ENDS WITH "-S-1-1-0" // EVERYONE 13 | OR n.objectid ENDS WITH "S-1-5-32-545" // USERS 14 | OR n.objectid ENDS WITH "S-1-5-32-546" // GUESTS 15 | OR n.objectid ENDS WITH "S-1-5-7" // ANONYMOUS 16 | RETURN p 17 | revision: 1 18 | resources: 19 | acknowledgements: Martin Sohn Christensen, @martinsohndk 20 | 21 | -------------------------------------------------------------------------------- /queries/Domain controllers with UPN certificate mapping enabled.yml: -------------------------------------------------------------------------------- 1 | name: Domain controllers with UPN certificate mapping enabled 2 | guid: 799ea3ce-572b-4594-98c4-041aa2ae6176 3 | prebuilt: true 4 | platforms: Active Directory 5 | category: Active Directory Certificate Services 6 | description: 7 | query: |- 8 | MATCH p = (s:Computer)-[:DCFor]->(:Domain) 9 | WHERE s.certificatemappingmethodsraw IN [4, 5, 6, 7, 12, 13, 14, 15, 20, 21, 22, 23, 28, 29, 30, 31] 10 | RETURN p 11 | LIMIT 1000 12 | revision: 1 13 | resources: 14 | - https://support.microsoft.com/en-us/topic/kb5014754-certificate-based-authentication-changes-on-windows-domain-controllers-ad2c23b0-15d8-4340-a468-4d4f3b188f16 15 | - https://specterops.io/blog/2024/02/28/adcs-esc14-abuse-technique/ 16 | acknowledgements: Jonas Bülow Knudsen, @Jonas_B_K 17 | 18 | -------------------------------------------------------------------------------- /queries/Users with non-default Primary Group membership.yml: -------------------------------------------------------------------------------- 1 | name: Users with non-default Primary Group membership 2 | guid: 93890f88-df2c-4167-a945-a53961d08d00 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Active Directory Hygiene 6 | description: 7 | query: |- 8 | MATCH p=(n:User)-[r:MemberOf]->(g:Group) 9 | WHERE NOT g.objectid ENDS WITH "-513" // Domain Users 10 | AND r.isprimarygroup = true 11 | AND NOT n.objectid ENDS WITH "-501" // Guests account, as it has primaryGroup to Guests 12 | AND (n.gmsa IS NULL OR n.gmsa = false) // Not gMSA, as it has primaryGroup to Domain Computers 13 | AND (n.msa IS NULL OR n.msa = false) // Not MSA, as it has primaryGroup to Domain Computers 14 | RETURN p 15 | revision: 1 16 | resources: 17 | acknowledgements: Martin Sohn Christensen, @martinsohndk 18 | 19 | -------------------------------------------------------------------------------- /queries/Non-Tier Zero principals with BadSuccessor rights (no prerequisites check).yml: -------------------------------------------------------------------------------- 1 | name: Non-Tier Zero principals with BadSuccessor rights (no prerequisites check) 2 | guid: 2b9fb71e-73ad-4061-a2df-40c7132b044d 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Dangerous Privileges 6 | description: Finds non-Tier Zero principals with BadSuccessor rights with no prerequisites check (DC2025 & KDC key). 7 | query: |- 8 | // Find OU control 9 | MATCH p = (ou:OU)<-[:WriteDacl|Owns|GenericAll|WriteOwner]-(n:Base) 10 | // Exclude Tier Zero 11 | WHERE NOT ((n:Tag_Tier_Zero) OR COALESCE(n.system_tags, '') CONTAINS 'admin_tier_0') 12 | RETURN p LIMIT 1000 13 | revision: 1 14 | resources: https://bsky.app/profile/specterops.io/post/3lpua65qeu22l 15 | acknowledgements: Martin Sohn Christensen, @martinsohndk 16 | 17 | -------------------------------------------------------------------------------- /queries/All incoming and local paths for a specific computer.yml: -------------------------------------------------------------------------------- 1 | name: All incoming and local paths for a specific computer 2 | guid: 1f67e538-19d4-4020-89c8-5b39b31571bd 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Domain Information 6 | description: All incoming and local paths for a specific computer; incoming from domain objects and paths local inside the computer. 7 | query: |- 8 | // Replace 'HOSTNAME' with the computer's shortname eg. 'SRV01', not FQDN 9 | MATCH p=(n:Base)-[:RemoteInteractiveLogonRight|AdminTo|CanRDP|LocalToComputer|MemberOfLocalGroup]-(m:Base) 10 | WHERE m.name CONTAINS 'HOSTNAME' 11 | AND m.name CONTAINS '.' // Only see computer-related objects (eg. not AD Groups) 12 | RETURN p 13 | revision: 2 14 | resources: 15 | acknowledgements: Martin Sohn Christensen, @martinsohndk 16 | 17 | -------------------------------------------------------------------------------- /queries/Tier Zero High Value enabled users not requiring smart card authentication.yml: -------------------------------------------------------------------------------- 1 | name: Tier Zero / High Value enabled users not requiring smart card authentication 2 | guid: 867f9f17-c149-4c4b-ad84-9a807622ff8c 3 | prebuilt: true 4 | platforms: Active Directory 5 | category: Active Directory Hygiene 6 | description: 7 | query: |- 8 | MATCH (u:User) 9 | WHERE ((u:Tag_Tier_Zero) OR COALESCE(u.system_tags, '') CONTAINS 'admin_tier_0') 10 | AND u.enabled = true 11 | AND u.smartcardrequired = false 12 | AND NOT u.name STARTS WITH 'MSOL_' // Removes false positive, Entra sync 13 | AND NOT u.name STARTS WITH 'PROVAGENTGMSA' // Removes false positive, Entra sync 14 | AND NOT u.name STARTS WITH 'ADSYNCMSA_' // Removes false positive, Entra sync 15 | RETURN u 16 | revision: 1 17 | resources: 18 | acknowledgements: 19 | 20 | -------------------------------------------------------------------------------- /queries/Entra ID SSO accounts not rolling Kerberos decryption key.yml: -------------------------------------------------------------------------------- 1 | name: Entra ID SSO accounts not rolling Kerberos decryption key 2 | guid: 1867abf8-08e3-4ea8-8f65-8366079d35c4 3 | prebuilt: false 4 | platforms: 5 | - Active Directory 6 | - Azure 7 | category: Configuration Weakness 8 | description: Microsoft highly recommends that you roll over the Entra ID SSO Kerberos decryption key at least every 30 days. 9 | query: |- 10 | MATCH (n:Computer) 11 | WHERE n.name STARTS WITH "AZUREADSSOACC." 12 | AND n.pwdlastset < (datetime().epochseconds - (30 * 86400)) 13 | RETURN n 14 | revision: 1 15 | resources: https://learn.microsoft.com/en-us/entra/identity/hybrid/connect/how-to-connect-sso-faq#how-can-i-roll-over-the-kerberos-decryption-key-of-the--azureadsso--computer-account- 16 | acknowledgements: Martin Sohn Christensen, @martinsohndk 17 | 18 | -------------------------------------------------------------------------------- /queries/Foreign Service Principals With an EntraID Admin Role.yml: -------------------------------------------------------------------------------- 1 | name: Foreign Service Principals With an EntraID Admin Role 2 | guid: b6235820-4e0d-4dfa-af5b-729b5644feb5 3 | prebuilt: false 4 | platforms: Azure 5 | category: Dangerous Privileges 6 | description: Entra ID admin roles grant significant control over a tenant environment, even if the role is not a default Tier Zero / High Value role 7 | query: |- 8 | MATCH p = (sp:AZServicePrincipal)-[:AZHasRole]->(r:AZRole) 9 | WHERE toUpper(sp.appownerorganizationid) <> toUpper(sp.tenantid) 10 | // Ensure AZServicePrincipal has a valid appownerorganizationid 11 | AND sp.appownerorganizationid CONTAINS "-" 12 | RETURN p 13 | LIMIT 1000 14 | revision: 1 15 | resources: https://posts.specterops.io/microsoft-breach-how-can-i-see-this-in-bloodhound-33c92dca4c65 16 | acknowledgements: Stephen Hinck 17 | 18 | -------------------------------------------------------------------------------- /queries/Foreign Service Principals With Group Memberships.yml: -------------------------------------------------------------------------------- 1 | name: Foreign Service Principals With Group Memberships 2 | guid: 327ef6a5-bfa8-4c92-b35a-d3df85264a24 3 | prebuilt: false 4 | platforms: Azure 5 | category: Azure Hygiene 6 | description: Review each to validate whether their presence is expected and whether the assigned group memberships are appropriate for the foreign service principal. 7 | query: |- 8 | MATCH p = (sp:AZServicePrincipal)-[:AZMemberOf]->(g:AZGroup) 9 | WHERE toUpper(sp.appownerorganizationid) <> toUpper(g.tenantid) 10 | // Ensure AZServicePrincipal has a valid appownerorganizationid 11 | AND sp.appownerorganizationid CONTAINS "-" 12 | RETURN p 13 | LIMIT 1000 14 | revision: 1 15 | resources: https://posts.specterops.io/microsoft-breach-how-can-i-see-this-in-bloodhound-33c92dca4c65 16 | acknowledgements: Stephen Hinck 17 | 18 | -------------------------------------------------------------------------------- /queries/Enrollment rights on published ESC2 certificate templates.yml: -------------------------------------------------------------------------------- 1 | name: Enrollment rights on published ESC2 certificate templates 2 | guid: ebc77984-1ceb-4ed2-a395-ce1067847941 3 | prebuilt: true 4 | platforms: Active Directory 5 | category: Active Directory Certificate Services 6 | description: 7 | query: |- 8 | MATCH p = (:Base)-[:Enroll|GenericAll|AllExtendedRights]->(c:CertTemplate)-[:PublishedTo]->(:EnterpriseCA) 9 | WHERE c.requiresmanagerapproval = false 10 | AND (c.effectiveekus = [''] OR '2.5.29.37.0' IN c.effectiveekus OR c.effectiveekus IS NULL) 11 | AND (c.authorizedsignatures = 0 OR c.schemaversion = 1) 12 | RETURN p 13 | LIMIT 1000 14 | revision: 2 15 | resources: 16 | - https://posts.specterops.io/certified-pre-owned-d95910965cd2 17 | - https://posts.specterops.io/adcs-attack-paths-in-bloodhound-part-2-ac7f925d1547 18 | acknowledgements: 19 | 20 | -------------------------------------------------------------------------------- /queries/Enabled users inactive for 180 days.yml: -------------------------------------------------------------------------------- 1 | name: Enabled users inactive for 180 days 2 | guid: 71972f3c-b32d-4023-a841-5cc8cc1c1867 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Active Directory Hygiene 6 | description: 7 | query: |- 8 | WITH 180 as inactive_days 9 | MATCH (n:User) 10 | WHERE n.enabled = true 11 | AND n.lastlogontimestamp < (datetime().epochseconds - (inactive_days * 86400)) // Replicated value 12 | AND n.lastlogon < (datetime().epochseconds - (inactive_days * 86400)) // Non-replicated value 13 | AND n.whencreated < (datetime().epochseconds - (inactive_days * 86400)) // Exclude recently created principals 14 | AND NOT n.objectid ENDS WITH '-500' // Removes false positive, built-in Administrator 15 | RETURN n 16 | LIMIT 1000 17 | revision: 1 18 | resources: 19 | acknowledgements: Martin Sohn Christensen, @martinsohndk 20 | 21 | -------------------------------------------------------------------------------- /queries/All Operators.yml: -------------------------------------------------------------------------------- 1 | name: All Operators 2 | guid: 3dfd0843-1ff9-4c21-aa67-feae08d109de 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Domain Information 6 | description: 7 | query: |- 8 | MATCH p=(:Base)-[:MemberOf]->(n:Group) 9 | WHERE ( 10 | n.objectid ENDS WITH 'S-1-5-32-551' OR // Backup Operators 11 | n.objectid ENDS WITH 'S-1-5-32-556' OR // Network Configuration Operators 12 | n.objectid ENDS WITH 'S-1-5-32-549' OR // Server Operators 13 | n.objectid ENDS WITH 'S-1-5-32-579' OR // Access Control Assistance Operators 14 | n.objectid ENDS WITH 'S-1-5-32-548' OR // Account Operators 15 | n.objectid ENDS WITH 'S-1-5-32-569' OR // Cryptographic Operators 16 | n.objectid ENDS WITH 'S-1-5-32-550' // Print Operators 17 | ) 18 | RETURN p 19 | revision: 1 20 | resources: 21 | acknowledgements: Martin Sohn Christensen, @martinsohndk 22 | 23 | -------------------------------------------------------------------------------- /queries/All privileged Azure Service Principals.yml: -------------------------------------------------------------------------------- 1 | name: All privileged Azure Service Principals 2 | guid: 92f269ee-3727-4ffa-947b-aad492ac0fa2 3 | prebuilt: false 4 | platforms: 5 | - Azure 6 | category: Azure Hygiene 7 | description: Return all privileged Azure Service Principals. 8 | query: |- 9 | MATCH p=(n:AZServicePrincipal)-[:AZHasRole|AZMemberOf*1..2]->(r:AZRole) 10 | WHERE r.displayname =~ '(?i)Global Administrator|User Administrator|Cloud Application Administrator|Authentication Policy Administrator|Exchange Administrator|Helpdesk Administrator|PRIVILEGED AUTHENTICATION ADMINISTRATOR|Domain Name Administrator|Hybrid Identity Administrator|External Identity Provider Administrator|Privileged Role Administrator|Partner Tier2 Support|Application Administrator|Directory Synchronization Accounts' 11 | RETURN p 12 | revision: 1 13 | resources: - 14 | acknowledgements: Daniel Scheidt, @theluemmel 15 | -------------------------------------------------------------------------------- /queries/Microsoft Entra Connect accounts with passwords not rotated in over 90 days.yml: -------------------------------------------------------------------------------- 1 | name: Microsoft Entra Connect accounts with passwords not rotated in over 90 days 2 | guid: 97fb1310-d15d-4d63-82a2-8788056250f1 3 | prebuilt: false 4 | platforms: 5 | - Active Directory 6 | - Azure 7 | category: Active Directory Hygiene 8 | description: Micosoft recommends to change the password of MSOL accounts every 90 days to prevent attackers from allowing use of the high privileges 9 | query: |- 10 | WITH 90 as days_since_change 11 | MATCH (u:User) 12 | WHERE u.name STARTS WITH "MSOL_" 13 | AND u.pwdlastset < (datetime().epochseconds - (days_since_change * 86400)) 14 | AND NOT u.pwdlastset IN [-1.0, 0.0] 15 | RETURN u 16 | revision: 1 17 | resources: https://learn.microsoft.com/en-us/defender-for-identity/rotate-password-microsoft-entra-connect 18 | acknowledgements: Martin Sohn Christensen, @martinsohndk 19 | 20 | -------------------------------------------------------------------------------- /queries/Computers with non-default Primary Group membership.yml: -------------------------------------------------------------------------------- 1 | name: Computers with non-default Primary Group membership 2 | guid: 5862dc4e-6f6f-4321-9474-d838968495ed 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Active Directory Hygiene 6 | description: 7 | query: |- 8 | MATCH p=(n:Computer)-[r:MemberOf]->(g:Group) 9 | WHERE NOT g.objectid ENDS WITH "-515" // Domain Computers 10 | AND NOT n.isdc = true 11 | AND NOT n.isreadonlydc = true 12 | AND r.isprimarygroup = true 13 | RETURN p 14 | revision: 2 15 | resources: 16 | - https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-ada3/e12954a4-6865-4432-94e6-00c310ca87c0 17 | - https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/5dbcf875-e802-4357-a6e2-1bdff19ff9b5 18 | - https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/73d11ea7-e634-453e-944d-559654cc91c5 19 | acknowledgements: Martin Sohn Christensen, @martinsohndk 20 | -------------------------------------------------------------------------------- /queries/Enrollment rights on published ESC15 certificate templates.yml: -------------------------------------------------------------------------------- 1 | name: Enrollment rights on published ESC15 certificate templates 2 | guid: 78d59fe1-e1a0-4813-adc9-c3c96ac08b66 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Active Directory Certificate Services 6 | description: Enrollment rights on certificate templates that meet the requirements for the ADCS ESC15 (EKUwu) attack. 7 | query: |- 8 | MATCH p=(:Base)-[:Enroll|AllExtendedRights]->(ct:CertTemplate)-[:PublishedTo]->(:EnterpriseCA)-[:TrustedForNTAuth]->(:NTAuthStore)-[:NTAuthStoreFor]->(:Domain) 9 | WHERE ct.enrolleesuppliessubject = True 10 | AND ct.authenticationenabled = False 11 | AND ct.requiresmanagerapproval = False 12 | AND ct.schemaversion = 1 13 | RETURN p 14 | revision: 1 15 | resources: 16 | - https://x.com/SpecterOps/status/1844800558151901639 17 | - https://msrc.microsoft.com/update-guide/en-US/advisory/CVE-2024-49019 18 | acknowledgements: Jonas Bülow Knudsen, @Jonas_B_K 19 | 20 | -------------------------------------------------------------------------------- /queries/Enabled computers inactive for 180 days.yml: -------------------------------------------------------------------------------- 1 | name: Enabled computers inactive for 180 days 2 | guid: 0768e810-1e1e-4319-a216-76d9c2058644 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Active Directory Hygiene 6 | description: 7 | query: |- 8 | WITH 180 as inactive_days 9 | MATCH (n:Computer) 10 | WHERE n.enabled = true 11 | AND n.lastlogontimestamp < (datetime().epochseconds - (inactive_days * 86400)) // Replicated value 12 | AND n.lastlogon < (datetime().epochseconds - (inactive_days * 86400)) // Non-replicated value 13 | AND n.whencreated < (datetime().epochseconds - (inactive_days * 86400)) // Exclude recently created principals 14 | AND NOT n.name STARTS WITH 'AZUREADKERBEROS.' // Removes false positive, Azure KRBTGT 15 | AND NOT n.name STARTS WITH 'AZUREADSSOACC.' // Removes false positive, Entra Seamless SSO 16 | RETURN n 17 | LIMIT 1000 18 | revision: 1 19 | resources: 20 | acknowledgements: Martin Sohn Christensen, @martinsohndk 21 | 22 | -------------------------------------------------------------------------------- /queries/Kerberos-enabled service accounts without AES encryption support.yml: -------------------------------------------------------------------------------- 1 | name: Kerberos-enabled service accounts without AES encryption support 2 | guid: cb8cf96e-21c9-422b-9439-390a13446ca6 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Active Directory Hygiene 6 | description: Accounts without Kerberos AES encryption support, or passwords set before the existence of Windows Server 2008 Domain Controller which therefore lack AES encryption keys. 7 | query: |- 8 | MATCH (n:Base) 9 | WHERE n.hasspn = true 10 | AND (( 11 | n.supportedencryptiontypes <> ['Not defined'] 12 | OR n.supportedencryptiontypes <> [] 13 | OR NONE(type IN n.supportedencryptiontypes WHERE type CONTAINS 'AES128' OR type CONTAINS 'AES256') 14 | ) 15 | OR (n.pwdlastset < 1204070400 // Password Last Set before Windows Server 2008 16 | AND NOT n.pwdlastset IN [-1.0, 0.0] 17 | )) 18 | RETURN n 19 | LIMIT 100 20 | revision: 2 21 | resources: 22 | acknowledgements: Martin Sohn Christensen, @martinsohndk 23 | 24 | -------------------------------------------------------------------------------- /queries/All GPOs applied to a specific computer.yml: -------------------------------------------------------------------------------- 1 | name: All GPOs applied to a specific Computer 2 | guid: 1d75a21e-0d34-40c5-9360-281b60737d87 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Domain Information 6 | description: View all GPOs that are applied to any specific computer. This query identifies GPOs that are applied at both the Domain Level and the OU level, saving time in large Active Directory environments where GPO inheritance is complex. Replace "COMPUTER_NAME" with the target computer name or a substring. Note this does not take OU 'Block inheritance' and GPO 'No Override' into account. 7 | query: |- 8 | // Replace "HOSTNAME/FQDN" with the computer's 9 | MATCH p=(c:Computer)<-[:Contains*..]-(:Base)<-[:GPLink]-(:GPO) 10 | WHERE toLower(c.name) CONTAINS toLower("HOSTNAME/FQDN") 11 | RETURN p 12 | revision: 1 13 | resources: 14 | - https://learn.microsoft.com/en-us/previous-versions/windows/desktop/Policy/overriding-and-blocking-group-policy 15 | acknowledgements: Adnan Ullah Khan, @auk0x01 16 | -------------------------------------------------------------------------------- /queries/Collection health of DC Registry Data.yml: -------------------------------------------------------------------------------- 1 | name: Collection health of DC Registry Data 2 | guid: 3f0fa2f3-fbdf-42c0-9e7d-97e689009161 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Domain Information 6 | description: BloodHound's ADCS analysis requires collecting CA registry data to increase accuracy/enable more edges. Collection by default requires SharpHound has Administrators membership. Requires SharpHound v2.3.5 or above. It only requires one misconfigured DC to potentially a full forest compromise by any principal. DCs returned by this query have not been collected. 7 | query: |- 8 | MATCH p=(:Domain)<-[:DCFor]-(c:Computer) 9 | WHERE c.strongcertificatebindingenforcementraw IS NULL 10 | // Exclude inactive DCs 11 | AND c.enabled = true 12 | AND c.lastlogontimestamp > (datetime().epochseconds - (30 * 86400)) 13 | RETURN p 14 | revision: 1 15 | resources: https://bloodhound.specterops.io/collect-data/enterprise-collection/permissions#dc-registry 16 | acknowledgements: Martin Sohn Christensen, @martinsohndk 17 | 18 | -------------------------------------------------------------------------------- /queries/Foreign Service Principals With any Abusable MS Graph App Role Assignment.yml: -------------------------------------------------------------------------------- 1 | name: Foreign Service Principals With any Abusable MS Graph App Role Assignment 2 | guid: d7a180c8-5624-4fc1-a407-deeb2ad3054c 3 | prebuilt: false 4 | platforms: Azure 5 | category: Dangerous Privileges 6 | description: MS Graph app role assignments provide significant power within an Entra ID tenant, similar to an Admin role. 7 | query: |- 8 | MATCH p = (sp1:AZServicePrincipal)-[r:AZMGGroupMember_ReadWrite_All|AZMGServicePrincipalEndpoint_ReadWrite_All|AZMGAppRoleAssignment_ReadWrite_All|AZMGGroup_ReadWrite_All|AZMGDirectory_ReadWrite_All|AZMGRoleManagement_ReadWrite_Directory]->(sp2:AZServicePrincipal) 9 | WHERE toUpper(sp1.appownerorganizationid) <> toUpper(sp1.tenantid) 10 | // Ensure AZServicePrincipal has a valid appownerorganizationid 11 | AND sp1.appownerorganizationid CONTAINS "-" 12 | RETURN p 13 | LIMIT 1000 14 | revision: 1 15 | resources: https://posts.specterops.io/microsoft-breach-how-can-i-see-this-in-bloodhound-33c92dca4c65 16 | acknowledgements: Stephen Hinck 17 | 18 | -------------------------------------------------------------------------------- /queries/Tier Zero accounts not members of Denied RODC Password Replication Group.yml: -------------------------------------------------------------------------------- 1 | name: Tier Zero accounts not members of Denied RODC Password Replication Group 2 | guid: e9613406-e346-410b-a033-690a6cf0c708 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Active Directory Hygiene 6 | description: 7 | query: |- 8 | // Get all Tier Zero accounts that are members of Denied RODC Password Replication Group 9 | MATCH (n:Base)-[:MemberOf*1..]->(m:Group) 10 | WHERE ((n:Tag_Tier_Zero) OR COALESCE(n.system_tags, '') CONTAINS 'admin_tier_0') 11 | AND (n:User OR n:Computer) 12 | AND m.objectid ENDS WITH '-519' 13 | WITH COLLECT(n.objectid) AS MembersOfDeniedGroup 14 | 15 | // Get all Tier Zero accounts 16 | MATCH (x:Base) 17 | WHERE ((x:Tag_Tier_Zero) OR COALESCE(x.system_tags, '') CONTAINS 'admin_tier_0') 18 | AND (x:User OR x:Computer) 19 | 20 | // Filter the members of Denied RODC Password Replication Group 21 | AND NOT x.objectid IN MembersOfDeniedGroup 22 | RETURN x 23 | revision: 2 24 | resources: 25 | acknowledgements: Martin Sohn Christensen, @martinsohndk 26 | 27 | -------------------------------------------------------------------------------- /queries/All ADCS ESC privilege escalation edges.yml: -------------------------------------------------------------------------------- 1 | name: All ADCS ESC privilege escalation edges 2 | guid: 49db8edc-8421-438f-b97b-23c042959bef 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Active Directory Certificate Services 6 | description: 7 | query: |- 8 | MATCH p=(:Base)-[:ADCSESC1|ADCSESC3|ADCSESC4|ADCSESC6a|ADCSESC6b|ADCSESC9a|ADCSESC9b|ADCSESC10a|ADCSESC10b|ADCSESC13|GoldenCert|CoerceAndRelayNTLMToADCS]->(:Base) 9 | RETURN p 10 | revision: 1 11 | resources: 12 | - https://posts.specterops.io/certified-pre-owned-d95910965cd2 13 | - https://posts.specterops.io/adcs-attack-paths-in-bloodhound-part-1-799f3d3b03cf 14 | - https://posts.specterops.io/adcs-attack-paths-in-bloodhound-part-2-ac7f925d1547 15 | - https://posts.specterops.io/adcs-attack-paths-in-bloodhound-part-3-33efb00856ac 16 | - https://posts.specterops.io/adcs-esc13-abuse-technique-fda4272fbd53 17 | - https://specterops.io/blog/2025/04/08/the-renaissance-of-ntlm-relay-attacks-everything-you-need-to-know/#:~:text=Introducing%20the%20CoerceAndRelayNTLMToADCS%20Edge 18 | acknowledgements: Jonas Bülow Knudsen, @Jonas_B_K 19 | 20 | -------------------------------------------------------------------------------- /queries/Non-Tier Zero principals with control of AdminSDHolder.yml: -------------------------------------------------------------------------------- 1 | name: Non-Tier Zero principals with control of AdminSDHolder 2 | guid: 4c1e0137-5b7f-48d8-bd09-9db7674bca61 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Dangerous Privileges 6 | description: 7 | query: |- 8 | MATCH p=(n:Group)-[r:Owns|GenericAll|GenericWrite|WriteOwner|WriteDacl|ForceChangePassword|AllExtendedRights|AddMember|AllowedToDelegate|CoerceToTGT|AllowedToAct|AdminTo|CanPSRemote|CanRDP|ExecuteDCOM|HasSIDHistory|AddSelf|DCSync|ReadLAPSPassword|ReadGMSAPassword|DumpSMSAPassword|SQLAdmin|AddAllowedToAct|WriteSPN|AddKeyCredentialLink|SyncLAPSPassword|WriteAccountRestrictions|WriteOwnerLimitedRights|OwnsLimitedRights]->(m:Container) 9 | WHERE NOT ((n:Tag_Tier_Zero) OR COALESCE(n.system_tags, '') CONTAINS 'admin_tier_0') 10 | AND m.name STARTS WITH "ADMINSDHOLDER@" 11 | RETURN p 12 | revision: 1 13 | resources: https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/plan/security-best-practices/appendix-c--protected-accounts-and-groups-in-active-directory#adminsdholder 14 | acknowledgements: Martin Sohn Christensen, @martinsohndk 15 | 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 SpecterOps 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /queries/Enabled Tier Zero High Value principals inactive for 60 days.yml: -------------------------------------------------------------------------------- 1 | name: Enabled Tier Zero / High Value principals inactive for 60 days 2 | guid: 72550bcb-3c4f-463d-8973-91a49163dc5a 3 | prebuilt: true 4 | platforms: Active Directory 5 | category: Active Directory Hygiene 6 | description: 7 | query: |- 8 | WITH 60 as inactive_days 9 | MATCH (n:Base) 10 | WHERE ((n:Tag_Tier_Zero) OR COALESCE(n.system_tags, '') CONTAINS 'admin_tier_0') 11 | AND n.enabled = true 12 | AND n.lastlogontimestamp < (datetime().epochseconds - (inactive_days * 86400)) // Replicated value 13 | AND n.lastlogon < (datetime().epochseconds - (inactive_days * 86400)) // Non-replicated value 14 | AND n.whencreated < (datetime().epochseconds - (inactive_days * 86400)) // Exclude recently created principals 15 | AND NOT n.name STARTS WITH 'AZUREADKERBEROS.' // Removes false positive, Azure KRBTGT 16 | AND NOT n.objectid ENDS WITH '-500' // Removes false positive, built-in Administrator 17 | AND NOT n.name STARTS WITH 'AZUREADSSOACC.' // Removes false positive, Entra Seamless SSO 18 | RETURN n 19 | revision: 1 20 | resources: 21 | acknowledgements: 22 | 23 | -------------------------------------------------------------------------------- /queries/Non-Tier Zero principals with BadSuccessor rights (with prerequisites check).yml: -------------------------------------------------------------------------------- 1 | name: Non-Tier Zero principals with BadSuccessor rights (with prerequisites check) 2 | guid: 74daaebe-6040-4f7a-9c9a-416faf73dcc3 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Dangerous Privileges 6 | description: Finds non-Tier Zero principals with BadSuccessor rights after checking prerequisites check (DC2025 & KDC key). 7 | query: |- 8 | // Find 2025 DCs 9 | MATCH (dc:Computer) 10 | WHERE dc.isdc = true AND dc.operatingsystem CONTAINS '2025' 11 | // Find gMSAs 12 | MATCH (m:User) 13 | WHERE m.gmsa = true 14 | // Find OU control 15 | MATCH p = (ou:OU)<-[:WriteDacl|Owns|GenericAll|WriteOwner]-(n:Base) 16 | // Confirm domain has a 2025 DC 17 | WHERE ou.domain = dc.domain 18 | // Confirm domain KDC key 19 | AND ou.domain = m.domain 20 | // Exclude Tier Zero 21 | AND NOT ((n:Tag_Tier_Zero) OR COALESCE(n.system_tags, '') CONTAINS 'admin_tier_0') 22 | RETURN p LIMIT 1000 23 | revision: 1 24 | resources: https://bsky.app/profile/specterops.io/post/3lpua65qeu22l 25 | acknowledgements: Martin Sohn Christensen, @martinsohndk 26 | 27 | -------------------------------------------------------------------------------- /queries/Tier Zero users with email.yml: -------------------------------------------------------------------------------- 1 | name: Tier Zero users with email 2 | guid: 9654c0d4-f1e8-4393-a2d1-53a5554a9de8 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Active Directory Hygiene 6 | description: Tier Zero accounts with email access have an increased attack surface. 7 | query: |- 8 | MATCH (n) 9 | WHERE ((n:Tag_Tier_Zero) OR COALESCE(n.system_tags, '') CONTAINS 'admin_tier_0') 10 | AND n.email <> "" 11 | AND n.enabled = true 12 | AND NOT toUpper(n.email) ENDS WITH ".ONMICROSOFT.COM" 13 | AND NOT ( 14 | (toUpper(n.email) STARTS WITH "HEALTHMAILBOX" 15 | OR toUpper(n.email) STARTS WITH "MSEXCHDISCOVERYMAILBOX" 16 | OR toUpper(n.email) STARTS WITH "MSEXCHDISCOVERY" 17 | OR toUpper(n.email) STARTS WITH "MSEXCHAPPROVAL" 18 | OR toUpper(n.email) STARTS WITH "FEDERATEDEMAIL" 19 | OR toUpper(n.email) STARTS WITH "SYSTEMMAILBOX" 20 | OR toUpper(n.email) STARTS WITH "MIGRATION.") 21 | AND 22 | (n.name STARTS WITH "SM_" 23 | OR n.name STARTS WITH "HEALTHMAILBOX") 24 | ) 25 | RETURN n 26 | revision: 1 27 | resources: 28 | acknowledgements: Martin Sohn Christensen, @martinsohndk 29 | 30 | -------------------------------------------------------------------------------- /queries/Potential GPO 'Apply' misconfiguration.yml: -------------------------------------------------------------------------------- 1 | name: Potential GPO 'Apply' misconfiguration 2 | guid: f5f2455e-afdc-4708-9a34-98f539ce52d8 3 | prebuilt: true 4 | platforms: Active Directory 5 | category: Dangerous Privileges 6 | description: In Active Directory, GPO's are applied to objects in the Group Policy Management Console by ticking "Allow - Apply group policy", but administrators can mistakenly tick "Allow - Write" or "Allow - Full Control" resulting in a misconfigured GPO that allows a principal to compromise other principals the GPO also applies to. Results are potential risks and must be audited for for correctness. 7 | query: |- 8 | MATCH p=(n:Base)-[:GenericAll|GenericWrite]->(g:GPO) 9 | 10 | // Exclude Enterprise Admins and Domain Admins 11 | WHERE NOT n.objectid =~ "-(519|512)$" 12 | 13 | // Exclude unresolved SIDs 14 | AND NOT (n.distinguishedname IS NULL) 15 | 16 | // Asset description may reveal if it's a delegation group (false-positive) or a filter group (true-positive) 17 | //AND n.description is not null 18 | //AND n.description =~ "(?i)apply" 19 | 20 | RETURN p 21 | LIMIT 1000 22 | revision: 2 23 | resources: 24 | acknowledgements: Martin Sohn Christensen, @martinsohndk 25 | 26 | -------------------------------------------------------------------------------- /queries/Collection health of CA Registry Data.yml: -------------------------------------------------------------------------------- 1 | name: Collection health of CA Registry Data 2 | guid: c8dd3479-8063-450a-9456-557bc5f39e10 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Domain Information 6 | description: BloodHound's ADCS analysis requires collecting CA registry data to increase accuracy/enable more edges. Collection by default requires SharpHound has Administrators membership. Requires SharpHound v2.3.5 or above. It only requires one misconfigured CA to potentially a full forest compromise by any principal. CAs returned by this query have not been collected. 7 | query: |- 8 | MATCH p=(eca:EnterpriseCA)<-[:HostsCAService]-(c:Computer) 9 | WHERE ( 10 | eca.isuserspecifiessanenabledcollected = false 11 | OR eca.casecuritycollected = false 12 | OR eca.enrollmentagentrestrictionscollected = false 13 | OR eca.roleseparationenabledcollected = false 14 | ) 15 | // Exclude inactive CAs 16 | AND c.enabled = true 17 | AND c.lastlogontimestamp > (datetime().epochseconds - (30 * 86400)) 18 | RETURN p 19 | revision: 1 20 | resources: https://bloodhound.specterops.io/collect-data/enterprise-collection/permissions#ca-registry 21 | acknowledgements: Martin Sohn Christensen, @martinsohndk 22 | 23 | -------------------------------------------------------------------------------- /queries/AdminSDHolder protected Accounts and Groups.yml: -------------------------------------------------------------------------------- 1 | name: AdminSDHolder protected Accounts and Groups 2 | guid: 5ee2f40e-a55c-4140-ab8a-91746ba3752b 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Domain Information 6 | description: Objects whose permissions are set by SDProp to the template AdminSDHolder object as per MS-ADTS 3.1.1.6.1.2 Protected Objects. Does not exclude objects if specified in dSHeuristics dwAdminSDExMask 7 | query: |- 8 | MATCH (n:Base)-[:MemberOf*0..]->(m:Group) 9 | WHERE ( 10 | n.objectid =~ ".*-(S-1-5-32-544|S-1-5-32-548|S-1-5-32-549|S-1-5-32-550|S-1-5-32-551|S-1-5-32-552|518|512|519)$" // Groups 11 | OR m.objectid =~ ".*-(S-1-5-32-544|S-1-5-32-548|S-1-5-32-549|S-1-5-32-550|S-1-5-32-551|S-1-5-32-552|518|512|519)$" // Members of groups 12 | OR n.objectid =~ ".*-(500|502|516|521)$" // Direct objects 13 | ) 14 | RETURN n 15 | revision: 1 16 | resources: 17 | - https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/a0d0b4fa-2895-4c64-b182-ba64ad0f84b8 18 | - https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/plan/security-best-practices/appendix-c--protected-accounts-and-groups-in-active-directory 19 | acknowledgements: Martin Sohn Christensen, @martinsohndk 20 | 21 | -------------------------------------------------------------------------------- /utilities/python/convert.py: -------------------------------------------------------------------------------- 1 | from typing_extensions import Annotated 2 | from pathlib import Path 3 | from schema import CypherQuery 4 | import json 5 | import glob 6 | import typer 7 | import yaml 8 | 9 | app = typer.Typer() 10 | 11 | 12 | @app.command() 13 | def to_json( 14 | input_dir: Annotated[ 15 | Path, 16 | typer.Argument( 17 | exists=True, 18 | file_okay=True, 19 | dir_okay=True, 20 | readable=True, 21 | resolve_path=True, 22 | ), 23 | ], 24 | output_file: Annotated[typer.FileTextWrite, typer.Argument()] 25 | ): 26 | cypher_queries = glob.glob(f"{input_dir}/**/*.yml", recursive=True) 27 | typer.echo(f"Converting Queries {len(cypher_queries)} to combined JSON") 28 | all_objects = [] 29 | for cypher_query in cypher_queries: 30 | with open(cypher_query, "r") as yaml_file: 31 | yaml_obj = yaml.safe_load(yaml_file) 32 | query = CypherQuery(**yaml_obj) 33 | all_objects.append(query.model_dump()) 34 | 35 | output_file.write(json.dumps(all_objects, indent=2)) 36 | typer.echo(f"Finished converting Cypher queries to JSON to {output_file.name}") 37 | 38 | 39 | if __name__ == "__main__": 40 | app() 41 | -------------------------------------------------------------------------------- /queries/Enabled computers inactive for 180 days - MSSQL Failover Cluster.yml: -------------------------------------------------------------------------------- 1 | name: Enabled computers inactive for 180 days - MSSQL Failover Cluster 2 | guid: d263e621-7f1b-4efb-ad25-098fc7d4fb72 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Active Directory Hygiene 6 | description: 7 | query: |- 8 | WITH 180 as inactive_days 9 | MATCH (n:Computer) 10 | WHERE n.enabled = true 11 | AND n.lastlogontimestamp < (datetime().epochseconds - (inactive_days * 86400)) // Replicated value 12 | AND n.lastlogon < (datetime().epochseconds - (inactive_days * 86400)) // Non-replicated value 13 | AND n.whencreated < (datetime().epochseconds - (inactive_days * 86400)) // Exclude recently created principals 14 | AND ANY(type IN n.serviceprincipalnames WHERE 15 | toLower(type) CONTAINS 'mssqlservercluster' OR 16 | toLower(type) CONTAINS 'mssqlserverclustermgmtapi' OR 17 | toLower(type) CONTAINS 'msclustervirtualserver') 18 | RETURN n 19 | LIMIT 1000 20 | revision: 1 21 | resources: https://learn.microsoft.com/en-us/troubleshoot/windows-server/high-availability/troubleshoot-issues-accounts-used-failover-clusters#troubleshoot-password-issues-with-the-cluster-name-account 22 | acknowledgements: Martin Sohn Christensen, @martinsohndk 23 | 24 | -------------------------------------------------------------------------------- /queries/Large default groups with outbound control.yml: -------------------------------------------------------------------------------- 1 | name: Large default groups with outbound control 2 | guid: a334f21a-3d7f-448e-b7ea-1465a3127bce 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Dangerous Privileges 6 | description: 7 | query: |- 8 | MATCH p=(n:Group)-[:Owns|GenericAll|GenericWrite|WriteOwner|WriteDacl|ForceChangePassword|AllExtendedRights|AddMember|AllowedToDelegate|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]->(:Base) 9 | WHERE n.objectid ENDS WITH "-513" // DOMAIN USERS 10 | OR n.objectid ENDS WITH "-515" // DOMAIN COMPUTERS 11 | OR n.objectid ENDS WITH "-S-1-5-11" // AUTHENTICATED USERS 12 | OR n.objectid ENDS WITH "-S-1-1-0" // EVERYONE 13 | OR n.objectid ENDS WITH "S-1-5-32-545" // USERS 14 | OR n.objectid ENDS WITH "S-1-5-32-546" // GUESTS 15 | OR n.objectid ENDS WITH "S-1-5-7" // ANONYMOUS 16 | RETURN p 17 | revision: 1 18 | resources: 19 | acknowledgements: Martin Sohn Christensen, @martinsohndk 20 | 21 | -------------------------------------------------------------------------------- /queries/Computers with passwords older than the default maximum password age.yml: -------------------------------------------------------------------------------- 1 | name: Computers with passwords older than the default maximum password age 2 | guid: 185c5010-8d4f-4f9b-b24e-831707dddfca 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Active Directory Hygiene 6 | description: Machine account passwords are regularly changed for security purposes. Starting with Windows 2000-based computers, the machine account password automatically changes every 30 days. 7 | query: |- 8 | WITH 60 as rotation_period 9 | MATCH (n:Computer) 10 | WHERE n.pwdlastset < (datetime().epochseconds - (rotation_period * 86400)) // password not rotated 11 | AND n.enabled = true // enabled computers 12 | AND n.whencreated < (datetime().epochseconds - (rotation_period * 86400)) // exclude recently created computers 13 | AND n.lastlogontimestamp > (datetime().epochseconds - (rotation_period * 86400)) // active computers (Replicated value) 14 | AND n.lastlogon > (datetime().epochseconds - (rotation_period * 86400)) // active computers (Non-replicated value) 15 | RETURN n 16 | revision: 1 17 | resources: https://learn.microsoft.com/en-us/troubleshoot/windows-server/windows-security/disable-machine-account-password 18 | acknowledgements: Martin Sohn Christensen, @martinsohndk 19 | 20 | -------------------------------------------------------------------------------- /queries/Direct Principal Rights Assignment.yml: -------------------------------------------------------------------------------- 1 | name: Direct Principal Rights Assignment 2 | guid: 1d9c6ae3-38fc-4089-b5ad-fc3be0fa8eec 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Active Directory Hygiene 6 | description: This query identifies rights assigned directly to users or computers instead of groups. Active Directory best practice requires granting rights to groups, then adding users as group members. This role-based access control (RBAC) approach ensures permissions are easily auditable and manageable. Results include inherited rights, which must be modified at the parent container level. 7 | query: |- 8 | MATCH p=(n:Base)-[r:GenericAll|GenericWrite|WriteOwner|WriteDacl|ForceChangePassword|AllExtendedRights|AddMember|AllowedToDelegate|AllowedToAct|AdminTo|CanPSRemote|CanRDP|ExecuteDCOM|AddSelf|DCSync|ReadLAPSPassword|ReadGMSAPassword|DumpSMSAPassword|AddAllowedToAct|WriteSPN|AddKeyCredentialLink|SyncLAPSPassword|WriteAccountRestrictions|WriteGPLink|ADCSESC1|ADCSESC3|ADCSESC4|ADCSESC6a|ADCSESC6b|ADCSESC9a|ADCSESC9b|ADCSESC10a|ADCSESC10b|ADCSESC13]->(:Base) 9 | WHERE (n:User OR n:Computer) 10 | RETURN p 11 | LIMIT 1000 12 | revision: 1 13 | resources: https://softwareengineering.stackexchange.com/questions/11856/whats-wrong-with-circular-references 14 | acknowledgements: Martin Sohn Christensen, @martinsohndk 15 | 16 | -------------------------------------------------------------------------------- /tests/schema.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel, field_validator, ConfigDict 2 | from typing import Optional, Union 3 | 4 | 5 | class CypherQuery(BaseModel): 6 | model_config = ConfigDict(extra='forbid') 7 | 8 | name: str 9 | guid: str 10 | prebuilt: bool = False 11 | platforms: Union[str, list[str]] 12 | category: str 13 | description: Optional[str] = None 14 | query: str 15 | revision: int 16 | resources: Optional[Union[str, list[str]]] = None 17 | acknowledgements: Optional[Union[str, list[str]]] = None 18 | 19 | @field_validator('platforms', mode='after') 20 | @classmethod 21 | def platforms_is_list(cls, value: str | list[str]) -> list[str]: 22 | if value is None: 23 | return [] 24 | return value if isinstance(value, list) else [value] 25 | 26 | @field_validator('resources', mode='after') 27 | @classmethod 28 | def resources_is_list(cls, value: str | list[str]) -> list[str]: 29 | if value is None: 30 | return [] 31 | return value if isinstance(value, list) else [value] 32 | 33 | @field_validator('acknowledgements', mode='after') 34 | @classmethod 35 | def acknowledgementsis_list(cls, value: str | list[str]) -> list[str]: 36 | if value is None: 37 | return [] 38 | return value if isinstance(value, list) else [value] 39 | -------------------------------------------------------------------------------- /tests/templates/githubsummary.md.j2: -------------------------------------------------------------------------------- 1 | # Test Results 2 | 3 | Run on: {{ data.timestamp }} 4 | 5 | | Status | Count | 6 | |--------|-------| 7 | | ✅ Passed | {{ data.summary.passed }} | 8 | | ❌ Failed | {{ data.summary.failed }} | 9 | | ⏭️ Skipped | {{ data.summary.skipped }} | 10 | 11 | {% if data.summary.failed > 0 -%} 12 | ## Failed Tests 13 | 14 | {%- for test in data.tests.failed %} 15 | ### {{ test.name }} 16 | {% if "query" in test %} 17 | ``` 18 | {{ test.query }} 19 | ``` 20 | {% endif %} 21 | - Path: `{{ test.path }}` 22 | - Reason: {{ test.message }} 23 | - Duration: {{ "%.2f"|format(test.duration) }}s 24 | {% if test.get('error') %} 25 |
26 | Error Details 27 | ``` 28 | {{ test.error }} 29 | ``` 30 |
31 | {% endif %} 32 | {% endfor %} 33 | {% endif %} 34 | 35 | {% if data.summary.passed > 0 -%} 36 | ## Passed Tests 37 | 38 |
39 | Show all passed tests 40 | {% for test in data.tests.passed %} 41 | ### {{ test.name }} 42 | - Path: `{{ test.path }}` 43 | - Duration: {{ "%.2f"|format(test.duration) }}s 44 | {% endfor %} 45 |
46 | {% endif %} 47 | 48 | {% if data.summary.skipped > 0 %} 49 | ## Skipped Tests 50 | 51 |
52 | Show all skipped tests 53 | 54 | {% for test in data.tests %} 55 | ### {{ test.name }} 56 | - Path: `{{ test.path }}` 57 | {% endfor %} 58 |
59 | {% endif %} -------------------------------------------------------------------------------- /utilities/python/schema.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel, field_validator, ConfigDict 2 | from typing import Optional, Union 3 | 4 | 5 | class CypherQuery(BaseModel): 6 | model_config = ConfigDict(extra='forbid') 7 | 8 | name: str 9 | guid: str 10 | prebuilt: bool = False 11 | platforms: Union[str, list[str]] 12 | category: str 13 | description: Optional[str] = None 14 | query: str 15 | revision: int 16 | resources: Optional[Union[str, list[str]]] = None 17 | acknowledgements: Optional[Union[str, list[str]]] = None 18 | 19 | @field_validator('platforms', mode='after') 20 | @classmethod 21 | def platforms_is_list(cls, value: str | list[str]) -> list[str]: 22 | if value is None: 23 | return [] 24 | return value if isinstance(value, list) else [value] 25 | 26 | @field_validator('resources', mode='after') 27 | @classmethod 28 | def resources_is_list(cls, value: str | list[str]) -> list[str]: 29 | if value is None: 30 | return [] 31 | return value if isinstance(value, list) else [value] 32 | 33 | @field_validator('acknowledgements', mode='after') 34 | @classmethod 35 | def acknowledgementsis_list(cls, value: str | list[str]) -> list[str]: 36 | if value is None: 37 | return [] 38 | return value if isinstance(value, list) else [value] 39 | -------------------------------------------------------------------------------- /docs/query-structure.yml: -------------------------------------------------------------------------------- 1 | # This file describes the basic YAML structure of a query in the BloodHound Query Library 2 | # Please use it as a template when creating new queries 3 | name: The name of the query. 4 | guid: 51cd8e42-f795-4551-a009-61395e6cedf5 # Unique GUID for the query, for example from PowerShell Cmdlet `New-Guid`. 5 | prebuilt: false # Whether the query is part of the default query set in BloodHound's Pre-built Searches. Your custom query must set this to false. 6 | platform: Active Directory # One or more platforms the query targets/shows risk in. 7 | # Use a list for multiple platforms: 8 | # - Active Directory 9 | # - Azure 10 | category: The category of the query # Should be one of the existing categories in BloodHound's Pre-built Searches. 11 | description: Short description of the query. 12 | query: |- 13 | // My clean and well-commented Cypher query 14 | MATCH p = (b:BloodHoundUsers) - [h:ThinkIn] -> (e:Graphs) 15 | RETURN p 16 | revision: 1 # Version number integer starting at 1. 17 | resources: # URL references, for example related to Acknowledgements. 18 | # Use a list for multiple Resources: 19 | # - https://specterops.io/ 20 | # - https://posts.specterops.io/bloodhound-community-edition-a-new-era-d64689806e90 21 | acknowledgements: Name of Person, @TheirHandle # For example the query author 22 | # Use a list for multiple Acknowledgements: 23 | # - Martin Sohn Christensen, @martinsohndk 24 | # - Joey Dreijer, @d3vzer0 -------------------------------------------------------------------------------- /queries/Non-Tier Zero account with 'Admin Count' flag.yml: -------------------------------------------------------------------------------- 1 | name: Non-Tier Zero account with 'Admin Count' flag 2 | guid: e7f703b3-5dba-4aef-8346-4d589be2c828 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Active Directory Hygiene 6 | description: Accounts that were members of AD's built-in administrative groups, thus had the 'AdminCount' flag set, but are not currently in those groups and not tagged as Tier Zero. These accounts could still be highly privileged by other means. 7 | query: |- 8 | MATCH (n:Base) 9 | WHERE (n:User OR n:Computer) 10 | AND n.admincount = true 11 | AND NOT ((n:Tag_Tier_Zero) OR COALESCE(n.system_tags, '') CONTAINS 'admin_tier_0') 12 | AND NOT n.objectid ENDS WITH '-502' // KRBTGT user 13 | AND NOT n.objectid ENDS WITH '-500' // Administrator user 14 | OPTIONAL MATCH (n)-[:MemberOf]->(g:Group) 15 | WHERE g.objectid ENDS WITH '-512' // Domain Admins 16 | OR g.objectid ENDS WITH '-548' // Account Operators 17 | OR g.objectid ENDS WITH '-544' // Administrators 18 | OR g.objectid ENDS WITH '-551' // Backup Operators 19 | OR g.objectid ENDS WITH '-516' // Domain Controllers 20 | OR g.objectid ENDS WITH '-519' // Enterprise Admins 21 | OR g.objectid ENDS WITH '-527' // Enterprise Key Admins 22 | OR g.objectid ENDS WITH '-526' // Key Admins 23 | OR g.objectid ENDS WITH '-550' // Print Operators 24 | OR g.objectid ENDS WITH '-521' // Read-Only Domain Controllers 25 | OR g.objectid ENDS WITH '-552' // Replicators 26 | OR g.objectid ENDS WITH '-518' // Schema Admins 27 | OR g.objectid ENDS WITH '-549' // Server Operators 28 | WITH n, g 29 | WHERE g IS NULL 30 | RETURN n 31 | revision: 2 32 | resources: 33 | - https://learn.microsoft.com/en-us/windows/win32/adschema/a-admincount 34 | - https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/plan/security-best-practices/appendix-c--protected-accounts-and-groups-in-active-directory#protected-groups 35 | acknowledgements: 36 | - Martin Sohn Christensen, @martinsohndk 37 | - kaasimir, @kaasimir 38 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/query-issue-report.yml: -------------------------------------------------------------------------------- 1 | name: Query issue report 2 | description: Report a query issue to help improve the project 3 | title: '[Query Issue]: ' 4 | labels: 5 | - query 6 | 7 | body: 8 | - type: markdown 9 | attributes: 10 | value: | 11 | # Report an issue with a BloodHound query 12 | Use this issue/form to report any issues with a query saved in the repository 13 | 14 | - type: markdown 15 | attributes: 16 | value: | 17 | ## Query details 18 | 19 | - type: input 20 | id: guid 21 | attributes: 22 | label: Query GUID 23 | description: The query unique's identifier 24 | validations: 25 | required: true 26 | 27 | - type: textarea 28 | id: query_body 29 | attributes: 30 | label: Query content 31 | description: | 32 | The BloodHound query content 33 | ```bash 34 | MATCH p = (s:Computer)-[:DCFor]->(:Domain) 35 | WHERE s.strongcertificatebindingenforcementraw = 0 OR s.strongcertificatebindingenforcementraw = 1 36 | RETURN p 37 | LIMIT 1000 38 | ``` 39 | validations: 40 | required: true 41 | 42 | - type: textarea 43 | id: issue_description 44 | attributes: 45 | label: Issue description 46 | description: | 47 | Describe the issue faced when using the query 48 | validations: 49 | required: true 50 | 51 | - type: markdown 52 | attributes: 53 | value: | 54 | ## BloodHound context 55 | 56 | - type: input 57 | id: bloodhound_version 58 | attributes: 59 | label: BloodHound version 60 | description: The version of BloodHound used when trying to run the query 61 | placeholder: BloodHound CE 7.0 62 | validations: 63 | required: true 64 | 65 | - type: input 66 | id: bloodhound_db 67 | attributes: 68 | label: BloodHound DB 69 | description: "The database used for BloodHound (hint: CE uses Neo4j by default, while BHE is always Postgres)" 70 | placeholder: e.g. Neo4j or Postgres 71 | validations: 72 | required: true 73 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from jinja2 import Environment, FileSystemLoader 3 | import pytest 4 | 5 | 6 | class GithubReporter: 7 | def __init__(self): 8 | self.results = { 9 | "summary": {"passed": 0, "failed": 0, "skipped": 0, "total": 0}, 10 | "tests": { 11 | "failed": [], 12 | "passed": [], 13 | "skipped": [], 14 | }, 15 | "timestamp": datetime.now().isoformat(), 16 | } 17 | 18 | @pytest.hookimpl(hookwrapper=True) 19 | def pytest_runtest_makereport(self, item: pytest.Item, call: pytest.CallInfo) -> None: 20 | outcome = yield 21 | report = outcome.get_result() 22 | if report.when == "call": 23 | test_info = { 24 | "name": item.name, 25 | "path": item.nodeid, 26 | "outcome": report.outcome, 27 | "duration": report.duration, 28 | } 29 | 30 | if report.outcome == "failed": 31 | extra_data = getattr(item, "user_data", {}) 32 | if "query" in extra_data: 33 | test_info['query'] = extra_data["query"] 34 | test_info['query'] = extra_data.get("query", "Query not found") 35 | if hasattr(report.longrepr, "reprcrash"): 36 | error_text = report.longrepr.reprcrash.message 37 | message = error_text.strip().split("\n")[0] 38 | test_info["message"] = message 39 | 40 | self.results["summary"][report.outcome] += 1 41 | self.results["summary"]["total"] += 1 42 | self.results["tests"][report.outcome].append(test_info) 43 | 44 | def pytest_sessionfinish(self, session: pytest.Session) -> None: 45 | env = Environment( 46 | loader=FileSystemLoader("tests/templates"), 47 | ) 48 | template = env.get_template("githubsummary.md.j2") 49 | rendered = template.render(data=self.results) 50 | with open("test-report.md", "w") as f: 51 | f.write(rendered) 52 | 53 | 54 | def pytest_configure(config: pytest.Config) -> None: 55 | reporter = GithubReporter() 56 | config.pluginmanager.register(reporter, "github_reporter") 57 | -------------------------------------------------------------------------------- /.github/workflows/syntax.yml: -------------------------------------------------------------------------------- 1 | name: Query syntax validation 2 | on: 3 | pull_request: 4 | branches: [ 'main' ] 5 | 6 | workflow_dispatch: 7 | 8 | permissions: 9 | contents: write 10 | 11 | jobs: 12 | test: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v4 17 | with: 18 | ref: ${{ github.head_ref }} 19 | - name: Set up Python 3.10 20 | uses: actions/setup-python@v3 21 | with: 22 | python-version: "3.10" 23 | - name: Install dependencies 24 | run: | 25 | python -m pip install --upgrade pip 26 | pip install -r requirements.txt 27 | 28 | - name: Test queries with pytest 29 | run: | 30 | pytest tests/test_cypher_syntax.py 31 | 32 | - name: Add test report to summary 33 | run: cat test-report.md >> $GITHUB_STEP_SUMMARY 34 | 35 | - name: Upload test report 36 | uses: actions/upload-artifact@v4 37 | with: 38 | name: test-report 39 | path: test-report.md 40 | 41 | build: 42 | runs-on: ubuntu-latest 43 | needs: test 44 | steps: 45 | - uses: actions/checkout@v4 46 | with: 47 | ref: ${{ github.head_ref }} 48 | - name: Set up Python 3.10 49 | uses: actions/setup-python@v3 50 | with: 51 | python-version: "3.10" 52 | - name: Install dependencies 53 | run: | 54 | python -m pip install --upgrade pip 55 | pip install -r requirements.txt 56 | 57 | - name: Install dependencies 58 | run: | 59 | python -m pip install --upgrade pip 60 | pip install -r requirements.txt 61 | 62 | - name: Convert queries into single json 63 | run: | 64 | python utilities/python/convert.py ./queries ./Queries.json 65 | 66 | - name: Configure Git 67 | run: | 68 | git config --global user.name "github-actions[bot]" 69 | git config --global user.email "github-actions[bot]@users.noreply.github.com" 70 | 71 | - name: Commit if changed 72 | run: | 73 | git add ./Queries.json 74 | if git diff --staged --quiet; then 75 | echo "No changes to commit" 76 | else 77 | git commit -m "Update combined queries" 78 | git push 79 | fi 80 | -------------------------------------------------------------------------------- /queries/Uncommon permission on containers.yml: -------------------------------------------------------------------------------- 1 | name: Uncommon permission on containers 2 | guid: 018c2b45-e30f-47d8-a751-22419c3d0736 3 | prebuilt: false 4 | platforms: Active Directory 5 | category: Active Directory Hygiene 6 | description: BloodHound typically identifies risk on Active Directory objects stored in OUs, however behind the scenes; Active Directory has a hieracy of containers e.g. CN=SYSTEM and CN=CONFIGURATION, on which control can lead to risk. Results are prone to false-positives but can assist auditing containers permissions. 7 | query: |- 8 | MATCH p=(:Domain)-[:Contains*1..]->(c:Container)<-[r]-(n:Base) 9 | 10 | // Exclude Tier Zero 11 | WHERE NOT ((n:Tag_Tier_Zero) OR COALESCE(n.system_tags, '') CONTAINS 'admin_tier_0') 12 | 13 | // Scope edges to ACLs 14 | AND r.isacl 15 | 16 | // Exclude CN=Users and CN=Computers containers 17 | AND NOT c.distinguishedname STARTS WITH "CN=COMPUTERS,DC=" 18 | AND NOT c.distinguishedname STARTS WITH "CN=USERS,DC=" 19 | 20 | // Exclude same-domain unresolved SIDs 21 | AND NOT (n.distinguishedname IS NULL AND n.domainsid = c.domainsid) 22 | 23 | // Exclude default: Cert Publishers 24 | AND NOT (c.distinguishedname CONTAINS ",CN=PUBLIC KEY SERVICES,CN=SERVICES,CN=CONFIGURATION,DC=" AND n.objectid ENDS WITH "-517") 25 | 26 | // Exclude default: RAS and IAS Servers 27 | AND NOT (c.distinguishedname CONTAINS "CN=RAS AND IAS SERVERS ACCESS CHECK,CN=SYSTEM,DC=" AND n.objectid ENDS WITH "-553") 28 | 29 | // Exclude default: DNS 30 | AND NOT (c.distinguishedname CONTAINS "CN=MICROSOFTDNS,CN=SYSTEM,DC=" AND n.name STARTS WITH "DNSADMINS@") 31 | 32 | // Exclude default: ConfigMgr 33 | AND NOT (c.distinguishedname STARTS WITH "CN=SYSTEM MANAGEMENT,CN=SYSTEM,DC=" AND n.samaccountname ENDS WITH "$") 34 | 35 | // Exclude default: Exchange pt1 36 | AND NOT (c.distinguishedname CONTAINS "CN=MICROSOFT EXCHANGE,CN=SERVICES,CN=CONFIGURATION,DC=" AND (n.name STARTS WITH "EXCHANGE TRUSTED SUBSYSTEM@" OR n.name STARTS WITH "ORGANIZATION MANAGEMENT@" OR n.name STARTS WITH "EXCHANGE SERVICES@")) 37 | 38 | // Exclude default: Exchange pt2 39 | AND NOT ((c.distinguishedname CONTAINS "CN=MONITORING MAILBOXES,CN=MICROSOFT EXCHANGE SYSTEM OBJECTS,DC=" OR c.distinguishedname CONTAINS "CN=MICROSOFT EXCHANGE SYSTEM OBJECTS,DC=") AND n.name STARTS WITH "EXCHANGE ENTERPRISE SERVERS@") 40 | 41 | // Exclude default: Exchange pt3 42 | AND NOT ((c.distinguishedname CONTAINS "CN=ACTIVE DIRECTORY CONNECTIONS,CN=MICROSOFT EXCHANGE,CN=SERVICES,CN=CONFIGURATION,DC=" OR c.distinguishedname CONTAINS "CN=MICROSOFT EXCHANGE SYSTEM OBJECTS,DC=" OR c.distinguishedname =~ "CN=RECIPIENT UPDATE SERVICES,CN=ADDRESS LISTS CONTAINER,CN=.*,CN=MICROSOFT EXCHANGE,CN=SERVICES,CN=CONFIGURATION,DC=") AND n.name STARTS WITH "EXCHANGE DOMAIN SERVERS@") 43 | 44 | RETURN p 45 | LIMIT 2000 46 | revision: 1 47 | resources: 48 | acknowledgements: Martin Sohn Christensen, @martinsohndk 49 | 50 | -------------------------------------------------------------------------------- /tests/grammar/cypher/Cypher.tokens: -------------------------------------------------------------------------------- 1 | T__0=1 2 | T__1=2 3 | T__2=3 4 | T__3=4 5 | T__4=5 6 | T__5=6 7 | T__6=7 8 | T__7=8 9 | T__8=9 10 | T__9=10 11 | T__10=11 12 | T__11=12 13 | T__12=13 14 | T__13=14 15 | T__14=15 16 | T__15=16 17 | T__16=17 18 | T__17=18 19 | T__18=19 20 | T__19=20 21 | T__20=21 22 | T__21=22 23 | T__22=23 24 | T__23=24 25 | T__24=25 26 | T__25=26 27 | T__26=27 28 | T__27=28 29 | T__28=29 30 | T__29=30 31 | T__30=31 32 | T__31=32 33 | T__32=33 34 | T__33=34 35 | T__34=35 36 | T__35=36 37 | T__36=37 38 | T__37=38 39 | T__38=39 40 | T__39=40 41 | T__40=41 42 | T__41=42 43 | T__42=43 44 | T__43=44 45 | T__44=45 46 | T__45=46 47 | CYPHER=47 48 | EXPLAIN=48 49 | PROFILE=49 50 | USING=50 51 | PERIODIC=51 52 | COMMIT=52 53 | UNION=53 54 | ALL=54 55 | CREATE=55 56 | DROP=56 57 | INDEX=57 58 | ON=58 59 | CONSTRAINT=59 60 | ASSERT=60 61 | IS=61 62 | UNIQUE=62 63 | EXISTS=63 64 | LOAD=64 65 | CSV=65 66 | WITH=66 67 | HEADERS=67 68 | FROM=68 69 | AS=69 70 | FIELDTERMINATOR=70 71 | OPTIONAL=71 72 | MATCH=72 73 | UNWIND=73 74 | MERGE=74 75 | SET=75 76 | DETACH=76 77 | DELETE=77 78 | REMOVE=78 79 | FOREACH=79 80 | IN=80 81 | CALL=81 82 | YIELD=82 83 | RETURN=83 84 | DISTINCT=84 85 | ORDER=85 86 | BY=86 87 | L_SKIP=87 88 | LIMIT=88 89 | ASCENDING=89 90 | ASC=90 91 | DESCENDING=91 92 | DESC=92 93 | JOIN=93 94 | SCAN=94 95 | START=95 96 | NODE=96 97 | RELATIONSHIP=97 98 | REL=98 99 | WHERE=99 100 | SHORTESTPATH=100 101 | ALLSHORTESTPATHS=101 102 | OR=102 103 | XOR=103 104 | AND=104 105 | NOT=105 106 | STARTS=106 107 | ENDS=107 108 | CONTAINS=108 109 | NULL=109 110 | COUNT=110 111 | CASE=111 112 | ELSE=112 113 | END=113 114 | WHEN=114 115 | THEN=115 116 | FILTER=116 117 | EXTRACT=117 118 | REDUCE=118 119 | ANY=119 120 | NONE=120 121 | SINGLE=121 122 | TRUE=122 123 | FALSE=123 124 | HexInteger=124 125 | DecimalInteger=125 126 | OctalInteger=126 127 | HexLetter=127 128 | HexDigit=128 129 | Digit=129 130 | NonZeroDigit=130 131 | NonZeroOctDigit=131 132 | OctDigit=132 133 | ZeroDigit=133 134 | ExponentDecimalReal=134 135 | RegularDecimalReal=135 136 | StringLiteral=136 137 | EscapedChar=137 138 | DO=138 139 | FOR=139 140 | REQUIRE=140 141 | MANDATORY=141 142 | SCALAR=142 143 | OF=143 144 | ADD=144 145 | UnescapedSymbolicName=145 146 | IdentifierStart=146 147 | IdentifierPart=147 148 | EscapedSymbolicName=148 149 | SP=149 150 | WHITESPACE=150 151 | Comment=151 152 | ';'=1 153 | '='=2 154 | '('=3 155 | ')'=4 156 | '['=5 157 | ']'=6 158 | ','=7 159 | '+='=8 160 | '|'=9 161 | '*'=10 162 | ':'=11 163 | '..'=12 164 | '<>'=13 165 | '<'=14 166 | '>'=15 167 | '<='=16 168 | '>='=17 169 | '=~'=18 170 | '+'=19 171 | '-'=20 172 | '/'=21 173 | '%'=22 174 | '^'=23 175 | '.'=24 176 | '{'=25 177 | '}'=26 178 | '$'=27 179 | '\u27e8'=28 180 | '\u3008'=29 181 | '\ufe64'=30 182 | '\uff1c'=31 183 | '\u27e9'=32 184 | '\u3009'=33 185 | '\ufe65'=34 186 | '\uff1e'=35 187 | '\u00ad'=36 188 | '\u2010'=37 189 | '\u2011'=38 190 | '\u2012'=39 191 | '\u2013'=40 192 | '\u2014'=41 193 | '\u2015'=42 194 | '\u2212'=43 195 | '\ufe58'=44 196 | '\ufe63'=45 197 | '\uff0d'=46 198 | '0'=133 199 | -------------------------------------------------------------------------------- /tests/grammar/cypher/CypherLexer.tokens: -------------------------------------------------------------------------------- 1 | T__0=1 2 | T__1=2 3 | T__2=3 4 | T__3=4 5 | T__4=5 6 | T__5=6 7 | T__6=7 8 | T__7=8 9 | T__8=9 10 | T__9=10 11 | T__10=11 12 | T__11=12 13 | T__12=13 14 | T__13=14 15 | T__14=15 16 | T__15=16 17 | T__16=17 18 | T__17=18 19 | T__18=19 20 | T__19=20 21 | T__20=21 22 | T__21=22 23 | T__22=23 24 | T__23=24 25 | T__24=25 26 | T__25=26 27 | T__26=27 28 | T__27=28 29 | T__28=29 30 | T__29=30 31 | T__30=31 32 | T__31=32 33 | T__32=33 34 | T__33=34 35 | T__34=35 36 | T__35=36 37 | T__36=37 38 | T__37=38 39 | T__38=39 40 | T__39=40 41 | T__40=41 42 | T__41=42 43 | T__42=43 44 | T__43=44 45 | T__44=45 46 | T__45=46 47 | CYPHER=47 48 | EXPLAIN=48 49 | PROFILE=49 50 | USING=50 51 | PERIODIC=51 52 | COMMIT=52 53 | UNION=53 54 | ALL=54 55 | CREATE=55 56 | DROP=56 57 | INDEX=57 58 | ON=58 59 | CONSTRAINT=59 60 | ASSERT=60 61 | IS=61 62 | UNIQUE=62 63 | EXISTS=63 64 | LOAD=64 65 | CSV=65 66 | WITH=66 67 | HEADERS=67 68 | FROM=68 69 | AS=69 70 | FIELDTERMINATOR=70 71 | OPTIONAL=71 72 | MATCH=72 73 | UNWIND=73 74 | MERGE=74 75 | SET=75 76 | DETACH=76 77 | DELETE=77 78 | REMOVE=78 79 | FOREACH=79 80 | IN=80 81 | CALL=81 82 | YIELD=82 83 | RETURN=83 84 | DISTINCT=84 85 | ORDER=85 86 | BY=86 87 | L_SKIP=87 88 | LIMIT=88 89 | ASCENDING=89 90 | ASC=90 91 | DESCENDING=91 92 | DESC=92 93 | JOIN=93 94 | SCAN=94 95 | START=95 96 | NODE=96 97 | RELATIONSHIP=97 98 | REL=98 99 | WHERE=99 100 | SHORTESTPATH=100 101 | ALLSHORTESTPATHS=101 102 | OR=102 103 | XOR=103 104 | AND=104 105 | NOT=105 106 | STARTS=106 107 | ENDS=107 108 | CONTAINS=108 109 | NULL=109 110 | COUNT=110 111 | CASE=111 112 | ELSE=112 113 | END=113 114 | WHEN=114 115 | THEN=115 116 | FILTER=116 117 | EXTRACT=117 118 | REDUCE=118 119 | ANY=119 120 | NONE=120 121 | SINGLE=121 122 | TRUE=122 123 | FALSE=123 124 | HexInteger=124 125 | DecimalInteger=125 126 | OctalInteger=126 127 | HexLetter=127 128 | HexDigit=128 129 | Digit=129 130 | NonZeroDigit=130 131 | NonZeroOctDigit=131 132 | OctDigit=132 133 | ZeroDigit=133 134 | ExponentDecimalReal=134 135 | RegularDecimalReal=135 136 | StringLiteral=136 137 | EscapedChar=137 138 | DO=138 139 | FOR=139 140 | REQUIRE=140 141 | MANDATORY=141 142 | SCALAR=142 143 | OF=143 144 | ADD=144 145 | UnescapedSymbolicName=145 146 | IdentifierStart=146 147 | IdentifierPart=147 148 | EscapedSymbolicName=148 149 | SP=149 150 | WHITESPACE=150 151 | Comment=151 152 | ';'=1 153 | '='=2 154 | '('=3 155 | ')'=4 156 | '['=5 157 | ']'=6 158 | ','=7 159 | '+='=8 160 | '|'=9 161 | '*'=10 162 | ':'=11 163 | '..'=12 164 | '<>'=13 165 | '<'=14 166 | '>'=15 167 | '<='=16 168 | '>='=17 169 | '=~'=18 170 | '+'=19 171 | '-'=20 172 | '/'=21 173 | '%'=22 174 | '^'=23 175 | '.'=24 176 | '{'=25 177 | '}'=26 178 | '$'=27 179 | '\u27e8'=28 180 | '\u3008'=29 181 | '\ufe64'=30 182 | '\uff1c'=31 183 | '\u27e9'=32 184 | '\u3009'=33 185 | '\ufe65'=34 186 | '\uff1e'=35 187 | '\u00ad'=36 188 | '\u2010'=37 189 | '\u2011'=38 190 | '\u2012'=39 191 | '\u2013'=40 192 | '\u2014'=41 193 | '\u2015'=42 194 | '\u2212'=43 195 | '\ufe58'=44 196 | '\ufe63'=45 197 | '\uff0d'=46 198 | '0'=133 199 | -------------------------------------------------------------------------------- /tests/test_cypher_syntax.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import glob 4 | import pytest 5 | import yaml 6 | from antlr4 import CommonTokenStream, InputStream 7 | from antlr4.error.ErrorListener import ErrorListener 8 | from grammar.CypherLexer import CypherLexer 9 | from grammar.CypherParser import CypherParser 10 | from schema import CypherQuery 11 | from pydantic import ValidationError 12 | 13 | sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) 14 | 15 | 16 | class CypherErrorListener(ErrorListener): 17 | 18 | def __init__(self): 19 | super(CypherErrorListener, self).__init__() 20 | 21 | def syntaxError(self, recognizer, offendingSymbol, line: int, column: int, 22 | msg: str, e: Exception) -> None: 23 | raise ValueError(f"Syntax error at line: {line}, msg: {msg}") 24 | 25 | 26 | def get_query_files(cypher_dir: str = "queries") -> list: 27 | if not os.path.exists(cypher_dir): 28 | return [] 29 | return glob.glob(os.path.join("queries", "**", "*.yml"), recursive=True) 30 | 31 | 32 | @pytest.mark.parametrize("file_path", get_query_files("queries")) 33 | def test_cypher_validation(file_path: str, request: pytest.FixtureRequest) -> None: 34 | with open(file_path, "r") as f: 35 | yaml_object = yaml.safe_load(f) 36 | 37 | try: 38 | # Load the query using the Pydantic schema 39 | validate_schema = CypherQuery(**yaml_object) 40 | except ValidationError as e: 41 | pytest.fail(f"Pydantic validation failed for {file_path}: {str(e)}", pytrace=False) 42 | 43 | # Save the query content for error reports 44 | request.node.user_data = {"query": validate_schema.query} 45 | 46 | # Split query into multiple lines in order to remove line comments 47 | lines = validate_schema.query.splitlines() 48 | uncomment_query = "\n".join(line.split("//")[0].rstrip() for line in lines) 49 | uncomment_query = uncomment_query.lstrip("\n") 50 | 51 | # Attempt to load/parse the query using the CypherParser 52 | lexer = CypherLexer(InputStream(uncomment_query)) 53 | stream = CommonTokenStream(lexer) 54 | parser = CypherParser(stream) 55 | parser.addErrorListener(CypherErrorListener()) 56 | 57 | # Attempt to parse the query or raise a generic exception 58 | try: 59 | parse_query = parser.oC_Query() 60 | assert parse_query.exception is None 61 | 62 | except Exception as e: 63 | pytest.fail(f"Parsing failed for file {file_path}: {str(e)}", pytrace=False) 64 | 65 | 66 | def test_duplicate_guid() -> None: 67 | query_files = get_query_files("queries") 68 | guids = set() 69 | 70 | # Iterate over all query files and check for duplicate GUIDs 71 | for file_path in query_files: 72 | with open(file_path, "r") as f: 73 | yaml_object = yaml.safe_load(f) 74 | 75 | query_guid = yaml_object["guid"] 76 | if query_guid in guids: 77 | pytest.fail(f"Duplicate GUID found: {query_guid} in file {file_path}", pytrace=False) 78 | guids.add(query_guid) 79 | 80 | 81 | if __name__ == "__main__": 82 | pytest.main(["-v", __file__]) 83 | -------------------------------------------------------------------------------- /docs/security-assessment-mapping.md: -------------------------------------------------------------------------------- 1 | # BloodHound as a Comprehensive Assessment Platform 2 | BloodHound was designed to solve the complex problem of attack paths. Beyond this primary function, users can utilize BloodHound's powerful query language to validate simpler security assessment scenarios tested by various other tools (e.g., “Which users have non-expiring passwords?”). 3 | 4 | To assist in these broader security assessment capabilities, BloodHound queries have been mapped to common security assessment tools, demonstrating overlap in capabilities. 5 | 6 | This approach enables different security teams to leverage BloodHound's comprehensive attack path data for multiple kinds of risk validation, whether they're conducting red team assessments, blue team analysis, or compliance audits. 7 | 8 | The BloodHound-centric mapping data is available at [security-assessment-mapping.json](/docs/security-assessment-mapping.json) 9 | 10 | ## Assessment Coverage Overview 11 | 12 | The following show which other security tools the mapping supports and the number BloodHound queries in the BloodHound Query Library that correspond to controls performed by the tools. 13 | 14 | | Security Tool | Total Controls | Mapped Controls | Coverage | 15 | |---------------|-------------------|---------------|----------| 16 | | [Netwrix PingCastle](https://www.pingcastle.com/PingCastleFiles/ad_hc_rules_list.html) | 186 | 105 | 56% | 17 | | [Microsoft Defender for Identity: Security Posture Assessment](https://learn.microsoft.com/en-us/defender-for-identity/security-assessment) | 45 | 35 | 78% | 18 | | [Tenable Nessus: Active Directory Starter Scan](https://www.tenable.com/blog/new-in-nessus-find-and-fix-these-10-active-directory-misconfigurations) | 10 | 10 | 100% | 19 | 20 | ## Mapping Structure 21 | Each mapping includes a type that describes the relationship: 22 | - `exact` - Query identifies the same risk with same scope 23 | - `partial` - Query covers the core risk but with different approach/scope 24 | - `superset` - Query covers everything the other tool does plus additional risk analysis 25 | - `combination` - Single query maps to multiple controls that together equal its functionality 26 | 27 | Each BloodHound query entry includes its GUID and an array of tool mappings. Tool mappings specify the security tool, specific control details, mapping type, and any relevant notes about scope differences. 28 | 29 | For example, the below mapping excerpt shows the BloodHound query [Tier Zero computers with passwords older than the default maximum password age](../queries/Tier%20Zero%20computers%20with%20passwords%20older%20than%20the%20default%20maximum%20password%20age.yml) maps to one PingCastle control and one MDI, while also supsesetting them - increasing risk coverage by expanding the scope to Tier Zero. 30 | 31 | ```json 32 | { 33 | "bloodhound_query": { 34 | "guid": "b6d6d0bf-130e-4719-996b-adc29bba36e9", 35 | "name": "Tier Zero computers with passwords older than the default maximum password age" 36 | }, 37 | "maps_to": [ 38 | { 39 | "source": "PingCastle", 40 | "controls": [ 41 | { 42 | "mapping_scope": "superset", 43 | "mapping_scope_detail": "Expanded scope to Tier Zero", 44 | "id": "S-PwdLastSet-DC", 45 | "name": "[M]Check if all DC are using regular password change practices" 46 | } 47 | ] 48 | }, 49 | { 50 | "source": "MDI", 51 | "controls": [ 52 | { 53 | "mapping_scope": "superset", 54 | "mapping_scope_detail": "Expanded scope to Tier Zero", 55 | "id": "Change Domain Controller computer account old password", 56 | "name": "Change Domain Controller computer account old password" 57 | } 58 | ] 59 | } 60 | ] 61 | } 62 | ``` 63 | 64 | > [!IMPORTANT] 65 | > These mappings are provided "as is" with [Limitation of Liability](/LICENSE). While every effort has been made to ensure their accuracy, they have been created based on public documentation. Please contribute to the project if you identify any inaccuracies by opening an Issue or submitting a Pull Request. It is best practice to use multiple tools to ensure comprehensive risk coverage and accuracy. 66 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Additional ignores 2 | .env 3 | .vscode 4 | test-report.md 5 | 6 | # Byte-compiled / optimized / DLL files 7 | __pycache__/ 8 | *.py[cod] 9 | *$py.class 10 | 11 | # C extensions 12 | *.so 13 | 14 | # Distribution / packaging 15 | .Python 16 | build/ 17 | develop-eggs/ 18 | dist/ 19 | downloads/ 20 | eggs/ 21 | .eggs/ 22 | lib/ 23 | lib64/ 24 | parts/ 25 | sdist/ 26 | var/ 27 | wheels/ 28 | share/python-wheels/ 29 | *.egg-info/ 30 | .installed.cfg 31 | *.egg 32 | MANIFEST 33 | 34 | # PyInstaller 35 | # Usually these files are written by a python script from a template 36 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 37 | *.manifest 38 | *.spec 39 | 40 | # Installer logs 41 | pip-log.txt 42 | pip-delete-this-directory.txt 43 | 44 | # Unit test / coverage reports 45 | htmlcov/ 46 | .tox/ 47 | .nox/ 48 | .coverage 49 | .coverage.* 50 | .cache 51 | nosetests.xml 52 | coverage.xml 53 | *.cover 54 | *.py,cover 55 | .hypothesis/ 56 | .pytest_cache/ 57 | cover/ 58 | 59 | # Translations 60 | *.mo 61 | *.pot 62 | 63 | # Django stuff: 64 | *.log 65 | local_settings.py 66 | db.sqlite3 67 | db.sqlite3-journal 68 | 69 | # Flask stuff: 70 | instance/ 71 | .webassets-cache 72 | 73 | # Scrapy stuff: 74 | .scrapy 75 | 76 | # Sphinx documentation 77 | docs/_build/ 78 | 79 | # PyBuilder 80 | .pybuilder/ 81 | target/ 82 | 83 | # Jupyter Notebook 84 | .ipynb_checkpoints 85 | 86 | # IPython 87 | profile_default/ 88 | ipython_config.py 89 | 90 | # pyenv 91 | # For a library or package, you might want to ignore these files since the code is 92 | # intended to run in multiple environments; otherwise, check them in: 93 | # .python-version 94 | 95 | # pipenv 96 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 97 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 98 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 99 | # install all needed dependencies. 100 | #Pipfile.lock 101 | 102 | # UV 103 | # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. 104 | # This is especially recommended for binary packages to ensure reproducibility, and is more 105 | # commonly ignored for libraries. 106 | #uv.lock 107 | 108 | # poetry 109 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 110 | # This is especially recommended for binary packages to ensure reproducibility, and is more 111 | # commonly ignored for libraries. 112 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 113 | #poetry.lock 114 | 115 | # pdm 116 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 117 | #pdm.lock 118 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 119 | # in version control. 120 | # https://pdm.fming.dev/latest/usage/project/#working-with-version-control 121 | .pdm.toml 122 | .pdm-python 123 | .pdm-build/ 124 | 125 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 126 | __pypackages__/ 127 | 128 | # Celery stuff 129 | celerybeat-schedule 130 | celerybeat.pid 131 | 132 | # SageMath parsed files 133 | *.sage.py 134 | 135 | # Environments 136 | .env 137 | .venv 138 | env/ 139 | venv/ 140 | ENV/ 141 | env.bak/ 142 | venv.bak/ 143 | 144 | # Spyder project settings 145 | .spyderproject 146 | .spyproject 147 | 148 | # Rope project settings 149 | .ropeproject 150 | 151 | # mkdocs documentation 152 | /site 153 | 154 | # mypy 155 | .mypy_cache/ 156 | .dmypy.json 157 | dmypy.json 158 | 159 | # Pyre type checker 160 | .pyre/ 161 | 162 | # pytype static type analyzer 163 | .pytype/ 164 | 165 | # Cython debug symbols 166 | cython_debug/ 167 | 168 | # PyCharm 169 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 170 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 171 | # and can be added to the global gitignore or merged into this file. For a more nuclear 172 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 173 | #.idea/ 174 | 175 | # Ruff stuff: 176 | .ruff_cache/ 177 | 178 | # PyPI configuration file 179 | .pypirc 180 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | 2 | # Contributor Covenant Code of Conduct 3 | 4 | ## Our Pledge 5 | 6 | We as members, contributors, and leaders pledge to make participation in our 7 | community a harassment-free experience for everyone, regardless of age, body 8 | size, visible or invisible disability, ethnicity, sex characteristics, gender 9 | identity and expression, level of experience, education, socio-economic status, 10 | nationality, personal appearance, race, caste, color, religion, or sexual 11 | identity and orientation. 12 | 13 | We pledge to act and interact in ways that contribute to an open, welcoming, 14 | diverse, inclusive, and healthy community. 15 | 16 | ## Our Standards 17 | 18 | Examples of behavior that contributes to a positive environment for our 19 | community include: 20 | 21 | * Demonstrating empathy and kindness toward other people 22 | * Being respectful of differing opinions, viewpoints, and experiences 23 | * Giving and gracefully accepting constructive feedback 24 | * Accepting responsibility and apologizing to those affected by our mistakes, 25 | and learning from the experience 26 | * Focusing on what is best not just for us as individuals, but for the overall 27 | community 28 | 29 | Examples of unacceptable behavior include: 30 | 31 | * The use of sexualized language or imagery, and sexual attention or advances of 32 | any kind 33 | * Trolling, insulting or derogatory comments, and personal or political attacks 34 | * Public or private harassment 35 | * Publishing others' private information, such as a physical or email address, 36 | without their explicit permission 37 | * Other conduct which could reasonably be considered inappropriate in a 38 | professional setting 39 | 40 | ## Enforcement Responsibilities 41 | 42 | Community leaders are responsible for clarifying and enforcing our standards of 43 | acceptable behavior and will take appropriate and fair corrective action in 44 | response to any behavior that they deem inappropriate, threatening, offensive, 45 | or harmful. 46 | 47 | Community leaders have the right and responsibility to remove, edit, or reject 48 | comments, commits, code, wiki edits, issues, and other contributions that are 49 | not aligned to this Code of Conduct, and will communicate reasons for moderation 50 | decisions when appropriate. 51 | 52 | ## Scope 53 | 54 | This Code of Conduct applies within all community spaces, and also applies when 55 | an individual is officially representing the community in public spaces. 56 | Examples of representing our community include using an official email address, 57 | posting via an official social media account, or acting as an appointed 58 | representative at an online or offline event. 59 | 60 | ## Enforcement 61 | 62 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 63 | reported to the community leaders responsible for enforcement at 64 | [@martinsohn.dk on BlueSky](https://bsky.app/profile/martinsohn.dk) or [@martinsohndk on X](https://x.com/martinsohndk). 65 | All complaints will be reviewed and investigated promptly and fairly. 66 | 67 | All community leaders are obligated to respect the privacy and security of the 68 | reporter of any incident. 69 | 70 | ## Enforcement Guidelines 71 | 72 | Community leaders will follow these Community Impact Guidelines in determining 73 | the consequences for any action they deem in violation of this Code of Conduct: 74 | 75 | ### 1. Correction 76 | 77 | **Community Impact**: Use of inappropriate language or other behavior deemed 78 | unprofessional or unwelcome in the community. 79 | 80 | **Consequence**: A private, written warning from community leaders, providing 81 | clarity around the nature of the violation and an explanation of why the 82 | behavior was inappropriate. A public apology may be requested. 83 | 84 | ### 2. Warning 85 | 86 | **Community Impact**: A violation through a single incident or series of 87 | actions. 88 | 89 | **Consequence**: A warning with consequences for continued behavior. No 90 | interaction with the people involved, including unsolicited interaction with 91 | those enforcing the Code of Conduct, for a specified period of time. This 92 | includes avoiding interactions in community spaces as well as external channels 93 | like social media. Violating these terms may lead to a temporary or permanent 94 | ban. 95 | 96 | ### 3. Temporary Ban 97 | 98 | **Community Impact**: A serious violation of community standards, including 99 | sustained inappropriate behavior. 100 | 101 | **Consequence**: A temporary ban from any sort of interaction or public 102 | communication with the community for a specified period of time. No public or 103 | private interaction with the people involved, including unsolicited interaction 104 | with those enforcing the Code of Conduct, is allowed during this period. 105 | Violating these terms may lead to a permanent ban. 106 | 107 | ### 4. Permanent Ban 108 | 109 | **Community Impact**: Demonstrating a pattern of violation of community 110 | standards, including sustained inappropriate behavior, harassment of an 111 | individual, or aggression toward or disparagement of classes of individuals. 112 | 113 | **Consequence**: A permanent ban from any sort of public interaction within the 114 | community. 115 | 116 | ## Attribution 117 | 118 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 119 | version 2.1, available at 120 | [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. 121 | 122 | Community Impact Guidelines were inspired by 123 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. 124 | 125 | For answers to common questions about this code of conduct, see the FAQ at 126 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at 127 | [https://www.contributor-covenant.org/translations][translations]. 128 | 129 | [homepage]: https://www.contributor-covenant.org 130 | [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html 131 | [Mozilla CoC]: https://github.com/mozilla/diversity 132 | [FAQ]: https://www.contributor-covenant.org/faq 133 | [translations]: https://www.contributor-covenant.org/translations -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | Sponsored by SpecterOps 5 | 6 | 7 | Slack 9 | 10 | 11 | Syntax check 13 | 14 |

15 | 16 | 17 | # BloodHound Query Library 18 | 19 | The BloodHound Query Library is a community-driven collection of [Cypher queries](https://support.bloodhoundenterprise.io/hc/en-us/articles/16721164740251) designed to help [BloodHound Community Edition](https://github.com/SpecterOps/BloodHound) and [BloodHound Enterprise](https://specterops.io/bloodhound-overview/) users to unlock the full potential of the flexible BloodHound platform by creating an open query ecosystem. 20 | 21 | The library is a free tool for the community maintained in a human-readable format (YAML) through this repository and the sleek and searchable front-end is found at https://queries.specterops.io/ 22 | 23 | ![BloodHound Query Library frontend screenshot](queries.specterops.io.png) 24 | 25 | For an introduction to the project, please read our blog post: 26 | 27 | - [Introducing the BloodHound Query Library](https://specterops.io/blog/2025/06/17/introducing-the-bloodhound-query-library/) 28 | 29 | # Overview 30 | 31 | The library contains queries that demonstrate BloodHound's versatility beyond traditional attack path analysis. This includes: 32 | - All existing pre-built queries from BloodHound 33 | - Cherry-picked community queries 34 | - SpecterOps-created queries BloodHound Enterprise customers found valuable 35 | - Community contributed queries (see [Contributing](#contributing)) 36 | - Novel queries to further showcase BloodHound's security assessment capabilities (see [security-assessment-mapping.md](/docs/security-assessment-mapping.md)) 37 | 38 | Individual query files are stored in stored in [/Queries](/Queries/) as `.yml` and are automatically combined into a single [Queries.json](/Queries.json) file that powers the front-end. 39 | 40 | The query files use the YAML structure found in [query-structure.yml](/docs/query-structure.yml), for example: 41 | 42 | ```yaml 43 | name: Entra ID SSO accounts not rolling Kerberos decryption key 44 | guid: 1867abf8-08e3-4ea8-8f65-8366079d35c4 45 | prebuilt: false 46 | platforms: 47 | - Active Directory 48 | - Azure 49 | category: Configuration Weakness 50 | description: Microsoft highly recommends that you roll over the Entra ID SSO Kerberos decryption key at least every 30 days. 51 | query: |- 52 | MATCH (n:Computer) 53 | WHERE n.name STARTS WITH "AZUREADSSOACC." 54 | AND n.pwdlastset < (datetime().epochseconds - (30 * 86400)) 55 | RETURN n 56 | revision: 1 57 | resources: https://learn.microsoft.com/en-us/entra/identity/hybrid/connect/how-to-connect-sso-faq#how-can-i-roll-over-the-kerberos-decryption-key-of-the--azureadsso--computer-account- 58 | acknowledgements: Martin Sohn Christensen, @martinsohndk 59 | ``` 60 | 61 | Whenever new queries are added, the syntax is automatically validated, ensuring that only syntactically compatible queries are added. 62 | 63 | ## Learning Cypher Queries 64 | 65 | One of BloodHound’s key features is its flexibility through Cypher queries – a query language to search the BloodHound graph database. 66 | 67 | Queries can answer anything from simple questions (e.g., “*Which users haven’t reset their passwords in 180 days?*”), to complex identity attack path problems (e.g., “*Which low-privileged users can compromise computers hosting a gMSA with unconstrained delegation?*”). 68 | 69 | The library gives you practical examples for learning Cypher and can be combined with these resources: 70 | - [BloodHound documentation: Searching with Cypher](https://support.bloodhoundenterprise.io/hc/en-us/articles/16721164740251) 71 | - [openCypher resources](https://opencypher.org/resources/) 72 | - [Neo4j Cypher Cheat Sheet](https://neo4j.com/docs/cypher-cheat-sheet/current/lists/) 73 | 74 | You can also learn with the communty by joining the #cypher_queries channel in the [BloodHound community Slack](https://support.bloodhoundenterprise.io/hc/en-us/articles/16730536907547). 75 | 76 | ## BloodHound Operator usage example 77 | 78 | Command line usage is easy with the [BloodHound Operator](https://github.com/SadProcessor/BloodHoundOperator) PowerShell module. 79 | 80 | First load the `Queries.json`: 81 | 82 | ```powershell 83 | $queries = Invoke-RestMethod "https://raw.githubusercontent.com/SpecterOps/BloodHoundQueryLibrary/refs/heads/main/Queries.json" 84 | ``` 85 | 86 | Example: Run a query in BloodHound: 87 | 88 | ```powershell 89 | $queries[0] | BHInvoke 90 | ``` 91 | ``` 92 | 93 | 94 | Name : Tier Zero / High Value external Entra ID users 95 | Query : MATCH (n:AZUser) 96 | WHERE ((n:Tag_Tier_Zero) OR COALESCE(n.system_tags, '') CONTAINS 'admin_tier_0') 97 | AND n.name CONTAINS '#EXT#@' 98 | RETURN n 99 | LIMIT 100 100 | Result : {@{label=ASADMIN_PHANTOMCORP.ONMICROSOFT.COM#EXT#@PHANTOMCORP.ONMICROSOFT.COM; kind=AZUser; objectId=D5C8A563-34C0-41EB-BC89-14 101 | A2ECB4CA62; isTierZero=True; isOwnedObject=False; lastSeen=2025-06-17T13:29:14.601282321Z; properties=}, @{label=RHADMIN_PHANTO 102 | MCORP.ONMICROSOFT.COM#EXT#@PHANTOMCORP.ONMICROSOFT.COM; kind=AZUser; objectId=C1C0F17B-58F1-4B04-B50C-2B194B74E75D; isTierZero= 103 | True; isOwnedObject=False; lastSeen=2025-06-17T13:29:24.198Z; properties=}} 104 | Count : 1 105 | Timestamp : 17-06-2025 13:55:27 106 | Duration : 00:00:00.0265562 107 | ``` 108 | 109 | Example: Import a few queries to BloodHound's Custom Searches: 110 | 111 | ```powershell 112 | $queries[0..4] | New-BHPathQuery 113 | ``` 114 | 115 | Example: Test all queries 116 | 117 | ```powershell 118 | $results = [System.Collections.ArrayList]::new() 119 | $queries | % { 120 | "$($results.Count + 1)/$($queries.Count) $($_.name)" 121 | $results.Add([PSCustomObject]@{ 122 | Name = $_.name 123 | Time = (Measure-Command { 124 | $errorMsg = $null 125 | try { 126 | $result = $_ | BHInvoke -WarningAction "Stop" 127 | } catch { 128 | $errorMsg = $_.Exception.Message 129 | } 130 | }).TotalSeconds 131 | Result = $result 132 | Error = $errorMsg 133 | }) | Out-Null 134 | } 135 | ``` 136 | 137 | ## Contributing 138 | 139 | The BloodHound Query Library's success depends on community participation. BloodHound users who have developed useful queries are encouraged to contribute them to the library. 140 | 141 | Before comitting, please ensure that: 142 | - The query follows the [YAML query structure](docs/query-structure.yml). 143 | - The query is compatible with the [latest BloodHound CE version](https://github.com/SpecterOps/BloodHound) 144 | - The query passess all automated CI/CD tests --------------------------------------------------------------------------------