public static ComputerProp GetComputerProps(SearchResultEntry entry, ResolvedEntry resolved)
        {
            var  uac = entry.GetProp("useraccountcontrol");
            bool enabled;

            if (int.TryParse(uac, out var flag))
            {
                var flags = (UacFlags)flag;
                enabled = (flags & UacFlags.AccountDisable) == 0;
            }
            else
            {
                enabled = true;
            }
            var lastLogon = ConvertToUnixEpoch(entry.GetProp("lastlogon"));
            var lastSet   = ConvertToUnixEpoch(entry.GetProp("pwdlastset"));
            var sid       = entry.GetSid();
            var os        = entry.GetProp("operatingsystem");
            var sp        = entry.GetProp("operatingsystemservicepack");

            if (sp != null)
            {
                os = $"{os} {sp}";
            }

            return(new ComputerProp
            {
                ComputerName = resolved.BloodHoundDisplay,
                Enabled = enabled,
                LastLogon = lastLogon,
                ObjectSid = sid,
                OperatingSystem = os,
                PwdLastSet = lastSet
            });
        }
예제 #2
0
        /// <summary>
        /// Processes the "member" property of groups and converts the resulting list of distinguishednames to TypedPrincipals
        /// </summary>
        /// <param name="entry"></param>
        /// <returns></returns>
        public static async IAsyncEnumerable <TypedPrincipal> ReadGroupMembers(SearchResultEntry entry)
        {
            var groupSid = entry.GetSid();

            Cache.AddConvertedValue(entry.DistinguishedName, groupSid);
            Cache.AddType(groupSid, Label.Group);

            var members = entry.GetPropertyAsArray("member");

            // If our returned array has a length of 0, one of two things is happening
            // The first possibility we'll look at is we need to use ranged retrieval, because AD will not return
            // more than a certain number of items. If we get nothing back from this, then the group is empty
            if (members.Length == 0)
            {
                foreach (var member in LDAPUtils.DoRangedRetrieval(entry.DistinguishedName, "member"))
                {
                    var res = await LDAPUtils.ResolveDistinguishedName(member);

                    if (res == null)
                    {
                        yield return new TypedPrincipal
                               {
                                   ObjectIdentifier = member,
                                   ObjectType       = Label.Unknown
                               }
                    }
                    ;
                    else
                    {
                        yield return(res);
                    }
                }
            }
            else
            {
                //If we're here, we just read the data directly and life is good
                foreach (var member in members)
                {
                    var res = await LDAPUtils.ResolveDistinguishedName(member);

                    if (res == null)
                    {
                        yield return new TypedPrincipal
                               {
                                   ObjectIdentifier = member,
                                   ObjectType       = Label.Unknown
                               }
                    }
                    ;
                    else
                    {
                        yield return(res);
                    }
                }
            }
        }
예제 #3
0
        public static UserProp GetUserProps(SearchResultEntry entry, ResolvedEntry resolved)
        {
            var  uac = entry.GetProp("useraccountcontrol");
            bool enabled;

            if (int.TryParse(uac, out var flag))
            {
                var flags = (UacFlags)flag;
                enabled = (flags & UacFlags.AccountDisable) == 0;
            }
            else
            {
                enabled = true;
            }
            var history          = entry.GetPropBytes("sidhistory");
            var lastlogon        = entry.GetProp("lastlogon");
            var pwdlastset       = entry.GetProp("pwdlastset");
            var spn              = entry.GetPropArray("serviceprincipalname");
            var displayName      = entry.GetProp("displayname");
            var hasSpn           = spn.Length != 0;
            var spnString        = string.Join("|", spn);
            var convertedlogon   = ConvertToUnixEpoch(lastlogon);
            var convertedlastset = ConvertToUnixEpoch(pwdlastset);
            var sid              = entry.GetSid();
            var sidhistory       = history != null ? new SecurityIdentifier(history, 0).Value : "";
            var mail             = entry.GetProp("mail");
            var domain           = resolved.BloodHoundDisplay.Split('@')[1].ToUpper();
            var title            = entry.GetProp("title");
            var homedir          = entry.GetProp("homeDirectory");

            return(new UserProp
            {
                AccountName = resolved.BloodHoundDisplay,
                Enabled = enabled,
                LastLogon = convertedlogon,
                ObjectSid = sid,
                PwdLastSet = convertedlastset,
                SidHistory = sidhistory,
                HasSpn = hasSpn,
                ServicePrincipalNames = spnString,
                DisplayName = displayName,
                Email = mail,
                Domain = domain,
                Title = title,
                HomeDirectory = homedir
            });
        }
