From 2343b28f8a222f34378d14027b60789eb4973845 Mon Sep 17 00:00:00 2001 From: Rohan Vazarkar Date: Thu, 17 Oct 2024 15:05:30 -0400 Subject: [PATCH 1/3] fix: switch to SHA1 and add a try/catch block on acl inheritance hashing (#172) * fix: switch to MD5 and add a try/catch block on acl inheritance hashing * chore: add missing inheritancehash value for owns * chore: switch to bitconverter for perf --- src/CommonLib/Processors/ACLProcessor.cs | 34 +++++++++++++++--------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/src/CommonLib/Processors/ACLProcessor.cs b/src/CommonLib/Processors/ACLProcessor.cs index 7afbb9d5..cec3692b 100644 --- a/src/CommonLib/Processors/ACLProcessor.cs +++ b/src/CommonLib/Processors/ACLProcessor.cs @@ -135,17 +135,21 @@ internal static string CalculateInheritanceHash(string identityReference, Active string aceType, string inheritedObjectType) { var hash = identityReference + rights + aceType + inheritedObjectType; /* - * We're using MD5 because its fast and this data isn't cryptographically important. + * We're using SHA1 because its fast and this data isn't cryptographically important. * Additionally, the chances of a collision in our data size is miniscule and irrelevant. + * We cannot use MD5 as it is not FIPS compliant and environments can enforce this setting */ - using (var md5 = MD5.Create()) { - var bytes = md5.ComputeHash(Encoding.UTF8.GetBytes(hash)); - var builder = new StringBuilder(); - foreach (var b in bytes) { - builder.Append(b.ToString("x2")); + try + { + using (var sha1 = SHA1.Create()) + { + var bytes = sha1.ComputeHash(Encoding.UTF8.GetBytes(hash)); + return BitConverter.ToString(bytes).Replace("-", string.Empty).ToUpper(); } - - return builder.ToString(); + } + catch + { + return ""; } } @@ -209,8 +213,12 @@ public IEnumerable GetInheritedAceHashes(byte[] ntSecurityDescriptor, st //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 inheritanceType = ace.InheritedObjectType(); - - yield return CalculateInheritanceHash(ir, aceRights, aceType, inheritanceType); + + var hash = CalculateInheritanceHash(ir, aceRights, aceType, inheritanceType); + if (!string.IsNullOrEmpty(hash)) + { + yield return hash; + } } } @@ -256,7 +264,8 @@ public async IAsyncEnumerable ProcessACL(byte[] ntSecurityDescriptor, strin PrincipalType = resolvedOwner.ObjectType, PrincipalSID = resolvedOwner.ObjectIdentifier, RightName = EdgeNames.Owns, - IsInherited = false + IsInherited = false, + InheritanceHash = "" }; } else { _log.LogTrace("Failed to resolve owner for {Name}", objectName); @@ -264,7 +273,8 @@ public async IAsyncEnumerable ProcessACL(byte[] ntSecurityDescriptor, strin PrincipalType = Label.Base, PrincipalSID = ownerSid, RightName = EdgeNames.Owns, - IsInherited = false + IsInherited = false, + InheritanceHash = "" }; } } From 1d9e9c070d18e5519e7e0331d1c659d34718d6b6 Mon Sep 17 00:00:00 2001 From: spyr0 <78267628+spyr0-sec@users.noreply.github.com> Date: Fri, 18 Oct 2024 16:09:10 +0100 Subject: [PATCH 2/3] Fixed LAPS attributes (#167) * Update LDAPProperties.cs Updated LAPS password attributes * Update ACLProcessor.cs Updated logic to create ReadLAPSPassword edges based on updated LAPS password attributes * Update ACLProcessor.cs Updated logic to pull GUIDs for new LAPS password attributes * Update LDAPProperties.cs Corrected new LAPS password expiry attribute * Fixed ReadLAPSPassword Logic --------- Co-authored-by: Rohan Vazarkar --- src/CommonLib/Enums/LDAPProperties.cs | 3 ++- src/CommonLib/Processors/ACLProcessor.cs | 30 +++++++++++++++--------- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/src/CommonLib/Enums/LDAPProperties.cs b/src/CommonLib/Enums/LDAPProperties.cs index 0b202e46..25bd4a8b 100644 --- a/src/CommonLib/Enums/LDAPProperties.cs +++ b/src/CommonLib/Enums/LDAPProperties.cs @@ -36,7 +36,8 @@ public static class LDAPProperties public const string ServicePack = "operatingsystemservicepack"; public const string DNSHostName = "dnshostname"; public const string LAPSExpirationTime = "mslaps-passwordexpirationtime"; - public const string LAPSPassword = "mslaps-password"; + public const string LAPSPlaintextPassword = "ms-laps-password"; + public const string LAPSEncryptedPassword = "ms-laps-encryptedpassword"; public const string LegacyLAPSExpirationTime = "ms-mcs-admpwdexpirationtime"; public const string LegacyLAPSPassword = "ms-mcs-admpwd"; public const string Members = "member"; diff --git a/src/CommonLib/Processors/ACLProcessor.cs b/src/CommonLib/Processors/ACLProcessor.cs index cec3692b..34aff91f 100644 --- a/src/CommonLib/Processors/ACLProcessor.cs +++ b/src/CommonLib/Processors/ACLProcessor.cs @@ -64,6 +64,7 @@ private async Task BuildGuidCache(string domain) { } name = name.ToLower(); + string guid; try { @@ -74,7 +75,7 @@ private async Task BuildGuidCache(string domain) { continue; } - if (name is LDAPProperties.LAPSPassword or LDAPProperties.LegacyLAPSPassword) { + 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); } @@ -309,8 +310,6 @@ public async IAsyncEnumerable ProcessACL(byte[] ntSecurityDescriptor, strin aceInheritanceHash = CalculateInheritanceHash(ir, aceRights, aceType, ace.InheritedObjectType()); } - _guidMap.TryGetValue(aceType, out var mappedGuid); - _log.LogTrace("Processing ACE with rights {Rights} and guid {GUID} on object {Name}", aceRights, aceType, objectName); @@ -423,14 +422,23 @@ public async IAsyncEnumerable ProcessACL(byte[] ntSecurityDescriptor, strin RightName = EdgeNames.AllExtendedRights, InheritanceHash = aceInheritanceHash }; - else if (mappedGuid is LDAPProperties.LegacyLAPSPassword or LDAPProperties.LAPSPassword) - yield return new ACE { - PrincipalType = resolvedPrincipal.ObjectType, - PrincipalSID = resolvedPrincipal.ObjectIdentifier, - IsInherited = inherited, - RightName = EdgeNames.ReadLAPSPassword, - 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 "") From 859d470e73f2187bd4b0cdfc9ed234b756cf8439 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20B=C3=BClow=20Knudsen?= <12843299+JonasBK@users.noreply.github.com> Date: Fri, 18 Oct 2024 17:46:31 +0200 Subject: [PATCH 3/3] prep for CoerceToTGT (#171) --- src/CommonLib/OutputTypes/Computer.cs | 1 + src/CommonLib/OutputTypes/User.cs | 2 ++ src/CommonLib/Processors/LdapPropertyProcessor.cs | 6 ++++++ 3 files changed, 9 insertions(+) diff --git a/src/CommonLib/OutputTypes/Computer.cs b/src/CommonLib/OutputTypes/Computer.cs index 2fbcfcf5..879013d5 100644 --- a/src/CommonLib/OutputTypes/Computer.cs +++ b/src/CommonLib/OutputTypes/Computer.cs @@ -20,6 +20,7 @@ public class Computer : OutputBase public DCRegistryData DCRegistryData { get; set; } = new(); public ComputerStatus Status { get; set; } public bool IsDC { get; set; } + public bool UnconstrainedDelegation { get; set; } public string DomainSID { get; set; } } diff --git a/src/CommonLib/OutputTypes/User.cs b/src/CommonLib/OutputTypes/User.cs index 387d1f0f..7de2ce95 100644 --- a/src/CommonLib/OutputTypes/User.cs +++ b/src/CommonLib/OutputTypes/User.cs @@ -8,5 +8,7 @@ public class User : OutputBase public string PrimaryGroupSID { get; set; } public TypedPrincipal[] HasSIDHistory { get; set; } = Array.Empty(); public SPNPrivilege[] SPNTargets { get; set; } = Array.Empty(); + public bool UnconstrainedDelegation { get; set; } + public string DomainSID { get; set; } } } \ No newline at end of file diff --git a/src/CommonLib/Processors/LdapPropertyProcessor.cs b/src/CommonLib/Processors/LdapPropertyProcessor.cs index 8577f0fe..c12764b1 100644 --- a/src/CommonLib/Processors/LdapPropertyProcessor.cs +++ b/src/CommonLib/Processors/LdapPropertyProcessor.cs @@ -206,6 +206,8 @@ public async Task ReadUserProperties(IDirectoryObject entry, str props.Add("passwordcantchange", uacFlags.HasFlag(UacFlags.PasswordCantChange)); props.Add("passwordexpired", uacFlags.HasFlag(UacFlags.PasswordExpired)); + userProps.UnconstrainedDelegation = uacFlags.HasFlag(UacFlags.TrustedForDelegation); + var comps = new List(); if (uacFlags.HasFlag(UacFlags.TrustedToAuthForDelegation) && entry.TryGetArrayProperty(LDAPProperties.AllowedToDelegateTo, out var delegates)) { @@ -321,6 +323,8 @@ public async Task ReadComputerProperties(IDirectoryObject en props.Add("lockedout", flags.HasFlag(UacFlags.Lockout)); props.Add("passwordexpired", flags.HasFlag(UacFlags.PasswordExpired)); + compProps.UnconstrainedDelegation = flags.HasFlag(UacFlags.TrustedForDelegation); + var encryptionTypes = ConvertEncryptionTypes(entry.GetProperty(LDAPProperties.SupportedEncryptionTypes)); props.Add("supportedencryptiontypes", encryptionTypes); @@ -908,6 +912,7 @@ public class UserProperties { public Dictionary Props { get; set; } = new(); public TypedPrincipal[] AllowedToDelegate { get; set; } = Array.Empty(); public TypedPrincipal[] SidHistory { get; set; } = Array.Empty(); + public bool UnconstrainedDelegation { get; set; } } public class ComputerProperties { @@ -916,6 +921,7 @@ public class ComputerProperties { public TypedPrincipal[] AllowedToAct { get; set; } = Array.Empty(); public TypedPrincipal[] SidHistory { get; set; } = Array.Empty(); public TypedPrincipal[] DumpSMSAPassword { get; set; } = Array.Empty(); + public bool UnconstrainedDelegation { get; set; } } public class IssuancePolicyProperties {