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 }); }
/// <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); } } } }
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 }); }
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()); }
/// <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); }
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 }); }
/// <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()); }
/// <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); }
/// <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()); }
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); }
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); }
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); }