< Summary

Class:SharpHoundCommonLib.SearchResultEntryWrapper
Assembly:SharpHoundCommonLib
File(s):D:\a\SharpHoundCommon\SharpHoundCommon\src\CommonLib\SearchResultEntryWrapper.cs
Covered lines:0
Uncovered lines:154
Coverable lines:154
Total lines:281
Line coverage:0% (0 of 154)
Covered branches:0
Total branches:58
Branch coverage:0% (0 of 58)

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.ctor(...)0%400%
ResolveBloodHoundInfo()0%5000%
GetProperty(...)100%100%
GetByteProperty(...)100%100%
GetArrayProperty(...)100%100%
GetByteArrayProperty(...)100%100%
GetIntProperty(...)100%100%
GetCertificateArrayProperty(...)100%100%
GetObjectIdentifier()100%100%
IsDeleted()100%100%
GetLabel()100%100%
GetSid()100%100%
GetGuid()100%100%
PropCount(...)100%100%
PropertyNames()0%200%
IsMSA()100%100%
IsGMSA()100%100%
HasLAPS()0%200%
GetEntry()100%100%

File(s)

D:\a\SharpHoundCommon\SharpHoundCommon\src\CommonLib\SearchResultEntryWrapper.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.DirectoryServices.Protocols;
 4using System.Linq;
 5using System.Security.Cryptography.X509Certificates;
 6using System.Security.Principal;
 7using Microsoft.Extensions.Logging;
 8using SharpHoundCommonLib.Enums;
 9
 10namespace SharpHoundCommonLib
 11{
 12    public interface ISearchResultEntry
 13    {
 14        string DistinguishedName { get; }
 15        ResolvedSearchResult ResolveBloodHoundInfo();
 16        string GetProperty(string propertyName);
 17        byte[] GetByteProperty(string propertyName);
 18        string[] GetArrayProperty(string propertyName);
 19        byte[][] GetByteArrayProperty(string propertyName);
 20        bool GetIntProperty(string propertyName, out int value);
 21        X509Certificate2[] GetCertificateArrayProperty(string propertyName);
 22        string GetObjectIdentifier();
 23        bool IsDeleted();
 24        Label GetLabel();
 25        string GetSid();
 26        string GetGuid();
 27        int PropCount(string prop);
 28        IEnumerable<string> PropertyNames();
 29        bool IsMSA();
 30        bool IsGMSA();
 31        bool HasLAPS();
 32    }
 33
 34    public class SearchResultEntryWrapper : ISearchResultEntry
 35    {
 36        private const string GMSAClass = "msds-groupmanagedserviceaccount";
 37        private const string MSAClass = "msds-managedserviceaccount";
 38        private readonly SearchResultEntry _entry;
 39        private readonly ILogger _log;
 40        private readonly ILDAPUtils _utils;
 41
 042        public SearchResultEntryWrapper(SearchResultEntry entry, ILDAPUtils utils = null, ILogger log = null)
 043        {
 044            _entry = entry;
 045            _utils = utils ?? new LDAPUtils();
 046            _log = log ?? Logging.LogProvider.CreateLogger("SearchResultWrapper");
 047        }
 48
 049        public string DistinguishedName => _entry.DistinguishedName;
 50
 51        public ResolvedSearchResult ResolveBloodHoundInfo()
 052        {
 053            var res = new ResolvedSearchResult();
 54
 055            var objectId = GetObjectIdentifier();
 056            if (objectId == null)
 057            {
 058                _log.LogWarning("ObjectIdentifier is null for {DN}", DistinguishedName);
 059                return null;
 60            }
 61
 062            var uac = _entry.GetProperty(LDAPProperties.UserAccountControl);
 063            if (int.TryParse(uac, out var flag))
 064            {
 065                var flags = (UacFlags) flag;
 066                if (flags.HasFlag(UacFlags.ServerTrustAccount))
 067                {
 068                    _log.LogTrace("Marked {SID} as a domain controller", objectId);
 069                    res.IsDomainController = true;
 070                    _utils.AddDomainController(objectId);
 071                }
 072            }
 73
 74            //Try to resolve the domain
 075            var distinguishedName = DistinguishedName;
 76            string itemDomain;
 077            if (distinguishedName == null)
 078            {
 079                if (objectId.StartsWith("S-1-"))
 080                {
 081                    itemDomain = _utils.GetDomainNameFromSid(objectId);
 082                }
 83                else
 084                {
 085                    _log.LogWarning("Failed to resolve domain for {ObjectID}", objectId);
 086                    return null;
 87                }
 088            }
 89            else
 090            {
 091                itemDomain = Helpers.DistinguishedNameToDomain(distinguishedName);
 092            }
 93
 094            _log.LogTrace("Resolved domain for {SID} to {Domain}", objectId, itemDomain);
 95
 096            res.ObjectId = objectId;
 097            res.Domain = itemDomain;
 098            if (IsDeleted())
 099            {
 0100                res.Deleted = IsDeleted();
 0101                _log.LogTrace("{SID} is tombstoned, skipping rest of resolution", objectId);
 0102                return res;
 103            }
 104
 0105            if (WellKnownPrincipal.GetWellKnownPrincipal(objectId, out var wkPrincipal))
 0106            {
 0107                res.DomainSid = _utils.GetSidFromDomainName(itemDomain);
 0108                res.DisplayName = $"{wkPrincipal.ObjectIdentifier}@{itemDomain}";
 0109                res.ObjectType = wkPrincipal.ObjectType;
 0110                res.ObjectId = _utils.ConvertWellKnownPrincipal(objectId, itemDomain);
 111
 0112                _log.LogTrace("Resolved {DN} to wkp {ObjectID}", DistinguishedName, res.ObjectId);
 0113                return res;
 114            }
 115
 0116            if (objectId.StartsWith("S-1-"))
 117                try
 0118                {
 0119                    res.DomainSid = new SecurityIdentifier(objectId).AccountDomainSid.Value;
 0120                }
 0121                catch
 0122                {
 0123                    res.DomainSid = _utils.GetSidFromDomainName(itemDomain);
 0124                }
 125            else
 0126                res.DomainSid = _utils.GetSidFromDomainName(itemDomain);
 127
 0128            var samAccountName = GetProperty(LDAPProperties.SAMAccountName);
 129
 0130            var itemType = GetLabel();
 0131            res.ObjectType = itemType;
 132
 0133            if (IsGMSA() || IsMSA())
 0134            {
 0135                res.ObjectType = Label.User;
 0136                itemType = Label.User;
 0137            }
 138
 0139            _log.LogTrace("Resolved type for {SID} to {Label}", objectId, itemType);
 140
 0141            switch (itemType)
 142            {
 143                case Label.User:
 144                case Label.Group:
 145                case Label.Base:
 0146                    res.DisplayName = $"{samAccountName}@{itemDomain}";
 0147                    break;
 148                case Label.Computer:
 0149                {
 0150                    var shortName = samAccountName?.TrimEnd('$');
 0151                    var dns = GetProperty(LDAPProperties.DNSHostName);
 0152                    var cn = GetProperty(LDAPProperties.CanonicalName);
 153
 0154                    if (dns != null)
 0155                        res.DisplayName = dns;
 0156                    else if (shortName == null && cn == null)
 0157                        res.DisplayName = $"UNKNOWN.{itemDomain}";
 0158                    else if (shortName != null)
 0159                        res.DisplayName = $"{shortName}.{itemDomain}";
 160                    else
 0161                        res.DisplayName = $"{cn}.{itemDomain}";
 162
 0163                    break;
 164                }
 165                case Label.GPO:
 166                case Label.IssuancePolicy:
 0167                {
 0168                    var cn = GetProperty(LDAPProperties.CanonicalName);
 0169                    var displayName = GetProperty(LDAPProperties.DisplayName);
 0170                    res.DisplayName = string.IsNullOrEmpty(displayName) ? $"{cn}@{itemDomain}" : $"{GetProperty(LDAPProp
 0171                    break;
 172                }
 173                case Label.Domain:
 0174                    res.DisplayName = itemDomain;
 0175                    break;
 176                case Label.OU:
 177                case Label.Container:
 178                case Label.Configuration:
 179                case Label.RootCA:
 180                case Label.AIACA:
 181                case Label.NTAuthStore:
 182                case Label.EnterpriseCA:
 183                case Label.CertTemplate:
 0184                    res.DisplayName = $"{GetProperty(LDAPProperties.Name)}@{itemDomain}";
 0185                    break;
 186                default:
 0187                    throw new ArgumentOutOfRangeException();
 188            }
 189
 0190            return res;
 0191        }
 192
 193        public string GetProperty(string propertyName)
 0194        {
 0195            return _entry.GetProperty(propertyName);
 0196        }
 197
 198        public byte[] GetByteProperty(string propertyName)
 0199        {
 0200            return _entry.GetPropertyAsBytes(propertyName);
 0201        }
 202
 203        public string[] GetArrayProperty(string propertyName)
 0204        {
 0205            return _entry.GetPropertyAsArray(propertyName);
 0206        }
 207
 208        public byte[][] GetByteArrayProperty(string propertyName)
 0209        {
 0210            return _entry.GetPropertyAsArrayOfBytes(propertyName);
 0211        }
 212
 213        public bool GetIntProperty(string propertyName, out int value)
 0214        {
 0215            return _entry.GetPropertyAsInt(propertyName, out value);
 0216        }
 217
 218        public X509Certificate2[] GetCertificateArrayProperty(string propertyName)
 0219        {
 0220            return _entry.GetPropertyAsArrayOfCertificates(propertyName);
 0221        }
 222
 223        public string GetObjectIdentifier()
 0224        {
 0225            return _entry.GetObjectIdentifier();
 0226        }
 227
 228        public bool IsDeleted()
 0229        {
 0230            return _entry.IsDeleted();
 0231        }
 232
 233        public Label GetLabel()
 0234        {
 0235            return _entry.GetLabel();
 0236        }
 237
 238        public string GetSid()
 0239        {
 0240            return _entry.GetSid();
 0241        }
 242
 243        public string GetGuid()
 0244        {
 0245            return _entry.GetGuid();
 0246        }
 247
 248        public int PropCount(string prop)
 0249        {
 0250            var coll = _entry.Attributes[prop];
 0251            return coll.Count;
 0252        }
 253
 254        public IEnumerable<string> PropertyNames()
 0255        {
 0256            foreach (var property in _entry.Attributes.AttributeNames) yield return property.ToString().ToLower();
 0257        }
 258
 259        public bool IsMSA()
 0260        {
 0261            var classes = GetArrayProperty(LDAPProperties.ObjectClass);
 0262            return classes.Contains(MSAClass, StringComparer.InvariantCultureIgnoreCase);
 0263        }
 264
 265        public bool IsGMSA()
 0266        {
 0267            var classes = GetArrayProperty(LDAPProperties.ObjectClass);
 0268            return classes.Contains(GMSAClass, StringComparer.InvariantCultureIgnoreCase);
 0269        }
 270
 271        public bool HasLAPS()
 0272        {
 0273            return GetProperty(LDAPProperties.LAPSExpirationTime) != null || GetProperty(LDAPProperties.LegacyLAPSExpira
 0274        }
 275
 276        public SearchResultEntry GetEntry()
 0277        {
 0278            return _entry;
 0279        }
 280    }
 281}