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/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/ACLProcessor.cs b/src/CommonLib/Processors/ACLProcessor.cs index 907ad758..45af8136 100644 --- a/src/CommonLib/Processors/ACLProcessor.cs +++ b/src/CommonLib/Processors/ACLProcessor.cs @@ -73,6 +73,7 @@ private async Task BuildGuidCache(string domain) { } name = name.ToLower(); + string guid; try { @@ -83,7 +84,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); } @@ -145,17 +146,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 ""; } } @@ -219,8 +224,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; + } } } @@ -263,7 +272,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); @@ -271,7 +281,8 @@ public async IAsyncEnumerable ProcessACL(byte[] ntSecurityDescriptor, strin PrincipalType = Label.Base, PrincipalSID = ownerSid, RightName = EdgeNames.Owns, - IsInherited = false + IsInherited = false, + InheritanceHash = "" }; } } @@ -306,8 +317,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); @@ -420,14 +429,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 "") diff --git a/src/CommonLib/Processors/LdapPropertyProcessor.cs b/src/CommonLib/Processors/LdapPropertyProcessor.cs index 3c640650..852619f7 100644 --- a/src/CommonLib/Processors/LdapPropertyProcessor.cs +++ b/src/CommonLib/Processors/LdapPropertyProcessor.cs @@ -227,6 +227,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)) { @@ -342,6 +344,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); @@ -929,6 +933,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 { @@ -937,6 +942,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 { diff --git a/src/CommonLib/SharpHoundCommonLib.csproj b/src/CommonLib/SharpHoundCommonLib.csproj index 85cffc92..6c88c8e8 100644 --- a/src/CommonLib/SharpHoundCommonLib.csproj +++ b/src/CommonLib/SharpHoundCommonLib.csproj @@ -9,7 +9,7 @@ Common library for C# BloodHound enumeration tasks GPL-3.0-only https://github.com/BloodHoundAD/SharpHoundCommon - 4.0.7 + 4.0.8 SharpHoundCommonLib SharpHoundCommonLib