Skip to content

Conversation

@JimSycurity
Copy link
Contributor

@JimSycurity JimSycurity commented Jul 28, 2025

  • Add class to represent ACE for hashing
  • Add method to calculate SHA1 hash of authoritative SD
  • Add method to determine if the SD hash of the provided object matches the AdminSDHolder SD hash and set 'adminsdholderprotected' if true
  • Add adminCount property to Computer nodes
  • Add mock tests

Resolves BED-6221

Description

These SharpHoundCommon feature updates implement features required by SharpHound and SharpHoundEnterprise to produce the adminsdholderprotected property on user, computer, and group nodes as part of the AdminSDHolder design.

Adds 2 methods to ACLProcessor class:

  • generate a SHA1 hash of an authoritative security descriptor (which consists of the SD flags and implicit ACEs in the DACL)
  • compare a provided hash for the AdminSDHolder container for the domain with the user, group, or computer node being processed to determine if adminsdholderprotected should be set to true or false

Added tests to ACLProcessorTests.

Added admincount property to computer nodes as computers and computer-derived objects like (g/d)MSAs can be protected by AdminSDHolder.

Motivation and Context

This code lays the foundation for SharpHound and SharpHoundEnterprise to set the adminsdholderprotected property, which will then enable graph expansion for AdminSDHolder in BloodHound and ultimately better findings in BHE.

Issue: https://specterops.atlassian.net/browse/BED-6178

This PR needs to be merged into a SharpHoundCommon version update prior to corresponding PRs for SharpHound and SharpHoundEnterprise being merged. Those PRs should correspond to a BHCE and BHE version update with the graph expansion changes.

How Has This Been Tested?

Code was first tested using debug functionality in Rider.

Code was tested as part of custom builds of both SharpHound and SharpHound Enterprise from the corresponding BED-6178 branches in my home lab environment using multiple AD forests configured for AdminSDHolder research. Code was also tested as part of SharpHound FOSS by Jonas in the Dev GOAD Azure lab.

Tests in ACLProcessorTests are largely Windows-specific. All tests were performed on a Windows 11 PC in Rider and via dotnet test:
image

All tests were also performed in my WSL2 dev instance, but windows-specific tests were skipped:
image

I also tested this SharpHoundCommon version with the main branch of SharpHound FOSS of version 2.6.7 before rebasing to 2.7.0. In that test in my lab environment there were no negative effects of running this SHC version with a SharpHound FOSS version that does not explicitly support it as none of the methods were called.

I also tested SharpHoundEnterprise with a bhe-dev environment running on my BED-6178 branch. The SHDelegator is running in my home lab and provided accurate data to the bhe-dev build.

Screenshots (if appropriate):

Types of changes

  • Chore (a change that does not modify the application functionality)
  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

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.

Summary by CodeRabbit

  • New Features

    • Added detection of AdminSDHolder protection via hash comparison in ACL processing.
    • Introduced calculation of implicit ACL hashes for security descriptors.
    • Added retrieval and processing of the AdminCount LDAP property for computer objects.
  • Bug Fixes

    • Corrected minor formatting and whitespace issues in various components.
  • Tests

    • Added new tests for AdminSDHolder protection detection and ACL hash calculation to ensure accuracy and reliability.

@JimSycurity JimSycurity self-assigned this Jul 28, 2025
@coderabbitai
Copy link

coderabbitai bot commented Jul 28, 2025

Walkthrough

The changes introduce new functionality in the ACLProcessor to detect AdminSDHolder protection by hashing implicit ACEs and comparing them to a reference hash. Supporting methods and an internal class for ACE hashing are added, with new tests verifying hash calculation and protection detection. Minor whitespace and formatting adjustments are also included.

Changes

Cohort / File(s) Change Summary
ACLProcessor AdminSDHolder protection & hashing
src/CommonLib/Processors/ACLProcessor.cs
Added AdminSDHolder protection detection via ACE hashing, new internal class for ACE hashing, new public methods for hash calculation and protection checks, extensive logging, and minor formatting/comment fixes.
ACLProcessor hashing & protection tests
test/unit/ACLProcessorTest.cs
Added tests for implicit ACL hash calculation and AdminSDHolder protection detection, introduced a base64 security descriptor constant, and performed minor whitespace fixes.
LDAP property processor minor fixes
src/CommonLib/Processors/LdapPropertyProcessor.cs
Added retrieval of the AdminCount LDAP property, minor whitespace and formatting adjustments.
Whitespace/comment cleanup
src/CommonLib/AdaptiveTimeout.cs
Removed a trailing blank line inside a comment block; no functional changes.

Sequence Diagram(s)