예제 #4
0
        public static string GetObjectIdentifier(this SearchResultEntry searchResultEntry)
        {
            if (!searchResultEntry.Attributes.Contains("objectsid") &&
                !searchResultEntry.Attributes.Contains("objectguid"))
            {
                return(null);
            }

            if (searchResultEntry.Attributes.Contains("objectsid"))
            {
                return(searchResultEntry.GetSid());
            }

            var guidBytes = searchResultEntry.GetPropertyAsBytes("objectguid");

            return(new Guid(guidBytes).ToString().ToUpper());
        }
예제 #5
0
        /// <summary>
        /// Extension method to determine the type of a SearchResultEntry.
        /// Requires objectsid, samaccounttype, objectclass
        /// </summary>
        /// <param name="searchResultEntry"></param>
        /// <returns></returns>
        public static LdapTypeEnum GetLdapType(this SearchResultEntry searchResultEntry)
        {
            var objectSid = searchResultEntry.GetSid();

            if (CommonPrincipal.GetCommonSid(objectSid, out var commonPrincipal))
            {
                return(commonPrincipal.Type);
            }

            var objectType     = LdapTypeEnum.Unknown;
            var samAccountType = searchResultEntry.GetProperty("samaccounttype");

            //Its not a common principal. Lets use properties to figure out what it actually is
            if (samAccountType != null)
            {
                if (samAccountType == "805306370")
                {
                    return(LdapTypeEnum.Unknown);
                }

                objectType = Helpers.SamAccountTypeToType(samAccountType);
            }
            else
            {
                var objectClasses = searchResultEntry.GetPropertyAsArray("objectClass");
                if (objectClasses == null)
                {
                    objectType = LdapTypeEnum.Unknown;
                }
                else if (objectClasses.Contains("groupPolicyContainer"))
                {
                    objectType = LdapTypeEnum.GPO;
                }
                else if (objectClasses.Contains("organizationalUnit"))
                {
                    objectType = LdapTypeEnum.OU;
                }
                else if (objectClasses.Contains("domain"))
                {
                    objectType = LdapTypeEnum.Domain;
                }
            }
            return(objectType);
        }
예제 #6
0
        public static ComputerProp GetComputerProps(SearchResultEntry entry, ResolvedEntry resolved)
        {
            var  uac = entry.GetProp("useraccountcontrol");
            bool enabled;
            bool unconstrained;

            if (int.TryParse(uac, out var flag))
            {
                var flags = (UacFlags)flag;
                enabled       = (flags & UacFlags.AccountDisable) == 0;
                unconstrained = (flags & UacFlags.TrustedForDelegation) == UacFlags.TrustedForDelegation;
            }
            else
            {
                unconstrained = false;
                enabled       = true;
            }
            var lastLogon = ConvertToUnixEpoch(entry.GetProp("lastlogon"));
            var lastSet   = ConvertToUnixEpoch(entry.GetProp("pwdlastset"));
            var sid       = entry.GetSid();
            var os        = entry.GetProp("operatingsystem");
            var sp        = entry.GetProp("operatingsystemservicepack");
            var domainS   = resolved.BloodHoundDisplay.Split('.');

            domainS = domainS.Skip(1).ToArray();
            var domain = string.Join(".", domainS).ToUpper();

            if (sp != null)
            {
                os = $"{os} {sp}";
            }

            return(new ComputerProp
            {
                ComputerName = resolved.BloodHoundDisplay,
                Enabled = enabled,
                LastLogon = lastLogon,
                ObjectSid = sid,
                OperatingSystem = os,
                PwdLastSet = lastSet,
                UnconstrainedDelegation = unconstrained,
                Domain = domain
            });
        }
