From c0c9505a4039f5c3ae3b0dffbd356f1c9e8bf3d1 Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 17 Feb 2026 16:33:59 +0100 Subject: [PATCH 001/109] Update README.md --- README.md | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 0815111..77988db 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@

-# BloodHound Query Library +# BloodHound Query Library 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. @@ -33,7 +33,7 @@ For an introduction to the project, please read our blog post: - [Introducing the BloodHound Query Library](https://specterops.io/blog/2025/06/17/introducing-the-bloodhound-query-library/) -# Overview +## Overview The library contains queries that demonstrate BloodHound's versatility beyond traditional attack path analysis. This includes: - All existing pre-built queries from BloodHound @@ -42,7 +42,7 @@ The library contains queries that demonstrate BloodHound's versatility beyond tr - Community contributed queries (see [Contributing](#contributing)) - Novel queries to further showcase BloodHound's security assessment capabilities (see [security-assessment-mapping.md](/docs/security-assessment-mapping.md)) -Individual query files are stored in stored [/queries](/queries/) as `.yml` and are automatically combined into a single Queries.json/Queries.zip as part of our [releases](https://github.com/SpecterOps/BloodHoundQueryLibrary/releases). +Individual query files are stored in [/queries](/queries/) as `.yml` and are automatically combined into a single Queries.json/Queries.zip as part of our [releases](https://github.com/SpecterOps/BloodHoundQueryLibrary/releases). The query files use the YAML structure found in [query-structure.yml](/docs/query-structure.yml), for example: @@ -67,6 +67,12 @@ acknowledgements: Martin Sohn Christensen, @martinsohndk Whenever new queries are added, the syntax is automatically validated, ensuring that only syntactically compatible queries are added. +## Security Assessment Mapping + +BloodHound queries in this library have been mapped to controls from common security assessment tools, demonstrating how BloodHound can validate findings typically associated with dedicated tools like PingCastle, Microsoft Defender for Identity, and Tenable Nessus. + +For full coverage details and mapping structure, see [security-assessment-mapping.md](/docs/security-assessment-mapping.md). + ## Learning Cypher Queries One of BloodHound’s key features is its flexibility through Cypher queries – a query language to search the BloodHound graph database. @@ -78,7 +84,7 @@ The library gives you practical examples for learning Cypher and can be combined - [openCypher resources](https://opencypher.org/resources/) - [Neo4j Cypher Cheat Sheet](https://neo4j.com/docs/cypher-cheat-sheet/current/lists/) -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). +You can also learn with the community by joining the #cypher_queries channel in the [BloodHound community Slack](https://support.bloodhoundenterprise.io/hc/en-us/articles/16730536907547). ## BloodHound Operator usage example @@ -95,9 +101,8 @@ Example: Run a query in BloodHound: ```powershell $queries[0] | BHInvoke ``` -``` - +```powershell Name : Tier Zero / High Value external Entra ID users Query : MATCH (n:AZUser) WHERE ((n:Tag_Tier_Zero) OR COALESCE(n.system_tags, '') CONTAINS 'admin_tier_0') @@ -145,7 +150,7 @@ $queries | % { 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. -Before comitting, please ensure that: +Before committing, please ensure that: - The query follows the [YAML query structure](docs/query-structure.yml). - The query is compatible with the [latest BloodHound CE version](https://github.com/SpecterOps/BloodHound) -- The query passess all automated CI/CD tests +- The query passes all automated CI/CD tests From 7b90296773fae2dad61e188945a57f4baa77a9f2 Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 17 Feb 2026 22:37:32 +0100 Subject: [PATCH 002/109] Rename and align to 'All members of Operator groups' --- docs/security-assessment-mapping.json | 2 +- .../{All Operators.yml => All members of Operator groups.yml} | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename queries/{All Operators.yml => All members of Operator groups.yml} (93%) diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index 5b3e351..0657535 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -1174,7 +1174,7 @@ { "bloodhound_query": { "guid": "3dfd0843-1ff9-4c21-aa67-feae08d109de", - "name": "All Operator groups" + "name": "All members of Operator groups" }, "maps_to": [ { diff --git a/queries/All Operators.yml b/queries/All members of Operator groups.yml similarity index 93% rename from queries/All Operators.yml rename to queries/All members of Operator groups.yml index 4dbdcb9..147ee5f 100644 --- a/queries/All Operators.yml +++ b/queries/All members of Operator groups.yml @@ -1,4 +1,4 @@ -name: All Operators +name: All members of Operator groups guid: 3dfd0843-1ff9-4c21-aa67-feae08d109de prebuilt: false platforms: Active Directory @@ -16,7 +16,7 @@ query: |- n.objectid ENDS WITH 'S-1-5-32-550' // Print Operators ) RETURN p -revision: 1 +revision: 2 resources: acknowledgements: Martin Sohn Christensen, @martinsohndk From 84f65cbc9d2967cb3229cdec057bf212e6bf92dd Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 17 Feb 2026 22:38:10 +0100 Subject: [PATCH 003/109] Fix mapping guid for "Enrollment rights on published certificate templates with no security extension" --- docs/security-assessment-mapping.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index 0657535..72748e9 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -1818,7 +1818,7 @@ }, { "bloodhound_query": { - "guid": "b7c20217-5223-4a39-b8d5-32c15fe2b1da", + "guid": "0677b70c-4e04-4e89-a6a2-f5764604a6a7", "name": "Enrollment rights on published certificate templates with no security extension" }, "maps_to": [ From 028ffaa3d7c3c8848ec718392a9a119b8f4773cb Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 17 Feb 2026 22:46:46 +0100 Subject: [PATCH 004/109] Add initial PurpleKnight queries --- docs/security-assessment-mapping.json | 246 +++++++++++++++++++++++++- docs/security-assessment-mapping.md | 1 + 2 files changed, 245 insertions(+), 2 deletions(-) diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index 72748e9..d1f6a4a 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -3,7 +3,8 @@ "maps_to": [ "PingCastle", "Nessus", - "MDI" + "MDI", + "PurpleKnight" ], "mapping_scope": { "partial": "Query covers the core assessment control but with different approach/scope", @@ -20,7 +21,7 @@ }, "maps_to": [ { - "source": "PingCastle", + "maps_to": "PingCastle", "controls": [ { "mapping_scope": "exact", @@ -54,6 +55,17 @@ "name": "[M]Check if all computers have changed their passwords in the last 3 months" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "Computers with password last set over 90 days ago", + "name": "Computers with password last set over 90 days ago" + } + ] } ] }, @@ -95,6 +107,23 @@ "name": "Dormant entities in sensitive groups" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "Enabled admin accounts that are inactive", + "name": "Enabled admin accounts that are inactive" + }, + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "Domain controllers that have not authenticated to the domain for more than 45 days", + "name": "Domain controllers that have not authenticated to the domain for more than 45 days" + } + ] } ] }, @@ -125,6 +154,17 @@ "name": "Change Domain Controller computer account old password" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "Domain controllers with old passwords", + "name": "Domain controllers with old passwords" + } + ] } ] }, @@ -462,6 +502,17 @@ "name": "[M]Ensure that the functional level of the domain and the forest are up to date to use the latest security features (1)" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "Domains with obsolete functional levels", + "name": "Domains with obsolete functional levels" + } + ] } ] }, @@ -572,6 +623,17 @@ "name": "[M]Obsolete OS (Windows XP)" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "Computers with older OS versions", + "name": "Computers with older OS versions" + } + ] } ] }, @@ -853,6 +915,23 @@ "name": "Change password of built-in domain Administrator account" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "Admins with old passwords", + "name": "Admins with old passwords" + }, + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "Built-in domain Administrator account with old password (180 days)", + "name": "Built-in domain Administrator account with old password (180 days)" + } + ] } ] }, @@ -872,6 +951,17 @@ "name": "Rotate password for Microsoft Entra Connect AD DS Connector account" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "Entra Connect sync account password reset", + "name": "Entra Connect sync account password reset" + } + ] } ] }, @@ -1149,6 +1239,17 @@ "name": "[M]Check for Native administrator usage" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "Built-in domain Administrator account used within the last two weeks", + "name": "Built-in domain Administrator account used within the last two weeks" + } + ] } ] }, @@ -1400,6 +1501,17 @@ "name": "Unsecure Kerberos delegation" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "Computer or user accounts with SPN that have unconstrained delegation", + "name": "Computer or user accounts with SPN that have unconstrained delegation" + } + ] } ] }, @@ -1517,6 +1629,23 @@ "name": "[M][T]Check if Kerberos delegation can be used to take control of the forest from a trusted forest" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "Dangerous Trust Attribute Set", + "name": "Dangerous Trust Attribute Set" + }, + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "Domain trust to a third-party domain without quarantine", + "name": "Domain trust to a third-party domain without quarantine" + } + ] } ] }, @@ -1769,6 +1898,23 @@ "name": "Insecure ADCS certificate enrollment IIS endpoints (ESC8)" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "Certificate templates that allow requesters to specify a subjectAltName", + "name": "Certificate templates that allow requesters to specify a subjectAltName" + }, + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "Certificate templates with 3 or more insecure configurations", + "name": "Certificate templates with 3 or more insecure configurations" + } + ] } ] }, @@ -1813,6 +1959,17 @@ "name": "[T]Check if certificate enrollment can be done with HTTP" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "AD Certificate Authority with Web Enrollment – ESC8", + "name": "AD Certificate Authority with Web Enrollment – ESC8" + } + ] } ] }, @@ -1873,6 +2030,17 @@ "name": "Change password for krbtgt account" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "Kerberos krbtgt account with old password", + "name": "Kerberos krbtgt account with old password" + } + ] } ] }, @@ -1939,6 +2107,17 @@ "name": "Unsecure domain configurations: LDAP Signing" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "LDAP signing is not required on Domain Controllers", + "name": "LDAP signing is not required on Domain Controllers" + } + ] } ] }, @@ -2129,6 +2308,17 @@ "name": "[T]Check for access without any account to the Name Service Provider Interface (NSPI) protocol" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "Anonymous NSPI access to AD enabled", + "name": "Anonymous NSPI access to AD enabled" + } + ] } ] }, @@ -2148,6 +2338,17 @@ "name": "[T]Check for access without any account via a forest wide setting" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "Anonymous access to Active Directory enabled", + "name": "Anonymous access to Active Directory enabled" + } + ] } ] }, @@ -2254,6 +2455,17 @@ "name": "[T]Check that the Pre-Windows 2000 Compatible Access group has not been modified from its default" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "Changes to PreWindows 2000 Compatible Access Group membership", + "name": "Changes to PreWindows 2000 Compatible Access Group membership" + } + ] } ] }, @@ -2322,6 +2534,36 @@ "name": "Built-in Active Directory Guest account is enabled" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "Built-in guest account is enabled", + "name": "Built-in guest account is enabled" + } + ] + } + ] + }, + { + "bloodhound_query": { + "guid": "95bec736-86ef-4017-8465-9b9b66548b17", + "name": "Foreign principals in Tier Zero / High Value targets" + }, + "maps_to": [ + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "Foreign Security Principals in Privileged Group", + "name": "Foreign Security Principals in Privileged Group" + } + ] } ] } diff --git a/docs/security-assessment-mapping.md b/docs/security-assessment-mapping.md index bd52596..fbd7cf3 100644 --- a/docs/security-assessment-mapping.md +++ b/docs/security-assessment-mapping.md @@ -14,6 +14,7 @@ The following show which other security tools the mapping supports and the numbe | Security Tool | Total Controls | Mapped Controls | Coverage | |---------------|-------------------|---------------|----------| | [Netwrix PingCastle](https://www.pingcastle.com/PingCastleFiles/ad_hc_rules_list.html) | 186 | 105 | 56% | +| [Semperis PurpleKnight](https://www.semperis.com/purple-knight/security-indicators/) | # | # | #% | | [Microsoft Defender for Identity: Security Posture Assessment](https://learn.microsoft.com/en-us/defender-for-identity/security-assessment) | 45 | 35 | 78% | | [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% | From f1a55a8ce5fa671c35366599a2a1fd2510ee36dd Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 17 Feb 2026 22:53:10 +0100 Subject: [PATCH 005/109] Add 'Domains without Group Managed Service Accounts' --- docs/security-assessment-mapping.json | 19 +++++++++++++++++++ ...without Group Managed Service Accounts.yml | 17 +++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 queries/Domains without Group Managed Service Accounts.yml diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index d1f6a4a..8c46c17 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -2566,6 +2566,25 @@ ] } ] + }, + { + "bloodhound_query": { + "guid": "86b0e862-88d6-4202-9fc1-009746d313f7", + "name": "Domains without Group Managed Service Accounts" + }, + "maps_to": [ + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "gMSA not used", + "name": "gMSA not used" + } + ] + } + ] } ] } \ No newline at end of file diff --git a/queries/Domains without Group Managed Service Accounts.yml b/queries/Domains without Group Managed Service Accounts.yml new file mode 100644 index 0000000..098932e --- /dev/null +++ b/queries/Domains without Group Managed Service Accounts.yml @@ -0,0 +1,17 @@ +name: Domains without Group Managed Service Accounts +guid: 86b0e862-88d6-4202-9fc1-009746d313f7 +prebuilt: false +platforms: Active Directory +category: Domain Information +description: Returns domains that do not use Group Managed Service Accounts (gMSAs) +query: |- + MATCH (domain:Domain) + OPTIONAL MATCH (gmsa:User) + WHERE gmsa.domainsid = domain.objectid AND gmsa.gmsa = true + WITH domain, COLLECT(gmsa) AS domaingmsa + WHERE SIZE(domaingmsa) = 0 + RETURN domain +revision: 1 +resources: https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/manage/group-managed-service-accounts/group-managed-service-accounts/group-managed-service-accounts-overview +acknowledgements: Martin Sohn Christensen, @martinsohndk + From 6040b7635ba69405408a0bfcd3ec33820c64d961 Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 17 Feb 2026 22:53:51 +0100 Subject: [PATCH 006/109] Create "Objects created in the last 10 days" --- docs/security-assessment-mapping.json | 19 +++++++++++++++++++ .../Objects created in the past 10 days.yml | 15 +++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 queries/Objects created in the past 10 days.yml diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index 8c46c17..a2a4bce 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -2585,6 +2585,25 @@ ] } ] + }, + { + "bloodhound_query": { + "guid": "eeed0434-28e3-4d84-9dfb-9108d5997589", + "name": "Objects created in the last 10 days" + }, + "maps_to": [ + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "AD objects created within the last 10 days", + "name": "AD objects created within the last 10 days" + } + ] + } + ] } ] } \ No newline at end of file diff --git a/queries/Objects created in the past 10 days.yml b/queries/Objects created in the past 10 days.yml new file mode 100644 index 0000000..82c544b --- /dev/null +++ b/queries/Objects created in the past 10 days.yml @@ -0,0 +1,15 @@ +name: Objects created in the last 10 days +guid: eeed0434-28e3-4d84-9dfb-9108d5997589 +prebuilt: false +platforms: Active Directory +category: Domain Information +description: +query: |- + MATCH (n:Base) + WHERE n.whencreated > (datetime().epochseconds - (10 * 86400)) + RETURN n + LIMIT 1000 +revision: 1 +resources: +acknowledgements: Martin Sohn Christensen, @martinsohndk + From 06a781a62a49e8d22d47b144957ca29424436bac Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 18 Feb 2026 10:53:13 +0100 Subject: [PATCH 007/109] Create "AdminSDHolder with ACL inheritance enabled" --- docs/security-assessment-mapping.json | 19 +++++++++++++++++++ ...nSDHolder with ACL inheritance enabled.yml | 14 ++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 queries/AdminSDHolder with ACL inheritance enabled.yml diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index a2a4bce..b048930 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -2604,6 +2604,25 @@ ] } ] + }, + { + "bloodhound_query": { + "guid": "fd32470e-a57e-4056-9383-7a0e2802194f", + "name": "AdminSDHolder with ACL inheritance enabled" + }, + "maps_to": [ + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "Inheritance enabled on AdminSDHolder object", + "name": "Inheritance enabled on AdminSDHolder object" + } + ] + } + ] } ] } \ No newline at end of file diff --git a/queries/AdminSDHolder with ACL inheritance enabled.yml b/queries/AdminSDHolder with ACL inheritance enabled.yml new file mode 100644 index 0000000..b02f12b --- /dev/null +++ b/queries/AdminSDHolder with ACL inheritance enabled.yml @@ -0,0 +1,14 @@ +name: AdminSDHolder with ACL inheritance enabled +guid: fd32470e-a57e-4056-9383-7a0e2802194f +prebuilt: false +platforms: Active Directory +category: Active Directory Hygiene +description: +query: |- + MATCH (n:Container) + WHERE n.name STARTS WITH "ADMINSDHOLDER@" + AND n.isaclprotected = false + RETURN n +revision: 1 +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 +acknowledgements: Martin Sohn Christensen, @martinsohndk From da11fdb03d6bd1538027a1f5a32aea5242baa622 Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 18 Feb 2026 11:00:40 +0100 Subject: [PATCH 008/109] Create "Non-Tier Zero principals with ownership of Domain Controllers" --- docs/security-assessment-mapping.json | 19 +++++++++++++++++++ ...rincipals owners of Domain Controllers.yml | 15 +++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 queries/Non-Tier Zero principals owners of Domain Controllers.yml diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index b048930..ceeea0f 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -2623,6 +2623,25 @@ ] } ] + }, + { + "bloodhound_query": { + "guid": "99b01f7f-6bd7-4309-8a67-d16d836236c6", + "name": "Non-Tier Zero principals with ownership of Domain Controllers" + }, + "maps_to": [ + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "superset", + "mapping_scope_detail": "Expanded scope to Non-Tier Zero", + "id": "Domain Controller owner is not an administrator", + "name": "Domain Controller owner is not an administrator" + } + ] + } + ] } ] } \ No newline at end of file diff --git a/queries/Non-Tier Zero principals owners of Domain Controllers.yml b/queries/Non-Tier Zero principals owners of Domain Controllers.yml new file mode 100644 index 0000000..915cefe --- /dev/null +++ b/queries/Non-Tier Zero principals owners of Domain Controllers.yml @@ -0,0 +1,15 @@ +name: Non-Tier Zero principals with ownership of Domain Controllers +guid: 99b01f7f-6bd7-4309-8a67-d16d836236c6 +prebuilt: false +platforms: Active Directory +category: Dangerous Privileges +description: +query: |- + MATCH p=(n:Base)-[r:OwnsRaw|Owns|OwnsLimitedRights]->(dc:Computer) + WHERE NOT ((n:Tag_Tier_Zero) OR COALESCE(n.system_tags, '') CONTAINS 'admin_tier_0') + AND dc.isdc = true + RETURN p +revision: 1 +resources: +acknowledgements: Martin Sohn Christensen, @martinsohndk + From 5bb4f4e54c666d3f5a1f42c7908afd9eaea43f7d Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 18 Feb 2026 11:12:46 +0100 Subject: [PATCH 009/109] Enhance "Domains affected by AdPrep privilege escalation risk", adjust mappings for it - Remove Tier Zero condition - Map to PingCastle - Change PingCastle to Exact mapping --- docs/security-assessment-mapping.json | 13 ++++++++++++- ...affected by AdPrep privilege escalation risk.yml | 6 ++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index ceeea0f..581c56f 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -1146,12 +1146,23 @@ "source": "PingCastle", "controls": [ { - "mapping_scope": "superset", + "mapping_scope": "exact", "mapping_scope_detail": "", "id": "P-DelegationKeyAdmin", "name": "[M][T]Ensure that bogus Windows Server 2016 AD prep did not introduce vulnerabilities" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "Enterprise Key Admins with full access to domain", + "name": "Enterprise Key Admins with full access to domain" + } + ] } ] }, diff --git a/queries/Domains affected by AdPrep privilege escalation risk.yml b/queries/Domains affected by AdPrep privilege escalation risk.yml index 694d2a8..8e93803 100644 --- a/queries/Domains affected by AdPrep privilege escalation risk.yml +++ b/queries/Domains affected by AdPrep privilege escalation risk.yml @@ -3,13 +3,15 @@ guid: 815ff190-f6f3-4757-a516-2f4bf589b705 prebuilt: false platforms: Active Directory category: Dangerous Privileges -description: +description: Finds cases where the Enterprise Key Admins group holds a GenericAll (Full Control) ACE on a Domain node. This condition indicates the adprep/domainprep bug from affected Windows Server 2016 builds, where instead of being scoped to WriteProperty on msDS-KeyCredentialLink, the group was inadvertently granted unrestricted control over the domain naming context and all descendant objects — enabling any member to perform a DCSync attack and harvest credential material from the entire domain. query: |- MATCH p=(n:Group)-[r:GenericAll]->(m:Domain) WHERE n.objectid ENDS WITH "-527" // Enterprise Key Admins - AND NOT ((n:Tag_Tier_Zero) OR COALESCE(n.system_tags, '') CONTAINS 'admin_tier_0') RETURN p revision: 1 resources: +- https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/deploy/domain-wide-updates#windows-server-semi-annual-channel-domain-wide-updates +- https://mskb.pkisolutions.com/kb/4033233 +- https://mskb.pkisolutions.com/kb/4469393 acknowledgements: Martin Sohn Christensen, @martinsohndk From 7af9e6d0809fe94ce610d1aca52ab2a02bccc01b Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 18 Feb 2026 11:58:27 +0100 Subject: [PATCH 010/109] Add PK mapping "AD privileged users that are synced to AAD" --- docs/security-assessment-mapping.json | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index 581c56f..6672c3e 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -2653,6 +2653,25 @@ ] } ] + }, + { + "bloodhound_query": { + "guid": "a8b6ec67-21aa-4dd2-8906-47bb81bf5262", + "name": "Tier Zero AD principals synchronized with Entra ID" + }, + "maps_to": [ + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "superset", + "mapping_scope_detail": "Expanded scope to Tier Zero", + "id": "AD privileged users that are synced to AAD", + "name": "AD privileged users that are synced to AAD" + } + ] + } + ] } ] } \ No newline at end of file From c1123aec5cc9bdf4c4fea04a3f0879f52239fd89 Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 18 Feb 2026 12:07:32 +0100 Subject: [PATCH 011/109] Add PK mapping "Hybrid-synced privileged role accounts" --- docs/security-assessment-mapping.json | 38 +++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index 6672c3e..4adafe0 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -2672,6 +2672,44 @@ ] } ] + }, + { + "bloodhound_query": { + "guid": "de717635-d31f-4fbd-930b-b4dac0f22118", + "name": "On-Prem Users synced to Entra Users with Entra Admin Roles (direct)" + }, + "maps_to": [ + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "subset", + "mapping_scope_detail": "Direct role assignments only.", + "id": "Hybrid-synced privileged role accounts", + "name": "Hybrid-synced privileged role accounts" + } + ] + } + ] + }, + { + "bloodhound_query": { + "guid": "609d648f-7fb8-42d3-ad99-626f9ce1f121", + "name": "On-Prem Users synced to Entra Users with Entra Admin Roles (group delegated)" + }, + "maps_to": [ + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "subset", + "mapping_scope_detail": "Group delegated role assignments only.", + "id": "Hybrid-synced privileged role accounts", + "name": "Hybrid-synced privileged role accounts" + } + ] + } + ] } ] } \ No newline at end of file From 80091eb04ac33ea836d6a91d12b2ef7fa301685e Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 18 Feb 2026 12:15:54 +0100 Subject: [PATCH 012/109] Removed duplicate query "Non-Tier Zero principals owners of Domain Controllers" --- docs/security-assessment-mapping.json | 6 +++--- ...ro principals owners of Domain Controllers.yml | 15 --------------- 2 files changed, 3 insertions(+), 18 deletions(-) delete mode 100644 queries/Non-Tier Zero principals owners of Domain Controllers.yml diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index 4adafe0..d017465 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -2637,8 +2637,8 @@ }, { "bloodhound_query": { - "guid": "99b01f7f-6bd7-4309-8a67-d16d836236c6", - "name": "Non-Tier Zero principals with ownership of Domain Controllers" + "guid": "99d29ded-223a-442b-a0e0-f8b5694c6441", + "name": "Tier Zero computers not owned by Tier Zero" }, "maps_to": [ { @@ -2646,7 +2646,7 @@ "controls": [ { "mapping_scope": "superset", - "mapping_scope_detail": "Expanded scope to Non-Tier Zero", + "mapping_scope_detail": "Expanded scopes to Tier Zero and Non-Tier Zero", "id": "Domain Controller owner is not an administrator", "name": "Domain Controller owner is not an administrator" } diff --git a/queries/Non-Tier Zero principals owners of Domain Controllers.yml b/queries/Non-Tier Zero principals owners of Domain Controllers.yml deleted file mode 100644 index 915cefe..0000000 --- a/queries/Non-Tier Zero principals owners of Domain Controllers.yml +++ /dev/null @@ -1,15 +0,0 @@ -name: Non-Tier Zero principals with ownership of Domain Controllers -guid: 99b01f7f-6bd7-4309-8a67-d16d836236c6 -prebuilt: false -platforms: Active Directory -category: Dangerous Privileges -description: -query: |- - MATCH p=(n:Base)-[r:OwnsRaw|Owns|OwnsLimitedRights]->(dc:Computer) - WHERE NOT ((n:Tag_Tier_Zero) OR COALESCE(n.system_tags, '') CONTAINS 'admin_tier_0') - AND dc.isdc = true - RETURN p -revision: 1 -resources: -acknowledgements: Martin Sohn Christensen, @martinsohndk - From cec1f6fee7419898bd66dc78986b7c8412a35282 Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 18 Feb 2026 12:25:41 +0100 Subject: [PATCH 013/109] Create "Tier Zero Entra ID principals synchronized with AD" --- ...Entra ID principals synchronized with AD.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 queries/Tier Zero Entra ID principals synchronized with AD.yml diff --git a/queries/Tier Zero Entra ID principals synchronized with AD.yml b/queries/Tier Zero Entra ID principals synchronized with AD.yml new file mode 100644 index 0000000..8f382a1 --- /dev/null +++ b/queries/Tier Zero Entra ID principals synchronized with AD.yml @@ -0,0 +1,17 @@ +name: Tier Zero Entra ID principals synchronized with AD +guid: 0d65c62e-bd77-4027-b967-fb36690739c9 +prebuilt: false +platforms: +- Active Directory +- Azure +category: Cross Platform Attack Paths +description: +query: |- + MATCH p=(ad:Base)-[:SyncedToEntraUser]->(entra:AZBase) + WHERE ((entra:Tag_Tier_Zero) OR COALESCE(entra.system_tags, '') CONTAINS 'admin_tier_0') + RETURN p + LIMIT 100 +revision: 1 +resources: +acknowledgements: Martin Sohn Christensen, @martinsohndk + From cb7180fe58f8e628cd9b2cd2985ba486c4c498da Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 18 Feb 2026 12:25:46 +0100 Subject: [PATCH 014/109] Add PK mapping for "AAD privileged users that are also privileged in AD" --- docs/security-assessment-mapping.json | 30 +++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index d017465..a771520 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -2670,6 +2670,36 @@ "name": "AD privileged users that are synced to AAD" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "subset", + "mapping_scope_detail": "Finds the direction AD -> Entra only.", + "id": "AAD privileged users that are also privileged in AD", + "name": "AAD privileged users that are also privileged in AD" + } + ] + } + ] + }, + { + "bloodhound_query": { + "guid": "0d65c62e-bd77-4027-b967-fb36690739c9", + "name": "Tier Zero Entra ID principals synchronized with AD" + }, + "maps_to": [ + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "subset", + "mapping_scope_detail": "Finds the direction Entra -> AD only.", + "id": "AAD privileged users that are also privileged in AD", + "name": "AAD privileged users that are also privileged in AD" + } + ] } ] }, From 0ce4187ba65647c7164306c8ab623f0a84b2be80 Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 18 Feb 2026 14:22:33 +0100 Subject: [PATCH 015/109] Create Non-Tier Zero Principals with ExecuteDCOM privileges on Domain Controllers.yml --- ...eDCOM privileges on Domain Controllers.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 queries/Non-Tier Zero Principals with ExecuteDCOM privileges on Domain Controllers.yml diff --git a/queries/Non-Tier Zero Principals with ExecuteDCOM privileges on Domain Controllers.yml b/queries/Non-Tier Zero Principals with ExecuteDCOM privileges on Domain Controllers.yml new file mode 100644 index 0000000..97343fe --- /dev/null +++ b/queries/Non-Tier Zero Principals with ExecuteDCOM privileges on Domain Controllers.yml @@ -0,0 +1,19 @@ +name: Non-Tier Zero Principals with ExecuteDCOM privileges on Domain Controllers +guid: 2aeefd57-cbe7-4f0e-907f-86a97d7b723c +prebuilt: true +platforms: Active Directory +category: Dangerous Privileges +description: +query: |- + MATCH p1=(n:Base)-[:ExecuteDCOM]->(c:Computer) + WHERE NOT ((n:Tag_Tier_Zero) OR COALESCE(n.system_tags, '') CONTAINS 'admin_tier_0') + AND c.isdc + MATCH p2=(n)-[:MemberOf]->(g:Group) + WHERE g.objectid ENDS WITH "S-1-5-32-562" // Distributed COM Users + OR g.objectid ENDS WITH "S-1-5-32-559" // Performance Log Users + RETURN p1,p2 + LIMIT 1000 +revision: 1 +resources: +acknowledgements: + From f6b44cd2d062a83979baf307f68d3bc567876631 Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 18 Feb 2026 14:23:50 +0100 Subject: [PATCH 016/109] Add PK mapping "Distributed COM Users group or Performance Log Users group are not empty" --- docs/security-assessment-mapping.json | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index a771520..7573b3d 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -2740,6 +2740,25 @@ ] } ] + }, + { + "bloodhound_query": { + "guid": "2aeefd57-cbe7-4f0e-907f-86a97d7b723c", + "name": "Non-Tier Zero Principals with ExecuteDCOM privileges on Domain Controllers" + }, + "maps_to": [ + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "superset", + "mapping_scope_detail": "Remove false positives by reducing scope to Non-Tier Zero", + "id": "Distributed COM Users group or Performance Log Users group are not empty", + "name": "Distributed COM Users group or Performance Log Users group are not empty" + } + ] + } + ] } ] } \ No newline at end of file From a55ec751fffa52988ed182ab982ab58308f2d797 Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 18 Feb 2026 14:28:45 +0100 Subject: [PATCH 017/109] Enhance mapping_scope_detail for non-tier zero queries --- docs/security-assessment-mapping.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index 7573b3d..02ed5d8 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -1196,7 +1196,7 @@ "controls": [ { "mapping_scope": "superset", - "mapping_scope_detail": "Expanded scope to Non Tier Zero", + "mapping_scope_detail": "Remove false positives by reducing scope to Non-Tier Zero", "id": "P-ExchangeAdminSDHolder", "name": "[M]Ensure that Exchange did not modify the AdminSDHolder object to introduce vulnerabilities" } @@ -1207,7 +1207,7 @@ "controls": [ { "mapping_scope": "superset", - "mapping_scope_detail": "Expanded scope to Non Tier Zero", + "mapping_scope_detail": "Remove false positives by reducing scope to Non-Tier Zero", "id": "Admin SDHolder permissions", "name": "Admin SDHolder permissions" } @@ -1362,7 +1362,7 @@ "controls": [ { "mapping_scope": "superset", - "mapping_scope_detail": "Expanded scope to Tier Zero. Expanded scope to include all BloodHound traversable edges", + "mapping_scope_detail": "Remove false positives by reducing scope to Non-Tier Zero. Expanded scope to include all BloodHound traversable edges", "id": "P-ControlPathIndirectMany", "name": "[T]Check if there is a control path involving too many users or computers" } @@ -1485,7 +1485,7 @@ "controls": [ { "mapping_scope": "superset", - "mapping_scope_detail": "Expanded exclusion scope to Tier Zero", + "mapping_scope_detail": "Remove false positives by reducing scope to Non-Tier Zero", "id": "P-UnconstrainedDelegation", "name": "[M][T]Ensure that no accounts are subject to unconstrained delegation" } @@ -1709,7 +1709,7 @@ "controls": [ { "mapping_scope": "superset", - "mapping_scope_detail": "Expanded scope to Tier Zero", + "mapping_scope_detail": "Remove false positives by reducing scope to Non-Tier Zero. Expanded target scope to Tier Zero", "id": "T-SIDHistoryDangerous", "name": "[M][T]Check if dangerous SID are stored in the SIDHistory attribute" } @@ -2491,7 +2491,7 @@ "controls": [ { "mapping_scope": "superset", - "mapping_scope_detail": "Expanded scope to Tier Zero", + "mapping_scope_detail": "Remove false positives by reducing scope to Non-Tier Zero. Expanded target scope to Tier Zero", "id": "A-AdminSDHolder", "name": "[M]Check for suspicious account(s) used in administrator activities" } @@ -2646,7 +2646,7 @@ "controls": [ { "mapping_scope": "superset", - "mapping_scope_detail": "Expanded scopes to Tier Zero and Non-Tier Zero", + "mapping_scope_detail": "Remove false positives by reducing scope to Non-Tier Zero. Expanded target scope to Tier Zero", "id": "Domain Controller owner is not an administrator", "name": "Domain Controller owner is not an administrator" } From 1a30aacb8825ca333b8cb277800e2bde3a48e02c Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 18 Feb 2026 14:28:55 +0100 Subject: [PATCH 018/109] Create PK map for "Dangerous control paths expose certificate containers" --- docs/security-assessment-mapping.json | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index 02ed5d8..c72c1f5 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -2759,6 +2759,25 @@ ] } ] + }, + { + "bloodhound_query": { + "guid": "396c7b67-fb5d-4c04-bb13-8007f0dfc9b1", + "name": "Compromising permissions on ADCS nodes (ESC5)" + }, + "maps_to": [ + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "superset", + "mapping_scope_detail": "Audits all objects in the 'PUBLIC KEY SERVICES' container", + "id": "Dangerous control paths expose certificate containers", + "name": "Dangerous control paths expose certificate containers" + } + ] + } + ] } ] } \ No newline at end of file From 6a7c5e9424c20a25be3c8cb67119b74144c197ba Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 18 Feb 2026 14:39:26 +0100 Subject: [PATCH 019/109] Add PK map "Forest contains more than 50 privileged accounts" --- docs/security-assessment-mapping.json | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index c72c1f5..1ef397a 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -1280,6 +1280,17 @@ "name": "[M]Check for number of Administrator accounts above the baseline" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "superset", + "mapping_scope_detail": "Expanded scope to Tier Zero", + "id": "Forest contains more than 50 privileged accounts", + "name": "Forest contains more than 50 privileged accounts" + } + ] } ] }, From 2c67ae4424fe402673377bbca4d52af3385b9a18 Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 18 Feb 2026 14:39:44 +0100 Subject: [PATCH 020/109] Add PK map "Dangerous control paths expose certificate templates" --- docs/security-assessment-mapping.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index 1ef397a..083e618 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -1935,6 +1935,12 @@ "mapping_scope_detail": "", "id": "Certificate templates with 3 or more insecure configurations", "name": "Certificate templates with 3 or more insecure configurations" + }, + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "Dangerous control paths expose certificate templates", + "name": "Dangerous control paths expose certificate templates" } ] } From feb757f8ee1ff612bcdd5b555d1d78dfbcdb3af3 Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 18 Feb 2026 14:51:17 +0100 Subject: [PATCH 021/109] Update Compromising permissions on ADCS nodes (ESC5).yml --- queries/Compromising permissions on ADCS nodes (ESC5).yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/queries/Compromising permissions on ADCS nodes (ESC5).yml b/queries/Compromising permissions on ADCS nodes (ESC5).yml index 0eedaa6..6f6056d 100644 --- a/queries/Compromising permissions on ADCS nodes (ESC5).yml +++ b/queries/Compromising permissions on ADCS nodes (ESC5).yml @@ -12,6 +12,6 @@ query: |- AND NOT n.objectid ENDS WITH "-544" // Administrators RETURN p LIMIT 1000 -revision: 1 -resources: +revision: 2 +resources: https://specterops.io/blog/2023/05/16/from-da-to-ea-with-esc5/ acknowledgements: From e5f5ae62f772595665cf552af7c96fe27f1c05a3 Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 18 Feb 2026 14:51:24 +0100 Subject: [PATCH 022/109] Create Principals with control of Entra ID SSO accounts.yml --- ...pals with control of Entra ID SSO accounts.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 queries/Principals with control of Entra ID SSO accounts.yml diff --git a/queries/Principals with control of Entra ID SSO accounts.yml b/queries/Principals with control of Entra ID SSO accounts.yml new file mode 100644 index 0000000..acdf501 --- /dev/null +++ b/queries/Principals with control of Entra ID SSO accounts.yml @@ -0,0 +1,15 @@ +name: Principals with control of Entra ID SSO accounts +guid: 215b98e2-14db-4d37-ba97-1467b42c5910 +prebuilt: false +platforms: Active Directory +category: Dangerous Privileges +description: +query: |- + MATCH p=(n:Base)-[:AD_ATTACK_PATHS]->(sso:Computer) + WHERE sso.name STARTS WITH "AZUREADSSOACC." + AND NOT (n:Container OR n:OU) + RETURN p +revision: 1 +resources: https://learn.microsoft.com/en-us/entra/identity/hybrid/connect/how-to-connect-sso-how-it-works +acknowledgements: Martin Sohn Christensen, @martinsohndk + From a0073f36b14a770dc59478f4c167ce33038cdf9a Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 18 Feb 2026 14:51:33 +0100 Subject: [PATCH 023/109] Added PK map "Accounts with Kerberos constrained delegation configured to SSO computer account" --- docs/security-assessment-mapping.json | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index 083e618..95f3cb4 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -2795,6 +2795,25 @@ ] } ] + }, + { + "bloodhound_query": { + "guid": "215b98e2-14db-4d37-ba97-1467b42c5910", + "name": "Principals with control of Entra ID SSO accounts" + }, + "maps_to": [ + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "superset", + "mapping_scope_detail": "Expanded technique scope to include all BloodHound traversable edges.", + "id": "Accounts with Kerberos constrained delegation configured to SSO computer account", + "name": "Accounts with Kerberos constrained delegation configured to SSO computer account" + } + ] + } + ] } ] } \ No newline at end of file From cd8cef38ba8ed3e828734ea50ce7093846f30c76 Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 18 Feb 2026 14:57:37 +0100 Subject: [PATCH 024/109] Update Principals with control of Entra ID SSO accounts.yml --- queries/Principals with control of Entra ID SSO accounts.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/queries/Principals with control of Entra ID SSO accounts.yml b/queries/Principals with control of Entra ID SSO accounts.yml index acdf501..905e097 100644 --- a/queries/Principals with control of Entra ID SSO accounts.yml +++ b/queries/Principals with control of Entra ID SSO accounts.yml @@ -7,7 +7,7 @@ description: query: |- MATCH p=(n:Base)-[:AD_ATTACK_PATHS]->(sso:Computer) WHERE sso.name STARTS WITH "AZUREADSSOACC." - AND NOT (n:Container OR n:OU) + AND NOT (n:Computer OR n:User) RETURN p revision: 1 resources: https://learn.microsoft.com/en-us/entra/identity/hybrid/connect/how-to-connect-sso-how-it-works From 7c2e97ed505b32c4824f4310c5fce38ae3d23a1c Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 18 Feb 2026 14:59:47 +0100 Subject: [PATCH 025/109] Add "Principals with control of KRBTGT accounts" and PK mapping to "Accounts with Constrained Delegation configured to krbtgt" --- docs/security-assessment-mapping.json | 19 +++++++++++++++++++ ...cipals with control of KRBTGT accounts.yml | 17 +++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 queries/Principals with control of KRBTGT accounts.yml diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index 95f3cb4..d1f6a7e 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -2814,6 +2814,25 @@ ] } ] + }, + { + "bloodhound_query": { + "guid": "ef2eae0c-20e2-4c29-998f-faab9eb03b41", + "name": "Principals with control of KRBTGT accounts" + }, + "maps_to": [ + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "superset", + "mapping_scope_detail": "Expanded technique scope to include all BloodHound traversable edges. Expanded target scope to all KRBTGT account types.", + "id": "Accounts with Constrained Delegation configured to krbtgt", + "name": "Accounts with Constrained Delegation configured to krbtgt" + } + ] + } + ] } ] } \ No newline at end of file diff --git a/queries/Principals with control of KRBTGT accounts.yml b/queries/Principals with control of KRBTGT accounts.yml new file mode 100644 index 0000000..1e2b821 --- /dev/null +++ b/queries/Principals with control of KRBTGT accounts.yml @@ -0,0 +1,17 @@ +name: Principals with control of KRBTGT accounts +guid: ef2eae0c-20e2-4c29-998f-faab9eb03b41 +prebuilt: false +platforms: Active Directory +category: Dangerous Privileges +description: +query: |- + MATCH p=(n:Base)-[:AD_ATTACK_PATHS]->(krbtgt:User) + WHERE (krbtgt.objectid ENDS WITH '-502' + OR krbtgt.name STARTS WITH 'AZUREADKERBEROS.' + OR krbtgt.name STARTS WITH 'KRBTGT_AZUREAD@') + AND (n:Computer OR n:User) + RETURN p +revision: 1 +resources: +acknowledgements: Martin Sohn Christensen, @martinsohndk + From 8ce2caf141fb70104f74c207fc210398bb7f99a9 Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 18 Feb 2026 17:45:12 +0100 Subject: [PATCH 026/109] fix json structure --- docs/security-assessment-mapping.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index d1f6a7e..9ae90fb 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -1,6 +1,6 @@ { "definitions": { - "maps_to": [ + "source": [ "PingCastle", "Nessus", "MDI", @@ -21,7 +21,7 @@ }, "maps_to": [ { - "maps_to": "PingCastle", + "source": "PingCastle", "controls": [ { "mapping_scope": "exact", From 688b8662e0282cc44d72a666bf7cbac50038ceb0 Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 18 Feb 2026 17:56:16 +0100 Subject: [PATCH 027/109] Create "Tier Zero objects created in the past 10 days" and PK mapping --- docs/security-assessment-mapping.json | 19 +++++++++++++++++++ ...ro objects created in the past 10 days.yml | 16 ++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 queries/Tier Zero objects created in the past 10 days.yml diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index 9ae90fb..0402c60 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -2633,6 +2633,25 @@ } ] }, + { + "bloodhound_query": { + "guid": "9c7c2b1d-6ffb-46ee-8b92-89237f7c7ef3", + "name": "Tier Zero objects created in the past 10 days" + }, + "maps_to": [ + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "Recent privileged account creation activity", + "name": "Recent privileged account creation activity" + } + ] + } + ] + }, { "bloodhound_query": { "guid": "fd32470e-a57e-4056-9383-7a0e2802194f", diff --git a/queries/Tier Zero objects created in the past 10 days.yml b/queries/Tier Zero objects created in the past 10 days.yml new file mode 100644 index 0000000..ed12482 --- /dev/null +++ b/queries/Tier Zero objects created in the past 10 days.yml @@ -0,0 +1,16 @@ +name: Tier Zero objects created in the past 10 days +guid: 9c7c2b1d-6ffb-46ee-8b92-89237f7c7ef3 +prebuilt: false +platforms: Active Directory +category: Domain Information +description: +query: |- + MATCH (n:Base) + WHERE n.whencreated > (datetime().epochseconds - (10 * 86400)) + AND ((n:Tag_Tier_Zero) OR COALESCE(n.system_tags, '') CONTAINS 'admin_tier_0') + RETURN n + LIMIT 1000 +revision: 1 +resources: +acknowledgements: Martin Sohn Christensen, @martinsohndk + From f3420cbc4b31fa186ca4812a0a85eef04bbd6a96 Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 18 Feb 2026 17:56:30 +0100 Subject: [PATCH 028/109] PK mapping "Protected Users group not in use" --- docs/security-assessment-mapping.json | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index 0402c60..1720493 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -1057,6 +1057,17 @@ "name": "[M]Check if all privileged accounts are in the special group Protected Users" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "Protected Users group not in use", + "name": "Protected Users group not in use" + } + ] } ] }, From 511639da4782d023ee4f32dcd3a71b265bc8d391 Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 18 Feb 2026 17:56:40 +0100 Subject: [PATCH 029/109] PK mapping "Privileged users that are disabled" --- docs/security-assessment-mapping.json | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index 1720493..3a94aee 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -2863,6 +2863,25 @@ ] } ] + }, + { + "bloodhound_query": { + "guid": "d65a801f-d3ef-4b7e-8030-99ebfd6dad12", + "name": "Disabled Tier Zero / High Value principals (AD)" + }, + "maps_to": [ + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "Privileged users that are disabled", + "name": "Privileged users that are disabled" + } + ] + } + ] } ] } \ No newline at end of file From 57a90870a2cbc2fe18e8f094e4f08defcf364f65 Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 18 Feb 2026 18:42:33 +0100 Subject: [PATCH 030/109] Create Principals with control of Domain Controllers.yml --- ...ncipals with control of Domain Controllers.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 queries/Principals with control of Domain Controllers.yml diff --git a/queries/Principals with control of Domain Controllers.yml b/queries/Principals with control of Domain Controllers.yml new file mode 100644 index 0000000..51570af --- /dev/null +++ b/queries/Principals with control of Domain Controllers.yml @@ -0,0 +1,15 @@ +name: Principals with control of Domain Controllers +guid: 37854ce4-5ae3-4005-a2a7-40a729b316cf +prebuilt: false +platforms: Active Directory +category: Dangerous Privileges +description: +query: |- + MATCH p=(n:Base)-[:AD_ATTACK_PATHS]->(dc:Computer) + WHERE dc.isdc = true + AND (n:Computer OR n:User) + RETURN p +revision: 1 +resources: +acknowledgements: Martin Sohn Christensen, @martinsohndk + From aec02c5f03f904137e2da33239bea2a906d2a788 Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 18 Feb 2026 18:57:24 +0100 Subject: [PATCH 031/109] Added PK mappings --- docs/security-assessment-mapping.json | 187 +++++++++++++++++++++++++- 1 file changed, 183 insertions(+), 4 deletions(-) diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index 3a94aee..65cf9c7 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -244,6 +244,17 @@ "name": "Accounts with non-default Primary Group ID" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "combination", + "mapping_scope_detail": "", + "id": "Users and computers with non-default Primary Group IDs", + "name": "Users and computers with non-default Primary Group IDs" + } + ] } ] }, @@ -285,6 +296,17 @@ "name": "Accounts with non-default Primary Group ID" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "combination", + "mapping_scope_detail": "", + "id": "Users and computers with non-default Primary Group IDs", + "name": "Users and computers with non-default Primary Group IDs" + } + ] } ] }, @@ -321,6 +343,17 @@ "name": "Unsecure account attributes: Remove Store password using reversible encryption" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "superset", + "mapping_scope_detail": "Expanded scope to include computer accounts", + "id": "User accounts that store passwords with reversible encryption", + "name": "User accounts that store passwords with reversible encryption" + } + ] } ] }, @@ -422,6 +455,17 @@ "name": "Unsecure account attributes: Remove Password not required" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "User accounts with password not required", + "name": "User accounts with password not required" + } + ] } ] }, @@ -452,6 +496,17 @@ "name": "Non-expiring account password" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "Users with Password Never Expires flag set", + "name": "Users with Password Never Expires flag set" + } + ] } ] }, @@ -935,6 +990,25 @@ } ] }, + { + "bloodhound_query": { + "guid": "be70d1bd-b7eb-40b0-971c-eefc50eca032", + "name": "Users with passwords not rotated in over 1 year" + }, + "maps_to": [ + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "Users with old passwords", + "name": "Users with old passwords" + } + ] + } + ] + }, { "bloodhound_query": { "guid": "97fb1310-d15d-4d63-82a2-8788056250f1", @@ -2176,6 +2250,17 @@ "name": "[T]Automatic password rotation for smart card is not in place" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "Smart card password rotation disabled", + "name": "Smart card password rotation disabled" + } + ] } ] }, @@ -2195,6 +2280,17 @@ "name": "[T]Check for accounts using smart card with unchanged password for a long time" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "User accounts using Smart Card authentication with old password", + "name": "User accounts using Smart Card authentication with old password" + } + ] } ] }, @@ -2246,12 +2342,23 @@ "source": "MDI", "controls": [ { - "mapping_scope": "exact", - "mapping_scope_detail": "", + "mapping_scope": "subset", + "mapping_scope_detail": "Returns principals of all privilege levels.", "id": "Remove non-admin accounts with DCSync permissions", "name": "Remove non-admin accounts with DCSync permissions" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "subset", + "mapping_scope_detail": "Returns principals of all privilege levels.", + "id": "Non-default principals with DC Sync rights on the domain", + "name": "Non-default principals with DC Sync rights on the domain" + } + ] } ] }, @@ -2328,6 +2435,17 @@ "name": "[T]Check if attributes unixUserPassword and userPassword are set" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "superset", + "mapping_scope_detail": "Expanded attributes scope to include 'unicodepwd' and 'msSFU30Password'", + "id": "Users with the attribute userPassword set", + "name": "Users with the attribute userPassword set" + } + ] } ] }, @@ -2524,6 +2642,17 @@ "name": "[M]Check for suspicious account(s) used in administrator activities" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "superset", + "mapping_scope_detail": "Remove false positives by reducing scope to Non-Tier Zero. Expanded target scope to Tier Zero", + "id": "Unprivileged accounts with adminCount=1", + "name": "Unprivileged accounts with adminCount=1" + } + ] } ] }, @@ -2837,7 +2966,7 @@ "controls": [ { "mapping_scope": "superset", - "mapping_scope_detail": "Expanded technique scope to include all BloodHound traversable edges.", + "mapping_scope_detail": "Expanded technique scope to include all BloodHound traversable edges, instead of just the AllowedToAct edge.", "id": "Accounts with Kerberos constrained delegation configured to SSO computer account", "name": "Accounts with Kerberos constrained delegation configured to SSO computer account" } @@ -2856,11 +2985,22 @@ "controls": [ { "mapping_scope": "superset", - "mapping_scope_detail": "Expanded technique scope to include all BloodHound traversable edges. Expanded target scope to all KRBTGT account types.", + "mapping_scope_detail": "Expanded technique scope to include all BloodHound traversable edges, instead of just the AllowedToAct edge. Expanded target scope to all KRBTGT account types.", "id": "Accounts with Constrained Delegation configured to krbtgt", "name": "Accounts with Constrained Delegation configured to krbtgt" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "superset", + "mapping_scope_detail": "Expanded technique scope to include all BloodHound traversable edges, instead of just the AddAllowedToAct edge. Expanded target scope to all KRBTGT account types.", + "id": "Write access to RBCD on krbtgt account", + "name": "Write access to RBCD on krbtgt account" + } + ] } ] }, @@ -2882,6 +3022,45 @@ ] } ] + }, + { + "bloodhound_query": { + "guid": "74daaebe-6040-4f7a-9c9a-416faf73dcc3", + "name": "Non-Tier Zero principals with BadSuccessor rights (with prerequisites check)" + }, + "maps_to": [ + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "superset", + "mapping_scope_detail": "Remove false positives by reducing scope to Non-Tier Zero. Checks prerequisites.", + "id": "OU permissions enabling BadSuccessor dMSA escalation", + "name": "OU permissions enabling BadSuccessor dMSA escalation" + } + ] + } + ] + }, + { + "bloodhound_query": { + "guid": "37854ce4-5ae3-4005-a2a7-40a729b316cf", + "name": "Principals with control of Domain Controllers" + }, + "maps_to": [ + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "superset", + "mapping_scope_detail": "Expanded technique scope to include all BloodHound traversable edges, instead of just the AllowedToAct edge.", + "id": "Write access to RBCD on DC", + "name": "Write access to RBCD on DC" + } + ] + } + ] + }, } ] } \ No newline at end of file From 08d4f466a1034d4042baa8fe7f420563e035a4ff Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 18 Feb 2026 18:57:45 +0100 Subject: [PATCH 032/109] Add PK mapping "Objects in privileged groups without adminCount=1 (SDProp)" --- docs/security-assessment-mapping.json | 18 ++++++++++++++++++ ...cted objects without 'Admin Count' flag.yml | 16 ++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 queries/AdminSDHolder protected objects without 'Admin Count' flag.yml diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index 65cf9c7..dd22d5f 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -3061,6 +3061,24 @@ } ] }, + { + "bloodhound_query": { + "guid": "f4b3243f-2a3c-497d-832f-352abde9b0a2", + "name": "AdminSDHolder protected objects without 'Admin Count' flag" + }, + "maps_to": [ + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "exact", + "mapping_scope_detail": "Expanded technique scope to include all BloodHound traversable edges, instead of just the AllowedToAct edge.", + "id": "Objects in privileged groups without adminCount=1 (SDProp)", + "name": "Objects in privileged groups without adminCount=1 (SDProp)" + } + ] + } + ] } ] } \ No newline at end of file diff --git a/queries/AdminSDHolder protected objects without 'Admin Count' flag.yml b/queries/AdminSDHolder protected objects without 'Admin Count' flag.yml new file mode 100644 index 0000000..d9291d1 --- /dev/null +++ b/queries/AdminSDHolder protected objects without 'Admin Count' flag.yml @@ -0,0 +1,16 @@ +name: AdminSDHolder protected objects without 'Admin Count' flag +guid: f4b3243f-2a3c-497d-832f-352abde9b0a2 +prebuilt: false +platforms: Active Directory +category: Active Directory Hygiene +description: The lack of the 'Admin Count' flag on objects protected by AdminSDHolder could signal an issue with SDProp. +query: |- + MATCH p=(n:Base)-[:ProtectAdminGroups]->(m:Base) + WHERE m.admincount = false + RETURN p + LIMIT 1000 +revision: 1 +resources: +acknowledgements: +- Jim Sykora, @JimSycurity +- Martin Sohn Christensen, @martinsohndk From edb8023db6eeddb5182abb1efe63c7ef1d440339 Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 18 Feb 2026 18:59:13 +0100 Subject: [PATCH 033/109] PK map "Operator groups no longer protected by AdminSDHolder and SDProp" --- docs/security-assessment-mapping.json | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index dd22d5f..6e8d3ca 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -1316,6 +1316,17 @@ "name": "[M]Ensure that the AdminSDHolder protection has not been disabled for some critical groups" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "Operator groups no longer protected by AdminSDHolder and SDProp", + "name": "Operator groups no longer protected by AdminSDHolder and SDProp" + } + ] } ] }, From 35095b26ac495c34cade3fbe950e9c36457908f4 Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 18 Feb 2026 19:00:31 +0100 Subject: [PATCH 034/109] PK map "Operator Groups that are not empty" --- docs/security-assessment-mapping.json | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index 6e8d3ca..c03dbb6 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -1406,6 +1406,17 @@ "name": "[M]Check that the operator groups are empty" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "Operator Groups that are not empty", + "name": "Operator Groups that are not empty" + } + ] } ] }, From 555a187573833ec47e7da5b0f9d727e927e92b1c Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 18 Feb 2026 19:28:20 +0100 Subject: [PATCH 035/109] Create Principals with write Shadow Credentials on Tier Zero principals.yml --- ...Shadow Credentials on Tier Zero principals.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 queries/Principals that can write Shadow Credentials on Tier Zero principals.yml diff --git a/queries/Principals that can write Shadow Credentials on Tier Zero principals.yml b/queries/Principals that can write Shadow Credentials on Tier Zero principals.yml new file mode 100644 index 0000000..e578e7d --- /dev/null +++ b/queries/Principals that can write Shadow Credentials on Tier Zero principals.yml @@ -0,0 +1,15 @@ +name: Principals with write Shadow Credentials on Tier Zero principals +guid: 96e86fb9-4cd6-4df3-81a6-e36fd7a34614 +prebuilt: false +platforms: Active Directory +category: Kerberos Interaction +description: Principals with the ability to write to the 'msds-KeyCredentialLink' property on a Tier Zero principal. +query: |- + MATCH p=(n:Base)-[:AddKeyCredentialLink]->(m:Base) + WHERE (n:User or n:Computer) + AND ((m:Tag_Tier_Zero) OR COALESCE(m.system_tags, '') CONTAINS 'admin_tier_0') + RETURN p + LIMIT 1000 +revision: 1 +resources: https://specterops.io/blog/2021/06/17/shadow-credentials-abusing-key-trust-account-mapping-for-account-takeover/ +acknowledgements: Martin Sohn Christensen, @martinsohndk From 0bd4fd7c97cbba50954014120f011b6a93eb02dc Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 18 Feb 2026 19:29:48 +0100 Subject: [PATCH 036/109] PK mappings --- docs/security-assessment-mapping.json | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index c03dbb6..5e0b241 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -1297,6 +1297,17 @@ "name": "Admin SDHolder permissions" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "superset", + "mapping_scope_detail": "Remove false positives by reducing scope to Non-Tier Zero", + "id": "Permission changes on AdminSDHolder object", + "name": "Permission changes on AdminSDHolder object" + } + ] } ] }, @@ -1775,6 +1786,17 @@ "name": "Domain trust to a third-party domain without quarantine" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "Outbound forest trust with SID History enabled", + "name": "Outbound forest trust with SID History enabled" + } + ] } ] }, From 5fff69be4c202b89ca2fdd955b0d5cec8c612f08 Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 18 Feb 2026 19:34:31 +0100 Subject: [PATCH 037/109] PK map "SMB Signing is not required on Domain Controllers" --- docs/security-assessment-mapping.json | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index 5e0b241..eccf7fc 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -2460,6 +2460,17 @@ "name": "[T]Check if the file share protocol can sign its network dialog" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "superset", + "mapping_scope_detail": "Expanded scope to Tier Zero", + "id": "SMB Signing is not required on Domain Controllers", + "name": "SMB Signing is not required on Domain Controllers" + } + ] } ] }, From 7b6cd7eb9cfd5fdfcce49c192b9d1042419654c7 Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 18 Feb 2026 19:34:41 +0100 Subject: [PATCH 038/109] PK map "Unprivileged principals as DNS Admins" --- docs/security-assessment-mapping.json | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index eccf7fc..e422066 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -1199,6 +1199,17 @@ "name": "[M]Check if the Dns Admins group is not empty" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "subset", + "mapping_scope_detail": "Returns all members.", + "id": "Unprivileged principals as DNS Admins", + "name": "Unprivileged principals as DNS Admins" + } + ] } ] }, From 09b50a4845d83dc9eddf66a6d80216709a15824c Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 18 Feb 2026 19:35:05 +0100 Subject: [PATCH 039/109] Standardize scope detail --- docs/security-assessment-mapping.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index e422066..c854a53 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -2398,7 +2398,7 @@ "controls": [ { "mapping_scope": "subset", - "mapping_scope_detail": "Returns principals of all privilege levels.", + "mapping_scope_detail": "Returns all principals with DCSync privileges.", "id": "Remove non-admin accounts with DCSync permissions", "name": "Remove non-admin accounts with DCSync permissions" } @@ -2409,7 +2409,7 @@ "controls": [ { "mapping_scope": "subset", - "mapping_scope_detail": "Returns principals of all privilege levels.", + "mapping_scope_detail": "Returns all principals with DCSync privileges.", "id": "Non-default principals with DC Sync rights on the domain", "name": "Non-default principals with DC Sync rights on the domain" } From 7e1b317146e884620190b72381fc97c3052d7e52 Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 18 Feb 2026 19:39:46 +0100 Subject: [PATCH 040/109] Ensure all mapping_scope values align --- docs/security-assessment-mapping.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index c854a53..32ee29b 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -1204,7 +1204,7 @@ "source": "PurpleKnight", "controls": [ { - "mapping_scope": "subset", + "mapping_scope": "partial", "mapping_scope_detail": "Returns all members.", "id": "Unprivileged principals as DNS Admins", "name": "Unprivileged principals as DNS Admins" @@ -2397,7 +2397,7 @@ "source": "MDI", "controls": [ { - "mapping_scope": "subset", + "mapping_scope": "partial", "mapping_scope_detail": "Returns all principals with DCSync privileges.", "id": "Remove non-admin accounts with DCSync permissions", "name": "Remove non-admin accounts with DCSync permissions" @@ -2408,7 +2408,7 @@ "source": "PurpleKnight", "controls": [ { - "mapping_scope": "subset", + "mapping_scope": "partial", "mapping_scope_detail": "Returns all principals with DCSync privileges.", "id": "Non-default principals with DC Sync rights on the domain", "name": "Non-default principals with DC Sync rights on the domain" @@ -2917,7 +2917,7 @@ "source": "PurpleKnight", "controls": [ { - "mapping_scope": "subset", + "mapping_scope": "partial", "mapping_scope_detail": "Finds the direction AD -> Entra only.", "id": "AAD privileged users that are also privileged in AD", "name": "AAD privileged users that are also privileged in AD" @@ -2936,7 +2936,7 @@ "source": "PurpleKnight", "controls": [ { - "mapping_scope": "subset", + "mapping_scope": "partial", "mapping_scope_detail": "Finds the direction Entra -> AD only.", "id": "AAD privileged users that are also privileged in AD", "name": "AAD privileged users that are also privileged in AD" @@ -2955,7 +2955,7 @@ "source": "PurpleKnight", "controls": [ { - "mapping_scope": "subset", + "mapping_scope": "partial", "mapping_scope_detail": "Direct role assignments only.", "id": "Hybrid-synced privileged role accounts", "name": "Hybrid-synced privileged role accounts" @@ -2974,7 +2974,7 @@ "source": "PurpleKnight", "controls": [ { - "mapping_scope": "subset", + "mapping_scope": "partial", "mapping_scope_detail": "Group delegated role assignments only.", "id": "Hybrid-synced privileged role accounts", "name": "Hybrid-synced privileged role accounts" From 3ac10f48d4e7b33bfa191b80445c3845c007c5d1 Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 18 Feb 2026 19:41:52 +0100 Subject: [PATCH 041/109] Add PK map "Unprivileged users can add computer accounts to domain" --- docs/security-assessment-mapping.json | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index 32ee29b..7c55e1f 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -850,6 +850,17 @@ "name": "Unsecure domain configurations: ms-DS-MachineAccountQuota" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "Unprivileged users can add computer accounts to domain", + "name": "Unprivileged users can add computer accounts to domain" + } + ] } ] }, From 94675b5a695fbdb75c08f0b8c4249256a786c7d3 Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 18 Feb 2026 21:21:48 +0100 Subject: [PATCH 042/109] Map PK "Privileged objects with unprivileged owners". --- docs/security-assessment-mapping.json | 15 +++++++++++++-- ...Tier Zero computers not owned by Tier Zero.yml | 7 ++++--- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index 7c55e1f..2f24ca9 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -2891,7 +2891,7 @@ { "bloodhound_query": { "guid": "99d29ded-223a-442b-a0e0-f8b5694c6441", - "name": "Tier Zero computers not owned by Tier Zero" + "name": "Tier Zero principals not owned by Tier Zero" }, "maps_to": [ { @@ -2899,11 +2899,22 @@ "controls": [ { "mapping_scope": "superset", - "mapping_scope_detail": "Remove false positives by reducing scope to Non-Tier Zero. Expanded target scope to Tier Zero", + "mapping_scope_detail": "Remove false positives by reducing source scope to Non-Tier Zero. Expanded target scope to Tier Zero", "id": "Domain Controller owner is not an administrator", "name": "Domain Controller owner is not an administrator" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "superset", + "mapping_scope_detail": "Remove false positives by reducing source scope to Non-Tier Zero. Expanded target scope to Tier Zero", + "id": "Privileged objects with unprivileged owners", + "name": "Privileged objects with unprivileged owners" + } + ] } ] }, diff --git a/queries/Tier Zero computers not owned by Tier Zero.yml b/queries/Tier Zero computers not owned by Tier Zero.yml index 57872e2..baebbbb 100644 --- a/queries/Tier Zero computers not owned by Tier Zero.yml +++ b/queries/Tier Zero computers not owned by Tier Zero.yml @@ -1,14 +1,15 @@ -name: Tier Zero computers not owned by Tier Zero +name: Tier Zero principals not owned by Tier Zero guid: 99d29ded-223a-442b-a0e0-f8b5694c6441 prebuilt: false platforms: Active Directory category: Dangerous Privileges description: query: |- - MATCH p=(n:Base)-[:Owns]->(:Computer) + MATCH p=(n:Base)-[:Owns]->(m:Base) WHERE NOT ((n:Tag_Tier_Zero) OR COALESCE(n.system_tags, '') CONTAINS 'admin_tier_0') + AND ((m:Tag_Tier_Zero) OR COALESCE(m.system_tags, '') CONTAINS 'admin_tier_0') RETURN p -revision: 2 +revision: 3 resources: acknowledgements: Martin Sohn Christensen, @martinsohndk From 4838f839493bab407e0099ec5d72b8b31ccfd01b Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 18 Feb 2026 21:21:59 +0100 Subject: [PATCH 043/109] Map PK "Well-known privileged SIDs in sIDHistory" --- docs/security-assessment-mapping.json | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index 2f24ca9..c6e890e 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -1887,6 +1887,17 @@ "name": "Unsecure SID History attributes" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "superset", + "mapping_scope_detail": "Expanded scope to Tier Zero", + "id": "Well-known privileged SIDs in sIDHistory", + "name": "Well-known privileged SIDs in sIDHistory" + } + ] } ] }, From 876b70996c2c254f58f050ac7f3df52bdfde6cca Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 18 Feb 2026 21:22:26 +0100 Subject: [PATCH 044/109] Rename query --- ...r Zero.yml => Tier Zero principals not owned by Tier Zero.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename queries/{Tier Zero computers not owned by Tier Zero.yml => Tier Zero principals not owned by Tier Zero.yml} (100%) diff --git a/queries/Tier Zero computers not owned by Tier Zero.yml b/queries/Tier Zero principals not owned by Tier Zero.yml similarity index 100% rename from queries/Tier Zero computers not owned by Tier Zero.yml rename to queries/Tier Zero principals not owned by Tier Zero.yml From 9a9ddfa3a527807bce986c8c37627f30aeeb8dd7 Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 18 Feb 2026 21:26:46 +0100 Subject: [PATCH 045/109] Update Tier Zero principals not owned by Tier Zero.yml Include all Owns edges, regardless of traversability. --- queries/Tier Zero principals not owned by Tier Zero.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/queries/Tier Zero principals not owned by Tier Zero.yml b/queries/Tier Zero principals not owned by Tier Zero.yml index baebbbb..b45a400 100644 --- a/queries/Tier Zero principals not owned by Tier Zero.yml +++ b/queries/Tier Zero principals not owned by Tier Zero.yml @@ -5,7 +5,7 @@ platforms: Active Directory category: Dangerous Privileges description: query: |- - MATCH p=(n:Base)-[:Owns]->(m:Base) + MATCH p=(n:Base)-[:OwnsRaw|Owns|OwnsLimitedRights]->(m:Base) WHERE NOT ((n:Tag_Tier_Zero) OR COALESCE(n.system_tags, '') CONTAINS 'admin_tier_0') AND ((m:Tag_Tier_Zero) OR COALESCE(m.system_tags, '') CONTAINS 'admin_tier_0') RETURN p From 4d69082da233729d47f74df31cc759f04a33d062 Mon Sep 17 00:00:00 2001 From: Martin Date: Fri, 20 Feb 2026 08:25:51 +0100 Subject: [PATCH 046/109] fix some PK mapping scopes --- docs/security-assessment-mapping.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index c6e890e..cfe1df6 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -2950,7 +2950,7 @@ "source": "PurpleKnight", "controls": [ { - "mapping_scope": "partial", + "mapping_scope": "combination", "mapping_scope_detail": "Finds the direction AD -> Entra only.", "id": "AAD privileged users that are also privileged in AD", "name": "AAD privileged users that are also privileged in AD" @@ -2969,7 +2969,7 @@ "source": "PurpleKnight", "controls": [ { - "mapping_scope": "partial", + "mapping_scope": "combination", "mapping_scope_detail": "Finds the direction Entra -> AD only.", "id": "AAD privileged users that are also privileged in AD", "name": "AAD privileged users that are also privileged in AD" @@ -2988,7 +2988,7 @@ "source": "PurpleKnight", "controls": [ { - "mapping_scope": "partial", + "mapping_scope": "combination", "mapping_scope_detail": "Direct role assignments only.", "id": "Hybrid-synced privileged role accounts", "name": "Hybrid-synced privileged role accounts" @@ -3007,7 +3007,7 @@ "source": "PurpleKnight", "controls": [ { - "mapping_scope": "partial", + "mapping_scope": "combination", "mapping_scope_detail": "Group delegated role assignments only.", "id": "Hybrid-synced privileged role accounts", "name": "Hybrid-synced privileged role accounts" @@ -3171,7 +3171,7 @@ "controls": [ { "mapping_scope": "exact", - "mapping_scope_detail": "Expanded technique scope to include all BloodHound traversable edges, instead of just the AllowedToAct edge.", + "mapping_scope_detail": "", "id": "Objects in privileged groups without adminCount=1 (SDProp)", "name": "Objects in privileged groups without adminCount=1 (SDProp)" } From 9868bca5ef0c4bd9996089bb33f9d9af0530c9c0 Mon Sep 17 00:00:00 2001 From: 5tuk0v <90915844+5tuk0v@users.noreply.github.com> Date: Wed, 11 Mar 2026 23:33:13 -0400 Subject: [PATCH 047/109] Add query for computers with local administrator permissions over other computers --- ...istrator permissions over other computers.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 queries/Computers with local administrator permissions over other computers.yml diff --git a/queries/Computers with local administrator permissions over other computers.yml b/queries/Computers with local administrator permissions over other computers.yml new file mode 100644 index 0000000..3971d1b --- /dev/null +++ b/queries/Computers with local administrator permissions over other computers.yml @@ -0,0 +1,16 @@ +name: Computers with local administrator permissions over other computers +guid: 093a8e04-4f79-4264-a64b-4e52a630cd0d +prebuilt: false +platform: Active Directory +category: NTLM Relay Attacks +description: Finds computers with AdminTo edges over other computers, including rights derived through nested group memberships. +query: |- + MATCH p = (s:Computer)-[:MemberOf*0..]->()-[:AdminTo]->(d:Computer) + WHERE s <> d + RETURN p + LIMIT 1000 +revision: 1 +resources: + - https://specterops.io/blog/2025/04/08/the-renaissance-of-ntlm-relay-attacks-everything-you-need-to-know/ + - https://github.com/subat0mik/Misconfiguration-Manager +acknowledgements: Mat Fiore, @stuk0v \ No newline at end of file From 64536e8a183e5d566850c5e1eeff284f3dd75fdc Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 16 Mar 2026 12:21:09 +0100 Subject: [PATCH 048/109] align mapping type descriptions with those in json file --- docs/security-assessment-mapping.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/security-assessment-mapping.md b/docs/security-assessment-mapping.md index fbd7cf3..733b366 100644 --- a/docs/security-assessment-mapping.md +++ b/docs/security-assessment-mapping.md @@ -1,5 +1,5 @@ # BloodHound as a Comprehensive Assessment Platform -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?”). +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 controls tested by various other tools (e.g., “Which users have non-expiring passwords?”). To assist in these broader security assessment capabilities, BloodHound queries have been mapped to common security assessment tools, demonstrating overlap in capabilities. @@ -20,10 +20,10 @@ The following show which other security tools the mapping supports and the numbe ## Mapping Structure Each mapping includes a type that describes the relationship: -- `exact` - Query identifies the same risk with same scope -- `partial` - Query covers the core risk but with different approach/scope -- `superset` - Query covers everything the other tool does plus additional risk analysis -- `combination` - Single query maps to multiple controls that together equal its functionality +- `partial` - Query covers the core assessment control but with different approach/scope +- `combination` - Multiple queries are combined to fully cover a single assessment control +- `exact` - Query identifies the same assessment control with same scope +- `superset` - Query covers everything the assessment control does plus covers additional risk 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. From 980f15ba8ecaf6f0b1aa99a3d3b7ddf1cf806feb Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 16 Mar 2026 12:32:32 +0100 Subject: [PATCH 049/109] Map PK "gMSA objects with old passwords" --- docs/security-assessment-mapping.json | 19 +++++++++++++++++ ... than the default maximum password age.yml | 21 +++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 queries/Managed Service Accounts with passwords older than the default maximum password age.yml diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index cfe1df6..414c432 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -69,6 +69,25 @@ } ] }, + { + "bloodhound_query": { + "guid": "e1ec8c3d-14c4-425a-b6fb-d401633b915d", + "name": "Managed Service Accounts with passwords older than the default maximum password age" + }, + "maps_to": [ + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "gMSA objects with old passwords", + "name": "gMSA objects with old passwords" + } + ] + } + ] + }, { "bloodhound_query": { "guid": "72550bcb-3c4f-463d-8973-91a49163dc5a", diff --git a/queries/Managed Service Accounts with passwords older than the default maximum password age.yml b/queries/Managed Service Accounts with passwords older than the default maximum password age.yml new file mode 100644 index 0000000..38b6273 --- /dev/null +++ b/queries/Managed Service Accounts with passwords older than the default maximum password age.yml @@ -0,0 +1,21 @@ +name: Managed Service Accounts with passwords older than the default maximum password age +guid: e1ec8c3d-14c4-425a-b6fb-d401633b915d +prebuilt: false +platforms: Active Directory +category: Active Directory Hygiene +description: Managed Service Account (gMSA / sMSA) passwords are automatically changed by AD every 30 days for security purposes. +query: |- + WITH 60 as rotation_period + MATCH (n:Base) + WHERE (n.gmsa = true OR n.msa = true) + AND n.pwdlastset < (datetime().epochseconds - (rotation_period * 86400)) // password not rotated + AND n.enabled = true // enabled principals + AND n.whencreated < (datetime().epochseconds - (rotation_period * 86400)) // exclude recently created computers + AND n.lastlogontimestamp > (datetime().epochseconds - (rotation_period * 86400)) // active computers (Replicated value) + AND n.lastlogon > (datetime().epochseconds - (rotation_period * 86400)) // active computers (Non-replicated value) + RETURN n +revision: 1 +resources: +- https://learn.microsoft.com/en-us/entra/architecture/service-accounts-group-managed +- https://learn.microsoft.com/en-us/entra/architecture/service-accounts-standalone-managed +acknowledgements: Martin Sohn Christensen, @martinsohndk From b97019d9fa47e1012addad2d854cf0b127b17cea Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 16 Mar 2026 12:35:58 +0100 Subject: [PATCH 050/109] Map PK "Privileged accounts with a password that never expires" --- docs/security-assessment-mapping.json | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index 414c432..d947c84 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -529,6 +529,25 @@ } ] }, + { + "bloodhound_query": { + "guid": "4eca1b69-00a2-48a0-abb3-b94ea647cf6b", + "name": "Tier Zero / High Value users with non-expiring passwords" + }, + "maps_to": [ + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "superset", + "mapping_scope_detail": "Expanded scope to Tier Zero", + "id": "Privileged accounts with a password that never expires", + "name": "Privileged accounts with a password that never expires" + } + ] + } + ] + }, { "bloodhound_query": { "guid": "8172d52c-a975-49bd-9180-5b6efc59c9ab", From 309a1cafedeaff6535fc3cc0bd9d58e6e94694cc Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 16 Mar 2026 12:36:06 +0100 Subject: [PATCH 051/109] Map PK "Kerberoastable members of Tier Zero / High Value groups" --- docs/security-assessment-mapping.json | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index d947c84..cc48a9e 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -989,6 +989,17 @@ "name": "Unsecure account attributes: Remove a Service Principal Name (SPN)" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "superset", + "mapping_scope_detail": "Expanded scope to Tier Zero", + "id": "Kerberoastable members of Tier Zero / High Value groups", + "name": "Kerberoastable members of Tier Zero / High Value groups" + } + ] } ] }, From face189b8d1da8202b7a191476a88cbf6be0e115 Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 16 Mar 2026 12:38:07 +0100 Subject: [PATCH 052/109] Map PK "Users with ServicePrincipalName defined" --- docs/security-assessment-mapping.json | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index cc48a9e..30a8be0 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -1003,6 +1003,25 @@ } ] }, + { + "bloodhound_query": { + "guid": "14ab4eaa-b73b-49c4-b2d1-1e020757c995", + "name": "All Kerberoastable users" + }, + "maps_to": [ + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "Users with ServicePrincipalName defined", + "name": "Users with ServicePrincipalName defined" + } + ] + } + ] + }, { "bloodhound_query": { "guid": "5e0d69b1-37d1-43ae-ac5d-f297f312fab5", From 12351aa04ad432f2b061bd97851e626fd3d77f60 Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 16 Mar 2026 14:56:04 +0100 Subject: [PATCH 053/109] correct PK names and IDs --- docs/security-assessment-mapping.json | 158 +++++++++++++------------- 1 file changed, 79 insertions(+), 79 deletions(-) diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index 30a8be0..bbdc628 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -62,7 +62,7 @@ { "mapping_scope": "exact", "mapping_scope_detail": "", - "id": "Computers with password last set over 90 days ago", + "id": "07362c0e-e675-4451-9d09-65ca46ab43a3", "name": "Computers with password last set over 90 days ago" } ] @@ -81,7 +81,7 @@ { "mapping_scope": "exact", "mapping_scope_detail": "", - "id": "gMSA objects with old passwords", + "id": "a7813b26-5472-4fbd-a6a4-1c93bfeb2784", "name": "gMSA objects with old passwords" } ] @@ -133,14 +133,14 @@ { "mapping_scope": "exact", "mapping_scope_detail": "", - "id": "Enabled admin accounts that are inactive", + "id": "750c9233-57b3-430c-af56-1e899e81b202", "name": "Enabled admin accounts that are inactive" }, { "mapping_scope": "exact", "mapping_scope_detail": "", - "id": "Domain controllers that have not authenticated to the domain for more than 45 days", - "name": "Domain controllers that have not authenticated to the domain for more than 45 days" + "id": "1b6df9e8-e5ed-45f7-880c-44b4a9a7d6bd", + "name": "Domain Controllers that have not authenticated to the domain for more than 45 days" } ] } @@ -180,8 +180,8 @@ { "mapping_scope": "exact", "mapping_scope_detail": "", - "id": "Domain controllers with old passwords", - "name": "Domain controllers with old passwords" + "id": "bd287262-50dd-4e99-9daa-7754eb27cddb", + "name": "Domain Controllers with old passwords" } ] } @@ -270,7 +270,7 @@ { "mapping_scope": "combination", "mapping_scope_detail": "", - "id": "Users and computers with non-default Primary Group IDs", + "id": "16262280-22e2-40e2-a227-9934e63dadaa", "name": "Users and computers with non-default Primary Group IDs" } ] @@ -322,7 +322,7 @@ { "mapping_scope": "combination", "mapping_scope_detail": "", - "id": "Users and computers with non-default Primary Group IDs", + "id": "16262280-22e2-40e2-a227-9934e63dadaa", "name": "Users and computers with non-default Primary Group IDs" } ] @@ -369,7 +369,7 @@ { "mapping_scope": "superset", "mapping_scope_detail": "Expanded scope to include computer accounts", - "id": "User accounts that store passwords with reversible encryption", + "id": "2c62cbf1-ebdb-4ac4-9c5c-6507a15fd9e2", "name": "User accounts that store passwords with reversible encryption" } ] @@ -481,7 +481,7 @@ { "mapping_scope": "exact", "mapping_scope_detail": "", - "id": "User accounts with password not required", + "id": "32c2a92b-fe99-4e5c-bfbb-7497b759946d", "name": "User accounts with password not required" } ] @@ -522,7 +522,7 @@ { "mapping_scope": "exact", "mapping_scope_detail": "", - "id": "Users with Password Never Expires flag set", + "id": "3653043f-2790-4255-a625-3359e6dc8ef6", "name": "Users with Password Never Expires flag set" } ] @@ -541,7 +541,7 @@ { "mapping_scope": "superset", "mapping_scope_detail": "Expanded scope to Tier Zero", - "id": "Privileged accounts with a password that never expires", + "id": "755d0eba-b8dc-4216-a9d4-44ab43bfb7b5", "name": "Privileged accounts with a password that never expires" } ] @@ -602,7 +602,7 @@ { "mapping_scope": "exact", "mapping_scope_detail": "", - "id": "Domains with obsolete functional levels", + "id": "ff50af43-c9c8-41c6-987f-eaaaedbca25c", "name": "Domains with obsolete functional levels" } ] @@ -723,7 +723,7 @@ { "mapping_scope": "exact", "mapping_scope_detail": "", - "id": "Computers with older OS versions", + "id": "a0d33c5f-fda5-4a06-9b80-5196e099131e", "name": "Computers with older OS versions" } ] @@ -895,8 +895,8 @@ { "mapping_scope": "exact", "mapping_scope_detail": "", - "id": "Unprivileged users can add computer accounts to domain", - "name": "Unprivileged users can add computer accounts to domain" + "id": "6317479a-c7df-49ca-bbf1-47ecdf199792", + "name": "Unprivileged users can add computer accounts to the domain" } ] } @@ -996,8 +996,8 @@ { "mapping_scope": "superset", "mapping_scope_detail": "Expanded scope to Tier Zero", - "id": "Kerberoastable members of Tier Zero / High Value groups", - "name": "Kerberoastable members of Tier Zero / High Value groups" + "id": "Privileged users with ServicePrincipalNames defined", + "name": "Privileged users with ServicePrincipalNames defined" } ] } @@ -1056,13 +1056,13 @@ { "mapping_scope": "exact", "mapping_scope_detail": "", - "id": "Admins with old passwords", + "id": "dc8f6889-7df9-4414-9bf5-865f1e3e9e83", "name": "Admins with old passwords" }, { "mapping_scope": "exact", "mapping_scope_detail": "", - "id": "Built-in domain Administrator account with old password (180 days)", + "id": "82901792-3b6e-4b3f-94d7-64d4743273fb", "name": "Built-in domain Administrator account with old password (180 days)" } ] @@ -1081,7 +1081,7 @@ { "mapping_scope": "exact", "mapping_scope_detail": "", - "id": "Users with old passwords", + "id": "4ef13866-3af9-477a-84d4-d2d7e39f3c0f", "name": "Users with old passwords" } ] @@ -1111,7 +1111,7 @@ { "mapping_scope": "exact", "mapping_scope_detail": "", - "id": "Entra Connect sync account password reset", + "id": "4f964cc3-7ce9-4b44-b6d0-799622027adc", "name": "Entra Connect sync account password reset" } ] @@ -1217,7 +1217,7 @@ { "mapping_scope": "exact", "mapping_scope_detail": "", - "id": "Protected Users group not in use", + "id": "b4ee6e7c-5b7c-4e63-82cd-668d3d0b3354", "name": "Protected Users group not in use" } ] @@ -1285,7 +1285,7 @@ { "mapping_scope": "partial", "mapping_scope_detail": "Returns all members.", - "id": "Unprivileged principals as DNS Admins", + "id": "e79191aa-b68f-4983-8420-b2ca25bca6ea", "name": "Unprivileged principals as DNS Admins" } ] @@ -1334,7 +1334,7 @@ { "mapping_scope": "exact", "mapping_scope_detail": "", - "id": "Enterprise Key Admins with full access to domain", + "id": "ec4a80dc-bec8-4557-b19f-cc3d15ed5517", "name": "Enterprise Key Admins with full access to domain" } ] @@ -1394,7 +1394,7 @@ { "mapping_scope": "superset", "mapping_scope_detail": "Remove false positives by reducing scope to Non-Tier Zero", - "id": "Permission changes on AdminSDHolder object", + "id": "39293315-4817-44f6-be2d-e76daeaf8208", "name": "Permission changes on AdminSDHolder object" } ] @@ -1424,7 +1424,7 @@ { "mapping_scope": "exact", "mapping_scope_detail": "", - "id": "Operator groups no longer protected by AdminSDHolder and SDProp", + "id": "f2f975fd-6ce2-491b-8247-9662a0126187", "name": "Operator groups no longer protected by AdminSDHolder and SDProp" } ] @@ -1454,7 +1454,7 @@ { "mapping_scope": "exact", "mapping_scope_detail": "", - "id": "Built-in domain Administrator account used within the last two weeks", + "id": "2c2b46b6-3bce-451c-bbc8-7c7652fecbf0", "name": "Built-in domain Administrator account used within the last two weeks" } ] @@ -1484,7 +1484,7 @@ { "mapping_scope": "superset", "mapping_scope_detail": "Expanded scope to Tier Zero", - "id": "Forest contains more than 50 privileged accounts", + "id": "9c9bfa49-9431-4043-a073-0f90f3008b54", "name": "Forest contains more than 50 privileged accounts" } ] @@ -1514,8 +1514,8 @@ { "mapping_scope": "exact", "mapping_scope_detail": "", - "id": "Operator Groups that are not empty", - "name": "Operator Groups that are not empty" + "id": "681db401-004b-4cec-a4b8-07926a63e281", + "name": "Operators Groups that are not empty" } ] } @@ -1738,7 +1738,7 @@ { "mapping_scope": "exact", "mapping_scope_detail": "", - "id": "Computer or user accounts with SPN that have unconstrained delegation", + "id": "5d5d5b9c-5685-4786-b3a4-eb2ab1480a69", "name": "Computer or user accounts with SPN that have unconstrained delegation" } ] @@ -1866,13 +1866,13 @@ { "mapping_scope": "exact", "mapping_scope_detail": "", - "id": "Dangerous Trust Attribute Set", + "id": "ffa8305f-ff2e-4196-b980-8d7cafce849d", "name": "Dangerous Trust Attribute Set" }, { "mapping_scope": "exact", "mapping_scope_detail": "", - "id": "Domain trust to a third-party domain without quarantine", + "id": "19a4b814-3561-4463-a083-2769fabf1490", "name": "Domain trust to a third-party domain without quarantine" } ] @@ -1883,7 +1883,7 @@ { "mapping_scope": "exact", "mapping_scope_detail": "", - "id": "Outbound forest trust with SID History enabled", + "id": "13b327cf-1283-4362-8815-6c1acb24f4de", "name": "Outbound forest trust with SID History enabled" } ] @@ -1962,8 +1962,8 @@ { "mapping_scope": "superset", "mapping_scope_detail": "Expanded scope to Tier Zero", - "id": "Well-known privileged SIDs in sIDHistory", - "name": "Well-known privileged SIDs in sIDHistory" + "id": "78a9064a-f3a0-4420-ab9d-2db003d6f4b4", + "name": "Well-known privileged SIDs in SIDHistory" } ] } @@ -2157,19 +2157,19 @@ { "mapping_scope": "exact", "mapping_scope_detail": "", - "id": "Certificate templates that allow requesters to specify a subjectAltName", + "id": "790e1c72-5786-4907-83cd-9f310db70f1b", "name": "Certificate templates that allow requesters to specify a subjectAltName" }, { "mapping_scope": "exact", "mapping_scope_detail": "", - "id": "Certificate templates with 3 or more insecure configurations", + "id": "d64cab17-754c-4643-872b-a9113fbb7808", "name": "Certificate templates with 3 or more insecure configurations" }, { "mapping_scope": "exact", "mapping_scope_detail": "", - "id": "Dangerous control paths expose certificate templates", + "id": "a76ea884-afef-4d00-9820-b24117a12661", "name": "Dangerous control paths expose certificate templates" } ] @@ -2224,8 +2224,8 @@ { "mapping_scope": "exact", "mapping_scope_detail": "", - "id": "AD Certificate Authority with Web Enrollment – ESC8", - "name": "AD Certificate Authority with Web Enrollment – ESC8" + "id": "08612871-a9dc-4d18-a2cc-580e086e3199", + "name": "AD Certificate Authority with Web Enrollment - ESC8" } ] } @@ -2295,8 +2295,8 @@ { "mapping_scope": "exact", "mapping_scope_detail": "", - "id": "Kerberos krbtgt account with old password", - "name": "Kerberos krbtgt account with old password" + "id": "1e43868c-9a46-41e8-8daf-d8bfe57aaef7", + "name": "Kerberos KRBTGT account with old password" } ] } @@ -2372,7 +2372,7 @@ { "mapping_scope": "exact", "mapping_scope_detail": "", - "id": "LDAP signing is not required on Domain Controllers", + "id": "4fe825ed-07fb-4b06-913a-be5c9542ca54", "name": "LDAP signing is not required on Domain Controllers" } ] @@ -2402,7 +2402,7 @@ { "mapping_scope": "exact", "mapping_scope_detail": "", - "id": "Smart card password rotation disabled", + "id": "04ad3f4e-b67c-49c3-a668-528214dd6c63", "name": "Smart card password rotation disabled" } ] @@ -2432,7 +2432,7 @@ { "mapping_scope": "exact", "mapping_scope_detail": "", - "id": "User accounts using Smart Card authentication with old password", + "id": "baeb89cc-e32d-4f17-ad37-29a6a45ff724", "name": "User accounts using Smart Card authentication with old password" } ] @@ -2500,7 +2500,7 @@ { "mapping_scope": "partial", "mapping_scope_detail": "Returns all principals with DCSync privileges.", - "id": "Non-default principals with DC Sync rights on the domain", + "id": "bcb85336-3507-4565-91a2-9c1360c5a5f1", "name": "Non-default principals with DC Sync rights on the domain" } ] @@ -2568,7 +2568,7 @@ { "mapping_scope": "superset", "mapping_scope_detail": "Expanded scope to Tier Zero", - "id": "SMB Signing is not required on Domain Controllers", + "id": "0d9236c4-98a1-4763-913b-783fdfe1de4c", "name": "SMB Signing is not required on Domain Controllers" } ] @@ -2598,7 +2598,7 @@ { "mapping_scope": "superset", "mapping_scope_detail": "Expanded attributes scope to include 'unicodepwd' and 'msSFU30Password'", - "id": "Users with the attribute userPassword set", + "id": "dfa45647-c2b5-4066-b7db-3a8890868cef", "name": "Users with the attribute userPassword set" } ] @@ -2628,7 +2628,7 @@ { "mapping_scope": "exact", "mapping_scope_detail": "", - "id": "Anonymous NSPI access to AD enabled", + "id": "aace861d-9d2c-47df-9d3a-eb8f07008abb", "name": "Anonymous NSPI access to AD enabled" } ] @@ -2658,7 +2658,7 @@ { "mapping_scope": "exact", "mapping_scope_detail": "", - "id": "Anonymous access to Active Directory enabled", + "id": "79d857a6-23a7-421d-a63d-5a2f9df5d080", "name": "Anonymous access to Active Directory enabled" } ] @@ -2775,8 +2775,8 @@ { "mapping_scope": "exact", "mapping_scope_detail": "", - "id": "Changes to PreWindows 2000 Compatible Access Group membership", - "name": "Changes to PreWindows 2000 Compatible Access Group membership" + "id": "c0c929cf-ab95-4d8f-975b-8193054f520b", + "name": "Changes to Pre-Windows 2000 Compatible Access Group membership" } ] } @@ -2805,7 +2805,7 @@ { "mapping_scope": "superset", "mapping_scope_detail": "Remove false positives by reducing scope to Non-Tier Zero. Expanded target scope to Tier Zero", - "id": "Unprivileged accounts with adminCount=1", + "id": "e08bbf6a-b17e-4417-a9cd-8f4b7b6210f9", "name": "Unprivileged accounts with adminCount=1" } ] @@ -2865,7 +2865,7 @@ { "mapping_scope": "exact", "mapping_scope_detail": "", - "id": "Built-in guest account is enabled", + "id": "d14af45f-009c-4840-8e35-36a97c979a8c", "name": "Built-in guest account is enabled" } ] @@ -2884,7 +2884,7 @@ { "mapping_scope": "exact", "mapping_scope_detail": "", - "id": "Foreign Security Principals in Privileged Group", + "id": "3bcaff82-ae70-4646-bc66-f7997be54e5e", "name": "Foreign Security Principals in Privileged Group" } ] @@ -2903,8 +2903,8 @@ { "mapping_scope": "exact", "mapping_scope_detail": "", - "id": "gMSA not used", - "name": "gMSA not used" + "id": "93402830-3bdf-4086-8629-7bdc654651f9", + "name": "gMSA not in use" } ] } @@ -2922,7 +2922,7 @@ { "mapping_scope": "exact", "mapping_scope_detail": "", - "id": "AD objects created within the last 10 days", + "id": "b3061191-07e0-468f-b2a0-bbf0485fa900", "name": "AD objects created within the last 10 days" } ] @@ -2941,7 +2941,7 @@ { "mapping_scope": "exact", "mapping_scope_detail": "", - "id": "Recent privileged account creation activity", + "id": "c4218bf3-aca4-4d17-90a3-9ba3f4ec42e7", "name": "Recent privileged account creation activity" } ] @@ -2960,7 +2960,7 @@ { "mapping_scope": "exact", "mapping_scope_detail": "", - "id": "Inheritance enabled on AdminSDHolder object", + "id": "216596bf-333e-4f59-a3f5-8af65acbba9b", "name": "Inheritance enabled on AdminSDHolder object" } ] @@ -2979,7 +2979,7 @@ { "mapping_scope": "superset", "mapping_scope_detail": "Remove false positives by reducing source scope to Non-Tier Zero. Expanded target scope to Tier Zero", - "id": "Domain Controller owner is not an administrator", + "id": "d2df85d9-abbc-4585-be11-123a6d90a871", "name": "Domain Controller owner is not an administrator" } ] @@ -2990,7 +2990,7 @@ { "mapping_scope": "superset", "mapping_scope_detail": "Remove false positives by reducing source scope to Non-Tier Zero. Expanded target scope to Tier Zero", - "id": "Privileged objects with unprivileged owners", + "id": "40499bf5-9087-4d55-9db3-2cb641a47ac8", "name": "Privileged objects with unprivileged owners" } ] @@ -3009,8 +3009,8 @@ { "mapping_scope": "superset", "mapping_scope_detail": "Expanded scope to Tier Zero", - "id": "AD privileged users that are synced to AAD", - "name": "AD privileged users that are synced to AAD" + "id": "5f6540a9-f226-4438-9e64-387a98beda2b", + "name": "AD privileged users that are synced to Entra ID" } ] }, @@ -3020,8 +3020,8 @@ { "mapping_scope": "combination", "mapping_scope_detail": "Finds the direction AD -> Entra only.", - "id": "AAD privileged users that are also privileged in AD", - "name": "AAD privileged users that are also privileged in AD" + "id": "c117b6a5-00c8-4e77-93d2-e291e36b462a", + "name": "Entra ID privileged users that are also privileged in AD" } ] } @@ -3039,8 +3039,8 @@ { "mapping_scope": "combination", "mapping_scope_detail": "Finds the direction Entra -> AD only.", - "id": "AAD privileged users that are also privileged in AD", - "name": "AAD privileged users that are also privileged in AD" + "id": "c117b6a5-00c8-4e77-93d2-e291e36b462a", + "name": "Entra ID privileged users that are also privileged in AD" } ] } @@ -3096,7 +3096,7 @@ { "mapping_scope": "superset", "mapping_scope_detail": "Remove false positives by reducing scope to Non-Tier Zero", - "id": "Distributed COM Users group or Performance Log Users group are not empty", + "id": "0e61db1b-b877-457e-9550-607330143d92", "name": "Distributed COM Users group or Performance Log Users group are not empty" } ] @@ -3115,7 +3115,7 @@ { "mapping_scope": "superset", "mapping_scope_detail": "Audits all objects in the 'PUBLIC KEY SERVICES' container", - "id": "Dangerous control paths expose certificate containers", + "id": "e441aeb0-ba69-426b-bbc1-028bf258c3d8", "name": "Dangerous control paths expose certificate containers" } ] @@ -3153,7 +3153,7 @@ { "mapping_scope": "superset", "mapping_scope_detail": "Expanded technique scope to include all BloodHound traversable edges, instead of just the AllowedToAct edge. Expanded target scope to all KRBTGT account types.", - "id": "Accounts with Constrained Delegation configured to krbtgt", + "id": "cfe3bfa1-f28e-4017-8e94-044bd6b914e3", "name": "Accounts with Constrained Delegation configured to krbtgt" } ] @@ -3164,7 +3164,7 @@ { "mapping_scope": "superset", "mapping_scope_detail": "Expanded technique scope to include all BloodHound traversable edges, instead of just the AddAllowedToAct edge. Expanded target scope to all KRBTGT account types.", - "id": "Write access to RBCD on krbtgt account", + "id": "eb99e786-3ce1-4172-9801-dd4203cfd3e2", "name": "Write access to RBCD on krbtgt account" } ] @@ -3183,7 +3183,7 @@ { "mapping_scope": "exact", "mapping_scope_detail": "", - "id": "Privileged users that are disabled", + "id": "87081486-fc4f-4027-842e-c5f17ec4f1bf", "name": "Privileged users that are disabled" } ] @@ -3202,7 +3202,7 @@ { "mapping_scope": "superset", "mapping_scope_detail": "Remove false positives by reducing scope to Non-Tier Zero. Checks prerequisites.", - "id": "OU permissions enabling BadSuccessor dMSA escalation", + "id": "5e0a22b9-2da5-4fde-9de6-e3a77961d6e6", "name": "OU permissions enabling BadSuccessor dMSA escalation" } ] @@ -3221,7 +3221,7 @@ { "mapping_scope": "superset", "mapping_scope_detail": "Expanded technique scope to include all BloodHound traversable edges, instead of just the AllowedToAct edge.", - "id": "Write access to RBCD on DC", + "id": "af90ba77-f497-479a-aa57-ed106191e302", "name": "Write access to RBCD on DC" } ] @@ -3240,7 +3240,7 @@ { "mapping_scope": "exact", "mapping_scope_detail": "", - "id": "Objects in privileged groups without adminCount=1 (SDProp)", + "id": "b5df966a-2202-401e-8c0c-0e212d7f666d", "name": "Objects in privileged groups without adminCount=1 (SDProp)" } ] From 4b26a8902e05bbab2422c4fb463721bd8a82bbd9 Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 16 Mar 2026 14:56:19 +0100 Subject: [PATCH 054/109] PK map "Computer Accounts in Privileged Groups" --- docs/security-assessment-mapping.json | 19 ++++++++++++ ...embership in default privileged groups.yml | 30 +++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 queries/Computers with membership in default privileged groups.yml diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index bbdc628..d3f142c 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -3246,6 +3246,25 @@ ] } ] + }, + { + "bloodhound_query": { + "guid": "622bf05c-b34b-4538-9a1e-524a2f6f58b0", + "name": "Computers members of built-in privileged groups" + }, + "maps_to": [ + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "113f7039-879b-4093-a42b-dce6b47b313c", + "name": "Computer Accounts in Privileged Groups" + } + ] + } + ] } ] } \ No newline at end of file diff --git a/queries/Computers with membership in default privileged groups.yml b/queries/Computers with membership in default privileged groups.yml new file mode 100644 index 0000000..6ba6dab --- /dev/null +++ b/queries/Computers with membership in default privileged groups.yml @@ -0,0 +1,30 @@ +name: Computers members of built-in privileged groups +guid: 622bf05c-b34b-4538-9a1e-524a2f6f58b0 +prebuilt: false +platforms: Active Directory +category: Domain Information +description: Finds computers that are members of builtin privileged groups, which is uncommon and possibly bad practice. +query: |- + MATCH p=(c:Computer)-[:MemberOf*1..]->(g:Group) + WHERE g.objectid ENDS WITH 'S-1-5-32-544' // Administrators + OR g.objectid ENDS WITH 'S-1-5-32-551' // Backup Operators + OR g.objectid ENDS WITH '-512' // Domain Admins + OR g.objectid ENDS WITH '-519' // Enterprise Admins + OR g.objectid ENDS WITH '-527' // Enterprise Key Admins + OR g.objectid ENDS WITH '-526' // Key Admins + OR g.objectid ENDS WITH '-518' // Schema Admins + OR g.objectid ENDS WITH 'S-1-5-32-548' // Account Operators + OR g.objectid ENDS WITH 'S-1-5-32-569' // Cryptographic Operators + OR g.objectid ENDS WITH 'S-1-5-32-562' // Distributed COM Users + OR g.name STARTS WITH 'DNSADMINS@' // DNS Admins + OR g.objectid ENDS WITH '-557' // Incoming Forest Trust Builders + OR g.objectid ENDS WITH 'S-1-5-32-559' // Performance Log Users + OR g.objectid ENDS WITH 'S-1-5-32-550' // Print Operators + OR g.objectid ENDS WITH 'S-1-5-32-549' // Server Operators + OR g.objectid ENDS WITH 'S-1-5-32-552' // Replicators + OR (g.objectid ENDS WITH '-516' AND NOT c.isdc = true) // Domain Controllers (excludes legitimate DCs) + RETURN p +revision: 1 +resources: +acknowledgements: Martin Sohn Christensen, @martinsohndk + From 995ee3fbf0bccd9f62598f150d9cf1e2e12d2765 Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 16 Mar 2026 15:01:17 +0100 Subject: [PATCH 055/109] Map PK "RC4 or DES encryption type are supported by Domain Controllers" --- docs/security-assessment-mapping.json | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index d3f142c..5293369 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -839,6 +839,17 @@ "name": "Weak cipher usage" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "superset", + "mapping_scope_detail": "Returns all principals with weak supported Kerberos encryption types.", + "id": "RC4 or DES encryption type are supported by Domain Controllers", + "name": "RC4 or DES encryption type are supported by Domain Controllers" + } + ] } ] }, From 16398e185cc71d715f599277639bd0f2e84f576d Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 16 Mar 2026 15:06:17 +0100 Subject: [PATCH 056/109] Fix: Map "RC4 or DES encryption type are supported by Domain Controllers" --- docs/security-assessment-mapping.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index 5293369..408ea83 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -846,7 +846,7 @@ { "mapping_scope": "superset", "mapping_scope_detail": "Returns all principals with weak supported Kerberos encryption types.", - "id": "RC4 or DES encryption type are supported by Domain Controllers", + "id": "f8af8921-8901-466e-ba91-df970a56cc21", "name": "RC4 or DES encryption type are supported by Domain Controllers" } ] From 338dde9f38c521418c68778d34b407dd93b8e4eb Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 16 Mar 2026 15:06:27 +0100 Subject: [PATCH 057/109] Map "SSO computer account with password last set over 90 days ago" --- docs/security-assessment-mapping.json | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index 408ea83..a1df71c 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -2045,6 +2045,17 @@ "name": "Change password for Microsoft Entra seamless SSO account" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "86e00792-a38f-409b-bd74-76e7ceecb93e", + "name": "SSO computer account with password last set over 90 days ago" + } + ] } ] }, From c4a3137e9f4a0bdf32213b35cc87f334d7d1b7fb Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 16 Mar 2026 15:06:39 +0100 Subject: [PATCH 058/109] Map "Shadow Credentials on privileged objects" --- docs/security-assessment-mapping.json | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index a1df71c..33f606f 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -3287,6 +3287,25 @@ ] } ] + }, + { + "bloodhound_query": { + "guid": "96e86fb9-4cd6-4df3-81a6-e36fd7a34614", + "name": "Principals with write Shadow Credentials on Tier Zero principals" + }, + "maps_to": [ + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "d6456fa7-456b-4cde-a8ef-9de5903d0419", + "name": "Shadow Credentials on privileged objects" + } + ] + } + ] } ] } \ No newline at end of file From bbe2f6a344e0b310dc372fcdf3cc173a964d7619 Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 16 Mar 2026 15:42:48 +0100 Subject: [PATCH 059/109] PK map "Non-privileged users with access to gMSA passwords" --- docs/security-assessment-mapping.json | 19 +++++++++++++++++++ ...incipals with access to gMSA passwords.yml | 16 ++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 queries/Non-Tier Zero principals with access to gMSA passwords.yml diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index 33f606f..a1ee991 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -3306,6 +3306,25 @@ ] } ] + }, + { + "bloodhound_query": { + "guid": "ef587ba1-a740-4bcf-b4e0-e1137d01b1af", + "name": "Non-Tier Zero principals with access to gMSA passwords" + }, + "maps_to": [ + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "superset", + "mapping_scope_detail": "Expanded scope to Tier Zero", + "id": "5962cacc-495f-4487-9770-2a87ee8fc50a", + "name": "Non-privileged users with access to gMSA passwords" + } + ] + } + ] } ] } \ No newline at end of file diff --git a/queries/Non-Tier Zero principals with access to gMSA passwords.yml b/queries/Non-Tier Zero principals with access to gMSA passwords.yml new file mode 100644 index 0000000..c22210b --- /dev/null +++ b/queries/Non-Tier Zero principals with access to gMSA passwords.yml @@ -0,0 +1,16 @@ +name: Non-Tier Zero principals with access to gMSA passwords +guid: ef587ba1-a740-4bcf-b4e0-e1137d01b1af +prebuilt: false +platforms: Active Directory +category: Dangerous Privileges +description: Finds non-Tier Zero principals that can read Group Managed Service Account (gMSA) passwords, or modify the msDS-GroupMSAMembership property which controls who can access the password. Unauthorized access to gMSA passwords allows an attacker to authenticate as the service account and access any resource it manages. +query: |- + MATCH p=(n:Base)-[r:ReadGMSAPassword|GenericAll|GenericWrite|WriteOwner|WriteDacl]->(m:User) + WHERE m.gmsa = true + // Exclude Tier Zero principals + AND NOT ((n:Tag_Tier_Zero) OR COALESCE(n.system_tags, '') CONTAINS 'admin_tier_0') + RETURN p +revision: 1 +resources: + - https://learn.microsoft.com/en-us/windows-server/security/group-managed-service-accounts/group-managed-service-accounts-overview +acknowledgements: Martin Sohn Christensen, @martinsohndk From 9b585ff5b6a4d0e591a7f8ed3a52a51a20bb5683 Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 16 Mar 2026 15:49:06 +0100 Subject: [PATCH 060/109] PK map "Primary users with SPN not supporting AES encryption on Kerberos" --- docs/security-assessment-mapping.json | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index a1ee991..5885caa 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -757,6 +757,17 @@ "name": "Unsecure account attributes: Enable Kerberos AES encryption support" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "superset", + "mapping_scope_detail": "BH query matches all base nodes with SPNs (users and computers), while PK targets only user accounts. BH also catches accounts with passwords predating Windows Server 2008 which PK does not.", + "id": "b608276e-3849-419d-bc34-6e5a362b3e79", + "name": "Primary users with SPN not supporting AES encryption on Kerberos" + } + ] } ] }, From 4f66642080fa9e5f446ea7d0a29876e79a1e2862 Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 16 Mar 2026 15:52:01 +0100 Subject: [PATCH 061/109] fix: PK map "Privileged users with SPN defined" --- .gitignore | 2 ++ docs/security-assessment-mapping.json | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 4d8bd57..14ba8c1 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,8 @@ .env .vscode test-report.md +/resources +/.claude # Byte-compiled / optimized / DLL files __pycache__/ diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index 5885caa..6864ab3 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -1018,8 +1018,8 @@ { "mapping_scope": "superset", "mapping_scope_detail": "Expanded scope to Tier Zero", - "id": "Privileged users with ServicePrincipalNames defined", - "name": "Privileged users with ServicePrincipalNames defined" + "id": "34d7d270-3b7b-4a0e-b0c4-15e8e2551c31", + "name": "Privileged users with SPN defined" } ] } From 6072905fdaa0ab635ff2f6b33477d14b07152210 Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 16 Mar 2026 15:53:22 +0100 Subject: [PATCH 062/109] PK map "User accounts that use DES encryption" --- docs/security-assessment-mapping.json | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index 6864ab3..13c9758 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -809,6 +809,17 @@ "name": "Unsecure account attributes: Remove Use Kerberos DES encryption types for this account" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "ca67406b-a063-4aba-ba61-261be7f9ee96", + "name": "User accounts that use DES encryption" + } + ] } ] }, From 0e7d11f7d645648e8dfbade12281e22a5e704ba0 Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 16 Mar 2026 15:54:47 +0100 Subject: [PATCH 063/109] Map PK "Users with Kerberos pre-authentication disabled" --- docs/security-assessment-mapping.json | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index 13c9758..c92b267 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -414,6 +414,17 @@ "name": "Unsecure account attributes: Remove Do not require Kerberos preauthentication" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "ad0f14a9-580c-4709-b8d2-c1be16b22a3e", + "name": "Users with Kerberos pre-authentication disabled" + } + ] } ] }, From 83781893013b5d01813650c0cb09823e757aee36 Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 16 Mar 2026 16:35:29 +0100 Subject: [PATCH 064/109] fix: PK map "Users with SPN defined" --- docs/security-assessment-mapping.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index c92b267..ad10477 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -1059,8 +1059,8 @@ { "mapping_scope": "exact", "mapping_scope_detail": "", - "id": "Users with ServicePrincipalName defined", - "name": "Users with ServicePrincipalName defined" + "id": "868c36af-b784-465b-a15c-291bd3c66d47", + "name": "Users with SPN defined" } ] } From e11ba6fd22843f30f42da625b546bd173ea0e68f Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 16 Mar 2026 16:38:57 +0100 Subject: [PATCH 065/109] Fix: PK map "krbtgt account with Resource-Based Constrained Delegation (RBCD) enabled" --- docs/security-assessment-mapping.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index ad10477..17596fb 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -3220,7 +3220,7 @@ "mapping_scope": "superset", "mapping_scope_detail": "Expanded technique scope to include all BloodHound traversable edges, instead of just the AddAllowedToAct edge. Expanded target scope to all KRBTGT account types.", "id": "eb99e786-3ce1-4172-9801-dd4203cfd3e2", - "name": "Write access to RBCD on krbtgt account" + "name": "krbtgt account with Resource-Based Constrained Delegation (RBCD) enabled" } ] } From b4dfb6632c79d716fed4fb2bceb4edafcdab25da Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 16 Mar 2026 17:12:29 +0100 Subject: [PATCH 066/109] Map PK "Global Administrators that signed in during the last 14 days" --- docs/security-assessment-mapping.json | 19 +++++++++++++++++++ ...istrators with recent sign-in activity.yml | 16 ++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 queries/Global Administrators with recent sign-in activity.yml diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index 17596fb..6c23713 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -3358,6 +3358,25 @@ ] } ] + }, + { + "bloodhound_query": { + "guid": "a9a78633-0cf1-4ab1-9152-dbfc8aab362f", + "name": "Global Administrators with recent sign-in activity" + }, + "maps_to": [ + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "superset", + "mapping_scope_detail": "BH includes PIM-eligible assignments (AZRoleEligible) which PK does not check via the roleAssignments API, so BH may surface additional principals PK would not flag.", + "id": "ca3c2e9e-40bd-4090-868b-db2446fac627", + "name": "Global Administrators that signed in during the last 14 days" + } + ] + } + ] } ] } \ No newline at end of file diff --git a/queries/Global Administrators with recent sign-in activity.yml b/queries/Global Administrators with recent sign-in activity.yml new file mode 100644 index 0000000..57ca32d --- /dev/null +++ b/queries/Global Administrators with recent sign-in activity.yml @@ -0,0 +1,16 @@ +name: Global Administrators with recent sign-in activity +guid: a9a78633-0cf1-4ab1-9152-dbfc8aab362f +prebuilt: false +platforms: Azure +category: Azure Hygiene +description: Entra ID principals assigned the Global Administrator role with sign-in activity in the past 14 days. Frequent use of the Global Administrator role indicates it is not being reserved for emergency access only. +query: |- + MATCH p=(m:AZBase)-[:AZHasRole|AZRoleEligible]->(n:AZRole) + WHERE n.objectid STARTS WITH '62E90394-69F5-4237-9190-012177145E10' // Global Administrator role + AND coalesce(m.lastsuccessfulsignindatetime, '') <> '' // sign-in data must be present + AND m.lastsuccessfulsignindatetime > date() - duration('P14D') // signed in within last 14 days + RETURN p +revision: 1 +resources: +acknowledgements: Martin Sohn Christensen, @martinsohndk + From 25090cf16249d01d8c75a8be49dcff1d9c32b780 Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 16 Mar 2026 17:23:24 +0100 Subject: [PATCH 067/109] Map PK "Guest accounts that were inactive for more than 30 days" --- docs/security-assessment-mapping.json | 19 +++++++++++++++++++ ...ra ID guest users inactive for 30 days.yml | 19 +++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 queries/Enabled Entra ID guest users inactive for 30 days.yml diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index 6c23713..6f98824 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -3377,6 +3377,25 @@ ] } ] + }, + { + "bloodhound_query": { + "guid": "5b7c14af-b044-4812-ae7f-898c17325a10", + "name": "Enabled Entra ID guest users inactive for 30 days" + }, + "maps_to": [ + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "b7f86499-830c-445d-bca9-4d289fc8dc89", + "name": "Guest accounts that were inactive for more than 30 days" + } + ] + } + ] } ] } \ No newline at end of file diff --git a/queries/Enabled Entra ID guest users inactive for 30 days.yml b/queries/Enabled Entra ID guest users inactive for 30 days.yml new file mode 100644 index 0000000..258ba38 --- /dev/null +++ b/queries/Enabled Entra ID guest users inactive for 30 days.yml @@ -0,0 +1,19 @@ +name: Enabled Entra ID guest users inactive for 30 days +guid: 5b7c14af-b044-4812-ae7f-898c17325a10 +prebuilt: false +platforms: Azure +category: Azure Hygiene +description: Entra ID guest (external) user accounts with no sign-in activity in the past 30 days, or that have never signed in. Stale guest accounts retain tenant access despite being unused and are a persistence risk if compromised. +query: |- + MATCH (n:AZUser) + WHERE n.userprincipalname CONTAINS '#EXT#' // Entra ID guest user (external UPN pattern) + AND n.enabled = true + AND ( + coalesce(n.lastsuccessfulsignindatetime, '') = '' // never signed in + OR n.lastsuccessfulsignindatetime < date() - duration('P30D') // no sign-in in 30+ days + ) + RETURN n +revision: 1 +resources: +acknowledgements: Martin Sohn Christensen, @martinsohndk + From 36c39d8b4bcb30682e81c91d33a50d75ec421922 Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 16 Mar 2026 17:30:28 +0100 Subject: [PATCH 068/109] Map PK "Less than 2 Global Administrators exist" --- docs/security-assessment-mapping.json | 19 +++++++++++++++++++ ...ith fewer than 2 Global Administrators.yml | 15 +++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 queries/Azure tenants with fewer than 2 Global Administrators.yml diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index 6f98824..0101a31 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -3396,6 +3396,25 @@ ] } ] + }, + { + "bloodhound_query": { + "guid": "10b363f9-09c0-4317-9040-083ebbbf8e71", + "name": "Azure tenants with fewer than 2 Global Administrators" + }, + "maps_to": [ + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "0ba04531-98ec-4cd3-a7fe-5bac5e99b454", + "name": "Less than 2 Global Administrators exist" + } + ] + } + ] } ] } \ No newline at end of file diff --git a/queries/Azure tenants with fewer than 2 Global Administrators.yml b/queries/Azure tenants with fewer than 2 Global Administrators.yml new file mode 100644 index 0000000..8583cb1 --- /dev/null +++ b/queries/Azure tenants with fewer than 2 Global Administrators.yml @@ -0,0 +1,15 @@ +name: Azure tenants with fewer than 2 Global Administrators +guid: 10b363f9-09c0-4317-9040-083ebbbf8e71 +prebuilt: false +platforms: Azure +category: Azure Hygiene +description: Global Administrator role with fewer than 2 assigned members, creating a single point of failure for Entra ID tenant administration and emergency access. +query: |- + MATCH (m:AZBase)-[:AZHasRole|AZRoleEligible]->(r:AZRole) + WHERE r.objectid STARTS WITH '62E90394-69F5-4237-9190-012177145E10' // Global Administrator role + WITH r, COUNT(m) AS gaCount + WHERE gaCount < 2 // fewer than 2 members assigned + RETURN r +revision: 1 +resources: https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/security-emergency-access +acknowledgements: Martin Sohn Christensen, @martinsohndk From 4e137fe8b691c18f527e4c3e44f7a27d621bb096 Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 16 Mar 2026 17:40:25 +0100 Subject: [PATCH 069/109] Map PK "More than 5 Global Administrators exist" --- docs/security-assessment-mapping.json | 19 +++++++++++++++++++ ...with more than 5 Global Administrators.yml | 15 +++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 queries/Azure tenants with more than 5 Global Administrators.yml diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index 0101a31..c4bf87e 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -3415,6 +3415,25 @@ ] } ] + }, + { + "bloodhound_query": { + "guid": "d527c445-aa1b-41cb-9a19-98cb24854528", + "name": "Azure tenants with more than 5 Global Administrators" + }, + "maps_to": [ + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "62386a3a-9d27-4969-af4e-a3528763ba59", + "name": "More than 5 Global Administrators exist" + } + ] + } + ] } ] } \ No newline at end of file diff --git a/queries/Azure tenants with more than 5 Global Administrators.yml b/queries/Azure tenants with more than 5 Global Administrators.yml new file mode 100644 index 0000000..f65e022 --- /dev/null +++ b/queries/Azure tenants with more than 5 Global Administrators.yml @@ -0,0 +1,15 @@ +name: Azure tenants with more than 5 Global Administrators +guid: d527c445-aa1b-41cb-9a19-98cb24854528 +prebuilt: false +platforms: Azure +category: Azure Hygiene +description: Global Administrator role with more than 5 assigned members, exceeding Microsoft's recommended maximum and unnecessarily expanding the blast radius of a tenant compromise. +query: |- + MATCH (m:AZBase)-[:AZHasRole|AZRoleEligible]->(r:AZRole) + WHERE r.objectid STARTS WITH '62E90394-69F5-4237-9190-012177145E10' // Global Administrator role + WITH r, COUNT(m) AS gaCount + WHERE gaCount > 5 // exceeds recommended maximum of 5 + RETURN r +revision: 1 +resources: https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/best-practices#5-limit-the-number-of-global-administrators-to-less-than-5 +acknowledgements: Martin Sohn Christensen, @martinsohndk From a8ef49aed58c3b836077137c95730fd87e4d0067 Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 16 Mar 2026 17:40:57 +0100 Subject: [PATCH 070/109] Map PK "More than 10 Privileged Administrators exist" --- docs/security-assessment-mapping.json | 19 +++++++++++++++++++ ... Azure roles with more than 10 members.yml | 15 +++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 queries/Tier Zero Azure roles with more than 10 members.yml diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index c4bf87e..ab97a4e 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -3434,6 +3434,25 @@ ] } ] + }, + { + "bloodhound_query": { + "guid": "04b34186-0a88-4b1c-90c1-2c4bea2adadd", + "name": "Tier Zero Azure roles with more than 10 members" + }, + "maps_to": [ + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "superset", + "mapping_scope_detail": "Expanded scope to Tier Zero", + "id": "c687bf2a-e2b6-4a8e-8651-c0f7c79a4338", + "name": "More than 10 Privileged Administrators exist" + } + ] + } + ] } ] } \ No newline at end of file diff --git a/queries/Tier Zero Azure roles with more than 10 members.yml b/queries/Tier Zero Azure roles with more than 10 members.yml new file mode 100644 index 0000000..c5f777c --- /dev/null +++ b/queries/Tier Zero Azure roles with more than 10 members.yml @@ -0,0 +1,15 @@ +name: Tier Zero Azure roles with more than 10 members +guid: 04b34186-0a88-4b1c-90c1-2c4bea2adadd +prebuilt: false +platforms: Azure +category: Azure Hygiene +description: Entra ID Tier Zero roles individually holding more than 10 assigned principals, indicating over-provisioning of highly privileged role membership. +query: |- + MATCH (m:AZBase)-[:AZHasRole|AZRoleEligible]->(r:AZRole) + WHERE (r:Tag_Tier_Zero) OR COALESCE(r.system_tags, '') CONTAINS 'admin_tier_0' // Tier Zero roles + WITH r, COUNT(m) AS memberCount + WHERE memberCount > 10 // more than 10 members assigned to a single Tier Zero role + RETURN r +revision: 1 +resources: https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/best-practices#6-limit-the-number-of-privileged-role-assignments-to-less-than-10 +acknowledgements: Martin Sohn Christensen, @martinsohndk From a854136c4202ef60d065b76ae1794e8db69c4060 Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 16 Mar 2026 17:48:28 +0100 Subject: [PATCH 071/109] Map PK "Privileged accounts with mailbox" --- docs/security-assessment-mapping.json | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index ab97a4e..356e077 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -1581,6 +1581,17 @@ "name": "[M]Check if administrator accounts are email enabled" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "superset", + "mapping_scope_detail": "Expanded scope to Tier Zero. Excluded false positives", + "id": "dbc58c9d-2df6-49cc-a732-ba370486211e", + "name": "Privileged accounts with mailbox" + } + ] } ] }, From d875807f41faf49cde8254febdd488aeddbe5665 Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 16 Mar 2026 17:49:56 +0100 Subject: [PATCH 072/109] Map PK "Privileged group contains guest account" --- docs/security-assessment-mapping.json | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index 356e077..e59464a 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -3464,6 +3464,25 @@ ] } ] + }, + { + "bloodhound_query": { + "guid": "20e07417-d286-4dca-a962-568f2b262f65", + "name": "Tier Zero / High Value external Entra ID users" + }, + "maps_to": [ + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "superset", + "mapping_scope_detail": "Expanded scope to Tier Zero", + "id": "5c8bd887-55ac-47d6-9835-6ed5370cda26", + "name": "Privileged group contains guest account" + } + ] + } + ] } ] } \ No newline at end of file From a0ab54d2f8a16e847f3af5121cb304e3427f56b8 Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 16 Mar 2026 18:16:31 +0100 Subject: [PATCH 073/109] Map PK "Suspicious Directory Synchronization Accounts role member" --- docs/security-assessment-mapping.json | 19 +++++++++++++++++++ ...nchronization Accounts role assignment.yml | 15 +++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 queries/Principals with Directory Synchronization Accounts role assignment.yml diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index e59464a..c9167a0 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -3483,6 +3483,25 @@ ] } ] + }, + { + "bloodhound_query": { + "guid": "c3f91e8b-4a72-4d5e-b6c1-9f3e2a8d7b04", + "name": "Principals with Directory Synchronization Accounts role assignment" + }, + "maps_to": [ + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "partial", + "mapping_scope_detail": "Query returns all principals with the role. PK automatically excludes expected Entra Connect service accounts (Sync_* and ADToAADSyncServiceAccount UPN patterns); manual review is required to identify unexpected assignments.", + "id": "0e9a08c9-9d01-4c87-9c01-5bad3b23a2dd", + "name": "Suspicious Directory Synchronization Accounts role member" + } + ] + } + ] } ] } \ No newline at end of file diff --git a/queries/Principals with Directory Synchronization Accounts role assignment.yml b/queries/Principals with Directory Synchronization Accounts role assignment.yml new file mode 100644 index 0000000..d1b6901 --- /dev/null +++ b/queries/Principals with Directory Synchronization Accounts role assignment.yml @@ -0,0 +1,15 @@ +name: Principals with Directory Synchronization Accounts role assignment +guid: c3f91e8b-4a72-4d5e-b6c1-9f3e2a8d7b04 +prebuilt: false +platforms: Azure +category: Dangerous Privileges +description: The Directory Synchronization Accounts role is reserved for Entra Connect service accounts. Any principal holding this role gains highly privileged directory synchronization capabilities that can be abused for credential access, privilege escalation, and persistence. +query: |- + MATCH p=(m:AZBase)-[:AZHasRole|AZRoleEligible]->(n:AZRole) + WHERE n.objectid STARTS WITH 'D29B2B05-8046-44BA-8758-1E26182FCF32' // Directory Synchronization Accounts role + RETURN p +revision: 1 +resources: +- https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/permissions-reference#directory-synchronization-accounts +- https://medium.com/tenable-techblog/stealthy-persistence-with-directory-synchronization-accounts-role-in-entra-id-63e56ce5871b +acknowledgements: Martin Sohn Christensen, @martinsohndk From c5fb9892d4387a471fd0393f4a3fe11c58aa7a10 Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 18 Mar 2026 13:14:22 +0100 Subject: [PATCH 074/109] Map PK "Unprivileged owner of a privileged group" --- docs/security-assessment-mapping.json | 20 +++++++++++++++++++ ... Zero owners of Tier Zero Entra groups.yml | 14 +++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 queries/Non-Tier Zero owners of Tier Zero Entra groups.yml diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index c9167a0..4112ef6 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -3502,6 +3502,26 @@ ] } ] + }, + { + "bloodhound_query": { + "guid": "f02f57b9-ac94-4954-a698-4584457962eb", + "name": "Non-Tier Zero owners of Tier Zero Entra groups" + }, + "maps_to": [ + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "superset", + "mapping_scope_detail": "Expanded scope to Tier Zero", + "id": "36e3878e-e65c-47a1-a9b8-0444ce978683", + "name": "Unprivileged owner of a privileged group" + } + ] + } + ] + ] } ] } \ No newline at end of file diff --git a/queries/Non-Tier Zero owners of Tier Zero Entra groups.yml b/queries/Non-Tier Zero owners of Tier Zero Entra groups.yml new file mode 100644 index 0000000..0070ef3 --- /dev/null +++ b/queries/Non-Tier Zero owners of Tier Zero Entra groups.yml @@ -0,0 +1,14 @@ +name: Non-Tier Zero owners of Tier Zero Entra groups +guid: f02f57b9-ac94-4954-a698-4584457962eb +prebuilt: false +platform: Azure +category: Dangerous Privileges +description: +query: |- + MATCH p=(n:AZBase)-[:AZOwns]->(g:AZGroup) + WHERE NOT ((n:Tag_Tier_Zero) OR COALESCE(n.system_tags, '') CONTAINS 'admin_tier_0') + AND ((g:Tag_Tier_Zero) OR COALESCE(g.system_tags, '') CONTAINS 'admin_tier_0') + RETURN p +revision: 1 +resources: +acknowledgements: Martin Sohn Christensen, @martinsohndk From 7ed423ddfc1f36f63d75dc99e5b758ee0a66fcd9 Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 18 Mar 2026 13:14:38 +0100 Subject: [PATCH 075/109] fix: PK "Suspicious Directory Synchronization Accounts role member" --- docs/security-assessment-mapping.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index 4112ef6..a561ea1 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -3495,7 +3495,7 @@ "controls": [ { "mapping_scope": "partial", - "mapping_scope_detail": "Query returns all principals with the role. PK automatically excludes expected Entra Connect service accounts (Sync_* and ADToAADSyncServiceAccount UPN patterns); manual review is required to identify unexpected assignments.", + "mapping_scope_detail": "Query returns all principals with the role; manual review is required to identify unexpected assignments.", "id": "0e9a08c9-9d01-4c87-9c01-5bad3b23a2dd", "name": "Suspicious Directory Synchronization Accounts role member" } From bd6f7ad2eec55ebf6196fcd4107187c775381f93 Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 18 Mar 2026 14:08:39 +0100 Subject: [PATCH 076/109] Update Enabled Entra ID guest users inactive for 30 days.yml --- ...abled Entra ID guest users inactive for 30 days.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/queries/Enabled Entra ID guest users inactive for 30 days.yml b/queries/Enabled Entra ID guest users inactive for 30 days.yml index 258ba38..4b5837e 100644 --- a/queries/Enabled Entra ID guest users inactive for 30 days.yml +++ b/queries/Enabled Entra ID guest users inactive for 30 days.yml @@ -9,8 +9,14 @@ query: |- WHERE n.userprincipalname CONTAINS '#EXT#' // Entra ID guest user (external UPN pattern) AND n.enabled = true AND ( - coalesce(n.lastsuccessfulsignindatetime, '') = '' // never signed in - OR n.lastsuccessfulsignindatetime < date() - duration('P30D') // no sign-in in 30+ days + // never signed in AND created more than 30+ days + ( + coalesce(n.lastsuccessfulsignindatetime, '') = '' + AND n.whencreated < tostring(datetime() - duration('P30D')) + ) + + // OR not signed-in for more than 30+ days + OR n.lastsuccessfulsignindatetime < tostring(datetime() - duration('P30D')) ) RETURN n revision: 1 From 3e4e678a67361c4d3351e4ec0c6dc9dac4b9313d Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 18 Mar 2026 14:08:57 +0100 Subject: [PATCH 077/109] PK map "Users or devices inactive for at least 90 days" --- docs/security-assessment-mapping.json | 18 +++++++++++++++ ...ed Entra ID users inactive for 90 days.yml | 23 +++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 queries/Enabled Entra ID users inactive for 90 days.yml diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index a561ea1..ffebb4e 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -3521,6 +3521,24 @@ ] } ] + }, + { + "bloodhound_query": { + "guid": "43481dea-6c9c-47f0-8a3a-0c012c1a811e", + "name": "Enabled Entra ID users inactive for 90 days" + }, + "maps_to": [ + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "partial", + "mapping_scope_detail": "PurpleKnight checks both users and devices inactive for 90 days and considers interactive and non-interactive sign-in activity; this query covers only Entra ID users using lastsuccessfulsignindatetime. Devices are not covered because BloodHound AZDevice nodes lack sign-in timestamp properties.", + "id": "47bfb163-2e5d-42c8-9218-0b1e9609d0f6", + "name": "Users or devices inactive for at least 90 days" + } + ] + } ] } ] diff --git a/queries/Enabled Entra ID users inactive for 90 days.yml b/queries/Enabled Entra ID users inactive for 90 days.yml new file mode 100644 index 0000000..c3ce07b --- /dev/null +++ b/queries/Enabled Entra ID users inactive for 90 days.yml @@ -0,0 +1,23 @@ +name: Enabled Entra ID users inactive for 90 days +guid: 43481dea-6c9c-47f0-8a3a-0c012c1a811e +prebuilt: false +platforms: Azure +category: Azure Hygiene +description: Enabled Entra ID user accounts with no sign-in activity in 90 days, or that have never signed in. Stale accounts expand the attack surface and may go unmonitored. +query: |- + MATCH (n:AZUser) + WHERE n.enabled = true + AND ( + // never signed in AND created more than 90+ days + ( + coalesce(n.lastsuccessfulsignindatetime, '') = '' + AND n.whencreated < tostring(datetime() - duration('P90D')) + ) + + // OR not signed-in for more than 90+ days + OR n.lastsuccessfulsignindatetime < tostring(datetime() - duration('P90D')) + ) + RETURN n +revision: 1 +resources: +acknowledgements: Martin Sohn Christensen, @martinsohndk From c7c7353be541f7783824c014cbbd566fe37ba301 Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 18 Mar 2026 14:11:01 +0100 Subject: [PATCH 078/109] Update security-assessment-mapping.json --- docs/security-assessment-mapping.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index ffebb4e..9502881 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -3533,7 +3533,7 @@ "controls": [ { "mapping_scope": "partial", - "mapping_scope_detail": "PurpleKnight checks both users and devices inactive for 90 days and considers interactive and non-interactive sign-in activity; this query covers only Entra ID users using lastsuccessfulsignindatetime. Devices are not covered because BloodHound AZDevice nodes lack sign-in timestamp properties.", + "mapping_scope_detail": "PurpleKnight checks both users and devices; the query covers only users.", "id": "47bfb163-2e5d-42c8-9218-0b1e9609d0f6", "name": "Users or devices inactive for at least 90 days" } From 332958a6de2e26ffd954e9eec4483ebb3b0186e8 Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 18 Mar 2026 14:36:39 +0100 Subject: [PATCH 079/109] Update Tier Zero accounts that can be delegated.yml --- queries/Tier Zero accounts that can be delegated.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/queries/Tier Zero accounts that can be delegated.yml b/queries/Tier Zero accounts that can be delegated.yml index f9f56bb..e0aa1dc 100644 --- a/queries/Tier Zero accounts that can be delegated.yml +++ b/queries/Tier Zero accounts that can be delegated.yml @@ -3,7 +3,7 @@ guid: 4316eaf1-6af0-4879-8f55-ac2633a711c3 prebuilt: false platforms: Active Directory category: Kerberos Interaction -description: +description: Delegation protection is achieved by either enabling "Account is sensitive and cannot be delegated" or making it a member of the Protected Users security group. query: |- MATCH (m:Base) WHERE ((m:Tag_Tier_Zero) OR COALESCE(m.system_tags, '') CONTAINS 'admin_tier_0') @@ -14,7 +14,7 @@ query: |- WITH m, COLLECT(n) AS matchingNs WHERE NONE(n IN matchingNs WHERE n.objectid = m.objectid) RETURN m -revision: 1 +revision: 2 resources: acknowledgements: Martin Sohn Christensen, @martinsohndk From f49281ec933cb8cc6f6231d4e09501f3c375e84c Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 18 Mar 2026 15:23:21 +0100 Subject: [PATCH 080/109] Map PK --- docs/security-assessment-mapping.json | 103 ++++++++++++++++++ ... resource-based constrained delegation.yml | 13 +++ ...d or prohibited Entra role assignments.yml | 20 ++++ ...s with outbound constrained delegation.yml | 12 ++ 4 files changed, 148 insertions(+) create mode 100644 queries/Non-Tier Zero computers with inbound resource-based constrained delegation.yml create mode 100644 queries/Principals with deprecated or prohibited Entra role assignments.yml create mode 100644 queries/Principals with outbound constrained delegation.yml diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index 9502881..aaf0ec0 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -1715,6 +1715,23 @@ "name": "Unsecure Kerberos delegation" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "superset", + "mapping_scope_detail": "BH query covers all Tier Zero computers, not just Domain Controllers. BloodHound cannot distinguish Kerberos-only constrained delegation from protocol transition.", + "id": "275b22b7-6386-46b8-a1bd-3f14965bf643", + "name": "Principals with constrained authentication delegation enabled for a DC service" + }, + { + "mapping_scope": "partial", + "mapping_scope_detail": "BH query covers all Tier Zero computers, not just Domain Controllers. BloodHound cannot distinguish protocol transition from Kerberos-only constrained delegation.", + "id": "31dcc5f6-ceb0-4132-a698-95bae64fe7df", + "name": "Principals with constrained delegation using protocol transition enabled for a DC service" + } + ] } ] }, @@ -1745,6 +1762,29 @@ "name": "Unsecure Kerberos delegation" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "superset", + "mapping_scope_detail": "BH query covers all Tier Zero computers with inbound RBCD, not just Domain Controllers", + "id": "6df6c2ee-a77b-411b-8540-4357e5d0a1fb", + "name": "Domain Controllers with Resource-Based Constrained Delegation (RBCD) enabled" + }, + { + "mapping_scope": "superset", + "mapping_scope_detail": "BH query covers RBCD on all Tier Zero computers including AZUREADSSOACC; PK control is limited to only the AZUREADSSOACC account", + "id": "3d6ff5b6-8d01-46b9-a261-7f5ab0c3b306", + "name": "Resource Based Constrained Delegation applied to AZUREADSSOACC account" + }, + { + "mapping_scope": "combination", + "mapping_scope_detail": "Combined with '5ba7ad73-b6a3-4f18-8818-74e2088beb39' to cover all computers with RBCD.", + "id": "88040bb0-ce23-48a8-850f-277edaafbac7", + "name": "Computer account takeover through Kerberos Resource-Based Constrained Delegation (RBCD)" + } + ] } ] }, @@ -3403,6 +3443,12 @@ "mapping_scope_detail": "", "id": "b7f86499-830c-445d-bca9-4d289fc8dc89", "name": "Guest accounts that were inactive for more than 30 days" + }, + { + "mapping_scope": "partial", + "mapping_scope_detail": "BloodHound does not ingest the externalUserState property, so it cannot directly identify unredeemed guest invitations. Instead, the query surfaces guests created over 30 days ago who have never signed in, which is a reasonable proxy but also includes guests who redeemed their invite but never used it.", + "id": "4c4029ed-2fdd-46c8-8d96-eebefd2d9799", + "name": "Guest invites not accepted in last 30 day" } ] } @@ -3540,6 +3586,63 @@ ] } ] + }, + { + "bloodhound_query": { + "guid": "5ba7ad73-b6a3-4f18-8818-74e2088beb39", + "name": "Non-Tier Zero computers with inbound resource-based constrained delegation" + }, + "maps_to": [ + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "combination", + "mapping_scope_detail": "Combined with '4dc97cf4-3c03-4fe6-8a8b-4f665c67e1e5' to cover all computers with RBCD.", + "id": "88040bb0-ce23-48a8-850f-277edaafbac7", + "name": "Computer account takeover through Kerberos Resource-Based Constrained Delegation (RBCD)" + } + ] + } + ] + }, + { + "bloodhound_query": { + "guid": "e569db13-ef1d-4b03-b3a2-5ee8efc8b283", + "name": "Principals with outbound constrained delegation" + }, + "maps_to": [ + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "partial", + "mapping_scope_detail": "BloodHound's AllowedToDelegate edge does not distinguish between constrained delegation with or without protocol transition. The BH query returns both variants, while the PK control specifically excludes objects with protocol transition enabled.", + "id": "b332f034-f6b9-4bc2-8796-6ea044db909f", + "name": "Objects with constrained delegation configured" + } + ] + } + ] + }, + { + "bloodhound_query": { + "guid": "adca90da-4298-4d05-92e0-2fc22676cd05", + "name": "Principals with deprecated or prohibited Entra role assignments" + }, + "maps_to": [ + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "partial", + "mapping_scope_detail": "The PK control resolves group transitive membership to individual principals, while the BH query traverses one hop via AZMemberOf.", + "id": "e231380a-a77e-481f-ab03-6310b7b5cdfa", + "name": "Prohibited Entra ID Roles Assigned" + } + ] + } + ] } ] } \ No newline at end of file diff --git a/queries/Non-Tier Zero computers with inbound resource-based constrained delegation.yml b/queries/Non-Tier Zero computers with inbound resource-based constrained delegation.yml new file mode 100644 index 0000000..6669501 --- /dev/null +++ b/queries/Non-Tier Zero computers with inbound resource-based constrained delegation.yml @@ -0,0 +1,13 @@ +name: Non-Tier Zero computers with inbound resource-based constrained delegation +guid: 5ba7ad73-b6a3-4f18-8818-74e2088beb39 +prebuilt: false +platforms: Active Directory +category: Dangerous Privileges +description: Computers outside of Tier Zero where another principal has been granted the ability to impersonate users via resource-based constrained delegation (RBCD). +query: |- + MATCH p = (m:Base)-[:AllowedToAct]->(n:Computer) + WHERE NOT ((n:Tag_Tier_Zero) OR COALESCE(n.system_tags, '') CONTAINS 'admin_tier_0') + RETURN p +revision: 1 +resources: https://learn.microsoft.com/en-us/windows-server/security/kerberos/kerberos-constrained-delegation-overview +acknowledgements: Martin Sohn Christensen, @martinsohndk diff --git a/queries/Principals with deprecated or prohibited Entra role assignments.yml b/queries/Principals with deprecated or prohibited Entra role assignments.yml new file mode 100644 index 0000000..91e1da7 --- /dev/null +++ b/queries/Principals with deprecated or prohibited Entra role assignments.yml @@ -0,0 +1,20 @@ +name: Principals with deprecated or prohibited Entra role assignments +guid: adca90da-4298-4d05-92e0-2fc22676cd05 +prebuilt: false +platform: Azure +category: Azure Hygiene +description: Identifies principals assigned to Entra ID roles that are deprecated or should never be used, indicating stale or risky role assignments. +query: |- + MATCH p = (m:AZBase)-[:AZHasRole|AZRoleEligible|AZMemberOf*1..2]->(r:AZRole) + WHERE r.roledefinitionid =~ '(?i)9C094953-4995-41C8-84C8-3EBB9B32C93F' // Deprecated: Device Join + OR r.roledefinitionid =~ '(?i)9F06204D-73C1-4D4C-880A-6EDB90606FD8' // Deprecated: Azure AD Joined Device Local Administrator + OR r.roledefinitionid =~ '(?i)2B499BCD-DA44-4968-8AEC-78E1674FA64D' // Deprecated: Device Managers + OR r.roledefinitionid =~ '(?i)D405C6DF-0AF8-4E3B-95E4-4D06E542189E' // Deprecated: Device Users + OR r.roledefinitionid =~ '(?i)C34F683F-4D5A-4403-AFFD-6615E00E3A7F' // Deprecated: Workplace Device Join + OR r.roledefinitionid =~ '(?i)4BA39CA4-527C-499A-B93D-D9B492C50246' // Prohibited: Partner Tier1 Support + OR r.roledefinitionid =~ '(?i)E00E864A-17C5-4A4B-9C06-F5B95A8D5BD8' // Prohibited: Partner Tier2 Support + RETURN p +revision: 1 +resources: +- https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/permissions-reference#deprecated-roles +acknowledgements: Martin Sohn Christensen, @martinsohndk diff --git a/queries/Principals with outbound constrained delegation.yml b/queries/Principals with outbound constrained delegation.yml new file mode 100644 index 0000000..9ee9f65 --- /dev/null +++ b/queries/Principals with outbound constrained delegation.yml @@ -0,0 +1,12 @@ +name: Principals with outbound constrained delegation +guid: e569db13-ef1d-4b03-b3a2-5ee8efc8b283 +prebuilt: false +platforms: Active Directory +category: Dangerous Privileges +description: Enumerates all principals that have constrained delegation configured, indicating outbound AllowedToDelegate edges to one or more targets. +query: |- + MATCH p = (n:Base)-[:AllowedToDelegate]->(m:Base) + RETURN p +revision: 1 +resources: https://learn.microsoft.com/en-us/windows-server/security/kerberos/kerberos-constrained-delegation-overview +acknowledgements: Martin Sohn Christensen, @martinsohndk From 1866455dc1635c529e51832fa918f61cf23558de Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 18 Mar 2026 15:39:40 +0100 Subject: [PATCH 081/109] added resource links --- ...ynced to Entra Users with Entra Admin Roles (direct).yml | 6 +++--- ...Entra Users with Entra Admin Roles (group delegated).yml | 6 +++--- ... Entra Users with Entra Admin Role approval (direct).yml | 4 ++-- ...ers with Entra Admin Role approval (group delegated).yml | 4 ++-- ...Entra Users with Entra Admin Role direct eligibility.yml | 4 ++-- ...s with Entra Admin Roles group delegated eligibility.yml | 4 ++-- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/queries/On-Prem Users synced to Entra Users with Entra Admin Roles (direct).yml b/queries/On-Prem Users synced to Entra Users with Entra Admin Roles (direct).yml index 8cdbad2..7a08e40 100644 --- a/queries/On-Prem Users synced to Entra Users with Entra Admin Roles (direct).yml +++ b/queries/On-Prem Users synced to Entra Users with Entra Admin Roles (direct).yml @@ -10,7 +10,7 @@ query: |- MATCH p = (:User)-[:SyncedToEntraUser]->(:AZUser)-[:AZHasRole]->(:AZRole) RETURN p LIMIT 1000 -revision: 1 -resources: -acknowledgements: +revision: 2 +resources: https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/best-practices#9-use-cloud-native-accounts-for-microsoft-entra-roles +acknowledgements: diff --git a/queries/On-Prem Users synced to Entra Users with Entra Admin Roles (group delegated).yml b/queries/On-Prem Users synced to Entra Users with Entra Admin Roles (group delegated).yml index 56fbeee..7f8a454 100644 --- a/queries/On-Prem Users synced to Entra Users with Entra Admin Roles (group delegated).yml +++ b/queries/On-Prem Users synced to Entra Users with Entra Admin Roles (group delegated).yml @@ -10,7 +10,7 @@ query: |- MATCH p = (:User)-[:SyncedToEntraUser]->(:AZUser)-[:AZMemberOf]->(:AZGroup)-[:AZHasRole]->(:AZRole) RETURN p LIMIT 1000 -revision: 1 -resources: -acknowledgements: +revision: 2 +resources: https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/best-practices#9-use-cloud-native-accounts-for-microsoft-entra-roles +acknowledgements: diff --git a/queries/Synced Entra Users with Entra Admin Role approval (direct).yml b/queries/Synced Entra Users with Entra Admin Role approval (direct).yml index 9363770..39368d7 100644 --- a/queries/Synced Entra Users with Entra Admin Role approval (direct).yml +++ b/queries/Synced Entra Users with Entra Admin Role approval (direct).yml @@ -9,6 +9,6 @@ description: query: |- MATCH p = (:User)-[:SyncedToEntraUser]->(:AZUser)-[:AZRoleApprover]->(:AZRole) RETURN p LIMIT 100 -revision: 1 -resources: +revision: 2 +resources: https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/best-practices#9-use-cloud-native-accounts-for-microsoft-entra-roles acknowledgements: diff --git a/queries/Synced Entra Users with Entra Admin Role approval (group delegated).yml b/queries/Synced Entra Users with Entra Admin Role approval (group delegated).yml index ab42435..26c0d84 100644 --- a/queries/Synced Entra Users with Entra Admin Role approval (group delegated).yml +++ b/queries/Synced Entra Users with Entra Admin Role approval (group delegated).yml @@ -9,6 +9,6 @@ description: query: |- MATCH p = (:User)-[:SyncedToEntraUser]->(:AZUser)-[:AZMemberOf]->(:AZGroup)-[:AZRoleApprover]->(:AZRole) RETURN p LIMIT 100 -revision: 1 -resources: +revision: 2 +resources: https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/best-practices#9-use-cloud-native-accounts-for-microsoft-entra-roles acknowledgements: diff --git a/queries/Synced Entra Users with Entra Admin Role direct eligibility.yml b/queries/Synced Entra Users with Entra Admin Role direct eligibility.yml index db1d12a..6fe9a6e 100644 --- a/queries/Synced Entra Users with Entra Admin Role direct eligibility.yml +++ b/queries/Synced Entra Users with Entra Admin Role direct eligibility.yml @@ -9,6 +9,6 @@ description: query: |- MATCH p = (:User)-[:SyncedToEntraUser]->(:AZUser)-[:AZRoleEligible]->(:AZRole) RETURN p LIMIT 100 -revision: 1 -resources: +revision: 2 +resources: https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/best-practices#9-use-cloud-native-accounts-for-microsoft-entra-roles acknowledgements: diff --git a/queries/Synced Entra Users with Entra Admin Roles group delegated eligibility.yml b/queries/Synced Entra Users with Entra Admin Roles group delegated eligibility.yml index 00b51b1..55915fb 100644 --- a/queries/Synced Entra Users with Entra Admin Roles group delegated eligibility.yml +++ b/queries/Synced Entra Users with Entra Admin Roles group delegated eligibility.yml @@ -9,6 +9,6 @@ description: query: |- MATCH p = (:User)-[:SyncedToEntraUser]->(:AZUser)-[:AZMemberOf]->(:AZGroup)-[:AZRoleEligible]->(:AZRole) RETURN p LIMIT 100 -revision: 1 -resources: +revision: 2 +resources: https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/best-practices#9-use-cloud-native-accounts-for-microsoft-entra-roles acknowledgements: From 8768c471c347db71274c918d402d909d8a758d03 Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 18 Mar 2026 15:39:55 +0100 Subject: [PATCH 082/109] Create Enabled Entra Tier Zero principals inactive for 60 days.yml --- ...r Zero principals inactive for 60 days.yml | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 queries/Enabled Entra Tier Zero principals inactive for 60 days.yml diff --git a/queries/Enabled Entra Tier Zero principals inactive for 60 days.yml b/queries/Enabled Entra Tier Zero principals inactive for 60 days.yml new file mode 100644 index 0000000..2f14e18 --- /dev/null +++ b/queries/Enabled Entra Tier Zero principals inactive for 60 days.yml @@ -0,0 +1,24 @@ +name: Enabled Entra Tier Zero principals inactive for 60 days +guid: 08c20218-f4bc-49be-bad7-498b1053b302 +prebuilt: false +platforms: Azure +category: Azure Hygiene +description: Enabled Entra ID Tier Zero principals with no sign-in activity in 60 days, or that have never signed in. Stale privileged accounts should be reviewed and removed to reduce the attack surface. +query: |- + MATCH (n:AZBase) + WHERE ((n:Tag_Tier_Zero) OR COALESCE(n.system_tags, '') CONTAINS 'admin_tier_0') + AND n.enabled = true + AND ( + // never signed in AND created more than 60+ days + ( + coalesce(n.lastsuccessfulsignindatetime, '') = '' + AND n.whencreated < tostring(datetime() - duration('P60D')) + ) + + // OR not signed-in for more than 60+ days + OR n.lastsuccessfulsignindatetime < tostring(datetime() - duration('P60D')) + ) + RETURN n +revision: 1 +resources: https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/best-practices#4-configure-recurring-access-reviews-to-revoke-unneeded-permissions-over-time +acknowledgements: Martin Sohn Christensen, @martinsohndk From ca6533b5272f1ac33b50e10991a9d8fddb6116d5 Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 18 Mar 2026 15:55:28 +0100 Subject: [PATCH 083/109] Add Nessus IDs --- docs/security-assessment-mapping.json | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index aaf0ec0..07594da 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -248,7 +248,7 @@ { "mapping_scope": "combination", "mapping_scope_detail": "", - "id": "Primary Group ID integrity", + "id": "https://www.tenable.com/plugins/nessus/150487", "name": "Primary Group ID integrity" } ] @@ -300,7 +300,7 @@ { "mapping_scope": "combination", "mapping_scope_detail": "", - "id": "Primary Group ID integrity", + "id": "https://www.tenable.com/plugins/nessus/150487", "name": "Primary Group ID integrity" } ] @@ -399,7 +399,7 @@ { "mapping_scope": "exact", "mapping_scope_detail": "", - "id": "Kerberos pre-authentication validation", + "id": "https://www.tenable.com/plugins/nessus/150482", "name": "Kerberos pre-authentication validation" } ] @@ -470,7 +470,7 @@ { "mapping_scope": "exact", "mapping_scope_detail": "", - "id": "Blank passwords", + "id": "https://www.tenable.com/plugins/nessus/150489", "name": "Blank passwords" } ] @@ -522,7 +522,7 @@ { "mapping_scope": "exact", "mapping_scope_detail": "", - "id": "Non-expiring account password", + "id": "https://www.tenable.com/plugins/nessus/150483", "name": "Non-expiring account password" } ] @@ -805,7 +805,7 @@ { "mapping_scope": "combination", "mapping_scope_detail": "", - "id": "Weak Kerberos encryption", + "id": "https://www.tenable.com/plugins/nessus/150481", "name": "Weak Kerberos encryption" } ] @@ -857,7 +857,7 @@ { "mapping_scope": "combination", "mapping_scope_detail": "", - "id": "Weak Kerberos encryption", + "id": "https://www.tenable.com/plugins/nessus/150481", "name": "Weak Kerberos encryption" } ] @@ -1018,7 +1018,7 @@ { "mapping_scope": "superset", "mapping_scope_detail": "Expanded scope to Tier Zero", - "id": "Kerberoasting", + "id": "https://www.tenable.com/plugins/nessus/150480", "name": "Kerberoasting" } ] @@ -1271,7 +1271,7 @@ { "bloodhound_query": { "guid": "99d29ded-223a-442b-a0e0-f8b5694c6441", - "name": "Tier Zero computers not owned by Tier Zero" + "name": "Tier Zero principals not owned by Tier Zero" }, "maps_to": [ { @@ -1811,7 +1811,7 @@ { "mapping_scope": "superset", "mapping_scope_detail": "Expanded exclusion scope to Tier Zero", - "id": "Unconstrained delegation", + "id": "https://www.tenable.com/plugins/nessus/150485", "name": "Unconstrained delegation" } ] @@ -1939,7 +1939,7 @@ { "mapping_scope": "exact", "mapping_scope_detail": "", - "id": "Dangerous trust relationship", + "id": "https://www.tenable.com/plugins/nessus/150486", "name": "Dangerous trust relationship" } ] @@ -2379,7 +2379,7 @@ { "mapping_scope": "superset", "mapping_scope_detail": "Expanded scope to include Entra ID KRBTGT accounts", - "id": "Kerberos KRBTGT", + "id": "https://www.tenable.com/plugins/nessus/150484", "name": "Kerberos KRBTGT" } ] @@ -2794,7 +2794,7 @@ { "mapping_scope": "exact", "mapping_scope_detail": "", - "id": "Null sessions", + "id": "https://www.tenable.com/plugins/nessus/150488", "name": "Null sessions" } ] From 242793e978a0a9c413eb5c16b3d283d73862fca6 Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 18 Mar 2026 15:55:58 +0100 Subject: [PATCH 084/109] Add PurpleKnight to assessment tool .md --- docs/security-assessment-mapping.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/security-assessment-mapping.md b/docs/security-assessment-mapping.md index 733b366..8f659a4 100644 --- a/docs/security-assessment-mapping.md +++ b/docs/security-assessment-mapping.md @@ -14,7 +14,7 @@ The following show which other security tools the mapping supports and the numbe | Security Tool | Total Controls | Mapped Controls | Coverage | |---------------|-------------------|---------------|----------| | [Netwrix PingCastle](https://www.pingcastle.com/PingCastleFiles/ad_hc_rules_list.html) | 186 | 105 | 56% | -| [Semperis PurpleKnight](https://www.semperis.com/purple-knight/security-indicators/) | # | # | #% | +| [Semperis PurpleKnight](https://www.semperis.com/purple-knight/security-indicators/) | 187 | 93 | 50% | | [Microsoft Defender for Identity: Security Posture Assessment](https://learn.microsoft.com/en-us/defender-for-identity/security-assessment) | 45 | 35 | 78% | | [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% | From b2024a29fda6076eeb0daf03d0de0fe0d87f0069 Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 18 Mar 2026 16:22:28 +0100 Subject: [PATCH 085/109] Map PK "GPO linking delegation at the domain level" --- docs/security-assessment-mapping.json | 25 +++++++++++++++++++ ...link control over Tier Zero containers.yml | 17 +++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 queries/Non-Tier Zero principals with GPO link control over Tier Zero containers.yml diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index 07594da..f7aa6bf 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -3643,6 +3643,31 @@ ] } ] + }, + { + "bloodhound_query": { + "guid": "41a9df23-99ab-40dd-a965-2ea89db96823", + "name": "Non-Tier Zero principals with GPO link control over Tier Zero containers" + }, + "maps_to": [ + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "superset", + "mapping_scope_detail": "BH query covers all Tier Zero containers rather than only the DC OU, and uses BloodHound Tier Zero tagging instead of a fixed SID exclusion list.", + "id": "154c93b1-02fc-435b-88b4-a5e3b0467f85", + "name": "GPO linking delegation at the domain controller OU level" + }, + { + "mapping_scope": "superset", + "mapping_scope_detail": "BH query covers all Tier Zero containers rather than only the domain head, and uses BloodHound Tier Zero tagging instead of a fixed SID exclusion list.", + "id": "2cfda02d-2ac4-4d4d-bc9c-bff9c51024d0", + "name": "GPO linking delegation at the domain level" + } + ] + } + ] } ] } \ No newline at end of file diff --git a/queries/Non-Tier Zero principals with GPO link control over Tier Zero containers.yml b/queries/Non-Tier Zero principals with GPO link control over Tier Zero containers.yml new file mode 100644 index 0000000..b32a204 --- /dev/null +++ b/queries/Non-Tier Zero principals with GPO link control over Tier Zero containers.yml @@ -0,0 +1,17 @@ +name: Non-Tier Zero principals with GPO link control over Tier Zero containers +guid: 41a9df23-99ab-40dd-a965-2ea89db96823 +prebuilt: false +platforms: Active Directory +category: Active Directory Hygiene +description: Non-Tier Zero principals that can link, or gain the ability to link, GPOs on Tier Zero containers such as the domain head or Domain Controllers OU. This grants indirect control over all objects within those containers. +query: |- + MATCH p = (n:Base)-[r:WriteGPLink|GenericAll|GenericWrite|WriteDacl|WriteOwner|Owns]->(t:Base) + WHERE (t:Domain OR t:OU) + AND ((t:Tag_Tier_Zero) OR COALESCE(t.system_tags, '') CONTAINS 'admin_tier_0') + AND NOT ((n:Tag_Tier_Zero) OR COALESCE(n.system_tags, '') CONTAINS 'admin_tier_0') + RETURN p + LIMIT 1000 +revision: 1 +resources: + - https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/plan/security-best-practices/best-practices-for-securing-active-directory#avoid-granting-excessive-privileges +acknowledgements: Martin Sohn Christensen, @martinsohndk From a3f025272cf4b285ff7c635ed3b85e9913d69262 Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 18 Mar 2026 16:50:04 +0100 Subject: [PATCH 086/109] Map PK "Changes to MS LAPS read permissions" --- docs/security-assessment-mapping.json | 19 ++++++++++++++++++ ...ero users that can read LAPS passwords.yml | 20 +++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 queries/Non-Tier Zero users that can read LAPS passwords.yml diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index f7aa6bf..23f5349 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -3668,6 +3668,25 @@ ] } ] + }, + { + "bloodhound_query": { + "guid": "d1779573-7ad5-4a78-a5e5-9dd53a1eb1a1", + "name": "Non-Tier Zero users that can read LAPS passwords" + }, + "maps_to": [ + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "superset", + "mapping_scope_detail": "BloodHound uses Tier Zero tagging for exclusions rather than the fixed SID list (SYSTEM, Administrators, Domain Admins, Enterprise Admins). BloodHound also includes SyncLAPSPassword.", + "id": "9f532969-6a43-40a5-9035-f4f2e9cf9e88", + "name": "Changes to MS LAPS read permissions" + } + ] + } + ] } ] } \ No newline at end of file diff --git a/queries/Non-Tier Zero users that can read LAPS passwords.yml b/queries/Non-Tier Zero users that can read LAPS passwords.yml new file mode 100644 index 0000000..8e704bf --- /dev/null +++ b/queries/Non-Tier Zero users that can read LAPS passwords.yml @@ -0,0 +1,20 @@ +name: Non-Tier Zero users that can read LAPS passwords +guid: d1779573-7ad5-4a78-a5e5-9dd53a1eb1a1 +prebuilt: false +platform: Active Directory +category: Dangerous Privileges +description: Non-Tier Zero principals with direct LAPS password read capability present a lateral movement risk through local administrator credential exposure. +query: |- + MATCH p=(n)-[:ReadLAPSPassword|AllExtendedRights|GenericAll|SyncLAPSPassword]->(c:Computer) + WHERE c.haslaps = true + AND NOT ((n:Tag_Tier_Zero) OR COALESCE(n.system_tags, '') CONTAINS 'admin_tier_0') + AND NOT n.objectid ENDS WITH '-512' // Domain Admins + AND NOT n.objectid ENDS WITH '-519' // Enterprise Admins + AND NOT n.objectid ENDS WITH 'S-1-5-32-544' // Builtin Administrators + AND NOT n.objectid ENDS WITH 'S-1-5-18' // Local System + RETURN p + LIMIT 1000 +revision: 1 +resources: + - https://learn.microsoft.com/en-us/windows-server/identity/laps/laps-overview +acknowledgements: Martin Sohn Christensen, @martinsohndk From 33f007b7bc1bc1e7eebbc26656f71c074b7bb20f Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 18 Mar 2026 16:50:53 +0100 Subject: [PATCH 087/109] Update security-assessment-mapping.md --- docs/security-assessment-mapping.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/security-assessment-mapping.md b/docs/security-assessment-mapping.md index 8f659a4..70c3e30 100644 --- a/docs/security-assessment-mapping.md +++ b/docs/security-assessment-mapping.md @@ -14,7 +14,7 @@ The following show which other security tools the mapping supports and the numbe | Security Tool | Total Controls | Mapped Controls | Coverage | |---------------|-------------------|---------------|----------| | [Netwrix PingCastle](https://www.pingcastle.com/PingCastleFiles/ad_hc_rules_list.html) | 186 | 105 | 56% | -| [Semperis PurpleKnight](https://www.semperis.com/purple-knight/security-indicators/) | 187 | 93 | 50% | +| [Semperis PurpleKnight](https://www.semperis.com/purple-knight/security-indicators/) | 187 | 96 | 51% | | [Microsoft Defender for Identity: Security Posture Assessment](https://learn.microsoft.com/en-us/defender-for-identity/security-assessment) | 45 | 35 | 78% | | [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% | From 843e5585e142228d47385c556319fceb6c6dcbed Mon Sep 17 00:00:00 2001 From: Martin Date: Fri, 20 Mar 2026 20:00:42 +0100 Subject: [PATCH 088/109] Update Non-Tier Zero users that can read LAPS passwords.yml --- .../Non-Tier Zero users that can read LAPS passwords.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/queries/Non-Tier Zero users that can read LAPS passwords.yml b/queries/Non-Tier Zero users that can read LAPS passwords.yml index 8e704bf..33ced3a 100644 --- a/queries/Non-Tier Zero users that can read LAPS passwords.yml +++ b/queries/Non-Tier Zero users that can read LAPS passwords.yml @@ -5,13 +5,10 @@ platform: Active Directory category: Dangerous Privileges description: Non-Tier Zero principals with direct LAPS password read capability present a lateral movement risk through local administrator credential exposure. query: |- - MATCH p=(n)-[:ReadLAPSPassword|AllExtendedRights|GenericAll|SyncLAPSPassword]->(c:Computer) + MATCH p=(n:Base)-[:ReadLAPSPassword|AllExtendedRights|GenericAll|SyncLAPSPassword]->(c:Computer) WHERE c.haslaps = true + AND c.enabled = true AND NOT ((n:Tag_Tier_Zero) OR COALESCE(n.system_tags, '') CONTAINS 'admin_tier_0') - AND NOT n.objectid ENDS WITH '-512' // Domain Admins - AND NOT n.objectid ENDS WITH '-519' // Enterprise Admins - AND NOT n.objectid ENDS WITH 'S-1-5-32-544' // Builtin Administrators - AND NOT n.objectid ENDS WITH 'S-1-5-18' // Local System RETURN p LIMIT 1000 revision: 1 From 7f0ab0a8afe77c7df80827022ef4391189b48b31 Mon Sep 17 00:00:00 2001 From: Martin Date: Fri, 20 Mar 2026 20:03:46 +0100 Subject: [PATCH 089/109] Update security-assessment-mapping.json --- docs/security-assessment-mapping.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index 23f5349..98182f9 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -3680,7 +3680,7 @@ "controls": [ { "mapping_scope": "superset", - "mapping_scope_detail": "BloodHound uses Tier Zero tagging for exclusions rather than the fixed SID list (SYSTEM, Administrators, Domain Admins, Enterprise Admins). BloodHound also includes SyncLAPSPassword.", + "mapping_scope_detail": "Expanded scope to Tier Zero. Includes SyncLAPSPassword", "id": "9f532969-6a43-40a5-9035-f4f2e9cf9e88", "name": "Changes to MS LAPS read permissions" } From 50a9ce7c387d37ad0a89b9ba5445518770c28cd8 Mon Sep 17 00:00:00 2001 From: Martin Date: Fri, 20 Mar 2026 21:18:09 +0100 Subject: [PATCH 090/109] Update security-assessment-mapping.md --- docs/security-assessment-mapping.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/security-assessment-mapping.md b/docs/security-assessment-mapping.md index 70c3e30..5829bfa 100644 --- a/docs/security-assessment-mapping.md +++ b/docs/security-assessment-mapping.md @@ -14,7 +14,7 @@ The following show which other security tools the mapping supports and the numbe | Security Tool | Total Controls | Mapped Controls | Coverage | |---------------|-------------------|---------------|----------| | [Netwrix PingCastle](https://www.pingcastle.com/PingCastleFiles/ad_hc_rules_list.html) | 186 | 105 | 56% | -| [Semperis PurpleKnight](https://www.semperis.com/purple-knight/security-indicators/) | 187 | 96 | 51% | +| [Semperis PurpleKnight](https://www.semperis.com/purple-knight/security-indicators/) | 190 | 96 | 51% | | [Microsoft Defender for Identity: Security Posture Assessment](https://learn.microsoft.com/en-us/defender-for-identity/security-assessment) | 45 | 35 | 78% | | [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% | From c45d8e1fdbea4b54d5653d26745b6efcf150c59b Mon Sep 17 00:00:00 2001 From: chryzsh Date: Fri, 13 Mar 2026 11:58:06 +0100 Subject: [PATCH 091/109] Add non-tier-zero shortest path queries to tier zero --- ...rom non-Tier Zero computers to Tier Zero.yml | 17 +++++++++++++++++ ...s from non-Tier Zero groups to Tier Zero.yml | 17 +++++++++++++++++ ... from non-Tier Zero objects to Tier Zero.yml | 17 +++++++++++++++++ ...non-Tier Zero user accounts to Tier Zero.yml | 17 +++++++++++++++++ 4 files changed, 68 insertions(+) create mode 100644 queries/Shortest paths from non-Tier Zero computers to Tier Zero.yml create mode 100644 queries/Shortest paths from non-Tier Zero groups to Tier Zero.yml create mode 100644 queries/Shortest paths from non-Tier Zero objects to Tier Zero.yml create mode 100644 queries/Shortest paths from non-Tier Zero user accounts to Tier Zero.yml diff --git a/queries/Shortest paths from non-Tier Zero computers to Tier Zero.yml b/queries/Shortest paths from non-Tier Zero computers to Tier Zero.yml new file mode 100644 index 0000000..0b5ac6e --- /dev/null +++ b/queries/Shortest paths from non-Tier Zero computers to Tier Zero.yml @@ -0,0 +1,17 @@ +name: Shortest paths from non-Tier Zero computers to Tier Zero +guid: 004374c9-4a81-4884-9837-c75dad71fa83 +prebuilt: false +platforms: Active Directory +category: Shortest Paths +description: WARNING! MANY-TO-MANY SHORTEST PATH QUERIES USE EXCESSIVE SYSTEM RESOURCES AND TYPICALLY WILL NOT COMPLETE +query: |- + // MANY TO MANY SHORTEST PATH QUERIES USE EXCESSIVE SYSTEM RESOURCES AND TYPICALLY WILL NOT COMPLETE + MATCH p=shortestPath((s:Computer)-[:AD_ATTACK_PATHS*1..]->(t:Base)) + WHERE s<>t + AND NOT ((s:Tag_Tier_Zero) OR COALESCE(s.system_tags, '') CONTAINS 'admin_tier_0') + AND ((t:Tag_Tier_Zero) OR COALESCE(t.system_tags, '') CONTAINS 'admin_tier_0') + RETURN p + LIMIT 1000 +revision: 1 +resources: +acknowledgements: diff --git a/queries/Shortest paths from non-Tier Zero groups to Tier Zero.yml b/queries/Shortest paths from non-Tier Zero groups to Tier Zero.yml new file mode 100644 index 0000000..0e42ab7 --- /dev/null +++ b/queries/Shortest paths from non-Tier Zero groups to Tier Zero.yml @@ -0,0 +1,17 @@ +name: Shortest paths from non-Tier Zero groups to Tier Zero +guid: 0ac20b7a-31df-4f3a-b2bd-3eecd541fcaa +prebuilt: false +platforms: Active Directory +category: Shortest Paths +description: WARNING! MANY-TO-MANY SHORTEST PATH QUERIES USE EXCESSIVE SYSTEM RESOURCES AND TYPICALLY WILL NOT COMPLETE +query: |- + // MANY TO MANY SHORTEST PATH QUERIES USE EXCESSIVE SYSTEM RESOURCES AND TYPICALLY WILL NOT COMPLETE + MATCH p=shortestPath((s:Group)-[:AD_ATTACK_PATHS*1..]->(t:Base)) + WHERE s<>t + AND NOT ((s:Tag_Tier_Zero) OR COALESCE(s.system_tags, '') CONTAINS 'admin_tier_0') + AND ((t:Tag_Tier_Zero) OR COALESCE(t.system_tags, '') CONTAINS 'admin_tier_0') + RETURN p + LIMIT 1000 +revision: 1 +resources: +acknowledgements: diff --git a/queries/Shortest paths from non-Tier Zero objects to Tier Zero.yml b/queries/Shortest paths from non-Tier Zero objects to Tier Zero.yml new file mode 100644 index 0000000..7026d00 --- /dev/null +++ b/queries/Shortest paths from non-Tier Zero objects to Tier Zero.yml @@ -0,0 +1,17 @@ +name: Shortest paths from non-Tier Zero objects to Tier Zero +guid: 47845724-94bc-4b68-944b-1114d230fc0c +prebuilt: false +platforms: Active Directory +category: Shortest Paths +description: WARNING! MANY-TO-MANY SHORTEST PATH QUERIES USE EXCESSIVE SYSTEM RESOURCES AND TYPICALLY WILL NOT COMPLETE +query: |- + // MANY TO MANY SHORTEST PATH QUERIES USE EXCESSIVE SYSTEM RESOURCES AND TYPICALLY WILL NOT COMPLETE + MATCH p=shortestPath((s:Base)-[:AD_ATTACK_PATHS*1..]->(t:Base)) + WHERE s<>t + AND NOT ((s:Tag_Tier_Zero) OR COALESCE(s.system_tags, '') CONTAINS 'admin_tier_0') + AND ((t:Tag_Tier_Zero) OR COALESCE(t.system_tags, '') CONTAINS 'admin_tier_0') + RETURN p + LIMIT 1000 +revision: 1 +resources: +acknowledgements: diff --git a/queries/Shortest paths from non-Tier Zero user accounts to Tier Zero.yml b/queries/Shortest paths from non-Tier Zero user accounts to Tier Zero.yml new file mode 100644 index 0000000..1fe6eef --- /dev/null +++ b/queries/Shortest paths from non-Tier Zero user accounts to Tier Zero.yml @@ -0,0 +1,17 @@ +name: Shortest paths from non-Tier Zero user accounts to Tier Zero +guid: 8b61bf34-5256-47e5-a5a8-370e8ddc3f90 +prebuilt: false +platforms: Active Directory +category: Shortest Paths +description: WARNING! MANY-TO-MANY SHORTEST PATH QUERIES USE EXCESSIVE SYSTEM RESOURCES AND TYPICALLY WILL NOT COMPLETE +query: |- + // MANY TO MANY SHORTEST PATH QUERIES USE EXCESSIVE SYSTEM RESOURCES AND TYPICALLY WILL NOT COMPLETE + MATCH p=shortestPath((s:User)-[:AD_ATTACK_PATHS*1..]->(t:Base)) + WHERE s<>t + AND NOT ((s:Tag_Tier_Zero) OR COALESCE(s.system_tags, '') CONTAINS 'admin_tier_0') + AND ((t:Tag_Tier_Zero) OR COALESCE(t.system_tags, '') CONTAINS 'admin_tier_0') + RETURN p + LIMIT 1000 +revision: 1 +resources: +acknowledgements: From 9bf9ad8b4af01d860a44ce9bef78d124ac4fa6fb Mon Sep 17 00:00:00 2001 From: Martin Date: Sat, 4 Apr 2026 13:06:49 +0200 Subject: [PATCH 092/109] add acknowledgement: crusher, @chryzsh --- ...Shortest paths from non-Tier Zero computers to Tier Zero.yml | 2 +- .../Shortest paths from non-Tier Zero groups to Tier Zero.yml | 2 +- .../Shortest paths from non-Tier Zero objects to Tier Zero.yml | 2 +- ...test paths from non-Tier Zero user accounts to Tier Zero.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/queries/Shortest paths from non-Tier Zero computers to Tier Zero.yml b/queries/Shortest paths from non-Tier Zero computers to Tier Zero.yml index 0b5ac6e..30c54f8 100644 --- a/queries/Shortest paths from non-Tier Zero computers to Tier Zero.yml +++ b/queries/Shortest paths from non-Tier Zero computers to Tier Zero.yml @@ -14,4 +14,4 @@ query: |- LIMIT 1000 revision: 1 resources: -acknowledgements: +acknowledgements: crusher, @chryzsh diff --git a/queries/Shortest paths from non-Tier Zero groups to Tier Zero.yml b/queries/Shortest paths from non-Tier Zero groups to Tier Zero.yml index 0e42ab7..18505bb 100644 --- a/queries/Shortest paths from non-Tier Zero groups to Tier Zero.yml +++ b/queries/Shortest paths from non-Tier Zero groups to Tier Zero.yml @@ -14,4 +14,4 @@ query: |- LIMIT 1000 revision: 1 resources: -acknowledgements: +acknowledgements: crusher, @chryzsh diff --git a/queries/Shortest paths from non-Tier Zero objects to Tier Zero.yml b/queries/Shortest paths from non-Tier Zero objects to Tier Zero.yml index 7026d00..501e7f7 100644 --- a/queries/Shortest paths from non-Tier Zero objects to Tier Zero.yml +++ b/queries/Shortest paths from non-Tier Zero objects to Tier Zero.yml @@ -14,4 +14,4 @@ query: |- LIMIT 1000 revision: 1 resources: -acknowledgements: +acknowledgements: crusher, @chryzsh diff --git a/queries/Shortest paths from non-Tier Zero user accounts to Tier Zero.yml b/queries/Shortest paths from non-Tier Zero user accounts to Tier Zero.yml index 1fe6eef..397a843 100644 --- a/queries/Shortest paths from non-Tier Zero user accounts to Tier Zero.yml +++ b/queries/Shortest paths from non-Tier Zero user accounts to Tier Zero.yml @@ -14,4 +14,4 @@ query: |- LIMIT 1000 revision: 1 resources: -acknowledgements: +acknowledgements: crusher, @chryzsh From 5b07e8518fcf44496c1223bae1c982551bf199b0 Mon Sep 17 00:00:00 2001 From: Martin Date: Sat, 4 Apr 2026 13:49:22 +0200 Subject: [PATCH 093/109] Change match logic for "Finds computers with AdminTo edges over other computers, including rights derived through nested group memberships" --- ...al administrator permissions over other computers.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/queries/Computers with local administrator permissions over other computers.yml b/queries/Computers with local administrator permissions over other computers.yml index 3971d1b..555b645 100644 --- a/queries/Computers with local administrator permissions over other computers.yml +++ b/queries/Computers with local administrator permissions over other computers.yml @@ -5,10 +5,11 @@ platform: Active Directory category: NTLM Relay Attacks description: Finds computers with AdminTo edges over other computers, including rights derived through nested group memberships. query: |- - MATCH p = (s:Computer)-[:MemberOf*0..]->()-[:AdminTo]->(d:Computer) - WHERE s <> d - RETURN p - LIMIT 1000 + MATCH p1 = (s1:Computer)-[:AdminTo]->(d1:Computer) + WHERE s1<>d1 + MATCH p2 = (s2:Computer)-[:MemberOf*1..]->()-[:AdminTo]->(d2:Computer) + WHERE s2<>d2 + RETURN p1, p2 revision: 1 resources: - https://specterops.io/blog/2025/04/08/the-renaissance-of-ntlm-relay-attacks-everything-you-need-to-know/ From 8255f78d9a1a5b3502730d79631aacc374d72db1 Mon Sep 17 00:00:00 2001 From: Martin Date: Sat, 4 Apr 2026 16:42:03 +0200 Subject: [PATCH 094/109] Acknowlegements: Non-Tier Zero account with unconstrained delegation --- .../Non-Tier Zero account with unconstrained delegation.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/queries/Non-Tier Zero account with unconstrained delegation.yml b/queries/Non-Tier Zero account with unconstrained delegation.yml index 1492393..bb28d63 100644 --- a/queries/Non-Tier Zero account with unconstrained delegation.yml +++ b/queries/Non-Tier Zero account with unconstrained delegation.yml @@ -16,5 +16,7 @@ query: |- RETURN n revision: 2 resources: -acknowledgements: Martin Sohn Christensen, @martinsohndk +acknowledgements: +- Martin Sohn Christensen, @martinsohndk +- @kaasimir From d53fca9ea0d71c15aa2a5c9864bd1ebdff712961 Mon Sep 17 00:00:00 2001 From: Martin Date: Sat, 4 Apr 2026 17:02:26 +0200 Subject: [PATCH 095/109] Add gMSA queries Addresses https://github.com/SpecterOps/BloodHoundQueryLibrary/pull/49 --- ...Group Managed Service Accounts (gMSAs).yml | 15 ++++++++++ ...incipals with access to gMSA passwords.yml | 28 +++++++++++++------ 2 files changed, 34 insertions(+), 9 deletions(-) create mode 100644 queries/All enabled Group Managed Service Accounts (gMSAs).yml diff --git a/queries/All enabled Group Managed Service Accounts (gMSAs).yml b/queries/All enabled Group Managed Service Accounts (gMSAs).yml new file mode 100644 index 0000000..a8d4207 --- /dev/null +++ b/queries/All enabled Group Managed Service Accounts (gMSAs).yml @@ -0,0 +1,15 @@ +name: All enabled Group Managed Service Accounts (gMSAs) +guid: 0fc16935-14b7-4859-9a2a-a191daa4c081 +prebuilt: false +platforms: Active Directory +category: Active Directory Hygiene +description: List all enabled Group Managed Service Accounts (gMSAs). +query: |- + MATCH (g:Base) + WHERE g.gmsa = true + AND g.enabled = true + RETURN g + LIMIT 1000 +revision: 1 +resources: https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/manage/group-managed-service-accounts/group-managed-service-accounts/group-managed-service-accounts-overview +acknowledgements: crusher, @chryzsh diff --git a/queries/Non-Tier Zero principals with access to gMSA passwords.yml b/queries/Non-Tier Zero principals with access to gMSA passwords.yml index c22210b..94fc28a 100644 --- a/queries/Non-Tier Zero principals with access to gMSA passwords.yml +++ b/queries/Non-Tier Zero principals with access to gMSA passwords.yml @@ -1,16 +1,26 @@ -name: Non-Tier Zero principals with access to gMSA passwords -guid: ef587ba1-a740-4bcf-b4e0-e1137d01b1af +name: Non-Tier Zero principals with access to enabled gMSA passwords +guid: 10d0ee8e-17ec-4f6c-9b94-8dffe548f9d4 prebuilt: false platforms: Active Directory -category: Dangerous Privileges -description: Finds non-Tier Zero principals that can read Group Managed Service Account (gMSA) passwords, or modify the msDS-GroupMSAMembership property which controls who can access the password. Unauthorized access to gMSA passwords allows an attacker to authenticate as the service account and access any resource it manages. +category: Active Directory Hygiene +description: Finds non-Tier Zero principals that can read Group Managed Service Account (gMSA) passwords, or modify the msDS-GroupMSAMembership property controlling read access. Unauthorized access to gMSA passwords allows an attacker to authenticate as the service account and access any resource it manages. query: |- - MATCH p=(n:Base)-[r:ReadGMSAPassword|GenericAll|GenericWrite|WriteOwner|WriteDacl]->(m:User) - WHERE m.gmsa = true + MATCH p1=(s1:Base)-[r:ReadGMSAPassword|GenericAll|GenericWrite|WriteOwner|WriteDacl]->(d1:Base) + WHERE s1<>d1 + AND d1.gmsa = true + AND d1.enabled = true // Exclude Tier Zero principals - AND NOT ((n:Tag_Tier_Zero) OR COALESCE(n.system_tags, '') CONTAINS 'admin_tier_0') - RETURN p + AND NOT ((s1:Tag_Tier_Zero) OR COALESCE(s1.system_tags, '') CONTAINS 'admin_tier_0') + MATCH p2 = (s2:Base)-[:MemberOf*1..]->()-[:ReadGMSAPassword]->(d2:Base) + WHERE s2<>d2 + AND d2.gmsa = true + AND d2.enabled = true + // Exclude Tier Zero principals + AND NOT ((s2:Tag_Tier_Zero) OR COALESCE(s2.system_tags, '') CONTAINS 'admin_tier_0') + RETURN p1,p2 revision: 1 resources: - https://learn.microsoft.com/en-us/windows-server/security/group-managed-service-accounts/group-managed-service-accounts-overview -acknowledgements: Martin Sohn Christensen, @martinsohndk +acknowledgements: +- Martin Sohn Christensen, @martinsohndk +- crusher, @chryzsh \ No newline at end of file From 0923a896c71811724b69c7f54e7ac421c1f1d1ab Mon Sep 17 00:00:00 2001 From: Martin Date: Fri, 10 Apr 2026 12:02:50 +0200 Subject: [PATCH 096/109] Update Computers with local administrator permissions over other computers.yml --- ...ith local administrator permissions over other computers.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/queries/Computers with local administrator permissions over other computers.yml b/queries/Computers with local administrator permissions over other computers.yml index 555b645..d8d8331 100644 --- a/queries/Computers with local administrator permissions over other computers.yml +++ b/queries/Computers with local administrator permissions over other computers.yml @@ -14,4 +14,4 @@ revision: 1 resources: - https://specterops.io/blog/2025/04/08/the-renaissance-of-ntlm-relay-attacks-everything-you-need-to-know/ - https://github.com/subat0mik/Misconfiguration-Manager -acknowledgements: Mat Fiore, @stuk0v \ No newline at end of file +acknowledgements: @stuk0v_ \ No newline at end of file From 9ebff06f8309a5c6e9699e7f977571e826f6b5fe Mon Sep 17 00:00:00 2001 From: Martin Date: Sun, 12 Apr 2026 15:20:23 -0400 Subject: [PATCH 097/109] REAME: Deprecation Notice: `system_tags` Queries --- README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/README.md b/README.md index 77988db..36d390a 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,24 @@ For an introduction to the project, please read our blog post: - [Introducing the BloodHound Query Library](https://specterops.io/blog/2025/06/17/introducing-the-bloodhound-query-library/) +## Deprecation Notice: `system_tags` Queries + +Queries in the library currently use two methods to scope nodes to Tier Zero and Owned, supporting both old and new versions of BloodHound. At the end of July 2026, all queries will be updated to use the newer simpler method. + +Old versions require scoping with a node property and null handling: + +```cypher +WHERE COALESCE(n.system_tags, '') CONTAINS 'admin_tier_0' +``` + +New versions can use node labels directly: + +```cypher +WHERE (n:Tag_Tier_Zero) +``` + +The simpler label-based approach was introduced with [Privilege Zones](https://specterops.io/privilege-zones/), which became generally available in [v2026.03.23](https://bloodhound.specterops.io/resources/release-notes/2026-03-23). Upgrade your BloodHound version to ensure queries from the library continue to work. + ## Overview The library contains queries that demonstrate BloodHound's versatility beyond traditional attack path analysis. This includes: From 9ee1cb0d3f38cfa4ccfa8cf815fe013ca5f6758f Mon Sep 17 00:00:00 2001 From: Martin Date: Sun, 12 Apr 2026 15:33:10 -0400 Subject: [PATCH 098/109] Update Computers with non-default Primary Group membership.yml Support for Domain Computer, Domain Controllers, and RODC groups in one query --- ...h non-default Primary Group membership.yml | 28 ++++++++++++++----- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/queries/Computers with non-default Primary Group membership.yml b/queries/Computers with non-default Primary Group membership.yml index 4e798d5..d45d02f 100644 --- a/queries/Computers with non-default Primary Group membership.yml +++ b/queries/Computers with non-default Primary Group membership.yml @@ -5,13 +5,27 @@ platforms: Active Directory category: Active Directory Hygiene description: query: |- - MATCH p=(n:Computer)-[r:MemberOf]->(g:Group) - WHERE NOT g.objectid ENDS WITH "-515" // Domain Computers - AND NOT n.isdc = true - AND NOT n.isreadonlydc = true - AND r.isprimarygroup = true - RETURN p -revision: 2 + // Domain Controllers + MATCH p1=(n:Computer)-[r:MemberOf]->(g:Group) + WHERE NOT g.objectid ENDS WITH "-516" + AND n.isdc = true + AND r.isprimarygroup = true + + // Domain Computers + OPTIONAL MATCH p2=(n:Computer)-[r:MemberOf]->(g:Group) + WHERE NOT g.objectid ENDS WITH "-515" + AND NOT n.isdc = true + AND NOT n.isreadonlydc = true + AND r.isprimarygroup = true + + // Read-Only Domain Controllers + OPTIONAL MATCH p3=(n:Computer)-[r:MemberOf]->(g:Group) + WHERE NOT g.objectid ENDS WITH "-521" + AND n.isreadonlydc = true + AND r.isprimarygroup = true + + RETURN p1,p2,p3 +revision: 3 resources: - https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-ada3/e12954a4-6865-4432-94e6-00c310ca87c0 - https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/5dbcf875-e802-4357-a6e2-1bdff19ff9b5 From bc1d526d1e24ebcf9e235f4dd13c0f08b9489325 Mon Sep 17 00:00:00 2001 From: Martin Date: Sun, 12 Apr 2026 15:36:51 -0400 Subject: [PATCH 099/109] bump missing revisions --- .../Domains affected by AdPrep privilege escalation risk.yml | 2 +- queries/Non-Tier Zero account with unconstrained delegation.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/queries/Domains affected by AdPrep privilege escalation risk.yml b/queries/Domains affected by AdPrep privilege escalation risk.yml index 8e93803..3bce0c2 100644 --- a/queries/Domains affected by AdPrep privilege escalation risk.yml +++ b/queries/Domains affected by AdPrep privilege escalation risk.yml @@ -8,7 +8,7 @@ query: |- MATCH p=(n:Group)-[r:GenericAll]->(m:Domain) WHERE n.objectid ENDS WITH "-527" // Enterprise Key Admins RETURN p -revision: 1 +revision: 2 resources: - https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/deploy/domain-wide-updates#windows-server-semi-annual-channel-domain-wide-updates - https://mskb.pkisolutions.com/kb/4033233 diff --git a/queries/Non-Tier Zero account with unconstrained delegation.yml b/queries/Non-Tier Zero account with unconstrained delegation.yml index bb28d63..9de0cb8 100644 --- a/queries/Non-Tier Zero account with unconstrained delegation.yml +++ b/queries/Non-Tier Zero account with unconstrained delegation.yml @@ -14,7 +14,7 @@ query: |- //AND NOT n.isdc = true RETURN n -revision: 2 +revision: 3 resources: acknowledgements: - Martin Sohn Christensen, @martinsohndk From 0da2ceae1c487d551028ba45cd3d8d5761362b20 Mon Sep 17 00:00:00 2001 From: Martin Date: Sun, 12 Apr 2026 15:43:04 -0400 Subject: [PATCH 100/109] rename file to match internal name related to https://github.com/SpecterOps/BloodHoundQueryLibrary/pull/48 --- ... in Protected Users.yml => All members of Protected Users.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename queries/{Computers with membership in Protected Users.yml => All members of Protected Users.yml} (100%) diff --git a/queries/Computers with membership in Protected Users.yml b/queries/All members of Protected Users.yml similarity index 100% rename from queries/Computers with membership in Protected Users.yml rename to queries/All members of Protected Users.yml From 8828ec7bdacc2475e1a35898704a9a20a5abb9a3 Mon Sep 17 00:00:00 2001 From: Martin Date: Sun, 12 Apr 2026 21:45:24 -0400 Subject: [PATCH 101/109] Create Tier Zero OU containing Non-Tier Zero principals.yml --- ...ro OU containing Non-Tier Zero principals.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 queries/Tier Zero OU containing Non-Tier Zero principals.yml diff --git a/queries/Tier Zero OU containing Non-Tier Zero principals.yml b/queries/Tier Zero OU containing Non-Tier Zero principals.yml new file mode 100644 index 0000000..994ce80 --- /dev/null +++ b/queries/Tier Zero OU containing Non-Tier Zero principals.yml @@ -0,0 +1,16 @@ +name: Tier Zero OU containing Non-Tier Zero principals +guid: 4a8f2e1c-3b6d-4e7a-9f1b-2c5d8e0a3f7b +prebuilt: false +platform: Active Directory +category: Active Directory Hygiene +description: Identifies Tier Zero OUs that contain Non-Tier Zero principals. Organizations should logically structure their administrative tier structure with separate root OUs for each tier; mixing Tier Zero and Non-Tier Zero principals in the same OU is an exception and can be an indicator of inadequate tier isolation. +query: |- + MATCH p = (ou:OU)-[:Contains]->(n:Base) + WHERE (n:User OR n:Group OR n:Computer) + AND (COALESCE(ou.system_tags, '') CONTAINS 'admin_tier_0' OR ou:Tag_Tier_Zero) + AND NOT (COALESCE(n.system_tags, '') CONTAINS 'admin_tier_0' OR n:Tag_Tier_Zero) + RETURN p + LIMIT 1000 +revision: 1 +resources: +acknowledgements: Martin Sohn Christensen, @martinsohndk From d0e3021d9103c1473855ddb06eff6e203baa6406 Mon Sep 17 00:00:00 2001 From: Martin Date: Sun, 12 Apr 2026 21:52:24 -0400 Subject: [PATCH 102/109] Create Azure groups nested more than 3 levels.yml --- queries/Azure groups nested more than 3 levels.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 queries/Azure groups nested more than 3 levels.yml diff --git a/queries/Azure groups nested more than 3 levels.yml b/queries/Azure groups nested more than 3 levels.yml new file mode 100644 index 0000000..19ddec3 --- /dev/null +++ b/queries/Azure groups nested more than 3 levels.yml @@ -0,0 +1,14 @@ +name: Azure groups nested more than 3 levels +guid: d819b346-b285-4e82-8d8d-2798d34716e2 +prebuilt: false +platform: Azure +category: Azure Hygiene +description: Identifies Azure AD groups nested more than 3 levels deep. Excessive nesting complexity reduces auditability and increases the risk of misconfigured permissions. +query: |- + MATCH p = (:AZGroup)-[:AZMemberOf*3..]->(:AZGroup) + // Matches Azure groups with 3 or more MemberOf hops to another Azure group + RETURN p + LIMIT 1000 +revision: 1 +resources: +acknowledgements: Martin Sohn Christensen, @martinsohndk From 225747eb7f71b6f5f15c5cd3019da3b7819a5aca Mon Sep 17 00:00:00 2001 From: Martin Date: Sun, 12 Apr 2026 21:52:27 -0400 Subject: [PATCH 103/109] Create AD Groups nested more than 3 levels.yml --- queries/AD Groups nested more than 3 levels.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 queries/AD Groups nested more than 3 levels.yml diff --git a/queries/AD Groups nested more than 3 levels.yml b/queries/AD Groups nested more than 3 levels.yml new file mode 100644 index 0000000..9318967 --- /dev/null +++ b/queries/AD Groups nested more than 3 levels.yml @@ -0,0 +1,14 @@ +name: AD Groups nested more than 3 levels +guid: 252ab5f8-0c96-470e-932a-66840914bdf6 +prebuilt: false +platform: Active Directory +category: Active Directory Hygiene +description: Identifies Active Directory groups nested more than 3 levels deep. Excessive nesting complexity reduces auditability and increases the risk of misconfigured permissions. +query: |- + MATCH p = (:Group)-[:MemberOf*3..]->(:Group) + // Matches groups with 3 or more MemberOf hops to another group + RETURN p + LIMIT 1000 +revision: 1 +resources: +acknowledgements: Martin Sohn Christensen, @martinsohndk From 6a3dcda06161ed7ec37be916b85c2457c7a7e7d2 Mon Sep 17 00:00:00 2001 From: Martin Date: Sun, 12 Apr 2026 22:13:44 -0400 Subject: [PATCH 104/109] Create No break-glass test using built-in domain Administrator account in the last year.yml --- ...Administrator account in the last year.yml | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 queries/No break-glass test using built-in domain Administrator account in the last year.yml diff --git a/queries/No break-glass test using built-in domain Administrator account in the last year.yml b/queries/No break-glass test using built-in domain Administrator account in the last year.yml new file mode 100644 index 0000000..a69c2df --- /dev/null +++ b/queries/No break-glass test using built-in domain Administrator account in the last year.yml @@ -0,0 +1,23 @@ +name: No break-glass test using built-in domain Administrator account in the last year +guid: f906e689-7d04-4051-b726-4ef45213d82c +prebuilt: false +platform: Active Directory +category: Active Directory Hygiene +description: Identifies enabled built-in domain Administrator accounts (RID 500) with no logon activity in the past year, indicating break-glass testing is not being performed. Regular break-glass tests are critical to ensuring disaster recovery procedures are operational. +query: |- + WITH 365 as maximum_days + MATCH (n:User) + WHERE n.objectid ENDS WITH "-500" // Built-in domain Administrator (RID 500) + AND n.enabled = true + AND n.whencreated < (datetime().epochseconds - (maximum_days * 86400)) // Account created more than 365 days ago + AND ( + n.lastlogontimestamp < (datetime().epochseconds - (maximum_days * 86400)) + AND n.lastlogon < (datetime().epochseconds - (maximum_days * 86400)) + ) + RETURN n + LIMIT 1000 +revision: 1 +resources: + - https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/security-emergency-access#validate-accounts-regularly + - https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/plan/security-best-practices/monitoring-active-directory-for-signs-of-compromise +acknowledgements: Martin Sohn Christensen, @martinsohndk From f08f86c2c752991418cfba7ad7f8f7beb9ae8a98 Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 14 Apr 2026 13:47:54 -0400 Subject: [PATCH 105/109] Update Domains where any user can join a computer to the domain.yml --- ... where any user can join a computer to the domain.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/queries/Domains where any user can join a computer to the domain.yml b/queries/Domains where any user can join a computer to the domain.yml index f1ff6b8..b68e9b6 100644 --- a/queries/Domains where any user can join a computer to the domain.yml +++ b/queries/Domains where any user can join a computer to the domain.yml @@ -3,12 +3,15 @@ guid: 421921fa-bc0f-4659-9680-b7481adcb132 prebuilt: true platforms: Active Directory category: Active Directory Hygiene -description: +description: Identifies domains where authenticated users can create machine accounts (machineaccountquota > 0 or unlimited). Does not check the "Add workstations to domain" security policy on Domain Controllers. query: |- MATCH (d:Domain) - WHERE d.machineaccountquota > 0 + WHERE ( + d.machineaccountquota IS NULL // NULL = no limit is set + OR d.machineaccountquota > 0) + AND d.collected = true RETURN d -revision: 2 +revision: 3 resources: - https://learn.microsoft.com/en-us/troubleshoot/windows-server/active-directory/default-workstation-numbers-join-domain - https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-10/security/threat-protection/security-policy-settings/add-workstations-to-domain From cf9ff22f7a42333147f3f09d2290a4a5fe66a152 Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 14 Apr 2026 13:48:08 -0400 Subject: [PATCH 106/109] Rename to: Non-Tier Zero principals with access to enabled gMSA passwords --- ...ier Zero principals with access to enabled gMSA passwords.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename queries/{Non-Tier Zero principals with access to gMSA passwords.yml => Non-Tier Zero principals with access to enabled gMSA passwords.yml} (100%) diff --git a/queries/Non-Tier Zero principals with access to gMSA passwords.yml b/queries/Non-Tier Zero principals with access to enabled gMSA passwords.yml similarity index 100% rename from queries/Non-Tier Zero principals with access to gMSA passwords.yml rename to queries/Non-Tier Zero principals with access to enabled gMSA passwords.yml From b45be62665d2595f0f10831f4239fb88aaeeb518 Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 14 Apr 2026 13:51:05 -0400 Subject: [PATCH 107/109] Sync filenames with name fields --- queries/All GPOs applied to a specific computer.yml | 4 ++-- ...Computers with membership in default privileged groups.yml | 2 +- queries/Non-Tier Zero account with excessive control.yml | 4 ++-- queries/Objects created in the past 10 days.yml | 2 +- ...t can write Shadow Credentials on Tier Zero principals.yml | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/queries/All GPOs applied to a specific computer.yml b/queries/All GPOs applied to a specific computer.yml index 38b560d..528289d 100644 --- a/queries/All GPOs applied to a specific computer.yml +++ b/queries/All GPOs applied to a specific computer.yml @@ -1,4 +1,4 @@ -name: All GPOs applied to a specific Computer +name: All GPOs applied to a specific computer guid: 1d75a21e-0d34-40c5-9360-281b60737d87 prebuilt: false platforms: Active Directory @@ -9,7 +9,7 @@ query: |- MATCH p=(c:Computer)<-[:Contains*..]-(:Base)<-[:GPLink]-(:GPO) WHERE toLower(c.name) CONTAINS toLower("HOSTNAME/FQDN") RETURN p -revision: 1 +revision: 2 resources: - https://learn.microsoft.com/en-us/previous-versions/windows/desktop/Policy/overriding-and-blocking-group-policy acknowledgements: Adnan Ullah Khan, @auk0x01 diff --git a/queries/Computers with membership in default privileged groups.yml b/queries/Computers with membership in default privileged groups.yml index 6ba6dab..3903c89 100644 --- a/queries/Computers with membership in default privileged groups.yml +++ b/queries/Computers with membership in default privileged groups.yml @@ -1,4 +1,4 @@ -name: Computers members of built-in privileged groups +name: Computers with membership in default privileged groups guid: 622bf05c-b34b-4538-9a1e-524a2f6f58b0 prebuilt: false platforms: Active Directory diff --git a/queries/Non-Tier Zero account with excessive control.yml b/queries/Non-Tier Zero account with excessive control.yml index 4437149..533dbff 100644 --- a/queries/Non-Tier Zero account with excessive control.yml +++ b/queries/Non-Tier Zero account with excessive control.yml @@ -1,4 +1,4 @@ -name: Non-Tier Zero object with excessive control +name: Non-Tier Zero principal with excessive control guid: 944cecfe-519b-4318-b226-e8520161b454 prebuilt: false platforms: Active Directory @@ -11,7 +11,7 @@ query: |- WITH n, COLLECT(DISTINCT(m)) AS endNodes WHERE SIZE(endNodes) >= 1000 RETURN n -revision: 4 +revision: 5 resources: acknowledgements: Martin Sohn Christensen, @martinsohndk diff --git a/queries/Objects created in the past 10 days.yml b/queries/Objects created in the past 10 days.yml index 82c544b..2b96e15 100644 --- a/queries/Objects created in the past 10 days.yml +++ b/queries/Objects created in the past 10 days.yml @@ -1,4 +1,4 @@ -name: Objects created in the last 10 days +name: Objects created in the past 10 days guid: eeed0434-28e3-4d84-9dfb-9108d5997589 prebuilt: false platforms: Active Directory diff --git a/queries/Principals that can write Shadow Credentials on Tier Zero principals.yml b/queries/Principals that can write Shadow Credentials on Tier Zero principals.yml index e578e7d..c149000 100644 --- a/queries/Principals that can write Shadow Credentials on Tier Zero principals.yml +++ b/queries/Principals that can write Shadow Credentials on Tier Zero principals.yml @@ -1,4 +1,4 @@ -name: Principals with write Shadow Credentials on Tier Zero principals +name: Principals that can write Shadow Credentials on Tier Zero principals guid: 96e86fb9-4cd6-4df3-81a6-e36fd7a34614 prebuilt: false platforms: Active Directory From 48a92ff24c61e323c87221de2496a6ba5ae69534 Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 15 Apr 2026 07:29:40 -0400 Subject: [PATCH 108/109] Update Non-Tier Zero principals with access to enabled gMSA passwords.yml --- ... Zero principals with access to enabled gMSA passwords.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/queries/Non-Tier Zero principals with access to enabled gMSA passwords.yml b/queries/Non-Tier Zero principals with access to enabled gMSA passwords.yml index 94fc28a..7e98917 100644 --- a/queries/Non-Tier Zero principals with access to enabled gMSA passwords.yml +++ b/queries/Non-Tier Zero principals with access to enabled gMSA passwords.yml @@ -11,14 +11,14 @@ query: |- AND d1.enabled = true // Exclude Tier Zero principals AND NOT ((s1:Tag_Tier_Zero) OR COALESCE(s1.system_tags, '') CONTAINS 'admin_tier_0') - MATCH p2 = (s2:Base)-[:MemberOf*1..]->()-[:ReadGMSAPassword]->(d2:Base) + MATCH p2 = (s2:Base)-[:MemberOf*1..]->()-[:ReadGMSAPassword|GenericAll|GenericWrite|WriteOwner|WriteDacl]->(d2:Base) WHERE s2<>d2 AND d2.gmsa = true AND d2.enabled = true // Exclude Tier Zero principals AND NOT ((s2:Tag_Tier_Zero) OR COALESCE(s2.system_tags, '') CONTAINS 'admin_tier_0') RETURN p1,p2 -revision: 1 +revision: 2 resources: - https://learn.microsoft.com/en-us/windows-server/security/group-managed-service-accounts/group-managed-service-accounts-overview acknowledgements: From 47c976313f7526b2f7c3e0a88d9f113865c2f90d Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 15 Apr 2026 07:40:12 -0400 Subject: [PATCH 109/109] Fix syntax checks --- .../AD Groups nested more than 3 levels.yml | 2 +- ...Azure groups nested more than 3 levels.yml | 2 +- ...rator permissions over other computers.yml | 34 +++++----- ...h non-default Primary Group membership.yml | 66 +++++++++---------- ...Administrator account in the last year.yml | 2 +- ... account with unconstrained delegation.yml | 44 ++++++------- ... Zero owners of Tier Zero Entra groups.yml | 28 ++++---- ...ero users that can read LAPS passwords.yml | 2 +- ...d or prohibited Entra role assignments.yml | 40 +++++------ ...OU containing Non-Tier Zero principals.yml | 2 +- 10 files changed, 111 insertions(+), 111 deletions(-) diff --git a/queries/AD Groups nested more than 3 levels.yml b/queries/AD Groups nested more than 3 levels.yml index 9318967..e6f6dfb 100644 --- a/queries/AD Groups nested more than 3 levels.yml +++ b/queries/AD Groups nested more than 3 levels.yml @@ -1,7 +1,7 @@ name: AD Groups nested more than 3 levels guid: 252ab5f8-0c96-470e-932a-66840914bdf6 prebuilt: false -platform: Active Directory +platforms: Active Directory category: Active Directory Hygiene description: Identifies Active Directory groups nested more than 3 levels deep. Excessive nesting complexity reduces auditability and increases the risk of misconfigured permissions. query: |- diff --git a/queries/Azure groups nested more than 3 levels.yml b/queries/Azure groups nested more than 3 levels.yml index 19ddec3..59bb068 100644 --- a/queries/Azure groups nested more than 3 levels.yml +++ b/queries/Azure groups nested more than 3 levels.yml @@ -1,7 +1,7 @@ name: Azure groups nested more than 3 levels guid: d819b346-b285-4e82-8d8d-2798d34716e2 prebuilt: false -platform: Azure +platforms: Azure category: Azure Hygiene description: Identifies Azure AD groups nested more than 3 levels deep. Excessive nesting complexity reduces auditability and increases the risk of misconfigured permissions. query: |- diff --git a/queries/Computers with local administrator permissions over other computers.yml b/queries/Computers with local administrator permissions over other computers.yml index d8d8331..c3b4e89 100644 --- a/queries/Computers with local administrator permissions over other computers.yml +++ b/queries/Computers with local administrator permissions over other computers.yml @@ -1,17 +1,17 @@ -name: Computers with local administrator permissions over other computers -guid: 093a8e04-4f79-4264-a64b-4e52a630cd0d -prebuilt: false -platform: Active Directory -category: NTLM Relay Attacks -description: Finds computers with AdminTo edges over other computers, including rights derived through nested group memberships. -query: |- - MATCH p1 = (s1:Computer)-[:AdminTo]->(d1:Computer) - WHERE s1<>d1 - MATCH p2 = (s2:Computer)-[:MemberOf*1..]->()-[:AdminTo]->(d2:Computer) - WHERE s2<>d2 - RETURN p1, p2 -revision: 1 -resources: - - https://specterops.io/blog/2025/04/08/the-renaissance-of-ntlm-relay-attacks-everything-you-need-to-know/ - - https://github.com/subat0mik/Misconfiguration-Manager -acknowledgements: @stuk0v_ \ No newline at end of file +name: Computers with local administrator permissions over other computers +guid: 093a8e04-4f79-4264-a64b-4e52a630cd0d +prebuilt: false +platforms: Active Directory +category: NTLM Relay Attacks +description: Finds computers with AdminTo edges over other computers, including rights derived through nested group memberships. +query: |- + MATCH p1 = (s1:Computer)-[:AdminTo]->(d1:Computer) + WHERE s1<>d1 + MATCH p2 = (s2:Computer)-[:MemberOf*1..]->()-[:AdminTo]->(d2:Computer) + WHERE s2<>d2 + RETURN p1, p2 +revision: 1 +resources: + - https://specterops.io/blog/2025/04/08/the-renaissance-of-ntlm-relay-attacks-everything-you-need-to-know/ + - https://github.com/subat0mik/Misconfiguration-Manager +acknowledgements: "@stuk0v_" \ No newline at end of file diff --git a/queries/Computers with non-default Primary Group membership.yml b/queries/Computers with non-default Primary Group membership.yml index d45d02f..2950851 100644 --- a/queries/Computers with non-default Primary Group membership.yml +++ b/queries/Computers with non-default Primary Group membership.yml @@ -1,33 +1,33 @@ -name: Computers with non-default Primary Group membership -guid: 5862dc4e-6f6f-4321-9474-d838968495ed -prebuilt: false -platforms: Active Directory -category: Active Directory Hygiene -description: -query: |- - // Domain Controllers - MATCH p1=(n:Computer)-[r:MemberOf]->(g:Group) - WHERE NOT g.objectid ENDS WITH "-516" - AND n.isdc = true - AND r.isprimarygroup = true - - // Domain Computers - OPTIONAL MATCH p2=(n:Computer)-[r:MemberOf]->(g:Group) - WHERE NOT g.objectid ENDS WITH "-515" - AND NOT n.isdc = true - AND NOT n.isreadonlydc = true - AND r.isprimarygroup = true - - // Read-Only Domain Controllers - OPTIONAL MATCH p3=(n:Computer)-[r:MemberOf]->(g:Group) - WHERE NOT g.objectid ENDS WITH "-521" - AND n.isreadonlydc = true - AND r.isprimarygroup = true - - RETURN p1,p2,p3 -revision: 3 -resources: -- https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-ada3/e12954a4-6865-4432-94e6-00c310ca87c0 -- https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/5dbcf875-e802-4357-a6e2-1bdff19ff9b5 -- https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/73d11ea7-e634-453e-944d-559654cc91c5 -acknowledgements: Martin Sohn Christensen, @martinsohndk +name: Computers with non-default Primary Group membership +guid: 5862dc4e-6f6f-4321-9474-d838968495ed +prebuilt: false +platforms: Active Directory +category: Active Directory Hygiene +description: +query: |- + // Domain Controllers + MATCH p1=(n:Computer)-[r:MemberOf]->(g:Group) + WHERE NOT g.objectid ENDS WITH "-516" + AND n.isdc = true + AND r.isprimarygroup = true + + // Domain Computers + OPTIONAL MATCH p2=(n:Computer)-[r:MemberOf]->(g:Group) + WHERE NOT g.objectid ENDS WITH "-515" + AND NOT n.isdc = true + AND NOT n.isreadonlydc = true + AND r.isprimarygroup = true + + // Read-Only Domain Controllers + OPTIONAL MATCH p3=(n:Computer)-[r:MemberOf]->(g:Group) + WHERE NOT g.objectid ENDS WITH "-521" + AND n.isreadonlydc = true + AND r.isprimarygroup = true + + RETURN p1,p2,p3 +revision: 3 +resources: +- https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-ada3/e12954a4-6865-4432-94e6-00c310ca87c0 +- https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/5dbcf875-e802-4357-a6e2-1bdff19ff9b5 +- https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/73d11ea7-e634-453e-944d-559654cc91c5 +acknowledgements: Martin Sohn Christensen, @martinsohndk diff --git a/queries/No break-glass test using built-in domain Administrator account in the last year.yml b/queries/No break-glass test using built-in domain Administrator account in the last year.yml index a69c2df..3f5cdd9 100644 --- a/queries/No break-glass test using built-in domain Administrator account in the last year.yml +++ b/queries/No break-glass test using built-in domain Administrator account in the last year.yml @@ -1,7 +1,7 @@ name: No break-glass test using built-in domain Administrator account in the last year guid: f906e689-7d04-4051-b726-4ef45213d82c prebuilt: false -platform: Active Directory +platforms: Active Directory category: Active Directory Hygiene description: Identifies enabled built-in domain Administrator accounts (RID 500) with no logon activity in the past year, indicating break-glass testing is not being performed. Regular break-glass tests are critical to ensuring disaster recovery procedures are operational. query: |- diff --git a/queries/Non-Tier Zero account with unconstrained delegation.yml b/queries/Non-Tier Zero account with unconstrained delegation.yml index 9de0cb8..8d4b613 100644 --- a/queries/Non-Tier Zero account with unconstrained delegation.yml +++ b/queries/Non-Tier Zero account with unconstrained delegation.yml @@ -1,22 +1,22 @@ -name: Non-Tier Zero account with unconstrained delegation -guid: e7e9a927-3f34-42c7-b921-d8bcf626011e -prebuilt: false -platforms: Active Directory -category: Dangerous Privileges -description: -query: |- - MATCH (n:Base) - WHERE n.unconstraineddelegation = true - - // The query excludes all Tier Zero objects by default - // Exclude only DCs by removing the line below and uncomment the 'NOT n.isdc' line after - AND NOT ((n:Tag_Tier_Zero) OR COALESCE(n.system_tags, '') CONTAINS 'admin_tier_0') - //AND NOT n.isdc = true - - RETURN n -revision: 3 -resources: -acknowledgements: -- Martin Sohn Christensen, @martinsohndk -- @kaasimir - +name: Non-Tier Zero account with unconstrained delegation +guid: e7e9a927-3f34-42c7-b921-d8bcf626011e +prebuilt: false +platforms: Active Directory +category: Dangerous Privileges +description: +query: |- + MATCH (n:Base) + WHERE n.unconstraineddelegation = true + + // The query excludes all Tier Zero objects by default + // Exclude only DCs by removing the line below and uncomment the 'NOT n.isdc' line after + AND NOT ((n:Tag_Tier_Zero) OR COALESCE(n.system_tags, '') CONTAINS 'admin_tier_0') + //AND NOT n.isdc = true + + RETURN n +revision: 3 +resources: +acknowledgements: +- Martin Sohn Christensen, @martinsohndk +- "@kaasimir" + diff --git a/queries/Non-Tier Zero owners of Tier Zero Entra groups.yml b/queries/Non-Tier Zero owners of Tier Zero Entra groups.yml index 0070ef3..40d4b5c 100644 --- a/queries/Non-Tier Zero owners of Tier Zero Entra groups.yml +++ b/queries/Non-Tier Zero owners of Tier Zero Entra groups.yml @@ -1,14 +1,14 @@ -name: Non-Tier Zero owners of Tier Zero Entra groups -guid: f02f57b9-ac94-4954-a698-4584457962eb -prebuilt: false -platform: Azure -category: Dangerous Privileges -description: -query: |- - MATCH p=(n:AZBase)-[:AZOwns]->(g:AZGroup) - WHERE NOT ((n:Tag_Tier_Zero) OR COALESCE(n.system_tags, '') CONTAINS 'admin_tier_0') - AND ((g:Tag_Tier_Zero) OR COALESCE(g.system_tags, '') CONTAINS 'admin_tier_0') - RETURN p -revision: 1 -resources: -acknowledgements: Martin Sohn Christensen, @martinsohndk +name: Non-Tier Zero owners of Tier Zero Entra groups +guid: f02f57b9-ac94-4954-a698-4584457962eb +prebuilt: false +platforms: Azure +category: Dangerous Privileges +description: +query: |- + MATCH p=(n:AZBase)-[:AZOwns]->(g:AZGroup) + WHERE NOT ((n:Tag_Tier_Zero) OR COALESCE(n.system_tags, '') CONTAINS 'admin_tier_0') + AND ((g:Tag_Tier_Zero) OR COALESCE(g.system_tags, '') CONTAINS 'admin_tier_0') + RETURN p +revision: 1 +resources: +acknowledgements: Martin Sohn Christensen, @martinsohndk diff --git a/queries/Non-Tier Zero users that can read LAPS passwords.yml b/queries/Non-Tier Zero users that can read LAPS passwords.yml index 33ced3a..9151337 100644 --- a/queries/Non-Tier Zero users that can read LAPS passwords.yml +++ b/queries/Non-Tier Zero users that can read LAPS passwords.yml @@ -1,7 +1,7 @@ name: Non-Tier Zero users that can read LAPS passwords guid: d1779573-7ad5-4a78-a5e5-9dd53a1eb1a1 prebuilt: false -platform: Active Directory +platforms: Active Directory category: Dangerous Privileges description: Non-Tier Zero principals with direct LAPS password read capability present a lateral movement risk through local administrator credential exposure. query: |- diff --git a/queries/Principals with deprecated or prohibited Entra role assignments.yml b/queries/Principals with deprecated or prohibited Entra role assignments.yml index 91e1da7..acfd963 100644 --- a/queries/Principals with deprecated or prohibited Entra role assignments.yml +++ b/queries/Principals with deprecated or prohibited Entra role assignments.yml @@ -1,20 +1,20 @@ -name: Principals with deprecated or prohibited Entra role assignments -guid: adca90da-4298-4d05-92e0-2fc22676cd05 -prebuilt: false -platform: Azure -category: Azure Hygiene -description: Identifies principals assigned to Entra ID roles that are deprecated or should never be used, indicating stale or risky role assignments. -query: |- - MATCH p = (m:AZBase)-[:AZHasRole|AZRoleEligible|AZMemberOf*1..2]->(r:AZRole) - WHERE r.roledefinitionid =~ '(?i)9C094953-4995-41C8-84C8-3EBB9B32C93F' // Deprecated: Device Join - OR r.roledefinitionid =~ '(?i)9F06204D-73C1-4D4C-880A-6EDB90606FD8' // Deprecated: Azure AD Joined Device Local Administrator - OR r.roledefinitionid =~ '(?i)2B499BCD-DA44-4968-8AEC-78E1674FA64D' // Deprecated: Device Managers - OR r.roledefinitionid =~ '(?i)D405C6DF-0AF8-4E3B-95E4-4D06E542189E' // Deprecated: Device Users - OR r.roledefinitionid =~ '(?i)C34F683F-4D5A-4403-AFFD-6615E00E3A7F' // Deprecated: Workplace Device Join - OR r.roledefinitionid =~ '(?i)4BA39CA4-527C-499A-B93D-D9B492C50246' // Prohibited: Partner Tier1 Support - OR r.roledefinitionid =~ '(?i)E00E864A-17C5-4A4B-9C06-F5B95A8D5BD8' // Prohibited: Partner Tier2 Support - RETURN p -revision: 1 -resources: -- https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/permissions-reference#deprecated-roles -acknowledgements: Martin Sohn Christensen, @martinsohndk +name: Principals with deprecated or prohibited Entra role assignments +guid: adca90da-4298-4d05-92e0-2fc22676cd05 +prebuilt: false +platforms: Azure +category: Azure Hygiene +description: Identifies principals assigned to Entra ID roles that are deprecated or should never be used, indicating stale or risky role assignments. +query: |- + MATCH p = (m:AZBase)-[:AZHasRole|AZRoleEligible|AZMemberOf*1..2]->(r:AZRole) + WHERE r.roledefinitionid =~ '(?i)9C094953-4995-41C8-84C8-3EBB9B32C93F' // Deprecated: Device Join + OR r.roledefinitionid =~ '(?i)9F06204D-73C1-4D4C-880A-6EDB90606FD8' // Deprecated: Azure AD Joined Device Local Administrator + OR r.roledefinitionid =~ '(?i)2B499BCD-DA44-4968-8AEC-78E1674FA64D' // Deprecated: Device Managers + OR r.roledefinitionid =~ '(?i)D405C6DF-0AF8-4E3B-95E4-4D06E542189E' // Deprecated: Device Users + OR r.roledefinitionid =~ '(?i)C34F683F-4D5A-4403-AFFD-6615E00E3A7F' // Deprecated: Workplace Device Join + OR r.roledefinitionid =~ '(?i)4BA39CA4-527C-499A-B93D-D9B492C50246' // Prohibited: Partner Tier1 Support + OR r.roledefinitionid =~ '(?i)E00E864A-17C5-4A4B-9C06-F5B95A8D5BD8' // Prohibited: Partner Tier2 Support + RETURN p +revision: 1 +resources: +- https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/permissions-reference#deprecated-roles +acknowledgements: Martin Sohn Christensen, @martinsohndk diff --git a/queries/Tier Zero OU containing Non-Tier Zero principals.yml b/queries/Tier Zero OU containing Non-Tier Zero principals.yml index 994ce80..f592864 100644 --- a/queries/Tier Zero OU containing Non-Tier Zero principals.yml +++ b/queries/Tier Zero OU containing Non-Tier Zero principals.yml @@ -1,7 +1,7 @@ name: Tier Zero OU containing Non-Tier Zero principals guid: 4a8f2e1c-3b6d-4e7a-9f1b-2c5d8e0a3f7b prebuilt: false -platform: Active Directory +platforms: Active Directory category: Active Directory Hygiene description: Identifies Tier Zero OUs that contain Non-Tier Zero principals. Organizations should logically structure their administrative tier structure with separate root OUs for each tier; mixing Tier Zero and Non-Tier Zero principals in the same OU is an exception and can be an indicator of inadequate tier isolation. query: |-