/// <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 objectId = searchResultEntry.GetObjectIdentifier(); if (objectId == null) { return(LdapTypeEnum.Unknown); } if (searchResultEntry.GetPropertyAsBytes("msds-groupmsamembership") != null) { return(LdapTypeEnum.User); } 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; } } //Override GMSA object type if (searchResultEntry.GetPropertyAsBytes("msds-groupmsamembership") != null) { objectType = LdapTypeEnum.User; } return(objectType); }
/// <summary> /// Attempts to parse all LDAP attributes outside of the ones already collected and converts them to a human readable format using a best guess /// </summary> /// <param name="entry"></param> private static Dictionary <string, object> ParseAllProperties(SearchResultEntry entry) { var flag = IsTextUnicodeFlags.IS_TEXT_UNICODE_STATISTICS; var props = new Dictionary <string, object>(); foreach (var property in entry.Attributes.AttributeNames) { var propName = property.ToString().ToLower(); if (ReservedAttributes.Contains(propName)) { continue; } var collection = entry.Attributes[propName]; if (collection.Count == 0) { continue; } if (collection.Count == 1) { var testBytes = entry.GetPropertyAsBytes(propName); if (testBytes == null || testBytes.Length == 0 || !IsTextUnicode(testBytes, testBytes.Length, ref flag)) { continue; } var testString = entry.GetProperty(propName); if (!string.IsNullOrEmpty(testString)) { if (propName == "badpasswordtime") { props.Add(propName, Helpers.ConvertFileTimeToUnixEpoch(testString)); } else { props.Add(propName, BestGuessConvert(testString)); } } } else { var arrBytes = entry.GetPropertyAsArrayOfBytes(propName); if (arrBytes.Length == 0 || !IsTextUnicode(arrBytes[0], arrBytes[0].Length, ref flag)) { continue; } var arr = entry.GetPropertyAsArray(propName); if (arr.Length > 0) { props.Add(propName, arr.Select(BestGuessConvert).ToArray()); } } } return(props); }
/// <summary> /// Get's the string representation of the "objectguid" property from the SearchResultEntry /// </summary> /// <param name="entry"></param> /// <returns>The string representation of the object's GUID if possible, otherwise null</returns> public static string GetGuid(this SearchResultEntry entry) { if (entry.Attributes.Contains("objectguid")) { var guidBytes = entry.GetPropertyAsBytes("objectguid"); return(new Guid(guidBytes).ToString().ToUpper()); } return(null); }
/// <summary> /// Extension method to determine the BloodHound type of a SearchResultEntry using LDAP properties /// Requires ldap properties objectsid, samaccounttype, objectclass /// </summary> /// <param name="entry"></param> /// <returns></returns> public static Label GetLabel(this SearchResultEntry entry) { //Test if we have the msds-groupmsamembership property first. We want to override this as a user object if (entry.GetPropertyAsBytes("msds-groupmsamembership") != null) { return(Label.User); } var objectId = entry.GetObjectIdentifier(); if (objectId.StartsWith("S-1") && WellKnownPrincipal.GetWellKnownPrincipal(objectId, out var commonPrincipal)) { return(commonPrincipal.ObjectType); } var objectType = Label.Unknown; var samAccountType = entry.GetProperty("samaccounttype"); //Its not a common principal. Lets use properties to figure out what it actually is if (samAccountType != null) { objectType = Helpers.SamAccountTypeToType(samAccountType); } else { var objectClasses = entry.GetPropertyAsArray("objectClass"); if (objectClasses == null) { objectType = Label.Unknown; } else if (objectClasses.Contains("groupPolicyContainer")) { objectType = Label.GPO; } else if (objectClasses.Contains("organizationalUnit")) { objectType = Label.OU; } else if (objectClasses.Contains("domain")) { objectType = Label.Domain; } else if (objectClasses.Contains("container")) { objectType = Label.Container; } } return(objectType); }
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()); }
public byte[] GetByteProperty(string propertyName) { return(_entry.GetPropertyAsBytes(propertyName)); }
/// <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); }
public static async Task <ComputerProperties> ReadComputerProperties(SearchResultEntry entry) { var compProps = new ComputerProperties(); var props = new Dictionary <string, object>(); var uac = entry.GetProperty("useraccountcontrol"); bool enabled, unconstrained, trustedToAuth; if (int.TryParse(uac, out var flag)) { var flags = (UAC.UacFlags)flag; enabled = (flags & UAC.UacFlags.AccountDisable) == 0; unconstrained = (flags & UAC.UacFlags.TrustedForDelegation) == UAC.UacFlags.TrustedForDelegation; trustedToAuth = (flags & UAC.UacFlags.TrustedToAuthForDelegation) != 0; } else { unconstrained = false; enabled = true; trustedToAuth = false; } var domain = Helpers.DistinguishedNameToDomain(entry.DistinguishedName); var comps = new List <TypedPrincipal>(); if (trustedToAuth) { var delegates = entry.GetPropertyAsArray("msds-allowedToDelegateTo"); props.Add("allowedtodelegate", delegates); foreach (var d in delegates) { var hname = d.Contains("/") ? d.Split('/')[1] : d; hname = hname.Split(':')[0]; var resolvedHost = await LDAPUtils.ResolveHostToSid(hname, domain); if (resolvedHost.Contains(".") || resolvedHost.Contains("S-1")) { comps.Add(new TypedPrincipal { ObjectIdentifier = resolvedHost, ObjectType = Label.Computer }); } } } compProps.AllowedToDelegate = comps.ToArray(); var allowedToActPrincipals = new List <TypedPrincipal>(); var rawAllowedToAct = entry.GetPropertyAsBytes("msDS-AllowedToActOnBehalfOfOtherIdentity"); if (rawAllowedToAct != null) { var sd = new ActiveDirectorySecurity(); sd.SetSecurityDescriptorBinaryForm(rawAllowedToAct, AccessControlSections.Access); foreach (ActiveDirectoryAccessRule rule in sd.GetAccessRules(true, true, typeof(SecurityIdentifier))) { var res = LDAPUtils.ResolveIDAndType(rule.IdentityReference.Value, domain); allowedToActPrincipals.Add(res); } } compProps.AllowedToAct = allowedToActPrincipals.ToArray(); props.Add("enabled", enabled); props.Add("unconstraineddelegation", unconstrained); props.Add("lastlogon", Helpers.ConvertFileTimeToUnixEpoch(entry.GetProperty("lastlogon"))); props.Add("lastlogontimestamp", Helpers.ConvertFileTimeToUnixEpoch(entry.GetProperty("lastlogontimestamp"))); props.Add("pwdlastset", Helpers.ConvertFileTimeToUnixEpoch(entry.GetProperty("pwdlastset"))); props.Add("serviceprincipalnames", entry.GetPropertyAsArray("serviceprincipalname")); var os = entry.GetProperty("operatingsystem"); var sp = entry.GetProperty("operatingsystemservicepack"); if (sp != null) { os = $"{os} {sp}"; } props.Add("operatingsystem", os); props.Add("description", entry.GetProperty("description")); var sh = entry.GetPropertyAsArrayOfBytes("sidhistory"); var sidHistoryList = new List <string>(); var sidHistoryPrincipals = new List <TypedPrincipal>(); foreach (var sid in sh) { string sSid; try { sSid = new SecurityIdentifier(sid, 0).Value; } catch { continue; } var res = LDAPUtils.ResolveIDAndType(sSid, domain); sidHistoryPrincipals.Add(res); } compProps.SidHistory = sidHistoryPrincipals.ToArray(); props.Add("sidhistory", sidHistoryList.ToArray()); compProps.Props = props; return(compProps); }