예제 #7
0
        /// <summary>
        /// Reads the primary group info from a user or computer object and massages it into the proper format.
        /// </summary>
        /// <param name="entry"></param>
        /// <returns></returns>
        public static string GetPrimaryGroupInfo(SearchResultEntry entry)
        {
            var primaryGroupId = entry.GetProperty("primarygroupid");

            if (primaryGroupId == null)
            {
                return(null);
            }

            try
            {
                var domainSid       = new SecurityIdentifier(entry.GetSid()).AccountDomainSid.Value;
                var primaryGroupSid = $"{domainSid}-{primaryGroupId}";
                return(primaryGroupSid);
            }
            catch
            {
                return(null);
            }
        }
 public string GetSid()
 {
     return(_entry.GetSid());
 }
예제 #9
0
        /// <summary>
        /// Converts a SaerchResultEntry into an LdapWrapper
        /// </summary>
        /// <param name="searchResultEntry"></param>
        /// <returns></returns>
        internal static LdapWrapper CreateLdapWrapper(SearchResultEntry searchResultEntry)
        {
            //Look for a null DN first. Not sure why this would happen.
            var distinguishedName = searchResultEntry.DistinguishedName;

            if (distinguishedName == null)
            {
                return(null);
            }

            var accountName    = searchResultEntry.GetProperty("samaccountname");
            var samAccountType = searchResultEntry.GetProperty("samaccounttype");
            var accountDomain  = Helpers.DistinguishedNameToDomain(distinguishedName);
            var objectSid      = searchResultEntry.GetSid();
            var objectId       = searchResultEntry.GetObjectIdentifier();

            //If objectsid/id is null, return
            if (objectSid == null && objectId == null)
            {
                return(null);
            }

            var    objectType = LdapTypeEnum.Unknown;
            string objectIdentifier;

            LdapWrapper wrapper;

            //Lets see if its a "common" principal
            if (objectSid != null && CommonPrincipal.GetCommonSid(objectSid, out var commonPrincipal))
            {
                accountName      = commonPrincipal.Name;
                objectType       = commonPrincipal.Type;
                objectIdentifier = Helpers.ConvertCommonSid(objectSid, accountDomain);
            }
            else
            {
                //Its not a common principal. Lets use properties to figure out what it actually is
                if (samAccountType != null)
                {
                    if (samAccountType == "805306370")
                    {
                        return(null);
                    }

                    objectType = Helpers.SamAccountTypeToType(samAccountType);
                }
                else
                {
                    var objectClasses = searchResultEntry.GetPropertyAsArray("objectClass");
                    if (objectClasses == null)
                    {
                        objectType = LdapTypeEnum.Unknown;
                    }
                    else if (objectClasses.Contains("groupPolicyContainer"))
                    {
                        objectType = LdapTypeEnum.GPO;
                    }
                    else if (objectClasses.Contains("organizationalUnit"))
                    {
                        objectType = LdapTypeEnum.OU;
                    }
                    else if (objectClasses.Contains("domain"))
                    {
                        objectType = LdapTypeEnum.Domain;
                    }
                }

                objectIdentifier = objectId;
            }

            //Override GMSA object type
            if (searchResultEntry.GetPropertyAsBytes("msds-groupmsamembership") != null)
            {
                objectType  = LdapTypeEnum.User;
                accountName = accountName?.TrimEnd('$');
            }

            //Depending on the object type, create the appropriate wrapper object
            switch (objectType)
            {
            case LdapTypeEnum.Computer:
                accountName = accountName?.TrimEnd('$');
                wrapper     = new Computer(searchResultEntry)
                {
                    DisplayName    = $"{accountName}.{accountDomain}".ToUpper(),
                    SamAccountName = accountName
                };

                var hasLaps = searchResultEntry.GetProperty("ms-mcs-admpwdexpirationtime") != null;
                wrapper.Properties.Add("haslaps", hasLaps);
                wrapper.Properties.Add("highvalue", false);
                break;

            case LdapTypeEnum.User:
                wrapper = new User(searchResultEntry)
                {
                    DisplayName = $"{accountName}@{accountDomain}".ToUpper()
                };
                wrapper.Properties.Add("highvalue", false);
                break;

            case LdapTypeEnum.Group:
                wrapper = new Group(searchResultEntry)
                {
                    DisplayName = $"{accountName}@{accountDomain}".ToUpper()
                };

                if (objectIdentifier.EndsWith("-512") || objectIdentifier.EndsWith("-516") || objectIdentifier.EndsWith("-519") ||
                    objectIdentifier.EndsWith("-520") || objectIdentifier.EndsWith("S-1-5-32-544") || objectIdentifier.EndsWith("S-1-5-32-550") ||
                    objectIdentifier.EndsWith("S-1-5-32-549") || objectIdentifier.EndsWith("S-1-5-32-551") || objectIdentifier.EndsWith("S-1-5-32-548"))
                {
                    wrapper.Properties.Add("highvalue", true);
                }
                else
                {
                    wrapper.Properties.Add("highvalue", false);
                }
                break;

            case LdapTypeEnum.GPO:
                accountName = searchResultEntry.GetProperty("displayname");
                wrapper     = new GPO(searchResultEntry)
                {
                    DisplayName = $"{accountName}@{accountDomain}".ToUpper()
                };
                wrapper.Properties.Add("highvalue", false);
                break;

            case LdapTypeEnum.OU:
                accountName = searchResultEntry.GetProperty("name");
                wrapper     = new OU(searchResultEntry)
                {
                    DisplayName = $"{accountName}@{accountDomain}".ToUpper()
                };
                wrapper.Properties.Add("highvalue", false);
                break;

            case LdapTypeEnum.Domain:
                wrapper = new Domain(searchResultEntry)
                {
                    DisplayName = accountDomain.ToUpper()
                };
                wrapper.Properties.Add("highvalue", true);
                break;

            case LdapTypeEnum.Unknown:
                wrapper = null;
                break;

            default:
                throw new ArgumentOutOfRangeException();
            }

            //Null wrappers happen when we cant resolve the object type. Shouldn't ever happen, but just in case, return null here
            if (wrapper == null)
            {
                Console.WriteLine($"Null Wrapper: {distinguishedName}");
                return(null);
            }

            //Set the DN/SID for the wrapper going forward and a couple other properties
            wrapper.DistinguishedName = distinguishedName;
            wrapper.Properties.Add("name", wrapper.DisplayName);
            wrapper.Properties.Add("domain", wrapper.Domain);
            wrapper.Properties.Add("objectid", objectIdentifier.ToUpper());
            wrapper.Properties.Add("distinguishedname", distinguishedName);
            wrapper.ObjectIdentifier = objectIdentifier;

            //Some post processing
            PostProcessWrapper(wrapper);

            //Cache the distinguished name from this object
            Cache.Instance.Add(wrapper.DistinguishedName, new ResolvedPrincipal
            {
                ObjectIdentifier = wrapper.ObjectIdentifier,
                ObjectType       = objectType
            });

            //If the objectidentifier is a SID, cache this mapping too
            if (objectIdentifier.StartsWith("S-1-5"))
            {
                Cache.Instance.Add(wrapper.ObjectIdentifier, objectType);
            }

            //Return our wrapper for the next step in the pipeline
            return(wrapper);
        }
