diff --git a/src/CommonLib/Processors/ACLProcessor.cs b/src/CommonLib/Processors/ACLProcessor.cs index 45af8136..49e0b13f 100644 --- a/src/CommonLib/Processors/ACLProcessor.cs +++ b/src/CommonLib/Processors/ACLProcessor.cs @@ -58,14 +58,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 +83,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 +92,7 @@ private async Task BuildGuidCache(string domain) { _log.LogDebug("Error while building GUID cache for {Domain}: {Message}", domain, result.Error); } } - + } /// @@ -123,25 +123,6 @@ public bool IsACLProtected(byte[] ntSecurityDescriptor) { return descriptor.AreAccessRulesProtected(); } - /// - /// Helper function 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(); - } - - var domain = result.Domain; - var type = result.ObjectType; - var hasLaps = searchResult.HasLAPS(); - var name = result.DisplayName; - - return ProcessACL(descriptor, domain, type, hasLaps, name); - } - internal static string CalculateInheritanceHash(string identityReference, ActiveDirectoryRights rights, string aceType, string inheritedObjectType) { var hash = identityReference + rights + aceType + inheritedObjectType; @@ -233,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(), checkForOwnerRights, result.DisplayName); + } + /// /// 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 @@ -244,51 +249,75 @@ public IEnumerable GetInheritedAceHashes(byte[] ntSecurityDescriptor, st /// /// public async IAsyncEnumerable ProcessACL(byte[] ntSecurityDescriptor, string objectDomain, - Label objectType, - bool hasLaps, string objectName = "") { + Label objectType, bool hasLaps, string objectName = "") + { + var (aces, _, _) = await ProcessACL(ntSecurityDescriptor, objectDomain, objectType, hasLaps, true, objectName); + foreach (var ace in aces) + { + yield return ace; + } + } + + public async Task<(ACE[], bool, bool)> ProcessACL(byte[] ntSecurityDescriptor, string objectDomain, + Label objectType, bool hasLaps, bool checkForOwnerRights, string objectName) + { + var aces = new List(); + bool isAnyPermissionForOwnerRightsSid = false; + bool isAnyPermissionForOwnerRightsSidInherited = false; + await BuildGuidCache(objectDomain); - if (ntSecurityDescriptor == null) { + if (ntSecurityDescriptor == null) + { _log.LogDebug("Security Descriptor is null for {Name}", objectName); - yield break; } var descriptor = _utils.MakeSecurityDescriptor(); - try { + try + { descriptor.SetSecurityDescriptorBinaryForm(ntSecurityDescriptor); - } catch (OverflowException) { + } + 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 { + 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 { + }); + } + else + { _log.LogTrace("Failed to resolve owner for {Name}", objectName); - yield return new ACE { + 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])) { + + foreach (var ace in descriptor.GetAccessRules(true, true, typeof(SecurityIdentifier))) + { + if (ace == null || ace.AccessControlType() == AccessControlType.Deny || !ace.IsAceInheritedFrom(BaseGuids[objectType])) + { continue; } @@ -296,139 +325,173 @@ public async IAsyncEnumerable ProcessACL(byte[] ntSecurityDescriptor, strin var principalSid = Helpers.PreProcessSID(ir); //Preprocess returns null if this is an ignored sid - if (principalSid == null) { + if (principalSid == null) + { continue; } var (success, resolvedPrincipal) = await _utils.ResolveIDAndType(principalSid, objectDomain); - if (!success) { + 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) { + 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, aceType, objectName); //GenericAll applies to every object - if (aceRights.HasFlag(ActiveDirectoryRights.GenericAll)) { + if (aceRights.HasFlag(ActiveDirectoryRights.GenericAll)) + { if (aceType is ACEGuids.AllGuid or "") - yield return new ACE { + 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)) - yield return new ACE { + aces.Add(new ACE + { PrincipalType = resolvedPrincipal.ObjectType, PrincipalSID = resolvedPrincipal.ObjectIdentifier, IsInherited = inherited, RightName = EdgeNames.WriteDacl, InheritanceHash = aceInheritanceHash - }; + }); if (aceRights.HasFlag(ActiveDirectoryRights.WriteOwner)) - yield return new ACE { + 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) - yield return new ACE { + 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 (aceRights.HasFlag(ActiveDirectoryRights.ExtendedRight)) + { + if (objectType == Label.Domain) + { if (aceType == ACEGuids.DSReplicationGetChanges) - yield return new ACE { + aces.Add(new ACE + { PrincipalType = resolvedPrincipal.ObjectType, PrincipalSID = resolvedPrincipal.ObjectIdentifier, IsInherited = inherited, RightName = EdgeNames.GetChanges, InheritanceHash = aceInheritanceHash - }; + }); else if (aceType == ACEGuids.DSReplicationGetChangesAll) - yield return new ACE { + aces.Add(new ACE + { PrincipalType = resolvedPrincipal.ObjectType, PrincipalSID = resolvedPrincipal.ObjectIdentifier, IsInherited = inherited, RightName = EdgeNames.GetChangesAll, InheritanceHash = aceInheritanceHash - }; + }); else if (aceType == ACEGuids.DSReplicationGetChangesInFilteredSet) - yield return new ACE { + aces.Add(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 { + aces.Add(new ACE + { PrincipalType = resolvedPrincipal.ObjectType, PrincipalSID = resolvedPrincipal.ObjectIdentifier, IsInherited = inherited, RightName = EdgeNames.AllExtendedRights, InheritanceHash = aceInheritanceHash - }; - } else if (objectType == Label.User) { + }); + } + else if (objectType == Label.User) + { if (aceType == ACEGuids.UserForceChangePassword) - yield return new ACE { + aces.Add(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 { + aces.Add(new ACE + { PrincipalType = resolvedPrincipal.ObjectType, PrincipalSID = resolvedPrincipal.ObjectIdentifier, IsInherited = inherited, RightName = EdgeNames.AllExtendedRights, InheritanceHash = aceInheritanceHash - }; - } else if (objectType == Label.Computer) { + }); + } + 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 (hasLaps) + { if (aceType is ACEGuids.AllGuid or "") - yield return new ACE { + 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 @@ -436,173 +499,194 @@ public async IAsyncEnumerable ProcessACL(byte[] ntSecurityDescriptor, strin lapsAttribute == LDAPProperties.LAPSPlaintextPassword || lapsAttribute == LDAPProperties.LAPSEncryptedPassword) { - yield return new ACE + aces.Add(new ACE { PrincipalType = resolvedPrincipal.ObjectType, PrincipalSID = resolvedPrincipal.ObjectIdentifier, IsInherited = inherited, RightName = EdgeNames.ReadLAPSPassword, InheritanceHash = aceInheritanceHash - }; + }); } } } - } else if (objectType == Label.CertTemplate) { + } + else if (objectType == Label.CertTemplate) + { if (aceType is ACEGuids.AllGuid or "") - yield return new ACE { + aces.Add(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 { + 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 + 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.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 { + aces.Add(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 { + 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) - yield return new ACE { + 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) - yield return new ACE { + 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) - yield return new ACE + 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) - yield return new ACE { + 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) - yield return new ACE { + aces.Add(new ACE + { PrincipalType = resolvedPrincipal.ObjectType, PrincipalSID = resolvedPrincipal.ObjectIdentifier, IsInherited = inherited, RightName = EdgeNames.AddKeyCredentialLink, InheritanceHash = aceInheritanceHash - }; - else if (objectType is Label.CertTemplate) { + }); + else if (objectType is Label.CertTemplate) + { if (aceType == ACEGuids.PKIEnrollmentFlag) - yield return new ACE { + aces.Add(new ACE + { PrincipalType = resolvedPrincipal.ObjectType, PrincipalSID = resolvedPrincipal.ObjectIdentifier, IsInherited = inherited, RightName = EdgeNames.WritePKIEnrollmentFlag, InheritanceHash = aceInheritanceHash - }; + }); else if (aceType == ACEGuids.PKINameFlag) - yield return new ACE { + aces.Add(new ACE + { PrincipalType = resolvedPrincipal.ObjectType, PrincipalSID = resolvedPrincipal.ObjectIdentifier, IsInherited = inherited, RightName = EdgeNames.WritePKINameFlag, InheritanceHash = aceInheritanceHash - }; + }); } } // EnterpriseCA rights - if (objectType == Label.EnterpriseCA) { + if (objectType == Label.EnterpriseCA) + { if (aceType is ACEGuids.Enroll) - yield return new ACE { + 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) - yield return new ACE { + aces.Add(new ACE + { PrincipalType = resolvedPrincipal.ObjectType, PrincipalSID = resolvedPrincipal.ObjectIdentifier, IsInherited = inherited, RightName = EdgeNames.ManageCA, InheritanceHash = aceInheritanceHash - }; + }); if ((cARights & CertificationAuthorityRights.ManageCertificates) != 0) - yield return new ACE { + aces.Add(new ACE + { PrincipalType = resolvedPrincipal.ObjectType, PrincipalSID = resolvedPrincipal.ObjectIdentifier, IsInherited = inherited, RightName = EdgeNames.ManageCertificates, InheritanceHash = aceInheritanceHash - }; + }); if ((cARights & CertificationAuthorityRights.Enroll) != 0) - yield return new ACE { + aces.Add(new ACE + { PrincipalType = resolvedPrincipal.ObjectType, PrincipalSID = resolvedPrincipal.ObjectIdentifier, IsInherited = inherited, RightName = EdgeNames.Enroll, InheritanceHash = aceInheritanceHash - }; + }); } } + return (aces.ToArray(), isAnyPermissionForOwnerRightsSid, isAnyPermissionForOwnerRightsSidInherited); } + /// /// Helper function to use commonlib types and pass to ProcessGMSAReaders ///