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/README.md b/README.md index 0815111..36d390a 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,25 @@ 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 +## 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: - All existing pre-built queries from BloodHound @@ -42,7 +60,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 +85,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 +102,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 +119,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 +168,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 diff --git a/docs/security-assessment-mapping.json b/docs/security-assessment-mapping.json index 5b3e351..98182f9 100644 --- a/docs/security-assessment-mapping.json +++ b/docs/security-assessment-mapping.json @@ -1,9 +1,10 @@ { "definitions": { - "maps_to": [ + "source": [ "PingCastle", "Nessus", - "MDI" + "MDI", + "PurpleKnight" ], "mapping_scope": { "partial": "Query covers the core assessment control but with different approach/scope", @@ -54,6 +55,36 @@ "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": "07362c0e-e675-4451-9d09-65ca46ab43a3", + "name": "Computers with password last set over 90 days ago" + } + ] + } + ] + }, + { + "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": "a7813b26-5472-4fbd-a6a4-1c93bfeb2784", + "name": "gMSA objects with old passwords" + } + ] } ] }, @@ -95,6 +126,23 @@ "name": "Dormant entities in sensitive groups" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "750c9233-57b3-430c-af56-1e899e81b202", + "name": "Enabled admin accounts that are inactive" + }, + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "1b6df9e8-e5ed-45f7-880c-44b4a9a7d6bd", + "name": "Domain Controllers that have not authenticated to the domain for more than 45 days" + } + ] } ] }, @@ -125,6 +173,17 @@ "name": "Change Domain Controller computer account old password" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "bd287262-50dd-4e99-9daa-7754eb27cddb", + "name": "Domain Controllers with old passwords" + } + ] } ] }, @@ -189,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" } ] @@ -204,6 +263,17 @@ "name": "Accounts with non-default Primary Group ID" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "combination", + "mapping_scope_detail": "", + "id": "16262280-22e2-40e2-a227-9934e63dadaa", + "name": "Users and computers with non-default Primary Group IDs" + } + ] } ] }, @@ -230,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" } ] @@ -245,6 +315,17 @@ "name": "Accounts with non-default Primary Group ID" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "combination", + "mapping_scope_detail": "", + "id": "16262280-22e2-40e2-a227-9934e63dadaa", + "name": "Users and computers with non-default Primary Group IDs" + } + ] } ] }, @@ -281,6 +362,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": "2c62cbf1-ebdb-4ac4-9c5c-6507a15fd9e2", + "name": "User accounts that store passwords with reversible encryption" + } + ] } ] }, @@ -307,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" } ] @@ -322,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" + } + ] } ] }, @@ -367,7 +470,7 @@ { "mapping_scope": "exact", "mapping_scope_detail": "", - "id": "Blank passwords", + "id": "https://www.tenable.com/plugins/nessus/150489", "name": "Blank passwords" } ] @@ -382,6 +485,17 @@ "name": "Unsecure account attributes: Remove Password not required" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "32c2a92b-fe99-4e5c-bfbb-7497b759946d", + "name": "User accounts with password not required" + } + ] } ] }, @@ -408,10 +522,40 @@ { "mapping_scope": "exact", "mapping_scope_detail": "", - "id": "Non-expiring account password", + "id": "https://www.tenable.com/plugins/nessus/150483", "name": "Non-expiring account password" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "3653043f-2790-4255-a625-3359e6dc8ef6", + "name": "Users with Password Never Expires flag set" + } + ] + } + ] + }, + { + "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": "755d0eba-b8dc-4216-a9d4-44ab43bfb7b5", + "name": "Privileged accounts with a password that never expires" + } + ] } ] }, @@ -462,6 +606,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": "ff50af43-c9c8-41c6-987f-eaaaedbca25c", + "name": "Domains with obsolete functional levels" + } + ] } ] }, @@ -572,6 +727,17 @@ "name": "[M]Obsolete OS (Windows XP)" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "a0d33c5f-fda5-4a06-9b80-5196e099131e", + "name": "Computers with older OS versions" + } + ] } ] }, @@ -602,6 +768,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" + } + ] } ] }, @@ -628,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" } ] @@ -643,6 +820,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" + } + ] } ] }, @@ -669,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" } ] @@ -684,6 +872,17 @@ "name": "Weak cipher usage" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "superset", + "mapping_scope_detail": "Returns all principals with weak supported Kerberos encryption types.", + "id": "f8af8921-8901-466e-ba91-df970a56cc21", + "name": "RC4 or DES encryption type are supported by Domain Controllers" + } + ] } ] }, @@ -733,6 +932,17 @@ "name": "Unsecure domain configurations: ms-DS-MachineAccountQuota" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "6317479a-c7df-49ca-bbf1-47ecdf199792", + "name": "Unprivileged users can add computer accounts to the domain" + } + ] } ] }, @@ -808,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" } ] @@ -823,6 +1033,36 @@ "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": "34d7d270-3b7b-4a0e-b0c4-15e8e2551c31", + "name": "Privileged users with SPN defined" + } + ] + } + ] + }, + { + "bloodhound_query": { + "guid": "14ab4eaa-b73b-49c4-b2d1-1e020757c995", + "name": "All Kerberoastable users" + }, + "maps_to": [ + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "868c36af-b784-465b-a15c-291bd3c66d47", + "name": "Users with SPN defined" + } + ] } ] }, @@ -853,6 +1093,42 @@ "name": "Change password of built-in domain Administrator account" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "dc8f6889-7df9-4414-9bf5-865f1e3e9e83", + "name": "Admins with old passwords" + }, + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "82901792-3b6e-4b3f-94d7-64d4743273fb", + "name": "Built-in domain Administrator account with old password (180 days)" + } + ] + } + ] + }, + { + "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": "4ef13866-3af9-477a-84d4-d2d7e39f3c0f", + "name": "Users with old passwords" + } + ] } ] }, @@ -872,6 +1148,17 @@ "name": "Rotate password for Microsoft Entra Connect AD DS Connector account" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "4f964cc3-7ce9-4b44-b6d0-799622027adc", + "name": "Entra Connect sync account password reset" + } + ] } ] }, @@ -967,13 +1254,24 @@ "name": "[M]Check if all privileged accounts are in the special group Protected Users" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "b4ee6e7c-5b7c-4e63-82cd-668d3d0b3354", + "name": "Protected Users group not in use" + } + ] } ] }, { "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": [ { @@ -1024,6 +1322,17 @@ "name": "[M]Check if the Dns Admins group is not empty" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "partial", + "mapping_scope_detail": "Returns all members.", + "id": "e79191aa-b68f-4983-8420-b2ca25bca6ea", + "name": "Unprivileged principals as DNS Admins" + } + ] } ] }, @@ -1056,12 +1365,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": "ec4a80dc-bec8-4557-b19f-cc3d15ed5517", + "name": "Enterprise Key Admins with full access to domain" + } + ] } ] }, @@ -1095,7 +1415,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" } @@ -1106,11 +1426,22 @@ "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" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "superset", + "mapping_scope_detail": "Remove false positives by reducing scope to Non-Tier Zero", + "id": "39293315-4817-44f6-be2d-e76daeaf8208", + "name": "Permission changes on AdminSDHolder object" + } + ] } ] }, @@ -1130,6 +1461,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": "f2f975fd-6ce2-491b-8247-9662a0126187", + "name": "Operator groups no longer protected by AdminSDHolder and SDProp" + } + ] } ] }, @@ -1149,6 +1491,17 @@ "name": "[M]Check for Native administrator usage" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "2c2b46b6-3bce-451c-bbc8-7c7652fecbf0", + "name": "Built-in domain Administrator account used within the last two weeks" + } + ] } ] }, @@ -1168,13 +1521,24 @@ "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": "9c9bfa49-9431-4043-a073-0f90f3008b54", + "name": "Forest contains more than 50 privileged accounts" + } + ] + } + ] + }, { "bloodhound_query": { "guid": "3dfd0843-1ff9-4c21-aa67-feae08d109de", - "name": "All Operator groups" + "name": "All members of Operator groups" }, "maps_to": [ { @@ -1187,6 +1551,17 @@ "name": "[M]Check that the operator groups are empty" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "681db401-004b-4cec-a4b8-07926a63e281", + "name": "Operators Groups that are not empty" + } + ] } ] }, @@ -1206,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" + } + ] } ] }, @@ -1250,7 +1636,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" } @@ -1329,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" + } + ] } ] }, @@ -1359,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)" + } + ] } ] }, @@ -1373,7 +1799,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" } @@ -1385,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" } ] @@ -1400,6 +1826,17 @@ "name": "Unsecure Kerberos delegation" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "5d5d5b9c-5685-4786-b3a4-eb2ab1480a69", + "name": "Computer or user accounts with SPN that have unconstrained delegation" + } + ] } ] }, @@ -1502,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" } ] @@ -1517,6 +1954,34 @@ "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": "ffa8305f-ff2e-4196-b980-8d7cafce849d", + "name": "Dangerous Trust Attribute Set" + }, + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "19a4b814-3561-4463-a083-2769fabf1490", + "name": "Domain trust to a third-party domain without quarantine" + } + ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "13b327cf-1283-4362-8815-6c1acb24f4de", + "name": "Outbound forest trust with SID History enabled" + } + ] } ] }, @@ -1569,7 +2034,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" } @@ -1585,6 +2050,17 @@ "name": "Unsecure SID History attributes" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "superset", + "mapping_scope_detail": "Expanded scope to Tier Zero", + "id": "78a9064a-f3a0-4420-ab9d-2db003d6f4b4", + "name": "Well-known privileged SIDs in SIDHistory" + } + ] } ] }, @@ -1653,6 +2129,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" + } + ] } ] }, @@ -1769,6 +2256,29 @@ "name": "Insecure ADCS certificate enrollment IIS endpoints (ESC8)" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "790e1c72-5786-4907-83cd-9f310db70f1b", + "name": "Certificate templates that allow requesters to specify a subjectAltName" + }, + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "d64cab17-754c-4643-872b-a9113fbb7808", + "name": "Certificate templates with 3 or more insecure configurations" + }, + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "a76ea884-afef-4d00-9820-b24117a12661", + "name": "Dangerous control paths expose certificate templates" + } + ] } ] }, @@ -1813,12 +2323,23 @@ "name": "[T]Check if certificate enrollment can be done with HTTP" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "08612871-a9dc-4d18-a2cc-580e086e3199", + "name": "AD Certificate Authority with Web Enrollment - ESC8" + } + ] } ] }, { "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": [ @@ -1858,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" } ] @@ -1873,6 +2394,17 @@ "name": "Change password for krbtgt account" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "1e43868c-9a46-41e8-8daf-d8bfe57aaef7", + "name": "Kerberos KRBTGT account with old password" + } + ] } ] }, @@ -1939,6 +2471,17 @@ "name": "Unsecure domain configurations: LDAP Signing" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "4fe825ed-07fb-4b06-913a-be5c9542ca54", + "name": "LDAP signing is not required on Domain Controllers" + } + ] } ] }, @@ -1958,6 +2501,17 @@ "name": "[T]Automatic password rotation for smart card is not in place" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "04ad3f4e-b67c-49c3-a668-528214dd6c63", + "name": "Smart card password rotation disabled" + } + ] } ] }, @@ -1977,6 +2531,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": "baeb89cc-e32d-4f17-ad37-29a6a45ff724", + "name": "User accounts using Smart Card authentication with old password" + } + ] } ] }, @@ -2028,12 +2593,23 @@ "source": "MDI", "controls": [ { - "mapping_scope": "exact", - "mapping_scope_detail": "", + "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" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "partial", + "mapping_scope_detail": "Returns all principals with DCSync privileges.", + "id": "bcb85336-3507-4565-91a2-9c1360c5a5f1", + "name": "Non-default principals with DC Sync rights on the domain" + } + ] } ] }, @@ -2091,6 +2667,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": "0d9236c4-98a1-4763-913b-783fdfe1de4c", + "name": "SMB Signing is not required on Domain Controllers" + } + ] } ] }, @@ -2110,6 +2697,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": "dfa45647-c2b5-4066-b7db-3a8890868cef", + "name": "Users with the attribute userPassword set" + } + ] } ] }, @@ -2129,6 +2727,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": "aace861d-9d2c-47df-9d3a-eb8f07008abb", + "name": "Anonymous NSPI access to AD enabled" + } + ] } ] }, @@ -2148,6 +2757,17 @@ "name": "[T]Check for access without any account via a forest wide setting" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "79d857a6-23a7-421d-a63d-5a2f9df5d080", + "name": "Anonymous access to Active Directory enabled" + } + ] } ] }, @@ -2174,7 +2794,7 @@ { "mapping_scope": "exact", "mapping_scope_detail": "", - "id": "Null sessions", + "id": "https://www.tenable.com/plugins/nessus/150488", "name": "Null sessions" } ] @@ -2254,6 +2874,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": "c0c929cf-ab95-4d8f-975b-8193054f520b", + "name": "Changes to Pre-Windows 2000 Compatible Access Group membership" + } + ] } ] }, @@ -2268,11 +2899,22 @@ "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" } ] + }, + { + "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": "e08bbf6a-b17e-4417-a9cd-8f4b7b6210f9", + "name": "Unprivileged accounts with adminCount=1" + } + ] } ] }, @@ -2322,6 +2964,727 @@ "name": "Built-in Active Directory Guest account is enabled" } ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "exact", + "mapping_scope_detail": "", + "id": "d14af45f-009c-4840-8e35-36a97c979a8c", + "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": "3bcaff82-ae70-4646-bc66-f7997be54e5e", + "name": "Foreign Security Principals in Privileged Group" + } + ] + } + ] + }, + { + "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": "93402830-3bdf-4086-8629-7bdc654651f9", + "name": "gMSA not in use" + } + ] + } + ] + }, + { + "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": "b3061191-07e0-468f-b2a0-bbf0485fa900", + "name": "AD objects created within the last 10 days" + } + ] + } + ] + }, + { + "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": "c4218bf3-aca4-4d17-90a3-9ba3f4ec42e7", + "name": "Recent privileged account creation activity" + } + ] + } + ] + }, + { + "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": "216596bf-333e-4f59-a3f5-8af65acbba9b", + "name": "Inheritance enabled on AdminSDHolder object" + } + ] + } + ] + }, + { + "bloodhound_query": { + "guid": "99d29ded-223a-442b-a0e0-f8b5694c6441", + "name": "Tier Zero principals not owned by Tier Zero" + }, + "maps_to": [ + { + "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": "d2df85d9-abbc-4585-be11-123a6d90a871", + "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": "40499bf5-9087-4d55-9db3-2cb641a47ac8", + "name": "Privileged objects with unprivileged owners" + } + ] + } + ] + }, + { + "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": "5f6540a9-f226-4438-9e64-387a98beda2b", + "name": "AD privileged users that are synced to Entra ID" + } + ] + }, + { + "source": "PurpleKnight", + "controls": [ + { + "mapping_scope": "combination", + "mapping_scope_detail": "Finds the direction AD -> Entra only.", + "id": "c117b6a5-00c8-4e77-93d2-e291e36b462a", + "name": "Entra ID 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": "combination", + "mapping_scope_detail": "Finds the direction Entra -> AD only.", + "id": "c117b6a5-00c8-4e77-93d2-e291e36b462a", + "name": "Entra ID privileged users that are also privileged in AD" + } + ] + } + ] + }, + { + "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": "combination", + "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": "combination", + "mapping_scope_detail": "Group delegated role assignments only.", + "id": "Hybrid-synced privileged role accounts", + "name": "Hybrid-synced privileged role accounts" + } + ] + } + ] + }, + { + "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": "0e61db1b-b877-457e-9550-607330143d92", + "name": "Distributed COM Users group or Performance Log Users group are not empty" + } + ] + } + ] + }, + { + "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": "e441aeb0-ba69-426b-bbc1-028bf258c3d8", + "name": "Dangerous control paths expose certificate containers" + } + ] + } + ] + }, + { + "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, 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" + } + ] + } + ] + }, + { + "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, instead of just the AllowedToAct edge. Expanded target scope to all KRBTGT account types.", + "id": "cfe3bfa1-f28e-4017-8e94-044bd6b914e3", + "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": "eb99e786-3ce1-4172-9801-dd4203cfd3e2", + "name": "krbtgt account with Resource-Based Constrained Delegation (RBCD) enabled" + } + ] + } + ] + }, + { + "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": "87081486-fc4f-4027-842e-c5f17ec4f1bf", + "name": "Privileged users that are disabled" + } + ] + } + ] + }, + { + "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": "5e0a22b9-2da5-4fde-9de6-e3a77961d6e6", + "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": "af90ba77-f497-479a-aa57-ed106191e302", + "name": "Write access to RBCD on DC" + } + ] + } + ] + }, + { + "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": "", + "id": "b5df966a-2202-401e-8c0c-0e212d7f666d", + "name": "Objects in privileged groups without adminCount=1 (SDProp)" + } + ] + } + ] + }, + { + "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" + } + ] + } + ] + }, + { + "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" + } + ] + } + ] + }, + { + "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" + } + ] + } + ] + }, + { + "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" + } + ] + } + ] + }, + { + "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" + }, + { + "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" + } + ] + } + ] + }, + { + "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" + } + ] + } + ] + }, + { + "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" + } + ] + } + ] + }, + { + "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" + } + ] + } + ] + }, + { + "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" + } + ] + } + ] + }, + { + "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; manual review is required to identify unexpected assignments.", + "id": "0e9a08c9-9d01-4c87-9c01-5bad3b23a2dd", + "name": "Suspicious Directory Synchronization Accounts role member" + } + ] + } + ] + }, + { + "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" + } + ] + } + ] + }, + { + "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; the query covers only users.", + "id": "47bfb163-2e5d-42c8-9218-0b1e9609d0f6", + "name": "Users or devices inactive for at least 90 days" + } + ] + } + ] + }, + { + "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" + } + ] + } + ] + }, + { + "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" + } + ] + } + ] + }, + { + "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": "Expanded scope to Tier Zero. Includes SyncLAPSPassword", + "id": "9f532969-6a43-40a5-9035-f4f2e9cf9e88", + "name": "Changes to MS LAPS read permissions" + } + ] } ] } diff --git a/docs/security-assessment-mapping.md b/docs/security-assessment-mapping.md index bd52596..5829bfa 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. @@ -14,15 +14,16 @@ 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/) | 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% | ## 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. 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..e6f6dfb --- /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 +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: |- + 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 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 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 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/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/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 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 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..59bb068 --- /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 +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: |- + 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 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 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 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: 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..c3b4e89 --- /dev/null +++ b/queries/Computers with local administrator permissions over other computers.yml @@ -0,0 +1,17 @@ +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 membership in default privileged groups.yml b/queries/Computers with membership in default privileged groups.yml new file mode 100644 index 0000000..3903c89 --- /dev/null +++ b/queries/Computers with membership in default privileged groups.yml @@ -0,0 +1,30 @@ +name: Computers with membership in default 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 + diff --git a/queries/Computers with non-default Primary Group membership.yml b/queries/Computers with non-default Primary Group membership.yml index 4e798d5..2950851 100644 --- a/queries/Computers with non-default Primary Group membership.yml +++ b/queries/Computers with non-default Primary Group membership.yml @@ -1,19 +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: |- - 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 -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/Domains affected by AdPrep privilege escalation risk.yml b/queries/Domains affected by AdPrep privilege escalation risk.yml index 694d2a8..3bce0c2 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 +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 +- https://mskb.pkisolutions.com/kb/4469393 acknowledgements: Martin Sohn Christensen, @martinsohndk 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 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 + 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..4b5837e --- /dev/null +++ b/queries/Enabled Entra ID guest users inactive for 30 days.yml @@ -0,0 +1,25 @@ +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 ( + // 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 +resources: +acknowledgements: Martin Sohn Christensen, @martinsohndk + 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 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 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 + 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 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..3f5cdd9 --- /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 +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: |- + 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 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: + 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/Non-Tier Zero account with unconstrained delegation.yml b/queries/Non-Tier Zero account with unconstrained delegation.yml index 1492393..8d4b613 100644 --- a/queries/Non-Tier Zero account with unconstrained delegation.yml +++ b/queries/Non-Tier Zero account with unconstrained delegation.yml @@ -1,20 +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: 2 -resources: -acknowledgements: Martin Sohn Christensen, @martinsohndk - +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 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/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..40d4b5c --- /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 +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 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 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 new file mode 100644 index 0000000..7e98917 --- /dev/null +++ b/queries/Non-Tier Zero principals with access to enabled gMSA passwords.yml @@ -0,0 +1,26 @@ +name: Non-Tier Zero principals with access to enabled gMSA passwords +guid: 10d0ee8e-17ec-4f6c-9b94-8dffe548f9d4 +prebuilt: false +platforms: Active Directory +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 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 ((s1:Tag_Tier_Zero) OR COALESCE(s1.system_tags, '') CONTAINS 'admin_tier_0') + 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: 2 +resources: + - https://learn.microsoft.com/en-us/windows-server/security/group-managed-service-accounts/group-managed-service-accounts-overview +acknowledgements: +- Martin Sohn Christensen, @martinsohndk +- crusher, @chryzsh \ 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..9151337 --- /dev/null +++ b/queries/Non-Tier Zero users that can read LAPS passwords.yml @@ -0,0 +1,17 @@ +name: Non-Tier Zero users that can read LAPS passwords +guid: d1779573-7ad5-4a78-a5e5-9dd53a1eb1a1 +prebuilt: false +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: |- + 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') + RETURN p + LIMIT 1000 +revision: 1 +resources: + - https://learn.microsoft.com/en-us/windows-server/identity/laps/laps-overview +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 new file mode 100644 index 0000000..2b96e15 --- /dev/null +++ b/queries/Objects created in the past 10 days.yml @@ -0,0 +1,15 @@ +name: Objects created in the past 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 + 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/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..c149000 --- /dev/null +++ b/queries/Principals that can write Shadow Credentials on Tier Zero principals.yml @@ -0,0 +1,15 @@ +name: Principals that can 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 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 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 + 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..905e097 --- /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: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 +acknowledgements: Martin Sohn Christensen, @martinsohndk + 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 + 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..acfd963 --- /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 +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/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 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..30c54f8 --- /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: 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 new file mode 100644 index 0000000..18505bb --- /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: 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 new file mode 100644 index 0000000..501e7f7 --- /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: 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 new file mode 100644 index 0000000..397a843 --- /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: crusher, @chryzsh 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: 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 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 + 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..f592864 --- /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 +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: |- + 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 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 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 + 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 60% rename from queries/Tier Zero computers not owned by Tier Zero.yml rename to queries/Tier Zero principals not owned by Tier Zero.yml index 57872e2..b45a400 100644 --- a/queries/Tier Zero computers not owned by Tier Zero.yml +++ b/queries/Tier Zero principals 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)-[: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 -revision: 2 +revision: 3 resources: acknowledgements: Martin Sohn Christensen, @martinsohndk