예제 #10
0
 /// <summary>
 /// Attempts to get the unique object identifier as used by BloodHound for the Search Result Entry. Tries to get objectsid first, and then objectguid next.
 /// </summary>
 /// <param name="entry"></param>
 /// <returns>String representation of the entry's object identifier or null</returns>
 public static string GetObjectIdentifier(this SearchResultEntry entry)
 {
     return(entry.GetSid() ?? entry.GetGuid());
 }
예제 #11
0
        internal static LdapWrapper FindLdapType(SearchResultEntry searchResultEntry)
        {
            //Look for a null DN first. Not sure why this would happen.
            var distinguishedName = searchResultEntry.DistinguishedName;

            if (distinguishedName == null)
            {
                return(null);
            }

            var accountName    = searchResultEntry.GetProp("samaccountname");
            var samAccountType = searchResultEntry.GetProp("samaccounttype");
            var accountDomain  = Helpers.DistinguishedNameToDomain(distinguishedName);
            var objectSid      = searchResultEntry.GetSid();
            var objectType     = LdapTypeEnum.Unknown;

            LdapWrapper wrapper;

            //Lets see if its a "common" principal
            if (CommonPrincipal.GetCommonSid(objectSid, out var commonPrincipal))
            {
                accountName = commonPrincipal.Name;
                objectType  = commonPrincipal.Type;
            }
            else
            {
                //Its not a common principal. Lets use properties to figure out what it actually is
                if (samAccountType != null)
                {
                    if (samAccountType == "805306370")
                    {
                        return(null);
                    }

                    objectType = Helpers.SamAccountTypeToType(samAccountType);
                }
                else
                {
                    var objectClasses = searchResultEntry.GetPropArray("objectClass");
                    if (objectClasses == null)
                    {
                        objectType = LdapTypeEnum.Unknown;
                    }
                    else if (objectClasses.Contains("groupPolicyContainer"))
                    {
                        objectType = LdapTypeEnum.GPO;
                    }
                    else if (objectClasses.Contains("organizationalUnit"))
                    {
                        objectType = LdapTypeEnum.OU;
                    }
                    else if (objectClasses.Contains("domain"))
                    {
                        objectType = LdapTypeEnum.Domain;
                    }
                }
            }

            //Depending on the object type, create the appropriate wrapper object
            switch (objectType)
            {
            case LdapTypeEnum.Computer:
                accountName = accountName?.TrimEnd('$');
                wrapper     = new Computer(searchResultEntry)
                {
                    DisplayName = $"{accountName}.{accountDomain}"
                };
                break;

            case LdapTypeEnum.User:
                wrapper = new User(searchResultEntry)
                {
                    DisplayName = $"{accountName}@{accountDomain}"
                };
                break;

            case LdapTypeEnum.Group:
                wrapper = new Group(searchResultEntry)
                {
                    DisplayName = $"{accountName}@{accountDomain}"
                };
                break;

            case LdapTypeEnum.GPO:
                accountName = searchResultEntry.GetProp("displayname");
                wrapper     = new GPO(searchResultEntry)
                {
                    DisplayName = $"{accountName}@{accountDomain}"
                };
                break;

            case LdapTypeEnum.OU:
                accountName = searchResultEntry.GetProp("name");
                wrapper     = new OU(searchResultEntry)
                {
                    DisplayName = $"{accountName}@{accountDomain}"
                };
                break;

            case LdapTypeEnum.Domain:
                wrapper = new Domain(searchResultEntry)
                {
                    DisplayName = accountDomain
                };
                break;

            case LdapTypeEnum.Unknown:
                wrapper = null;
                break;

            default:
                throw new ArgumentOutOfRangeException();
            }

            //Set the DN/SID for the wrapper going forward
            if (wrapper == null)
            {
                return(wrapper);
            }
            wrapper.DistinguishedName  = distinguishedName;
            wrapper.SecurityIdentifier = objectSid;

            //Return our wrapper for the next step in the pipeline
            return(wrapper);
        }
