Skip to content

Conversation

@ncha-syn
Copy link

@ncha-syn ncha-syn commented Oct 24, 2025

Description

This pull request adds the ability to SharpHound to collect user privileges using GPOs and increases of the scope of the collected privileges.

Motivation and Context

This PR follows SpecterOps/BloodHound#1999.

How Has This Been Tested?

The changes have been tested manually on a lab environment.

Screenshots (if appropriate):

image

Types of changes

  • New feature (non-breaking change which adds functionality)

Checklist:

  • Documentation updates are needed, and have been made accordingly.
  • I have added and/or updated tests to cover my changes.
  • All new and existing tests passed.
  • My changes include a database migration.

@coderabbitai
Copy link

coderabbitai bot commented Oct 24, 2025

Walkthrough

This pull request adds GPO-based user rights collection capability to the codebase. Changes include a new collection method enum value, expanded privilege definitions, updated LDAP query generation, new output types for storing GPO user rights data, and a processor that extracts privilege assignments from Group Policy template files and maps them to affected computers.

Changes

Cohort / File(s) Summary
Enum & Privilege Definitions
src/CommonLib/Enums/CollectionMethod.cs, src/CommonLib/Enums/LSAPrivileges.cs
Added GPOUserRights enum value (1 << 24) to CollectionMethod with updated bitmasks (DCOnly, All). Expanded LSAPrivileges.DesiredPrivileges from single RemoteInteractiveLogon to 12-element array including InteractiveLogon, AssignPrimaryToken, Backup, CreateToken, Debug, Impersonate, LoadDriver, ManageVolume, Restore, TakeOwnership, and Tcb.
LDAP Query & Property Configuration
src/CommonLib/LdapProducerQueryGenerator.cs, src/CommonLib/LdapQueries/CommonProperties.cs, src/CommonLib/Processors/LdapPropertyProcessor.cs
Added GPOUserRights property collection to LDAP query generation with OU-based filtering. Created GPOUserRights property array in CommonProperties containing GPLink and Name. Added GPOUserRights to reserved attributes in LdapPropertyProcessor.
Output Types
src/CommonLib/OutputTypes/ResultingGPOUserRights.cs, src/CommonLib/OutputTypes/OU.cs
Created new ResultingGPOUserRights class containing AffectedComputers array and UserRightAssignments dictionary mapping privilege names to principals. Added GPOUserRights field to OU class.
GPO User Rights Processing
src/CommonLib/Processors/GPOUserRightsProcessor.cs
Implemented GPOUserRightsAssignmentProcessor to parse GptTmpl.inf template files, extract privilege rights with member SIDs, resolve accounts to TypedPrincipal objects, apply last-write-wins ordering semantics across GPO links with per-link caching, and produce ResultingGPOUserRights output.
Processor Maintenance
src/CommonLib/Processors/GPOLocalGroupProcessor.cs, src/CommonLib/Processors/UserRightsAssignmentProcessor.cs
Minor whitespace and formatting adjustments to GPOLocalGroupProcessor and UserRightsAssignmentProcessor.

Sequence Diagram

sequenceDiagram
    actor Consumer
    participant GOURP as GPOUserRights<br/>Processor
    participant LDAP as LDAP Utils
    participant FS as File System
    participant Cache as GPO Cache

    Consumer->>GOURP: ReadGPOUserRights(entry)
    GOURP->>LDAP: Extract GPLink & OUs
    GOURP->>LDAP: Query computers under OU
    alt No computers found
        GOURP-->>Consumer: Return empty result
    end
    GOURP->>GOURP: Parse GPLink (unenforced + enforced)
    loop For each GPO link
        GOURP->>Cache: Check cached privileges
        alt Not cached
            GOURP->>FS: Read GptTmpl.inf
            GOURP->>GOURP: Parse [Privilege Rights]
            loop For each privilege entry
                GOURP->>LDAP: Resolve SIDs/accounts
                GOURP->>GOURP: Yield PrivilegeAction
            end
            GOURP->>Cache: Store results
        end
    end
    GOURP->>GOURP: Aggregate privileges (last-write-wins)
    GOURP->>Consumer: Return ResultingGPOUserRights
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Multiple files with diverse responsibilities (enums, query generation, output types, complex processor logic) requiring separate reasoning for each area. The GPOUserRightsAssignmentProcessor introduces substantial new logic for template file parsing, SID resolution, caching, and aggregation semantics that demands careful review.

