/// <summary> /// Grabs computers names from the text file specified in the options, and attempts to resolve them to LDAP objects. /// Pushes the corresponding LDAP objects to the queue. /// </summary> /// <param name="queue"></param> /// <returns></returns> protected override async Task ProduceLdap(ITargetBlock <SearchResultEntry> queue) { var computerFile = Options.Instance.ComputerFile; var token = Helpers.GetCancellationToken(); OutputTasks.StartOutputTimer(); //Open the file for reading using (var fileStream = new StreamReader(new FileStream(computerFile, FileMode.Open, FileAccess.Read))) { string computer; // Loop over each line in the file while ((computer = fileStream.ReadLine()) != null) { //If the cancellation token is set, cancel enumeration if (token.IsCancellationRequested) { break; } string sid; if (!computer.StartsWith("S-1-5-21")) { //The computer isn't a SID so try to convert it to one sid = await ResolutionHelpers.ResolveHostToSid(computer, DomainName); } else { //The computer is already a sid, so just store it off sid = computer; } try { //Convert the sid to a hex representation and find the entry in the domain var hexSid = Helpers.ConvertSidToHexSid(sid); var entry = await Searcher.GetOne($"(objectsid={hexSid})", Props, SearchScope.Subtree); if (entry == null) { //We couldn't find the entry for whatever reason Console.WriteLine($"Failed to resolve {computer}"); continue; } //Success! Send the computer to be processed await queue.SendAsync(entry); } catch { Console.WriteLine($"Failed to resolve {computer}"); } } } queue.Complete(); }
/// <summary> /// Processes the msds-groupmsamembership property, and determines who can read the password /// </summary> /// <param name="wrapper"></param> /// <returns></returns> private static async Task <List <ACL> > ProcessGMSA(LdapWrapper wrapper) { var aces = new List <ACL>(); //Grab the property as a byte array var securityDescriptor = wrapper.SearchResult.GetPropertyAsBytes("msds-groupmsamembership"); //If the property is null, its either not a GMSA or something went wrong, so just exit out if (securityDescriptor == null) { return(aces); } //Create a new ActiveDirectorySecurity object and set the bytes to the descriptor var descriptor = new ActiveDirectorySecurity(); descriptor.SetSecurityDescriptorBinaryForm(securityDescriptor); // Loop over the entries in the security descriptor foreach (ActiveDirectoryAccessRule ace in descriptor.GetAccessRules(true, true, typeof(SecurityIdentifier))) { //Ignore null aces if (ace == null) { continue; } //Ignore deny aces (although this should never show up in GMSAs if (ace.AccessControlType == AccessControlType.Deny) { continue; } //Pre-process the principal for the SID var principalSid = FilterAceSids(ace.IdentityReference.Value); //Ignore null SIDs if (principalSid == null) { continue; } //Resolve the principal SID and grab its type var(finalSid, type) = await ResolutionHelpers.ResolveSidAndGetType(principalSid, wrapper.Domain); aces.Add(new ACL { RightName = "ReadGMSAPassword", AceType = "", PrincipalSID = finalSid, PrincipalType = type, IsInherited = false }); } return(aces); }
/// <summary> /// Finds stealth targets using ldap properties. /// </summary> /// <returns></returns> private async Task <Dictionary <string, SearchResultEntry> > FindPathTargetSids() { var paths = new ConcurrentDictionary <string, byte>(); var sids = new Dictionary <string, SearchResultEntry>(); //Request user objects with the "homedirectory", "scriptpath", or "profilepath" attributes Parallel.ForEach(Searcher.QueryLdap( "(&(samAccountType=805306368)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(|(homedirectory=*)(scriptpath=*)(profilepath=*)))", new[] { "homedirectory", "scriptpath", "profilepath" }, SearchScope.Subtree), (searchResult) => { //Grab any properties that exist, filter out null values var poss = new[] { searchResult.GetProperty("homedirectory"), searchResult.GetProperty("scriptpath"), searchResult.GetProperty("profilepath") }.Where(s => s != null); // Loop over each possibility, and grab the hostname from the path, adding it to a list foreach (var s in poss) { var split = s?.Split('\\'); if (!(split?.Length >= 3)) { continue; } var path = split[2]; paths.TryAdd(path, new byte()); } }); // Loop over the paths we grabbed, and resolve them to sids. foreach (var path in paths.Keys) { var sid = await ResolutionHelpers.ResolveHostToSid(path, DomainName); if (sid != null) { var searchResult = await Searcher.GetOne($"(objectsid={Helpers.ConvertSidToHexSid(sid)})", Props, SearchScope.Subtree); sids.Add(sid, searchResult); } } //Return all the sids corresponding to objects return(sids); }
private static async Task ProcessUserSPNs(User user) { var servicePrincipalNames = user.SearchResult.GetPropertyAsArray("serviceprincipalname"); var domain = user.Domain; var resolved = new List <SPNTarget>(); //Loop over the spns, and look for any that start with mssqlsvc foreach (var spn in servicePrincipalNames.Where(x => x.StartsWith("mssqlsvc", StringComparison.OrdinalIgnoreCase))) { int port; if (spn.Contains(":")) { var success = int.TryParse(spn.Split(':')[1], out port); if (!success) { port = 1433; } } else { port = 1433; } //Try to turn the host into a SID var hostSid = await ResolutionHelpers.ResolveHostToSid(spn, domain); if (hostSid.StartsWith("S-1-5")) { resolved.Add(new SPNTarget { ComputerSid = hostSid, Port = port, Service = "SQLAdmin" }); } } user.SPNTargets = resolved.Distinct().ToArray(); }
/// <summary> /// Processes domain objects /// </summary> /// <param name="domain"></param> /// <returns></returns> private static async Task ProcessDomainObject(Domain domain) { var searchResult = domain.SearchResult; var resolvedLinks = new List <GPLink>(); //Grab the gplink property var gpLinks = searchResult.GetProperty("gplink"); //If gplink is null, return if (gpLinks != null) { //Loop over each link in the property, which will be encapsulated by [] and start with LDAP:// foreach (var link in gpLinks.Split(']', '[').Where(l => l.StartsWith("LDAP"))) { //Split the GPLink value. The distinguishedname will be in the first part, and the status of the gplink in the second var splitLink = link.Split(';'); var distinguishedName = splitLink[0]; distinguishedName = distinguishedName.Substring(distinguishedName.IndexOf("CN=", StringComparison.OrdinalIgnoreCase)); var status = splitLink[1]; //Status 1 and status 3 correspond to disabled/unenforced and disabled/enforced, so filter them out if (status == "1" || status == "3") { continue; } //If the status is 0, its unenforced, 2 is enforced var enforced = status == "2"; //Try to get the GUID of the OU from its distinguishedname var(success, guid) = await ResolutionHelpers.OUDistinguishedNameToGuid(distinguishedName); if (success) { resolvedLinks.Add(new GPLink { IsEnforced = enforced, Guid = guid }); } } } // Find the descendant users, computers, and OUs directly under this domain object var users = new List <string>(); var computers = new List <string>(); var ous = new List <string>(); //Create a directory searcher object for the domain var searcher = Helpers.GetDirectorySearcher(domain.Domain); //Search for descendant objects with the OneLevel specification foreach (var containedObject in searcher.QueryLdap( "(|(samAccountType=805306368)(samAccountType=805306369)(objectclass=organizationalUnit))", Helpers.ResolutionProps, SearchScope.OneLevel, domain.DistinguishedName)) { //Grab the type of the object found var type = containedObject.GetLdapType(); // Get the identifier of the object var id = containedObject.GetObjectIdentifier(); //If we dont have an identifier for this object, something is wrong, so just continue if (id == null) { continue; } switch (type) { case LdapTypeEnum.OU: ous.Add(id); break; case LdapTypeEnum.Computer: computers.Add(id); break; case LdapTypeEnum.User: users.Add(id); break; default: continue; } } //Search for descendant container objects foreach (var containerObject in searcher.QueryLdap("(objectclass=container)", Helpers.ResolutionProps, SearchScope.OneLevel, domain.DistinguishedName)) { // Search for all the user/computer objects inside the container foreach (var subObject in searcher.QueryLdap("(|(samAccountType=805306368)(samAccountType=805306369))", Helpers.ResolutionProps, SearchScope.Subtree, containerObject.DistinguishedName)) { var type = subObject.GetLdapType(); var id = subObject.GetObjectIdentifier(); if (id == null) { continue; } switch (type) { case LdapTypeEnum.OU: ous.Add(id); break; case LdapTypeEnum.Computer: computers.Add(id); break; case LdapTypeEnum.User: users.Add(id); break; default: continue; } } } domain.Computers = computers.ToArray(); domain.Users = users.ToArray(); domain.ChildOus = ous.ToArray(); domain.Links = resolvedLinks.ToArray(); }
/// <summary> /// Processes OU objects /// </summary> /// <param name="ou"></param> /// <returns></returns> private static async Task ProcessOUObject(OU ou) { var searchResult = ou.SearchResult; //Grab the gpoptions attribute var gpOptions = searchResult.GetProperty("gpoptions"); //Add a property for blocking inheritance ou.Properties.Add("blocksinheritance", gpOptions != null && gpOptions == "1"); var resolvedLinks = new List <GPLink>(); //Grab the gplink property var gpLinks = searchResult.GetProperty("gplink"); if (gpLinks != null) { //Loop over the links in the gplink property foreach (var link in gpLinks.Split(']', '[').Where(l => l.StartsWith("LDAP"))) { var splitLink = link.Split(';'); var distinguishedName = splitLink[0]; distinguishedName = distinguishedName.Substring(distinguishedName.IndexOf("CN=", StringComparison.OrdinalIgnoreCase)); var status = splitLink[1]; //Status 1 and status 3 correspond to disabled/unenforced and disabled/enforced, so filter them out if (status == "1" || status == "3") { continue; } //If the status is 0, its unenforced, 2 is enforced var enforced = status == "2"; var(success, guid) = await ResolutionHelpers.OUDistinguishedNameToGuid(distinguishedName); if (success) { resolvedLinks.Add(new GPLink { IsEnforced = enforced, Guid = guid }); } } } var users = new List <string>(); var computers = new List <string>(); var ous = new List <string>(); var searcher = Helpers.GetDirectorySearcher(ou.Domain); // Find descendant User, Computer, OU objects foreach (var containedObject in searcher.QueryLdap( "(|(samAccountType=805306368)(samAccountType=805306369)(objectclass=organizationalUnit))", Helpers.ResolutionProps, SearchScope.OneLevel, ou.DistinguishedName)) { var type = containedObject.GetLdapType(); var id = containedObject.GetObjectIdentifier(); if (id == null) { continue; } switch (type) { case LdapTypeEnum.OU: ous.Add(id); break; case LdapTypeEnum.Computer: computers.Add(id); break; case LdapTypeEnum.User: users.Add(id); break; default: continue; } } ou.Computers = computers.ToArray(); ou.Users = users.ToArray(); ou.ChildOus = ous.ToArray(); ou.Links = resolvedLinks.ToArray(); }
/// <summary> /// Wraps the GetNetLocalGroupMembers call with a timeout, and then processes the results into objects /// </summary> /// <param name="computer"></param> /// <param name="rid">The relative ID of the group we want to query</param> /// <returns></returns> private static async Task <List <GenericMember> > GetNetLocalGroupMembers(Computer computer, LocalGroupRids rid) { var sids = new IntPtr[0]; var groupMemberList = new List <GenericMember>(); var task = Task.Run(() => CallLocalGroupApi(computer, rid, out sids)); //Run the API call along with a 10 second timeout if (await Task.WhenAny(task, Task.Delay(10000)) != task) { OutputTasks.AddComputerStatus(new ComputerStatus { ComputerName = computer.DisplayName, Status = "Timeout", Task = $"GetNetLocalGroup-{rid}" }); return(groupMemberList); } //Check the result of the task var taskResult = task.Result; if (!taskResult) { return(groupMemberList); } if (Options.Instance.DumpComputerStatus) { OutputTasks.AddComputerStatus(new ComputerStatus { ComputerName = computer.DisplayName, Status = "Success", Task = $"GetNetLocalGroup-{rid}" }); } //Take our pointers to sids and convert them into string sids for matching var convertedSids = new List <string>(); for (var i = 0; i < sids.Length; i++) { try { var sid = new SecurityIdentifier(sids[i]).Value; convertedSids.Add(sid); } catch { //SID Resolution failed for some reason, so ignore it } finally { //Set the IntPtr to zero, so we can GC those sids[i] = IntPtr.Zero; } } //Null out sids, so garbage collection takes care of it sids = null; //Extract the domain SID from the computer's sid, to avoid creating more SecurityIdentifier objects var domainSid = computer.ObjectIdentifier.Substring(0, computer.ObjectIdentifier.LastIndexOf('-')); // The first account in our list should always be the default RID 500 for the machine, but we'll take some extra precautions var machineSid = convertedSids.DefaultIfEmpty("DUMMYSTRING").FirstOrDefault(x => x.EndsWith("-500") && !x.StartsWith(domainSid)) ?? "DUMMYSTRING"; //If we found a machine sid, strip the ending bit off if (machineSid.StartsWith("S-1-5-21")) { machineSid = machineSid.Substring(0, machineSid.LastIndexOf('-')); } foreach (var sid in convertedSids) { //Filter out local accounts if (sid.StartsWith(machineSid)) { continue; } var(finalSid, type) = await ResolutionHelpers.ResolveSidAndGetType(sid, computer.Domain); //Filter out null sids, usually from deconflictions if (finalSid == null) { continue; } groupMemberList.Add(new GenericMember { MemberType = type, MemberId = finalSid }); } return(groupMemberList); }
/// <summary> /// Grab properties from User objects /// </summary> /// <param name="wrapper"></param> /// <returns></returns> private static async Task ParseUserProperties(User wrapper) { var result = wrapper.SearchResult; // Start with UAC properties var userAccountControl = result.GetProperty("useraccountcontrol"); var enabled = true; var trustedToAuth = false; var sensitive = false; var dontReqPreAuth = false; var passwdNotReq = false; var unconstrained = false; var pwdNeverExires = false; if (int.TryParse(userAccountControl, out var baseFlags)) { var uacFlags = (UacFlags)baseFlags; enabled = (uacFlags & UacFlags.AccountDisable) == 0; trustedToAuth = (uacFlags & UacFlags.TrustedToAuthForDelegation) != 0; sensitive = (uacFlags & UacFlags.NotDelegated) != 0; dontReqPreAuth = (uacFlags & UacFlags.DontReqPreauth) != 0; passwdNotReq = (uacFlags & UacFlags.PasswordNotRequired) != 0; unconstrained = (uacFlags & UacFlags.TrustedForDelegation) != 0; pwdNeverExires = (uacFlags & UacFlags.DontExpirePassword) != 0; } wrapper.Properties.Add("dontreqpreauth", dontReqPreAuth); wrapper.Properties.Add("passwordnotreqd", passwdNotReq); wrapper.Properties.Add("unconstraineddelegation", unconstrained); wrapper.Properties.Add("sensitive", sensitive); wrapper.Properties.Add("enabled", enabled); wrapper.Properties.Add("pwdneverexpires", pwdNeverExires); var trustedToAuthComputers = new List <string>(); // Parse Allowed To Delegate if (trustedToAuth) { var delegates = result.GetPropertyAsArray("msds-AllowedToDelegateTo"); wrapper.Properties.Add("allowedtodelegate", delegates); //Try to resolve each computer to a SID foreach (var computerName in delegates) { var resolvedHost = await ResolutionHelpers.ResolveHostToSid(computerName, wrapper.Domain); trustedToAuthComputers.Add(resolvedHost); } } wrapper.AllowedToDelegate = trustedToAuthComputers.Distinct().ToArray(); //Grab time based properties wrapper.Properties.Add("lastlogon", ConvertToUnixEpoch(result.GetProperty("lastlogon"))); wrapper.Properties.Add("lastlogontimestamp", ConvertToUnixEpoch(result.GetProperty("lastlogontimestamp"))); wrapper.Properties.Add("pwdlastset", ConvertToUnixEpoch(result.GetProperty("pwdlastset"))); var servicePrincipalNames = result.GetPropertyAsArray("serviceprincipalname"); wrapper.Properties.Add("serviceprincipalnames", servicePrincipalNames); wrapper.Properties.Add("hasspn", servicePrincipalNames.Length > 0); wrapper.Properties.Add("displayname", result.GetProperty("displayname")); wrapper.Properties.Add("email", result.GetProperty("mail")); wrapper.Properties.Add("title", result.GetProperty("title")); wrapper.Properties.Add("homedirectory", result.GetProperty("homedirectory")); wrapper.Properties.Add("userpassword", result.GetProperty("userpassword")); var adminCount = result.GetProperty("admincount"); if (adminCount != null) { var a = int.Parse(adminCount); wrapper.Properties.Add("admincount", a != 0); } else { wrapper.Properties.Add("admincount", false); } var sidHistory = result.GetPropertyAsArrayOfBytes("sidhistory"); var sidHistoryList = new List <string>(); var sidHistoryPrincipals = new List <GenericMember>(); foreach (var sid in sidHistory) { var s = Helpers.CreateSecurityIdentifier(sid)?.Value; if (s != null) { sidHistoryList.Add(s); var sidType = await ResolutionHelpers.LookupSidType(s, wrapper.Domain); if (sidType != LdapTypeEnum.Unknown) { sidHistoryPrincipals.Add(new GenericMember { MemberId = s, MemberType = sidType }); } } } wrapper.HasSIDHistory = sidHistoryPrincipals.ToArray(); wrapper.Properties.Add("sidhistory", sidHistoryList.ToArray()); }
/// <summary> /// Grabs properties from Computer objects /// </summary> /// <param name="wrapper"></param> /// <returns></returns> private static async Task ParseComputerProperties(Computer wrapper) { var result = wrapper.SearchResult; var userAccountControl = result.GetProperty("useraccountcontrol"); var enabled = true; var trustedToAuth = false; var unconstrained = false; if (int.TryParse(userAccountControl, out var baseFlags)) { var uacFlags = (UacFlags)baseFlags; enabled = (uacFlags & UacFlags.AccountDisable) == 0; trustedToAuth = (uacFlags & UacFlags.TrustedToAuthForDelegation) != 0; unconstrained = (uacFlags & UacFlags.TrustedForDelegation) != 0; } wrapper.Properties.Add("enabled", enabled); wrapper.Properties.Add("unconstraineddelegation", unconstrained); var trustedToAuthComputers = new List <string>(); // Parse Allowed To Delegate if (trustedToAuth) { var delegates = result.GetPropertyAsArray("msds-AllowedToDelegateTo"); wrapper.Properties.Add("allowedtodelegate", delegates); // For each computer thats in this array, try and turn it into a SID foreach (var computerName in delegates) { var resolvedHost = await ResolutionHelpers.ResolveHostToSid(computerName, wrapper.Domain); trustedToAuthComputers.Add(resolvedHost); } } wrapper.AllowedToDelegate = trustedToAuthComputers.Distinct().ToArray(); var allowedToAct = result.GetPropertyAsBytes("msDS-AllowedToActOnBehalfOfOtherIdentity"); var allowedToActPrincipals = new List <GenericMember>(); if (allowedToAct != null) { var securityDescriptor = new ActiveDirectorySecurity(); securityDescriptor.SetSecurityDescriptorBinaryForm(allowedToAct); foreach (ActiveDirectoryAccessRule ace in securityDescriptor.GetAccessRules(true, true, typeof(SecurityIdentifier))) { var sid = ace.IdentityReference.Value; LdapTypeEnum type; if (CommonPrincipal.GetCommonSid(sid, out var principal)) { type = principal.Type; sid = Helpers.ConvertCommonSid(sid, wrapper.Domain); } else { type = await ResolutionHelpers.LookupSidType(sid, wrapper.Domain); } allowedToActPrincipals.Add(new GenericMember { MemberType = type, MemberId = sid }); } } wrapper.AllowedToAct = allowedToActPrincipals.Distinct().ToArray(); wrapper.Properties.Add("serviceprincipalnames", result.GetPropertyAsArray("serviceprincipalname")); wrapper.Properties.Add("lastlogontimestamp", ConvertToUnixEpoch(result.GetProperty("lastlogontimestamp"))); wrapper.Properties.Add("pwdlastset", ConvertToUnixEpoch(result.GetProperty("pwdlastset"))); var os = result.GetProperty("operatingsystem"); var sp = result.GetProperty("operatingsystemservicepack"); if (sp != null) { os = $"{os} {sp}"; } wrapper.Properties.Add("operatingsystem", os); }
/// <summary> /// Wraps the NetWkstaUserEnum API call in a timeout /// </summary> /// <param name="computer"></param> /// <returns></returns> private static async Task <List <Session> > GetLoggedOnUsersAPI(Computer computer) { var resumeHandle = 0; var workstationInfoType = typeof(WKSTA_USER_INFO_1); var ptrInfo = IntPtr.Zero; var entriesRead = 0; var sessionList = new List <Session>(); try { var task = Task.Run(() => NetWkstaUserEnum(computer.APIName, 1, out ptrInfo, -1, out entriesRead, out _, ref resumeHandle)); if (await Task.WhenAny(task, Task.Delay(10000)) != task) { if (Options.Instance.DumpComputerStatus) { OutputTasks.AddComputerStatus(new ComputerStatus { ComputerName = computer.DisplayName, Status = "Timeout", Task = "NetWkstaUserEnum" }); } return(sessionList); } var taskResult = task.Result; //Check the result of the task. 234 and 0 are both acceptable. if (taskResult != 0 && taskResult != 234) { if (Options.Instance.DumpComputerStatus) { OutputTasks.AddComputerStatus(new ComputerStatus { ComputerName = computer.DisplayName, Status = ((NetApiStatus)taskResult).ToString(), Task = "NetWkstaUserEnum" }); } return(sessionList); } var iterator = ptrInfo; if (Options.Instance.DumpComputerStatus) { OutputTasks.AddComputerStatus(new ComputerStatus { ComputerName = computer.DisplayName, Status = "Success", Task = "NetWkstaUserEnum" }); } for (var i = 0; i < entriesRead; i++) { var data = (WKSTA_USER_INFO_1)Marshal.PtrToStructure(iterator, workstationInfoType); iterator = (IntPtr)(iterator.ToInt64() + Marshal.SizeOf(workstationInfoType)); var domain = data.wkui1_logon_domain; var username = data.wkui1_username; //Remove local accounts if (domain.Equals(computer.SamAccountName, StringComparison.CurrentCultureIgnoreCase)) { continue; } //Remove blank accounts and computer accounts if (username.Trim() == "" || username.EndsWith("$") || username == "ANONYMOUS LOGON" || username == Options.Instance.CurrentUserName) { continue; } //Any domain with a space is unusable (ex: NT AUTHORITY, FONT DRIVER HOST) if (domain.Contains(" ")) { continue; } var(rSuccess, sid, _) = await ResolutionHelpers.ResolveAccountNameToSidAndType(username, domain); if (rSuccess) { sessionList.Add(new Session { UserId = sid, ComputerId = computer.ObjectIdentifier }); } else { sessionList.Add(new Session { UserId = $"{username}@{Helpers.NormalizeDomainName(domain)}".ToUpper(), ComputerId = computer.ObjectIdentifier }); } } return(sessionList); } finally { if (ptrInfo != IntPtr.Zero) { NetApiBufferFree(ptrInfo); } } }
/// <summary> /// Processes the ACL for an object /// </summary> /// <param name="wrapper"></param> /// <returns></returns> private static async Task <List <ACL> > ProcessDACL(LdapWrapper wrapper) { var aces = new List <ACL>(); //Grab the ntsecuritydescriptor attribute as bytes var ntSecurityDescriptor = wrapper.SearchResult.GetPropertyAsBytes("ntsecuritydescriptor"); //If the NTSecurityDescriptor is null, something screwy is happening. Nothing to process here, so continue in the pipeline if (ntSecurityDescriptor == null) { return(aces); } //Create a new ActiveDirectorySecurity object and set the bytes in to this value var descriptor = new ActiveDirectorySecurity(); descriptor.SetSecurityDescriptorBinaryForm(ntSecurityDescriptor); //Pre-process the sid of the object owner var ownerSid = FilterAceSids(descriptor.GetOwner(typeof(SecurityIdentifier)).Value); if (ownerSid != null) { //Resolve the owner's SID to its corresponding type var(finalSid, type) = await ResolutionHelpers.ResolveSidAndGetType(ownerSid, wrapper.Domain); //If resolution worked, store the Owner ACE into our final result if (finalSid != null) { aces.Add(new ACL { PrincipalSID = finalSid, RightName = "Owner", AceType = "", PrincipalType = type, IsInherited = false }); } } foreach (ActiveDirectoryAccessRule ace in descriptor.GetAccessRules(true, true, typeof(SecurityIdentifier))) { //Ignore Null Aces if (ace == null) { continue; } //Ignore deny aces if (ace.AccessControlType == AccessControlType.Deny) { continue; } //Check if the ACE actually applies to our object based on the object type if (!IsAceInherited(ace, BaseGuids[wrapper.GetType()])) { continue; } //Grab the sid of the principal on this ACE var principalSid = FilterAceSids(ace.IdentityReference.Value); if (principalSid == null) { continue; } //Resolve the principal's SID to its type var(finalSid, type) = await ResolutionHelpers.ResolveSidAndGetType(principalSid, wrapper.Domain); if (finalSid == null) { continue; } //Start processing the rights in this ACE var rights = ace.ActiveDirectoryRights; var objectAceType = ace.ObjectType.ToString(); var isInherited = ace.IsInherited; //GenericAll is applicable to everything if (rights.HasFlag(ActiveDirectoryRights.GenericAll)) { if (objectAceType == AllGuid || objectAceType == "") { aces.Add(new ACL { PrincipalSID = finalSid, RightName = "GenericAll", AceType = "", PrincipalType = type, IsInherited = isInherited }); } //GenericAll includes every other right, and we dont want to duplicate. So continue in the loop continue; } //WriteDacl and WriteOwner are always useful to us regardless of object type if (rights.HasFlag(ActiveDirectoryRights.WriteDacl)) { aces.Add(new ACL { PrincipalSID = finalSid, AceType = "", RightName = "WriteDacl", PrincipalType = type, IsInherited = isInherited }); } if (rights.HasFlag(ActiveDirectoryRights.WriteOwner)) { aces.Add(new ACL { RightName = "WriteOwner", AceType = "", PrincipalSID = finalSid, PrincipalType = type, IsInherited = isInherited }); } //Process object specific ACEs //Extended rights apply to Users, Domains, Computers if (rights.HasFlag(ActiveDirectoryRights.ExtendedRight)) { if (wrapper is Domain) { switch (objectAceType) { case "1131f6aa-9c07-11d1-f79f-00c04fc2dcd2": aces.Add(new ACL { AceType = "GetChanges", RightName = "ExtendedRight", PrincipalSID = finalSid, PrincipalType = type, IsInherited = isInherited }); break; case "1131f6ad-9c07-11d1-f79f-00c04fc2dcd2": aces.Add(new ACL { AceType = "GetChangesAll", RightName = "ExtendedRight", PrincipalSID = finalSid, PrincipalType = type, IsInherited = isInherited }); break; case AllGuid: case "": aces.Add(new ACL { AceType = "All", RightName = "ExtendedRight", PrincipalSID = finalSid, PrincipalType = type, IsInherited = isInherited }); break; } } else if (wrapper is User) { switch (objectAceType) { case "00299570-246d-11d0-a768-00aa006e0529": aces.Add(new ACL { AceType = "User-Force-Change-Password", PrincipalSID = finalSid, RightName = "ExtendedRight", PrincipalType = type, IsInherited = isInherited }); break; case AllGuid: case "": aces.Add(new ACL { AceType = "All", PrincipalSID = finalSid, RightName = "ExtendedRight", PrincipalType = type, IsInherited = isInherited }); break; } } else if (wrapper is Computer) { //Computer extended rights are important when the computer has LAPS Helpers.GetDirectorySearcher(wrapper.Domain).GetAttributeFromGuid(objectAceType, out var mappedGuid); if (wrapper.SearchResult.GetProperty("ms-mcs-admpwdexpirationtime") != null) { if (objectAceType == AllGuid || objectAceType == "") { aces.Add(new ACL { AceType = "All", RightName = "ExtendedRight", PrincipalSID = finalSid, PrincipalType = type, IsInherited = isInherited }); } else if (mappedGuid != null && mappedGuid == "ms-Mcs-AdmPwd") { aces.Add(new ACL { AceType = "", RightName = "ReadLAPSPassword", PrincipalSID = finalSid, PrincipalType = type, IsInherited = isInherited }); } } } } //PropertyWrites apply to Groups, User, Computer, GPO //GenericWrite encapsulates WriteProperty, so we need to check them at the same time to avoid duplicate edges if (rights.HasFlag(ActiveDirectoryRights.GenericWrite) || rights.HasFlag(ActiveDirectoryRights.WriteProperty)) { if (wrapper is User || wrapper is Group || wrapper is Computer || wrapper is GPO) { if (objectAceType == AllGuid || objectAceType == "") { aces.Add(new ACL { AceType = "", RightName = "GenericWrite", PrincipalSID = finalSid, PrincipalType = type, IsInherited = isInherited }); } } if (wrapper is User) { if (objectAceType == "f3a64788-5306-11d1-a9c5-0000f80367c1") { aces.Add(new ACL { AceType = "WriteSPN", RightName = "WriteProperty", PrincipalSID = finalSid, PrincipalType = type, IsInherited = isInherited }); } } else if (wrapper is Group) { if (objectAceType == "bf9679c0-0de6-11d0-a285-00aa003049e2") { aces.Add(new ACL { AceType = "AddMember", RightName = "WriteProperty", PrincipalSID = finalSid, PrincipalType = type, IsInherited = isInherited }); } } else if (wrapper is Computer) { if (objectAceType == "3f78c3e5-f79a-46bd-a0b8-9d18116ddc79") { aces.Add(new ACL { AceType = "AllowedToAct", RightName = "WriteProperty", PrincipalSID = finalSid, PrincipalType = type, IsInherited = isInherited }); } } } } return(aces); }
/// <summary> /// Gets the members of a group /// </summary> /// <param name="group"></param> /// <returns></returns> private static async Task GetGroupMembership(Group group) { var finalMembers = new List <GenericMember>(); var searchResult = group.SearchResult; AppCache.Add(group.DistinguishedName, new ResolvedPrincipal { ObjectIdentifier = group.ObjectIdentifier, ObjectType = LdapTypeEnum.Group }); var groupMembers = searchResult.GetPropertyAsArray("member"); //If we get 0 back for member length, its either a ranged retrieval issue, or its an empty group. if (groupMembers.Length == 0) { Timer timer = null; var count = 0; //Lets try ranged retrieval here var searcher = Helpers.GetDirectorySearcher(group.Domain); var range = await searcher.RangedRetrievalAsync(group.DistinguishedName, "member"); //If we get null back, then something went wrong. if (range == null) { group.Members = finalMembers.ToArray(); return; } if (range.Count > 1000 && Options.Instance.Verbose) { timer = new Timer(30000); timer.Elapsed += (sender, args) => { Console.WriteLine($"Group Enumeration - {group.DisplayName} {count} / {range.Count}"); }; timer.AutoReset = true; timer.Start(); } foreach (var groupMemberDistinguishedName in range) { var(sid, type) = await ResolutionHelpers.ResolveDistinguishedName(groupMemberDistinguishedName); if (sid == null) { sid = groupMemberDistinguishedName; } finalMembers.Add(new GenericMember { MemberId = sid, MemberType = type }); count++; } timer?.Stop(); timer?.Dispose(); } else { //We got our members back foreach (var groupMemberDistinguishedName in groupMembers) { //Resolve DistinguishedNames to SIDS var(sid, type) = await ResolutionHelpers.ResolveDistinguishedName(groupMemberDistinguishedName); if (sid == null) { sid = groupMemberDistinguishedName; } finalMembers.Add(new GenericMember { MemberId = sid, MemberType = type }); } } group.Members = finalMembers.Distinct().ToArray(); }
/// <summary> /// Wraps the NetSessionEnum API call with a timeout and parses the results /// </summary> /// <param name="computer"></param> /// <returns></returns> private static async Task <List <Session> > GetNetSessions(Computer computer) { var resumeHandle = IntPtr.Zero; var sessionInfoType = typeof(SESSION_INFO_10); var entriesRead = 0; var ptrInfo = IntPtr.Zero; var sessionList = new List <Session>(); try { var task = Task.Run(() => NetSessionEnum(computer.APIName, null, null, 10, out ptrInfo, -1, out entriesRead, out _, ref resumeHandle)); //10 second timeout if (await Task.WhenAny(task, Task.Delay(10000)) != task) { if (Options.Instance.DumpComputerStatus) { OutputTasks.AddComputerStatus(new ComputerStatus { ComputerName = computer.DisplayName, Status = "Timeout", Task = "NetSessionEnum" }); } return(sessionList); } var taskResult = task.Result; if (taskResult != 0) { if (Options.Instance.DumpComputerStatus) { OutputTasks.AddComputerStatus(new ComputerStatus { ComputerName = computer.DisplayName, Status = ((NetApiStatus)taskResult).ToString(), Task = "NetSessionEnum" }); } return(sessionList); } var sessions = new SESSION_INFO_10[entriesRead]; var iterator = ptrInfo; for (var i = 0; i < entriesRead; i++) { sessions[i] = (SESSION_INFO_10)Marshal.PtrToStructure(iterator, sessionInfoType); iterator = (IntPtr)(iterator.ToInt64() + Marshal.SizeOf(sessionInfoType)); } if (Options.Instance.DumpComputerStatus) { OutputTasks.AddComputerStatus(new ComputerStatus { ComputerName = computer.DisplayName, Status = "Success", Task = "NetSessionEnum" }); } foreach (var session in sessions) { var sessionUsername = session.sesi10_username; var computerName = session.sesi10_cname; if (computerName == null) { continue; } string computerSid = null; //Filter out computer accounts, Anonymous Logon, empty users if (sessionUsername.EndsWith( "$") || sessionUsername.Trim() == "" || sessionUsername == "$" || sessionUsername == Options.Instance.CurrentUserName || sessionUsername == "ANONYMOUS LOGON") { continue; } //Remove leading backslashes if (computerName.StartsWith("\\")) { computerName = computerName.TrimStart('\\'); } //Remove empty sessions if (string.IsNullOrEmpty(computerName)) { continue; } //If the session is pointing to localhost, we already know what the SID of the computer is if (computerName.Equals("[::1]") || computerName.Equals("127.0.0.1")) { computerSid = computer.ObjectIdentifier; } //Try converting the computer name to a SID if we didn't already get it from a localhost computerSid = computerSid ?? await ResolutionHelpers.ResolveHostToSid(computerName, computer.Domain); //Try converting the username to a SID var searcher = Helpers.GetDirectorySearcher(computer.Domain); var sids = await searcher.LookupUserInGC(sessionUsername); if (sids?.Length > 0) { foreach (var sid in sids) { sessionList.Add(new Session { ComputerId = computerSid, UserId = sid }); } } else { var(success, sid, _) = await ResolutionHelpers.ResolveAccountNameToSidAndType(sessionUsername, computer.Domain); if (success) { sessionList.Add(new Session { ComputerId = computerSid, UserId = sid }); } else { sessionList.Add(new Session { ComputerId = computerSid, UserId = sessionUsername }); } } } return(sessionList); } finally { if (ptrInfo != IntPtr.Zero) { NetApiBufferFree(ptrInfo); } } }