예제 #12
0
        internal static ResolvedEntry ResolveAdEntry(this SearchResultEntry result, bool bypassDns = false)
        {
            var entry = new ResolvedEntry();

            var accountName       = result.GetProp("samaccountname");
            var distinguishedName = result.DistinguishedName;
            var accountType       = result.GetProp("samaccounttype");

            if (distinguishedName == null)
            {
                return(null);
            }

            var domainName = Utils.ConvertDnToDomain(distinguishedName);

            if (MappedPrincipal.GetCommon(result.GetSid(), out var temp))
            {
                return(new ResolvedEntry
                {
                    IngestCacheDisplay = $"{temp.PrincipalName}@{domainName}".ToUpper(),
                    ObjectType = temp.ObjectType
                });
            }

            if (Groups.Contains(accountType))
            {
                entry.IngestCacheDisplay = $"{accountName}@{domainName}".ToUpper();
                entry.ObjectType         = "group";
                return(entry);
            }

            if (Users.Contains(accountType))
            {
                entry.IngestCacheDisplay = $"{accountName}@{domainName}".ToUpper();
                entry.ObjectType         = "user";
                return(entry);
            }

            if (Computers.Contains(accountType))
            {
                var shortName   = accountName?.TrimEnd('$');
                var dnshostname = result.GetProp("dnshostname");
                if (dnshostname == null)
                {
                    var domain = Utils.ConvertDnToDomain(result.DistinguishedName);
                    dnshostname = $"{shortName}.{domain}".ToUpper();
                }

                entry.IngestCacheDisplay     = dnshostname;
                entry.ObjectType             = "computer";
                entry.ComputerSamAccountName = shortName;
                return(entry);
            }

            if (accountType == null)
            {
                var objClass = result.GetPropArray("objectClass");
                if (objClass.Contains("groupPolicyContainer"))
                {
                    entry.IngestCacheDisplay = $"{result.GetProp("displayname")}@{domainName}";
                    entry.ObjectType         = "gpo";
                    return(entry);
                }

                if (objClass.Contains("organizationalUnit"))
                {
                    entry.IngestCacheDisplay = $"{result.GetProp("name")}@{domainName}";
                    entry.ObjectType         = "ou";
                    return(entry);
                }

                if (objClass.Contains("container"))
                {
                    entry.IngestCacheDisplay = domainName;
                    entry.ObjectType         = "container";
                    return(entry);
                }
            }
            else
            {
                if (accountType.Equals("805306370"))
                {
                    return(null);
                }
            }
            entry.IngestCacheDisplay = domainName;
            entry.ObjectType         = "domain";
            return(entry);
        }