sequenceDiagram
    participant Caller
    participant ACLProcessor
    participant SecurityDescriptor

    Caller->>ACLProcessor: IsAdminSDHolderProtected(securityDescriptor, adminSdHolderHash)
    ACLProcessor->>ACLProcessor: CalculateImplicitACLHash(securityDescriptor)
    ACLProcessor->>SecurityDescriptor: Extract ACEs and DACL protection
    SecurityDescriptor-->>ACLProcessor: ACEs, DACL protection
    ACLProcessor->>ACLProcessor: Hash ACEs + DACL protection
    ACLProcessor-->>Caller: Compare hash to adminSdHolderHash, return true/false/null
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~15–20 minutes

Poem

A rabbit hops through code so neat,
With hashes, ACEs, tests complete!
AdminSDHolder’s secrets found,
Protection checks now safe and sound.
Whitespace trimmed, the logic clear—
This code’s a joy to burrow near!
🐇✨

Note

⚡️ Unit Test Generation is now available in beta!

Learn more here, or try it out under "Finishing Touches" below.


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0bf150c and 4cc4846.

📒 Files selected for processing (4)
  • src/CommonLib/AdaptiveTimeout.cs (1 hunks)
  • src/CommonLib/Processors/ACLProcessor.cs (6 hunks)
  • src/CommonLib/Processors/LdapPropertyProcessor.cs (3 hunks)
  • test/unit/ACLProcessorTest.cs (24 hunks)
✅ Files skipped from review due to trivial changes (1)
  • src/CommonLib/AdaptiveTimeout.cs
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/CommonLib/Processors/LdapPropertyProcessor.cs
  • src/CommonLib/Processors/ACLProcessor.cs
  • test/unit/ACLProcessorTest.cs
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch jsykora/BED-6178

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@definitelynotagoblin definitelynotagoblin self-requested a review July 28, 2025 18:53
@JimSycurity JimSycurity marked this pull request as ready for review July 28, 2025 19:38
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: 4

🧹 Nitpick comments (4)
test/unit/ACLProcessorTest.cs (1)

1435-1459: Simplify test setup and remove unused variables.

While this test correctly validates that different inputs produce unique hashes, it has some issues:

  1. Unused variables: expectedPrincipalType, expectedPrincipalSID, and expectedRightName are defined but not used in assertions
  2. Overly complex mock setup: For testing hash uniqueness, the extensive mocking seems unnecessary

Consider simplifying to focus on the core hash functionality:

 [WindowsOnlyFact]
 public void ACLProcessor_CalculateImplicitACLHash_DifferentInputs_ProducesUniqueHashes()
 {
     // Setup
-    var expectedPrincipalType = Label.CertTemplate;
-    var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512";
-    var expectedRightName = EdgeNames.WritePKINameFlag;
     var mockLDAPUtils = new Mock<ILdapUtils>();
     var sd = new ActiveDirectorySecurityDescriptor(new ActiveDirectorySecurity());
     mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(sd);
-    mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny<string>(), It.IsAny<string>()))
-        .ReturnsAsync((true, new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)));
-    mockLDAPUtils.Setup(x => x.PagedQuery(It.IsAny<LdapQueryParameters>(), It.IsAny<CancellationToken>()))
-        .Returns(Array.Empty<LdapResult<IDirectoryObject>>().ToAsyncEnumerable);
     var proc = new ACLProcessor(mockLDAPUtils.Object);
src/CommonLib/Processors/ACLProcessor.cs (3)

294-304: Consider optimizing the sorting approach for large ACLs

The TODO comment correctly identifies potential inefficiency. Creating intermediate objects and sorting with multiple criteria could be expensive for objects with many ACEs.

Consider these optimizations:

  1. Use a custom IComparer to avoid multiple sorting passes
  2. Consider serializing directly to bytes instead of string concatenation
  3. Use a more memory-efficient approach for large ACLs

Example custom comparer:

private class ACEForHashingComparer : IComparer<ACEForHashing> {
    public int Compare(ACEForHashing x, ACEForHashing y) {
        int result = x.AccessControlType.CompareTo(y.AccessControlType);
        if (result != 0) return result;
        result = string.Compare(x.IdentityReference, y.IdentityReference, StringComparison.Ordinal);
        if (result != 0) return result;
        // ... continue for other properties
        return result;
    }
}

312-312: Consider basing StringBuilder capacity on actual ACE string length

The hardcoded estimate of 50 characters per ACE might be too low or too high depending on the actual data.

Consider calculating a more accurate estimate:

-var stringBuilder = new StringBuilder(sortedAces.Count * 50); // Pre-allocate with estimated capacity  TODO: is this a good estimate?
+// Calculate more accurate capacity based on first ACE or use a conservative estimate
+var estimatedCapacity = sortedAces.Count > 0 ? sortedAces[0].ToString().Length * sortedAces.Count * 1.2 : 1024;
+var stringBuilder = new StringBuilder((int)estimatedCapacity);

847-847: Address TODO: Extract duplicated CA rights checks to shared method

