diff --git a/README.md b/README.md index fe61e32..864f3e9 100644 --- a/README.md +++ b/README.md @@ -109,6 +109,8 @@ The listing below details the CLI arguments SharpHound supports. Additional deta --collectallproperties Collect all LDAP properties from objects + --skipdenyacescount Skip collecting custom deny ACE counts in LDAP object properties + -l, --Loop Loop computer collection --loopduration Loop duration (hh:mm:ss - 05:00:00 is 5 hours, default: 2 hrs) diff --git a/src/Client/Flags.cs b/src/Client/Flags.cs index 0f04ccc..234f0f4 100644 --- a/src/Client/Flags.cs +++ b/src/Client/Flags.cs @@ -22,6 +22,7 @@ public class Flags public bool NoRegistryLoggedOn { get; set; } public bool DumpComputerStatus { get; set; } public bool CollectAllProperties { get; set; } + public bool SkipDenyAcesCount { get; set; } public bool DCOnly { get; set; } public bool PrettyPrint { get; set; } public bool SearchForest { get; set; } @@ -30,4 +31,4 @@ public class Flags public bool ParititonLdapQueries { get; set; } public bool Metrics { get; set; } } -} \ No newline at end of file +} diff --git a/src/Options.cs b/src/Options.cs index bdc84e2..98ed40b 100644 --- a/src/Options.cs +++ b/src/Options.cs @@ -139,6 +139,9 @@ public class Options [Option(HelpText = "Collect all LDAP properties from objects")] public bool CollectAllProperties { get; set; } + + [Option(HelpText = "Skip collecting custom deny ACE counts in LDAP object properties")] + public bool SkipDenyAcesCount { get; set; } [Option(HelpText = "Split the main ldap query into smaller chunks to attempt to reduce server load")] public bool PartitionLdapQueries { get; set; } diff --git a/src/PowerShell/Template.ps1 b/src/PowerShell/Template.ps1 index f19d7bb..6292b2e 100644 --- a/src/PowerShell/Template.ps1 +++ b/src/PowerShell/Template.ps1 @@ -184,6 +184,10 @@ .PARAMETER CollectAllProperties Collect all string LDAP properties on objects + + .PARAMETER SkipDenyAcesCount + + Skip collecting custom deny ACE counts in LDAP object properties .PARAMETER Loop @@ -360,6 +364,9 @@ [Switch] $CollectAllProperties, + [Switch] + $SkipDenyAcesCount, + [Switch] $Loop, diff --git a/src/Runtime/ObjectProcessors.cs b/src/Runtime/ObjectProcessors.cs index 457ddbf..89f8acf 100644 --- a/src/Runtime/ObjectProcessors.cs +++ b/src/Runtime/ObjectProcessors.cs @@ -22,6 +22,8 @@ namespace Sharphound.Runtime { public class ObjectProcessors { private const string StatusSuccess = "Success"; + private const string CustomExplicitDenyAcesCountProperty = "customexplicitdenyacescount"; + private const string CustomInheritedDenyAcesCountProperty = "custominheriteddenyacescount"; private readonly ACLProcessor _aclProcessor; private readonly CertAbuseProcessor _certAbuseProcessor; private readonly CancellationToken _cancellationToken; @@ -165,6 +167,28 @@ private string GetAdminSdHolderHash(string domain) return null; } + private async Task ProcessACL(IDirectoryObject entry, ResolvedSearchResult resolvedSearchResult, + Dictionary properties) { + if (_context.Flags.SkipDenyAcesCount) { + return await _aclProcessor.ProcessACL(resolvedSearchResult, entry, true) + .ToArrayAsync(cancellationToken: _cancellationToken); + } + + var result = await _aclProcessor.ProcessACLWithCustomDenyAces(resolvedSearchResult, entry); + AddCustomDenyAceCounts(properties, result.CustomDenyAceCounts); + return result.Aces; + } + + private static void AddCustomDenyAceCounts(Dictionary properties, + ACLProcessor.CustomDenyAceCounts counts) { + if (counts.Total == 0) { + return; + } + + properties[CustomExplicitDenyAcesCountProperty] = counts.ExplicitCount; + properties[CustomInheritedDenyAcesCountProperty] = counts.InheritedCount; + } + private async Task ProcessUserObject(IDirectoryObject entry, ResolvedSearchResult resolvedSearchResult) { @@ -185,8 +209,7 @@ private async Task ProcessUserObject(IDirectoryObject entry, // AdminSDHolderProtected only on security principal nodes: User, Computer, Group var adminSdHolderHash = GetAdminSdHolderHash(resolvedSearchResult.Domain); - var aces = await _aclProcessor.ProcessACL(resolvedSearchResult, entry, true) - .ToArrayAsync(cancellationToken: _cancellationToken); + var aces = await ProcessACL(entry, resolvedSearchResult, ret.Properties); ret.Properties.Add("doesanyacegrantownerrights", aces.Any(ace => ace.IsPermissionForOwnerRightsSid)); ret.Properties.Add("doesanyinheritedacegrantownerrights", aces.Any(ace => ace.IsInheritedPermissionForOwnerRightsSid)); var gmsa = entry.GetByteProperty(LDAPProperties.GroupMSAMembership); @@ -262,8 +285,7 @@ ResolvedSearchResult resolvedSearchResult // AdminSDHolderProtected only on security principal nodes: User, Computer, Group var adminSdHolderHash = GetAdminSdHolderHash(resolvedSearchResult.Domain); - var aces = await _aclProcessor.ProcessACL(resolvedSearchResult, entry, true) - .ToArrayAsync(cancellationToken: _cancellationToken); + var aces = await ProcessACL(entry, resolvedSearchResult, ret.Properties); ret.Properties.Add("doesanyacegrantownerrights", aces.Any(ace => ace.IsPermissionForOwnerRightsSid)); ret.Properties.Add("doesanyinheritedacegrantownerrights", aces.Any(ace => ace.IsInheritedPermissionForOwnerRightsSid)); ret.Aces = aces; @@ -475,8 +497,7 @@ private async Task ProcessGroupObject(IDirectoryObject entry, // AdminSDHolderProtected only on security principal nodes: User, Computer, Group var adminSdHolderHash = GetAdminSdHolderHash(resolvedSearchResult.Domain); - var aces = await _aclProcessor.ProcessACL(resolvedSearchResult, entry, true) - .ToArrayAsync(cancellationToken: _cancellationToken); + var aces = await ProcessACL(entry, resolvedSearchResult, ret.Properties); ret.Properties.Add("doesanyacegrantownerrights", aces.Any(ace => ace.IsPermissionForOwnerRightsSid)); ret.Properties.Add("doesanyinheritedacegrantownerrights", aces.Any(ace => ace.IsInheritedPermissionForOwnerRightsSid)); ret.Aces = aces; @@ -528,8 +549,7 @@ await _context.LDAPUtils.GetDomainSidFromDomainName(forest) is (true, var forest ret.Properties = new Dictionary(GetCommonProperties(entry, resolvedSearchResult)); if (_methods.HasFlag(CollectionMethod.ACL)) { - var aces = await _aclProcessor.ProcessACL(resolvedSearchResult, entry, true) - .ToArrayAsync(cancellationToken: _cancellationToken); + var aces = await ProcessACL(entry, resolvedSearchResult, ret.Properties); ret.Aces = aces; ret.Properties.Add("doesanyacegrantownerrights", aces.Any(ace => ace.IsPermissionForOwnerRightsSid)); ret.Properties.Add("doesanyinheritedacegrantownerrights", aces.Any(ace => ace.IsInheritedPermissionForOwnerRightsSid)); @@ -571,8 +591,7 @@ private async Task ProcessGPOObject(IDirectoryObject entry, ret.Properties = new Dictionary(GetCommonProperties(entry, resolvedSearchResult)); if (_methods.HasFlag(CollectionMethod.ACL)) { - var aces = await _aclProcessor.ProcessACL(resolvedSearchResult, entry, true) - .ToArrayAsync(cancellationToken: _cancellationToken); + var aces = await ProcessACL(entry, resolvedSearchResult, ret.Properties); ret.Properties.Add("doesanyacegrantownerrights", aces.Any(ace => ace.IsPermissionForOwnerRightsSid)); ret.Properties.Add("doesanyinheritedacegrantownerrights", aces.Any(ace => ace.IsInheritedPermissionForOwnerRightsSid)); ret.Aces = aces; @@ -600,8 +619,7 @@ private async Task ProcessOUObject(IDirectoryObject entry, ret.Properties = new Dictionary(GetCommonProperties(entry, resolvedSearchResult)); if (_methods.HasFlag(CollectionMethod.ACL)) { - var aces = await _aclProcessor.ProcessACL(resolvedSearchResult, entry, true) - .ToArrayAsync(cancellationToken: _cancellationToken); + var aces = await ProcessACL(entry, resolvedSearchResult, ret.Properties); ret.Properties.Add("doesanyacegrantownerrights", aces.Any(ace => ace.IsPermissionForOwnerRightsSid)); ret.Properties.Add("doesanyinheritedacegrantownerrights", aces.Any(ace => ace.IsInheritedPermissionForOwnerRightsSid)); ret.Aces = aces; @@ -650,8 +668,7 @@ private async Task ProcessContainerObject(IDirectoryObject entry, } if (_methods.HasFlag(CollectionMethod.ACL) || _methods.HasFlag(CollectionMethod.CertServices)) { - var aces = await _aclProcessor.ProcessACL(resolvedSearchResult, entry, true) - .ToArrayAsync(cancellationToken: _cancellationToken); + var aces = await ProcessACL(entry, resolvedSearchResult, ret.Properties); ret.Properties.Add("doesanyacegrantownerrights", aces.Any(ace => ace.IsPermissionForOwnerRightsSid)); ret.Properties.Add("doesanyinheritedacegrantownerrights", aces.Any(ace => ace.IsInheritedPermissionForOwnerRightsSid)); ret.Aces = aces; @@ -683,8 +700,7 @@ private async Task ProcessRootCA(IDirectoryObject entry, ResolvedSearchR if (_methods.HasFlag(CollectionMethod.ACL) || _methods.HasFlag(CollectionMethod.CertServices)) { - var aces = await _aclProcessor.ProcessACL(resolvedSearchResult, entry, true) - .ToArrayAsync(cancellationToken: _cancellationToken); + var aces = await ProcessACL(entry, resolvedSearchResult, ret.Properties); ret.Properties.Add("doesanyacegrantownerrights", aces.Any(ace => ace.IsPermissionForOwnerRightsSid)); ret.Properties.Add("doesanyinheritedacegrantownerrights", aces.Any(ace => ace.IsInheritedPermissionForOwnerRightsSid)); ret.Aces = aces; @@ -714,8 +730,7 @@ private async Task ProcessAIACA(IDirectoryObject entry, ResolvedSearchRes ret.Properties = new Dictionary(GetCommonProperties(entry, resolvedSearchResult)); if (_methods.HasFlag(CollectionMethod.ACL) || _methods.HasFlag(CollectionMethod.CertServices)) { - var aces = await _aclProcessor.ProcessACL(resolvedSearchResult, entry, true) - .ToArrayAsync(cancellationToken: _cancellationToken); + var aces = await ProcessACL(entry, resolvedSearchResult, ret.Properties); ret.Properties.Add("doesanyacegrantownerrights", aces.Any(ace => ace.IsPermissionForOwnerRightsSid)); ret.Properties.Add("doesanyinheritedacegrantownerrights", aces.Any(ace => ace.IsInheritedPermissionForOwnerRightsSid)); ret.Aces = aces; @@ -744,8 +759,7 @@ private async Task ProcessEnterpriseCA(IDirectoryObject entry, Res }; if (_methods.HasFlag(CollectionMethod.ACL) || _methods.HasFlag(CollectionMethod.CertServices)) { - var aces = await _aclProcessor.ProcessACL(resolvedSearchResult, entry, true) - .ToArrayAsync(cancellationToken: _cancellationToken); + var aces = await ProcessACL(entry, resolvedSearchResult, ret.Properties); ret.Properties.Add("doesanyacegrantownerrights", aces.Any(ace => ace.IsPermissionForOwnerRightsSid)); ret.Properties.Add("doesanyinheritedacegrantownerrights", aces.Any(ace => ace.IsInheritedPermissionForOwnerRightsSid)); ret.Aces = aces; @@ -861,8 +875,7 @@ private async Task ProcessNTAuthStore(IDirectoryObject entry, ret.Properties = new Dictionary(GetCommonProperties(entry, resolvedSearchResult)); if (_methods.HasFlag(CollectionMethod.ACL) || _methods.HasFlag(CollectionMethod.CertServices)) { - var aces = await _aclProcessor.ProcessACL(resolvedSearchResult, entry, true) - .ToArrayAsync(cancellationToken: _cancellationToken); + var aces = await ProcessACL(entry, resolvedSearchResult, ret.Properties); ret.Properties.Add("doesanyacegrantownerrights", aces.Any(ace => ace.IsPermissionForOwnerRightsSid)); ret.Properties.Add("doesanyinheritedacegrantownerrights", aces.Any(ace => ace.IsInheritedPermissionForOwnerRightsSid)); ret.Aces = aces; @@ -900,8 +913,7 @@ private async Task ProcessCertTemplate(IDirectoryObject entry, ret.Properties = new Dictionary(GetCommonProperties(entry, resolvedSearchResult)); if (_methods.HasFlag(CollectionMethod.ACL) || _methods.HasFlag(CollectionMethod.CertServices)) { - var aces = await _aclProcessor.ProcessACL(resolvedSearchResult, entry, true) - .ToArrayAsync(cancellationToken: _cancellationToken); + var aces = await ProcessACL(entry, resolvedSearchResult, ret.Properties); ret.Properties.Add("doesanyacegrantownerrights", aces.Any(ace => ace.IsPermissionForOwnerRightsSid)); ret.Properties.Add("doesanyinheritedacegrantownerrights", aces.Any(ace => ace.IsInheritedPermissionForOwnerRightsSid)); ret.Aces = aces; @@ -932,8 +944,7 @@ private async Task ProcessIssuancePolicy(IDirectoryObject entry, ret.Properties = new Dictionary(GetCommonProperties(entry, resolvedSearchResult)); if (_methods.HasFlag(CollectionMethod.ACL) || _methods.HasFlag(CollectionMethod.CertServices)) { - var aces = await _aclProcessor.ProcessACL(resolvedSearchResult, entry, true) - .ToArrayAsync(cancellationToken: _cancellationToken); + var aces = await ProcessACL(entry, resolvedSearchResult, ret.Properties); ret.Properties.Add("doesanyacegrantownerrights", aces.Any(ace => ace.IsPermissionForOwnerRightsSid)); ret.Properties.Add("doesanyinheritedacegrantownerrights", aces.Any(ace => ace.IsInheritedPermissionForOwnerRightsSid)); ret.Aces = aces; @@ -956,4 +967,4 @@ private async Task ProcessIssuancePolicy(IDirectoryObject entry, return ret; } } -} \ No newline at end of file +} diff --git a/src/Sharphound.cs b/src/Sharphound.cs index 1229650..443ee94 100644 --- a/src/Sharphound.cs +++ b/src/Sharphound.cs @@ -88,6 +88,7 @@ await options.WithParsedAsync(async options => RandomizeFilenames = options.RandomFileNames, MemCache = options.MemCache, CollectAllProperties = options.CollectAllProperties, + SkipDenyAcesCount = options.SkipDenyAcesCount, DCOnly = dconly, PrettyPrint = options.PrettyPrint, SearchForest = options.SearchForest, @@ -264,4 +265,4 @@ public static void InvokeSharpHound(string[] args) { } #endregion -} \ No newline at end of file +}