예제 #13
0
        internal static ResolvedEntry ResolveAdEntry(this SearchResultEntry result)
        {
            var entry = new ResolvedEntry();

            var accountName       = result.GetProp("samaccountname");
            var distinguishedName = result.DistinguishedName;
            var accountType       = result.GetProp("samaccounttype");

            if (distinguishedName == null)
            {
                return(null);
            }

            var domainName = Utils.ConvertDnToDomain(distinguishedName);

            if (MappedPrincipal.GetCommon(result.GetSid(), out var temp))
            {
                return(new ResolvedEntry
                {
                    BloodHoundDisplay = $"{temp.PrincipalName}@{domainName}".ToUpper(),
                    ObjectType = temp.ObjectType
                });
            }

            if (Groups.Contains(accountType))
            {
                entry.BloodHoundDisplay = $"{accountName}@{domainName}".ToUpper();
                entry.ObjectType        = "group";
                return(entry);
            }

            if (Users.Contains(accountType))
            {
                entry.BloodHoundDisplay = $"{accountName}@{domainName}".ToUpper();
                entry.ObjectType        = "user";
                return(entry);
            }

            if (Computers.Contains(accountType))
            {
                var shortName   = accountName?.TrimEnd('$');
                var dnshostname = result.GetProp("dnshostname");

                if (dnshostname == null)
                {
                    bool hostFound;
                    if (domainName.Equals(_primaryDomain, StringComparison.CurrentCultureIgnoreCase))
                    {
                        hostFound = DnsManager.HostExistsDns(shortName, out dnshostname);
                        if (!hostFound)
                        {
                            hostFound = DnsManager.HostExistsDns($"{shortName}.{domainName}", out dnshostname);
                        }
                    }
                    else
                    {
                        hostFound = DnsManager.HostExistsDns($"{shortName}.{domainName}", out dnshostname);
                        if (!hostFound)
                        {
                            hostFound = DnsManager.HostExistsDns(shortName, out dnshostname);
                        }
                    }

                    if (!hostFound)
                    {
                        return(null);
                    }
                }
                entry.BloodHoundDisplay      = dnshostname;
                entry.ObjectType             = "computer";
                entry.ComputerSamAccountName = shortName;
                return(entry);
            }



            if (accountType == null)
            {
                var objClass = result.GetPropArray("objectClass");
                if (objClass.Contains("groupPolicyContainer"))
                {
                    entry.BloodHoundDisplay = $"{result.GetProp("displayname")}@{domainName}";
                    entry.ObjectType        = "gpo";
                    return(entry);
                }

                if (objClass.Contains("organizationalUnit"))
                {
                    entry.BloodHoundDisplay = $"{result.GetProp("name")}@{domainName}";
                    entry.ObjectType        = "ou";
                    return(entry);
                }

                if (objClass.Contains("container"))
                {
                    entry.BloodHoundDisplay = domainName;
                    entry.ObjectType        = "container";
                    return(entry);
                }
            }
            else
            {
                if (accountType.Equals("805306370"))
                {
                    return(null);
                }
            }
            entry.BloodHoundDisplay = domainName;
            entry.ObjectType        = "domain";
            return(entry);
        }