The TODO comment indicates code duplication between this method and ProcessRegistryEnrollmentPermissions.

Would you like me to help refactor these CA rights checks into a shared method to eliminate the duplication and improve maintainability?

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bea975e and f23b9e0.

📒 Files selected for processing (4)
  • src/CommonLib/AdaptiveTimeout.cs (1 hunks)
  • src/CommonLib/Processors/ACLProcessor.cs (6 hunks)
  • src/CommonLib/Processors/LdapPropertyProcessor.cs (3 hunks)
  • test/unit/ACLProcessorTest.cs (25 hunks)
🧰 Additional context used
🧠 Learnings (1)
src/CommonLib/AdaptiveTimeout.cs (2)

Learnt from: definitelynotagoblin
PR: #222
File: src/CommonLib/Processors/LocalGroupProcessor.cs:19-27
Timestamp: 2025-07-15T17:45:25.688Z
Learning: In SharpHoundCommon, the team prefers to keep code simple rather than implement perfect resource management when the resources being managed are non-critical. Specifically, they accept not implementing IDisposable for AdaptiveTimeout instances when the Dispose method is primarily for flushing analytics logs from ExecutionTimeSampler, viewing it as a courtesy rather than a safety requirement.

Learnt from: definitelynotagoblin
PR: #217
File: src/CommonLib/Timeout.cs:17-36
Timestamp: 2025-06-26T16:38:49.677Z
Learning: In SharpHoundCommonLib's Timeout.cs, the team initially had concerns about disposing CancellationTokenSource instances before tasks could check cancellation tokens, but they understand that orphaned tasks (those that exceed timeout) won't be impacted by disposed tokens since their results are already ignored by the timeout handler. They prefer proper resource management with using statements for CancellationTokenSource disposal.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (7)
src/CommonLib/AdaptiveTimeout.cs (1)

323-323: LGTM: Minor whitespace cleanup in comment block.

The removal of the trailing blank line in the comment example improves consistency without affecting functionality.

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

123-123: LGTM: Formatting improvement.

Removed extra space in the if condition for better code consistency.


368-370: LGTM: AdminCount property addition supports AdminSDHolder detection.

The addition of the admincount property with boolean conversion (ac != 0) correctly implements the LDAP AdminCount attribute retrieval for computer objects. This aligns with the PR's objective to support AdminSDHolder protection detection functionality.


945-945: LGTM: Minor whitespace cleanup.

Removed unnecessary blank line in the enum declaration area for better code consistency.

test/unit/ACLProcessorTest.cs (2)

35-36: LGTM! AdminSDHolder test fixture properly defined.

The new AdminSDHolderSecurityDescriptor constant follows the established pattern of other security descriptor constants in the test file and provides necessary test data for the AdminSDHolder functionality.


1417-1433: LGTM! Well-structured test for hash calculation.

This test method effectively validates the CalculateImplicitACLHash functionality with:

  • Clear arrange-act-assert structure
  • Appropriate use of [WindowsOnlyFact] for platform-specific functionality
  • Proper mock setup and assertion against expected SHA1 hash value
src/CommonLib/Processors/ACLProcessor.cs (1)

47-63: Add null safety to ToString() method

The ToString() method doesn't handle null values for IdentityReference, ObjectType, or InheritedObjectType. This could lead to null reference exceptions during hash calculation.

Apply this diff to add null safety:

 public override string ToString() {
-    return $"{IdentityReference}|{Rights}|{AccessControlType}|{ObjectType}|{InheritedObjectType}|{InheritanceFlags}";
+    return $"{IdentityReference ?? ""}|{Rights}|{AccessControlType}|{ObjectType ?? ""}|{InheritedObjectType ?? ""}|{InheritanceFlags}";
 }

Likely an incorrect or invalid review comment.

Copy link
Contributor

@mykeelium mykeelium left a comment

Choose a reason for hiding this comment

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

Looks excellent!! Great work on this, it is fantastic how much you put into this

definitelynotagoblin and others added 4 commits July 29, 2025 11:57
- Add class to represent ACE for hashing
- Add method to determine if the SD hash of the provided object matches
  the AdminSDHolder SD hash and set 'adminsdholderprotected' if true
- Add method to calculate SHA1 hash of authoratative SD
- Add adminCount property to Comptuer nodes
- Add mock tests

Resolves BED-6221
@JimSycurity JimSycurity merged commit c25048c into v4 Jul 29, 2025
3 checks passed
@JimSycurity JimSycurity deleted the jsykora/BED-6178 branch July 29, 2025 17:37
@github-actions github-actions bot locked and limited conversation to collaborators Jul 29, 2025
@JimSycurity
Copy link
Contributor Author

Resolves BED-6242

@JimSycurity JimSycurity changed the title feat: AdminSDHolder hash functions feat: AdminSDHolder hash functions - BED-6242 Jul 30, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants