From c068cf4ed01e1fb1c5970abea3913dab2a5652ef Mon Sep 17 00:00:00 2001 From: Mayyhem Date: Thu, 7 Nov 2024 14:46:10 -0500 Subject: [PATCH 1/5] Added check for ACEs granting OWNER RIGHTS SID permissions --- src/CommonLib/OutputTypes/OutputBase.cs | 1 + src/CommonLib/Processors/ACLProcessor.cs | 453 ++++++++++++++++++++++- 2 files changed, 443 insertions(+), 11 deletions(-) diff --git a/src/CommonLib/OutputTypes/OutputBase.cs b/src/CommonLib/OutputTypes/OutputBase.cs index dacb7ece..f79485e9 100644 --- a/src/CommonLib/OutputTypes/OutputBase.cs +++ b/src/CommonLib/OutputTypes/OutputBase.cs @@ -14,5 +14,6 @@ public class OutputBase public bool IsDeleted { get; set; } public bool IsACLProtected { get; set; } public TypedPrincipal ContainedBy { get; set; } + public bool DoesAnyAceGrantOwnerRights { get; set; } } } \ No newline at end of file diff --git a/src/CommonLib/Processors/ACLProcessor.cs b/src/CommonLib/Processors/ACLProcessor.cs index 45af8136..6df4be3d 100644 --- a/src/CommonLib/Processors/ACLProcessor.cs +++ b/src/CommonLib/Processors/ACLProcessor.cs @@ -2,10 +2,13 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.DirectoryServices; +using System.Linq; +using System.Net.Configuration; using System.Security.AccessControl; using System.Security.Cryptography; using System.Security.Principal; using System.Text; +using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using SharpHoundCommonLib.DirectoryObjects; @@ -58,14 +61,14 @@ private async Task BuildGuidCache(string domain) { _builtDomainCaches.Add(domain); } - + _log.LogInformation("Building GUID Cache for {Domain}", domain); await foreach (var result in _utils.PagedQuery(new LdapQueryParameters { - DomainName = domain, - LDAPFilter = "(schemaIDGUID=*)", - NamingContext = NamingContext.Schema, - Attributes = new[] { LDAPProperties.SchemaIDGUID, LDAPProperties.Name }, - })) { + DomainName = domain, + LDAPFilter = "(schemaIDGUID=*)", + NamingContext = NamingContext.Schema, + Attributes = new[] { LDAPProperties.SchemaIDGUID, LDAPProperties.Name }, + })) { if (result.IsSuccess) { if (!result.Value.TryGetProperty(LDAPProperties.Name, out var name) || !result.Value.TryGetByteProperty(LDAPProperties.SchemaIDGUID, out var schemaGuid)) { @@ -83,7 +86,7 @@ private async Task BuildGuidCache(string domain) { { continue; } - + if (name is LDAPProperties.LAPSPlaintextPassword or LDAPProperties.LAPSEncryptedPassword or LDAPProperties.LegacyLAPSPassword) { _log.LogInformation("Found GUID for ACL Right {Name}: {Guid} in domain {Domain}", name, guid, domain); _guidMap.TryAdd(guid, name); @@ -92,7 +95,7 @@ private async Task BuildGuidCache(string domain) { _log.LogDebug("Error while building GUID cache for {Domain}: {Message}", domain, result.Error); } } - + } /// @@ -129,9 +132,9 @@ public bool IsACLProtected(byte[] ntSecurityDescriptor) { /// /// /// - public IAsyncEnumerable ProcessACL(ResolvedSearchResult result, IDirectoryObject searchResult) { + public async Task<(ACE[], bool)> ProcessACL(ResolvedSearchResult result, IDirectoryObject searchResult, bool checkForOwnerRights) { if (!searchResult.TryGetByteProperty(LDAPProperties.SecurityDescriptor, out var descriptor)) { - return AsyncEnumerable.Empty(); + return ([], false); } var domain = result.Domain; @@ -139,7 +142,13 @@ public IAsyncEnumerable ProcessACL(ResolvedSearchResult result, IDirectoryO var hasLaps = searchResult.HasLAPS(); var name = result.DisplayName; - return ProcessACL(descriptor, domain, type, hasLaps, name); + if (!checkForOwnerRights) + { + var aces = ProcessACL(descriptor, domain, type, hasLaps, name); + // Convert to array for return + return (await aces.ToArrayAsync(), false); + } + return await ProcessACL(descriptor, domain, type, hasLaps, name, checkForOwnerRights); } internal static string CalculateInheritanceHash(string identityReference, ActiveDirectoryRights rights, @@ -603,6 +612,428 @@ or Label.NTAuthStore } } + public async Task<(ACE[], bool)> ProcessACL(byte[] ntSecurityDescriptor, string objectDomain, + Label objectType, bool hasLaps, string objectName = "", bool checkForOwnerRights = true) + { + var aces = new List(); + bool isAnyPermissionForOwnerRightsSid = false; + + await BuildGuidCache(objectDomain); + + if (ntSecurityDescriptor == null) + { + _log.LogDebug("Security Descriptor is null for {Name}", objectName); + } + + var descriptor = _utils.MakeSecurityDescriptor(); + try + { + descriptor.SetSecurityDescriptorBinaryForm(ntSecurityDescriptor); + } + catch (OverflowException) + { + _log.LogWarning( + "Security descriptor on object {Name} exceeds maximum allowable length. Unable to process", + objectName); + } + + _log.LogDebug("Processing ACL for {ObjectName}", objectName); + var ownerSid = Helpers.PreProcessSID(descriptor.GetOwner(typeof(SecurityIdentifier))); + + if (ownerSid != null) + { + if (await _utils.ResolveIDAndType(ownerSid, objectDomain) is (true, var resolvedOwner)) + { + aces.Add(new ACE + { + PrincipalType = resolvedOwner.ObjectType, + PrincipalSID = resolvedOwner.ObjectIdentifier, + RightName = EdgeNames.Owns, + IsInherited = false, + InheritanceHash = "" + }); + } + else + { + _log.LogTrace("Failed to resolve owner for {Name}", objectName); + aces.Add(new ACE + { + PrincipalType = Label.Base, + PrincipalSID = ownerSid, + RightName = EdgeNames.Owns, + IsInherited = false, + InheritanceHash = "" + }); + } + } + + foreach (var ace in descriptor.GetAccessRules(true, true, typeof(SecurityIdentifier))) + { + if (ace == null || ace.AccessControlType() == AccessControlType.Deny || !ace.IsAceInheritedFrom(BaseGuids[objectType])) + { + continue; + } + + var ir = ace.IdentityReference(); + var principalSid = Helpers.PreProcessSID(ir); + + //Preprocess returns null if this is an ignored sid + if (principalSid == null) + { + continue; + } + + var (success, resolvedPrincipal) = await _utils.ResolveIDAndType(principalSid, objectDomain); + if (!success) + { + _log.LogTrace("Failed to resolve type for principal {Sid} on ACE for {Object}", principalSid, objectName); + resolvedPrincipal.ObjectIdentifier = principalSid; + resolvedPrincipal.ObjectType = Label.Base; + } + + // Check if any rights are explicitly defined for the OWNER RIGHTS SID + if (checkForOwnerRights && resolvedPrincipal.ObjectIdentifier.EndsWith("S-1-3-4")) + { + isAnyPermissionForOwnerRightsSid = true; + } + + var aceRights = ace.ActiveDirectoryRights(); + //Lowercase this just in case. As far as I know it should always come back that way anyways, but better safe than sorry + var aceType = ace.ObjectType().ToString().ToLower(); + var inherited = ace.IsInherited(); + + var aceInheritanceHash = ""; + if (inherited) + { + aceInheritanceHash = CalculateInheritanceHash(ir, aceRights, aceType, ace.InheritedObjectType()); + } + + _log.LogTrace("Processing ACE with rights {Rights} and guid {GUID} on object {Name}", aceRights, + aceType, objectName); + + //GenericAll applies to every object + if (aceRights.HasFlag(ActiveDirectoryRights.GenericAll)) + { + if (aceType is ACEGuids.AllGuid or "") + aces.Add(new ACE + { + PrincipalType = resolvedPrincipal.ObjectType, + PrincipalSID = resolvedPrincipal.ObjectIdentifier, + IsInherited = inherited, + RightName = EdgeNames.GenericAll, + InheritanceHash = aceInheritanceHash + }); + //This is a special case. If we don't continue here, every other ACE will match because GenericAll includes all other permissions + continue; + } + + //WriteDACL and WriteOwner are always useful no matter what the object type is as well because they enable all other attacks + if (aceRights.HasFlag(ActiveDirectoryRights.WriteDacl)) + aces.Add(new ACE + { + PrincipalType = resolvedPrincipal.ObjectType, + PrincipalSID = resolvedPrincipal.ObjectIdentifier, + IsInherited = inherited, + RightName = EdgeNames.WriteDacl, + InheritanceHash = aceInheritanceHash + }); + + if (aceRights.HasFlag(ActiveDirectoryRights.WriteOwner)) + aces.Add(new ACE + { + PrincipalType = resolvedPrincipal.ObjectType, + PrincipalSID = resolvedPrincipal.ObjectIdentifier, + IsInherited = inherited, + RightName = EdgeNames.WriteOwner, + InheritanceHash = aceInheritanceHash + }); + + //Cool ACE courtesy of @rookuu. Allows a principal to add itself to a group and no one else + if (aceRights.HasFlag(ActiveDirectoryRights.Self) && + !aceRights.HasFlag(ActiveDirectoryRights.WriteProperty) && + !aceRights.HasFlag(ActiveDirectoryRights.GenericWrite) && objectType == Label.Group && + aceType == ACEGuids.WriteMember) + aces.Add(new ACE + { + PrincipalType = resolvedPrincipal.ObjectType, + PrincipalSID = resolvedPrincipal.ObjectIdentifier, + IsInherited = inherited, + RightName = EdgeNames.AddSelf, + InheritanceHash = aceInheritanceHash + }); + + //Process object type specific ACEs. Extended rights apply to users, domains, computers, and cert templates + if (aceRights.HasFlag(ActiveDirectoryRights.ExtendedRight)) + { + if (objectType == Label.Domain) + { + if (aceType == ACEGuids.DSReplicationGetChanges) + aces.Add(new ACE + { + PrincipalType = resolvedPrincipal.ObjectType, + PrincipalSID = resolvedPrincipal.ObjectIdentifier, + IsInherited = inherited, + RightName = EdgeNames.GetChanges, + InheritanceHash = aceInheritanceHash + }); + else if (aceType == ACEGuids.DSReplicationGetChangesAll) + aces.Add(new ACE + { + PrincipalType = resolvedPrincipal.ObjectType, + PrincipalSID = resolvedPrincipal.ObjectIdentifier, + IsInherited = inherited, + RightName = EdgeNames.GetChangesAll, + InheritanceHash = aceInheritanceHash + }); + else if (aceType == ACEGuids.DSReplicationGetChangesInFilteredSet) + aces.Add(new ACE + { + PrincipalType = resolvedPrincipal.ObjectType, + PrincipalSID = resolvedPrincipal.ObjectIdentifier, + IsInherited = inherited, + RightName = EdgeNames.GetChangesInFilteredSet, + InheritanceHash = aceInheritanceHash + }); + else if (aceType is ACEGuids.AllGuid or "") + aces.Add(new ACE + { + PrincipalType = resolvedPrincipal.ObjectType, + PrincipalSID = resolvedPrincipal.ObjectIdentifier, + IsInherited = inherited, + RightName = EdgeNames.AllExtendedRights, + InheritanceHash = aceInheritanceHash + }); + } + else if (objectType == Label.User) + { + if (aceType == ACEGuids.UserForceChangePassword) + aces.Add(new ACE + { + PrincipalType = resolvedPrincipal.ObjectType, + PrincipalSID = resolvedPrincipal.ObjectIdentifier, + IsInherited = inherited, + RightName = EdgeNames.ForceChangePassword, + InheritanceHash = aceInheritanceHash + }); + else if (aceType is ACEGuids.AllGuid or "") + aces.Add(new ACE + { + PrincipalType = resolvedPrincipal.ObjectType, + PrincipalSID = resolvedPrincipal.ObjectIdentifier, + IsInherited = inherited, + RightName = EdgeNames.AllExtendedRights, + InheritanceHash = aceInheritanceHash + }); + } + else if (objectType == Label.Computer) + { + //ReadLAPSPassword is only applicable if the computer actually has LAPS. Check the world readable property ms-mcs-admpwdexpirationtime + if (hasLaps) + { + if (aceType is ACEGuids.AllGuid or "") + aces.Add(new ACE + { + PrincipalType = resolvedPrincipal.ObjectType, + PrincipalSID = resolvedPrincipal.ObjectIdentifier, + IsInherited = inherited, + RightName = EdgeNames.AllExtendedRights, + InheritanceHash = aceInheritanceHash + }); + else if (_guidMap.TryGetValue(aceType, out var lapsAttribute)) + { + // Compare the retrieved attribute name against LDAPProperties values + if (lapsAttribute == LDAPProperties.LegacyLAPSPassword || + lapsAttribute == LDAPProperties.LAPSPlaintextPassword || + lapsAttribute == LDAPProperties.LAPSEncryptedPassword) + { + aces.Add(new ACE + { + PrincipalType = resolvedPrincipal.ObjectType, + PrincipalSID = resolvedPrincipal.ObjectIdentifier, + IsInherited = inherited, + RightName = EdgeNames.ReadLAPSPassword, + InheritanceHash = aceInheritanceHash + }); + } + } + } + } + else if (objectType == Label.CertTemplate) + { + if (aceType is ACEGuids.AllGuid or "") + aces.Add(new ACE + { + PrincipalType = resolvedPrincipal.ObjectType, + PrincipalSID = resolvedPrincipal.ObjectIdentifier, + IsInherited = inherited, + RightName = EdgeNames.AllExtendedRights, + InheritanceHash = aceInheritanceHash + }); + else if (aceType is ACEGuids.Enroll) + aces.Add(new ACE + { + PrincipalType = resolvedPrincipal.ObjectType, + PrincipalSID = resolvedPrincipal.ObjectIdentifier, + IsInherited = inherited, + RightName = EdgeNames.Enroll, + InheritanceHash = aceInheritanceHash + }); + } + } + + //GenericWrite encapsulates WriteProperty, so process them in tandem to avoid duplicate edges + if (aceRights.HasFlag(ActiveDirectoryRights.GenericWrite) || + aceRights.HasFlag(ActiveDirectoryRights.WriteProperty)) + { + if (objectType is Label.User + or Label.Group + or Label.Computer + or Label.GPO + or Label.OU + or Label.Domain + or Label.CertTemplate + or Label.RootCA + or Label.EnterpriseCA + or Label.AIACA + or Label.NTAuthStore + or Label.IssuancePolicy) + if (aceType is ACEGuids.AllGuid or "") + aces.Add(new ACE + { + PrincipalType = resolvedPrincipal.ObjectType, + PrincipalSID = resolvedPrincipal.ObjectIdentifier, + IsInherited = inherited, + RightName = EdgeNames.GenericWrite, + InheritanceHash = aceInheritanceHash + }); + + if (objectType == Label.User && aceType == ACEGuids.WriteSPN) + aces.Add(new ACE + { + PrincipalType = resolvedPrincipal.ObjectType, + PrincipalSID = resolvedPrincipal.ObjectIdentifier, + IsInherited = inherited, + RightName = EdgeNames.WriteSPN, + InheritanceHash = aceInheritanceHash + }); + else if (objectType == Label.Computer && aceType == ACEGuids.WriteAllowedToAct) + aces.Add(new ACE + { + PrincipalType = resolvedPrincipal.ObjectType, + PrincipalSID = resolvedPrincipal.ObjectIdentifier, + IsInherited = inherited, + RightName = EdgeNames.AddAllowedToAct, + InheritanceHash = aceInheritanceHash + }); + else if (objectType == Label.Computer && aceType == ACEGuids.UserAccountRestrictions) + aces.Add(new ACE + { + PrincipalType = resolvedPrincipal.ObjectType, + PrincipalSID = resolvedPrincipal.ObjectIdentifier, + IsInherited = inherited, + RightName = EdgeNames.WriteAccountRestrictions, + InheritanceHash = aceInheritanceHash + }); + else if (objectType is Label.OU or Label.Domain && aceType == ACEGuids.WriteGPLink) + aces.Add(new ACE + { + PrincipalType = resolvedPrincipal.ObjectType, + PrincipalSID = resolvedPrincipal.ObjectIdentifier, + IsInherited = inherited, + RightName = EdgeNames.WriteGPLink, + InheritanceHash = aceInheritanceHash + }); + else if (objectType == Label.Group && aceType == ACEGuids.WriteMember) + aces.Add(new ACE + { + PrincipalType = resolvedPrincipal.ObjectType, + PrincipalSID = resolvedPrincipal.ObjectIdentifier, + IsInherited = inherited, + RightName = EdgeNames.AddMember, + InheritanceHash = aceInheritanceHash + }); + else if (objectType is Label.User or Label.Computer && aceType == ACEGuids.AddKeyPrincipal) + aces.Add(new ACE + { + PrincipalType = resolvedPrincipal.ObjectType, + PrincipalSID = resolvedPrincipal.ObjectIdentifier, + IsInherited = inherited, + RightName = EdgeNames.AddKeyCredentialLink, + InheritanceHash = aceInheritanceHash + }); + else if (objectType is Label.CertTemplate) + { + if (aceType == ACEGuids.PKIEnrollmentFlag) + aces.Add(new ACE + { + PrincipalType = resolvedPrincipal.ObjectType, + PrincipalSID = resolvedPrincipal.ObjectIdentifier, + IsInherited = inherited, + RightName = EdgeNames.WritePKIEnrollmentFlag, + InheritanceHash = aceInheritanceHash + }); + else if (aceType == ACEGuids.PKINameFlag) + aces.Add(new ACE + { + PrincipalType = resolvedPrincipal.ObjectType, + PrincipalSID = resolvedPrincipal.ObjectIdentifier, + IsInherited = inherited, + RightName = EdgeNames.WritePKINameFlag, + InheritanceHash = aceInheritanceHash + }); + } + } + + // EnterpriseCA rights + if (objectType == Label.EnterpriseCA) + { + if (aceType is ACEGuids.Enroll) + aces.Add(new ACE + { + PrincipalType = resolvedPrincipal.ObjectType, + PrincipalSID = resolvedPrincipal.ObjectIdentifier, + IsInherited = inherited, + RightName = EdgeNames.Enroll, + InheritanceHash = aceInheritanceHash + }); + + var cARights = (CertificationAuthorityRights)aceRights; + + // TODO: These if statements are also present in ProcessRegistryEnrollmentPermissions. Move to shared location. + if ((cARights & CertificationAuthorityRights.ManageCA) != 0) + aces.Add(new ACE + { + PrincipalType = resolvedPrincipal.ObjectType, + PrincipalSID = resolvedPrincipal.ObjectIdentifier, + IsInherited = inherited, + RightName = EdgeNames.ManageCA, + InheritanceHash = aceInheritanceHash + }); + if ((cARights & CertificationAuthorityRights.ManageCertificates) != 0) + aces.Add(new ACE + { + PrincipalType = resolvedPrincipal.ObjectType, + PrincipalSID = resolvedPrincipal.ObjectIdentifier, + IsInherited = inherited, + RightName = EdgeNames.ManageCertificates, + InheritanceHash = aceInheritanceHash + }); + + if ((cARights & CertificationAuthorityRights.Enroll) != 0) + aces.Add(new ACE + { + PrincipalType = resolvedPrincipal.ObjectType, + PrincipalSID = resolvedPrincipal.ObjectIdentifier, + IsInherited = inherited, + RightName = EdgeNames.Enroll, + InheritanceHash = aceInheritanceHash + }); + } + } + return (aces.ToArray(), isAnyPermissionForOwnerRightsSid); + } + + /// /// Helper function to use commonlib types and pass to ProcessGMSAReaders /// From feb924748c5f85df5ef27fe21a19223f02cae361 Mon Sep 17 00:00:00 2001 From: Mayyhem Date: Thu, 7 Nov 2024 17:00:10 -0500 Subject: [PATCH 2/5] Removed OutputBase property --- src/CommonLib/OutputTypes/OutputBase.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/CommonLib/OutputTypes/OutputBase.cs b/src/CommonLib/OutputTypes/OutputBase.cs index f79485e9..dacb7ece 100644 --- a/src/CommonLib/OutputTypes/OutputBase.cs +++ b/src/CommonLib/OutputTypes/OutputBase.cs @@ -14,6 +14,5 @@ public class OutputBase public bool IsDeleted { get; set; } public bool IsACLProtected { get; set; } public TypedPrincipal ContainedBy { get; set; } - public bool DoesAnyAceGrantOwnerRights { get; set; } } } \ No newline at end of file From 31454f88137848eb10caaa661465c878ede1ddd2 Mon Sep 17 00:00:00 2001 From: Mayyhem Date: Mon, 2 Dec 2024 17:44:42 -0500 Subject: [PATCH 3/5] Added check for inherited ACEs granting OWNER RIGHTS SID permissions --- src/CommonLib/Processors/ACLProcessor.cs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/CommonLib/Processors/ACLProcessor.cs b/src/CommonLib/Processors/ACLProcessor.cs index 6df4be3d..d050ef58 100644 --- a/src/CommonLib/Processors/ACLProcessor.cs +++ b/src/CommonLib/Processors/ACLProcessor.cs @@ -132,9 +132,9 @@ public bool IsACLProtected(byte[] ntSecurityDescriptor) { /// /// /// - public async Task<(ACE[], bool)> ProcessACL(ResolvedSearchResult result, IDirectoryObject searchResult, bool checkForOwnerRights) { + public async Task<(ACE[], bool, bool)> ProcessACL(ResolvedSearchResult result, IDirectoryObject searchResult, bool checkForOwnerRights) { if (!searchResult.TryGetByteProperty(LDAPProperties.SecurityDescriptor, out var descriptor)) { - return ([], false); + return ([], false, false); } var domain = result.Domain; @@ -146,7 +146,7 @@ public bool IsACLProtected(byte[] ntSecurityDescriptor) { { var aces = ProcessACL(descriptor, domain, type, hasLaps, name); // Convert to array for return - return (await aces.ToArrayAsync(), false); + return (await aces.ToArrayAsync(), false, false); } return await ProcessACL(descriptor, domain, type, hasLaps, name, checkForOwnerRights); } @@ -612,11 +612,12 @@ or Label.NTAuthStore } } - public async Task<(ACE[], bool)> ProcessACL(byte[] ntSecurityDescriptor, string objectDomain, + public async Task<(ACE[], bool, bool)> ProcessACL(byte[] ntSecurityDescriptor, string objectDomain, Label objectType, bool hasLaps, string objectName = "", bool checkForOwnerRights = true) { var aces = new List(); bool isAnyPermissionForOwnerRightsSid = false; + bool isAnyPermissionForOwnerRightsSidInherited = false; await BuildGuidCache(objectDomain); @@ -691,7 +692,7 @@ or Label.NTAuthStore resolvedPrincipal.ObjectType = Label.Base; } - // Check if any rights are explicitly defined for the OWNER RIGHTS SID + //Check if any rights are explicitly defined for the OWNER RIGHTS SID if (checkForOwnerRights && resolvedPrincipal.ObjectIdentifier.EndsWith("S-1-3-4")) { isAnyPermissionForOwnerRightsSid = true; @@ -706,6 +707,12 @@ or Label.NTAuthStore if (inherited) { aceInheritanceHash = CalculateInheritanceHash(ir, aceRights, aceType, ace.InheritedObjectType()); + + //Check if any rights that are explicitly defined for the OWNER RIGHTS SID are inherited + if (checkForOwnerRights && resolvedPrincipal.ObjectIdentifier.EndsWith("S-1-3-4")) + { + isAnyPermissionForOwnerRightsSidInherited = true; + } } _log.LogTrace("Processing ACE with rights {Rights} and guid {GUID} on object {Name}", aceRights, @@ -1030,7 +1037,7 @@ or Label.NTAuthStore }); } } - return (aces.ToArray(), isAnyPermissionForOwnerRightsSid); + return (aces.ToArray(), isAnyPermissionForOwnerRightsSid, isAnyPermissionForOwnerRightsSidInherited); } From 6859953e8b77c9a83d77b876ab63acaa1c131d46 Mon Sep 17 00:00:00 2001 From: Mayyhem Date: Tue, 3 Dec 2024 17:54:18 -0500 Subject: [PATCH 4/5] Reduce code reuse, preserve API contract --- src/CommonLib/Processors/ACLProcessor.cs | 414 ++--------------------- 1 file changed, 30 insertions(+), 384 deletions(-) diff --git a/src/CommonLib/Processors/ACLProcessor.cs b/src/CommonLib/Processors/ACLProcessor.cs index d050ef58..f63c8ad2 100644 --- a/src/CommonLib/Processors/ACLProcessor.cs +++ b/src/CommonLib/Processors/ACLProcessor.cs @@ -2,13 +2,10 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.DirectoryServices; -using System.Linq; -using System.Net.Configuration; using System.Security.AccessControl; using System.Security.Cryptography; using System.Security.Principal; using System.Text; -using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using SharpHoundCommonLib.DirectoryObjects; @@ -126,31 +123,6 @@ public bool IsACLProtected(byte[] ntSecurityDescriptor) { return descriptor.AreAccessRulesProtected(); } - /// - /// Helper function to use common lib types and pass appropriate vars to ProcessACL - /// - /// - /// - /// - public async Task<(ACE[], bool, bool)> ProcessACL(ResolvedSearchResult result, IDirectoryObject searchResult, bool checkForOwnerRights) { - if (!searchResult.TryGetByteProperty(LDAPProperties.SecurityDescriptor, out var descriptor)) { - return ([], false, false); - } - - var domain = result.Domain; - var type = result.ObjectType; - var hasLaps = searchResult.HasLAPS(); - var name = result.DisplayName; - - if (!checkForOwnerRights) - { - var aces = ProcessACL(descriptor, domain, type, hasLaps, name); - // Convert to array for return - return (await aces.ToArrayAsync(), false, false); - } - return await ProcessACL(descriptor, domain, type, hasLaps, name, checkForOwnerRights); - } - internal static string CalculateInheritanceHash(string identityReference, ActiveDirectoryRights rights, string aceType, string inheritedObjectType) { var hash = identityReference + rights + aceType + inheritedObjectType; @@ -242,6 +214,30 @@ public IEnumerable GetInheritedAceHashes(byte[] ntSecurityDescriptor, st } } + /// + /// Helper functions to use common lib types and pass appropriate vars to ProcessACL + /// + /// + /// + /// + public IAsyncEnumerable ProcessACL(ResolvedSearchResult result, IDirectoryObject searchResult) + { + if (!searchResult.TryGetByteProperty(LDAPProperties.SecurityDescriptor, out var descriptor)) + { + return AsyncEnumerable.Empty(); + } + return ProcessACL(descriptor, result.Domain, result.ObjectType, searchResult.HasLAPS(), result.DisplayName); + } + + public async Task<(ACE[], bool, bool)> ProcessACL(ResolvedSearchResult result, IDirectoryObject searchResult, bool checkForOwnerRights) + { + if (!searchResult.TryGetByteProperty(LDAPProperties.SecurityDescriptor, out var descriptor)) + { + return (Array.Empty(), false, false); + } + return await ProcessACL(descriptor, result.Domain, result.ObjectType, searchResult.HasLAPS(), result.DisplayName, checkForOwnerRights); + } + /// /// Read's a raw ntSecurityDescriptor and processes the ACEs in the ACL, filtering out ACEs that /// BloodHound is not interested in as well as principals we don't care about @@ -253,362 +249,12 @@ public IEnumerable GetInheritedAceHashes(byte[] ntSecurityDescriptor, st /// /// public async IAsyncEnumerable ProcessACL(byte[] ntSecurityDescriptor, string objectDomain, - Label objectType, - bool hasLaps, string objectName = "") { - await BuildGuidCache(objectDomain); - - if (ntSecurityDescriptor == null) { - _log.LogDebug("Security Descriptor is null for {Name}", objectName); - yield break; - } - - var descriptor = _utils.MakeSecurityDescriptor(); - try { - descriptor.SetSecurityDescriptorBinaryForm(ntSecurityDescriptor); - } catch (OverflowException) { - _log.LogWarning( - "Security descriptor on object {Name} exceeds maximum allowable length. Unable to process", - objectName); - yield break; - } - - _log.LogDebug("Processing ACL for {ObjectName}", objectName); - var ownerSid = Helpers.PreProcessSID(descriptor.GetOwner(typeof(SecurityIdentifier))); - - if (ownerSid != null) { - if (await _utils.ResolveIDAndType(ownerSid, objectDomain) is (true, var resolvedOwner)) { - yield return new ACE { - PrincipalType = resolvedOwner.ObjectType, - PrincipalSID = resolvedOwner.ObjectIdentifier, - RightName = EdgeNames.Owns, - IsInherited = false, - InheritanceHash = "" - }; - } else { - _log.LogTrace("Failed to resolve owner for {Name}", objectName); - yield return new ACE { - PrincipalType = Label.Base, - PrincipalSID = ownerSid, - RightName = EdgeNames.Owns, - IsInherited = false, - InheritanceHash = "" - }; - } - } - - foreach (var ace in descriptor.GetAccessRules(true, true, typeof(SecurityIdentifier))) { - if (ace == null || ace.AccessControlType() == AccessControlType.Deny || !ace.IsAceInheritedFrom(BaseGuids[objectType])) { - continue; - } - - var ir = ace.IdentityReference(); - var principalSid = Helpers.PreProcessSID(ir); - - //Preprocess returns null if this is an ignored sid - if (principalSid == null) { - continue; - } - - var (success, resolvedPrincipal) = await _utils.ResolveIDAndType(principalSid, objectDomain); - if (!success) { - _log.LogTrace("Failed to resolve type for principal {Sid} on ACE for {Object}", principalSid, objectName); - resolvedPrincipal.ObjectIdentifier = principalSid; - resolvedPrincipal.ObjectType = Label.Base; - } - - var aceRights = ace.ActiveDirectoryRights(); - //Lowercase this just in case. As far as I know it should always come back that way anyways, but better safe than sorry - var aceType = ace.ObjectType().ToString().ToLower(); - var inherited = ace.IsInherited(); - - var aceInheritanceHash = ""; - if (inherited) { - aceInheritanceHash = CalculateInheritanceHash(ir, aceRights, aceType, ace.InheritedObjectType()); - } - - _log.LogTrace("Processing ACE with rights {Rights} and guid {GUID} on object {Name}", aceRights, - aceType, objectName); - - //GenericAll applies to every object - if (aceRights.HasFlag(ActiveDirectoryRights.GenericAll)) { - if (aceType is ACEGuids.AllGuid or "") - yield return new ACE { - PrincipalType = resolvedPrincipal.ObjectType, - PrincipalSID = resolvedPrincipal.ObjectIdentifier, - IsInherited = inherited, - RightName = EdgeNames.GenericAll, - InheritanceHash = aceInheritanceHash - }; - //This is a special case. If we don't continue here, every other ACE will match because GenericAll includes all other permissions - continue; - } - - //WriteDACL and WriteOwner are always useful no matter what the object type is as well because they enable all other attacks - if (aceRights.HasFlag(ActiveDirectoryRights.WriteDacl)) - yield return new ACE { - PrincipalType = resolvedPrincipal.ObjectType, - PrincipalSID = resolvedPrincipal.ObjectIdentifier, - IsInherited = inherited, - RightName = EdgeNames.WriteDacl, - InheritanceHash = aceInheritanceHash - }; - - if (aceRights.HasFlag(ActiveDirectoryRights.WriteOwner)) - yield return new ACE { - PrincipalType = resolvedPrincipal.ObjectType, - PrincipalSID = resolvedPrincipal.ObjectIdentifier, - IsInherited = inherited, - RightName = EdgeNames.WriteOwner, - InheritanceHash = aceInheritanceHash - }; - - //Cool ACE courtesy of @rookuu. Allows a principal to add itself to a group and no one else - if (aceRights.HasFlag(ActiveDirectoryRights.Self) && - !aceRights.HasFlag(ActiveDirectoryRights.WriteProperty) && - !aceRights.HasFlag(ActiveDirectoryRights.GenericWrite) && objectType == Label.Group && - aceType == ACEGuids.WriteMember) - yield return new ACE { - PrincipalType = resolvedPrincipal.ObjectType, - PrincipalSID = resolvedPrincipal.ObjectIdentifier, - IsInherited = inherited, - RightName = EdgeNames.AddSelf, - InheritanceHash = aceInheritanceHash - }; - - //Process object type specific ACEs. Extended rights apply to users, domains, computers, and cert templates - if (aceRights.HasFlag(ActiveDirectoryRights.ExtendedRight)) { - if (objectType == Label.Domain) { - if (aceType == ACEGuids.DSReplicationGetChanges) - yield return new ACE { - PrincipalType = resolvedPrincipal.ObjectType, - PrincipalSID = resolvedPrincipal.ObjectIdentifier, - IsInherited = inherited, - RightName = EdgeNames.GetChanges, - InheritanceHash = aceInheritanceHash - }; - else if (aceType == ACEGuids.DSReplicationGetChangesAll) - yield return new ACE { - PrincipalType = resolvedPrincipal.ObjectType, - PrincipalSID = resolvedPrincipal.ObjectIdentifier, - IsInherited = inherited, - RightName = EdgeNames.GetChangesAll, - InheritanceHash = aceInheritanceHash - }; - else if (aceType == ACEGuids.DSReplicationGetChangesInFilteredSet) - yield return new ACE { - PrincipalType = resolvedPrincipal.ObjectType, - PrincipalSID = resolvedPrincipal.ObjectIdentifier, - IsInherited = inherited, - RightName = EdgeNames.GetChangesInFilteredSet, - InheritanceHash = aceInheritanceHash - }; - else if (aceType is ACEGuids.AllGuid or "") - yield return new ACE { - PrincipalType = resolvedPrincipal.ObjectType, - PrincipalSID = resolvedPrincipal.ObjectIdentifier, - IsInherited = inherited, - RightName = EdgeNames.AllExtendedRights, - InheritanceHash = aceInheritanceHash - }; - } else if (objectType == Label.User) { - if (aceType == ACEGuids.UserForceChangePassword) - yield return new ACE { - PrincipalType = resolvedPrincipal.ObjectType, - PrincipalSID = resolvedPrincipal.ObjectIdentifier, - IsInherited = inherited, - RightName = EdgeNames.ForceChangePassword, - InheritanceHash = aceInheritanceHash - }; - else if (aceType is ACEGuids.AllGuid or "") - yield return new ACE { - PrincipalType = resolvedPrincipal.ObjectType, - PrincipalSID = resolvedPrincipal.ObjectIdentifier, - IsInherited = inherited, - RightName = EdgeNames.AllExtendedRights, - InheritanceHash = aceInheritanceHash - }; - } else if (objectType == Label.Computer) { - //ReadLAPSPassword is only applicable if the computer actually has LAPS. Check the world readable property ms-mcs-admpwdexpirationtime - if (hasLaps) { - if (aceType is ACEGuids.AllGuid or "") - yield return new ACE { - PrincipalType = resolvedPrincipal.ObjectType, - PrincipalSID = resolvedPrincipal.ObjectIdentifier, - IsInherited = inherited, - RightName = EdgeNames.AllExtendedRights, - InheritanceHash = aceInheritanceHash - }; - else if (_guidMap.TryGetValue(aceType, out var lapsAttribute)) - { - // Compare the retrieved attribute name against LDAPProperties values - if (lapsAttribute == LDAPProperties.LegacyLAPSPassword || - lapsAttribute == LDAPProperties.LAPSPlaintextPassword || - lapsAttribute == LDAPProperties.LAPSEncryptedPassword) - { - yield return new ACE - { - PrincipalType = resolvedPrincipal.ObjectType, - PrincipalSID = resolvedPrincipal.ObjectIdentifier, - IsInherited = inherited, - RightName = EdgeNames.ReadLAPSPassword, - InheritanceHash = aceInheritanceHash - }; - } - } - } - } else if (objectType == Label.CertTemplate) { - if (aceType is ACEGuids.AllGuid or "") - yield return new ACE { - PrincipalType = resolvedPrincipal.ObjectType, - PrincipalSID = resolvedPrincipal.ObjectIdentifier, - IsInherited = inherited, - RightName = EdgeNames.AllExtendedRights, - InheritanceHash = aceInheritanceHash - }; - else if (aceType is ACEGuids.Enroll) - yield return new ACE { - PrincipalType = resolvedPrincipal.ObjectType, - PrincipalSID = resolvedPrincipal.ObjectIdentifier, - IsInherited = inherited, - RightName = EdgeNames.Enroll, - InheritanceHash = aceInheritanceHash - }; - } - } - - //GenericWrite encapsulates WriteProperty, so process them in tandem to avoid duplicate edges - if (aceRights.HasFlag(ActiveDirectoryRights.GenericWrite) || - aceRights.HasFlag(ActiveDirectoryRights.WriteProperty)) { - if (objectType is Label.User - or Label.Group - or Label.Computer - or Label.GPO - or Label.OU - or Label.Domain - or Label.CertTemplate - or Label.RootCA - or Label.EnterpriseCA - or Label.AIACA - or Label.NTAuthStore - or Label.IssuancePolicy) - if (aceType is ACEGuids.AllGuid or "") - yield return new ACE { - PrincipalType = resolvedPrincipal.ObjectType, - PrincipalSID = resolvedPrincipal.ObjectIdentifier, - IsInherited = inherited, - RightName = EdgeNames.GenericWrite, - InheritanceHash = aceInheritanceHash - }; - - if (objectType == Label.User && aceType == ACEGuids.WriteSPN) - yield return new ACE { - PrincipalType = resolvedPrincipal.ObjectType, - PrincipalSID = resolvedPrincipal.ObjectIdentifier, - IsInherited = inherited, - RightName = EdgeNames.WriteSPN, - InheritanceHash = aceInheritanceHash - }; - else if (objectType == Label.Computer && aceType == ACEGuids.WriteAllowedToAct) - yield return new ACE { - PrincipalType = resolvedPrincipal.ObjectType, - PrincipalSID = resolvedPrincipal.ObjectIdentifier, - IsInherited = inherited, - RightName = EdgeNames.AddAllowedToAct, - InheritanceHash = aceInheritanceHash - }; - else if (objectType == Label.Computer && aceType == ACEGuids.UserAccountRestrictions) - yield return new ACE { - PrincipalType = resolvedPrincipal.ObjectType, - PrincipalSID = resolvedPrincipal.ObjectIdentifier, - IsInherited = inherited, - RightName = EdgeNames.WriteAccountRestrictions, - InheritanceHash = aceInheritanceHash - }; - else if (objectType is Label.OU or Label.Domain && aceType == ACEGuids.WriteGPLink) - yield return new ACE - { - PrincipalType = resolvedPrincipal.ObjectType, - PrincipalSID = resolvedPrincipal.ObjectIdentifier, - IsInherited = inherited, - RightName = EdgeNames.WriteGPLink, - InheritanceHash = aceInheritanceHash - }; - else if (objectType == Label.Group && aceType == ACEGuids.WriteMember) - yield return new ACE { - PrincipalType = resolvedPrincipal.ObjectType, - PrincipalSID = resolvedPrincipal.ObjectIdentifier, - IsInherited = inherited, - RightName = EdgeNames.AddMember, - InheritanceHash = aceInheritanceHash - }; - else if (objectType is Label.User or Label.Computer && aceType == ACEGuids.AddKeyPrincipal) - yield return new ACE { - PrincipalType = resolvedPrincipal.ObjectType, - PrincipalSID = resolvedPrincipal.ObjectIdentifier, - IsInherited = inherited, - RightName = EdgeNames.AddKeyCredentialLink, - InheritanceHash = aceInheritanceHash - }; - else if (objectType is Label.CertTemplate) { - if (aceType == ACEGuids.PKIEnrollmentFlag) - yield return new ACE { - PrincipalType = resolvedPrincipal.ObjectType, - PrincipalSID = resolvedPrincipal.ObjectIdentifier, - IsInherited = inherited, - RightName = EdgeNames.WritePKIEnrollmentFlag, - InheritanceHash = aceInheritanceHash - }; - else if (aceType == ACEGuids.PKINameFlag) - yield return new ACE { - PrincipalType = resolvedPrincipal.ObjectType, - PrincipalSID = resolvedPrincipal.ObjectIdentifier, - IsInherited = inherited, - RightName = EdgeNames.WritePKINameFlag, - InheritanceHash = aceInheritanceHash - }; - } - } - - // EnterpriseCA rights - if (objectType == Label.EnterpriseCA) { - if (aceType is ACEGuids.Enroll) - yield return new ACE { - PrincipalType = resolvedPrincipal.ObjectType, - PrincipalSID = resolvedPrincipal.ObjectIdentifier, - IsInherited = inherited, - RightName = EdgeNames.Enroll, - InheritanceHash = aceInheritanceHash - }; - - var cARights = (CertificationAuthorityRights)aceRights; - - // TODO: These if statements are also present in ProcessRegistryEnrollmentPermissions. Move to shared location. - if ((cARights & CertificationAuthorityRights.ManageCA) != 0) - yield return new ACE { - PrincipalType = resolvedPrincipal.ObjectType, - PrincipalSID = resolvedPrincipal.ObjectIdentifier, - IsInherited = inherited, - RightName = EdgeNames.ManageCA, - InheritanceHash = aceInheritanceHash - }; - if ((cARights & CertificationAuthorityRights.ManageCertificates) != 0) - yield return new ACE { - PrincipalType = resolvedPrincipal.ObjectType, - PrincipalSID = resolvedPrincipal.ObjectIdentifier, - IsInherited = inherited, - RightName = EdgeNames.ManageCertificates, - InheritanceHash = aceInheritanceHash - }; - - if ((cARights & CertificationAuthorityRights.Enroll) != 0) - yield return new ACE { - PrincipalType = resolvedPrincipal.ObjectType, - PrincipalSID = resolvedPrincipal.ObjectIdentifier, - IsInherited = inherited, - RightName = EdgeNames.Enroll, - InheritanceHash = aceInheritanceHash - }; - } + Label objectType, bool hasLaps, string objectName = "") + { + var (aces, _, _) = await ProcessACL(ntSecurityDescriptor, objectDomain, objectType, hasLaps, objectName, true); + foreach (var ace in aces) + { + yield return ace; } } From e92a5336f28934fc026ba878ec2e7f484a8bc41e Mon Sep 17 00:00:00 2001 From: Mayyhem Date: Thu, 5 Dec 2024 13:21:49 -0500 Subject: [PATCH 5/5] Change optional parameters to required --- src/CommonLib/Processors/ACLProcessor.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/CommonLib/Processors/ACLProcessor.cs b/src/CommonLib/Processors/ACLProcessor.cs index f63c8ad2..49e0b13f 100644 --- a/src/CommonLib/Processors/ACLProcessor.cs +++ b/src/CommonLib/Processors/ACLProcessor.cs @@ -235,7 +235,7 @@ public IAsyncEnumerable ProcessACL(ResolvedSearchResult result, IDirectoryO { return (Array.Empty(), false, false); } - return await ProcessACL(descriptor, result.Domain, result.ObjectType, searchResult.HasLAPS(), result.DisplayName, checkForOwnerRights); + return await ProcessACL(descriptor, result.Domain, result.ObjectType, searchResult.HasLAPS(), checkForOwnerRights, result.DisplayName); } /// @@ -251,7 +251,7 @@ public IAsyncEnumerable ProcessACL(ResolvedSearchResult result, IDirectoryO public async IAsyncEnumerable ProcessACL(byte[] ntSecurityDescriptor, string objectDomain, Label objectType, bool hasLaps, string objectName = "") { - var (aces, _, _) = await ProcessACL(ntSecurityDescriptor, objectDomain, objectType, hasLaps, objectName, true); + var (aces, _, _) = await ProcessACL(ntSecurityDescriptor, objectDomain, objectType, hasLaps, true, objectName); foreach (var ace in aces) { yield return ace; @@ -259,7 +259,7 @@ public async IAsyncEnumerable ProcessACL(byte[] ntSecurityDescriptor, strin } public async Task<(ACE[], bool, bool)> ProcessACL(byte[] ntSecurityDescriptor, string objectDomain, - Label objectType, bool hasLaps, string objectName = "", bool checkForOwnerRights = true) + Label objectType, bool hasLaps, bool checkForOwnerRights, string objectName) { var aces = new List(); bool isAnyPermissionForOwnerRightsSid = false;