Possibly related issues

  • Feature: Collect local user privileges on machines BloodHound#1998: This PR directly implements the infrastructure for collecting local user privileges, expanding LSAPrivileges.DesiredPrivileges definitions and introducing GPO-based user rights collection across the full pipeline (query generation, output types, and processing), which aligns with the objectives of broader privilege enumeration support.

Poem

🐰 Hops through the GPO trees so green,
Where user rights were never seen,
Template files parsed with care,
Privileges mapped everywhere,
Computers now know what they share! 🎀

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings, 1 inconclusive)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The pull request description significantly lacks the required content compared to the template. The "Description" section is completely empty with no explanation of the actual changes made. While the "Motivation and Context" section references a related PR (#1999), it provides no explanation of what problem this PR solves or what has been implemented. The "How Has This Been Tested" section provides only a brief statement about manual lab testing without the requested details about the testing environment or test coverage. Additionally, the "Types of changes" checkboxes are not marked despite this clearly being a new feature, and all checklist items remain unchecked. The description must be substantially expanded to meet the template requirements. Add a detailed description of all changes in the "Description" section, provide context and rationale for the changes, include comprehensive testing details (environment, test cases, coverage), mark the appropriate checkbox for "New feature" in the Types of changes section, and fill in the applicable checklist items to reflect the completion status of documentation, tests, and other requirements.
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title Check ❓ Inconclusive The title "Add local privilege collection" is related to the main changes but lacks specificity regarding the implementation approach. The changeset is specifically about adding GPO-based user rights collection capabilities, as evidenced by the introduction of GPOUserRightsAssignmentProcessor, GPOUserRights enum member, and supporting data structures. However, the title uses the broader term "local privilege collection" without clarifying that this is GPO-specific, making it somewhat vague about the actual mechanism. While the title captures the general purpose, a more precise title would better communicate the GPO-focused nature of the implementation. Consider revising the title to be more specific about the GPO context, such as "Add GPO-based user rights collection" or "Add local privilege collection via GPO," to provide clearer communication about the implementation's primary mechanism while still keeping it concise.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
src/CommonLib/Processors/GPOLocalGroupProcessor.cs (1)

60-60: Remove trailing whitespace.

The method signature has trailing whitespace that should be removed for consistency with coding standards.

src/CommonLib/Processors/GPOUserRightsProcessor.cs (1)

125-159: Consider caching LDAP query failures to avoid repeated retries.

Lines 139-141 skip caching when LDAP queries fail, causing the same GPO to be re-queried on subsequent calls. While not a correctness issue, caching failed results could improve performance.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2069840 and 156a252.

📒 Files selected for processing (10)
  • src/CommonLib/Enums/CollectionMethod.cs (1 hunks)
  • src/CommonLib/Enums/LSAPrivileges.cs (1 hunks)
  • src/CommonLib/LdapProducerQueryGenerator.cs (2 hunks)
  • src/CommonLib/LdapQueries/CommonProperties.cs (1 hunks)
  • src/CommonLib/OutputTypes/OU.cs (1 hunks)
  • src/CommonLib/OutputTypes/ResultingGPOUserRights.cs (1 hunks)
  • src/CommonLib/Processors/GPOLocalGroupProcessor.cs (1 hunks)
  • src/CommonLib/Processors/GPOUserRightsProcessor.cs (1 hunks)
  • src/CommonLib/Processors/LdapPropertyProcessor.cs (1 hunks)
  • src/CommonLib/Processors/UserRightsAssignmentProcessor.cs (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (8)
src/CommonLib/Processors/GPOLocalGroupProcessor.cs (1)
src/CommonLib/OutputTypes/ResultingGPOChanges.cs (1)
  • ResultingGPOChanges (5-12)
src/CommonLib/OutputTypes/OU.cs (3)
src/CommonLib/OutputTypes/OutputBase.cs (1)
  • OutputBase (9-17)
src/CommonLib/OutputTypes/ResultingGPOChanges.cs (1)
  • ResultingGPOChanges (5-12)
src/CommonLib/OutputTypes/ResultingGPOUserRights.cs (1)
  • ResultingGPOUserRights (6-13)
src/CommonLib/OutputTypes/ResultingGPOUserRights.cs (1)
src/CommonLib/Processors/GPOLocalGroupProcessor.cs (1)
  • TypedPrincipal (514-519)
src/CommonLib/LdapQueries/CommonProperties.cs (1)
src/CommonLib/Enums/LDAPProperties.cs (1)
  • LDAPProperties (3-99)
src/CommonLib/Enums/LSAPrivileges.cs (1)
src/CommonLib/Impersonate.cs (1)
  • Impersonate (131-164)
src/CommonLib/Processors/LdapPropertyProcessor.cs (1)
src/CommonLib/LdapQueries/CommonProperties.cs (1)
  • CommonProperties (3-105)
src/CommonLib/LdapProducerQueryGenerator.cs (1)
src/CommonLib/LdapQueries/CommonProperties.cs (1)
  • CommonProperties (3-105)
src/CommonLib/Processors/GPOUserRightsProcessor.cs (5)
src/CommonLib/Logging/Logging.cs (3)
  • Logging (7-20)
  • ILogger (31-34)
  • LogProvider (22-35)
src/CommonLib/OutputTypes/ResultingGPOUserRights.cs (1)
  • ResultingGPOUserRights (6-13)
src/CommonLib/Enums/LDAPProperties.cs (1)
  • LDAPProperties (3-99)
src/CommonLib/Helpers.cs (1)
  • DistinguishedNameToDomain (135-151)
src/CommonLib/LdapQueries/CommonProperties.cs (1)
  • CommonProperties (3-105)
🔇 Additional comments (15)
src/CommonLib/Enums/CollectionMethod.cs (1)

30-30: LGTM! GPOUserRights flag is correctly integrated.

The new collection method flag is properly defined with a unique bit position (1 << 24) and correctly included in both the DCOnly and All composite flags, following the established pattern for GPO-related collection methods.

Also applies to: 35-35, 40-41

src/CommonLib/Processors/LdapPropertyProcessor.cs (1)

34-34: LGTM! Reserved attribute registration is correct.

The GPOUserRights property is properly registered as a reserved attribute, ensuring it's excluded from generic property parsing. This follows the established pattern for special-purpose LDAP properties.

src/CommonLib/Processors/UserRightsAssignmentProcessor.cs (1)

216-216: Formatting-only change.

This is a minor formatting adjustment with no functional impact.

src/CommonLib/OutputTypes/OU.cs (1)

9-9: LGTM! New field follows established pattern.

The GPOUserRights field is properly added alongside the existing GPOChanges field, maintaining consistency in how GPO-related results are stored in OUs.

src/CommonLib/LdapQueries/CommonProperties.cs (1)

89-91: LGTM! Property set correctly defined.

The GPOUserRights property array is properly defined with the necessary LDAP properties (GPLink and Name). Using the same properties as GPOLocalGroupProps is appropriate since both features read from GPO configurations.

src/CommonLib/LdapProducerQueryGenerator.cs (2)

47-48: LGTM! GPOUserRights correctly integrated into comprehensive collection path.

The GPOUserRights properties are properly added when the flag is set, following the same pattern as GPOLocalGroup.


86-89: LGTM! GPOUserRights correctly integrated into targeted collection path.

The filter and properties are properly configured for GPOUserRights-only collection, mirroring the established pattern for GPOLocalGroup collection.

src/CommonLib/OutputTypes/ResultingGPOUserRights.cs (1)

6-13: LGTM! Well-designed data container.

The ResultingGPOUserRights class provides a clean structure for GPO user rights collection results. The design follows the established pattern of ResultingGPOChanges, with appropriate default initializations to prevent null reference issues. The inline comment helpfully clarifies the dictionary's purpose.

src/CommonLib/Enums/LSAPrivileges.cs (1)

50-50: LGTM: Privilege expansion aligns with GPO user rights collection.

The expanded privilege set includes security-critical rights appropriate for monitoring and analysis. All referenced constants are properly defined.

src/CommonLib/Processors/GPOUserRightsProcessor.cs (6)

17-36: LGTM: Well-structured processor initialization.

The use of compiled regex patterns, thread-safe caching, and dependency injection with logger fallback follows best practices.


72-98: LGTM: Good optimization to check for affected computers early.

Querying computers first and returning early if none exist avoids unnecessary GPO processing.


100-118: GPLink ordering correctly implements precedence rules.

The code properly orders unenforced GPOs before enforced ones, ensuring enforced GPOs override unenforced ones with the last-write-wins processing at line 175.


196-266: LGTM: Template file parsing correctly handles GptTmpl.inf format.

The method properly extracts privilege rights sections, parses the INI-style key=value format, handles asterisk-prefixed SIDs, and resolves principals with appropriate error handling.


268-304: LGTM: SID resolution handles multiple account formats correctly.

The method appropriately handles DOMAIN\user format, plain usernames, computer account fallback (with $ suffix), and direct SID resolution.


182-185: No issues found—TypedPrincipal equality implementation correctly supports Distinct().

The TypedPrincipal class properly implements both Equals() and GetHashCode() with consistent logic. The Equals() method compares ObjectIdentifier and ObjectType, and the GetHashCode() method combines their hashes using a standard pattern. This implementation correctly supports deduplication via Distinct().

@@ -1,10 +1,12 @@
using System;
using SharpHoundCommonLib.Processors;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Remove unnecessary using directive.

The using SharpHoundCommonLib.Processors directive appears unused. ResultingGPOUserRights is in the SharpHoundCommonLib.OutputTypes namespace (same as this file), so this using directive is not needed.

🤖 Prompt for AI Agents
In src/CommonLib/OutputTypes/OU.cs around lines 1 to 1, remove the unused using
directive "using SharpHoundCommonLib.Processors" because ResultingGPOUserRights
resides in the same SharpHoundCommonLib.OutputTypes namespace; simply delete
that using line from the top of the file so only necessary directives remain.

Comment on lines +1 to +14
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using SharpHoundCommonLib.Enums;
using SharpHoundCommonLib.LDAPQueries;
using SharpHoundCommonLib.OutputTypes;
using System.DirectoryServices.Protocols;
using System.Drawing.Printing;

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove unused import.

The System.Drawing.Printing namespace (Line 13) is unused and unrelated to GPO user rights processing.

Apply this diff:

 using System.DirectoryServices.Protocols;
-using System.Drawing.Printing;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using SharpHoundCommonLib.Enums;
using SharpHoundCommonLib.LDAPQueries;
using SharpHoundCommonLib.OutputTypes;
using System.DirectoryServices.Protocols;
using System.Drawing.Printing;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using SharpHoundCommonLib.Enums;
using SharpHoundCommonLib.LDAPQueries;
using SharpHoundCommonLib.OutputTypes;
using System.DirectoryServices.Protocols;
🤖 Prompt for AI Agents
In src/CommonLib/Processors/GPOUserRightsProcessor.cs around lines 1 to 14,
remove the unused and unrelated using directive for System.Drawing.Printing
(line 13). Edit the top-of-file using list to delete that import so only
required namespaces remain; run a build or IDE analyzer to confirm no other
references to that namespace exist.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant