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 }); }
public static string ResolveBloodhoundDisplay(this SearchResultEntry result) { var accountName = result.GetProp("samaccountname"); var distinguishedName = result.DistinguishedName; var accountType = result.GetProp("samaccounttype"); //I have no idea if this is a thing if (distinguishedName == null) { return(null); } var domain = Utils.ConvertDnToDomain(distinguishedName); if (Groups.Contains(accountType) || Users.Contains(accountType)) { return($"{accountName.ToUpper()}@{domain}"); } if (Computers.Contains(accountType)) { var dnsHostName = result.GetProp("dnshostname") ?? $"{accountName.TrimEnd('$')}.{domain}"; return(dnsHostName.ToUpper()); } //If we got here, we have a domain ACL object return(Utils.ConvertDnToDomain(distinguishedName)); }
internal static void GetProps(SearchResultEntry entry, ResolvedEntry resolved, ref Gpo obj) { if (!Utils.IsMethodSet(ResolvedCollectionMethod.ObjectProps)) { return; } obj.Properties.Add("description", entry.GetProp("description")); obj.Properties.Add("gpcpath", entry.GetProp("gpcfilesyspath")); }
internal static void GetProps(SearchResultEntry entry, ResolvedEntry resolved, ref Domain obj) { if (!Utils.IsMethodSet(ResolvedCollectionMethod.ObjectProps)) { return; } obj.Properties.Add("description", entry.GetProp("description")); if (!int.TryParse(entry.GetProp("msds-behavior-version"), out var level)) { level = -1; } string func; switch (level) { case 0: func = "2000 Mixed/Native"; break; case 1: func = "2003 Interim"; break; case 2: func = "2003"; break; case 3: func = "2008"; break; case 4: func = "2008 R2"; break; case 5: func = "2012"; break; case 6: func = "2012 R2"; break; case 7: func = "2016"; break; default: func = "Unknown"; break; } obj.Properties.Add("functionallevel", func); }
public static string GetObjectType(this SearchResultEntry result) { var accountType = result.GetProp("samaccounttype"); if (Groups.Contains(accountType)) { return("group"); } if (Users.Contains(accountType)) { return("user"); } if (Computers.Contains(accountType)) { return("computer"); } if (TrustAccount.Contains(accountType)) { return("trustaccount"); } if (result.DistinguishedName.Contains("ForeignSecurityPrincipals")) { return("foreignsecurityprincipal"); } return("domain"); }
internal static void GetProps(SearchResultEntry entry, ResolvedEntry resolved, ref Computer obj) { if (!Utils.IsMethodSet(ResolvedCollectionMethod.ObjectProps)) { return; } var uac = entry.GetProp("useraccountcontrol"); bool enabled, unconstrained, trustedToAuth; if (int.TryParse(uac, out var flag)) { var flags = (UacFlags)flag; enabled = (flags & UacFlags.AccountDisable) == 0; unconstrained = (flags & UacFlags.TrustedForDelegation) == UacFlags.TrustedForDelegation; trustedToAuth = (flags & UacFlags.TrustedToAuthForDelegation) != 0; } else { unconstrained = false; enabled = true; trustedToAuth = false; } var comps = new List <string>(); if (trustedToAuth) { var delegates = entry.GetPropArray("msds-allowedToDelegateTo"); obj.Properties.Add("allowedtodelegate", delegates); foreach (var d in delegates) { var hname = d.Contains("/") ? d.Split('/')[1] : d; hname = hname.Split(':')[0]; var resolvedHost = Utils.Instance.ResolveHost(hname); if (resolvedHost.Contains(".")) { comps.Add(resolvedHost.ToUpper()); } } } obj.AllowedToDelegate = comps.Distinct().ToArray(); obj.Properties.Add("enabled", enabled); obj.Properties.Add("unconstraineddelegation", unconstrained); obj.Properties.Add("lastlogon", ConvertToUnixEpoch(entry.GetProp("lastlogon"))); obj.Properties.Add("lastlogontimestamp", ConvertToUnixEpoch(entry.GetProp("lastlogontimestamp"))); obj.Properties.Add("pwdlastset", ConvertToUnixEpoch(entry.GetProp("pwdlastset"))); obj.Properties.Add("serviceprincipalnames", entry.GetPropArray("serviceprincipalname")); var os = entry.GetProp("operatingsystem"); var sp = entry.GetProp("operatingsystemservicepack"); if (sp != null) { os = $"{os} {sp}"; } obj.Properties.Add("operatingsystem", os); obj.Properties.Add("description", entry.GetProp("description")); }
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 }); }
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(); 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 }); }
internal static void GetProps(SearchResultEntry entry, ResolvedEntry resolved, ref Group obj) { if (!Utils.IsMethodSet(ResolvedCollectionMethod.ObjectProps)) { return; } var ac = entry.GetProp("admincount"); if (ac != null) { var a = int.Parse(ac); obj.Properties.Add("admincount", a != 0); } else { obj.Properties.Add("admincount", false); } obj.Properties.Add("description", entry.GetProp("description")); }
internal static void GetGroupInfo(SearchResultEntry entry, ResolvedEntry resolved, string domainSid, ref Computer u) { if (!Utils.IsMethodSet(ResolvedCollectionMethod.Group)) { return; } var pgi = entry.GetProp("primarygroupid"); if (pgi == null) { return; } var pgsid = $"{domainSid}-{pgi}"; var primaryGroupName = _utils.SidToDisplay(pgsid, Utils.ConvertDnToDomain(entry.DistinguishedName), Props, "group"); u.PrimaryGroup = primaryGroupName; }
/// <summary> /// Processes the ACL entries for an AD object /// </summary> /// <param name="entry">LDAP entry to process</param> /// <param name="domainName">Domain name the entry belongs too</param> /// <returns>A list of ACL objects for the entry</returns> public static IEnumerable <ACL> ProcessAdObject(SearchResultEntry entry, string domainName) { var ntSecurityDescriptor = entry.GetPropBytes("ntsecuritydescriptor"); //If the ntsecuritydescriptor is null, no point in continuing //I'm still not entirely sure what causes this, but it can happen if (ntSecurityDescriptor == null) { yield break; } //Convert the ntsecuritydescriptor bytes to a .net object var descriptor = new RawSecurityDescriptor(ntSecurityDescriptor, 0); //Grab the DACL var rawAcl = descriptor.DiscretionaryAcl; //Grab the Owner var ownerSid = descriptor.Owner.ToString(); //Resolve the entry name/type var resolvedEntry = entry.ResolveAdEntry(); //If our name is null, we dont know what the principal is if (resolvedEntry == null) { yield break; } var entryDisplayName = resolvedEntry.BloodHoundDisplay; var entryType = resolvedEntry.ObjectType; string entryGuid; if (entryType.Equals("gpo")) { var n = entry.GetProp("name").ToUpper(); entryGuid = n.Substring(1, n.Length - 2); } else { entryGuid = ""; } //We have no exploitable paths for Computer, so just ignore them if (entryType.Equals("computer")) { yield break; } //Determine the owner of the object. Start by checking if we've already determined this is null if (!_nullSids.TryGetValue(ownerSid, out byte _)) { //Check if its a common SID if (!MappedPrincipal.GetCommon(ownerSid, out var owner)) { //Resolve the sid manually if we still dont have it var ownerDomain = _utils.SidToDomainName(ownerSid) ?? domainName; owner = _utils.UnknownSidTypeToDisplay(ownerSid, ownerDomain, Props); } else { owner.PrincipalName = $"{owner.PrincipalName}@{domainName}"; } //Filter out the Local System principal which pretty much every entry has if (owner != null && !owner.PrincipalName.Contains("Local System")) { yield return(new ACL { PrincipalName = owner.PrincipalName, PrincipalType = owner.ObjectType, Inherited = false, RightName = "Owner", AceType = "", ObjectName = entryDisplayName, ObjectType = entryType, Qualifier = "AccessAllowed", ObjectGuid = entryGuid }); } else { //We'll cache SIDs we've failed to resolve previously so we dont keep trying _nullSids.TryAdd(ownerSid, new byte()); } } //Loop over the actual entries in the DACL foreach (var genericAce in rawAcl) { var qAce = (QualifiedAce)genericAce; var objectSid = qAce.SecurityIdentifier.ToString(); //If this is something we already resolved to null, just keep on moving if (_nullSids.TryGetValue(objectSid, out byte _)) { continue; } //Check if its a common sid if (!MappedPrincipal.GetCommon(objectSid, out var mappedPrincipal)) { //If not common, lets resolve it normally var objectDomain = _utils.SidToDomainName(objectSid) ?? domainName; mappedPrincipal = _utils.UnknownSidTypeToDisplay(objectSid, objectDomain, Props); if (mappedPrincipal == null) { _nullSids.TryAdd(objectSid, new byte()); continue; } } else { mappedPrincipal.PrincipalName = $"{mappedPrincipal.PrincipalName}@{domainName}"; } //bf967a86-0de6-11d0-a285-00aa003049e2 - Computer //bf967aba-0de6-11d0-a285-00aa003049e2 - User //bf967a9c-0de6-11d0-a285-00aa003049e2 - Group //19195a5a-6da0-11d0-afd3-00c04fd930c9 - Domain //f30e3bc2-9ff0-11d1-b603-0000f80367c1 - GPC if (mappedPrincipal.PrincipalName.Contains("Local System")) { continue; } //We have a principal and we've successfully resolved the object. Lets process stuff! //Convert our right to an ActiveDirectoryRight enum object, and then to a string var adRight = (ActiveDirectoryRights)Enum.ToObject(typeof(ActiveDirectoryRights), qAce.AccessMask); var adRightString = adRight.ToString(); //Get the ACE for our right var ace = qAce as ObjectAce; var guid = ace != null?ace.ObjectAceType.ToString() : ""; var toContinue = false; //Figure out if we need more processing by matching the right name + guid together toContinue |= (adRightString.Contains("WriteDacl") || adRightString.Contains("WriteOwner")); if (adRightString.Contains("GenericWrite") || adRightString.Contains("GenericAll")) { toContinue |= ("00000000-0000-0000-0000-000000000000".Equals(guid) || guid.Equals("") || toContinue); } if (adRightString.Contains("ExtendedRight")) { toContinue |= (guid.Equals("00000000-0000-0000-0000-000000000000") || guid.Equals("") || guid.Equals("00299570-246d-11d0-a768-00aa006e0529") || toContinue); //DCSync rights toContinue |= (guid.Equals("1131f6aa-9c07-11d1-f79f-00c04fc2dcd2") || guid.Equals("1131f6ad-9c07-11d1-f79f-00c04fc2dcd2") || toContinue); } if (adRightString.Contains("WriteProperty")) { toContinue |= (guid.Equals("00000000-0000-0000-0000-000000000000") || guid.Equals("bf9679c0-0de6-11d0-a285-00aa003049e2") || guid.Equals("bf9679a8-0de6-11d0-a285-00aa003049e2") || guid.Equals("f30e3bc1-9ff0-11d1-b603-0000f80367c1") || toContinue); } var inheritedObjectType = ace != null?ace.InheritedObjectAceType.ToString() : "00000000-0000-0000-0000-000000000000"; var isInherited = false; switch (entryType) { case "user": isInherited = inheritedObjectType.Equals("00000000-0000-0000-0000-000000000000") || inheritedObjectType.Equals("bf967aba-0de6-11d0-a285-00aa003049e2"); break; case "group": isInherited = inheritedObjectType.Equals("00000000-0000-0000-0000-000000000000") || inheritedObjectType.Equals("bf967a9c-0de6-11d0-a285-00aa003049e2"); break; case "domain": isInherited = inheritedObjectType.Equals("00000000-0000-0000-0000-000000000000") || inheritedObjectType.Equals("19195a5a-6da0-11d0-afd3-00c04fd930c9"); break; case "gpo": isInherited = inheritedObjectType.Equals("00000000-0000-0000-0000-000000000000") || inheritedObjectType.Equals("f30e3bc2-9ff0-11d1-b603-0000f80367c1"); break; } if (!toContinue || !isInherited) { continue; } string aceType = null; if (adRightString.Contains("ExtendedRight")) { switch (guid) { case "1131f6aa-9c07-11d1-f79f-00c04fc2dcd2": aceType = "DS-Replication-Get-Changes"; break; case "1131f6ad-9c07-11d1-f79f-00c04fc2dcd2": aceType = "DS-Replication-Get-Changes-All"; break; default: aceType = "All"; break; } } //If we have either of the DCSync rights, store it in a temporary object and continue. //We need both rights for either right to mean anything to us if (aceType != null && entryType.Equals("domain") && (aceType.Equals("DS-Replication-Get-Changes-All") || aceType.Equals("DS-Replication-Get-Changes"))) { if (!_syncers.TryGetValue(mappedPrincipal.PrincipalName, out var sync)) { sync = new DcSync { Domain = entryDisplayName, PrincipalName = mappedPrincipal.PrincipalName, PrincipalType = mappedPrincipal.ObjectType }; } if (aceType.Contains("-All")) { sync.GetChangesAll = true; } else { sync.GetChanges = true; } _syncers.AddOrUpdate(mappedPrincipal.PrincipalName, sync, (key, oldVar) => sync); continue; } if (aceType != null && entryType.Equals("domain") && aceType.Equals("All")) { if (!_syncers.TryGetValue(mappedPrincipal.PrincipalName, out var sync)) { sync = new DcSync { Domain = entryDisplayName, PrincipalName = mappedPrincipal.PrincipalName, PrincipalType = mappedPrincipal.ObjectType }; } sync.GetChangesAll = true; sync.GetChanges = true; _syncers.AddOrUpdate(mappedPrincipal.PrincipalName, sync, (key, oldVar) => sync); } if (aceType != null && entryType.Equals("domain") && adRightString.Contains("GenericAll")) { if (!_syncers.TryGetValue(mappedPrincipal.PrincipalName, out var sync)) { sync = new DcSync { Domain = entryDisplayName, PrincipalName = mappedPrincipal.PrincipalName, PrincipalType = mappedPrincipal.ObjectType }; } sync.GetChangesAll = true; sync.GetChanges = true; _syncers.AddOrUpdate(mappedPrincipal.PrincipalName, sync, (key, oldVar) => sync); } //Return ACL objects based on rights + guid combos if (adRightString.Contains("GenericAll")) { yield return(new ACL { AceType = "", Inherited = qAce.IsInherited, PrincipalName = mappedPrincipal.PrincipalName, PrincipalType = mappedPrincipal.ObjectType, ObjectType = entryType, ObjectName = entryDisplayName, RightName = "GenericAll", Qualifier = qAce.AceQualifier.ToString(), ObjectGuid = entryGuid }); } if (adRightString.Contains("GenericWrite")) { yield return(new ACL { AceType = "", Inherited = qAce.IsInherited, PrincipalName = mappedPrincipal.PrincipalName, PrincipalType = mappedPrincipal.ObjectType, ObjectType = entryType, ObjectName = entryDisplayName, RightName = "GenericWrite", Qualifier = qAce.AceQualifier.ToString(), ObjectGuid = entryGuid }); } if (adRightString.Contains("WriteOwner")) { yield return(new ACL { AceType = "", Inherited = qAce.IsInherited, PrincipalName = mappedPrincipal.PrincipalName, PrincipalType = mappedPrincipal.ObjectType, ObjectType = entryType, ObjectName = entryDisplayName, RightName = "WriteOwner", Qualifier = qAce.AceQualifier.ToString(), ObjectGuid = entryGuid }); } if (adRightString.Contains("WriteDacl")) { yield return(new ACL { AceType = "", Inherited = qAce.IsInherited, PrincipalName = mappedPrincipal.PrincipalName, PrincipalType = mappedPrincipal.ObjectType, ObjectType = entryType, ObjectName = entryDisplayName, RightName = "WriteDacl", Qualifier = qAce.AceQualifier.ToString(), ObjectGuid = entryGuid }); } if (adRightString.Contains("WriteProperty")) { if (guid.Equals("bf9679c0-0de6-11d0-a285-00aa003049e2") && !entryType.Equals("domain") && !entryType.Equals("gpo")) { yield return(new ACL { AceType = "Member", Inherited = qAce.IsInherited, PrincipalName = mappedPrincipal.PrincipalName, PrincipalType = mappedPrincipal.ObjectType, ObjectType = entryType, ObjectName = entryDisplayName, RightName = "WriteProperty", Qualifier = qAce.AceQualifier.ToString(), ObjectGuid = entryGuid }); } //else if (guid.Equals("f30e3bc1-9ff0-11d1-b603-0000f80367c1") && entryType.Equals("gpo")) //{ // yield return new ACL // { // AceType = "GPC-File-Sys-Path", // Inherited = qAce.IsInherited, // PrincipalName = mappedPrincipal.PrincipalName, // PrincipalType = mappedPrincipal.ObjectType, // ObjectType = entryType, // ObjectName = entryDisplayName, // RightName = "WriteProperty", // Qualifier = qAce.AceQualifier.ToString(), // ObjectGuid = entryGuid // }; //} } if (adRightString.Contains("ExtendedRight")) { if (guid.Equals("00299570-246d-11d0-a768-00aa006e0529") && !entryType.Equals("domain") && !entryType.Equals("gpo")) { yield return(new ACL { AceType = "User-Force-Change-Password", Inherited = qAce.IsInherited, PrincipalName = mappedPrincipal.PrincipalName, PrincipalType = mappedPrincipal.ObjectType, ObjectType = entryType, ObjectName = entryDisplayName, RightName = "ExtendedRight", Qualifier = qAce.AceQualifier.ToString(), ObjectGuid = entryGuid }); } else if (guid.Equals("00000000-0000-0000-0000-000000000000")) { yield return(new ACL { AceType = "All", Inherited = qAce.IsInherited, PrincipalName = mappedPrincipal.PrincipalName, PrincipalType = mappedPrincipal.ObjectType, ObjectType = entryType, ObjectName = entryDisplayName, RightName = "ExtendedRight", Qualifier = qAce.AceQualifier.ToString(), ObjectGuid = entryGuid }); } } } }
//public static List<LocalAdmin> LocalGroupWinNt(string target, string group) //{ // var members = new DirectoryEntry($"WinNT://{target}/{group},group"); // var localAdmins = new List<LocalAdmin>(); // try // { // foreach (var member in (System.Collections.IEnumerable)members.Invoke("Members")) // { // using (var m = new DirectoryEntry(member)) // { // //Convert sid bytes to a string // var sidstring = new SecurityIdentifier(m.GetSid(), 0).ToString(); // string type; // switch (m.SchemaClassName) // { // case "Group": // type = "group"; // break; // case "User": // //If its a user but the name ends in $, it's actually a computer (probably) // type = m.Properties["Name"][0].ToString().EndsWith("$", StringComparison.Ordinal) ? "computer" : "user"; // break; // default: // type = "group"; // break; // } // //Start by checking the cache // if (!_cache.GetMapValue(sidstring, type, out var adminName)) // { // //Get the domain from the SID // var domainName = _utils.SidToDomainName(sidstring); // //Search for the object in AD // var entry = _utils // .DoSearch($"(objectsid={sidstring})", SearchScope.Subtree, AdminProps, domainName) // .DefaultIfEmpty(null).FirstOrDefault(); // //If it's not null, we have an object, yay! Otherwise, meh // if (entry != null) // { // adminName = entry.ResolveAdEntry().BloodHoundDisplay; // _cache.AddMapValue(sidstring, type, adminName); // } // else // { // adminName = null; // } // } // if (adminName != null) // { // localAdmins.Add(new LocalAdmin { ObjectName = adminName, ObjectType = type, Server = target }); // } // } // } // } // catch (COMException) // { // //You can get a COMException, so just return a blank array // return localAdmins; // } // return localAdmins; //} //public static List<LocalAdmin> LocalGroupApi(string target, string group, string domainName, string domainSid) //{ // const int queryLevel = 2; // var resumeHandle = IntPtr.Zero; // var machineSid = "DUMMYSTRING"; // var LMI2 = typeof(LOCALGROUP_MEMBERS_INFO_2); // var returnValue = NetLocalGroupGetMembers(target, group, queryLevel, out IntPtr ptrInfo, -1, out int entriesRead, out int _, resumeHandle); // //Return value of 1722 indicates the system is down, so no reason to fallback to WinNT // if (returnValue == 1722) // { // throw new SystemDownException(); // } // //If its not 0, something went wrong, but we can fallback to WinNT provider. Throw an exception // if (returnValue != 0) // { // throw new ApiFailedException(); // } // var toReturn = new List<LocalAdmin>(); // if (entriesRead <= 0) return toReturn; // var iter = ptrInfo; // var list = new List<API_Encapsulator>(); // //Loop through the data and save them into a list for processing // for (var i = 0; i < entriesRead; i++) // { // var data = (LOCALGROUP_MEMBERS_INFO_2)Marshal.PtrToStructure(iter, LMI2); // ConvertSidToStringSid(data.lgrmi2_sid, out string sid); // list.Add(new API_Encapsulator // { // Lgmi2 = data, // sid = sid // }); // iter = (IntPtr)(iter.ToInt64() + Marshal.SizeOf(LMI2)); // } // NetApiBufferFree(ptrInfo); // //Try and determine the machine sid // foreach (var data in list) // { // if (data.sid == null) // { // continue; // } // //If the sid ends with -500 and doesn't start with the DomainSID, there's a very good chance we've identified the RID500 account // //Take the machine sid from there. If we don't find it, we use a dummy string // if (!data.sid.EndsWith("-500", StringComparison.Ordinal) || // data.sid.StartsWith(domainSid, StringComparison.Ordinal)) continue; // machineSid = new SecurityIdentifier(data.sid).AccountDomainSid.Value; // break; // } // foreach (var data in list) // { // if (data.sid == null) // continue; // var objectName = data.Lgmi2.lgrmi2_domainandname; // if (objectName.Split('\\').Last().Equals("")) // { // //Sometimes we get weird objects that are just a domain name with no user behind it. // continue; // } // if (data.sid.StartsWith(machineSid, StringComparison.Ordinal)) // { // //This should filter out local accounts // continue; // } // string type; // switch (data.Lgmi2.lgrmi2_sidusage) // { // case SID_NAME_USE.SidTypeUser: // type = "user"; // break; // case SID_NAME_USE.SidTypeGroup: // type = "group"; // break; // case SID_NAME_USE.SidTypeComputer: // type = "computer"; // break; // case SID_NAME_USE.SidTypeWellKnownGroup: // type = "wellknown"; // break; // default: // type = null; // break; // } // //I have no idea what would cause this condition // if (type == null) // { // continue; // } // if (objectName.EndsWith("$", StringComparison.Ordinal)) // { // type = "computer"; // } // var resolved = _utils.SidToDisplay(data.sid, _utils.SidToDomainName(data.sid), AdminProps, type); // if (resolved == null) // { // continue; // } // toReturn.Add(new LocalAdmin // { // ObjectName = resolved, // ObjectType = type, // Server = target // }); // } // return toReturn; //} #endregion public static IEnumerable <LocalAdmin> GetGpoAdmins(SearchResultEntry entry, string domainName) { const string targetSid = "S-1-5-32-544__Members"; var displayName = entry.GetProp("displayname"); var name = entry.GetProp("name"); var path = entry.GetProp("gpcfilesyspath"); if (displayName == null || name == null || path == null) { yield break; } var resolvedList = new List <MappedPrincipal>(); var template = $"{path}\\MACHINE\\Microsoft\\Windows NT\\SecEdit\\GptTmpl.inf"; var currentSection = string.Empty; if (File.Exists(template)) { using (var reader = new StreamReader(template)) { string line; while ((line = reader.ReadLine()) != null) { var sMatch = SectionRegex.Match(line); if (sMatch.Success) { currentSection = sMatch.Captures[0].Value.Trim(); } if (!currentSection.Equals("[Group Membership]")) { continue; } var kMatch = KeyRegex.Match(line); if (!kMatch.Success) { continue; } var n = kMatch.Groups[1].Value; var v = kMatch.Groups[2].Value; if (!n.Contains(targetSid)) { continue; } v = v.Trim(); var members = v.Split(','); foreach (var m in members) { var member = m.Trim('*'); string sid; if (!member.StartsWith("S-1-", StringComparison.CurrentCulture)) { try { sid = new NTAccount(domainName, m).Translate(typeof(SecurityIdentifier)).Value; } catch { sid = null; } } else { sid = member; } if (sid == null) { continue; } var domain = _utils.SidToDomainName(sid) ?? domainName; var resolvedPrincipal = _utils.UnknownSidTypeToDisplay(sid, domain, Props); if (resolvedPrincipal != null) { resolvedList.Add(resolvedPrincipal); } } } } } var xml = $"{path}\\USER\\Preferences\\Groups\\Groups.xml"; if (File.Exists(xml)) { var doc = new XPathDocument(xml); var nav = doc.CreateNavigator(); var nodes = nav.Select("/Groups/Group"); while (nodes.MoveNext()) { var properties = nodes.Current.Select("Properties"); while (properties.MoveNext()) { var groupSid = properties.Current.GetAttribute("groupSid", ""); if (groupSid == "") { continue; } if (!groupSid.Equals("S-1-5-32-544")) { continue; } var members = properties.Current.Select("Members"); while (members.MoveNext()) { var subMembers = members.Current.Select("Member"); while (subMembers.MoveNext()) { var action = subMembers.Current.GetAttribute("action", ""); if (action.Equals("ADD")) { var sid = subMembers.Current.GetAttribute("sid", ""); if (sid == "") { continue; } var domain = _utils.SidToDomainName(sid) ?? domainName; var resolvedPrincipal = _utils.UnknownSidTypeToDisplay(sid, domain, Props); if (resolvedPrincipal != null) { resolvedList.Add(resolvedPrincipal); } } } } } } } if (resolvedList.Count > 0) { foreach (var ouObject in _utils.DoSearch($"(gplink=*{name}*)", SearchScope.Subtree, GpLinkProps, domainName)) { var adspath = ouObject.DistinguishedName; foreach (var compEntry in _utils.DoSearch("(objectclass=computer)", SearchScope.Subtree, GpoProps, domainName, adspath)) { var samAccountType = compEntry.GetProp("samaccounttype"); if (samAccountType == null || samAccountType != "805306369") { continue; } var server = compEntry.ResolveAdEntry()?.BloodHoundDisplay; if (server == null) { continue; } foreach (var user in resolvedList) { yield return(new LocalAdmin { ObjectName = user.PrincipalName, ObjectType = user.ObjectType, Server = server }); } } } } }
/// <summary> /// Processes an LDAP entry to resolve PrimaryGroup/MemberOf properties /// </summary> /// <param name="entry">LDAP entry</param> /// <param name="resolvedEntry">The resolved object with the name/type of the entry</param> /// <param name="domainSid">SID for the domain being enumerated. Used to resolve PrimaryGroupID</param> /// <returns></returns> public static IEnumerable <GroupMember> ProcessAdObject(SearchResultEntry entry, ResolvedEntry resolvedEntry, string domainSid) { var principalDisplayName = resolvedEntry.BloodHoundDisplay; var principalDomainName = Utils.ConvertDnToDomain(entry.DistinguishedName); //If this object is a group, add it to our DN cache if (resolvedEntry.ObjectType.Equals("group")) { _cache.AddMapValue(entry.DistinguishedName, "group", principalDisplayName); } var members = entry.GetPropArray("member"); if (members.Length == 0) { var tempMembers = new List <string>(); var finished = false; var bottom = 0; while (!finished) { var top = bottom + 1499; var range = $"member;range={bottom}-{top}"; bottom += 1500; //Try ranged retrieval foreach (var result in _utils.DoSearch("(objectclass=*)", SearchScope.Base, new[] { range }, principalDomainName, entry.DistinguishedName)) { if (result.Attributes.AttributeNames == null) { continue; } var en = result.Attributes.AttributeNames.GetEnumerator(); //If the enumerator fails, that means theres really no members at all if (!en.MoveNext()) { finished = true; break; } if (en.Current == null) { continue; } var attrib = en.Current.ToString(); if (attrib.EndsWith("-*")) { finished = true; } tempMembers.AddRange(result.GetPropArray(attrib)); } } members = tempMembers.ToArray(); } foreach (var dn in members) { //Check our cache first if (!_cache.GetMapValueUnknownType(dn, out var principal)) { if (dn.Contains("ForeignSecurityPrincipals") && dn.Contains("CN=S-1-5-21")) { var sid = dn.Split(',')[0].Substring(3); principal = _utils.UnknownSidTypeToDisplay(sid, _utils.SidToDomainName(sid), Props); } else { var objEntry = _utils .DoSearch("(objectclass=*)", SearchScope.Base, Props, Utils.ConvertDnToDomain(dn), dn) .DefaultIfEmpty(null).FirstOrDefault(); if (objEntry == null) { principal = null; } else { var resolvedObj = objEntry.ResolveAdEntry(); if (resolvedObj == null) { principal = null; } else { _cache.AddMapValue(dn, resolvedObj.ObjectType, resolvedObj.BloodHoundDisplay); principal = new MappedPrincipal ( resolvedObj.BloodHoundDisplay, resolvedObj.ObjectType ); } } } } if (principal != null) { yield return(new GroupMember { AccountName = principal.PrincipalName, GroupName = principalDisplayName, ObjectType = principal.ObjectType }); } } var pgi = entry.GetProp("primarygroupid"); if (pgi == null) { yield break; } var pgsid = $"{domainSid}-{pgi}"; var primaryGroupName = _utils.SidToDisplay(pgsid, principalDomainName, Props, "group"); if (primaryGroupName != null) { yield return new GroupMember { AccountName = resolvedEntry.BloodHoundDisplay, GroupName = primaryGroupName, ObjectType = resolvedEntry.ObjectType } } ; }
internal static void ResolveContainer(SearchResultEntry entry, ResolvedEntry resolved, ref Ou obj) { if (!Utils.IsMethodSet(ResolvedCollectionMethod.Container)) { return; } var domain = Utils.ConvertDnToDomain(entry.DistinguishedName); var opts = entry.GetProp("gpoptions"); obj.Properties.Add("blocksinheritance", opts != null && opts.Equals("1")); //Resolve GPLinks on the ou var links = new List <GpLink>(); var gpLinks = entry.GetProp("gplink"); if (gpLinks != null) { foreach (var l in gpLinks.Split(']', '[').Where(x => x.StartsWith("LDAP"))) { var split = l.Split(';'); var dn = split[0]; var status = split[1]; if (status.Equals("3") || status.Equals("1")) { continue; } var enforced = status.Equals("2"); var index = dn.IndexOf("CN=", StringComparison.OrdinalIgnoreCase) + 4; var name = dn.Substring(index, index + 25); if (!_gpoCache.ContainsKey(name)) { continue; } var dName = _gpoCache[name]; links.Add(new GpLink { IsEnforced = enforced, Name = $"{dName}@{domain}" }); } obj.Links = links.ToArray(); } var computers = new List <string>(); var users = new List <string>(); var ous = new List <string>(); foreach (var subEntry in _utils.DoSearch( "(|(samAccountType=805306368)(samAccountType=805306369)(objectclass=organizationalUnit))", SearchScope.OneLevel, new[] { "samaccountname", "name", "objectguid", "objectclass", "objectsid", "samaccounttype", "dnshostname" }, domain, entry.DistinguishedName)) { var subResolved = subEntry.ResolveAdEntry(); if (subResolved == null) { continue; } if (subResolved.ObjectType.Equals("ou")) { ous.Add(new Guid(subEntry.GetPropBytes("objectguid")).ToString().ToUpper()); } else if (subResolved.ObjectType.Equals("computer")) { computers.Add(subResolved.BloodHoundDisplay); } else { users.Add(subResolved.BloodHoundDisplay); } } obj.Users = users.ToArray(); obj.Computers = computers.ToArray(); obj.ChildOus = ous.ToArray(); }
/// <summary> /// Processes an LDAP entry to resolve PrimaryGroup/MemberOf properties /// </summary> /// <param name="entry">LDAP entry</param> /// <param name="resolvedEntry">The resolved object with the name/type of the entry</param> /// <param name="domainSid">SID for the domain being enumerated. Used to resolve PrimaryGroupID</param> /// <returns></returns> public static IEnumerable <GroupMember> ProcessAdObject(SearchResultEntry entry, ResolvedEntry resolvedEntry, string domainSid) { var principalDisplayName = resolvedEntry.BloodHoundDisplay; var principalDomainName = Utils.ConvertDnToDomain(entry.DistinguishedName); //If this object is a group, add it to our DN cache if (resolvedEntry.ObjectType.Equals("group")) { _cache.AddMapValue(entry.DistinguishedName, "group", principalDisplayName); } foreach (var dn in entry.GetPropArray("member")) { //Check our cache first if (!_cache.GetMapValueUnknownType(dn, out var principal)) { if (dn.Contains("ForeignSecurityPrincipals") && !dn.StartsWith("CN=S-1-5-21")) { if (dn.Contains("S-1-5-21")) { var sid = entry.GetProp("cn"); principal = _utils.UnknownSidTypeToDisplay(sid, _utils.SidToDomainName(sid), Props); } else { principal = null; } } else { var objEntry = _utils .DoSearch("(objectclass=*)", SearchScope.Base, Props, Utils.ConvertDnToDomain(dn), dn) .DefaultIfEmpty(null).FirstOrDefault(); if (objEntry == null) { principal = null; } else { var resolvedObj = objEntry.ResolveAdEntry(); _cache.AddMapValue(dn, resolvedObj.ObjectType, resolvedObj.BloodHoundDisplay); principal = new MappedPrincipal ( resolvedObj.BloodHoundDisplay, resolvedObj.ObjectType ); } } } if (principal != null) { yield return(new GroupMember { AccountName = principal.PrincipalName, GroupName = principalDisplayName, ObjectType = principal.ObjectType }); } } var pgi = entry.GetProp("primarygroupid"); if (pgi == null) { yield break; } var pgsid = $"{domainSid}-{pgi}"; var primaryGroupName = _utils.SidToDisplay(pgsid, principalDomainName, Props, "group"); if (primaryGroupName != null) { yield return new GroupMember { AccountName = resolvedEntry.BloodHoundDisplay, GroupName = primaryGroupName, ObjectType = resolvedEntry.ObjectType } } ; }
internal static void GetProps(SearchResultEntry entry, ResolvedEntry resolved, ref Computer obj) { if (!Utils.IsMethodSet(ResolvedCollectionMethod.ObjectProps)) { return; } var uac = entry.GetProp("useraccountcontrol"); bool enabled, unconstrained, trustedToAuth; if (int.TryParse(uac, out var flag)) { var flags = (UacFlags)flag; enabled = (flags & UacFlags.AccountDisable) == 0; unconstrained = (flags & UacFlags.TrustedForDelegation) == UacFlags.TrustedForDelegation; trustedToAuth = (flags & UacFlags.TrustedToAuthForDelegation) != 0; } else { unconstrained = false; enabled = true; trustedToAuth = false; } var comps = new List <string>(); if (trustedToAuth) { var delegates = entry.GetPropArray("msds-allowedToDelegateTo"); obj.Properties.Add("allowedtodelegate", delegates); foreach (var d in delegates) { var hname = d.Contains("/") ? d.Split('/')[1] : d; hname = hname.Split(':')[0]; var resolvedHost = Utils.Instance.ResolveHost(hname); if (resolvedHost.Contains(".")) { comps.Add(resolvedHost.ToUpper()); } } } obj.AllowedToDelegate = comps.Distinct().ToArray(); var allowedToAct = entry.GetPropBytes("msDS-AllowedToActOnBehalfOfOtherIdentity"); if (allowedToAct != null) { var principals = new List <LocalMember>(); var sd = new ActiveDirectorySecurity(); sd.SetSecurityDescriptorBinaryForm(allowedToAct, AccessControlSections.Access); var utils = Utils.Instance; foreach (ActiveDirectoryAccessRule rule in sd.GetAccessRules(true, true, typeof(SecurityIdentifier))) { var sid = rule.IdentityReference.Value; var domain = utils.SidToDomainName(sid) ?? Utils.ConvertDnToDomain(entry.DistinguishedName); if (!MappedPrincipal.GetCommon(sid, out var principal)) { principal = utils.UnknownSidTypeToDisplay(sid, domain, Props); } else { if (sid == "S-1-5-9") { var dObj = utils.GetForest(domain); var d = dObj == null ? domain : dObj.RootDomain.Name; principal.PrincipalName = $"ENTERPRISE DOMAIN CONTROLLERS@{d}".ToUpper(); } else { principal.PrincipalName = $"{principal.PrincipalName}@{domain}".ToUpper(); } } if (principal == null) { continue; } principals.Add(new LocalMember { Name = principal.PrincipalName, Type = principal.ObjectType }); } obj.AllowedToAct = principals.ToArray(); } obj.Properties.Add("enabled", enabled); obj.Properties.Add("unconstraineddelegation", unconstrained); obj.Properties.Add("lastlogon", ConvertToUnixEpoch(entry.GetProp("lastlogon"))); obj.Properties.Add("lastlogontimestamp", ConvertToUnixEpoch(entry.GetProp("lastlogontimestamp"))); obj.Properties.Add("pwdlastset", ConvertToUnixEpoch(entry.GetProp("pwdlastset"))); obj.Properties.Add("serviceprincipalnames", entry.GetPropArray("serviceprincipalname")); var os = entry.GetProp("operatingsystem"); var sp = entry.GetProp("operatingsystemservicepack"); if (sp != null) { os = $"{os} {sp}"; } obj.Properties.Add("operatingsystem", os); obj.Properties.Add("description", entry.GetProp("description")); }
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 void GetProps(SearchResultEntry entry, ResolvedEntry resolved, ref User obj) { if (!Utils.IsMethodSet(ResolvedCollectionMethod.ObjectProps)) { return; } var uac = entry.GetProp("useraccountcontrol"); bool enabled, trustedToAuth; if (int.TryParse(uac, out var flag)) { var flags = (UacFlags)flag; enabled = (flags & UacFlags.AccountDisable) == 0; trustedToAuth = (flags & UacFlags.TrustedToAuthForDelegation) != 0; } else { trustedToAuth = false; enabled = true; } var comps = new List <string>(); if (trustedToAuth) { var delegates = entry.GetPropArray("msds-allowedToDelegateTo"); obj.Properties.Add("allowedtodelegate", delegates); foreach (var d in delegates) { var hname = d.Contains("/") ? d.Split('/')[1] : d; hname = hname.Split(':')[0]; var resolvedHost = Utils.Instance.ResolveHost(hname); if (resolvedHost.Contains(".")) { comps.Add(resolvedHost.ToUpper()); } } } obj.AllowedToDelegate = comps.Distinct().ToArray(); obj.Properties.Add("enabled", enabled); //var history = entry.GetPropBytes("sidhistory"); //obj.SidHistory = history != null ? new SecurityIdentifier(history, 0).Value : ""; obj.Properties.Add("lastlogon", ConvertToUnixEpoch(entry.GetProp("lastlogon"))); obj.Properties.Add("pwdlastset", ConvertToUnixEpoch(entry.GetProp("pwdlastset"))); var spn = entry.GetPropArray("serviceprincipalname"); obj.Properties.Add("serviceprincipalnames", spn); obj.Properties.Add("hasspn", spn.Length > 0); obj.Properties.Add("displayname", entry.GetProp("displayname")); obj.Properties.Add("email", entry.GetProp("mail")); obj.Properties.Add("title", entry.GetProp("title")); obj.Properties.Add("homedirectory", entry.GetProp("homedirectory")); obj.Properties.Add("description", entry.GetProp("description")); obj.Properties.Add("userpassword", entry.GetProp("userpassword")); var ac = entry.GetProp("admincount"); if (ac != null) { var a = int.Parse(ac); obj.Properties.Add("admincount", a != 0); } else { obj.Properties.Add("admincount", false); } }
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); }
public static IEnumerable <GpoMember> GetGpoMembers(SearchResultEntry entry, string domainName) { if (!Utils.IsMethodSet(ResolvedCollectionMethod.GPOLocalGroup)) { yield break; } var displayName = entry.GetProp("displayname"); var name = entry.GetProp("name"); var path = entry.GetProp("gpcfilesyspath"); if (displayName == null || name == null || path == null) { yield break; } var resolvedList = new List <TempGPOStorage>(); var template = $"{path}\\MACHINE\\Microsoft\\Windows NT\\SecEdit\\GptTmpl.inf"; if (File.Exists(template)) { var content = File.ReadAllText(template); var mMatch = MemberRegex.Match(content); if (mMatch.Success) { var memberText = mMatch.Groups[1].Value; var lines = Regex.Split(memberText.Trim(), @"\r\n|\r|\n"); foreach (var line in lines) { var kMatch = KeyRegex.Match(line); var key = kMatch.Groups[1].Value.Trim(); var val = kMatch.Groups[2].Value.Trim(); var keyMatch = MemberLeftRegex.Match(key); var valMatch = MemberRightRegex.Matches(val); //The first case is when the members of a local group are explicitly set if (keyMatch.Success) { var rid = int.Parse(ExtractRid.Match(keyMatch.Value).Groups[1].Value); foreach (var member in val.Split(',')) { var sid = GetSid(member.Trim('*'), domainName); if (sid == null) { continue; } var obj = _utils.UnknownSidTypeToDisplay(sid, _utils.SidToDomainName(sid), Props); if (obj == null) { continue; } resolvedList.Add(new TempGPOStorage { Name = obj.PrincipalName, RID = rid, Type = obj.ObjectType }); } } //A group has been set as memberof to one of our rids var index = key.IndexOf("MemberOf", StringComparison.CurrentCultureIgnoreCase); if (valMatch.Count > 0 && index > 0) { var sid = key.Trim('*').Substring(0, index - 3); var obj = _utils.UnknownSidTypeToDisplay(sid, _utils.SidToDomainName(sid), Props); if (obj != null) { foreach (var x in valMatch) { var rid = int.Parse(ExtractRid.Match(x.ToString()).Groups[1].Value); resolvedList.Add(new TempGPOStorage { Name = obj.PrincipalName, RID = rid, Type = obj.ObjectType }); } } } } } } var xml = $"{path}\\MACHINE\\Preferences\\Groups\\Groups.xml"; if (File.Exists(xml)) { var doc = new XPathDocument(xml); var nav = doc.CreateNavigator(); var nodes = nav.Select("/Groups/Group"); while (nodes.MoveNext()) { var properties = nodes.Current.Select("Properties"); while (properties.MoveNext()) { var groupSid = properties.Current.GetAttribute("groupSid", ""); if (groupSid == "") { continue; } if (!groupSid.Equals("S-1-5-32-544")) { continue; } var rid = int.Parse(ExtractRid.Match(groupSid).Groups[1].Value); var members = properties.Current.Select("Members"); while (members.MoveNext()) { var subMembers = members.Current.Select("Member"); while (subMembers.MoveNext()) { var action = subMembers.Current.GetAttribute("action", ""); if (action.Equals("ADD")) { var sid = subMembers.Current.GetAttribute("sid", ""); if (string.IsNullOrEmpty(sid)) { continue; } var resolvedPrincipal = _utils.UnknownSidTypeToDisplay(sid, domainName, Props); if (resolvedPrincipal != null) { resolvedList.Add(new TempGPOStorage { RID = rid, Name = resolvedPrincipal.PrincipalName, Type = resolvedPrincipal.ObjectType }); } } } } } } } var affected = new List <string>(); if (resolvedList.Count > 0) { foreach (var ouObject in _utils.DoSearch($"(gplink=*{name}*)", SearchScope.Subtree, GpLinkProps, domainName)) { var adspath = ouObject.DistinguishedName; foreach (var compEntry in _utils.DoSearch("(objectclass=computer)", SearchScope.Subtree, GpoProps, domainName, adspath)) { var samAccountType = compEntry.GetProp("samaccounttype"); if (samAccountType == null || samAccountType != "805306369") { continue; } var server = compEntry.ResolveAdEntry()?.BloodHoundDisplay; if (server == null) { continue; } affected.Add(server); } } } if (affected.Count > 0) { var g = new GpoMember { AffectedComputers = affected.ToArray(), LocalAdmins = resolvedList.Where((x) => x.RID == 544).Select((x) => new LocalMember { Name = x.Name, Type = x.Type }).ToArray(), RemoteDesktopUsers = resolvedList.Where((x) => x.RID == 555).Select((x) => new LocalMember { Name = x.Name, Type = x.Type }).ToArray(), DcomUsers = resolvedList.Where((x) => x.RID == 562).Select((x) => new LocalMember { Name = x.Name, Type = x.Type }).ToArray() }; yield return(g); } }
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 (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); }
public static void GetObjectAces(SearchResultEntry entry, ResolvedEntry resolved, ref Computer g) { if (!Utils.IsMethodSet(ResolvedCollectionMethod.ACL)) { return; } //We only care about computers if they have LAPS installed if (entry.GetProp("ms-mcs-admpwdexpirationtime") == null) { return; } var aces = new List <ACL>(); var ntSecurityDescriptor = entry.GetPropBytes("ntsecuritydescriptor"); //If the ntsecuritydescriptor is null, no point in continuing //I'm still not entirely sure what causes this, but it can happen if (ntSecurityDescriptor == null) { return; } var domainName = Utils.ConvertDnToDomain(entry.DistinguishedName); //Convert the ntsecuritydescriptor bytes to a .net object var descriptor = new RawSecurityDescriptor(ntSecurityDescriptor, 0); //Grab the DACL var rawAcl = descriptor.DiscretionaryAcl; //Grab the Owner var ownerSid = descriptor.Owner.ToString(); //Determine the owner of the object. Start by checking if we've already determined this is null if (!_nullSids.TryGetValue(ownerSid, out _)) { //Check if its a common SID if (!MappedPrincipal.GetCommon(ownerSid, out var owner)) { //Resolve the sid manually if we still dont have it var ownerDomain = _utils.SidToDomainName(ownerSid) ?? domainName; owner = _utils.UnknownSidTypeToDisplay(ownerSid, ownerDomain, Props); } else { owner.PrincipalName = $"{owner.PrincipalName}@{domainName}"; } //Filter out the Local System principal which pretty much every entry has if (owner != null && !owner.PrincipalName.Contains("LOCAL SYSTEM") && !owner.PrincipalName.Contains("CREATOR OWNER")) { aces.Add(new ACL { AceType = "", RightName = "Owner", PrincipalName = owner.PrincipalName, PrincipalType = owner.ObjectType }); } else { //We'll cache SIDs we've failed to resolve previously so we dont keep trying _nullSids.TryAdd(ownerSid, new byte()); } } foreach (var genericAce in rawAcl) { var qAce = genericAce as QualifiedAce; if (qAce == null) { continue; } var objectSid = qAce.SecurityIdentifier.ToString(); if (_nullSids.TryGetValue(objectSid, out _)) { continue; } //Check if its a common sid if (!MappedPrincipal.GetCommon(objectSid, out var mappedPrincipal)) { //If not common, lets resolve it normally var objectDomain = _utils.SidToDomainName(objectSid) ?? domainName; mappedPrincipal = _utils.UnknownSidTypeToDisplay(objectSid, objectDomain, Props); if (mappedPrincipal == null) { _nullSids.TryAdd(objectSid, new byte()); continue; } } else { if (mappedPrincipal.PrincipalName == "ENTERPRISE DOMAIN CONTROLLERS") { var dObj = _utils.GetForest(domainName); var d = dObj == null ? domainName : dObj.RootDomain.Name; mappedPrincipal.PrincipalName = $"{mappedPrincipal.PrincipalName}@{d}".ToUpper(); } else { mappedPrincipal.PrincipalName = $"{mappedPrincipal.PrincipalName}@{domainName}".ToUpper(); } } if (mappedPrincipal.PrincipalName.Contains("LOCAL SYSTEM") || mappedPrincipal.PrincipalName.Contains("CREATOR OWNER")) { continue; } //Convert our right to an ActiveDirectoryRight enum object, and then to a string var adRight = (ActiveDirectoryRights)Enum.ToObject(typeof(ActiveDirectoryRights), qAce.AccessMask); var adRightString = adRight.ToString(); //Get the ACE for our right var ace = qAce as ObjectAce; var guid = ace != null?ace.ObjectAceType.ToString() : ""; var inheritedObjectType = ace != null?ace.InheritedObjectAceType.ToString() : "00000000-0000-0000-0000-000000000000"; var isInherited = inheritedObjectType == "00000000-0000-0000-0000-000000000000" || inheritedObjectType == "bf967a86-0de6-11d0-a285-00aa003049e2"; if (!isInherited && !((ace.AceFlags & AceFlags.InheritOnly) == AceFlags.InheritOnly) && (ace.AceFlags & AceFlags.Inherited) != AceFlags.Inherited) { //If these conditions hold the ACE applies to this object anyway isInherited = true; } if (!isInherited) { continue; } var toContinue = false; _guidMap.TryGetValue(guid, out var mappedGuid); //Interesting Computer ACEs - GenericAll, WriteDacl, WriteOwner, ExtendedRight toContinue |= adRightString.Contains("WriteDacl") || adRightString.Contains("WriteOwner"); if (adRightString.Contains("GenericAll")) { toContinue |= "00000000-0000-0000-0000-000000000000".Equals(guid) || guid.Equals("") || toContinue; toContinue |= mappedGuid != null && mappedGuid == "ms-Mcs-AdmPwd" || toContinue; } if (adRightString.Contains("ExtendedRight")) { toContinue |= guid.Equals("00000000-0000-0000-0000-000000000000") || guid.Equals("") || toContinue; toContinue |= mappedGuid != null && mappedGuid == "ms-Mcs-AdmPwd" || toContinue; } if (!toContinue) { continue; } if (adRightString.Contains("GenericAll")) { if (mappedGuid == "ms-Mcs-AdmPwd") { aces.Add(new ACL { AceType = "", PrincipalName = mappedPrincipal.PrincipalName, PrincipalType = mappedPrincipal.ObjectType, RightName = "ReadLAPSPassword" }); } else { aces.Add(new ACL { AceType = "", PrincipalName = mappedPrincipal.PrincipalName, PrincipalType = mappedPrincipal.ObjectType, RightName = "GenericAll" }); } } if (adRightString.Contains("WriteOwner")) { aces.Add(new ACL { AceType = "", PrincipalName = mappedPrincipal.PrincipalName, PrincipalType = mappedPrincipal.ObjectType, RightName = "WriteOwner" }); } if (adRightString.Contains("WriteDacl")) { aces.Add(new ACL { AceType = "", PrincipalName = mappedPrincipal.PrincipalName, PrincipalType = mappedPrincipal.ObjectType, RightName = "WriteDacl" }); } if (adRightString.Contains("ExtendedRight")) { if (mappedGuid == "ms-Mcs-AdmPwd") { aces.Add(new ACL { AceType = "", PrincipalName = mappedPrincipal.PrincipalName, PrincipalType = mappedPrincipal.ObjectType, RightName = "ReadLAPSPassword" }); } else { aces.Add(new ACL { AceType = "All", PrincipalName = mappedPrincipal.PrincipalName, PrincipalType = mappedPrincipal.ObjectType, RightName = "ExtendedRight" }); } } } g.Aces = aces.Distinct().ToArray(); }
public static void GetObjectAces(SearchResultEntry entry, ResolvedEntry resolved, ref Computer obj) { if (!Utils.IsMethodSet(ResolvedCollectionMethod.ACL)) { return; } var aces = new List <ACL>(); var ntSecurityDescriptor = entry.GetPropBytes("ntsecuritydescriptor"); //If the ntsecuritydescriptor is null, no point in continuing //I'm still not entirely sure what causes this, but it can happen if (ntSecurityDescriptor == null) { return; } var domainName = Utils.ConvertDnToDomain(entry.DistinguishedName); var newDescriptor = new ActiveDirectorySecurity(); newDescriptor.SetSecurityDescriptorBinaryForm(ntSecurityDescriptor); var owner = GetAclOwner(newDescriptor, domainName); if (owner != null) { aces.Add(new ACL { AceType = "", RightName = "Owner", PrincipalName = owner.PrincipalName, PrincipalType = owner.ObjectType }); } foreach (ActiveDirectoryAccessRule ace in newDescriptor.GetAccessRules(true, true, typeof(SecurityIdentifier))) { //Ignore null aces if (ace == null) { continue; } //Ignore Deny aces if (!ace.AccessControlType.Equals(AccessControlType.Allow)) { continue; } //Resolve the principal in the ACE var principal = GetAcePrincipal(ace, domainName); //If its null, we don't care so move on if (principal == null) { continue; } //Check if our ACE applies through inheritance rules if (!CheckAceInheritanceRules(ace, resolved.ObjectType)) { continue; } //Interesting Computer ACEs - GenericAll, WriteDacl, GenericWrite, WriteProperty (AllowedToAct), WriteOwner, ExtendedRight (LAPS) var rights = ace.ActiveDirectoryRights; var objectAceType = ace.ObjectType.ToString(); _guidMap.TryGetValue(objectAceType, out var mappedGuid); if (rights.HasFlag(ActiveDirectoryRights.GenericAll)) { if (objectAceType == AllGuid || objectAceType == "") { aces.Add(new ACL { AceType = "", RightName = "GenericAll", PrincipalName = principal.PrincipalName, PrincipalType = principal.ObjectType }); } else if (mappedGuid != null && mappedGuid == "ms-Mcs-AdmPwd") { aces.Add(new ACL { AceType = "", RightName = "ReadLAPSPassword", PrincipalName = principal.PrincipalName, PrincipalType = principal.ObjectType }); } //GenericAll includes every other flag, so continue here so we don't duplicate privs continue; } if (rights.HasFlag(ActiveDirectoryRights.GenericWrite) || rights.HasFlag(ActiveDirectoryRights.WriteProperty)) { //GenericWrite encapsulates WriteProperty if (rights.HasFlag(ActiveDirectoryRights.GenericWrite) && (objectAceType == AllGuid || objectAceType == "")) { aces.Add(new ACL { AceType = "", RightName = "GenericWrite", PrincipalName = principal.PrincipalName, PrincipalType = principal.ObjectType }); } else if (rights.HasFlag(ActiveDirectoryRights.WriteProperty)) { if (objectAceType == AllGuid || objectAceType == "") { aces.Add(new ACL { AceType = "", RightName = "GenericWrite", PrincipalName = principal.PrincipalName, PrincipalType = principal.ObjectType }); } else if (objectAceType == "3f78c3e5-f79a-46bd-a0b8-9d18116ddc79") { aces.Add(new ACL { AceType = "AllowedToAct", PrincipalName = principal.PrincipalName, PrincipalType = principal.ObjectType, RightName = "WriteProperty" }); } } } if (rights.HasFlag(ActiveDirectoryRights.WriteDacl)) { aces.Add(new ACL { AceType = "", RightName = "WriteDacl", PrincipalName = principal.PrincipalName, PrincipalType = principal.ObjectType }); } if (rights.HasFlag(ActiveDirectoryRights.WriteOwner)) { aces.Add(new ACL { AceType = "", RightName = "WriteOwner", PrincipalName = principal.PrincipalName, PrincipalType = principal.ObjectType }); } if (rights.HasFlag(ActiveDirectoryRights.ExtendedRight)) { if (mappedGuid != null && mappedGuid == "ms-Mcs-AdmPwd" && entry.GetProp("ms-mcs-admpwdexpirationtime") != null) { aces.Add(new ACL { AceType = "", RightName = "ReadLAPSPassword", PrincipalName = principal.PrincipalName, PrincipalType = principal.ObjectType }); } else if (objectAceType == AllGuid || objectAceType == "") { aces.Add(new ACL { AceType = "All", RightName = "ExtendedRight", PrincipalName = principal.PrincipalName, PrincipalType = principal.ObjectType }); } } } obj.Aces = aces.Distinct().ToArray(); }
/// <summary> /// Processes an LDAP entry to resolve PrimaryGroup/MemberOf properties /// </summary> /// <param name="entry">LDAP entry</param> /// <param name="resolvedEntry">The resolved object with the name/type of the entry</param> /// <param name="domainSid">SID for the domain being enumerated. Used to resolve PrimaryGroupID</param> /// <returns></returns> public static IEnumerable <GroupMember> ProcessAdObject(SearchResultEntry entry, ResolvedEntry resolvedEntry, string domainSid) { var principalDomainName = Utils.ConvertDnToDomain(entry.DistinguishedName); var principalDisplayName = resolvedEntry.BloodHoundDisplay; var objectType = resolvedEntry.ObjectType; //If this object is a group, add it to our DN cache if (objectType.Equals("group")) { _cache.AddMapValue(entry.DistinguishedName, "group", principalDisplayName); } foreach (var dn in entry.GetPropArray("memberof")) { //Check our cache first if (!_cache.GetMapValue(dn, "group", out var groupName)) { //Search for the object directly var groupEntry = _utils .DoSearch("(objectclass=group)", SearchScope.Base, Props, Utils.ConvertDnToDomain(dn), dn) .DefaultIfEmpty(null).FirstOrDefault(); if (groupEntry == null) { //Our search didn't return anything so fallback //Try convertadname first groupName = ConvertAdName(dn, AdsTypes.AdsNameTypeDn, AdsTypes.AdsNameTypeNt4); //If convertadname is null, just screw with the distinguishedname to get the group groupName = groupName != null ? groupName.Split('\\').Last() : dn.Substring(0, dn.IndexOf(",", StringComparison.Ordinal)).Split('=').Last(); } else { //We got an object back! groupName = groupEntry.ResolveAdEntry().BloodHoundDisplay; } //If we got a group back, add it to the cache for later use if (groupName != null) { _cache.AddMapValue(dn, "group", groupName); } } //We got our group! Return it if (groupName != null) { yield return new GroupMember { AccountName = principalDisplayName, GroupName = groupName, ObjectType = objectType } } ; } var primaryGroupId = entry.GetProp("primarygroupid"); if (primaryGroupId == null) { yield break; } //As far as I know you cant belong to a primary group of another domain var pgsid = $"{domainSid}-{primaryGroupId}"; var primaryGroupName = _utils.SidToDisplay(pgsid, principalDomainName, Props, "group"); if (primaryGroupName != null) { yield return new GroupMember { AccountName = principalDisplayName, GroupName = primaryGroupName, ObjectType = objectType } } ; }