From ee7d009e4b0647b8aab9e5384820f56779e3a07a Mon Sep 17 00:00:00 2001 From: rvazarkar Date: Fri, 7 Jun 2024 13:39:33 -0400 Subject: [PATCH 01/68] wip: wip --- src/CommonLib/Enums/LdapErrorCodes.cs | 1 + src/CommonLib/Enums/LdapFailureReason.cs | 12 + src/CommonLib/Enums/NamingContexts.cs | 8 + .../Exceptions/LdapAuthenticationException.cs | 2 + .../Exceptions/NoLdapDataException.cs | 4 +- src/CommonLib/Extensions.cs | 41 +- src/CommonLib/LDAPProperties.cs | 2 + src/CommonLib/LDAPUtils.cs | 21 +- src/CommonLib/LDAPUtilsNew.cs | 592 ++++++++++++++++++ src/CommonLib/NativeMethods.cs | 4 +- .../NetAPINative/NetAPIMethods.cs | 6 +- 11 files changed, 674 insertions(+), 19 deletions(-) create mode 100644 src/CommonLib/Enums/LdapFailureReason.cs create mode 100644 src/CommonLib/Enums/NamingContexts.cs create mode 100644 src/CommonLib/LDAPUtilsNew.cs diff --git a/src/CommonLib/Enums/LdapErrorCodes.cs b/src/CommonLib/Enums/LdapErrorCodes.cs index ff81967c..becf71c4 100644 --- a/src/CommonLib/Enums/LdapErrorCodes.cs +++ b/src/CommonLib/Enums/LdapErrorCodes.cs @@ -3,6 +3,7 @@ public enum LdapErrorCodes : int { Success = 0, + InvalidCredentials = 49, Busy = 51, ServerDown = 81, LocalError = 82, diff --git a/src/CommonLib/Enums/LdapFailureReason.cs b/src/CommonLib/Enums/LdapFailureReason.cs new file mode 100644 index 00000000..4986f9d6 --- /dev/null +++ b/src/CommonLib/Enums/LdapFailureReason.cs @@ -0,0 +1,12 @@ +namespace SharpHoundCommonLib.Enums; + +public enum LdapFailureReason +{ + None, + NoData, + FailedBind, + FailedRequest, + FailedAuthentication, + AuthenticationException, + Unknown +} \ No newline at end of file diff --git a/src/CommonLib/Enums/NamingContexts.cs b/src/CommonLib/Enums/NamingContexts.cs new file mode 100644 index 00000000..6a585d03 --- /dev/null +++ b/src/CommonLib/Enums/NamingContexts.cs @@ -0,0 +1,8 @@ +namespace SharpHoundCommonLib.Enums; + +public enum NamingContexts +{ + Default, + Configuration, + Schema, +} \ No newline at end of file diff --git a/src/CommonLib/Exceptions/LdapAuthenticationException.cs b/src/CommonLib/Exceptions/LdapAuthenticationException.cs index e55413fb..a628d959 100644 --- a/src/CommonLib/Exceptions/LdapAuthenticationException.cs +++ b/src/CommonLib/Exceptions/LdapAuthenticationException.cs @@ -5,8 +5,10 @@ namespace SharpHoundCommonLib.Exceptions { public class LdapAuthenticationException : Exception { + public readonly LdapException LdapException; public LdapAuthenticationException(LdapException exception) : base("Error authenticating to LDAP", exception) { + LdapException = exception; } } } \ No newline at end of file diff --git a/src/CommonLib/Exceptions/NoLdapDataException.cs b/src/CommonLib/Exceptions/NoLdapDataException.cs index 860f1694..628a0b1b 100644 --- a/src/CommonLib/Exceptions/NoLdapDataException.cs +++ b/src/CommonLib/Exceptions/NoLdapDataException.cs @@ -4,10 +4,8 @@ namespace SharpHoundCommonLib.Exceptions { public class NoLdapDataException : Exception { - public int ErrorCode { get; set; } - public NoLdapDataException(int errorCode) + public NoLdapDataException() { - ErrorCode = errorCode; } } } \ No newline at end of file diff --git a/src/CommonLib/Extensions.cs b/src/CommonLib/Extensions.cs index e2c732e8..d85c0890 100644 --- a/src/CommonLib/Extensions.cs +++ b/src/CommonLib/Extensions.cs @@ -120,6 +120,42 @@ public static int Rid(this SecurityIdentifier securityIdentifier) return rid; } + public static bool GetNamingContextSearchBase(this LdapConnection connection, NamingContexts context, + out string searchBase) + { + var searchRequest = + new SearchRequest("", new LDAPFilter().AddAllObjects().GetFilter(), SearchScope.Base, null); + searchRequest.Controls.Add(new SearchOptionsControl(SearchOption.DomainScope)); + SearchResponse response; + try + { + response = (SearchResponse)connection.SendRequest(searchRequest); + } + catch + { + searchBase = ""; + return false; + } + + if (response?.Entries == null || response.Entries.Count == 0) + { + searchBase = ""; + return false; + } + + var entry = response.Entries[0]; + searchBase = context switch + { + NamingContexts.Default => entry.GetProperty(LDAPProperties.DefaultNamingContext), + NamingContexts.Configuration => entry.GetProperty(LDAPProperties.ConfigurationNamingContext), + NamingContexts.Schema => entry.GetProperty(LDAPProperties.SchemaNamingContext), + _ => throw new ArgumentOutOfRangeException(nameof(context), context, null) + }; + + searchBase = searchBase?.Trim().ToUpper(); + return searchBase != null; + } + #region SearchResultEntry /// @@ -392,7 +428,8 @@ public static Label GetLabel(this SearchResultEntry entry) objectType = Label.AIACA; else if (entry.DistinguishedName.Contains(DirectoryPaths.NTAuthStoreLocation)) objectType = Label.NTAuthStore; - }else if (objectClasses.Contains(OIDContainerClass, StringComparer.InvariantCultureIgnoreCase)) + } + else if (objectClasses.Contains(OIDContainerClass, StringComparer.InvariantCultureIgnoreCase)) { if (entry.DistinguishedName.StartsWith(DirectoryPaths.OIDContainerLocation, StringComparison.InvariantCultureIgnoreCase)) @@ -401,7 +438,7 @@ public static Label GetLabel(this SearchResultEntry entry) { if (entry.GetPropertyAsInt(LDAPProperties.Flags, out var flags) && flags == 2) { - objectType = Label.IssuancePolicy; + objectType = Label.IssuancePolicy; } } } diff --git a/src/CommonLib/LDAPProperties.cs b/src/CommonLib/LDAPProperties.cs index 77197baf..e02b2353 100644 --- a/src/CommonLib/LDAPProperties.cs +++ b/src/CommonLib/LDAPProperties.cs @@ -69,8 +69,10 @@ public static class LDAPProperties public const string CertificateTemplates = "certificatetemplates"; public const string CrossCertificatePair = "crosscertificatepair"; public const string Flags = "flags"; + public const string DefaultNamingContext = "defaultnamingcontext"; public const string RootDomainNamingContext = "rootdomainnamingcontext"; public const string ConfigurationNamingContext = "configurationnamingcontext"; + public const string SchemaNamingContext = "schemanamingcontext"; public const string NetbiosName = "netbiosName"; public const string DnsRoot = "dnsroot"; public const string ServerName = "servername"; diff --git a/src/CommonLib/LDAPUtils.cs b/src/CommonLib/LDAPUtils.cs index a97b3a1e..71cc5b97 100644 --- a/src/CommonLib/LDAPUtils.cs +++ b/src/CommonLib/LDAPUtils.cs @@ -97,7 +97,7 @@ public LDAPUtils(NativeMethods nativeMethods = null, PortScanner scanner = null, /// public void SetLDAPConfig(LDAPConfig config) { - _ldapConfig = config ?? throw new Exception("LDAP Configuration can not be null"); + _ldapConfig = config ?? throw new ArgumentNullException(nameof(config), "LDAP Configuration can not be null"); //Close out any existing LDAP connections to request a new incoming config foreach (var kv in _ldapConnections) { @@ -1517,6 +1517,7 @@ private async Task CreateLDAPConnectionWrapper(string dom //If a server has been manually specified, we should never get past this block for opsec reasons if (_ldapConfig.Server != null) { + _log.LogInformation("Server is set via config, attempting to create ldap connection to {Server}", _ldapConfig.Server); if (!skipCache) { if (GetCachedConnection(_ldapConfig.Server, globalCatalog, out var conn)) @@ -1526,11 +1527,14 @@ private async Task CreateLDAPConnectionWrapper(string dom } var singleServerConn = CreateLDAPConnection(_ldapConfig.Server, authType, globalCatalog); - if (singleServerConn == null) return new LdapConnectionWrapper() - { - Connection = null, - DomainInfo = null - }; + if (singleServerConn == null) { + return new LdapConnectionWrapper + { + Connection = null, + DomainInfo = null + }; + } + var cacheKey = new LDAPConnectionCacheKey(_ldapConfig.Server, globalCatalog); _ldapConnections.AddOrUpdate(cacheKey, singleServerConn, (_, ldapConnection) => { @@ -1546,6 +1550,7 @@ private async Task CreateLDAPConnectionWrapper(string dom //If our domain is STILL null, we're not going to get anything reliable, so exit out if (domain == null) { + _log.LogWarning("Initial domain name for new LDAP connection is null and/or unresolvable. Unable to create a new connection"); return new LdapConnectionWrapper { Connection = null, @@ -1560,6 +1565,8 @@ private async Task CreateLDAPConnectionWrapper(string dom return conn; } } + + _log.LogInformation("No cached LDAP connection found for {Domain}, attempting a new connection", domain); var connectionWrapper = CreateLDAPConnection(domain, authType, globalCatalog); //If our connection isn't null, it means we have a good connection @@ -1669,7 +1676,6 @@ private async Task CreateLDAPConnectionWithPortCheck(stri await _portScanner.CheckPort(target, _ldapConfig.GetGCPort(false)))) { return CreateLDAPConnection(target, authType, true); - } } else @@ -1682,7 +1688,6 @@ await _portScanner.CheckPort(target, _ldapConfig.GetGCPort(false)))) return null; } - private LdapConnectionWrapper CreateLDAPConnection(string target, AuthType authType, bool globalCatalog) { diff --git a/src/CommonLib/LDAPUtilsNew.cs b/src/CommonLib/LDAPUtilsNew.cs new file mode 100644 index 00000000..8f8ccb9f --- /dev/null +++ b/src/CommonLib/LDAPUtilsNew.cs @@ -0,0 +1,592 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.DirectoryServices; +using System.DirectoryServices.ActiveDirectory; +using System.DirectoryServices.Protocols; +using System.Net; +using System.Security.Principal; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using SharpHoundCommonLib.Enums; +using SharpHoundCommonLib.Exceptions; +using SharpHoundCommonLib.LDAPQueries; +using SharpHoundCommonLib.Processors; +using SharpHoundRPC.NetAPINative; +using SearchScope = System.DirectoryServices.Protocols.SearchScope; + +namespace SharpHoundCommonLib; + +public class LDAPUtilsNew +{ + private readonly string _nullCacheKey = Guid.NewGuid().ToString(); + private LDAPConfig _ldapConfig = new(); + private readonly ILogger _log; + //This cache is indexed by domain sid + private readonly ConcurrentDictionary _ldapConnectionCache = new(); + private readonly ConcurrentDictionary _domainCache = new(); + private readonly string[] _translateNames = { "Administrator", "admin" }; + private readonly PortScanner _portScanner; + private readonly NativeMethods _nativeMethods; + + public async IAsyncEnumerable PagedQuery(string ldapFilter) + { + + } + + private async Task<(bool, LdapConnection)> GetLdapConnection(string domainName, AuthType authType = AuthType.Negotiate, bool globalCatalog = false, + bool forceCreateNewConnection = false) + { + //TODO: Pull out individual strategies into single functions for readability and better logging + if (string.IsNullOrWhiteSpace(domainName)) + { + throw new ArgumentNullException(nameof(domainName)); + } + + LdapConnection connection; + + try + { + /* + * If a server is explicitly set on the config, we should only test this config + */ + if (_ldapConfig.Server != null) + { + _log.LogWarning("Server is overridden via config, creating connection to {Server}", _ldapConfig.Server); + if (!forceCreateNewConnection && GetCachedConnection(domainName, globalCatalog, out connection)) + { + return (true, connection); + } + + if (CreateLdapConnection(_ldapConfig.Server, authType, globalCatalog, out var serverConnection)) + { + connection = CheckCacheConnection(serverConnection, domainName, globalCatalog, + forceCreateNewConnection); + return (true, connection); + } + + return (false, null); + } + + if (!forceCreateNewConnection && GetCachedConnection(domainName, globalCatalog, out connection)) + { + return (true, connection); + } + + _log.LogInformation("No cached connection found for domain {Domain}, attempting a new connection", + domainName); + + if (CreateLdapConnection(domainName.ToUpper().Trim(), authType, globalCatalog, out connection)) + { + _log.LogDebug("Successfully created ldap connection for domain: {Domain} using strategy 1", domainName); + connection = CheckCacheConnection(connection, domainName, globalCatalog, forceCreateNewConnection); + return (true, connection); + } + + string tempDomainName; + + var dsGetDcNameResult = _nativeMethods.CallDsGetDcName(null, domainName, (uint)(NetAPIEnums.DSGETDCNAME_FLAGS.DS_FORCE_REDISCOVERY | NetAPIEnums.DSGETDCNAME_FLAGS.DS_RETURN_DNS_NAME | NetAPIEnums.DSGETDCNAME_FLAGS.DS_DIRECTORY_SERVICE_REQUIRED)); + if (dsGetDcNameResult.IsSuccess) + { + tempDomainName = dsGetDcNameResult.Value.DomainName; + if (!forceCreateNewConnection && GetCachedConnection(tempDomainName, globalCatalog, out connection)) + { + return (true, connection); + } + + if (!tempDomainName.Equals(domainName, StringComparison.OrdinalIgnoreCase) && CreateLdapConnection(tempDomainName, authType, globalCatalog, out connection)) + { + _log.LogDebug("Successfully created ldap connection for domain: {Domain} using strategy 2 with name {NewName}", domainName, tempDomainName); + connection = CheckCacheConnection(connection, tempDomainName, globalCatalog, forceCreateNewConnection); + return (true, connection); + } + + var server = dsGetDcNameResult.Value.DomainControllerName.TrimStart('\\'); + + var result = + await CreateLDAPConnectionWithPortCheck(server, authType, globalCatalog); + if (result.success) + { + _log.LogDebug("Successfully created ldap connection for domain: {Domain} using strategy 3 to server {Server}", domainName,server); + connection = CheckCacheConnection(result.connection, tempDomainName, globalCatalog, + forceCreateNewConnection); + return (true, connection); + } + } + + if (!GetDomain(domainName, out var domainObject) || domainObject.Name == null) + { + //If we don't get a result here, we effectively have no other ways to resolve this domain, so we'll just have to exit out + _log.LogDebug("Could not get domain object from GetDomain, unable to create ldap connection for domain {Domain}", domainName); + return (false, null); + } + + tempDomainName = domainObject.Name.ToUpper().Trim(); + if (!forceCreateNewConnection && GetCachedConnection(tempDomainName, globalCatalog, out connection)) + { + return (true, connection); + } + + if (!tempDomainName.Equals(domainName, StringComparison.OrdinalIgnoreCase) && CreateLdapConnection(tempDomainName, authType, globalCatalog, out connection)) + { + _log.LogDebug("Successfully created ldap connection for domain: {Domain} using strategy 4 with name {NewName}", domainName, tempDomainName); + connection = CheckCacheConnection(connection, tempDomainName, globalCatalog, forceCreateNewConnection); + return (true, connection); + } + + var primaryDomainController = domainObject.PdcRoleOwner.Name; + var portConnectionResult = + await CreateLDAPConnectionWithPortCheck(primaryDomainController, authType, globalCatalog); + if (portConnectionResult.success) + { + _log.LogDebug("Successfully created ldap connection for domain: {Domain} using strategy 5 with to pdc {Server}", domainName, primaryDomainController); + connection = CheckCacheConnection(portConnectionResult.connection, tempDomainName, globalCatalog, + forceCreateNewConnection); + return (true, connection); + } + + //Loop over all other domain controllers and see if we can make a good connection to any + foreach (DomainController dc in domainObject.DomainControllers) + { + portConnectionResult = + await CreateLDAPConnectionWithPortCheck(primaryDomainController, authType, globalCatalog); + if (portConnectionResult.success) + { + _log.LogDebug("Successfully created ldap connection for domain: {Domain} using strategy 6 with to pdc {Server}", domainName, primaryDomainController); + connection = CheckCacheConnection(portConnectionResult.connection, tempDomainName, globalCatalog, + forceCreateNewConnection); + return (true, connection); + } + } + + _log.LogWarning("Exhausted all potential methods of creating ldap connection to {DomainName}", domainName); + return (false, null); + } + catch (LdapAuthenticationException e) + { + _log.LogError("Error connecting to {Domain}: credentials are invalid (error code {ErrorCode})", domainName, + e.LdapException.ErrorCode); + return (false, null); + } + catch (NoLdapDataException) + { + _log.LogError("No data returned for domain {Domain} during initial LDAP test.", domainName); + return (false, null); + } + } + + private async Task<(bool success, LdapConnection connection)> CreateLDAPConnectionWithPortCheck(string target, AuthType authType, bool globalCatalog) + { + if (globalCatalog) + { + if (await _portScanner.CheckPort(target, _ldapConfig.GetGCPort(true)) || (!_ldapConfig.ForceSSL && + await _portScanner.CheckPort(target, _ldapConfig.GetGCPort(false)))) + { + return (CreateLdapConnection(target, authType, true, out var connection), connection); + } + } + else + { + if (await _portScanner.CheckPort(target, _ldapConfig.GetPort(true)) || (!_ldapConfig.ForceSSL && await _portScanner.CheckPort(target, _ldapConfig.GetPort(false)))) + { + return (CreateLdapConnection(target, authType, true, out var connection), connection); + } + } + + return (false, null); + } + + private LdapConnection CheckCacheConnection(LdapConnection connection, string domainName, bool globalCatalog, bool forceCreateNewConnection) + { + LDAPConnectionCacheKey cacheKey; + if (_ldapConfig.Server != null) + { + cacheKey = new LDAPConnectionCacheKey(_ldapConfig.Server, globalCatalog); + } + else + { + if (!GetDomainSidFromDomainName(domainName, out var cacheIdentifier)) + { + //This is kinda gross, but its another way to get the correct domain sid + if (!connection.GetNamingContextSearchBase(NamingContexts.Default, out var searchBase) || !GetDomainSidFromConnection(connection, searchBase, out cacheIdentifier)) + { + /* + * If we get here, we couldn't resolve a domain sid, which is hella bad, but we also want to keep from creating a shitton of new connections + * Cache using the domainname and pray it all works out + */ + cacheIdentifier = domainName.ToUpper().Trim(); + } + } + + cacheKey = new LDAPConnectionCacheKey(cacheIdentifier, globalCatalog); + } + + if (forceCreateNewConnection) + { + return _ldapConnectionCache.AddOrUpdate(cacheKey, connection, (_, existingConnection) => + { + existingConnection.Dispose(); + return connection; + }); + } + + return _ldapConnectionCache.AddOrUpdate(cacheKey, connection, (_, existingConnection) => + { + connection.Dispose(); + return existingConnection; + }); + } + + private bool GetCachedConnection(string domain, bool globalCatalog, out LdapConnection connection) + { + LDAPConnectionCacheKey cacheKey; + //If server is set via our config, we'll always just use this as the cache key + if (_ldapConfig.Server != null) + { + cacheKey = new LDAPConnectionCacheKey(_ldapConfig.Server, globalCatalog); + return _ldapConnectionCache.TryGetValue(cacheKey, out connection); + } + + if (GetDomainSidFromDomainName(domain, out var domainSid)) + { + cacheKey = new LDAPConnectionCacheKey(domainSid, globalCatalog); + if (_ldapConnectionCache.TryGetValue(cacheKey, out connection)) + { + return true; + } + } + + cacheKey = new LDAPConnectionCacheKey(domain.ToUpper().Trim(), globalCatalog); + return _ldapConnectionCache.TryGetValue(cacheKey, out connection); + } + + + private bool GetDomainSidFromConnection(LdapConnection connection, string searchBase, out string domainSid) + { + try + { + //This ldap filter searches for domain controllers + //Searches for any accounts with a UAC value inclusive of 8192 bitwise + //8192 is the flag for SERVER_TRUST_ACCOUNT, which is set only on Domain Controllers + var searchRequest = new SearchRequest(searchBase, + "(&(objectCategory=computer)(userAccountControl:1.2.840.113556.1.4.803:=8192))", + SearchScope.Subtree, new[] { "objectsid"}); + + var response = (SearchResponse)connection.SendRequest(searchRequest); + if (response == null || response.Entries.Count == 0) + { + domainSid = ""; + return false; + } + + var entry = response.Entries[0]; + var sid = entry.GetSid(); + domainSid = sid.Substring(0, sid.LastIndexOf('-')).ToUpper(); + return true; + } + catch (LdapException) + { + _log.LogWarning("Failed grabbing domainsid from ldap for {domain}", searchBase); + domainSid = ""; + return false; + } + } + + private bool CreateLdapConnection(string target, AuthType authType, bool globalCatalog, out LdapConnection connection) + { + var baseConnection = CreateBaseConnection(target, true, authType, globalCatalog); + if (TestLdapConnection(baseConnection, target)) + { + connection = baseConnection; + return true; + } + + try + { + baseConnection.Dispose(); + } + catch + { + //this is just in case + } + + if (_ldapConfig.ForceSSL) + { + connection = null; + return false; + } + + baseConnection = CreateBaseConnection(target, false, authType, globalCatalog); + if (TestLdapConnection(baseConnection, target)) + { + connection = baseConnection; + return true; + } + + try + { + baseConnection.Dispose(); + } + catch + { + //this is just in case + } + + connection = null; + return false; + } + + private LdapConnection CreateBaseConnection(string directoryIdentifier, bool ssl, AuthType authType, + bool globalCatalog) + { + var port = globalCatalog ? _ldapConfig.GetGCPort(ssl) : _ldapConfig.GetPort(ssl); + var identifier = new LdapDirectoryIdentifier(directoryIdentifier, port, false, false); + var connection = new LdapConnection(identifier) { Timeout = new TimeSpan(0, 0, 5, 0) }; + + //These options are important! + connection.SessionOptions.ProtocolVersion = 3; + //Referral chasing does not work with paged searches + connection.SessionOptions.ReferralChasing = ReferralChasingOptions.None; + if (ssl) + { + connection.SessionOptions.SecureSocketLayer = true; + } + + if (_ldapConfig.DisableSigning) + { + connection.SessionOptions.Sealing = false; + connection.SessionOptions.Signing = false; + } + + if (_ldapConfig.DisableCertVerification) + connection.SessionOptions.VerifyServerCertificate = (_, _) => true; + + if (_ldapConfig.Username != null) + { + var cred = new NetworkCredential(_ldapConfig.Username, _ldapConfig.Password); + connection.Credential = cred; + } + + connection.AuthType = authType; + + return connection; + } + + private struct LdapFailure + { + public LdapFailureReason FailureReason { get; set; } + public string Message { get; set; } + } + + /// + /// Tests whether an LDAP connection is working + /// + /// + /// + /// + /// Something is wrong with the supplied credentials + /// A connection "succeeded" but no data was returned. This can be related to kerberos auth across trusts or just simply lack of permissions + private bool TestLdapConnection(LdapConnection connection, string identifier) + { + try + { + //Attempt an initial bind. If this fails, likely auth is invalid, or its not a valid target + connection.Bind(); + } + catch (LdapException e) + { + //TODO: Maybe look at this and find a better way? + if (e.ErrorCode is (int)LdapErrorCodes.InvalidCredentials or (int)ResultCode.InappropriateAuthentication) + { + connection.Dispose(); + throw new LdapAuthenticationException(e); + } + return false; + } + catch (Exception e) + { + return false; + } + + SearchResponse response; + try + { + //Do an initial search request to get the rootDSE + //This ldap filter is equivalent to (objectclass=*) + var searchRequest = CreateSearchRequest("", new LDAPFilter().AddAllObjects().GetFilter(), + SearchScope.Base, null); + + response = (SearchResponse)connection.SendRequest(searchRequest); + } + catch (LdapException e) + { + /* + * If we can't send the initial search request, its unlikely any other search requests will work so we will immediately return false + */ + _log.LogDebug(e, "TestLdapConnection failed during search request against target {Target}", identifier); + return false; + } + + if (response?.Entries == null || response.Entries.Count == 0) + { + /* + * This can happen for one of two reasons, either we dont have permission to query AD or we're authenticating + * across external trusts with kerberos authentication without Forest Search Order properly configured. + * Either way, this connection isn't useful for us because we're not going to get data, so return false + */ + + _log.LogDebug("TestLdapConnection failed to return results against target {Target}", identifier); + connection.Dispose(); + throw new NoLdapDataException(); + } + + return true; + } + + private SearchRequest CreateSearchRequest(string distinguishedName, string ldapFilter, SearchScope searchScope, + string[] attributes) + { + var searchRequest = new SearchRequest(distinguishedName, ldapFilter, + searchScope, attributes); + searchRequest.Controls.Add(new SearchOptionsControl(SearchOption.DomainScope)); + return searchRequest; + } + + public bool GetDomainSidFromDomainName(string domainName, out string domainSid) + { + if (Cache.GetDomainSidMapping(domainName, out domainSid)) + { + return true; + } + + try + { + var entry = new DirectoryEntry($"LDAP://{domainName}"); + //Force load objectsid into the object cache + entry.RefreshCache(new[] {"objectSid"}); + var sid = entry.GetSid(); + if (sid != null) + { + Cache.AddDomainSidMapping(domainName, sid); + domainSid = sid; + return true; + } + } + catch + { + //we expect this to fail sometimes + } + + if (GetDomain(domainName, out var domainObject)) + { + try + { + domainSid = domainObject.GetDirectoryEntry().GetSid(); + if (domainSid != null) + { + Cache.AddDomainSidMapping(domainName, domainSid); + return true; + } + } + catch + { + //we expect this to fail sometimes (not sure why, but better safe than sorry) + } + } + + foreach (var name in _translateNames) + { + try + { + var account = new NTAccount(domainName, name); + var sid = (SecurityIdentifier)account.Translate(typeof(SecurityIdentifier)); + domainSid = sid.AccountDomainSid.ToString(); + Cache.AddDomainSidMapping(domainName, domainSid); + return true; + } + catch + { + //We expect this to fail if the username doesn't exist in the domain + } + } + + return false; + } + + private string ResolveDomainCrossRef(string domainName) + { + + } + + /// + /// Attempts to get the Domain object representing the target domain. If null is specified for the domain name, gets the user's current domain + /// + /// + /// + /// + public bool GetDomain(string domainName, out Domain domain) + { + var cacheKey = domainName ?? _nullCacheKey; + if (_domainCache.TryGetValue(cacheKey, out domain)) return true; + + try + { + DirectoryContext context; + if (_ldapConfig.Username != null) + { + context = domainName != null + ? new DirectoryContext(DirectoryContextType.Domain, domainName, _ldapConfig.Username, + _ldapConfig.Password) + : new DirectoryContext(DirectoryContextType.Domain, _ldapConfig.Username, + _ldapConfig.Password); + } + else + { + context = domainName != null + ? new DirectoryContext(DirectoryContextType.Domain, domainName) + : new DirectoryContext(DirectoryContextType.Domain); + } + + domain = Domain.GetDomain(context); + if (domain == null) return false; + _domainCache.TryAdd(cacheKey, domain); + return true; + + } + catch (Exception e) + { + _log.LogDebug(e, "GetDomain call failed for domain name {Name}", domainName); + return false; + } + } + + /// + /// Attempts to get the Domain object representing the target domain. If null is specified for the domain name, gets the user's current domain + /// + /// + /// + /// + public bool GetDomain(out Domain domain) + { + var cacheKey = _nullCacheKey; + if (_domainCache.TryGetValue(cacheKey, out domain)) return true; + + try + { + var context= _ldapConfig.Username != null + ? new DirectoryContext(DirectoryContextType.Domain, _ldapConfig.Username, + _ldapConfig.Password) + : new DirectoryContext(DirectoryContextType.Domain); + + domain = Domain.GetDomain(context); + _domainCache.TryAdd(cacheKey, domain); + return true; + } + catch (Exception e) + { + _log.LogDebug(e, "GetDomain call failed for blank domain"); + return false; + } + } + +} \ No newline at end of file diff --git a/src/CommonLib/NativeMethods.cs b/src/CommonLib/NativeMethods.cs index fb0c4c69..d560f45d 100644 --- a/src/CommonLib/NativeMethods.cs +++ b/src/CommonLib/NativeMethods.cs @@ -32,9 +32,9 @@ public virtual NetAPIResult> NetWkstaUserEn } public virtual NetAPIResult CallDsGetDcName(string computerName, - string domainName) + string domainName, uint flags) { - return NetAPIMethods.DsGetDcName(computerName, domainName); + return NetAPIMethods.DsGetDcName(computerName, domainName, flags); } } } \ No newline at end of file diff --git a/src/SharpHoundRPC/NetAPINative/NetAPIMethods.cs b/src/SharpHoundRPC/NetAPINative/NetAPIMethods.cs index a2dafc8f..0a6c5093 100644 --- a/src/SharpHoundRPC/NetAPINative/NetAPIMethods.cs +++ b/src/SharpHoundRPC/NetAPINative/NetAPIMethods.cs @@ -82,11 +82,9 @@ private static extern NetAPIEnums.NetAPIStatus NetWkstaGetInfo( out NetAPIPointer bufPtr); public static NetAPIResult DsGetDcName(string computerName, - string domainName) + string domainName, uint flags) { - var result = DsGetDcName(computerName, domainName, null, null, - (uint) (NetAPIEnums.DSGETDCNAME_FLAGS.DS_IS_FLAT_NAME | - NetAPIEnums.DSGETDCNAME_FLAGS.DS_RETURN_DNS_NAME), out var buffer); + var result = DsGetDcName(computerName, domainName, null, null, flags, out var buffer); if (result != NetAPIEnums.NetAPIStatus.Success) return result; return buffer.GetData(); From 2b2ef57ea93a99a41c8a8d934d6b39e4f2a2501a Mon Sep 17 00:00:00 2001 From: Alex Nemeth <80649445+definitelynotagoblin@users.noreply.github.com> Date: Mon, 10 Jun 2024 13:11:09 -0700 Subject: [PATCH 02/68] DC Connection Cache Breakout (#129) * Breaking dc connection cache out of utils * Normalize cache key * Correct domainSid cache key in utils * Remove unused property Server from LdapConnectionCacheKey --- src/CommonLib/DCConnectionCache.cs | 79 +++++++++++++++++++++++++ src/CommonLib/LDAPConnectionCacheKey.cs | 36 ----------- src/CommonLib/LDAPUtilsNew.cs | 34 ++++------- 3 files changed, 89 insertions(+), 60 deletions(-) create mode 100644 src/CommonLib/DCConnectionCache.cs delete mode 100644 src/CommonLib/LDAPConnectionCacheKey.cs diff --git a/src/CommonLib/DCConnectionCache.cs b/src/CommonLib/DCConnectionCache.cs new file mode 100644 index 00000000..b7efe26e --- /dev/null +++ b/src/CommonLib/DCConnectionCache.cs @@ -0,0 +1,79 @@ +using System.Collections.Concurrent; +using System.DirectoryServices.Protocols; + +namespace SharpHoundCommonLib +{ + public class DCConnectionCache + { + private readonly ConcurrentDictionary _ldapConnectionCache; + + public DCConnectionCache() + { + _ldapConnectionCache = new ConcurrentDictionary(); + } + + public bool TryGet(string key, bool isGlobalCatalog, out LdapConnection connection) + { + var cacheKey = GetKey(key, isGlobalCatalog); + return _ldapConnectionCache.TryGetValue(cacheKey, out connection); + } + + public LdapConnection AddOrUpdate(string key, bool isGlobalCatalog, LdapConnection connection) + { + var cacheKey = GetKey(key, isGlobalCatalog); + return _ldapConnectionCache.AddOrUpdate(cacheKey, connection, (_, existingConnection) => + { + existingConnection.Dispose(); + return connection; + }); + } + + public LdapConnection TryAdd(string key, bool isGlobalCatalog, LdapConnection connection) + { + var cacheKey = GetKey(key, isGlobalCatalog); + return _ldapConnectionCache.AddOrUpdate(cacheKey, connection, (_, existingConnection) => + { + connection.Dispose(); + return existingConnection; + }); + } + + private LDAPConnectionCacheKey GetKey(string key, bool isGlobalCatalog) + { + return new LDAPConnectionCacheKey(key.ToUpper().Trim(), isGlobalCatalog); + } + + private class LDAPConnectionCacheKey + { + public string Domain { get; } + public bool GlobalCatalog { get; } + + public LDAPConnectionCacheKey(string domain, bool globalCatalog) + { + GlobalCatalog = globalCatalog; + Domain = domain; + } + + protected bool Equals(LDAPConnectionCacheKey other) + { + return GlobalCatalog == other.GlobalCatalog && Domain == other.Domain; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((LDAPConnectionCacheKey)obj); + } + + public override int GetHashCode() + { + unchecked + { + return (GlobalCatalog.GetHashCode() * 397) ^ (Domain != null ? Domain.GetHashCode() : 0); + } + } + } + } +} \ No newline at end of file diff --git a/src/CommonLib/LDAPConnectionCacheKey.cs b/src/CommonLib/LDAPConnectionCacheKey.cs deleted file mode 100644 index f1f76464..00000000 --- a/src/CommonLib/LDAPConnectionCacheKey.cs +++ /dev/null @@ -1,36 +0,0 @@ -namespace SharpHoundCommonLib -{ - public class LDAPConnectionCacheKey - { - public bool GlobalCatalog { get; } - public string Domain { get; } - public string Server { get; set; } - - public LDAPConnectionCacheKey(string domain, bool globalCatalog) - { - GlobalCatalog = globalCatalog; - Domain = domain; - } - - protected bool Equals(LDAPConnectionCacheKey other) - { - return GlobalCatalog == other.GlobalCatalog && Domain == other.Domain; - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((LDAPConnectionCacheKey)obj); - } - - public override int GetHashCode() - { - unchecked - { - return (GlobalCatalog.GetHashCode() * 397) ^ (Domain != null ? Domain.GetHashCode() : 0); - } - } - } -} \ No newline at end of file diff --git a/src/CommonLib/LDAPUtilsNew.cs b/src/CommonLib/LDAPUtilsNew.cs index 8f8ccb9f..66c90b42 100644 --- a/src/CommonLib/LDAPUtilsNew.cs +++ b/src/CommonLib/LDAPUtilsNew.cs @@ -23,7 +23,7 @@ public class LDAPUtilsNew private LDAPConfig _ldapConfig = new(); private readonly ILogger _log; //This cache is indexed by domain sid - private readonly ConcurrentDictionary _ldapConnectionCache = new(); + private readonly DCConnectionCache _ldapConnectionCache; private readonly ConcurrentDictionary _domainCache = new(); private readonly string[] _translateNames = { "Administrator", "admin" }; private readonly PortScanner _portScanner; @@ -198,14 +198,14 @@ await _portScanner.CheckPort(target, _ldapConfig.GetGCPort(false)))) private LdapConnection CheckCacheConnection(LdapConnection connection, string domainName, bool globalCatalog, bool forceCreateNewConnection) { - LDAPConnectionCacheKey cacheKey; + string cacheIdentifier; if (_ldapConfig.Server != null) { - cacheKey = new LDAPConnectionCacheKey(_ldapConfig.Server, globalCatalog); + cacheIdentifier = _ldapConfig.Server; } else { - if (!GetDomainSidFromDomainName(domainName, out var cacheIdentifier)) + if (!GetDomainSidFromDomainName(domainName, out cacheIdentifier)) { //This is kinda gross, but its another way to get the correct domain sid if (!connection.GetNamingContextSearchBase(NamingContexts.Default, out var searchBase) || !GetDomainSidFromConnection(connection, searchBase, out cacheIdentifier)) @@ -214,50 +214,36 @@ private LdapConnection CheckCacheConnection(LdapConnection connection, string do * If we get here, we couldn't resolve a domain sid, which is hella bad, but we also want to keep from creating a shitton of new connections * Cache using the domainname and pray it all works out */ - cacheIdentifier = domainName.ToUpper().Trim(); + cacheIdentifier = domainName; } } - - cacheKey = new LDAPConnectionCacheKey(cacheIdentifier, globalCatalog); } if (forceCreateNewConnection) { - return _ldapConnectionCache.AddOrUpdate(cacheKey, connection, (_, existingConnection) => - { - existingConnection.Dispose(); - return connection; - }); + return _ldapConnectionCache.AddOrUpdate(cacheIdentifier, globalCatalog, connection); } - return _ldapConnectionCache.AddOrUpdate(cacheKey, connection, (_, existingConnection) => - { - connection.Dispose(); - return existingConnection; - }); + return _ldapConnectionCache.TryAdd(cacheIdentifier, globalCatalog, connection); } private bool GetCachedConnection(string domain, bool globalCatalog, out LdapConnection connection) { - LDAPConnectionCacheKey cacheKey; //If server is set via our config, we'll always just use this as the cache key if (_ldapConfig.Server != null) { - cacheKey = new LDAPConnectionCacheKey(_ldapConfig.Server, globalCatalog); - return _ldapConnectionCache.TryGetValue(cacheKey, out connection); + return _ldapConnectionCache.TryGet(_ldapConfig.Server, globalCatalog, out connection); } if (GetDomainSidFromDomainName(domain, out var domainSid)) { - cacheKey = new LDAPConnectionCacheKey(domainSid, globalCatalog); - if (_ldapConnectionCache.TryGetValue(cacheKey, out connection)) + if (_ldapConnectionCache.TryGet(domainSid, globalCatalog, out connection)) { return true; } } - cacheKey = new LDAPConnectionCacheKey(domain.ToUpper().Trim(), globalCatalog); - return _ldapConnectionCache.TryGetValue(cacheKey, out connection); + return _ldapConnectionCache.TryGet(domain, globalCatalog, out connection); } From 78e5d9d8894c2fd38082cf9072002bc71a084d86 Mon Sep 17 00:00:00 2001 From: rvazarkar Date: Mon, 10 Jun 2024 16:29:03 -0400 Subject: [PATCH 03/68] wip: wip --- src/CommonLib/DCConnectionCache.cs | 14 +- src/CommonLib/LDAPUtilsNew.cs | 649 ++++++++++++++-------- src/CommonLib/LdapConnectionWrapperNew.cs | 65 +++ src/CommonLib/LdapQueryParameters.cs | 23 + src/CommonLib/SharpHoundCommonLib.csproj | 2 +- 5 files changed, 507 insertions(+), 246 deletions(-) create mode 100644 src/CommonLib/LdapConnectionWrapperNew.cs create mode 100644 src/CommonLib/LdapQueryParameters.cs diff --git a/src/CommonLib/DCConnectionCache.cs b/src/CommonLib/DCConnectionCache.cs index b7efe26e..36e1e82b 100644 --- a/src/CommonLib/DCConnectionCache.cs +++ b/src/CommonLib/DCConnectionCache.cs @@ -5,35 +5,35 @@ namespace SharpHoundCommonLib { public class DCConnectionCache { - private readonly ConcurrentDictionary _ldapConnectionCache; + private readonly ConcurrentDictionary _ldapConnectionCache; public DCConnectionCache() { - _ldapConnectionCache = new ConcurrentDictionary(); + _ldapConnectionCache = new ConcurrentDictionary(); } - public bool TryGet(string key, bool isGlobalCatalog, out LdapConnection connection) + public bool TryGet(string key, bool isGlobalCatalog, out LdapConnectionWrapperNew connection) { var cacheKey = GetKey(key, isGlobalCatalog); return _ldapConnectionCache.TryGetValue(cacheKey, out connection); } - public LdapConnection AddOrUpdate(string key, bool isGlobalCatalog, LdapConnection connection) + public LdapConnectionWrapperNew AddOrUpdate(string key, bool isGlobalCatalog, LdapConnectionWrapperNew connection) { var cacheKey = GetKey(key, isGlobalCatalog); return _ldapConnectionCache.AddOrUpdate(cacheKey, connection, (_, existingConnection) => { - existingConnection.Dispose(); + existingConnection.Connection.Dispose(); return connection; }); } - public LdapConnection TryAdd(string key, bool isGlobalCatalog, LdapConnection connection) + public LdapConnectionWrapperNew TryAdd(string key, bool isGlobalCatalog, LdapConnectionWrapperNew connection) { var cacheKey = GetKey(key, isGlobalCatalog); return _ldapConnectionCache.AddOrUpdate(cacheKey, connection, (_, existingConnection) => { - connection.Dispose(); + connection.Connection.Dispose(); return existingConnection; }); } diff --git a/src/CommonLib/LDAPUtilsNew.cs b/src/CommonLib/LDAPUtilsNew.cs index 66c90b42..84c80d18 100644 --- a/src/CommonLib/LDAPUtilsNew.cs +++ b/src/CommonLib/LDAPUtilsNew.cs @@ -4,8 +4,11 @@ using System.DirectoryServices; using System.DirectoryServices.ActiveDirectory; using System.DirectoryServices.Protocols; +using System.Linq; using System.Net; +using System.Runtime.CompilerServices; using System.Security.Principal; +using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using SharpHoundCommonLib.Enums; @@ -14,189 +17,393 @@ using SharpHoundCommonLib.Processors; using SharpHoundRPC.NetAPINative; using SearchScope = System.DirectoryServices.Protocols.SearchScope; +using SecurityMasks = System.DirectoryServices.Protocols.SecurityMasks; namespace SharpHoundCommonLib; -public class LDAPUtilsNew -{ - private readonly string _nullCacheKey = Guid.NewGuid().ToString(); - private LDAPConfig _ldapConfig = new(); - private readonly ILogger _log; +public class LDAPUtilsNew { //This cache is indexed by domain sid - private readonly DCConnectionCache _ldapConnectionCache; + private readonly ConcurrentDictionary _dcInfoCache = new(); + private readonly DCConnectionCache _ldapConnectionCache = new(); private readonly ConcurrentDictionary _domainCache = new(); - private readonly string[] _translateNames = { "Administrator", "admin" }; - private readonly PortScanner _portScanner; + private readonly ILogger _log; private readonly NativeMethods _nativeMethods; - - public async IAsyncEnumerable PagedQuery(string ldapFilter) - { - + private readonly string _nullCacheKey = Guid.NewGuid().ToString(); + private readonly PortScanner _portScanner; + private readonly string[] _translateNames = { "Administrator", "admin" }; + private readonly LDAPConfig _ldapConfig = new(); + + private static readonly TimeSpan MinBackoffDelay = TimeSpan.FromSeconds(2); + private static readonly TimeSpan MaxBackoffDelay = TimeSpan.FromSeconds(20); + private const int BackoffDelayMultiplier = 2; + private const int MaxRetries = 3; + private readonly object _lockObj = new(); + private readonly ManualResetEvent _connectionResetEvent = new(false); + + public async IAsyncEnumerable PagedQuery(LdapQueryParameters queryParameters, + [EnumeratorCancellation] CancellationToken cancellationToken = new()) { + //Always force create a new connection + var (success, connectionWrapper, message) = await GetLdapConnection(queryParameters.DomainName, + _ldapConfig.AuthType, + queryParameters.GlobalCatalog, true); + if (!success) { + _log.LogDebug("PagedQuery failure: unable to create a connection: {Reason}\n{Info}", message, + queryParameters.GetQueryInfo()); + yield break; + } + + //This should never happen as far as I know, so just checking for safety + if (connectionWrapper == null) { + _log.LogWarning("PagedQuery failure: ldap connection is null\n{Info}", queryParameters.GetQueryInfo()); + yield break; + } + + //Pull the server name from the connection for retry logic later + if (!GetServerFromConnection(connectionWrapper.Connection, out var serverName)) { + serverName = null; + } + + if (!CreateSearchRequest(queryParameters, ref connectionWrapper, out var searchRequest)) { + _log.LogError("PagedQuery failure: unable to resolve search base\n{Info}", queryParameters.GetQueryInfo()); + yield break; + } + + var pageControl = new PageResultRequestControl(500); + searchRequest.Controls.Add(pageControl); + + PageResultResponseControl pageResponse = null; + var backoffDelay = MinBackoffDelay; + var retryCount = 0; + + while (true) { + if (cancellationToken.IsCancellationRequested) { + yield break; + } + + SearchResponse response; + try { + _log.LogTrace("Sending paged ldap request - {Info}", queryParameters.GetQueryInfo()); + response = (SearchResponse)connectionWrapper.Connection.SendRequest(searchRequest); + if (response != null) { + pageResponse = (PageResultResponseControl)response.Controls + .Where(x => x is PageResultResponseControl).DefaultIfEmpty(null).FirstOrDefault(); + } + } + catch (LdapException le) when (le.ErrorCode == (int)LdapErrorCodes.ServerDown && + retryCount < MaxRetries) { + /* + * If we dont have a servername, we're not going to be able to re-establish a connection here. Page cookies are only valid for the server they were generated on. Bail out. + */ + if (serverName == null) { + _log.LogError( + "PagedQuery - Received serverdown exception with server unknown. Unable to generate new connection\n{Info}", + queryParameters.GetQueryInfo()); + yield break; + } + /*A ServerDown exception indicates that our connection is no longer valid for one of many reasons. + However, this function is generally called by multiple threads, so we need to be careful in recreating + the connection. Using a semaphore, we can ensure that only one thread is actually recreating the connection + while the other threads that hit the ServerDown exception simply wait. The initial caller will hold the semaphore + and do a backoff delay before trying to make a new connection which will replace the existing connection in the + _ldapConnections cache. Other threads will retrieve the new connection from the cache instead of making a new one + This minimizes overhead of new connections while still fixing our core problem.*/ + + retryCount++; + + //Attempt to acquire a lock + if (Monitor.TryEnter(_lockObj)) { + //If we've acquired the lock, we want to immediately signal our reset event so everyone else waits + _connectionResetEvent.Reset(); + try { + //Sleep for our backoff + Thread.Sleep(backoffDelay); + //Explicitly skip the cache so we don't get the same connection back + conn = CreateNewConnection(domainName, globalCatalog, true).Connection; + if (conn == null) { + _log.LogError( + "Unable to create replacement ldap connection for ServerDown exception. Breaking loop"); + yield break; + } + + _log.LogInformation("Created new LDAP connection after receiving ServerDown from server"); + } + finally { + //Reset our event + release the lock + _connectionResetEvent.Set(); + Monitor.Exit(_lockObj); + } + } + else { + //If someone else is holding the reset event, we want to just wait and then pull the newly created connection out of the cache + //This event will be released after the first entrant thread is done making a new connection + //The thread.sleep is to prevent a potential, very unlikely race + Thread.Sleep(50); + _connectionResetEvent.WaitOne(); + conn = CreateNewConnection(domainName, globalCatalog).Connection; + } + + backoffDelay = GetNextBackoff(retryCount); + continue; + } + } } - private async Task<(bool, LdapConnection)> GetLdapConnection(string domainName, AuthType authType = AuthType.Negotiate, bool globalCatalog = false, - bool forceCreateNewConnection = false) - { - //TODO: Pull out individual strategies into single functions for readability and better logging - if (string.IsNullOrWhiteSpace(domainName)) - { - throw new ArgumentNullException(nameof(domainName)); + private bool CreateSearchRequest(LdapQueryParameters queryParameters, + ref LdapConnectionWrapperNew connectionWrapper, out SearchRequest searchRequest, bool paged = false) { + string basePath; + if (!string.IsNullOrWhiteSpace(queryParameters.SearchBase)) { + basePath = queryParameters.SearchBase; + } + else if (!connectionWrapper.GetSearchBase(queryParameters.NamingContext, out basePath)) { + string tempPath; + if (CallDsGetDcName(queryParameters.DomainName, out var info) && info != null) { + tempPath = DomainNameToDistinguishedName(info.Value.DomainName); + connectionWrapper.SaveContext(queryParameters.NamingContext, basePath); + } + else if (GetDomain(queryParameters.DomainName, out var domainObject)) { + tempPath = DomainNameToDistinguishedName(domainObject.Name); + } + else { + searchRequest = null; + return false; + } + + basePath = queryParameters.NamingContext switch { + NamingContexts.Configuration => $"CN=Configuration,{tempPath}", + NamingContexts.Schema => $"CN=Schema,CN=Configuration,{tempPath}", + NamingContexts.Default => tempPath, + _ => throw new ArgumentOutOfRangeException() + }; + + connectionWrapper.SaveContext(queryParameters.NamingContext, basePath); } - LdapConnection connection; + searchRequest = new SearchRequest(basePath, queryParameters.LDAPFilter, queryParameters.SearchScope, + queryParameters.Attributes); + searchRequest.Controls.Add(new SearchOptionsControl(SearchOption.DomainScope)); + if (queryParameters.IncludeDeleted) { + searchRequest.Controls.Add(new ShowDeletedControl()); + } - try - { + if (queryParameters.IncludeSecurityDescriptor) { + searchRequest.Controls.Add(new SecurityDescriptorFlagControl { + SecurityMasks = SecurityMasks.Dacl | SecurityMasks.Owner + }); + } + + return true; + } + + private static string DomainNameToDistinguishedName(string domainName) { + return $"DC={domainName.Replace(".", ",DC=")}"; + } + + private bool CallDsGetDcName(string domainName, out NetAPIStructs.DomainControllerInfo? info) { + if (_dcInfoCache.TryGetValue(domainName.ToUpper().Trim(), out info)) return info != null; + + var apiResult = _nativeMethods.CallDsGetDcName(null, domainName, + (uint)(NetAPIEnums.DSGETDCNAME_FLAGS.DS_FORCE_REDISCOVERY | + NetAPIEnums.DSGETDCNAME_FLAGS.DS_RETURN_DNS_NAME | + NetAPIEnums.DSGETDCNAME_FLAGS.DS_DIRECTORY_SERVICE_REQUIRED)); + + if (apiResult.IsFailed) { + _dcInfoCache.TryAdd(domainName.ToUpper().Trim(), null); + return false; + } + + info = apiResult.Value; + return true; + } + + private bool + GetLdapConnectionForServer(string serverName, AuthType authType, out LdapConnectionWrapperNew connectionWrapper, + bool globalCatalog = false, bool forceCreateNewConnection = false) { + if (string.IsNullOrWhiteSpace(serverName)) { + throw new ArgumentNullException(nameof(serverName)); + } + + try { + if (!forceCreateNewConnection && + GetCachedConnection(serverName, globalCatalog, out connectionWrapper)) + return true; + + if (CreateLdapConnection(serverName, authType, globalCatalog, out var serverConnection)) { + return true; + } + + connectionWrapper = null; + return false; + } + catch (LdapAuthenticationException e) { + _log.LogError("Error connecting to {Domain}: credentials are invalid (error code {ErrorCode})", serverName, + e.LdapException.ErrorCode); + connectionWrapper = null; + return false; + } + catch (NoLdapDataException) { + _log.LogError("No data returned for domain {Domain} during initial LDAP test.", serverName); + connectionWrapper = null; + return false; + } + } + + private async Task<(bool Success, LdapConnectionWrapperNew Connection, string Message )> GetLdapConnection( + string domainName, AuthType authType = AuthType.Negotiate, bool globalCatalog = false, + bool forceCreateNewConnection = false) { + //TODO: Pull out individual strategies into single functions for readability and better logging + if (string.IsNullOrWhiteSpace(domainName)) throw new ArgumentNullException(nameof(domainName)); + + try { /* * If a server is explicitly set on the config, we should only test this config */ - if (_ldapConfig.Server != null) - { + LdapConnectionWrapperNew connectionWrapper; + LdapConnection connection; + if (_ldapConfig.Server != null) { _log.LogWarning("Server is overridden via config, creating connection to {Server}", _ldapConfig.Server); - if (!forceCreateNewConnection && GetCachedConnection(domainName, globalCatalog, out connection)) - { - return (true, connection); - } + if (!forceCreateNewConnection && + GetCachedConnection(domainName, globalCatalog, out connectionWrapper)) + return (true, connectionWrapper, ""); - if (CreateLdapConnection(_ldapConfig.Server, authType, globalCatalog, out var serverConnection)) - { - connection = CheckCacheConnection(serverConnection, domainName, globalCatalog, + if (CreateLdapConnection(_ldapConfig.Server, authType, globalCatalog, out var serverConnection)) { + connectionWrapper = CheckCacheConnection(serverConnection, domainName, globalCatalog, forceCreateNewConnection); - return (true, connection); + return (true, connectionWrapper, ""); } - - return (false, null); - } - if (!forceCreateNewConnection && GetCachedConnection(domainName, globalCatalog, out connection)) - { - return (true, connection); + return (false, null, "Failed to connect to specified server"); } + if (!forceCreateNewConnection && GetCachedConnection(domainName, globalCatalog, out connectionWrapper)) + return (true, connectionWrapper, ""); + _log.LogInformation("No cached connection found for domain {Domain}, attempting a new connection", domainName); - if (CreateLdapConnection(domainName.ToUpper().Trim(), authType, globalCatalog, out connection)) - { + if (CreateLdapConnection(domainName.ToUpper().Trim(), authType, globalCatalog, out connection)) { _log.LogDebug("Successfully created ldap connection for domain: {Domain} using strategy 1", domainName); - connection = CheckCacheConnection(connection, domainName, globalCatalog, forceCreateNewConnection); - return (true, connection); + connectionWrapper = + CheckCacheConnection(connection, domainName, globalCatalog, forceCreateNewConnection); + return (true, connectionWrapper, ""); } string tempDomainName; - var dsGetDcNameResult = _nativeMethods.CallDsGetDcName(null, domainName, (uint)(NetAPIEnums.DSGETDCNAME_FLAGS.DS_FORCE_REDISCOVERY | NetAPIEnums.DSGETDCNAME_FLAGS.DS_RETURN_DNS_NAME | NetAPIEnums.DSGETDCNAME_FLAGS.DS_DIRECTORY_SERVICE_REQUIRED)); - if (dsGetDcNameResult.IsSuccess) - { + var dsGetDcNameResult = _nativeMethods.CallDsGetDcName(null, domainName, + (uint)(NetAPIEnums.DSGETDCNAME_FLAGS.DS_FORCE_REDISCOVERY | + NetAPIEnums.DSGETDCNAME_FLAGS.DS_RETURN_DNS_NAME | + NetAPIEnums.DSGETDCNAME_FLAGS.DS_DIRECTORY_SERVICE_REQUIRED)); + if (dsGetDcNameResult.IsSuccess) { tempDomainName = dsGetDcNameResult.Value.DomainName; - if (!forceCreateNewConnection && GetCachedConnection(tempDomainName, globalCatalog, out connection)) - { - return (true, connection); - } - - if (!tempDomainName.Equals(domainName, StringComparison.OrdinalIgnoreCase) && CreateLdapConnection(tempDomainName, authType, globalCatalog, out connection)) - { - _log.LogDebug("Successfully created ldap connection for domain: {Domain} using strategy 2 with name {NewName}", domainName, tempDomainName); - connection = CheckCacheConnection(connection, tempDomainName, globalCatalog, forceCreateNewConnection); - return (true, connection); + if (!forceCreateNewConnection && + GetCachedConnection(tempDomainName, globalCatalog, out connectionWrapper)) + return (true, connectionWrapper, ""); + + if (!tempDomainName.Equals(domainName, StringComparison.OrdinalIgnoreCase) && + CreateLdapConnection(tempDomainName, authType, globalCatalog, out connection)) { + _log.LogDebug( + "Successfully created ldap connection for domain: {Domain} using strategy 2 with name {NewName}", + domainName, tempDomainName); + connectionWrapper = CheckCacheConnection(connection, tempDomainName, globalCatalog, + forceCreateNewConnection); + return (true, connectionWrapper, ""); } var server = dsGetDcNameResult.Value.DomainControllerName.TrimStart('\\'); var result = await CreateLDAPConnectionWithPortCheck(server, authType, globalCatalog); - if (result.success) - { - _log.LogDebug("Successfully created ldap connection for domain: {Domain} using strategy 3 to server {Server}", domainName,server); - connection = CheckCacheConnection(result.connection, tempDomainName, globalCatalog, + if (result.success) { + _log.LogDebug( + "Successfully created ldap connection for domain: {Domain} using strategy 3 to server {Server}", + domainName, server); + connectionWrapper = CheckCacheConnection(result.connection, tempDomainName, globalCatalog, forceCreateNewConnection); - return (true, connection); + return (true, connectionWrapper, ""); } } - if (!GetDomain(domainName, out var domainObject) || domainObject.Name == null) - { + if (!GetDomain(domainName, out var domainObject) || domainObject.Name == null) { //If we don't get a result here, we effectively have no other ways to resolve this domain, so we'll just have to exit out - _log.LogDebug("Could not get domain object from GetDomain, unable to create ldap connection for domain {Domain}", domainName); - return (false, null); + _log.LogDebug( + "Could not get domain object from GetDomain, unable to create ldap connection for domain {Domain}", + domainName); + return (false, null, "Unable to get domain object for further methods"); } tempDomainName = domainObject.Name.ToUpper().Trim(); - if (!forceCreateNewConnection && GetCachedConnection(tempDomainName, globalCatalog, out connection)) - { - return (true, connection); - } - - if (!tempDomainName.Equals(domainName, StringComparison.OrdinalIgnoreCase) && CreateLdapConnection(tempDomainName, authType, globalCatalog, out connection)) - { - _log.LogDebug("Successfully created ldap connection for domain: {Domain} using strategy 4 with name {NewName}", domainName, tempDomainName); - connection = CheckCacheConnection(connection, tempDomainName, globalCatalog, forceCreateNewConnection); - return (true, connection); + if (!forceCreateNewConnection && + GetCachedConnection(tempDomainName, globalCatalog, out connectionWrapper)) + return (true, connectionWrapper, ""); + + if (!tempDomainName.Equals(domainName, StringComparison.OrdinalIgnoreCase) && + CreateLdapConnection(tempDomainName, authType, globalCatalog, out connection)) { + _log.LogDebug( + "Successfully created ldap connection for domain: {Domain} using strategy 4 with name {NewName}", + domainName, tempDomainName); + connectionWrapper = + CheckCacheConnection(connection, tempDomainName, globalCatalog, forceCreateNewConnection); + return (true, connectionWrapper, ""); } var primaryDomainController = domainObject.PdcRoleOwner.Name; var portConnectionResult = await CreateLDAPConnectionWithPortCheck(primaryDomainController, authType, globalCatalog); - if (portConnectionResult.success) - { - _log.LogDebug("Successfully created ldap connection for domain: {Domain} using strategy 5 with to pdc {Server}", domainName, primaryDomainController); - connection = CheckCacheConnection(portConnectionResult.connection, tempDomainName, globalCatalog, + if (portConnectionResult.success) { + _log.LogDebug( + "Successfully created ldap connection for domain: {Domain} using strategy 5 with to pdc {Server}", + domainName, primaryDomainController); + connectionWrapper = CheckCacheConnection(portConnectionResult.connection, tempDomainName, globalCatalog, forceCreateNewConnection); - return (true, connection); + return (true, connectionWrapper, ""); } - + //Loop over all other domain controllers and see if we can make a good connection to any - foreach (DomainController dc in domainObject.DomainControllers) - { + foreach (DomainController dc in domainObject.DomainControllers) { portConnectionResult = await CreateLDAPConnectionWithPortCheck(primaryDomainController, authType, globalCatalog); - if (portConnectionResult.success) - { - _log.LogDebug("Successfully created ldap connection for domain: {Domain} using strategy 6 with to pdc {Server}", domainName, primaryDomainController); - connection = CheckCacheConnection(portConnectionResult.connection, tempDomainName, globalCatalog, + if (portConnectionResult.success) { + _log.LogDebug( + "Successfully created ldap connection for domain: {Domain} using strategy 6 with to pdc {Server}", + domainName, primaryDomainController); + connectionWrapper = CheckCacheConnection(portConnectionResult.connection, tempDomainName, + globalCatalog, forceCreateNewConnection); - return (true, connection); + return (true, connectionWrapper, ""); } } _log.LogWarning("Exhausted all potential methods of creating ldap connection to {DomainName}", domainName); - return (false, null); + return (false, null, "All attempted connections failed"); } - catch (LdapAuthenticationException e) - { + catch (LdapAuthenticationException e) { _log.LogError("Error connecting to {Domain}: credentials are invalid (error code {ErrorCode})", domainName, e.LdapException.ErrorCode); - return (false, null); + return (false, null, "Invalid credentials for connection"); } - catch (NoLdapDataException) - { + catch (NoLdapDataException) { _log.LogError("No data returned for domain {Domain} during initial LDAP test.", domainName); - return (false, null); + return (false, null, "No data returned from ldap connection"); } } - - private async Task<(bool success, LdapConnection connection)> CreateLDAPConnectionWithPortCheck(string target, AuthType authType, bool globalCatalog) - { - if (globalCatalog) - { + + private async Task<(bool success, LdapConnection connection)> CreateLDAPConnectionWithPortCheck(string target, + AuthType authType, bool globalCatalog) { + if (globalCatalog) { if (await _portScanner.CheckPort(target, _ldapConfig.GetGCPort(true)) || (!_ldapConfig.ForceSSL && await _portScanner.CheckPort(target, _ldapConfig.GetGCPort(false)))) - { return (CreateLdapConnection(target, authType, true, out var connection), connection); - } } - else - { - if (await _portScanner.CheckPort(target, _ldapConfig.GetPort(true)) || (!_ldapConfig.ForceSSL && await _portScanner.CheckPort(target, _ldapConfig.GetPort(false)))) - { + else { + if (await _portScanner.CheckPort(target, _ldapConfig.GetPort(true)) || (!_ldapConfig.ForceSSL && + await _portScanner.CheckPort(target, _ldapConfig.GetPort(false)))) return (CreateLdapConnection(target, authType, true, out var connection), connection); - } } - + return (false, null); } - - private LdapConnection CheckCacheConnection(LdapConnection connection, string domainName, bool globalCatalog, bool forceCreateNewConnection) + + private LdapConnectionWrapperNew CheckCacheConnection(LdapConnection connection, string domainName, bool globalCatalog, bool forceCreateNewConnection) { string cacheIdentifier; if (_ldapConfig.Server != null) @@ -218,16 +425,18 @@ private LdapConnection CheckCacheConnection(LdapConnection connection, string do } } } + + var wrapper = new LdapConnectionWrapperNew(connection); if (forceCreateNewConnection) { - return _ldapConnectionCache.AddOrUpdate(cacheIdentifier, globalCatalog, connection); + return _ldapConnectionCache.AddOrUpdate(cacheIdentifier, globalCatalog, wrapper); } - return _ldapConnectionCache.TryAdd(cacheIdentifier, globalCatalog, connection); + return _ldapConnectionCache.TryAdd(cacheIdentifier, globalCatalog, wrapper); } - private bool GetCachedConnection(string domain, bool globalCatalog, out LdapConnection connection) + private bool GetCachedConnection(string domain, bool globalCatalog, out LdapConnectionWrapperNew connection) { //If server is set via our config, we'll always just use this as the cache key if (_ldapConfig.Server != null) @@ -245,76 +454,80 @@ private bool GetCachedConnection(string domain, bool globalCatalog, out LdapConn return _ldapConnectionCache.TryGet(domain, globalCatalog, out connection); } - - private bool GetDomainSidFromConnection(LdapConnection connection, string searchBase, out string domainSid) - { - try - { + private bool GetDomainSidFromConnection(LdapConnection connection, string searchBase, out string domainSid) { + try { //This ldap filter searches for domain controllers //Searches for any accounts with a UAC value inclusive of 8192 bitwise //8192 is the flag for SERVER_TRUST_ACCOUNT, which is set only on Domain Controllers var searchRequest = new SearchRequest(searchBase, "(&(objectCategory=computer)(userAccountControl:1.2.840.113556.1.4.803:=8192))", - SearchScope.Subtree, new[] { "objectsid"}); + SearchScope.Subtree, "objectsid"); var response = (SearchResponse)connection.SendRequest(searchRequest); - if (response == null || response.Entries.Count == 0) - { + if (response == null || response.Entries.Count == 0) { domainSid = ""; return false; } var entry = response.Entries[0]; var sid = entry.GetSid(); - domainSid = sid.Substring(0, sid.LastIndexOf('-')).ToUpper(); + domainSid = sid.Substring(0, sid.LastIndexOf('-')).ToUpper(); return true; } - catch (LdapException) - { + catch (LdapException) { _log.LogWarning("Failed grabbing domainsid from ldap for {domain}", searchBase); domainSid = ""; return false; } } - private bool CreateLdapConnection(string target, AuthType authType, bool globalCatalog, out LdapConnection connection) - { + private bool GetServerFromConnection(LdapConnection connection, out string server) { + var searchRequest = new SearchRequest("", new LDAPFilter().AddAllObjects().GetFilter(), + SearchScope.Base, null); + searchRequest.Controls.Add(new SearchOptionsControl(SearchOption.DomainScope)); + + var response = (SearchResponse)connection.SendRequest(searchRequest); + if (response?.Entries == null || response.Entries.Count == 0) { + server = ""; + return false; + } + + var entry = response.Entries[0]; + server = entry.GetProperty(LDAPProperties.DNSHostName); + return server != null; + } + + private bool CreateLdapConnection(string target, AuthType authType, bool globalCatalog, + out LdapConnection connection) { var baseConnection = CreateBaseConnection(target, true, authType, globalCatalog); - if (TestLdapConnection(baseConnection, target)) - { + if (TestLdapConnection(baseConnection, target)) { connection = baseConnection; return true; } - try - { + try { baseConnection.Dispose(); } - catch - { + catch { //this is just in case } - if (_ldapConfig.ForceSSL) - { + if (_ldapConfig.ForceSSL) { connection = null; return false; } - + baseConnection = CreateBaseConnection(target, false, authType, globalCatalog); - if (TestLdapConnection(baseConnection, target)) - { + if (TestLdapConnection(baseConnection, target)) { connection = baseConnection; return true; } - - try - { + + try { baseConnection.Dispose(); } - catch - { + catch { //this is just in case } @@ -323,80 +536,66 @@ private bool CreateLdapConnection(string target, AuthType authType, bool globalC } private LdapConnection CreateBaseConnection(string directoryIdentifier, bool ssl, AuthType authType, - bool globalCatalog) - { + bool globalCatalog) { var port = globalCatalog ? _ldapConfig.GetGCPort(ssl) : _ldapConfig.GetPort(ssl); var identifier = new LdapDirectoryIdentifier(directoryIdentifier, port, false, false); var connection = new LdapConnection(identifier) { Timeout = new TimeSpan(0, 0, 5, 0) }; - + //These options are important! connection.SessionOptions.ProtocolVersion = 3; //Referral chasing does not work with paged searches connection.SessionOptions.ReferralChasing = ReferralChasingOptions.None; - if (ssl) - { - connection.SessionOptions.SecureSocketLayer = true; - } - - if (_ldapConfig.DisableSigning) - { + if (ssl) connection.SessionOptions.SecureSocketLayer = true; + + if (_ldapConfig.DisableSigning) { connection.SessionOptions.Sealing = false; connection.SessionOptions.Signing = false; } - + if (_ldapConfig.DisableCertVerification) connection.SessionOptions.VerifyServerCertificate = (_, _) => true; - - if (_ldapConfig.Username != null) - { + + if (_ldapConfig.Username != null) { var cred = new NetworkCredential(_ldapConfig.Username, _ldapConfig.Password); connection.Credential = cred; } - + connection.AuthType = authType; return connection; } - private struct LdapFailure - { - public LdapFailureReason FailureReason { get; set; } - public string Message { get; set; } - } - /// - /// Tests whether an LDAP connection is working + /// Tests whether an LDAP connection is working /// /// /// /// /// Something is wrong with the supplied credentials - /// A connection "succeeded" but no data was returned. This can be related to kerberos auth across trusts or just simply lack of permissions - private bool TestLdapConnection(LdapConnection connection, string identifier) - { - try - { + /// + /// A connection "succeeded" but no data was returned. This can be related to + /// kerberos auth across trusts or just simply lack of permissions + /// + private bool TestLdapConnection(LdapConnection connection, string identifier) { + try { //Attempt an initial bind. If this fails, likely auth is invalid, or its not a valid target connection.Bind(); } - catch (LdapException e) - { + catch (LdapException e) { //TODO: Maybe look at this and find a better way? - if (e.ErrorCode is (int)LdapErrorCodes.InvalidCredentials or (int)ResultCode.InappropriateAuthentication) - { + if (e.ErrorCode is (int)LdapErrorCodes.InvalidCredentials or (int)ResultCode.InappropriateAuthentication) { connection.Dispose(); throw new LdapAuthenticationException(e); } + return false; } - catch (Exception e) - { + catch (Exception e) { return false; } SearchResponse response; - try - { + try { //Do an initial search request to get the rootDSE //This ldap filter is equivalent to (objectclass=*) var searchRequest = CreateSearchRequest("", new LDAPFilter().AddAllObjects().GetFilter(), @@ -404,162 +603,133 @@ private bool TestLdapConnection(LdapConnection connection, string identifier) response = (SearchResponse)connection.SendRequest(searchRequest); } - catch (LdapException e) - { + catch (LdapException e) { /* * If we can't send the initial search request, its unlikely any other search requests will work so we will immediately return false */ _log.LogDebug(e, "TestLdapConnection failed during search request against target {Target}", identifier); return false; } - - if (response?.Entries == null || response.Entries.Count == 0) - { + + if (response?.Entries == null || response.Entries.Count == 0) { /* * This can happen for one of two reasons, either we dont have permission to query AD or we're authenticating * across external trusts with kerberos authentication without Forest Search Order properly configured. * Either way, this connection isn't useful for us because we're not going to get data, so return false */ - + _log.LogDebug("TestLdapConnection failed to return results against target {Target}", identifier); connection.Dispose(); throw new NoLdapDataException(); } - + return true; } private SearchRequest CreateSearchRequest(string distinguishedName, string ldapFilter, SearchScope searchScope, - string[] attributes) - { + string[] attributes) { var searchRequest = new SearchRequest(distinguishedName, ldapFilter, - searchScope, attributes); + searchScope, attributes); searchRequest.Controls.Add(new SearchOptionsControl(SearchOption.DomainScope)); return searchRequest; } - public bool GetDomainSidFromDomainName(string domainName, out string domainSid) - { - if (Cache.GetDomainSidMapping(domainName, out domainSid)) - { - return true; - } + public bool GetDomainSidFromDomainName(string domainName, out string domainSid) { + if (Cache.GetDomainSidMapping(domainName, out domainSid)) return true; - try - { + try { var entry = new DirectoryEntry($"LDAP://{domainName}"); //Force load objectsid into the object cache - entry.RefreshCache(new[] {"objectSid"}); + entry.RefreshCache(new[] { "objectSid" }); var sid = entry.GetSid(); - if (sid != null) - { + if (sid != null) { Cache.AddDomainSidMapping(domainName, sid); domainSid = sid; return true; } } - catch - { + catch { //we expect this to fail sometimes } if (GetDomain(domainName, out var domainObject)) - { - try - { + try { domainSid = domainObject.GetDirectoryEntry().GetSid(); - if (domainSid != null) - { + if (domainSid != null) { Cache.AddDomainSidMapping(domainName, domainSid); return true; } } - catch - { + catch { //we expect this to fail sometimes (not sure why, but better safe than sorry) } - } foreach (var name in _translateNames) - { - try - { + try { var account = new NTAccount(domainName, name); var sid = (SecurityIdentifier)account.Translate(typeof(SecurityIdentifier)); domainSid = sid.AccountDomainSid.ToString(); Cache.AddDomainSidMapping(domainName, domainSid); return true; } - catch - { + catch { //We expect this to fail if the username doesn't exist in the domain } - } return false; } - private string ResolveDomainCrossRef(string domainName) - { - + private string ResolveDomainCrossRef(string domainName) { } - + /// - /// Attempts to get the Domain object representing the target domain. If null is specified for the domain name, gets the user's current domain + /// Attempts to get the Domain object representing the target domain. If null is specified for the domain name, gets + /// the user's current domain /// /// /// /// - public bool GetDomain(string domainName, out Domain domain) - { + public bool GetDomain(string domainName, out Domain domain) { var cacheKey = domainName ?? _nullCacheKey; if (_domainCache.TryGetValue(cacheKey, out domain)) return true; - try - { + try { DirectoryContext context; if (_ldapConfig.Username != null) - { context = domainName != null ? new DirectoryContext(DirectoryContextType.Domain, domainName, _ldapConfig.Username, _ldapConfig.Password) : new DirectoryContext(DirectoryContextType.Domain, _ldapConfig.Username, _ldapConfig.Password); - } else - { context = domainName != null ? new DirectoryContext(DirectoryContextType.Domain, domainName) : new DirectoryContext(DirectoryContextType.Domain); - } domain = Domain.GetDomain(context); if (domain == null) return false; _domainCache.TryAdd(cacheKey, domain); return true; - } - catch (Exception e) - { + catch (Exception e) { _log.LogDebug(e, "GetDomain call failed for domain name {Name}", domainName); return false; } } - + /// - /// Attempts to get the Domain object representing the target domain. If null is specified for the domain name, gets the user's current domain + /// Attempts to get the Domain object representing the target domain. If null is specified for the domain name, gets + /// the user's current domain /// /// /// /// - public bool GetDomain(out Domain domain) - { + public bool GetDomain(out Domain domain) { var cacheKey = _nullCacheKey; if (_domainCache.TryGetValue(cacheKey, out domain)) return true; - try - { - var context= _ldapConfig.Username != null + try { + var context = _ldapConfig.Username != null ? new DirectoryContext(DirectoryContextType.Domain, _ldapConfig.Username, _ldapConfig.Password) : new DirectoryContext(DirectoryContextType.Domain); @@ -568,11 +738,14 @@ public bool GetDomain(out Domain domain) _domainCache.TryAdd(cacheKey, domain); return true; } - catch (Exception e) - { + catch (Exception e) { _log.LogDebug(e, "GetDomain call failed for blank domain"); return false; } } + private struct LdapFailure { + public LdapFailureReason FailureReason { get; set; } + public string Message { get; set; } + } } \ No newline at end of file diff --git a/src/CommonLib/LdapConnectionWrapperNew.cs b/src/CommonLib/LdapConnectionWrapperNew.cs new file mode 100644 index 00000000..518a4451 --- /dev/null +++ b/src/CommonLib/LdapConnectionWrapperNew.cs @@ -0,0 +1,65 @@ +using System; +using System.DirectoryServices.Protocols; +using SharpHoundCommonLib.Enums; + +namespace SharpHoundCommonLib; + +public class LdapConnectionWrapperNew +{ + public LdapConnection Connection; + private string _domainSearchBase; + private string _configurationSearchBase; + private string _schemaSearchBase; + private const string Unknown = "UNKNOWN"; + + public LdapConnectionWrapperNew(LdapConnection connection) + { + Connection = connection; + } + + public bool GetSearchBase(NamingContexts context, out string searchBase) + { + searchBase = GetSavedContext(context); + if (searchBase != null) + { + return true; + } + + if (Connection.GetNamingContextSearchBase(context, out searchBase)) + { + SaveContext(context, searchBase); + return true; + } + + return false; + } + + private string GetSavedContext(NamingContexts context) + { + return context switch + { + NamingContexts.Configuration => _configurationSearchBase, + NamingContexts.Default => _domainSearchBase, + NamingContexts.Schema => _schemaSearchBase, + _ => throw new ArgumentOutOfRangeException(nameof(context), context, null) + }; + } + + public void SaveContext(NamingContexts context, string searchBase) + { + switch (context) + { + case NamingContexts.Default: + _domainSearchBase = searchBase; + break; + case NamingContexts.Configuration: + _configurationSearchBase = searchBase; + break; + case NamingContexts.Schema: + _schemaSearchBase = searchBase; + break; + default: + throw new ArgumentOutOfRangeException(nameof(context), context, null); + } + } +} \ No newline at end of file diff --git a/src/CommonLib/LdapQueryParameters.cs b/src/CommonLib/LdapQueryParameters.cs new file mode 100644 index 00000000..dc277851 --- /dev/null +++ b/src/CommonLib/LdapQueryParameters.cs @@ -0,0 +1,23 @@ +using System; +using System.DirectoryServices.Protocols; +using SharpHoundCommonLib.Enums; + +namespace SharpHoundCommonLib; + +public class LdapQueryParameters +{ + public string LDAPFilter { get; set; } + public SearchScope SearchScope { get; set; } = SearchScope.Subtree; + public string[] Attributes { get; set; } = Array.Empty(); + public string DomainName { get; set; } + public bool GlobalCatalog { get; set; } + public bool IncludeSecurityDescriptor { get; set; } = false; + public bool IncludeDeleted { get; set; } = false; + public string SearchBase { get; set; } + public NamingContexts NamingContext { get; set; } = NamingContexts.Default; + + public string GetQueryInfo() + { + return $"Query Information - Filter: {LDAPFilter}, Domain: {DomainName}, GlobalCatalog: {GlobalCatalog}, ADSPath: {SearchBase}"; + } +} \ No newline at end of file diff --git a/src/CommonLib/SharpHoundCommonLib.csproj b/src/CommonLib/SharpHoundCommonLib.csproj index 25ffa95a..eca94af0 100644 --- a/src/CommonLib/SharpHoundCommonLib.csproj +++ b/src/CommonLib/SharpHoundCommonLib.csproj @@ -3,7 +3,7 @@ net462 library SharpHoundCommon - 11 + default Rohan Vazarkar SpecterOps Common library for C# BloodHound enumeration tasks From 8f37393a11d382e77a13d0c2551d6dbe33c40245 Mon Sep 17 00:00:00 2001 From: rvazarkar Date: Tue, 11 Jun 2024 15:33:42 -0400 Subject: [PATCH 04/68] wip: remove authtype parameter --- .../{NamingContexts.cs => NamingContext.cs} | 2 +- src/CommonLib/Extensions.cs | 8 +- src/CommonLib/LDAPUtilsNew.cs | 91 +++++++++---------- src/CommonLib/LdapConnectionWrapperNew.cs | 46 +++++++--- src/CommonLib/LdapQueryParameters.cs | 2 +- 5 files changed, 84 insertions(+), 65 deletions(-) rename src/CommonLib/Enums/{NamingContexts.cs => NamingContext.cs} (75%) diff --git a/src/CommonLib/Enums/NamingContexts.cs b/src/CommonLib/Enums/NamingContext.cs similarity index 75% rename from src/CommonLib/Enums/NamingContexts.cs rename to src/CommonLib/Enums/NamingContext.cs index 6a585d03..7a215e2c 100644 --- a/src/CommonLib/Enums/NamingContexts.cs +++ b/src/CommonLib/Enums/NamingContext.cs @@ -1,6 +1,6 @@ namespace SharpHoundCommonLib.Enums; -public enum NamingContexts +public enum NamingContext { Default, Configuration, diff --git a/src/CommonLib/Extensions.cs b/src/CommonLib/Extensions.cs index d85c0890..49c59269 100644 --- a/src/CommonLib/Extensions.cs +++ b/src/CommonLib/Extensions.cs @@ -120,7 +120,7 @@ public static int Rid(this SecurityIdentifier securityIdentifier) return rid; } - public static bool GetNamingContextSearchBase(this LdapConnection connection, NamingContexts context, + public static bool GetNamingContextSearchBase(this LdapConnection connection, NamingContext context, out string searchBase) { var searchRequest = @@ -146,9 +146,9 @@ public static bool GetNamingContextSearchBase(this LdapConnection connection, Na var entry = response.Entries[0]; searchBase = context switch { - NamingContexts.Default => entry.GetProperty(LDAPProperties.DefaultNamingContext), - NamingContexts.Configuration => entry.GetProperty(LDAPProperties.ConfigurationNamingContext), - NamingContexts.Schema => entry.GetProperty(LDAPProperties.SchemaNamingContext), + NamingContext.Default => entry.GetProperty(LDAPProperties.DefaultNamingContext), + NamingContext.Configuration => entry.GetProperty(LDAPProperties.ConfigurationNamingContext), + NamingContext.Schema => entry.GetProperty(LDAPProperties.SchemaNamingContext), _ => throw new ArgumentOutOfRangeException(nameof(context), context, null) }; diff --git a/src/CommonLib/LDAPUtilsNew.cs b/src/CommonLib/LDAPUtilsNew.cs index 84c80d18..89174d55 100644 --- a/src/CommonLib/LDAPUtilsNew.cs +++ b/src/CommonLib/LDAPUtilsNew.cs @@ -44,7 +44,6 @@ public async IAsyncEnumerable PagedQuery(LdapQueryParameters [EnumeratorCancellation] CancellationToken cancellationToken = new()) { //Always force create a new connection var (success, connectionWrapper, message) = await GetLdapConnection(queryParameters.DomainName, - _ldapConfig.AuthType, queryParameters.GlobalCatalog, true); if (!success) { _log.LogDebug("PagedQuery failure: unable to create a connection: {Reason}\n{Info}", message, @@ -59,7 +58,7 @@ public async IAsyncEnumerable PagedQuery(LdapQueryParameters } //Pull the server name from the connection for retry logic later - if (!GetServerFromConnection(connectionWrapper.Connection, out var serverName)) { + if (!connectionWrapper.GetServer(out var serverName)) { serverName = null; } @@ -72,8 +71,6 @@ public async IAsyncEnumerable PagedQuery(LdapQueryParameters searchRequest.Controls.Add(pageControl); PageResultResponseControl pageResponse = null; - var backoffDelay = MinBackoffDelay; - var retryCount = 0; while (true) { if (cancellationToken.IsCancellationRequested) { @@ -89,14 +86,13 @@ public async IAsyncEnumerable PagedQuery(LdapQueryParameters .Where(x => x is PageResultResponseControl).DefaultIfEmpty(null).FirstOrDefault(); } } - catch (LdapException le) when (le.ErrorCode == (int)LdapErrorCodes.ServerDown && - retryCount < MaxRetries) { + catch (LdapException le) when (le.ErrorCode == (int)LdapErrorCodes.ServerDown) { /* * If we dont have a servername, we're not going to be able to re-establish a connection here. Page cookies are only valid for the server they were generated on. Bail out. */ if (serverName == null) { _log.LogError( - "PagedQuery - Received serverdown exception with server unknown. Unable to generate new connection\n{Info}", + "PagedQuery - Received server down exception without a known servername. Unable to generate new connection\n{Info}", queryParameters.GetQueryInfo()); yield break; } @@ -108,7 +104,8 @@ public async IAsyncEnumerable PagedQuery(LdapQueryParameters _ldapConnections cache. Other threads will retrieve the new connection from the cache instead of making a new one This minimizes overhead of new connections while still fixing our core problem.*/ - retryCount++; + var backoffDelay = MinBackoffDelay; + var retryCount = 0; //Attempt to acquire a lock if (Monitor.TryEnter(_lockObj)) { @@ -118,6 +115,7 @@ public async IAsyncEnumerable PagedQuery(LdapQueryParameters //Sleep for our backoff Thread.Sleep(backoffDelay); //Explicitly skip the cache so we don't get the same connection back + connectionWrapper = GetLdapConnectionForServer(serverName) conn = CreateNewConnection(domainName, globalCatalog, true).Connection; if (conn == null) { _log.LogError( @@ -169,9 +167,9 @@ private bool CreateSearchRequest(LdapQueryParameters queryParameters, } basePath = queryParameters.NamingContext switch { - NamingContexts.Configuration => $"CN=Configuration,{tempPath}", - NamingContexts.Schema => $"CN=Schema,CN=Configuration,{tempPath}", - NamingContexts.Default => tempPath, + NamingContext.Configuration => $"CN=Configuration,{tempPath}", + NamingContext.Schema => $"CN=Schema,CN=Configuration,{tempPath}", + NamingContext.Default => tempPath, _ => throw new ArgumentOutOfRangeException() }; @@ -216,7 +214,7 @@ private bool CallDsGetDcName(string domainName, out NetAPIStructs.DomainControll } private bool - GetLdapConnectionForServer(string serverName, AuthType authType, out LdapConnectionWrapperNew connectionWrapper, + GetLdapConnectionForServer(string serverName, out LdapConnectionWrapperNew connectionWrapper, bool globalCatalog = false, bool forceCreateNewConnection = false) { if (string.IsNullOrWhiteSpace(serverName)) { throw new ArgumentNullException(nameof(serverName)); @@ -227,7 +225,7 @@ private bool GetCachedConnection(serverName, globalCatalog, out connectionWrapper)) return true; - if (CreateLdapConnection(serverName, authType, globalCatalog, out var serverConnection)) { + if (CreateLdapConnection(serverName, globalCatalog, out connectionWrapper)) { return true; } @@ -248,7 +246,7 @@ private bool } private async Task<(bool Success, LdapConnectionWrapperNew Connection, string Message )> GetLdapConnection( - string domainName, AuthType authType = AuthType.Negotiate, bool globalCatalog = false, + string domainName, bool globalCatalog = false, bool forceCreateNewConnection = false) { //TODO: Pull out individual strategies into single functions for readability and better logging if (string.IsNullOrWhiteSpace(domainName)) throw new ArgumentNullException(nameof(domainName)); @@ -258,14 +256,13 @@ private bool * If a server is explicitly set on the config, we should only test this config */ LdapConnectionWrapperNew connectionWrapper; - LdapConnection connection; if (_ldapConfig.Server != null) { _log.LogWarning("Server is overridden via config, creating connection to {Server}", _ldapConfig.Server); if (!forceCreateNewConnection && GetCachedConnection(domainName, globalCatalog, out connectionWrapper)) return (true, connectionWrapper, ""); - if (CreateLdapConnection(_ldapConfig.Server, authType, globalCatalog, out var serverConnection)) { + if (CreateLdapConnection(_ldapConfig.Server, globalCatalog, out var serverConnection)) { connectionWrapper = CheckCacheConnection(serverConnection, domainName, globalCatalog, forceCreateNewConnection); return (true, connectionWrapper, ""); @@ -280,10 +277,10 @@ private bool _log.LogInformation("No cached connection found for domain {Domain}, attempting a new connection", domainName); - if (CreateLdapConnection(domainName.ToUpper().Trim(), authType, globalCatalog, out connection)) { + if (CreateLdapConnection(domainName.ToUpper().Trim(), globalCatalog, out connectionWrapper)) { _log.LogDebug("Successfully created ldap connection for domain: {Domain} using strategy 1", domainName); connectionWrapper = - CheckCacheConnection(connection, domainName, globalCatalog, forceCreateNewConnection); + CheckCacheConnection(connectionWrapper, domainName, globalCatalog, forceCreateNewConnection); return (true, connectionWrapper, ""); } @@ -300,11 +297,11 @@ private bool return (true, connectionWrapper, ""); if (!tempDomainName.Equals(domainName, StringComparison.OrdinalIgnoreCase) && - CreateLdapConnection(tempDomainName, authType, globalCatalog, out connection)) { + CreateLdapConnection(tempDomainName, globalCatalog, out connectionWrapper)) { _log.LogDebug( "Successfully created ldap connection for domain: {Domain} using strategy 2 with name {NewName}", domainName, tempDomainName); - connectionWrapper = CheckCacheConnection(connection, tempDomainName, globalCatalog, + connectionWrapper = CheckCacheConnection(connectionWrapper, tempDomainName, globalCatalog, forceCreateNewConnection); return (true, connectionWrapper, ""); } @@ -312,7 +309,7 @@ private bool var server = dsGetDcNameResult.Value.DomainControllerName.TrimStart('\\'); var result = - await CreateLDAPConnectionWithPortCheck(server, authType, globalCatalog); + await CreateLDAPConnectionWithPortCheck(server, globalCatalog); if (result.success) { _log.LogDebug( "Successfully created ldap connection for domain: {Domain} using strategy 3 to server {Server}", @@ -337,18 +334,18 @@ private bool return (true, connectionWrapper, ""); if (!tempDomainName.Equals(domainName, StringComparison.OrdinalIgnoreCase) && - CreateLdapConnection(tempDomainName, authType, globalCatalog, out connection)) { + CreateLdapConnection(tempDomainName, globalCatalog, out connectionWrapper)) { _log.LogDebug( "Successfully created ldap connection for domain: {Domain} using strategy 4 with name {NewName}", domainName, tempDomainName); connectionWrapper = - CheckCacheConnection(connection, tempDomainName, globalCatalog, forceCreateNewConnection); + CheckCacheConnection(connectionWrapper, tempDomainName, globalCatalog, forceCreateNewConnection); return (true, connectionWrapper, ""); } var primaryDomainController = domainObject.PdcRoleOwner.Name; var portConnectionResult = - await CreateLDAPConnectionWithPortCheck(primaryDomainController, authType, globalCatalog); + await CreateLDAPConnectionWithPortCheck(primaryDomainController, globalCatalog); if (portConnectionResult.success) { _log.LogDebug( "Successfully created ldap connection for domain: {Domain} using strategy 5 with to pdc {Server}", @@ -361,7 +358,7 @@ private bool //Loop over all other domain controllers and see if we can make a good connection to any foreach (DomainController dc in domainObject.DomainControllers) { portConnectionResult = - await CreateLDAPConnectionWithPortCheck(primaryDomainController, authType, globalCatalog); + await CreateLDAPConnectionWithPortCheck(primaryDomainController, globalCatalog); if (portConnectionResult.success) { _log.LogDebug( "Successfully created ldap connection for domain: {Domain} using strategy 6 with to pdc {Server}", @@ -387,23 +384,22 @@ private bool } } - private async Task<(bool success, LdapConnection connection)> CreateLDAPConnectionWithPortCheck(string target, - AuthType authType, bool globalCatalog) { + private async Task<(bool success, LdapConnectionWrapperNew connection)> CreateLDAPConnectionWithPortCheck(string target, bool globalCatalog) { if (globalCatalog) { if (await _portScanner.CheckPort(target, _ldapConfig.GetGCPort(true)) || (!_ldapConfig.ForceSSL && await _portScanner.CheckPort(target, _ldapConfig.GetGCPort(false)))) - return (CreateLdapConnection(target, authType, true, out var connection), connection); + return (CreateLdapConnection(target, true, out var connection), connection); } else { if (await _portScanner.CheckPort(target, _ldapConfig.GetPort(true)) || (!_ldapConfig.ForceSSL && await _portScanner.CheckPort(target, _ldapConfig.GetPort(false)))) - return (CreateLdapConnection(target, authType, true, out var connection), connection); + return (CreateLdapConnection(target, true, out var connection), connection); } return (false, null); } - private LdapConnectionWrapperNew CheckCacheConnection(LdapConnection connection, string domainName, bool globalCatalog, bool forceCreateNewConnection) + private LdapConnectionWrapperNew CheckCacheConnection(LdapConnectionWrapperNew connectionWrapper, string domainName, bool globalCatalog, bool forceCreateNewConnection) { string cacheIdentifier; if (_ldapConfig.Server != null) @@ -415,7 +411,7 @@ private LdapConnectionWrapperNew CheckCacheConnection(LdapConnection connection, if (!GetDomainSidFromDomainName(domainName, out cacheIdentifier)) { //This is kinda gross, but its another way to get the correct domain sid - if (!connection.GetNamingContextSearchBase(NamingContexts.Default, out var searchBase) || !GetDomainSidFromConnection(connection, searchBase, out cacheIdentifier)) + if (!connectionWrapper.Connection.GetNamingContextSearchBase(NamingContext.Default, out var searchBase) || !GetDomainSidFromConnection(connectionWrapper.Connection, searchBase, out cacheIdentifier)) { /* * If we get here, we couldn't resolve a domain sid, which is hella bad, but we also want to keep from creating a shitton of new connections @@ -425,15 +421,13 @@ private LdapConnectionWrapperNew CheckCacheConnection(LdapConnection connection, } } } - - var wrapper = new LdapConnectionWrapperNew(connection); if (forceCreateNewConnection) { - return _ldapConnectionCache.AddOrUpdate(cacheIdentifier, globalCatalog, wrapper); + return _ldapConnectionCache.AddOrUpdate(cacheIdentifier, globalCatalog, connectionWrapper); } - return _ldapConnectionCache.TryAdd(cacheIdentifier, globalCatalog, wrapper); + return _ldapConnectionCache.TryAdd(cacheIdentifier, globalCatalog, connectionWrapper); } private bool GetCachedConnection(string domain, bool globalCatalog, out LdapConnectionWrapperNew connection) @@ -498,11 +492,11 @@ private bool GetServerFromConnection(LdapConnection connection, out string serve return server != null; } - private bool CreateLdapConnection(string target, AuthType authType, bool globalCatalog, - out LdapConnection connection) { - var baseConnection = CreateBaseConnection(target, true, authType, globalCatalog); - if (TestLdapConnection(baseConnection, target)) { - connection = baseConnection; + private bool CreateLdapConnection(string target, bool globalCatalog, + out LdapConnectionWrapperNew connection) { + var baseConnection = CreateBaseConnection(target, true, globalCatalog); + if (TestLdapConnection(baseConnection, target, out var entry)) { + connection = new LdapConnectionWrapperNew(baseConnection, entry); return true; } @@ -518,9 +512,9 @@ private bool CreateLdapConnection(string target, AuthType authType, bool globalC return false; } - baseConnection = CreateBaseConnection(target, false, authType, globalCatalog); - if (TestLdapConnection(baseConnection, target)) { - connection = baseConnection; + baseConnection = CreateBaseConnection(target, false, globalCatalog); + if (TestLdapConnection(baseConnection, target, out entry)) { + connection = new LdapConnectionWrapperNew(baseConnection, entry); return true; } @@ -535,7 +529,7 @@ private bool CreateLdapConnection(string target, AuthType authType, bool globalC return false; } - private LdapConnection CreateBaseConnection(string directoryIdentifier, bool ssl, AuthType authType, + private LdapConnection CreateBaseConnection(string directoryIdentifier, bool ssl, bool globalCatalog) { var port = globalCatalog ? _ldapConfig.GetGCPort(ssl) : _ldapConfig.GetPort(ssl); var identifier = new LdapDirectoryIdentifier(directoryIdentifier, port, false, false); @@ -560,7 +554,7 @@ private LdapConnection CreateBaseConnection(string directoryIdentifier, bool ssl connection.Credential = cred; } - connection.AuthType = authType; + connection.AuthType = _ldapConfig.AuthType; return connection; } @@ -570,13 +564,14 @@ private LdapConnection CreateBaseConnection(string directoryIdentifier, bool ssl /// /// /// + /// The rootdse object for this connection if successful /// /// Something is wrong with the supplied credentials /// /// A connection "succeeded" but no data was returned. This can be related to /// kerberos auth across trusts or just simply lack of permissions /// - private bool TestLdapConnection(LdapConnection connection, string identifier) { + private bool TestLdapConnection(LdapConnection connection, string identifier, out ISearchResultEntry entry) { try { //Attempt an initial bind. If this fails, likely auth is invalid, or its not a valid target connection.Bind(); @@ -588,9 +583,11 @@ private bool TestLdapConnection(LdapConnection connection, string identifier) { throw new LdapAuthenticationException(e); } + entry = null; return false; } catch (Exception e) { + entry = null; return false; } @@ -608,6 +605,7 @@ private bool TestLdapConnection(LdapConnection connection, string identifier) { * If we can't send the initial search request, its unlikely any other search requests will work so we will immediately return false */ _log.LogDebug(e, "TestLdapConnection failed during search request against target {Target}", identifier); + entry = null; return false; } @@ -623,6 +621,7 @@ private bool TestLdapConnection(LdapConnection connection, string identifier) { throw new NoLdapDataException(); } + entry = new SearchResultEntryWrapper(response.Entries[0]); return true; } diff --git a/src/CommonLib/LdapConnectionWrapperNew.cs b/src/CommonLib/LdapConnectionWrapperNew.cs index 518a4451..7e8191ce 100644 --- a/src/CommonLib/LdapConnectionWrapperNew.cs +++ b/src/CommonLib/LdapConnectionWrapperNew.cs @@ -6,27 +6,47 @@ namespace SharpHoundCommonLib; public class LdapConnectionWrapperNew { - public LdapConnection Connection; + public LdapConnection Connection { get; private set; } + private readonly ISearchResultEntry _searchResultEntry; private string _domainSearchBase; private string _configurationSearchBase; private string _schemaSearchBase; + private string _server; private const string Unknown = "UNKNOWN"; - public LdapConnectionWrapperNew(LdapConnection connection) + public LdapConnectionWrapperNew(LdapConnection connection, ISearchResultEntry entry) { Connection = connection; + _searchResultEntry = entry; } - public bool GetSearchBase(NamingContexts context, out string searchBase) + public bool GetServer(out string server) { + if (_server != null) { + server = _server; + return true; + } + + _server = _searchResultEntry.GetProperty(LDAPProperties.DNSHostName); + server = _server; + return server != null; + } + + public bool GetSearchBase(NamingContext context, out string searchBase) { searchBase = GetSavedContext(context); if (searchBase != null) { return true; } + + searchBase = context switch { + NamingContext.Default => _searchResultEntry.GetProperty(LDAPProperties.DefaultNamingContext), + NamingContext.Configuration => _searchResultEntry.GetProperty(LDAPProperties.ConfigurationNamingContext), + NamingContext.Schema => _searchResultEntry.GetProperty(LDAPProperties.SchemaNamingContext), + _ => throw new ArgumentOutOfRangeException(nameof(context), context, null) + }; - if (Connection.GetNamingContextSearchBase(context, out searchBase)) - { + if (searchBase != null) { SaveContext(context, searchBase); return true; } @@ -34,28 +54,28 @@ public bool GetSearchBase(NamingContexts context, out string searchBase) return false; } - private string GetSavedContext(NamingContexts context) + private string GetSavedContext(NamingContext context) { return context switch { - NamingContexts.Configuration => _configurationSearchBase, - NamingContexts.Default => _domainSearchBase, - NamingContexts.Schema => _schemaSearchBase, + NamingContext.Configuration => _configurationSearchBase, + NamingContext.Default => _domainSearchBase, + NamingContext.Schema => _schemaSearchBase, _ => throw new ArgumentOutOfRangeException(nameof(context), context, null) }; } - public void SaveContext(NamingContexts context, string searchBase) + public void SaveContext(NamingContext context, string searchBase) { switch (context) { - case NamingContexts.Default: + case NamingContext.Default: _domainSearchBase = searchBase; break; - case NamingContexts.Configuration: + case NamingContext.Configuration: _configurationSearchBase = searchBase; break; - case NamingContexts.Schema: + case NamingContext.Schema: _schemaSearchBase = searchBase; break; default: diff --git a/src/CommonLib/LdapQueryParameters.cs b/src/CommonLib/LdapQueryParameters.cs index dc277851..8859d3b2 100644 --- a/src/CommonLib/LdapQueryParameters.cs +++ b/src/CommonLib/LdapQueryParameters.cs @@ -14,7 +14,7 @@ public class LdapQueryParameters public bool IncludeSecurityDescriptor { get; set; } = false; public bool IncludeDeleted { get; set; } = false; public string SearchBase { get; set; } - public NamingContexts NamingContext { get; set; } = NamingContexts.Default; + public NamingContext NamingContext { get; set; } = NamingContext.Default; public string GetQueryInfo() { From 673cacd0aeaa486c9dae7c2fd9d2a79a66e42cdd Mon Sep 17 00:00:00 2001 From: rvazarkar Date: Wed, 12 Jun 2024 13:23:29 -0400 Subject: [PATCH 05/68] wip: wip --- src/CommonLib/LDAPUtilsNew.cs | 109 +++++++++++------- src/CommonLib/LdapConnectionWrapperNew.cs | 7 ++ src/CommonLib/LdapQueryParameters.cs | 1 + src/CommonLib/LdapResult.cs | 11 ++ .../Processors/CertAbuseProcessor.cs | 6 +- .../Processors/LocalGroupProcessor.cs | 6 +- .../UserRightsAssignmentProcessor.cs | 6 +- 7 files changed, 93 insertions(+), 53 deletions(-) create mode 100644 src/CommonLib/LdapResult.cs diff --git a/src/CommonLib/LDAPUtilsNew.cs b/src/CommonLib/LDAPUtilsNew.cs index 89174d55..f1ae92f0 100644 --- a/src/CommonLib/LDAPUtilsNew.cs +++ b/src/CommonLib/LDAPUtilsNew.cs @@ -40,7 +40,7 @@ public class LDAPUtilsNew { private readonly object _lockObj = new(); private readonly ManualResetEvent _connectionResetEvent = new(false); - public async IAsyncEnumerable PagedQuery(LdapQueryParameters queryParameters, + public async IAsyncEnumerable> PagedQuery(LdapQueryParameters queryParameters, [EnumeratorCancellation] CancellationToken cancellationToken = new()) { //Always force create a new connection var (success, connectionWrapper, message) = await GetLdapConnection(queryParameters.DomainName, @@ -48,22 +48,35 @@ public async IAsyncEnumerable PagedQuery(LdapQueryParameters if (!success) { _log.LogDebug("PagedQuery failure: unable to create a connection: {Reason}\n{Info}", message, queryParameters.GetQueryInfo()); + yield return new LdapResult { + Error = $"Unable to create a connection: {message}", + QueryInfo = queryParameters.GetQueryInfo() + }; yield break; } //This should never happen as far as I know, so just checking for safety if (connectionWrapper == null) { - _log.LogWarning("PagedQuery failure: ldap connection is null\n{Info}", queryParameters.GetQueryInfo()); + _log.LogError("PagedQuery failure: ldap connection is null\n{Info}", queryParameters.GetQueryInfo()); + yield return new LdapResult { + Error = "Connection is null", + QueryInfo = queryParameters.GetQueryInfo() + }; yield break; } //Pull the server name from the connection for retry logic later if (!connectionWrapper.GetServer(out var serverName)) { + _log.LogDebug("PagedQuery: Failed to get server value"); serverName = null; } if (!CreateSearchRequest(queryParameters, ref connectionWrapper, out var searchRequest)) { _log.LogError("PagedQuery failure: unable to resolve search base\n{Info}", queryParameters.GetQueryInfo()); + yield return new LdapResult { + Error = "Unable to create search request", + QueryInfo = queryParameters.GetQueryInfo() + }; yield break; } @@ -71,6 +84,7 @@ public async IAsyncEnumerable PagedQuery(LdapQueryParameters searchRequest.Controls.Add(pageControl); PageResultResponseControl pageResponse = null; + var busyRetryCount = 0; while (true) { if (cancellationToken.IsCancellationRequested) { @@ -96,58 +110,65 @@ public async IAsyncEnumerable PagedQuery(LdapQueryParameters queryParameters.GetQueryInfo()); yield break; } - /*A ServerDown exception indicates that our connection is no longer valid for one of many reasons. - However, this function is generally called by multiple threads, so we need to be careful in recreating - the connection. Using a semaphore, we can ensure that only one thread is actually recreating the connection - while the other threads that hit the ServerDown exception simply wait. The initial caller will hold the semaphore - and do a backoff delay before trying to make a new connection which will replace the existing connection in the - _ldapConnections cache. Other threads will retrieve the new connection from the cache instead of making a new one - This minimizes overhead of new connections while still fixing our core problem.*/ - - var backoffDelay = MinBackoffDelay; - var retryCount = 0; - - //Attempt to acquire a lock - if (Monitor.TryEnter(_lockObj)) { - //If we've acquired the lock, we want to immediately signal our reset event so everyone else waits - _connectionResetEvent.Reset(); - try { - //Sleep for our backoff - Thread.Sleep(backoffDelay); - //Explicitly skip the cache so we don't get the same connection back - connectionWrapper = GetLdapConnectionForServer(serverName) - conn = CreateNewConnection(domainName, globalCatalog, true).Connection; - if (conn == null) { - _log.LogError( - "Unable to create replacement ldap connection for ServerDown exception. Breaking loop"); - yield break; - } - - _log.LogInformation("Created new LDAP connection after receiving ServerDown from server"); + + /* + * Paged queries will not use the cached ldap connections, as the intention is to only have 1 or a couple of these queries running at once. + * The connection logic here is simplified accordingly + */ + for (var retryCount = 0; retryCount < MaxRetries; retryCount++) { + var backoffDelay = GetNextBackoff(retryCount); + await Task.Delay(backoffDelay, cancellationToken); + if (GetLdapConnectionForServer(serverName, out var newConnectionWrapper, + queryParameters.GlobalCatalog, + true)) { + newConnectionWrapper.CopyContexts(connectionWrapper); + connectionWrapper.Connection.Dispose(); + connectionWrapper = newConnectionWrapper; + _log.LogDebug( + "PagedQuery - Successfully created new ldap connection to {Server} after ServerDown", + serverName); + break; } - finally { - //Reset our event + release the lock - _connectionResetEvent.Set(); - Monitor.Exit(_lockObj); + + if (retryCount == MaxRetries - 1) { + _log.LogError("PagedQuery - Failed to get a new connection after ServerDown.\n{Info}", + queryParameters.GetQueryInfo()); + yield break; } } - else { - //If someone else is holding the reset event, we want to just wait and then pull the newly created connection out of the cache - //This event will be released after the first entrant thread is done making a new connection - //The thread.sleep is to prevent a potential, very unlikely race - Thread.Sleep(50); - _connectionResetEvent.WaitOne(); - conn = CreateNewConnection(domainName, globalCatalog).Connection; + } + catch (LdapException le) when (le.ErrorCode == (int)ResultCode.Busy && busyRetryCount < MaxRetries) { + /* + * If we get a busy error, we want to do an exponential backoff, but maintain the current connection + * The expectation is that given enough time, the server should stop being busy and service our query appropriately + */ + busyRetryCount++; + var backoffDelay = GetNextBackoff(busyRetryCount); + await Task.Delay(backoffDelay, cancellationToken); + } + catch (LdapException le) { + //No point in printing local exceptions because they're literally worthless + if (le.ErrorCode != (int)LdapErrorCodes.LocalError) + { + _log.LogWarning(le, + "LDAP Exception in Loop: {ErrorCode}. {ServerErrorMessage}. {Message}. Filter: {Filter}. Domain: {Domain}", + le.ErrorCode, le.ServerErrorMessage, le.Message, ldapFilter, domainName); } - backoffDelay = GetNextBackoff(retryCount); - continue; + yield break; } } } + + private static TimeSpan GetNextBackoff(int retryCount) + { + return TimeSpan.FromSeconds(Math.Min( + MinBackoffDelay.TotalSeconds * Math.Pow(BackoffDelayMultiplier, retryCount), + MaxBackoffDelay.TotalSeconds)); + } private bool CreateSearchRequest(LdapQueryParameters queryParameters, - ref LdapConnectionWrapperNew connectionWrapper, out SearchRequest searchRequest, bool paged = false) { + ref LdapConnectionWrapperNew connectionWrapper, out SearchRequest searchRequest) { string basePath; if (!string.IsNullOrWhiteSpace(queryParameters.SearchBase)) { basePath = queryParameters.SearchBase; diff --git a/src/CommonLib/LdapConnectionWrapperNew.cs b/src/CommonLib/LdapConnectionWrapperNew.cs index 7e8191ce..8a87ebdd 100644 --- a/src/CommonLib/LdapConnectionWrapperNew.cs +++ b/src/CommonLib/LdapConnectionWrapperNew.cs @@ -20,6 +20,13 @@ public LdapConnectionWrapperNew(LdapConnection connection, ISearchResultEntry en _searchResultEntry = entry; } + public void CopyContexts(LdapConnectionWrapperNew other) { + _domainSearchBase = other._domainSearchBase; + _configurationSearchBase = other._configurationSearchBase; + _schemaSearchBase = other._schemaSearchBase; + _server = other._server; + } + public bool GetServer(out string server) { if (_server != null) { server = _server; diff --git a/src/CommonLib/LdapQueryParameters.cs b/src/CommonLib/LdapQueryParameters.cs index 8859d3b2..be8785d2 100644 --- a/src/CommonLib/LdapQueryParameters.cs +++ b/src/CommonLib/LdapQueryParameters.cs @@ -15,6 +15,7 @@ public class LdapQueryParameters public bool IncludeDeleted { get; set; } = false; public string SearchBase { get; set; } public NamingContext NamingContext { get; set; } = NamingContext.Default; + public bool ThrowException { get; set; } = false; public string GetQueryInfo() { diff --git a/src/CommonLib/LdapResult.cs b/src/CommonLib/LdapResult.cs new file mode 100644 index 00000000..ffcedd03 --- /dev/null +++ b/src/CommonLib/LdapResult.cs @@ -0,0 +1,11 @@ +using System; + +namespace SharpHoundCommonLib; + +public class LdapResult +{ + public T Value { get; set; } + public string Error { get; set; } + public bool IsSuccess => Error == null; + public string QueryInfo { get; set; } +} \ No newline at end of file diff --git a/src/CommonLib/Processors/CertAbuseProcessor.cs b/src/CommonLib/Processors/CertAbuseProcessor.cs index faa93851..c66a7433 100644 --- a/src/CommonLib/Processors/CertAbuseProcessor.cs +++ b/src/CommonLib/Processors/CertAbuseProcessor.cs @@ -384,15 +384,15 @@ await SendComputerStatus(new CSVComputerStatus // TODO: Copied from URA processor. Find a way to have this function in a shared spot - public virtual Result OpenSamServer(string computerName) + public virtual LdapResult OpenSamServer(string computerName) { var result = SAMServer.OpenServer(computerName); if (result.IsFailed) { - return Result.Fail(result.SError); + return LdapResult.Fail(result.SError); } - return Result.Ok(result.Value); + return LdapResult.Ok(result.Value); } private async Task SendComputerStatus(CSVComputerStatus status) diff --git a/src/CommonLib/Processors/LocalGroupProcessor.cs b/src/CommonLib/Processors/LocalGroupProcessor.cs index 620da44e..416a0924 100644 --- a/src/CommonLib/Processors/LocalGroupProcessor.cs +++ b/src/CommonLib/Processors/LocalGroupProcessor.cs @@ -26,15 +26,15 @@ public LocalGroupProcessor(ILDAPUtils utils, ILogger log = null) public event ComputerStatusDelegate ComputerStatusEvent; - public virtual Result OpenSamServer(string computerName) + public virtual LdapResult OpenSamServer(string computerName) { var result = SAMServer.OpenServer(computerName); if (result.IsFailed) { - return Result.Fail(result.SError); + return LdapResult.Fail(result.SError); } - return Result.Ok(result.Value); + return LdapResult.Ok(result.Value); } public IAsyncEnumerable GetLocalGroups(ResolvedSearchResult result) diff --git a/src/CommonLib/Processors/UserRightsAssignmentProcessor.cs b/src/CommonLib/Processors/UserRightsAssignmentProcessor.cs index cedb73d0..bee3a570 100644 --- a/src/CommonLib/Processors/UserRightsAssignmentProcessor.cs +++ b/src/CommonLib/Processors/UserRightsAssignmentProcessor.cs @@ -25,12 +25,12 @@ public UserRightsAssignmentProcessor(ILDAPUtils utils, ILogger log = null) public event ComputerStatusDelegate ComputerStatusEvent; - public virtual Result OpenLSAPolicy(string computerName) + public virtual LdapResult OpenLSAPolicy(string computerName) { var result = LSAPolicy.OpenPolicy(computerName); - if (result.IsFailed) return Result.Fail(result.SError); + if (result.IsFailed) return LdapResult.Fail(result.SError); - return Result.Ok(result.Value); + return LdapResult.Ok(result.Value); } public IAsyncEnumerable GetUserRightsAssignments(ResolvedSearchResult result, From faf950188659ebe159e202112e0593a89b5dcb10 Mon Sep 17 00:00:00 2001 From: rvazarkar Date: Wed, 12 Jun 2024 14:44:55 -0400 Subject: [PATCH 06/68] wip: store off temp result in paged query --- src/CommonLib/LDAPUtilsNew.cs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/CommonLib/LDAPUtilsNew.cs b/src/CommonLib/LDAPUtilsNew.cs index f1ae92f0..1a533d34 100644 --- a/src/CommonLib/LDAPUtilsNew.cs +++ b/src/CommonLib/LDAPUtilsNew.cs @@ -85,12 +85,18 @@ public async IAsyncEnumerable> PagedQuery(LdapQue PageResultResponseControl pageResponse = null; var busyRetryCount = 0; + LdapResult tempResult = null; while (true) { if (cancellationToken.IsCancellationRequested) { yield break; } + if (tempResult != null) { + yield return tempResult; + yield break; + } + SearchResponse response; try { _log.LogTrace("Sending paged ldap request - {Info}", queryParameters.GetQueryInfo()); @@ -148,14 +154,11 @@ public async IAsyncEnumerable> PagedQuery(LdapQue } catch (LdapException le) { //No point in printing local exceptions because they're literally worthless - if (le.ErrorCode != (int)LdapErrorCodes.LocalError) - { - _log.LogWarning(le, - "LDAP Exception in Loop: {ErrorCode}. {ServerErrorMessage}. {Message}. Filter: {Filter}. Domain: {Domain}", - le.ErrorCode, le.ServerErrorMessage, le.Message, ldapFilter, domainName); - } - - yield break; + tempResult = new LdapResult() { + Error = + $"PagedQuery - Caught unrecoverable exception: {le.Message} (ServerMessage: {le.ServerErrorMessage}) (ErrorCode: {le.ErrorCode})", + QueryInfo = queryParameters.GetQueryInfo() + }; } } } From 3fcbc9dbc40fa907e1ccbd33c8c5b13b5c93a11c Mon Sep 17 00:00:00 2001 From: rvazarkar Date: Wed, 12 Jun 2024 15:58:57 -0400 Subject: [PATCH 07/68] wip: fill out the rest of pagedquery --- src/CommonLib/LDAPUtilsNew.cs | 40 ++++++++++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/src/CommonLib/LDAPUtilsNew.cs b/src/CommonLib/LDAPUtilsNew.cs index 1a533d34..28d842bd 100644 --- a/src/CommonLib/LDAPUtilsNew.cs +++ b/src/CommonLib/LDAPUtilsNew.cs @@ -65,6 +65,8 @@ public async IAsyncEnumerable> PagedQuery(LdapQue yield break; } + var connection = connectionWrapper.Connection; + //Pull the server name from the connection for retry logic later if (!connectionWrapper.GetServer(out var serverName)) { _log.LogDebug("PagedQuery: Failed to get server value"); @@ -97,10 +99,10 @@ public async IAsyncEnumerable> PagedQuery(LdapQue yield break; } - SearchResponse response; + SearchResponse response = null; try { _log.LogTrace("Sending paged ldap request - {Info}", queryParameters.GetQueryInfo()); - response = (SearchResponse)connectionWrapper.Connection.SendRequest(searchRequest); + response = (SearchResponse)connection.SendRequest(searchRequest); if (response != null) { pageResponse = (PageResultResponseControl)response.Controls .Where(x => x is PageResultResponseControl).DefaultIfEmpty(null).FirstOrDefault(); @@ -156,10 +158,42 @@ public async IAsyncEnumerable> PagedQuery(LdapQue //No point in printing local exceptions because they're literally worthless tempResult = new LdapResult() { Error = - $"PagedQuery - Caught unrecoverable exception: {le.Message} (ServerMessage: {le.ServerErrorMessage}) (ErrorCode: {le.ErrorCode})", + $"PagedQuery - Caught unrecoverable ldap exception: {le.Message} (ServerMessage: {le.ServerErrorMessage}) (ErrorCode: {le.ErrorCode})", + QueryInfo = queryParameters.GetQueryInfo() + }; + } + catch (Exception e) { + tempResult = new LdapResult { + Error = + $"PagedQuery - Caught unrecoverable exception: {e.Message}", QueryInfo = queryParameters.GetQueryInfo() }; } + + if (cancellationToken.IsCancellationRequested) { + yield break; + } + + //I'm not sure why this happens sometimes, but if we try the request again, it works sometimes, other times we get an exception + if (response == null || pageResponse == null) { + continue; + } + + foreach (ISearchResultEntry entry in response.Entries) { + if (cancellationToken.IsCancellationRequested) { + yield break; + } + + yield return new LdapResult() { + Value = entry + }; + } + + if (pageResponse.Cookie.Length == 0 || response.Entries.Count == 0 || + cancellationToken.IsCancellationRequested) + yield break; + + pageControl.Cookie = pageResponse.Cookie; } } From 6d5cd65563348c4194bc6aa1c19d87ef9e51a4bb Mon Sep 17 00:00:00 2001 From: rvazarkar Date: Tue, 18 Jun 2024 16:21:51 -0400 Subject: [PATCH 08/68] wip: implement connection pool --- src/CommonLib/LDAPUtilsNew.cs | 284 ++++++++++++---- src/CommonLib/LdapConnectionPool.cs | 376 ++++++++++++++++++++++ src/CommonLib/LdapConnectionWrapperNew.cs | 23 +- src/CommonLib/LdapQuerySetupResult.cs | 12 + 4 files changed, 632 insertions(+), 63 deletions(-) create mode 100644 src/CommonLib/LdapConnectionPool.cs create mode 100644 src/CommonLib/LdapQuerySetupResult.cs diff --git a/src/CommonLib/LDAPUtilsNew.cs b/src/CommonLib/LDAPUtilsNew.cs index 28d842bd..d8677da5 100644 --- a/src/CommonLib/LDAPUtilsNew.cs +++ b/src/CommonLib/LDAPUtilsNew.cs @@ -40,48 +40,179 @@ public class LDAPUtilsNew { private readonly object _lockObj = new(); private readonly ManualResetEvent _connectionResetEvent = new(false); - public async IAsyncEnumerable> PagedQuery(LdapQueryParameters queryParameters, + public async IAsyncEnumerable> Query(LdapQueryParameters queryParameters, [EnumeratorCancellation] CancellationToken cancellationToken = new()) { - //Always force create a new connection - var (success, connectionWrapper, message) = await GetLdapConnection(queryParameters.DomainName, - queryParameters.GlobalCatalog, true); - if (!success) { - _log.LogDebug("PagedQuery failure: unable to create a connection: {Reason}\n{Info}", message, + var setupResult = await SetupLdapQuery(queryParameters, true); + + if (!setupResult.Success) { + _log.LogInformation("PagedQuery - Failure during query setup: {Reason}\n{Info}", setupResult.Message, queryParameters.GetQueryInfo()); - yield return new LdapResult { - Error = $"Unable to create a connection: {message}", - QueryInfo = queryParameters.GetQueryInfo() - }; yield break; } - //This should never happen as far as I know, so just checking for safety - if (connectionWrapper == null) { - _log.LogError("PagedQuery failure: ldap connection is null\n{Info}", queryParameters.GetQueryInfo()); - yield return new LdapResult { - Error = "Connection is null", - QueryInfo = queryParameters.GetQueryInfo() - }; + var searchRequest = setupResult.SearchRequest; + var connectionWrapper = setupResult.ConnectionWrapper; + var connection = connectionWrapper.Connection; + var serverName = setupResult.Server; + + if (serverName == null) { + _log.LogWarning("PagedQuery - Failed to get a server name for connection, retry not possible"); + } + + if (cancellationToken.IsCancellationRequested) { yield break; } - var connection = connectionWrapper.Connection; + var queryRetryCount = 0; + var busyRetryCount = 0; + LdapResult tempResult = null; + while (queryRetryCount < MaxRetries) { + SearchResponse response = null; + try { + _log.LogTrace("Sending ldap request - {Info}", queryParameters.GetQueryInfo()); + response = (SearchResponse)connection.SendRequest(searchRequest); + } + catch (LdapException le) when (le.ErrorCode == (int)LdapErrorCodes.ServerDown) { + /* + * If we dont have a servername, we're not going to be able to re-establish a connection here. Page cookies are only valid for the server they were generated on. Bail out. + */ + if (serverName == null) { + _log.LogError( + "Query - Received server down exception without a known servername. Unable to generate new connection\n{Info}", + queryParameters.GetQueryInfo()); + yield break; + } + + /*A ServerDown exception indicates that our connection is no longer valid for one of many reasons. + However, this function is generally called by multiple threads, so we need to be careful in recreating + the connection. Using a semaphore, we can ensure that only one thread is actually recreating the connection + while the other threads that hit the ServerDown exception simply wait. The initial caller will hold the semaphore + and do a backoff delay before trying to make a new connection which will replace the existing connection in the + _ldapConnections cache. Other threads will retrieve the new connection from the cache instead of making a new one + This minimizes overhead of new connections while still fixing our core problem.*/ + + //Increment our query retry count + queryRetryCount++; + + //Attempt to acquire a lock + if (Monitor.TryEnter(_lockObj)) { + //Signal the reset event to ensure no everyone else waits + _connectionResetEvent.Reset(); + try { + //Try up to MaxRetries time to make a new connection, ensuring we're not using the cache + for (var retryCount = 0; retryCount < MaxRetries; retryCount++) { + var backoffDelay = GetNextBackoff(retryCount); + await Task.Delay(backoffDelay, cancellationToken); + var (success, newConnectionWrapper, _) = await GetLdapConnection(queryParameters, true); + if (success) { + newConnectionWrapper.CopyContexts(connectionWrapper); + connectionWrapper.Connection.Dispose(); + connectionWrapper = newConnectionWrapper; + break; + } + + if (retryCount == MaxRetries - 1) { + _log.LogError("Query - Failed to get a new connection after ServerDown.\n{Info}", + queryParameters.GetQueryInfo()); + yield break; + } + } + }finally{ + _connectionResetEvent.Set(); + Monitor.Exit(_lockObj); + } + } + else { + //If someone else is holding the reset event, we want to just wait and then pull the newly created connection out of the cache + //This event will be released after the first entrant thread is done making a new connection + //The thread.sleep is to prevent a potential, very unlikely race + Thread.Sleep(50); + _connectionResetEvent.WaitOne(); + + //At this point, our connection reset event should be tripped, and there should be a new connection on the cache + var (success, newConnectionWrapper, _) = await GetLdapConnection(queryParameters); + if (!success) { + _log.LogError("Query - Failed to recover from ServerDown error\n{Info}", queryParameters.GetQueryInfo()); + yield break; + } + + newConnectionWrapper.CopyContexts(connectionWrapper); + connectionWrapper = newConnectionWrapper; + connection = connectionWrapper.Connection; + } + } + catch (LdapException le) when (le.ErrorCode == (int)ResultCode.Busy && busyRetryCount < MaxRetries) { + /* + * If we get a busy error, we want to do an exponential backoff, but maintain the current connection + * The expectation is that given enough time, the server should stop being busy and service our query appropriately + */ + busyRetryCount++; + var backoffDelay = GetNextBackoff(busyRetryCount); + await Task.Delay(backoffDelay, cancellationToken); + } + catch (LdapException le) { + //No point in printing local exceptions because they're literally worthless + tempResult = new LdapResult() { + Error = + $"Query - Caught unrecoverable ldap exception: {le.Message} (ServerMessage: {le.ServerErrorMessage}) (ErrorCode: {le.ErrorCode})", + QueryInfo = queryParameters.GetQueryInfo() + }; + } + catch (Exception e) { + tempResult = new LdapResult { + Error = + $"PagedQuery - Caught unrecoverable exception: {e.Message}", + QueryInfo = queryParameters.GetQueryInfo() + }; + } - //Pull the server name from the connection for retry logic later - if (!connectionWrapper.GetServer(out var serverName)) { - _log.LogDebug("PagedQuery: Failed to get server value"); - serverName = null; + if (tempResult != null) { + yield return tempResult; + yield break; + } } + } - if (!CreateSearchRequest(queryParameters, ref connectionWrapper, out var searchRequest)) { - _log.LogError("PagedQuery failure: unable to resolve search base\n{Info}", queryParameters.GetQueryInfo()); - yield return new LdapResult { - Error = "Unable to create search request", - QueryInfo = queryParameters.GetQueryInfo() - }; + private bool SendSearchRequestWithRetryHandling(LdapConnectionWrapperNew connectionWrapper, + SearchRequest searchRequest, out SearchResponse searchResponse) { + for (var retryCount = 0; retryCount < MaxRetries; retryCount++) { + try { + searchResponse = (SearchResponse)connectionWrapper.Connection.SendRequest(searchRequest); + return true; + } + catch (LdapException le) when (le.ErrorCode == (int)LdapErrorCodes.ServerDown) { + + } + } + } + + private bool SendPagedSearchRequestWithRetryHandling(LdapConnectionWrapperNew connectionWrapper, + SearchRequest searchRequest, out SearchResponse searchResponse) { + + + + + } + + public async IAsyncEnumerable> PagedQuery(LdapQueryParameters queryParameters, + [EnumeratorCancellation] CancellationToken cancellationToken = new()) { + var setupResult = await SetupLdapQuery(queryParameters, true); + + if (!setupResult.Success) { + _log.LogInformation("PagedQuery - Failure during query setup: {Reason}\n{Info}", setupResult.Message, + queryParameters.GetQueryInfo()); yield break; } + var searchRequest = setupResult.SearchRequest; + var connectionWrapper = setupResult.ConnectionWrapper; + var connection = connectionWrapper.Connection; + var serverName = setupResult.Server; + + if (serverName == null) { + _log.LogWarning("PagedQuery - Failed to get a server name for connection, retry not possible"); + } + var pageControl = new PageResultRequestControl(500); searchRequest.Controls.Add(pageControl); @@ -188,17 +319,16 @@ public async IAsyncEnumerable> PagedQuery(LdapQue Value = entry }; } - + if (pageResponse.Cookie.Length == 0 || response.Entries.Count == 0 || cancellationToken.IsCancellationRequested) yield break; - + pageControl.Cookie = pageResponse.Cookie; } } - - private static TimeSpan GetNextBackoff(int retryCount) - { + + private static TimeSpan GetNextBackoff(int retryCount) { return TimeSpan.FromSeconds(Math.Min( MinBackoffDelay.TotalSeconds * Math.Pow(BackoffDelayMultiplier, retryCount), MaxBackoffDelay.TotalSeconds)); @@ -303,6 +433,44 @@ private bool } } + private async Task SetupLdapQuery(LdapQueryParameters queryParameters, + bool forceNewConnection = false) { + var result = new LdapQuerySetupResult(); + var (success, connectionWrapper, message) = await GetLdapConnection(queryParameters, forceNewConnection); + if (!success) { + result.Success = false; + result.Message = $"Unable to create a connection: {message}"; + return result; + } + + //This should never happen as far as I know, so just checking for safety + if (connectionWrapper.Connection == null) { + result.Success = false; + result.Message = $"Connection object is null"; + return result; + } + + if (!CreateSearchRequest(queryParameters, ref connectionWrapper, out var searchRequest)) { + result.Success = false; + result.Message = "Failed to create search request"; + return result; + } + + if (GetServerFromConnection(connectionWrapper.Connection, out var server)) { + result.Server = server; + } + + result.Success = true; + result.SearchRequest = searchRequest; + result.ConnectionWrapper = connectionWrapper; + return result; + } + + private Task<(bool Success, LdapConnectionWrapperNew Connection, string Message )> GetLdapConnection( + LdapQueryParameters queryParameters, bool forceCreateNewConnection = false) { + return GetLdapConnection(queryParameters.DomainName, queryParameters.GlobalCatalog, forceCreateNewConnection); + } + private async Task<(bool Success, LdapConnectionWrapperNew Connection, string Message )> GetLdapConnection( string domainName, bool globalCatalog = false, bool forceCreateNewConnection = false) { @@ -442,7 +610,8 @@ private bool } } - private async Task<(bool success, LdapConnectionWrapperNew connection)> CreateLDAPConnectionWithPortCheck(string target, bool globalCatalog) { + private async Task<(bool success, LdapConnectionWrapperNew connection)> CreateLDAPConnectionWithPortCheck( + string target, bool globalCatalog) { if (globalCatalog) { if (await _portScanner.CheckPort(target, _ldapConfig.GetGCPort(true)) || (!_ldapConfig.ForceSSL && await _portScanner.CheckPort(target, _ldapConfig.GetGCPort(false)))) @@ -457,20 +626,18 @@ await _portScanner.CheckPort(target, _ldapConfig.GetPort(false)))) return (false, null); } - private LdapConnectionWrapperNew CheckCacheConnection(LdapConnectionWrapperNew connectionWrapper, string domainName, bool globalCatalog, bool forceCreateNewConnection) - { + private LdapConnectionWrapperNew CheckCacheConnection(LdapConnectionWrapperNew connectionWrapper, string domainName, + bool globalCatalog, bool forceCreateNewConnection) { string cacheIdentifier; - if (_ldapConfig.Server != null) - { + if (_ldapConfig.Server != null) { cacheIdentifier = _ldapConfig.Server; } - else - { - if (!GetDomainSidFromDomainName(domainName, out cacheIdentifier)) - { + else { + if (!GetDomainSidFromDomainName(domainName, out cacheIdentifier)) { //This is kinda gross, but its another way to get the correct domain sid - if (!connectionWrapper.Connection.GetNamingContextSearchBase(NamingContext.Default, out var searchBase) || !GetDomainSidFromConnection(connectionWrapper.Connection, searchBase, out cacheIdentifier)) - { + if (!connectionWrapper.Connection.GetNamingContextSearchBase(NamingContext.Default, + out var searchBase) || !GetDomainSidFromConnection(connectionWrapper.Connection, searchBase, + out cacheIdentifier)) { /* * If we get here, we couldn't resolve a domain sid, which is hella bad, but we also want to keep from creating a shitton of new connections * Cache using the domainname and pray it all works out @@ -479,27 +646,22 @@ private LdapConnectionWrapperNew CheckCacheConnection(LdapConnectionWrapperNew c } } } - - if (forceCreateNewConnection) - { + + if (forceCreateNewConnection) { return _ldapConnectionCache.AddOrUpdate(cacheIdentifier, globalCatalog, connectionWrapper); } return _ldapConnectionCache.TryAdd(cacheIdentifier, globalCatalog, connectionWrapper); } - - private bool GetCachedConnection(string domain, bool globalCatalog, out LdapConnectionWrapperNew connection) - { + + private bool GetCachedConnection(string domain, bool globalCatalog, out LdapConnectionWrapperNew connection) { //If server is set via our config, we'll always just use this as the cache key - if (_ldapConfig.Server != null) - { + if (_ldapConfig.Server != null) { return _ldapConnectionCache.TryGet(_ldapConfig.Server, globalCatalog, out connection); } - - if (GetDomainSidFromDomainName(domain, out var domainSid)) - { - if (_ldapConnectionCache.TryGet(domainSid, globalCatalog, out connection)) - { + + if (GetDomainSidFromDomainName(domain, out var domainSid)) { + if (_ldapConnectionCache.TryGet(domainSid, globalCatalog, out connection)) { return true; } } @@ -599,10 +761,8 @@ private LdapConnection CreateBaseConnection(string directoryIdentifier, bool ssl connection.SessionOptions.ReferralChasing = ReferralChasingOptions.None; if (ssl) connection.SessionOptions.SecureSocketLayer = true; - if (_ldapConfig.DisableSigning) { - connection.SessionOptions.Sealing = false; - connection.SessionOptions.Signing = false; - } + connection.SessionOptions.Sealing = !_ldapConfig.DisableSigning; + connection.SessionOptions.Signing = !_ldapConfig.DisableSigning; if (_ldapConfig.DisableCertVerification) connection.SessionOptions.VerifyServerCertificate = (_, _) => true; @@ -683,7 +843,7 @@ private bool TestLdapConnection(LdapConnection connection, string identifier, ou return true; } - private SearchRequest CreateSearchRequest(string distinguishedName, string ldapFilter, SearchScope searchScope, + public static SearchRequest CreateSearchRequest(string distinguishedName, string ldapFilter, SearchScope searchScope, string[] attributes) { var searchRequest = new SearchRequest(distinguishedName, ldapFilter, searchScope, attributes); diff --git a/src/CommonLib/LdapConnectionPool.cs b/src/CommonLib/LdapConnectionPool.cs new file mode 100644 index 00000000..c85bdd17 --- /dev/null +++ b/src/CommonLib/LdapConnectionPool.cs @@ -0,0 +1,376 @@ +using System; +using System.Collections.Concurrent; +using System.DirectoryServices.ActiveDirectory; +using System.DirectoryServices.Protocols; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using SharpHoundCommonLib.Enums; +using SharpHoundCommonLib.Exceptions; +using SharpHoundCommonLib.LDAPQueries; +using SharpHoundCommonLib.Processors; +using SharpHoundRPC.NetAPINative; + +namespace SharpHoundCommonLib; + +public class LdapConnectionPool : IDisposable{ + private readonly ConcurrentBag _connections; + private readonly ConcurrentBag _globalCatalogConnection; + private static readonly ConcurrentDictionary DomainCache = new(); + private readonly SemaphoreSlim _semaphore; + private readonly string _identifier; + private readonly LDAPConfig _ldapConfig; + private readonly ILogger _log; + private readonly PortScanner _portScanner; + private readonly NativeMethods _nativeMethods; + + public LdapConnectionPool(string identifier, LDAPConfig config, int maxConnections = 10, PortScanner scanner = null, NativeMethods nativeMethods = null, ILogger log = null) { + _connections = new ConcurrentBag(); + _globalCatalogConnection = new ConcurrentBag(); + _semaphore = new SemaphoreSlim(maxConnections, maxConnections); + _identifier = identifier; + _ldapConfig = config; + _log = log ?? Logging.LogProvider.CreateLogger("LdapConnectionPool"); + _portScanner = scanner ?? new PortScanner(); + _nativeMethods = nativeMethods ?? new NativeMethods(); + } + + public async Task<(bool Success, LdapConnectionWrapperNew connectionWrapper, string Message)> GetConnectionAsync() { + await _semaphore.WaitAsync(); + if (!_connections.TryTake(out var connectionWrapper)) { + var (success, connection, message) = await CreateNewConnection(); + if (!success) { + return (false, null, message); + } + + connectionWrapper = connection; + } + + return (true, connectionWrapper, null); + } + + public async Task<(bool Success, LdapConnectionWrapperNew connectionWrapper, string Message)> + GetConnectionForSpecificServerAsync(string server, bool globalCatalog) { + await _semaphore.WaitAsync(); + + return CreateNewConnectionForServer(server, globalCatalog); + } + + public async Task<(bool Success, LdapConnectionWrapperNew connectionWrapper, string Message)> GetGlobalCatalogConnectionAsync() { + await _semaphore.WaitAsync(); + if (!_globalCatalogConnection.TryTake(out var connectionWrapper)) { + var (success, connection, message) = await CreateNewConnection(true); + if (!success) { + return (false, null, message); + } + + connectionWrapper = connection; + } + + return (true, connectionWrapper, null); + } + + public void ReleaseConnection(LdapConnectionWrapperNew connectionWrapper, bool returnToPool = true) { + if (returnToPool) { + if (connectionWrapper.GlobalCatalog) { + _globalCatalogConnection.Add(connectionWrapper); + } + else { + _connections.Add(connectionWrapper); + } + } + else { + connectionWrapper.Connection.Dispose(); + } + + _semaphore.Release(); + } + + public void Dispose() { + while (_connections.TryTake(out var wrapper)) { + wrapper.Connection.Dispose(); + } + } + + private async Task<(bool Success, LdapConnectionWrapperNew Connection, string Message)> CreateNewConnection(bool globalCatalog = false) { + if (!string.IsNullOrWhiteSpace(_ldapConfig.Server)) { + return CreateNewConnectionForServer(_ldapConfig.Server, globalCatalog); + } + + if (CreateLdapConnection(_identifier.ToUpper().Trim(), globalCatalog, out var connectionWrapper)) { + _log.LogDebug("Successfully created ldap connection for domain: {Domain} using strategy 1", _identifier); + return (true, connectionWrapper, ""); + } + + string tempDomainName; + + var dsGetDcNameResult = _nativeMethods.CallDsGetDcName(null, _identifier, + (uint)(NetAPIEnums.DSGETDCNAME_FLAGS.DS_FORCE_REDISCOVERY | + NetAPIEnums.DSGETDCNAME_FLAGS.DS_RETURN_DNS_NAME | + NetAPIEnums.DSGETDCNAME_FLAGS.DS_DIRECTORY_SERVICE_REQUIRED)); + if (dsGetDcNameResult.IsSuccess) { + tempDomainName = dsGetDcNameResult.Value.DomainName; + + if (!tempDomainName.Equals(_identifier, StringComparison.OrdinalIgnoreCase) && + CreateLdapConnection(tempDomainName, globalCatalog, out connectionWrapper)) { + _log.LogDebug( + "Successfully created ldap connection for domain: {Domain} using strategy 2 with name {NewName}", + _identifier, tempDomainName); + return (true, connectionWrapper, ""); + } + + var server = dsGetDcNameResult.Value.DomainControllerName.TrimStart('\\'); + + var result = + await CreateLDAPConnectionWithPortCheck(server, globalCatalog); + if (result.success) { + _log.LogDebug( + "Successfully created ldap connection for domain: {Domain} using strategy 3 to server {Server}", + _identifier, server); + return (true, result.connection, ""); + } + } + + if (!GetDomain(_identifier, out var domainObject) || domainObject.Name == null) { + //If we don't get a result here, we effectively have no other ways to resolve this domain, so we'll just have to exit out + _log.LogDebug( + "Could not get domain object from GetDomain, unable to create ldap connection for domain {Domain}", + _identifier); + return (false, null, "Unable to get domain object for further strategies"); + } + tempDomainName = domainObject.Name.ToUpper().Trim(); + + if (!tempDomainName.Equals(_identifier, StringComparison.OrdinalIgnoreCase) && + CreateLdapConnection(tempDomainName, globalCatalog, out connectionWrapper)) { + _log.LogDebug( + "Successfully created ldap connection for domain: {Domain} using strategy 4 with name {NewName}", + _identifier, tempDomainName); + return (true, connectionWrapper, ""); + } + + var primaryDomainController = domainObject.PdcRoleOwner.Name; + var portConnectionResult = + await CreateLDAPConnectionWithPortCheck(primaryDomainController, globalCatalog); + if (portConnectionResult.success) { + _log.LogDebug( + "Successfully created ldap connection for domain: {Domain} using strategy 5 with to pdc {Server}", + _identifier, primaryDomainController); + return (true, connectionWrapper, ""); + } + + foreach (DomainController dc in domainObject.DomainControllers) { + portConnectionResult = + await CreateLDAPConnectionWithPortCheck(primaryDomainController, globalCatalog); + if (portConnectionResult.success) { + _log.LogDebug( + "Successfully created ldap connection for domain: {Domain} using strategy 6 with to pdc {Server}", + _identifier, primaryDomainController); + return (true, connectionWrapper, ""); + } + } + + return (false, null, "All attempted connections failed"); + } + + private (bool Success, LdapConnectionWrapperNew Connection, string Message ) CreateNewConnectionForServer(string identifier, bool globalCatalog = false) { + if (CreateLdapConnection(identifier, globalCatalog, out var serverConnection)) { + return (true, serverConnection, ""); + } + + return (false, null, $"Failed to create ldap connection for {identifier}"); + } + + private bool CreateLdapConnection(string target, bool globalCatalog, + out LdapConnectionWrapperNew connection) { + var baseConnection = CreateBaseConnection(target, true, globalCatalog); + if (TestLdapConnection(baseConnection, out var result)) { + connection = new LdapConnectionWrapperNew(baseConnection, result.SearchResultEntry, globalCatalog, _identifier); + return true; + } + + try { + baseConnection.Dispose(); + } + catch { + //this is just in case + } + + if (_ldapConfig.ForceSSL) { + connection = null; + return false; + } + + baseConnection = CreateBaseConnection(target, false, globalCatalog); + if (TestLdapConnection(baseConnection, out result)) { + connection = new LdapConnectionWrapperNew(baseConnection, result.SearchResultEntry, globalCatalog, _identifier); + return true; + } + + try { + baseConnection.Dispose(); + } + catch { + //this is just in case + } + + connection = null; + return false; + } + + private LdapConnection CreateBaseConnection(string directoryIdentifier, bool ssl, + bool globalCatalog) { + var port = globalCatalog ? _ldapConfig.GetGCPort(ssl) : _ldapConfig.GetPort(ssl); + var identifier = new LdapDirectoryIdentifier(directoryIdentifier, port, false, false); + var connection = new LdapConnection(identifier) { Timeout = new TimeSpan(0, 0, 5, 0) }; + + //These options are important! + connection.SessionOptions.ProtocolVersion = 3; + //Referral chasing does not work with paged searches + connection.SessionOptions.ReferralChasing = ReferralChasingOptions.None; + if (ssl) connection.SessionOptions.SecureSocketLayer = true; + + connection.SessionOptions.Sealing = !_ldapConfig.DisableSigning; + connection.SessionOptions.Signing = !_ldapConfig.DisableSigning; + + if (_ldapConfig.DisableCertVerification) + connection.SessionOptions.VerifyServerCertificate = (_, _) => true; + + if (_ldapConfig.Username != null) { + var cred = new NetworkCredential(_ldapConfig.Username, _ldapConfig.Password); + connection.Credential = cred; + } + + connection.AuthType = _ldapConfig.AuthType; + + return connection; + } + + /// + /// Tests whether an LDAP connection is working + /// + /// The ldap connection object to test + /// The results fo the connection test + /// True if connection was successful, false otherwise + /// Something is wrong with the supplied credentials + /// + /// A connection "succeeded" but no data was returned. This can be related to + /// kerberos auth across trusts or just simply lack of permissions + /// + private bool TestLdapConnection(LdapConnection connection, out LdapConnectionTestResult testResult) { + testResult = new LdapConnectionTestResult(); + try { + //Attempt an initial bind. If this fails, likely auth is invalid, or its not a valid target + connection.Bind(); + } + catch (LdapException e) { + //TODO: Maybe look at this and find a better way? + if (e.ErrorCode is (int)LdapErrorCodes.InvalidCredentials or (int)ResultCode.InappropriateAuthentication) { + connection.Dispose(); + throw new LdapAuthenticationException(e); + } + + testResult.Message = e.Message; + testResult.ErrorCode = e.ErrorCode; + return false; + } + catch (Exception e) { + testResult.Message = e.Message; + return false; + } + + SearchResponse response; + try { + //Do an initial search request to get the rootDSE + //This ldap filter is equivalent to (objectclass=*) + var searchRequest = LDAPUtilsNew.CreateSearchRequest("", new LDAPFilter().AddAllObjects().GetFilter(), + SearchScope.Base, null); + + response = (SearchResponse)connection.SendRequest(searchRequest); + } + catch (LdapException e) { + /* + * If we can't send the initial search request, its unlikely any other search requests will work so we will immediately return false + */ + testResult.Message = e.Message; + testResult.ErrorCode = e.ErrorCode; + return false; + } + + if (response?.Entries == null || response.Entries.Count == 0) { + /* + * This can happen for one of two reasons, either we dont have permission to query AD or we're authenticating + * across external trusts with kerberos authentication without Forest Search Order properly configured. + * Either way, this connection isn't useful for us because we're not going to get data, so return false + */ + + connection.Dispose(); + throw new NoLdapDataException(); + } + + testResult.SearchResultEntry = new SearchResultEntryWrapper(response.Entries[0]); + testResult.Message = ""; + return true; + } + + private class LdapConnectionTestResult { + public string Message { get; set; } + public ISearchResultEntry SearchResultEntry { get; set; } + public int ErrorCode { get; set; } + } + + private async Task<(bool success, LdapConnectionWrapperNew connection)> CreateLDAPConnectionWithPortCheck( + string target, bool globalCatalog) { + if (globalCatalog) { + if (await _portScanner.CheckPort(target, _ldapConfig.GetGCPort(true)) || (!_ldapConfig.ForceSSL && + await _portScanner.CheckPort(target, _ldapConfig.GetGCPort(false)))) + return (CreateLdapConnection(target, true, out var connection), connection); + } + else { + if (await _portScanner.CheckPort(target, _ldapConfig.GetPort(true)) || (!_ldapConfig.ForceSSL && + await _portScanner.CheckPort(target, _ldapConfig.GetPort(false)))) + return (CreateLdapConnection(target, true, out var connection), connection); + } + + return (false, null); + } + + /// + /// Attempts to get the Domain object representing the target domain. If null is specified for the domain name, gets + /// the user's current domain + /// + /// + /// + /// + private bool GetDomain(string domainName, out Domain domain) { + var cacheKey = domainName; + if (DomainCache.TryGetValue(cacheKey, out domain)) return true; + + try { + DirectoryContext context; + if (_ldapConfig.Username != null) + context = domainName != null + ? new DirectoryContext(DirectoryContextType.Domain, domainName, _ldapConfig.Username, + _ldapConfig.Password) + : new DirectoryContext(DirectoryContextType.Domain, _ldapConfig.Username, + _ldapConfig.Password); + else + context = domainName != null + ? new DirectoryContext(DirectoryContextType.Domain, domainName) + : new DirectoryContext(DirectoryContextType.Domain); + + domain = Domain.GetDomain(context); + if (domain == null) return false; + DomainCache.TryAdd(cacheKey, domain); + return true; + } + catch (Exception e) { + _log.LogDebug(e, "GetDomain call failed for domain name {Name}", domainName); + return false; + } + } +} + +//TESTLAB +//TESTLAB.LOCAL +//PRIMARY.TESTLAB.LOCAL \ No newline at end of file diff --git a/src/CommonLib/LdapConnectionWrapperNew.cs b/src/CommonLib/LdapConnectionWrapperNew.cs index 8a87ebdd..1c3392da 100644 --- a/src/CommonLib/LdapConnectionWrapperNew.cs +++ b/src/CommonLib/LdapConnectionWrapperNew.cs @@ -12,12 +12,18 @@ public class LdapConnectionWrapperNew private string _configurationSearchBase; private string _schemaSearchBase; private string _server; + public string Guid { get; set; } private const string Unknown = "UNKNOWN"; + public bool GlobalCatalog; + public string PoolIdentifier; - public LdapConnectionWrapperNew(LdapConnection connection, ISearchResultEntry entry) + public LdapConnectionWrapperNew(LdapConnection connection, ISearchResultEntry entry, bool globalCatalog, string poolIdentifier) { Connection = connection; _searchResultEntry = entry; + Guid = new Guid().ToString(); + GlobalCatalog = globalCatalog; + PoolIdentifier = poolIdentifier; } public void CopyContexts(LdapConnectionWrapperNew other) { @@ -89,4 +95,19 @@ public void SaveContext(NamingContext context, string searchBase) throw new ArgumentOutOfRangeException(nameof(context), context, null); } } + + protected bool Equals(LdapConnectionWrapperNew other) { + return Guid == other.Guid; + } + + public override bool Equals(object obj) { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((LdapConnectionWrapperNew)obj); + } + + public override int GetHashCode() { + return (Guid != null ? Guid.GetHashCode() : 0); + } } \ No newline at end of file diff --git a/src/CommonLib/LdapQuerySetupResult.cs b/src/CommonLib/LdapQuerySetupResult.cs new file mode 100644 index 00000000..4c8ed2cc --- /dev/null +++ b/src/CommonLib/LdapQuerySetupResult.cs @@ -0,0 +1,12 @@ +using System.DirectoryServices; +using System.DirectoryServices.Protocols; + +namespace SharpHoundCommonLib; + +public class LdapQuerySetupResult { + public LdapConnectionWrapperNew ConnectionWrapper { get; set; } + public SearchRequest SearchRequest { get; set; } + public string Server { get; set; } + public bool Success { get; set; } + public string Message { get; set; } +} \ No newline at end of file From 284aa99dcf64b461d0aa3757b90d5a5a1c6b6e57 Mon Sep 17 00:00:00 2001 From: rvazarkar Date: Thu, 20 Jun 2024 10:24:35 -0400 Subject: [PATCH 09/68] wip: add connection pool manager --- src/CommonLib/ConnectionPoolManager.cs | 102 +++++++++++++++++++++++++ src/CommonLib/LDAPUtilsNew.cs | 28 ++++++- src/CommonLib/LdapConnectionPool.cs | 37 +-------- 3 files changed, 130 insertions(+), 37 deletions(-) create mode 100644 src/CommonLib/ConnectionPoolManager.cs diff --git a/src/CommonLib/ConnectionPoolManager.cs b/src/CommonLib/ConnectionPoolManager.cs new file mode 100644 index 00000000..a671d16e --- /dev/null +++ b/src/CommonLib/ConnectionPoolManager.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections.Concurrent; +using System.DirectoryServices; +using System.Security.Principal; +using System.Threading.Tasks; + +namespace SharpHoundCommonLib; + +public class ConnectionPoolManager : IDisposable{ + private readonly ConcurrentDictionary _pools = new(); + private readonly LDAPConfig _ldapConfig; + private readonly string[] _translateNames = { "Administrator", "admin" }; + + public ConnectionPoolManager(LDAPConfig config) { + _ldapConfig = config; + } + + public async Task<(bool Success, LdapConnectionWrapperNew connectionWrapper, string Message)> GetLdapConnection( + string identifier, bool globalCatalog) { + var resolved = ResolveIdentifier(identifier); + + if (!_pools.TryGetValue(identifier, out var pool)) { + pool = new LdapConnectionPool(resolved, _ldapConfig); + _pools.TryAdd(identifier, pool); + } + + if (globalCatalog) { + return await pool.GetGlobalCatalogConnectionAsync(); + } + return await pool.GetConnectionAsync(); + } + + public async Task<(bool Success, LdapConnectionWrapperNew connectionWrapper, string Message)> GetLdapConnectionForServer( + string identifier, string server, bool globalCatalog) { + var resolved = ResolveIdentifier(identifier); + + if (!_pools.TryGetValue(identifier, out var pool)) { + pool = new LdapConnectionPool(resolved, _ldapConfig); + _pools.TryAdd(identifier, pool); + } + + return await pool.GetConnectionForSpecificServerAsync(server, globalCatalog); + } + + private string ResolveIdentifier(string identifier) { + return GetDomainSidFromDomainName(identifier, out var sid) ? sid : identifier; + } + + private bool GetDomainSidFromDomainName(string domainName, out string domainSid) { + if (Cache.GetDomainSidMapping(domainName, out domainSid)) return true; + + try { + var entry = new DirectoryEntry($"LDAP://{domainName}"); + //Force load objectsid into the object cache + entry.RefreshCache(new[] { "objectSid" }); + var sid = entry.GetSid(); + if (sid != null) { + Cache.AddDomainSidMapping(domainName, sid); + domainSid = sid; + return true; + } + } + catch { + //we expect this to fail sometimes + } + + if (LDAPUtilsNew.GetDomain(domainName, _ldapConfig, out var domainObject)) + try { + domainSid = domainObject.GetDirectoryEntry().GetSid(); + if (domainSid != null) { + Cache.AddDomainSidMapping(domainName, domainSid); + return true; + } + } + catch { + //we expect this to fail sometimes (not sure why, but better safe than sorry) + } + + foreach (var name in _translateNames) + try { + var account = new NTAccount(domainName, name); + var sid = (SecurityIdentifier)account.Translate(typeof(SecurityIdentifier)); + domainSid = sid.AccountDomainSid.ToString(); + Cache.AddDomainSidMapping(domainName, domainSid); + return true; + } + catch { + //We expect this to fail if the username doesn't exist in the domain + } + + return false; + } + + public void Dispose() { + foreach (var kv in _pools) + { + kv.Value.Dispose(); + } + + _pools.Clear(); + } +} \ No newline at end of file diff --git a/src/CommonLib/LDAPUtilsNew.cs b/src/CommonLib/LDAPUtilsNew.cs index d8677da5..ca183358 100644 --- a/src/CommonLib/LDAPUtilsNew.cs +++ b/src/CommonLib/LDAPUtilsNew.cs @@ -25,7 +25,7 @@ public class LDAPUtilsNew { //This cache is indexed by domain sid private readonly ConcurrentDictionary _dcInfoCache = new(); private readonly DCConnectionCache _ldapConnectionCache = new(); - private readonly ConcurrentDictionary _domainCache = new(); + private static readonly ConcurrentDictionary _domainCache = new(); private readonly ILogger _log; private readonly NativeMethods _nativeMethods; private readonly string _nullCacheKey = Guid.NewGuid().ToString(); @@ -934,6 +934,32 @@ public bool GetDomain(string domainName, out Domain domain) { } } + public static bool GetDomain(string domainName, LDAPConfig ldapConfig, out Domain domain) { + if (_domainCache.TryGetValue(domainName, out domain)) return true; + + try { + DirectoryContext context; + if (ldapConfig.Username != null) + context = domainName != null + ? new DirectoryContext(DirectoryContextType.Domain, domainName, ldapConfig.Username, + ldapConfig.Password) + : new DirectoryContext(DirectoryContextType.Domain, ldapConfig.Username, + ldapConfig.Password); + else + context = domainName != null + ? new DirectoryContext(DirectoryContextType.Domain, domainName) + : new DirectoryContext(DirectoryContextType.Domain); + + domain = Domain.GetDomain(context); + if (domain == null) return false; + _domainCache.TryAdd(domainName, domain); + return true; + } + catch (Exception e) { + return false; + } + } + /// /// Attempts to get the Domain object representing the target domain. If null is specified for the domain name, gets /// the user's current domain diff --git a/src/CommonLib/LdapConnectionPool.cs b/src/CommonLib/LdapConnectionPool.cs index c85bdd17..6cbc1796 100644 --- a/src/CommonLib/LdapConnectionPool.cs +++ b/src/CommonLib/LdapConnectionPool.cs @@ -132,7 +132,7 @@ public void Dispose() { } } - if (!GetDomain(_identifier, out var domainObject) || domainObject.Name == null) { + if (!LDAPUtilsNew.GetDomain(_identifier, _ldapConfig, out var domainObject) || domainObject.Name == null) { //If we don't get a result here, we effectively have no other ways to resolve this domain, so we'll just have to exit out _log.LogDebug( "Could not get domain object from GetDomain, unable to create ldap connection for domain {Domain}", @@ -334,41 +334,6 @@ await _portScanner.CheckPort(target, _ldapConfig.GetPort(false)))) return (false, null); } - - /// - /// Attempts to get the Domain object representing the target domain. If null is specified for the domain name, gets - /// the user's current domain - /// - /// - /// - /// - private bool GetDomain(string domainName, out Domain domain) { - var cacheKey = domainName; - if (DomainCache.TryGetValue(cacheKey, out domain)) return true; - - try { - DirectoryContext context; - if (_ldapConfig.Username != null) - context = domainName != null - ? new DirectoryContext(DirectoryContextType.Domain, domainName, _ldapConfig.Username, - _ldapConfig.Password) - : new DirectoryContext(DirectoryContextType.Domain, _ldapConfig.Username, - _ldapConfig.Password); - else - context = domainName != null - ? new DirectoryContext(DirectoryContextType.Domain, domainName) - : new DirectoryContext(DirectoryContextType.Domain); - - domain = Domain.GetDomain(context); - if (domain == null) return false; - DomainCache.TryAdd(cacheKey, domain); - return true; - } - catch (Exception e) { - _log.LogDebug(e, "GetDomain call failed for domain name {Name}", domainName); - return false; - } - } } //TESTLAB From 3a6e16510a16c8992e3785d5c1c8cc2cd0e17d59 Mon Sep 17 00:00:00 2001 From: rvazarkar Date: Mon, 24 Jun 2024 14:43:11 -0400 Subject: [PATCH 10/68] wip: more wip stuff --- src/CommonLib/ConnectionPoolManager.cs | 38 +- src/CommonLib/Extensions.cs | 18 + src/CommonLib/Helpers.cs | 9 + src/CommonLib/LDAPUtilsNew.cs | 994 ------------------- src/CommonLib/LdapConnectionPool.cs | 24 +- src/CommonLib/LdapConnectionWrapperNew.cs | 11 +- src/CommonLib/LdapResult.cs | 17 +- src/CommonLib/LdapUtilsNew.cs | 756 ++++++++++++++ src/CommonLib/Result.cs | 41 + src/CommonLib/SearchResultEntryWrapperNew.cs | 79 ++ src/CommonLib/SharpHoundCommonLib.csproj | 5 +- 11 files changed, 973 insertions(+), 1019 deletions(-) delete mode 100644 src/CommonLib/LDAPUtilsNew.cs create mode 100644 src/CommonLib/LdapUtilsNew.cs create mode 100644 src/CommonLib/Result.cs create mode 100644 src/CommonLib/SearchResultEntryWrapperNew.cs diff --git a/src/CommonLib/ConnectionPoolManager.cs b/src/CommonLib/ConnectionPoolManager.cs index a671d16e..e74526bc 100644 --- a/src/CommonLib/ConnectionPoolManager.cs +++ b/src/CommonLib/ConnectionPoolManager.cs @@ -3,6 +3,8 @@ using System.DirectoryServices; using System.Security.Principal; using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using SharpHoundCommonLib.Processors; namespace SharpHoundCommonLib; @@ -10,9 +12,24 @@ public class ConnectionPoolManager : IDisposable{ private readonly ConcurrentDictionary _pools = new(); private readonly LDAPConfig _ldapConfig; private readonly string[] _translateNames = { "Administrator", "admin" }; + private readonly ConcurrentDictionary _resolvedIdentifiers = new(StringComparer.OrdinalIgnoreCase); + private readonly ILogger _log; + private readonly PortScanner _portScanner; - public ConnectionPoolManager(LDAPConfig config) { + public ConnectionPoolManager(LDAPConfig config, ILogger log = null, PortScanner scanner = null) { _ldapConfig = config; + _log = log ?? Logging.LogProvider.CreateLogger("ConnectionPoolManager"); + _portScanner = scanner ?? new PortScanner(); + } + + public void ReleaseConnection(LdapConnectionWrapperNew connectionWrapper, bool connectionFaulted = false) { + //I dont think this is possible, but at least account for it + if (!_pools.TryGetValue(connectionWrapper.PoolIdentifier, out var pool)) { + connectionWrapper.Connection.Dispose(); + return; + } + + pool.ReleaseConnection(connectionWrapper, connectionFaulted); } public async Task<(bool Success, LdapConnectionWrapperNew connectionWrapper, string Message)> GetLdapConnection( @@ -20,7 +37,7 @@ public ConnectionPoolManager(LDAPConfig config) { var resolved = ResolveIdentifier(identifier); if (!_pools.TryGetValue(identifier, out var pool)) { - pool = new LdapConnectionPool(resolved, _ldapConfig); + pool = new LdapConnectionPool(resolved, _ldapConfig,scanner: _portScanner); _pools.TryAdd(identifier, pool); } @@ -35,7 +52,7 @@ public ConnectionPoolManager(LDAPConfig config) { var resolved = ResolveIdentifier(identifier); if (!_pools.TryGetValue(identifier, out var pool)) { - pool = new LdapConnectionPool(resolved, _ldapConfig); + pool = new LdapConnectionPool(resolved, _ldapConfig,scanner: _portScanner); _pools.TryAdd(identifier, pool); } @@ -43,7 +60,18 @@ public ConnectionPoolManager(LDAPConfig config) { } private string ResolveIdentifier(string identifier) { - return GetDomainSidFromDomainName(identifier, out var sid) ? sid : identifier; + if (_resolvedIdentifiers.TryGetValue(identifier, out var resolved)) { + return resolved; + } + + + if (GetDomainSidFromDomainName(identifier, out var sid)) { + _log.LogDebug("Resolved identifier {Identifier} to {Resolved}", identifier, sid); + _resolvedIdentifiers.TryAdd(identifier, sid); + return sid; + } + + return identifier; } private bool GetDomainSidFromDomainName(string domainName, out string domainSid) { @@ -64,7 +92,7 @@ private bool GetDomainSidFromDomainName(string domainName, out string domainSid) //we expect this to fail sometimes } - if (LDAPUtilsNew.GetDomain(domainName, _ldapConfig, out var domainObject)) + if (LdapUtilsNew.GetDomain(domainName, _ldapConfig, out var domainObject)) try { domainSid = domainObject.GetDirectoryEntry().GetSid(); if (domainSid != null) { diff --git a/src/CommonLib/Extensions.cs b/src/CommonLib/Extensions.cs index 49c59269..c7c2622f 100644 --- a/src/CommonLib/Extensions.cs +++ b/src/CommonLib/Extensions.cs @@ -67,6 +67,24 @@ public static string LdapValue(this Guid s) return output; } + public static string GetProperty(this DirectoryEntry entry, string propertyName) { + try { + if (!entry.Properties.Contains(propertyName)) { + return null; + } + } + catch { + return null; + } + + var s = entry.Properties[propertyName][0]; + return s switch + { + string st => st, + _ => null + }; + } + public static string GetSid(this DirectoryEntry result) { try diff --git a/src/CommonLib/Helpers.cs b/src/CommonLib/Helpers.cs index 7ac7cf7d..76f2b0ee 100644 --- a/src/CommonLib/Helpers.cs +++ b/src/CommonLib/Helpers.cs @@ -164,6 +164,15 @@ public static string DistinguishedNameToDomain(string distinguishedName) temp = DCReplaceRegex.Replace(temp, "").Replace(",", ".").ToUpper(); return temp; } + + /// + /// Converts a domain name to a distinguished name using simple string substitution + /// + /// + /// + public static string DomainNameToDistinguishedName(string domainName) { + return $"DC={domainName.Replace(".", ",DC=")}"; + } /// /// Strips a "serviceprincipalname" entry down to just its hostname diff --git a/src/CommonLib/LDAPUtilsNew.cs b/src/CommonLib/LDAPUtilsNew.cs deleted file mode 100644 index ca183358..00000000 --- a/src/CommonLib/LDAPUtilsNew.cs +++ /dev/null @@ -1,994 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.DirectoryServices; -using System.DirectoryServices.ActiveDirectory; -using System.DirectoryServices.Protocols; -using System.Linq; -using System.Net; -using System.Runtime.CompilerServices; -using System.Security.Principal; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Logging; -using SharpHoundCommonLib.Enums; -using SharpHoundCommonLib.Exceptions; -using SharpHoundCommonLib.LDAPQueries; -using SharpHoundCommonLib.Processors; -using SharpHoundRPC.NetAPINative; -using SearchScope = System.DirectoryServices.Protocols.SearchScope; -using SecurityMasks = System.DirectoryServices.Protocols.SecurityMasks; - -namespace SharpHoundCommonLib; - -public class LDAPUtilsNew { - //This cache is indexed by domain sid - private readonly ConcurrentDictionary _dcInfoCache = new(); - private readonly DCConnectionCache _ldapConnectionCache = new(); - private static readonly ConcurrentDictionary _domainCache = new(); - private readonly ILogger _log; - private readonly NativeMethods _nativeMethods; - private readonly string _nullCacheKey = Guid.NewGuid().ToString(); - private readonly PortScanner _portScanner; - private readonly string[] _translateNames = { "Administrator", "admin" }; - private readonly LDAPConfig _ldapConfig = new(); - - private static readonly TimeSpan MinBackoffDelay = TimeSpan.FromSeconds(2); - private static readonly TimeSpan MaxBackoffDelay = TimeSpan.FromSeconds(20); - private const int BackoffDelayMultiplier = 2; - private const int MaxRetries = 3; - private readonly object _lockObj = new(); - private readonly ManualResetEvent _connectionResetEvent = new(false); - - public async IAsyncEnumerable> Query(LdapQueryParameters queryParameters, - [EnumeratorCancellation] CancellationToken cancellationToken = new()) { - var setupResult = await SetupLdapQuery(queryParameters, true); - - if (!setupResult.Success) { - _log.LogInformation("PagedQuery - Failure during query setup: {Reason}\n{Info}", setupResult.Message, - queryParameters.GetQueryInfo()); - yield break; - } - - var searchRequest = setupResult.SearchRequest; - var connectionWrapper = setupResult.ConnectionWrapper; - var connection = connectionWrapper.Connection; - var serverName = setupResult.Server; - - if (serverName == null) { - _log.LogWarning("PagedQuery - Failed to get a server name for connection, retry not possible"); - } - - if (cancellationToken.IsCancellationRequested) { - yield break; - } - - var queryRetryCount = 0; - var busyRetryCount = 0; - LdapResult tempResult = null; - while (queryRetryCount < MaxRetries) { - SearchResponse response = null; - try { - _log.LogTrace("Sending ldap request - {Info}", queryParameters.GetQueryInfo()); - response = (SearchResponse)connection.SendRequest(searchRequest); - } - catch (LdapException le) when (le.ErrorCode == (int)LdapErrorCodes.ServerDown) { - /* - * If we dont have a servername, we're not going to be able to re-establish a connection here. Page cookies are only valid for the server they were generated on. Bail out. - */ - if (serverName == null) { - _log.LogError( - "Query - Received server down exception without a known servername. Unable to generate new connection\n{Info}", - queryParameters.GetQueryInfo()); - yield break; - } - - /*A ServerDown exception indicates that our connection is no longer valid for one of many reasons. - However, this function is generally called by multiple threads, so we need to be careful in recreating - the connection. Using a semaphore, we can ensure that only one thread is actually recreating the connection - while the other threads that hit the ServerDown exception simply wait. The initial caller will hold the semaphore - and do a backoff delay before trying to make a new connection which will replace the existing connection in the - _ldapConnections cache. Other threads will retrieve the new connection from the cache instead of making a new one - This minimizes overhead of new connections while still fixing our core problem.*/ - - //Increment our query retry count - queryRetryCount++; - - //Attempt to acquire a lock - if (Monitor.TryEnter(_lockObj)) { - //Signal the reset event to ensure no everyone else waits - _connectionResetEvent.Reset(); - try { - //Try up to MaxRetries time to make a new connection, ensuring we're not using the cache - for (var retryCount = 0; retryCount < MaxRetries; retryCount++) { - var backoffDelay = GetNextBackoff(retryCount); - await Task.Delay(backoffDelay, cancellationToken); - var (success, newConnectionWrapper, _) = await GetLdapConnection(queryParameters, true); - if (success) { - newConnectionWrapper.CopyContexts(connectionWrapper); - connectionWrapper.Connection.Dispose(); - connectionWrapper = newConnectionWrapper; - break; - } - - if (retryCount == MaxRetries - 1) { - _log.LogError("Query - Failed to get a new connection after ServerDown.\n{Info}", - queryParameters.GetQueryInfo()); - yield break; - } - } - }finally{ - _connectionResetEvent.Set(); - Monitor.Exit(_lockObj); - } - } - else { - //If someone else is holding the reset event, we want to just wait and then pull the newly created connection out of the cache - //This event will be released after the first entrant thread is done making a new connection - //The thread.sleep is to prevent a potential, very unlikely race - Thread.Sleep(50); - _connectionResetEvent.WaitOne(); - - //At this point, our connection reset event should be tripped, and there should be a new connection on the cache - var (success, newConnectionWrapper, _) = await GetLdapConnection(queryParameters); - if (!success) { - _log.LogError("Query - Failed to recover from ServerDown error\n{Info}", queryParameters.GetQueryInfo()); - yield break; - } - - newConnectionWrapper.CopyContexts(connectionWrapper); - connectionWrapper = newConnectionWrapper; - connection = connectionWrapper.Connection; - } - } - catch (LdapException le) when (le.ErrorCode == (int)ResultCode.Busy && busyRetryCount < MaxRetries) { - /* - * If we get a busy error, we want to do an exponential backoff, but maintain the current connection - * The expectation is that given enough time, the server should stop being busy and service our query appropriately - */ - busyRetryCount++; - var backoffDelay = GetNextBackoff(busyRetryCount); - await Task.Delay(backoffDelay, cancellationToken); - } - catch (LdapException le) { - //No point in printing local exceptions because they're literally worthless - tempResult = new LdapResult() { - Error = - $"Query - Caught unrecoverable ldap exception: {le.Message} (ServerMessage: {le.ServerErrorMessage}) (ErrorCode: {le.ErrorCode})", - QueryInfo = queryParameters.GetQueryInfo() - }; - } - catch (Exception e) { - tempResult = new LdapResult { - Error = - $"PagedQuery - Caught unrecoverable exception: {e.Message}", - QueryInfo = queryParameters.GetQueryInfo() - }; - } - - if (tempResult != null) { - yield return tempResult; - yield break; - } - } - } - - private bool SendSearchRequestWithRetryHandling(LdapConnectionWrapperNew connectionWrapper, - SearchRequest searchRequest, out SearchResponse searchResponse) { - for (var retryCount = 0; retryCount < MaxRetries; retryCount++) { - try { - searchResponse = (SearchResponse)connectionWrapper.Connection.SendRequest(searchRequest); - return true; - } - catch (LdapException le) when (le.ErrorCode == (int)LdapErrorCodes.ServerDown) { - - } - } - } - - private bool SendPagedSearchRequestWithRetryHandling(LdapConnectionWrapperNew connectionWrapper, - SearchRequest searchRequest, out SearchResponse searchResponse) { - - - - - } - - public async IAsyncEnumerable> PagedQuery(LdapQueryParameters queryParameters, - [EnumeratorCancellation] CancellationToken cancellationToken = new()) { - var setupResult = await SetupLdapQuery(queryParameters, true); - - if (!setupResult.Success) { - _log.LogInformation("PagedQuery - Failure during query setup: {Reason}\n{Info}", setupResult.Message, - queryParameters.GetQueryInfo()); - yield break; - } - - var searchRequest = setupResult.SearchRequest; - var connectionWrapper = setupResult.ConnectionWrapper; - var connection = connectionWrapper.Connection; - var serverName = setupResult.Server; - - if (serverName == null) { - _log.LogWarning("PagedQuery - Failed to get a server name for connection, retry not possible"); - } - - var pageControl = new PageResultRequestControl(500); - searchRequest.Controls.Add(pageControl); - - PageResultResponseControl pageResponse = null; - var busyRetryCount = 0; - LdapResult tempResult = null; - - while (true) { - if (cancellationToken.IsCancellationRequested) { - yield break; - } - - if (tempResult != null) { - yield return tempResult; - yield break; - } - - SearchResponse response = null; - try { - _log.LogTrace("Sending paged ldap request - {Info}", queryParameters.GetQueryInfo()); - response = (SearchResponse)connection.SendRequest(searchRequest); - if (response != null) { - pageResponse = (PageResultResponseControl)response.Controls - .Where(x => x is PageResultResponseControl).DefaultIfEmpty(null).FirstOrDefault(); - } - } - catch (LdapException le) when (le.ErrorCode == (int)LdapErrorCodes.ServerDown) { - /* - * If we dont have a servername, we're not going to be able to re-establish a connection here. Page cookies are only valid for the server they were generated on. Bail out. - */ - if (serverName == null) { - _log.LogError( - "PagedQuery - Received server down exception without a known servername. Unable to generate new connection\n{Info}", - queryParameters.GetQueryInfo()); - yield break; - } - - /* - * Paged queries will not use the cached ldap connections, as the intention is to only have 1 or a couple of these queries running at once. - * The connection logic here is simplified accordingly - */ - for (var retryCount = 0; retryCount < MaxRetries; retryCount++) { - var backoffDelay = GetNextBackoff(retryCount); - await Task.Delay(backoffDelay, cancellationToken); - if (GetLdapConnectionForServer(serverName, out var newConnectionWrapper, - queryParameters.GlobalCatalog, - true)) { - newConnectionWrapper.CopyContexts(connectionWrapper); - connectionWrapper.Connection.Dispose(); - connectionWrapper = newConnectionWrapper; - _log.LogDebug( - "PagedQuery - Successfully created new ldap connection to {Server} after ServerDown", - serverName); - break; - } - - if (retryCount == MaxRetries - 1) { - _log.LogError("PagedQuery - Failed to get a new connection after ServerDown.\n{Info}", - queryParameters.GetQueryInfo()); - yield break; - } - } - } - catch (LdapException le) when (le.ErrorCode == (int)ResultCode.Busy && busyRetryCount < MaxRetries) { - /* - * If we get a busy error, we want to do an exponential backoff, but maintain the current connection - * The expectation is that given enough time, the server should stop being busy and service our query appropriately - */ - busyRetryCount++; - var backoffDelay = GetNextBackoff(busyRetryCount); - await Task.Delay(backoffDelay, cancellationToken); - } - catch (LdapException le) { - //No point in printing local exceptions because they're literally worthless - tempResult = new LdapResult() { - Error = - $"PagedQuery - Caught unrecoverable ldap exception: {le.Message} (ServerMessage: {le.ServerErrorMessage}) (ErrorCode: {le.ErrorCode})", - QueryInfo = queryParameters.GetQueryInfo() - }; - } - catch (Exception e) { - tempResult = new LdapResult { - Error = - $"PagedQuery - Caught unrecoverable exception: {e.Message}", - QueryInfo = queryParameters.GetQueryInfo() - }; - } - - if (cancellationToken.IsCancellationRequested) { - yield break; - } - - //I'm not sure why this happens sometimes, but if we try the request again, it works sometimes, other times we get an exception - if (response == null || pageResponse == null) { - continue; - } - - foreach (ISearchResultEntry entry in response.Entries) { - if (cancellationToken.IsCancellationRequested) { - yield break; - } - - yield return new LdapResult() { - Value = entry - }; - } - - if (pageResponse.Cookie.Length == 0 || response.Entries.Count == 0 || - cancellationToken.IsCancellationRequested) - yield break; - - pageControl.Cookie = pageResponse.Cookie; - } - } - - private static TimeSpan GetNextBackoff(int retryCount) { - return TimeSpan.FromSeconds(Math.Min( - MinBackoffDelay.TotalSeconds * Math.Pow(BackoffDelayMultiplier, retryCount), - MaxBackoffDelay.TotalSeconds)); - } - - private bool CreateSearchRequest(LdapQueryParameters queryParameters, - ref LdapConnectionWrapperNew connectionWrapper, out SearchRequest searchRequest) { - string basePath; - if (!string.IsNullOrWhiteSpace(queryParameters.SearchBase)) { - basePath = queryParameters.SearchBase; - } - else if (!connectionWrapper.GetSearchBase(queryParameters.NamingContext, out basePath)) { - string tempPath; - if (CallDsGetDcName(queryParameters.DomainName, out var info) && info != null) { - tempPath = DomainNameToDistinguishedName(info.Value.DomainName); - connectionWrapper.SaveContext(queryParameters.NamingContext, basePath); - } - else if (GetDomain(queryParameters.DomainName, out var domainObject)) { - tempPath = DomainNameToDistinguishedName(domainObject.Name); - } - else { - searchRequest = null; - return false; - } - - basePath = queryParameters.NamingContext switch { - NamingContext.Configuration => $"CN=Configuration,{tempPath}", - NamingContext.Schema => $"CN=Schema,CN=Configuration,{tempPath}", - NamingContext.Default => tempPath, - _ => throw new ArgumentOutOfRangeException() - }; - - connectionWrapper.SaveContext(queryParameters.NamingContext, basePath); - } - - searchRequest = new SearchRequest(basePath, queryParameters.LDAPFilter, queryParameters.SearchScope, - queryParameters.Attributes); - searchRequest.Controls.Add(new SearchOptionsControl(SearchOption.DomainScope)); - if (queryParameters.IncludeDeleted) { - searchRequest.Controls.Add(new ShowDeletedControl()); - } - - if (queryParameters.IncludeSecurityDescriptor) { - searchRequest.Controls.Add(new SecurityDescriptorFlagControl { - SecurityMasks = SecurityMasks.Dacl | SecurityMasks.Owner - }); - } - - return true; - } - - private static string DomainNameToDistinguishedName(string domainName) { - return $"DC={domainName.Replace(".", ",DC=")}"; - } - - private bool CallDsGetDcName(string domainName, out NetAPIStructs.DomainControllerInfo? info) { - if (_dcInfoCache.TryGetValue(domainName.ToUpper().Trim(), out info)) return info != null; - - var apiResult = _nativeMethods.CallDsGetDcName(null, domainName, - (uint)(NetAPIEnums.DSGETDCNAME_FLAGS.DS_FORCE_REDISCOVERY | - NetAPIEnums.DSGETDCNAME_FLAGS.DS_RETURN_DNS_NAME | - NetAPIEnums.DSGETDCNAME_FLAGS.DS_DIRECTORY_SERVICE_REQUIRED)); - - if (apiResult.IsFailed) { - _dcInfoCache.TryAdd(domainName.ToUpper().Trim(), null); - return false; - } - - info = apiResult.Value; - return true; - } - - private bool - GetLdapConnectionForServer(string serverName, out LdapConnectionWrapperNew connectionWrapper, - bool globalCatalog = false, bool forceCreateNewConnection = false) { - if (string.IsNullOrWhiteSpace(serverName)) { - throw new ArgumentNullException(nameof(serverName)); - } - - try { - if (!forceCreateNewConnection && - GetCachedConnection(serverName, globalCatalog, out connectionWrapper)) - return true; - - if (CreateLdapConnection(serverName, globalCatalog, out connectionWrapper)) { - return true; - } - - connectionWrapper = null; - return false; - } - catch (LdapAuthenticationException e) { - _log.LogError("Error connecting to {Domain}: credentials are invalid (error code {ErrorCode})", serverName, - e.LdapException.ErrorCode); - connectionWrapper = null; - return false; - } - catch (NoLdapDataException) { - _log.LogError("No data returned for domain {Domain} during initial LDAP test.", serverName); - connectionWrapper = null; - return false; - } - } - - private async Task SetupLdapQuery(LdapQueryParameters queryParameters, - bool forceNewConnection = false) { - var result = new LdapQuerySetupResult(); - var (success, connectionWrapper, message) = await GetLdapConnection(queryParameters, forceNewConnection); - if (!success) { - result.Success = false; - result.Message = $"Unable to create a connection: {message}"; - return result; - } - - //This should never happen as far as I know, so just checking for safety - if (connectionWrapper.Connection == null) { - result.Success = false; - result.Message = $"Connection object is null"; - return result; - } - - if (!CreateSearchRequest(queryParameters, ref connectionWrapper, out var searchRequest)) { - result.Success = false; - result.Message = "Failed to create search request"; - return result; - } - - if (GetServerFromConnection(connectionWrapper.Connection, out var server)) { - result.Server = server; - } - - result.Success = true; - result.SearchRequest = searchRequest; - result.ConnectionWrapper = connectionWrapper; - return result; - } - - private Task<(bool Success, LdapConnectionWrapperNew Connection, string Message )> GetLdapConnection( - LdapQueryParameters queryParameters, bool forceCreateNewConnection = false) { - return GetLdapConnection(queryParameters.DomainName, queryParameters.GlobalCatalog, forceCreateNewConnection); - } - - private async Task<(bool Success, LdapConnectionWrapperNew Connection, string Message )> GetLdapConnection( - string domainName, bool globalCatalog = false, - bool forceCreateNewConnection = false) { - //TODO: Pull out individual strategies into single functions for readability and better logging - if (string.IsNullOrWhiteSpace(domainName)) throw new ArgumentNullException(nameof(domainName)); - - try { - /* - * If a server is explicitly set on the config, we should only test this config - */ - LdapConnectionWrapperNew connectionWrapper; - if (_ldapConfig.Server != null) { - _log.LogWarning("Server is overridden via config, creating connection to {Server}", _ldapConfig.Server); - if (!forceCreateNewConnection && - GetCachedConnection(domainName, globalCatalog, out connectionWrapper)) - return (true, connectionWrapper, ""); - - if (CreateLdapConnection(_ldapConfig.Server, globalCatalog, out var serverConnection)) { - connectionWrapper = CheckCacheConnection(serverConnection, domainName, globalCatalog, - forceCreateNewConnection); - return (true, connectionWrapper, ""); - } - - return (false, null, "Failed to connect to specified server"); - } - - if (!forceCreateNewConnection && GetCachedConnection(domainName, globalCatalog, out connectionWrapper)) - return (true, connectionWrapper, ""); - - _log.LogInformation("No cached connection found for domain {Domain}, attempting a new connection", - domainName); - - if (CreateLdapConnection(domainName.ToUpper().Trim(), globalCatalog, out connectionWrapper)) { - _log.LogDebug("Successfully created ldap connection for domain: {Domain} using strategy 1", domainName); - connectionWrapper = - CheckCacheConnection(connectionWrapper, domainName, globalCatalog, forceCreateNewConnection); - return (true, connectionWrapper, ""); - } - - string tempDomainName; - - var dsGetDcNameResult = _nativeMethods.CallDsGetDcName(null, domainName, - (uint)(NetAPIEnums.DSGETDCNAME_FLAGS.DS_FORCE_REDISCOVERY | - NetAPIEnums.DSGETDCNAME_FLAGS.DS_RETURN_DNS_NAME | - NetAPIEnums.DSGETDCNAME_FLAGS.DS_DIRECTORY_SERVICE_REQUIRED)); - if (dsGetDcNameResult.IsSuccess) { - tempDomainName = dsGetDcNameResult.Value.DomainName; - if (!forceCreateNewConnection && - GetCachedConnection(tempDomainName, globalCatalog, out connectionWrapper)) - return (true, connectionWrapper, ""); - - if (!tempDomainName.Equals(domainName, StringComparison.OrdinalIgnoreCase) && - CreateLdapConnection(tempDomainName, globalCatalog, out connectionWrapper)) { - _log.LogDebug( - "Successfully created ldap connection for domain: {Domain} using strategy 2 with name {NewName}", - domainName, tempDomainName); - connectionWrapper = CheckCacheConnection(connectionWrapper, tempDomainName, globalCatalog, - forceCreateNewConnection); - return (true, connectionWrapper, ""); - } - - var server = dsGetDcNameResult.Value.DomainControllerName.TrimStart('\\'); - - var result = - await CreateLDAPConnectionWithPortCheck(server, globalCatalog); - if (result.success) { - _log.LogDebug( - "Successfully created ldap connection for domain: {Domain} using strategy 3 to server {Server}", - domainName, server); - connectionWrapper = CheckCacheConnection(result.connection, tempDomainName, globalCatalog, - forceCreateNewConnection); - return (true, connectionWrapper, ""); - } - } - - if (!GetDomain(domainName, out var domainObject) || domainObject.Name == null) { - //If we don't get a result here, we effectively have no other ways to resolve this domain, so we'll just have to exit out - _log.LogDebug( - "Could not get domain object from GetDomain, unable to create ldap connection for domain {Domain}", - domainName); - return (false, null, "Unable to get domain object for further methods"); - } - - tempDomainName = domainObject.Name.ToUpper().Trim(); - if (!forceCreateNewConnection && - GetCachedConnection(tempDomainName, globalCatalog, out connectionWrapper)) - return (true, connectionWrapper, ""); - - if (!tempDomainName.Equals(domainName, StringComparison.OrdinalIgnoreCase) && - CreateLdapConnection(tempDomainName, globalCatalog, out connectionWrapper)) { - _log.LogDebug( - "Successfully created ldap connection for domain: {Domain} using strategy 4 with name {NewName}", - domainName, tempDomainName); - connectionWrapper = - CheckCacheConnection(connectionWrapper, tempDomainName, globalCatalog, forceCreateNewConnection); - return (true, connectionWrapper, ""); - } - - var primaryDomainController = domainObject.PdcRoleOwner.Name; - var portConnectionResult = - await CreateLDAPConnectionWithPortCheck(primaryDomainController, globalCatalog); - if (portConnectionResult.success) { - _log.LogDebug( - "Successfully created ldap connection for domain: {Domain} using strategy 5 with to pdc {Server}", - domainName, primaryDomainController); - connectionWrapper = CheckCacheConnection(portConnectionResult.connection, tempDomainName, globalCatalog, - forceCreateNewConnection); - return (true, connectionWrapper, ""); - } - - //Loop over all other domain controllers and see if we can make a good connection to any - foreach (DomainController dc in domainObject.DomainControllers) { - portConnectionResult = - await CreateLDAPConnectionWithPortCheck(primaryDomainController, globalCatalog); - if (portConnectionResult.success) { - _log.LogDebug( - "Successfully created ldap connection for domain: {Domain} using strategy 6 with to pdc {Server}", - domainName, primaryDomainController); - connectionWrapper = CheckCacheConnection(portConnectionResult.connection, tempDomainName, - globalCatalog, - forceCreateNewConnection); - return (true, connectionWrapper, ""); - } - } - - _log.LogWarning("Exhausted all potential methods of creating ldap connection to {DomainName}", domainName); - return (false, null, "All attempted connections failed"); - } - catch (LdapAuthenticationException e) { - _log.LogError("Error connecting to {Domain}: credentials are invalid (error code {ErrorCode})", domainName, - e.LdapException.ErrorCode); - return (false, null, "Invalid credentials for connection"); - } - catch (NoLdapDataException) { - _log.LogError("No data returned for domain {Domain} during initial LDAP test.", domainName); - return (false, null, "No data returned from ldap connection"); - } - } - - private async Task<(bool success, LdapConnectionWrapperNew connection)> CreateLDAPConnectionWithPortCheck( - string target, bool globalCatalog) { - if (globalCatalog) { - if (await _portScanner.CheckPort(target, _ldapConfig.GetGCPort(true)) || (!_ldapConfig.ForceSSL && - await _portScanner.CheckPort(target, _ldapConfig.GetGCPort(false)))) - return (CreateLdapConnection(target, true, out var connection), connection); - } - else { - if (await _portScanner.CheckPort(target, _ldapConfig.GetPort(true)) || (!_ldapConfig.ForceSSL && - await _portScanner.CheckPort(target, _ldapConfig.GetPort(false)))) - return (CreateLdapConnection(target, true, out var connection), connection); - } - - return (false, null); - } - - private LdapConnectionWrapperNew CheckCacheConnection(LdapConnectionWrapperNew connectionWrapper, string domainName, - bool globalCatalog, bool forceCreateNewConnection) { - string cacheIdentifier; - if (_ldapConfig.Server != null) { - cacheIdentifier = _ldapConfig.Server; - } - else { - if (!GetDomainSidFromDomainName(domainName, out cacheIdentifier)) { - //This is kinda gross, but its another way to get the correct domain sid - if (!connectionWrapper.Connection.GetNamingContextSearchBase(NamingContext.Default, - out var searchBase) || !GetDomainSidFromConnection(connectionWrapper.Connection, searchBase, - out cacheIdentifier)) { - /* - * If we get here, we couldn't resolve a domain sid, which is hella bad, but we also want to keep from creating a shitton of new connections - * Cache using the domainname and pray it all works out - */ - cacheIdentifier = domainName; - } - } - } - - if (forceCreateNewConnection) { - return _ldapConnectionCache.AddOrUpdate(cacheIdentifier, globalCatalog, connectionWrapper); - } - - return _ldapConnectionCache.TryAdd(cacheIdentifier, globalCatalog, connectionWrapper); - } - - private bool GetCachedConnection(string domain, bool globalCatalog, out LdapConnectionWrapperNew connection) { - //If server is set via our config, we'll always just use this as the cache key - if (_ldapConfig.Server != null) { - return _ldapConnectionCache.TryGet(_ldapConfig.Server, globalCatalog, out connection); - } - - if (GetDomainSidFromDomainName(domain, out var domainSid)) { - if (_ldapConnectionCache.TryGet(domainSid, globalCatalog, out connection)) { - return true; - } - } - - return _ldapConnectionCache.TryGet(domain, globalCatalog, out connection); - } - - private bool GetDomainSidFromConnection(LdapConnection connection, string searchBase, out string domainSid) { - try { - //This ldap filter searches for domain controllers - //Searches for any accounts with a UAC value inclusive of 8192 bitwise - //8192 is the flag for SERVER_TRUST_ACCOUNT, which is set only on Domain Controllers - var searchRequest = new SearchRequest(searchBase, - "(&(objectCategory=computer)(userAccountControl:1.2.840.113556.1.4.803:=8192))", - SearchScope.Subtree, "objectsid"); - - var response = (SearchResponse)connection.SendRequest(searchRequest); - if (response == null || response.Entries.Count == 0) { - domainSid = ""; - return false; - } - - var entry = response.Entries[0]; - var sid = entry.GetSid(); - domainSid = sid.Substring(0, sid.LastIndexOf('-')).ToUpper(); - return true; - } - catch (LdapException) { - _log.LogWarning("Failed grabbing domainsid from ldap for {domain}", searchBase); - domainSid = ""; - return false; - } - } - - private bool GetServerFromConnection(LdapConnection connection, out string server) { - var searchRequest = new SearchRequest("", new LDAPFilter().AddAllObjects().GetFilter(), - SearchScope.Base, null); - searchRequest.Controls.Add(new SearchOptionsControl(SearchOption.DomainScope)); - - var response = (SearchResponse)connection.SendRequest(searchRequest); - if (response?.Entries == null || response.Entries.Count == 0) { - server = ""; - return false; - } - - var entry = response.Entries[0]; - server = entry.GetProperty(LDAPProperties.DNSHostName); - return server != null; - } - - private bool CreateLdapConnection(string target, bool globalCatalog, - out LdapConnectionWrapperNew connection) { - var baseConnection = CreateBaseConnection(target, true, globalCatalog); - if (TestLdapConnection(baseConnection, target, out var entry)) { - connection = new LdapConnectionWrapperNew(baseConnection, entry); - return true; - } - - try { - baseConnection.Dispose(); - } - catch { - //this is just in case - } - - if (_ldapConfig.ForceSSL) { - connection = null; - return false; - } - - baseConnection = CreateBaseConnection(target, false, globalCatalog); - if (TestLdapConnection(baseConnection, target, out entry)) { - connection = new LdapConnectionWrapperNew(baseConnection, entry); - return true; - } - - try { - baseConnection.Dispose(); - } - catch { - //this is just in case - } - - connection = null; - return false; - } - - private LdapConnection CreateBaseConnection(string directoryIdentifier, bool ssl, - bool globalCatalog) { - var port = globalCatalog ? _ldapConfig.GetGCPort(ssl) : _ldapConfig.GetPort(ssl); - var identifier = new LdapDirectoryIdentifier(directoryIdentifier, port, false, false); - var connection = new LdapConnection(identifier) { Timeout = new TimeSpan(0, 0, 5, 0) }; - - //These options are important! - connection.SessionOptions.ProtocolVersion = 3; - //Referral chasing does not work with paged searches - connection.SessionOptions.ReferralChasing = ReferralChasingOptions.None; - if (ssl) connection.SessionOptions.SecureSocketLayer = true; - - connection.SessionOptions.Sealing = !_ldapConfig.DisableSigning; - connection.SessionOptions.Signing = !_ldapConfig.DisableSigning; - - if (_ldapConfig.DisableCertVerification) - connection.SessionOptions.VerifyServerCertificate = (_, _) => true; - - if (_ldapConfig.Username != null) { - var cred = new NetworkCredential(_ldapConfig.Username, _ldapConfig.Password); - connection.Credential = cred; - } - - connection.AuthType = _ldapConfig.AuthType; - - return connection; - } - - /// - /// Tests whether an LDAP connection is working - /// - /// - /// - /// The rootdse object for this connection if successful - /// - /// Something is wrong with the supplied credentials - /// - /// A connection "succeeded" but no data was returned. This can be related to - /// kerberos auth across trusts or just simply lack of permissions - /// - private bool TestLdapConnection(LdapConnection connection, string identifier, out ISearchResultEntry entry) { - try { - //Attempt an initial bind. If this fails, likely auth is invalid, or its not a valid target - connection.Bind(); - } - catch (LdapException e) { - //TODO: Maybe look at this and find a better way? - if (e.ErrorCode is (int)LdapErrorCodes.InvalidCredentials or (int)ResultCode.InappropriateAuthentication) { - connection.Dispose(); - throw new LdapAuthenticationException(e); - } - - entry = null; - return false; - } - catch (Exception e) { - entry = null; - return false; - } - - SearchResponse response; - try { - //Do an initial search request to get the rootDSE - //This ldap filter is equivalent to (objectclass=*) - var searchRequest = CreateSearchRequest("", new LDAPFilter().AddAllObjects().GetFilter(), - SearchScope.Base, null); - - response = (SearchResponse)connection.SendRequest(searchRequest); - } - catch (LdapException e) { - /* - * If we can't send the initial search request, its unlikely any other search requests will work so we will immediately return false - */ - _log.LogDebug(e, "TestLdapConnection failed during search request against target {Target}", identifier); - entry = null; - return false; - } - - if (response?.Entries == null || response.Entries.Count == 0) { - /* - * This can happen for one of two reasons, either we dont have permission to query AD or we're authenticating - * across external trusts with kerberos authentication without Forest Search Order properly configured. - * Either way, this connection isn't useful for us because we're not going to get data, so return false - */ - - _log.LogDebug("TestLdapConnection failed to return results against target {Target}", identifier); - connection.Dispose(); - throw new NoLdapDataException(); - } - - entry = new SearchResultEntryWrapper(response.Entries[0]); - return true; - } - - public static SearchRequest CreateSearchRequest(string distinguishedName, string ldapFilter, SearchScope searchScope, - string[] attributes) { - var searchRequest = new SearchRequest(distinguishedName, ldapFilter, - searchScope, attributes); - searchRequest.Controls.Add(new SearchOptionsControl(SearchOption.DomainScope)); - return searchRequest; - } - - public bool GetDomainSidFromDomainName(string domainName, out string domainSid) { - if (Cache.GetDomainSidMapping(domainName, out domainSid)) return true; - - try { - var entry = new DirectoryEntry($"LDAP://{domainName}"); - //Force load objectsid into the object cache - entry.RefreshCache(new[] { "objectSid" }); - var sid = entry.GetSid(); - if (sid != null) { - Cache.AddDomainSidMapping(domainName, sid); - domainSid = sid; - return true; - } - } - catch { - //we expect this to fail sometimes - } - - if (GetDomain(domainName, out var domainObject)) - try { - domainSid = domainObject.GetDirectoryEntry().GetSid(); - if (domainSid != null) { - Cache.AddDomainSidMapping(domainName, domainSid); - return true; - } - } - catch { - //we expect this to fail sometimes (not sure why, but better safe than sorry) - } - - foreach (var name in _translateNames) - try { - var account = new NTAccount(domainName, name); - var sid = (SecurityIdentifier)account.Translate(typeof(SecurityIdentifier)); - domainSid = sid.AccountDomainSid.ToString(); - Cache.AddDomainSidMapping(domainName, domainSid); - return true; - } - catch { - //We expect this to fail if the username doesn't exist in the domain - } - - return false; - } - - private string ResolveDomainCrossRef(string domainName) { - } - - /// - /// Attempts to get the Domain object representing the target domain. If null is specified for the domain name, gets - /// the user's current domain - /// - /// - /// - /// - public bool GetDomain(string domainName, out Domain domain) { - var cacheKey = domainName ?? _nullCacheKey; - if (_domainCache.TryGetValue(cacheKey, out domain)) return true; - - try { - DirectoryContext context; - if (_ldapConfig.Username != null) - context = domainName != null - ? new DirectoryContext(DirectoryContextType.Domain, domainName, _ldapConfig.Username, - _ldapConfig.Password) - : new DirectoryContext(DirectoryContextType.Domain, _ldapConfig.Username, - _ldapConfig.Password); - else - context = domainName != null - ? new DirectoryContext(DirectoryContextType.Domain, domainName) - : new DirectoryContext(DirectoryContextType.Domain); - - domain = Domain.GetDomain(context); - if (domain == null) return false; - _domainCache.TryAdd(cacheKey, domain); - return true; - } - catch (Exception e) { - _log.LogDebug(e, "GetDomain call failed for domain name {Name}", domainName); - return false; - } - } - - public static bool GetDomain(string domainName, LDAPConfig ldapConfig, out Domain domain) { - if (_domainCache.TryGetValue(domainName, out domain)) return true; - - try { - DirectoryContext context; - if (ldapConfig.Username != null) - context = domainName != null - ? new DirectoryContext(DirectoryContextType.Domain, domainName, ldapConfig.Username, - ldapConfig.Password) - : new DirectoryContext(DirectoryContextType.Domain, ldapConfig.Username, - ldapConfig.Password); - else - context = domainName != null - ? new DirectoryContext(DirectoryContextType.Domain, domainName) - : new DirectoryContext(DirectoryContextType.Domain); - - domain = Domain.GetDomain(context); - if (domain == null) return false; - _domainCache.TryAdd(domainName, domain); - return true; - } - catch (Exception e) { - return false; - } - } - - /// - /// Attempts to get the Domain object representing the target domain. If null is specified for the domain name, gets - /// the user's current domain - /// - /// - /// - /// - public bool GetDomain(out Domain domain) { - var cacheKey = _nullCacheKey; - if (_domainCache.TryGetValue(cacheKey, out domain)) return true; - - try { - var context = _ldapConfig.Username != null - ? new DirectoryContext(DirectoryContextType.Domain, _ldapConfig.Username, - _ldapConfig.Password) - : new DirectoryContext(DirectoryContextType.Domain); - - domain = Domain.GetDomain(context); - _domainCache.TryAdd(cacheKey, domain); - return true; - } - catch (Exception e) { - _log.LogDebug(e, "GetDomain call failed for blank domain"); - return false; - } - } - - private struct LdapFailure { - public LdapFailureReason FailureReason { get; set; } - public string Message { get; set; } - } -} \ No newline at end of file diff --git a/src/CommonLib/LdapConnectionPool.cs b/src/CommonLib/LdapConnectionPool.cs index 6cbc1796..23f38580 100644 --- a/src/CommonLib/LdapConnectionPool.cs +++ b/src/CommonLib/LdapConnectionPool.cs @@ -41,9 +41,11 @@ public LdapConnectionPool(string identifier, LDAPConfig config, int maxConnectio if (!_connections.TryTake(out var connectionWrapper)) { var (success, connection, message) = await CreateNewConnection(); if (!success) { + //If we didn't get a connection, immediately release the semaphore so we don't have hanging ones + _semaphore.Release(); return (false, null, message); } - + connectionWrapper = connection; } @@ -54,7 +56,13 @@ public LdapConnectionPool(string identifier, LDAPConfig config, int maxConnectio GetConnectionForSpecificServerAsync(string server, bool globalCatalog) { await _semaphore.WaitAsync(); - return CreateNewConnectionForServer(server, globalCatalog); + var result= CreateNewConnectionForServer(server, globalCatalog); + if (!result.Success) { + //If we didn't get a connection, immediately release the semaphore so we don't have hanging ones + _semaphore.Release(); + } + + return result; } public async Task<(bool Success, LdapConnectionWrapperNew connectionWrapper, string Message)> GetGlobalCatalogConnectionAsync() { @@ -62,6 +70,8 @@ public LdapConnectionPool(string identifier, LDAPConfig config, int maxConnectio if (!_globalCatalogConnection.TryTake(out var connectionWrapper)) { var (success, connection, message) = await CreateNewConnection(true); if (!success) { + //If we didn't get a connection, immediately release the semaphore so we don't have hanging ones + _semaphore.Release(); return (false, null, message); } @@ -71,8 +81,8 @@ public LdapConnectionPool(string identifier, LDAPConfig config, int maxConnectio return (true, connectionWrapper, null); } - public void ReleaseConnection(LdapConnectionWrapperNew connectionWrapper, bool returnToPool = true) { - if (returnToPool) { + public void ReleaseConnection(LdapConnectionWrapperNew connectionWrapper, bool connectionFaulted = false) { + if (!connectionFaulted) { if (connectionWrapper.GlobalCatalog) { _globalCatalogConnection.Add(connectionWrapper); } @@ -132,7 +142,7 @@ public void Dispose() { } } - if (!LDAPUtilsNew.GetDomain(_identifier, _ldapConfig, out var domainObject) || domainObject.Name == null) { + if (!LdapUtilsNew.GetDomain(_identifier, _ldapConfig, out var domainObject) || domainObject.Name == null) { //If we don't get a result here, we effectively have no other ways to resolve this domain, so we'll just have to exit out _log.LogDebug( "Could not get domain object from GetDomain, unable to create ldap connection for domain {Domain}", @@ -161,7 +171,7 @@ public void Dispose() { foreach (DomainController dc in domainObject.DomainControllers) { portConnectionResult = - await CreateLDAPConnectionWithPortCheck(primaryDomainController, globalCatalog); + await CreateLDAPConnectionWithPortCheck(dc.Name, globalCatalog); if (portConnectionResult.success) { _log.LogDebug( "Successfully created ldap connection for domain: {Domain} using strategy 6 with to pdc {Server}", @@ -283,7 +293,7 @@ private bool TestLdapConnection(LdapConnection connection, out LdapConnectionTes try { //Do an initial search request to get the rootDSE //This ldap filter is equivalent to (objectclass=*) - var searchRequest = LDAPUtilsNew.CreateSearchRequest("", new LDAPFilter().AddAllObjects().GetFilter(), + var searchRequest = LdapUtilsNew.CreateSearchRequest("", new LDAPFilter().AddAllObjects().GetFilter(), SearchScope.Base, null); response = (SearchResponse)connection.SendRequest(searchRequest); diff --git a/src/CommonLib/LdapConnectionWrapperNew.cs b/src/CommonLib/LdapConnectionWrapperNew.cs index 1c3392da..bd0e0fc1 100644 --- a/src/CommonLib/LdapConnectionWrapperNew.cs +++ b/src/CommonLib/LdapConnectionWrapperNew.cs @@ -12,8 +12,7 @@ public class LdapConnectionWrapperNew private string _configurationSearchBase; private string _schemaSearchBase; private string _server; - public string Guid { get; set; } - private const string Unknown = "UNKNOWN"; + private string Guid { get; set; } public bool GlobalCatalog; public string PoolIdentifier; @@ -33,15 +32,13 @@ public void CopyContexts(LdapConnectionWrapperNew other) { _server = other._server; } - public bool GetServer(out string server) { + public string GetServer() { if (_server != null) { - server = _server; - return true; + return _server; } _server = _searchResultEntry.GetProperty(LDAPProperties.DNSHostName); - server = _server; - return server != null; + return _server; } public bool GetSearchBase(NamingContext context, out string searchBase) diff --git a/src/CommonLib/LdapResult.cs b/src/CommonLib/LdapResult.cs index ffcedd03..68ca06c0 100644 --- a/src/CommonLib/LdapResult.cs +++ b/src/CommonLib/LdapResult.cs @@ -2,10 +2,19 @@ namespace SharpHoundCommonLib; -public class LdapResult +public class LdapResult : Result { - public T Value { get; set; } - public string Error { get; set; } - public bool IsSuccess => Error == null; public string QueryInfo { get; set; } + + protected LdapResult(T value, bool success, string error, string queryInfo) : base(value, success, error) { + QueryInfo = queryInfo; + } + + public static LdapResult Ok(T value) { + return new LdapResult(value, true, string.Empty, null); + } + + public static LdapResult Fail(string message, LdapQueryParameters queryInfo) { + return new LdapResult(default, false, message, queryInfo.GetQueryInfo()); + } } \ No newline at end of file diff --git a/src/CommonLib/LdapUtilsNew.cs b/src/CommonLib/LdapUtilsNew.cs new file mode 100644 index 00000000..467ec910 --- /dev/null +++ b/src/CommonLib/LdapUtilsNew.cs @@ -0,0 +1,756 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.DirectoryServices; +using System.DirectoryServices.ActiveDirectory; +using System.DirectoryServices.Protocols; +using System.Linq; +using System.Net; +using System.Runtime.CompilerServices; +using System.Security.Principal; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using SharpHoundCommonLib.Enums; +using SharpHoundCommonLib.Exceptions; +using SharpHoundCommonLib.LDAPQueries; +using SharpHoundCommonLib.OutputTypes; +using SharpHoundCommonLib.Processors; +using SharpHoundRPC.NetAPINative; +using Domain = System.DirectoryServices.ActiveDirectory.Domain; +using SearchScope = System.DirectoryServices.Protocols.SearchScope; +using SecurityMasks = System.DirectoryServices.Protocols.SecurityMasks; + +namespace SharpHoundCommonLib; + +public class LdapUtilsNew { + //This cache is indexed by domain sid + private readonly ConcurrentDictionary _dcInfoCache = new(); + private static readonly ConcurrentDictionary DomainCache = new(); + private static readonly ConcurrentDictionary DomainToForestCache = new(StringComparer.OrdinalIgnoreCase); + private static readonly ConcurrentDictionary + SeenWellKnownPrincipals = new(); + private readonly ILogger _log; + private readonly PortScanner _portScanner; + private readonly NativeMethods _nativeMethods; + private readonly string _nullCacheKey = Guid.NewGuid().ToString(); + private readonly Regex SidRegex = new Regex(@"^(S-\d+-\d+-\d+-\d+-\d+-\d+)-\d+$"); + + private readonly string[] _translateNames = { "Administrator", "admin" }; + private LDAPConfig _ldapConfig = new(); + + private ConnectionPoolManager _connectionPool; + + private static readonly TimeSpan MinBackoffDelay = TimeSpan.FromSeconds(2); + private static readonly TimeSpan MaxBackoffDelay = TimeSpan.FromSeconds(20); + private const int BackoffDelayMultiplier = 2; + private const int MaxRetries = 3; + + private class ResolvedWellKnownPrincipal + { + public string DomainName { get; set; } + public string WkpId { get; set; } + } + + public LdapUtilsNew() { + _nativeMethods = new NativeMethods(); + _portScanner = new PortScanner(); + _log = Logging.LogProvider.CreateLogger("LDAPUtils"); + _connectionPool = new ConnectionPoolManager(_ldapConfig); + } + + public LdapUtilsNew(NativeMethods nativeMethods = null, PortScanner scanner = null, ILogger log = null) { + _nativeMethods = nativeMethods ?? new NativeMethods(); + _portScanner = scanner ?? new PortScanner(); + _log = log ?? Logging.LogProvider.CreateLogger("LDAPUtils"); + _connectionPool = new ConnectionPoolManager(_ldapConfig, scanner:_portScanner); + } + + public void SetLDAPConfig(LDAPConfig config) { + _ldapConfig = config; + _connectionPool.Dispose(); + _connectionPool = new ConnectionPoolManager(_ldapConfig, scanner: _portScanner); + } + + public async IAsyncEnumerable> Query(LdapQueryParameters queryParameters, + [EnumeratorCancellation] CancellationToken cancellationToken = new()) { + var setupResult = await SetupLdapQuery(queryParameters); + + if (!setupResult.Success) { + _log.LogInformation("Query - Failure during query setup: {Reason}\n{Info}", setupResult.Message, + queryParameters.GetQueryInfo()); + yield break; + } + + var searchRequest = setupResult.SearchRequest; + var connectionWrapper = setupResult.ConnectionWrapper; + + if (cancellationToken.IsCancellationRequested) { + yield break; + } + + var queryRetryCount = 0; + var busyRetryCount = 0; + LdapResult tempResult = null; + var querySuccess = false; + SearchResponse response = null; + while (true) { + if (cancellationToken.IsCancellationRequested) { + yield break; + } + + try { + _log.LogTrace("Sending ldap request - {Info}", queryParameters.GetQueryInfo()); + response = (SearchResponse)connectionWrapper.Connection.SendRequest(searchRequest); + + if (response != null) { + querySuccess = true; + } + else if (queryRetryCount == MaxRetries) { + tempResult = LdapResult.Fail($"Failed to get a response after {MaxRetries} attempts", queryParameters); + }else { + queryRetryCount++; + continue; + } + } + catch (LdapException le) when (le.ErrorCode == (int)LdapErrorCodes.ServerDown && queryRetryCount < MaxRetries) { + /* + * A ServerDown exception indicates that our connection is no longer valid for one of many reasons. + * We'll want to release our connection back to the pool, but dispose it. We need a new connection, + * and because this is not a paged query, we can get this connection from anywhere. + */ + + //Increment our query retry count + queryRetryCount++; + _connectionPool.ReleaseConnection(connectionWrapper, true); + + for (var retryCount = 0; retryCount < MaxRetries; retryCount++) { + var backoffDelay = GetNextBackoff(retryCount); + await Task.Delay(backoffDelay, cancellationToken); + var (success, newConnectionWrapper, message) = await _connectionPool.GetLdapConnection(queryParameters.DomainName, queryParameters.GlobalCatalog); + if (success) { + _log.LogDebug("Query - Recovered from ServerDown successfully, connection made to {NewServer}", newConnectionWrapper.GetServer()); + connectionWrapper = newConnectionWrapper; + break; + } + + //If we hit our max retries for making a new connection, set tempResult so we can yield it after this logic + if (retryCount == MaxRetries - 1) { + _log.LogError("Query - Failed to get a new connection after ServerDown.\n{Info}", + queryParameters.GetQueryInfo()); + tempResult = + LdapResult.Fail( + "Query - Failed to get a new connection after ServerDown.", queryParameters); + } + } + } + catch (LdapException le) when (le.ErrorCode == (int)ResultCode.Busy && busyRetryCount < MaxRetries) { + /* + * If we get a busy error, we want to do an exponential backoff, but maintain the current connection + * The expectation is that given enough time, the server should stop being busy and service our query appropriately + */ + busyRetryCount++; + var backoffDelay = GetNextBackoff(busyRetryCount); + await Task.Delay(backoffDelay, cancellationToken); + } + catch (LdapException le) { + tempResult = LdapResult.Fail($"Query - Caught unrecoverable ldap exception: {le.Message} (ServerMessage: {le.ServerErrorMessage}) (ErrorCode: {le.ErrorCode})", queryParameters); + } + catch (Exception e) { + tempResult = + LdapResult.Fail($"PagedQuery - Caught unrecoverable exception: {e.Message}", + queryParameters); + } + + //If we have a tempResult set it means we hit an error we couldn't recover from, so yield that result and then break out of the function + if (tempResult != null) { + yield return tempResult; + yield break; + } + + //If we've successfully made our query, break out of the while loop + if (querySuccess) { + break; + } + } + + //TODO: Fix this with a new wrapper object + foreach (ISearchResultEntry entry in response.Entries) { + yield return LdapResult.Ok(entry); + } + } + + public async IAsyncEnumerable> PagedQuery(LdapQueryParameters queryParameters, + [EnumeratorCancellation] CancellationToken cancellationToken = new()) { + var setupResult = await SetupLdapQuery(queryParameters); + + if (!setupResult.Success) { + _log.LogInformation("PagedQuery - Failure during query setup: {Reason}\n{Info}", setupResult.Message, + queryParameters.GetQueryInfo()); + yield break; + } + + var searchRequest = setupResult.SearchRequest; + var connectionWrapper = setupResult.ConnectionWrapper; + var serverName = setupResult.Server; + + if (serverName == null) { + _log.LogWarning("PagedQuery - Failed to get a server name for connection, retry not possible"); + } + + var pageControl = new PageResultRequestControl(500); + searchRequest.Controls.Add(pageControl); + + PageResultResponseControl pageResponse = null; + var busyRetryCount = 0; + var queryRetryCount = 0; + LdapResult tempResult = null; + + while (true) { + if (cancellationToken.IsCancellationRequested) { + yield break; + } + + if (tempResult != null) { + yield return tempResult; + yield break; + } + + SearchResponse response = null; + try { + _log.LogTrace("Sending paged ldap request - {Info}", queryParameters.GetQueryInfo()); + response = (SearchResponse)connectionWrapper.Connection.SendRequest(searchRequest); + if (response != null) { + pageResponse = (PageResultResponseControl)response.Controlsdo + .Where(x => x is PageResultResponseControl).DefaultIfEmpty(null).FirstOrDefault(); + queryRetryCount = 0; + }else if (queryRetryCount == MaxRetries) { + tempResult = LdapResult.Fail($"PagedQuery - Failed to get a response after {MaxRetries} attempts", + queryParameters); + } + else { + queryRetryCount++; + } + } + catch (LdapException le) when (le.ErrorCode == (int)LdapErrorCodes.ServerDown) { + /* + * If we dont have a servername, we're not going to be able to re-establish a connection here. Page cookies are only valid for the server they were generated on. Bail out. + */ + if (serverName == null) { + _log.LogError( + "PagedQuery - Received server down exception without a known servername. Unable to generate new connection\n{Info}", + queryParameters.GetQueryInfo()); + yield break; + } + + /* + * Paged queries will not use the cached ldap connections, as the intention is to only have 1 or a couple of these queries running at once. + * The connection logic here is simplified accordingly + */ + _connectionPool.ReleaseConnection(connectionWrapper, true); + for (var retryCount = 0; retryCount < MaxRetries; retryCount++) { + var backoffDelay = GetNextBackoff(retryCount); + await Task.Delay(backoffDelay, cancellationToken); + var (success, ldapConnectionWrapperNew, message) = await _connectionPool.GetLdapConnectionForServer( + queryParameters.DomainName,serverName, queryParameters.GlobalCatalog); + + if (success) { + _log.LogDebug("PagedQuery - Recovered from ServerDown successfully"); + connectionWrapper = ldapConnectionWrapperNew; + break; + } + + if (retryCount == MaxRetries - 1) { + _log.LogError("PagedQuery - Failed to get a new connection after ServerDown.\n{Info}", + queryParameters.GetQueryInfo()); + yield break; + } + } + } + catch (LdapException le) when (le.ErrorCode == (int)ResultCode.Busy && busyRetryCount < MaxRetries) { + /* + * If we get a busy error, we want to do an exponential backoff, but maintain the current connection + * The expectation is that given enough time, the server should stop being busy and service our query appropriately + */ + busyRetryCount++; + var backoffDelay = GetNextBackoff(busyRetryCount); + await Task.Delay(backoffDelay, cancellationToken); + } + catch (LdapException le) { + tempResult = LdapResult.Fail($"PagedQuery - Caught unrecoverable ldap exception: {le.Message} (ServerMessage: {le.ServerErrorMessage}) (ErrorCode: {le.ErrorCode})", queryParameters); + } + catch (Exception e) { + tempResult = LdapResult.Fail($"PagedQuery - Caught unrecoverable exception: {e.Message}", queryParameters); + } + + if (tempResult != null) { + yield return tempResult; + yield break; + } + + if (cancellationToken.IsCancellationRequested) { + yield break; + } + + //I'm not sure why this happens sometimes, but if we try the request again, it works sometimes, other times we get an exception + if (response == null || pageResponse == null) { + continue; + } + + foreach (ISearchResultEntry entry in response.Entries) { + if (cancellationToken.IsCancellationRequested) { + yield break; + } + + yield return LdapResult.Ok(entry); + } + + if (pageResponse.Cookie.Length == 0 || response.Entries.Count == 0 || + cancellationToken.IsCancellationRequested) + yield break; + + pageControl.Cookie = pageResponse.Cookie; + } + } + + public bool ResolveIDAndType(SecurityIdentifier securityIdentifier, string objectDomain, out TypedPrincipal resolvedPrincipal) { + return ResolveIDAndType(securityIdentifier.Value, objectDomain, out resolvedPrincipal); + } + + public async Task<(bool Success, TypedPrincipal Principal)> ResolveIDAndType(string identifier, string objectDomain) { + if (identifier.Contains("0ACNF")) { + return (false, null); + } + + if (await GetWellKnownPrincipal(identifier, objectDomain) is (true, var principal)) { + return (true, principal); + } + + var type = identifier.StartsWith("S-") ? LookupSidType(id, fallbackDomain) : LookupGuidType(id, fallbackDomain); + return new TypedPrincipal(id, type); + } + + private async Task<(bool Success, Label type)> LookupSidType(string sid, string domain) { + if (Cache.GetIDType(sid, out var type)) { + return (true, type); + } + + if (await GetDomainSidFromDomainName(domain) is (true, var domainSid)) { + + } + } + + public async Task<(bool Success, TypedPrincipal wellKnownPrincipal)> GetWellKnownPrincipal(string securityIdentifier, string objectDomain) { + if (!WellKnownPrincipal.GetWellKnownPrincipal(securityIdentifier, out var wellKnownPrincipal)) { + return (false, null); + } + + var (newIdentifier, newDomain) = await GetWellKnownPrincipalObjectIdentifier(securityIdentifier, objectDomain); + + wellKnownPrincipal.ObjectIdentifier = newIdentifier; + SeenWellKnownPrincipals.TryAdd(wellKnownPrincipal.ObjectIdentifier, new ResolvedWellKnownPrincipal { + DomainName = newDomain, + WkpId = securityIdentifier + }); + + return (true, wellKnownPrincipal); + } + + private async Task<(string ObjectID, string Domain)> GetWellKnownPrincipalObjectIdentifier(string securityIdentifier, string domain) { + if (!WellKnownPrincipal.GetWellKnownPrincipal(securityIdentifier, out _)) return (securityIdentifier, string.Empty); + + if (!securityIdentifier.Equals("S-1-5-9", StringComparison.OrdinalIgnoreCase)) { + var tempDomain = domain; + if (GetDomain(tempDomain, out var domainObject) && domainObject.Name != null) { + tempDomain = domainObject.Name; + } + return ($"{tempDomain}-{securityIdentifier}".ToUpper(), tempDomain); + } + + if (await GetForest(domain) is (true, var forest)) { + return ($"{forest}-{securityIdentifier}".ToUpper(), forest); + } + + _log.LogWarning("Failed to get a forest name for domain {Domain}, unable to resolve enterprise DC sid", domain); + return ($"UNKNOWN-{securityIdentifier}", "UNKNOWN"); + } + + private async Task<(bool Success, string ForestName)> GetForest(string domain) { + if (DomainToForestCache.TryGetValue(domain, out var cachedForest)) { + return (true, cachedForest); + } + if (GetDomain(domain, out var domainObject)) { + var forestName = domainObject.Forest.Name.ToUpper(); + DomainToForestCache.TryAdd(domain, forestName); + return (true, forestName); + } + + var (success, forest) = await GetForestFromLdap(domain); + if (success) { + DomainToForestCache.TryAdd(domain, forest); + return (true, forest); + } + + return (false, null); + } + + private async Task<(bool Success, string ForestName)> GetForestFromLdap(string domain) { + var queryParameters = new LdapQueryParameters { + Attributes = new[] { LDAPProperties.RootDomainNamingContext }, + SearchScope = SearchScope.Base, + DomainName = domain, + LDAPFilter = new LDAPFilter().AddAllObjects().GetFilter(), + }; + + var result = await Query(queryParameters).FirstAsync(); + if (result.IsSuccess) { + var rdn = result.Value.GetProperty(LDAPProperties.RootDomainNamingContext); + if (!string.IsNullOrEmpty(rdn)) { + return (true, Helpers.DistinguishedNameToDomain(rdn).ToUpper()); + } + } + + return (false, null); + } + + private static TimeSpan GetNextBackoff(int retryCount) { + return TimeSpan.FromSeconds(Math.Min( + MinBackoffDelay.TotalSeconds * Math.Pow(BackoffDelayMultiplier, retryCount), + MaxBackoffDelay.TotalSeconds)); + } + + private bool CreateSearchRequest(LdapQueryParameters queryParameters, + ref LdapConnectionWrapperNew connectionWrapper, out SearchRequest searchRequest) { + string basePath; + if (!string.IsNullOrWhiteSpace(queryParameters.SearchBase)) { + basePath = queryParameters.SearchBase; + } + else if (!connectionWrapper.GetSearchBase(queryParameters.NamingContext, out basePath)) { + string tempPath; + if (CallDsGetDcName(queryParameters.DomainName, out var info) && info != null) { + tempPath = Helpers.DomainNameToDistinguishedName(info.Value.DomainName); + connectionWrapper.SaveContext(queryParameters.NamingContext, basePath); + } + else if (GetDomain(queryParameters.DomainName, out var domainObject)) { + tempPath = Helpers.DomainNameToDistinguishedName(domainObject.Name); + } + else { + searchRequest = null; + return false; + } + + basePath = queryParameters.NamingContext switch { + NamingContext.Configuration => $"CN=Configuration,{tempPath}", + NamingContext.Schema => $"CN=Schema,CN=Configuration,{tempPath}", + NamingContext.Default => tempPath, + _ => throw new ArgumentOutOfRangeException() + }; + + connectionWrapper.SaveContext(queryParameters.NamingContext, basePath); + } + + searchRequest = new SearchRequest(basePath, queryParameters.LDAPFilter, queryParameters.SearchScope, + queryParameters.Attributes); + searchRequest.Controls.Add(new SearchOptionsControl(SearchOption.DomainScope)); + if (queryParameters.IncludeDeleted) { + searchRequest.Controls.Add(new ShowDeletedControl()); + } + + if (queryParameters.IncludeSecurityDescriptor) { + searchRequest.Controls.Add(new SecurityDescriptorFlagControl { + SecurityMasks = SecurityMasks.Dacl | SecurityMasks.Owner + }); + } + + return true; + } + + + + private bool CallDsGetDcName(string domainName, out NetAPIStructs.DomainControllerInfo? info) { + if (_dcInfoCache.TryGetValue(domainName.ToUpper().Trim(), out info)) return info != null; + + var apiResult = _nativeMethods.CallDsGetDcName(null, domainName, + (uint)(NetAPIEnums.DSGETDCNAME_FLAGS.DS_FORCE_REDISCOVERY | + NetAPIEnums.DSGETDCNAME_FLAGS.DS_RETURN_DNS_NAME | + NetAPIEnums.DSGETDCNAME_FLAGS.DS_DIRECTORY_SERVICE_REQUIRED)); + + if (apiResult.IsFailed) { + _dcInfoCache.TryAdd(domainName.ToUpper().Trim(), null); + return false; + } + + info = apiResult.Value; + return true; + } + + private async Task SetupLdapQuery(LdapQueryParameters queryParameters) { + var result = new LdapQuerySetupResult(); + var (success, connectionWrapper, message) = + await _connectionPool.GetLdapConnection(queryParameters.DomainName, queryParameters.GlobalCatalog); + if (!success) { + result.Success = false; + result.Message = $"Unable to create a connection: {message}"; + return result; + } + + //This should never happen as far as I know, so just checking for safety + if (connectionWrapper.Connection == null) { + result.Success = false; + result.Message = $"Connection object is null"; + return result; + } + + if (!CreateSearchRequest(queryParameters, ref connectionWrapper, out var searchRequest)) { + result.Success = false; + result.Message = "Failed to create search request"; + return result; + } + + result.Server = connectionWrapper.GetServer(); + result.Success = true; + result.SearchRequest = searchRequest; + result.ConnectionWrapper = connectionWrapper; + return result; + } + + public static SearchRequest CreateSearchRequest(string distinguishedName, string ldapFilter, SearchScope searchScope, + string[] attributes) { + var searchRequest = new SearchRequest(distinguishedName, ldapFilter, + searchScope, attributes); + searchRequest.Controls.Add(new SearchOptionsControl(SearchOption.DomainScope)); + return searchRequest; + } + + public async Task<(bool Success, string DomainName)> GetDomainNameFromSid(string sid) { + string domainSid; + try { + domainSid = new SecurityIdentifier(sid).AccountDomainSid?.Value.ToUpper(); + } + catch { + var match = SidRegex.Match(sid); + domainSid = match.Success ? match.Groups[1].Value : null; + } + + if (domainSid == null) { + return (false, ""); + } + + if (Cache.GetDomainSidMapping(domainSid, out var domain)) { + return (true, domain); + } + + try { + var entry = new DirectoryEntry($"LDAP://"); + entry.RefreshCache(new[] { LDAPProperties.DistinguishedName }); + var dn = entry.GetProperty(LDAPProperties.DistinguishedName); + if (!string.IsNullOrEmpty(dn)) { + Cache.AddDomainSidMapping(domainSid, Helpers.DistinguishedNameToDomain(dn)); + return (true, Helpers.DistinguishedNameToDomain(dn)); + } + } + catch { + //pass + } + + if (await ConvertDomainSidToDomainNameFromLdap(sid) is (true, var domainName)) { + Cache.AddDomainSidMapping(domainSid, domainName); + return (true, domainName); + } + + return (false, string.Empty); + } + + private async Task<(bool Success, string DomainName)> ConvertDomainSidToDomainNameFromLdap(string domainSid) { + if (!GetDomain(out var domain) || domain?.Name == null) { + return (false, string.Empty); + } + + var result = await Query(new LdapQueryParameters { + DomainName = domain.Name, + Attributes = new[] { LDAPProperties.DistinguishedName }, + GlobalCatalog = true, + LDAPFilter = new LDAPFilter().AddDomains(CommonFilters.SpecificSID(domainSid)).GetFilter() + }).FirstAsync(); + + if (result.IsSuccess) { + return (true, Helpers.DistinguishedNameToDomain(result.Value.DistinguishedName)); + } + + result = await Query(new LdapQueryParameters { + DomainName = domain.Name, + Attributes = new[] { LDAPProperties.DistinguishedName }, + GlobalCatalog = true, + LDAPFilter = new LDAPFilter().AddFilter("(objectclass=trusteddomain)", true).AddFilter($"(securityidentifier={Helpers.ConvertSidToHexSid(domainSid)})", true).GetFilter() + }).FirstAsync(); + + if (result.IsSuccess) { + return (true, Helpers.DistinguishedNameToDomain(result.Value.DistinguishedName)); + } + + result = await Query(new LdapQueryParameters { + DomainName = domain.Name, + Attributes = new[] { LDAPProperties.DistinguishedName }, + LDAPFilter = new LDAPFilter().AddFilter("(objectclass=domaindns)", true).AddFilter(CommonFilters.SpecificSID(domainSid), true).GetFilter() + }).FirstAsync(); + + if (result.IsSuccess) { + return (true, Helpers.DistinguishedNameToDomain(result.Value.DistinguishedName)); + } + + return (false, string.Empty); + } + + public async Task<(bool Success, string domainSid)> GetDomainSidFromDomainName(string domainName) { + if (Cache.GetDomainSidMapping(domainName, out var domainSid)) return (true, domainSid); + + try { + var entry = new DirectoryEntry($"LDAP://{domainName}"); + //Force load objectsid into the object cache + entry.RefreshCache(new[] { "objectSid" }); + var sid = entry.GetSid(); + if (sid != null) { + Cache.AddDomainSidMapping(domainName, sid); + domainSid = sid; + return (true, domainSid); + } + } + catch { + //we expect this to fail sometimes + } + + if (GetDomain(domainName, out var domainObject)) + try { + domainSid = domainObject.GetDirectoryEntry().GetSid(); + if (domainSid != null) { + Cache.AddDomainSidMapping(domainName, domainSid); + return (true, domainSid); + } + } + catch { + //we expect this to fail sometimes (not sure why, but better safe than sorry) + } + + foreach (var name in _translateNames) + try { + var account = new NTAccount(domainName, name); + var sid = (SecurityIdentifier)account.Translate(typeof(SecurityIdentifier)); + domainSid = sid.AccountDomainSid.ToString(); + Cache.AddDomainSidMapping(domainName, domainSid); + return (true, domainSid); + } + catch { + //We expect this to fail if the username doesn't exist in the domain + } + + var result = await Query(new LdapQueryParameters() { + DomainName = domainName, + Attributes = new[] { LDAPProperties.ObjectSID }, + LDAPFilter = new LDAPFilter().AddFilter(CommonFilters.DomainControllers, true).GetFilter() + }).FirstAsync(); + + if (result.Success) { + var sid = result.Value.GetSid(); + if (!string.IsNullOrEmpty(sid)) { + domainSid = new SecurityIdentifier(sid).AccountDomainSid.Value; + Cache.AddDomainSidMapping(domainName, domainSid); + return (true, domainSid); + } + } + return (false, string.Empty); + } + + /// + /// Attempts to get the Domain object representing the target domain. If null is specified for the domain name, gets + /// the user's current domain + /// + /// + /// + /// + public bool GetDomain(string domainName, out Domain domain) { + var cacheKey = domainName ?? _nullCacheKey; + if (DomainCache.TryGetValue(cacheKey, out domain)) return true; + + try { + DirectoryContext context; + if (_ldapConfig.Username != null) + context = domainName != null + ? new DirectoryContext(DirectoryContextType.Domain, domainName, _ldapConfig.Username, + _ldapConfig.Password) + : new DirectoryContext(DirectoryContextType.Domain, _ldapConfig.Username, + _ldapConfig.Password); + else + context = domainName != null + ? new DirectoryContext(DirectoryContextType.Domain, domainName) + : new DirectoryContext(DirectoryContextType.Domain); + + domain = Domain.GetDomain(context); + if (domain == null) return false; + DomainCache.TryAdd(cacheKey, domain); + return true; + } + catch (Exception e) { + _log.LogDebug(e, "GetDomain call failed for domain name {Name}", domainName); + return false; + } + } + + public static bool GetDomain(string domainName, LDAPConfig ldapConfig, out Domain domain) { + if (DomainCache.TryGetValue(domainName, out domain)) return true; + + try { + DirectoryContext context; + if (ldapConfig.Username != null) + context = domainName != null + ? new DirectoryContext(DirectoryContextType.Domain, domainName, ldapConfig.Username, + ldapConfig.Password) + : new DirectoryContext(DirectoryContextType.Domain, ldapConfig.Username, + ldapConfig.Password); + else + context = domainName != null + ? new DirectoryContext(DirectoryContextType.Domain, domainName) + : new DirectoryContext(DirectoryContextType.Domain); + + domain = Domain.GetDomain(context); + if (domain == null) return false; + DomainCache.TryAdd(domainName, domain); + return true; + } + catch (Exception e) { + return false; + } + } + + /// + /// Attempts to get the Domain object representing the target domain. If null is specified for the domain name, gets + /// the user's current domain + /// + /// + /// + /// + public bool GetDomain(out Domain domain) { + var cacheKey = _nullCacheKey; + if (DomainCache.TryGetValue(cacheKey, out domain)) return true; + + try { + var context = _ldapConfig.Username != null + ? new DirectoryContext(DirectoryContextType.Domain, _ldapConfig.Username, + _ldapConfig.Password) + : new DirectoryContext(DirectoryContextType.Domain); + + domain = Domain.GetDomain(context); + DomainCache.TryAdd(cacheKey, domain); + return true; + } + catch (Exception e) { + _log.LogDebug(e, "GetDomain call failed for blank domain"); + return false; + } + } + + private struct LdapFailure { + public LdapFailureReason FailureReason { get; set; } + public string Message { get; set; } + } +} \ No newline at end of file diff --git a/src/CommonLib/Result.cs b/src/CommonLib/Result.cs new file mode 100644 index 00000000..b0ac9ba1 --- /dev/null +++ b/src/CommonLib/Result.cs @@ -0,0 +1,41 @@ +namespace SharpHoundCommonLib; + +public class Result : Result { + public T Value { get; set; } + + protected internal Result(T value, bool success, string error) : base(success, error) { + Value = value; + } + + public static Result Fail(string message) { + return new Result(default, false, message); + } + + public static Result Fail() { + return new Result(default, false, string.Empty); + } + + public static Result Ok(T value) { + return new Result(value, true, string.Empty); + } +} + +public class Result { + + public string Error { get; set; } + public bool IsSuccess => Error == null && Success; + public bool Success { get; set; } + + protected Result(bool success, string error) { + Success = success; + Error = error; + } + + public static Result Fail(string message) { + return new Result(false, message); + } + + public static Result Ok() { + return new Result(true, string.Empty); + } +} \ No newline at end of file diff --git a/src/CommonLib/SearchResultEntryWrapperNew.cs b/src/CommonLib/SearchResultEntryWrapperNew.cs new file mode 100644 index 00000000..be04cb3c --- /dev/null +++ b/src/CommonLib/SearchResultEntryWrapperNew.cs @@ -0,0 +1,79 @@ +using System.Collections.Generic; +using System.DirectoryServices.Protocols; +using System.Security.Cryptography.X509Certificates; +using SharpHoundCommonLib.Enums; + +namespace SharpHoundCommonLib; + +public class SearchResultEntryWrapperNew : ISearchResultEntry { + private readonly SearchResultEntry _entry; + + public string DistinguishedName => _entry.DistinguishedName; + public ResolvedSearchResult ResolveBloodHoundInfo() { + throw new System.NotImplementedException(); + } + + public string GetProperty(string propertyName) { + throw new System.NotImplementedException(); + } + + public byte[] GetByteProperty(string propertyName) { + throw new System.NotImplementedException(); + } + + public string[] GetArrayProperty(string propertyName) { + throw new System.NotImplementedException(); + } + + public byte[][] GetByteArrayProperty(string propertyName) { + throw new System.NotImplementedException(); + } + + public bool GetIntProperty(string propertyName, out int value) { + throw new System.NotImplementedException(); + } + + public X509Certificate2[] GetCertificateArrayProperty(string propertyName) { + throw new System.NotImplementedException(); + } + + public string GetObjectIdentifier() { + throw new System.NotImplementedException(); + } + + public bool IsDeleted() { + throw new System.NotImplementedException(); + } + + public Label GetLabel() { + throw new System.NotImplementedException(); + } + + public string GetSid() { + throw new System.NotImplementedException(); + } + + public string GetGuid() { + throw new System.NotImplementedException(); + } + + public int PropCount(string prop) { + throw new System.NotImplementedException(); + } + + public IEnumerable PropertyNames() { + throw new System.NotImplementedException(); + } + + public bool IsMSA() { + throw new System.NotImplementedException(); + } + + public bool IsGMSA() { + throw new System.NotImplementedException(); + } + + public bool HasLAPS() { + throw new System.NotImplementedException(); + } +} \ No newline at end of file diff --git a/src/CommonLib/SharpHoundCommonLib.csproj b/src/CommonLib/SharpHoundCommonLib.csproj index eca94af0..c5d9fb04 100644 --- a/src/CommonLib/SharpHoundCommonLib.csproj +++ b/src/CommonLib/SharpHoundCommonLib.csproj @@ -19,8 +19,9 @@ - - + + + From 71c61564f7af46e1ea8c38535eaafabebd5bddf6 Mon Sep 17 00:00:00 2001 From: rvazarkar Date: Tue, 25 Jun 2024 17:08:13 -0400 Subject: [PATCH 11/68] wip: more stuff --- src/CommonLib/Cache.cs | 2 +- src/CommonLib/Extensions.cs | 231 +++++---- src/CommonLib/Helpers.cs | 1 + src/CommonLib/LDAPQueries/CommonProperties.cs | 3 +- src/CommonLib/LdapUtilsNew.cs | 446 +++++++++++++++--- src/CommonLib/NativeMethods.cs | 4 + .../Processors/CertAbuseProcessor.cs | 19 +- .../Processors/LocalGroupProcessor.cs | 7 +- 8 files changed, 552 insertions(+), 161 deletions(-) diff --git a/src/CommonLib/Cache.cs b/src/CommonLib/Cache.cs index ba46e968..d34eb514 100644 --- a/src/CommonLib/Cache.cs +++ b/src/CommonLib/Cache.cs @@ -107,7 +107,7 @@ internal static void AddGCCache(string key, string[] value) internal static bool GetGCCache(string key, out string[] value) { - if (CacheInstance != null) return CacheInstance.GlobalCatalogCache.TryGetValue(key, out value); + if (CacheInstance != null) return CacheInstance.GlobalCatalogCache.TryGetValue(key.ToUpper(), out value); value = null; return false; } diff --git a/src/CommonLib/Extensions.cs b/src/CommonLib/Extensions.cs index c7c2622f..7475e66c 100644 --- a/src/CommonLib/Extensions.cs +++ b/src/CommonLib/Extensions.cs @@ -69,9 +69,11 @@ public static string LdapValue(this Guid s) public static string GetProperty(this DirectoryEntry entry, string propertyName) { try { - if (!entry.Properties.Contains(propertyName)) { + if (!entry.Properties.Contains(propertyName)) + entry.RefreshCache(new[] { propertyName }); + + if (!entry.Properties.Contains(propertyName)) return null; - } } catch { return null; @@ -85,11 +87,40 @@ public static string GetProperty(this DirectoryEntry entry, string propertyName) }; } - public static string GetSid(this DirectoryEntry result) + public static string[] GetPropertyAsArray(this DirectoryEntry entry, string propertyName) { + try { + if (!entry.Properties.Contains(propertyName)) + entry.RefreshCache(new[] { propertyName }); + + if (!entry.Properties.Contains(propertyName)) + return null; + } + catch { + return null; + } + + var dest = new List(); + foreach (var val in entry.Properties[propertyName]) { + if (val is string s) { + dest.Add(s); + } + } + + return dest.ToArray(); + } + + public static string GetObjectIdentifier(this DirectoryEntry entry) { + return entry.GetSid() ?? entry.GetGuid(); + } + + public static string GetSid(this DirectoryEntry entry) { try { - if (!result.Properties.Contains(LDAPProperties.ObjectSID)) + if (!entry.Properties.Contains(LDAPProperties.ObjectSID)) + entry.RefreshCache(new[] { LDAPProperties.ObjectSID }); + + if (!entry.Properties.Contains(LDAPProperties.ObjectSID)) return null; } catch @@ -97,7 +128,7 @@ public static string GetSid(this DirectoryEntry result) return null; } - var s = result.Properties[LDAPProperties.ObjectSID][0]; + var s = entry.Properties[LDAPProperties.ObjectSID][0]; return s switch { byte[] b => new SecurityIdentifier(b, 0).ToString(), @@ -105,6 +136,31 @@ public static string GetSid(this DirectoryEntry result) _ => null }; } + + public static string GetGuid(this DirectoryEntry entry) + { + try + { + //Attempt to refresh the props first + if (!entry.Properties.Contains(LDAPProperties.ObjectGUID)) + entry.RefreshCache(new[] { LDAPProperties.ObjectGUID }); + + if (!entry.Properties.Contains(LDAPProperties.ObjectGUID)) + return null; + } + catch + { + return null; + } + + var s = entry.Properties[LDAPProperties.ObjectGUID][0]; + return s switch + { + byte[] b => new Guid(b).ToString(), + string st => st, + _ => null + }; + } /// /// Returns true if any computer collection methods are set @@ -363,110 +419,103 @@ public static bool IsDeleted(this SearchResultEntry entry) return bool.TryParse(deleted, out var isDeleted) && isDeleted; } - /// - /// Extension method to determine the BloodHound type of a SearchResultEntry using LDAP properties - /// Requires ldap properties objectsid, samaccounttype, objectclass - /// - /// - /// - public static Label GetLabel(this SearchResultEntry entry) - { - var objectId = entry.GetObjectIdentifier(); - - if (objectId == null) - { - Log.LogWarning("Failed to get an object identifier for {DN}", entry.DistinguishedName); - return Label.Base; + public static bool GetLabel(this DirectoryEntry entry, out Label type) { + try { + entry.RefreshCache(CommonProperties.TypeResolutionProps); } - - if (objectId.StartsWith("S-1") && - WellKnownPrincipal.GetWellKnownPrincipal(objectId, out var commonPrincipal)) - { - Log.LogDebug("GetLabel - {ObjectID} is a WellKnownPrincipal with {Type}", objectId, - commonPrincipal.ObjectType); - return commonPrincipal.ObjectType; + catch { + //pass } + var flagString = entry.GetProperty(LDAPProperties.Flags); + if (!int.TryParse(flagString, out var flags)) { + flags = 0; + } - var objectType = Label.Base; - var samAccountType = entry.GetProperty(LDAPProperties.SAMAccountType); - var objectClasses = entry.GetPropertyAsArray(LDAPProperties.ObjectClass); + return ResolveLabel(entry.GetObjectIdentifier(), entry.GetProperty(LDAPProperties.DistinguishedName), + entry.GetProperty(LDAPProperties.SAMAccountType), + entry.GetPropertyAsArray(LDAPProperties.SAMAccountType), flags, out type); + } - //Override object class for GMSA/MSA accounts + private static bool ResolveLabel(string objectIdentifier, string distinguishedName, string samAccountType, string[] objectClasses, int flags, out Label type) { + if (objectIdentifier != null && WellKnownPrincipal.GetWellKnownPrincipal(objectIdentifier, out var principal)) { + type = principal.ObjectType; + return true; + } + + //Override GMSA/MSA account to treat them as users for the graph if (objectClasses != null && (objectClasses.Contains(MSAClass, StringComparer.OrdinalIgnoreCase) || objectClasses.Contains(GMSAClass, StringComparer.OrdinalIgnoreCase))) { - Log.LogDebug("GetLabel - {ObjectID} is an MSA/GMSA, returning User", objectId); - Cache.AddConvertedValue(entry.DistinguishedName, objectId); - Cache.AddType(objectId, objectType); - return Label.User; + type = Label.User; + return true; } - - //Its not a common principal. Lets use properties to figure out what it actually is - if (samAccountType != null) objectType = Helpers.SamAccountTypeToType(samAccountType); - - Log.LogDebug("GetLabel - SamAccountTypeToType returned {Label}", objectType); - if (objectType != Label.Base) - { - Cache.AddConvertedValue(entry.DistinguishedName, objectId); - Cache.AddType(objectId, objectType); - return objectType; + if (samAccountType != null) { + var objectType = Helpers.SamAccountTypeToType(samAccountType); + if (objectType != Label.Base) { + type = objectType; + return true; + } } - - if (objectClasses == null) - { - Log.LogDebug("GetLabel - ObjectClasses for {ObjectID} is null", objectId); - objectType = Label.Base; + if (objectClasses == null) { + type = Label.Base; + return false; } - else - { - Log.LogDebug("GetLabel - ObjectClasses for {ObjectID}: {Classes}", objectId, - string.Join(", ", objectClasses)); - if (objectClasses.Contains(GroupPolicyContainerClass, StringComparer.InvariantCultureIgnoreCase)) - objectType = Label.GPO; - else if (objectClasses.Contains(OrganizationalUnitClass, StringComparer.InvariantCultureIgnoreCase)) - objectType = Label.OU; - else if (objectClasses.Contains(DomainClass, StringComparer.InvariantCultureIgnoreCase)) - objectType = Label.Domain; - else if (objectClasses.Contains(ContainerClass, StringComparer.InvariantCultureIgnoreCase)) - objectType = Label.Container; - else if (objectClasses.Contains(ConfigurationClass, StringComparer.InvariantCultureIgnoreCase)) - objectType = Label.Configuration; - else if (objectClasses.Contains(PKICertificateTemplateClass, StringComparer.InvariantCultureIgnoreCase)) - objectType = Label.CertTemplate; - else if (objectClasses.Contains(PKIEnrollmentServiceClass, StringComparer.InvariantCultureIgnoreCase)) - objectType = Label.EnterpriseCA; - else if (objectClasses.Contains(CertificationAuthorityClass, StringComparer.InvariantCultureIgnoreCase)) - { - if (entry.DistinguishedName.Contains(DirectoryPaths.RootCALocation)) - objectType = Label.RootCA; - else if (entry.DistinguishedName.Contains(DirectoryPaths.AIACALocation)) - objectType = Label.AIACA; - else if (entry.DistinguishedName.Contains(DirectoryPaths.NTAuthStoreLocation)) - objectType = Label.NTAuthStore; - } - else if (objectClasses.Contains(OIDContainerClass, StringComparer.InvariantCultureIgnoreCase)) + + if (objectClasses.Contains(GroupPolicyContainerClass, StringComparer.InvariantCultureIgnoreCase)) + type = Label.GPO; + if (objectClasses.Contains(OrganizationalUnitClass, StringComparer.InvariantCultureIgnoreCase)) + type = Label.OU; + if (objectClasses.Contains(DomainClass, StringComparer.InvariantCultureIgnoreCase)) + type = Label.Domain; + if (objectClasses.Contains(ContainerClass, StringComparer.InvariantCultureIgnoreCase)) + type = Label.Container; + if (objectClasses.Contains(ConfigurationClass, StringComparer.InvariantCultureIgnoreCase)) + type = Label.Configuration; + if (objectClasses.Contains(PKICertificateTemplateClass, StringComparer.InvariantCultureIgnoreCase)) + type = Label.CertTemplate; + if (objectClasses.Contains(PKIEnrollmentServiceClass, StringComparer.InvariantCultureIgnoreCase)) + type = Label.EnterpriseCA; + if (objectClasses.Contains(CertificationAuthorityClass, StringComparer.InvariantCultureIgnoreCase)) { + if (distinguishedName.Contains(DirectoryPaths.RootCALocation)) + type = Label.RootCA; + if (distinguishedName.Contains(DirectoryPaths.AIACALocation)) + type = Label.AIACA; + if (distinguishedName.Contains(DirectoryPaths.NTAuthStoreLocation)) + type = Label.NTAuthStore; + } + + if (objectClasses.Contains(OIDContainerClass, StringComparer.InvariantCultureIgnoreCase)) { + if (distinguishedName.StartsWith(DirectoryPaths.OIDContainerLocation, + StringComparison.InvariantCultureIgnoreCase)) + type = Label.Container; + if (flags == 2) { - if (entry.DistinguishedName.StartsWith(DirectoryPaths.OIDContainerLocation, - StringComparison.InvariantCultureIgnoreCase)) - objectType = Label.Container; - else - { - if (entry.GetPropertyAsInt(LDAPProperties.Flags, out var flags) && flags == 2) - { - objectType = Label.IssuancePolicy; - } - } + type = Label.IssuancePolicy; } } - Log.LogDebug("GetLabel - Final label for {ObjectID}: {Label}", objectId, objectType); + type = Label.Base; + return false; + } + + /// + /// Extension method to determine the BloodHound type of a SearchResultEntry using LDAP properties + /// Requires ldap properties objectsid, samaccounttype, objectclass + /// + /// + /// + public static bool GetLabel(this SearchResultEntry entry, out Label type) + { + if (!entry.GetPropertyAsInt(LDAPProperties.Flags, out var flags)) { + flags = 0; + } - Cache.AddConvertedValue(entry.DistinguishedName, objectId); - Cache.AddType(objectId, objectType); - return objectType; + return ResolveLabel(entry.GetObjectIdentifier(), entry.DistinguishedName, + entry.GetProperty(LDAPProperties.SAMAccountType), entry.GetPropertyAsArray(LDAPProperties.ObjectClass), + flags, out type); } private const string GroupPolicyContainerClass = "groupPolicyContainer"; diff --git a/src/CommonLib/Helpers.cs b/src/CommonLib/Helpers.cs index 76f2b0ee..c59da55e 100644 --- a/src/CommonLib/Helpers.cs +++ b/src/CommonLib/Helpers.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.DirectoryServices; using System.Globalization; using System.Linq; using System.Security.Principal; diff --git a/src/CommonLib/LDAPQueries/CommonProperties.cs b/src/CommonLib/LDAPQueries/CommonProperties.cs index c1644aac..6cda11de 100644 --- a/src/CommonLib/LDAPQueries/CommonProperties.cs +++ b/src/CommonLib/LDAPQueries/CommonProperties.cs @@ -5,7 +5,8 @@ public static class CommonProperties public static readonly string[] TypeResolutionProps = { LDAPProperties.SAMAccountType, LDAPProperties.ObjectSID, LDAPProperties.ObjectGUID, - LDAPProperties.ObjectClass, LDAPProperties.SAMAccountName, LDAPProperties.GroupMSAMembership + LDAPProperties.ObjectClass, LDAPProperties.SAMAccountName, LDAPProperties.GroupMSAMembership, + LDAPProperties.Flags }; public static readonly string[] ObjectID = { LDAPProperties.ObjectSID, LDAPProperties.ObjectGUID }; diff --git a/src/CommonLib/LdapUtilsNew.cs b/src/CommonLib/LdapUtilsNew.cs index 467ec910..b8ad134e 100644 --- a/src/CommonLib/LdapUtilsNew.cs +++ b/src/CommonLib/LdapUtilsNew.cs @@ -6,8 +6,10 @@ using System.DirectoryServices.Protocols; using System.Linq; using System.Net; +using System.Net.Sockets; using System.Runtime.CompilerServices; using System.Security.Principal; +using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; @@ -28,27 +30,41 @@ public class LdapUtilsNew { //This cache is indexed by domain sid private readonly ConcurrentDictionary _dcInfoCache = new(); private static readonly ConcurrentDictionary DomainCache = new(); - private static readonly ConcurrentDictionary DomainToForestCache = new(StringComparer.OrdinalIgnoreCase); + + private static readonly ConcurrentDictionary DomainToForestCache = + new(StringComparer.OrdinalIgnoreCase); + private static readonly ConcurrentDictionary SeenWellKnownPrincipals = new(); + + private readonly ConcurrentDictionary _hostResolutionMap = new(StringComparer.OrdinalIgnoreCase); private readonly ILogger _log; private readonly PortScanner _portScanner; private readonly NativeMethods _nativeMethods; private readonly string _nullCacheKey = Guid.NewGuid().ToString(); private readonly Regex SidRegex = new Regex(@"^(S-\d+-\d+-\d+-\d+-\d+-\d+)-\d+$"); - + private readonly string[] _translateNames = { "Administrator", "admin" }; private LDAPConfig _ldapConfig = new(); - + private ConnectionPoolManager _connectionPool; - + private static readonly TimeSpan MinBackoffDelay = TimeSpan.FromSeconds(2); private static readonly TimeSpan MaxBackoffDelay = TimeSpan.FromSeconds(20); private const int BackoffDelayMultiplier = 2; private const int MaxRetries = 3; - - private class ResolvedWellKnownPrincipal - { + + private static readonly byte[] NameRequest = { + 0x80, 0x94, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x20, 0x43, 0x4b, 0x41, + 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, + 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, + 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, + 0x41, 0x41, 0x41, 0x41, 0x41, 0x00, 0x00, 0x21, + 0x00, 0x01 + }; + + private class ResolvedWellKnownPrincipal { public string DomainName { get; set; } public string WkpId { get; set; } } @@ -64,7 +80,7 @@ public LdapUtilsNew(NativeMethods nativeMethods = null, PortScanner scanner = nu _nativeMethods = nativeMethods ?? new NativeMethods(); _portScanner = scanner ?? new PortScanner(); _log = log ?? Logging.LogProvider.CreateLogger("LDAPUtils"); - _connectionPool = new ConnectionPoolManager(_ldapConfig, scanner:_portScanner); + _connectionPool = new ConnectionPoolManager(_ldapConfig, scanner: _portScanner); } public void SetLDAPConfig(LDAPConfig config) { @@ -99,42 +115,49 @@ public async IAsyncEnumerable> Query(LdapQueryPar if (cancellationToken.IsCancellationRequested) { yield break; } - + try { _log.LogTrace("Sending ldap request - {Info}", queryParameters.GetQueryInfo()); response = (SearchResponse)connectionWrapper.Connection.SendRequest(searchRequest); if (response != null) { - querySuccess = true; + querySuccess = true; } else if (queryRetryCount == MaxRetries) { - tempResult = LdapResult.Fail($"Failed to get a response after {MaxRetries} attempts", queryParameters); - }else { + tempResult = + LdapResult.Fail($"Failed to get a response after {MaxRetries} attempts", + queryParameters); + } + else { queryRetryCount++; continue; } } - catch (LdapException le) when (le.ErrorCode == (int)LdapErrorCodes.ServerDown && queryRetryCount < MaxRetries) { + catch (LdapException le) when (le.ErrorCode == (int)LdapErrorCodes.ServerDown && + queryRetryCount < MaxRetries) { /* * A ServerDown exception indicates that our connection is no longer valid for one of many reasons. * We'll want to release our connection back to the pool, but dispose it. We need a new connection, * and because this is not a paged query, we can get this connection from anywhere. */ - + //Increment our query retry count queryRetryCount++; _connectionPool.ReleaseConnection(connectionWrapper, true); - + for (var retryCount = 0; retryCount < MaxRetries; retryCount++) { var backoffDelay = GetNextBackoff(retryCount); await Task.Delay(backoffDelay, cancellationToken); - var (success, newConnectionWrapper, message) = await _connectionPool.GetLdapConnection(queryParameters.DomainName, queryParameters.GlobalCatalog); + var (success, newConnectionWrapper, message) = + await _connectionPool.GetLdapConnection(queryParameters.DomainName, + queryParameters.GlobalCatalog); if (success) { - _log.LogDebug("Query - Recovered from ServerDown successfully, connection made to {NewServer}", newConnectionWrapper.GetServer()); + _log.LogDebug("Query - Recovered from ServerDown successfully, connection made to {NewServer}", + newConnectionWrapper.GetServer()); connectionWrapper = newConnectionWrapper; break; } - + //If we hit our max retries for making a new connection, set tempResult so we can yield it after this logic if (retryCount == MaxRetries - 1) { _log.LogError("Query - Failed to get a new connection after ServerDown.\n{Info}", @@ -155,7 +178,9 @@ public async IAsyncEnumerable> Query(LdapQueryPar await Task.Delay(backoffDelay, cancellationToken); } catch (LdapException le) { - tempResult = LdapResult.Fail($"Query - Caught unrecoverable ldap exception: {le.Message} (ServerMessage: {le.ServerErrorMessage}) (ErrorCode: {le.ErrorCode})", queryParameters); + tempResult = LdapResult.Fail( + $"Query - Caught unrecoverable ldap exception: {le.Message} (ServerMessage: {le.ServerErrorMessage}) (ErrorCode: {le.ErrorCode})", + queryParameters); } catch (Exception e) { tempResult = @@ -222,11 +247,13 @@ public async IAsyncEnumerable> PagedQuery(LdapQue _log.LogTrace("Sending paged ldap request - {Info}", queryParameters.GetQueryInfo()); response = (SearchResponse)connectionWrapper.Connection.SendRequest(searchRequest); if (response != null) { - pageResponse = (PageResultResponseControl)response.Controlsdo + pageResponse = (PageResultResponseControl)response.Controls .Where(x => x is PageResultResponseControl).DefaultIfEmpty(null).FirstOrDefault(); queryRetryCount = 0; - }else if (queryRetryCount == MaxRetries) { - tempResult = LdapResult.Fail($"PagedQuery - Failed to get a response after {MaxRetries} attempts", + } + else if (queryRetryCount == MaxRetries) { + tempResult = LdapResult.Fail( + $"PagedQuery - Failed to get a response after {MaxRetries} attempts", queryParameters); } else { @@ -253,14 +280,14 @@ public async IAsyncEnumerable> PagedQuery(LdapQue var backoffDelay = GetNextBackoff(retryCount); await Task.Delay(backoffDelay, cancellationToken); var (success, ldapConnectionWrapperNew, message) = await _connectionPool.GetLdapConnectionForServer( - queryParameters.DomainName,serverName, queryParameters.GlobalCatalog); + queryParameters.DomainName, serverName, queryParameters.GlobalCatalog); if (success) { _log.LogDebug("PagedQuery - Recovered from ServerDown successfully"); connectionWrapper = ldapConnectionWrapperNew; break; } - + if (retryCount == MaxRetries - 1) { _log.LogError("PagedQuery - Failed to get a new connection after ServerDown.\n{Info}", queryParameters.GetQueryInfo()); @@ -278,10 +305,14 @@ public async IAsyncEnumerable> PagedQuery(LdapQue await Task.Delay(backoffDelay, cancellationToken); } catch (LdapException le) { - tempResult = LdapResult.Fail($"PagedQuery - Caught unrecoverable ldap exception: {le.Message} (ServerMessage: {le.ServerErrorMessage}) (ErrorCode: {le.ErrorCode})", queryParameters); + tempResult = LdapResult.Fail( + $"PagedQuery - Caught unrecoverable ldap exception: {le.Message} (ServerMessage: {le.ServerErrorMessage}) (ErrorCode: {le.ErrorCode})", + queryParameters); } catch (Exception e) { - tempResult = LdapResult.Fail($"PagedQuery - Caught unrecoverable exception: {e.Message}", queryParameters); + tempResult = + LdapResult.Fail($"PagedQuery - Caught unrecoverable exception: {e.Message}", + queryParameters); } if (tempResult != null) { @@ -314,11 +345,13 @@ public async IAsyncEnumerable> PagedQuery(LdapQue } } - public bool ResolveIDAndType(SecurityIdentifier securityIdentifier, string objectDomain, out TypedPrincipal resolvedPrincipal) { - return ResolveIDAndType(securityIdentifier.Value, objectDomain, out resolvedPrincipal); + public async Task<(bool Success, TypedPrincipal Principal)> ResolveIDAndType(SecurityIdentifier securityIdentifier, + string objectDomain) { + return await ResolveIDAndType(securityIdentifier.Value, objectDomain); } - - public async Task<(bool Success, TypedPrincipal Principal)> ResolveIDAndType(string identifier, string objectDomain) { + + public async Task<(bool Success, TypedPrincipal Principal)> + ResolveIDAndType(string identifier, string objectDomain) { if (identifier.Contains("0ACNF")) { return (false, null); } @@ -326,28 +359,93 @@ public bool ResolveIDAndType(SecurityIdentifier securityIdentifier, string objec if (await GetWellKnownPrincipal(identifier, objectDomain) is (true, var principal)) { return (true, principal); } - - var type = identifier.StartsWith("S-") ? LookupSidType(id, fallbackDomain) : LookupGuidType(id, fallbackDomain); - return new TypedPrincipal(id, type); + + if (identifier.StartsWith("S-")) { + var result = await LookupSidType(identifier, objectDomain); + return (result.Success, new TypedPrincipal(identifier, result.Type)); + } + + var (success, type) = await LookupGuidType(identifier, objectDomain); + return (success, new TypedPrincipal(identifier, type)); } - private async Task<(bool Success, Label type)> LookupSidType(string sid, string domain) { + private async Task<(bool Success, Label Type)> LookupSidType(string sid, string domain) { if (Cache.GetIDType(sid, out var type)) { return (true, type); } - if (await GetDomainSidFromDomainName(domain) is (true, var domainSid)) { - + var tempDomain = domain; + + if (await GetDomainNameFromSid(sid) is (true, var domainName)) { + tempDomain = domainName; + } + + var result = await Query(new LdapQueryParameters() { + DomainName = tempDomain, + LDAPFilter = CommonFilters.SpecificSID(sid), + Attributes = CommonProperties.TypeResolutionProps + }).FirstAsync(); + + if (result.IsSuccess) { + type = result.Value.GetLabel(); + Cache.AddType(sid, type); + return (true, type); + } + + + try { + var entry = new DirectoryEntry($"LDAP://"); + if (entry.GetLabel(out type)) { + Cache.AddType(sid, type); + return (true, type); + } + + return (false, Label.Base); + } + catch { + return (false, Label.Base); } } - public async Task<(bool Success, TypedPrincipal wellKnownPrincipal)> GetWellKnownPrincipal(string securityIdentifier, string objectDomain) { + private async Task<(bool Success, Label type)> LookupGuidType(string guid, string domain) { + if (Cache.GetIDType(guid, out var type)) { + return (true, type); + } + + var result = await Query(new LdapQueryParameters() { + DomainName = domain, + LDAPFilter = CommonFilters.SpecificGUID(guid), + Attributes = CommonProperties.TypeResolutionProps + }).FirstAsync(); + + if (result.IsSuccess) { + type = result.Value.GetLabel(); + Cache.AddType(guid, type); + return (true, type); + } + + try { + var entry = new DirectoryEntry($"LDAP://"); + if (entry.GetLabel(out type)) { + Cache.AddType(guid, type); + return (true, type); + } + + return (false, Label.Base); + } + catch { + return (false, Label.Base); + } + } + + public async Task<(bool Success, TypedPrincipal wellKnownPrincipal)> GetWellKnownPrincipal( + string securityIdentifier, string objectDomain) { if (!WellKnownPrincipal.GetWellKnownPrincipal(securityIdentifier, out var wellKnownPrincipal)) { return (false, null); } var (newIdentifier, newDomain) = await GetWellKnownPrincipalObjectIdentifier(securityIdentifier, objectDomain); - + wellKnownPrincipal.ObjectIdentifier = newIdentifier; SeenWellKnownPrincipals.TryAdd(wellKnownPrincipal.ObjectIdentifier, new ResolvedWellKnownPrincipal { DomainName = newDomain, @@ -357,14 +455,17 @@ public bool ResolveIDAndType(SecurityIdentifier securityIdentifier, string objec return (true, wellKnownPrincipal); } - private async Task<(string ObjectID, string Domain)> GetWellKnownPrincipalObjectIdentifier(string securityIdentifier, string domain) { - if (!WellKnownPrincipal.GetWellKnownPrincipal(securityIdentifier, out _)) return (securityIdentifier, string.Empty); + private async Task<(string ObjectID, string Domain)> GetWellKnownPrincipalObjectIdentifier( + string securityIdentifier, string domain) { + if (!WellKnownPrincipal.GetWellKnownPrincipal(securityIdentifier, out _)) + return (securityIdentifier, string.Empty); if (!securityIdentifier.Equals("S-1-5-9", StringComparison.OrdinalIgnoreCase)) { var tempDomain = domain; if (GetDomain(tempDomain, out var domainObject) && domainObject.Name != null) { tempDomain = domainObject.Name; } + return ($"{tempDomain}-{securityIdentifier}".ToUpper(), tempDomain); } @@ -380,6 +481,7 @@ public bool ResolveIDAndType(SecurityIdentifier securityIdentifier, string objec if (DomainToForestCache.TryGetValue(domain, out var cachedForest)) { return (true, cachedForest); } + if (GetDomain(domain, out var domainObject)) { var forestName = domainObject.Forest.Name.ToUpper(); DomainToForestCache.TryAdd(domain, forestName); @@ -466,8 +568,6 @@ private bool CreateSearchRequest(LdapQueryParameters queryParameters, return true; } - - private bool CallDsGetDcName(string domainName, out NetAPIStructs.DomainControllerInfo? info) { if (_dcInfoCache.TryGetValue(domainName.ToUpper().Trim(), out info)) return info != null; @@ -514,8 +614,9 @@ private async Task SetupLdapQuery(LdapQueryParameters quer result.ConnectionWrapper = connectionWrapper; return result; } - - public static SearchRequest CreateSearchRequest(string distinguishedName, string ldapFilter, SearchScope searchScope, + + public static SearchRequest CreateSearchRequest(string distinguishedName, string ldapFilter, + SearchScope searchScope, string[] attributes) { var searchRequest = new SearchRequest(distinguishedName, ldapFilter, searchScope, attributes); @@ -577,24 +678,26 @@ public static SearchRequest CreateSearchRequest(string distinguishedName, string if (result.IsSuccess) { return (true, Helpers.DistinguishedNameToDomain(result.Value.DistinguishedName)); } - + result = await Query(new LdapQueryParameters { DomainName = domain.Name, Attributes = new[] { LDAPProperties.DistinguishedName }, GlobalCatalog = true, - LDAPFilter = new LDAPFilter().AddFilter("(objectclass=trusteddomain)", true).AddFilter($"(securityidentifier={Helpers.ConvertSidToHexSid(domainSid)})", true).GetFilter() + LDAPFilter = new LDAPFilter().AddFilter("(objectclass=trusteddomain)", true) + .AddFilter($"(securityidentifier={Helpers.ConvertSidToHexSid(domainSid)})", true).GetFilter() }).FirstAsync(); if (result.IsSuccess) { return (true, Helpers.DistinguishedNameToDomain(result.Value.DistinguishedName)); } - + result = await Query(new LdapQueryParameters { DomainName = domain.Name, Attributes = new[] { LDAPProperties.DistinguishedName }, - LDAPFilter = new LDAPFilter().AddFilter("(objectclass=domaindns)", true).AddFilter(CommonFilters.SpecificSID(domainSid), true).GetFilter() + LDAPFilter = new LDAPFilter().AddFilter("(objectclass=domaindns)", true) + .AddFilter(CommonFilters.SpecificSID(domainSid), true).GetFilter() }).FirstAsync(); - + if (result.IsSuccess) { return (true, Helpers.DistinguishedNameToDomain(result.Value.DistinguishedName)); } @@ -604,7 +707,7 @@ public static SearchRequest CreateSearchRequest(string distinguishedName, string public async Task<(bool Success, string domainSid)> GetDomainSidFromDomainName(string domainName) { if (Cache.GetDomainSidMapping(domainName, out var domainSid)) return (true, domainSid); - + try { var entry = new DirectoryEntry($"LDAP://{domainName}"); //Force load objectsid into the object cache @@ -658,6 +761,7 @@ public static SearchRequest CreateSearchRequest(string distinguishedName, string return (true, domainSid); } } + return (false, string.Empty); } @@ -749,8 +853,244 @@ public bool GetDomain(out Domain domain) { } } - private struct LdapFailure { - public LdapFailureReason FailureReason { get; set; } - public string Message { get; set; } + public async Task<(bool Success, TypedPrincipal Principal)> ResolveAccountName(string name, string domain) { + if (string.IsNullOrWhiteSpace(name)) { + return (false, null); + } + + if (Cache.GetPrefixedValue(name, domain, out var id) && Cache.GetIDType(id, out var type)) + return (true, new TypedPrincipal { + ObjectIdentifier = id, + ObjectType = type + }); + + var result = await Query(new LdapQueryParameters() { + DomainName = domain, + Attributes = CommonProperties.TypeResolutionProps, + LDAPFilter = $"(samaccountname={name})" + }).FirstAsync(); + + if (result.IsSuccess) { + type = result.Value.GetLabel(); + id = result.Value.GetObjectIdentifier(); + + if (!string.IsNullOrWhiteSpace(id)) { + Cache.AddPrefixedValue(name, domain, id); + Cache.AddType(id, type); + } + + var (tempID, _) = await GetWellKnownPrincipalObjectIdentifier(id, domain); + return (true, new TypedPrincipal(tempID, type)); + } + + return (false, null); + } + + public async Task<(bool Success, string SecurityIdentifier)> ResolveHostToSid(string host, string domain) { + //Remove SPN prefixes from the host name so we're working with a clean name + var strippedHost = Helpers.StripServicePrincipalName(host).ToUpper().TrimEnd('$'); + if (string.IsNullOrEmpty(strippedHost)) { + return (false, string.Empty); + } + + if (_hostResolutionMap.TryGetValue(strippedHost, out var sid)) return (true, sid); + + //Immediately start with NetWekstaGetInfo as its our most reliable indicator if successful + var workstationInfo = await GetWorkstationInfo(strippedHost); + if (workstationInfo.HasValue) { + var tempName = workstationInfo.Value.ComputerName; + var tempDomain = workstationInfo.Value.LanGroup; + + if (string.IsNullOrWhiteSpace(tempDomain)) { + tempDomain = domain; + } + + if (!string.IsNullOrWhiteSpace(tempName)) { + tempName = $"{tempName}$".ToUpper(); + if (await ResolveAccountName(tempName, tempDomain) is (true, var principal)) { + _hostResolutionMap.TryAdd(strippedHost, principal.ObjectIdentifier); + return (true, principal.ObjectIdentifier); + } + } + } + + //Try some socket magic to get the NETBIOS name + if (RequestNETBIOSNameFromComputer(strippedHost, domain, out var netBiosName)) { + if (!string.IsNullOrWhiteSpace(netBiosName)) { + var result = await ResolveAccountName($"{netBiosName}$", domain); + if (result.Success) { + _hostResolutionMap.TryAdd(strippedHost, result.Principal.ObjectIdentifier); + return (true, result.Principal.ObjectIdentifier); + } + } + } + + //Start by handling non-IP address names + if (!IPAddress.TryParse(strippedHost, out _)) { + //PRIMARY.TESTLAB.LOCAL + if (strippedHost.Contains(".")) { + var split = strippedHost.Split('.'); + var name = split[0]; + var result = await ResolveAccountName($"{name}$", domain); + if (result.Success) { + _hostResolutionMap.TryAdd(strippedHost, result.Principal.ObjectIdentifier); + return (true, result.Principal.ObjectIdentifier); + } + + var tempDomain = string.Join(".", split.Skip(1).ToArray()); + result = await ResolveAccountName($"{name}$", tempDomain); + if (result.Success) { + _hostResolutionMap.TryAdd(strippedHost, result.Principal.ObjectIdentifier); + return (true, result.Principal.ObjectIdentifier); + } + } + else { + //Format: WIN10 (probably a netbios name) + var result = await ResolveAccountName($"{strippedHost}$", domain); + if (result.Success) { + _hostResolutionMap.TryAdd(strippedHost, result.Principal.ObjectIdentifier); + return (true, result.Principal.ObjectIdentifier); + } + } + } + + try { + var resolvedHostname = (await Dns.GetHostEntryAsync(strippedHost)).HostName; + var split = resolvedHostname.Split('.'); + var name = split[0]; + var result = await ResolveAccountName($"{name}$", domain); + if (result.Success) { + _hostResolutionMap.TryAdd(strippedHost, result.Principal.ObjectIdentifier); + return (true, result.Principal.ObjectIdentifier); + } + + var tempDomain = string.Join(".", split.Skip(1).ToArray()); + result = await ResolveAccountName($"{name}$", tempDomain); + if (result.Success) { + _hostResolutionMap.TryAdd(strippedHost, result.Principal.ObjectIdentifier); + return (true, result.Principal.ObjectIdentifier); + } + } + catch { + //pass + } + + return (false, ""); + } + + /// + /// Calls the NetWkstaGetInfo API on a hostname + /// + /// + /// + private async Task GetWorkstationInfo(string hostname) { + if (!await _portScanner.CheckPort(hostname)) + return null; + + var result = _nativeMethods.CallNetWkstaGetInfo(hostname); + if (result.IsSuccess) return result.Value; + + return null; + } + + public async Task<(bool success, string[] sids)> GetGlobalCatalogMatches(string name, string domain) { + if (Cache.GetGCCache(name, out var matches)) { + return (true, matches); + } + + var sids = new List(); + + await foreach (var result in Query(new LdapQueryParameters { + DomainName = domain, + Attributes = new[] { LDAPProperties.ObjectSID }, + GlobalCatalog = true, + LDAPFilter = new LDAPFilter().AddUsers($"(samaccountname={name})").GetFilter() + })) { + if (result.IsSuccess) { + var sid = result.Value.GetSid(); + if (!string.IsNullOrWhiteSpace(sid)) { + sids.Add(sid); + } + } + else { + return (false, Array.Empty()); + } + } + + return (true, sids.ToArray()); + } + + /// + /// Uses a socket and a set of bytes to request the NETBIOS name from a remote computer + /// + /// + /// + /// + /// + private static bool RequestNETBIOSNameFromComputer(string server, string domain, out string netbios) { + var receiveBuffer = new byte[1024]; + var requestSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); + try { + //Set receive timeout to 1 second + requestSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, 1000); + EndPoint remoteEndpoint; + + //We need to create an endpoint to bind too. If its an IP, just use that. + if (IPAddress.TryParse(server, out var parsedAddress)) + remoteEndpoint = new IPEndPoint(parsedAddress, 137); + else + //If its not an IP, we're going to try and resolve it from DNS + try { + IPAddress address; + if (server.Contains(".")) + address = Dns + .GetHostAddresses(server).First(x => x.AddressFamily == AddressFamily.InterNetwork); + else + address = Dns.GetHostAddresses($"{server}.{domain}")[0]; + + if (address == null) { + netbios = null; + return false; + } + + remoteEndpoint = new IPEndPoint(address, 137); + } + catch { + //Failed to resolve an IP, so return null + netbios = null; + return false; + } + + var originEndpoint = new IPEndPoint(IPAddress.Any, 0); + requestSocket.Bind(originEndpoint); + + try { + requestSocket.SendTo(NameRequest, remoteEndpoint); + var receivedByteCount = requestSocket.ReceiveFrom(receiveBuffer, ref remoteEndpoint); + if (receivedByteCount >= 90) { + netbios = new ASCIIEncoding().GetString(receiveBuffer, 57, 16).Trim('\0', ' '); + return true; + } + + netbios = null; + return false; + } + catch (SocketException) { + netbios = null; + return false; + } + } + finally { + //Make sure we close the socket if its open + requestSocket.Close(); + } + } + + /// + /// Created for testing purposes + /// + /// + public static ActiveDirectorySecurityDescriptor MakeSecurityDescriptor() { + return new ActiveDirectorySecurityDescriptor(new ActiveDirectorySecurity()); } } \ No newline at end of file diff --git a/src/CommonLib/NativeMethods.cs b/src/CommonLib/NativeMethods.cs index d560f45d..96d2e5a5 100644 --- a/src/CommonLib/NativeMethods.cs +++ b/src/CommonLib/NativeMethods.cs @@ -36,5 +36,9 @@ public virtual NetAPIResult> NetWkstaUserEn { return NetAPIMethods.DsGetDcName(computerName, domainName, flags); } + + public virtual NetAPIResult CallNetWkstaGetInfo(string serverName) { + return NetAPIMethods.NetWkstaGetInfo(serverName); + } } } \ No newline at end of file diff --git a/src/CommonLib/Processors/CertAbuseProcessor.cs b/src/CommonLib/Processors/CertAbuseProcessor.cs index c66a7433..4ab0f156 100644 --- a/src/CommonLib/Processors/CertAbuseProcessor.cs +++ b/src/CommonLib/Processors/CertAbuseProcessor.cs @@ -17,7 +17,7 @@ namespace SharpHoundCommonLib.Processors public class CertAbuseProcessor { private readonly ILogger _log; - public readonly ILDAPUtils _utils; + private readonly ILDAPUtils _utils; public delegate Task ComputerStatusDelegate(CSVComputerStatus status); public event ComputerStatusDelegate ComputerStatusEvent; @@ -162,7 +162,7 @@ public async Task ProcessEAPermissions(string foreach (var genericAce in descriptor.DiscretionaryAcl) { var ace = (QualifiedAce)genericAce; - enrollmentAgentRestrictions.Add(new EnrollmentAgentRestriction(ace, computerDomain, certTemplatesLocation, this, computerName, isDomainController, computerObjectId, machineSid)); + enrollmentAgentRestrictions.Add(new EnrollmentAgentRestriction(ace, computerDomain, certTemplatesLocation, this, _utils, computerName, isDomainController, computerObjectId, machineSid)); } ret.Restrictions = enrollmentAgentRestrictions.ToArray(); @@ -381,18 +381,15 @@ await SendComputerStatus(new CSVComputerStatus return machineSid; } - // TODO: Copied from URA processor. Find a way to have this function in a shared spot - - - public virtual LdapResult OpenSamServer(string computerName) + public virtual SharpHoundRPC.Result OpenSamServer(string computerName) { var result = SAMServer.OpenServer(computerName); if (result.IsFailed) { - return LdapResult.Fail(result.SError); + return SharpHoundRPC.Result.Fail(result.SError); } - return LdapResult.Ok(result.Value); + return SharpHoundRPC.Result.Ok(result.Value); } private async Task SendComputerStatus(CSVComputerStatus status) @@ -404,7 +401,7 @@ private async Task SendComputerStatus(CSVComputerStatus status) public class EnrollmentAgentRestriction { - public EnrollmentAgentRestriction(QualifiedAce ace, string computerDomain, string certTemplatesLocation, CertAbuseProcessor certAbuseProcessor, string computerName, bool isDomainController, string computerObjectId, SecurityIdentifier machineSid) + public EnrollmentAgentRestriction(QualifiedAce ace, string computerDomain, string certTemplatesLocation, CertAbuseProcessor certAbuseProcessor, ILDAPUtils utils, string computerName, bool isDomainController, string computerObjectId, SecurityIdentifier machineSid) { var targets = new List(); var index = 0; @@ -434,12 +431,12 @@ public EnrollmentAgentRestriction(QualifiedAce ace, string computerDomain, strin var template = Encoding.Unicode.GetString(opaque, index, opaque.Length - index - 2).Replace("\u0000", string.Empty); // Attempt to resolve the cert template by CN - Template = certAbuseProcessor._utils.ResolveCertTemplateByProperty(Encoder.LdapFilterEncode(template), LDAPProperties.CanonicalName, certTemplatesLocation, computerDomain); + Template = utils.ResolveCertTemplateByProperty(Encoder.LdapFilterEncode(template), LDAPProperties.CanonicalName, certTemplatesLocation, computerDomain); // Attempt to resolve the cert template by OID if (Template == null) { - Template = certAbuseProcessor._utils.ResolveCertTemplateByProperty(template, LDAPProperties.CertTemplateOID, certTemplatesLocation, computerDomain); + Template = utils.ResolveCertTemplateByProperty(template, LDAPProperties.CertTemplateOID, certTemplatesLocation, computerDomain); } } else diff --git a/src/CommonLib/Processors/LocalGroupProcessor.cs b/src/CommonLib/Processors/LocalGroupProcessor.cs index 416a0924..9662ef1e 100644 --- a/src/CommonLib/Processors/LocalGroupProcessor.cs +++ b/src/CommonLib/Processors/LocalGroupProcessor.cs @@ -6,7 +6,6 @@ using Microsoft.Extensions.Logging; using SharpHoundCommonLib.Enums; using SharpHoundCommonLib.OutputTypes; -using SharpHoundRPC; using SharpHoundRPC.Shared; using SharpHoundRPC.Wrappers; @@ -26,15 +25,15 @@ public LocalGroupProcessor(ILDAPUtils utils, ILogger log = null) public event ComputerStatusDelegate ComputerStatusEvent; - public virtual LdapResult OpenSamServer(string computerName) + public virtual SharpHoundRPC.Result OpenSamServer(string computerName) { var result = SAMServer.OpenServer(computerName); if (result.IsFailed) { - return LdapResult.Fail(result.SError); + return SharpHoundRPC.Result.Fail(result.SError); } - return LdapResult.Ok(result.Value); + return SharpHoundRPC.Result.Ok(result.Value); } public IAsyncEnumerable GetLocalGroups(ResolvedSearchResult result) From fa3bfe760c6f8e4c758350f3a2751f3d7999a8e8 Mon Sep 17 00:00:00 2001 From: rvazarkar Date: Wed, 26 Jun 2024 13:52:13 -0400 Subject: [PATCH 12/68] wip: more ldap utils work --- src/CommonLib/ConnectionPoolManager.cs | 2 +- src/CommonLib/ILdapUtilsNew.cs | 43 ++++++ src/CommonLib/LdapConnectionPool.cs | 15 +- src/CommonLib/LdapUtilsNew.cs | 189 +++++++++++++++++++++++-- test/unit/CommonLibTest.csproj | 12 +- 5 files changed, 242 insertions(+), 19 deletions(-) create mode 100644 src/CommonLib/ILdapUtilsNew.cs diff --git a/src/CommonLib/ConnectionPoolManager.cs b/src/CommonLib/ConnectionPoolManager.cs index e74526bc..aadb3585 100644 --- a/src/CommonLib/ConnectionPoolManager.cs +++ b/src/CommonLib/ConnectionPoolManager.cs @@ -32,7 +32,7 @@ public void ReleaseConnection(LdapConnectionWrapperNew connectionWrapper, bool c pool.ReleaseConnection(connectionWrapper, connectionFaulted); } - public async Task<(bool Success, LdapConnectionWrapperNew connectionWrapper, string Message)> GetLdapConnection( + public async Task<(bool Success, LdapConnectionWrapperNew ConnectionWrapper, string Message)> GetLdapConnection( string identifier, bool globalCatalog) { var resolved = ResolveIdentifier(identifier); diff --git a/src/CommonLib/ILdapUtilsNew.cs b/src/CommonLib/ILdapUtilsNew.cs new file mode 100644 index 00000000..2a87b2df --- /dev/null +++ b/src/CommonLib/ILdapUtilsNew.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; +using System.DirectoryServices.Protocols; +using System.Runtime.CompilerServices; +using System.Security.Principal; +using System.Threading; +using System.Threading.Tasks; +using SharpHoundCommonLib.Enums; +using SharpHoundCommonLib.OutputTypes; + +namespace SharpHoundCommonLib; + +public interface ILdapUtilsNew { + IAsyncEnumerable> Query(LdapQueryParameters queryParameters, + CancellationToken cancellationToken); + + IAsyncEnumerable> PagedQuery(LdapQueryParameters queryParameters, + CancellationToken cancellationToken); + + IAsyncEnumerable> RangedRetrieval(string distinguishedName, + string attributeName, CancellationToken cancellationToken = new()); + + Task<(bool Success, TypedPrincipal Principal)> ResolveIDAndType(SecurityIdentifier securityIdentifier, + string objectDomain); + + Task<(bool Success, TypedPrincipal Principal)> + ResolveIDAndType(string identifier, string objectDomain); + + Task<(bool Success, TypedPrincipal WellKnownPrincipal)> GetWellKnownPrincipal( + string securityIdentifier, string objectDomain); + + Task<(bool Success, string DomainName)> GetDomainNameFromSid(string sid); + Task<(bool Success, string DomainSid)> GetDomainSidFromDomainName(string domainName); + bool GetDomain(string domainName, out System.DirectoryServices.ActiveDirectory.Domain domain); + bool GetDomain(out System.DirectoryServices.ActiveDirectory.Domain domain); + Task<(bool Success, TypedPrincipal Principal)> ResolveAccountName(string name, string domain); + Task<(bool Success, string SecurityIdentifier)> ResolveHostToSid(string host, string domain); + Task<(bool Success, string[] Sids)> GetGlobalCatalogMatches(string name, string domain); + Task<(bool Success, TypedPrincipal Principal)> ResolveCertTemplateByProperty(string propValue, string propName, string containerDN, string domainName); + ActiveDirectorySecurityDescriptor MakeSecurityDescriptor(); + + public Task<(bool Success, TypedPrincipal Principal)> ConvertLocalWellKnownPrincipal(SecurityIdentifier sid, + string computerDomainSid, string computerDomain); +} \ No newline at end of file diff --git a/src/CommonLib/LdapConnectionPool.cs b/src/CommonLib/LdapConnectionPool.cs index 23f38580..1f71c944 100644 --- a/src/CommonLib/LdapConnectionPool.cs +++ b/src/CommonLib/LdapConnectionPool.cs @@ -36,7 +36,7 @@ public LdapConnectionPool(string identifier, LDAPConfig config, int maxConnectio _nativeMethods = nativeMethods ?? new NativeMethods(); } - public async Task<(bool Success, LdapConnectionWrapperNew connectionWrapper, string Message)> GetConnectionAsync() { + public async Task<(bool Success, LdapConnectionWrapperNew ConnectionWrapper, string Message)> GetConnectionAsync() { await _semaphore.WaitAsync(); if (!_connections.TryTake(out var connectionWrapper)) { var (success, connection, message) = await CreateNewConnection(); @@ -65,7 +65,7 @@ public LdapConnectionPool(string identifier, LDAPConfig config, int maxConnectio return result; } - public async Task<(bool Success, LdapConnectionWrapperNew connectionWrapper, string Message)> GetGlobalCatalogConnectionAsync() { + public async Task<(bool Success, LdapConnectionWrapperNew ConnectionWrapper, string Message)> GetGlobalCatalogConnectionAsync() { await _semaphore.WaitAsync(); if (!_globalCatalogConnection.TryTake(out var connectionWrapper)) { var (success, connection, message) = await CreateNewConnection(true); @@ -293,7 +293,7 @@ private bool TestLdapConnection(LdapConnection connection, out LdapConnectionTes try { //Do an initial search request to get the rootDSE //This ldap filter is equivalent to (objectclass=*) - var searchRequest = LdapUtilsNew.CreateSearchRequest("", new LDAPFilter().AddAllObjects().GetFilter(), + var searchRequest = CreateSearchRequest("", new LDAPFilter().AddAllObjects().GetFilter(), SearchScope.Base, null); response = (SearchResponse)connection.SendRequest(searchRequest); @@ -344,6 +344,15 @@ await _portScanner.CheckPort(target, _ldapConfig.GetPort(false)))) return (false, null); } + + private SearchRequest CreateSearchRequest(string distinguishedName, string ldapFilter, + SearchScope searchScope, + string[] attributes) { + var searchRequest = new SearchRequest(distinguishedName, ldapFilter, + searchScope, attributes); + searchRequest.Controls.Add(new SearchOptionsControl(SearchOption.DomainScope)); + return searchRequest; + } } //TESTLAB diff --git a/src/CommonLib/LdapUtilsNew.cs b/src/CommonLib/LdapUtilsNew.cs index b8ad134e..6a126950 100644 --- a/src/CommonLib/LdapUtilsNew.cs +++ b/src/CommonLib/LdapUtilsNew.cs @@ -26,7 +26,7 @@ namespace SharpHoundCommonLib; -public class LdapUtilsNew { +public class LdapUtilsNew : ILdapUtilsNew{ //This cache is indexed by domain sid private readonly ConcurrentDictionary _dcInfoCache = new(); private static readonly ConcurrentDictionary DomainCache = new(); @@ -89,6 +89,123 @@ public void SetLDAPConfig(LDAPConfig config) { _connectionPool = new ConnectionPoolManager(_ldapConfig, scanner: _portScanner); } + public async IAsyncEnumerable> RangedRetrieval(string distinguishedName, + string attributeName, [EnumeratorCancellation] CancellationToken cancellationToken = new()) { + var domain = Helpers.DistinguishedNameToDomain(distinguishedName); + + var connectionResult = await _connectionPool.GetLdapConnection(domain, false); + if (!connectionResult.Success) { + yield return Result.Fail(connectionResult.Message); + yield break; + } + + var index = 0; + var step = 0; + + //Start by using * as our upper index, which will automatically give us the range size + var currentRange = $"{attributeName};range={index}-*"; + var complete = false; + + var queryParameters = new LdapQueryParameters() { + DomainName = domain, + LDAPFilter = $"{attributeName}=*", + Attributes = new[] { currentRange }, + SearchScope = SearchScope.Base, + SearchBase = distinguishedName + }; + var connectionWrapper = connectionResult.ConnectionWrapper; + + if (!CreateSearchRequest(queryParameters, connectionWrapper, out var searchRequest)) { + yield return Result.Fail("Failed to create search request"); + yield break; + } + + var queryRetryCount = 0; + var busyRetryCount = 0; + + Result tempResult = null; + + while (true) { + if (cancellationToken.IsCancellationRequested) { + yield break; + } + SearchResponse response = null; + try { + response = (SearchResponse)connectionWrapper.Connection.SendRequest(searchRequest); + } + catch (LdapException le) when (le.ErrorCode == (int)ResultCode.Busy && busyRetryCount < MaxRetries) { + busyRetryCount++; + var backoffDelay = GetNextBackoff(busyRetryCount); + await Task.Delay(backoffDelay, cancellationToken); + } + catch (LdapException le) when (le.ErrorCode == (int)LdapErrorCodes.ServerDown && queryRetryCount < MaxRetries) { + queryRetryCount++; + _connectionPool.ReleaseConnection(connectionWrapper, true); + for (var retryCount = 0; retryCount < MaxRetries; retryCount++) { + var backoffDelay = GetNextBackoff(retryCount); + await Task.Delay(backoffDelay, cancellationToken); + var (success, newConnectionWrapper, message) = + await _connectionPool.GetLdapConnection(domain, + false); + if (success) { + _log.LogDebug("RangedRetrieval - Recovered from ServerDown successfully, connection made to {NewServer}", + newConnectionWrapper.GetServer()); + connectionWrapper = newConnectionWrapper; + break; + } + + //If we hit our max retries for making a new connection, set tempResult so we can yield it after this logic + if (retryCount == MaxRetries - 1) { + _log.LogError("RangedRetrieval - Failed to get a new connection after ServerDown for path {Path}", distinguishedName); + tempResult = + Result.Fail( + "RangedRetrieval - Failed to get a new connection after ServerDown."); + } + } + }catch (LdapException le) { + tempResult = Result.Fail( + $"Caught unrecoverable ldap exception: {le.Message} (ServerMessage: {le.ServerErrorMessage}) (ErrorCode: {le.ErrorCode})"); + } + catch (Exception e) { + tempResult = + Result.Fail($"Caught unrecoverable exception: {e.Message}"); + } + + //If we have a tempResult set it means we hit an error we couldn't recover from, so yield that result and then break out of the function + if (tempResult != null) { + yield return tempResult; + yield break; + } + + if (response?.Entries.Count == 1) { + var entry = response.Entries[0]; + //We dont know the name of our attribute, but there should only be one, so we're safe to just use a loop here + foreach (string attr in entry.Attributes.AttributeNames) { + currentRange = attr; + complete = currentRange.IndexOf("*", 0, StringComparison.OrdinalIgnoreCase) > 0; + step = entry.Attributes[currentRange].Count; + } + + foreach (string dn in entry.Attributes[currentRange].GetValues(typeof(string))) { + yield return Result.Ok(dn); + index++; + } + + if (complete) { + yield break; + } + + currentRange = $"{attributeName};range={index}-{index + step}"; + searchRequest.Attributes.Clear(); + searchRequest.Attributes.Add(currentRange); + } + else { + //I dont know what can cause a RR to have multiple entries, but its nothing good. Break out + yield break; + } + } + } + public async IAsyncEnumerable> Query(LdapQueryParameters queryParameters, [EnumeratorCancellation] CancellationToken cancellationToken = new()) { var setupResult = await SetupLdapQuery(queryParameters); @@ -291,7 +408,9 @@ public async IAsyncEnumerable> PagedQuery(LdapQue if (retryCount == MaxRetries - 1) { _log.LogError("PagedQuery - Failed to get a new connection after ServerDown.\n{Info}", queryParameters.GetQueryInfo()); - yield break; + tempResult = + LdapResult.Fail("Failed to get a new connection after serverdown", + queryParameters); } } } @@ -438,7 +557,7 @@ public async IAsyncEnumerable> PagedQuery(LdapQue } } - public async Task<(bool Success, TypedPrincipal wellKnownPrincipal)> GetWellKnownPrincipal( + public async Task<(bool Success, TypedPrincipal WellKnownPrincipal)> GetWellKnownPrincipal( string securityIdentifier, string objectDomain) { if (!WellKnownPrincipal.GetWellKnownPrincipal(securityIdentifier, out var wellKnownPrincipal)) { return (false, null); @@ -523,7 +642,7 @@ private static TimeSpan GetNextBackoff(int retryCount) { } private bool CreateSearchRequest(LdapQueryParameters queryParameters, - ref LdapConnectionWrapperNew connectionWrapper, out SearchRequest searchRequest) { + LdapConnectionWrapperNew connectionWrapper, out SearchRequest searchRequest) { string basePath; if (!string.IsNullOrWhiteSpace(queryParameters.SearchBase)) { basePath = queryParameters.SearchBase; @@ -602,7 +721,7 @@ private async Task SetupLdapQuery(LdapQueryParameters quer return result; } - if (!CreateSearchRequest(queryParameters, ref connectionWrapper, out var searchRequest)) { + if (!CreateSearchRequest(queryParameters, connectionWrapper, out var searchRequest)) { result.Success = false; result.Message = "Failed to create search request"; return result; @@ -615,7 +734,7 @@ private async Task SetupLdapQuery(LdapQueryParameters quer return result; } - public static SearchRequest CreateSearchRequest(string distinguishedName, string ldapFilter, + private SearchRequest CreateSearchRequest(string distinguishedName, string ldapFilter, SearchScope searchScope, string[] attributes) { var searchRequest = new SearchRequest(distinguishedName, ldapFilter, @@ -705,7 +824,7 @@ public static SearchRequest CreateSearchRequest(string distinguishedName, string return (false, string.Empty); } - public async Task<(bool Success, string domainSid)> GetDomainSidFromDomainName(string domainName) { + public async Task<(bool Success, string DomainSid)> GetDomainSidFromDomainName(string domainName) { if (Cache.GetDomainSidMapping(domainName, out var domainSid)) return (true, domainSid); try { @@ -993,7 +1112,7 @@ public bool GetDomain(out Domain domain) { return null; } - public async Task<(bool success, string[] sids)> GetGlobalCatalogMatches(string name, string domain) { + public async Task<(bool Success, string[] Sids)> GetGlobalCatalogMatches(string name, string domain) { if (Cache.GetGCCache(name, out var matches)) { return (true, matches); } @@ -1020,6 +1139,31 @@ public bool GetDomain(out Domain domain) { return (true, sids.ToArray()); } + public async Task<(bool Success, TypedPrincipal Principal)> ResolveCertTemplateByProperty(string propertyValue, + string propertyName, string containerDistinguishedName, string domainName) { + var filter = new LDAPFilter().AddCertificateTemplates().AddFilter($"({propertyName}={propertyValue})", true); + var result = await Query(new LdapQueryParameters() { + DomainName = domainName, + Attributes = CommonProperties.TypeResolutionProps, + SearchScope = SearchScope.OneLevel, + SearchBase = containerDistinguishedName, + LDAPFilter = filter.GetFilter(), + }).DefaultIfEmpty(null).FirstAsync(); + + if (result == null) { + _log.LogWarning("Could not find certificate template with {PropertyName}:{PropertyValue} under {Container}", propertyName, propertyName, containerDistinguishedName); + return (false, null); + } + + if (!result.IsSuccess) { + _log.LogWarning("Could not find certificate template with {PropertyName}:{PropertyValue} under {Container}: {Error}", propertyName, propertyName, containerDistinguishedName, result.Error); + return (false, null); + } + + var entry = result.Value; + return (true, new TypedPrincipal(entry.GetGuid(), Label.CertTemplate)); + } + /// /// Uses a socket and a set of bytes to request the NETBIOS name from a remote computer /// @@ -1090,7 +1234,34 @@ private static bool RequestNETBIOSNameFromComputer(string server, string domain, /// Created for testing purposes /// /// - public static ActiveDirectorySecurityDescriptor MakeSecurityDescriptor() { + public ActiveDirectorySecurityDescriptor MakeSecurityDescriptor() { return new ActiveDirectorySecurityDescriptor(new ActiveDirectorySecurity()); } + + public async Task<(bool Success, TypedPrincipal Principal)> ConvertLocalWellKnownPrincipal(SecurityIdentifier sid, string computerDomainSid, string computerDomain) { + if (WellKnownPrincipal.GetWellKnownPrincipal(sid.Value, out var common)) + { + //The everyone and auth users principals are special and will be converted to the domain equivalent + if (sid.Value is "S-1-1-0" or "S-1-5-11") + { + return await GetWellKnownPrincipal(sid.Value, computerDomain); + } + + //Use the computer object id + the RID of the sid we looked up to create our new principal + var principal = new TypedPrincipal + { + ObjectIdentifier = $"{computerDomainSid}-{sid.Rid()}", + ObjectType = common.ObjectType switch + { + Label.User => Label.LocalUser, + Label.Group => Label.LocalGroup, + _ => common.ObjectType + } + }; + + return (true, principal); + } + + return (false, null); + } } \ No newline at end of file diff --git a/test/unit/CommonLibTest.csproj b/test/unit/CommonLibTest.csproj index 11591e28..717d5c93 100644 --- a/test/unit/CommonLibTest.csproj +++ b/test/unit/CommonLibTest.csproj @@ -1,7 +1,7 @@ - net5.0 + net7.0 false true ..\..\docfx\coverage\ @@ -16,14 +16,14 @@ - + - + - - - + + + From 6d9003fd34c35ef36f0ab99af3e972da42a5fbc0 Mon Sep 17 00:00:00 2001 From: rvazarkar Date: Thu, 27 Jun 2024 12:49:19 -0400 Subject: [PATCH 13/68] wip: update acl processor --- src/CommonLib/ILdapUtilsNew.cs | 4 +- src/CommonLib/LDAPProperties.cs | 2 + src/CommonLib/LdapUtilsNew.cs | 12 ++-- src/CommonLib/Processors/ACLProcessor.cs | 87 +++++++++++------------- 4 files changed, 50 insertions(+), 55 deletions(-) diff --git a/src/CommonLib/ILdapUtilsNew.cs b/src/CommonLib/ILdapUtilsNew.cs index 2a87b2df..4320a264 100644 --- a/src/CommonLib/ILdapUtilsNew.cs +++ b/src/CommonLib/ILdapUtilsNew.cs @@ -11,10 +11,10 @@ namespace SharpHoundCommonLib; public interface ILdapUtilsNew { IAsyncEnumerable> Query(LdapQueryParameters queryParameters, - CancellationToken cancellationToken); + CancellationToken cancellationToken = new()); IAsyncEnumerable> PagedQuery(LdapQueryParameters queryParameters, - CancellationToken cancellationToken); + CancellationToken cancellationToken = new()); IAsyncEnumerable> RangedRetrieval(string distinguishedName, string attributeName, CancellationToken cancellationToken = new()); diff --git a/src/CommonLib/LDAPProperties.cs b/src/CommonLib/LDAPProperties.cs index e02b2353..57b0d685 100644 --- a/src/CommonLib/LDAPProperties.cs +++ b/src/CommonLib/LDAPProperties.cs @@ -36,7 +36,9 @@ public static class LDAPProperties public const string ServicePack = "operatingsystemservicepack"; public const string DNSHostName = "dnshostname"; public const string LAPSExpirationTime = "mslaps-passwordexpirationtime"; + public const string LAPSPassword = "mslaps-password"; public const string LegacyLAPSExpirationTime = "ms-mcs-admpwdexpirationtime"; + public const string LegacyLAPSPassword = "ms-mcs-admpwd"; public const string Members = "member"; public const string SecurityDescriptor = "ntsecuritydescriptor"; public const string SecurityIdentifier = "securityidentifier"; diff --git a/src/CommonLib/LdapUtilsNew.cs b/src/CommonLib/LdapUtilsNew.cs index 6a126950..e614a690 100644 --- a/src/CommonLib/LdapUtilsNew.cs +++ b/src/CommonLib/LdapUtilsNew.cs @@ -15,7 +15,6 @@ using System.Threading.Tasks; using Microsoft.Extensions.Logging; using SharpHoundCommonLib.Enums; -using SharpHoundCommonLib.Exceptions; using SharpHoundCommonLib.LDAPQueries; using SharpHoundCommonLib.OutputTypes; using SharpHoundCommonLib.Processors; @@ -42,7 +41,7 @@ private static readonly ConcurrentDictionary private readonly PortScanner _portScanner; private readonly NativeMethods _nativeMethods; private readonly string _nullCacheKey = Guid.NewGuid().ToString(); - private readonly Regex SidRegex = new Regex(@"^(S-\d+-\d+-\d+-\d+-\d+-\d+)-\d+$"); + private readonly Regex _sidRegex = new(@"^(S-\d+-\d+-\d+-\d+-\d+-\d+)-\d+$"); private readonly string[] _translateNames = { "Administrator", "admin" }; private LDAPConfig _ldapConfig = new(); @@ -194,7 +193,7 @@ await _connectionPool.GetLdapConnection(domain, if (complete) { yield break; } - + currentRange = $"{attributeName};range={index}-{index + step}"; searchRequest.Attributes.Clear(); searchRequest.Attributes.Add(currentRange); @@ -472,7 +471,7 @@ public async IAsyncEnumerable> PagedQuery(LdapQue public async Task<(bool Success, TypedPrincipal Principal)> ResolveIDAndType(string identifier, string objectDomain) { if (identifier.Contains("0ACNF")) { - return (false, null); + return (false, new TypedPrincipal(identifier, Label.Base)); } if (await GetWellKnownPrincipal(identifier, objectDomain) is (true, var principal)) { @@ -510,8 +509,7 @@ public async IAsyncEnumerable> PagedQuery(LdapQue Cache.AddType(sid, type); return (true, type); } - - + try { var entry = new DirectoryEntry($"LDAP://"); if (entry.GetLabel(out type)) { @@ -749,7 +747,7 @@ private SearchRequest CreateSearchRequest(string distinguishedName, string ldapF domainSid = new SecurityIdentifier(sid).AccountDomainSid?.Value.ToUpper(); } catch { - var match = SidRegex.Match(sid); + var match = _sidRegex.Match(sid); domainSid = match.Success ? match.Groups[1].Value : null; } diff --git a/src/CommonLib/Processors/ACLProcessor.cs b/src/CommonLib/Processors/ACLProcessor.cs index 815b30bc..30ff9c0e 100644 --- a/src/CommonLib/Processors/ACLProcessor.cs +++ b/src/CommonLib/Processors/ACLProcessor.cs @@ -4,6 +4,7 @@ using System.DirectoryServices; using System.Security.AccessControl; using System.Security.Principal; +using System.Threading.Tasks; using Microsoft.Extensions.Logging; using SharpHoundCommonLib.Enums; using SharpHoundCommonLib.OutputTypes; @@ -17,8 +18,9 @@ public class ACLProcessor private static readonly ConcurrentDictionary GuidMap = new(); private static bool _isCacheBuilt; private readonly ILogger _log; - private readonly ILDAPUtils _utils; - + private readonly ILdapUtilsNew _utils; + private static readonly HashSet BuiltDomainCaches = new(StringComparer.OrdinalIgnoreCase); + static ACLProcessor() { //Create a dictionary with the base GUIDs of each object type @@ -41,50 +43,37 @@ static ACLProcessor() }; } - public ACLProcessor(ILDAPUtils utils, bool noGuidCache = false, ILogger log = null, string domain = null) + public ACLProcessor(ILdapUtilsNew utils, ILogger log = null) { _utils = utils; _log = log ?? Logging.LogProvider.CreateLogger("ACLProc"); - if (!noGuidCache) - BuildGUIDCache(domain); } /// /// Builds a mapping of GUID -> Name for LDAP rights. Used for rights that are created using an extended schema such as /// LAPS /// - private void BuildGUIDCache(string domain) - { - if (_isCacheBuilt) - return; - - var forest = _utils.GetForest(domain); - if (forest == null) + private async Task BuildGuidCache(string domain) { + BuiltDomainCaches.Add(domain); + await foreach (var result in _utils.Query(new LdapQueryParameters() { + DomainName = domain, + LDAPFilter = "(schemaIDGUID=*)", + NamingContext = NamingContext.Schema, + Attributes = new[] {LDAPProperties.SchemaIDGUID, LDAPProperties.Name}, + })) { - _log.LogError("BuildGUIDCache - Unable to resolve forest"); - return; - } - - var schema = forest.Schema.Name; - if (string.IsNullOrEmpty(schema)) - { - _log.LogError("BuildGUIDCache - Schema string is null or empty"); - return; - } + if (result.Success) { + var name = result.Value.GetProperty(LDAPProperties.Name)?.ToLower(); + var guid = result.Value.GetGuid(); + if (name == null || guid == null) { + continue; + } - _log.LogTrace("Requesting schema from {Schema}", schema); - - foreach (var entry in _utils.QueryLDAP("(schemaIDGUID=*)", SearchScope.Subtree, - new[] {LDAPProperties.SchemaIDGUID, LDAPProperties.Name}, adsPath: schema)) - { - var name = entry.GetProperty(LDAPProperties.Name)?.ToLower(); - var guid = new Guid(entry.GetByteProperty(LDAPProperties.SchemaIDGUID)).ToString(); - GuidMap.TryAdd(guid, name); + if (name is LDAPProperties.LAPSPassword or LDAPProperties.LegacyLAPSPassword) { + GuidMap.TryAdd(guid, name); + } + } } - - _log.LogTrace("BuildGUIDCache - Successfully grabbed schema"); - - _isCacheBuilt = true; } /// @@ -120,7 +109,7 @@ public bool IsACLProtected(byte[] ntSecurityDescriptor) /// /// /// - public IEnumerable ProcessACL(ResolvedSearchResult result, ISearchResultEntry searchResult) + public IAsyncEnumerable ProcessACL(ResolvedSearchResult result, ISearchResultEntry searchResult) { var descriptor = searchResult.GetByteProperty(LDAPProperties.SecurityDescriptor); var domain = result.Domain; @@ -141,10 +130,13 @@ public IEnumerable ProcessACL(ResolvedSearchResult result, ISearchResultEnt /// /// /// - public IEnumerable ProcessACL(byte[] ntSecurityDescriptor, string objectDomain, + public async IAsyncEnumerable ProcessACL(byte[] ntSecurityDescriptor, string objectDomain, Label objectType, bool hasLaps, string objectName = "") { + if (!BuiltDomainCaches.Contains(objectDomain)) { + await BuildGuidCache(objectDomain); + } if (ntSecurityDescriptor == null) { _log.LogDebug("Security Descriptor is null for {Name}", objectName); @@ -168,8 +160,7 @@ public IEnumerable ProcessACL(byte[] ntSecurityDescriptor, string objectDom if (ownerSid != null) { - var resolvedOwner = _utils.ResolveIDAndType(ownerSid, objectDomain); - if (resolvedOwner != null) + if (await _utils.ResolveIDAndType(ownerSid, objectDomain) is (true, var resolvedOwner)) { yield return new ACE { PrincipalType = resolvedOwner.ObjectType, @@ -177,6 +168,7 @@ public IEnumerable ProcessACL(byte[] ntSecurityDescriptor, string objectDom RightName = EdgeNames.Owns, IsInherited = false }; + } } else { @@ -212,7 +204,10 @@ public IEnumerable ProcessACL(byte[] ntSecurityDescriptor, string objectDom continue; } - var resolvedPrincipal = _utils.ResolveIDAndType(principalSid, objectDomain); + var (success, resolvedPrincipal) = await _utils.ResolveIDAndType(principalSid, objectDomain); + if (!success) { + _log.LogDebug("Failed to resolve type for principal {Sid}", principalSid); + } var aceRights = ace.ActiveDirectoryRights(); //Lowercase this just in case. As far as I know it should always come back that way anyways, but better safe than sorry @@ -341,7 +336,7 @@ public IEnumerable ProcessACL(byte[] ntSecurityDescriptor, string objectDom IsInherited = inherited, RightName = EdgeNames.AllExtendedRights }; - else if (mappedGuid is "ms-mcs-admpwd") + else if (mappedGuid is LDAPProperties.LegacyLAPSPassword or LDAPProperties.LAPSPassword) yield return new ACE { PrincipalType = resolvedPrincipal.ObjectType, @@ -506,7 +501,7 @@ or Label.NTAuthStore /// /// /// - public IEnumerable ProcessGMSAReaders(ResolvedSearchResult resolvedSearchResult, + public IAsyncEnumerable ProcessGMSAReaders(ResolvedSearchResult resolvedSearchResult, ISearchResultEntry searchResultEntry) { var descriptor = searchResultEntry.GetByteProperty(LDAPProperties.GroupMSAMembership); @@ -522,7 +517,7 @@ public IEnumerable ProcessGMSAReaders(ResolvedSearchResult resolvedSearchRe /// /// /// - public IEnumerable ProcessGMSAReaders(byte[] groupMSAMembership, string objectDomain) + public IAsyncEnumerable ProcessGMSAReaders(byte[] groupMSAMembership, string objectDomain) { return ProcessGMSAReaders(groupMSAMembership, "", objectDomain); } @@ -535,7 +530,7 @@ public IEnumerable ProcessGMSAReaders(byte[] groupMSAMembership, string obj /// /// /// - public IEnumerable ProcessGMSAReaders(byte[] groupMSAMembership, string objectName, string objectDomain) + public async IAsyncEnumerable ProcessGMSAReaders(byte[] groupMSAMembership, string objectName, string objectDomain) { if (groupMSAMembership == null) { @@ -580,9 +575,8 @@ public IEnumerable ProcessGMSAReaders(byte[] groupMSAMembership, string obj _log.LogTrace("Processing GMSA ACE with principal {Principal}", principalSid); - var resolvedPrincipal = _utils.ResolveIDAndType(principalSid, objectDomain); - - if (resolvedPrincipal != null) + var (success, resolvedPrincipal) = await _utils.ResolveIDAndType(principalSid, objectDomain); + if (success) { yield return new ACE { RightName = EdgeNames.ReadGMSAPassword, @@ -590,6 +584,7 @@ public IEnumerable ProcessGMSAReaders(byte[] groupMSAMembership, string obj PrincipalSID = resolvedPrincipal.ObjectIdentifier, IsInherited = ace.IsInherited() }; + } } } } From 87ed4c451fe1f0698952bc72dfc806d0a6fca5c7 Mon Sep 17 00:00:00 2001 From: Alex Nemeth Date: Fri, 28 Jun 2024 13:16:26 -0700 Subject: [PATCH 14/68] WIP: rewiring processors to ILdapUtilsNew --- .../Processors/LDAPPropertyProcessor.cs | 28 +++++++------- .../Processors/LocalGroupProcessor.cs | 37 +++++++++---------- src/CommonLib/Processors/SPNProcessors.cs | 8 ++-- .../UserRightsAssignmentProcessor.cs | 28 +++++++------- 4 files changed, 49 insertions(+), 52 deletions(-) diff --git a/src/CommonLib/Processors/LDAPPropertyProcessor.cs b/src/CommonLib/Processors/LDAPPropertyProcessor.cs index 33cd6d4d..c322c2b5 100644 --- a/src/CommonLib/Processors/LDAPPropertyProcessor.cs +++ b/src/CommonLib/Processors/LDAPPropertyProcessor.cs @@ -23,9 +23,9 @@ public class LDAPPropertyProcessor .Concat(CommonProperties.SPNTargetProps).Concat(CommonProperties.DomainTrustProps) .Concat(CommonProperties.GPOLocalGroupProps).ToArray(); - private readonly ILDAPUtils _utils; + private readonly ILdapUtilsNew _utils; - public LDAPPropertyProcessor(ILDAPUtils utils) + public LDAPPropertyProcessor(ILdapUtilsNew utils) { _utils = utils; } @@ -178,10 +178,10 @@ public async Task ReadUserProperties(ISearchResultEntry entry) continue; var resolvedHost = await _utils.ResolveHostToSid(d, domain); - if (resolvedHost != null && resolvedHost.Contains("S-1")) + if (resolvedHost.Success && resolvedHost.SecurityIdentifier.Contains("S-1")) comps.Add(new TypedPrincipal { - ObjectIdentifier = resolvedHost, + ObjectIdentifier = resolvedHost.SecurityIdentifier, ObjectType = Label.Computer }); } @@ -237,9 +237,8 @@ public async Task ReadUserProperties(ISearchResultEntry entry) sidHistoryList.Add(sSid); - var res = _utils.ResolveIDAndType(sSid, domain); - - sidHistoryPrincipals.Add(res); + if (await _utils.ResolveIDAndType(sSid, domain) is (true, var res)) + sidHistoryPrincipals.Add(res); } userProps.SidHistory = sidHistoryPrincipals.Distinct().ToArray(); @@ -286,10 +285,10 @@ public async Task ReadComputerProperties(ISearchResultEntry var hname = d.Contains("/") ? d.Split('/')[1] : d; hname = hname.Split(':')[0]; var resolvedHost = await _utils.ResolveHostToSid(hname, domain); - if (resolvedHost != null && resolvedHost.Contains("S-1")) + if (resolvedHost.Success && resolvedHost.SecurityIdentifier.Contains("S-1")) comps.Add(new TypedPrincipal { - ObjectIdentifier = resolvedHost, + ObjectIdentifier = resolvedHost.SecurityIdentifier, ObjectType = Label.Computer }); } @@ -305,8 +304,8 @@ public async Task ReadComputerProperties(ISearchResultEntry sd.SetSecurityDescriptorBinaryForm(rawAllowedToAct, AccessControlSections.Access); foreach (var rule in sd.GetAccessRules(true, true, typeof(SecurityIdentifier))) { - var res = _utils.ResolveIDAndType(rule.IdentityReference(), domain); - allowedToActPrincipals.Add(res); + if (await _utils.ResolveIDAndType(rule.IdentityReference(), domain) is (true, var res)) + allowedToActPrincipals.Add(res); } } @@ -343,9 +342,8 @@ public async Task ReadComputerProperties(ISearchResultEntry sidHistoryList.Add(sSid); - var res = _utils.ResolveIDAndType(sSid, domain); - - sidHistoryPrincipals.Add(res); + if (await _utils.ResolveIDAndType(sSid, domain) is (true, var res)) + sidHistoryPrincipals.Add(res); } compProps.SidHistory = sidHistoryPrincipals.ToArray(); @@ -358,7 +356,7 @@ public async Task ReadComputerProperties(ISearchResultEntry { foreach (var dn in hsa) { - var resolvedPrincipal = _utils.ResolveDistinguishedName(dn); + var resolvedPrincipal = await _utils.ResolveDistinguishedName(dn); if (resolvedPrincipal != null) smsaPrincipals.Add(resolvedPrincipal); diff --git a/src/CommonLib/Processors/LocalGroupProcessor.cs b/src/CommonLib/Processors/LocalGroupProcessor.cs index 9662ef1e..b7161e7f 100644 --- a/src/CommonLib/Processors/LocalGroupProcessor.cs +++ b/src/CommonLib/Processors/LocalGroupProcessor.cs @@ -15,9 +15,9 @@ public class LocalGroupProcessor { public delegate Task ComputerStatusDelegate(CSVComputerStatus status); private readonly ILogger _log; - private readonly ILDAPUtils _utils; + private readonly ILdapUtilsNew _utils; - public LocalGroupProcessor(ILDAPUtils utils, ILogger log = null) + public LocalGroupProcessor(ILdapUtilsNew utils, ILogger log = null) { _utils = utils; _log = log ?? Logging.LogProvider.CreateLogger("LocalGroupProcessor"); @@ -152,7 +152,7 @@ await SendComputerStatus(new CSVComputerStatus { _log.LogTrace("Opening alias {Alias} with RID {Rid} in domain {Domain} on computer {ComputerName}", alias.Name, alias.Rid, domainResult.Name, computerName); //Try and resolve the group name using several different criteria - var resolvedName = ResolveGroupName(alias.Name, computerName, computerObjectId, computerDomain, alias.Rid, + var resolvedName = await ResolveGroupName(alias.Name, computerName, computerObjectId, computerDomain, alias.Rid, isDomainController, domainResult.Name.Equals("builtin", StringComparison.OrdinalIgnoreCase)); @@ -218,17 +218,16 @@ await SendComputerStatus(new CSVComputerStatus if (isDomainController) { - var result = ResolveDomainControllerPrincipal(sidValue, computerDomain); + var result = await ResolveDomainControllerPrincipal(sidValue, computerDomain); if (result != null) results.Add(result); continue; } //If we get a local well known principal, we need to convert it using the computer's objectid - if (_utils.ConvertLocalWellKnownPrincipal(securityIdentifier, computerObjectId, computerDomain, out var principal)) + if (await _utils.ConvertLocalWellKnownPrincipal(securityIdentifier, computerObjectId, computerDomain) is (true, var principal)) { //If the principal is null, it means we hit a weird edge case, but this is a local well known principal - if (principal != null) - results.Add(principal); + results.Add(principal); continue; } @@ -294,8 +293,8 @@ await SendComputerStatus(new CSVComputerStatus } //If we get here, we most likely have a domain principal in a local group - var resolvedPrincipal = _utils.ResolveIDAndType(sidValue, computerDomain); - if (resolvedPrincipal != null) results.Add(resolvedPrincipal); + var resolvedPrincipal = await _utils.ResolveIDAndType(sidValue, computerDomain); + if (resolvedPrincipal.Success) results.Add(resolvedPrincipal.Principal); } ret.Collected = true; @@ -306,15 +305,15 @@ await SendComputerStatus(new CSVComputerStatus } } - private TypedPrincipal ResolveDomainControllerPrincipal(string sid, string computerDomain) + private async Task ResolveDomainControllerPrincipal(string sid, string computerDomain) { //If the server is a domain controller and we have a well known group, use the domain value - if (_utils.GetWellKnownPrincipal(sid, computerDomain, out var wellKnown)) + if (await _utils.GetWellKnownPrincipal(sid, computerDomain) is (true, var wellKnown)) return wellKnown; - return _utils.ResolveIDAndType(sid, computerDomain); + return (await _utils.ResolveIDAndType(sid, computerDomain)).Principal; } - private NamedPrincipal ResolveGroupName(string baseName, string computerName, string computerDomainSid, + private async Task ResolveGroupName(string baseName, string computerName, string computerDomainSid, string domainName, int groupRid, bool isDc, bool isBuiltIn) { if (isDc) @@ -322,12 +321,12 @@ private NamedPrincipal ResolveGroupName(string baseName, string computerName, st if (isBuiltIn) { //If this is the builtin group on the DC, the groups correspond to the domain well known groups - _utils.GetWellKnownPrincipal($"S-1-5-32-{groupRid}".ToUpper(), domainName, out var principal); - return new NamedPrincipal - { - ObjectId = principal.ObjectIdentifier, - PrincipalName = "IGNOREME" - }; + if (await _utils.GetWellKnownPrincipal($"S-1-5-32-{groupRid}".ToUpper(), domainName) is (true, var principal)) + return new NamedPrincipal + { + ObjectId = principal.ObjectIdentifier, + PrincipalName = "IGNOREME" + }; } if (computerDomainSid == null) diff --git a/src/CommonLib/Processors/SPNProcessors.cs b/src/CommonLib/Processors/SPNProcessors.cs index 0796232f..a582e61f 100644 --- a/src/CommonLib/Processors/SPNProcessors.cs +++ b/src/CommonLib/Processors/SPNProcessors.cs @@ -8,9 +8,9 @@ public class SPNProcessors { private const string MSSQLSPNString = "mssqlsvc"; private readonly ILogger _log; - private readonly ILDAPUtils _utils; + private readonly ILdapUtilsNew _utils; - public SPNProcessors(ILDAPUtils utils, ILogger log = null) + public SPNProcessors(ILdapUtilsNew utils, ILogger log = null) { _utils = utils; _log = log ?? Logging.LogProvider.CreateLogger("SPNProc"); @@ -59,10 +59,10 @@ public async IAsyncEnumerable ReadSPNTargets(string[] servicePrinc var host = await _utils.ResolveHostToSid(spn, domain); _log.LogTrace("Resolved {SPN} to {Hostname}", spn, host); - if (host != null && host.StartsWith("S-1-")) + if (host.Success && host.SecurityIdentifier.StartsWith("S-1-")) yield return new SPNPrivilege { - ComputerSID = host, + ComputerSID = host.SecurityIdentifier, Port = port, Service = EdgeNames.SQLAdmin }; diff --git a/src/CommonLib/Processors/UserRightsAssignmentProcessor.cs b/src/CommonLib/Processors/UserRightsAssignmentProcessor.cs index bee3a570..82687e66 100644 --- a/src/CommonLib/Processors/UserRightsAssignmentProcessor.cs +++ b/src/CommonLib/Processors/UserRightsAssignmentProcessor.cs @@ -4,7 +4,6 @@ using Microsoft.Extensions.Logging; using SharpHoundCommonLib.Enums; using SharpHoundCommonLib.OutputTypes; -using SharpHoundRPC; using SharpHoundRPC.Shared; using SharpHoundRPC.Wrappers; @@ -15,9 +14,9 @@ public class UserRightsAssignmentProcessor public delegate Task ComputerStatusDelegate(CSVComputerStatus status); private readonly ILogger _log; - private readonly ILDAPUtils _utils; + private readonly ILdapUtilsNew _utils; - public UserRightsAssignmentProcessor(ILDAPUtils utils, ILogger log = null) + public UserRightsAssignmentProcessor(ILdapUtilsNew utils, ILogger log = null) { _utils = utils; _log = log ?? Logging.LogProvider.CreateLogger("UserRightsAssignmentProcessor"); @@ -53,15 +52,15 @@ public async IAsyncEnumerable GetUserRightsAssign string computerObjectId, string computerDomain, bool isDomainController, string[] desiredPrivileges = null) { var policyOpenResult = OpenLSAPolicy(computerName); - if (policyOpenResult.IsFailed) + if (!policyOpenResult.IsSuccess) { _log.LogDebug("LSAOpenPolicy failed on {ComputerName} with status {Status}", computerName, - policyOpenResult.SError); + policyOpenResult.Error); await SendComputerStatus(new CSVComputerStatus { Task = "LSAOpenPolicy", ComputerName = computerName, - Status = policyOpenResult.SError + Status = policyOpenResult.Error }); yield break; } @@ -109,7 +108,7 @@ await SendComputerStatus(new CSVComputerStatus { _log.LogDebug( "LSAEnumerateAccountsWithUserRight failed on {ComputerName} with status {Status} for privilege {Privilege}", - computerName, policyOpenResult.SError, privilege); + computerName, policyOpenResult.Error, privilege); await SendComputerStatus(new CSVComputerStatus { ComputerName = computerName, @@ -142,14 +141,14 @@ await SendComputerStatus(new CSVComputerStatus if (isDomainController) { - var result = ResolveDomainControllerPrincipal(sid.Value, computerDomain); + var result = await ResolveDomainControllerPrincipal(sid.Value, computerDomain); if (result != null) resolved.Add(result); continue; } //If we get a local well known principal, we need to convert it using the computer's domain sid - if (_utils.ConvertLocalWellKnownPrincipal(sid, computerObjectId, computerDomain, out var principal)) + if (await _utils.ConvertLocalWellKnownPrincipal(sid, computerObjectId, computerDomain) is (true, var principal)) { _log.LogTrace("Got Well Known Principal {SID} on computer {Computer} for privilege {Privilege} and type {Type}", principal.ObjectIdentifier, computerName, privilege, principal.ObjectType); resolved.Add(principal); @@ -191,8 +190,8 @@ await SendComputerStatus(new CSVComputerStatus } //If we get here, we most likely have a domain principal in a local group. Do a lookup - var resolvedPrincipal = _utils.ResolveIDAndType(sid.Value, computerDomain); - if (resolvedPrincipal != null) resolved.Add(resolvedPrincipal); + var resolvedPrincipal = await _utils.ResolveIDAndType(sid.Value, computerDomain); + if (resolvedPrincipal.Success) resolved.Add(resolvedPrincipal.Principal); } ret.Collected = true; @@ -202,13 +201,14 @@ await SendComputerStatus(new CSVComputerStatus } } - private TypedPrincipal ResolveDomainControllerPrincipal(string sid, string computerDomain) + private async Task ResolveDomainControllerPrincipal(string sid, string computerDomain) { //If the server is a domain controller and we have a well known group, use the domain value - if (_utils.GetWellKnownPrincipal(sid, computerDomain, out var wellKnown)) + if (await _utils.GetWellKnownPrincipal(sid, computerDomain) is (true, var wellKnown)) return wellKnown; //Otherwise, do a domain lookup - return _utils.ResolveIDAndType(sid, computerDomain); + var domainPrinciple = await _utils.ResolveIDAndType(sid, computerDomain); + return domainPrinciple.Principal; } From 48e4eff077ed03201b812aaac59bdfdd2a0cf4ea Mon Sep 17 00:00:00 2001 From: rvazarkar Date: Fri, 28 Jun 2024 16:23:24 -0400 Subject: [PATCH 15/68] wip: add some missing functions --- src/CommonLib/ILdapUtilsNew.cs | 3 + src/CommonLib/LdapUtilsNew.cs | 154 ++++++++++++++++++++++++++++----- 2 files changed, 134 insertions(+), 23 deletions(-) diff --git a/src/CommonLib/ILdapUtilsNew.cs b/src/CommonLib/ILdapUtilsNew.cs index 4320a264..13dd628c 100644 --- a/src/CommonLib/ILdapUtilsNew.cs +++ b/src/CommonLib/ILdapUtilsNew.cs @@ -40,4 +40,7 @@ IAsyncEnumerable> RangedRetrieval(string distinguishedName, public Task<(bool Success, TypedPrincipal Principal)> ConvertLocalWellKnownPrincipal(SecurityIdentifier sid, string computerDomainSid, string computerDomain); + + public Task IsDomainController(string computerObjectId, string domainName); + public Task<(bool Success, TypedPrincipal Principal)> LookupDistinguishedName(string distinguishedName); } \ No newline at end of file diff --git a/src/CommonLib/LdapUtilsNew.cs b/src/CommonLib/LdapUtilsNew.cs index e614a690..afcb668f 100644 --- a/src/CommonLib/LdapUtilsNew.cs +++ b/src/CommonLib/LdapUtilsNew.cs @@ -2,6 +2,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.DirectoryServices; +using System.DirectoryServices.AccountManagement; using System.DirectoryServices.ActiveDirectory; using System.DirectoryServices.Protocols; using System.Linq; @@ -37,6 +38,9 @@ private static readonly ConcurrentDictionary SeenWellKnownPrincipals = new(); private readonly ConcurrentDictionary _hostResolutionMap = new(StringComparer.OrdinalIgnoreCase); + + private readonly ConcurrentDictionary _distinguishedNameCache = + new(StringComparer.OrdinalIgnoreCase); private readonly ILogger _log; private readonly PortScanner _portScanner; private readonly NativeMethods _nativeMethods; @@ -516,12 +520,28 @@ public async IAsyncEnumerable> PagedQuery(LdapQue Cache.AddType(sid, type); return (true, type); } - - return (false, Label.Base); } catch { - return (false, Label.Base); + //pass } + + using (var ctx = new PrincipalContext(ContextType.Domain)) { + try { + var principal = Principal.FindByIdentity(ctx, IdentityType.Sid, sid); + if (principal != null) { + var entry = (DirectoryEntry)principal.GetUnderlyingObject(); + if (entry.GetLabel(out type)) { + Cache.AddType(sid, type); + return (true, type); + } + } + } + catch { + //pass + } + } + + return (false, Label.Base); } private async Task<(bool Success, Label type)> LookupGuidType(string guid, string domain) { @@ -547,12 +567,28 @@ public async IAsyncEnumerable> PagedQuery(LdapQue Cache.AddType(guid, type); return (true, type); } - - return (false, Label.Base); } catch { - return (false, Label.Base); + //pass } + + using (var ctx = new PrincipalContext(ContextType.Domain)) { + try { + var principal = Principal.FindByIdentity(ctx, IdentityType.Guid, guid); + if (principal != null) { + var entry = (DirectoryEntry)principal.GetUnderlyingObject(); + if (entry.GetLabel(out type)) { + Cache.AddType(guid, type); + return (true, type); + } + } + } + catch { + //pass + } + } + + return (false, Label.Base); } public async Task<(bool Success, TypedPrincipal WellKnownPrincipal)> GetWellKnownPrincipal( @@ -763,7 +799,7 @@ private SearchRequest CreateSearchRequest(string distinguishedName, string ldapF var entry = new DirectoryEntry($"LDAP://"); entry.RefreshCache(new[] { LDAPProperties.DistinguishedName }); var dn = entry.GetProperty(LDAPProperties.DistinguishedName); - if (!string.IsNullOrEmpty(dn)) { + if (!string.IsNullOrWhiteSpace(dn)) { Cache.AddDomainSidMapping(domainSid, Helpers.DistinguishedNameToDomain(dn)); return (true, Helpers.DistinguishedNameToDomain(dn)); } @@ -777,6 +813,22 @@ private SearchRequest CreateSearchRequest(string distinguishedName, string ldapF return (true, domainName); } + using (var ctx = new PrincipalContext(ContextType.Domain)) { + try { + var principal = Principal.FindByIdentity(ctx, IdentityType.Sid, sid); + if (principal != null) { + var dn = principal.DistinguishedName; + if (!string.IsNullOrWhiteSpace(dn)) { + Cache.AddDomainSidMapping(domainSid, Helpers.DistinguishedNameToDomain(dn)); + return (true, Helpers.DistinguishedNameToDomain(dn)); + } + } + } + catch { + //pass + } + } + return (false, string.Empty); } @@ -1237,29 +1289,85 @@ public ActiveDirectorySecurityDescriptor MakeSecurityDescriptor() { } public async Task<(bool Success, TypedPrincipal Principal)> ConvertLocalWellKnownPrincipal(SecurityIdentifier sid, string computerDomainSid, string computerDomain) { - if (WellKnownPrincipal.GetWellKnownPrincipal(sid.Value, out var common)) + if (!WellKnownPrincipal.GetWellKnownPrincipal(sid.Value, out var common)) return (false, null); + //The everyone and auth users principals are special and will be converted to the domain equivalent + if (sid.Value is "S-1-1-0" or "S-1-5-11") { - //The everyone and auth users principals are special and will be converted to the domain equivalent - if (sid.Value is "S-1-1-0" or "S-1-5-11") + return await GetWellKnownPrincipal(sid.Value, computerDomain); + } + + //Use the computer object id + the RID of the sid we looked up to create our new principal + var principal = new TypedPrincipal + { + ObjectIdentifier = $"{computerDomainSid}-{sid.Rid()}", + ObjectType = common.ObjectType switch { - return await GetWellKnownPrincipal(sid.Value, computerDomain); + Label.User => Label.LocalUser, + Label.Group => Label.LocalGroup, + _ => common.ObjectType } + }; - //Use the computer object id + the RID of the sid we looked up to create our new principal - var principal = new TypedPrincipal - { - ObjectIdentifier = $"{computerDomainSid}-{sid.Rid()}", - ObjectType = common.ObjectType switch - { - Label.User => Label.LocalUser, - Label.Group => Label.LocalGroup, - _ => common.ObjectType - } - }; + return (true, principal); + + } + + public async Task IsDomainController(string computerObjectId, string domainName) + { + var filter = new LDAPFilter().AddFilter(CommonFilters.SpecificSID(computerObjectId), true) + .AddFilter(CommonFilters.DomainControllers, true); + var result = await Query(new LdapQueryParameters() { + DomainName = domainName, + Attributes = CommonProperties.ObjectID, + LDAPFilter = filter.GetFilter(), + }).DefaultIfEmpty(null).FirstOrDefaultAsync(); + return result is { IsSuccess: true }; + } + public async Task<(bool Success, TypedPrincipal Principal)> LookupDistinguishedName(string distinguishedName) { + if (_distinguishedNameCache.TryGetValue(distinguishedName, out var principal)) { return (true, principal); } - return (false, null); + var domain = Helpers.DistinguishedNameToDomain(distinguishedName); + var result = await Query(new LdapQueryParameters { + DomainName = domain, + Attributes = CommonProperties.TypeResolutionProps, + SearchBase = distinguishedName, + SearchScope = SearchScope.Base, + LDAPFilter = new LDAPFilter().AddAllObjects().GetFilter() + }).DefaultIfEmpty(null).FirstOrDefaultAsync(); + + if (result is { IsSuccess: true }) { + var entry = result.Value; + var id = entry.GetObjectIdentifier(); + if (id == null) { + return (false, default); + } + + if (await GetWellKnownPrincipal(id, domain) is (true, var wellKnownPrincipal)) { + _distinguishedNameCache.TryAdd(distinguishedName, wellKnownPrincipal); + return (true, wellKnownPrincipal); + } + + var type = entry.GetLabel(); + principal = new TypedPrincipal(id, type); + _distinguishedNameCache.TryAdd(distinguishedName, principal); + return (true, principal); + } + + using (var ctx = new PrincipalContext(ContextType.Domain)) { + try { + var lookupPrincipal = Principal.FindByIdentity(ctx, IdentityType.DistinguishedName, distinguishedName); + if (lookupPrincipal != null && ((DirectoryEntry)lookupPrincipal.GetUnderlyingObject()).GetTypedPrincipal(out principal)) { + return (true, principal); + } + + return (false, default); + } + catch { + return (false, default); + } + } } } \ No newline at end of file From 8093ec9d7b26c7e46816e0fd9ed38d558f70a12b Mon Sep 17 00:00:00 2001 From: Alex Nemeth Date: Fri, 28 Jun 2024 13:28:54 -0700 Subject: [PATCH 16/68] LdapPropertyProcessor: use new distinguished name lookup --- src/CommonLib/Processors/LDAPPropertyProcessor.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/CommonLib/Processors/LDAPPropertyProcessor.cs b/src/CommonLib/Processors/LDAPPropertyProcessor.cs index c322c2b5..c8df3ee1 100644 --- a/src/CommonLib/Processors/LDAPPropertyProcessor.cs +++ b/src/CommonLib/Processors/LDAPPropertyProcessor.cs @@ -356,9 +356,7 @@ public async Task ReadComputerProperties(ISearchResultEntry { foreach (var dn in hsa) { - var resolvedPrincipal = await _utils.ResolveDistinguishedName(dn); - - if (resolvedPrincipal != null) + if (await _utils.LookupDistinguishedName(dn) is (true, var resolvedPrincipal)) smsaPrincipals.Add(resolvedPrincipal); } } @@ -536,7 +534,7 @@ public static Dictionary ReadCertTemplateProperties(ISearchResul return props; } - public IssuancePolicyProperties ReadIssuancePolicyProperties(ISearchResultEntry entry) + public async Task ReadIssuancePolicyProperties(ISearchResultEntry entry) { var ret = new IssuancePolicyProperties(); var props = GetCommonProps(entry); @@ -546,8 +544,7 @@ public IssuancePolicyProperties ReadIssuancePolicyProperties(ISearchResultEntry var link = entry.GetProperty(LDAPProperties.OIDGroupLink); if (!string.IsNullOrEmpty(link)) { - var linkedGroup = _utils.ResolveDistinguishedName(link); - if (linkedGroup != null) + if (await _utils.LookupDistinguishedName(link) is (true, var linkedGroup)) { props.Add("oidgrouplink", linkedGroup.ObjectIdentifier); ret.GroupLink = linkedGroup; From e365e18beedd5dfb29e419585bd93e3db113234a Mon Sep 17 00:00:00 2001 From: rvazarkar Date: Mon, 1 Jul 2024 12:52:54 -0400 Subject: [PATCH 17/68] wip: processor updates --- src/CommonLib/Extensions.cs | 8 + src/CommonLib/Impersonate.cs | 141 ++++---- src/CommonLib/LdapUtilsNew.cs | 76 ++-- .../Processors/CertAbuseProcessor.cs | 32 +- .../Processors/ComputerSessionProcessor.cs | 55 +-- .../Processors/ContainerProcessor.cs | 77 ++-- .../Processors/DCRegistryProcessor.cs | 8 +- .../Processors/DomainTrustProcessor.cs | 27 +- .../Processors/GPOLocalGroupProcessor.cs | 329 +++++++----------- src/CommonLib/Processors/GroupProcessor.cs | 49 ++- src/CommonLib/SharpHoundCommonLib.csproj | 1 + 11 files changed, 362 insertions(+), 441 deletions(-) diff --git a/src/CommonLib/Extensions.cs b/src/CommonLib/Extensions.cs index 7475e66c..ea4d58dc 100644 --- a/src/CommonLib/Extensions.cs +++ b/src/CommonLib/Extensions.cs @@ -10,6 +10,7 @@ using Microsoft.Extensions.Logging; using SharpHoundCommonLib.Enums; using SharpHoundCommonLib.LDAPQueries; +using SharpHoundCommonLib.OutputTypes; using SearchScope = System.DirectoryServices.Protocols.SearchScope; namespace SharpHoundCommonLib @@ -109,6 +110,13 @@ public static string[] GetPropertyAsArray(this DirectoryEntry entry, string prop return dest.ToArray(); } + public static bool GetTypedPrincipal(this DirectoryEntry entry, out TypedPrincipal principal) { + var identifier = entry.GetObjectIdentifier(); + var success = entry.GetLabel(out var label); + principal = new TypedPrincipal(identifier, label); + return (success && !string.IsNullOrWhiteSpace(identifier)); + } + public static string GetObjectIdentifier(this DirectoryEntry entry) { return entry.GetSid() ?? entry.GetGuid(); } diff --git a/src/CommonLib/Impersonate.cs b/src/CommonLib/Impersonate.cs index fe222c54..f6e03aae 100644 --- a/src/CommonLib/Impersonate.cs +++ b/src/CommonLib/Impersonate.cs @@ -1,14 +1,13 @@ //credit to Phillip Allan-Harding (Twitter @phillipharding) for this library. + using System; using System.ComponentModel; using System.Runtime.InteropServices; using System.Security.Principal; using System.Xml.Linq; -namespace Impersonate -{ - public enum LogonType - { +namespace Impersonate { + public enum LogonType { LOGON32_LOGON_INTERACTIVE = 2, LOGON32_LOGON_NETWORK = 3, LOGON32_LOGON_BATCH = 4, @@ -18,36 +17,33 @@ public enum LogonType LOGON32_LOGON_NEW_CREDENTIALS = 9 // Win2K or higher }; - public enum LogonProvider - { + public enum LogonProvider { LOGON32_PROVIDER_DEFAULT = 0, LOGON32_PROVIDER_WINNT35 = 1, LOGON32_PROVIDER_WINNT40 = 2, LOGON32_PROVIDER_WINNT50 = 3 }; - public enum ImpersonationLevel - { + public enum ImpersonationLevel { SecurityAnonymous = 0, SecurityIdentification = 1, SecurityImpersonation = 2, SecurityDelegation = 3 } - class Win32NativeMethods - { + class Win32NativeMethods { [DllImport("advapi32.dll", SetLastError = true)] public static extern int LogonUser(string lpszUserName, - string lpszDomain, - string lpszPassword, - int dwLogonType, - int dwLogonProvider, - ref IntPtr phToken); + string lpszDomain, + string lpszPassword, + int dwLogonType, + int dwLogonProvider, + ref IntPtr phToken); [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern int DuplicateToken(IntPtr hToken, - int impersonationLevel, - ref IntPtr hNewToken); + int impersonationLevel, + ref IntPtr hNewToken); [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern bool RevertToSelf(); @@ -84,88 +80,73 @@ public static extern int DuplicateToken(IntPtr hToken, /// /// ... /// - public class Impersonator : IDisposable - { + public class Impersonator : IDisposable { private WindowsImpersonationContext _wic; - - /// - /// Begins impersonation with the given credentials, Logon type and Logon provider. - /// - /// Name of the user. - /// Name of the domain. - /// The password. - ///< param name="logonType">Type of the logon. - /// The logon provider. - public Impersonator(string userName, string domainName, string password, LogonType logonType, LogonProvider logonProvider) - { + + /// + /// Begins impersonation with the given credentials, Logon type and Logon provider. + /// + /// Name of the user. + /// Name of the domain. + /// The password. + ///< param name="logonType">Type of the logon. + /// The logon provider. + public Impersonator(string userName, string domainName, string password, LogonType logonType, + LogonProvider logonProvider) { Impersonate(userName, domainName, password, logonType, logonProvider); } - - /// - /// Begins impersonation with the given credentials. - /// - /// Name of the user. - /// Name of the domain. - /// The password. - public Impersonator(string userName, string domainName, string password) - { - Impersonate(userName, domainName, password, LogonType.LOGON32_LOGON_INTERACTIVE, LogonProvider.LOGON32_PROVIDER_DEFAULT); + + /// + /// Begins impersonation with the given credentials. + /// + /// Name of the user. + /// Name of the domain. + /// The password. + public Impersonator(string userName, string domainName, string password) { + Impersonate(userName, domainName, password, LogonType.LOGON32_LOGON_INTERACTIVE, + LogonProvider.LOGON32_PROVIDER_DEFAULT); } /// /// Initializes a new instance of the class. /// - public Impersonator() - { } + public Impersonator() { + } /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// - public void Dispose() - { + public void Dispose() { UndoImpersonation(); } - - /// - /// Impersonates the specified user account. - /// - /// Name of the user. - /// Name of the domain. - /// The password. - public void Impersonate(string userName, string domainName, string password) - { - Impersonate(userName, domainName, password, LogonType.LOGON32_LOGON_INTERACTIVE, LogonProvider.LOGON32_PROVIDER_DEFAULT); - } - - /// - /// Impersonates the specified user account. - /// - /// Name of the user. - /// Name of the domain. - /// The password. - ///< param name="logonType">Type of the logon. - /// The logon provider. - public void Impersonate(string userName, string domainName, string password, LogonType logonType, LogonProvider logonProvider) - { + + /// + /// Impersonates the specified user account. + /// + /// Name of the user. + /// Name of the domain. + /// The password. + ///< param name="logonType">Type of the logon. + /// The logon provider. + public void Impersonate(string userName, string domainName, string password, LogonType logonType = LogonType.LOGON32_LOGON_INTERACTIVE, + LogonProvider logonProvider = LogonProvider.LOGON32_PROVIDER_DEFAULT) { UndoImpersonation(); IntPtr logonToken = IntPtr.Zero; IntPtr logonTokenDuplicate = IntPtr.Zero; - try - { + try { // revert to the application pool identity, saving the identity of the current requestor _wic = WindowsIdentity.Impersonate(IntPtr.Zero); // do logon & impersonate if (Win32NativeMethods.LogonUser(userName, - domainName, - password, - (int)logonType, - (int)logonProvider, - ref logonToken) != 0) - { - if (Win32NativeMethods.DuplicateToken(logonToken, (int)ImpersonationLevel.SecurityImpersonation, ref logonTokenDuplicate) != 0) - { + domainName, + password, + (int)logonType, + (int)logonProvider, + ref logonToken) != 0) { + if (Win32NativeMethods.DuplicateToken(logonToken, (int)ImpersonationLevel.SecurityImpersonation, + ref logonTokenDuplicate) != 0) { var wi = new WindowsIdentity(logonTokenDuplicate); wi.Impersonate(); // discard the returned identity context (which is the context of the application pool) } @@ -175,8 +156,7 @@ public void Impersonate(string userName, string domainName, string password, Log else throw new Win32Exception(Marshal.GetLastWin32Error()); } - finally - { + finally { if (logonToken != IntPtr.Zero) Win32NativeMethods.CloseHandle(logonToken); @@ -188,12 +168,11 @@ public void Impersonate(string userName, string domainName, string password, Log /// /// Stops impersonation. /// - private void UndoImpersonation() - { + private void UndoImpersonation() { // restore saved requestor identity if (_wic != null) _wic.Undo(); _wic = null; } } -} +} \ No newline at end of file diff --git a/src/CommonLib/LdapUtilsNew.cs b/src/CommonLib/LdapUtilsNew.cs index afcb668f..7dfda806 100644 --- a/src/CommonLib/LdapUtilsNew.cs +++ b/src/CommonLib/LdapUtilsNew.cs @@ -26,7 +26,7 @@ namespace SharpHoundCommonLib; -public class LdapUtilsNew : ILdapUtilsNew{ +public class LdapUtilsNew : ILdapUtilsNew { //This cache is indexed by domain sid private readonly ConcurrentDictionary _dcInfoCache = new(); private static readonly ConcurrentDictionary DomainCache = new(); @@ -41,6 +41,7 @@ private static readonly ConcurrentDictionary private readonly ConcurrentDictionary _distinguishedNameCache = new(StringComparer.OrdinalIgnoreCase); + private readonly ILogger _log; private readonly PortScanner _portScanner; private readonly NativeMethods _nativeMethods; @@ -122,16 +123,17 @@ public async IAsyncEnumerable> RangedRetrieval(string distinguish yield return Result.Fail("Failed to create search request"); yield break; } - + var queryRetryCount = 0; var busyRetryCount = 0; - + Result tempResult = null; while (true) { if (cancellationToken.IsCancellationRequested) { yield break; } + SearchResponse response = null; try { response = (SearchResponse)connectionWrapper.Connection.SendRequest(searchRequest); @@ -141,7 +143,8 @@ public async IAsyncEnumerable> RangedRetrieval(string distinguish var backoffDelay = GetNextBackoff(busyRetryCount); await Task.Delay(backoffDelay, cancellationToken); } - catch (LdapException le) when (le.ErrorCode == (int)LdapErrorCodes.ServerDown && queryRetryCount < MaxRetries) { + catch (LdapException le) when (le.ErrorCode == (int)LdapErrorCodes.ServerDown && + queryRetryCount < MaxRetries) { queryRetryCount++; _connectionPool.ReleaseConnection(connectionWrapper, true); for (var retryCount = 0; retryCount < MaxRetries; retryCount++) { @@ -151,7 +154,8 @@ public async IAsyncEnumerable> RangedRetrieval(string distinguish await _connectionPool.GetLdapConnection(domain, false); if (success) { - _log.LogDebug("RangedRetrieval - Recovered from ServerDown successfully, connection made to {NewServer}", + _log.LogDebug( + "RangedRetrieval - Recovered from ServerDown successfully, connection made to {NewServer}", newConnectionWrapper.GetServer()); connectionWrapper = newConnectionWrapper; break; @@ -159,13 +163,16 @@ await _connectionPool.GetLdapConnection(domain, //If we hit our max retries for making a new connection, set tempResult so we can yield it after this logic if (retryCount == MaxRetries - 1) { - _log.LogError("RangedRetrieval - Failed to get a new connection after ServerDown for path {Path}", distinguishedName); + _log.LogError( + "RangedRetrieval - Failed to get a new connection after ServerDown for path {Path}", + distinguishedName); tempResult = Result.Fail( "RangedRetrieval - Failed to get a new connection after ServerDown."); } } - }catch (LdapException le) { + } + catch (LdapException le) { tempResult = Result.Fail( $"Caught unrecoverable ldap exception: {le.Message} (ServerMessage: {le.ServerErrorMessage}) (ErrorCode: {le.ErrorCode})"); } @@ -173,7 +180,7 @@ await _connectionPool.GetLdapConnection(domain, tempResult = Result.Fail($"Caught unrecoverable exception: {e.Message}"); } - + //If we have a tempResult set it means we hit an error we couldn't recover from, so yield that result and then break out of the function if (tempResult != null) { yield return tempResult; @@ -197,7 +204,7 @@ await _connectionPool.GetLdapConnection(domain, if (complete) { yield break; } - + currentRange = $"{attributeName};range={index}-{index + step}"; searchRequest.Attributes.Clear(); searchRequest.Attributes.Add(currentRange); @@ -513,7 +520,7 @@ public async IAsyncEnumerable> PagedQuery(LdapQue Cache.AddType(sid, type); return (true, type); } - + try { var entry = new DirectoryEntry($"LDAP://"); if (entry.GetLabel(out type)) { @@ -524,7 +531,7 @@ public async IAsyncEnumerable> PagedQuery(LdapQue catch { //pass } - + using (var ctx = new PrincipalContext(ContextType.Domain)) { try { var principal = Principal.FindByIdentity(ctx, IdentityType.Sid, sid); @@ -571,7 +578,7 @@ public async IAsyncEnumerable> PagedQuery(LdapQue catch { //pass } - + using (var ctx = new PrincipalContext(ContextType.Domain)) { try { var principal = Principal.FindByIdentity(ctx, IdentityType.Guid, guid); @@ -1170,15 +1177,15 @@ public bool GetDomain(out Domain domain) { var sids = new List(); await foreach (var result in Query(new LdapQueryParameters { - DomainName = domain, - Attributes = new[] { LDAPProperties.ObjectSID }, - GlobalCatalog = true, - LDAPFilter = new LDAPFilter().AddUsers($"(samaccountname={name})").GetFilter() - })) { + DomainName = domain, + Attributes = new[] { LDAPProperties.ObjectSID }, + GlobalCatalog = true, + LDAPFilter = new LDAPFilter().AddUsers($"(samaccountname={name})").GetFilter() + })) { if (result.IsSuccess) { var sid = result.Value.GetSid(); if (!string.IsNullOrWhiteSpace(sid)) { - sids.Add(sid); + sids.Add(sid); } } else { @@ -1201,15 +1208,18 @@ public bool GetDomain(out Domain domain) { }).DefaultIfEmpty(null).FirstAsync(); if (result == null) { - _log.LogWarning("Could not find certificate template with {PropertyName}:{PropertyValue} under {Container}", propertyName, propertyName, containerDistinguishedName); + _log.LogWarning("Could not find certificate template with {PropertyName}:{PropertyValue} under {Container}", + propertyName, propertyName, containerDistinguishedName); return (false, null); } if (!result.IsSuccess) { - _log.LogWarning("Could not find certificate template with {PropertyName}:{PropertyValue} under {Container}: {Error}", propertyName, propertyName, containerDistinguishedName, result.Error); + _log.LogWarning( + "Could not find certificate template with {PropertyName}:{PropertyValue} under {Container}: {Error}", + propertyName, propertyName, containerDistinguishedName, result.Error); return (false, null); } - + var entry = result.Value; return (true, new TypedPrincipal(entry.GetGuid(), Label.CertTemplate)); } @@ -1288,20 +1298,18 @@ public ActiveDirectorySecurityDescriptor MakeSecurityDescriptor() { return new ActiveDirectorySecurityDescriptor(new ActiveDirectorySecurity()); } - public async Task<(bool Success, TypedPrincipal Principal)> ConvertLocalWellKnownPrincipal(SecurityIdentifier sid, string computerDomainSid, string computerDomain) { + public async Task<(bool Success, TypedPrincipal Principal)> ConvertLocalWellKnownPrincipal(SecurityIdentifier sid, + string computerDomainSid, string computerDomain) { if (!WellKnownPrincipal.GetWellKnownPrincipal(sid.Value, out var common)) return (false, null); //The everyone and auth users principals are special and will be converted to the domain equivalent - if (sid.Value is "S-1-1-0" or "S-1-5-11") - { + if (sid.Value is "S-1-1-0" or "S-1-5-11") { return await GetWellKnownPrincipal(sid.Value, computerDomain); } //Use the computer object id + the RID of the sid we looked up to create our new principal - var principal = new TypedPrincipal - { + var principal = new TypedPrincipal { ObjectIdentifier = $"{computerDomainSid}-{sid.Rid()}", - ObjectType = common.ObjectType switch - { + ObjectType = common.ObjectType switch { Label.User => Label.LocalUser, Label.Group => Label.LocalGroup, _ => common.ObjectType @@ -1309,15 +1317,14 @@ public ActiveDirectorySecurityDescriptor MakeSecurityDescriptor() { }; return (true, principal); - } - - public async Task IsDomainController(string computerObjectId, string domainName) - { + + public async Task IsDomainController(string computerObjectId, string domainName) { + var resDomain = await GetDomainNameFromSid(domainName) is (false, var tempDomain) ? tempDomain : domainName; var filter = new LDAPFilter().AddFilter(CommonFilters.SpecificSID(computerObjectId), true) .AddFilter(CommonFilters.DomainControllers, true); var result = await Query(new LdapQueryParameters() { - DomainName = domainName, + DomainName = resDomain, Attributes = CommonProperties.ObjectID, LDAPFilter = filter.GetFilter(), }).DefaultIfEmpty(null).FirstOrDefaultAsync(); @@ -1359,7 +1366,8 @@ public async Task IsDomainController(string computerObjectId, string domai using (var ctx = new PrincipalContext(ContextType.Domain)) { try { var lookupPrincipal = Principal.FindByIdentity(ctx, IdentityType.DistinguishedName, distinguishedName); - if (lookupPrincipal != null && ((DirectoryEntry)lookupPrincipal.GetUnderlyingObject()).GetTypedPrincipal(out principal)) { + if (lookupPrincipal != null && + ((DirectoryEntry)lookupPrincipal.GetUnderlyingObject()).GetTypedPrincipal(out principal)) { return (true, principal); } diff --git a/src/CommonLib/Processors/CertAbuseProcessor.cs b/src/CommonLib/Processors/CertAbuseProcessor.cs index 4ab0f156..35c8bd5b 100644 --- a/src/CommonLib/Processors/CertAbuseProcessor.cs +++ b/src/CommonLib/Processors/CertAbuseProcessor.cs @@ -17,12 +17,12 @@ namespace SharpHoundCommonLib.Processors public class CertAbuseProcessor { private readonly ILogger _log; - private readonly ILDAPUtils _utils; + private readonly ILdapUtilsNew _utils; public delegate Task ComputerStatusDelegate(CSVComputerStatus status); public event ComputerStatusDelegate ComputerStatusEvent; - public CertAbuseProcessor(ILDAPUtils utils, ILogger log = null) + public CertAbuseProcessor(ILdapUtilsNew utils, ILogger log = null) { _utils = utils; _log = log ?? Logging.LogProvider.CreateLogger("CAProc"); @@ -57,9 +57,9 @@ public async Task ProcessRegistryEnrollmentPermissions(str descriptor.SetSecurityDescriptorBinaryForm(aceData.Value as byte[], AccessControlSections.All); var ownerSid = Helpers.PreProcessSID(descriptor.GetOwner(typeof(SecurityIdentifier))); - var computerDomain = _utils.GetDomainNameFromSid(computerObjectId); - var isDomainController = _utils.IsDomainController(computerObjectId, computerDomain); - var machineSid = await GetMachineSid(computerName, computerObjectId, computerDomain, isDomainController); + var (success,computerDomain) = await _utils.GetDomainNameFromSid(computerObjectId); + var isDomainController = await _utils.IsDomainController(computerObjectId, computerDomain); + var machineSid = await GetMachineSid(computerName, computerObjectId); var aces = new List(); @@ -92,7 +92,10 @@ public async Task ProcessRegistryEnrollmentPermissions(str if (principalSid == null) continue; - var principalDomain = _utils.GetDomainNameFromSid(principalSid) ?? objectDomain; + var (getDomainSuccess, principalDomain) = await _utils.GetDomainNameFromSid(principalSid); + if (!getDomainSuccess) { + + } var resolvedPrincipal = GetRegistryPrincipal(new SecurityIdentifier(principalSid), principalDomain, computerName, isDomainController, computerObjectId, machineSid); var isInherited = rule.IsInherited(); @@ -153,16 +156,15 @@ public async Task ProcessEAPermissions(string return ret; } - var computerDomain = _utils.GetDomainNameFromSid(computerObjectId); - var isDomainController = _utils.IsDomainController(computerObjectId, computerDomain); - var machineSid = await GetMachineSid(computerName, computerObjectId, computerDomain, isDomainController); - var certTemplatesLocation = _utils.BuildLdapPath(DirectoryPaths.CertTemplateLocation, computerDomain); + var isDomainController = await _utils.IsDomainController(computerObjectId, objectDomain); + var machineSid = await GetMachineSid(computerName, computerObjectId); + var certTemplatesLocation = _utils.BuildLdapPath(DirectoryPaths.CertTemplateLocation, objectDomain); var descriptor = new RawSecurityDescriptor(regData.Value as byte[], 0); var enrollmentAgentRestrictions = new List(); foreach (var genericAce in descriptor.DiscretionaryAcl) { var ace = (QualifiedAce)genericAce; - enrollmentAgentRestrictions.Add(new EnrollmentAgentRestriction(ace, computerDomain, certTemplatesLocation, this, _utils, computerName, isDomainController, computerObjectId, machineSid)); + enrollmentAgentRestrictions.Add(new EnrollmentAgentRestriction(ace, objectDomain, certTemplatesLocation, this, _utils, computerName, isDomainController, computerObjectId, machineSid)); } ret.Restrictions = enrollmentAgentRestrictions.ToArray(); @@ -170,10 +172,10 @@ public async Task ProcessEAPermissions(string return ret; } - public (IEnumerable resolvedTemplates, IEnumerable unresolvedTemplates) ProcessCertTemplates(string[] templates, string domainName) + public (IEnumerable resolvedTemplates, IEnumerable unresolvedTemplates) ProcessCertTemplates(string[] templates, string domainName) { var resolvedTemplates = new List(); - var unresolvedTemplates = new List(); + var unresolvedTemplates = new List(); var certTemplatesLocation = _utils.BuildLdapPath(DirectoryPaths.CertTemplateLocation, domainName); foreach (var templateCN in templates) @@ -333,7 +335,7 @@ public TypedPrincipal GetRegistryPrincipal(SecurityIdentifier sid, string comput return _utils.ResolveIDAndType(sid.Value, computerDomain); } - private async Task GetMachineSid(string computerName, string computerObjectId, string computerDomain, bool isDomainController) + private async Task GetMachineSid(string computerName, string computerObjectId) { SecurityIdentifier machineSid = null; @@ -401,7 +403,7 @@ private async Task SendComputerStatus(CSVComputerStatus status) public class EnrollmentAgentRestriction { - public EnrollmentAgentRestriction(QualifiedAce ace, string computerDomain, string certTemplatesLocation, CertAbuseProcessor certAbuseProcessor, ILDAPUtils utils, string computerName, bool isDomainController, string computerObjectId, SecurityIdentifier machineSid) + public EnrollmentAgentRestriction(QualifiedAce ace, string computerDomain, string certTemplatesLocation, CertAbuseProcessor certAbuseProcessor, ILdapUtilsNew utils, string computerName, bool isDomainController, string computerObjectId, SecurityIdentifier machineSid) { var targets = new List(); var index = 0; diff --git a/src/CommonLib/Processors/ComputerSessionProcessor.cs b/src/CommonLib/Processors/ComputerSessionProcessor.cs index f8028f17..b7736008 100644 --- a/src/CommonLib/Processors/ComputerSessionProcessor.cs +++ b/src/CommonLib/Processors/ComputerSessionProcessor.cs @@ -19,12 +19,12 @@ public class ComputerSessionProcessor private readonly string _currentUserName; private readonly ILogger _log; private readonly NativeMethods _nativeMethods; - private readonly ILDAPUtils _utils; + private readonly ILdapUtilsNew _utils; private readonly bool _doLocalAdminSessionEnum; private readonly string _localAdminUsername; private readonly string _localAdminPassword; - public ComputerSessionProcessor(ILDAPUtils utils, string currentUserName = null, NativeMethods nativeMethods = null, ILogger log = null, bool doLocalAdminSessionEnum = false, string localAdminUsername = null, string localAdminPassword = null) + public ComputerSessionProcessor(ILdapUtilsNew utils, string currentUserName = null, NativeMethods nativeMethods = null, ILogger log = null, bool doLocalAdminSessionEnum = false, string localAdminUsername = null, string localAdminPassword = null) { _utils = utils; _nativeMethods = nativeMethods ?? new NativeMethods(); @@ -54,8 +54,7 @@ public async Task ReadUserSessions(string computerName, string if (_doLocalAdminSessionEnum) { // If we are authenticating using a local admin, we need to impersonate for this - Impersonator Impersonate; - using (Impersonate = new Impersonator(_localAdminUsername, ".", _localAdminPassword, LogonType.LOGON32_LOGON_NEW_CREDENTIALS, LogonProvider.LOGON32_PROVIDER_WINNT50)) + using (new Impersonator(_localAdminUsername, ".", _localAdminPassword, LogonType.LOGON32_LOGON_NEW_CREDENTIALS, LogonProvider.LOGON32_PROVIDER_WINNT50)) { result = _nativeMethods.NetSessionEnum(computerName); } @@ -125,13 +124,12 @@ await SendComputerStatus(new CSVComputerStatus computerSessionName = computerSessionName.TrimStart('\\'); string resolvedComputerSID = null; - //Resolve "localhost" equivalents to the computer sid if (computerSessionName is "[::1]" or "127.0.0.1") resolvedComputerSID = computerSid; - else + else if (await _utils.ResolveHostToSid(computerSessionName, computerDomain) is (true, var tempSid)) //Attempt to resolve the host name to a SID - resolvedComputerSID = await _utils.ResolveHostToSid(computerSessionName, computerDomain); + resolvedComputerSID = tempSid; //Throw out this data if we couldn't resolve it successfully. if (resolvedComputerSID == null || !resolvedComputerSID.StartsWith("S-1")) @@ -140,20 +138,20 @@ await SendComputerStatus(new CSVComputerStatus continue; } - var matches = _utils.GetUserGlobalCatalogMatches(username); - if (matches.Length > 0) + var (matchSuccess, sids) = await _utils.GetGlobalCatalogMatches(username, computerDomain); + if (matchSuccess) { results.AddRange( - matches.Select(s => new Session {ComputerSID = resolvedComputerSID, UserSID = s})); + sids.Select(s => new Session {ComputerSID = resolvedComputerSID, UserSID = s})); } else { - var res = _utils.ResolveAccountName(username, computerDomain); - if (res != null) + var res = await _utils.ResolveAccountName(username, computerDomain); + if (res.Success) results.Add(new Session { ComputerSID = resolvedComputerSID, - UserSID = res.ObjectIdentifier + UserSID = res.Principal.ObjectIdentifier }); } } @@ -180,8 +178,7 @@ public async Task ReadUserSessionsPrivileged(string computerNa if (_doLocalAdminSessionEnum) { // If we are authenticating using a local admin, we need to impersonate for this - Impersonator Impersonate; - using (Impersonate = new Impersonator(_localAdminUsername, ".", _localAdminPassword, LogonType.LOGON32_LOGON_NEW_CREDENTIALS, LogonProvider.LOGON32_PROVIDER_WINNT50)) + using (new Impersonator(_localAdminUsername, ".", _localAdminPassword, LogonType.LOGON32_LOGON_NEW_CREDENTIALS, LogonProvider.LOGON32_PROVIDER_WINNT50)) { result = _nativeMethods.NetWkstaUserEnum(computerName); } @@ -259,8 +256,8 @@ await SendComputerStatus(new CSVComputerStatus continue; } - var res = _utils.ResolveAccountName(username, domain); - if (res == null) + var (success, res) = await _utils.ResolveAccountName(username, domain); + if (!success) continue; _log.LogTrace("Resolved NetWkstaUserEnum entry: {SID}", res.ObjectIdentifier); @@ -311,17 +308,21 @@ await SendComputerStatus(new CSVComputerStatus ComputerName = computerName }); _log.LogDebug("Registry session enum succeeded on {ComputerName}", computerName); - ret.Results = key.GetSubKeyNames() - .Where(subkey => SidRegex.IsMatch(subkey)) - .Select(x => _utils.ResolveIDAndType(x, computerDomain)) - .Where(x => x != null) - .Select(x => - new Session - { + var results = new List(); + foreach (var subkey in key.GetSubKeyNames()) { + if (!SidRegex.IsMatch(subkey)) { + continue; + } + + if (await _utils.ResolveIDAndType(subkey, computerDomain) is (true, var principal)) { + results.Add(new Session() { ComputerSID = computerSid, - UserSID = x.ObjectIdentifier - }) - .ToArray(); + UserSID = principal.ObjectIdentifier + }); + } + } + + ret.Results = results.ToArray(); return ret; } diff --git a/src/CommonLib/Processors/ContainerProcessor.cs b/src/CommonLib/Processors/ContainerProcessor.cs index 8e73ddc6..37fac3c2 100644 --- a/src/CommonLib/Processors/ContainerProcessor.cs +++ b/src/CommonLib/Processors/ContainerProcessor.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.DirectoryServices.Protocols; +using System.Threading.Tasks; using Microsoft.Extensions.Logging; using SharpHoundCommonLib.Enums; using SharpHoundCommonLib.LDAPQueries; @@ -11,9 +12,9 @@ namespace SharpHoundCommonLib.Processors public class ContainerProcessor { private readonly ILogger _log; - private readonly ILDAPUtils _utils; + private readonly ILdapUtilsNew _utils; - public ContainerProcessor(ILDAPUtils utils, ILogger log = null) + public ContainerProcessor(ILdapUtilsNew utils, ILogger log = null) { _utils = utils; _log = log ?? Logging.LogProvider.CreateLogger("ContainerProc"); @@ -34,9 +35,9 @@ private static bool IsDistinguishedNameFiltered(string distinguishedName) /// /// /// - public TypedPrincipal GetContainingObject(ISearchResultEntry entry) + public async Task<(bool Success, TypedPrincipal principal)> GetContainingObject(ISearchResultEntry entry) { - return GetContainingObject(entry.DistinguishedName); + return await GetContainingObject(entry.DistinguishedName); } /// @@ -45,21 +46,22 @@ public TypedPrincipal GetContainingObject(ISearchResultEntry entry) /// /// /// - public TypedPrincipal GetContainingObject(string distinguishedName) + public async Task<(bool Success, TypedPrincipal Principal)> GetContainingObject(string distinguishedName) { var containerDn = Helpers.RemoveDistinguishedNamePrefix(distinguishedName); if (containerDn.StartsWith("CN=BUILTIN", StringComparison.OrdinalIgnoreCase)) { var domain = Helpers.DistinguishedNameToDomain(distinguishedName); - var domainSid = _utils.GetSidFromDomainName(domain); - return new TypedPrincipal(domainSid, Label.Domain); - } + var (success, domainSid) = await _utils.GetDomainSidFromDomainName(domain); + if (success) { + return (true, new TypedPrincipal(domainSid, Label.Domain)); + } - if (string.IsNullOrEmpty(containerDn)) - return null; + return (false, default); + } - return _utils.ResolveDistinguishedName(containerDn); + return await _utils.LookupDistinguishedName(containerDn); } /// @@ -68,7 +70,7 @@ public TypedPrincipal GetContainingObject(string distinguishedName) /// /// /// - public IEnumerable GetContainerChildObjects(ResolvedSearchResult result, + public IAsyncEnumerable GetContainerChildObjects(ResolvedSearchResult result, ISearchResultEntry entry) { var name = result.DisplayName; @@ -83,14 +85,23 @@ public IEnumerable GetContainerChildObjects(ResolvedSearchResult /// /// /// - public IEnumerable GetContainerChildObjects(string distinguishedName, string containerName = "") + public async IAsyncEnumerable GetContainerChildObjects(string distinguishedName, string containerName = "") { var filter = new LDAPFilter().AddComputers().AddUsers().AddGroups().AddOUs().AddContainers(); filter.AddCertificateAuthorities().AddCertificateTemplates().AddEnterpriseCertificationAuthorities(); - foreach (var childEntry in _utils.QueryLDAP(filter.GetFilter(), SearchScope.OneLevel, - CommonProperties.ObjectID, Helpers.DistinguishedNameToDomain(distinguishedName), - adsPath: distinguishedName)) - { + await foreach (var childEntryResult in _utils.Query(new LdapQueryParameters { + DomainName = Helpers.DistinguishedNameToDomain(distinguishedName), + SearchScope = SearchScope.OneLevel, + Attributes = CommonProperties.ObjectID, + LDAPFilter = filter.GetFilter(), + SearchBase = distinguishedName + })) { + if (!childEntryResult.Success) { + _log.LogWarning("Error while getting container child objects for {DistinguishedName}: {Reason}", distinguishedName, childEntryResult.Error); + yield break; + } + + var childEntry = childEntryResult.Value; var dn = childEntry.DistinguishedName; if (IsDistinguishedNameFiltered(dn)) { @@ -106,18 +117,14 @@ public IEnumerable GetContainerChildObjects(string distinguished continue; } - var res = _utils.ResolveIDAndType(id, Helpers.DistinguishedNameToDomain(dn)); - if (res == null) - { - _log.LogTrace("Failed to resolve principal for {ID}", id); - continue; + var res = await _utils.ResolveIDAndType(id, Helpers.DistinguishedNameToDomain(dn)); + if (res.Success) { + yield return res.Principal; } - - yield return res; } } - public IEnumerable ReadContainerGPLinks(ResolvedSearchResult result, ISearchResultEntry entry) + public IAsyncEnumerable ReadContainerGPLinks(ResolvedSearchResult result, ISearchResultEntry entry) { var links = entry.GetProperty(LDAPProperties.GPLink); @@ -129,7 +136,7 @@ public IEnumerable ReadContainerGPLinks(ResolvedSearchResult result, ISe /// /// /// - public IEnumerable ReadContainerGPLinks(string gpLink) + public async IAsyncEnumerable ReadContainerGPLinks(string gpLink) { if (gpLink == null) yield break; @@ -138,19 +145,15 @@ public IEnumerable ReadContainerGPLinks(string gpLink) { var enforced = link.Status.Equals("2"); - var res = _utils.ResolveDistinguishedName(link.DistinguishedName); + var res = await _utils.LookupDistinguishedName(link.DistinguishedName); - if (res == null) - { - _log.LogTrace("Failed to resolve DN {DN}", link.DistinguishedName); - continue; + if (res.Success) { + yield return new GPLink + { + GUID = res.Principal.ObjectIdentifier, + IsEnforced = enforced + }; } - - yield return new GPLink - { - GUID = res.ObjectIdentifier, - IsEnforced = enforced - }; } } diff --git a/src/CommonLib/Processors/DCRegistryProcessor.cs b/src/CommonLib/Processors/DCRegistryProcessor.cs index e3fb4b14..8d5cc189 100644 --- a/src/CommonLib/Processors/DCRegistryProcessor.cs +++ b/src/CommonLib/Processors/DCRegistryProcessor.cs @@ -9,10 +9,10 @@ namespace SharpHoundCommonLib.Processors public class DCRegistryProcessor { private readonly ILogger _log; - public readonly ILDAPUtils _utils; + public readonly ILdapUtilsNew _utils; public delegate Task ComputerStatusDelegate(CSVComputerStatus status); - public DCRegistryProcessor(ILDAPUtils utils, ILogger log = null) + public DCRegistryProcessor(ILdapUtilsNew utils, ILogger log = null) { _utils = utils; _log = log ?? Logging.LogProvider.CreateLogger("DCRegProc"); @@ -29,7 +29,7 @@ public DCRegistryProcessor(ILDAPUtils utils, ILogger log = null) public IntRegistryAPIResult GetCertificateMappingMethods(string target) { var ret = new IntRegistryAPIResult(); - var subKey = $"SYSTEM\\CurrentControlSet\\Control\\SecurityProviders\\Schannel"; + const string subKey = @"SYSTEM\CurrentControlSet\Control\SecurityProviders\Schannel"; const string subValue = "CertificateMappingMethods"; var data = Helpers.GetRegistryKeyData(target, subKey, subValue, _log); @@ -62,7 +62,7 @@ public IntRegistryAPIResult GetCertificateMappingMethods(string target) public IntRegistryAPIResult GetStrongCertificateBindingEnforcement(string target) { var ret = new IntRegistryAPIResult(); - var subKey = $"SYSTEM\\CurrentControlSet\\Services\\Kdc"; + const string subKey = @"SYSTEM\CurrentControlSet\Services\Kdc"; const string subValue = "StrongCertificateBindingEnforcement"; var data = Helpers.GetRegistryKeyData(target, subKey, subValue, _log); diff --git a/src/CommonLib/Processors/DomainTrustProcessor.cs b/src/CommonLib/Processors/DomainTrustProcessor.cs index bb157e8e..23e586ef 100644 --- a/src/CommonLib/Processors/DomainTrustProcessor.cs +++ b/src/CommonLib/Processors/DomainTrustProcessor.cs @@ -11,9 +11,9 @@ namespace SharpHoundCommonLib.Processors public class DomainTrustProcessor { private readonly ILogger _log; - private readonly ILDAPUtils _utils; + private readonly ILdapUtilsNew _utils; - public DomainTrustProcessor(ILDAPUtils utils, ILogger log = null) + public DomainTrustProcessor(ILdapUtilsNew utils, ILogger log = null) { _utils = utils; _log = log ?? Logging.LogProvider.CreateLogger("DomainTrustProc"); @@ -24,14 +24,21 @@ public DomainTrustProcessor(ILDAPUtils utils, ILogger log = null) /// /// /// - public IEnumerable EnumerateDomainTrusts(string domain) + public async IAsyncEnumerable EnumerateDomainTrusts(string domain) { - var query = CommonFilters.TrustedDomains; - foreach (var result in _utils.QueryLDAP(query, SearchScope.Subtree, CommonProperties.DomainTrustProps, - domain)) + await foreach (var result in _utils.Query(new LdapQueryParameters { + LDAPFilter = CommonFilters.TrustedDomains, + Attributes = CommonProperties.DomainTrustProps, + DomainName = domain + })) { + if (!result.Success) { + yield break; + } + + var entry = result.Value; var trust = new DomainTrust(); - var targetSidBytes = result.GetByteProperty(LDAPProperties.SecurityIdentifier); + var targetSidBytes = entry.GetByteProperty(LDAPProperties.SecurityIdentifier); if (targetSidBytes == null || targetSidBytes.Length == 0) { _log.LogTrace("Trust sid is null or empty for target: {Domain}", domain); @@ -51,7 +58,7 @@ public IEnumerable EnumerateDomainTrusts(string domain) trust.TargetDomainSid = sid; - if (int.TryParse(result.GetProperty(LDAPProperties.TrustDirection), out var td)) + if (int.TryParse(entry.GetProperty(LDAPProperties.TrustDirection), out var td)) { trust.TrustDirection = (TrustDirection) td; } @@ -64,7 +71,7 @@ public IEnumerable EnumerateDomainTrusts(string domain) TrustAttributes attributes; - if (int.TryParse(result.GetProperty(LDAPProperties.TrustAttributes), out var ta)) + if (int.TryParse(entry.GetProperty(LDAPProperties.TrustAttributes), out var ta)) { attributes = (TrustAttributes) ta; } @@ -75,7 +82,7 @@ public IEnumerable EnumerateDomainTrusts(string domain) } trust.IsTransitive = !attributes.HasFlag(TrustAttributes.NonTransitive); - var name = result.GetProperty(LDAPProperties.CanonicalName)?.ToUpper(); + var name = entry.GetProperty(LDAPProperties.CanonicalName)?.ToUpper(); if (name != null) trust.TargetDomainName = name; diff --git a/src/CommonLib/Processors/GPOLocalGroupProcessor.cs b/src/CommonLib/Processors/GPOLocalGroupProcessor.cs index fa7041be..5f86b8b4 100644 --- a/src/CommonLib/Processors/GPOLocalGroupProcessor.cs +++ b/src/CommonLib/Processors/GPOLocalGroupProcessor.cs @@ -12,10 +12,8 @@ using SharpHoundCommonLib.LDAPQueries; using SharpHoundCommonLib.OutputTypes; -namespace SharpHoundCommonLib.Processors -{ - public class GPOLocalGroupProcessor - { +namespace SharpHoundCommonLib.Processors { + public class GPOLocalGroupProcessor { private static readonly Regex KeyRegex = new(@"(.+?)\s*=(.*)", RegexOptions.Compiled); private static readonly Regex MemberRegex = @@ -35,59 +33,57 @@ public class GPOLocalGroupProcessor private static readonly ConcurrentDictionary> GpoActionCache = new(); private static readonly Dictionary ValidGroupNames = - new(StringComparer.OrdinalIgnoreCase) - { - {"Administrators", LocalGroupRids.Administrators}, - {"Remote Desktop Users", LocalGroupRids.RemoteDesktopUsers}, - {"Remote Management Users", LocalGroupRids.PSRemote}, - {"Distributed COM Users", LocalGroupRids.DcomUsers} + new(StringComparer.OrdinalIgnoreCase) { + { "Administrators", LocalGroupRids.Administrators }, + { "Remote Desktop Users", LocalGroupRids.RemoteDesktopUsers }, + { "Remote Management Users", LocalGroupRids.PSRemote }, + { "Distributed COM Users", LocalGroupRids.DcomUsers } }; private readonly ILogger _log; - private readonly ILDAPUtils _utils; + private readonly ILdapUtilsNew _utils; - public GPOLocalGroupProcessor(ILDAPUtils utils, ILogger log = null) - { + public GPOLocalGroupProcessor(ILdapUtilsNew utils, ILogger log = null) { _utils = utils; _log = log ?? Logging.LogProvider.CreateLogger("GPOLocalGroupProc"); } - public Task ReadGPOLocalGroups(ISearchResultEntry entry) - { + public Task ReadGPOLocalGroups(ISearchResultEntry entry) { var links = entry.GetProperty(LDAPProperties.GPLink); var dn = entry.DistinguishedName; return ReadGPOLocalGroups(links, dn); } - public async Task ReadGPOLocalGroups(string gpLink, string distinguishedName) - { + public async Task ReadGPOLocalGroups(string gpLink, string distinguishedName) { var ret = new ResultingGPOChanges(); //If the gplink property is null, we don't need to process anything if (gpLink == null) return ret; // First lets check if this OU actually has computers that it contains. If not, then we'll ignore it. - // Its cheaper to fetch the affected computers from LDAP first and then process the GPLinks - var options = new LDAPQueryOptions - { - Filter = new LDAPFilter().AddComputersNoMSAs().GetFilter(), - Scope = SearchScope.Subtree, - Properties = CommonProperties.ObjectSID, - AdsPath = distinguishedName - }; + // Its cheaper to fetch the affected computers from LDAP first and then process the GPLinks + var affectedComputers = new List(); + await foreach (var result in _utils.Query(new LdapQueryParameters() { + LDAPFilter = new LDAPFilter().AddComputersNoMSAs().GetFilter(), + Attributes = CommonProperties.ObjectSID, + SearchBase = distinguishedName + })) { + if (!result.IsSuccess) { + break; + } - var affectedComputers = _utils.QueryLDAP(options) - .Select(x => x.GetSid()) - .Where(x => x != null) - .Select(x => new TypedPrincipal - { - ObjectIdentifier = x, - ObjectType = Label.Computer - }).ToArray(); + var entry = result.Value; + var sid = entry.GetSid(); + if (string.IsNullOrWhiteSpace(sid)) { + continue; + } + + affectedComputers.Add(new TypedPrincipal(sid, Label.Computer)); + } //If there's no computers then we don't care about this OU - if (affectedComputers.Length == 0) + if (affectedComputers.Count == 0) return ret; var enforced = new List(); @@ -95,8 +91,7 @@ public async Task ReadGPOLocalGroups(string gpLink, string // Split our link property up and remove disabled links foreach (var link in Helpers.SplitGPLinkProperty(gpLink)) - switch (link.Status) - { + switch (link.Status) { case "0": unenforced.Add(link.DistinguishedName); break; @@ -106,40 +101,45 @@ public async Task ReadGPOLocalGroups(string gpLink, string } //Set up our links in the correct order. - // Enforced links override unenforced, and also respect the order in which they are placed in the GPLink property + //Enforced links override unenforced, and also respect the order in which they are placed in the GPLink property var orderedLinks = new List(); orderedLinks.AddRange(unenforced); orderedLinks.AddRange(enforced); var data = new Dictionary(); - foreach (var rid in Enum.GetValues(typeof(LocalGroupRids))) data[(LocalGroupRids) rid] = new GroupResults(); + foreach (var rid in Enum.GetValues(typeof(LocalGroupRids))) data[(LocalGroupRids)rid] = new GroupResults(); - foreach (var linkDn in orderedLinks) - { - if (!GpoActionCache.TryGetValue(linkDn.ToLower(), out var actions)) - { + foreach (var linkDn in orderedLinks) { + if (!GpoActionCache.TryGetValue(linkDn.ToLower(), out var actions)) { actions = new List(); var gpoDomain = Helpers.DistinguishedNameToDomain(linkDn); - var opts = new LDAPQueryOptions - { + var opts = new LDAPQueryOptions { Filter = new LDAPFilter().AddAllObjects().GetFilter(), Scope = SearchScope.Base, Properties = CommonProperties.GPCFileSysPath, AdsPath = linkDn }; - var filePath = _utils.QueryLDAP(opts).FirstOrDefault()? - .GetProperty(LDAPProperties.GPCFileSYSPath); + var result = await _utils.Query(new LdapQueryParameters() { + LDAPFilter = new LDAPFilter().AddAllObjects().GetFilter(), + SearchScope = SearchScope.Base, + Attributes = CommonProperties.GPCFileSysPath, + SearchBase = linkDn + }).DefaultIfEmpty(null).FirstOrDefaultAsync(); + + if (result is not { Success: true }) { + continue; + } - if (filePath == null) - { + var filePath = result.Value.GetProperty(LDAPProperties.GPCFileSYSPath); + if (string.IsNullOrWhiteSpace(filePath)) { GpoActionCache.TryAdd(linkDn, actions); continue; } //Add the actions for each file. The GPO template file actions will override the XML file actions - actions.AddRange(ProcessGPOXmlFile(filePath, gpoDomain).ToList()); + await foreach (var item in ProcessGPOXmlFile(filePath, gpoDomain)) actions.Add(item); await foreach (var item in ProcessGPOTemplateFile(filePath, gpoDomain)) actions.Add(item); } @@ -154,8 +154,7 @@ public async Task ReadGPOLocalGroups(string gpLink, string var restrictedMemberSets = actions.Where(x => x.Target == GroupActionTarget.RestrictedMember) .GroupBy(x => x.TargetRid); - foreach (var set in restrictedMemberSets) - { + foreach (var set in restrictedMemberSets) { var results = data[set.Key]; var members = set.Select(x => x.ToTypedPrincipal()).ToList(); results.RestrictedMember = members; @@ -166,8 +165,7 @@ public async Task ReadGPOLocalGroups(string gpLink, string var restrictedMemberOfSets = actions.Where(x => x.Target == GroupActionTarget.RestrictedMemberOf) .GroupBy(x => x.TargetRid); - foreach (var set in restrictedMemberOfSets) - { + foreach (var set in restrictedMemberOfSets) { var results = data[set.Key]; var members = set.Select(x => x.ToTypedPrincipal()).ToList(); results.RestrictedMemberOf = members; @@ -178,15 +176,12 @@ public async Task ReadGPOLocalGroups(string gpLink, string var localGroupSets = actions.Where(x => x.Target == GroupActionTarget.LocalGroup) .GroupBy(x => x.TargetRid); - foreach (var set in localGroupSets) - { + foreach (var set in localGroupSets) { var results = data[set.Key]; - foreach (var temp in set) - { + foreach (var temp in set) { var res = temp.ToTypedPrincipal(); var newMembers = results.LocalGroups; - switch (temp.Action) - { + switch (temp.Action) { case GroupActionOperation.Add: newMembers.Add(res); break; @@ -206,12 +201,11 @@ public async Task ReadGPOLocalGroups(string gpLink, string } } - ret.AffectedComputers = affectedComputers; + ret.AffectedComputers = affectedComputers.ToArray(); //At this point, we've resolved individual add/substract methods for each linked GPO. //Now we need to actually squish them together into the resulting set of changes - foreach (var kvp in data) - { + foreach (var kvp in data) { var key = kvp.Key; var val = kvp.Value; var rm = val.RestrictedMember; @@ -226,8 +220,7 @@ public async Task ReadGPOLocalGroups(string gpLink, string var finalArr = final.Distinct().ToArray(); - switch (key) - { + switch (key) { case LocalGroupRids.Administrators: ret.LocalAdmins = finalArr; break; @@ -252,20 +245,17 @@ public async Task ReadGPOLocalGroups(string gpLink, string /// /// /// - internal async IAsyncEnumerable ProcessGPOTemplateFile(string basePath, string gpoDomain) - { + internal async IAsyncEnumerable ProcessGPOTemplateFile(string basePath, string gpoDomain) { var templatePath = Path.Combine(basePath, "MACHINE", "Microsoft", "Windows NT", "SecEdit", "GptTmpl.inf"); if (!File.Exists(templatePath)) yield break; FileStream fs; - try - { + try { fs = new FileStream(templatePath, FileMode.Open, FileAccess.Read); } - catch - { + catch { yield break; } @@ -281,8 +271,7 @@ internal async IAsyncEnumerable ProcessGPOTemplateFile(string baseP //Split our text into individual lines var memberLines = Regex.Split(memberText, @"\r\n|\r|\n"); - foreach (var memberLine in memberLines) - { + foreach (var memberLine in memberLines) { //Check if the Key regex matches (S-1-5.*_memberof=blah) var keyMatch = KeyRegex.Match(memberLine); @@ -296,53 +285,44 @@ internal async IAsyncEnumerable ProcessGPOTemplateFile(string baseP var rightMatches = MemberRightRegex.Matches(val); //If leftmatch is a success, the members of a group are being explicitly set - if (leftMatch.Success) - { + if (leftMatch.Success) { var extracted = ExtractRid.Match(leftMatch.Value); var rid = int.Parse(extracted.Groups[1].Value); if (Enum.IsDefined(typeof(LocalGroupRids), rid)) //Loop over the members in the match, and try to convert them to SIDs - foreach (var member in val.Split(',')) - { - var res = GetSid(member.Trim('*'), gpoDomain); - if (res == null) - continue; - yield return new GroupAction - { - Target = GroupActionTarget.RestrictedMember, - Action = GroupActionOperation.Add, - TargetSid = res.ObjectIdentifier, - TargetType = res.ObjectType, - TargetRid = (LocalGroupRids) rid - }; + foreach (var member in val.Split(',')) { + if (await GetSid(member.Trim('*'), gpoDomain) is (true, var res)) { + yield return new GroupAction { + Target = GroupActionTarget.RestrictedMember, + Action = GroupActionOperation.Add, + TargetSid = res.ObjectIdentifier, + TargetType = res.ObjectType, + TargetRid = (LocalGroupRids)rid + }; + } } } //If right match is a success, a group has been set as a member of one of our local groups var index = key.IndexOf("MemberOf", StringComparison.CurrentCultureIgnoreCase); - if (rightMatches.Count > 0 && index > 0) - { + if (rightMatches.Count > 0 && index > 0) { var account = key.Trim('*').Substring(0, index - 3).ToUpper(); - var res = GetSid(account, gpoDomain); - if (res == null) - continue; + if (await GetSid(account, gpoDomain) is (true, var res)) { + foreach (var match in rightMatches) { + var rid = int.Parse(ExtractRid.Match(match.ToString()).Groups[1].Value); + if (!Enum.IsDefined(typeof(LocalGroupRids), rid)) continue; - foreach (var match in rightMatches) - { - var rid = int.Parse(ExtractRid.Match(match.ToString()).Groups[1].Value); - if (!Enum.IsDefined(typeof(LocalGroupRids), rid)) continue; - - var targetGroup = (LocalGroupRids) rid; - yield return new GroupAction - { - Target = GroupActionTarget.RestrictedMemberOf, - Action = GroupActionOperation.Add, - TargetRid = targetGroup, - TargetSid = res.ObjectIdentifier, - TargetType = res.ObjectType - }; + var targetGroup = (LocalGroupRids)rid; + yield return new GroupAction { + Target = GroupActionTarget.RestrictedMemberOf, + Action = GroupActionOperation.Add, + TargetRid = targetGroup, + TargetSid = res.ObjectIdentifier, + TargetType = res.ObjectType + }; + } } } } @@ -354,21 +334,17 @@ internal async IAsyncEnumerable ProcessGPOTemplateFile(string baseP /// /// /// - private TypedPrincipal GetSid(string account, string domainName) - { - if (!account.StartsWith("S-1-", StringComparison.CurrentCulture)) - { + private async Task<(bool Success, TypedPrincipal Principal)> GetSid(string account, string domainName) { + if (!account.StartsWith("S-1-", StringComparison.CurrentCulture)) { string user; string domain; - if (account.Contains('\\')) - { + if (account.Contains('\\')) { //The account is in the format DOMAIN\\username var split = account.Split('\\'); domain = split[0]; user = split[1]; } - else - { + else { //The account is just a username, so try with the current domain domain = domainName; user = account; @@ -377,21 +353,15 @@ private TypedPrincipal GetSid(string account, string domainName) user = user.ToUpper(); //Try to resolve as a user object first - var res = _utils.ResolveAccountName(user, domain); - if (res != null) - return res; + var (success, res) = await _utils.ResolveAccountName(user, domain); + if (success) + return (true, res); - res = _utils.ResolveAccountName($"{user}$", domain); - return res; + return await _utils.ResolveAccountName($"{user}$", domain); } //The element is just a sid, so return it straight - var lType = _utils.LookupSidType(account, domainName); - return new TypedPrincipal - { - ObjectIdentifier = account, - ObjectType = lType - }; + return await _utils.ResolveIDAndType(account, domainName); } /// @@ -400,8 +370,7 @@ private TypedPrincipal GetSid(string account, string domainName) /// /// /// A list of GPO "Actions" - internal IEnumerable ProcessGPOXmlFile(string basePath, string gpoDomain) - { + internal async IAsyncEnumerable ProcessGPOXmlFile(string basePath, string gpoDomain) { var xmlPath = Path.Combine(basePath, "MACHINE", "Preferences", "Groups", "Groups.xml"); //If the file doesn't exist, then just return @@ -410,12 +379,10 @@ internal IEnumerable ProcessGPOXmlFile(string basePath, string gpoD //Create an XPathDocument to let us navigate the XML XPathDocument doc; - try - { + try { doc = new XPathDocument(xmlPath); } - catch (Exception e) - { + catch (Exception e) { _log.LogError(e, "error reading GPO XML file {File}", xmlPath); yield break; } @@ -424,20 +391,17 @@ internal IEnumerable ProcessGPOXmlFile(string basePath, string gpoD //Grab all the Groups nodes var groupsNodes = navigator.Select("/Groups"); - while (groupsNodes.MoveNext()) - { + while (groupsNodes.MoveNext()) { var current = groupsNodes.Current; //If disable is set to 1, then this Group wont apply if (current.GetAttribute("disabled", "") is "1") continue; var groupNodes = current.Select("Group"); - while (groupNodes.MoveNext()) - { + while (groupNodes.MoveNext()) { //Grab the properties for each Group node. Current path is /Groups/Group var groupProperties = groupNodes.Current.Select("Properties"); - while (groupProperties.MoveNext()) - { + while (groupProperties.MoveNext()) { var currentProperties = groupProperties.Current; var action = currentProperties.GetAttribute("action", ""); @@ -451,15 +415,13 @@ internal IEnumerable ProcessGPOXmlFile(string basePath, string gpoD //Next is to determine what group is being updated. var targetGroup = LocalGroupRids.None; - if (!string.IsNullOrWhiteSpace(groupSid)) - { + if (!string.IsNullOrWhiteSpace(groupSid)) { //Use a regex to match and attempt to extract the RID var s = ExtractRid.Match(groupSid); - if (s.Success) - { + if (s.Success) { var rid = int.Parse(s.Groups[1].Value); if (Enum.IsDefined(typeof(LocalGroupRids), rid)) - targetGroup = (LocalGroupRids) rid; + targetGroup = (LocalGroupRids)rid; } } @@ -474,16 +436,14 @@ internal IEnumerable ProcessGPOXmlFile(string basePath, string gpoD var deleteGroups = currentProperties.GetAttribute("deleteAllGroups", "") == "1"; if (deleteUsers) - yield return new GroupAction - { + yield return new GroupAction { Action = GroupActionOperation.DeleteUsers, Target = GroupActionTarget.LocalGroup, TargetRid = targetGroup }; if (deleteGroups) - yield return new GroupAction - { + yield return new GroupAction { Action = GroupActionOperation.DeleteGroups, Target = GroupActionTarget.LocalGroup, TargetRid = targetGroup @@ -491,8 +451,7 @@ internal IEnumerable ProcessGPOXmlFile(string basePath, string gpoD //Get all the actual members being added var members = currentProperties.Select("Members/Member"); - while (members.MoveNext()) - { + while (members.MoveNext()) { var memberAction = members.Current.GetAttribute("action", "") .Equals("ADD", StringComparison.OrdinalIgnoreCase) ? GroupActionOperation.Add @@ -501,55 +460,25 @@ internal IEnumerable ProcessGPOXmlFile(string basePath, string gpoD var memberName = members.Current.GetAttribute("name", ""); var memberSid = members.Current.GetAttribute("sid", ""); - var ga = new GroupAction - { + var ga = new GroupAction { Action = memberAction }; //If we have a memberSid, this is the best case scenario - if (!string.IsNullOrWhiteSpace(memberSid)) - { - var memberType = - _utils.LookupSidType(memberSid, _utils.GetDomainNameFromSid(memberSid)); - ga.Target = GroupActionTarget.LocalGroup; - ga.TargetSid = memberSid; - ga.TargetType = memberType; - ga.TargetRid = targetGroup; - - yield return ga; - continue; - } - - //If we have a memberName, we need to resolve it to a SID/Type - if (!string.IsNullOrWhiteSpace(memberName)) - { - //Check if the name is domain prefixed - if (memberName.Contains("\\")) - { - var s = memberName.Split('\\'); - var name = s[1]; - var domain = s[0]; - - var res = _utils.ResolveAccountName(name, domain); - if (res == null) - { - _log.LogWarning("Failed to resolve member {memberName}", memberName); - continue; - } + if (!string.IsNullOrWhiteSpace(memberSid)) { + if (await _utils.ResolveIDAndType(memberSid, gpoDomain) is (true, var res)) { ga.Target = GroupActionTarget.LocalGroup; - ga.TargetSid = res.ObjectIdentifier; + ga.TargetSid = memberSid; ga.TargetType = res.ObjectType; ga.TargetRid = targetGroup; + yield return ga; } - else - { - var res = _utils.ResolveAccountName(memberName, gpoDomain); - if (res == null) - { - _log.LogWarning("Failed to resolve member {memberName}", memberName); - continue; - } + } + + //If we have a memberName, we need to resolve it to a SID/Type + if (!string.IsNullOrWhiteSpace(memberName)) { + if (await GetSid(memberName, gpoDomain) is (true, var res)) { ga.Target = GroupActionTarget.LocalGroup; ga.TargetSid = res.ObjectIdentifier; ga.TargetType = res.ObjectType; @@ -566,25 +495,21 @@ internal IEnumerable ProcessGPOXmlFile(string basePath, string gpoD /// /// Represents an action from a GPO /// - internal class GroupAction - { + internal class GroupAction { internal GroupActionOperation Action { get; set; } internal GroupActionTarget Target { get; set; } internal string TargetSid { get; set; } internal Label TargetType { get; set; } internal LocalGroupRids TargetRid { get; set; } - public TypedPrincipal ToTypedPrincipal() - { - return new TypedPrincipal - { + public TypedPrincipal ToTypedPrincipal() { + return new TypedPrincipal { ObjectIdentifier = TargetSid, ObjectType = TargetType }; } - public override string ToString() - { + public override string ToString() { return $"{nameof(Action)}: {Action}, {nameof(Target)}: {Target}, {nameof(TargetSid)}: {TargetSid}, {nameof(TargetType)}: {TargetType}, {nameof(TargetRid)}: {TargetRid}"; } @@ -593,30 +518,26 @@ public override string ToString() /// /// Storage for each different group type /// - public class GroupResults - { + public class GroupResults { public List LocalGroups = new(); public List RestrictedMember = new(); public List RestrictedMemberOf = new(); } - internal enum GroupActionOperation - { + internal enum GroupActionOperation { Add, Delete, DeleteUsers, DeleteGroups } - internal enum GroupActionTarget - { + internal enum GroupActionTarget { RestrictedMemberOf, RestrictedMember, LocalGroup } - internal enum LocalGroupRids - { + internal enum LocalGroupRids { None = 0, Administrators = 544, RemoteDesktopUsers = 555, diff --git a/src/CommonLib/Processors/GroupProcessor.cs b/src/CommonLib/Processors/GroupProcessor.cs index 26bad106..17592fdf 100644 --- a/src/CommonLib/Processors/GroupProcessor.cs +++ b/src/CommonLib/Processors/GroupProcessor.cs @@ -11,15 +11,15 @@ namespace SharpHoundCommonLib.Processors public class GroupProcessor { private readonly ILogger _log; - private readonly ILDAPUtils _utils; + private readonly ILdapUtilsNew _utils; - public GroupProcessor(ILDAPUtils utils, ILogger log = null) + public GroupProcessor(ILdapUtilsNew utils, ILogger log = null) { _utils = utils; _log = log ?? Logging.LogProvider.CreateLogger("GroupProc"); } - public IEnumerable ReadGroupMembers(ResolvedSearchResult result, ISearchResultEntry entry) + public IAsyncEnumerable ReadGroupMembers(ResolvedSearchResult result, ISearchResultEntry entry) { var members = entry.GetArrayProperty(LDAPProperties.Members); var name = result.DisplayName; @@ -35,7 +35,7 @@ public IEnumerable ReadGroupMembers(ResolvedSearchResult result, /// /// /// - public IEnumerable ReadGroupMembers(string distinguishedName, string[] members, + public async IAsyncEnumerable ReadGroupMembers(string distinguishedName, string[] members, string objectName = "") { // If our returned array has a length of 0, one of two things is happening @@ -45,21 +45,19 @@ public IEnumerable ReadGroupMembers(string distinguishedName, st { _log.LogTrace("Member property for {ObjectName} is empty, trying range retrieval", objectName); - foreach (var member in _utils.DoRangedRetrieval(distinguishedName, "member")) + await foreach (var result in _utils.RangedRetrieval(distinguishedName, "member")) { - _log.LogTrace("Got member {DN} for {ObjectName} from ranged retrieval", member, objectName); - var res = _utils.ResolveDistinguishedName(member); + if (!result.Success) { + yield break; + } - if (res == null) - yield return new TypedPrincipal - { - ObjectIdentifier = member.ToUpper(), - ObjectType = Label.Base - }; - else - { - if (!Helpers.IsSidFiltered(res.ObjectIdentifier)) - yield return res; + var member = result.Value; + _log.LogTrace("Got member {DN} for {ObjectName} from ranged retrieval", member, objectName); + if (await _utils.LookupDistinguishedName(member) is (true, var res) && !Helpers.IsSidFiltered(res.ObjectIdentifier)) { + yield return res; + } + else { + yield return new TypedPrincipal(member.ToUpper(), Label.Base); } } } @@ -69,18 +67,11 @@ public IEnumerable ReadGroupMembers(string distinguishedName, st foreach (var member in members) { _log.LogTrace("Got member {DN} for {ObjectName}", member, objectName); - var res = _utils.ResolveDistinguishedName(member); - - if (res == null) - yield return new TypedPrincipal - { - ObjectIdentifier = member.ToUpper(), - ObjectType = Label.Base - }; - else - { - if (!Helpers.IsSidFiltered(res.ObjectIdentifier)) - yield return res; + if (await _utils.LookupDistinguishedName(member) is (true, var res) && !Helpers.IsSidFiltered(res.ObjectIdentifier)) { + yield return res; + } + else { + yield return new TypedPrincipal(member.ToUpper(), Label.Base); } } } diff --git a/src/CommonLib/SharpHoundCommonLib.csproj b/src/CommonLib/SharpHoundCommonLib.csproj index c5d9fb04..db4fd810 100644 --- a/src/CommonLib/SharpHoundCommonLib.csproj +++ b/src/CommonLib/SharpHoundCommonLib.csproj @@ -25,6 +25,7 @@ + From 023f227ce60d19bd53863cd144eb862c3796b969 Mon Sep 17 00:00:00 2001 From: rvazarkar Date: Mon, 1 Jul 2024 17:54:29 -0400 Subject: [PATCH 18/68] wip: more processor fixes --- src/CommonLib/Enums/DirectoryPaths.cs | 14 +- src/CommonLib/ILdapUtilsNew.cs | 2 +- src/CommonLib/LdapQueryParameters.cs | 20 ++- src/CommonLib/LdapUtilsNew.cs | 21 ++- src/CommonLib/Processors/ACLProcessor.cs | 2 +- .../Processors/CertAbuseProcessor.cs | 150 +++++++++--------- .../Processors/ContainerProcessor.cs | 2 +- .../Processors/DomainTrustProcessor.cs | 2 +- .../Processors/GPOLocalGroupProcessor.cs | 2 +- src/CommonLib/Processors/GroupProcessor.cs | 2 +- src/CommonLib/Result.cs | 2 +- 11 files changed, 123 insertions(+), 96 deletions(-) diff --git a/src/CommonLib/Enums/DirectoryPaths.cs b/src/CommonLib/Enums/DirectoryPaths.cs index 744639d9..d30e19e4 100644 --- a/src/CommonLib/Enums/DirectoryPaths.cs +++ b/src/CommonLib/Enums/DirectoryPaths.cs @@ -2,13 +2,13 @@ { public class DirectoryPaths { - public const string EnterpriseCALocation = "CN=Enrollment Services,CN=Public Key Services,CN=Services,CN=Configuration"; - public const string RootCALocation = "CN=Certification Authorities,CN=Public Key Services,CN=Services,CN=Configuration"; - public const string AIACALocation = "CN=AIA,CN=Public Key Services,CN=Services,CN=Configuration"; - public const string CertTemplateLocation = "CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration"; - public const string NTAuthStoreLocation = "CN=NTAuthCertificates,CN=Public Key Services,CN=Services,CN=Configuration"; - public const string PKILocation = "CN=Public Key Services,CN=Services,CN=Configuration"; + public const string EnterpriseCALocation = "CN=Enrollment Services,CN=Public Key Services,CN=Services"; + public const string RootCALocation = "CN=Certification Authorities,CN=Public Key Services,CN=Services"; + public const string AIACALocation = "CN=AIA,CN=Public Key Services,CN=Services"; + public const string CertTemplateLocation = "CN=Certificate Templates,CN=Public Key Services,CN=Services"; + public const string NTAuthStoreLocation = "CN=NTAuthCertificates,CN=Public Key Services,CN=Services"; + public const string PKILocation = "CN=Public Key Services,CN=Services"; public const string ConfigLocation = "CN=Configuration"; - public const string OIDContainerLocation = "CN=OID,CN=Public Key Services,CN=Services,CN=Configuration"; + public const string OIDContainerLocation = "CN=OID,CN=Public Key Services,CN=Services"; } } \ No newline at end of file diff --git a/src/CommonLib/ILdapUtilsNew.cs b/src/CommonLib/ILdapUtilsNew.cs index 13dd628c..a9af6284 100644 --- a/src/CommonLib/ILdapUtilsNew.cs +++ b/src/CommonLib/ILdapUtilsNew.cs @@ -35,7 +35,7 @@ IAsyncEnumerable> RangedRetrieval(string distinguishedName, Task<(bool Success, TypedPrincipal Principal)> ResolveAccountName(string name, string domain); Task<(bool Success, string SecurityIdentifier)> ResolveHostToSid(string host, string domain); Task<(bool Success, string[] Sids)> GetGlobalCatalogMatches(string name, string domain); - Task<(bool Success, TypedPrincipal Principal)> ResolveCertTemplateByProperty(string propValue, string propName, string containerDN, string domainName); + Task<(bool Success, TypedPrincipal Principal)> ResolveCertTemplateByProperty(string propValue, string propName, string domainName); ActiveDirectorySecurityDescriptor MakeSecurityDescriptor(); public Task<(bool Success, TypedPrincipal Principal)> ConvertLocalWellKnownPrincipal(SecurityIdentifier sid, diff --git a/src/CommonLib/LdapQueryParameters.cs b/src/CommonLib/LdapQueryParameters.cs index be8785d2..e91ae135 100644 --- a/src/CommonLib/LdapQueryParameters.cs +++ b/src/CommonLib/LdapQueryParameters.cs @@ -6,6 +6,8 @@ namespace SharpHoundCommonLib; public class LdapQueryParameters { + private string _searchBase; + private string _relativeSearchBase; public string LDAPFilter { get; set; } public SearchScope SearchScope { get; set; } = SearchScope.Subtree; public string[] Attributes { get; set; } = Array.Empty(); @@ -13,7 +15,23 @@ public class LdapQueryParameters public bool GlobalCatalog { get; set; } public bool IncludeSecurityDescriptor { get; set; } = false; public bool IncludeDeleted { get; set; } = false; - public string SearchBase { get; set; } + + public string SearchBase { + get => _searchBase; + set { + _relativeSearchBase = null; + _searchBase = value; + } + } + + public string RelativeSearchBase { + get => _relativeSearchBase; + set { + _relativeSearchBase = value; + _searchBase = null; + } + } + public NamingContext NamingContext { get; set; } = NamingContext.Default; public bool ThrowException { get; set; } = false; diff --git a/src/CommonLib/LdapUtilsNew.cs b/src/CommonLib/LdapUtilsNew.cs index 7dfda806..737ca89c 100644 --- a/src/CommonLib/LdapUtilsNew.cs +++ b/src/CommonLib/LdapUtilsNew.cs @@ -710,6 +710,10 @@ private bool CreateSearchRequest(LdapQueryParameters queryParameters, }; connectionWrapper.SaveContext(queryParameters.NamingContext, basePath); + + if (!string.IsNullOrWhiteSpace(queryParameters.RelativeSearchBase)) { + basePath = $"{queryParameters.RelativeSearchBase},{basePath}"; + } } searchRequest = new SearchRequest(basePath, queryParameters.LDAPFilter, queryParameters.SearchScope, @@ -929,7 +933,7 @@ private SearchRequest CreateSearchRequest(string distinguishedName, string ldapF LDAPFilter = new LDAPFilter().AddFilter(CommonFilters.DomainControllers, true).GetFilter() }).FirstAsync(); - if (result.Success) { + if (result.IsSuccess) { var sid = result.Value.GetSid(); if (!string.IsNullOrEmpty(sid)) { domainSid = new SecurityIdentifier(sid).AccountDomainSid.Value; @@ -1197,26 +1201,27 @@ public bool GetDomain(out Domain domain) { } public async Task<(bool Success, TypedPrincipal Principal)> ResolveCertTemplateByProperty(string propertyValue, - string propertyName, string containerDistinguishedName, string domainName) { + string propertyName, string domainName) { var filter = new LDAPFilter().AddCertificateTemplates().AddFilter($"({propertyName}={propertyValue})", true); - var result = await Query(new LdapQueryParameters() { + var result = await Query(new LdapQueryParameters { DomainName = domainName, Attributes = CommonProperties.TypeResolutionProps, SearchScope = SearchScope.OneLevel, - SearchBase = containerDistinguishedName, + NamingContext = NamingContext.Configuration, + RelativeSearchBase = DirectoryPaths.CertTemplateLocation, LDAPFilter = filter.GetFilter(), }).DefaultIfEmpty(null).FirstAsync(); if (result == null) { - _log.LogWarning("Could not find certificate template with {PropertyName}:{PropertyValue} under {Container}", - propertyName, propertyName, containerDistinguishedName); + _log.LogWarning("Could not find certificate template with {PropertyName}:{PropertyValue}", + propertyName, propertyName); return (false, null); } if (!result.IsSuccess) { _log.LogWarning( - "Could not find certificate template with {PropertyName}:{PropertyValue} under {Container}: {Error}", - propertyName, propertyName, containerDistinguishedName, result.Error); + "Could not find certificate template with {PropertyName}:{PropertyValue}: {Error}", + propertyName, propertyName, result.Error); return (false, null); } diff --git a/src/CommonLib/Processors/ACLProcessor.cs b/src/CommonLib/Processors/ACLProcessor.cs index 30ff9c0e..25ff115b 100644 --- a/src/CommonLib/Processors/ACLProcessor.cs +++ b/src/CommonLib/Processors/ACLProcessor.cs @@ -62,7 +62,7 @@ private async Task BuildGuidCache(string domain) { Attributes = new[] {LDAPProperties.SchemaIDGUID, LDAPProperties.Name}, })) { - if (result.Success) { + if (result.IsSuccess) { var name = result.Value.GetProperty(LDAPProperties.Name)?.ToLower(); var guid = result.Value.GetGuid(); if (name == null || guid == null) { diff --git a/src/CommonLib/Processors/CertAbuseProcessor.cs b/src/CommonLib/Processors/CertAbuseProcessor.cs index 35c8bd5b..0fe0db3b 100644 --- a/src/CommonLib/Processors/CertAbuseProcessor.cs +++ b/src/CommonLib/Processors/CertAbuseProcessor.cs @@ -65,8 +65,8 @@ public async Task ProcessRegistryEnrollmentPermissions(str if (ownerSid != null) { - var resolvedOwner = GetRegistryPrincipal(new SecurityIdentifier(ownerSid), computerDomain, computerName, isDomainController, computerObjectId, machineSid); - if (resolvedOwner != null) + if (await GetRegistryPrincipal(new SecurityIdentifier(ownerSid), computerDomain, computerName, + isDomainController, computerObjectId, machineSid) is (true, var resolvedOwner)) { aces.Add(new ACE { PrincipalType = resolvedOwner.ObjectType, @@ -74,6 +74,8 @@ public async Task ProcessRegistryEnrollmentPermissions(str RightName = EdgeNames.Owns, IsInherited = false }); + } + } else { @@ -96,7 +98,9 @@ public async Task ProcessRegistryEnrollmentPermissions(str if (!getDomainSuccess) { } - var resolvedPrincipal = GetRegistryPrincipal(new SecurityIdentifier(principalSid), principalDomain, computerName, isDomainController, computerObjectId, machineSid); + var (resSuccess, resolvedPrincipal) = await GetRegistryPrincipal(new SecurityIdentifier(principalSid), principalDomain, computerName, isDomainController, computerObjectId, machineSid); + if (!resSuccess) + continue; var isInherited = rule.IsInherited(); var cARights = (CertificationAuthorityRights)rule.ActiveDirectoryRights(); @@ -158,13 +162,15 @@ public async Task ProcessEAPermissions(string var isDomainController = await _utils.IsDomainController(computerObjectId, objectDomain); var machineSid = await GetMachineSid(computerName, computerObjectId); - var certTemplatesLocation = _utils.BuildLdapPath(DirectoryPaths.CertTemplateLocation, objectDomain); var descriptor = new RawSecurityDescriptor(regData.Value as byte[], 0); var enrollmentAgentRestrictions = new List(); foreach (var genericAce in descriptor.DiscretionaryAcl) { var ace = (QualifiedAce)genericAce; - enrollmentAgentRestrictions.Add(new EnrollmentAgentRestriction(ace, objectDomain, certTemplatesLocation, this, _utils, computerName, isDomainController, computerObjectId, machineSid)); + if (await CreateEnrollmentAgentRestriction(ace, objectDomain, computerName, isDomainController, + computerObjectId, machineSid) is (true, var restriction)) { + enrollmentAgentRestrictions.Add(restriction); + } } ret.Restrictions = enrollmentAgentRestrictions.ToArray(); @@ -172,17 +178,16 @@ public async Task ProcessEAPermissions(string return ret; } - public (IEnumerable resolvedTemplates, IEnumerable unresolvedTemplates) ProcessCertTemplates(string[] templates, string domainName) + public async Task<(IEnumerable resolvedTemplates, IEnumerable unresolvedTemplates)> ProcessCertTemplates(IEnumerable templates, string domainName) { var resolvedTemplates = new List(); var unresolvedTemplates = new List(); - var certTemplatesLocation = _utils.BuildLdapPath(DirectoryPaths.CertTemplateLocation, domainName); foreach (var templateCN in templates) { - var res = _utils.ResolveCertTemplateByProperty(Encoder.LdapFilterEncode(templateCN), LDAPProperties.CanonicalName, certTemplatesLocation, domainName); - if (res != null) { - resolvedTemplates.Add(res); + var res = await _utils.ResolveCertTemplateByProperty(Encoder.LdapFilterEncode(templateCN), LDAPProperties.CanonicalName, domainName); + if (res.Success) { + resolvedTemplates.Add(res.Principal); } else { unresolvedTemplates.Add(templateCN); } @@ -291,26 +296,23 @@ public BoolRegistryAPIResult RoleSeparationEnabled(string target, string caName) return ret; } - public TypedPrincipal GetRegistryPrincipal(SecurityIdentifier sid, string computerDomain, string computerName, bool isDomainController, string computerObjectId, SecurityIdentifier machineSid) + public async Task<(bool Success, TypedPrincipal Principal)> GetRegistryPrincipal(SecurityIdentifier sid, string computerDomain, string computerName, bool isDomainController, string computerObjectId, SecurityIdentifier machineSid) { _log.LogTrace("Got principal with sid {SID} on computer {ComputerName}", sid.Value, computerName); //Check if our sid is filtered if (Helpers.IsSidFiltered(sid.Value)) - return null; + return (false, default); - if (isDomainController) - { - var result = _utils.ResolveIDAndType(sid.Value, computerDomain); - if (result != null) - return result; + if (isDomainController && + await _utils.ResolveIDAndType(sid.Value, computerDomain) is (true, var resolvedPrincipal)) { + return (true, resolvedPrincipal); } //If we get a local well known principal, we need to convert it using the computer's domain sid - if (_utils.ConvertLocalWellKnownPrincipal(sid, computerObjectId, computerDomain, out var principal)) - { - _log.LogTrace("Got Well Known Principal {SID} on computer {Computer} with type {Type}", principal.ObjectIdentifier, computerName, principal.ObjectType); - return principal; + if (await _utils.ConvertLocalWellKnownPrincipal(sid, computerObjectId, computerDomain) is + (true, var principal)) { + return (true, principal); } //If the security identifier starts with the machine sid, we need to resolve it as a local principal @@ -319,20 +321,14 @@ public TypedPrincipal GetRegistryPrincipal(SecurityIdentifier sid, string comput _log.LogTrace("Got local principal {sid} on computer {Computer}", sid.Value, computerName); // Set label to be local group. It could be a local user or alias but I'm not sure how we can confirm. Besides, it will not have any effect on the end result - var objectType = Label.LocalGroup; - // The local group sid is computer machine sid - group rid. var groupRid = sid.Rid(); var newSid = $"{computerObjectId}-{groupRid}"; - return (new TypedPrincipal - { - ObjectIdentifier = newSid, - ObjectType = objectType - }); + return (true, new TypedPrincipal(newSid, Label.LocalGroup)); } //If we get here, we most likely have a domain principal. Do a lookup - return _utils.ResolveIDAndType(sid.Value, computerDomain); + return await _utils.ResolveIDAndType(sid.Value, computerDomain); } private async Task GetMachineSid(string computerName, string computerObjectId) @@ -383,6 +379,58 @@ await SendComputerStatus(new CSVComputerStatus return machineSid; } + private async Task<(bool success, EnrollmentAgentRestriction restriction)> CreateEnrollmentAgentRestriction(QualifiedAce ace, string computerDomain, string computerName, bool isDomainController, string computerObjectId, SecurityIdentifier machineSid) { + var targets = new List(); + var index = 0; + + var accessType = ace.AceType.ToString(); + var agent = await GetRegistryPrincipal(ace.SecurityIdentifier, computerDomain, computerName, isDomainController, + computerObjectId, machineSid); + + var opaque = ace.GetOpaque(); + var sidCount = BitConverter.ToUInt32(opaque, 0); + index += 4; + + for (var i = 0; i < sidCount; i++) { + var sid = new SecurityIdentifier(opaque, index); + if (await GetRegistryPrincipal(sid, computerDomain, computerName, isDomainController, computerObjectId, + machineSid) is (true, var regPrincipal)) { + targets.Add(regPrincipal); + } + + index += sid.BinaryLength; + } + + var finalTargets = targets.ToArray(); + var allTemplates = index >= opaque.Length; + if (index < opaque.Length) { + var template = Encoding.Unicode.GetString(opaque, index, opaque.Length - index - 2).Replace("\u0000", string.Empty); + if (await _utils.ResolveCertTemplateByProperty(Encoder.LdapFilterEncode(template), LDAPProperties.CanonicalName, computerDomain) is (true, var resolvedTemplate)) { + return (true, new EnrollmentAgentRestriction { + Template = resolvedTemplate, + Agent = agent.Principal, + AllTemplates = allTemplates, + AccessType = accessType, + Targets = finalTargets + }); + } + + if (await _utils.ResolveCertTemplateByProperty( + Encoder.LdapFilterEncode(template), LDAPProperties.CertTemplateOID, computerDomain) is + (true, var resolvedOidTemplate)) { + return (true, new EnrollmentAgentRestriction { + Template = resolvedOidTemplate, + Agent = agent.Principal, + AllTemplates = allTemplates, + AccessType = accessType, + Targets = finalTargets + }); + } + } + + return (false, default); + } + public virtual SharpHoundRPC.Result OpenSamServer(string computerName) { var result = SAMServer.OpenServer(computerName); @@ -403,50 +451,6 @@ private async Task SendComputerStatus(CSVComputerStatus status) public class EnrollmentAgentRestriction { - public EnrollmentAgentRestriction(QualifiedAce ace, string computerDomain, string certTemplatesLocation, CertAbuseProcessor certAbuseProcessor, ILdapUtilsNew utils, string computerName, bool isDomainController, string computerObjectId, SecurityIdentifier machineSid) - { - var targets = new List(); - var index = 0; - - // Access type (Allow/Deny) - AccessType = ace.AceType.ToString(); - - // Agent - Agent = certAbuseProcessor.GetRegistryPrincipal(ace.SecurityIdentifier, computerDomain, computerName, isDomainController, computerObjectId, machineSid); - - // Targets - var opaque = ace.GetOpaque(); - var sidCount = BitConverter.ToUInt32(opaque, 0); - index += 4; - for (var i = 0; i < sidCount; i++) - { - var sid = new SecurityIdentifier(opaque, index); - targets.Add(certAbuseProcessor.GetRegistryPrincipal(ace.SecurityIdentifier, computerDomain, computerName, isDomainController, computerObjectId, machineSid)); - index += sid.BinaryLength; - } - Targets = targets.ToArray(); - - // Template - if (index < opaque.Length) - { - AllTemplates = false; - var template = Encoding.Unicode.GetString(opaque, index, opaque.Length - index - 2).Replace("\u0000", string.Empty); - - // Attempt to resolve the cert template by CN - Template = utils.ResolveCertTemplateByProperty(Encoder.LdapFilterEncode(template), LDAPProperties.CanonicalName, certTemplatesLocation, computerDomain); - - // Attempt to resolve the cert template by OID - if (Template == null) - { - Template = utils.ResolveCertTemplateByProperty(template, LDAPProperties.CertTemplateOID, certTemplatesLocation, computerDomain); - } - } - else - { - AllTemplates = true; - } - } - public string AccessType { get; set; } public TypedPrincipal Agent { get; set; } public TypedPrincipal[] Targets { get; set; } diff --git a/src/CommonLib/Processors/ContainerProcessor.cs b/src/CommonLib/Processors/ContainerProcessor.cs index 37fac3c2..9b7b59ea 100644 --- a/src/CommonLib/Processors/ContainerProcessor.cs +++ b/src/CommonLib/Processors/ContainerProcessor.cs @@ -96,7 +96,7 @@ public async IAsyncEnumerable GetContainerChildObjects(string di LDAPFilter = filter.GetFilter(), SearchBase = distinguishedName })) { - if (!childEntryResult.Success) { + if (!childEntryResult.IsSuccess) { _log.LogWarning("Error while getting container child objects for {DistinguishedName}: {Reason}", distinguishedName, childEntryResult.Error); yield break; } diff --git a/src/CommonLib/Processors/DomainTrustProcessor.cs b/src/CommonLib/Processors/DomainTrustProcessor.cs index 23e586ef..edaf42e7 100644 --- a/src/CommonLib/Processors/DomainTrustProcessor.cs +++ b/src/CommonLib/Processors/DomainTrustProcessor.cs @@ -32,7 +32,7 @@ public async IAsyncEnumerable EnumerateDomainTrusts(string domain) DomainName = domain })) { - if (!result.Success) { + if (!result.IsSuccess) { yield break; } diff --git a/src/CommonLib/Processors/GPOLocalGroupProcessor.cs b/src/CommonLib/Processors/GPOLocalGroupProcessor.cs index 5f86b8b4..3b7650ca 100644 --- a/src/CommonLib/Processors/GPOLocalGroupProcessor.cs +++ b/src/CommonLib/Processors/GPOLocalGroupProcessor.cs @@ -128,7 +128,7 @@ public async Task ReadGPOLocalGroups(string gpLink, string SearchBase = linkDn }).DefaultIfEmpty(null).FirstOrDefaultAsync(); - if (result is not { Success: true }) { + if (result is not { IsSuccess: true }) { continue; } diff --git a/src/CommonLib/Processors/GroupProcessor.cs b/src/CommonLib/Processors/GroupProcessor.cs index 17592fdf..9414e103 100644 --- a/src/CommonLib/Processors/GroupProcessor.cs +++ b/src/CommonLib/Processors/GroupProcessor.cs @@ -47,7 +47,7 @@ public async IAsyncEnumerable ReadGroupMembers(string distinguis objectName); await foreach (var result in _utils.RangedRetrieval(distinguishedName, "member")) { - if (!result.Success) { + if (!result.IsSuccess) { yield break; } diff --git a/src/CommonLib/Result.cs b/src/CommonLib/Result.cs index b0ac9ba1..5f29c304 100644 --- a/src/CommonLib/Result.cs +++ b/src/CommonLib/Result.cs @@ -24,7 +24,7 @@ public class Result { public string Error { get; set; } public bool IsSuccess => Error == null && Success; - public bool Success { get; set; } + private bool Success { get; set; } protected Result(bool success, string error) { Success = success; From e57db761395cb830ea3c86120115de983da523f6 Mon Sep 17 00:00:00 2001 From: rvazarkar Date: Mon, 1 Jul 2024 18:21:19 -0400 Subject: [PATCH 19/68] wip: fix block scoping --- src/CommonLib/ConnectionPoolManager.cs | 186 +- src/CommonLib/Enums/LdapFailureReason.cs | 22 +- src/CommonLib/Enums/NamingContext.cs | 14 +- src/CommonLib/ILdapUtilsNew.cs | 72 +- src/CommonLib/LdapConnectionPool.cs | 558 ++--- src/CommonLib/LdapConnectionWrapperNew.cs | 44 +- src/CommonLib/LdapQueryParameters.cs | 60 +- src/CommonLib/LdapQuerySetupResult.cs | 16 +- src/CommonLib/LdapResult.cs | 26 +- src/CommonLib/LdapUtilsNew.cs | 2156 +++++++++--------- src/CommonLib/Result.cs | 58 +- src/CommonLib/SearchResultEntryWrapperNew.cs | 44 +- 12 files changed, 1628 insertions(+), 1628 deletions(-) diff --git a/src/CommonLib/ConnectionPoolManager.cs b/src/CommonLib/ConnectionPoolManager.cs index aadb3585..7d51c502 100644 --- a/src/CommonLib/ConnectionPoolManager.cs +++ b/src/CommonLib/ConnectionPoolManager.cs @@ -6,125 +6,125 @@ using Microsoft.Extensions.Logging; using SharpHoundCommonLib.Processors; -namespace SharpHoundCommonLib; - -public class ConnectionPoolManager : IDisposable{ - private readonly ConcurrentDictionary _pools = new(); - private readonly LDAPConfig _ldapConfig; - private readonly string[] _translateNames = { "Administrator", "admin" }; - private readonly ConcurrentDictionary _resolvedIdentifiers = new(StringComparer.OrdinalIgnoreCase); - private readonly ILogger _log; - private readonly PortScanner _portScanner; - - public ConnectionPoolManager(LDAPConfig config, ILogger log = null, PortScanner scanner = null) { - _ldapConfig = config; - _log = log ?? Logging.LogProvider.CreateLogger("ConnectionPoolManager"); - _portScanner = scanner ?? new PortScanner(); - } - - public void ReleaseConnection(LdapConnectionWrapperNew connectionWrapper, bool connectionFaulted = false) { - //I dont think this is possible, but at least account for it - if (!_pools.TryGetValue(connectionWrapper.PoolIdentifier, out var pool)) { - connectionWrapper.Connection.Dispose(); - return; +namespace SharpHoundCommonLib { + public class ConnectionPoolManager : IDisposable{ + private readonly ConcurrentDictionary _pools = new(); + private readonly LDAPConfig _ldapConfig; + private readonly string[] _translateNames = { "Administrator", "admin" }; + private readonly ConcurrentDictionary _resolvedIdentifiers = new(StringComparer.OrdinalIgnoreCase); + private readonly ILogger _log; + private readonly PortScanner _portScanner; + + public ConnectionPoolManager(LDAPConfig config, ILogger log = null, PortScanner scanner = null) { + _ldapConfig = config; + _log = log ?? Logging.LogProvider.CreateLogger("ConnectionPoolManager"); + _portScanner = scanner ?? new PortScanner(); } + + public void ReleaseConnection(LdapConnectionWrapperNew connectionWrapper, bool connectionFaulted = false) { + //I dont think this is possible, but at least account for it + if (!_pools.TryGetValue(connectionWrapper.PoolIdentifier, out var pool)) { + connectionWrapper.Connection.Dispose(); + return; + } - pool.ReleaseConnection(connectionWrapper, connectionFaulted); - } + pool.ReleaseConnection(connectionWrapper, connectionFaulted); + } - public async Task<(bool Success, LdapConnectionWrapperNew ConnectionWrapper, string Message)> GetLdapConnection( - string identifier, bool globalCatalog) { - var resolved = ResolveIdentifier(identifier); + public async Task<(bool Success, LdapConnectionWrapperNew ConnectionWrapper, string Message)> GetLdapConnection( + string identifier, bool globalCatalog) { + var resolved = ResolveIdentifier(identifier); - if (!_pools.TryGetValue(identifier, out var pool)) { - pool = new LdapConnectionPool(resolved, _ldapConfig,scanner: _portScanner); - _pools.TryAdd(identifier, pool); - } + if (!_pools.TryGetValue(identifier, out var pool)) { + pool = new LdapConnectionPool(resolved, _ldapConfig,scanner: _portScanner); + _pools.TryAdd(identifier, pool); + } - if (globalCatalog) { - return await pool.GetGlobalCatalogConnectionAsync(); + if (globalCatalog) { + return await pool.GetGlobalCatalogConnectionAsync(); + } + return await pool.GetConnectionAsync(); } - return await pool.GetConnectionAsync(); - } - public async Task<(bool Success, LdapConnectionWrapperNew connectionWrapper, string Message)> GetLdapConnectionForServer( - string identifier, string server, bool globalCatalog) { - var resolved = ResolveIdentifier(identifier); + public async Task<(bool Success, LdapConnectionWrapperNew connectionWrapper, string Message)> GetLdapConnectionForServer( + string identifier, string server, bool globalCatalog) { + var resolved = ResolveIdentifier(identifier); - if (!_pools.TryGetValue(identifier, out var pool)) { - pool = new LdapConnectionPool(resolved, _ldapConfig,scanner: _portScanner); - _pools.TryAdd(identifier, pool); - } + if (!_pools.TryGetValue(identifier, out var pool)) { + pool = new LdapConnectionPool(resolved, _ldapConfig,scanner: _portScanner); + _pools.TryAdd(identifier, pool); + } - return await pool.GetConnectionForSpecificServerAsync(server, globalCatalog); - } - - private string ResolveIdentifier(string identifier) { - if (_resolvedIdentifiers.TryGetValue(identifier, out var resolved)) { - return resolved; + return await pool.GetConnectionForSpecificServerAsync(server, globalCatalog); } + private string ResolveIdentifier(string identifier) { + if (_resolvedIdentifiers.TryGetValue(identifier, out var resolved)) { + return resolved; + } - if (GetDomainSidFromDomainName(identifier, out var sid)) { - _log.LogDebug("Resolved identifier {Identifier} to {Resolved}", identifier, sid); - _resolvedIdentifiers.TryAdd(identifier, sid); - return sid; - } - - return identifier; - } - - private bool GetDomainSidFromDomainName(string domainName, out string domainSid) { - if (Cache.GetDomainSidMapping(domainName, out domainSid)) return true; - try { - var entry = new DirectoryEntry($"LDAP://{domainName}"); - //Force load objectsid into the object cache - entry.RefreshCache(new[] { "objectSid" }); - var sid = entry.GetSid(); - if (sid != null) { - Cache.AddDomainSidMapping(domainName, sid); - domainSid = sid; - return true; + if (GetDomainSidFromDomainName(identifier, out var sid)) { + _log.LogDebug("Resolved identifier {Identifier} to {Resolved}", identifier, sid); + _resolvedIdentifiers.TryAdd(identifier, sid); + return sid; } + + return identifier; } - catch { - //we expect this to fail sometimes - } + + private bool GetDomainSidFromDomainName(string domainName, out string domainSid) { + if (Cache.GetDomainSidMapping(domainName, out domainSid)) return true; - if (LdapUtilsNew.GetDomain(domainName, _ldapConfig, out var domainObject)) try { - domainSid = domainObject.GetDirectoryEntry().GetSid(); - if (domainSid != null) { - Cache.AddDomainSidMapping(domainName, domainSid); + var entry = new DirectoryEntry($"LDAP://{domainName}"); + //Force load objectsid into the object cache + entry.RefreshCache(new[] { "objectSid" }); + var sid = entry.GetSid(); + if (sid != null) { + Cache.AddDomainSidMapping(domainName, sid); + domainSid = sid; return true; } } catch { - //we expect this to fail sometimes (not sure why, but better safe than sorry) + //we expect this to fail sometimes } - foreach (var name in _translateNames) - try { - var account = new NTAccount(domainName, name); - var sid = (SecurityIdentifier)account.Translate(typeof(SecurityIdentifier)); - domainSid = sid.AccountDomainSid.ToString(); - Cache.AddDomainSidMapping(domainName, domainSid); - return true; - } - catch { - //We expect this to fail if the username doesn't exist in the domain - } + if (LdapUtilsNew.GetDomain(domainName, _ldapConfig, out var domainObject)) + try { + domainSid = domainObject.GetDirectoryEntry().GetSid(); + if (domainSid != null) { + Cache.AddDomainSidMapping(domainName, domainSid); + return true; + } + } + catch { + //we expect this to fail sometimes (not sure why, but better safe than sorry) + } - return false; - } + foreach (var name in _translateNames) + try { + var account = new NTAccount(domainName, name); + var sid = (SecurityIdentifier)account.Translate(typeof(SecurityIdentifier)); + domainSid = sid.AccountDomainSid.ToString(); + Cache.AddDomainSidMapping(domainName, domainSid); + return true; + } + catch { + //We expect this to fail if the username doesn't exist in the domain + } - public void Dispose() { - foreach (var kv in _pools) - { - kv.Value.Dispose(); + return false; } + + public void Dispose() { + foreach (var kv in _pools) + { + kv.Value.Dispose(); + } - _pools.Clear(); + _pools.Clear(); + } } } \ No newline at end of file diff --git a/src/CommonLib/Enums/LdapFailureReason.cs b/src/CommonLib/Enums/LdapFailureReason.cs index 4986f9d6..434e3a69 100644 --- a/src/CommonLib/Enums/LdapFailureReason.cs +++ b/src/CommonLib/Enums/LdapFailureReason.cs @@ -1,12 +1,12 @@ -namespace SharpHoundCommonLib.Enums; - -public enum LdapFailureReason -{ - None, - NoData, - FailedBind, - FailedRequest, - FailedAuthentication, - AuthenticationException, - Unknown +namespace SharpHoundCommonLib.Enums { + public enum LdapFailureReason + { + None, + NoData, + FailedBind, + FailedRequest, + FailedAuthentication, + AuthenticationException, + Unknown + } } \ No newline at end of file diff --git a/src/CommonLib/Enums/NamingContext.cs b/src/CommonLib/Enums/NamingContext.cs index 7a215e2c..29943cdb 100644 --- a/src/CommonLib/Enums/NamingContext.cs +++ b/src/CommonLib/Enums/NamingContext.cs @@ -1,8 +1,8 @@ -namespace SharpHoundCommonLib.Enums; - -public enum NamingContext -{ - Default, - Configuration, - Schema, +namespace SharpHoundCommonLib.Enums { + public enum NamingContext + { + Default, + Configuration, + Schema, + } } \ No newline at end of file diff --git a/src/CommonLib/ILdapUtilsNew.cs b/src/CommonLib/ILdapUtilsNew.cs index a9af6284..de90c441 100644 --- a/src/CommonLib/ILdapUtilsNew.cs +++ b/src/CommonLib/ILdapUtilsNew.cs @@ -7,40 +7,40 @@ using SharpHoundCommonLib.Enums; using SharpHoundCommonLib.OutputTypes; -namespace SharpHoundCommonLib; - -public interface ILdapUtilsNew { - IAsyncEnumerable> Query(LdapQueryParameters queryParameters, - CancellationToken cancellationToken = new()); - - IAsyncEnumerable> PagedQuery(LdapQueryParameters queryParameters, - CancellationToken cancellationToken = new()); - - IAsyncEnumerable> RangedRetrieval(string distinguishedName, - string attributeName, CancellationToken cancellationToken = new()); - - Task<(bool Success, TypedPrincipal Principal)> ResolveIDAndType(SecurityIdentifier securityIdentifier, - string objectDomain); - - Task<(bool Success, TypedPrincipal Principal)> - ResolveIDAndType(string identifier, string objectDomain); - - Task<(bool Success, TypedPrincipal WellKnownPrincipal)> GetWellKnownPrincipal( - string securityIdentifier, string objectDomain); - - Task<(bool Success, string DomainName)> GetDomainNameFromSid(string sid); - Task<(bool Success, string DomainSid)> GetDomainSidFromDomainName(string domainName); - bool GetDomain(string domainName, out System.DirectoryServices.ActiveDirectory.Domain domain); - bool GetDomain(out System.DirectoryServices.ActiveDirectory.Domain domain); - Task<(bool Success, TypedPrincipal Principal)> ResolveAccountName(string name, string domain); - Task<(bool Success, string SecurityIdentifier)> ResolveHostToSid(string host, string domain); - Task<(bool Success, string[] Sids)> GetGlobalCatalogMatches(string name, string domain); - Task<(bool Success, TypedPrincipal Principal)> ResolveCertTemplateByProperty(string propValue, string propName, string domainName); - ActiveDirectorySecurityDescriptor MakeSecurityDescriptor(); - - public Task<(bool Success, TypedPrincipal Principal)> ConvertLocalWellKnownPrincipal(SecurityIdentifier sid, - string computerDomainSid, string computerDomain); - - public Task IsDomainController(string computerObjectId, string domainName); - public Task<(bool Success, TypedPrincipal Principal)> LookupDistinguishedName(string distinguishedName); +namespace SharpHoundCommonLib { + public interface ILdapUtilsNew { + IAsyncEnumerable> Query(LdapQueryParameters queryParameters, + CancellationToken cancellationToken = new()); + + IAsyncEnumerable> PagedQuery(LdapQueryParameters queryParameters, + CancellationToken cancellationToken = new()); + + IAsyncEnumerable> RangedRetrieval(string distinguishedName, + string attributeName, CancellationToken cancellationToken = new()); + + Task<(bool Success, TypedPrincipal Principal)> ResolveIDAndType(SecurityIdentifier securityIdentifier, + string objectDomain); + + Task<(bool Success, TypedPrincipal Principal)> + ResolveIDAndType(string identifier, string objectDomain); + + Task<(bool Success, TypedPrincipal WellKnownPrincipal)> GetWellKnownPrincipal( + string securityIdentifier, string objectDomain); + + Task<(bool Success, string DomainName)> GetDomainNameFromSid(string sid); + Task<(bool Success, string DomainSid)> GetDomainSidFromDomainName(string domainName); + bool GetDomain(string domainName, out System.DirectoryServices.ActiveDirectory.Domain domain); + bool GetDomain(out System.DirectoryServices.ActiveDirectory.Domain domain); + Task<(bool Success, TypedPrincipal Principal)> ResolveAccountName(string name, string domain); + Task<(bool Success, string SecurityIdentifier)> ResolveHostToSid(string host, string domain); + Task<(bool Success, string[] Sids)> GetGlobalCatalogMatches(string name, string domain); + Task<(bool Success, TypedPrincipal Principal)> ResolveCertTemplateByProperty(string propValue, string propName, string domainName); + ActiveDirectorySecurityDescriptor MakeSecurityDescriptor(); + + public Task<(bool Success, TypedPrincipal Principal)> ConvertLocalWellKnownPrincipal(SecurityIdentifier sid, + string computerDomainSid, string computerDomain); + + public Task IsDomainController(string computerObjectId, string domainName); + public Task<(bool Success, TypedPrincipal Principal)> LookupDistinguishedName(string distinguishedName); + } } \ No newline at end of file diff --git a/src/CommonLib/LdapConnectionPool.cs b/src/CommonLib/LdapConnectionPool.cs index 1f71c944..dab33d32 100644 --- a/src/CommonLib/LdapConnectionPool.cs +++ b/src/CommonLib/LdapConnectionPool.cs @@ -12,346 +12,346 @@ using SharpHoundCommonLib.Processors; using SharpHoundRPC.NetAPINative; -namespace SharpHoundCommonLib; - -public class LdapConnectionPool : IDisposable{ - private readonly ConcurrentBag _connections; - private readonly ConcurrentBag _globalCatalogConnection; - private static readonly ConcurrentDictionary DomainCache = new(); - private readonly SemaphoreSlim _semaphore; - private readonly string _identifier; - private readonly LDAPConfig _ldapConfig; - private readonly ILogger _log; - private readonly PortScanner _portScanner; - private readonly NativeMethods _nativeMethods; - - public LdapConnectionPool(string identifier, LDAPConfig config, int maxConnections = 10, PortScanner scanner = null, NativeMethods nativeMethods = null, ILogger log = null) { - _connections = new ConcurrentBag(); - _globalCatalogConnection = new ConcurrentBag(); - _semaphore = new SemaphoreSlim(maxConnections, maxConnections); - _identifier = identifier; - _ldapConfig = config; - _log = log ?? Logging.LogProvider.CreateLogger("LdapConnectionPool"); - _portScanner = scanner ?? new PortScanner(); - _nativeMethods = nativeMethods ?? new NativeMethods(); - } - - public async Task<(bool Success, LdapConnectionWrapperNew ConnectionWrapper, string Message)> GetConnectionAsync() { - await _semaphore.WaitAsync(); - if (!_connections.TryTake(out var connectionWrapper)) { - var (success, connection, message) = await CreateNewConnection(); - if (!success) { - //If we didn't get a connection, immediately release the semaphore so we don't have hanging ones - _semaphore.Release(); - return (false, null, message); - } - - connectionWrapper = connection; +namespace SharpHoundCommonLib { + public class LdapConnectionPool : IDisposable{ + private readonly ConcurrentBag _connections; + private readonly ConcurrentBag _globalCatalogConnection; + private static readonly ConcurrentDictionary DomainCache = new(); + private readonly SemaphoreSlim _semaphore; + private readonly string _identifier; + private readonly LDAPConfig _ldapConfig; + private readonly ILogger _log; + private readonly PortScanner _portScanner; + private readonly NativeMethods _nativeMethods; + + public LdapConnectionPool(string identifier, LDAPConfig config, int maxConnections = 10, PortScanner scanner = null, NativeMethods nativeMethods = null, ILogger log = null) { + _connections = new ConcurrentBag(); + _globalCatalogConnection = new ConcurrentBag(); + _semaphore = new SemaphoreSlim(maxConnections, maxConnections); + _identifier = identifier; + _ldapConfig = config; + _log = log ?? Logging.LogProvider.CreateLogger("LdapConnectionPool"); + _portScanner = scanner ?? new PortScanner(); + _nativeMethods = nativeMethods ?? new NativeMethods(); } - return (true, connectionWrapper, null); - } - - public async Task<(bool Success, LdapConnectionWrapperNew connectionWrapper, string Message)> - GetConnectionForSpecificServerAsync(string server, bool globalCatalog) { - await _semaphore.WaitAsync(); + public async Task<(bool Success, LdapConnectionWrapperNew ConnectionWrapper, string Message)> GetConnectionAsync() { + await _semaphore.WaitAsync(); + if (!_connections.TryTake(out var connectionWrapper)) { + var (success, connection, message) = await CreateNewConnection(); + if (!success) { + //If we didn't get a connection, immediately release the semaphore so we don't have hanging ones + _semaphore.Release(); + return (false, null, message); + } + + connectionWrapper = connection; + } - var result= CreateNewConnectionForServer(server, globalCatalog); - if (!result.Success) { - //If we didn't get a connection, immediately release the semaphore so we don't have hanging ones - _semaphore.Release(); + return (true, connectionWrapper, null); } - return result; - } + public async Task<(bool Success, LdapConnectionWrapperNew connectionWrapper, string Message)> + GetConnectionForSpecificServerAsync(string server, bool globalCatalog) { + await _semaphore.WaitAsync(); - public async Task<(bool Success, LdapConnectionWrapperNew ConnectionWrapper, string Message)> GetGlobalCatalogConnectionAsync() { - await _semaphore.WaitAsync(); - if (!_globalCatalogConnection.TryTake(out var connectionWrapper)) { - var (success, connection, message) = await CreateNewConnection(true); - if (!success) { + var result= CreateNewConnectionForServer(server, globalCatalog); + if (!result.Success) { //If we didn't get a connection, immediately release the semaphore so we don't have hanging ones _semaphore.Release(); - return (false, null, message); } - connectionWrapper = connection; + return result; } - return (true, connectionWrapper, null); - } + public async Task<(bool Success, LdapConnectionWrapperNew ConnectionWrapper, string Message)> GetGlobalCatalogConnectionAsync() { + await _semaphore.WaitAsync(); + if (!_globalCatalogConnection.TryTake(out var connectionWrapper)) { + var (success, connection, message) = await CreateNewConnection(true); + if (!success) { + //If we didn't get a connection, immediately release the semaphore so we don't have hanging ones + _semaphore.Release(); + return (false, null, message); + } + + connectionWrapper = connection; + } - public void ReleaseConnection(LdapConnectionWrapperNew connectionWrapper, bool connectionFaulted = false) { - if (!connectionFaulted) { - if (connectionWrapper.GlobalCatalog) { - _globalCatalogConnection.Add(connectionWrapper); + return (true, connectionWrapper, null); + } + + public void ReleaseConnection(LdapConnectionWrapperNew connectionWrapper, bool connectionFaulted = false) { + if (!connectionFaulted) { + if (connectionWrapper.GlobalCatalog) { + _globalCatalogConnection.Add(connectionWrapper); + } + else { + _connections.Add(connectionWrapper); + } } else { - _connections.Add(connectionWrapper); + connectionWrapper.Connection.Dispose(); } - } - else { - connectionWrapper.Connection.Dispose(); - } - _semaphore.Release(); - } + _semaphore.Release(); + } - public void Dispose() { - while (_connections.TryTake(out var wrapper)) { - wrapper.Connection.Dispose(); + public void Dispose() { + while (_connections.TryTake(out var wrapper)) { + wrapper.Connection.Dispose(); + } } - } - private async Task<(bool Success, LdapConnectionWrapperNew Connection, string Message)> CreateNewConnection(bool globalCatalog = false) { - if (!string.IsNullOrWhiteSpace(_ldapConfig.Server)) { - return CreateNewConnectionForServer(_ldapConfig.Server, globalCatalog); - } + private async Task<(bool Success, LdapConnectionWrapperNew Connection, string Message)> CreateNewConnection(bool globalCatalog = false) { + if (!string.IsNullOrWhiteSpace(_ldapConfig.Server)) { + return CreateNewConnectionForServer(_ldapConfig.Server, globalCatalog); + } - if (CreateLdapConnection(_identifier.ToUpper().Trim(), globalCatalog, out var connectionWrapper)) { - _log.LogDebug("Successfully created ldap connection for domain: {Domain} using strategy 1", _identifier); - return (true, connectionWrapper, ""); - } + if (CreateLdapConnection(_identifier.ToUpper().Trim(), globalCatalog, out var connectionWrapper)) { + _log.LogDebug("Successfully created ldap connection for domain: {Domain} using strategy 1", _identifier); + return (true, connectionWrapper, ""); + } - string tempDomainName; + string tempDomainName; + + var dsGetDcNameResult = _nativeMethods.CallDsGetDcName(null, _identifier, + (uint)(NetAPIEnums.DSGETDCNAME_FLAGS.DS_FORCE_REDISCOVERY | + NetAPIEnums.DSGETDCNAME_FLAGS.DS_RETURN_DNS_NAME | + NetAPIEnums.DSGETDCNAME_FLAGS.DS_DIRECTORY_SERVICE_REQUIRED)); + if (dsGetDcNameResult.IsSuccess) { + tempDomainName = dsGetDcNameResult.Value.DomainName; + + if (!tempDomainName.Equals(_identifier, StringComparison.OrdinalIgnoreCase) && + CreateLdapConnection(tempDomainName, globalCatalog, out connectionWrapper)) { + _log.LogDebug( + "Successfully created ldap connection for domain: {Domain} using strategy 2 with name {NewName}", + _identifier, tempDomainName); + return (true, connectionWrapper, ""); + } + + var server = dsGetDcNameResult.Value.DomainControllerName.TrimStart('\\'); + + var result = + await CreateLDAPConnectionWithPortCheck(server, globalCatalog); + if (result.success) { + _log.LogDebug( + "Successfully created ldap connection for domain: {Domain} using strategy 3 to server {Server}", + _identifier, server); + return (true, result.connection, ""); + } + } + + if (!LdapUtilsNew.GetDomain(_identifier, _ldapConfig, out var domainObject) || domainObject.Name == null) { + //If we don't get a result here, we effectively have no other ways to resolve this domain, so we'll just have to exit out + _log.LogDebug( + "Could not get domain object from GetDomain, unable to create ldap connection for domain {Domain}", + _identifier); + return (false, null, "Unable to get domain object for further strategies"); + } + tempDomainName = domainObject.Name.ToUpper().Trim(); - var dsGetDcNameResult = _nativeMethods.CallDsGetDcName(null, _identifier, - (uint)(NetAPIEnums.DSGETDCNAME_FLAGS.DS_FORCE_REDISCOVERY | - NetAPIEnums.DSGETDCNAME_FLAGS.DS_RETURN_DNS_NAME | - NetAPIEnums.DSGETDCNAME_FLAGS.DS_DIRECTORY_SERVICE_REQUIRED)); - if (dsGetDcNameResult.IsSuccess) { - tempDomainName = dsGetDcNameResult.Value.DomainName; - if (!tempDomainName.Equals(_identifier, StringComparison.OrdinalIgnoreCase) && CreateLdapConnection(tempDomainName, globalCatalog, out connectionWrapper)) { _log.LogDebug( - "Successfully created ldap connection for domain: {Domain} using strategy 2 with name {NewName}", + "Successfully created ldap connection for domain: {Domain} using strategy 4 with name {NewName}", _identifier, tempDomainName); return (true, connectionWrapper, ""); } - - var server = dsGetDcNameResult.Value.DomainControllerName.TrimStart('\\'); - - var result = - await CreateLDAPConnectionWithPortCheck(server, globalCatalog); - if (result.success) { - _log.LogDebug( - "Successfully created ldap connection for domain: {Domain} using strategy 3 to server {Server}", - _identifier, server); - return (true, result.connection, ""); - } - } - if (!LdapUtilsNew.GetDomain(_identifier, _ldapConfig, out var domainObject) || domainObject.Name == null) { - //If we don't get a result here, we effectively have no other ways to resolve this domain, so we'll just have to exit out - _log.LogDebug( - "Could not get domain object from GetDomain, unable to create ldap connection for domain {Domain}", - _identifier); - return (false, null, "Unable to get domain object for further strategies"); - } - tempDomainName = domainObject.Name.ToUpper().Trim(); - - if (!tempDomainName.Equals(_identifier, StringComparison.OrdinalIgnoreCase) && - CreateLdapConnection(tempDomainName, globalCatalog, out connectionWrapper)) { - _log.LogDebug( - "Successfully created ldap connection for domain: {Domain} using strategy 4 with name {NewName}", - _identifier, tempDomainName); - return (true, connectionWrapper, ""); - } - - var primaryDomainController = domainObject.PdcRoleOwner.Name; - var portConnectionResult = - await CreateLDAPConnectionWithPortCheck(primaryDomainController, globalCatalog); - if (portConnectionResult.success) { - _log.LogDebug( - "Successfully created ldap connection for domain: {Domain} using strategy 5 with to pdc {Server}", - _identifier, primaryDomainController); - return (true, connectionWrapper, ""); - } - - foreach (DomainController dc in domainObject.DomainControllers) { - portConnectionResult = - await CreateLDAPConnectionWithPortCheck(dc.Name, globalCatalog); + var primaryDomainController = domainObject.PdcRoleOwner.Name; + var portConnectionResult = + await CreateLDAPConnectionWithPortCheck(primaryDomainController, globalCatalog); if (portConnectionResult.success) { _log.LogDebug( - "Successfully created ldap connection for domain: {Domain} using strategy 6 with to pdc {Server}", + "Successfully created ldap connection for domain: {Domain} using strategy 5 with to pdc {Server}", _identifier, primaryDomainController); return (true, connectionWrapper, ""); } - } + + foreach (DomainController dc in domainObject.DomainControllers) { + portConnectionResult = + await CreateLDAPConnectionWithPortCheck(dc.Name, globalCatalog); + if (portConnectionResult.success) { + _log.LogDebug( + "Successfully created ldap connection for domain: {Domain} using strategy 6 with to pdc {Server}", + _identifier, primaryDomainController); + return (true, connectionWrapper, ""); + } + } - return (false, null, "All attempted connections failed"); - } - - private (bool Success, LdapConnectionWrapperNew Connection, string Message ) CreateNewConnectionForServer(string identifier, bool globalCatalog = false) { - if (CreateLdapConnection(identifier, globalCatalog, out var serverConnection)) { - return (true, serverConnection, ""); + return (false, null, "All attempted connections failed"); } - - return (false, null, $"Failed to create ldap connection for {identifier}"); - } - private bool CreateLdapConnection(string target, bool globalCatalog, - out LdapConnectionWrapperNew connection) { - var baseConnection = CreateBaseConnection(target, true, globalCatalog); - if (TestLdapConnection(baseConnection, out var result)) { - connection = new LdapConnectionWrapperNew(baseConnection, result.SearchResultEntry, globalCatalog, _identifier); - return true; - } + private (bool Success, LdapConnectionWrapperNew Connection, string Message ) CreateNewConnectionForServer(string identifier, bool globalCatalog = false) { + if (CreateLdapConnection(identifier, globalCatalog, out var serverConnection)) { + return (true, serverConnection, ""); + } - try { - baseConnection.Dispose(); - } - catch { - //this is just in case + return (false, null, $"Failed to create ldap connection for {identifier}"); } + + private bool CreateLdapConnection(string target, bool globalCatalog, + out LdapConnectionWrapperNew connection) { + var baseConnection = CreateBaseConnection(target, true, globalCatalog); + if (TestLdapConnection(baseConnection, out var result)) { + connection = new LdapConnectionWrapperNew(baseConnection, result.SearchResultEntry, globalCatalog, _identifier); + return true; + } + + try { + baseConnection.Dispose(); + } + catch { + //this is just in case + } + + if (_ldapConfig.ForceSSL) { + connection = null; + return false; + } + + baseConnection = CreateBaseConnection(target, false, globalCatalog); + if (TestLdapConnection(baseConnection, out result)) { + connection = new LdapConnectionWrapperNew(baseConnection, result.SearchResultEntry, globalCatalog, _identifier); + return true; + } + + try { + baseConnection.Dispose(); + } + catch { + //this is just in case + } - if (_ldapConfig.ForceSSL) { connection = null; return false; } + + private LdapConnection CreateBaseConnection(string directoryIdentifier, bool ssl, + bool globalCatalog) { + var port = globalCatalog ? _ldapConfig.GetGCPort(ssl) : _ldapConfig.GetPort(ssl); + var identifier = new LdapDirectoryIdentifier(directoryIdentifier, port, false, false); + var connection = new LdapConnection(identifier) { Timeout = new TimeSpan(0, 0, 5, 0) }; + + //These options are important! + connection.SessionOptions.ProtocolVersion = 3; + //Referral chasing does not work with paged searches + connection.SessionOptions.ReferralChasing = ReferralChasingOptions.None; + if (ssl) connection.SessionOptions.SecureSocketLayer = true; + + connection.SessionOptions.Sealing = !_ldapConfig.DisableSigning; + connection.SessionOptions.Signing = !_ldapConfig.DisableSigning; + + if (_ldapConfig.DisableCertVerification) + connection.SessionOptions.VerifyServerCertificate = (_, _) => true; + + if (_ldapConfig.Username != null) { + var cred = new NetworkCredential(_ldapConfig.Username, _ldapConfig.Password); + connection.Credential = cred; + } - baseConnection = CreateBaseConnection(target, false, globalCatalog); - if (TestLdapConnection(baseConnection, out result)) { - connection = new LdapConnectionWrapperNew(baseConnection, result.SearchResultEntry, globalCatalog, _identifier); - return true; - } + connection.AuthType = _ldapConfig.AuthType; - try { - baseConnection.Dispose(); - } - catch { - //this is just in case + return connection; } - connection = null; - return false; - } - - private LdapConnection CreateBaseConnection(string directoryIdentifier, bool ssl, - bool globalCatalog) { - var port = globalCatalog ? _ldapConfig.GetGCPort(ssl) : _ldapConfig.GetPort(ssl); - var identifier = new LdapDirectoryIdentifier(directoryIdentifier, port, false, false); - var connection = new LdapConnection(identifier) { Timeout = new TimeSpan(0, 0, 5, 0) }; - - //These options are important! - connection.SessionOptions.ProtocolVersion = 3; - //Referral chasing does not work with paged searches - connection.SessionOptions.ReferralChasing = ReferralChasingOptions.None; - if (ssl) connection.SessionOptions.SecureSocketLayer = true; - - connection.SessionOptions.Sealing = !_ldapConfig.DisableSigning; - connection.SessionOptions.Signing = !_ldapConfig.DisableSigning; - - if (_ldapConfig.DisableCertVerification) - connection.SessionOptions.VerifyServerCertificate = (_, _) => true; - - if (_ldapConfig.Username != null) { - var cred = new NetworkCredential(_ldapConfig.Username, _ldapConfig.Password); - connection.Credential = cred; - } + /// + /// Tests whether an LDAP connection is working + /// + /// The ldap connection object to test + /// The results fo the connection test + /// True if connection was successful, false otherwise + /// Something is wrong with the supplied credentials + /// + /// A connection "succeeded" but no data was returned. This can be related to + /// kerberos auth across trusts or just simply lack of permissions + /// + private bool TestLdapConnection(LdapConnection connection, out LdapConnectionTestResult testResult) { + testResult = new LdapConnectionTestResult(); + try { + //Attempt an initial bind. If this fails, likely auth is invalid, or its not a valid target + connection.Bind(); + } + catch (LdapException e) { + //TODO: Maybe look at this and find a better way? + if (e.ErrorCode is (int)LdapErrorCodes.InvalidCredentials or (int)ResultCode.InappropriateAuthentication) { + connection.Dispose(); + throw new LdapAuthenticationException(e); + } + + testResult.Message = e.Message; + testResult.ErrorCode = e.ErrorCode; + return false; + } + catch (Exception e) { + testResult.Message = e.Message; + return false; + } - connection.AuthType = _ldapConfig.AuthType; + SearchResponse response; + try { + //Do an initial search request to get the rootDSE + //This ldap filter is equivalent to (objectclass=*) + var searchRequest = CreateSearchRequest("", new LDAPFilter().AddAllObjects().GetFilter(), + SearchScope.Base, null); - return connection; - } + response = (SearchResponse)connection.SendRequest(searchRequest); + } + catch (LdapException e) { + /* + * If we can't send the initial search request, its unlikely any other search requests will work so we will immediately return false + */ + testResult.Message = e.Message; + testResult.ErrorCode = e.ErrorCode; + return false; + } - /// - /// Tests whether an LDAP connection is working - /// - /// The ldap connection object to test - /// The results fo the connection test - /// True if connection was successful, false otherwise - /// Something is wrong with the supplied credentials - /// - /// A connection "succeeded" but no data was returned. This can be related to - /// kerberos auth across trusts or just simply lack of permissions - /// - private bool TestLdapConnection(LdapConnection connection, out LdapConnectionTestResult testResult) { - testResult = new LdapConnectionTestResult(); - try { - //Attempt an initial bind. If this fails, likely auth is invalid, or its not a valid target - connection.Bind(); - } - catch (LdapException e) { - //TODO: Maybe look at this and find a better way? - if (e.ErrorCode is (int)LdapErrorCodes.InvalidCredentials or (int)ResultCode.InappropriateAuthentication) { + if (response?.Entries == null || response.Entries.Count == 0) { + /* + * This can happen for one of two reasons, either we dont have permission to query AD or we're authenticating + * across external trusts with kerberos authentication without Forest Search Order properly configured. + * Either way, this connection isn't useful for us because we're not going to get data, so return false + */ + connection.Dispose(); - throw new LdapAuthenticationException(e); + throw new NoLdapDataException(); } - testResult.Message = e.Message; - testResult.ErrorCode = e.ErrorCode; - return false; - } - catch (Exception e) { - testResult.Message = e.Message; - return false; + testResult.SearchResultEntry = new SearchResultEntryWrapper(response.Entries[0]); + testResult.Message = ""; + return true; } - SearchResponse response; - try { - //Do an initial search request to get the rootDSE - //This ldap filter is equivalent to (objectclass=*) - var searchRequest = CreateSearchRequest("", new LDAPFilter().AddAllObjects().GetFilter(), - SearchScope.Base, null); - - response = (SearchResponse)connection.SendRequest(searchRequest); - } - catch (LdapException e) { - /* - * If we can't send the initial search request, its unlikely any other search requests will work so we will immediately return false - */ - testResult.Message = e.Message; - testResult.ErrorCode = e.ErrorCode; - return false; + private class LdapConnectionTestResult { + public string Message { get; set; } + public ISearchResultEntry SearchResultEntry { get; set; } + public int ErrorCode { get; set; } } + + private async Task<(bool success, LdapConnectionWrapperNew connection)> CreateLDAPConnectionWithPortCheck( + string target, bool globalCatalog) { + if (globalCatalog) { + if (await _portScanner.CheckPort(target, _ldapConfig.GetGCPort(true)) || (!_ldapConfig.ForceSSL && + await _portScanner.CheckPort(target, _ldapConfig.GetGCPort(false)))) + return (CreateLdapConnection(target, true, out var connection), connection); + } + else { + if (await _portScanner.CheckPort(target, _ldapConfig.GetPort(true)) || (!_ldapConfig.ForceSSL && + await _portScanner.CheckPort(target, _ldapConfig.GetPort(false)))) + return (CreateLdapConnection(target, true, out var connection), connection); + } - if (response?.Entries == null || response.Entries.Count == 0) { - /* - * This can happen for one of two reasons, either we dont have permission to query AD or we're authenticating - * across external trusts with kerberos authentication without Forest Search Order properly configured. - * Either way, this connection isn't useful for us because we're not going to get data, so return false - */ - - connection.Dispose(); - throw new NoLdapDataException(); + return (false, null); } - - testResult.SearchResultEntry = new SearchResultEntryWrapper(response.Entries[0]); - testResult.Message = ""; - return true; - } - - private class LdapConnectionTestResult { - public string Message { get; set; } - public ISearchResultEntry SearchResultEntry { get; set; } - public int ErrorCode { get; set; } - } - private async Task<(bool success, LdapConnectionWrapperNew connection)> CreateLDAPConnectionWithPortCheck( - string target, bool globalCatalog) { - if (globalCatalog) { - if (await _portScanner.CheckPort(target, _ldapConfig.GetGCPort(true)) || (!_ldapConfig.ForceSSL && - await _portScanner.CheckPort(target, _ldapConfig.GetGCPort(false)))) - return (CreateLdapConnection(target, true, out var connection), connection); - } - else { - if (await _portScanner.CheckPort(target, _ldapConfig.GetPort(true)) || (!_ldapConfig.ForceSSL && - await _portScanner.CheckPort(target, _ldapConfig.GetPort(false)))) - return (CreateLdapConnection(target, true, out var connection), connection); + private SearchRequest CreateSearchRequest(string distinguishedName, string ldapFilter, + SearchScope searchScope, + string[] attributes) { + var searchRequest = new SearchRequest(distinguishedName, ldapFilter, + searchScope, attributes); + searchRequest.Controls.Add(new SearchOptionsControl(SearchOption.DomainScope)); + return searchRequest; } - - return (false, null); - } - - private SearchRequest CreateSearchRequest(string distinguishedName, string ldapFilter, - SearchScope searchScope, - string[] attributes) { - var searchRequest = new SearchRequest(distinguishedName, ldapFilter, - searchScope, attributes); - searchRequest.Controls.Add(new SearchOptionsControl(SearchOption.DomainScope)); - return searchRequest; } } diff --git a/src/CommonLib/LdapConnectionWrapperNew.cs b/src/CommonLib/LdapConnectionWrapperNew.cs index bd0e0fc1..2e85d894 100644 --- a/src/CommonLib/LdapConnectionWrapperNew.cs +++ b/src/CommonLib/LdapConnectionWrapperNew.cs @@ -2,21 +2,20 @@ using System.DirectoryServices.Protocols; using SharpHoundCommonLib.Enums; -namespace SharpHoundCommonLib; - -public class LdapConnectionWrapperNew -{ - public LdapConnection Connection { get; private set; } - private readonly ISearchResultEntry _searchResultEntry; - private string _domainSearchBase; - private string _configurationSearchBase; - private string _schemaSearchBase; - private string _server; - private string Guid { get; set; } - public bool GlobalCatalog; - public string PoolIdentifier; +namespace SharpHoundCommonLib { + public class LdapConnectionWrapperNew + { + public LdapConnection Connection { get; private set; } + private readonly ISearchResultEntry _searchResultEntry; + private string _domainSearchBase; + private string _configurationSearchBase; + private string _schemaSearchBase; + private string _server; + private string Guid { get; set; } + public bool GlobalCatalog; + public string PoolIdentifier; - public LdapConnectionWrapperNew(LdapConnection connection, ISearchResultEntry entry, bool globalCatalog, string poolIdentifier) + public LdapConnectionWrapperNew(LdapConnection connection, ISearchResultEntry entry, bool globalCatalog, string poolIdentifier) { Connection = connection; _searchResultEntry = entry; @@ -25,14 +24,14 @@ public LdapConnectionWrapperNew(LdapConnection connection, ISearchResultEntry en PoolIdentifier = poolIdentifier; } - public void CopyContexts(LdapConnectionWrapperNew other) { + public void CopyContexts(LdapConnectionWrapperNew other) { _domainSearchBase = other._domainSearchBase; _configurationSearchBase = other._configurationSearchBase; _schemaSearchBase = other._schemaSearchBase; _server = other._server; } - public string GetServer() { + public string GetServer() { if (_server != null) { return _server; } @@ -41,7 +40,7 @@ public string GetServer() { return _server; } - public bool GetSearchBase(NamingContext context, out string searchBase) + public bool GetSearchBase(NamingContext context, out string searchBase) { searchBase = GetSavedContext(context); if (searchBase != null) @@ -64,7 +63,7 @@ public bool GetSearchBase(NamingContext context, out string searchBase) return false; } - private string GetSavedContext(NamingContext context) + private string GetSavedContext(NamingContext context) { return context switch { @@ -75,7 +74,7 @@ private string GetSavedContext(NamingContext context) }; } - public void SaveContext(NamingContext context, string searchBase) + public void SaveContext(NamingContext context, string searchBase) { switch (context) { @@ -93,18 +92,19 @@ public void SaveContext(NamingContext context, string searchBase) } } - protected bool Equals(LdapConnectionWrapperNew other) { + protected bool Equals(LdapConnectionWrapperNew other) { return Guid == other.Guid; } - public override bool Equals(object obj) { + public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) return false; if (ReferenceEquals(this, obj)) return true; if (obj.GetType() != this.GetType()) return false; return Equals((LdapConnectionWrapperNew)obj); } - public override int GetHashCode() { + public override int GetHashCode() { return (Guid != null ? Guid.GetHashCode() : 0); } + } } \ No newline at end of file diff --git a/src/CommonLib/LdapQueryParameters.cs b/src/CommonLib/LdapQueryParameters.cs index e91ae135..4cb60a77 100644 --- a/src/CommonLib/LdapQueryParameters.cs +++ b/src/CommonLib/LdapQueryParameters.cs @@ -2,41 +2,41 @@ using System.DirectoryServices.Protocols; using SharpHoundCommonLib.Enums; -namespace SharpHoundCommonLib; - -public class LdapQueryParameters -{ - private string _searchBase; - private string _relativeSearchBase; - public string LDAPFilter { get; set; } - public SearchScope SearchScope { get; set; } = SearchScope.Subtree; - public string[] Attributes { get; set; } = Array.Empty(); - public string DomainName { get; set; } - public bool GlobalCatalog { get; set; } - public bool IncludeSecurityDescriptor { get; set; } = false; - public bool IncludeDeleted { get; set; } = false; +namespace SharpHoundCommonLib { + public class LdapQueryParameters + { + private string _searchBase; + private string _relativeSearchBase; + public string LDAPFilter { get; set; } + public SearchScope SearchScope { get; set; } = SearchScope.Subtree; + public string[] Attributes { get; set; } = Array.Empty(); + public string DomainName { get; set; } + public bool GlobalCatalog { get; set; } + public bool IncludeSecurityDescriptor { get; set; } = false; + public bool IncludeDeleted { get; set; } = false; - public string SearchBase { - get => _searchBase; - set { - _relativeSearchBase = null; - _searchBase = value; + public string SearchBase { + get => _searchBase; + set { + _relativeSearchBase = null; + _searchBase = value; + } } - } - public string RelativeSearchBase { - get => _relativeSearchBase; - set { - _relativeSearchBase = value; - _searchBase = null; + public string RelativeSearchBase { + get => _relativeSearchBase; + set { + _relativeSearchBase = value; + _searchBase = null; + } } - } - public NamingContext NamingContext { get; set; } = NamingContext.Default; - public bool ThrowException { get; set; } = false; + public NamingContext NamingContext { get; set; } = NamingContext.Default; + public bool ThrowException { get; set; } = false; - public string GetQueryInfo() - { - return $"Query Information - Filter: {LDAPFilter}, Domain: {DomainName}, GlobalCatalog: {GlobalCatalog}, ADSPath: {SearchBase}"; + public string GetQueryInfo() + { + return $"Query Information - Filter: {LDAPFilter}, Domain: {DomainName}, GlobalCatalog: {GlobalCatalog}, ADSPath: {SearchBase}"; + } } } \ No newline at end of file diff --git a/src/CommonLib/LdapQuerySetupResult.cs b/src/CommonLib/LdapQuerySetupResult.cs index 4c8ed2cc..3842ac18 100644 --- a/src/CommonLib/LdapQuerySetupResult.cs +++ b/src/CommonLib/LdapQuerySetupResult.cs @@ -1,12 +1,12 @@ using System.DirectoryServices; using System.DirectoryServices.Protocols; -namespace SharpHoundCommonLib; - -public class LdapQuerySetupResult { - public LdapConnectionWrapperNew ConnectionWrapper { get; set; } - public SearchRequest SearchRequest { get; set; } - public string Server { get; set; } - public bool Success { get; set; } - public string Message { get; set; } +namespace SharpHoundCommonLib { + public class LdapQuerySetupResult { + public LdapConnectionWrapperNew ConnectionWrapper { get; set; } + public SearchRequest SearchRequest { get; set; } + public string Server { get; set; } + public bool Success { get; set; } + public string Message { get; set; } + } } \ No newline at end of file diff --git a/src/CommonLib/LdapResult.cs b/src/CommonLib/LdapResult.cs index 68ca06c0..09166cd7 100644 --- a/src/CommonLib/LdapResult.cs +++ b/src/CommonLib/LdapResult.cs @@ -1,20 +1,20 @@ using System; -namespace SharpHoundCommonLib; +namespace SharpHoundCommonLib { + public class LdapResult : Result + { + public string QueryInfo { get; set; } -public class LdapResult : Result -{ - public string QueryInfo { get; set; } - - protected LdapResult(T value, bool success, string error, string queryInfo) : base(value, success, error) { - QueryInfo = queryInfo; - } + protected LdapResult(T value, bool success, string error, string queryInfo) : base(value, success, error) { + QueryInfo = queryInfo; + } - public static LdapResult Ok(T value) { - return new LdapResult(value, true, string.Empty, null); - } + public static LdapResult Ok(T value) { + return new LdapResult(value, true, string.Empty, null); + } - public static LdapResult Fail(string message, LdapQueryParameters queryInfo) { - return new LdapResult(default, false, message, queryInfo.GetQueryInfo()); + public static LdapResult Fail(string message, LdapQueryParameters queryInfo) { + return new LdapResult(default, false, message, queryInfo.GetQueryInfo()); + } } } \ No newline at end of file diff --git a/src/CommonLib/LdapUtilsNew.cs b/src/CommonLib/LdapUtilsNew.cs index 737ca89c..54f2ea81 100644 --- a/src/CommonLib/LdapUtilsNew.cs +++ b/src/CommonLib/LdapUtilsNew.cs @@ -24,1092 +24,1118 @@ using SearchScope = System.DirectoryServices.Protocols.SearchScope; using SecurityMasks = System.DirectoryServices.Protocols.SecurityMasks; -namespace SharpHoundCommonLib; - -public class LdapUtilsNew : ILdapUtilsNew { - //This cache is indexed by domain sid - private readonly ConcurrentDictionary _dcInfoCache = new(); - private static readonly ConcurrentDictionary DomainCache = new(); - - private static readonly ConcurrentDictionary DomainToForestCache = - new(StringComparer.OrdinalIgnoreCase); - - private static readonly ConcurrentDictionary - SeenWellKnownPrincipals = new(); - - private readonly ConcurrentDictionary _hostResolutionMap = new(StringComparer.OrdinalIgnoreCase); - - private readonly ConcurrentDictionary _distinguishedNameCache = - new(StringComparer.OrdinalIgnoreCase); - - private readonly ILogger _log; - private readonly PortScanner _portScanner; - private readonly NativeMethods _nativeMethods; - private readonly string _nullCacheKey = Guid.NewGuid().ToString(); - private readonly Regex _sidRegex = new(@"^(S-\d+-\d+-\d+-\d+-\d+-\d+)-\d+$"); - - private readonly string[] _translateNames = { "Administrator", "admin" }; - private LDAPConfig _ldapConfig = new(); - - private ConnectionPoolManager _connectionPool; - - private static readonly TimeSpan MinBackoffDelay = TimeSpan.FromSeconds(2); - private static readonly TimeSpan MaxBackoffDelay = TimeSpan.FromSeconds(20); - private const int BackoffDelayMultiplier = 2; - private const int MaxRetries = 3; - - private static readonly byte[] NameRequest = { - 0x80, 0x94, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x20, 0x43, 0x4b, 0x41, - 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, - 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, - 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, - 0x41, 0x41, 0x41, 0x41, 0x41, 0x00, 0x00, 0x21, - 0x00, 0x01 - }; - - private class ResolvedWellKnownPrincipal { - public string DomainName { get; set; } - public string WkpId { get; set; } - } - - public LdapUtilsNew() { - _nativeMethods = new NativeMethods(); - _portScanner = new PortScanner(); - _log = Logging.LogProvider.CreateLogger("LDAPUtils"); - _connectionPool = new ConnectionPoolManager(_ldapConfig); - } - - public LdapUtilsNew(NativeMethods nativeMethods = null, PortScanner scanner = null, ILogger log = null) { - _nativeMethods = nativeMethods ?? new NativeMethods(); - _portScanner = scanner ?? new PortScanner(); - _log = log ?? Logging.LogProvider.CreateLogger("LDAPUtils"); - _connectionPool = new ConnectionPoolManager(_ldapConfig, scanner: _portScanner); - } +namespace SharpHoundCommonLib { + public class LdapUtilsNew : ILdapUtilsNew { + //This cache is indexed by domain sid + private readonly ConcurrentDictionary _dcInfoCache = new(); + private static readonly ConcurrentDictionary DomainCache = new(); + + private static readonly ConcurrentDictionary DomainToForestCache = + new(StringComparer.OrdinalIgnoreCase); + + private static readonly ConcurrentDictionary + SeenWellKnownPrincipals = new(); + + private readonly ConcurrentDictionary _hostResolutionMap = new(StringComparer.OrdinalIgnoreCase); + + private readonly ConcurrentDictionary _distinguishedNameCache = + new(StringComparer.OrdinalIgnoreCase); + + private readonly ILogger _log; + private readonly PortScanner _portScanner; + private readonly NativeMethods _nativeMethods; + private readonly string _nullCacheKey = Guid.NewGuid().ToString(); + private readonly Regex _sidRegex = new(@"^(S-\d+-\d+-\d+-\d+-\d+-\d+)-\d+$"); + + private readonly string[] _translateNames = { "Administrator", "admin" }; + private LDAPConfig _ldapConfig = new(); + + private ConnectionPoolManager _connectionPool; + + private static readonly TimeSpan MinBackoffDelay = TimeSpan.FromSeconds(2); + private static readonly TimeSpan MaxBackoffDelay = TimeSpan.FromSeconds(20); + private const int BackoffDelayMultiplier = 2; + private const int MaxRetries = 3; + + private static readonly byte[] NameRequest = { + 0x80, 0x94, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x20, 0x43, 0x4b, 0x41, + 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, + 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, + 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, + 0x41, 0x41, 0x41, 0x41, 0x41, 0x00, 0x00, 0x21, + 0x00, 0x01 + }; - public void SetLDAPConfig(LDAPConfig config) { - _ldapConfig = config; - _connectionPool.Dispose(); - _connectionPool = new ConnectionPoolManager(_ldapConfig, scanner: _portScanner); - } + private class ResolvedWellKnownPrincipal { + public string DomainName { get; set; } + public string WkpId { get; set; } + } - public async IAsyncEnumerable> RangedRetrieval(string distinguishedName, - string attributeName, [EnumeratorCancellation] CancellationToken cancellationToken = new()) { - var domain = Helpers.DistinguishedNameToDomain(distinguishedName); + public LdapUtilsNew() { + _nativeMethods = new NativeMethods(); + _portScanner = new PortScanner(); + _log = Logging.LogProvider.CreateLogger("LDAPUtils"); + _connectionPool = new ConnectionPoolManager(_ldapConfig); + } - var connectionResult = await _connectionPool.GetLdapConnection(domain, false); - if (!connectionResult.Success) { - yield return Result.Fail(connectionResult.Message); - yield break; + public LdapUtilsNew(NativeMethods nativeMethods = null, PortScanner scanner = null, ILogger log = null) { + _nativeMethods = nativeMethods ?? new NativeMethods(); + _portScanner = scanner ?? new PortScanner(); + _log = log ?? Logging.LogProvider.CreateLogger("LDAPUtils"); + _connectionPool = new ConnectionPoolManager(_ldapConfig, scanner: _portScanner); } - var index = 0; - var step = 0; + public void SetLDAPConfig(LDAPConfig config) { + _ldapConfig = config; + _connectionPool.Dispose(); + _connectionPool = new ConnectionPoolManager(_ldapConfig, scanner: _portScanner); + } - //Start by using * as our upper index, which will automatically give us the range size - var currentRange = $"{attributeName};range={index}-*"; - var complete = false; + public async IAsyncEnumerable> RangedRetrieval(string distinguishedName, + string attributeName, [EnumeratorCancellation] CancellationToken cancellationToken = new()) { + var domain = Helpers.DistinguishedNameToDomain(distinguishedName); - var queryParameters = new LdapQueryParameters() { - DomainName = domain, - LDAPFilter = $"{attributeName}=*", - Attributes = new[] { currentRange }, - SearchScope = SearchScope.Base, - SearchBase = distinguishedName - }; - var connectionWrapper = connectionResult.ConnectionWrapper; + var connectionResult = await _connectionPool.GetLdapConnection(domain, false); + if (!connectionResult.Success) { + yield return Result.Fail(connectionResult.Message); + yield break; + } - if (!CreateSearchRequest(queryParameters, connectionWrapper, out var searchRequest)) { - yield return Result.Fail("Failed to create search request"); - yield break; - } + var index = 0; + var step = 0; - var queryRetryCount = 0; - var busyRetryCount = 0; + //Start by using * as our upper index, which will automatically give us the range size + var currentRange = $"{attributeName};range={index}-*"; + var complete = false; - Result tempResult = null; + var queryParameters = new LdapQueryParameters() { + DomainName = domain, + LDAPFilter = $"{attributeName}=*", + Attributes = new[] { currentRange }, + SearchScope = SearchScope.Base, + SearchBase = distinguishedName + }; + var connectionWrapper = connectionResult.ConnectionWrapper; - while (true) { - if (cancellationToken.IsCancellationRequested) { + if (!CreateSearchRequest(queryParameters, connectionWrapper, out var searchRequest)) { + yield return Result.Fail("Failed to create search request"); yield break; } - SearchResponse response = null; - try { - response = (SearchResponse)connectionWrapper.Connection.SendRequest(searchRequest); - } - catch (LdapException le) when (le.ErrorCode == (int)ResultCode.Busy && busyRetryCount < MaxRetries) { - busyRetryCount++; - var backoffDelay = GetNextBackoff(busyRetryCount); - await Task.Delay(backoffDelay, cancellationToken); - } - catch (LdapException le) when (le.ErrorCode == (int)LdapErrorCodes.ServerDown && - queryRetryCount < MaxRetries) { - queryRetryCount++; - _connectionPool.ReleaseConnection(connectionWrapper, true); - for (var retryCount = 0; retryCount < MaxRetries; retryCount++) { - var backoffDelay = GetNextBackoff(retryCount); + var queryRetryCount = 0; + var busyRetryCount = 0; + + Result tempResult = null; + + while (true) { + if (cancellationToken.IsCancellationRequested) { + yield break; + } + + SearchResponse response = null; + try { + response = (SearchResponse)connectionWrapper.Connection.SendRequest(searchRequest); + } + catch (LdapException le) when (le.ErrorCode == (int)ResultCode.Busy && busyRetryCount < MaxRetries) { + busyRetryCount++; + var backoffDelay = GetNextBackoff(busyRetryCount); await Task.Delay(backoffDelay, cancellationToken); - var (success, newConnectionWrapper, message) = - await _connectionPool.GetLdapConnection(domain, - false); - if (success) { - _log.LogDebug( - "RangedRetrieval - Recovered from ServerDown successfully, connection made to {NewServer}", - newConnectionWrapper.GetServer()); - connectionWrapper = newConnectionWrapper; - break; + } + catch (LdapException le) when (le.ErrorCode == (int)LdapErrorCodes.ServerDown && + queryRetryCount < MaxRetries) { + queryRetryCount++; + _connectionPool.ReleaseConnection(connectionWrapper, true); + for (var retryCount = 0; retryCount < MaxRetries; retryCount++) { + var backoffDelay = GetNextBackoff(retryCount); + await Task.Delay(backoffDelay, cancellationToken); + var (success, newConnectionWrapper, message) = + await _connectionPool.GetLdapConnection(domain, + false); + if (success) { + _log.LogDebug( + "RangedRetrieval - Recovered from ServerDown successfully, connection made to {NewServer}", + newConnectionWrapper.GetServer()); + connectionWrapper = newConnectionWrapper; + break; + } + + //If we hit our max retries for making a new connection, set tempResult so we can yield it after this logic + if (retryCount == MaxRetries - 1) { + _log.LogError( + "RangedRetrieval - Failed to get a new connection after ServerDown for path {Path}", + distinguishedName); + tempResult = + Result.Fail( + "RangedRetrieval - Failed to get a new connection after ServerDown."); + } } + } + catch (LdapException le) { + tempResult = Result.Fail( + $"Caught unrecoverable ldap exception: {le.Message} (ServerMessage: {le.ServerErrorMessage}) (ErrorCode: {le.ErrorCode})"); + } + catch (Exception e) { + tempResult = + Result.Fail($"Caught unrecoverable exception: {e.Message}"); + } - //If we hit our max retries for making a new connection, set tempResult so we can yield it after this logic - if (retryCount == MaxRetries - 1) { - _log.LogError( - "RangedRetrieval - Failed to get a new connection after ServerDown for path {Path}", - distinguishedName); - tempResult = - Result.Fail( - "RangedRetrieval - Failed to get a new connection after ServerDown."); - } + //If we have a tempResult set it means we hit an error we couldn't recover from, so yield that result and then break out of the function + if (tempResult != null) { + yield return tempResult; + yield break; } - } - catch (LdapException le) { - tempResult = Result.Fail( - $"Caught unrecoverable ldap exception: {le.Message} (ServerMessage: {le.ServerErrorMessage}) (ErrorCode: {le.ErrorCode})"); - } - catch (Exception e) { - tempResult = - Result.Fail($"Caught unrecoverable exception: {e.Message}"); - } - //If we have a tempResult set it means we hit an error we couldn't recover from, so yield that result and then break out of the function - if (tempResult != null) { - yield return tempResult; - yield break; - } + if (response?.Entries.Count == 1) { + var entry = response.Entries[0]; + //We dont know the name of our attribute, but there should only be one, so we're safe to just use a loop here + foreach (string attr in entry.Attributes.AttributeNames) { + currentRange = attr; + complete = currentRange.IndexOf("*", 0, StringComparison.OrdinalIgnoreCase) > 0; + step = entry.Attributes[currentRange].Count; + } - if (response?.Entries.Count == 1) { - var entry = response.Entries[0]; - //We dont know the name of our attribute, but there should only be one, so we're safe to just use a loop here - foreach (string attr in entry.Attributes.AttributeNames) { - currentRange = attr; - complete = currentRange.IndexOf("*", 0, StringComparison.OrdinalIgnoreCase) > 0; - step = entry.Attributes[currentRange].Count; - } + foreach (string dn in entry.Attributes[currentRange].GetValues(typeof(string))) { + yield return Result.Ok(dn); + index++; + } - foreach (string dn in entry.Attributes[currentRange].GetValues(typeof(string))) { - yield return Result.Ok(dn); - index++; - } + if (complete) { + yield break; + } - if (complete) { + currentRange = $"{attributeName};range={index}-{index + step}"; + searchRequest.Attributes.Clear(); + searchRequest.Attributes.Add(currentRange); + } + else { + //I dont know what can cause a RR to have multiple entries, but its nothing good. Break out yield break; } - - currentRange = $"{attributeName};range={index}-{index + step}"; - searchRequest.Attributes.Clear(); - searchRequest.Attributes.Add(currentRange); - } - else { - //I dont know what can cause a RR to have multiple entries, but its nothing good. Break out - yield break; } } - } - public async IAsyncEnumerable> Query(LdapQueryParameters queryParameters, - [EnumeratorCancellation] CancellationToken cancellationToken = new()) { - var setupResult = await SetupLdapQuery(queryParameters); - - if (!setupResult.Success) { - _log.LogInformation("Query - Failure during query setup: {Reason}\n{Info}", setupResult.Message, - queryParameters.GetQueryInfo()); - yield break; - } + public async IAsyncEnumerable> Query(LdapQueryParameters queryParameters, + [EnumeratorCancellation] CancellationToken cancellationToken = new()) { + var setupResult = await SetupLdapQuery(queryParameters); - var searchRequest = setupResult.SearchRequest; - var connectionWrapper = setupResult.ConnectionWrapper; + if (!setupResult.Success) { + _log.LogInformation("Query - Failure during query setup: {Reason}\n{Info}", setupResult.Message, + queryParameters.GetQueryInfo()); + yield break; + } - if (cancellationToken.IsCancellationRequested) { - yield break; - } + var searchRequest = setupResult.SearchRequest; + var connectionWrapper = setupResult.ConnectionWrapper; - var queryRetryCount = 0; - var busyRetryCount = 0; - LdapResult tempResult = null; - var querySuccess = false; - SearchResponse response = null; - while (true) { if (cancellationToken.IsCancellationRequested) { yield break; } - try { - _log.LogTrace("Sending ldap request - {Info}", queryParameters.GetQueryInfo()); - response = (SearchResponse)connectionWrapper.Connection.SendRequest(searchRequest); - - if (response != null) { - querySuccess = true; - } - else if (queryRetryCount == MaxRetries) { - tempResult = - LdapResult.Fail($"Failed to get a response after {MaxRetries} attempts", - queryParameters); - } - else { - queryRetryCount++; - continue; + var queryRetryCount = 0; + var busyRetryCount = 0; + LdapResult tempResult = null; + var querySuccess = false; + SearchResponse response = null; + while (true) { + if (cancellationToken.IsCancellationRequested) { + yield break; } - } - catch (LdapException le) when (le.ErrorCode == (int)LdapErrorCodes.ServerDown && - queryRetryCount < MaxRetries) { - /* - * A ServerDown exception indicates that our connection is no longer valid for one of many reasons. - * We'll want to release our connection back to the pool, but dispose it. We need a new connection, - * and because this is not a paged query, we can get this connection from anywhere. - */ - //Increment our query retry count - queryRetryCount++; - _connectionPool.ReleaseConnection(connectionWrapper, true); + try { + _log.LogTrace("Sending ldap request - {Info}", queryParameters.GetQueryInfo()); + response = (SearchResponse)connectionWrapper.Connection.SendRequest(searchRequest); - for (var retryCount = 0; retryCount < MaxRetries; retryCount++) { - var backoffDelay = GetNextBackoff(retryCount); - await Task.Delay(backoffDelay, cancellationToken); - var (success, newConnectionWrapper, message) = - await _connectionPool.GetLdapConnection(queryParameters.DomainName, - queryParameters.GlobalCatalog); - if (success) { - _log.LogDebug("Query - Recovered from ServerDown successfully, connection made to {NewServer}", - newConnectionWrapper.GetServer()); - connectionWrapper = newConnectionWrapper; - break; + if (response != null) { + querySuccess = true; } - - //If we hit our max retries for making a new connection, set tempResult so we can yield it after this logic - if (retryCount == MaxRetries - 1) { - _log.LogError("Query - Failed to get a new connection after ServerDown.\n{Info}", - queryParameters.GetQueryInfo()); + else if (queryRetryCount == MaxRetries) { tempResult = - LdapResult.Fail( - "Query - Failed to get a new connection after ServerDown.", queryParameters); + LdapResult.Fail($"Failed to get a response after {MaxRetries} attempts", + queryParameters); + } + else { + queryRetryCount++; + continue; } } - } - catch (LdapException le) when (le.ErrorCode == (int)ResultCode.Busy && busyRetryCount < MaxRetries) { - /* - * If we get a busy error, we want to do an exponential backoff, but maintain the current connection - * The expectation is that given enough time, the server should stop being busy and service our query appropriately - */ - busyRetryCount++; - var backoffDelay = GetNextBackoff(busyRetryCount); - await Task.Delay(backoffDelay, cancellationToken); - } - catch (LdapException le) { - tempResult = LdapResult.Fail( - $"Query - Caught unrecoverable ldap exception: {le.Message} (ServerMessage: {le.ServerErrorMessage}) (ErrorCode: {le.ErrorCode})", - queryParameters); - } - catch (Exception e) { - tempResult = - LdapResult.Fail($"PagedQuery - Caught unrecoverable exception: {e.Message}", + catch (LdapException le) when (le.ErrorCode == (int)LdapErrorCodes.ServerDown && + queryRetryCount < MaxRetries) { + /* + * A ServerDown exception indicates that our connection is no longer valid for one of many reasons. + * We'll want to release our connection back to the pool, but dispose it. We need a new connection, + * and because this is not a paged query, we can get this connection from anywhere. + */ + + //Increment our query retry count + queryRetryCount++; + _connectionPool.ReleaseConnection(connectionWrapper, true); + + for (var retryCount = 0; retryCount < MaxRetries; retryCount++) { + var backoffDelay = GetNextBackoff(retryCount); + await Task.Delay(backoffDelay, cancellationToken); + var (success, newConnectionWrapper, message) = + await _connectionPool.GetLdapConnection(queryParameters.DomainName, + queryParameters.GlobalCatalog); + if (success) { + _log.LogDebug("Query - Recovered from ServerDown successfully, connection made to {NewServer}", + newConnectionWrapper.GetServer()); + connectionWrapper = newConnectionWrapper; + break; + } + + //If we hit our max retries for making a new connection, set tempResult so we can yield it after this logic + if (retryCount == MaxRetries - 1) { + _log.LogError("Query - Failed to get a new connection after ServerDown.\n{Info}", + queryParameters.GetQueryInfo()); + tempResult = + LdapResult.Fail( + "Query - Failed to get a new connection after ServerDown.", queryParameters); + } + } + } + catch (LdapException le) when (le.ErrorCode == (int)ResultCode.Busy && busyRetryCount < MaxRetries) { + /* + * If we get a busy error, we want to do an exponential backoff, but maintain the current connection + * The expectation is that given enough time, the server should stop being busy and service our query appropriately + */ + busyRetryCount++; + var backoffDelay = GetNextBackoff(busyRetryCount); + await Task.Delay(backoffDelay, cancellationToken); + } + catch (LdapException le) { + tempResult = LdapResult.Fail( + $"Query - Caught unrecoverable ldap exception: {le.Message} (ServerMessage: {le.ServerErrorMessage}) (ErrorCode: {le.ErrorCode})", queryParameters); - } + } + catch (Exception e) { + tempResult = + LdapResult.Fail($"PagedQuery - Caught unrecoverable exception: {e.Message}", + queryParameters); + } - //If we have a tempResult set it means we hit an error we couldn't recover from, so yield that result and then break out of the function - if (tempResult != null) { - yield return tempResult; - yield break; - } + //If we have a tempResult set it means we hit an error we couldn't recover from, so yield that result and then break out of the function + if (tempResult != null) { + yield return tempResult; + yield break; + } - //If we've successfully made our query, break out of the while loop - if (querySuccess) { - break; + //If we've successfully made our query, break out of the while loop + if (querySuccess) { + break; + } } - } - - //TODO: Fix this with a new wrapper object - foreach (ISearchResultEntry entry in response.Entries) { - yield return LdapResult.Ok(entry); - } - } - - public async IAsyncEnumerable> PagedQuery(LdapQueryParameters queryParameters, - [EnumeratorCancellation] CancellationToken cancellationToken = new()) { - var setupResult = await SetupLdapQuery(queryParameters); - - if (!setupResult.Success) { - _log.LogInformation("PagedQuery - Failure during query setup: {Reason}\n{Info}", setupResult.Message, - queryParameters.GetQueryInfo()); - yield break; - } - - var searchRequest = setupResult.SearchRequest; - var connectionWrapper = setupResult.ConnectionWrapper; - var serverName = setupResult.Server; - if (serverName == null) { - _log.LogWarning("PagedQuery - Failed to get a server name for connection, retry not possible"); + //TODO: Fix this with a new wrapper object + foreach (ISearchResultEntry entry in response.Entries) { + yield return LdapResult.Ok(entry); + } } - var pageControl = new PageResultRequestControl(500); - searchRequest.Controls.Add(pageControl); - - PageResultResponseControl pageResponse = null; - var busyRetryCount = 0; - var queryRetryCount = 0; - LdapResult tempResult = null; + public async IAsyncEnumerable> PagedQuery(LdapQueryParameters queryParameters, + [EnumeratorCancellation] CancellationToken cancellationToken = new()) { + var setupResult = await SetupLdapQuery(queryParameters); - while (true) { - if (cancellationToken.IsCancellationRequested) { + if (!setupResult.Success) { + _log.LogInformation("PagedQuery - Failure during query setup: {Reason}\n{Info}", setupResult.Message, + queryParameters.GetQueryInfo()); yield break; } - if (tempResult != null) { - yield return tempResult; - yield break; - } + var searchRequest = setupResult.SearchRequest; + var connectionWrapper = setupResult.ConnectionWrapper; + var serverName = setupResult.Server; - SearchResponse response = null; - try { - _log.LogTrace("Sending paged ldap request - {Info}", queryParameters.GetQueryInfo()); - response = (SearchResponse)connectionWrapper.Connection.SendRequest(searchRequest); - if (response != null) { - pageResponse = (PageResultResponseControl)response.Controls - .Where(x => x is PageResultResponseControl).DefaultIfEmpty(null).FirstOrDefault(); - queryRetryCount = 0; - } - else if (queryRetryCount == MaxRetries) { - tempResult = LdapResult.Fail( - $"PagedQuery - Failed to get a response after {MaxRetries} attempts", - queryParameters); - } - else { - queryRetryCount++; - } + if (serverName == null) { + _log.LogWarning("PagedQuery - Failed to get a server name for connection, retry not possible"); } - catch (LdapException le) when (le.ErrorCode == (int)LdapErrorCodes.ServerDown) { - /* - * If we dont have a servername, we're not going to be able to re-establish a connection here. Page cookies are only valid for the server they were generated on. Bail out. - */ - if (serverName == null) { - _log.LogError( - "PagedQuery - Received server down exception without a known servername. Unable to generate new connection\n{Info}", - queryParameters.GetQueryInfo()); + + var pageControl = new PageResultRequestControl(500); + searchRequest.Controls.Add(pageControl); + + PageResultResponseControl pageResponse = null; + var busyRetryCount = 0; + var queryRetryCount = 0; + LdapResult tempResult = null; + + while (true) { + if (cancellationToken.IsCancellationRequested) { yield break; } - /* - * Paged queries will not use the cached ldap connections, as the intention is to only have 1 or a couple of these queries running at once. - * The connection logic here is simplified accordingly - */ - _connectionPool.ReleaseConnection(connectionWrapper, true); - for (var retryCount = 0; retryCount < MaxRetries; retryCount++) { - var backoffDelay = GetNextBackoff(retryCount); - await Task.Delay(backoffDelay, cancellationToken); - var (success, ldapConnectionWrapperNew, message) = await _connectionPool.GetLdapConnectionForServer( - queryParameters.DomainName, serverName, queryParameters.GlobalCatalog); + if (tempResult != null) { + yield return tempResult; + yield break; + } - if (success) { - _log.LogDebug("PagedQuery - Recovered from ServerDown successfully"); - connectionWrapper = ldapConnectionWrapperNew; - break; + SearchResponse response = null; + try { + _log.LogTrace("Sending paged ldap request - {Info}", queryParameters.GetQueryInfo()); + response = (SearchResponse)connectionWrapper.Connection.SendRequest(searchRequest); + if (response != null) { + pageResponse = (PageResultResponseControl)response.Controls + .Where(x => x is PageResultResponseControl).DefaultIfEmpty(null).FirstOrDefault(); + queryRetryCount = 0; } - - if (retryCount == MaxRetries - 1) { - _log.LogError("PagedQuery - Failed to get a new connection after ServerDown.\n{Info}", + else if (queryRetryCount == MaxRetries) { + tempResult = LdapResult.Fail( + $"PagedQuery - Failed to get a response after {MaxRetries} attempts", + queryParameters); + } + else { + queryRetryCount++; + } + } + catch (LdapException le) when (le.ErrorCode == (int)LdapErrorCodes.ServerDown) { + /* + * If we dont have a servername, we're not going to be able to re-establish a connection here. Page cookies are only valid for the server they were generated on. Bail out. + */ + if (serverName == null) { + _log.LogError( + "PagedQuery - Received server down exception without a known servername. Unable to generate new connection\n{Info}", queryParameters.GetQueryInfo()); - tempResult = - LdapResult.Fail("Failed to get a new connection after serverdown", - queryParameters); + yield break; + } + + /* + * Paged queries will not use the cached ldap connections, as the intention is to only have 1 or a couple of these queries running at once. + * The connection logic here is simplified accordingly + */ + _connectionPool.ReleaseConnection(connectionWrapper, true); + for (var retryCount = 0; retryCount < MaxRetries; retryCount++) { + var backoffDelay = GetNextBackoff(retryCount); + await Task.Delay(backoffDelay, cancellationToken); + var (success, ldapConnectionWrapperNew, message) = await _connectionPool.GetLdapConnectionForServer( + queryParameters.DomainName, serverName, queryParameters.GlobalCatalog); + + if (success) { + _log.LogDebug("PagedQuery - Recovered from ServerDown successfully"); + connectionWrapper = ldapConnectionWrapperNew; + break; + } + + if (retryCount == MaxRetries - 1) { + _log.LogError("PagedQuery - Failed to get a new connection after ServerDown.\n{Info}", + queryParameters.GetQueryInfo()); + tempResult = + LdapResult.Fail("Failed to get a new connection after serverdown", + queryParameters); + } } } - } - catch (LdapException le) when (le.ErrorCode == (int)ResultCode.Busy && busyRetryCount < MaxRetries) { - /* - * If we get a busy error, we want to do an exponential backoff, but maintain the current connection - * The expectation is that given enough time, the server should stop being busy and service our query appropriately - */ - busyRetryCount++; - var backoffDelay = GetNextBackoff(busyRetryCount); - await Task.Delay(backoffDelay, cancellationToken); - } - catch (LdapException le) { - tempResult = LdapResult.Fail( - $"PagedQuery - Caught unrecoverable ldap exception: {le.Message} (ServerMessage: {le.ServerErrorMessage}) (ErrorCode: {le.ErrorCode})", - queryParameters); - } - catch (Exception e) { - tempResult = - LdapResult.Fail($"PagedQuery - Caught unrecoverable exception: {e.Message}", + catch (LdapException le) when (le.ErrorCode == (int)ResultCode.Busy && busyRetryCount < MaxRetries) { + /* + * If we get a busy error, we want to do an exponential backoff, but maintain the current connection + * The expectation is that given enough time, the server should stop being busy and service our query appropriately + */ + busyRetryCount++; + var backoffDelay = GetNextBackoff(busyRetryCount); + await Task.Delay(backoffDelay, cancellationToken); + } + catch (LdapException le) { + tempResult = LdapResult.Fail( + $"PagedQuery - Caught unrecoverable ldap exception: {le.Message} (ServerMessage: {le.ServerErrorMessage}) (ErrorCode: {le.ErrorCode})", queryParameters); - } - - if (tempResult != null) { - yield return tempResult; - yield break; - } - - if (cancellationToken.IsCancellationRequested) { - yield break; - } + } + catch (Exception e) { + tempResult = + LdapResult.Fail($"PagedQuery - Caught unrecoverable exception: {e.Message}", + queryParameters); + } - //I'm not sure why this happens sometimes, but if we try the request again, it works sometimes, other times we get an exception - if (response == null || pageResponse == null) { - continue; - } + if (tempResult != null) { + yield return tempResult; + yield break; + } - foreach (ISearchResultEntry entry in response.Entries) { if (cancellationToken.IsCancellationRequested) { yield break; } - yield return LdapResult.Ok(entry); - } + //I'm not sure why this happens sometimes, but if we try the request again, it works sometimes, other times we get an exception + if (response == null || pageResponse == null) { + continue; + } - if (pageResponse.Cookie.Length == 0 || response.Entries.Count == 0 || - cancellationToken.IsCancellationRequested) - yield break; + foreach (ISearchResultEntry entry in response.Entries) { + if (cancellationToken.IsCancellationRequested) { + yield break; + } - pageControl.Cookie = pageResponse.Cookie; - } - } + yield return LdapResult.Ok(entry); + } - public async Task<(bool Success, TypedPrincipal Principal)> ResolveIDAndType(SecurityIdentifier securityIdentifier, - string objectDomain) { - return await ResolveIDAndType(securityIdentifier.Value, objectDomain); - } + if (pageResponse.Cookie.Length == 0 || response.Entries.Count == 0 || + cancellationToken.IsCancellationRequested) + yield break; - public async Task<(bool Success, TypedPrincipal Principal)> - ResolveIDAndType(string identifier, string objectDomain) { - if (identifier.Contains("0ACNF")) { - return (false, new TypedPrincipal(identifier, Label.Base)); + pageControl.Cookie = pageResponse.Cookie; + } } - if (await GetWellKnownPrincipal(identifier, objectDomain) is (true, var principal)) { - return (true, principal); + public async Task<(bool Success, TypedPrincipal Principal)> ResolveIDAndType(SecurityIdentifier securityIdentifier, + string objectDomain) { + return await ResolveIDAndType(securityIdentifier.Value, objectDomain); } - if (identifier.StartsWith("S-")) { - var result = await LookupSidType(identifier, objectDomain); - return (result.Success, new TypedPrincipal(identifier, result.Type)); - } + public async Task<(bool Success, TypedPrincipal Principal)> + ResolveIDAndType(string identifier, string objectDomain) { + if (identifier.Contains("0ACNF")) { + return (false, new TypedPrincipal(identifier, Label.Base)); + } - var (success, type) = await LookupGuidType(identifier, objectDomain); - return (success, new TypedPrincipal(identifier, type)); - } + if (await GetWellKnownPrincipal(identifier, objectDomain) is (true, var principal)) { + return (true, principal); + } + + if (identifier.StartsWith("S-")) { + var result = await LookupSidType(identifier, objectDomain); + return (result.Success, new TypedPrincipal(identifier, result.Type)); + } - private async Task<(bool Success, Label Type)> LookupSidType(string sid, string domain) { - if (Cache.GetIDType(sid, out var type)) { - return (true, type); + var (success, type) = await LookupGuidType(identifier, objectDomain); + return (success, new TypedPrincipal(identifier, type)); } - var tempDomain = domain; + private async Task<(bool Success, Label Type)> LookupSidType(string sid, string domain) { + if (Cache.GetIDType(sid, out var type)) { + return (true, type); + } - if (await GetDomainNameFromSid(sid) is (true, var domainName)) { - tempDomain = domainName; - } + var tempDomain = domain; - var result = await Query(new LdapQueryParameters() { - DomainName = tempDomain, - LDAPFilter = CommonFilters.SpecificSID(sid), - Attributes = CommonProperties.TypeResolutionProps - }).FirstAsync(); + if (await GetDomainNameFromSid(sid) is (true, var domainName)) { + tempDomain = domainName; + } - if (result.IsSuccess) { - type = result.Value.GetLabel(); - Cache.AddType(sid, type); - return (true, type); - } + var result = await Query(new LdapQueryParameters() { + DomainName = tempDomain, + LDAPFilter = CommonFilters.SpecificSID(sid), + Attributes = CommonProperties.TypeResolutionProps + }).FirstAsync(); - try { - var entry = new DirectoryEntry($"LDAP://"); - if (entry.GetLabel(out type)) { + if (result.IsSuccess) { + type = result.Value.GetLabel(); Cache.AddType(sid, type); return (true, type); } - } - catch { - //pass - } - using (var ctx = new PrincipalContext(ContextType.Domain)) { try { - var principal = Principal.FindByIdentity(ctx, IdentityType.Sid, sid); - if (principal != null) { - var entry = (DirectoryEntry)principal.GetUnderlyingObject(); - if (entry.GetLabel(out type)) { - Cache.AddType(sid, type); - return (true, type); - } + var entry = new DirectoryEntry($"LDAP://"); + if (entry.GetLabel(out type)) { + Cache.AddType(sid, type); + return (true, type); } } catch { //pass } - } - return (false, Label.Base); - } + using (var ctx = new PrincipalContext(ContextType.Domain)) { + try { + var principal = Principal.FindByIdentity(ctx, IdentityType.Sid, sid); + if (principal != null) { + var entry = (DirectoryEntry)principal.GetUnderlyingObject(); + if (entry.GetLabel(out type)) { + Cache.AddType(sid, type); + return (true, type); + } + } + } + catch { + //pass + } + } - private async Task<(bool Success, Label type)> LookupGuidType(string guid, string domain) { - if (Cache.GetIDType(guid, out var type)) { - return (true, type); + return (false, Label.Base); } - var result = await Query(new LdapQueryParameters() { - DomainName = domain, - LDAPFilter = CommonFilters.SpecificGUID(guid), - Attributes = CommonProperties.TypeResolutionProps - }).FirstAsync(); + private async Task<(bool Success, Label type)> LookupGuidType(string guid, string domain) { + if (Cache.GetIDType(guid, out var type)) { + return (true, type); + } - if (result.IsSuccess) { - type = result.Value.GetLabel(); - Cache.AddType(guid, type); - return (true, type); - } + var result = await Query(new LdapQueryParameters() { + DomainName = domain, + LDAPFilter = CommonFilters.SpecificGUID(guid), + Attributes = CommonProperties.TypeResolutionProps + }).FirstAsync(); - try { - var entry = new DirectoryEntry($"LDAP://"); - if (entry.GetLabel(out type)) { + if (result.IsSuccess) { + type = result.Value.GetLabel(); Cache.AddType(guid, type); return (true, type); } - } - catch { - //pass - } - using (var ctx = new PrincipalContext(ContextType.Domain)) { try { - var principal = Principal.FindByIdentity(ctx, IdentityType.Guid, guid); - if (principal != null) { - var entry = (DirectoryEntry)principal.GetUnderlyingObject(); - if (entry.GetLabel(out type)) { - Cache.AddType(guid, type); - return (true, type); - } + var entry = new DirectoryEntry($"LDAP://"); + if (entry.GetLabel(out type)) { + Cache.AddType(guid, type); + return (true, type); } } catch { //pass } - } - return (false, Label.Base); - } + using (var ctx = new PrincipalContext(ContextType.Domain)) { + try { + var principal = Principal.FindByIdentity(ctx, IdentityType.Guid, guid); + if (principal != null) { + var entry = (DirectoryEntry)principal.GetUnderlyingObject(); + if (entry.GetLabel(out type)) { + Cache.AddType(guid, type); + return (true, type); + } + } + } + catch { + //pass + } + } - public async Task<(bool Success, TypedPrincipal WellKnownPrincipal)> GetWellKnownPrincipal( - string securityIdentifier, string objectDomain) { - if (!WellKnownPrincipal.GetWellKnownPrincipal(securityIdentifier, out var wellKnownPrincipal)) { - return (false, null); + return (false, Label.Base); } - var (newIdentifier, newDomain) = await GetWellKnownPrincipalObjectIdentifier(securityIdentifier, objectDomain); - - wellKnownPrincipal.ObjectIdentifier = newIdentifier; - SeenWellKnownPrincipals.TryAdd(wellKnownPrincipal.ObjectIdentifier, new ResolvedWellKnownPrincipal { - DomainName = newDomain, - WkpId = securityIdentifier - }); - - return (true, wellKnownPrincipal); - } + public async Task<(bool Success, TypedPrincipal WellKnownPrincipal)> GetWellKnownPrincipal( + string securityIdentifier, string objectDomain) { + if (!WellKnownPrincipal.GetWellKnownPrincipal(securityIdentifier, out var wellKnownPrincipal)) { + return (false, null); + } - private async Task<(string ObjectID, string Domain)> GetWellKnownPrincipalObjectIdentifier( - string securityIdentifier, string domain) { - if (!WellKnownPrincipal.GetWellKnownPrincipal(securityIdentifier, out _)) - return (securityIdentifier, string.Empty); + var (newIdentifier, newDomain) = await GetWellKnownPrincipalObjectIdentifier(securityIdentifier, objectDomain); - if (!securityIdentifier.Equals("S-1-5-9", StringComparison.OrdinalIgnoreCase)) { - var tempDomain = domain; - if (GetDomain(tempDomain, out var domainObject) && domainObject.Name != null) { - tempDomain = domainObject.Name; - } + wellKnownPrincipal.ObjectIdentifier = newIdentifier; + SeenWellKnownPrincipals.TryAdd(wellKnownPrincipal.ObjectIdentifier, new ResolvedWellKnownPrincipal { + DomainName = newDomain, + WkpId = securityIdentifier + }); - return ($"{tempDomain}-{securityIdentifier}".ToUpper(), tempDomain); + return (true, wellKnownPrincipal); } - if (await GetForest(domain) is (true, var forest)) { - return ($"{forest}-{securityIdentifier}".ToUpper(), forest); - } + private async Task<(string ObjectID, string Domain)> GetWellKnownPrincipalObjectIdentifier( + string securityIdentifier, string domain) { + if (!WellKnownPrincipal.GetWellKnownPrincipal(securityIdentifier, out _)) + return (securityIdentifier, string.Empty); - _log.LogWarning("Failed to get a forest name for domain {Domain}, unable to resolve enterprise DC sid", domain); - return ($"UNKNOWN-{securityIdentifier}", "UNKNOWN"); - } + if (!securityIdentifier.Equals("S-1-5-9", StringComparison.OrdinalIgnoreCase)) { + var tempDomain = domain; + if (GetDomain(tempDomain, out var domainObject) && domainObject.Name != null) { + tempDomain = domainObject.Name; + } - private async Task<(bool Success, string ForestName)> GetForest(string domain) { - if (DomainToForestCache.TryGetValue(domain, out var cachedForest)) { - return (true, cachedForest); - } + return ($"{tempDomain}-{securityIdentifier}".ToUpper(), tempDomain); + } - if (GetDomain(domain, out var domainObject)) { - var forestName = domainObject.Forest.Name.ToUpper(); - DomainToForestCache.TryAdd(domain, forestName); - return (true, forestName); - } + if (await GetForest(domain) is (true, var forest)) { + return ($"{forest}-{securityIdentifier}".ToUpper(), forest); + } - var (success, forest) = await GetForestFromLdap(domain); - if (success) { - DomainToForestCache.TryAdd(domain, forest); - return (true, forest); + _log.LogWarning("Failed to get a forest name for domain {Domain}, unable to resolve enterprise DC sid", domain); + return ($"UNKNOWN-{securityIdentifier}", "UNKNOWN"); } - return (false, null); - } - - private async Task<(bool Success, string ForestName)> GetForestFromLdap(string domain) { - var queryParameters = new LdapQueryParameters { - Attributes = new[] { LDAPProperties.RootDomainNamingContext }, - SearchScope = SearchScope.Base, - DomainName = domain, - LDAPFilter = new LDAPFilter().AddAllObjects().GetFilter(), - }; - - var result = await Query(queryParameters).FirstAsync(); - if (result.IsSuccess) { - var rdn = result.Value.GetProperty(LDAPProperties.RootDomainNamingContext); - if (!string.IsNullOrEmpty(rdn)) { - return (true, Helpers.DistinguishedNameToDomain(rdn).ToUpper()); + private async Task<(bool Success, string ForestName)> GetForest(string domain) { + if (DomainToForestCache.TryGetValue(domain, out var cachedForest)) { + return (true, cachedForest); } - } - return (false, null); - } + if (GetDomain(domain, out var domainObject)) { + var forestName = domainObject.Forest.Name.ToUpper(); + DomainToForestCache.TryAdd(domain, forestName); + return (true, forestName); + } - private static TimeSpan GetNextBackoff(int retryCount) { - return TimeSpan.FromSeconds(Math.Min( - MinBackoffDelay.TotalSeconds * Math.Pow(BackoffDelayMultiplier, retryCount), - MaxBackoffDelay.TotalSeconds)); - } + var (success, forest) = await GetForestFromLdap(domain); + if (success) { + DomainToForestCache.TryAdd(domain, forest); + return (true, forest); + } - private bool CreateSearchRequest(LdapQueryParameters queryParameters, - LdapConnectionWrapperNew connectionWrapper, out SearchRequest searchRequest) { - string basePath; - if (!string.IsNullOrWhiteSpace(queryParameters.SearchBase)) { - basePath = queryParameters.SearchBase; + return (false, null); } - else if (!connectionWrapper.GetSearchBase(queryParameters.NamingContext, out basePath)) { - string tempPath; - if (CallDsGetDcName(queryParameters.DomainName, out var info) && info != null) { - tempPath = Helpers.DomainNameToDistinguishedName(info.Value.DomainName); - connectionWrapper.SaveContext(queryParameters.NamingContext, basePath); - } - else if (GetDomain(queryParameters.DomainName, out var domainObject)) { - tempPath = Helpers.DomainNameToDistinguishedName(domainObject.Name); - } - else { - searchRequest = null; - return false; - } - basePath = queryParameters.NamingContext switch { - NamingContext.Configuration => $"CN=Configuration,{tempPath}", - NamingContext.Schema => $"CN=Schema,CN=Configuration,{tempPath}", - NamingContext.Default => tempPath, - _ => throw new ArgumentOutOfRangeException() + private async Task<(bool Success, string ForestName)> GetForestFromLdap(string domain) { + var queryParameters = new LdapQueryParameters { + Attributes = new[] { LDAPProperties.RootDomainNamingContext }, + SearchScope = SearchScope.Base, + DomainName = domain, + LDAPFilter = new LDAPFilter().AddAllObjects().GetFilter(), }; - connectionWrapper.SaveContext(queryParameters.NamingContext, basePath); - - if (!string.IsNullOrWhiteSpace(queryParameters.RelativeSearchBase)) { - basePath = $"{queryParameters.RelativeSearchBase},{basePath}"; + var result = await Query(queryParameters).FirstAsync(); + if (result.IsSuccess) { + var rdn = result.Value.GetProperty(LDAPProperties.RootDomainNamingContext); + if (!string.IsNullOrEmpty(rdn)) { + return (true, Helpers.DistinguishedNameToDomain(rdn).ToUpper()); + } } - } - searchRequest = new SearchRequest(basePath, queryParameters.LDAPFilter, queryParameters.SearchScope, - queryParameters.Attributes); - searchRequest.Controls.Add(new SearchOptionsControl(SearchOption.DomainScope)); - if (queryParameters.IncludeDeleted) { - searchRequest.Controls.Add(new ShowDeletedControl()); + return (false, null); } - if (queryParameters.IncludeSecurityDescriptor) { - searchRequest.Controls.Add(new SecurityDescriptorFlagControl { - SecurityMasks = SecurityMasks.Dacl | SecurityMasks.Owner - }); + private static TimeSpan GetNextBackoff(int retryCount) { + return TimeSpan.FromSeconds(Math.Min( + MinBackoffDelay.TotalSeconds * Math.Pow(BackoffDelayMultiplier, retryCount), + MaxBackoffDelay.TotalSeconds)); } - return true; - } + private bool CreateSearchRequest(LdapQueryParameters queryParameters, + LdapConnectionWrapperNew connectionWrapper, out SearchRequest searchRequest) { + string basePath; + if (!string.IsNullOrWhiteSpace(queryParameters.SearchBase)) { + basePath = queryParameters.SearchBase; + } + else if (!connectionWrapper.GetSearchBase(queryParameters.NamingContext, out basePath)) { + string tempPath; + if (CallDsGetDcName(queryParameters.DomainName, out var info) && info != null) { + tempPath = Helpers.DomainNameToDistinguishedName(info.Value.DomainName); + connectionWrapper.SaveContext(queryParameters.NamingContext, basePath); + } + else if (GetDomain(queryParameters.DomainName, out var domainObject)) { + tempPath = Helpers.DomainNameToDistinguishedName(domainObject.Name); + } + else { + searchRequest = null; + return false; + } - private bool CallDsGetDcName(string domainName, out NetAPIStructs.DomainControllerInfo? info) { - if (_dcInfoCache.TryGetValue(domainName.ToUpper().Trim(), out info)) return info != null; + basePath = queryParameters.NamingContext switch { + NamingContext.Configuration => $"CN=Configuration,{tempPath}", + NamingContext.Schema => $"CN=Schema,CN=Configuration,{tempPath}", + NamingContext.Default => tempPath, + _ => throw new ArgumentOutOfRangeException() + }; - var apiResult = _nativeMethods.CallDsGetDcName(null, domainName, - (uint)(NetAPIEnums.DSGETDCNAME_FLAGS.DS_FORCE_REDISCOVERY | - NetAPIEnums.DSGETDCNAME_FLAGS.DS_RETURN_DNS_NAME | - NetAPIEnums.DSGETDCNAME_FLAGS.DS_DIRECTORY_SERVICE_REQUIRED)); + connectionWrapper.SaveContext(queryParameters.NamingContext, basePath); - if (apiResult.IsFailed) { - _dcInfoCache.TryAdd(domainName.ToUpper().Trim(), null); - return false; - } + if (!string.IsNullOrWhiteSpace(queryParameters.RelativeSearchBase)) { + basePath = $"{queryParameters.RelativeSearchBase},{basePath}"; + } + } - info = apiResult.Value; - return true; - } + searchRequest = new SearchRequest(basePath, queryParameters.LDAPFilter, queryParameters.SearchScope, + queryParameters.Attributes); + searchRequest.Controls.Add(new SearchOptionsControl(SearchOption.DomainScope)); + if (queryParameters.IncludeDeleted) { + searchRequest.Controls.Add(new ShowDeletedControl()); + } - private async Task SetupLdapQuery(LdapQueryParameters queryParameters) { - var result = new LdapQuerySetupResult(); - var (success, connectionWrapper, message) = - await _connectionPool.GetLdapConnection(queryParameters.DomainName, queryParameters.GlobalCatalog); - if (!success) { - result.Success = false; - result.Message = $"Unable to create a connection: {message}"; - return result; - } + if (queryParameters.IncludeSecurityDescriptor) { + searchRequest.Controls.Add(new SecurityDescriptorFlagControl { + SecurityMasks = SecurityMasks.Dacl | SecurityMasks.Owner + }); + } - //This should never happen as far as I know, so just checking for safety - if (connectionWrapper.Connection == null) { - result.Success = false; - result.Message = $"Connection object is null"; - return result; + return true; } - if (!CreateSearchRequest(queryParameters, connectionWrapper, out var searchRequest)) { - result.Success = false; - result.Message = "Failed to create search request"; - return result; - } + private bool CallDsGetDcName(string domainName, out NetAPIStructs.DomainControllerInfo? info) { + if (_dcInfoCache.TryGetValue(domainName.ToUpper().Trim(), out info)) return info != null; - result.Server = connectionWrapper.GetServer(); - result.Success = true; - result.SearchRequest = searchRequest; - result.ConnectionWrapper = connectionWrapper; - return result; - } + var apiResult = _nativeMethods.CallDsGetDcName(null, domainName, + (uint)(NetAPIEnums.DSGETDCNAME_FLAGS.DS_FORCE_REDISCOVERY | + NetAPIEnums.DSGETDCNAME_FLAGS.DS_RETURN_DNS_NAME | + NetAPIEnums.DSGETDCNAME_FLAGS.DS_DIRECTORY_SERVICE_REQUIRED)); - private SearchRequest CreateSearchRequest(string distinguishedName, string ldapFilter, - SearchScope searchScope, - string[] attributes) { - var searchRequest = new SearchRequest(distinguishedName, ldapFilter, - searchScope, attributes); - searchRequest.Controls.Add(new SearchOptionsControl(SearchOption.DomainScope)); - return searchRequest; - } + if (apiResult.IsFailed) { + _dcInfoCache.TryAdd(domainName.ToUpper().Trim(), null); + return false; + } - public async Task<(bool Success, string DomainName)> GetDomainNameFromSid(string sid) { - string domainSid; - try { - domainSid = new SecurityIdentifier(sid).AccountDomainSid?.Value.ToUpper(); - } - catch { - var match = _sidRegex.Match(sid); - domainSid = match.Success ? match.Groups[1].Value : null; + info = apiResult.Value; + return true; } - if (domainSid == null) { - return (false, ""); - } + private async Task SetupLdapQuery(LdapQueryParameters queryParameters) { + var result = new LdapQuerySetupResult(); + var (success, connectionWrapper, message) = + await _connectionPool.GetLdapConnection(queryParameters.DomainName, queryParameters.GlobalCatalog); + if (!success) { + result.Success = false; + result.Message = $"Unable to create a connection: {message}"; + return result; + } - if (Cache.GetDomainSidMapping(domainSid, out var domain)) { - return (true, domain); - } + //This should never happen as far as I know, so just checking for safety + if (connectionWrapper.Connection == null) { + result.Success = false; + result.Message = $"Connection object is null"; + return result; + } - try { - var entry = new DirectoryEntry($"LDAP://"); - entry.RefreshCache(new[] { LDAPProperties.DistinguishedName }); - var dn = entry.GetProperty(LDAPProperties.DistinguishedName); - if (!string.IsNullOrWhiteSpace(dn)) { - Cache.AddDomainSidMapping(domainSid, Helpers.DistinguishedNameToDomain(dn)); - return (true, Helpers.DistinguishedNameToDomain(dn)); + if (!CreateSearchRequest(queryParameters, connectionWrapper, out var searchRequest)) { + result.Success = false; + result.Message = "Failed to create search request"; + return result; } - } - catch { - //pass + + result.Server = connectionWrapper.GetServer(); + result.Success = true; + result.SearchRequest = searchRequest; + result.ConnectionWrapper = connectionWrapper; + return result; } - if (await ConvertDomainSidToDomainNameFromLdap(sid) is (true, var domainName)) { - Cache.AddDomainSidMapping(domainSid, domainName); - return (true, domainName); + private SearchRequest CreateSearchRequest(string distinguishedName, string ldapFilter, + SearchScope searchScope, + string[] attributes) { + var searchRequest = new SearchRequest(distinguishedName, ldapFilter, + searchScope, attributes); + searchRequest.Controls.Add(new SearchOptionsControl(SearchOption.DomainScope)); + return searchRequest; } - using (var ctx = new PrincipalContext(ContextType.Domain)) { + public async Task<(bool Success, string DomainName)> GetDomainNameFromSid(string sid) { + string domainSid; try { - var principal = Principal.FindByIdentity(ctx, IdentityType.Sid, sid); - if (principal != null) { - var dn = principal.DistinguishedName; - if (!string.IsNullOrWhiteSpace(dn)) { - Cache.AddDomainSidMapping(domainSid, Helpers.DistinguishedNameToDomain(dn)); - return (true, Helpers.DistinguishedNameToDomain(dn)); - } + domainSid = new SecurityIdentifier(sid).AccountDomainSid?.Value.ToUpper(); + } + catch { + var match = _sidRegex.Match(sid); + domainSid = match.Success ? match.Groups[1].Value : null; + } + + if (domainSid == null) { + return (false, ""); + } + + if (Cache.GetDomainSidMapping(domainSid, out var domain)) { + return (true, domain); + } + + try { + var entry = new DirectoryEntry($"LDAP://"); + entry.RefreshCache(new[] { LDAPProperties.DistinguishedName }); + var dn = entry.GetProperty(LDAPProperties.DistinguishedName); + if (!string.IsNullOrWhiteSpace(dn)) { + Cache.AddDomainSidMapping(domainSid, Helpers.DistinguishedNameToDomain(dn)); + return (true, Helpers.DistinguishedNameToDomain(dn)); } } catch { //pass } - } - - return (false, string.Empty); - } - private async Task<(bool Success, string DomainName)> ConvertDomainSidToDomainNameFromLdap(string domainSid) { - if (!GetDomain(out var domain) || domain?.Name == null) { - return (false, string.Empty); - } + if (await ConvertDomainSidToDomainNameFromLdap(sid) is (true, var domainName)) { + Cache.AddDomainSidMapping(domainSid, domainName); + return (true, domainName); + } - var result = await Query(new LdapQueryParameters { - DomainName = domain.Name, - Attributes = new[] { LDAPProperties.DistinguishedName }, - GlobalCatalog = true, - LDAPFilter = new LDAPFilter().AddDomains(CommonFilters.SpecificSID(domainSid)).GetFilter() - }).FirstAsync(); + using (var ctx = new PrincipalContext(ContextType.Domain)) { + try { + var principal = Principal.FindByIdentity(ctx, IdentityType.Sid, sid); + if (principal != null) { + var dn = principal.DistinguishedName; + if (!string.IsNullOrWhiteSpace(dn)) { + Cache.AddDomainSidMapping(domainSid, Helpers.DistinguishedNameToDomain(dn)); + return (true, Helpers.DistinguishedNameToDomain(dn)); + } + } + } + catch { + //pass + } + } - if (result.IsSuccess) { - return (true, Helpers.DistinguishedNameToDomain(result.Value.DistinguishedName)); + return (false, string.Empty); } - result = await Query(new LdapQueryParameters { - DomainName = domain.Name, - Attributes = new[] { LDAPProperties.DistinguishedName }, - GlobalCatalog = true, - LDAPFilter = new LDAPFilter().AddFilter("(objectclass=trusteddomain)", true) - .AddFilter($"(securityidentifier={Helpers.ConvertSidToHexSid(domainSid)})", true).GetFilter() - }).FirstAsync(); + private async Task<(bool Success, string DomainName)> ConvertDomainSidToDomainNameFromLdap(string domainSid) { + if (!GetDomain(out var domain) || domain?.Name == null) { + return (false, string.Empty); + } - if (result.IsSuccess) { - return (true, Helpers.DistinguishedNameToDomain(result.Value.DistinguishedName)); - } + var result = await Query(new LdapQueryParameters { + DomainName = domain.Name, + Attributes = new[] { LDAPProperties.DistinguishedName }, + GlobalCatalog = true, + LDAPFilter = new LDAPFilter().AddDomains(CommonFilters.SpecificSID(domainSid)).GetFilter() + }).FirstAsync(); - result = await Query(new LdapQueryParameters { - DomainName = domain.Name, - Attributes = new[] { LDAPProperties.DistinguishedName }, - LDAPFilter = new LDAPFilter().AddFilter("(objectclass=domaindns)", true) - .AddFilter(CommonFilters.SpecificSID(domainSid), true).GetFilter() - }).FirstAsync(); + if (result.IsSuccess) { + return (true, Helpers.DistinguishedNameToDomain(result.Value.DistinguishedName)); + } - if (result.IsSuccess) { - return (true, Helpers.DistinguishedNameToDomain(result.Value.DistinguishedName)); - } + result = await Query(new LdapQueryParameters { + DomainName = domain.Name, + Attributes = new[] { LDAPProperties.DistinguishedName }, + GlobalCatalog = true, + LDAPFilter = new LDAPFilter().AddFilter("(objectclass=trusteddomain)", true) + .AddFilter($"(securityidentifier={Helpers.ConvertSidToHexSid(domainSid)})", true).GetFilter() + }).FirstAsync(); - return (false, string.Empty); - } + if (result.IsSuccess) { + return (true, Helpers.DistinguishedNameToDomain(result.Value.DistinguishedName)); + } - public async Task<(bool Success, string DomainSid)> GetDomainSidFromDomainName(string domainName) { - if (Cache.GetDomainSidMapping(domainName, out var domainSid)) return (true, domainSid); + result = await Query(new LdapQueryParameters { + DomainName = domain.Name, + Attributes = new[] { LDAPProperties.DistinguishedName }, + LDAPFilter = new LDAPFilter().AddFilter("(objectclass=domaindns)", true) + .AddFilter(CommonFilters.SpecificSID(domainSid), true).GetFilter() + }).FirstAsync(); - try { - var entry = new DirectoryEntry($"LDAP://{domainName}"); - //Force load objectsid into the object cache - entry.RefreshCache(new[] { "objectSid" }); - var sid = entry.GetSid(); - if (sid != null) { - Cache.AddDomainSidMapping(domainName, sid); - domainSid = sid; - return (true, domainSid); + if (result.IsSuccess) { + return (true, Helpers.DistinguishedNameToDomain(result.Value.DistinguishedName)); } + + return (false, string.Empty); } - catch { - //we expect this to fail sometimes - } - if (GetDomain(domainName, out var domainObject)) + public async Task<(bool Success, string DomainSid)> GetDomainSidFromDomainName(string domainName) { + if (Cache.GetDomainSidMapping(domainName, out var domainSid)) return (true, domainSid); + try { - domainSid = domainObject.GetDirectoryEntry().GetSid(); - if (domainSid != null) { - Cache.AddDomainSidMapping(domainName, domainSid); + var entry = new DirectoryEntry($"LDAP://{domainName}"); + //Force load objectsid into the object cache + entry.RefreshCache(new[] { "objectSid" }); + var sid = entry.GetSid(); + if (sid != null) { + Cache.AddDomainSidMapping(domainName, sid); + domainSid = sid; return (true, domainSid); } } catch { - //we expect this to fail sometimes (not sure why, but better safe than sorry) + //we expect this to fail sometimes } - foreach (var name in _translateNames) + if (GetDomain(domainName, out var domainObject)) + try { + domainSid = domainObject.GetDirectoryEntry().GetSid(); + if (domainSid != null) { + Cache.AddDomainSidMapping(domainName, domainSid); + return (true, domainSid); + } + } + catch { + //we expect this to fail sometimes (not sure why, but better safe than sorry) + } + + foreach (var name in _translateNames) + try { + var account = new NTAccount(domainName, name); + var sid = (SecurityIdentifier)account.Translate(typeof(SecurityIdentifier)); + domainSid = sid.AccountDomainSid.ToString(); + Cache.AddDomainSidMapping(domainName, domainSid); + return (true, domainSid); + } + catch { + //We expect this to fail if the username doesn't exist in the domain + } + + var result = await Query(new LdapQueryParameters() { + DomainName = domainName, + Attributes = new[] { LDAPProperties.ObjectSID }, + LDAPFilter = new LDAPFilter().AddFilter(CommonFilters.DomainControllers, true).GetFilter() + }).FirstAsync(); + + if (result.IsSuccess) { + var sid = result.Value.GetSid(); + if (!string.IsNullOrEmpty(sid)) { + domainSid = new SecurityIdentifier(sid).AccountDomainSid.Value; + Cache.AddDomainSidMapping(domainName, domainSid); + return (true, domainSid); + } + } + + return (false, string.Empty); + } + + /// + /// Attempts to get the Domain object representing the target domain. If null is specified for the domain name, gets + /// the user's current domain + /// + /// + /// + /// + public bool GetDomain(string domainName, out Domain domain) { + var cacheKey = domainName ?? _nullCacheKey; + if (DomainCache.TryGetValue(cacheKey, out domain)) return true; + try { - var account = new NTAccount(domainName, name); - var sid = (SecurityIdentifier)account.Translate(typeof(SecurityIdentifier)); - domainSid = sid.AccountDomainSid.ToString(); - Cache.AddDomainSidMapping(domainName, domainSid); - return (true, domainSid); + DirectoryContext context; + if (_ldapConfig.Username != null) + context = domainName != null + ? new DirectoryContext(DirectoryContextType.Domain, domainName, _ldapConfig.Username, + _ldapConfig.Password) + : new DirectoryContext(DirectoryContextType.Domain, _ldapConfig.Username, + _ldapConfig.Password); + else + context = domainName != null + ? new DirectoryContext(DirectoryContextType.Domain, domainName) + : new DirectoryContext(DirectoryContextType.Domain); + + domain = Domain.GetDomain(context); + if (domain == null) return false; + DomainCache.TryAdd(cacheKey, domain); + return true; } - catch { - //We expect this to fail if the username doesn't exist in the domain + catch (Exception e) { + _log.LogDebug(e, "GetDomain call failed for domain name {Name}", domainName); + return false; } + } - var result = await Query(new LdapQueryParameters() { - DomainName = domainName, - Attributes = new[] { LDAPProperties.ObjectSID }, - LDAPFilter = new LDAPFilter().AddFilter(CommonFilters.DomainControllers, true).GetFilter() - }).FirstAsync(); + public static bool GetDomain(string domainName, LDAPConfig ldapConfig, out Domain domain) { + if (DomainCache.TryGetValue(domainName, out domain)) return true; - if (result.IsSuccess) { - var sid = result.Value.GetSid(); - if (!string.IsNullOrEmpty(sid)) { - domainSid = new SecurityIdentifier(sid).AccountDomainSid.Value; - Cache.AddDomainSidMapping(domainName, domainSid); - return (true, domainSid); + try { + DirectoryContext context; + if (ldapConfig.Username != null) + context = domainName != null + ? new DirectoryContext(DirectoryContextType.Domain, domainName, ldapConfig.Username, + ldapConfig.Password) + : new DirectoryContext(DirectoryContextType.Domain, ldapConfig.Username, + ldapConfig.Password); + else + context = domainName != null + ? new DirectoryContext(DirectoryContextType.Domain, domainName) + : new DirectoryContext(DirectoryContextType.Domain); + + domain = Domain.GetDomain(context); + if (domain == null) return false; + DomainCache.TryAdd(domainName, domain); + return true; + } + catch (Exception e) { + return false; } } - return (false, string.Empty); - } + /// + /// Attempts to get the Domain object representing the target domain. If null is specified for the domain name, gets + /// the user's current domain + /// + /// + /// + /// + public bool GetDomain(out Domain domain) { + var cacheKey = _nullCacheKey; + if (DomainCache.TryGetValue(cacheKey, out domain)) return true; - /// - /// Attempts to get the Domain object representing the target domain. If null is specified for the domain name, gets - /// the user's current domain - /// - /// - /// - /// - public bool GetDomain(string domainName, out Domain domain) { - var cacheKey = domainName ?? _nullCacheKey; - if (DomainCache.TryGetValue(cacheKey, out domain)) return true; - - try { - DirectoryContext context; - if (_ldapConfig.Username != null) - context = domainName != null - ? new DirectoryContext(DirectoryContextType.Domain, domainName, _ldapConfig.Username, + try { + var context = _ldapConfig.Username != null + ? new DirectoryContext(DirectoryContextType.Domain, _ldapConfig.Username, _ldapConfig.Password) - : new DirectoryContext(DirectoryContextType.Domain, _ldapConfig.Username, - _ldapConfig.Password); - else - context = domainName != null - ? new DirectoryContext(DirectoryContextType.Domain, domainName) - : new DirectoryContext(DirectoryContextType.Domain); - - domain = Domain.GetDomain(context); - if (domain == null) return false; - DomainCache.TryAdd(cacheKey, domain); - return true; - } - catch (Exception e) { - _log.LogDebug(e, "GetDomain call failed for domain name {Name}", domainName); - return false; - } - } - - public static bool GetDomain(string domainName, LDAPConfig ldapConfig, out Domain domain) { - if (DomainCache.TryGetValue(domainName, out domain)) return true; - - try { - DirectoryContext context; - if (ldapConfig.Username != null) - context = domainName != null - ? new DirectoryContext(DirectoryContextType.Domain, domainName, ldapConfig.Username, - ldapConfig.Password) - : new DirectoryContext(DirectoryContextType.Domain, ldapConfig.Username, - ldapConfig.Password); - else - context = domainName != null - ? new DirectoryContext(DirectoryContextType.Domain, domainName) : new DirectoryContext(DirectoryContextType.Domain); - domain = Domain.GetDomain(context); - if (domain == null) return false; - DomainCache.TryAdd(domainName, domain); - return true; - } - catch (Exception e) { - return false; + domain = Domain.GetDomain(context); + DomainCache.TryAdd(cacheKey, domain); + return true; + } + catch (Exception e) { + _log.LogDebug(e, "GetDomain call failed for blank domain"); + return false; + } } - } - /// - /// Attempts to get the Domain object representing the target domain. If null is specified for the domain name, gets - /// the user's current domain - /// - /// - /// - /// - public bool GetDomain(out Domain domain) { - var cacheKey = _nullCacheKey; - if (DomainCache.TryGetValue(cacheKey, out domain)) return true; - - try { - var context = _ldapConfig.Username != null - ? new DirectoryContext(DirectoryContextType.Domain, _ldapConfig.Username, - _ldapConfig.Password) - : new DirectoryContext(DirectoryContextType.Domain); - - domain = Domain.GetDomain(context); - DomainCache.TryAdd(cacheKey, domain); - return true; - } - catch (Exception e) { - _log.LogDebug(e, "GetDomain call failed for blank domain"); - return false; - } - } + public async Task<(bool Success, TypedPrincipal Principal)> ResolveAccountName(string name, string domain) { + if (string.IsNullOrWhiteSpace(name)) { + return (false, null); + } - public async Task<(bool Success, TypedPrincipal Principal)> ResolveAccountName(string name, string domain) { - if (string.IsNullOrWhiteSpace(name)) { - return (false, null); - } + if (Cache.GetPrefixedValue(name, domain, out var id) && Cache.GetIDType(id, out var type)) + return (true, new TypedPrincipal { + ObjectIdentifier = id, + ObjectType = type + }); - if (Cache.GetPrefixedValue(name, domain, out var id) && Cache.GetIDType(id, out var type)) - return (true, new TypedPrincipal { - ObjectIdentifier = id, - ObjectType = type - }); + var result = await Query(new LdapQueryParameters() { + DomainName = domain, + Attributes = CommonProperties.TypeResolutionProps, + LDAPFilter = $"(samaccountname={name})" + }).FirstAsync(); - var result = await Query(new LdapQueryParameters() { - DomainName = domain, - Attributes = CommonProperties.TypeResolutionProps, - LDAPFilter = $"(samaccountname={name})" - }).FirstAsync(); + if (result.IsSuccess) { + type = result.Value.GetLabel(); + id = result.Value.GetObjectIdentifier(); - if (result.IsSuccess) { - type = result.Value.GetLabel(); - id = result.Value.GetObjectIdentifier(); + if (!string.IsNullOrWhiteSpace(id)) { + Cache.AddPrefixedValue(name, domain, id); + Cache.AddType(id, type); + } - if (!string.IsNullOrWhiteSpace(id)) { - Cache.AddPrefixedValue(name, domain, id); - Cache.AddType(id, type); + var (tempID, _) = await GetWellKnownPrincipalObjectIdentifier(id, domain); + return (true, new TypedPrincipal(tempID, type)); } - var (tempID, _) = await GetWellKnownPrincipalObjectIdentifier(id, domain); - return (true, new TypedPrincipal(tempID, type)); + return (false, null); } - return (false, null); - } + public async Task<(bool Success, string SecurityIdentifier)> ResolveHostToSid(string host, string domain) { + //Remove SPN prefixes from the host name so we're working with a clean name + var strippedHost = Helpers.StripServicePrincipalName(host).ToUpper().TrimEnd('$'); + if (string.IsNullOrEmpty(strippedHost)) { + return (false, string.Empty); + } - public async Task<(bool Success, string SecurityIdentifier)> ResolveHostToSid(string host, string domain) { - //Remove SPN prefixes from the host name so we're working with a clean name - var strippedHost = Helpers.StripServicePrincipalName(host).ToUpper().TrimEnd('$'); - if (string.IsNullOrEmpty(strippedHost)) { - return (false, string.Empty); - } + if (_hostResolutionMap.TryGetValue(strippedHost, out var sid)) return (true, sid); - if (_hostResolutionMap.TryGetValue(strippedHost, out var sid)) return (true, sid); + //Immediately start with NetWekstaGetInfo as its our most reliable indicator if successful + var workstationInfo = await GetWorkstationInfo(strippedHost); + if (workstationInfo.HasValue) { + var tempName = workstationInfo.Value.ComputerName; + var tempDomain = workstationInfo.Value.LanGroup; - //Immediately start with NetWekstaGetInfo as its our most reliable indicator if successful - var workstationInfo = await GetWorkstationInfo(strippedHost); - if (workstationInfo.HasValue) { - var tempName = workstationInfo.Value.ComputerName; - var tempDomain = workstationInfo.Value.LanGroup; + if (string.IsNullOrWhiteSpace(tempDomain)) { + tempDomain = domain; + } - if (string.IsNullOrWhiteSpace(tempDomain)) { - tempDomain = domain; + if (!string.IsNullOrWhiteSpace(tempName)) { + tempName = $"{tempName}$".ToUpper(); + if (await ResolveAccountName(tempName, tempDomain) is (true, var principal)) { + _hostResolutionMap.TryAdd(strippedHost, principal.ObjectIdentifier); + return (true, principal.ObjectIdentifier); + } + } } - if (!string.IsNullOrWhiteSpace(tempName)) { - tempName = $"{tempName}$".ToUpper(); - if (await ResolveAccountName(tempName, tempDomain) is (true, var principal)) { - _hostResolutionMap.TryAdd(strippedHost, principal.ObjectIdentifier); - return (true, principal.ObjectIdentifier); + //Try some socket magic to get the NETBIOS name + if (RequestNETBIOSNameFromComputer(strippedHost, domain, out var netBiosName)) { + if (!string.IsNullOrWhiteSpace(netBiosName)) { + var result = await ResolveAccountName($"{netBiosName}$", domain); + if (result.Success) { + _hostResolutionMap.TryAdd(strippedHost, result.Principal.ObjectIdentifier); + return (true, result.Principal.ObjectIdentifier); + } } } - } - //Try some socket magic to get the NETBIOS name - if (RequestNETBIOSNameFromComputer(strippedHost, domain, out var netBiosName)) { - if (!string.IsNullOrWhiteSpace(netBiosName)) { - var result = await ResolveAccountName($"{netBiosName}$", domain); - if (result.Success) { - _hostResolutionMap.TryAdd(strippedHost, result.Principal.ObjectIdentifier); - return (true, result.Principal.ObjectIdentifier); + //Start by handling non-IP address names + if (!IPAddress.TryParse(strippedHost, out _)) { + //PRIMARY.TESTLAB.LOCAL + if (strippedHost.Contains(".")) { + var split = strippedHost.Split('.'); + var name = split[0]; + var result = await ResolveAccountName($"{name}$", domain); + if (result.Success) { + _hostResolutionMap.TryAdd(strippedHost, result.Principal.ObjectIdentifier); + return (true, result.Principal.ObjectIdentifier); + } + + var tempDomain = string.Join(".", split.Skip(1).ToArray()); + result = await ResolveAccountName($"{name}$", tempDomain); + if (result.Success) { + _hostResolutionMap.TryAdd(strippedHost, result.Principal.ObjectIdentifier); + return (true, result.Principal.ObjectIdentifier); + } + } + else { + //Format: WIN10 (probably a netbios name) + var result = await ResolveAccountName($"{strippedHost}$", domain); + if (result.Success) { + _hostResolutionMap.TryAdd(strippedHost, result.Principal.ObjectIdentifier); + return (true, result.Principal.ObjectIdentifier); + } } } - } - //Start by handling non-IP address names - if (!IPAddress.TryParse(strippedHost, out _)) { - //PRIMARY.TESTLAB.LOCAL - if (strippedHost.Contains(".")) { - var split = strippedHost.Split('.'); + try { + var resolvedHostname = (await Dns.GetHostEntryAsync(strippedHost)).HostName; + var split = resolvedHostname.Split('.'); var name = split[0]; var result = await ResolveAccountName($"{name}$", domain); if (result.Success) { @@ -1124,262 +1150,236 @@ public bool GetDomain(out Domain domain) { return (true, result.Principal.ObjectIdentifier); } } - else { - //Format: WIN10 (probably a netbios name) - var result = await ResolveAccountName($"{strippedHost}$", domain); - if (result.Success) { - _hostResolutionMap.TryAdd(strippedHost, result.Principal.ObjectIdentifier); - return (true, result.Principal.ObjectIdentifier); - } - } - } - - try { - var resolvedHostname = (await Dns.GetHostEntryAsync(strippedHost)).HostName; - var split = resolvedHostname.Split('.'); - var name = split[0]; - var result = await ResolveAccountName($"{name}$", domain); - if (result.Success) { - _hostResolutionMap.TryAdd(strippedHost, result.Principal.ObjectIdentifier); - return (true, result.Principal.ObjectIdentifier); + catch { + //pass } - var tempDomain = string.Join(".", split.Skip(1).ToArray()); - result = await ResolveAccountName($"{name}$", tempDomain); - if (result.Success) { - _hostResolutionMap.TryAdd(strippedHost, result.Principal.ObjectIdentifier); - return (true, result.Principal.ObjectIdentifier); - } - } - catch { - //pass + return (false, ""); } - return (false, ""); - } + /// + /// Calls the NetWkstaGetInfo API on a hostname + /// + /// + /// + private async Task GetWorkstationInfo(string hostname) { + if (!await _portScanner.CheckPort(hostname)) + return null; - /// - /// Calls the NetWkstaGetInfo API on a hostname - /// - /// - /// - private async Task GetWorkstationInfo(string hostname) { - if (!await _portScanner.CheckPort(hostname)) - return null; - - var result = _nativeMethods.CallNetWkstaGetInfo(hostname); - if (result.IsSuccess) return result.Value; - - return null; - } + var result = _nativeMethods.CallNetWkstaGetInfo(hostname); + if (result.IsSuccess) return result.Value; - public async Task<(bool Success, string[] Sids)> GetGlobalCatalogMatches(string name, string domain) { - if (Cache.GetGCCache(name, out var matches)) { - return (true, matches); + return null; } - var sids = new List(); + public async Task<(bool Success, string[] Sids)> GetGlobalCatalogMatches(string name, string domain) { + if (Cache.GetGCCache(name, out var matches)) { + return (true, matches); + } - await foreach (var result in Query(new LdapQueryParameters { - DomainName = domain, - Attributes = new[] { LDAPProperties.ObjectSID }, - GlobalCatalog = true, - LDAPFilter = new LDAPFilter().AddUsers($"(samaccountname={name})").GetFilter() - })) { - if (result.IsSuccess) { - var sid = result.Value.GetSid(); - if (!string.IsNullOrWhiteSpace(sid)) { - sids.Add(sid); + var sids = new List(); + + await foreach (var result in Query(new LdapQueryParameters { + DomainName = domain, + Attributes = new[] { LDAPProperties.ObjectSID }, + GlobalCatalog = true, + LDAPFilter = new LDAPFilter().AddUsers($"(samaccountname={name})").GetFilter() + })) { + if (result.IsSuccess) { + var sid = result.Value.GetSid(); + if (!string.IsNullOrWhiteSpace(sid)) { + sids.Add(sid); + } + } + else { + return (false, Array.Empty()); } } - else { - return (false, Array.Empty()); - } - } - - return (true, sids.ToArray()); - } - public async Task<(bool Success, TypedPrincipal Principal)> ResolveCertTemplateByProperty(string propertyValue, - string propertyName, string domainName) { - var filter = new LDAPFilter().AddCertificateTemplates().AddFilter($"({propertyName}={propertyValue})", true); - var result = await Query(new LdapQueryParameters { - DomainName = domainName, - Attributes = CommonProperties.TypeResolutionProps, - SearchScope = SearchScope.OneLevel, - NamingContext = NamingContext.Configuration, - RelativeSearchBase = DirectoryPaths.CertTemplateLocation, - LDAPFilter = filter.GetFilter(), - }).DefaultIfEmpty(null).FirstAsync(); - - if (result == null) { - _log.LogWarning("Could not find certificate template with {PropertyName}:{PropertyValue}", - propertyName, propertyName); - return (false, null); + return (true, sids.ToArray()); } - if (!result.IsSuccess) { - _log.LogWarning( - "Could not find certificate template with {PropertyName}:{PropertyValue}: {Error}", - propertyName, propertyName, result.Error); - return (false, null); - } + public async Task<(bool Success, TypedPrincipal Principal)> ResolveCertTemplateByProperty(string propertyValue, + string propertyName, string domainName) { + var filter = new LDAPFilter().AddCertificateTemplates().AddFilter($"({propertyName}={propertyValue})", true); + var result = await Query(new LdapQueryParameters { + DomainName = domainName, + Attributes = CommonProperties.TypeResolutionProps, + SearchScope = SearchScope.OneLevel, + NamingContext = NamingContext.Configuration, + RelativeSearchBase = DirectoryPaths.CertTemplateLocation, + LDAPFilter = filter.GetFilter(), + }).DefaultIfEmpty(null).FirstAsync(); - var entry = result.Value; - return (true, new TypedPrincipal(entry.GetGuid(), Label.CertTemplate)); - } + if (result == null) { + _log.LogWarning("Could not find certificate template with {PropertyName}:{PropertyValue}", + propertyName, propertyName); + return (false, null); + } - /// - /// Uses a socket and a set of bytes to request the NETBIOS name from a remote computer - /// - /// - /// - /// - /// - private static bool RequestNETBIOSNameFromComputer(string server, string domain, out string netbios) { - var receiveBuffer = new byte[1024]; - var requestSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); - try { - //Set receive timeout to 1 second - requestSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, 1000); - EndPoint remoteEndpoint; - - //We need to create an endpoint to bind too. If its an IP, just use that. - if (IPAddress.TryParse(server, out var parsedAddress)) - remoteEndpoint = new IPEndPoint(parsedAddress, 137); - else - //If its not an IP, we're going to try and resolve it from DNS - try { - IPAddress address; - if (server.Contains(".")) - address = Dns - .GetHostAddresses(server).First(x => x.AddressFamily == AddressFamily.InterNetwork); - else - address = Dns.GetHostAddresses($"{server}.{domain}")[0]; - - if (address == null) { + if (!result.IsSuccess) { + _log.LogWarning( + "Could not find certificate template with {PropertyName}:{PropertyValue}: {Error}", + propertyName, propertyName, result.Error); + return (false, null); + } + + var entry = result.Value; + return (true, new TypedPrincipal(entry.GetGuid(), Label.CertTemplate)); + } + + /// + /// Uses a socket and a set of bytes to request the NETBIOS name from a remote computer + /// + /// + /// + /// + /// + private static bool RequestNETBIOSNameFromComputer(string server, string domain, out string netbios) { + var receiveBuffer = new byte[1024]; + var requestSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); + try { + //Set receive timeout to 1 second + requestSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, 1000); + EndPoint remoteEndpoint; + + //We need to create an endpoint to bind too. If its an IP, just use that. + if (IPAddress.TryParse(server, out var parsedAddress)) + remoteEndpoint = new IPEndPoint(parsedAddress, 137); + else + //If its not an IP, we're going to try and resolve it from DNS + try { + IPAddress address; + if (server.Contains(".")) + address = Dns + .GetHostAddresses(server).First(x => x.AddressFamily == AddressFamily.InterNetwork); + else + address = Dns.GetHostAddresses($"{server}.{domain}")[0]; + + if (address == null) { + netbios = null; + return false; + } + + remoteEndpoint = new IPEndPoint(address, 137); + } + catch { + //Failed to resolve an IP, so return null netbios = null; return false; } - remoteEndpoint = new IPEndPoint(address, 137); - } - catch { - //Failed to resolve an IP, so return null + var originEndpoint = new IPEndPoint(IPAddress.Any, 0); + requestSocket.Bind(originEndpoint); + + try { + requestSocket.SendTo(NameRequest, remoteEndpoint); + var receivedByteCount = requestSocket.ReceiveFrom(receiveBuffer, ref remoteEndpoint); + if (receivedByteCount >= 90) { + netbios = new ASCIIEncoding().GetString(receiveBuffer, 57, 16).Trim('\0', ' '); + return true; + } + netbios = null; return false; } - - var originEndpoint = new IPEndPoint(IPAddress.Any, 0); - requestSocket.Bind(originEndpoint); - - try { - requestSocket.SendTo(NameRequest, remoteEndpoint); - var receivedByteCount = requestSocket.ReceiveFrom(receiveBuffer, ref remoteEndpoint); - if (receivedByteCount >= 90) { - netbios = new ASCIIEncoding().GetString(receiveBuffer, 57, 16).Trim('\0', ' '); - return true; + catch (SocketException) { + netbios = null; + return false; } - - netbios = null; - return false; } - catch (SocketException) { - netbios = null; - return false; + finally { + //Make sure we close the socket if its open + requestSocket.Close(); } } - finally { - //Make sure we close the socket if its open - requestSocket.Close(); - } - } - /// - /// Created for testing purposes - /// - /// - public ActiveDirectorySecurityDescriptor MakeSecurityDescriptor() { - return new ActiveDirectorySecurityDescriptor(new ActiveDirectorySecurity()); - } - - public async Task<(bool Success, TypedPrincipal Principal)> ConvertLocalWellKnownPrincipal(SecurityIdentifier sid, - string computerDomainSid, string computerDomain) { - if (!WellKnownPrincipal.GetWellKnownPrincipal(sid.Value, out var common)) return (false, null); - //The everyone and auth users principals are special and will be converted to the domain equivalent - if (sid.Value is "S-1-1-0" or "S-1-5-11") { - return await GetWellKnownPrincipal(sid.Value, computerDomain); + /// + /// Created for testing purposes + /// + /// + public ActiveDirectorySecurityDescriptor MakeSecurityDescriptor() { + return new ActiveDirectorySecurityDescriptor(new ActiveDirectorySecurity()); } - //Use the computer object id + the RID of the sid we looked up to create our new principal - var principal = new TypedPrincipal { - ObjectIdentifier = $"{computerDomainSid}-{sid.Rid()}", - ObjectType = common.ObjectType switch { - Label.User => Label.LocalUser, - Label.Group => Label.LocalGroup, - _ => common.ObjectType + public async Task<(bool Success, TypedPrincipal Principal)> ConvertLocalWellKnownPrincipal(SecurityIdentifier sid, + string computerDomainSid, string computerDomain) { + if (!WellKnownPrincipal.GetWellKnownPrincipal(sid.Value, out var common)) return (false, null); + //The everyone and auth users principals are special and will be converted to the domain equivalent + if (sid.Value is "S-1-1-0" or "S-1-5-11") { + return await GetWellKnownPrincipal(sid.Value, computerDomain); } - }; - return (true, principal); - } - - public async Task IsDomainController(string computerObjectId, string domainName) { - var resDomain = await GetDomainNameFromSid(domainName) is (false, var tempDomain) ? tempDomain : domainName; - var filter = new LDAPFilter().AddFilter(CommonFilters.SpecificSID(computerObjectId), true) - .AddFilter(CommonFilters.DomainControllers, true); - var result = await Query(new LdapQueryParameters() { - DomainName = resDomain, - Attributes = CommonProperties.ObjectID, - LDAPFilter = filter.GetFilter(), - }).DefaultIfEmpty(null).FirstOrDefaultAsync(); - return result is { IsSuccess: true }; - } + //Use the computer object id + the RID of the sid we looked up to create our new principal + var principal = new TypedPrincipal { + ObjectIdentifier = $"{computerDomainSid}-{sid.Rid()}", + ObjectType = common.ObjectType switch { + Label.User => Label.LocalUser, + Label.Group => Label.LocalGroup, + _ => common.ObjectType + } + }; - public async Task<(bool Success, TypedPrincipal Principal)> LookupDistinguishedName(string distinguishedName) { - if (_distinguishedNameCache.TryGetValue(distinguishedName, out var principal)) { return (true, principal); } - var domain = Helpers.DistinguishedNameToDomain(distinguishedName); - var result = await Query(new LdapQueryParameters { - DomainName = domain, - Attributes = CommonProperties.TypeResolutionProps, - SearchBase = distinguishedName, - SearchScope = SearchScope.Base, - LDAPFilter = new LDAPFilter().AddAllObjects().GetFilter() - }).DefaultIfEmpty(null).FirstOrDefaultAsync(); + public async Task IsDomainController(string computerObjectId, string domainName) { + var resDomain = await GetDomainNameFromSid(domainName) is (false, var tempDomain) ? tempDomain : domainName; + var filter = new LDAPFilter().AddFilter(CommonFilters.SpecificSID(computerObjectId), true) + .AddFilter(CommonFilters.DomainControllers, true); + var result = await Query(new LdapQueryParameters() { + DomainName = resDomain, + Attributes = CommonProperties.ObjectID, + LDAPFilter = filter.GetFilter(), + }).DefaultIfEmpty(null).FirstOrDefaultAsync(); + return result is { IsSuccess: true }; + } + + public async Task<(bool Success, TypedPrincipal Principal)> LookupDistinguishedName(string distinguishedName) { + if (_distinguishedNameCache.TryGetValue(distinguishedName, out var principal)) { + return (true, principal); + } + + var domain = Helpers.DistinguishedNameToDomain(distinguishedName); + var result = await Query(new LdapQueryParameters { + DomainName = domain, + Attributes = CommonProperties.TypeResolutionProps, + SearchBase = distinguishedName, + SearchScope = SearchScope.Base, + LDAPFilter = new LDAPFilter().AddAllObjects().GetFilter() + }).DefaultIfEmpty(null).FirstOrDefaultAsync(); + + if (result is { IsSuccess: true }) { + var entry = result.Value; + var id = entry.GetObjectIdentifier(); + if (id == null) { + return (false, default); + } - if (result is { IsSuccess: true }) { - var entry = result.Value; - var id = entry.GetObjectIdentifier(); - if (id == null) { - return (false, default); - } + if (await GetWellKnownPrincipal(id, domain) is (true, var wellKnownPrincipal)) { + _distinguishedNameCache.TryAdd(distinguishedName, wellKnownPrincipal); + return (true, wellKnownPrincipal); + } - if (await GetWellKnownPrincipal(id, domain) is (true, var wellKnownPrincipal)) { - _distinguishedNameCache.TryAdd(distinguishedName, wellKnownPrincipal); - return (true, wellKnownPrincipal); + var type = entry.GetLabel(); + principal = new TypedPrincipal(id, type); + _distinguishedNameCache.TryAdd(distinguishedName, principal); + return (true, principal); } - var type = entry.GetLabel(); - principal = new TypedPrincipal(id, type); - _distinguishedNameCache.TryAdd(distinguishedName, principal); - return (true, principal); - } + using (var ctx = new PrincipalContext(ContextType.Domain)) { + try { + var lookupPrincipal = Principal.FindByIdentity(ctx, IdentityType.DistinguishedName, distinguishedName); + if (lookupPrincipal != null && + ((DirectoryEntry)lookupPrincipal.GetUnderlyingObject()).GetTypedPrincipal(out principal)) { + return (true, principal); + } - using (var ctx = new PrincipalContext(ContextType.Domain)) { - try { - var lookupPrincipal = Principal.FindByIdentity(ctx, IdentityType.DistinguishedName, distinguishedName); - if (lookupPrincipal != null && - ((DirectoryEntry)lookupPrincipal.GetUnderlyingObject()).GetTypedPrincipal(out principal)) { - return (true, principal); + return (false, default); + } + catch { + return (false, default); } - - return (false, default); - } - catch { - return (false, default); } } } diff --git a/src/CommonLib/Result.cs b/src/CommonLib/Result.cs index 5f29c304..d699b26d 100644 --- a/src/CommonLib/Result.cs +++ b/src/CommonLib/Result.cs @@ -1,41 +1,41 @@ -namespace SharpHoundCommonLib; - -public class Result : Result { - public T Value { get; set; } +namespace SharpHoundCommonLib { + public class Result : Result { + public T Value { get; set; } - protected internal Result(T value, bool success, string error) : base(success, error) { - Value = value; - } + protected internal Result(T value, bool success, string error) : base(success, error) { + Value = value; + } - public static Result Fail(string message) { - return new Result(default, false, message); - } + public static Result Fail(string message) { + return new Result(default, false, message); + } - public static Result Fail() { - return new Result(default, false, string.Empty); - } + public static Result Fail() { + return new Result(default, false, string.Empty); + } - public static Result Ok(T value) { - return new Result(value, true, string.Empty); + public static Result Ok(T value) { + return new Result(value, true, string.Empty); + } } -} -public class Result { + public class Result { - public string Error { get; set; } - public bool IsSuccess => Error == null && Success; - private bool Success { get; set; } + public string Error { get; set; } + public bool IsSuccess => Error == null && Success; + private bool Success { get; set; } - protected Result(bool success, string error) { - Success = success; - Error = error; - } + protected Result(bool success, string error) { + Success = success; + Error = error; + } - public static Result Fail(string message) { - return new Result(false, message); - } + public static Result Fail(string message) { + return new Result(false, message); + } - public static Result Ok() { - return new Result(true, string.Empty); + public static Result Ok() { + return new Result(true, string.Empty); + } } } \ No newline at end of file diff --git a/src/CommonLib/SearchResultEntryWrapperNew.cs b/src/CommonLib/SearchResultEntryWrapperNew.cs index be04cb3c..b031d892 100644 --- a/src/CommonLib/SearchResultEntryWrapperNew.cs +++ b/src/CommonLib/SearchResultEntryWrapperNew.cs @@ -3,77 +3,77 @@ using System.Security.Cryptography.X509Certificates; using SharpHoundCommonLib.Enums; -namespace SharpHoundCommonLib; +namespace SharpHoundCommonLib { + public class SearchResultEntryWrapperNew : ISearchResultEntry { + private readonly SearchResultEntry _entry; -public class SearchResultEntryWrapperNew : ISearchResultEntry { - private readonly SearchResultEntry _entry; - - public string DistinguishedName => _entry.DistinguishedName; - public ResolvedSearchResult ResolveBloodHoundInfo() { + public string DistinguishedName => _entry.DistinguishedName; + public ResolvedSearchResult ResolveBloodHoundInfo() { throw new System.NotImplementedException(); } - public string GetProperty(string propertyName) { + public string GetProperty(string propertyName) { throw new System.NotImplementedException(); } - public byte[] GetByteProperty(string propertyName) { + public byte[] GetByteProperty(string propertyName) { throw new System.NotImplementedException(); } - public string[] GetArrayProperty(string propertyName) { + public string[] GetArrayProperty(string propertyName) { throw new System.NotImplementedException(); } - public byte[][] GetByteArrayProperty(string propertyName) { + public byte[][] GetByteArrayProperty(string propertyName) { throw new System.NotImplementedException(); } - public bool GetIntProperty(string propertyName, out int value) { + public bool GetIntProperty(string propertyName, out int value) { throw new System.NotImplementedException(); } - public X509Certificate2[] GetCertificateArrayProperty(string propertyName) { + public X509Certificate2[] GetCertificateArrayProperty(string propertyName) { throw new System.NotImplementedException(); } - public string GetObjectIdentifier() { + public string GetObjectIdentifier() { throw new System.NotImplementedException(); } - public bool IsDeleted() { + public bool IsDeleted() { throw new System.NotImplementedException(); } - public Label GetLabel() { + public Label GetLabel() { throw new System.NotImplementedException(); } - public string GetSid() { + public string GetSid() { throw new System.NotImplementedException(); } - public string GetGuid() { + public string GetGuid() { throw new System.NotImplementedException(); } - public int PropCount(string prop) { + public int PropCount(string prop) { throw new System.NotImplementedException(); } - public IEnumerable PropertyNames() { + public IEnumerable PropertyNames() { throw new System.NotImplementedException(); } - public bool IsMSA() { + public bool IsMSA() { throw new System.NotImplementedException(); } - public bool IsGMSA() { + public bool IsGMSA() { throw new System.NotImplementedException(); } - public bool HasLAPS() { + public bool HasLAPS() { throw new System.NotImplementedException(); } + } } \ No newline at end of file From ef2b38e2666e49e2b2600d9069bd07fd09e65338 Mon Sep 17 00:00:00 2001 From: Alex Nemeth Date: Mon, 1 Jul 2024 22:18:19 -0700 Subject: [PATCH 20/68] Make some corrections on UserRightsAssignmentProcessor and SearchResultEntryWrapper --- .../UserRightsAssignmentProcessor.cs | 8 ++--- src/CommonLib/SearchResultEntryWrapper.cs | 31 ++++++++++++------- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/src/CommonLib/Processors/UserRightsAssignmentProcessor.cs b/src/CommonLib/Processors/UserRightsAssignmentProcessor.cs index bee3a570..b0242d64 100644 --- a/src/CommonLib/Processors/UserRightsAssignmentProcessor.cs +++ b/src/CommonLib/Processors/UserRightsAssignmentProcessor.cs @@ -53,15 +53,15 @@ public async IAsyncEnumerable GetUserRightsAssign string computerObjectId, string computerDomain, bool isDomainController, string[] desiredPrivileges = null) { var policyOpenResult = OpenLSAPolicy(computerName); - if (policyOpenResult.IsFailed) + if (!policyOpenResult.IsSuccess) { _log.LogDebug("LSAOpenPolicy failed on {ComputerName} with status {Status}", computerName, - policyOpenResult.SError); + policyOpenResult.Error); await SendComputerStatus(new CSVComputerStatus { Task = "LSAOpenPolicy", ComputerName = computerName, - Status = policyOpenResult.SError + Status = policyOpenResult.Error }); yield break; } @@ -109,7 +109,7 @@ await SendComputerStatus(new CSVComputerStatus { _log.LogDebug( "LSAEnumerateAccountsWithUserRight failed on {ComputerName} with status {Status} for privilege {Privilege}", - computerName, policyOpenResult.SError, privilege); + computerName, policyOpenResult.Error, privilege); await SendComputerStatus(new CSVComputerStatus { ComputerName = computerName, diff --git a/src/CommonLib/SearchResultEntryWrapper.cs b/src/CommonLib/SearchResultEntryWrapper.cs index ef681ba8..002c0b98 100644 --- a/src/CommonLib/SearchResultEntryWrapper.cs +++ b/src/CommonLib/SearchResultEntryWrapper.cs @@ -2,8 +2,10 @@ using System.Collections.Generic; using System.DirectoryServices.Protocols; using System.Linq; +using System.Runtime.InteropServices; using System.Security.Cryptography.X509Certificates; using System.Security.Principal; +using System.Threading.Tasks; using Microsoft.Extensions.Logging; using SharpHoundCommonLib.Enums; @@ -12,7 +14,7 @@ namespace SharpHoundCommonLib public interface ISearchResultEntry { string DistinguishedName { get; } - ResolvedSearchResult ResolveBloodHoundInfo(); + Task ResolveBloodHoundInfo(); string GetProperty(string propertyName); byte[] GetByteProperty(string propertyName); string[] GetArrayProperty(string propertyName); @@ -37,18 +39,18 @@ public class SearchResultEntryWrapper : ISearchResultEntry private const string MSAClass = "msds-managedserviceaccount"; private readonly SearchResultEntry _entry; private readonly ILogger _log; - private readonly ILDAPUtils _utils; + private readonly ILdapUtilsNew _utils; - public SearchResultEntryWrapper(SearchResultEntry entry, ILDAPUtils utils = null, ILogger log = null) + public SearchResultEntryWrapper(SearchResultEntry entry, ILdapUtilsNew utils = null, ILogger log = null) { _entry = entry; - _utils = utils ?? new LDAPUtils(); + _utils = utils ?? new LdapUtilsNew(); _log = log ?? Logging.LogProvider.CreateLogger("SearchResultWrapper"); } public string DistinguishedName => _entry.DistinguishedName; - public ResolvedSearchResult ResolveBloodHoundInfo() + public async Task ResolveBloodHoundInfo() { var res = new ResolvedSearchResult(); @@ -76,9 +78,9 @@ public ResolvedSearchResult ResolveBloodHoundInfo() string itemDomain; if (distinguishedName == null) { - if (objectId.StartsWith("S-1-")) + if (objectId.StartsWith("S-1-") && await _utils.GetDomainNameFromSid(objectId) is (true, var domain)) { - itemDomain = _utils.GetDomainNameFromSid(objectId); + itemDomain = domain; } else { @@ -104,10 +106,12 @@ public ResolvedSearchResult ResolveBloodHoundInfo() if (WellKnownPrincipal.GetWellKnownPrincipal(objectId, out var wkPrincipal)) { - res.DomainSid = _utils.GetSidFromDomainName(itemDomain); + if (await _utils.GetDomainSidFromDomainName(itemDomain) is (true, var sid)) + res.DomainSid = sid; res.DisplayName = $"{wkPrincipal.ObjectIdentifier}@{itemDomain}"; res.ObjectType = wkPrincipal.ObjectType; - res.ObjectId = _utils.ConvertWellKnownPrincipal(objectId, itemDomain); + if (await _utils.ConvertLocalWellKnownPrincipal(new SecurityIdentifier(objectId), res.DomainSid, itemDomain) is (true, var principal)) + res.ObjectId = principal.ObjectIdentifier; _log.LogTrace("Resolved {DN} to wkp {ObjectID}", DistinguishedName, res.ObjectId); return res; @@ -120,10 +124,12 @@ public ResolvedSearchResult ResolveBloodHoundInfo() } catch { - res.DomainSid = _utils.GetSidFromDomainName(itemDomain); + if (await _utils.GetDomainSidFromDomainName(itemDomain) is (true, var sid)) + res.DomainSid = sid; } else - res.DomainSid = _utils.GetSidFromDomainName(itemDomain); + if (await _utils.GetDomainSidFromDomainName(itemDomain) is (true, var sid)) + res.DomainSid = sid; var samAccountName = GetProperty(LDAPProperties.SAMAccountName); @@ -232,7 +238,8 @@ public bool IsDeleted() public Label GetLabel() { - return _entry.GetLabel(); + _entry.GetLabel(out var label); + return label; } public string GetSid() From 4673bbd8178e450a4b62f836f10ad0c5311cd119 Mon Sep 17 00:00:00 2001 From: rvazarkar Date: Tue, 2 Jul 2024 11:03:33 -0400 Subject: [PATCH 21/68] wip: remove some dead code, add some missing functions --- src/CommonLib/DCConnectionCache.cs | 79 -------------------- src/CommonLib/ILdapUtilsNew.cs | 2 + src/CommonLib/LdapUtilsNew.cs | 45 +++++++++++ src/CommonLib/SearchResultEntryWrapperNew.cs | 79 -------------------- 4 files changed, 47 insertions(+), 158 deletions(-) delete mode 100644 src/CommonLib/DCConnectionCache.cs delete mode 100644 src/CommonLib/SearchResultEntryWrapperNew.cs diff --git a/src/CommonLib/DCConnectionCache.cs b/src/CommonLib/DCConnectionCache.cs deleted file mode 100644 index 36e1e82b..00000000 --- a/src/CommonLib/DCConnectionCache.cs +++ /dev/null @@ -1,79 +0,0 @@ -using System.Collections.Concurrent; -using System.DirectoryServices.Protocols; - -namespace SharpHoundCommonLib -{ - public class DCConnectionCache - { - private readonly ConcurrentDictionary _ldapConnectionCache; - - public DCConnectionCache() - { - _ldapConnectionCache = new ConcurrentDictionary(); - } - - public bool TryGet(string key, bool isGlobalCatalog, out LdapConnectionWrapperNew connection) - { - var cacheKey = GetKey(key, isGlobalCatalog); - return _ldapConnectionCache.TryGetValue(cacheKey, out connection); - } - - public LdapConnectionWrapperNew AddOrUpdate(string key, bool isGlobalCatalog, LdapConnectionWrapperNew connection) - { - var cacheKey = GetKey(key, isGlobalCatalog); - return _ldapConnectionCache.AddOrUpdate(cacheKey, connection, (_, existingConnection) => - { - existingConnection.Connection.Dispose(); - return connection; - }); - } - - public LdapConnectionWrapperNew TryAdd(string key, bool isGlobalCatalog, LdapConnectionWrapperNew connection) - { - var cacheKey = GetKey(key, isGlobalCatalog); - return _ldapConnectionCache.AddOrUpdate(cacheKey, connection, (_, existingConnection) => - { - connection.Connection.Dispose(); - return existingConnection; - }); - } - - private LDAPConnectionCacheKey GetKey(string key, bool isGlobalCatalog) - { - return new LDAPConnectionCacheKey(key.ToUpper().Trim(), isGlobalCatalog); - } - - private class LDAPConnectionCacheKey - { - public string Domain { get; } - public bool GlobalCatalog { get; } - - public LDAPConnectionCacheKey(string domain, bool globalCatalog) - { - GlobalCatalog = globalCatalog; - Domain = domain; - } - - protected bool Equals(LDAPConnectionCacheKey other) - { - return GlobalCatalog == other.GlobalCatalog && Domain == other.Domain; - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((LDAPConnectionCacheKey)obj); - } - - public override int GetHashCode() - { - unchecked - { - return (GlobalCatalog.GetHashCode() * 397) ^ (Domain != null ? Domain.GetHashCode() : 0); - } - } - } - } -} \ No newline at end of file diff --git a/src/CommonLib/ILdapUtilsNew.cs b/src/CommonLib/ILdapUtilsNew.cs index de90c441..8bb57820 100644 --- a/src/CommonLib/ILdapUtilsNew.cs +++ b/src/CommonLib/ILdapUtilsNew.cs @@ -42,5 +42,7 @@ IAsyncEnumerable> RangedRetrieval(string distinguishedName, public Task IsDomainController(string computerObjectId, string domainName); public Task<(bool Success, TypedPrincipal Principal)> LookupDistinguishedName(string distinguishedName); + public void AddDomainController(string domainControllerSID); + IAsyncEnumerable GetWellKnownPrincipalOutput(); } } \ No newline at end of file diff --git a/src/CommonLib/LdapUtilsNew.cs b/src/CommonLib/LdapUtilsNew.cs index 54f2ea81..0132207d 100644 --- a/src/CommonLib/LdapUtilsNew.cs +++ b/src/CommonLib/LdapUtilsNew.cs @@ -29,6 +29,7 @@ public class LdapUtilsNew : ILdapUtilsNew { //This cache is indexed by domain sid private readonly ConcurrentDictionary _dcInfoCache = new(); private static readonly ConcurrentDictionary DomainCache = new(); + private static readonly ConcurrentDictionary DomainControllers = new(); private static readonly ConcurrentDictionary DomainToForestCache = new(StringComparer.OrdinalIgnoreCase); @@ -165,6 +166,7 @@ await _connectionPool.GetLdapConnection(domain, _log.LogError( "RangedRetrieval - Failed to get a new connection after ServerDown for path {Path}", distinguishedName); + _connectionPool.ReleaseConnection(connectionWrapper); tempResult = Result.Fail( "RangedRetrieval - Failed to get a new connection after ServerDown."); @@ -172,15 +174,23 @@ await _connectionPool.GetLdapConnection(domain, } } catch (LdapException le) { + if (le.ErrorCode is (int)LdapErrorCodes.ServerDown) { + _connectionPool.ReleaseConnection(connectionWrapper, true); + } + else { + _connectionPool.ReleaseConnection(connectionWrapper); + } tempResult = Result.Fail( $"Caught unrecoverable ldap exception: {le.Message} (ServerMessage: {le.ServerErrorMessage}) (ErrorCode: {le.ErrorCode})"); } catch (Exception e) { + _connectionPool.ReleaseConnection(connectionWrapper); tempResult = Result.Fail($"Caught unrecoverable exception: {e.Message}"); } //If we have a tempResult set it means we hit an error we couldn't recover from, so yield that result and then break out of the function + //We handle connection release in the relevant exception blocks if (tempResult != null) { yield return tempResult; yield break; @@ -201,6 +211,7 @@ await _connectionPool.GetLdapConnection(domain, } if (complete) { + _connectionPool.ReleaseConnection(connectionWrapper); yield break; } @@ -210,6 +221,7 @@ await _connectionPool.GetLdapConnection(domain, } else { //I dont know what can cause a RR to have multiple entries, but its nothing good. Break out + _connectionPool.ReleaseConnection(connectionWrapper); yield break; } } @@ -1382,5 +1394,38 @@ public async Task IsDomainController(string computerObjectId, string domai } } } + + public void AddDomainController(string domainControllerSID) + { + DomainControllers.TryAdd(domainControllerSID, new byte()); + } + + public async IAsyncEnumerable GetWellKnownPrincipalOutput() { + foreach (var wkp in SeenWellKnownPrincipals) + { + WellKnownPrincipal.GetWellKnownPrincipal(wkp.Value.WkpId, out var principal); + OutputBase output = principal.ObjectType switch + { + Label.User => new User(), + Label.Computer => new Computer(), + Label.Group => new OutputTypes.Group(), + Label.GPO => new GPO(), + Label.Domain => new OutputTypes.Domain(), + Label.OU => new OU(), + Label.Container => new Container(), + Label.Configuration => new Container(), + _ => throw new ArgumentOutOfRangeException() + }; + + output.Properties.Add("name", $"{principal.ObjectIdentifier}@{wkp.Value.DomainName}".ToUpper()); + if (await GetDomainSidFromDomainName(wkp.Value.DomainName) is (true, var sid)) { + output.Properties.Add("domainsid", sid); + } + + output.Properties.Add("domain", wkp.Value.DomainName.ToUpper()); + output.ObjectIdentifier = wkp.Key; + yield return output; + } + } } } \ No newline at end of file diff --git a/src/CommonLib/SearchResultEntryWrapperNew.cs b/src/CommonLib/SearchResultEntryWrapperNew.cs deleted file mode 100644 index b031d892..00000000 --- a/src/CommonLib/SearchResultEntryWrapperNew.cs +++ /dev/null @@ -1,79 +0,0 @@ -using System.Collections.Generic; -using System.DirectoryServices.Protocols; -using System.Security.Cryptography.X509Certificates; -using SharpHoundCommonLib.Enums; - -namespace SharpHoundCommonLib { - public class SearchResultEntryWrapperNew : ISearchResultEntry { - private readonly SearchResultEntry _entry; - - public string DistinguishedName => _entry.DistinguishedName; - public ResolvedSearchResult ResolveBloodHoundInfo() { - throw new System.NotImplementedException(); - } - - public string GetProperty(string propertyName) { - throw new System.NotImplementedException(); - } - - public byte[] GetByteProperty(string propertyName) { - throw new System.NotImplementedException(); - } - - public string[] GetArrayProperty(string propertyName) { - throw new System.NotImplementedException(); - } - - public byte[][] GetByteArrayProperty(string propertyName) { - throw new System.NotImplementedException(); - } - - public bool GetIntProperty(string propertyName, out int value) { - throw new System.NotImplementedException(); - } - - public X509Certificate2[] GetCertificateArrayProperty(string propertyName) { - throw new System.NotImplementedException(); - } - - public string GetObjectIdentifier() { - throw new System.NotImplementedException(); - } - - public bool IsDeleted() { - throw new System.NotImplementedException(); - } - - public Label GetLabel() { - throw new System.NotImplementedException(); - } - - public string GetSid() { - throw new System.NotImplementedException(); - } - - public string GetGuid() { - throw new System.NotImplementedException(); - } - - public int PropCount(string prop) { - throw new System.NotImplementedException(); - } - - public IEnumerable PropertyNames() { - throw new System.NotImplementedException(); - } - - public bool IsMSA() { - throw new System.NotImplementedException(); - } - - public bool IsGMSA() { - throw new System.NotImplementedException(); - } - - public bool HasLAPS() { - throw new System.NotImplementedException(); - } - } -} \ No newline at end of file From 9e690d517edd1d1079fd48d50bc29599224f8a59 Mon Sep 17 00:00:00 2001 From: rvazarkar Date: Tue, 2 Jul 2024 12:30:02 -0400 Subject: [PATCH 22/68] wip: get stuff building --- src/CommonLib/LDAPUtils.cs | 4230 ++++++++--------- src/CommonLib/LdapResult.cs | 12 +- src/CommonLib/LdapUtilsNew.cs | 90 +- .../UserRightsAssignmentProcessor.cs | 6 +- 4 files changed, 2174 insertions(+), 2164 deletions(-) diff --git a/src/CommonLib/LDAPUtils.cs b/src/CommonLib/LDAPUtils.cs index 71cc5b97..91bf5c59 100644 --- a/src/CommonLib/LDAPUtils.cs +++ b/src/CommonLib/LDAPUtils.cs @@ -1,2115 +1,2115 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Diagnostics; -using System.DirectoryServices; -using System.DirectoryServices.ActiveDirectory; -using System.DirectoryServices.Protocols; -using System.Linq; -using System.Net; -using System.Net.Sockets; -using System.Security.Principal; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Logging; -using SharpHoundCommonLib.Enums; -using SharpHoundCommonLib.Exceptions; -using SharpHoundCommonLib.LDAPQueries; -using SharpHoundCommonLib.OutputTypes; -using SharpHoundCommonLib.Processors; -using SharpHoundRPC.NetAPINative; -using Domain = System.DirectoryServices.ActiveDirectory.Domain; -using SearchScope = System.DirectoryServices.Protocols.SearchScope; -using SecurityMasks = System.DirectoryServices.Protocols.SecurityMasks; - -namespace SharpHoundCommonLib -{ - public class LDAPUtils : ILDAPUtils - { - private const string NullCacheKey = "UNIQUENULL"; - - // The following byte stream contains the necessary message to request a NetBios name from a machine - // http://web.archive.org/web/20100409111218/http://msdn.microsoft.com/en-us/library/system.net.sockets.socket.aspx - private static readonly byte[] NameRequest = - { - 0x80, 0x94, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x20, 0x43, 0x4b, 0x41, - 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, - 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, - 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, - 0x41, 0x41, 0x41, 0x41, 0x41, 0x00, 0x00, 0x21, - 0x00, 0x01 - }; - - - private static readonly ConcurrentDictionary - SeenWellKnownPrincipals = new(); - - private static readonly ConcurrentDictionary DomainControllers = new(); - private static readonly ConcurrentDictionary CachedDomainInfo = new(StringComparer.OrdinalIgnoreCase); - - private readonly ConcurrentDictionary _domainCache = new(); - private static readonly TimeSpan MinBackoffDelay = TimeSpan.FromSeconds(2); - private static readonly TimeSpan MaxBackoffDelay = TimeSpan.FromSeconds(20); - private const int BackoffDelayMultiplier = 2; - private const int MaxRetries = 3; - - private readonly ConcurrentDictionary _hostResolutionMap = new(); - private readonly ConcurrentDictionary _ldapConnections = new(); - private readonly ConcurrentDictionary _ldapRangeSizeCache = new(); - private readonly ILogger _log; - private readonly NativeMethods _nativeMethods; - private readonly ConcurrentDictionary _netbiosCache = new(); - private readonly PortScanner _portScanner; - private LDAPConfig _ldapConfig = new(); - private readonly ManualResetEvent _connectionResetEvent = new(false); - private readonly object _lockObj = new(); - - - /// - /// Creates a new instance of LDAP Utils with defaults - /// - public LDAPUtils() - { - _nativeMethods = new NativeMethods(); - _portScanner = new PortScanner(); - _log = Logging.LogProvider.CreateLogger("LDAPUtils"); - } - - /// - /// Creates a new instance of LDAP utils and allows overriding implementations - /// - /// - /// - /// - public LDAPUtils(NativeMethods nativeMethods = null, PortScanner scanner = null, ILogger log = null) - { - _nativeMethods = nativeMethods ?? new NativeMethods(); - _portScanner = scanner ?? new PortScanner(); - _log = log ?? Logging.LogProvider.CreateLogger("LDAPUtils"); - } - - /// - /// Sets the configuration for LDAP queries - /// - /// - /// - public void SetLDAPConfig(LDAPConfig config) - { - _ldapConfig = config ?? throw new ArgumentNullException(nameof(config), "LDAP Configuration can not be null"); - //Close out any existing LDAP connections to request a new incoming config - foreach (var kv in _ldapConnections) - { - kv.Value.Connection.Dispose(); - } - - _ldapConnections.Clear(); - } - - /// - /// Turns a sid into a well known principal ID. - /// - /// - /// - /// - /// True if a well known principal was identified, false if not - public bool GetWellKnownPrincipal(string sid, string domain, out TypedPrincipal commonPrincipal) - { - if (!WellKnownPrincipal.GetWellKnownPrincipal(sid, out commonPrincipal)) return false; - var tempDomain = domain ?? GetDomain()?.Name ?? "UNKNOWN"; - commonPrincipal.ObjectIdentifier = ConvertWellKnownPrincipal(sid, tempDomain); - SeenWellKnownPrincipals.TryAdd(commonPrincipal.ObjectIdentifier, new ResolvedWellKnownPrincipal - { - DomainName = domain, - WkpId = sid - }); - return true; - } - - public bool ConvertLocalWellKnownPrincipal(SecurityIdentifier sid, string computerDomainSid, - string computerDomain, out TypedPrincipal principal) - { - if (WellKnownPrincipal.GetWellKnownPrincipal(sid.Value, out var common)) - { - //The everyone and auth users principals are special and will be converted to the domain equivalent - if (sid.Value is "S-1-1-0" or "S-1-5-11") - { - GetWellKnownPrincipal(sid.Value, computerDomain, out principal); - return true; - } - - //Use the computer object id + the RID of the sid we looked up to create our new principal - principal = new TypedPrincipal - { - ObjectIdentifier = $"{computerDomainSid}-{sid.Rid()}", - ObjectType = common.ObjectType switch - { - Label.User => Label.LocalUser, - Label.Group => Label.LocalGroup, - _ => common.ObjectType - } - }; - - return true; - } - - principal = null; - return false; - } - - /// - /// Adds a SID to an internal list of domain controllers - /// - /// - public void AddDomainController(string domainControllerSID) - { - DomainControllers.TryAdd(domainControllerSID, new byte()); - } - - /// - /// Gets output objects for currently observed well known principals - /// - /// - /// - public IEnumerable GetWellKnownPrincipalOutput(string domain) - { - foreach (var wkp in SeenWellKnownPrincipals) - { - WellKnownPrincipal.GetWellKnownPrincipal(wkp.Value.WkpId, out var principal); - OutputBase output = principal.ObjectType switch - { - Label.User => new User(), - Label.Computer => new Computer(), - Label.Group => new Group(), - Label.GPO => new GPO(), - Label.Domain => new OutputTypes.Domain(), - Label.OU => new OU(), - Label.Container => new Container(), - Label.Configuration => new Container(), - _ => throw new ArgumentOutOfRangeException() - }; - - output.Properties.Add("name", $"{principal.ObjectIdentifier}@{wkp.Value.DomainName}".ToUpper()); - var domainSid = GetSidFromDomainName(wkp.Value.DomainName); - output.Properties.Add("domainsid", domainSid); - output.Properties.Add("domain", wkp.Value.DomainName.ToUpper()); - output.ObjectIdentifier = wkp.Key; - yield return output; - } - - var entdc = GetBaseEnterpriseDC(domain); - entdc.Members = DomainControllers.Select(x => new TypedPrincipal(x.Key, Label.Computer)).ToArray(); - yield return entdc; - } - - /// - /// Converts a - /// - /// - /// - /// - public string ConvertWellKnownPrincipal(string sid, string domain) - { - if (!WellKnownPrincipal.GetWellKnownPrincipal(sid, out _)) return sid; - - if (sid != "S-1-5-9") return $"{domain}-{sid}".ToUpper(); - - var forest = GetForest(domain)?.Name; - if (forest == null) _log.LogWarning("Error getting forest, ENTDC sid is likely incorrect"); - return $"{forest ?? "UNKNOWN"}-{sid}".ToUpper(); - } - - /// - /// Queries the global catalog to get potential SID matches for a username in the forest - /// - /// - /// - public string[] GetUserGlobalCatalogMatches(string name) - { - var tempName = name.ToLower(); - if (Cache.GetGCCache(tempName, out var sids)) - return sids; - - var query = new LDAPFilter().AddUsers($"samaccountname={tempName}").GetFilter(); - var results = QueryLDAP(query, SearchScope.Subtree, new[] { "objectsid" }, globalCatalog: true) - .Select(x => x.GetSid()).Where(x => x != null).ToArray(); - Cache.AddGCCache(tempName, results); - return results; - } - - /// - /// Uses an LDAP lookup to attempt to find the Label for a given SID - /// Will also convert to a well known principal ID if needed - /// - /// - /// - /// - public TypedPrincipal ResolveIDAndType(string id, string fallbackDomain) - { - //This is a duplicated SID object which is weird and makes things unhappy. Throw it out - if (id.Contains("0ACNF")) - return null; - - if (GetWellKnownPrincipal(id, fallbackDomain, out var principal)) - return principal; - - var type = id.StartsWith("S-") ? LookupSidType(id, fallbackDomain) : LookupGuidType(id, fallbackDomain); - return new TypedPrincipal(id, type); - } - - public TypedPrincipal ResolveCertTemplateByProperty(string propValue, string propertyName, string containerDN, - string domainName) - { - var filter = new LDAPFilter().AddCertificateTemplates().AddFilter(propertyName + "=" + propValue, true); - var res = QueryLDAP(filter.GetFilter(), SearchScope.OneLevel, - CommonProperties.TypeResolutionProps, adsPath: containerDN, domainName: domainName); - - if (res == null) - { - _log.LogWarning( - "Could not find certificate template with '{propertyName}:{propValue}' under {containerDN}; null result", - propertyName, propValue, containerDN); - return null; - } - - List resList = new List(res); - if (resList.Count == 0) - { - _log.LogWarning( - "Could not find certificate template with '{propertyName}:{propValue}' under {containerDN}; empty list", - propertyName, propValue, containerDN); - return null; - } - - if (resList.Count > 1) - { - _log.LogWarning( - "Found more than one certificate template with '{propertyName}:{propValue}' under {containerDN}", - propertyName, propValue, containerDN); - return null; - } - - ISearchResultEntry searchResultEntry = resList.FirstOrDefault(); - return new TypedPrincipal(searchResultEntry.GetGuid(), Label.CertTemplate); - } - - /// - /// Attempts to lookup the Label for a sid - /// - /// - /// - /// - public Label LookupSidType(string sid, string domain) - { - if (Cache.GetIDType(sid, out var type)) - return type; - - var rDomain = GetDomainNameFromSid(sid) ?? domain; - - var result = - QueryLDAP(CommonFilters.SpecificSID(sid), SearchScope.Subtree, CommonProperties.TypeResolutionProps, - rDomain) - .DefaultIfEmpty(null).FirstOrDefault(); - - type = result?.GetLabel() ?? Label.Base; - Cache.AddType(sid, type); - return type; - } - - /// - /// Attempts to lookup the Label for a GUID - /// - /// - /// - /// - public Label LookupGuidType(string guid, string domain) - { - if (Cache.GetIDType(guid, out var type)) - return type; - - var hex = Helpers.ConvertGuidToHexGuid(guid); - if (hex == null) - return Label.Base; - - var result = - QueryLDAP($"(objectguid={hex})", SearchScope.Subtree, CommonProperties.TypeResolutionProps, domain) - .DefaultIfEmpty(null).FirstOrDefault(); - - type = result?.GetLabel() ?? Label.Base; - Cache.AddType(guid, type); - return type; - } - - /// - /// Attempts to find the domain associated with a SID - /// - /// - /// - public string GetDomainNameFromSid(string sid) - { - try - { - var parsedSid = new SecurityIdentifier(sid); - var domainSid = parsedSid.AccountDomainSid?.Value.ToUpper(); - if (domainSid == null) - return null; - - _log.LogDebug("Resolving sid {DomainSid}", domainSid); - - if (Cache.GetDomainSidMapping(domainSid, out var domain)) - return domain; - - _log.LogDebug("No cache hit for {DomainSid}", domainSid); - domain = GetDomainNameFromSidLdap(domainSid); - _log.LogDebug("Resolved to {Domain}", domain); - - //Cache both to and from so we can use this later - if (domain != null) - { - Cache.AddDomainSidMapping(domainSid, domain); - Cache.AddDomainSidMapping(domain, domainSid); - } - - return domain; - } - catch - { - return null; - } - } - - /// - /// Attempts to get the SID associated with a domain name - /// - /// - /// - public string GetSidFromDomainName(string domainName) - { - var tempDomainName = NormalizeDomainName(domainName); - if (tempDomainName == null) - return null; - if (Cache.GetDomainSidMapping(tempDomainName, out var sid)) return sid; - - var domainObj = GetDomain(tempDomainName); - - if (domainObj != null) - sid = domainObj.GetDirectoryEntry().GetSid(); - else - sid = null; - - if (sid != null) - { - Cache.AddDomainSidMapping(sid, tempDomainName); - Cache.AddDomainSidMapping(tempDomainName, sid); - if (tempDomainName != domainName) - { - Cache.AddDomainSidMapping(domainName, sid); - } - } - - return sid; - } - - // Saving this code for an eventual async implementation - // public async IAsyncEnumerable DoRangedRetrievalAsync(string distinguishedName, string attributeName) - // { - // var domainName = Helpers.DistinguishedNameToDomain(distinguishedName); - // LdapConnection conn; - // try - // { - // conn = await CreateLDAPConnection(domainName, authType: _ldapConfig.AuthType); - // } - // catch - // { - // yield break; - // } - // - // if (conn == null) - // yield break; - // - // var index = 0; - // var step = 0; - // var currentRange = $"{attributeName};range={index}-*"; - // var complete = false; - // - // var searchRequest = CreateSearchRequest($"{attributeName}=*", SearchScope.Base, new[] {currentRange}, - // domainName, distinguishedName); - // - // var backoffDelay = MinBackoffDelay; - // var retryCount = 0; - // - // while (true) - // { - // DirectoryResponse searchResult; - // try - // { - // searchResult = await Task.Factory.FromAsync(conn.BeginSendRequest, conn.EndSendRequest, - // searchRequest, - // PartialResultProcessing.NoPartialResultSupport, null); - // } - // catch (LdapException le) when (le.ErrorCode == 51 && retryCount < MaxRetries) - // { - // //Allow three retries with a backoff on each one if we get a "Server is Busy" error - // retryCount++; - // await Task.Delay(backoffDelay); - // backoffDelay = TimeSpan.FromSeconds(Math.Min( - // backoffDelay.TotalSeconds * BackoffDelayMultiplier.TotalSeconds, MaxBackoffDelay.TotalSeconds)); - // continue; - // } - // catch (Exception e) - // { - // _log.LogWarning(e,"Caught exception during ranged retrieval for {DN}", distinguishedName); - // yield break; - // } - // - // if (searchResult is SearchResponse response && response.Entries.Count == 1) - // { - // var entry = response.Entries[0]; - // var attributeNames = entry?.Attributes?.AttributeNames; - // if (attributeNames != null) - // { - // foreach (string attr in attributeNames) - // { - // //Set our current range to the name of the attribute, which will tell us how far we are in "paging" - // currentRange = attr; - // //Check if the string has the * character in it. If it does, we've reached the end of this search - // complete = currentRange.IndexOf("*", 0, StringComparison.Ordinal) > 0; - // //Set our step to the number of attributes that came back. - // step = entry.Attributes[currentRange].Count; - // } - // } - // - // - // foreach (string val in entry.Attributes[currentRange].GetValues(typeof(string))) - // { - // yield return val; - // index++; - // } - // - // if (complete) yield break; - // - // currentRange = $"{attributeName};range={index}-{index + step}"; - // searchRequest.Attributes.Clear(); - // searchRequest.Attributes.Add(currentRange); - // } - // else - // { - // yield break; - // } - // } - // } - - /// - /// Performs Attribute Ranged Retrieval - /// https://docs.microsoft.com/en-us/windows/win32/adsi/attribute-range-retrieval - /// The function self-determines the range and internally handles the maximum step allowed by the server - /// - /// - /// - /// - public IEnumerable DoRangedRetrieval(string distinguishedName, string attributeName) - { - var domainName = Helpers.DistinguishedNameToDomain(distinguishedName); - var task = Task.Run(() => CreateLDAPConnectionWrapper(domainName, authType: _ldapConfig.AuthType)); - - LdapConnectionWrapper connWrapper; - - try - { - connWrapper = task.ConfigureAwait(false).GetAwaiter().GetResult(); - } - catch - { - yield break; - } - - if (connWrapper.Connection == null) - yield break; - - var conn = connWrapper.Connection; - - var index = 0; - var step = 0; - var baseString = $"{attributeName}"; - //Example search string: member;range=0-1000 - var currentRange = $"{baseString};range={index}-*"; - var complete = false; - - var searchRequest = CreateSearchRequest($"{attributeName}=*", SearchScope.Base, new[] { currentRange }, - connWrapper.DomainInfo, distinguishedName); - - if (searchRequest == null) - yield break; - - var backoffDelay = MinBackoffDelay; - var retryCount = 0; - - while (true) - { - SearchResponse response; - try - { - response = (SearchResponse)conn.SendRequest(searchRequest); - } - catch (LdapException le) when (le.ErrorCode == (int)LdapErrorCodes.Busy && retryCount < MaxRetries) - { - //Allow three retries with a backoff on each one if we get a "Server is Busy" error - retryCount++; - Thread.Sleep(backoffDelay); - backoffDelay = GetNextBackoff(retryCount); - continue; - } - catch (Exception e) - { - _log.LogError(e, "Error doing ranged retrieval for {Attribute} on {Dn}", attributeName, - distinguishedName); - yield break; - } - - //If we ever get more than one response from here, something is horribly wrong - if (response?.Entries.Count == 1) - { - var entry = response.Entries[0]; - //Process the attribute we get back to determine a few things - foreach (string attr in entry.Attributes.AttributeNames) - { - //Set our current range to the name of the attribute, which will tell us how far we are in "paging" - currentRange = attr; - //Check if the string has the * character in it. If it does, we've reached the end of this search - complete = currentRange.IndexOf("*", 0, StringComparison.Ordinal) > 0; - //Set our step to the number of attributes that came back. - step = entry.Attributes[currentRange].Count; - } - - foreach (string val in entry.Attributes[currentRange].GetValues(typeof(string))) - { - yield return val; - index++; - } - - if (complete) yield break; - - currentRange = $"{baseString};range={index}-{index + step}"; - searchRequest.Attributes.Clear(); - searchRequest.Attributes.Add(currentRange); - } - else - { - //Something went wrong here. - yield break; - } - } - } - - /// - /// Takes a host in most applicable forms from AD and attempts to resolve it into a SID. - /// - /// - /// - /// - public async Task ResolveHostToSid(string hostname, string domain) - { - var strippedHost = Helpers.StripServicePrincipalName(hostname).ToUpper().TrimEnd('$'); - if (string.IsNullOrEmpty(strippedHost)) - { - return null; - } - - if (_hostResolutionMap.TryGetValue(strippedHost, out var sid)) return sid; - - var normalDomain = NormalizeDomainName(domain); - - string tempName; - string tempDomain = null; - - //Step 1: Handle non-IP address values - if (!IPAddress.TryParse(strippedHost, out _)) - { - // Format: ABC.TESTLAB.LOCAL - if (strippedHost.Contains(".")) - { - var split = strippedHost.Split('.'); - tempName = split[0]; - tempDomain = string.Join(".", split.Skip(1).ToArray()); - } - // Format: WINDOWS - else - { - tempName = strippedHost; - tempDomain = normalDomain; - } - - // Add $ to the end of the name to match how computers are stored in AD - tempName = $"{tempName}$".ToUpper(); - var principal = ResolveAccountName(tempName, tempDomain); - sid = principal?.ObjectIdentifier; - if (sid != null) - { - _hostResolutionMap.TryAdd(strippedHost, sid); - return sid; - } - } - - //Step 2: Try NetWkstaGetInfo - //Next we'll try calling NetWkstaGetInfo in hopes of getting the NETBIOS name directly from the computer - //We'll use the hostname that we started with instead of the one from our previous step - var workstationInfo = await GetWorkstationInfo(strippedHost); - if (workstationInfo.HasValue) - { - tempName = workstationInfo.Value.ComputerName; - tempDomain = workstationInfo.Value.LanGroup; - - if (string.IsNullOrEmpty(tempDomain)) - tempDomain = normalDomain; - - if (!string.IsNullOrEmpty(tempName)) - { - //Append the $ to indicate this is a computer - tempName = $"{tempName}$".ToUpper(); - var principal = ResolveAccountName(tempName, tempDomain); - sid = principal?.ObjectIdentifier; - if (sid != null) - { - _hostResolutionMap.TryAdd(strippedHost, sid); - return sid; - } - } - } - - //Step 3: Socket magic - // Attempt to request the NETBIOS name of the computer directly - if (RequestNETBIOSNameFromComputer(strippedHost, normalDomain, out tempName)) - { - tempDomain ??= normalDomain; - tempName = $"{tempName}$".ToUpper(); - - var principal = ResolveAccountName(tempName, tempDomain); - sid = principal?.ObjectIdentifier; - if (sid != null) - { - _hostResolutionMap.TryAdd(strippedHost, sid); - return sid; - } - } - - //Try DNS resolution next - string resolvedHostname; - try - { - resolvedHostname = (await Dns.GetHostEntryAsync(strippedHost)).HostName; - } - catch - { - resolvedHostname = null; - } - - if (resolvedHostname != null) - { - var splitName = resolvedHostname.Split('.'); - tempName = $"{splitName[0]}$".ToUpper(); - tempDomain = string.Join(".", splitName.Skip(1)); - - var principal = ResolveAccountName(tempName, tempDomain); - sid = principal?.ObjectIdentifier; - if (sid != null) - { - _hostResolutionMap.TryAdd(strippedHost, sid); - return sid; - } - } - - //If we get here, everything has failed, and life is very sad. - tempName = strippedHost; - tempDomain = normalDomain; - - if (tempName.Contains(".")) - { - _hostResolutionMap.TryAdd(strippedHost, tempName); - return tempName; - } - - tempName = $"{tempName}.{tempDomain}"; - _hostResolutionMap.TryAdd(strippedHost, tempName); - return tempName; - } - - /// - /// Attempts to convert a bare account name (usually from session enumeration) to its corresponding ID and object type - /// - /// - /// - /// - public TypedPrincipal ResolveAccountName(string name, string domain) - { - if (string.IsNullOrWhiteSpace(name)) - return null; - - if (Cache.GetPrefixedValue(name, domain, out var id) && Cache.GetIDType(id, out var type)) - return new TypedPrincipal - { - ObjectIdentifier = id, - ObjectType = type - }; - - var d = NormalizeDomainName(domain); - var result = QueryLDAP($"(samaccountname={name})", SearchScope.Subtree, - CommonProperties.TypeResolutionProps, - d).DefaultIfEmpty(null).FirstOrDefault(); - - if (result == null) - { - _log.LogDebug("ResolveAccountName - unable to get result for {Name}", name); - return null; - } - - type = result.GetLabel(); - id = result.GetObjectIdentifier(); - - if (id == null) - { - _log.LogDebug("ResolveAccountName - could not retrieve ID on {DN} for {Name}", result.DistinguishedName, - name); - return null; - } - - Cache.AddPrefixedValue(name, domain, id); - Cache.AddType(id, type); - - id = ConvertWellKnownPrincipal(id, domain); - - return new TypedPrincipal - { - ObjectIdentifier = id, - ObjectType = type - }; - } - - /// - /// Attempts to convert a distinguishedname to its corresponding ID and object type. - /// - /// DistinguishedName - /// A TypedPrincipal object with the SID and Label - public TypedPrincipal ResolveDistinguishedName(string dn) - { - if (Cache.GetConvertedValue(dn, out var id) && Cache.GetIDType(id, out var type)) - return new TypedPrincipal - { - ObjectIdentifier = id, - ObjectType = type - }; - - var domain = Helpers.DistinguishedNameToDomain(dn); - var result = QueryLDAP("(objectclass=*)", SearchScope.Base, CommonProperties.TypeResolutionProps, domain, - adsPath: dn) - .DefaultIfEmpty(null).FirstOrDefault(); - - if (result == null) - { - _log.LogDebug("ResolveDistinguishedName - No result for {DN}", dn); - return null; - } - - id = result.GetObjectIdentifier(); - if (id == null) - { - _log.LogDebug("ResolveDistinguishedName - could not retrieve object identifier from {DN}", dn); - return null; - } - - if (GetWellKnownPrincipal(id, domain, out var principal)) return principal; - - type = result.GetLabel(); - - Cache.AddConvertedValue(dn, id); - Cache.AddType(id, type); - - id = ConvertWellKnownPrincipal(id, domain); - - return new TypedPrincipal - { - ObjectIdentifier = id, - ObjectType = type - }; - } - - /// - /// Queries LDAP using LDAPQueryOptions - /// - /// - /// - public IEnumerable QueryLDAP(LDAPQueryOptions options) - { - return QueryLDAP( - options.Filter, - options.Scope, - options.Properties, - options.CancellationToken, - options.DomainName, - options.IncludeAcl, - options.ShowDeleted, - options.AdsPath, - options.GlobalCatalog, - options.SkipCache, - options.ThrowException - ); - } - - /// - /// Performs an LDAP query using the parameters specified by the user. - /// - /// LDAP filter - /// SearchScope to query - /// LDAP properties to fetch for each object - /// Cancellation Token - /// Include the DACL and Owner values in the NTSecurityDescriptor - /// Include deleted objects - /// Domain to query - /// ADS path to limit the query too - /// Use the global catalog instead of the regular LDAP server - /// - /// Skip the connection cache and force a new connection. You must dispose of this connection - /// yourself. - /// - /// Throw exceptions rather than logging the errors directly - /// All LDAP search results matching the specified parameters - /// - /// Thrown when an error occurs during LDAP query (only when throwException = true) - /// - public IEnumerable QueryLDAP(string ldapFilter, SearchScope scope, - string[] props, CancellationToken cancellationToken, string domainName = null, bool includeAcl = false, - bool showDeleted = false, string adsPath = null, bool globalCatalog = false, bool skipCache = false, - bool throwException = false) - { - var queryParams = SetupLDAPQueryFilter( - ldapFilter, scope, props, includeAcl, domainName, includeAcl, adsPath, globalCatalog, skipCache); - - if (queryParams.Exception != null) - { - _log.LogWarning("Failed to setup LDAP Query Filter: {Message}", queryParams.Exception.Message); - if (throwException) - throw new LDAPQueryException("Failed to setup LDAP Query Filter", queryParams.Exception); - yield break; - } - - var conn = queryParams.Connection; - var request = queryParams.SearchRequest; - var pageControl = queryParams.PageControl; - - PageResultResponseControl pageResponse = null; - var backoffDelay = MinBackoffDelay; - var retryCount = 0; - while (true) - { - if (cancellationToken.IsCancellationRequested) - yield break; - - SearchResponse response; - try - { - _log.LogTrace("Sending LDAP request for {Filter}", ldapFilter); - response = (SearchResponse)conn.SendRequest(request); - if (response != null) - pageResponse = (PageResultResponseControl)response.Controls - .Where(x => x is PageResultResponseControl).DefaultIfEmpty(null).FirstOrDefault(); - } - catch (LdapException le) when (le.ErrorCode == (int)LdapErrorCodes.ServerDown && - retryCount < MaxRetries) - { - /*A ServerDown exception indicates that our connection is no longer valid for one of many reasons. - However, this function is generally called by multiple threads, so we need to be careful in recreating - the connection. Using a semaphore, we can ensure that only one thread is actually recreating the connection - while the other threads that hit the ServerDown exception simply wait. The initial caller will hold the semaphore - and do a backoff delay before trying to make a new connection which will replace the existing connection in the - _ldapConnections cache. Other threads will retrieve the new connection from the cache instead of making a new one - This minimizes overhead of new connections while still fixing our core problem.*/ - - //Always increment retry count - retryCount++; - - //Attempt to acquire a lock - if (Monitor.TryEnter(_lockObj)) - { - //If we've acquired the lock, we want to immediately signal our reset event so everyone else waits - _connectionResetEvent.Reset(); - try - { - //Sleep for our backoff - Thread.Sleep(backoffDelay); - //Explicitly skip the cache so we don't get the same connection back - conn = CreateNewConnection(domainName, globalCatalog, true).Connection; - if (conn == null) - { - _log.LogError( - "Unable to create replacement ldap connection for ServerDown exception. Breaking loop"); - yield break; - } - - _log.LogInformation("Created new LDAP connection after receiving ServerDown from server"); - } - finally - { - //Reset our event + release the lock - _connectionResetEvent.Set(); - Monitor.Exit(_lockObj); - } - } - else - { - //If someone else is holding the reset event, we want to just wait and then pull the newly created connection out of the cache - //This event will be released after the first entrant thread is done making a new connection - //The thread.sleep is to prevent a potential, very unlikely race - Thread.Sleep(50); - _connectionResetEvent.WaitOne(); - conn = CreateNewConnection(domainName, globalCatalog).Connection; - } - - backoffDelay = GetNextBackoff(retryCount); - continue; - } - catch (LdapException le) when (le.ErrorCode == (int)LdapErrorCodes.Busy && retryCount < MaxRetries) - { - retryCount++; - backoffDelay = GetNextBackoff(retryCount); - continue; - } - catch (LdapException le) - { - if (le.ErrorCode != (int)LdapErrorCodes.LocalError) - { - if (throwException) - { - throw new LDAPQueryException( - $"LDAP Exception in Loop: {le.ErrorCode}. {le.ServerErrorMessage}. {le.Message}. Filter: {ldapFilter}. Domain: {domainName}", - le); - } - - _log.LogWarning(le, - "LDAP Exception in Loop: {ErrorCode}. {ServerErrorMessage}. {Message}. Filter: {Filter}. Domain: {Domain}", - le.ErrorCode, le.ServerErrorMessage, le.Message, ldapFilter, domainName); - } - - yield break; - } - catch (Exception e) - { - _log.LogWarning(e, "Exception in LDAP loop for {Filter} and {Domain}", ldapFilter, domainName); - if (throwException) - throw new LDAPQueryException($"Exception in LDAP loop for {ldapFilter} and {domainName}", e); - - yield break; - } - - if (cancellationToken.IsCancellationRequested) - yield break; - - if (response == null || pageResponse == null) - continue; - - foreach (SearchResultEntry entry in response.Entries) - { - if (cancellationToken.IsCancellationRequested) - yield break; - - yield return new SearchResultEntryWrapper(entry, this); - } - - if (pageResponse.Cookie.Length == 0 || response.Entries.Count == 0 || - cancellationToken.IsCancellationRequested) - yield break; - - pageControl.Cookie = pageResponse.Cookie; - } - } - - private LdapConnectionWrapper CreateNewConnection(string domainName = null, bool globalCatalog = false, - bool skipCache = false) - { - var task = Task.Run(() => CreateLDAPConnectionWrapper(domainName, skipCache, _ldapConfig.AuthType, globalCatalog)); - - try - { - return task.ConfigureAwait(false).GetAwaiter().GetResult(); - } - catch - { - return null; - } - } - - /// - /// Performs an LDAP query using the parameters specified by the user. - /// - /// LDAP filter - /// SearchScope to query - /// LDAP properties to fetch for each object - /// Include the DACL and Owner values in the NTSecurityDescriptor - /// Include deleted objects - /// Domain to query - /// ADS path to limit the query too - /// Use the global catalog instead of the regular LDAP server - /// - /// Skip the connection cache and force a new connection. You must dispose of this connection - /// yourself. - /// - /// Throw exceptions rather than logging the errors directly - /// All LDAP search results matching the specified parameters - /// - /// Thrown when an error occurs during LDAP query (only when throwException = true) - /// - public virtual IEnumerable QueryLDAP(string ldapFilter, SearchScope scope, - string[] props, string domainName = null, bool includeAcl = false, bool showDeleted = false, - string adsPath = null, bool globalCatalog = false, bool skipCache = false, bool throwException = false) - { - return QueryLDAP(ldapFilter, scope, props, new CancellationToken(), domainName, includeAcl, showDeleted, - adsPath, globalCatalog, skipCache, throwException); - } - - private static TimeSpan GetNextBackoff(int retryCount) - { - return TimeSpan.FromSeconds(Math.Min( - MinBackoffDelay.TotalSeconds * Math.Pow(BackoffDelayMultiplier, retryCount), - MaxBackoffDelay.TotalSeconds)); - } - - /// - /// Gets the forest associated with a domain. - /// If no domain is provided, defaults to current domain - /// - /// - /// - public virtual Forest GetForest(string domainName = null) - { - try - { - if (domainName == null && _ldapConfig.Username == null) - return Forest.GetCurrentForest(); - - var domain = GetDomain(domainName); - return domain?.Forest; - } - catch - { - return null; - } - } - - /// - /// Creates a new ActiveDirectorySecurityDescriptor - /// Function created for testing purposes - /// - /// - public ActiveDirectorySecurityDescriptor MakeSecurityDescriptor() - { - return new ActiveDirectorySecurityDescriptor(new ActiveDirectorySecurity()); - } - - public string BuildLdapPath(string dnPath, string domainName) - { - //Check our cached info for a fast check - if (CachedDomainInfo.TryGetValue(domainName, out var info)) - { - return $"{dnPath},{info.DomainSearchBase}"; - } - var domain = GetDomain(domainName)?.Name; - if (domain == null) - return null; - - var adPath = $"{dnPath},DC={domain.Replace(".", ",DC=")}"; - return adPath; - } - - /// - /// Tests the current LDAP config to ensure its valid by pulling a domain object - /// - /// True if connection was successful, else false - public bool TestLDAPConfig(string domain) - { - var filter = new LDAPFilter(); - filter.AddDomains(); - - _log.LogTrace("Testing LDAP connection for domain {Domain}", domain); - var result = QueryLDAP(filter.GetFilter(), SearchScope.Subtree, CommonProperties.ObjectID, domain, - throwException: true) - .DefaultIfEmpty(null).FirstOrDefault(); - _log.LogTrace("Result object from LDAP connection test is {DN}", result?.DistinguishedName ?? "null"); - return result != null; - } - - /// - /// Gets the domain object associated with the specified domain name. - /// Defaults to current domain if none specified - /// - /// - /// - public virtual Domain GetDomain(string domainName = null) - { - var cacheKey = domainName ?? NullCacheKey; - if (_domainCache.TryGetValue(cacheKey, out var domain)) return domain; - - try - { - DirectoryContext context; - if (_ldapConfig.Username != null) - context = domainName != null - ? new DirectoryContext(DirectoryContextType.Domain, domainName, _ldapConfig.Username, - _ldapConfig.Password) - : new DirectoryContext(DirectoryContextType.Domain, _ldapConfig.Username, - _ldapConfig.Password); - else - context = domainName != null - ? new DirectoryContext(DirectoryContextType.Domain, domainName) - : new DirectoryContext(DirectoryContextType.Domain); - - domain = Domain.GetDomain(context); - } - catch (Exception e) - { - _log.LogDebug(e, "GetDomain call failed at {StackTrace}", new StackFrame()); - domain = null; - } - - _domainCache.TryAdd(cacheKey, domain); - return domain; - } - - /// - /// Setup LDAP query for filter - /// - /// LDAP filter - /// SearchScope to query - /// LDAP properties to fetch for each object - /// Include the DACL and Owner values in the NTSecurityDescriptor - /// Domain to query - /// Include deleted objects - /// ADS path to limit the query too - /// Use the global catalog instead of the regular LDAP server - /// - /// Skip the connection cache and force a new connection. You must dispose of this connection - /// yourself. - /// - /// Tuple of LdapConnection, SearchRequest, PageResultRequestControl and LDAPQueryException - // ReSharper disable once MemberCanBePrivate.Global - internal LDAPQueryParams SetupLDAPQueryFilter( - string ldapFilter, - SearchScope scope, string[] props, bool includeAcl = false, string domainName = null, - bool showDeleted = false, - string adsPath = null, bool globalCatalog = false, bool skipCache = false) - { - _log.LogTrace("Creating ldap connection for {Target} with filter {Filter}", - globalCatalog ? "Global Catalog" : "DC", ldapFilter); - var task = Task.Run(() => CreateLDAPConnectionWrapper(domainName, skipCache, _ldapConfig.AuthType, globalCatalog)); - - var queryParams = new LDAPQueryParams(); - - LdapConnectionWrapper connWrapper; - try - { - connWrapper = task.ConfigureAwait(false).GetAwaiter().GetResult(); - } - catch (NoLdapDataException) - { - var errorString = - $"Successfully connected via LDAP to {domainName ?? "Default Domain"} but no data received. This is most likely due to permissions or using kerberos authentication across trusts."; - queryParams.Exception = new LDAPQueryException(errorString, null); - return queryParams; - } - catch (LdapAuthenticationException e) - { - var errorString = - $"Failed to connect via LDAP to {domainName ?? "Default Domain"}: Authentication is invalid"; - queryParams.Exception = new LDAPQueryException(errorString, e.InnerException); - return queryParams; - } - catch (LdapConnectionException e) - { - var errorString = - $"Failed to connect via LDAP to {domainName ?? "Default Domain"}: {e.InnerException.Message} (Code: {e.ErrorCode}"; - queryParams.Exception = new LDAPQueryException(errorString, e.InnerException); - return queryParams; - } - - var conn = connWrapper.Connection; - - //If we get a null connection, something went wrong, but we don't have an error to go with it for whatever reason - if (conn == null) - { - var errorString = - $"LDAP connection is null for filter {ldapFilter} and domain {domainName ?? "Default Domain"}"; - queryParams.Exception = new LDAPQueryException(errorString); - return queryParams; - } - - SearchRequest request; - - try - { - request = CreateSearchRequest(ldapFilter, scope, props, connWrapper.DomainInfo, adsPath, showDeleted); - } - catch (LDAPQueryException ldapQueryException) - { - queryParams.Exception = ldapQueryException; - return queryParams; - } - - if (request == null) - { - var errorString = - $"Search request is null for filter {ldapFilter} and domain {domainName ?? "Default Domain"}"; - queryParams.Exception = new LDAPQueryException(errorString); - return queryParams; - } - - var pageControl = new PageResultRequestControl(500); - request.Controls.Add(pageControl); - - if (includeAcl) - request.Controls.Add(new SecurityDescriptorFlagControl - { - SecurityMasks = SecurityMasks.Dacl | SecurityMasks.Owner - }); - - queryParams.Connection = conn; - queryParams.SearchRequest = request; - queryParams.PageControl = pageControl; - - return queryParams; - } - - private Group GetBaseEnterpriseDC(string domain) - { - var forest = GetForest(domain)?.Name; - if (forest == null) _log.LogWarning("Error getting forest, ENTDC sid is likely incorrect"); - var g = new Group { ObjectIdentifier = $"{forest}-S-1-5-9".ToUpper() }; - g.Properties.Add("name", $"ENTERPRISE DOMAIN CONTROLLERS@{forest ?? "UNKNOWN"}".ToUpper()); - g.Properties.Add("domainsid", GetSidFromDomainName(forest)); - g.Properties.Add("domain", forest); - return g; - } - - /// - /// Updates the config for querying LDAP - /// - /// - public void UpdateLDAPConfig(LDAPConfig config) - { - _ldapConfig = config; - } - - private string GetDomainNameFromSidLdap(string sid) - { - var hexSid = Helpers.ConvertSidToHexSid(sid); - - if (hexSid == null) - return null; - - //Search using objectsid first - var result = - QueryLDAP($"(&(objectclass=domain)(objectsid={hexSid}))", SearchScope.Subtree, - new[] { "distinguishedname" }, globalCatalog: true).DefaultIfEmpty(null).FirstOrDefault(); - - if (result != null) - { - var domainName = Helpers.DistinguishedNameToDomain(result.DistinguishedName); - return domainName; - } - - //Try trusteddomain objects with the securityidentifier attribute - result = - QueryLDAP($"(&(objectclass=trusteddomain)(securityidentifier={sid}))", SearchScope.Subtree, - new[] { "cn" }, globalCatalog: true).DefaultIfEmpty(null).FirstOrDefault(); - - if (result != null) - { - var domainName = result.GetProperty(LDAPProperties.CanonicalName); - return domainName; - } - - //We didn't find anything so just return null - return null; - } - - /// - /// Uses a socket and a set of bytes to request the NETBIOS name from a remote computer - /// - /// - /// - /// - /// - private static bool RequestNETBIOSNameFromComputer(string server, string domain, out string netbios) - { - var receiveBuffer = new byte[1024]; - var requestSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); - try - { - //Set receive timeout to 1 second - requestSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, 1000); - EndPoint remoteEndpoint; - - //We need to create an endpoint to bind too. If its an IP, just use that. - if (IPAddress.TryParse(server, out var parsedAddress)) - remoteEndpoint = new IPEndPoint(parsedAddress, 137); - else - //If its not an IP, we're going to try and resolve it from DNS - try - { - IPAddress address; - if (server.Contains(".")) - address = Dns - .GetHostAddresses(server).First(x => x.AddressFamily == AddressFamily.InterNetwork); - else - address = Dns.GetHostAddresses($"{server}.{domain}")[0]; - - if (address == null) - { - netbios = null; - return false; - } - - remoteEndpoint = new IPEndPoint(address, 137); - } - catch - { - //Failed to resolve an IP, so return null - netbios = null; - return false; - } - - var originEndpoint = new IPEndPoint(IPAddress.Any, 0); - requestSocket.Bind(originEndpoint); - - try - { - requestSocket.SendTo(NameRequest, remoteEndpoint); - var receivedByteCount = requestSocket.ReceiveFrom(receiveBuffer, ref remoteEndpoint); - if (receivedByteCount >= 90) - { - netbios = new ASCIIEncoding().GetString(receiveBuffer, 57, 16).Trim('\0', ' '); - return true; - } - - netbios = null; - return false; - } - catch (SocketException) - { - netbios = null; - return false; - } - } - finally - { - //Make sure we close the socket if its open - requestSocket.Close(); - } - } - - /// - /// Calls the NetWkstaGetInfo API on a hostname - /// - /// - /// - private async Task GetWorkstationInfo(string hostname) - { - if (!await _portScanner.CheckPort(hostname)) - return null; - - var result = NetAPIMethods.NetWkstaGetInfo(hostname); - if (result.IsSuccess) return result.Value; - - return null; - } - - /// - /// Creates a SearchRequest object for use in querying LDAP. - /// - /// LDAP filter - /// SearchScope to query - /// LDAP properties to fetch for each object - /// Domain info object which is created alongside the LDAP connection - /// ADS path to limit the query too - /// Include deleted objects in results - /// A built SearchRequest - private SearchRequest CreateSearchRequest(string filter, SearchScope scope, string[] attributes, - DomainInfo domainInfo, string adsPath = null, bool showDeleted = false) - { - var adPath = adsPath?.Replace("LDAP://", "") ?? domainInfo.DomainSearchBase; - - var request = new SearchRequest(adPath, filter, scope, attributes); - request.Controls.Add(new SearchOptionsControl(SearchOption.DomainScope)); - if (showDeleted) - request.Controls.Add(new ShowDeletedControl()); - - return request; - } - - private LdapConnection CreateConnectionHelper(string directoryIdentifier, bool ssl, AuthType authType, bool globalCatalog) - { - var port = globalCatalog ? _ldapConfig.GetGCPort(ssl) : _ldapConfig.GetPort(ssl); - var identifier = new LdapDirectoryIdentifier(directoryIdentifier, port, false, false); - var connection = new LdapConnection(identifier) { Timeout = new TimeSpan(0, 0, 5, 0) }; - SetupLdapConnection(connection, true, authType); - return connection; - } - - private static void CheckAndThrowException(LdapException ldapException) - { - //A null error code with success false indicates that we successfully created a connection but got no data back, this is generally because our AuthType isn't compatible. - //AuthType Kerberos will only work across trusts in very specific scenarios. Alternatively, we don't have read rights. - //Throw this exception for clients to handle - if (ldapException.ErrorCode is (int)LdapErrorCodes.KerberosAuthType or (int)ResultCode.InsufficientAccessRights) - { - throw new NoLdapDataException(ldapException.ErrorCode); - } - - //We shouldn't ever hit this in theory, but we'll error out if its the case - if (ldapException.ErrorCode is (int)ResultCode.InappropriateAuthentication) - { - throw new LdapAuthenticationException(ldapException); - } - - //Any other error we dont have specific ways to handle - if (ldapException.ErrorCode != (int)ResultCode.Unavailable && ldapException.ErrorCode != (int)ResultCode.Busy) - { - throw new LdapConnectionException(ldapException); - } - } - - private string ResolveDomainToFullName(string domain) - { - if (string.IsNullOrEmpty(domain)) - { - return GetDomain()?.Name.ToUpper().Trim(); - } - - if (CachedDomainInfo.TryGetValue(domain.ToUpper(), out var info)) - { - return info.DomainFQDN; - } - - return GetDomain(domain)?.Name.ToUpper().Trim(); - } - - /// - /// Creates an LDAP connection with appropriate options based off the ldap configuration. Caches connections - /// - /// The domain to connect too - /// Skip the connection cache - /// Auth type to use. Defaults to Kerberos. Use Negotiate for netonly/cross trust(forest) scenarios - /// Use global catalog or not - /// A connected LDAP connection or null - - private async Task CreateLDAPConnectionWrapper(string domainName = null, bool skipCache = false, - AuthType authType = AuthType.Kerberos, bool globalCatalog = false) - { - // Step 1: If domain passed in is non-null, skip this step - // - Call GetDomain with a null domain to get the user's current domain - // Step 2: Take domain passed in to the function or resolved from step 1 - // - Try an ldap connection on SSL - // - If ServerUnavailable - Try an ldap connection on non-SSL - // Step 3: Pass the domain to GetDomain to resolve to a better name (potentially) - // - If we get a better name, repeat step 2 with the new name - // Step 4: - // - Use GetDomain to get a domain object along with a list of domain controllers - // - Try the primary domain controller on both ssl/non-ssl - // - Loop over domain controllers and try each on ssl/non-ssl - - //If a server has been manually specified, we should never get past this block for opsec reasons - if (_ldapConfig.Server != null) - { - _log.LogInformation("Server is set via config, attempting to create ldap connection to {Server}", _ldapConfig.Server); - if (!skipCache) - { - if (GetCachedConnection(_ldapConfig.Server, globalCatalog, out var conn)) - { - return conn; - } - } - - var singleServerConn = CreateLDAPConnection(_ldapConfig.Server, authType, globalCatalog); - if (singleServerConn == null) { - return new LdapConnectionWrapper - { - Connection = null, - DomainInfo = null - }; - } - - var cacheKey = new LDAPConnectionCacheKey(_ldapConfig.Server, globalCatalog); - _ldapConnections.AddOrUpdate(cacheKey, singleServerConn, (_, ldapConnection) => - { - ldapConnection.Connection.Dispose(); - return singleServerConn; - }); - return singleServerConn; - } - - //Take the incoming domain name and Upper/Trim it. If the name is null, we'll have to use GetDomain to figure out the user's domain context - var domain = domainName?.ToUpper().Trim() ?? ResolveDomainToFullName(domainName); - - //If our domain is STILL null, we're not going to get anything reliable, so exit out - if (domain == null) - { - _log.LogWarning("Initial domain name for new LDAP connection is null and/or unresolvable. Unable to create a new connection"); - return new LdapConnectionWrapper - { - Connection = null, - DomainInfo = null - }; - } - - if (!skipCache) - { - if (GetCachedConnection(domain, globalCatalog, out var conn)) - { - return conn; - } - } - - _log.LogInformation("No cached LDAP connection found for {Domain}, attempting a new connection", domain); - - var connectionWrapper = CreateLDAPConnection(domain, authType, globalCatalog); - //If our connection isn't null, it means we have a good connection - if (connectionWrapper != null) - { - var cacheKey = new LDAPConnectionCacheKey(domain, globalCatalog); - _ldapConnections.AddOrUpdate(cacheKey, connectionWrapper, (_, ldapConnection) => - { - ldapConnection.Connection.Dispose(); - return connectionWrapper; - }); - return connectionWrapper; - } - - //If our incoming domain name wasn't null, try to re-resolve the name for a better potential match and then retry - if (domainName != null) - { - var newDomain = ResolveDomainToFullName(domainName); - if (!string.IsNullOrEmpty(newDomain) && !newDomain.Equals(domain, StringComparison.OrdinalIgnoreCase)) - { - //Set our domain name to the newly resolved value for future steps - domain = newDomain; - if (!skipCache) - { - //Check our cache again, maybe the new name works - if (GetCachedConnection(domain, globalCatalog, out var conn)) - { - return conn; - } - } - - connectionWrapper = CreateLDAPConnection(domain, authType, globalCatalog); - //If our connection isn't null, it means we have a good connection - if (connectionWrapper != null) - { - var cacheKey = new LDAPConnectionCacheKey(domain, globalCatalog); - _ldapConnections.AddOrUpdate(cacheKey, connectionWrapper, (_, ldapConnection) => - { - ldapConnection.Connection.Dispose(); - return connectionWrapper; - }); - return connectionWrapper; - } - } - } - - //Next step, look for domain controllers - var domainObj = GetDomain(domain); - if (domainObj?.Name == null) - { - return null; - } - - //Start with the PDC of the domain and see if we can connect - var pdc = domainObj.PdcRoleOwner.Name; - connectionWrapper = await CreateLDAPConnectionWithPortCheck(pdc, authType, globalCatalog); - if (connectionWrapper != null) - { - var cacheKey = new LDAPConnectionCacheKey(domain, globalCatalog); - _ldapConnections.AddOrUpdate(cacheKey, connectionWrapper, (_, ldapConnection) => - { - ldapConnection.Connection.Dispose(); - return connectionWrapper; - }); - return connectionWrapper; - } - - //Loop over all other domain controllers and see if we can make a good connection to any - foreach (DomainController dc in domainObj.DomainControllers) - { - connectionWrapper = await CreateLDAPConnectionWithPortCheck(dc.Name, authType, globalCatalog); - if (connectionWrapper != null) - { - var cacheKey = new LDAPConnectionCacheKey(domain, globalCatalog); - _ldapConnections.AddOrUpdate(cacheKey, connectionWrapper, (_, ldapConnection) => - { - ldapConnection.Connection.Dispose(); - return connectionWrapper; - }); - return connectionWrapper; - } - } - - return new LdapConnectionWrapper() - { - Connection = null, - DomainInfo = null - }; - } - - private bool GetCachedConnection(string domain, bool globalCatalog, out LdapConnectionWrapper connectionWrapper) - { - var domainName = domain; - if (CachedDomainInfo.TryGetValue(domain.ToUpper(), out var resolved)) - { - domainName = resolved.DomainFQDN; - } - var key = new LDAPConnectionCacheKey(domainName, globalCatalog); - return _ldapConnections.TryGetValue(key, out connectionWrapper); - } - - private async Task CreateLDAPConnectionWithPortCheck(string target, AuthType authType, bool globalCatalog) - { - if (globalCatalog) - { - if (await _portScanner.CheckPort(target, _ldapConfig.GetGCPort(true)) || (!_ldapConfig.ForceSSL && - await _portScanner.CheckPort(target, _ldapConfig.GetGCPort(false)))) - { - return CreateLDAPConnection(target, authType, true); - } - } - else - { - if (await _portScanner.CheckPort(target, _ldapConfig.GetPort(true)) || (!_ldapConfig.ForceSSL && await _portScanner.CheckPort(target, _ldapConfig.GetPort(false)))) - { - return CreateLDAPConnection(target, authType, false); - } - } - - return null; - } - - private LdapConnectionWrapper CreateLDAPConnection(string target, AuthType authType, bool globalCatalog) - { - //Lets build a new connection - //Always try SSL first - var connection = CreateConnectionHelper(target, true, authType, globalCatalog); - var connectionResult = TestConnection(connection); - DomainInfo info; - - if (connectionResult.Success) - { - var domain = connectionResult.DomainInfo.DomainFQDN; - if (!CachedDomainInfo.ContainsKey(domain)) - { - var baseDomainInfo = connectionResult.DomainInfo; - baseDomainInfo.DomainSID = GetDomainSidFromConnection(connection, baseDomainInfo); - baseDomainInfo.DomainNetbiosName = GetDomainNetbiosName(connection, baseDomainInfo); - _log.LogInformation("Got info for domain: {info}", baseDomainInfo); - CachedDomainInfo.TryAdd(baseDomainInfo.DomainFQDN, baseDomainInfo); - CachedDomainInfo.TryAdd(baseDomainInfo.DomainNetbiosName, baseDomainInfo); - CachedDomainInfo.TryAdd(baseDomainInfo.DomainSID, baseDomainInfo); - if (!string.IsNullOrEmpty(baseDomainInfo.DomainSID)) - { - Cache.AddDomainSidMapping(baseDomainInfo.DomainFQDN, baseDomainInfo.DomainSID); - Cache.AddDomainSidMapping(baseDomainInfo.DomainSID, baseDomainInfo.DomainFQDN); - if (!string.IsNullOrEmpty(baseDomainInfo.DomainNetbiosName)) - { - Cache.AddDomainSidMapping(baseDomainInfo.DomainNetbiosName, baseDomainInfo.DomainSID); - } - } - - if (!string.IsNullOrEmpty(baseDomainInfo.DomainNetbiosName)) - { - _netbiosCache.TryAdd(baseDomainInfo.DomainFQDN, baseDomainInfo.DomainNetbiosName); - } - - info = baseDomainInfo; - } - else - { - CachedDomainInfo.TryGetValue(domain, out info); - } - return new LdapConnectionWrapper - { - Connection = connection, - DomainInfo = info - }; - } - - CheckAndThrowException(connectionResult.Exception); - - //If we're not allowing fallbacks to LDAP from LDAPS, just return here - if (_ldapConfig.ForceSSL) - { - return null; - } - //If we get to this point, it means we have an unsuccessful connection, but our error code doesn't indicate an outright failure - //Try a new connection without SSL - connection = CreateConnectionHelper(target, false, authType, globalCatalog); - - connectionResult = TestConnection(connection); - - if (connectionResult.Success) - { - var domain = connectionResult.DomainInfo.DomainFQDN; - if (!CachedDomainInfo.ContainsKey(domain.ToUpper())) - { - var baseDomainInfo = connectionResult.DomainInfo; - baseDomainInfo.DomainSID = GetDomainSidFromConnection(connection, baseDomainInfo); - baseDomainInfo.DomainNetbiosName = GetDomainNetbiosName(connection, baseDomainInfo); - CachedDomainInfo.TryAdd(baseDomainInfo.DomainFQDN, baseDomainInfo); - CachedDomainInfo.TryAdd(baseDomainInfo.DomainNetbiosName, baseDomainInfo); - CachedDomainInfo.TryAdd(baseDomainInfo.DomainSID, baseDomainInfo); - - if (!string.IsNullOrEmpty(baseDomainInfo.DomainSID)) - { - Cache.AddDomainSidMapping(baseDomainInfo.DomainFQDN, baseDomainInfo.DomainSID); - } - - if (!string.IsNullOrEmpty(baseDomainInfo.DomainNetbiosName)) - { - Cache.AddDomainSidMapping(baseDomainInfo.DomainNetbiosName, baseDomainInfo.DomainSID); - } - - info = baseDomainInfo; - }else - { - CachedDomainInfo.TryGetValue(domain, out info); - } - return new LdapConnectionWrapper - { - Connection = connection, - DomainInfo = info - }; - } - - CheckAndThrowException(connectionResult.Exception); - return null; - } - - private LdapConnectionTestResult TestConnection(LdapConnection connection) - { - try - { - //Attempt an initial bind. If this fails, likely auth is invalid, or its not a valid target - connection.Bind(); - } - catch (LdapException e) - { - connection.Dispose(); - return new LdapConnectionTestResult(false, e, null, null); - } - - try - { - //Do an initial search request to get the rootDSE - //This ldap filter is equivalent to (objectclass=*) - var searchRequest = new SearchRequest("", new LDAPFilter().AddAllObjects().GetFilter(), - SearchScope.Base, null); - searchRequest.Controls.Add(new SearchOptionsControl(SearchOption.DomainScope)); - - var response = (SearchResponse)connection.SendRequest(searchRequest); - if (response?.Entries == null) - { - connection.Dispose(); - return new LdapConnectionTestResult(false, null, null, null); - } - - if (response.Entries.Count == 0) - { - connection.Dispose(); - return new LdapConnectionTestResult(false, new LdapException((int)LdapErrorCodes.KerberosAuthType), null, null); - } - - var entry = response.Entries[0]; - var baseDN = entry.GetProperty(LDAPProperties.RootDomainNamingContext).ToUpper().Trim(); - var configurationDN = entry.GetProperty(LDAPProperties.ConfigurationNamingContext).ToUpper().Trim(); - var domainname = Helpers.DistinguishedNameToDomain(baseDN).ToUpper().Trim(); - var servername = entry.GetProperty(LDAPProperties.ServerName); - var compName = servername.Substring(0, servername.IndexOf(',')).Substring(3).Trim(); - var fullServerName = $"{compName}.{domainname}".ToUpper().Trim(); - - return new LdapConnectionTestResult(true, null, new DomainInfo - { - DomainConfigurationPath = configurationDN, - DomainSearchBase = baseDN, - DomainFQDN = domainname - }, fullServerName); - } - catch (LdapException e) - { - try - { - connection.Dispose(); - } - catch - { - //pass - } - return new LdapConnectionTestResult(false, e, null, null); - } - } - - public class LdapConnectionTestResult - { - public bool Success { get; set; } - public LdapException Exception { get; set; } - public DomainInfo DomainInfo { get; set; } - public string ServerName { get; set; } - - public LdapConnectionTestResult(bool success, LdapException e, DomainInfo info, string server) - { - Success = success; - Exception = e; - DomainInfo = info; - ServerName = server; - } - } - - private string GetDomainNetbiosName(LdapConnection connection, DomainInfo info) - { - try - { - var searchRequest = new SearchRequest($"CN=Partitions,{info.DomainConfigurationPath}", - "(&(nETBIOSName=*)(dnsRoot=*))", - SearchScope.Subtree, new[] { LDAPProperties.NetbiosName, LDAPProperties.DnsRoot }); - - var response = (SearchResponse)connection.SendRequest(searchRequest); - if (response == null || response.Entries.Count == 0) - { - return ""; - } - - foreach (SearchResultEntry entry in response.Entries) - { - var root = entry.GetProperty(LDAPProperties.DnsRoot); - var netbios = entry.GetProperty(LDAPProperties.NetbiosName); - _log.LogInformation(root); - _log.LogInformation(netbios); - - if (root.ToUpper().Equals(info.DomainFQDN)) - { - return netbios.ToUpper(); - } - } - - return ""; - } - catch (LdapException e) - { - _log.LogWarning("Failed grabbing netbios name from ldap for {domain}: {e}", info.DomainFQDN, e); - return ""; - } - } - - private string GetDomainSidFromConnection(LdapConnection connection, DomainInfo info) - { - try - { - //This ldap filter searches for domain controllers - //Searches for any accounts with a UAC value inclusive of 8192 bitwise - //8192 is the flag for SERVER_TRUST_ACCOUNT, which is set only on Domain Controllers - var searchRequest = new SearchRequest(info.DomainSearchBase, - "(&(objectCategory=computer)(userAccountControl:1.2.840.113556.1.4.803:=8192))", - SearchScope.Subtree, new[] { "objectsid"}); - - var response = (SearchResponse)connection.SendRequest(searchRequest); - if (response == null || response.Entries.Count == 0) - { - return ""; - } - - var entry = response.Entries[0]; - var sid = entry.GetSid(); - return sid.Substring(0, sid.LastIndexOf('-')).ToUpper(); - } - catch (LdapException) - { - _log.LogWarning("Failed grabbing domainsid from ldap for {domain}", info.DomainFQDN); - return ""; - } - } - - private void SetupLdapConnection(LdapConnection connection, bool ssl, AuthType authType) - { - //These options are important! - connection.SessionOptions.ProtocolVersion = 3; - //Referral chasing does not work with paged searches - connection.SessionOptions.ReferralChasing = ReferralChasingOptions.None; - if (ssl) - { - connection.SessionOptions.SecureSocketLayer = true; - } - - if (_ldapConfig.DisableSigning) - { - connection.SessionOptions.Sealing = false; - connection.SessionOptions.Signing = false; - } - - if (_ldapConfig.DisableCertVerification) - connection.SessionOptions.VerifyServerCertificate = (_, _) => true; - - if (_ldapConfig.Username != null) - { - var cred = new NetworkCredential(_ldapConfig.Username, _ldapConfig.Password); - connection.Credential = cred; - } - - connection.AuthType = authType; - } - - /// - /// Normalizes a domain name to its full DNS name - /// - /// - /// - internal string NormalizeDomainName(string domain) - { - if (domain == null) - return null; - - var resolved = domain; - - if (resolved.Contains(".")) - return domain.ToUpper(); - - resolved = ResolveDomainNetbiosToDns(domain) ?? domain; - - return resolved.ToUpper(); - } - - /// - /// Turns a domain Netbios name into its FQDN using the DsGetDcName function (TESTLAB -> TESTLAB.LOCAL) - /// - /// - /// - internal string ResolveDomainNetbiosToDns(string domainName) - { - var key = domainName.ToUpper(); - if (_netbiosCache.TryGetValue(key, out var flatName)) - return flatName; - - var domain = GetDomain(domainName); - if (domain != null) - { - _netbiosCache.TryAdd(key, domain.Name); - return domain.Name; - } - - var computerName = _ldapConfig.Server; - - var dci = _nativeMethods.CallDsGetDcName(computerName, domainName); - if (dci.IsSuccess) - { - flatName = dci.Value.DomainName; - _netbiosCache.TryAdd(key, flatName); - return flatName; - } - - return domainName.ToUpper(); - } - - /// - /// Gets the range retrieval limit for a domain - /// - /// - /// - /// - public int GetDomainRangeSize(string domainName = null, int defaultRangeSize = 750) - { - var domainPath = DomainNameToDistinguishedName(domainName); - //Default to a page size of 750 for safety - if (domainPath == null) - { - _log.LogDebug("Unable to resolve domain {Domain} to distinguishedname to get page size", - domainName ?? "current domain"); - return defaultRangeSize; - } - - if (_ldapRangeSizeCache.TryGetValue(domainPath.ToUpper(), out var parsedPageSize)) - { - return parsedPageSize; - } - - var configPath = CommonPaths.CreateDNPath(CommonPaths.QueryPolicyPath, domainPath); - var enumerable = QueryLDAP("(objectclass=*)", SearchScope.Base, null, adsPath: configPath); - var config = enumerable.DefaultIfEmpty(null).FirstOrDefault(); - var pageSize = config?.GetArrayProperty(LDAPProperties.LdapAdminLimits) - .FirstOrDefault(x => x.StartsWith("MaxPageSize", StringComparison.OrdinalIgnoreCase)); - if (pageSize == null) - { - _log.LogDebug("No LDAPAdminLimits object found for {Domain}", domainName); - _ldapRangeSizeCache.TryAdd(domainPath.ToUpper(), defaultRangeSize); - return defaultRangeSize; - } - - if (int.TryParse(pageSize.Split('=').Last(), out parsedPageSize)) - { - _ldapRangeSizeCache.TryAdd(domainPath.ToUpper(), parsedPageSize); - _log.LogInformation("Found page size {PageSize} for {Domain}", parsedPageSize, - domainName ?? "current domain"); - return parsedPageSize; - } - - _log.LogDebug("Failed to parse pagesize for {Domain}, returning default", domainName ?? "current domain"); - - _ldapRangeSizeCache.TryAdd(domainPath.ToUpper(), defaultRangeSize); - return defaultRangeSize; - } - - private string DomainNameToDistinguishedName(string domain) - { - var resolvedDomain = GetDomain(domain)?.Name ?? domain; - return resolvedDomain == null ? null : $"DC={resolvedDomain.Replace(".", ",DC=")}"; - } - - private class ResolvedWellKnownPrincipal - { - public string DomainName { get; set; } - public string WkpId { get; set; } - } - - public string GetConfigurationPath(string domainName = null) - { - string path = domainName == null - ? "LDAP://RootDSE" - : $"LDAP://{NormalizeDomainName(domainName)}/RootDSE"; - - DirectoryEntry rootDse; - if (_ldapConfig.Username != null) - rootDse = new DirectoryEntry(path, _ldapConfig.Username, _ldapConfig.Password); - else - rootDse = new DirectoryEntry(path); - - return $"{rootDse.Properties["configurationNamingContext"]?[0]}"; - } - - public string GetSchemaPath(string domainName) - { - string path = domainName == null - ? "LDAP://RootDSE" - : $"LDAP://{NormalizeDomainName(domainName)}/RootDSE"; - - DirectoryEntry rootDse; - if (_ldapConfig.Username != null) - rootDse = new DirectoryEntry(path, _ldapConfig.Username, _ldapConfig.Password); - else - rootDse = new DirectoryEntry(path); - - return $"{rootDse.Properties["schemaNamingContext"]?[0]}"; - } - - public bool IsDomainController(string computerObjectId, string domainName) - { - var filter = new LDAPFilter().AddFilter(LDAPProperties.ObjectSID + "=" + computerObjectId, true) - .AddFilter(CommonFilters.DomainControllers, true); - var res = QueryLDAP(filter.GetFilter(), SearchScope.Subtree, - CommonProperties.ObjectID, domainName: domainName); - if (res.Count() > 0) - return true; - return false; - } - } -} \ No newline at end of file +// using System; +// using System.Collections.Concurrent; +// using System.Collections.Generic; +// using System.Diagnostics; +// using System.DirectoryServices; +// using System.DirectoryServices.ActiveDirectory; +// using System.DirectoryServices.Protocols; +// using System.Linq; +// using System.Net; +// using System.Net.Sockets; +// using System.Security.Principal; +// using System.Text; +// using System.Threading; +// using System.Threading.Tasks; +// using Microsoft.Extensions.Logging; +// using SharpHoundCommonLib.Enums; +// using SharpHoundCommonLib.Exceptions; +// using SharpHoundCommonLib.LDAPQueries; +// using SharpHoundCommonLib.OutputTypes; +// using SharpHoundCommonLib.Processors; +// using SharpHoundRPC.NetAPINative; +// using Domain = System.DirectoryServices.ActiveDirectory.Domain; +// using SearchScope = System.DirectoryServices.Protocols.SearchScope; +// using SecurityMasks = System.DirectoryServices.Protocols.SecurityMasks; +// +// namespace SharpHoundCommonLib +// { +// public class LDAPUtils : ILDAPUtils +// { +// private const string NullCacheKey = "UNIQUENULL"; +// +// // The following byte stream contains the necessary message to request a NetBios name from a machine +// // http://web.archive.org/web/20100409111218/http://msdn.microsoft.com/en-us/library/system.net.sockets.socket.aspx +// private static readonly byte[] NameRequest = +// { +// 0x80, 0x94, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, +// 0x00, 0x00, 0x00, 0x00, 0x20, 0x43, 0x4b, 0x41, +// 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, +// 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, +// 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, +// 0x41, 0x41, 0x41, 0x41, 0x41, 0x00, 0x00, 0x21, +// 0x00, 0x01 +// }; +// +// +// private static readonly ConcurrentDictionary +// SeenWellKnownPrincipals = new(); +// +// private static readonly ConcurrentDictionary DomainControllers = new(); +// private static readonly ConcurrentDictionary CachedDomainInfo = new(StringComparer.OrdinalIgnoreCase); +// +// private readonly ConcurrentDictionary _domainCache = new(); +// private static readonly TimeSpan MinBackoffDelay = TimeSpan.FromSeconds(2); +// private static readonly TimeSpan MaxBackoffDelay = TimeSpan.FromSeconds(20); +// private const int BackoffDelayMultiplier = 2; +// private const int MaxRetries = 3; +// +// private readonly ConcurrentDictionary _hostResolutionMap = new(); +// private readonly ConcurrentDictionary _ldapConnections = new(); +// private readonly ConcurrentDictionary _ldapRangeSizeCache = new(); +// private readonly ILogger _log; +// private readonly NativeMethods _nativeMethods; +// private readonly ConcurrentDictionary _netbiosCache = new(); +// private readonly PortScanner _portScanner; +// private LDAPConfig _ldapConfig = new(); +// private readonly ManualResetEvent _connectionResetEvent = new(false); +// private readonly object _lockObj = new(); +// +// +// /// +// /// Creates a new instance of LDAP Utils with defaults +// /// +// public LDAPUtils() +// { +// _nativeMethods = new NativeMethods(); +// _portScanner = new PortScanner(); +// _log = Logging.LogProvider.CreateLogger("LDAPUtils"); +// } +// +// /// +// /// Creates a new instance of LDAP utils and allows overriding implementations +// /// +// /// +// /// +// /// +// public LDAPUtils(NativeMethods nativeMethods = null, PortScanner scanner = null, ILogger log = null) +// { +// _nativeMethods = nativeMethods ?? new NativeMethods(); +// _portScanner = scanner ?? new PortScanner(); +// _log = log ?? Logging.LogProvider.CreateLogger("LDAPUtils"); +// } +// +// /// +// /// Sets the configuration for LDAP queries +// /// +// /// +// /// +// public void SetLDAPConfig(LDAPConfig config) +// { +// _ldapConfig = config ?? throw new ArgumentNullException(nameof(config), "LDAP Configuration can not be null"); +// //Close out any existing LDAP connections to request a new incoming config +// foreach (var kv in _ldapConnections) +// { +// kv.Value.Connection.Dispose(); +// } +// +// _ldapConnections.Clear(); +// } +// +// /// +// /// Turns a sid into a well known principal ID. +// /// +// /// +// /// +// /// +// /// True if a well known principal was identified, false if not +// public bool GetWellKnownPrincipal(string sid, string domain, out TypedPrincipal commonPrincipal) +// { +// if (!WellKnownPrincipal.GetWellKnownPrincipal(sid, out commonPrincipal)) return false; +// var tempDomain = domain ?? GetDomain()?.Name ?? "UNKNOWN"; +// commonPrincipal.ObjectIdentifier = ConvertWellKnownPrincipal(sid, tempDomain); +// SeenWellKnownPrincipals.TryAdd(commonPrincipal.ObjectIdentifier, new ResolvedWellKnownPrincipal +// { +// DomainName = domain, +// WkpId = sid +// }); +// return true; +// } +// +// public bool ConvertLocalWellKnownPrincipal(SecurityIdentifier sid, string computerDomainSid, +// string computerDomain, out TypedPrincipal principal) +// { +// if (WellKnownPrincipal.GetWellKnownPrincipal(sid.Value, out var common)) +// { +// //The everyone and auth users principals are special and will be converted to the domain equivalent +// if (sid.Value is "S-1-1-0" or "S-1-5-11") +// { +// GetWellKnownPrincipal(sid.Value, computerDomain, out principal); +// return true; +// } +// +// //Use the computer object id + the RID of the sid we looked up to create our new principal +// principal = new TypedPrincipal +// { +// ObjectIdentifier = $"{computerDomainSid}-{sid.Rid()}", +// ObjectType = common.ObjectType switch +// { +// Label.User => Label.LocalUser, +// Label.Group => Label.LocalGroup, +// _ => common.ObjectType +// } +// }; +// +// return true; +// } +// +// principal = null; +// return false; +// } +// +// /// +// /// Adds a SID to an internal list of domain controllers +// /// +// /// +// public void AddDomainController(string domainControllerSID) +// { +// DomainControllers.TryAdd(domainControllerSID, new byte()); +// } +// +// /// +// /// Gets output objects for currently observed well known principals +// /// +// /// +// /// +// public IEnumerable GetWellKnownPrincipalOutput(string domain) +// { +// foreach (var wkp in SeenWellKnownPrincipals) +// { +// WellKnownPrincipal.GetWellKnownPrincipal(wkp.Value.WkpId, out var principal); +// OutputBase output = principal.ObjectType switch +// { +// Label.User => new User(), +// Label.Computer => new Computer(), +// Label.Group => new Group(), +// Label.GPO => new GPO(), +// Label.Domain => new OutputTypes.Domain(), +// Label.OU => new OU(), +// Label.Container => new Container(), +// Label.Configuration => new Container(), +// _ => throw new ArgumentOutOfRangeException() +// }; +// +// output.Properties.Add("name", $"{principal.ObjectIdentifier}@{wkp.Value.DomainName}".ToUpper()); +// var domainSid = GetSidFromDomainName(wkp.Value.DomainName); +// output.Properties.Add("domainsid", domainSid); +// output.Properties.Add("domain", wkp.Value.DomainName.ToUpper()); +// output.ObjectIdentifier = wkp.Key; +// yield return output; +// } +// +// var entdc = GetBaseEnterpriseDC(domain); +// entdc.Members = DomainControllers.Select(x => new TypedPrincipal(x.Key, Label.Computer)).ToArray(); +// yield return entdc; +// } +// +// /// +// /// Converts a +// /// +// /// +// /// +// /// +// public string ConvertWellKnownPrincipal(string sid, string domain) +// { +// if (!WellKnownPrincipal.GetWellKnownPrincipal(sid, out _)) return sid; +// +// if (sid != "S-1-5-9") return $"{domain}-{sid}".ToUpper(); +// +// var forest = GetForest(domain)?.Name; +// if (forest == null) _log.LogWarning("Error getting forest, ENTDC sid is likely incorrect"); +// return $"{forest ?? "UNKNOWN"}-{sid}".ToUpper(); +// } +// +// /// +// /// Queries the global catalog to get potential SID matches for a username in the forest +// /// +// /// +// /// +// public string[] GetUserGlobalCatalogMatches(string name) +// { +// var tempName = name.ToLower(); +// if (Cache.GetGCCache(tempName, out var sids)) +// return sids; +// +// var query = new LDAPFilter().AddUsers($"samaccountname={tempName}").GetFilter(); +// var results = QueryLDAP(query, SearchScope.Subtree, new[] { "objectsid" }, globalCatalog: true) +// .Select(x => x.GetSid()).Where(x => x != null).ToArray(); +// Cache.AddGCCache(tempName, results); +// return results; +// } +// +// /// +// /// Uses an LDAP lookup to attempt to find the Label for a given SID +// /// Will also convert to a well known principal ID if needed +// /// +// /// +// /// +// /// +// public TypedPrincipal ResolveIDAndType(string id, string fallbackDomain) +// { +// //This is a duplicated SID object which is weird and makes things unhappy. Throw it out +// if (id.Contains("0ACNF")) +// return null; +// +// if (GetWellKnownPrincipal(id, fallbackDomain, out var principal)) +// return principal; +// +// var type = id.StartsWith("S-") ? LookupSidType(id, fallbackDomain) : LookupGuidType(id, fallbackDomain); +// return new TypedPrincipal(id, type); +// } +// +// public TypedPrincipal ResolveCertTemplateByProperty(string propValue, string propertyName, string containerDN, +// string domainName) +// { +// var filter = new LDAPFilter().AddCertificateTemplates().AddFilter(propertyName + "=" + propValue, true); +// var res = QueryLDAP(filter.GetFilter(), SearchScope.OneLevel, +// CommonProperties.TypeResolutionProps, adsPath: containerDN, domainName: domainName); +// +// if (res == null) +// { +// _log.LogWarning( +// "Could not find certificate template with '{propertyName}:{propValue}' under {containerDN}; null result", +// propertyName, propValue, containerDN); +// return null; +// } +// +// List resList = new List(res); +// if (resList.Count == 0) +// { +// _log.LogWarning( +// "Could not find certificate template with '{propertyName}:{propValue}' under {containerDN}; empty list", +// propertyName, propValue, containerDN); +// return null; +// } +// +// if (resList.Count > 1) +// { +// _log.LogWarning( +// "Found more than one certificate template with '{propertyName}:{propValue}' under {containerDN}", +// propertyName, propValue, containerDN); +// return null; +// } +// +// ISearchResultEntry searchResultEntry = resList.FirstOrDefault(); +// return new TypedPrincipal(searchResultEntry.GetGuid(), Label.CertTemplate); +// } +// +// /// +// /// Attempts to lookup the Label for a sid +// /// +// /// +// /// +// /// +// public Label LookupSidType(string sid, string domain) +// { +// if (Cache.GetIDType(sid, out var type)) +// return type; +// +// var rDomain = GetDomainNameFromSid(sid) ?? domain; +// +// var result = +// QueryLDAP(CommonFilters.SpecificSID(sid), SearchScope.Subtree, CommonProperties.TypeResolutionProps, +// rDomain) +// .DefaultIfEmpty(null).FirstOrDefault(); +// +// type = result?.GetLabel() ?? Label.Base; +// Cache.AddType(sid, type); +// return type; +// } +// +// /// +// /// Attempts to lookup the Label for a GUID +// /// +// /// +// /// +// /// +// public Label LookupGuidType(string guid, string domain) +// { +// if (Cache.GetIDType(guid, out var type)) +// return type; +// +// var hex = Helpers.ConvertGuidToHexGuid(guid); +// if (hex == null) +// return Label.Base; +// +// var result = +// QueryLDAP($"(objectguid={hex})", SearchScope.Subtree, CommonProperties.TypeResolutionProps, domain) +// .DefaultIfEmpty(null).FirstOrDefault(); +// +// type = result?.GetLabel() ?? Label.Base; +// Cache.AddType(guid, type); +// return type; +// } +// +// /// +// /// Attempts to find the domain associated with a SID +// /// +// /// +// /// +// public string GetDomainNameFromSid(string sid) +// { +// try +// { +// var parsedSid = new SecurityIdentifier(sid); +// var domainSid = parsedSid.AccountDomainSid?.Value.ToUpper(); +// if (domainSid == null) +// return null; +// +// _log.LogDebug("Resolving sid {DomainSid}", domainSid); +// +// if (Cache.GetDomainSidMapping(domainSid, out var domain)) +// return domain; +// +// _log.LogDebug("No cache hit for {DomainSid}", domainSid); +// domain = GetDomainNameFromSidLdap(domainSid); +// _log.LogDebug("Resolved to {Domain}", domain); +// +// //Cache both to and from so we can use this later +// if (domain != null) +// { +// Cache.AddDomainSidMapping(domainSid, domain); +// Cache.AddDomainSidMapping(domain, domainSid); +// } +// +// return domain; +// } +// catch +// { +// return null; +// } +// } +// +// /// +// /// Attempts to get the SID associated with a domain name +// /// +// /// +// /// +// public string GetSidFromDomainName(string domainName) +// { +// var tempDomainName = NormalizeDomainName(domainName); +// if (tempDomainName == null) +// return null; +// if (Cache.GetDomainSidMapping(tempDomainName, out var sid)) return sid; +// +// var domainObj = GetDomain(tempDomainName); +// +// if (domainObj != null) +// sid = domainObj.GetDirectoryEntry().GetSid(); +// else +// sid = null; +// +// if (sid != null) +// { +// Cache.AddDomainSidMapping(sid, tempDomainName); +// Cache.AddDomainSidMapping(tempDomainName, sid); +// if (tempDomainName != domainName) +// { +// Cache.AddDomainSidMapping(domainName, sid); +// } +// } +// +// return sid; +// } +// +// // Saving this code for an eventual async implementation +// // public async IAsyncEnumerable DoRangedRetrievalAsync(string distinguishedName, string attributeName) +// // { +// // var domainName = Helpers.DistinguishedNameToDomain(distinguishedName); +// // LdapConnection conn; +// // try +// // { +// // conn = await CreateLDAPConnection(domainName, authType: _ldapConfig.AuthType); +// // } +// // catch +// // { +// // yield break; +// // } +// // +// // if (conn == null) +// // yield break; +// // +// // var index = 0; +// // var step = 0; +// // var currentRange = $"{attributeName};range={index}-*"; +// // var complete = false; +// // +// // var searchRequest = CreateSearchRequest($"{attributeName}=*", SearchScope.Base, new[] {currentRange}, +// // domainName, distinguishedName); +// // +// // var backoffDelay = MinBackoffDelay; +// // var retryCount = 0; +// // +// // while (true) +// // { +// // DirectoryResponse searchResult; +// // try +// // { +// // searchResult = await Task.Factory.FromAsync(conn.BeginSendRequest, conn.EndSendRequest, +// // searchRequest, +// // PartialResultProcessing.NoPartialResultSupport, null); +// // } +// // catch (LdapException le) when (le.ErrorCode == 51 && retryCount < MaxRetries) +// // { +// // //Allow three retries with a backoff on each one if we get a "Server is Busy" error +// // retryCount++; +// // await Task.Delay(backoffDelay); +// // backoffDelay = TimeSpan.FromSeconds(Math.Min( +// // backoffDelay.TotalSeconds * BackoffDelayMultiplier.TotalSeconds, MaxBackoffDelay.TotalSeconds)); +// // continue; +// // } +// // catch (Exception e) +// // { +// // _log.LogWarning(e,"Caught exception during ranged retrieval for {DN}", distinguishedName); +// // yield break; +// // } +// // +// // if (searchResult is SearchResponse response && response.Entries.Count == 1) +// // { +// // var entry = response.Entries[0]; +// // var attributeNames = entry?.Attributes?.AttributeNames; +// // if (attributeNames != null) +// // { +// // foreach (string attr in attributeNames) +// // { +// // //Set our current range to the name of the attribute, which will tell us how far we are in "paging" +// // currentRange = attr; +// // //Check if the string has the * character in it. If it does, we've reached the end of this search +// // complete = currentRange.IndexOf("*", 0, StringComparison.Ordinal) > 0; +// // //Set our step to the number of attributes that came back. +// // step = entry.Attributes[currentRange].Count; +// // } +// // } +// // +// // +// // foreach (string val in entry.Attributes[currentRange].GetValues(typeof(string))) +// // { +// // yield return val; +// // index++; +// // } +// // +// // if (complete) yield break; +// // +// // currentRange = $"{attributeName};range={index}-{index + step}"; +// // searchRequest.Attributes.Clear(); +// // searchRequest.Attributes.Add(currentRange); +// // } +// // else +// // { +// // yield break; +// // } +// // } +// // } +// +// /// +// /// Performs Attribute Ranged Retrieval +// /// https://docs.microsoft.com/en-us/windows/win32/adsi/attribute-range-retrieval +// /// The function self-determines the range and internally handles the maximum step allowed by the server +// /// +// /// +// /// +// /// +// public IEnumerable DoRangedRetrieval(string distinguishedName, string attributeName) +// { +// var domainName = Helpers.DistinguishedNameToDomain(distinguishedName); +// var task = Task.Run(() => CreateLDAPConnectionWrapper(domainName, authType: _ldapConfig.AuthType)); +// +// LdapConnectionWrapper connWrapper; +// +// try +// { +// connWrapper = task.ConfigureAwait(false).GetAwaiter().GetResult(); +// } +// catch +// { +// yield break; +// } +// +// if (connWrapper.Connection == null) +// yield break; +// +// var conn = connWrapper.Connection; +// +// var index = 0; +// var step = 0; +// var baseString = $"{attributeName}"; +// //Example search string: member;range=0-1000 +// var currentRange = $"{baseString};range={index}-*"; +// var complete = false; +// +// var searchRequest = CreateSearchRequest($"{attributeName}=*", SearchScope.Base, new[] { currentRange }, +// connWrapper.DomainInfo, distinguishedName); +// +// if (searchRequest == null) +// yield break; +// +// var backoffDelay = MinBackoffDelay; +// var retryCount = 0; +// +// while (true) +// { +// SearchResponse response; +// try +// { +// response = (SearchResponse)conn.SendRequest(searchRequest); +// } +// catch (LdapException le) when (le.ErrorCode == (int)LdapErrorCodes.Busy && retryCount < MaxRetries) +// { +// //Allow three retries with a backoff on each one if we get a "Server is Busy" error +// retryCount++; +// Thread.Sleep(backoffDelay); +// backoffDelay = GetNextBackoff(retryCount); +// continue; +// } +// catch (Exception e) +// { +// _log.LogError(e, "Error doing ranged retrieval for {Attribute} on {Dn}", attributeName, +// distinguishedName); +// yield break; +// } +// +// //If we ever get more than one response from here, something is horribly wrong +// if (response?.Entries.Count == 1) +// { +// var entry = response.Entries[0]; +// //Process the attribute we get back to determine a few things +// foreach (string attr in entry.Attributes.AttributeNames) +// { +// //Set our current range to the name of the attribute, which will tell us how far we are in "paging" +// currentRange = attr; +// //Check if the string has the * character in it. If it does, we've reached the end of this search +// complete = currentRange.IndexOf("*", 0, StringComparison.Ordinal) > 0; +// //Set our step to the number of attributes that came back. +// step = entry.Attributes[currentRange].Count; +// } +// +// foreach (string val in entry.Attributes[currentRange].GetValues(typeof(string))) +// { +// yield return val; +// index++; +// } +// +// if (complete) yield break; +// +// currentRange = $"{baseString};range={index}-{index + step}"; +// searchRequest.Attributes.Clear(); +// searchRequest.Attributes.Add(currentRange); +// } +// else +// { +// //Something went wrong here. +// yield break; +// } +// } +// } +// +// /// +// /// Takes a host in most applicable forms from AD and attempts to resolve it into a SID. +// /// +// /// +// /// +// /// +// public async Task ResolveHostToSid(string hostname, string domain) +// { +// var strippedHost = Helpers.StripServicePrincipalName(hostname).ToUpper().TrimEnd('$'); +// if (string.IsNullOrEmpty(strippedHost)) +// { +// return null; +// } +// +// if (_hostResolutionMap.TryGetValue(strippedHost, out var sid)) return sid; +// +// var normalDomain = NormalizeDomainName(domain); +// +// string tempName; +// string tempDomain = null; +// +// //Step 1: Handle non-IP address values +// if (!IPAddress.TryParse(strippedHost, out _)) +// { +// // Format: ABC.TESTLAB.LOCAL +// if (strippedHost.Contains(".")) +// { +// var split = strippedHost.Split('.'); +// tempName = split[0]; +// tempDomain = string.Join(".", split.Skip(1).ToArray()); +// } +// // Format: WINDOWS +// else +// { +// tempName = strippedHost; +// tempDomain = normalDomain; +// } +// +// // Add $ to the end of the name to match how computers are stored in AD +// tempName = $"{tempName}$".ToUpper(); +// var principal = ResolveAccountName(tempName, tempDomain); +// sid = principal?.ObjectIdentifier; +// if (sid != null) +// { +// _hostResolutionMap.TryAdd(strippedHost, sid); +// return sid; +// } +// } +// +// //Step 2: Try NetWkstaGetInfo +// //Next we'll try calling NetWkstaGetInfo in hopes of getting the NETBIOS name directly from the computer +// //We'll use the hostname that we started with instead of the one from our previous step +// var workstationInfo = await GetWorkstationInfo(strippedHost); +// if (workstationInfo.HasValue) +// { +// tempName = workstationInfo.Value.ComputerName; +// tempDomain = workstationInfo.Value.LanGroup; +// +// if (string.IsNullOrEmpty(tempDomain)) +// tempDomain = normalDomain; +// +// if (!string.IsNullOrEmpty(tempName)) +// { +// //Append the $ to indicate this is a computer +// tempName = $"{tempName}$".ToUpper(); +// var principal = ResolveAccountName(tempName, tempDomain); +// sid = principal?.ObjectIdentifier; +// if (sid != null) +// { +// _hostResolutionMap.TryAdd(strippedHost, sid); +// return sid; +// } +// } +// } +// +// //Step 3: Socket magic +// // Attempt to request the NETBIOS name of the computer directly +// if (RequestNETBIOSNameFromComputer(strippedHost, normalDomain, out tempName)) +// { +// tempDomain ??= normalDomain; +// tempName = $"{tempName}$".ToUpper(); +// +// var principal = ResolveAccountName(tempName, tempDomain); +// sid = principal?.ObjectIdentifier; +// if (sid != null) +// { +// _hostResolutionMap.TryAdd(strippedHost, sid); +// return sid; +// } +// } +// +// //Try DNS resolution next +// string resolvedHostname; +// try +// { +// resolvedHostname = (await Dns.GetHostEntryAsync(strippedHost)).HostName; +// } +// catch +// { +// resolvedHostname = null; +// } +// +// if (resolvedHostname != null) +// { +// var splitName = resolvedHostname.Split('.'); +// tempName = $"{splitName[0]}$".ToUpper(); +// tempDomain = string.Join(".", splitName.Skip(1)); +// +// var principal = ResolveAccountName(tempName, tempDomain); +// sid = principal?.ObjectIdentifier; +// if (sid != null) +// { +// _hostResolutionMap.TryAdd(strippedHost, sid); +// return sid; +// } +// } +// +// //If we get here, everything has failed, and life is very sad. +// tempName = strippedHost; +// tempDomain = normalDomain; +// +// if (tempName.Contains(".")) +// { +// _hostResolutionMap.TryAdd(strippedHost, tempName); +// return tempName; +// } +// +// tempName = $"{tempName}.{tempDomain}"; +// _hostResolutionMap.TryAdd(strippedHost, tempName); +// return tempName; +// } +// +// /// +// /// Attempts to convert a bare account name (usually from session enumeration) to its corresponding ID and object type +// /// +// /// +// /// +// /// +// public TypedPrincipal ResolveAccountName(string name, string domain) +// { +// if (string.IsNullOrWhiteSpace(name)) +// return null; +// +// if (Cache.GetPrefixedValue(name, domain, out var id) && Cache.GetIDType(id, out var type)) +// return new TypedPrincipal +// { +// ObjectIdentifier = id, +// ObjectType = type +// }; +// +// var d = NormalizeDomainName(domain); +// var result = QueryLDAP($"(samaccountname={name})", SearchScope.Subtree, +// CommonProperties.TypeResolutionProps, +// d).DefaultIfEmpty(null).FirstOrDefault(); +// +// if (result == null) +// { +// _log.LogDebug("ResolveAccountName - unable to get result for {Name}", name); +// return null; +// } +// +// type = result.GetLabel(); +// id = result.GetObjectIdentifier(); +// +// if (id == null) +// { +// _log.LogDebug("ResolveAccountName - could not retrieve ID on {DN} for {Name}", result.DistinguishedName, +// name); +// return null; +// } +// +// Cache.AddPrefixedValue(name, domain, id); +// Cache.AddType(id, type); +// +// id = ConvertWellKnownPrincipal(id, domain); +// +// return new TypedPrincipal +// { +// ObjectIdentifier = id, +// ObjectType = type +// }; +// } +// +// /// +// /// Attempts to convert a distinguishedname to its corresponding ID and object type. +// /// +// /// DistinguishedName +// /// A TypedPrincipal object with the SID and Label +// public TypedPrincipal ResolveDistinguishedName(string dn) +// { +// if (Cache.GetConvertedValue(dn, out var id) && Cache.GetIDType(id, out var type)) +// return new TypedPrincipal +// { +// ObjectIdentifier = id, +// ObjectType = type +// }; +// +// var domain = Helpers.DistinguishedNameToDomain(dn); +// var result = QueryLDAP("(objectclass=*)", SearchScope.Base, CommonProperties.TypeResolutionProps, domain, +// adsPath: dn) +// .DefaultIfEmpty(null).FirstOrDefault(); +// +// if (result == null) +// { +// _log.LogDebug("ResolveDistinguishedName - No result for {DN}", dn); +// return null; +// } +// +// id = result.GetObjectIdentifier(); +// if (id == null) +// { +// _log.LogDebug("ResolveDistinguishedName - could not retrieve object identifier from {DN}", dn); +// return null; +// } +// +// if (GetWellKnownPrincipal(id, domain, out var principal)) return principal; +// +// type = result.GetLabel(); +// +// Cache.AddConvertedValue(dn, id); +// Cache.AddType(id, type); +// +// id = ConvertWellKnownPrincipal(id, domain); +// +// return new TypedPrincipal +// { +// ObjectIdentifier = id, +// ObjectType = type +// }; +// } +// +// /// +// /// Queries LDAP using LDAPQueryOptions +// /// +// /// +// /// +// public IEnumerable QueryLDAP(LDAPQueryOptions options) +// { +// return QueryLDAP( +// options.Filter, +// options.Scope, +// options.Properties, +// options.CancellationToken, +// options.DomainName, +// options.IncludeAcl, +// options.ShowDeleted, +// options.AdsPath, +// options.GlobalCatalog, +// options.SkipCache, +// options.ThrowException +// ); +// } +// +// /// +// /// Performs an LDAP query using the parameters specified by the user. +// /// +// /// LDAP filter +// /// SearchScope to query +// /// LDAP properties to fetch for each object +// /// Cancellation Token +// /// Include the DACL and Owner values in the NTSecurityDescriptor +// /// Include deleted objects +// /// Domain to query +// /// ADS path to limit the query too +// /// Use the global catalog instead of the regular LDAP server +// /// +// /// Skip the connection cache and force a new connection. You must dispose of this connection +// /// yourself. +// /// +// /// Throw exceptions rather than logging the errors directly +// /// All LDAP search results matching the specified parameters +// /// +// /// Thrown when an error occurs during LDAP query (only when throwException = true) +// /// +// public IEnumerable QueryLDAP(string ldapFilter, SearchScope scope, +// string[] props, CancellationToken cancellationToken, string domainName = null, bool includeAcl = false, +// bool showDeleted = false, string adsPath = null, bool globalCatalog = false, bool skipCache = false, +// bool throwException = false) +// { +// var queryParams = SetupLDAPQueryFilter( +// ldapFilter, scope, props, includeAcl, domainName, includeAcl, adsPath, globalCatalog, skipCache); +// +// if (queryParams.Exception != null) +// { +// _log.LogWarning("Failed to setup LDAP Query Filter: {Message}", queryParams.Exception.Message); +// if (throwException) +// throw new LDAPQueryException("Failed to setup LDAP Query Filter", queryParams.Exception); +// yield break; +// } +// +// var conn = queryParams.Connection; +// var request = queryParams.SearchRequest; +// var pageControl = queryParams.PageControl; +// +// PageResultResponseControl pageResponse = null; +// var backoffDelay = MinBackoffDelay; +// var retryCount = 0; +// while (true) +// { +// if (cancellationToken.IsCancellationRequested) +// yield break; +// +// SearchResponse response; +// try +// { +// _log.LogTrace("Sending LDAP request for {Filter}", ldapFilter); +// response = (SearchResponse)conn.SendRequest(request); +// if (response != null) +// pageResponse = (PageResultResponseControl)response.Controls +// .Where(x => x is PageResultResponseControl).DefaultIfEmpty(null).FirstOrDefault(); +// } +// catch (LdapException le) when (le.ErrorCode == (int)LdapErrorCodes.ServerDown && +// retryCount < MaxRetries) +// { +// /*A ServerDown exception indicates that our connection is no longer valid for one of many reasons. +// However, this function is generally called by multiple threads, so we need to be careful in recreating +// the connection. Using a semaphore, we can ensure that only one thread is actually recreating the connection +// while the other threads that hit the ServerDown exception simply wait. The initial caller will hold the semaphore +// and do a backoff delay before trying to make a new connection which will replace the existing connection in the +// _ldapConnections cache. Other threads will retrieve the new connection from the cache instead of making a new one +// This minimizes overhead of new connections while still fixing our core problem.*/ +// +// //Always increment retry count +// retryCount++; +// +// //Attempt to acquire a lock +// if (Monitor.TryEnter(_lockObj)) +// { +// //If we've acquired the lock, we want to immediately signal our reset event so everyone else waits +// _connectionResetEvent.Reset(); +// try +// { +// //Sleep for our backoff +// Thread.Sleep(backoffDelay); +// //Explicitly skip the cache so we don't get the same connection back +// conn = CreateNewConnection(domainName, globalCatalog, true).Connection; +// if (conn == null) +// { +// _log.LogError( +// "Unable to create replacement ldap connection for ServerDown exception. Breaking loop"); +// yield break; +// } +// +// _log.LogInformation("Created new LDAP connection after receiving ServerDown from server"); +// } +// finally +// { +// //Reset our event + release the lock +// _connectionResetEvent.Set(); +// Monitor.Exit(_lockObj); +// } +// } +// else +// { +// //If someone else is holding the reset event, we want to just wait and then pull the newly created connection out of the cache +// //This event will be released after the first entrant thread is done making a new connection +// //The thread.sleep is to prevent a potential, very unlikely race +// Thread.Sleep(50); +// _connectionResetEvent.WaitOne(); +// conn = CreateNewConnection(domainName, globalCatalog).Connection; +// } +// +// backoffDelay = GetNextBackoff(retryCount); +// continue; +// } +// catch (LdapException le) when (le.ErrorCode == (int)LdapErrorCodes.Busy && retryCount < MaxRetries) +// { +// retryCount++; +// backoffDelay = GetNextBackoff(retryCount); +// continue; +// } +// catch (LdapException le) +// { +// if (le.ErrorCode != (int)LdapErrorCodes.LocalError) +// { +// if (throwException) +// { +// throw new LDAPQueryException( +// $"LDAP Exception in Loop: {le.ErrorCode}. {le.ServerErrorMessage}. {le.Message}. Filter: {ldapFilter}. Domain: {domainName}", +// le); +// } +// +// _log.LogWarning(le, +// "LDAP Exception in Loop: {ErrorCode}. {ServerErrorMessage}. {Message}. Filter: {Filter}. Domain: {Domain}", +// le.ErrorCode, le.ServerErrorMessage, le.Message, ldapFilter, domainName); +// } +// +// yield break; +// } +// catch (Exception e) +// { +// _log.LogWarning(e, "Exception in LDAP loop for {Filter} and {Domain}", ldapFilter, domainName); +// if (throwException) +// throw new LDAPQueryException($"Exception in LDAP loop for {ldapFilter} and {domainName}", e); +// +// yield break; +// } +// +// if (cancellationToken.IsCancellationRequested) +// yield break; +// +// if (response == null || pageResponse == null) +// continue; +// +// foreach (SearchResultEntry entry in response.Entries) +// { +// if (cancellationToken.IsCancellationRequested) +// yield break; +// +// yield return new SearchResultEntryWrapper(entry, this); +// } +// +// if (pageResponse.Cookie.Length == 0 || response.Entries.Count == 0 || +// cancellationToken.IsCancellationRequested) +// yield break; +// +// pageControl.Cookie = pageResponse.Cookie; +// } +// } +// +// private LdapConnectionWrapper CreateNewConnection(string domainName = null, bool globalCatalog = false, +// bool skipCache = false) +// { +// var task = Task.Run(() => CreateLDAPConnectionWrapper(domainName, skipCache, _ldapConfig.AuthType, globalCatalog)); +// +// try +// { +// return task.ConfigureAwait(false).GetAwaiter().GetResult(); +// } +// catch +// { +// return null; +// } +// } +// +// /// +// /// Performs an LDAP query using the parameters specified by the user. +// /// +// /// LDAP filter +// /// SearchScope to query +// /// LDAP properties to fetch for each object +// /// Include the DACL and Owner values in the NTSecurityDescriptor +// /// Include deleted objects +// /// Domain to query +// /// ADS path to limit the query too +// /// Use the global catalog instead of the regular LDAP server +// /// +// /// Skip the connection cache and force a new connection. You must dispose of this connection +// /// yourself. +// /// +// /// Throw exceptions rather than logging the errors directly +// /// All LDAP search results matching the specified parameters +// /// +// /// Thrown when an error occurs during LDAP query (only when throwException = true) +// /// +// public virtual IEnumerable QueryLDAP(string ldapFilter, SearchScope scope, +// string[] props, string domainName = null, bool includeAcl = false, bool showDeleted = false, +// string adsPath = null, bool globalCatalog = false, bool skipCache = false, bool throwException = false) +// { +// return QueryLDAP(ldapFilter, scope, props, new CancellationToken(), domainName, includeAcl, showDeleted, +// adsPath, globalCatalog, skipCache, throwException); +// } +// +// private static TimeSpan GetNextBackoff(int retryCount) +// { +// return TimeSpan.FromSeconds(Math.Min( +// MinBackoffDelay.TotalSeconds * Math.Pow(BackoffDelayMultiplier, retryCount), +// MaxBackoffDelay.TotalSeconds)); +// } +// +// /// +// /// Gets the forest associated with a domain. +// /// If no domain is provided, defaults to current domain +// /// +// /// +// /// +// public virtual Forest GetForest(string domainName = null) +// { +// try +// { +// if (domainName == null && _ldapConfig.Username == null) +// return Forest.GetCurrentForest(); +// +// var domain = GetDomain(domainName); +// return domain?.Forest; +// } +// catch +// { +// return null; +// } +// } +// +// /// +// /// Creates a new ActiveDirectorySecurityDescriptor +// /// Function created for testing purposes +// /// +// /// +// public ActiveDirectorySecurityDescriptor MakeSecurityDescriptor() +// { +// return new ActiveDirectorySecurityDescriptor(new ActiveDirectorySecurity()); +// } +// +// public string BuildLdapPath(string dnPath, string domainName) +// { +// //Check our cached info for a fast check +// if (CachedDomainInfo.TryGetValue(domainName, out var info)) +// { +// return $"{dnPath},{info.DomainSearchBase}"; +// } +// var domain = GetDomain(domainName)?.Name; +// if (domain == null) +// return null; +// +// var adPath = $"{dnPath},DC={domain.Replace(".", ",DC=")}"; +// return adPath; +// } +// +// /// +// /// Tests the current LDAP config to ensure its valid by pulling a domain object +// /// +// /// True if connection was successful, else false +// public bool TestLDAPConfig(string domain) +// { +// var filter = new LDAPFilter(); +// filter.AddDomains(); +// +// _log.LogTrace("Testing LDAP connection for domain {Domain}", domain); +// var result = QueryLDAP(filter.GetFilter(), SearchScope.Subtree, CommonProperties.ObjectID, domain, +// throwException: true) +// .DefaultIfEmpty(null).FirstOrDefault(); +// _log.LogTrace("Result object from LDAP connection test is {DN}", result?.DistinguishedName ?? "null"); +// return result != null; +// } +// +// /// +// /// Gets the domain object associated with the specified domain name. +// /// Defaults to current domain if none specified +// /// +// /// +// /// +// public virtual Domain GetDomain(string domainName = null) +// { +// var cacheKey = domainName ?? NullCacheKey; +// if (_domainCache.TryGetValue(cacheKey, out var domain)) return domain; +// +// try +// { +// DirectoryContext context; +// if (_ldapConfig.Username != null) +// context = domainName != null +// ? new DirectoryContext(DirectoryContextType.Domain, domainName, _ldapConfig.Username, +// _ldapConfig.Password) +// : new DirectoryContext(DirectoryContextType.Domain, _ldapConfig.Username, +// _ldapConfig.Password); +// else +// context = domainName != null +// ? new DirectoryContext(DirectoryContextType.Domain, domainName) +// : new DirectoryContext(DirectoryContextType.Domain); +// +// domain = Domain.GetDomain(context); +// } +// catch (Exception e) +// { +// _log.LogDebug(e, "GetDomain call failed at {StackTrace}", new StackFrame()); +// domain = null; +// } +// +// _domainCache.TryAdd(cacheKey, domain); +// return domain; +// } +// +// /// +// /// Setup LDAP query for filter +// /// +// /// LDAP filter +// /// SearchScope to query +// /// LDAP properties to fetch for each object +// /// Include the DACL and Owner values in the NTSecurityDescriptor +// /// Domain to query +// /// Include deleted objects +// /// ADS path to limit the query too +// /// Use the global catalog instead of the regular LDAP server +// /// +// /// Skip the connection cache and force a new connection. You must dispose of this connection +// /// yourself. +// /// +// /// Tuple of LdapConnection, SearchRequest, PageResultRequestControl and LDAPQueryException +// // ReSharper disable once MemberCanBePrivate.Global +// internal LDAPQueryParams SetupLDAPQueryFilter( +// string ldapFilter, +// SearchScope scope, string[] props, bool includeAcl = false, string domainName = null, +// bool showDeleted = false, +// string adsPath = null, bool globalCatalog = false, bool skipCache = false) +// { +// _log.LogTrace("Creating ldap connection for {Target} with filter {Filter}", +// globalCatalog ? "Global Catalog" : "DC", ldapFilter); +// var task = Task.Run(() => CreateLDAPConnectionWrapper(domainName, skipCache, _ldapConfig.AuthType, globalCatalog)); +// +// var queryParams = new LDAPQueryParams(); +// +// LdapConnectionWrapper connWrapper; +// try +// { +// connWrapper = task.ConfigureAwait(false).GetAwaiter().GetResult(); +// } +// catch (NoLdapDataException) +// { +// var errorString = +// $"Successfully connected via LDAP to {domainName ?? "Default Domain"} but no data received. This is most likely due to permissions or using kerberos authentication across trusts."; +// queryParams.Exception = new LDAPQueryException(errorString, null); +// return queryParams; +// } +// catch (LdapAuthenticationException e) +// { +// var errorString = +// $"Failed to connect via LDAP to {domainName ?? "Default Domain"}: Authentication is invalid"; +// queryParams.Exception = new LDAPQueryException(errorString, e.InnerException); +// return queryParams; +// } +// catch (LdapConnectionException e) +// { +// var errorString = +// $"Failed to connect via LDAP to {domainName ?? "Default Domain"}: {e.InnerException.Message} (Code: {e.ErrorCode}"; +// queryParams.Exception = new LDAPQueryException(errorString, e.InnerException); +// return queryParams; +// } +// +// var conn = connWrapper.Connection; +// +// //If we get a null connection, something went wrong, but we don't have an error to go with it for whatever reason +// if (conn == null) +// { +// var errorString = +// $"LDAP connection is null for filter {ldapFilter} and domain {domainName ?? "Default Domain"}"; +// queryParams.Exception = new LDAPQueryException(errorString); +// return queryParams; +// } +// +// SearchRequest request; +// +// try +// { +// request = CreateSearchRequest(ldapFilter, scope, props, connWrapper.DomainInfo, adsPath, showDeleted); +// } +// catch (LDAPQueryException ldapQueryException) +// { +// queryParams.Exception = ldapQueryException; +// return queryParams; +// } +// +// if (request == null) +// { +// var errorString = +// $"Search request is null for filter {ldapFilter} and domain {domainName ?? "Default Domain"}"; +// queryParams.Exception = new LDAPQueryException(errorString); +// return queryParams; +// } +// +// var pageControl = new PageResultRequestControl(500); +// request.Controls.Add(pageControl); +// +// if (includeAcl) +// request.Controls.Add(new SecurityDescriptorFlagControl +// { +// SecurityMasks = SecurityMasks.Dacl | SecurityMasks.Owner +// }); +// +// queryParams.Connection = conn; +// queryParams.SearchRequest = request; +// queryParams.PageControl = pageControl; +// +// return queryParams; +// } +// +// private Group GetBaseEnterpriseDC(string domain) +// { +// var forest = GetForest(domain)?.Name; +// if (forest == null) _log.LogWarning("Error getting forest, ENTDC sid is likely incorrect"); +// var g = new Group { ObjectIdentifier = $"{forest}-S-1-5-9".ToUpper() }; +// g.Properties.Add("name", $"ENTERPRISE DOMAIN CONTROLLERS@{forest ?? "UNKNOWN"}".ToUpper()); +// g.Properties.Add("domainsid", GetSidFromDomainName(forest)); +// g.Properties.Add("domain", forest); +// return g; +// } +// +// /// +// /// Updates the config for querying LDAP +// /// +// /// +// public void UpdateLDAPConfig(LDAPConfig config) +// { +// _ldapConfig = config; +// } +// +// private string GetDomainNameFromSidLdap(string sid) +// { +// var hexSid = Helpers.ConvertSidToHexSid(sid); +// +// if (hexSid == null) +// return null; +// +// //Search using objectsid first +// var result = +// QueryLDAP($"(&(objectclass=domain)(objectsid={hexSid}))", SearchScope.Subtree, +// new[] { "distinguishedname" }, globalCatalog: true).DefaultIfEmpty(null).FirstOrDefault(); +// +// if (result != null) +// { +// var domainName = Helpers.DistinguishedNameToDomain(result.DistinguishedName); +// return domainName; +// } +// +// //Try trusteddomain objects with the securityidentifier attribute +// result = +// QueryLDAP($"(&(objectclass=trusteddomain)(securityidentifier={sid}))", SearchScope.Subtree, +// new[] { "cn" }, globalCatalog: true).DefaultIfEmpty(null).FirstOrDefault(); +// +// if (result != null) +// { +// var domainName = result.GetProperty(LDAPProperties.CanonicalName); +// return domainName; +// } +// +// //We didn't find anything so just return null +// return null; +// } +// +// /// +// /// Uses a socket and a set of bytes to request the NETBIOS name from a remote computer +// /// +// /// +// /// +// /// +// /// +// private static bool RequestNETBIOSNameFromComputer(string server, string domain, out string netbios) +// { +// var receiveBuffer = new byte[1024]; +// var requestSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); +// try +// { +// //Set receive timeout to 1 second +// requestSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, 1000); +// EndPoint remoteEndpoint; +// +// //We need to create an endpoint to bind too. If its an IP, just use that. +// if (IPAddress.TryParse(server, out var parsedAddress)) +// remoteEndpoint = new IPEndPoint(parsedAddress, 137); +// else +// //If its not an IP, we're going to try and resolve it from DNS +// try +// { +// IPAddress address; +// if (server.Contains(".")) +// address = Dns +// .GetHostAddresses(server).First(x => x.AddressFamily == AddressFamily.InterNetwork); +// else +// address = Dns.GetHostAddresses($"{server}.{domain}")[0]; +// +// if (address == null) +// { +// netbios = null; +// return false; +// } +// +// remoteEndpoint = new IPEndPoint(address, 137); +// } +// catch +// { +// //Failed to resolve an IP, so return null +// netbios = null; +// return false; +// } +// +// var originEndpoint = new IPEndPoint(IPAddress.Any, 0); +// requestSocket.Bind(originEndpoint); +// +// try +// { +// requestSocket.SendTo(NameRequest, remoteEndpoint); +// var receivedByteCount = requestSocket.ReceiveFrom(receiveBuffer, ref remoteEndpoint); +// if (receivedByteCount >= 90) +// { +// netbios = new ASCIIEncoding().GetString(receiveBuffer, 57, 16).Trim('\0', ' '); +// return true; +// } +// +// netbios = null; +// return false; +// } +// catch (SocketException) +// { +// netbios = null; +// return false; +// } +// } +// finally +// { +// //Make sure we close the socket if its open +// requestSocket.Close(); +// } +// } +// +// /// +// /// Calls the NetWkstaGetInfo API on a hostname +// /// +// /// +// /// +// private async Task GetWorkstationInfo(string hostname) +// { +// if (!await _portScanner.CheckPort(hostname)) +// return null; +// +// var result = NetAPIMethods.NetWkstaGetInfo(hostname); +// if (result.IsSuccess) return result.Value; +// +// return null; +// } +// +// /// +// /// Creates a SearchRequest object for use in querying LDAP. +// /// +// /// LDAP filter +// /// SearchScope to query +// /// LDAP properties to fetch for each object +// /// Domain info object which is created alongside the LDAP connection +// /// ADS path to limit the query too +// /// Include deleted objects in results +// /// A built SearchRequest +// private SearchRequest CreateSearchRequest(string filter, SearchScope scope, string[] attributes, +// DomainInfo domainInfo, string adsPath = null, bool showDeleted = false) +// { +// var adPath = adsPath?.Replace("LDAP://", "") ?? domainInfo.DomainSearchBase; +// +// var request = new SearchRequest(adPath, filter, scope, attributes); +// request.Controls.Add(new SearchOptionsControl(SearchOption.DomainScope)); +// if (showDeleted) +// request.Controls.Add(new ShowDeletedControl()); +// +// return request; +// } +// +// private LdapConnection CreateConnectionHelper(string directoryIdentifier, bool ssl, AuthType authType, bool globalCatalog) +// { +// var port = globalCatalog ? _ldapConfig.GetGCPort(ssl) : _ldapConfig.GetPort(ssl); +// var identifier = new LdapDirectoryIdentifier(directoryIdentifier, port, false, false); +// var connection = new LdapConnection(identifier) { Timeout = new TimeSpan(0, 0, 5, 0) }; +// SetupLdapConnection(connection, true, authType); +// return connection; +// } +// +// private static void CheckAndThrowException(LdapException ldapException) +// { +// //A null error code with success false indicates that we successfully created a connection but got no data back, this is generally because our AuthType isn't compatible. +// //AuthType Kerberos will only work across trusts in very specific scenarios. Alternatively, we don't have read rights. +// //Throw this exception for clients to handle +// if (ldapException.ErrorCode is (int)LdapErrorCodes.KerberosAuthType or (int)ResultCode.InsufficientAccessRights) +// { +// throw new NoLdapDataException(ldapException.ErrorCode); +// } +// +// //We shouldn't ever hit this in theory, but we'll error out if its the case +// if (ldapException.ErrorCode is (int)ResultCode.InappropriateAuthentication) +// { +// throw new LdapAuthenticationException(ldapException); +// } +// +// //Any other error we dont have specific ways to handle +// if (ldapException.ErrorCode != (int)ResultCode.Unavailable && ldapException.ErrorCode != (int)ResultCode.Busy) +// { +// throw new LdapConnectionException(ldapException); +// } +// } +// +// private string ResolveDomainToFullName(string domain) +// { +// if (string.IsNullOrEmpty(domain)) +// { +// return GetDomain()?.Name.ToUpper().Trim(); +// } +// +// if (CachedDomainInfo.TryGetValue(domain.ToUpper(), out var info)) +// { +// return info.DomainFQDN; +// } +// +// return GetDomain(domain)?.Name.ToUpper().Trim(); +// } +// +// /// +// /// Creates an LDAP connection with appropriate options based off the ldap configuration. Caches connections +// /// +// /// The domain to connect too +// /// Skip the connection cache +// /// Auth type to use. Defaults to Kerberos. Use Negotiate for netonly/cross trust(forest) scenarios +// /// Use global catalog or not +// /// A connected LDAP connection or null +// +// private async Task CreateLDAPConnectionWrapper(string domainName = null, bool skipCache = false, +// AuthType authType = AuthType.Kerberos, bool globalCatalog = false) +// { +// // Step 1: If domain passed in is non-null, skip this step +// // - Call GetDomain with a null domain to get the user's current domain +// // Step 2: Take domain passed in to the function or resolved from step 1 +// // - Try an ldap connection on SSL +// // - If ServerUnavailable - Try an ldap connection on non-SSL +// // Step 3: Pass the domain to GetDomain to resolve to a better name (potentially) +// // - If we get a better name, repeat step 2 with the new name +// // Step 4: +// // - Use GetDomain to get a domain object along with a list of domain controllers +// // - Try the primary domain controller on both ssl/non-ssl +// // - Loop over domain controllers and try each on ssl/non-ssl +// +// //If a server has been manually specified, we should never get past this block for opsec reasons +// if (_ldapConfig.Server != null) +// { +// _log.LogInformation("Server is set via config, attempting to create ldap connection to {Server}", _ldapConfig.Server); +// if (!skipCache) +// { +// if (GetCachedConnection(_ldapConfig.Server, globalCatalog, out var conn)) +// { +// return conn; +// } +// } +// +// var singleServerConn = CreateLDAPConnection(_ldapConfig.Server, authType, globalCatalog); +// if (singleServerConn == null) { +// return new LdapConnectionWrapper +// { +// Connection = null, +// DomainInfo = null +// }; +// } +// +// var cacheKey = new LDAPConnectionCacheKey(_ldapConfig.Server, globalCatalog); +// _ldapConnections.AddOrUpdate(cacheKey, singleServerConn, (_, ldapConnection) => +// { +// ldapConnection.Connection.Dispose(); +// return singleServerConn; +// }); +// return singleServerConn; +// } +// +// //Take the incoming domain name and Upper/Trim it. If the name is null, we'll have to use GetDomain to figure out the user's domain context +// var domain = domainName?.ToUpper().Trim() ?? ResolveDomainToFullName(domainName); +// +// //If our domain is STILL null, we're not going to get anything reliable, so exit out +// if (domain == null) +// { +// _log.LogWarning("Initial domain name for new LDAP connection is null and/or unresolvable. Unable to create a new connection"); +// return new LdapConnectionWrapper +// { +// Connection = null, +// DomainInfo = null +// }; +// } +// +// if (!skipCache) +// { +// if (GetCachedConnection(domain, globalCatalog, out var conn)) +// { +// return conn; +// } +// } +// +// _log.LogInformation("No cached LDAP connection found for {Domain}, attempting a new connection", domain); +// +// var connectionWrapper = CreateLDAPConnection(domain, authType, globalCatalog); +// //If our connection isn't null, it means we have a good connection +// if (connectionWrapper != null) +// { +// var cacheKey = new LDAPConnectionCacheKey(domain, globalCatalog); +// _ldapConnections.AddOrUpdate(cacheKey, connectionWrapper, (_, ldapConnection) => +// { +// ldapConnection.Connection.Dispose(); +// return connectionWrapper; +// }); +// return connectionWrapper; +// } +// +// //If our incoming domain name wasn't null, try to re-resolve the name for a better potential match and then retry +// if (domainName != null) +// { +// var newDomain = ResolveDomainToFullName(domainName); +// if (!string.IsNullOrEmpty(newDomain) && !newDomain.Equals(domain, StringComparison.OrdinalIgnoreCase)) +// { +// //Set our domain name to the newly resolved value for future steps +// domain = newDomain; +// if (!skipCache) +// { +// //Check our cache again, maybe the new name works +// if (GetCachedConnection(domain, globalCatalog, out var conn)) +// { +// return conn; +// } +// } +// +// connectionWrapper = CreateLDAPConnection(domain, authType, globalCatalog); +// //If our connection isn't null, it means we have a good connection +// if (connectionWrapper != null) +// { +// var cacheKey = new LDAPConnectionCacheKey(domain, globalCatalog); +// _ldapConnections.AddOrUpdate(cacheKey, connectionWrapper, (_, ldapConnection) => +// { +// ldapConnection.Connection.Dispose(); +// return connectionWrapper; +// }); +// return connectionWrapper; +// } +// } +// } +// +// //Next step, look for domain controllers +// var domainObj = GetDomain(domain); +// if (domainObj?.Name == null) +// { +// return null; +// } +// +// //Start with the PDC of the domain and see if we can connect +// var pdc = domainObj.PdcRoleOwner.Name; +// connectionWrapper = await CreateLDAPConnectionWithPortCheck(pdc, authType, globalCatalog); +// if (connectionWrapper != null) +// { +// var cacheKey = new LDAPConnectionCacheKey(domain, globalCatalog); +// _ldapConnections.AddOrUpdate(cacheKey, connectionWrapper, (_, ldapConnection) => +// { +// ldapConnection.Connection.Dispose(); +// return connectionWrapper; +// }); +// return connectionWrapper; +// } +// +// //Loop over all other domain controllers and see if we can make a good connection to any +// foreach (DomainController dc in domainObj.DomainControllers) +// { +// connectionWrapper = await CreateLDAPConnectionWithPortCheck(dc.Name, authType, globalCatalog); +// if (connectionWrapper != null) +// { +// var cacheKey = new LDAPConnectionCacheKey(domain, globalCatalog); +// _ldapConnections.AddOrUpdate(cacheKey, connectionWrapper, (_, ldapConnection) => +// { +// ldapConnection.Connection.Dispose(); +// return connectionWrapper; +// }); +// return connectionWrapper; +// } +// } +// +// return new LdapConnectionWrapper() +// { +// Connection = null, +// DomainInfo = null +// }; +// } +// +// private bool GetCachedConnection(string domain, bool globalCatalog, out LdapConnectionWrapper connectionWrapper) +// { +// var domainName = domain; +// if (CachedDomainInfo.TryGetValue(domain.ToUpper(), out var resolved)) +// { +// domainName = resolved.DomainFQDN; +// } +// var key = new LDAPConnectionCacheKey(domainName, globalCatalog); +// return _ldapConnections.TryGetValue(key, out connectionWrapper); +// } +// +// private async Task CreateLDAPConnectionWithPortCheck(string target, AuthType authType, bool globalCatalog) +// { +// if (globalCatalog) +// { +// if (await _portScanner.CheckPort(target, _ldapConfig.GetGCPort(true)) || (!_ldapConfig.ForceSSL && +// await _portScanner.CheckPort(target, _ldapConfig.GetGCPort(false)))) +// { +// return CreateLDAPConnection(target, authType, true); +// } +// } +// else +// { +// if (await _portScanner.CheckPort(target, _ldapConfig.GetPort(true)) || (!_ldapConfig.ForceSSL && await _portScanner.CheckPort(target, _ldapConfig.GetPort(false)))) +// { +// return CreateLDAPConnection(target, authType, false); +// } +// } +// +// return null; +// } +// +// private LdapConnectionWrapper CreateLDAPConnection(string target, AuthType authType, bool globalCatalog) +// { +// //Lets build a new connection +// //Always try SSL first +// var connection = CreateConnectionHelper(target, true, authType, globalCatalog); +// var connectionResult = TestConnection(connection); +// DomainInfo info; +// +// if (connectionResult.Success) +// { +// var domain = connectionResult.DomainInfo.DomainFQDN; +// if (!CachedDomainInfo.ContainsKey(domain)) +// { +// var baseDomainInfo = connectionResult.DomainInfo; +// baseDomainInfo.DomainSID = GetDomainSidFromConnection(connection, baseDomainInfo); +// baseDomainInfo.DomainNetbiosName = GetDomainNetbiosName(connection, baseDomainInfo); +// _log.LogInformation("Got info for domain: {info}", baseDomainInfo); +// CachedDomainInfo.TryAdd(baseDomainInfo.DomainFQDN, baseDomainInfo); +// CachedDomainInfo.TryAdd(baseDomainInfo.DomainNetbiosName, baseDomainInfo); +// CachedDomainInfo.TryAdd(baseDomainInfo.DomainSID, baseDomainInfo); +// if (!string.IsNullOrEmpty(baseDomainInfo.DomainSID)) +// { +// Cache.AddDomainSidMapping(baseDomainInfo.DomainFQDN, baseDomainInfo.DomainSID); +// Cache.AddDomainSidMapping(baseDomainInfo.DomainSID, baseDomainInfo.DomainFQDN); +// if (!string.IsNullOrEmpty(baseDomainInfo.DomainNetbiosName)) +// { +// Cache.AddDomainSidMapping(baseDomainInfo.DomainNetbiosName, baseDomainInfo.DomainSID); +// } +// } +// +// if (!string.IsNullOrEmpty(baseDomainInfo.DomainNetbiosName)) +// { +// _netbiosCache.TryAdd(baseDomainInfo.DomainFQDN, baseDomainInfo.DomainNetbiosName); +// } +// +// info = baseDomainInfo; +// } +// else +// { +// CachedDomainInfo.TryGetValue(domain, out info); +// } +// return new LdapConnectionWrapper +// { +// Connection = connection, +// DomainInfo = info +// }; +// } +// +// CheckAndThrowException(connectionResult.Exception); +// +// //If we're not allowing fallbacks to LDAP from LDAPS, just return here +// if (_ldapConfig.ForceSSL) +// { +// return null; +// } +// //If we get to this point, it means we have an unsuccessful connection, but our error code doesn't indicate an outright failure +// //Try a new connection without SSL +// connection = CreateConnectionHelper(target, false, authType, globalCatalog); +// +// connectionResult = TestConnection(connection); +// +// if (connectionResult.Success) +// { +// var domain = connectionResult.DomainInfo.DomainFQDN; +// if (!CachedDomainInfo.ContainsKey(domain.ToUpper())) +// { +// var baseDomainInfo = connectionResult.DomainInfo; +// baseDomainInfo.DomainSID = GetDomainSidFromConnection(connection, baseDomainInfo); +// baseDomainInfo.DomainNetbiosName = GetDomainNetbiosName(connection, baseDomainInfo); +// CachedDomainInfo.TryAdd(baseDomainInfo.DomainFQDN, baseDomainInfo); +// CachedDomainInfo.TryAdd(baseDomainInfo.DomainNetbiosName, baseDomainInfo); +// CachedDomainInfo.TryAdd(baseDomainInfo.DomainSID, baseDomainInfo); +// +// if (!string.IsNullOrEmpty(baseDomainInfo.DomainSID)) +// { +// Cache.AddDomainSidMapping(baseDomainInfo.DomainFQDN, baseDomainInfo.DomainSID); +// } +// +// if (!string.IsNullOrEmpty(baseDomainInfo.DomainNetbiosName)) +// { +// Cache.AddDomainSidMapping(baseDomainInfo.DomainNetbiosName, baseDomainInfo.DomainSID); +// } +// +// info = baseDomainInfo; +// }else +// { +// CachedDomainInfo.TryGetValue(domain, out info); +// } +// return new LdapConnectionWrapper +// { +// Connection = connection, +// DomainInfo = info +// }; +// } +// +// CheckAndThrowException(connectionResult.Exception); +// return null; +// } +// +// private LdapConnectionTestResult TestConnection(LdapConnection connection) +// { +// try +// { +// //Attempt an initial bind. If this fails, likely auth is invalid, or its not a valid target +// connection.Bind(); +// } +// catch (LdapException e) +// { +// connection.Dispose(); +// return new LdapConnectionTestResult(false, e, null, null); +// } +// +// try +// { +// //Do an initial search request to get the rootDSE +// //This ldap filter is equivalent to (objectclass=*) +// var searchRequest = new SearchRequest("", new LDAPFilter().AddAllObjects().GetFilter(), +// SearchScope.Base, null); +// searchRequest.Controls.Add(new SearchOptionsControl(SearchOption.DomainScope)); +// +// var response = (SearchResponse)connection.SendRequest(searchRequest); +// if (response?.Entries == null) +// { +// connection.Dispose(); +// return new LdapConnectionTestResult(false, null, null, null); +// } +// +// if (response.Entries.Count == 0) +// { +// connection.Dispose(); +// return new LdapConnectionTestResult(false, new LdapException((int)LdapErrorCodes.KerberosAuthType), null, null); +// } +// +// var entry = response.Entries[0]; +// var baseDN = entry.GetProperty(LDAPProperties.RootDomainNamingContext).ToUpper().Trim(); +// var configurationDN = entry.GetProperty(LDAPProperties.ConfigurationNamingContext).ToUpper().Trim(); +// var domainname = Helpers.DistinguishedNameToDomain(baseDN).ToUpper().Trim(); +// var servername = entry.GetProperty(LDAPProperties.ServerName); +// var compName = servername.Substring(0, servername.IndexOf(',')).Substring(3).Trim(); +// var fullServerName = $"{compName}.{domainname}".ToUpper().Trim(); +// +// return new LdapConnectionTestResult(true, null, new DomainInfo +// { +// DomainConfigurationPath = configurationDN, +// DomainSearchBase = baseDN, +// DomainFQDN = domainname +// }, fullServerName); +// } +// catch (LdapException e) +// { +// try +// { +// connection.Dispose(); +// } +// catch +// { +// //pass +// } +// return new LdapConnectionTestResult(false, e, null, null); +// } +// } +// +// public class LdapConnectionTestResult +// { +// public bool Success { get; set; } +// public LdapException Exception { get; set; } +// public DomainInfo DomainInfo { get; set; } +// public string ServerName { get; set; } +// +// public LdapConnectionTestResult(bool success, LdapException e, DomainInfo info, string server) +// { +// Success = success; +// Exception = e; +// DomainInfo = info; +// ServerName = server; +// } +// } +// +// private string GetDomainNetbiosName(LdapConnection connection, DomainInfo info) +// { +// try +// { +// var searchRequest = new SearchRequest($"CN=Partitions,{info.DomainConfigurationPath}", +// "(&(nETBIOSName=*)(dnsRoot=*))", +// SearchScope.Subtree, new[] { LDAPProperties.NetbiosName, LDAPProperties.DnsRoot }); +// +// var response = (SearchResponse)connection.SendRequest(searchRequest); +// if (response == null || response.Entries.Count == 0) +// { +// return ""; +// } +// +// foreach (SearchResultEntry entry in response.Entries) +// { +// var root = entry.GetProperty(LDAPProperties.DnsRoot); +// var netbios = entry.GetProperty(LDAPProperties.NetbiosName); +// _log.LogInformation(root); +// _log.LogInformation(netbios); +// +// if (root.ToUpper().Equals(info.DomainFQDN)) +// { +// return netbios.ToUpper(); +// } +// } +// +// return ""; +// } +// catch (LdapException e) +// { +// _log.LogWarning("Failed grabbing netbios name from ldap for {domain}: {e}", info.DomainFQDN, e); +// return ""; +// } +// } +// +// private string GetDomainSidFromConnection(LdapConnection connection, DomainInfo info) +// { +// try +// { +// //This ldap filter searches for domain controllers +// //Searches for any accounts with a UAC value inclusive of 8192 bitwise +// //8192 is the flag for SERVER_TRUST_ACCOUNT, which is set only on Domain Controllers +// var searchRequest = new SearchRequest(info.DomainSearchBase, +// "(&(objectCategory=computer)(userAccountControl:1.2.840.113556.1.4.803:=8192))", +// SearchScope.Subtree, new[] { "objectsid"}); +// +// var response = (SearchResponse)connection.SendRequest(searchRequest); +// if (response == null || response.Entries.Count == 0) +// { +// return ""; +// } +// +// var entry = response.Entries[0]; +// var sid = entry.GetSid(); +// return sid.Substring(0, sid.LastIndexOf('-')).ToUpper(); +// } +// catch (LdapException) +// { +// _log.LogWarning("Failed grabbing domainsid from ldap for {domain}", info.DomainFQDN); +// return ""; +// } +// } +// +// private void SetupLdapConnection(LdapConnection connection, bool ssl, AuthType authType) +// { +// //These options are important! +// connection.SessionOptions.ProtocolVersion = 3; +// //Referral chasing does not work with paged searches +// connection.SessionOptions.ReferralChasing = ReferralChasingOptions.None; +// if (ssl) +// { +// connection.SessionOptions.SecureSocketLayer = true; +// } +// +// if (_ldapConfig.DisableSigning) +// { +// connection.SessionOptions.Sealing = false; +// connection.SessionOptions.Signing = false; +// } +// +// if (_ldapConfig.DisableCertVerification) +// connection.SessionOptions.VerifyServerCertificate = (_, _) => true; +// +// if (_ldapConfig.Username != null) +// { +// var cred = new NetworkCredential(_ldapConfig.Username, _ldapConfig.Password); +// connection.Credential = cred; +// } +// +// connection.AuthType = authType; +// } +// +// /// +// /// Normalizes a domain name to its full DNS name +// /// +// /// +// /// +// internal string NormalizeDomainName(string domain) +// { +// if (domain == null) +// return null; +// +// var resolved = domain; +// +// if (resolved.Contains(".")) +// return domain.ToUpper(); +// +// resolved = ResolveDomainNetbiosToDns(domain) ?? domain; +// +// return resolved.ToUpper(); +// } +// +// /// +// /// Turns a domain Netbios name into its FQDN using the DsGetDcName function (TESTLAB -> TESTLAB.LOCAL) +// /// +// /// +// /// +// internal string ResolveDomainNetbiosToDns(string domainName) +// { +// var key = domainName.ToUpper(); +// if (_netbiosCache.TryGetValue(key, out var flatName)) +// return flatName; +// +// var domain = GetDomain(domainName); +// if (domain != null) +// { +// _netbiosCache.TryAdd(key, domain.Name); +// return domain.Name; +// } +// +// var computerName = _ldapConfig.Server; +// +// var dci = _nativeMethods.CallDsGetDcName(computerName, domainName); +// if (dci.IsSuccess) +// { +// flatName = dci.Value.DomainName; +// _netbiosCache.TryAdd(key, flatName); +// return flatName; +// } +// +// return domainName.ToUpper(); +// } +// +// /// +// /// Gets the range retrieval limit for a domain +// /// +// /// +// /// +// /// +// public int GetDomainRangeSize(string domainName = null, int defaultRangeSize = 750) +// { +// var domainPath = DomainNameToDistinguishedName(domainName); +// //Default to a page size of 750 for safety +// if (domainPath == null) +// { +// _log.LogDebug("Unable to resolve domain {Domain} to distinguishedname to get page size", +// domainName ?? "current domain"); +// return defaultRangeSize; +// } +// +// if (_ldapRangeSizeCache.TryGetValue(domainPath.ToUpper(), out var parsedPageSize)) +// { +// return parsedPageSize; +// } +// +// var configPath = CommonPaths.CreateDNPath(CommonPaths.QueryPolicyPath, domainPath); +// var enumerable = QueryLDAP("(objectclass=*)", SearchScope.Base, null, adsPath: configPath); +// var config = enumerable.DefaultIfEmpty(null).FirstOrDefault(); +// var pageSize = config?.GetArrayProperty(LDAPProperties.LdapAdminLimits) +// .FirstOrDefault(x => x.StartsWith("MaxPageSize", StringComparison.OrdinalIgnoreCase)); +// if (pageSize == null) +// { +// _log.LogDebug("No LDAPAdminLimits object found for {Domain}", domainName); +// _ldapRangeSizeCache.TryAdd(domainPath.ToUpper(), defaultRangeSize); +// return defaultRangeSize; +// } +// +// if (int.TryParse(pageSize.Split('=').Last(), out parsedPageSize)) +// { +// _ldapRangeSizeCache.TryAdd(domainPath.ToUpper(), parsedPageSize); +// _log.LogInformation("Found page size {PageSize} for {Domain}", parsedPageSize, +// domainName ?? "current domain"); +// return parsedPageSize; +// } +// +// _log.LogDebug("Failed to parse pagesize for {Domain}, returning default", domainName ?? "current domain"); +// +// _ldapRangeSizeCache.TryAdd(domainPath.ToUpper(), defaultRangeSize); +// return defaultRangeSize; +// } +// +// private string DomainNameToDistinguishedName(string domain) +// { +// var resolvedDomain = GetDomain(domain)?.Name ?? domain; +// return resolvedDomain == null ? null : $"DC={resolvedDomain.Replace(".", ",DC=")}"; +// } +// +// private class ResolvedWellKnownPrincipal +// { +// public string DomainName { get; set; } +// public string WkpId { get; set; } +// } +// +// public string GetConfigurationPath(string domainName = null) +// { +// string path = domainName == null +// ? "LDAP://RootDSE" +// : $"LDAP://{NormalizeDomainName(domainName)}/RootDSE"; +// +// DirectoryEntry rootDse; +// if (_ldapConfig.Username != null) +// rootDse = new DirectoryEntry(path, _ldapConfig.Username, _ldapConfig.Password); +// else +// rootDse = new DirectoryEntry(path); +// +// return $"{rootDse.Properties["configurationNamingContext"]?[0]}"; +// } +// +// public string GetSchemaPath(string domainName) +// { +// string path = domainName == null +// ? "LDAP://RootDSE" +// : $"LDAP://{NormalizeDomainName(domainName)}/RootDSE"; +// +// DirectoryEntry rootDse; +// if (_ldapConfig.Username != null) +// rootDse = new DirectoryEntry(path, _ldapConfig.Username, _ldapConfig.Password); +// else +// rootDse = new DirectoryEntry(path); +// +// return $"{rootDse.Properties["schemaNamingContext"]?[0]}"; +// } +// +// public bool IsDomainController(string computerObjectId, string domainName) +// { +// var filter = new LDAPFilter().AddFilter(LDAPProperties.ObjectSID + "=" + computerObjectId, true) +// .AddFilter(CommonFilters.DomainControllers, true); +// var res = QueryLDAP(filter.GetFilter(), SearchScope.Subtree, +// CommonProperties.ObjectID, domainName: domainName); +// if (res.Count() > 0) +// return true; +// return false; +// } +// } +// } \ No newline at end of file diff --git a/src/CommonLib/LdapResult.cs b/src/CommonLib/LdapResult.cs index 09166cd7..d4316486 100644 --- a/src/CommonLib/LdapResult.cs +++ b/src/CommonLib/LdapResult.cs @@ -4,17 +4,23 @@ namespace SharpHoundCommonLib { public class LdapResult : Result { public string QueryInfo { get; set; } + public int ErrorCode { get; set; } - protected LdapResult(T value, bool success, string error, string queryInfo) : base(value, success, error) { + protected LdapResult(T value, bool success, string error, string queryInfo, int errorCode) : base(value, success, error) { QueryInfo = queryInfo; + ErrorCode = errorCode; } public static LdapResult Ok(T value) { - return new LdapResult(value, true, string.Empty, null); + return new LdapResult(value, true, string.Empty, null, 0); } public static LdapResult Fail(string message, LdapQueryParameters queryInfo) { - return new LdapResult(default, false, message, queryInfo.GetQueryInfo()); + return new LdapResult(default, false, message, queryInfo.GetQueryInfo(), 0); + } + + public static LdapResult Fail(string message, LdapQueryParameters queryInfo, int errorCode) { + return new LdapResult(default, false, message, queryInfo.GetQueryInfo(), errorCode); } } } \ No newline at end of file diff --git a/src/CommonLib/LdapUtilsNew.cs b/src/CommonLib/LdapUtilsNew.cs index 0132207d..b1c84fd6 100644 --- a/src/CommonLib/LdapUtilsNew.cs +++ b/src/CommonLib/LdapUtilsNew.cs @@ -110,7 +110,7 @@ public async IAsyncEnumerable> RangedRetrieval(string distinguish var currentRange = $"{attributeName};range={index}-*"; var complete = false; - var queryParameters = new LdapQueryParameters() { + var queryParameters = new LdapQueryParameters { DomainName = domain, LDAPFilter = $"{attributeName}=*", Attributes = new[] { currentRange }, @@ -120,6 +120,7 @@ public async IAsyncEnumerable> RangedRetrieval(string distinguish var connectionWrapper = connectionResult.ConnectionWrapper; if (!CreateSearchRequest(queryParameters, connectionWrapper, out var searchRequest)) { + _connectionPool.ReleaseConnection(connectionWrapper); yield return Result.Fail("Failed to create search request"); yield break; } @@ -127,13 +128,9 @@ public async IAsyncEnumerable> RangedRetrieval(string distinguish var queryRetryCount = 0; var busyRetryCount = 0; - Result tempResult = null; - - while (true) { - if (cancellationToken.IsCancellationRequested) { - yield break; - } + LdapResult tempResult = null; + while (!cancellationToken.IsCancellationRequested) { SearchResponse response = null; try { response = (SearchResponse)connectionWrapper.Connection.SendRequest(searchRequest); @@ -166,32 +163,30 @@ await _connectionPool.GetLdapConnection(domain, _log.LogError( "RangedRetrieval - Failed to get a new connection after ServerDown for path {Path}", distinguishedName); - _connectionPool.ReleaseConnection(connectionWrapper); tempResult = - Result.Fail( - "RangedRetrieval - Failed to get a new connection after ServerDown."); + LdapResult.Fail( + "RangedRetrieval - Failed to get a new connection after ServerDown.", queryParameters, le.ErrorCode); } } } catch (LdapException le) { - if (le.ErrorCode is (int)LdapErrorCodes.ServerDown) { - _connectionPool.ReleaseConnection(connectionWrapper, true); - } - else { - _connectionPool.ReleaseConnection(connectionWrapper); - } - tempResult = Result.Fail( - $"Caught unrecoverable ldap exception: {le.Message} (ServerMessage: {le.ServerErrorMessage}) (ErrorCode: {le.ErrorCode})"); + tempResult = LdapResult.Fail( + $"Caught unrecoverable ldap exception: {le.Message} (ServerMessage: {le.ServerErrorMessage}) (ErrorCode: {le.ErrorCode})", queryParameters, le.ErrorCode); } catch (Exception e) { - _connectionPool.ReleaseConnection(connectionWrapper); tempResult = - Result.Fail($"Caught unrecoverable exception: {e.Message}"); + LdapResult.Fail($"Caught unrecoverable exception: {e.Message}", queryParameters); } //If we have a tempResult set it means we hit an error we couldn't recover from, so yield that result and then break out of the function //We handle connection release in the relevant exception blocks if (tempResult != null) { + if (tempResult.ErrorCode == (int)LdapErrorCodes.ServerDown) { + _connectionPool.ReleaseConnection(connectionWrapper, true); + } + else { + _connectionPool.ReleaseConnection(connectionWrapper); + } yield return tempResult; yield break; } @@ -225,6 +220,8 @@ await _connectionPool.GetLdapConnection(domain, yield break; } } + + _connectionPool.ReleaseConnection(connectionWrapper); } public async IAsyncEnumerable> Query(LdapQueryParameters queryParameters, @@ -241,6 +238,7 @@ public async IAsyncEnumerable> Query(LdapQueryPar var connectionWrapper = setupResult.ConnectionWrapper; if (cancellationToken.IsCancellationRequested) { + _connectionPool.ReleaseConnection(connectionWrapper); yield break; } @@ -249,11 +247,7 @@ public async IAsyncEnumerable> Query(LdapQueryPar LdapResult tempResult = null; var querySuccess = false; SearchResponse response = null; - while (true) { - if (cancellationToken.IsCancellationRequested) { - yield break; - } - + while (!cancellationToken.IsCancellationRequested) { try { _log.LogTrace("Sending ldap request - {Info}", queryParameters.GetQueryInfo()); response = (SearchResponse)connectionWrapper.Connection.SendRequest(searchRequest); @@ -328,6 +322,12 @@ await _connectionPool.GetLdapConnection(queryParameters.DomainName, //If we have a tempResult set it means we hit an error we couldn't recover from, so yield that result and then break out of the function if (tempResult != null) { + if (tempResult.ErrorCode == (int)LdapErrorCodes.ServerDown) { + _connectionPool.ReleaseConnection(connectionWrapper, true); + } + else { + _connectionPool.ReleaseConnection(connectionWrapper); + } yield return tempResult; yield break; } @@ -338,10 +338,11 @@ await _connectionPool.GetLdapConnection(queryParameters.DomainName, } } - //TODO: Fix this with a new wrapper object - foreach (ISearchResultEntry entry in response.Entries) { - yield return LdapResult.Ok(entry); + foreach (SearchResultEntry entry in response.Entries) { + yield return LdapResult.Ok(new SearchResultEntryWrapper(entry, this)); } + + _connectionPool.ReleaseConnection(connectionWrapper); } public async IAsyncEnumerable> PagedQuery(LdapQueryParameters queryParameters, @@ -370,16 +371,7 @@ public async IAsyncEnumerable> PagedQuery(LdapQue var queryRetryCount = 0; LdapResult tempResult = null; - while (true) { - if (cancellationToken.IsCancellationRequested) { - yield break; - } - - if (tempResult != null) { - yield return tempResult; - yield break; - } - + while (!cancellationToken.IsCancellationRequested) { SearchResponse response = null; try { _log.LogTrace("Sending paged ldap request - {Info}", queryParameters.GetQueryInfo()); @@ -406,6 +398,7 @@ public async IAsyncEnumerable> PagedQuery(LdapQue _log.LogError( "PagedQuery - Received server down exception without a known servername. Unable to generate new connection\n{Info}", queryParameters.GetQueryInfo()); + _connectionPool.ReleaseConnection(connectionWrapper, true); yield break; } @@ -431,7 +424,7 @@ public async IAsyncEnumerable> PagedQuery(LdapQue queryParameters.GetQueryInfo()); tempResult = LdapResult.Fail("Failed to get a new connection after serverdown", - queryParameters); + queryParameters, le.ErrorCode); } } } @@ -447,7 +440,7 @@ public async IAsyncEnumerable> PagedQuery(LdapQue catch (LdapException le) { tempResult = LdapResult.Fail( $"PagedQuery - Caught unrecoverable ldap exception: {le.Message} (ServerMessage: {le.ServerErrorMessage}) (ErrorCode: {le.ErrorCode})", - queryParameters); + queryParameters, le.ErrorCode); } catch (Exception e) { tempResult = @@ -456,11 +449,18 @@ public async IAsyncEnumerable> PagedQuery(LdapQue } if (tempResult != null) { + if (tempResult.ErrorCode == (int)LdapErrorCodes.ServerDown) { + _connectionPool.ReleaseConnection(connectionWrapper, true); + } + else { + _connectionPool.ReleaseConnection(connectionWrapper); + } yield return tempResult; yield break; } if (cancellationToken.IsCancellationRequested) { + _connectionPool.ReleaseConnection(connectionWrapper); yield break; } @@ -471,6 +471,7 @@ public async IAsyncEnumerable> PagedQuery(LdapQue foreach (ISearchResultEntry entry in response.Entries) { if (cancellationToken.IsCancellationRequested) { + _connectionPool.ReleaseConnection(connectionWrapper); yield break; } @@ -478,9 +479,11 @@ public async IAsyncEnumerable> PagedQuery(LdapQue } if (pageResponse.Cookie.Length == 0 || response.Entries.Count == 0 || - cancellationToken.IsCancellationRequested) + cancellationToken.IsCancellationRequested) { + _connectionPool.ReleaseConnection(connectionWrapper); yield break; - + } + pageControl.Cookie = pageResponse.Cookie; } } @@ -773,13 +776,14 @@ private async Task SetupLdapQuery(LdapQueryParameters quer //This should never happen as far as I know, so just checking for safety if (connectionWrapper.Connection == null) { result.Success = false; - result.Message = $"Connection object is null"; + result.Message = "Connection object is null"; return result; } if (!CreateSearchRequest(queryParameters, connectionWrapper, out var searchRequest)) { result.Success = false; result.Message = "Failed to create search request"; + _connectionPool.ReleaseConnection(connectionWrapper); return result; } diff --git a/src/CommonLib/Processors/UserRightsAssignmentProcessor.cs b/src/CommonLib/Processors/UserRightsAssignmentProcessor.cs index 82687e66..da484101 100644 --- a/src/CommonLib/Processors/UserRightsAssignmentProcessor.cs +++ b/src/CommonLib/Processors/UserRightsAssignmentProcessor.cs @@ -24,12 +24,12 @@ public UserRightsAssignmentProcessor(ILdapUtilsNew utils, ILogger log = null) public event ComputerStatusDelegate ComputerStatusEvent; - public virtual LdapResult OpenLSAPolicy(string computerName) + public virtual SharpHoundRPC.Result OpenLSAPolicy(string computerName) { var result = LSAPolicy.OpenPolicy(computerName); - if (result.IsFailed) return LdapResult.Fail(result.SError); + if (result.IsFailed) return SharpHoundRPC.Result.Fail(result.SError); - return LdapResult.Ok(result.Value); + return SharpHoundRPC.Result.Ok(result.Value); } public IAsyncEnumerable GetUserRightsAssignments(ResolvedSearchResult result, From bd077d6b2212ef5d1e6adee932ed868d46096658 Mon Sep 17 00:00:00 2001 From: rvazarkar Date: Tue, 2 Jul 2024 12:40:20 -0400 Subject: [PATCH 23/68] wip: rename files --- src/CommonLib/ConnectionPoolManager.cs | 2 +- src/CommonLib/ILDAPUtils.cs | 144 ----------------- .../{ILdapUtilsNew.cs => ILdapUtils.cs} | 5 +- .../{LDAPUtils.cs => LDAPUtilsBackup.cs} | 148 +++++++++++++++++- src/CommonLib/LdapConnectionPool.cs | 2 +- src/CommonLib/LdapResult.cs | 2 +- .../{LdapUtilsNew.cs => LdapUtils.cs} | 7 +- src/CommonLib/Processors/ACLProcessor.cs | 5 +- .../Processors/CertAbuseProcessor.cs | 4 +- .../Processors/ComputerSessionProcessor.cs | 4 +- .../Processors/ContainerProcessor.cs | 4 +- .../Processors/DCRegistryProcessor.cs | 4 +- .../Processors/DomainTrustProcessor.cs | 4 +- .../Processors/GPOLocalGroupProcessor.cs | 11 +- src/CommonLib/Processors/GroupProcessor.cs | 4 +- .../Processors/LDAPPropertyProcessor.cs | 4 +- .../Processors/LocalGroupProcessor.cs | 4 +- src/CommonLib/Processors/SPNProcessors.cs | 4 +- .../UserRightsAssignmentProcessor.cs | 4 +- src/CommonLib/Result.cs | 4 +- src/CommonLib/SearchResultEntryWrapper.cs | 6 +- 21 files changed, 184 insertions(+), 192 deletions(-) delete mode 100644 src/CommonLib/ILDAPUtils.cs rename src/CommonLib/{ILdapUtilsNew.cs => ILdapUtils.cs} (94%) rename src/CommonLib/{LDAPUtils.cs => LDAPUtilsBackup.cs} (92%) rename src/CommonLib/{LdapUtilsNew.cs => LdapUtils.cs} (99%) diff --git a/src/CommonLib/ConnectionPoolManager.cs b/src/CommonLib/ConnectionPoolManager.cs index 7d51c502..6dbcdc3c 100644 --- a/src/CommonLib/ConnectionPoolManager.cs +++ b/src/CommonLib/ConnectionPoolManager.cs @@ -91,7 +91,7 @@ private bool GetDomainSidFromDomainName(string domainName, out string domainSid) //we expect this to fail sometimes } - if (LdapUtilsNew.GetDomain(domainName, _ldapConfig, out var domainObject)) + if (LdapUtils.GetDomain(domainName, _ldapConfig, out var domainObject)) try { domainSid = domainObject.GetDirectoryEntry().GetSid(); if (domainSid != null) { diff --git a/src/CommonLib/ILDAPUtils.cs b/src/CommonLib/ILDAPUtils.cs deleted file mode 100644 index ff6a3517..00000000 --- a/src/CommonLib/ILDAPUtils.cs +++ /dev/null @@ -1,144 +0,0 @@ -using System.Collections.Generic; -using System.DirectoryServices.ActiveDirectory; -using System.DirectoryServices.Protocols; -using System.Security.Principal; -using System.Threading; -using System.Threading.Tasks; -using SharpHoundCommonLib.Enums; -using SharpHoundCommonLib.OutputTypes; -using SharpHoundRPC.Wrappers; -using Domain = System.DirectoryServices.ActiveDirectory.Domain; - -namespace SharpHoundCommonLib -{ - /// - /// Struct representing options to create an LDAP query - /// - public struct LDAPQueryOptions - { - public string Filter; - public SearchScope Scope; - public string[] Properties; - public CancellationToken CancellationToken; - public string DomainName; - public bool IncludeAcl; - public bool ShowDeleted; - public string AdsPath; - public bool GlobalCatalog; - public bool SkipCache; - public bool ThrowException; - } - - public interface ILDAPUtils - { - void SetLDAPConfig(LDAPConfig config); - bool TestLDAPConfig(string domain); - string[] GetUserGlobalCatalogMatches(string name); - TypedPrincipal ResolveIDAndType(string id, string fallbackDomain); - TypedPrincipal ResolveCertTemplateByProperty(string propValue, string propName, string containerDN, string domainName); - Label LookupSidType(string sid, string domain); - Label LookupGuidType(string guid, string domain); - string GetDomainNameFromSid(string sid); - string GetSidFromDomainName(string domainName); - string ConvertWellKnownPrincipal(string sid, string domain); - bool GetWellKnownPrincipal(string sid, string domain, out TypedPrincipal commonPrincipal); - - bool ConvertLocalWellKnownPrincipal(SecurityIdentifier sid, string computerDomainSid, string computerDomain, - out TypedPrincipal principal); - Domain GetDomain(string domainName = null); - void AddDomainController(string domainControllerSID); - IEnumerable GetWellKnownPrincipalOutput(string domain); - - /// - /// Performs Attribute Ranged Retrieval - /// https://docs.microsoft.com/en-us/windows/win32/adsi/attribute-range-retrieval - /// The function self-determines the range and internally handles the maximum step allowed by the server - /// - /// - /// - /// - IEnumerable DoRangedRetrieval(string distinguishedName, string attributeName); - - /// - /// Takes a host in most applicable forms from AD and attempts to resolve it into a SID. - /// - /// - /// - /// - Task ResolveHostToSid(string hostname, string domain); - - /// - /// Attempts to convert a bare account name (usually from session enumeration) to its corresponding ID and object type - /// - /// - /// - /// - TypedPrincipal ResolveAccountName(string name, string domain); - - /// - /// Attempts to convert a distinguishedname to its corresponding ID and object type. - /// - /// DistinguishedName - /// A TypedPrincipal object with the SID and Label - TypedPrincipal ResolveDistinguishedName(string dn); - - /// - /// Performs an LDAP query using the parameters specified by the user. - /// - /// LDAP query options - /// All LDAP search results matching the specified parameters - IEnumerable QueryLDAP(LDAPQueryOptions options); - - /// - /// Performs an LDAP query using the parameters specified by the user. - /// - /// LDAP filter - /// SearchScope to query - /// LDAP properties to fetch for each object - /// Cancellation Token - /// Include the DACL and Owner values in the NTSecurityDescriptor - /// Include deleted objects - /// Domain to query - /// ADS path to limit the query too - /// Use the global catalog instead of the regular LDAP server - /// - /// Skip the connection cache and force a new connection. You must dispose of this connection - /// yourself. - /// - /// Throw exceptions rather than logging the errors directly - /// All LDAP search results matching the specified parameters - IEnumerable QueryLDAP(string ldapFilter, SearchScope scope, - string[] props, CancellationToken cancellationToken, string domainName = null, bool includeAcl = false, - bool showDeleted = false, string adsPath = null, bool globalCatalog = false, bool skipCache = false, - bool throwException = false); - - /// - /// Performs an LDAP query using the parameters specified by the user. - /// - /// LDAP filter - /// SearchScope to query - /// LDAP properties to fetch for each object - /// Include the DACL and Owner values in the NTSecurityDescriptor - /// Include deleted objects - /// Domain to query - /// ADS path to limit the query too - /// Use the global catalog instead of the regular LDAP server - /// - /// Skip the connection cache and force a new connection. You must dispose of this connection - /// yourself. - /// - /// Throw exceptions rather than logging the errors directly - /// All LDAP search results matching the specified parameters - IEnumerable QueryLDAP(string ldapFilter, SearchScope scope, - string[] props, string domainName = null, bool includeAcl = false, bool showDeleted = false, - string adsPath = null, bool globalCatalog = false, bool skipCache = false, bool throwException = false); - - Forest GetForest(string domainName = null); - string GetConfigurationPath(string domainName); - string GetSchemaPath(string domainName); - - ActiveDirectorySecurityDescriptor MakeSecurityDescriptor(); - string BuildLdapPath(string dnPath, string domain); - bool IsDomainController(string computerObjectId, string domainName); - } -} \ No newline at end of file diff --git a/src/CommonLib/ILdapUtilsNew.cs b/src/CommonLib/ILdapUtils.cs similarity index 94% rename from src/CommonLib/ILdapUtilsNew.cs rename to src/CommonLib/ILdapUtils.cs index 8bb57820..251c26f8 100644 --- a/src/CommonLib/ILdapUtilsNew.cs +++ b/src/CommonLib/ILdapUtils.cs @@ -1,14 +1,11 @@ using System.Collections.Generic; -using System.DirectoryServices.Protocols; -using System.Runtime.CompilerServices; using System.Security.Principal; using System.Threading; using System.Threading.Tasks; -using SharpHoundCommonLib.Enums; using SharpHoundCommonLib.OutputTypes; namespace SharpHoundCommonLib { - public interface ILdapUtilsNew { + public interface ILdapUtils { IAsyncEnumerable> Query(LdapQueryParameters queryParameters, CancellationToken cancellationToken = new()); diff --git a/src/CommonLib/LDAPUtils.cs b/src/CommonLib/LDAPUtilsBackup.cs similarity index 92% rename from src/CommonLib/LDAPUtils.cs rename to src/CommonLib/LDAPUtilsBackup.cs index 91bf5c59..f606bb89 100644 --- a/src/CommonLib/LDAPUtils.cs +++ b/src/CommonLib/LDAPUtilsBackup.cs @@ -1,4 +1,4 @@ -// using System; +// using System; // using System.Collections.Concurrent; // using System.Collections.Generic; // using System.Diagnostics; @@ -2112,4 +2112,150 @@ // return false; // } // } +// } + + +// using System.Collections.Generic; +// using System.DirectoryServices.ActiveDirectory; +// using System.DirectoryServices.Protocols; +// using System.Security.Principal; +// using System.Threading; +// using System.Threading.Tasks; +// using SharpHoundCommonLib.Enums; +// using SharpHoundCommonLib.OutputTypes; +// using SharpHoundRPC.Wrappers; +// using Domain = System.DirectoryServices.ActiveDirectory.Domain; +// +// namespace SharpHoundCommonLib +// { +// /// +// /// Struct representing options to create an LDAP query +// /// +// public struct LDAPQueryOptions +// { +// public string Filter; +// public SearchScope Scope; +// public string[] Properties; +// public CancellationToken CancellationToken; +// public string DomainName; +// public bool IncludeAcl; +// public bool ShowDeleted; +// public string AdsPath; +// public bool GlobalCatalog; +// public bool SkipCache; +// public bool ThrowException; +// } +// +// public interface ILDAPUtils +// { +// void SetLDAPConfig(LDAPConfig config); +// bool TestLDAPConfig(string domain); +// string[] GetUserGlobalCatalogMatches(string name); +// TypedPrincipal ResolveIDAndType(string id, string fallbackDomain); +// TypedPrincipal ResolveCertTemplateByProperty(string propValue, string propName, string containerDN, string domainName); +// Label LookupSidType(string sid, string domain); +// Label LookupGuidType(string guid, string domain); +// string GetDomainNameFromSid(string sid); +// string GetSidFromDomainName(string domainName); +// string ConvertWellKnownPrincipal(string sid, string domain); +// bool GetWellKnownPrincipal(string sid, string domain, out TypedPrincipal commonPrincipal); +// +// bool ConvertLocalWellKnownPrincipal(SecurityIdentifier sid, string computerDomainSid, string computerDomain, +// out TypedPrincipal principal); +// Domain GetDomain(string domainName = null); +// void AddDomainController(string domainControllerSID); +// IEnumerable GetWellKnownPrincipalOutput(string domain); +// +// /// +// /// Performs Attribute Ranged Retrieval +// /// https://docs.microsoft.com/en-us/windows/win32/adsi/attribute-range-retrieval +// /// The function self-determines the range and internally handles the maximum step allowed by the server +// /// +// /// +// /// +// /// +// IEnumerable DoRangedRetrieval(string distinguishedName, string attributeName); +// +// /// +// /// Takes a host in most applicable forms from AD and attempts to resolve it into a SID. +// /// +// /// +// /// +// /// +// Task ResolveHostToSid(string hostname, string domain); +// +// /// +// /// Attempts to convert a bare account name (usually from session enumeration) to its corresponding ID and object type +// /// +// /// +// /// +// /// +// TypedPrincipal ResolveAccountName(string name, string domain); +// +// /// +// /// Attempts to convert a distinguishedname to its corresponding ID and object type. +// /// +// /// DistinguishedName +// /// A TypedPrincipal object with the SID and Label +// TypedPrincipal ResolveDistinguishedName(string dn); +// +// /// +// /// Performs an LDAP query using the parameters specified by the user. +// /// +// /// LDAP query options +// /// All LDAP search results matching the specified parameters +// IEnumerable QueryLDAP(LDAPQueryOptions options); +// +// /// +// /// Performs an LDAP query using the parameters specified by the user. +// /// +// /// LDAP filter +// /// SearchScope to query +// /// LDAP properties to fetch for each object +// /// Cancellation Token +// /// Include the DACL and Owner values in the NTSecurityDescriptor +// /// Include deleted objects +// /// Domain to query +// /// ADS path to limit the query too +// /// Use the global catalog instead of the regular LDAP server +// /// +// /// Skip the connection cache and force a new connection. You must dispose of this connection +// /// yourself. +// /// +// /// Throw exceptions rather than logging the errors directly +// /// All LDAP search results matching the specified parameters +// IEnumerable QueryLDAP(string ldapFilter, SearchScope scope, +// string[] props, CancellationToken cancellationToken, string domainName = null, bool includeAcl = false, +// bool showDeleted = false, string adsPath = null, bool globalCatalog = false, bool skipCache = false, +// bool throwException = false); +// +// /// +// /// Performs an LDAP query using the parameters specified by the user. +// /// +// /// LDAP filter +// /// SearchScope to query +// /// LDAP properties to fetch for each object +// /// Include the DACL and Owner values in the NTSecurityDescriptor +// /// Include deleted objects +// /// Domain to query +// /// ADS path to limit the query too +// /// Use the global catalog instead of the regular LDAP server +// /// +// /// Skip the connection cache and force a new connection. You must dispose of this connection +// /// yourself. +// /// +// /// Throw exceptions rather than logging the errors directly +// /// All LDAP search results matching the specified parameters +// IEnumerable QueryLDAP(string ldapFilter, SearchScope scope, +// string[] props, string domainName = null, bool includeAcl = false, bool showDeleted = false, +// string adsPath = null, bool globalCatalog = false, bool skipCache = false, bool throwException = false); +// +// Forest GetForest(string domainName = null); +// string GetConfigurationPath(string domainName); +// string GetSchemaPath(string domainName); +// +// ActiveDirectorySecurityDescriptor MakeSecurityDescriptor(); +// string BuildLdapPath(string dnPath, string domain); +// bool IsDomainController(string computerObjectId, string domainName); +// } // } \ No newline at end of file diff --git a/src/CommonLib/LdapConnectionPool.cs b/src/CommonLib/LdapConnectionPool.cs index dab33d32..289c51d1 100644 --- a/src/CommonLib/LdapConnectionPool.cs +++ b/src/CommonLib/LdapConnectionPool.cs @@ -141,7 +141,7 @@ public void Dispose() { } } - if (!LdapUtilsNew.GetDomain(_identifier, _ldapConfig, out var domainObject) || domainObject.Name == null) { + if (!LdapUtils.GetDomain(_identifier, _ldapConfig, out var domainObject) || domainObject.Name == null) { //If we don't get a result here, we effectively have no other ways to resolve this domain, so we'll just have to exit out _log.LogDebug( "Could not get domain object from GetDomain, unable to create ldap connection for domain {Domain}", diff --git a/src/CommonLib/LdapResult.cs b/src/CommonLib/LdapResult.cs index d4316486..7a951597 100644 --- a/src/CommonLib/LdapResult.cs +++ b/src/CommonLib/LdapResult.cs @@ -11,7 +11,7 @@ protected LdapResult(T value, bool success, string error, string queryInfo, int ErrorCode = errorCode; } - public static LdapResult Ok(T value) { + public new static LdapResult Ok(T value) { return new LdapResult(value, true, string.Empty, null, 0); } diff --git a/src/CommonLib/LdapUtilsNew.cs b/src/CommonLib/LdapUtils.cs similarity index 99% rename from src/CommonLib/LdapUtilsNew.cs rename to src/CommonLib/LdapUtils.cs index b1c84fd6..847827f1 100644 --- a/src/CommonLib/LdapUtilsNew.cs +++ b/src/CommonLib/LdapUtils.cs @@ -25,7 +25,7 @@ using SecurityMasks = System.DirectoryServices.Protocols.SecurityMasks; namespace SharpHoundCommonLib { - public class LdapUtilsNew : ILdapUtilsNew { + public class LdapUtils : ILdapUtils { //This cache is indexed by domain sid private readonly ConcurrentDictionary _dcInfoCache = new(); private static readonly ConcurrentDictionary DomainCache = new(); @@ -73,14 +73,14 @@ private class ResolvedWellKnownPrincipal { public string WkpId { get; set; } } - public LdapUtilsNew() { + public LdapUtils() { _nativeMethods = new NativeMethods(); _portScanner = new PortScanner(); _log = Logging.LogProvider.CreateLogger("LDAPUtils"); _connectionPool = new ConnectionPoolManager(_ldapConfig); } - public LdapUtilsNew(NativeMethods nativeMethods = null, PortScanner scanner = null, ILogger log = null) { + public LdapUtils(NativeMethods nativeMethods = null, PortScanner scanner = null, ILogger log = null) { _nativeMethods = nativeMethods ?? new NativeMethods(); _portScanner = scanner ?? new PortScanner(); _log = log ?? Logging.LogProvider.CreateLogger("LDAPUtils"); @@ -1017,6 +1017,7 @@ public static bool GetDomain(string domainName, LDAPConfig ldapConfig, out Domai return true; } catch (Exception e) { + Logging.Logger.LogDebug("Static GetDomain call failed for domain {DomainName}: {Error}", domainName, e.Message); return false; } } diff --git a/src/CommonLib/Processors/ACLProcessor.cs b/src/CommonLib/Processors/ACLProcessor.cs index 25ff115b..d20e4b22 100644 --- a/src/CommonLib/Processors/ACLProcessor.cs +++ b/src/CommonLib/Processors/ACLProcessor.cs @@ -16,9 +16,8 @@ public class ACLProcessor { private static readonly Dictionary BaseGuids; private static readonly ConcurrentDictionary GuidMap = new(); - private static bool _isCacheBuilt; private readonly ILogger _log; - private readonly ILdapUtilsNew _utils; + private readonly ILdapUtils _utils; private static readonly HashSet BuiltDomainCaches = new(StringComparer.OrdinalIgnoreCase); static ACLProcessor() @@ -43,7 +42,7 @@ static ACLProcessor() }; } - public ACLProcessor(ILdapUtilsNew utils, ILogger log = null) + public ACLProcessor(ILdapUtils utils, ILogger log = null) { _utils = utils; _log = log ?? Logging.LogProvider.CreateLogger("ACLProc"); diff --git a/src/CommonLib/Processors/CertAbuseProcessor.cs b/src/CommonLib/Processors/CertAbuseProcessor.cs index 0fe0db3b..3a03e944 100644 --- a/src/CommonLib/Processors/CertAbuseProcessor.cs +++ b/src/CommonLib/Processors/CertAbuseProcessor.cs @@ -17,12 +17,12 @@ namespace SharpHoundCommonLib.Processors public class CertAbuseProcessor { private readonly ILogger _log; - private readonly ILdapUtilsNew _utils; + private readonly ILdapUtils _utils; public delegate Task ComputerStatusDelegate(CSVComputerStatus status); public event ComputerStatusDelegate ComputerStatusEvent; - public CertAbuseProcessor(ILdapUtilsNew utils, ILogger log = null) + public CertAbuseProcessor(ILdapUtils utils, ILogger log = null) { _utils = utils; _log = log ?? Logging.LogProvider.CreateLogger("CAProc"); diff --git a/src/CommonLib/Processors/ComputerSessionProcessor.cs b/src/CommonLib/Processors/ComputerSessionProcessor.cs index b7736008..e7659ba1 100644 --- a/src/CommonLib/Processors/ComputerSessionProcessor.cs +++ b/src/CommonLib/Processors/ComputerSessionProcessor.cs @@ -19,12 +19,12 @@ public class ComputerSessionProcessor private readonly string _currentUserName; private readonly ILogger _log; private readonly NativeMethods _nativeMethods; - private readonly ILdapUtilsNew _utils; + private readonly ILdapUtils _utils; private readonly bool _doLocalAdminSessionEnum; private readonly string _localAdminUsername; private readonly string _localAdminPassword; - public ComputerSessionProcessor(ILdapUtilsNew utils, string currentUserName = null, NativeMethods nativeMethods = null, ILogger log = null, bool doLocalAdminSessionEnum = false, string localAdminUsername = null, string localAdminPassword = null) + public ComputerSessionProcessor(ILdapUtils utils, string currentUserName = null, NativeMethods nativeMethods = null, ILogger log = null, bool doLocalAdminSessionEnum = false, string localAdminUsername = null, string localAdminPassword = null) { _utils = utils; _nativeMethods = nativeMethods ?? new NativeMethods(); diff --git a/src/CommonLib/Processors/ContainerProcessor.cs b/src/CommonLib/Processors/ContainerProcessor.cs index 9b7b59ea..a960dd42 100644 --- a/src/CommonLib/Processors/ContainerProcessor.cs +++ b/src/CommonLib/Processors/ContainerProcessor.cs @@ -12,9 +12,9 @@ namespace SharpHoundCommonLib.Processors public class ContainerProcessor { private readonly ILogger _log; - private readonly ILdapUtilsNew _utils; + private readonly ILdapUtils _utils; - public ContainerProcessor(ILdapUtilsNew utils, ILogger log = null) + public ContainerProcessor(ILdapUtils utils, ILogger log = null) { _utils = utils; _log = log ?? Logging.LogProvider.CreateLogger("ContainerProc"); diff --git a/src/CommonLib/Processors/DCRegistryProcessor.cs b/src/CommonLib/Processors/DCRegistryProcessor.cs index 8d5cc189..0bd5bad0 100644 --- a/src/CommonLib/Processors/DCRegistryProcessor.cs +++ b/src/CommonLib/Processors/DCRegistryProcessor.cs @@ -9,10 +9,10 @@ namespace SharpHoundCommonLib.Processors public class DCRegistryProcessor { private readonly ILogger _log; - public readonly ILdapUtilsNew _utils; + public readonly ILdapUtils _utils; public delegate Task ComputerStatusDelegate(CSVComputerStatus status); - public DCRegistryProcessor(ILdapUtilsNew utils, ILogger log = null) + public DCRegistryProcessor(ILdapUtils utils, ILogger log = null) { _utils = utils; _log = log ?? Logging.LogProvider.CreateLogger("DCRegProc"); diff --git a/src/CommonLib/Processors/DomainTrustProcessor.cs b/src/CommonLib/Processors/DomainTrustProcessor.cs index edaf42e7..7f64b987 100644 --- a/src/CommonLib/Processors/DomainTrustProcessor.cs +++ b/src/CommonLib/Processors/DomainTrustProcessor.cs @@ -11,9 +11,9 @@ namespace SharpHoundCommonLib.Processors public class DomainTrustProcessor { private readonly ILogger _log; - private readonly ILdapUtilsNew _utils; + private readonly ILdapUtils _utils; - public DomainTrustProcessor(ILdapUtilsNew utils, ILogger log = null) + public DomainTrustProcessor(ILdapUtils utils, ILogger log = null) { _utils = utils; _log = log ?? Logging.LogProvider.CreateLogger("DomainTrustProc"); diff --git a/src/CommonLib/Processors/GPOLocalGroupProcessor.cs b/src/CommonLib/Processors/GPOLocalGroupProcessor.cs index 3b7650ca..e6b754d3 100644 --- a/src/CommonLib/Processors/GPOLocalGroupProcessor.cs +++ b/src/CommonLib/Processors/GPOLocalGroupProcessor.cs @@ -42,9 +42,9 @@ public class GPOLocalGroupProcessor { private readonly ILogger _log; - private readonly ILdapUtilsNew _utils; + private readonly ILdapUtils _utils; - public GPOLocalGroupProcessor(ILdapUtilsNew utils, ILogger log = null) { + public GPOLocalGroupProcessor(ILdapUtils utils, ILogger log = null) { _utils = utils; _log = log ?? Logging.LogProvider.CreateLogger("GPOLocalGroupProc"); } @@ -114,13 +114,6 @@ public async Task ReadGPOLocalGroups(string gpLink, string actions = new List(); var gpoDomain = Helpers.DistinguishedNameToDomain(linkDn); - - var opts = new LDAPQueryOptions { - Filter = new LDAPFilter().AddAllObjects().GetFilter(), - Scope = SearchScope.Base, - Properties = CommonProperties.GPCFileSysPath, - AdsPath = linkDn - }; var result = await _utils.Query(new LdapQueryParameters() { LDAPFilter = new LDAPFilter().AddAllObjects().GetFilter(), SearchScope = SearchScope.Base, diff --git a/src/CommonLib/Processors/GroupProcessor.cs b/src/CommonLib/Processors/GroupProcessor.cs index 9414e103..ae11d788 100644 --- a/src/CommonLib/Processors/GroupProcessor.cs +++ b/src/CommonLib/Processors/GroupProcessor.cs @@ -11,9 +11,9 @@ namespace SharpHoundCommonLib.Processors public class GroupProcessor { private readonly ILogger _log; - private readonly ILdapUtilsNew _utils; + private readonly ILdapUtils _utils; - public GroupProcessor(ILdapUtilsNew utils, ILogger log = null) + public GroupProcessor(ILdapUtils utils, ILogger log = null) { _utils = utils; _log = log ?? Logging.LogProvider.CreateLogger("GroupProc"); diff --git a/src/CommonLib/Processors/LDAPPropertyProcessor.cs b/src/CommonLib/Processors/LDAPPropertyProcessor.cs index c8df3ee1..a4f925d3 100644 --- a/src/CommonLib/Processors/LDAPPropertyProcessor.cs +++ b/src/CommonLib/Processors/LDAPPropertyProcessor.cs @@ -23,9 +23,9 @@ public class LDAPPropertyProcessor .Concat(CommonProperties.SPNTargetProps).Concat(CommonProperties.DomainTrustProps) .Concat(CommonProperties.GPOLocalGroupProps).ToArray(); - private readonly ILdapUtilsNew _utils; + private readonly ILdapUtils _utils; - public LDAPPropertyProcessor(ILdapUtilsNew utils) + public LDAPPropertyProcessor(ILdapUtils utils) { _utils = utils; } diff --git a/src/CommonLib/Processors/LocalGroupProcessor.cs b/src/CommonLib/Processors/LocalGroupProcessor.cs index b7161e7f..a1c35090 100644 --- a/src/CommonLib/Processors/LocalGroupProcessor.cs +++ b/src/CommonLib/Processors/LocalGroupProcessor.cs @@ -15,9 +15,9 @@ public class LocalGroupProcessor { public delegate Task ComputerStatusDelegate(CSVComputerStatus status); private readonly ILogger _log; - private readonly ILdapUtilsNew _utils; + private readonly ILdapUtils _utils; - public LocalGroupProcessor(ILdapUtilsNew utils, ILogger log = null) + public LocalGroupProcessor(ILdapUtils utils, ILogger log = null) { _utils = utils; _log = log ?? Logging.LogProvider.CreateLogger("LocalGroupProcessor"); diff --git a/src/CommonLib/Processors/SPNProcessors.cs b/src/CommonLib/Processors/SPNProcessors.cs index a582e61f..3ba2ba65 100644 --- a/src/CommonLib/Processors/SPNProcessors.cs +++ b/src/CommonLib/Processors/SPNProcessors.cs @@ -8,9 +8,9 @@ public class SPNProcessors { private const string MSSQLSPNString = "mssqlsvc"; private readonly ILogger _log; - private readonly ILdapUtilsNew _utils; + private readonly ILdapUtils _utils; - public SPNProcessors(ILdapUtilsNew utils, ILogger log = null) + public SPNProcessors(ILdapUtils utils, ILogger log = null) { _utils = utils; _log = log ?? Logging.LogProvider.CreateLogger("SPNProc"); diff --git a/src/CommonLib/Processors/UserRightsAssignmentProcessor.cs b/src/CommonLib/Processors/UserRightsAssignmentProcessor.cs index da484101..404427ee 100644 --- a/src/CommonLib/Processors/UserRightsAssignmentProcessor.cs +++ b/src/CommonLib/Processors/UserRightsAssignmentProcessor.cs @@ -14,9 +14,9 @@ public class UserRightsAssignmentProcessor public delegate Task ComputerStatusDelegate(CSVComputerStatus status); private readonly ILogger _log; - private readonly ILdapUtilsNew _utils; + private readonly ILdapUtils _utils; - public UserRightsAssignmentProcessor(ILdapUtilsNew utils, ILogger log = null) + public UserRightsAssignmentProcessor(ILdapUtils utils, ILogger log = null) { _utils = utils; _log = log ?? Logging.LogProvider.CreateLogger("UserRightsAssignmentProcessor"); diff --git a/src/CommonLib/Result.cs b/src/CommonLib/Result.cs index d699b26d..390bb5ac 100644 --- a/src/CommonLib/Result.cs +++ b/src/CommonLib/Result.cs @@ -2,11 +2,11 @@ namespace SharpHoundCommonLib { public class Result : Result { public T Value { get; set; } - protected internal Result(T value, bool success, string error) : base(success, error) { + protected Result(T value, bool success, string error) : base(success, error) { Value = value; } - public static Result Fail(string message) { + public new static Result Fail(string message) { return new Result(default, false, message); } diff --git a/src/CommonLib/SearchResultEntryWrapper.cs b/src/CommonLib/SearchResultEntryWrapper.cs index 002c0b98..b1f317cd 100644 --- a/src/CommonLib/SearchResultEntryWrapper.cs +++ b/src/CommonLib/SearchResultEntryWrapper.cs @@ -39,12 +39,12 @@ public class SearchResultEntryWrapper : ISearchResultEntry private const string MSAClass = "msds-managedserviceaccount"; private readonly SearchResultEntry _entry; private readonly ILogger _log; - private readonly ILdapUtilsNew _utils; + private readonly ILdapUtils _utils; - public SearchResultEntryWrapper(SearchResultEntry entry, ILdapUtilsNew utils = null, ILogger log = null) + public SearchResultEntryWrapper(SearchResultEntry entry, ILdapUtils utils = null, ILogger log = null) { _entry = entry; - _utils = utils ?? new LdapUtilsNew(); + _utils = utils ?? new LdapUtils(); _log = log ?? Logging.LogProvider.CreateLogger("SearchResultWrapper"); } From 3e94ec3fe82094df736f6a799cd7183dc0fc154f Mon Sep 17 00:00:00 2001 From: rvazarkar Date: Tue, 2 Jul 2024 12:44:42 -0400 Subject: [PATCH 24/68] wip: version update --- src/CommonLib/SharpHoundCommonLib.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CommonLib/SharpHoundCommonLib.csproj b/src/CommonLib/SharpHoundCommonLib.csproj index db4fd810..d11f23a7 100644 --- a/src/CommonLib/SharpHoundCommonLib.csproj +++ b/src/CommonLib/SharpHoundCommonLib.csproj @@ -9,7 +9,7 @@ Common library for C# BloodHound enumeration tasks GPL-3.0-only https://github.com/BloodHoundAD/SharpHoundCommon - 3.2.0-rc1 + 4.0.0-rc1 SharpHoundCommonLib SharpHoundCommonLib From ddeb0033794f58ec868c42ba9833fbdb65ebface Mon Sep 17 00:00:00 2001 From: rvazarkar Date: Tue, 2 Jul 2024 13:17:24 -0400 Subject: [PATCH 25/68] chore: add test connection --- src/CommonLib/ConnectionPoolManager.cs | 6 ++++++ src/CommonLib/ILdapUtils.cs | 10 ++++++---- src/CommonLib/LdapUtils.cs | 10 ++++++++++ 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/CommonLib/ConnectionPoolManager.cs b/src/CommonLib/ConnectionPoolManager.cs index 6dbcdc3c..7c9c2f39 100644 --- a/src/CommonLib/ConnectionPoolManager.cs +++ b/src/CommonLib/ConnectionPoolManager.cs @@ -31,6 +31,12 @@ public void ReleaseConnection(LdapConnectionWrapperNew connectionWrapper, bool c pool.ReleaseConnection(connectionWrapper, connectionFaulted); } + public async Task<(bool Success, string Message)> TestDomainConnection(string identifier, bool globalCatalog) { + var (success, connection, message) = await GetLdapConnection(identifier, globalCatalog); + ReleaseConnection(connection); + return (success, message); + } + public async Task<(bool Success, LdapConnectionWrapperNew ConnectionWrapper, string Message)> GetLdapConnection( string identifier, bool globalCatalog) { var resolved = ResolveIdentifier(identifier); diff --git a/src/CommonLib/ILdapUtils.cs b/src/CommonLib/ILdapUtils.cs index 251c26f8..7f6d3703 100644 --- a/src/CommonLib/ILdapUtils.cs +++ b/src/CommonLib/ILdapUtils.cs @@ -34,12 +34,14 @@ IAsyncEnumerable> RangedRetrieval(string distinguishedName, Task<(bool Success, TypedPrincipal Principal)> ResolveCertTemplateByProperty(string propValue, string propName, string domainName); ActiveDirectorySecurityDescriptor MakeSecurityDescriptor(); - public Task<(bool Success, TypedPrincipal Principal)> ConvertLocalWellKnownPrincipal(SecurityIdentifier sid, + Task<(bool Success, TypedPrincipal Principal)> ConvertLocalWellKnownPrincipal(SecurityIdentifier sid, string computerDomainSid, string computerDomain); - public Task IsDomainController(string computerObjectId, string domainName); - public Task<(bool Success, TypedPrincipal Principal)> LookupDistinguishedName(string distinguishedName); - public void AddDomainController(string domainControllerSID); + Task IsDomainController(string computerObjectId, string domainName); + Task<(bool Success, TypedPrincipal Principal)> LookupDistinguishedName(string distinguishedName); + void AddDomainController(string domainControllerSID); IAsyncEnumerable GetWellKnownPrincipalOutput(); + void SetLdapConfig(LDAPConfig config); + Task<(bool Success, string Message)> TestLdapConnection(string domain); } } \ No newline at end of file diff --git a/src/CommonLib/LdapUtils.cs b/src/CommonLib/LdapUtils.cs index 847827f1..ad5f1601 100644 --- a/src/CommonLib/LdapUtils.cs +++ b/src/CommonLib/LdapUtils.cs @@ -1432,5 +1432,15 @@ public async IAsyncEnumerable GetWellKnownPrincipalOutput() { yield return output; } } + + public void SetLdapConfig(LDAPConfig config) { + _ldapConfig = config; + _connectionPool.Dispose(); + _connectionPool = new ConnectionPoolManager(_ldapConfig, scanner: _portScanner); + } + + public Task<(bool Success, string Message)> TestLdapConnection(string domain) { + return _connectionPool.TestDomainConnection(domain, false); + } } } \ No newline at end of file From 2bf6ee46fad34da5150d4ecee1096266dacd3cbe Mon Sep 17 00:00:00 2001 From: rvazarkar Date: Tue, 2 Jul 2024 14:24:40 -0400 Subject: [PATCH 26/68] chore: add disposable to ldap utils --- src/CommonLib/Cache.cs | 12 ------------ src/CommonLib/ILdapUtils.cs | 3 ++- src/CommonLib/LdapUtils.cs | 5 +++++ 3 files changed, 7 insertions(+), 13 deletions(-) diff --git a/src/CommonLib/Cache.cs b/src/CommonLib/Cache.cs index d34eb514..e3ba46f2 100644 --- a/src/CommonLib/Cache.cs +++ b/src/CommonLib/Cache.cs @@ -85,11 +85,6 @@ internal static bool GetMachineSid(string key, out string value) return false; } - internal static void AddConvertedValue(string key, string value) - { - CacheInstance?.ValueToIdCache.TryAdd(key, value); - } - internal static void AddPrefixedValue(string key, string domain, string value) { CacheInstance?.ValueToIdCache.TryAdd(GetPrefixKey(key, domain), value); @@ -112,13 +107,6 @@ internal static bool GetGCCache(string key, out string[] value) return false; } - internal static bool GetConvertedValue(string key, out string value) - { - if (CacheInstance != null) return CacheInstance.ValueToIdCache.TryGetValue(key, out value); - value = null; - return false; - } - internal static bool GetPrefixedValue(string key, string domain, out string value) { if (CacheInstance != null) diff --git a/src/CommonLib/ILdapUtils.cs b/src/CommonLib/ILdapUtils.cs index 7f6d3703..aafcdbfc 100644 --- a/src/CommonLib/ILdapUtils.cs +++ b/src/CommonLib/ILdapUtils.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Security.Principal; using System.Threading; @@ -5,7 +6,7 @@ using SharpHoundCommonLib.OutputTypes; namespace SharpHoundCommonLib { - public interface ILdapUtils { + public interface ILdapUtils : IDisposable { IAsyncEnumerable> Query(LdapQueryParameters queryParameters, CancellationToken cancellationToken = new()); diff --git a/src/CommonLib/LdapUtils.cs b/src/CommonLib/LdapUtils.cs index ad5f1601..60c9e116 100644 --- a/src/CommonLib/LdapUtils.cs +++ b/src/CommonLib/LdapUtils.cs @@ -1213,6 +1213,7 @@ public bool GetDomain(out Domain domain) { } } + Cache.AddGCCache(name, sids.ToArray()); return (true, sids.ToArray()); } @@ -1442,5 +1443,9 @@ public void SetLdapConfig(LDAPConfig config) { public Task<(bool Success, string Message)> TestLdapConnection(string domain) { return _connectionPool.TestDomainConnection(domain, false); } + + public void Dispose() { + _connectionPool?.Dispose(); + } } } \ No newline at end of file From 9a3feaf8487b6dcd4ff0af841d4e13c4633dd998 Mon Sep 17 00:00:00 2001 From: rvazarkar Date: Tue, 2 Jul 2024 20:20:59 -0400 Subject: [PATCH 27/68] wip: rename connection wrapper --- src/CommonLib/ConnectionPoolManager.cs | 6 +- src/CommonLib/ILdapUtils.cs | 4 +- src/CommonLib/LdapConnectionPool.cs | 28 ++--- src/CommonLib/LdapConnectionWrapper.cs | 99 +++++++++++++++- src/CommonLib/LdapConnectionWrapperNew.cs | 110 ------------------ src/CommonLib/LdapQueryParameters.cs | 1 - src/CommonLib/LdapQuerySetupResult.cs | 2 +- src/CommonLib/LdapUtils.cs | 69 ++++++++++- .../Processors/ContainerProcessor.cs | 4 +- src/CommonLib/Processors/GroupProcessor.cs | 4 +- .../Processors/LDAPPropertyProcessor.cs | 4 +- 11 files changed, 187 insertions(+), 144 deletions(-) delete mode 100644 src/CommonLib/LdapConnectionWrapperNew.cs diff --git a/src/CommonLib/ConnectionPoolManager.cs b/src/CommonLib/ConnectionPoolManager.cs index 7c9c2f39..afc7d299 100644 --- a/src/CommonLib/ConnectionPoolManager.cs +++ b/src/CommonLib/ConnectionPoolManager.cs @@ -21,7 +21,7 @@ public ConnectionPoolManager(LDAPConfig config, ILogger log = null, PortScanner _portScanner = scanner ?? new PortScanner(); } - public void ReleaseConnection(LdapConnectionWrapperNew connectionWrapper, bool connectionFaulted = false) { + public void ReleaseConnection(LdapConnectionWrapper connectionWrapper, bool connectionFaulted = false) { //I dont think this is possible, but at least account for it if (!_pools.TryGetValue(connectionWrapper.PoolIdentifier, out var pool)) { connectionWrapper.Connection.Dispose(); @@ -37,7 +37,7 @@ public void ReleaseConnection(LdapConnectionWrapperNew connectionWrapper, bool c return (success, message); } - public async Task<(bool Success, LdapConnectionWrapperNew ConnectionWrapper, string Message)> GetLdapConnection( + public async Task<(bool Success, LdapConnectionWrapper ConnectionWrapper, string Message)> GetLdapConnection( string identifier, bool globalCatalog) { var resolved = ResolveIdentifier(identifier); @@ -52,7 +52,7 @@ public void ReleaseConnection(LdapConnectionWrapperNew connectionWrapper, bool c return await pool.GetConnectionAsync(); } - public async Task<(bool Success, LdapConnectionWrapperNew connectionWrapper, string Message)> GetLdapConnectionForServer( + public async Task<(bool Success, LdapConnectionWrapper connectionWrapper, string Message)> GetLdapConnectionForServer( string identifier, string server, bool globalCatalog) { var resolved = ResolveIdentifier(identifier); diff --git a/src/CommonLib/ILdapUtils.cs b/src/CommonLib/ILdapUtils.cs index aafcdbfc..bf15cac2 100644 --- a/src/CommonLib/ILdapUtils.cs +++ b/src/CommonLib/ILdapUtils.cs @@ -3,6 +3,7 @@ using System.Security.Principal; using System.Threading; using System.Threading.Tasks; +using SharpHoundCommonLib.Enums; using SharpHoundCommonLib.OutputTypes; namespace SharpHoundCommonLib { @@ -39,10 +40,11 @@ IAsyncEnumerable> RangedRetrieval(string distinguishedName, string computerDomainSid, string computerDomain); Task IsDomainController(string computerObjectId, string domainName); - Task<(bool Success, TypedPrincipal Principal)> LookupDistinguishedName(string distinguishedName); + Task<(bool Success, TypedPrincipal Principal)> ResolveDistinguishedName(string distinguishedName); void AddDomainController(string domainControllerSID); IAsyncEnumerable GetWellKnownPrincipalOutput(); void SetLdapConfig(LDAPConfig config); Task<(bool Success, string Message)> TestLdapConnection(string domain); + Task<(bool Success, string Path)> GetNamingContextPath(string domain, NamingContext context); } } \ No newline at end of file diff --git a/src/CommonLib/LdapConnectionPool.cs b/src/CommonLib/LdapConnectionPool.cs index 289c51d1..1cd52345 100644 --- a/src/CommonLib/LdapConnectionPool.cs +++ b/src/CommonLib/LdapConnectionPool.cs @@ -14,8 +14,8 @@ namespace SharpHoundCommonLib { public class LdapConnectionPool : IDisposable{ - private readonly ConcurrentBag _connections; - private readonly ConcurrentBag _globalCatalogConnection; + private readonly ConcurrentBag _connections; + private readonly ConcurrentBag _globalCatalogConnection; private static readonly ConcurrentDictionary DomainCache = new(); private readonly SemaphoreSlim _semaphore; private readonly string _identifier; @@ -25,8 +25,8 @@ public class LdapConnectionPool : IDisposable{ private readonly NativeMethods _nativeMethods; public LdapConnectionPool(string identifier, LDAPConfig config, int maxConnections = 10, PortScanner scanner = null, NativeMethods nativeMethods = null, ILogger log = null) { - _connections = new ConcurrentBag(); - _globalCatalogConnection = new ConcurrentBag(); + _connections = new ConcurrentBag(); + _globalCatalogConnection = new ConcurrentBag(); _semaphore = new SemaphoreSlim(maxConnections, maxConnections); _identifier = identifier; _ldapConfig = config; @@ -35,7 +35,7 @@ public LdapConnectionPool(string identifier, LDAPConfig config, int maxConnectio _nativeMethods = nativeMethods ?? new NativeMethods(); } - public async Task<(bool Success, LdapConnectionWrapperNew ConnectionWrapper, string Message)> GetConnectionAsync() { + public async Task<(bool Success, LdapConnectionWrapper ConnectionWrapper, string Message)> GetConnectionAsync() { await _semaphore.WaitAsync(); if (!_connections.TryTake(out var connectionWrapper)) { var (success, connection, message) = await CreateNewConnection(); @@ -51,7 +51,7 @@ public LdapConnectionPool(string identifier, LDAPConfig config, int maxConnectio return (true, connectionWrapper, null); } - public async Task<(bool Success, LdapConnectionWrapperNew connectionWrapper, string Message)> + public async Task<(bool Success, LdapConnectionWrapper connectionWrapper, string Message)> GetConnectionForSpecificServerAsync(string server, bool globalCatalog) { await _semaphore.WaitAsync(); @@ -64,7 +64,7 @@ public LdapConnectionPool(string identifier, LDAPConfig config, int maxConnectio return result; } - public async Task<(bool Success, LdapConnectionWrapperNew ConnectionWrapper, string Message)> GetGlobalCatalogConnectionAsync() { + public async Task<(bool Success, LdapConnectionWrapper ConnectionWrapper, string Message)> GetGlobalCatalogConnectionAsync() { await _semaphore.WaitAsync(); if (!_globalCatalogConnection.TryTake(out var connectionWrapper)) { var (success, connection, message) = await CreateNewConnection(true); @@ -80,7 +80,7 @@ public LdapConnectionPool(string identifier, LDAPConfig config, int maxConnectio return (true, connectionWrapper, null); } - public void ReleaseConnection(LdapConnectionWrapperNew connectionWrapper, bool connectionFaulted = false) { + public void ReleaseConnection(LdapConnectionWrapper connectionWrapper, bool connectionFaulted = false) { if (!connectionFaulted) { if (connectionWrapper.GlobalCatalog) { _globalCatalogConnection.Add(connectionWrapper); @@ -102,7 +102,7 @@ public void Dispose() { } } - private async Task<(bool Success, LdapConnectionWrapperNew Connection, string Message)> CreateNewConnection(bool globalCatalog = false) { + private async Task<(bool Success, LdapConnectionWrapper Connection, string Message)> CreateNewConnection(bool globalCatalog = false) { if (!string.IsNullOrWhiteSpace(_ldapConfig.Server)) { return CreateNewConnectionForServer(_ldapConfig.Server, globalCatalog); } @@ -182,7 +182,7 @@ public void Dispose() { return (false, null, "All attempted connections failed"); } - private (bool Success, LdapConnectionWrapperNew Connection, string Message ) CreateNewConnectionForServer(string identifier, bool globalCatalog = false) { + private (bool Success, LdapConnectionWrapper Connection, string Message ) CreateNewConnectionForServer(string identifier, bool globalCatalog = false) { if (CreateLdapConnection(identifier, globalCatalog, out var serverConnection)) { return (true, serverConnection, ""); } @@ -191,10 +191,10 @@ public void Dispose() { } private bool CreateLdapConnection(string target, bool globalCatalog, - out LdapConnectionWrapperNew connection) { + out LdapConnectionWrapper connection) { var baseConnection = CreateBaseConnection(target, true, globalCatalog); if (TestLdapConnection(baseConnection, out var result)) { - connection = new LdapConnectionWrapperNew(baseConnection, result.SearchResultEntry, globalCatalog, _identifier); + connection = new LdapConnectionWrapper(baseConnection, result.SearchResultEntry, globalCatalog, _identifier); return true; } @@ -212,7 +212,7 @@ private bool CreateLdapConnection(string target, bool globalCatalog, baseConnection = CreateBaseConnection(target, false, globalCatalog); if (TestLdapConnection(baseConnection, out result)) { - connection = new LdapConnectionWrapperNew(baseConnection, result.SearchResultEntry, globalCatalog, _identifier); + connection = new LdapConnectionWrapper(baseConnection, result.SearchResultEntry, globalCatalog, _identifier); return true; } @@ -328,7 +328,7 @@ private class LdapConnectionTestResult { public int ErrorCode { get; set; } } - private async Task<(bool success, LdapConnectionWrapperNew connection)> CreateLDAPConnectionWithPortCheck( + private async Task<(bool success, LdapConnectionWrapper connection)> CreateLDAPConnectionWithPortCheck( string target, bool globalCatalog) { if (globalCatalog) { if (await _portScanner.CheckPort(target, _ldapConfig.GetGCPort(true)) || (!_ldapConfig.ForceSSL && diff --git a/src/CommonLib/LdapConnectionWrapper.cs b/src/CommonLib/LdapConnectionWrapper.cs index 031b32c9..12d521b6 100644 --- a/src/CommonLib/LdapConnectionWrapper.cs +++ b/src/CommonLib/LdapConnectionWrapper.cs @@ -1,10 +1,97 @@ +using System; using System.DirectoryServices.Protocols; +using SharpHoundCommonLib.Enums; -namespace SharpHoundCommonLib -{ - public class LdapConnectionWrapper - { - public LdapConnection Connection; - public DomainInfo DomainInfo; +namespace SharpHoundCommonLib { + public class LdapConnectionWrapper { + public LdapConnection Connection { get; private set; } + private readonly ISearchResultEntry _searchResultEntry; + private string _domainSearchBase; + private string _configurationSearchBase; + private string _schemaSearchBase; + private string _server; + private string Guid { get; set; } + public bool GlobalCatalog; + public string PoolIdentifier; + + public LdapConnectionWrapper(LdapConnection connection, ISearchResultEntry entry, bool globalCatalog, + string poolIdentifier) { + Connection = connection; + _searchResultEntry = entry; + Guid = new Guid().ToString(); + GlobalCatalog = globalCatalog; + PoolIdentifier = poolIdentifier; + } + + public string GetServer() { + if (_server != null) { + return _server; + } + + _server = _searchResultEntry.GetProperty(LDAPProperties.DNSHostName); + return _server; + } + + public bool GetSearchBase(NamingContext context, out string searchBase) { + searchBase = GetSavedContext(context); + if (searchBase != null) { + return true; + } + + searchBase = context switch { + NamingContext.Default => _searchResultEntry.GetProperty(LDAPProperties.DefaultNamingContext), + NamingContext.Configuration => + _searchResultEntry.GetProperty(LDAPProperties.ConfigurationNamingContext), + NamingContext.Schema => _searchResultEntry.GetProperty(LDAPProperties.SchemaNamingContext), + _ => throw new ArgumentOutOfRangeException(nameof(context), context, null) + }; + + if (searchBase != null) { + SaveContext(context, searchBase); + return true; + } + + return false; + } + + private string GetSavedContext(NamingContext context) { + return context switch { + NamingContext.Configuration => _configurationSearchBase, + NamingContext.Default => _domainSearchBase, + NamingContext.Schema => _schemaSearchBase, + _ => throw new ArgumentOutOfRangeException(nameof(context), context, null) + }; + } + + public void SaveContext(NamingContext context, string searchBase) { + switch (context) { + case NamingContext.Default: + _domainSearchBase = searchBase; + break; + case NamingContext.Configuration: + _configurationSearchBase = searchBase; + break; + case NamingContext.Schema: + _schemaSearchBase = searchBase; + break; + default: + throw new ArgumentOutOfRangeException(nameof(context), context, null); + } + } + + protected bool Equals(LdapConnectionWrapper other) { + return Guid == other.Guid; + } + + public override bool Equals(object obj) { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((LdapConnectionWrapper)obj); + } + + public override int GetHashCode() { + return (Guid != null ? Guid.GetHashCode() : 0); + } } } \ No newline at end of file diff --git a/src/CommonLib/LdapConnectionWrapperNew.cs b/src/CommonLib/LdapConnectionWrapperNew.cs deleted file mode 100644 index 2e85d894..00000000 --- a/src/CommonLib/LdapConnectionWrapperNew.cs +++ /dev/null @@ -1,110 +0,0 @@ -using System; -using System.DirectoryServices.Protocols; -using SharpHoundCommonLib.Enums; - -namespace SharpHoundCommonLib { - public class LdapConnectionWrapperNew - { - public LdapConnection Connection { get; private set; } - private readonly ISearchResultEntry _searchResultEntry; - private string _domainSearchBase; - private string _configurationSearchBase; - private string _schemaSearchBase; - private string _server; - private string Guid { get; set; } - public bool GlobalCatalog; - public string PoolIdentifier; - - public LdapConnectionWrapperNew(LdapConnection connection, ISearchResultEntry entry, bool globalCatalog, string poolIdentifier) - { - Connection = connection; - _searchResultEntry = entry; - Guid = new Guid().ToString(); - GlobalCatalog = globalCatalog; - PoolIdentifier = poolIdentifier; - } - - public void CopyContexts(LdapConnectionWrapperNew other) { - _domainSearchBase = other._domainSearchBase; - _configurationSearchBase = other._configurationSearchBase; - _schemaSearchBase = other._schemaSearchBase; - _server = other._server; - } - - public string GetServer() { - if (_server != null) { - return _server; - } - - _server = _searchResultEntry.GetProperty(LDAPProperties.DNSHostName); - return _server; - } - - public bool GetSearchBase(NamingContext context, out string searchBase) - { - searchBase = GetSavedContext(context); - if (searchBase != null) - { - return true; - } - - searchBase = context switch { - NamingContext.Default => _searchResultEntry.GetProperty(LDAPProperties.DefaultNamingContext), - NamingContext.Configuration => _searchResultEntry.GetProperty(LDAPProperties.ConfigurationNamingContext), - NamingContext.Schema => _searchResultEntry.GetProperty(LDAPProperties.SchemaNamingContext), - _ => throw new ArgumentOutOfRangeException(nameof(context), context, null) - }; - - if (searchBase != null) { - SaveContext(context, searchBase); - return true; - } - - return false; - } - - private string GetSavedContext(NamingContext context) - { - return context switch - { - NamingContext.Configuration => _configurationSearchBase, - NamingContext.Default => _domainSearchBase, - NamingContext.Schema => _schemaSearchBase, - _ => throw new ArgumentOutOfRangeException(nameof(context), context, null) - }; - } - - public void SaveContext(NamingContext context, string searchBase) - { - switch (context) - { - case NamingContext.Default: - _domainSearchBase = searchBase; - break; - case NamingContext.Configuration: - _configurationSearchBase = searchBase; - break; - case NamingContext.Schema: - _schemaSearchBase = searchBase; - break; - default: - throw new ArgumentOutOfRangeException(nameof(context), context, null); - } - } - - protected bool Equals(LdapConnectionWrapperNew other) { - return Guid == other.Guid; - } - - public override bool Equals(object obj) { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((LdapConnectionWrapperNew)obj); - } - - public override int GetHashCode() { - return (Guid != null ? Guid.GetHashCode() : 0); - } - } -} \ No newline at end of file diff --git a/src/CommonLib/LdapQueryParameters.cs b/src/CommonLib/LdapQueryParameters.cs index 4cb60a77..e4f099d9 100644 --- a/src/CommonLib/LdapQueryParameters.cs +++ b/src/CommonLib/LdapQueryParameters.cs @@ -32,7 +32,6 @@ public string RelativeSearchBase { } public NamingContext NamingContext { get; set; } = NamingContext.Default; - public bool ThrowException { get; set; } = false; public string GetQueryInfo() { diff --git a/src/CommonLib/LdapQuerySetupResult.cs b/src/CommonLib/LdapQuerySetupResult.cs index 3842ac18..8a2f0e73 100644 --- a/src/CommonLib/LdapQuerySetupResult.cs +++ b/src/CommonLib/LdapQuerySetupResult.cs @@ -3,7 +3,7 @@ namespace SharpHoundCommonLib { public class LdapQuerySetupResult { - public LdapConnectionWrapperNew ConnectionWrapper { get; set; } + public LdapConnectionWrapper ConnectionWrapper { get; set; } public SearchRequest SearchRequest { get; set; } public string Server { get; set; } public bool Success { get; set; } diff --git a/src/CommonLib/LdapUtils.cs b/src/CommonLib/LdapUtils.cs index 60c9e116..23d0e296 100644 --- a/src/CommonLib/LdapUtils.cs +++ b/src/CommonLib/LdapUtils.cs @@ -697,7 +697,7 @@ private static TimeSpan GetNextBackoff(int retryCount) { } private bool CreateSearchRequest(LdapQueryParameters queryParameters, - LdapConnectionWrapperNew connectionWrapper, out SearchRequest searchRequest) { + LdapConnectionWrapper connectionWrapper, out SearchRequest searchRequest) { string basePath; if (!string.IsNullOrWhiteSpace(queryParameters.SearchBase)) { basePath = queryParameters.SearchBase; @@ -1353,7 +1353,7 @@ public async Task IsDomainController(string computerObjectId, string domai return result is { IsSuccess: true }; } - public async Task<(bool Success, TypedPrincipal Principal)> LookupDistinguishedName(string distinguishedName) { + public async Task<(bool Success, TypedPrincipal Principal)> ResolveDistinguishedName(string distinguishedName) { if (_distinguishedNameCache.TryGetValue(distinguishedName, out var principal)) { return (true, principal); } @@ -1444,6 +1444,71 @@ public void SetLdapConfig(LDAPConfig config) { return _connectionPool.TestDomainConnection(domain, false); } + public async Task<(bool Success, string Path)> GetNamingContextPath(string domain, NamingContext context) { + if (await _connectionPool.GetLdapConnection(domain, false) is (true, var wrapper, _)) { + _connectionPool.ReleaseConnection(wrapper); + if (wrapper.GetSearchBase(context, out var searchBase)) { + return (true, searchBase); + } + } + + var property = context switch { + NamingContext.Default => LDAPProperties.DefaultNamingContext, + NamingContext.Configuration => LDAPProperties.ConfigurationNamingContext, + NamingContext.Schema => LDAPProperties.SchemaNamingContext, + _ => throw new ArgumentOutOfRangeException(nameof(context), context, null) + }; + + try { + var entry = CreateDirectoryEntry($"LDAP://{domain}/RootDSE"); + entry.RefreshCache(new[] { property }); + var searchBase = entry.GetProperty(property); + if (!string.IsNullOrWhiteSpace(searchBase)) { + return (true, searchBase); + } + } + catch { + //pass + } + + if (GetDomain(domain, out var domainObj)) { + try { + var entry = domainObj.GetDirectoryEntry(); + entry.RefreshCache(new[] { property }); + var searchBase = entry.GetProperty(property); + if (!string.IsNullOrWhiteSpace(searchBase)) { + return (true, searchBase); + } + } + catch { + //pass + } + + var name = domainObj.Name; + if (!string.IsNullOrWhiteSpace(name)) { + var tempPath = Helpers.DomainNameToDistinguishedName(name); + + var searchBase = context switch { + NamingContext.Configuration => $"CN=Configuration,{tempPath}", + NamingContext.Schema => $"CN=Schema,CN=Configuration,{tempPath}", + NamingContext.Default => tempPath, + _ => throw new ArgumentOutOfRangeException() + }; + + return (true, searchBase); + } + } + + return (false, default); + } + + private DirectoryEntry CreateDirectoryEntry(string path) { + if (_ldapConfig.Username != null) { + return new DirectoryEntry(path, _ldapConfig.Username, _ldapConfig.Password); + } + + return new DirectoryEntry(path); + } public void Dispose() { _connectionPool?.Dispose(); } diff --git a/src/CommonLib/Processors/ContainerProcessor.cs b/src/CommonLib/Processors/ContainerProcessor.cs index a960dd42..79022dc9 100644 --- a/src/CommonLib/Processors/ContainerProcessor.cs +++ b/src/CommonLib/Processors/ContainerProcessor.cs @@ -61,7 +61,7 @@ private static bool IsDistinguishedNameFiltered(string distinguishedName) return (false, default); } - return await _utils.LookupDistinguishedName(containerDn); + return await _utils.ResolveDistinguishedName(containerDn); } /// @@ -145,7 +145,7 @@ public async IAsyncEnumerable ReadContainerGPLinks(string gpLink) { var enforced = link.Status.Equals("2"); - var res = await _utils.LookupDistinguishedName(link.DistinguishedName); + var res = await _utils.ResolveDistinguishedName(link.DistinguishedName); if (res.Success) { yield return new GPLink diff --git a/src/CommonLib/Processors/GroupProcessor.cs b/src/CommonLib/Processors/GroupProcessor.cs index ae11d788..bfcbbbb7 100644 --- a/src/CommonLib/Processors/GroupProcessor.cs +++ b/src/CommonLib/Processors/GroupProcessor.cs @@ -53,7 +53,7 @@ public async IAsyncEnumerable ReadGroupMembers(string distinguis var member = result.Value; _log.LogTrace("Got member {DN} for {ObjectName} from ranged retrieval", member, objectName); - if (await _utils.LookupDistinguishedName(member) is (true, var res) && !Helpers.IsSidFiltered(res.ObjectIdentifier)) { + if (await _utils.ResolveDistinguishedName(member) is (true, var res) && !Helpers.IsSidFiltered(res.ObjectIdentifier)) { yield return res; } else { @@ -67,7 +67,7 @@ public async IAsyncEnumerable ReadGroupMembers(string distinguis foreach (var member in members) { _log.LogTrace("Got member {DN} for {ObjectName}", member, objectName); - if (await _utils.LookupDistinguishedName(member) is (true, var res) && !Helpers.IsSidFiltered(res.ObjectIdentifier)) { + if (await _utils.ResolveDistinguishedName(member) is (true, var res) && !Helpers.IsSidFiltered(res.ObjectIdentifier)) { yield return res; } else { diff --git a/src/CommonLib/Processors/LDAPPropertyProcessor.cs b/src/CommonLib/Processors/LDAPPropertyProcessor.cs index a4f925d3..9437fb15 100644 --- a/src/CommonLib/Processors/LDAPPropertyProcessor.cs +++ b/src/CommonLib/Processors/LDAPPropertyProcessor.cs @@ -356,7 +356,7 @@ public async Task ReadComputerProperties(ISearchResultEntry { foreach (var dn in hsa) { - if (await _utils.LookupDistinguishedName(dn) is (true, var resolvedPrincipal)) + if (await _utils.ResolveDistinguishedName(dn) is (true, var resolvedPrincipal)) smsaPrincipals.Add(resolvedPrincipal); } } @@ -544,7 +544,7 @@ public async Task ReadIssuancePolicyProperties(ISearch var link = entry.GetProperty(LDAPProperties.OIDGroupLink); if (!string.IsNullOrEmpty(link)) { - if (await _utils.LookupDistinguishedName(link) is (true, var linkedGroup)) + if (await _utils.ResolveDistinguishedName(link) is (true, var linkedGroup)) { props.Add("oidgrouplink", linkedGroup.ObjectIdentifier); ret.GroupLink = linkedGroup; From 9c767f92ef33e0e34f5852886f7776b3bbe041e0 Mon Sep 17 00:00:00 2001 From: rvazarkar Date: Wed, 3 Jul 2024 16:55:52 -0400 Subject: [PATCH 28/68] wip: more fixes identified during during testing --- src/CommonLib/ConnectionPoolManager.cs | 13 ++++---- src/CommonLib/Extensions.cs | 22 ++++++------- src/CommonLib/LDAPConfig.cs | 6 +++- src/CommonLib/LdapConnectionPool.cs | 31 ++++++++++++------- src/CommonLib/LdapConnectionWrapper.cs | 4 +-- src/CommonLib/LdapResult.cs | 4 +++ src/CommonLib/LdapUtils.cs | 43 +++++++++----------------- src/CommonLib/Result.cs | 2 +- 8 files changed, 63 insertions(+), 62 deletions(-) diff --git a/src/CommonLib/ConnectionPoolManager.cs b/src/CommonLib/ConnectionPoolManager.cs index afc7d299..95fc9615 100644 --- a/src/CommonLib/ConnectionPoolManager.cs +++ b/src/CommonLib/ConnectionPoolManager.cs @@ -24,6 +24,7 @@ public ConnectionPoolManager(LDAPConfig config, ILogger log = null, PortScanner public void ReleaseConnection(LdapConnectionWrapper connectionWrapper, bool connectionFaulted = false) { //I dont think this is possible, but at least account for it if (!_pools.TryGetValue(connectionWrapper.PoolIdentifier, out var pool)) { + _log.LogWarning("Could not find pool for {Identifier}", connectionWrapper.PoolIdentifier); connectionWrapper.Connection.Dispose(); return; } @@ -41,9 +42,9 @@ public void ReleaseConnection(LdapConnectionWrapper connectionWrapper, bool conn string identifier, bool globalCatalog) { var resolved = ResolveIdentifier(identifier); - if (!_pools.TryGetValue(identifier, out var pool)) { - pool = new LdapConnectionPool(resolved, _ldapConfig,scanner: _portScanner); - _pools.TryAdd(identifier, pool); + if (!_pools.TryGetValue(resolved, out var pool)) { + pool = new LdapConnectionPool(identifier, resolved, _ldapConfig,scanner: _portScanner); + _pools.TryAdd(resolved, pool); } if (globalCatalog) { @@ -56,9 +57,9 @@ public void ReleaseConnection(LdapConnectionWrapper connectionWrapper, bool conn string identifier, string server, bool globalCatalog) { var resolved = ResolveIdentifier(identifier); - if (!_pools.TryGetValue(identifier, out var pool)) { - pool = new LdapConnectionPool(resolved, _ldapConfig,scanner: _portScanner); - _pools.TryAdd(identifier, pool); + if (!_pools.TryGetValue(resolved, out var pool)) { + pool = new LdapConnectionPool(resolved, identifier, _ldapConfig,scanner: _portScanner); + _pools.TryAdd(resolved, pool); } return await pool.GetConnectionForSpecificServerAsync(server, globalCatalog); diff --git a/src/CommonLib/Extensions.cs b/src/CommonLib/Extensions.cs index ea4d58dc..869ef104 100644 --- a/src/CommonLib/Extensions.cs +++ b/src/CommonLib/Extensions.cs @@ -446,6 +446,7 @@ public static bool GetLabel(this DirectoryEntry entry, out Label type) { } private static bool ResolveLabel(string objectIdentifier, string distinguishedName, string samAccountType, string[] objectClasses, int flags, out Label type) { + type = Label.Base; if (objectIdentifier != null && WellKnownPrincipal.GetWellKnownPrincipal(objectIdentifier, out var principal)) { type = principal.ObjectType; return true; @@ -474,28 +475,26 @@ private static bool ResolveLabel(string objectIdentifier, string distinguishedNa if (objectClasses.Contains(GroupPolicyContainerClass, StringComparer.InvariantCultureIgnoreCase)) type = Label.GPO; - if (objectClasses.Contains(OrganizationalUnitClass, StringComparer.InvariantCultureIgnoreCase)) + else if (objectClasses.Contains(OrganizationalUnitClass, StringComparer.InvariantCultureIgnoreCase)) type = Label.OU; - if (objectClasses.Contains(DomainClass, StringComparer.InvariantCultureIgnoreCase)) + else if (objectClasses.Contains(DomainClass, StringComparer.InvariantCultureIgnoreCase)) type = Label.Domain; - if (objectClasses.Contains(ContainerClass, StringComparer.InvariantCultureIgnoreCase)) + else if (objectClasses.Contains(ContainerClass, StringComparer.InvariantCultureIgnoreCase)) type = Label.Container; - if (objectClasses.Contains(ConfigurationClass, StringComparer.InvariantCultureIgnoreCase)) + else if (objectClasses.Contains(ConfigurationClass, StringComparer.InvariantCultureIgnoreCase)) type = Label.Configuration; - if (objectClasses.Contains(PKICertificateTemplateClass, StringComparer.InvariantCultureIgnoreCase)) + else if (objectClasses.Contains(PKICertificateTemplateClass, StringComparer.InvariantCultureIgnoreCase)) type = Label.CertTemplate; - if (objectClasses.Contains(PKIEnrollmentServiceClass, StringComparer.InvariantCultureIgnoreCase)) + else if (objectClasses.Contains(PKIEnrollmentServiceClass, StringComparer.InvariantCultureIgnoreCase)) type = Label.EnterpriseCA; - if (objectClasses.Contains(CertificationAuthorityClass, StringComparer.InvariantCultureIgnoreCase)) { + else if (objectClasses.Contains(CertificationAuthorityClass, StringComparer.InvariantCultureIgnoreCase)) { if (distinguishedName.Contains(DirectoryPaths.RootCALocation)) type = Label.RootCA; if (distinguishedName.Contains(DirectoryPaths.AIACALocation)) type = Label.AIACA; if (distinguishedName.Contains(DirectoryPaths.NTAuthStoreLocation)) type = Label.NTAuthStore; - } - - if (objectClasses.Contains(OIDContainerClass, StringComparer.InvariantCultureIgnoreCase)) { + }else if (objectClasses.Contains(OIDContainerClass, StringComparer.InvariantCultureIgnoreCase)) { if (distinguishedName.StartsWith(DirectoryPaths.OIDContainerLocation, StringComparison.InvariantCultureIgnoreCase)) type = Label.Container; @@ -505,8 +504,7 @@ private static bool ResolveLabel(string objectIdentifier, string distinguishedNa } } - type = Label.Base; - return false; + return type != Label.Base; } /// diff --git a/src/CommonLib/LDAPConfig.cs b/src/CommonLib/LDAPConfig.cs index e8bb26e4..c4cdd9ff 100644 --- a/src/CommonLib/LDAPConfig.cs +++ b/src/CommonLib/LDAPConfig.cs @@ -8,6 +8,7 @@ public class LDAPConfig public string Password { get; set; } = null; public string Server { get; set; } = null; public int Port { get; set; } = 0; + public int SSLPort { get; set; } = 0; public bool ForceSSL { get; set; } = false; public bool DisableSigning { get; set; } = false; public bool DisableCertVerification { get; set; } = false; @@ -16,7 +17,10 @@ public class LDAPConfig //Returns the port for connecting to LDAP. Will always respect a user's overridden config over anything else public int GetPort(bool ssl) { - if (Port != 0) + if (ssl && SSLPort != 0) { + return SSLPort; + } + if (!ssl && Port != 0) { return Port; } diff --git a/src/CommonLib/LdapConnectionPool.cs b/src/CommonLib/LdapConnectionPool.cs index 1cd52345..a555cf57 100644 --- a/src/CommonLib/LdapConnectionPool.cs +++ b/src/CommonLib/LdapConnectionPool.cs @@ -16,19 +16,20 @@ namespace SharpHoundCommonLib { public class LdapConnectionPool : IDisposable{ private readonly ConcurrentBag _connections; private readonly ConcurrentBag _globalCatalogConnection; - private static readonly ConcurrentDictionary DomainCache = new(); private readonly SemaphoreSlim _semaphore; private readonly string _identifier; + private readonly string _poolIdentifier; private readonly LDAPConfig _ldapConfig; private readonly ILogger _log; private readonly PortScanner _portScanner; private readonly NativeMethods _nativeMethods; - public LdapConnectionPool(string identifier, LDAPConfig config, int maxConnections = 10, PortScanner scanner = null, NativeMethods nativeMethods = null, ILogger log = null) { + public LdapConnectionPool(string identifier, string poolIdentifier, LDAPConfig config, int maxConnections = 10, PortScanner scanner = null, NativeMethods nativeMethods = null, ILogger log = null) { _connections = new ConcurrentBag(); _globalCatalogConnection = new ConcurrentBag(); _semaphore = new SemaphoreSlim(maxConnections, maxConnections); _identifier = identifier; + _poolIdentifier = poolIdentifier; _ldapConfig = config; _log = log ?? Logging.LogProvider.CreateLogger("LdapConnectionPool"); _portScanner = scanner ?? new PortScanner(); @@ -81,6 +82,7 @@ public LdapConnectionPool(string identifier, LDAPConfig config, int maxConnectio } public void ReleaseConnection(LdapConnectionWrapper connectionWrapper, bool connectionFaulted = false) { + _semaphore.Release(); if (!connectionFaulted) { if (connectionWrapper.GlobalCatalog) { _globalCatalogConnection.Add(connectionWrapper); @@ -92,8 +94,6 @@ public void ReleaseConnection(LdapConnectionWrapper connectionWrapper, bool conn else { connectionWrapper.Connection.Dispose(); } - - _semaphore.Release(); } public void Dispose() { @@ -108,7 +108,7 @@ public void Dispose() { } if (CreateLdapConnection(_identifier.ToUpper().Trim(), globalCatalog, out var connectionWrapper)) { - _log.LogDebug("Successfully created ldap connection for domain: {Domain} using strategy 1", _identifier); + _log.LogDebug("Successfully created ldap connection for domain: {Domain} using strategy 1. SSL: {SSl}", _identifier, connectionWrapper.Connection.SessionOptions.SecureSocketLayer); return (true, connectionWrapper, ""); } @@ -194,7 +194,7 @@ private bool CreateLdapConnection(string target, bool globalCatalog, out LdapConnectionWrapper connection) { var baseConnection = CreateBaseConnection(target, true, globalCatalog); if (TestLdapConnection(baseConnection, out var result)) { - connection = new LdapConnectionWrapper(baseConnection, result.SearchResultEntry, globalCatalog, _identifier); + connection = new LdapConnectionWrapper(baseConnection, result.SearchResultEntry, globalCatalog, _poolIdentifier); return true; } @@ -212,7 +212,7 @@ private bool CreateLdapConnection(string target, bool globalCatalog, baseConnection = CreateBaseConnection(target, false, globalCatalog); if (TestLdapConnection(baseConnection, out result)) { - connection = new LdapConnectionWrapper(baseConnection, result.SearchResultEntry, globalCatalog, _identifier); + connection = new LdapConnectionWrapper(baseConnection, result.SearchResultEntry, globalCatalog, _poolIdentifier); return true; } @@ -229,19 +229,26 @@ private bool CreateLdapConnection(string target, bool globalCatalog, private LdapConnection CreateBaseConnection(string directoryIdentifier, bool ssl, bool globalCatalog) { + _log.LogDebug("Creating connection for identifier {Identifier}", directoryIdentifier); var port = globalCatalog ? _ldapConfig.GetGCPort(ssl) : _ldapConfig.GetPort(ssl); var identifier = new LdapDirectoryIdentifier(directoryIdentifier, port, false, false); var connection = new LdapConnection(identifier) { Timeout = new TimeSpan(0, 0, 5, 0) }; - + //These options are important! connection.SessionOptions.ProtocolVersion = 3; //Referral chasing does not work with paged searches connection.SessionOptions.ReferralChasing = ReferralChasingOptions.None; if (ssl) connection.SessionOptions.SecureSocketLayer = true; - - connection.SessionOptions.Sealing = !_ldapConfig.DisableSigning; - connection.SessionOptions.Signing = !_ldapConfig.DisableSigning; - + + if (_ldapConfig.DisableSigning || ssl) { + connection.SessionOptions.Signing = false; + connection.SessionOptions.Sealing = false; + } + else { + connection.SessionOptions.Signing = true; + connection.SessionOptions.Sealing = true; + } + if (_ldapConfig.DisableCertVerification) connection.SessionOptions.VerifyServerCertificate = (_, _) => true; diff --git a/src/CommonLib/LdapConnectionWrapper.cs b/src/CommonLib/LdapConnectionWrapper.cs index 12d521b6..0482603d 100644 --- a/src/CommonLib/LdapConnectionWrapper.cs +++ b/src/CommonLib/LdapConnectionWrapper.cs @@ -11,8 +11,8 @@ public class LdapConnectionWrapper { private string _schemaSearchBase; private string _server; private string Guid { get; set; } - public bool GlobalCatalog; - public string PoolIdentifier; + public readonly bool GlobalCatalog; + public readonly string PoolIdentifier; public LdapConnectionWrapper(LdapConnection connection, ISearchResultEntry entry, bool globalCatalog, string poolIdentifier) { diff --git a/src/CommonLib/LdapResult.cs b/src/CommonLib/LdapResult.cs index 7a951597..f565bbcf 100644 --- a/src/CommonLib/LdapResult.cs +++ b/src/CommonLib/LdapResult.cs @@ -14,6 +14,10 @@ protected LdapResult(T value, bool success, string error, string queryInfo, int public new static LdapResult Ok(T value) { return new LdapResult(value, true, string.Empty, null, 0); } + + public new static LdapResult Fail() { + return new LdapResult(default, false, string.Empty, null, 0); + } public static LdapResult Fail(string message, LdapQueryParameters queryInfo) { return new LdapResult(default, false, message, queryInfo.GetQueryInfo(), 0); diff --git a/src/CommonLib/LdapUtils.cs b/src/CommonLib/LdapUtils.cs index 23d0e296..0f235c1c 100644 --- a/src/CommonLib/LdapUtils.cs +++ b/src/CommonLib/LdapUtils.cs @@ -77,20 +77,14 @@ public LdapUtils() { _nativeMethods = new NativeMethods(); _portScanner = new PortScanner(); _log = Logging.LogProvider.CreateLogger("LDAPUtils"); - _connectionPool = new ConnectionPoolManager(_ldapConfig); + _connectionPool = new ConnectionPoolManager(_ldapConfig, _log); } public LdapUtils(NativeMethods nativeMethods = null, PortScanner scanner = null, ILogger log = null) { _nativeMethods = nativeMethods ?? new NativeMethods(); _portScanner = scanner ?? new PortScanner(); _log = log ?? Logging.LogProvider.CreateLogger("LDAPUtils"); - _connectionPool = new ConnectionPoolManager(_ldapConfig, scanner: _portScanner); - } - - public void SetLDAPConfig(LDAPConfig config) { - _ldapConfig = config; - _connectionPool.Dispose(); - _connectionPool = new ConnectionPoolManager(_ldapConfig, scanner: _portScanner); + _connectionPool = new ConnectionPoolManager(_ldapConfig, scanner:_portScanner); } public async IAsyncEnumerable> RangedRetrieval(string distinguishedName, @@ -316,7 +310,7 @@ await _connectionPool.GetLdapConnection(queryParameters.DomainName, } catch (Exception e) { tempResult = - LdapResult.Fail($"PagedQuery - Caught unrecoverable exception: {e.Message}", + LdapResult.Fail($"Query - Caught unrecoverable exception: {e.Message}", queryParameters); } @@ -337,12 +331,11 @@ await _connectionPool.GetLdapConnection(queryParameters.DomainName, break; } } - + + _connectionPool.ReleaseConnection(connectionWrapper); foreach (SearchResultEntry entry in response.Entries) { yield return LdapResult.Ok(new SearchResultEntryWrapper(entry, this)); } - - _connectionPool.ReleaseConnection(connectionWrapper); } public async IAsyncEnumerable> PagedQuery(LdapQueryParameters queryParameters, @@ -469,13 +462,13 @@ public async IAsyncEnumerable> PagedQuery(LdapQue continue; } - foreach (ISearchResultEntry entry in response.Entries) { + foreach (SearchResultEntry entry in response.Entries) { if (cancellationToken.IsCancellationRequested) { _connectionPool.ReleaseConnection(connectionWrapper); yield break; } - yield return LdapResult.Ok(entry); + yield return LdapResult.Ok(new SearchResultEntryWrapper(entry, this)); } if (pageResponse.Cookie.Length == 0 || response.Entries.Count == 0 || @@ -527,7 +520,7 @@ public async IAsyncEnumerable> PagedQuery(LdapQue DomainName = tempDomain, LDAPFilter = CommonFilters.SpecificSID(sid), Attributes = CommonProperties.TypeResolutionProps - }).FirstAsync(); + }).DefaultIfEmpty(LdapResult.Fail()).FirstOrDefaultAsync(); if (result.IsSuccess) { type = result.Value.GetLabel(); @@ -574,7 +567,7 @@ public async IAsyncEnumerable> PagedQuery(LdapQue DomainName = domain, LDAPFilter = CommonFilters.SpecificGUID(guid), Attributes = CommonProperties.TypeResolutionProps - }).FirstAsync(); + }).DefaultIfEmpty(LdapResult.Fail()).FirstOrDefaultAsync(); if (result.IsSuccess) { type = result.Value.GetLabel(); @@ -868,7 +861,7 @@ private SearchRequest CreateSearchRequest(string distinguishedName, string ldapF Attributes = new[] { LDAPProperties.DistinguishedName }, GlobalCatalog = true, LDAPFilter = new LDAPFilter().AddDomains(CommonFilters.SpecificSID(domainSid)).GetFilter() - }).FirstAsync(); + }).DefaultIfEmpty(LdapResult.Fail()).FirstOrDefaultAsync(); if (result.IsSuccess) { return (true, Helpers.DistinguishedNameToDomain(result.Value.DistinguishedName)); @@ -880,7 +873,7 @@ private SearchRequest CreateSearchRequest(string distinguishedName, string ldapF GlobalCatalog = true, LDAPFilter = new LDAPFilter().AddFilter("(objectclass=trusteddomain)", true) .AddFilter($"(securityidentifier={Helpers.ConvertSidToHexSid(domainSid)})", true).GetFilter() - }).FirstAsync(); + }).DefaultIfEmpty(LdapResult.Fail()).FirstOrDefaultAsync(); if (result.IsSuccess) { return (true, Helpers.DistinguishedNameToDomain(result.Value.DistinguishedName)); @@ -891,7 +884,7 @@ private SearchRequest CreateSearchRequest(string distinguishedName, string ldapF Attributes = new[] { LDAPProperties.DistinguishedName }, LDAPFilter = new LDAPFilter().AddFilter("(objectclass=domaindns)", true) .AddFilter(CommonFilters.SpecificSID(domainSid), true).GetFilter() - }).FirstAsync(); + }).DefaultIfEmpty(LdapResult.Fail()).FirstOrDefaultAsync(); if (result.IsSuccess) { return (true, Helpers.DistinguishedNameToDomain(result.Value.DistinguishedName)); @@ -946,7 +939,7 @@ private SearchRequest CreateSearchRequest(string distinguishedName, string ldapF DomainName = domainName, Attributes = new[] { LDAPProperties.ObjectSID }, LDAPFilter = new LDAPFilter().AddFilter(CommonFilters.DomainControllers, true).GetFilter() - }).FirstAsync(); + }).DefaultIfEmpty(LdapResult.Fail()).FirstOrDefaultAsync(); if (result.IsSuccess) { var sid = result.Value.GetSid(); @@ -1064,7 +1057,7 @@ public bool GetDomain(out Domain domain) { DomainName = domain, Attributes = CommonProperties.TypeResolutionProps, LDAPFilter = $"(samaccountname={name})" - }).FirstAsync(); + }).DefaultIfEmpty(LdapResult.Fail()).FirstOrDefaultAsync(); if (result.IsSuccess) { type = result.Value.GetLabel(); @@ -1227,13 +1220,7 @@ public bool GetDomain(out Domain domain) { NamingContext = NamingContext.Configuration, RelativeSearchBase = DirectoryPaths.CertTemplateLocation, LDAPFilter = filter.GetFilter(), - }).DefaultIfEmpty(null).FirstAsync(); - - if (result == null) { - _log.LogWarning("Could not find certificate template with {PropertyName}:{PropertyValue}", - propertyName, propertyName); - return (false, null); - } + }).DefaultIfEmpty(LdapResult.Fail()).FirstOrDefaultAsync(); if (!result.IsSuccess) { _log.LogWarning( diff --git a/src/CommonLib/Result.cs b/src/CommonLib/Result.cs index 390bb5ac..c73cfec3 100644 --- a/src/CommonLib/Result.cs +++ b/src/CommonLib/Result.cs @@ -22,7 +22,7 @@ public static Result Ok(T value) { public class Result { public string Error { get; set; } - public bool IsSuccess => Error == null && Success; + public bool IsSuccess => string.IsNullOrWhiteSpace(Error) && Success; private bool Success { get; set; } protected Result(bool success, string error) { From 05498d6adda1e051ce86165f4fb81c5da511a3c9 Mon Sep 17 00:00:00 2001 From: rvazarkar Date: Wed, 3 Jul 2024 18:26:57 -0400 Subject: [PATCH 29/68] wip: fix --- src/CommonLib/LdapUtils.cs | 11 ++++++++--- src/CommonLib/SearchResultEntryWrapper.cs | 2 +- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/CommonLib/LdapUtils.cs b/src/CommonLib/LdapUtils.cs index 0f235c1c..7676fc52 100644 --- a/src/CommonLib/LdapUtils.cs +++ b/src/CommonLib/LdapUtils.cs @@ -650,9 +650,14 @@ public async IAsyncEnumerable> PagedQuery(LdapQue } if (GetDomain(domain, out var domainObject)) { - var forestName = domainObject.Forest.Name.ToUpper(); - DomainToForestCache.TryAdd(domain, forestName); - return (true, forestName); + try { + var forestName = domainObject.Forest.Name.ToUpper(); + DomainToForestCache.TryAdd(domain, forestName); + return (true, forestName); + } + catch { + //pass + } } var (success, forest) = await GetForestFromLdap(domain); diff --git a/src/CommonLib/SearchResultEntryWrapper.cs b/src/CommonLib/SearchResultEntryWrapper.cs index b1f317cd..cc64c1fd 100644 --- a/src/CommonLib/SearchResultEntryWrapper.cs +++ b/src/CommonLib/SearchResultEntryWrapper.cs @@ -110,7 +110,7 @@ public async Task ResolveBloodHoundInfo() res.DomainSid = sid; res.DisplayName = $"{wkPrincipal.ObjectIdentifier}@{itemDomain}"; res.ObjectType = wkPrincipal.ObjectType; - if (await _utils.ConvertLocalWellKnownPrincipal(new SecurityIdentifier(objectId), res.DomainSid, itemDomain) is (true, var principal)) + if (await _utils.GetWellKnownPrincipal(objectId, itemDomain) is (true, var principal)) res.ObjectId = principal.ObjectIdentifier; _log.LogTrace("Resolved {DN} to wkp {ObjectID}", DistinguishedName, res.ObjectId); From 6353acfc1d70f8afe31e575e538a1cd639fe6ee0 Mon Sep 17 00:00:00 2001 From: rvazarkar Date: Fri, 5 Jul 2024 12:06:58 -0400 Subject: [PATCH 30/68] chore: move some code around --- src/CommonLib/DirectoryEntryExtensions.cs | 133 +++++ src/CommonLib/DomainInfo.cs | 20 - src/CommonLib/Extensions.cs | 482 +------------------ src/CommonLib/LDAPQueryParams.cs | 14 - src/CommonLib/LdapUtils.cs | 74 +++ src/CommonLib/SearchResultEntryExtensions.cs | 232 +++++++++ test/unit/Facades/MockLDAPUtils.cs | 2 +- 7 files changed, 449 insertions(+), 508 deletions(-) create mode 100644 src/CommonLib/DirectoryEntryExtensions.cs delete mode 100644 src/CommonLib/DomainInfo.cs delete mode 100644 src/CommonLib/LDAPQueryParams.cs create mode 100644 src/CommonLib/SearchResultEntryExtensions.cs diff --git a/src/CommonLib/DirectoryEntryExtensions.cs b/src/CommonLib/DirectoryEntryExtensions.cs new file mode 100644 index 00000000..e0159bcd --- /dev/null +++ b/src/CommonLib/DirectoryEntryExtensions.cs @@ -0,0 +1,133 @@ +using System; +using System.Collections.Generic; +using System.DirectoryServices; +using System.Security.Principal; +using System.Text; +using SharpHoundCommonLib.Enums; +using SharpHoundCommonLib.LDAPQueries; +using SharpHoundCommonLib.OutputTypes; + +namespace SharpHoundCommonLib; + +public static class DirectoryEntryExtensions { + public static string GetProperty(this DirectoryEntry entry, string propertyName) { + try { + if (!entry.Properties.Contains(propertyName)) + entry.RefreshCache(new[] { propertyName }); + + if (!entry.Properties.Contains(propertyName)) + return null; + } + catch { + return null; + } + + var s = entry.Properties[propertyName][0]; + return s switch + { + string st => st, + _ => null + }; + } + + public static string[] GetPropertyAsArray(this DirectoryEntry entry, string propertyName) { + try { + if (!entry.Properties.Contains(propertyName)) + entry.RefreshCache(new[] { propertyName }); + + if (!entry.Properties.Contains(propertyName)) + return null; + } + catch { + return null; + } + + var dest = new List(); + foreach (var val in entry.Properties[propertyName]) { + if (val is string s) { + dest.Add(s); + } + } + + return dest.ToArray(); + } + + public static bool GetTypedPrincipal(this DirectoryEntry entry, out TypedPrincipal principal) { + var identifier = entry.GetObjectIdentifier(); + var success = entry.GetLabel(out var label); + principal = new TypedPrincipal(identifier, label); + return (success && !string.IsNullOrWhiteSpace(identifier)); + } + + public static string GetObjectIdentifier(this DirectoryEntry entry) { + return entry.GetSid() ?? entry.GetGuid(); + } + + public static string GetSid(this DirectoryEntry entry) + { + try + { + if (!entry.Properties.Contains(LDAPProperties.ObjectSID)) + entry.RefreshCache(new[] { LDAPProperties.ObjectSID }); + + if (!entry.Properties.Contains(LDAPProperties.ObjectSID)) + return null; + } + catch + { + return null; + } + + var s = entry.Properties[LDAPProperties.ObjectSID][0]; + return s switch + { + byte[] b => new SecurityIdentifier(b, 0).ToString(), + string st => new SecurityIdentifier(Encoding.ASCII.GetBytes(st), 0).ToString(), + _ => null + }; + } + + public static string GetGuid(this DirectoryEntry entry) + { + try + { + //Attempt to refresh the props first + if (!entry.Properties.Contains(LDAPProperties.ObjectGUID)) + entry.RefreshCache(new[] { LDAPProperties.ObjectGUID }); + + if (!entry.Properties.Contains(LDAPProperties.ObjectGUID)) + return null; + } + catch + { + return null; + } + + var s = entry.Properties[LDAPProperties.ObjectGUID][0]; + return s switch + { + byte[] b => new Guid(b).ToString(), + string st => st, + _ => null + }; + } + + + public static bool GetLabel(this DirectoryEntry entry, out Label type) { + try { + entry.RefreshCache(CommonProperties.TypeResolutionProps); + } + catch { + //pass + } + + var flagString = entry.GetProperty(LDAPProperties.Flags); + if (!int.TryParse(flagString, out var flags)) { + flags = 0; + } + + return LdapUtils.ResolveLabel(entry.GetObjectIdentifier(), entry.GetProperty(LDAPProperties.DistinguishedName), + entry.GetProperty(LDAPProperties.SAMAccountType), + entry.GetPropertyAsArray(LDAPProperties.SAMAccountType), flags, out type); + } +} \ No newline at end of file diff --git a/src/CommonLib/DomainInfo.cs b/src/CommonLib/DomainInfo.cs deleted file mode 100644 index 369d912f..00000000 --- a/src/CommonLib/DomainInfo.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.DirectoryServices.Protocols; - -namespace SharpHoundCommonLib -{ - public class DomainInfo - { - public string DomainSID { get; set; } - public string DomainFQDN { get; set; } - public string DomainSearchBase { get; set; } - public string DomainConfigurationPath { get; set; } - public string DomainNetbiosName { get; set; } - - public override string ToString() - { - return $"{nameof(DomainSID)}: {DomainSID}, {nameof(DomainFQDN)}: {DomainFQDN}, {nameof(DomainSearchBase)}: {DomainSearchBase}, {nameof(DomainConfigurationPath)}: {DomainConfigurationPath}, {nameof(DomainNetbiosName)}: {DomainNetbiosName}"; - } - } -} \ No newline at end of file diff --git a/src/CommonLib/Extensions.cs b/src/CommonLib/Extensions.cs index 869ef104..4128fb58 100644 --- a/src/CommonLib/Extensions.cs +++ b/src/CommonLib/Extensions.cs @@ -1,56 +1,28 @@ using System; -using System.Collections.Generic; -using System.DirectoryServices; -using System.DirectoryServices.Protocols; using System.Linq; -using System.Security.Cryptography.X509Certificates; using System.Security.Principal; -using System.Text; -using System.Threading.Tasks; using Microsoft.Extensions.Logging; using SharpHoundCommonLib.Enums; -using SharpHoundCommonLib.LDAPQueries; -using SharpHoundCommonLib.OutputTypes; -using SearchScope = System.DirectoryServices.Protocols.SearchScope; namespace SharpHoundCommonLib { public static class Extensions { - private const string GMSAClass = "msds-groupmanagedserviceaccount"; - private const string MSAClass = "msds-managedserviceaccount"; private static readonly ILogger Log; static Extensions() { Log = Logging.LogProvider.CreateLogger("Extensions"); } - - internal static async Task> ToListAsync(this IAsyncEnumerable items) - { - var results = new List(); - await foreach (var item in items - .ConfigureAwait(false)) - results.Add(item); - return results; - } - - /// - /// Helper function to print attributes of a SearchResultEntry - /// - /// - public static string PrintEntry(this SearchResultEntry searchResultEntry) - { - var sb = new StringBuilder(); - if (searchResultEntry.Attributes.AttributeNames == null) return sb.ToString(); - foreach (var propertyName in searchResultEntry.Attributes.AttributeNames) - { - var property = propertyName.ToString(); - sb.Append(property).Append("\t").Append(searchResultEntry.GetProperty(property)).Append("\n"); - } - - return sb.ToString(); - } + + // internal static async Task> ToListAsync(this IAsyncEnumerable items) + // { + // var results = new List(); + // await foreach (var item in items + // .ConfigureAwait(false)) + // results.Add(item); + // return results; + // } public static string LdapValue(this SecurityIdentifier s) { @@ -67,109 +39,7 @@ public static string LdapValue(this Guid s) var output = $"\\{BitConverter.ToString(bytes).Replace('-', '\\')}"; return output; } - - public static string GetProperty(this DirectoryEntry entry, string propertyName) { - try { - if (!entry.Properties.Contains(propertyName)) - entry.RefreshCache(new[] { propertyName }); - - if (!entry.Properties.Contains(propertyName)) - return null; - } - catch { - return null; - } - - var s = entry.Properties[propertyName][0]; - return s switch - { - string st => st, - _ => null - }; - } - - public static string[] GetPropertyAsArray(this DirectoryEntry entry, string propertyName) { - try { - if (!entry.Properties.Contains(propertyName)) - entry.RefreshCache(new[] { propertyName }); - - if (!entry.Properties.Contains(propertyName)) - return null; - } - catch { - return null; - } - - var dest = new List(); - foreach (var val in entry.Properties[propertyName]) { - if (val is string s) { - dest.Add(s); - } - } - - return dest.ToArray(); - } - - public static bool GetTypedPrincipal(this DirectoryEntry entry, out TypedPrincipal principal) { - var identifier = entry.GetObjectIdentifier(); - var success = entry.GetLabel(out var label); - principal = new TypedPrincipal(identifier, label); - return (success && !string.IsNullOrWhiteSpace(identifier)); - } - - public static string GetObjectIdentifier(this DirectoryEntry entry) { - return entry.GetSid() ?? entry.GetGuid(); - } - - public static string GetSid(this DirectoryEntry entry) - { - try - { - if (!entry.Properties.Contains(LDAPProperties.ObjectSID)) - entry.RefreshCache(new[] { LDAPProperties.ObjectSID }); - - if (!entry.Properties.Contains(LDAPProperties.ObjectSID)) - return null; - } - catch - { - return null; - } - - var s = entry.Properties[LDAPProperties.ObjectSID][0]; - return s switch - { - byte[] b => new SecurityIdentifier(b, 0).ToString(), - string st => new SecurityIdentifier(Encoding.ASCII.GetBytes(st), 0).ToString(), - _ => null - }; - } - public static string GetGuid(this DirectoryEntry entry) - { - try - { - //Attempt to refresh the props first - if (!entry.Properties.Contains(LDAPProperties.ObjectGUID)) - entry.RefreshCache(new[] { LDAPProperties.ObjectGUID }); - - if (!entry.Properties.Contains(LDAPProperties.ObjectGUID)) - return null; - } - catch - { - return null; - } - - var s = entry.Properties[LDAPProperties.ObjectGUID][0]; - return s switch - { - byte[] b => new Guid(b).ToString(), - string st => st, - _ => null - }; - } - /// /// Returns true if any computer collection methods are set /// @@ -201,339 +71,5 @@ public static int Rid(this SecurityIdentifier securityIdentifier) var rid = int.Parse(value.Substring(value.LastIndexOf("-", StringComparison.Ordinal) + 1)); return rid; } - - public static bool GetNamingContextSearchBase(this LdapConnection connection, NamingContext context, - out string searchBase) - { - var searchRequest = - new SearchRequest("", new LDAPFilter().AddAllObjects().GetFilter(), SearchScope.Base, null); - searchRequest.Controls.Add(new SearchOptionsControl(SearchOption.DomainScope)); - SearchResponse response; - try - { - response = (SearchResponse)connection.SendRequest(searchRequest); - } - catch - { - searchBase = ""; - return false; - } - - if (response?.Entries == null || response.Entries.Count == 0) - { - searchBase = ""; - return false; - } - - var entry = response.Entries[0]; - searchBase = context switch - { - NamingContext.Default => entry.GetProperty(LDAPProperties.DefaultNamingContext), - NamingContext.Configuration => entry.GetProperty(LDAPProperties.ConfigurationNamingContext), - NamingContext.Schema => entry.GetProperty(LDAPProperties.SchemaNamingContext), - _ => throw new ArgumentOutOfRangeException(nameof(context), context, null) - }; - - searchBase = searchBase?.Trim().ToUpper(); - return searchBase != null; - } - - #region SearchResultEntry - - /// - /// Gets the specified property as a string from the SearchResultEntry - /// - /// - /// The LDAP name of the property you want to get - /// The string value of the property if it exists or null - public static string GetProperty(this SearchResultEntry entry, string property) - { - if (!entry.Attributes.Contains(property)) - return null; - - var collection = entry.Attributes[property]; - //Use GetValues to auto-convert to the proper type - var lookups = collection.GetValues(typeof(string)); - if (lookups.Length == 0) - return null; - - if (lookups[0] is not string prop || prop.Length == 0) - return null; - - return prop; - } - - /// - /// Get's the string representation of the "objectguid" property from the SearchResultEntry - /// - /// - /// The string representation of the object's GUID if possible, otherwise null - public static string GetGuid(this SearchResultEntry entry) - { - if (entry.Attributes.Contains(LDAPProperties.ObjectGUID)) - { - var guidBytes = entry.GetPropertyAsBytes(LDAPProperties.ObjectGUID); - - return new Guid(guidBytes).ToString().ToUpper(); - } - - return null; - } - - /// - /// Gets the "objectsid" property as a string from the SearchResultEntry - /// - /// - /// The string representation of the object's SID if possible, otherwise null - public static string GetSid(this SearchResultEntry entry) - { - if (!entry.Attributes.Contains(LDAPProperties.ObjectSID)) return null; - - object[] s; - try - { - s = entry.Attributes[LDAPProperties.ObjectSID].GetValues(typeof(byte[])); - } - catch (NotSupportedException) - { - return null; - } - - if (s.Length == 0) - return null; - - if (s[0] is not byte[] sidBytes || sidBytes.Length == 0) - return null; - - try - { - var sid = new SecurityIdentifier(sidBytes, 0); - return sid.Value.ToUpper(); - } - catch (ArgumentNullException) - { - return null; - } - } - - /// - /// Gets the specified property as a string array from the SearchResultEntry - /// - /// - /// The LDAP name of the property you want to get - /// The specified property as an array of strings if possible, else an empty array - public static string[] GetPropertyAsArray(this SearchResultEntry entry, string property) - { - if (!entry.Attributes.Contains(property)) - return Array.Empty(); - - var values = entry.Attributes[property]; - var strings = values.GetValues(typeof(string)); - - return strings is not string[] result ? Array.Empty() : result; - } - - /// - /// Gets the specified property as an array of byte arrays from the SearchResultEntry - /// Used for SIDHistory - /// - /// - /// The LDAP name of the property you want to get - /// The specified property as an array of bytes if possible, else an empty array - public static byte[][] GetPropertyAsArrayOfBytes(this SearchResultEntry entry, string property) - { - if (!entry.Attributes.Contains(property)) - return Array.Empty(); - - var values = entry.Attributes[property]; - var bytes = values.GetValues(typeof(byte[])); - - return bytes is not byte[][] result ? Array.Empty() : result; - } - - /// - /// Gets the specified property as a byte array - /// - /// - /// The LDAP name of the property you want to get - /// An array of bytes if possible, else null - public static byte[] GetPropertyAsBytes(this SearchResultEntry searchResultEntry, string property) - { - if (!searchResultEntry.Attributes.Contains(property)) - return null; - - var collection = searchResultEntry.Attributes[property]; - var lookups = collection.GetValues(typeof(byte[])); - - if (lookups.Length == 0) - return Array.Empty(); - - if (lookups[0] is not byte[] bytes || bytes.Length == 0) - return Array.Empty(); - - return bytes; - } - - /// - /// Gets the specified property as an int - /// - /// - /// - /// - /// - public static bool GetPropertyAsInt(this SearchResultEntry entry, string property, out int value) - { - var prop = entry.GetProperty(property); - if (prop != null) return int.TryParse(prop, out value); - value = 0; - return false; - } - - /// - /// Gets the specified property as an array of X509 certificates. - /// - /// - /// - /// - public static X509Certificate2[] GetPropertyAsArrayOfCertificates(this SearchResultEntry searchResultEntry, - string property) - { - if (!searchResultEntry.Attributes.Contains(property)) - return null; - - return searchResultEntry.GetPropertyAsArrayOfBytes(property).Select(x => new X509Certificate2(x)).ToArray(); - } - - - /// - /// Attempts to get the unique object identifier as used by BloodHound for the Search Result Entry. Tries to get - /// objectsid first, and then objectguid next. - /// - /// - /// String representation of the entry's object identifier or null - public static string GetObjectIdentifier(this SearchResultEntry entry) - { - return entry.GetSid() ?? entry.GetGuid(); - } - - /// - /// Checks the isDeleted LDAP property to determine if an entry has been deleted from the directory - /// - /// - /// - public static bool IsDeleted(this SearchResultEntry entry) - { - var deleted = entry.GetProperty(LDAPProperties.IsDeleted); - return bool.TryParse(deleted, out var isDeleted) && isDeleted; - } - - public static bool GetLabel(this DirectoryEntry entry, out Label type) { - try { - entry.RefreshCache(CommonProperties.TypeResolutionProps); - } - catch { - //pass - } - - var flagString = entry.GetProperty(LDAPProperties.Flags); - if (!int.TryParse(flagString, out var flags)) { - flags = 0; - } - - return ResolveLabel(entry.GetObjectIdentifier(), entry.GetProperty(LDAPProperties.DistinguishedName), - entry.GetProperty(LDAPProperties.SAMAccountType), - entry.GetPropertyAsArray(LDAPProperties.SAMAccountType), flags, out type); - } - - private static bool ResolveLabel(string objectIdentifier, string distinguishedName, string samAccountType, string[] objectClasses, int flags, out Label type) { - type = Label.Base; - if (objectIdentifier != null && WellKnownPrincipal.GetWellKnownPrincipal(objectIdentifier, out var principal)) { - type = principal.ObjectType; - return true; - } - - //Override GMSA/MSA account to treat them as users for the graph - if (objectClasses != null && (objectClasses.Contains(MSAClass, StringComparer.OrdinalIgnoreCase) || - objectClasses.Contains(GMSAClass, StringComparer.OrdinalIgnoreCase))) - { - type = Label.User; - return true; - } - - if (samAccountType != null) { - var objectType = Helpers.SamAccountTypeToType(samAccountType); - if (objectType != Label.Base) { - type = objectType; - return true; - } - } - - if (objectClasses == null) { - type = Label.Base; - return false; - } - - if (objectClasses.Contains(GroupPolicyContainerClass, StringComparer.InvariantCultureIgnoreCase)) - type = Label.GPO; - else if (objectClasses.Contains(OrganizationalUnitClass, StringComparer.InvariantCultureIgnoreCase)) - type = Label.OU; - else if (objectClasses.Contains(DomainClass, StringComparer.InvariantCultureIgnoreCase)) - type = Label.Domain; - else if (objectClasses.Contains(ContainerClass, StringComparer.InvariantCultureIgnoreCase)) - type = Label.Container; - else if (objectClasses.Contains(ConfigurationClass, StringComparer.InvariantCultureIgnoreCase)) - type = Label.Configuration; - else if (objectClasses.Contains(PKICertificateTemplateClass, StringComparer.InvariantCultureIgnoreCase)) - type = Label.CertTemplate; - else if (objectClasses.Contains(PKIEnrollmentServiceClass, StringComparer.InvariantCultureIgnoreCase)) - type = Label.EnterpriseCA; - else if (objectClasses.Contains(CertificationAuthorityClass, StringComparer.InvariantCultureIgnoreCase)) { - if (distinguishedName.Contains(DirectoryPaths.RootCALocation)) - type = Label.RootCA; - if (distinguishedName.Contains(DirectoryPaths.AIACALocation)) - type = Label.AIACA; - if (distinguishedName.Contains(DirectoryPaths.NTAuthStoreLocation)) - type = Label.NTAuthStore; - }else if (objectClasses.Contains(OIDContainerClass, StringComparer.InvariantCultureIgnoreCase)) { - if (distinguishedName.StartsWith(DirectoryPaths.OIDContainerLocation, - StringComparison.InvariantCultureIgnoreCase)) - type = Label.Container; - if (flags == 2) - { - type = Label.IssuancePolicy; - } - } - - return type != Label.Base; - } - - /// - /// Extension method to determine the BloodHound type of a SearchResultEntry using LDAP properties - /// Requires ldap properties objectsid, samaccounttype, objectclass - /// - /// - /// - public static bool GetLabel(this SearchResultEntry entry, out Label type) - { - if (!entry.GetPropertyAsInt(LDAPProperties.Flags, out var flags)) { - flags = 0; - } - - return ResolveLabel(entry.GetObjectIdentifier(), entry.DistinguishedName, - entry.GetProperty(LDAPProperties.SAMAccountType), entry.GetPropertyAsArray(LDAPProperties.ObjectClass), - flags, out type); - } - - private const string GroupPolicyContainerClass = "groupPolicyContainer"; - private const string OrganizationalUnitClass = "organizationalUnit"; - private const string DomainClass = "domain"; - private const string ContainerClass = "container"; - private const string ConfigurationClass = "configuration"; - private const string PKICertificateTemplateClass = "pKICertificateTemplate"; - private const string PKIEnrollmentServiceClass = "pKIEnrollmentService"; - private const string CertificationAuthorityClass = "certificationAuthority"; - private const string OIDContainerClass = "msPKI-Enterprise-Oid"; - - #endregion } } \ No newline at end of file diff --git a/src/CommonLib/LDAPQueryParams.cs b/src/CommonLib/LDAPQueryParams.cs deleted file mode 100644 index 42d0587e..00000000 --- a/src/CommonLib/LDAPQueryParams.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.DirectoryServices.Protocols; -using SharpHoundCommonLib.Exceptions; - -namespace SharpHoundCommonLib -{ - internal class LDAPQueryParams - { - public LdapConnection Connection { get; set; } - public SearchRequest SearchRequest { get; set; } - public PageResultRequestControl PageControl { get; set; } - public LDAPQueryException Exception { get; set; } - } -} - diff --git a/src/CommonLib/LdapUtils.cs b/src/CommonLib/LdapUtils.cs index 7676fc52..c8461f61 100644 --- a/src/CommonLib/LdapUtils.cs +++ b/src/CommonLib/LdapUtils.cs @@ -1504,5 +1504,79 @@ private DirectoryEntry CreateDirectoryEntry(string path) { public void Dispose() { _connectionPool?.Dispose(); } + + internal static bool ResolveLabel(string objectIdentifier, string distinguishedName, string samAccountType, string[] objectClasses, int flags, out Label type) { + type = Label.Base; + if (objectIdentifier != null && WellKnownPrincipal.GetWellKnownPrincipal(objectIdentifier, out var principal)) { + type = principal.ObjectType; + return true; + } + + //Override GMSA/MSA account to treat them as users for the graph + if (objectClasses != null && (objectClasses.Contains(MSAClass, StringComparer.OrdinalIgnoreCase) || + objectClasses.Contains(GMSAClass, StringComparer.OrdinalIgnoreCase))) + { + type = Label.User; + return true; + } + + if (samAccountType != null) { + var objectType = Helpers.SamAccountTypeToType(samAccountType); + if (objectType != Label.Base) { + type = objectType; + return true; + } + } + + if (objectClasses == null) { + type = Label.Base; + return false; + } + + if (objectClasses.Contains(GroupPolicyContainerClass, StringComparer.InvariantCultureIgnoreCase)) + type = Label.GPO; + else if (objectClasses.Contains(OrganizationalUnitClass, StringComparer.InvariantCultureIgnoreCase)) + type = Label.OU; + else if (objectClasses.Contains(DomainClass, StringComparer.InvariantCultureIgnoreCase)) + type = Label.Domain; + else if (objectClasses.Contains(ContainerClass, StringComparer.InvariantCultureIgnoreCase)) + type = Label.Container; + else if (objectClasses.Contains(ConfigurationClass, StringComparer.InvariantCultureIgnoreCase)) + type = Label.Configuration; + else if (objectClasses.Contains(PKICertificateTemplateClass, StringComparer.InvariantCultureIgnoreCase)) + type = Label.CertTemplate; + else if (objectClasses.Contains(PKIEnrollmentServiceClass, StringComparer.InvariantCultureIgnoreCase)) + type = Label.EnterpriseCA; + else if (objectClasses.Contains(CertificationAuthorityClass, StringComparer.InvariantCultureIgnoreCase)) { + if (distinguishedName.Contains(DirectoryPaths.RootCALocation)) + type = Label.RootCA; + if (distinguishedName.Contains(DirectoryPaths.AIACALocation)) + type = Label.AIACA; + if (distinguishedName.Contains(DirectoryPaths.NTAuthStoreLocation)) + type = Label.NTAuthStore; + }else if (objectClasses.Contains(OIDContainerClass, StringComparer.InvariantCultureIgnoreCase)) { + if (distinguishedName.StartsWith(DirectoryPaths.OIDContainerLocation, + StringComparison.InvariantCultureIgnoreCase)) + type = Label.Container; + if (flags == 2) + { + type = Label.IssuancePolicy; + } + } + + return type != Label.Base; + } + + private const string GroupPolicyContainerClass = "groupPolicyContainer"; + private const string OrganizationalUnitClass = "organizationalUnit"; + private const string DomainClass = "domain"; + private const string ContainerClass = "container"; + private const string ConfigurationClass = "configuration"; + private const string PKICertificateTemplateClass = "pKICertificateTemplate"; + private const string PKIEnrollmentServiceClass = "pKIEnrollmentService"; + private const string CertificationAuthorityClass = "certificationAuthority"; + private const string OIDContainerClass = "msPKI-Enterprise-Oid"; + private const string GMSAClass = "msds-groupmanagedserviceaccount"; + private const string MSAClass = "msds-managedserviceaccount"; } } \ No newline at end of file diff --git a/src/CommonLib/SearchResultEntryExtensions.cs b/src/CommonLib/SearchResultEntryExtensions.cs new file mode 100644 index 00000000..bebb3dc5 --- /dev/null +++ b/src/CommonLib/SearchResultEntryExtensions.cs @@ -0,0 +1,232 @@ +using System; +using System.DirectoryServices.Protocols; +using System.Linq; +using System.Security.Cryptography.X509Certificates; +using System.Security.Principal; +using System.Text; +using SharpHoundCommonLib.Enums; + +namespace SharpHoundCommonLib; + +public static class SearchResultEntryExtensions { + /// + /// Extension method to determine the BloodHound type of a SearchResultEntry using LDAP properties + /// Requires ldap properties objectsid, samaccounttype, objectclass + /// + /// + /// + public static bool GetLabel(this SearchResultEntry entry, out Label type) + { + if (!entry.GetPropertyAsInt(LDAPProperties.Flags, out var flags)) { + flags = 0; + } + + return LdapUtils.ResolveLabel(entry.GetObjectIdentifier(), entry.DistinguishedName, + entry.GetProperty(LDAPProperties.SAMAccountType), entry.GetPropertyAsArray(LDAPProperties.ObjectClass), + flags, out type); + } + + /// + /// Gets the specified property as a string from the SearchResultEntry + /// + /// + /// The LDAP name of the property you want to get + /// The string value of the property if it exists or null + public static string GetProperty(this SearchResultEntry entry, string property) + { + if (!entry.Attributes.Contains(property)) + return null; + + var collection = entry.Attributes[property]; + //Use GetValues to auto-convert to the proper type + var lookups = collection.GetValues(typeof(string)); + if (lookups.Length == 0) + return null; + + if (lookups[0] is not string prop || prop.Length == 0) + return null; + + return prop; + } + + /// + /// Get's the string representation of the "objectguid" property from the SearchResultEntry + /// + /// + /// The string representation of the object's GUID if possible, otherwise null + public static string GetGuid(this SearchResultEntry entry) + { + if (entry.Attributes.Contains(LDAPProperties.ObjectGUID)) + { + var guidBytes = entry.GetPropertyAsBytes(LDAPProperties.ObjectGUID); + + return new Guid(guidBytes).ToString().ToUpper(); + } + + return null; + } + + /// + /// Gets the "objectsid" property as a string from the SearchResultEntry + /// + /// + /// The string representation of the object's SID if possible, otherwise null + public static string GetSid(this SearchResultEntry entry) + { + if (!entry.Attributes.Contains(LDAPProperties.ObjectSID)) return null; + + object[] s; + try + { + s = entry.Attributes[LDAPProperties.ObjectSID].GetValues(typeof(byte[])); + } + catch (NotSupportedException) + { + return null; + } + + if (s.Length == 0) + return null; + + if (s[0] is not byte[] sidBytes || sidBytes.Length == 0) + return null; + + try + { + var sid = new SecurityIdentifier(sidBytes, 0); + return sid.Value.ToUpper(); + } + catch (ArgumentNullException) + { + return null; + } + } + + /// + /// Gets the specified property as a string array from the SearchResultEntry + /// + /// + /// The LDAP name of the property you want to get + /// The specified property as an array of strings if possible, else an empty array + public static string[] GetPropertyAsArray(this SearchResultEntry entry, string property) + { + if (!entry.Attributes.Contains(property)) + return Array.Empty(); + + var values = entry.Attributes[property]; + var strings = values.GetValues(typeof(string)); + + return strings is not string[] result ? Array.Empty() : result; + } + + /// + /// Gets the specified property as an array of byte arrays from the SearchResultEntry + /// Used for SIDHistory + /// + /// + /// The LDAP name of the property you want to get + /// The specified property as an array of bytes if possible, else an empty array + public static byte[][] GetPropertyAsArrayOfBytes(this SearchResultEntry entry, string property) + { + if (!entry.Attributes.Contains(property)) + return Array.Empty(); + + var values = entry.Attributes[property]; + var bytes = values.GetValues(typeof(byte[])); + + return bytes is not byte[][] result ? Array.Empty() : result; + } + + /// + /// Gets the specified property as a byte array + /// + /// + /// The LDAP name of the property you want to get + /// An array of bytes if possible, else null + public static byte[] GetPropertyAsBytes(this SearchResultEntry searchResultEntry, string property) + { + if (!searchResultEntry.Attributes.Contains(property)) + return null; + + var collection = searchResultEntry.Attributes[property]; + var lookups = collection.GetValues(typeof(byte[])); + + if (lookups.Length == 0) + return Array.Empty(); + + if (lookups[0] is not byte[] bytes || bytes.Length == 0) + return Array.Empty(); + + return bytes; + } + + /// + /// Gets the specified property as an int + /// + /// + /// + /// + /// + public static bool GetPropertyAsInt(this SearchResultEntry entry, string property, out int value) + { + var prop = entry.GetProperty(property); + if (prop != null) return int.TryParse(prop, out value); + value = 0; + return false; + } + + /// + /// Gets the specified property as an array of X509 certificates. + /// + /// + /// + /// + public static X509Certificate2[] GetPropertyAsArrayOfCertificates(this SearchResultEntry searchResultEntry, + string property) + { + if (!searchResultEntry.Attributes.Contains(property)) + return null; + + return searchResultEntry.GetPropertyAsArrayOfBytes(property).Select(x => new X509Certificate2(x)).ToArray(); + } + + + /// + /// Attempts to get the unique object identifier as used by BloodHound for the Search Result Entry. Tries to get + /// objectsid first, and then objectguid next. + /// + /// + /// String representation of the entry's object identifier or null + public static string GetObjectIdentifier(this SearchResultEntry entry) + { + return entry.GetSid() ?? entry.GetGuid(); + } + + /// + /// Checks the isDeleted LDAP property to determine if an entry has been deleted from the directory + /// + /// + /// + public static bool IsDeleted(this SearchResultEntry entry) + { + var deleted = entry.GetProperty(LDAPProperties.IsDeleted); + return bool.TryParse(deleted, out var isDeleted) && isDeleted; + } + + /// + /// Helper function to print attributes of a SearchResultEntry + /// + /// + public static string PrintEntry(this SearchResultEntry searchResultEntry) + { + var sb = new StringBuilder(); + if (searchResultEntry.Attributes.AttributeNames == null) return sb.ToString(); + foreach (var propertyName in searchResultEntry.Attributes.AttributeNames) + { + var property = propertyName.ToString(); + sb.Append(property).Append("\t").Append(searchResultEntry.GetProperty(property)).Append("\n"); + } + + return sb.ToString(); + } +} \ No newline at end of file diff --git a/test/unit/Facades/MockLDAPUtils.cs b/test/unit/Facades/MockLDAPUtils.cs index f03fea3f..5bf03336 100644 --- a/test/unit/Facades/MockLDAPUtils.cs +++ b/test/unit/Facades/MockLDAPUtils.cs @@ -17,7 +17,7 @@ namespace CommonLibTest.Facades { - public class MockLDAPUtils : ILDAPUtils + public class MockLDAPUtils : ILdapUtils { private readonly ConcurrentDictionary _domainControllers = new(); private readonly Forest _forest; From 020af28c96af2d78f205f5d841c19cf4bd15216f Mon Sep 17 00:00:00 2001 From: rvazarkar Date: Fri, 5 Jul 2024 12:20:05 -0400 Subject: [PATCH 31/68] chore: more moving code --- src/CommonLib/{ => Enums}/EdgeNames.cs | 2 +- src/CommonLib/{ => Enums}/LDAPProperties.cs | 0 src/CommonLib/{ => Logging}/Logging.cs | 0 src/CommonLib/{ => Logging}/NoOpLogger.cs | 0 src/CommonLib/{ => Logging}/PassThroughLogger.cs | 0 src/CommonLib/Processors/SPNProcessors.cs | 1 + test/unit/SPNProcessorsTest.cs | 1 + 7 files changed, 3 insertions(+), 1 deletion(-) rename src/CommonLib/{ => Enums}/EdgeNames.cs (97%) rename src/CommonLib/{ => Enums}/LDAPProperties.cs (100%) rename src/CommonLib/{ => Logging}/Logging.cs (100%) rename src/CommonLib/{ => Logging}/NoOpLogger.cs (100%) rename src/CommonLib/{ => Logging}/PassThroughLogger.cs (100%) diff --git a/src/CommonLib/EdgeNames.cs b/src/CommonLib/Enums/EdgeNames.cs similarity index 97% rename from src/CommonLib/EdgeNames.cs rename to src/CommonLib/Enums/EdgeNames.cs index 276d5b00..b7e3b5a8 100644 --- a/src/CommonLib/EdgeNames.cs +++ b/src/CommonLib/Enums/EdgeNames.cs @@ -1,4 +1,4 @@ -namespace SharpHoundCommonLib +namespace SharpHoundCommonLib.Enums { public static class EdgeNames { diff --git a/src/CommonLib/LDAPProperties.cs b/src/CommonLib/Enums/LDAPProperties.cs similarity index 100% rename from src/CommonLib/LDAPProperties.cs rename to src/CommonLib/Enums/LDAPProperties.cs diff --git a/src/CommonLib/Logging.cs b/src/CommonLib/Logging/Logging.cs similarity index 100% rename from src/CommonLib/Logging.cs rename to src/CommonLib/Logging/Logging.cs diff --git a/src/CommonLib/NoOpLogger.cs b/src/CommonLib/Logging/NoOpLogger.cs similarity index 100% rename from src/CommonLib/NoOpLogger.cs rename to src/CommonLib/Logging/NoOpLogger.cs diff --git a/src/CommonLib/PassThroughLogger.cs b/src/CommonLib/Logging/PassThroughLogger.cs similarity index 100% rename from src/CommonLib/PassThroughLogger.cs rename to src/CommonLib/Logging/PassThroughLogger.cs diff --git a/src/CommonLib/Processors/SPNProcessors.cs b/src/CommonLib/Processors/SPNProcessors.cs index 3ba2ba65..13af5a81 100644 --- a/src/CommonLib/Processors/SPNProcessors.cs +++ b/src/CommonLib/Processors/SPNProcessors.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using Microsoft.Extensions.Logging; +using SharpHoundCommonLib.Enums; using SharpHoundCommonLib.OutputTypes; namespace SharpHoundCommonLib.Processors diff --git a/test/unit/SPNProcessorsTest.cs b/test/unit/SPNProcessorsTest.cs index 0ba7411b..fa83f0e3 100644 --- a/test/unit/SPNProcessorsTest.cs +++ b/test/unit/SPNProcessorsTest.cs @@ -2,6 +2,7 @@ using System.Threading.Tasks; using CommonLibTest.Facades; using SharpHoundCommonLib; +using SharpHoundCommonLib.Enums; using SharpHoundCommonLib.OutputTypes; using SharpHoundCommonLib.Processors; using Xunit; From 4bb93134af33807ca6f9f4785457c153e7de3b3a Mon Sep 17 00:00:00 2001 From: anemeth Date: Fri, 5 Jul 2024 09:35:07 -0700 Subject: [PATCH 32/68] Don't let TestLdapConnection bomb out on exception --- src/CommonLib/LdapUtils.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/CommonLib/LdapUtils.cs b/src/CommonLib/LdapUtils.cs index c8461f61..acb6a1ec 100644 --- a/src/CommonLib/LdapUtils.cs +++ b/src/CommonLib/LdapUtils.cs @@ -1433,7 +1433,11 @@ public void SetLdapConfig(LDAPConfig config) { } public Task<(bool Success, string Message)> TestLdapConnection(string domain) { - return _connectionPool.TestDomainConnection(domain, false); + try { + return _connectionPool.TestDomainConnection(domain, false); + } catch (Exception e) { + return (false, e.Message); + } } public async Task<(bool Success, string Path)> GetNamingContextPath(string domain, NamingContext context) { From 9e4434b5be82cbd53a6ea15dd9a58fa6665783f9 Mon Sep 17 00:00:00 2001 From: anemeth Date: Fri, 5 Jul 2024 10:42:19 -0700 Subject: [PATCH 33/68] Give up on a domain if NoLdapDataException thrown (or any exception really on CreateLdapConnection) --- src/CommonLib/LdapConnectionPool.cs | 128 ++++++++++++++-------------- 1 file changed, 66 insertions(+), 62 deletions(-) diff --git a/src/CommonLib/LdapConnectionPool.cs b/src/CommonLib/LdapConnectionPool.cs index a555cf57..b21d38cf 100644 --- a/src/CommonLib/LdapConnectionPool.cs +++ b/src/CommonLib/LdapConnectionPool.cs @@ -103,80 +103,84 @@ public void Dispose() { } private async Task<(bool Success, LdapConnectionWrapper Connection, string Message)> CreateNewConnection(bool globalCatalog = false) { - if (!string.IsNullOrWhiteSpace(_ldapConfig.Server)) { - return CreateNewConnectionForServer(_ldapConfig.Server, globalCatalog); - } - - if (CreateLdapConnection(_identifier.ToUpper().Trim(), globalCatalog, out var connectionWrapper)) { - _log.LogDebug("Successfully created ldap connection for domain: {Domain} using strategy 1. SSL: {SSl}", _identifier, connectionWrapper.Connection.SessionOptions.SecureSocketLayer); - return (true, connectionWrapper, ""); - } - - string tempDomainName; - - var dsGetDcNameResult = _nativeMethods.CallDsGetDcName(null, _identifier, - (uint)(NetAPIEnums.DSGETDCNAME_FLAGS.DS_FORCE_REDISCOVERY | - NetAPIEnums.DSGETDCNAME_FLAGS.DS_RETURN_DNS_NAME | - NetAPIEnums.DSGETDCNAME_FLAGS.DS_DIRECTORY_SERVICE_REQUIRED)); - if (dsGetDcNameResult.IsSuccess) { - tempDomainName = dsGetDcNameResult.Value.DomainName; + try { + if (!string.IsNullOrWhiteSpace(_ldapConfig.Server)) { + return CreateNewConnectionForServer(_ldapConfig.Server, globalCatalog); + } + if (CreateLdapConnection(_identifier.ToUpper().Trim(), globalCatalog, out var connectionWrapper)) { + _log.LogDebug("Successfully created ldap connection for domain: {Domain} using strategy 1. SSL: {SSl}", _identifier, connectionWrapper.Connection.SessionOptions.SecureSocketLayer); + return (true, connectionWrapper, ""); + } + + string tempDomainName; + + var dsGetDcNameResult = _nativeMethods.CallDsGetDcName(null, _identifier, + (uint)(NetAPIEnums.DSGETDCNAME_FLAGS.DS_FORCE_REDISCOVERY | + NetAPIEnums.DSGETDCNAME_FLAGS.DS_RETURN_DNS_NAME | + NetAPIEnums.DSGETDCNAME_FLAGS.DS_DIRECTORY_SERVICE_REQUIRED)); + if (dsGetDcNameResult.IsSuccess) { + tempDomainName = dsGetDcNameResult.Value.DomainName; + + if (!tempDomainName.Equals(_identifier, StringComparison.OrdinalIgnoreCase) && + CreateLdapConnection(tempDomainName, globalCatalog, out connectionWrapper)) { + _log.LogDebug( + "Successfully created ldap connection for domain: {Domain} using strategy 2 with name {NewName}", + _identifier, tempDomainName); + return (true, connectionWrapper, ""); + } + + var server = dsGetDcNameResult.Value.DomainControllerName.TrimStart('\\'); + + var result = + await CreateLDAPConnectionWithPortCheck(server, globalCatalog); + if (result.success) { + _log.LogDebug( + "Successfully created ldap connection for domain: {Domain} using strategy 3 to server {Server}", + _identifier, server); + return (true, result.connection, ""); + } + } + + if (!LdapUtils.GetDomain(_identifier, _ldapConfig, out var domainObject) || domainObject.Name == null) { + //If we don't get a result here, we effectively have no other ways to resolve this domain, so we'll just have to exit out + _log.LogDebug( + "Could not get domain object from GetDomain, unable to create ldap connection for domain {Domain}", + _identifier); + return (false, null, "Unable to get domain object for further strategies"); + } + tempDomainName = domainObject.Name.ToUpper().Trim(); + if (!tempDomainName.Equals(_identifier, StringComparison.OrdinalIgnoreCase) && CreateLdapConnection(tempDomainName, globalCatalog, out connectionWrapper)) { _log.LogDebug( - "Successfully created ldap connection for domain: {Domain} using strategy 2 with name {NewName}", + "Successfully created ldap connection for domain: {Domain} using strategy 4 with name {NewName}", _identifier, tempDomainName); return (true, connectionWrapper, ""); } - var server = dsGetDcNameResult.Value.DomainControllerName.TrimStart('\\'); - - var result = - await CreateLDAPConnectionWithPortCheck(server, globalCatalog); - if (result.success) { - _log.LogDebug( - "Successfully created ldap connection for domain: {Domain} using strategy 3 to server {Server}", - _identifier, server); - return (true, result.connection, ""); - } - } - - if (!LdapUtils.GetDomain(_identifier, _ldapConfig, out var domainObject) || domainObject.Name == null) { - //If we don't get a result here, we effectively have no other ways to resolve this domain, so we'll just have to exit out - _log.LogDebug( - "Could not get domain object from GetDomain, unable to create ldap connection for domain {Domain}", - _identifier); - return (false, null, "Unable to get domain object for further strategies"); - } - tempDomainName = domainObject.Name.ToUpper().Trim(); - - if (!tempDomainName.Equals(_identifier, StringComparison.OrdinalIgnoreCase) && - CreateLdapConnection(tempDomainName, globalCatalog, out connectionWrapper)) { - _log.LogDebug( - "Successfully created ldap connection for domain: {Domain} using strategy 4 with name {NewName}", - _identifier, tempDomainName); - return (true, connectionWrapper, ""); - } - - var primaryDomainController = domainObject.PdcRoleOwner.Name; - var portConnectionResult = - await CreateLDAPConnectionWithPortCheck(primaryDomainController, globalCatalog); - if (portConnectionResult.success) { - _log.LogDebug( - "Successfully created ldap connection for domain: {Domain} using strategy 5 with to pdc {Server}", - _identifier, primaryDomainController); - return (true, connectionWrapper, ""); - } - - foreach (DomainController dc in domainObject.DomainControllers) { - portConnectionResult = - await CreateLDAPConnectionWithPortCheck(dc.Name, globalCatalog); + var primaryDomainController = domainObject.PdcRoleOwner.Name; + var portConnectionResult = + await CreateLDAPConnectionWithPortCheck(primaryDomainController, globalCatalog); if (portConnectionResult.success) { _log.LogDebug( - "Successfully created ldap connection for domain: {Domain} using strategy 6 with to pdc {Server}", + "Successfully created ldap connection for domain: {Domain} using strategy 5 with to pdc {Server}", _identifier, primaryDomainController); - return (true, connectionWrapper, ""); + return (true, portConnectionResult.connection, ""); + } + + foreach (DomainController dc in domainObject.DomainControllers) { + portConnectionResult = + await CreateLDAPConnectionWithPortCheck(dc.Name, globalCatalog); + if (portConnectionResult.success) { + _log.LogDebug( + "Successfully created ldap connection for domain: {Domain} using strategy 6 with to pdc {Server}", + _identifier, primaryDomainController); + return (true, portConnectionResult.connection, ""); + } } + } catch (Exception e) { + _log.LogInformation(e, "We will not be able to connect to domain {Domain} by any strategy, leaving it.", _identifier); } return (false, null, "All attempted connections failed"); From 12a33578bcd6577bf4dd7e0d8f25aa7acec99081 Mon Sep 17 00:00:00 2001 From: anemeth Date: Fri, 5 Jul 2024 11:30:47 -0700 Subject: [PATCH 34/68] Namespace syntax corrections --- src/CommonLib/DirectoryEntryExtensions.cs | 203 +++++----- src/CommonLib/LdapUtils.cs | 6 +- src/CommonLib/SearchResultEntryExtensions.cs | 371 ++++++++++--------- 3 files changed, 289 insertions(+), 291 deletions(-) diff --git a/src/CommonLib/DirectoryEntryExtensions.cs b/src/CommonLib/DirectoryEntryExtensions.cs index e0159bcd..3469b926 100644 --- a/src/CommonLib/DirectoryEntryExtensions.cs +++ b/src/CommonLib/DirectoryEntryExtensions.cs @@ -7,127 +7,128 @@ using SharpHoundCommonLib.LDAPQueries; using SharpHoundCommonLib.OutputTypes; -namespace SharpHoundCommonLib; +namespace SharpHoundCommonLib { -public static class DirectoryEntryExtensions { - public static string GetProperty(this DirectoryEntry entry, string propertyName) { - try { - if (!entry.Properties.Contains(propertyName)) - entry.RefreshCache(new[] { propertyName }); - - if (!entry.Properties.Contains(propertyName)) + public static class DirectoryEntryExtensions { + public static string GetProperty(this DirectoryEntry entry, string propertyName) { + try { + if (!entry.Properties.Contains(propertyName)) + entry.RefreshCache(new[] { propertyName }); + + if (!entry.Properties.Contains(propertyName)) + return null; + } + catch { return null; - } - catch { - return null; - } + } - var s = entry.Properties[propertyName][0]; - return s switch - { - string st => st, - _ => null - }; - } + var s = entry.Properties[propertyName][0]; + return s switch + { + string st => st, + _ => null + }; + } - public static string[] GetPropertyAsArray(this DirectoryEntry entry, string propertyName) { - try { - if (!entry.Properties.Contains(propertyName)) - entry.RefreshCache(new[] { propertyName }); - - if (!entry.Properties.Contains(propertyName)) + public static string[] GetPropertyAsArray(this DirectoryEntry entry, string propertyName) { + try { + if (!entry.Properties.Contains(propertyName)) + entry.RefreshCache(new[] { propertyName }); + + if (!entry.Properties.Contains(propertyName)) + return null; + } + catch { return null; - } - catch { - return null; - } + } - var dest = new List(); - foreach (var val in entry.Properties[propertyName]) { - if (val is string s) { - dest.Add(s); + var dest = new List(); + foreach (var val in entry.Properties[propertyName]) { + if (val is string s) { + dest.Add(s); + } } - } - return dest.ToArray(); - } + return dest.ToArray(); + } - public static bool GetTypedPrincipal(this DirectoryEntry entry, out TypedPrincipal principal) { - var identifier = entry.GetObjectIdentifier(); - var success = entry.GetLabel(out var label); - principal = new TypedPrincipal(identifier, label); - return (success && !string.IsNullOrWhiteSpace(identifier)); - } + public static bool GetTypedPrincipal(this DirectoryEntry entry, out TypedPrincipal principal) { + var identifier = entry.GetObjectIdentifier(); + var success = entry.GetLabel(out var label); + principal = new TypedPrincipal(identifier, label); + return (success && !string.IsNullOrWhiteSpace(identifier)); + } - public static string GetObjectIdentifier(this DirectoryEntry entry) { - return entry.GetSid() ?? entry.GetGuid(); - } + public static string GetObjectIdentifier(this DirectoryEntry entry) { + return entry.GetSid() ?? entry.GetGuid(); + } - public static string GetSid(this DirectoryEntry entry) - { - try + public static string GetSid(this DirectoryEntry entry) { - if (!entry.Properties.Contains(LDAPProperties.ObjectSID)) - entry.RefreshCache(new[] { LDAPProperties.ObjectSID }); + try + { + if (!entry.Properties.Contains(LDAPProperties.ObjectSID)) + entry.RefreshCache(new[] { LDAPProperties.ObjectSID }); - if (!entry.Properties.Contains(LDAPProperties.ObjectSID)) + if (!entry.Properties.Contains(LDAPProperties.ObjectSID)) + return null; + } + catch + { return null; - } - catch - { - return null; - } + } - var s = entry.Properties[LDAPProperties.ObjectSID][0]; - return s switch - { - byte[] b => new SecurityIdentifier(b, 0).ToString(), - string st => new SecurityIdentifier(Encoding.ASCII.GetBytes(st), 0).ToString(), - _ => null - }; - } - - public static string GetGuid(this DirectoryEntry entry) - { - try + var s = entry.Properties[LDAPProperties.ObjectSID][0]; + return s switch + { + byte[] b => new SecurityIdentifier(b, 0).ToString(), + string st => new SecurityIdentifier(Encoding.ASCII.GetBytes(st), 0).ToString(), + _ => null + }; + } + + public static string GetGuid(this DirectoryEntry entry) { - //Attempt to refresh the props first - if (!entry.Properties.Contains(LDAPProperties.ObjectGUID)) - entry.RefreshCache(new[] { LDAPProperties.ObjectGUID }); + try + { + //Attempt to refresh the props first + if (!entry.Properties.Contains(LDAPProperties.ObjectGUID)) + entry.RefreshCache(new[] { LDAPProperties.ObjectGUID }); - if (!entry.Properties.Contains(LDAPProperties.ObjectGUID)) + if (!entry.Properties.Contains(LDAPProperties.ObjectGUID)) + return null; + } + catch + { return null; - } - catch - { - return null; - } + } - var s = entry.Properties[LDAPProperties.ObjectGUID][0]; - return s switch - { - byte[] b => new Guid(b).ToString(), - string st => st, - _ => null - }; - } - - - public static bool GetLabel(this DirectoryEntry entry, out Label type) { - try { - entry.RefreshCache(CommonProperties.TypeResolutionProps); - } - catch { - //pass + var s = entry.Properties[LDAPProperties.ObjectGUID][0]; + return s switch + { + byte[] b => new Guid(b).ToString(), + string st => st, + _ => null + }; } + + + public static bool GetLabel(this DirectoryEntry entry, out Label type) { + try { + entry.RefreshCache(CommonProperties.TypeResolutionProps); + } + catch { + //pass + } - var flagString = entry.GetProperty(LDAPProperties.Flags); - if (!int.TryParse(flagString, out var flags)) { - flags = 0; - } + var flagString = entry.GetProperty(LDAPProperties.Flags); + if (!int.TryParse(flagString, out var flags)) { + flags = 0; + } - return LdapUtils.ResolveLabel(entry.GetObjectIdentifier(), entry.GetProperty(LDAPProperties.DistinguishedName), - entry.GetProperty(LDAPProperties.SAMAccountType), - entry.GetPropertyAsArray(LDAPProperties.SAMAccountType), flags, out type); - } + return LdapUtils.ResolveLabel(entry.GetObjectIdentifier(), entry.GetProperty(LDAPProperties.DistinguishedName), + entry.GetProperty(LDAPProperties.SAMAccountType), + entry.GetPropertyAsArray(LDAPProperties.SAMAccountType), flags, out type); + } + } } \ No newline at end of file diff --git a/src/CommonLib/LdapUtils.cs b/src/CommonLib/LdapUtils.cs index acb6a1ec..c8461f61 100644 --- a/src/CommonLib/LdapUtils.cs +++ b/src/CommonLib/LdapUtils.cs @@ -1433,11 +1433,7 @@ public void SetLdapConfig(LDAPConfig config) { } public Task<(bool Success, string Message)> TestLdapConnection(string domain) { - try { - return _connectionPool.TestDomainConnection(domain, false); - } catch (Exception e) { - return (false, e.Message); - } + return _connectionPool.TestDomainConnection(domain, false); } public async Task<(bool Success, string Path)> GetNamingContextPath(string domain, NamingContext context) { diff --git a/src/CommonLib/SearchResultEntryExtensions.cs b/src/CommonLib/SearchResultEntryExtensions.cs index bebb3dc5..264f6544 100644 --- a/src/CommonLib/SearchResultEntryExtensions.cs +++ b/src/CommonLib/SearchResultEntryExtensions.cs @@ -6,227 +6,228 @@ using System.Text; using SharpHoundCommonLib.Enums; -namespace SharpHoundCommonLib; - -public static class SearchResultEntryExtensions { - /// - /// Extension method to determine the BloodHound type of a SearchResultEntry using LDAP properties - /// Requires ldap properties objectsid, samaccounttype, objectclass - /// - /// - /// - public static bool GetLabel(this SearchResultEntry entry, out Label type) - { - if (!entry.GetPropertyAsInt(LDAPProperties.Flags, out var flags)) { - flags = 0; - } +namespace SharpHoundCommonLib { - return LdapUtils.ResolveLabel(entry.GetObjectIdentifier(), entry.DistinguishedName, - entry.GetProperty(LDAPProperties.SAMAccountType), entry.GetPropertyAsArray(LDAPProperties.ObjectClass), - flags, out type); - } - - /// - /// Gets the specified property as a string from the SearchResultEntry + public static class SearchResultEntryExtensions { + /// + /// Extension method to determine the BloodHound type of a SearchResultEntry using LDAP properties + /// Requires ldap properties objectsid, samaccounttype, objectclass /// /// - /// The LDAP name of the property you want to get - /// The string value of the property if it exists or null - public static string GetProperty(this SearchResultEntry entry, string property) + /// + public static bool GetLabel(this SearchResultEntry entry, out Label type) { - if (!entry.Attributes.Contains(property)) - return null; - - var collection = entry.Attributes[property]; - //Use GetValues to auto-convert to the proper type - var lookups = collection.GetValues(typeof(string)); - if (lookups.Length == 0) - return null; - - if (lookups[0] is not string prop || prop.Length == 0) - return null; + if (!entry.GetPropertyAsInt(LDAPProperties.Flags, out var flags)) { + flags = 0; + } - return prop; + return LdapUtils.ResolveLabel(entry.GetObjectIdentifier(), entry.DistinguishedName, + entry.GetProperty(LDAPProperties.SAMAccountType), entry.GetPropertyAsArray(LDAPProperties.ObjectClass), + flags, out type); } - + /// - /// Get's the string representation of the "objectguid" property from the SearchResultEntry - /// - /// - /// The string representation of the object's GUID if possible, otherwise null - public static string GetGuid(this SearchResultEntry entry) - { - if (entry.Attributes.Contains(LDAPProperties.ObjectGUID)) + /// Gets the specified property as a string from the SearchResultEntry + /// + /// + /// The LDAP name of the property you want to get + /// The string value of the property if it exists or null + public static string GetProperty(this SearchResultEntry entry, string property) { - var guidBytes = entry.GetPropertyAsBytes(LDAPProperties.ObjectGUID); + if (!entry.Attributes.Contains(property)) + return null; - return new Guid(guidBytes).ToString().ToUpper(); - } + var collection = entry.Attributes[property]; + //Use GetValues to auto-convert to the proper type + var lookups = collection.GetValues(typeof(string)); + if (lookups.Length == 0) + return null; - return null; - } + if (lookups[0] is not string prop || prop.Length == 0) + return null; - /// - /// Gets the "objectsid" property as a string from the SearchResultEntry - /// - /// - /// The string representation of the object's SID if possible, otherwise null - public static string GetSid(this SearchResultEntry entry) - { - if (!entry.Attributes.Contains(LDAPProperties.ObjectSID)) return null; - - object[] s; - try - { - s = entry.Attributes[LDAPProperties.ObjectSID].GetValues(typeof(byte[])); + return prop; } - catch (NotSupportedException) + + /// + /// Get's the string representation of the "objectguid" property from the SearchResultEntry + /// + /// + /// The string representation of the object's GUID if possible, otherwise null + public static string GetGuid(this SearchResultEntry entry) { - return null; - } + if (entry.Attributes.Contains(LDAPProperties.ObjectGUID)) + { + var guidBytes = entry.GetPropertyAsBytes(LDAPProperties.ObjectGUID); - if (s.Length == 0) - return null; + return new Guid(guidBytes).ToString().ToUpper(); + } - if (s[0] is not byte[] sidBytes || sidBytes.Length == 0) return null; - - try - { - var sid = new SecurityIdentifier(sidBytes, 0); - return sid.Value.ToUpper(); } - catch (ArgumentNullException) + + /// + /// Gets the "objectsid" property as a string from the SearchResultEntry + /// + /// + /// The string representation of the object's SID if possible, otherwise null + public static string GetSid(this SearchResultEntry entry) { - return null; + if (!entry.Attributes.Contains(LDAPProperties.ObjectSID)) return null; + + object[] s; + try + { + s = entry.Attributes[LDAPProperties.ObjectSID].GetValues(typeof(byte[])); + } + catch (NotSupportedException) + { + return null; + } + + if (s.Length == 0) + return null; + + if (s[0] is not byte[] sidBytes || sidBytes.Length == 0) + return null; + + try + { + var sid = new SecurityIdentifier(sidBytes, 0); + return sid.Value.ToUpper(); + } + catch (ArgumentNullException) + { + return null; + } } - } - /// - /// Gets the specified property as a string array from the SearchResultEntry - /// - /// - /// The LDAP name of the property you want to get - /// The specified property as an array of strings if possible, else an empty array - public static string[] GetPropertyAsArray(this SearchResultEntry entry, string property) - { - if (!entry.Attributes.Contains(property)) - return Array.Empty(); - - var values = entry.Attributes[property]; - var strings = values.GetValues(typeof(string)); + /// + /// Gets the specified property as a string array from the SearchResultEntry + /// + /// + /// The LDAP name of the property you want to get + /// The specified property as an array of strings if possible, else an empty array + public static string[] GetPropertyAsArray(this SearchResultEntry entry, string property) + { + if (!entry.Attributes.Contains(property)) + return Array.Empty(); - return strings is not string[] result ? Array.Empty() : result; - } + var values = entry.Attributes[property]; + var strings = values.GetValues(typeof(string)); - /// - /// Gets the specified property as an array of byte arrays from the SearchResultEntry - /// Used for SIDHistory - /// - /// - /// The LDAP name of the property you want to get - /// The specified property as an array of bytes if possible, else an empty array - public static byte[][] GetPropertyAsArrayOfBytes(this SearchResultEntry entry, string property) - { - if (!entry.Attributes.Contains(property)) - return Array.Empty(); + return strings is not string[] result ? Array.Empty() : result; + } - var values = entry.Attributes[property]; - var bytes = values.GetValues(typeof(byte[])); + /// + /// Gets the specified property as an array of byte arrays from the SearchResultEntry + /// Used for SIDHistory + /// + /// + /// The LDAP name of the property you want to get + /// The specified property as an array of bytes if possible, else an empty array + public static byte[][] GetPropertyAsArrayOfBytes(this SearchResultEntry entry, string property) + { + if (!entry.Attributes.Contains(property)) + return Array.Empty(); - return bytes is not byte[][] result ? Array.Empty() : result; - } + var values = entry.Attributes[property]; + var bytes = values.GetValues(typeof(byte[])); - /// - /// Gets the specified property as a byte array - /// - /// - /// The LDAP name of the property you want to get - /// An array of bytes if possible, else null - public static byte[] GetPropertyAsBytes(this SearchResultEntry searchResultEntry, string property) - { - if (!searchResultEntry.Attributes.Contains(property)) - return null; + return bytes is not byte[][] result ? Array.Empty() : result; + } - var collection = searchResultEntry.Attributes[property]; - var lookups = collection.GetValues(typeof(byte[])); + /// + /// Gets the specified property as a byte array + /// + /// + /// The LDAP name of the property you want to get + /// An array of bytes if possible, else null + public static byte[] GetPropertyAsBytes(this SearchResultEntry searchResultEntry, string property) + { + if (!searchResultEntry.Attributes.Contains(property)) + return null; - if (lookups.Length == 0) - return Array.Empty(); + var collection = searchResultEntry.Attributes[property]; + var lookups = collection.GetValues(typeof(byte[])); - if (lookups[0] is not byte[] bytes || bytes.Length == 0) - return Array.Empty(); + if (lookups.Length == 0) + return Array.Empty(); - return bytes; - } + if (lookups[0] is not byte[] bytes || bytes.Length == 0) + return Array.Empty(); - /// - /// Gets the specified property as an int - /// - /// - /// - /// - /// - public static bool GetPropertyAsInt(this SearchResultEntry entry, string property, out int value) - { - var prop = entry.GetProperty(property); - if (prop != null) return int.TryParse(prop, out value); - value = 0; - return false; - } + return bytes; + } - /// - /// Gets the specified property as an array of X509 certificates. - /// - /// - /// - /// - public static X509Certificate2[] GetPropertyAsArrayOfCertificates(this SearchResultEntry searchResultEntry, - string property) - { - if (!searchResultEntry.Attributes.Contains(property)) - return null; + /// + /// Gets the specified property as an int + /// + /// + /// + /// + /// + public static bool GetPropertyAsInt(this SearchResultEntry entry, string property, out int value) + { + var prop = entry.GetProperty(property); + if (prop != null) return int.TryParse(prop, out value); + value = 0; + return false; + } - return searchResultEntry.GetPropertyAsArrayOfBytes(property).Select(x => new X509Certificate2(x)).ToArray(); - } + /// + /// Gets the specified property as an array of X509 certificates. + /// + /// + /// + /// + public static X509Certificate2[] GetPropertyAsArrayOfCertificates(this SearchResultEntry searchResultEntry, + string property) + { + if (!searchResultEntry.Attributes.Contains(property)) + return null; + return searchResultEntry.GetPropertyAsArrayOfBytes(property).Select(x => new X509Certificate2(x)).ToArray(); + } - /// - /// Attempts to get the unique object identifier as used by BloodHound for the Search Result Entry. Tries to get - /// objectsid first, and then objectguid next. - /// - /// - /// String representation of the entry's object identifier or null - public static string GetObjectIdentifier(this SearchResultEntry entry) - { - return entry.GetSid() ?? entry.GetGuid(); - } - /// - /// Checks the isDeleted LDAP property to determine if an entry has been deleted from the directory - /// - /// - /// - public static bool IsDeleted(this SearchResultEntry entry) - { - var deleted = entry.GetProperty(LDAPProperties.IsDeleted); - return bool.TryParse(deleted, out var isDeleted) && isDeleted; - } - - /// - /// Helper function to print attributes of a SearchResultEntry - /// - /// - public static string PrintEntry(this SearchResultEntry searchResultEntry) - { - var sb = new StringBuilder(); - if (searchResultEntry.Attributes.AttributeNames == null) return sb.ToString(); - foreach (var propertyName in searchResultEntry.Attributes.AttributeNames) + /// + /// Attempts to get the unique object identifier as used by BloodHound for the Search Result Entry. Tries to get + /// objectsid first, and then objectguid next. + /// + /// + /// String representation of the entry's object identifier or null + public static string GetObjectIdentifier(this SearchResultEntry entry) { - var property = propertyName.ToString(); - sb.Append(property).Append("\t").Append(searchResultEntry.GetProperty(property)).Append("\n"); + return entry.GetSid() ?? entry.GetGuid(); } - return sb.ToString(); - } + /// + /// Checks the isDeleted LDAP property to determine if an entry has been deleted from the directory + /// + /// + /// + public static bool IsDeleted(this SearchResultEntry entry) + { + var deleted = entry.GetProperty(LDAPProperties.IsDeleted); + return bool.TryParse(deleted, out var isDeleted) && isDeleted; + } + + /// + /// Helper function to print attributes of a SearchResultEntry + /// + /// + public static string PrintEntry(this SearchResultEntry searchResultEntry) + { + var sb = new StringBuilder(); + if (searchResultEntry.Attributes.AttributeNames == null) return sb.ToString(); + foreach (var propertyName in searchResultEntry.Attributes.AttributeNames) + { + var property = propertyName.ToString(); + sb.Append(property).Append("\t").Append(searchResultEntry.GetProperty(property)).Append("\n"); + } + + return sb.ToString(); + } + } } \ No newline at end of file From cda27fd1ac241241ab6a2a13028fa4905c4d8c76 Mon Sep 17 00:00:00 2001 From: rvazarkar Date: Mon, 8 Jul 2024 11:44:24 -0400 Subject: [PATCH 35/68] wip: comment out tests for initial build --- test/unit/ACLProcessorTest.cs | 2032 ++++++++++---------- test/unit/ContainerProcessorTest.cs | 324 ++-- test/unit/DomainTrustProcessorTest.cs | 258 +-- test/unit/Facades/MockLDAPUtils.cs | 99 +- test/unit/Facades/MockSearchResultEntry.cs | 3 +- test/unit/GPOLocalGroupProcessorTest.cs | 670 +++---- test/unit/GroupProcessorTest.cs | 260 +-- test/unit/LDAPPropertyTests.cs | 144 +- test/unit/LDAPUtilsTest.cs | 561 +++--- test/unit/SearchResultEntryTests.cs | 8 +- 10 files changed, 2223 insertions(+), 2136 deletions(-) diff --git a/test/unit/ACLProcessorTest.cs b/test/unit/ACLProcessorTest.cs index 0aa6c1df..ca91ebc2 100644 --- a/test/unit/ACLProcessorTest.cs +++ b/test/unit/ACLProcessorTest.cs @@ -1,1016 +1,1016 @@ -using System; -using System.Collections.Generic; -using System.DirectoryServices; -using System.Linq; -using System.Security.AccessControl; -using CommonLibTest.Facades; -using Moq; -using Newtonsoft.Json; -using SharpHoundCommonLib; -using SharpHoundCommonLib.Enums; -using SharpHoundCommonLib.OutputTypes; -using SharpHoundCommonLib.Processors; -using Xunit; -using Xunit.Abstractions; - -namespace CommonLibTest -{ - public class ACLProcessorTest : IDisposable - { - private const string ProtectedUserNTSecurityDescriptor = - "AQAEnIgEAAAAAAAAAAAAABQAAAAEAHQEGAAAAAUAPAAQAAAAAwAAAABCFkzAINARp2gAqgBuBSkUzChINxS8RZsHrW8BXl8oAQIAAAAAAAUgAAAAKgIAAAUAPAAQAAAAAwAAAABCFkzAINARp2gAqgBuBSm6epa/5g3QEaKFAKoAMEniAQIAAAAAAAUgAAAAKgIAAAUAPAAQAAAAAwAAABAgIF+ledARkCAAwE/C1M8UzChINxS8RZsHrW8BXl8oAQIAAAAAAAUgAAAAKgIAAAUAPAAQAAAAAwAAABAgIF+ledARkCAAwE/C1M+6epa/5g3QEaKFAKoAMEniAQIAAAAAAAUgAAAAKgIAAAUAPAAQAAAAAwAAAEDCCrypedARkCAAwE/C1M8UzChINxS8RZsHrW8BXl8oAQIAAAAAAAUgAAAAKgIAAAUAPAAQAAAAAwAAAEDCCrypedARkCAAwE/C1M+6epa/5g3QEaKFAKoAMEniAQIAAAAAAAUgAAAAKgIAAAUAPAAQAAAAAwAAAEIvulmiedARkCAAwE/C088UzChINxS8RZsHrW8BXl8oAQIAAAAAAAUgAAAAKgIAAAUAPAAQAAAAAwAAAEIvulmiedARkCAAwE/C08+6epa/5g3QEaKFAKoAMEniAQIAAAAAAAUgAAAAKgIAAAUAPAAQAAAAAwAAAPiIcAPhCtIRtCIAoMlo+TkUzChINxS8RZsHrW8BXl8oAQIAAAAAAAUgAAAAKgIAAAUAPAAQAAAAAwAAAPiIcAPhCtIRtCIAoMlo+Tm6epa/5g3QEaKFAKoAMEniAQIAAAAAAAUgAAAAKgIAAAUAOAAwAAAAAQAAAH96lr/mDdARooUAqgAwSeIBBQAAAAAABRUAAAAgT5C6f0aEpXZIFpAFAgAABQAsABAAAAABAAAAHbGpRq5gWkC36P+KWNRW0gECAAAAAAAFIAAAADACAAAFACwAMAAAAAEAAAAcmrZtIpTREa69AAD4A2fBAQIAAAAAAAUgAAAAMQIAAAUALAAwAAAAAQAAAGK8BVjJvShEpeKFag9MGF4BAgAAAAAABSAAAAAxAgAABQAsAJQAAgACAAAAFMwoSDcUvEWbB61vAV5fKAECAAAAAAAFIAAAACoCAAAFACwAlAACAAIAAAC6epa/5g3QEaKFAKoAMEniAQIAAAAAAAUgAAAAKgIAAAUAKAAAAQAAAQAAAFMacqsvHtARmBkAqgBAUpsBAQAAAAAAAQAAAAAFACgAAAEAAAEAAABTGnKrLx7QEZgZAKoAQFKbAQEAAAAAAAUKAAAABQIoADABAAABAAAA3kfmkW/ZcEuVV9Y/9PPM2AEBAAAAAAAFCgAAAAAAJAC/AQ4AAQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQAAIAAAAAJAC/AQ4AAQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQBwIAAAAAGAC/AQ8AAQIAAAAAAAUgAAAAIAIAAAAAFACUAAIAAQEAAAAAAAULAAAAAAAUAP8BDwABAQAAAAAABRIAAAABBQAAAAAABRUAAAAgT5C6f0aEpXZIFpAAAgAA"; - - private const string UnProtectedUserNtSecurityDescriptor = - "AQAEjJgGAAAAAAAAAAAAABQAAAAEAIQGJwAAAAUAOAAQAAAAAQAAAABCFkzAINARp2gAqgBuBSkBBQAAAAAABRUAAAAgT5C6f0aEpXZIFpApAgAABQA4ABAAAAABAAAAECAgX6V50BGQIADAT8LUzwEFAAAAAAAFFQAAACBPkLp/RoSldkgWkCkCAAAFADgAEAAAAAEAAABAwgq8qXnQEZAgAMBPwtTPAQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQKQIAAAUAOAAQAAAAAQAAAPiIcAPhCtIRtCIAoMlo+TkBBQAAAAAABRUAAAAgT5C6f0aEpXZIFpApAgAABQA4ADAAAAABAAAAf3qWv+YN0BGihQCqADBJ4gEFAAAAAAAFFQAAACBPkLp/RoSldkgWkAUCAAAFACwAEAAAAAEAAAAdsalGrmBaQLfo/4pY1FbSAQIAAAAAAAUgAAAAMAIAAAUALAAwAAAAAQAAAByatm0ilNERrr0AAPgDZ8EBAgAAAAAABSAAAAAxAgAABQAsADAAAAABAAAAYrwFWMm9KESl4oVqD0wYXgECAAAAAAAFIAAAADECAAAFACgAAAEAAAEAAABTGnKrLx7QEZgZAKoAQFKbAQEAAAAAAAEAAAAABQAoAAABAAABAAAAUxpyqy8e0BGYGQCqAEBSmwEBAAAAAAAFCgAAAAUAKAAAAQAAAQAAAFQacqsvHtARmBkAqgBAUpsBAQAAAAAABQoAAAAFACgAAAEAAAEAAABWGnKrLx7QEZgZAKoAQFKbAQEAAAAAAAUKAAAABQAoABAAAAABAAAAQi+6WaJ50BGQIADAT8LTzwEBAAAAAAAFCwAAAAUAKAAQAAAAAQAAAFQBjeT4vNERhwIAwE+5YFABAQAAAAAABQsAAAAFACgAEAAAAAEAAACGuLV3SpTREa69AAD4A2fBAQEAAAAAAAULAAAABQAoABAAAAABAAAAs5VX5FWU0RGuvQAA+ANnwQEBAAAAAAAFCwAAAAUAKAAwAAAAAQAAAIa4tXdKlNERrr0AAPgDZ8EBAQAAAAAABQoAAAAFACgAMAAAAAEAAACylVfkVZTREa69AAD4A2fBAQEAAAAAAAUKAAAABQAoADAAAAABAAAAs5VX5FWU0RGuvQAA+ANnwQEBAAAAAAAFCgAAAAAAJAD/AQ8AAQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQAAIAAAAAGAD/AQ8AAQIAAAAAAAUgAAAAJAIAAAAAFAAAAAIAAQEAAAAAAAULAAAAAAAUAJQAAgABAQAAAAAABQoAAAAAABQA/wEPAAEBAAAAAAAFEgAAAAUSOAAAAQAAAQAAAKr2MREHnNER958AwE/C3NIBBQAAAAAABRUAAAAgT5C6f0aEpXZIFpBKCAAABRI4AAABAAABAAAArfYxEQec0RH3nwDAT8Lc0gEFAAAAAAAFFQAAACBPkLp/RoSldkgWkD8IAAAFEjgAAAEAAAEAAACt9jERB5zREfefAMBPwtzSAQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQSggAAAUaOAAQAAAAAwAAAG2exrfHLNIRhU4AoMmD9giGepa/5g3QEaKFAKoAMEniAQEAAAAAAAUJAAAABRo4ABAAAAADAAAAbZ7Gt8cs0hGFTgCgyYP2CJx6lr/mDdARooUAqgAwSeIBAQAAAAAABQkAAAAFEjgAEAAAAAMAAABtnsa3xyzSEYVOAKDJg/YIunqWv+YN0BGihQCqADBJ4gEBAAAAAAAFCQAAAAUaOAAgAAAAAwAAAJN7G+pIXtVGvGxN9P2nijWGepa/5g3QEaKFAKoAMEniAQEAAAAAAAUKAAAABRosAJQAAgACAAAAFMwoSDcUvEWbB61vAV5fKAECAAAAAAAFIAAAACoCAAAFGiwAlAACAAIAAACcepa/5g3QEaKFAKoAMEniAQIAAAAAAAUgAAAAKgIAAAUSLACUAAIAAgAAALp6lr/mDdARooUAqgAwSeIBAgAAAAAABSAAAAAqAgAABRIoADAAAAABAAAA5cN4P5r3vUaguJ0YEW3ceQEBAAAAAAAFCgAAAAUSKAAwAQAAAQAAAN5H5pFv2XBLlVfWP/TzzNgBAQAAAAAABQoAAAAAEiQA/wEPAAEFAAAAAAAFFQAAACBPkLp/RoSldkgWkAcCAAAAEhgABAAAAAECAAAAAAAFIAAAACoCAAAAEhgAvQEPAAECAAAAAAAFIAAAACACAAABBQAAAAAABRUAAAAgT5C6f0aEpXZIFpAAAgAA"; - - private const string GMSAProperty = - "AQAEgEAAAAAAAAAAAAAAABQAAAAEACwAAQAAAAAAJAD/AQ8AAQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQ9AEAAAECAAAAAAAFIAAAACACAAA\u003d"; - - private const string AddMemberSecurityDescriptor = - "AQAEjGADAAAAAAAAAAAAABQAAAAEAEwDFQAAAAUAOAAIAAAAAQAAAMB5lr/mDdARooUAqgAwSeIBBQAAAAAABRUAAAAgT5C6f0aEpXZIFpAuCgAABQA4ACAAAAABAAAAwHmWv+YN0BGihQCqADBJ4gEFAAAAAAAFFQAAACBPkLp/RoSldkgWkEcIAAAFACwAEAAAAAEAAAAdsalGrmBaQLfo/4pY1FbSAQIAAAAAAAUgAAAAMAIAAAUAKAAAAQAAAQAAAFUacqsvHtARmBkAqgBAUpsBAQAAAAAABQsAAAAAACQA/wEPAAEFAAAAAAAFFQAAACBPkLp/RoSldkgWkAACAAAAABgA/wEPAAECAAAAAAAFIAAAACQCAAAAABQAlAACAAEBAAAAAAAFCgAAAAAAFACUAAIAAQEAAAAAAAULAAAAAAAUAP8BDwABAQAAAAAABRIAAAAFGjgAEAAAAAMAAABtnsa3xyzSEYVOAKDJg/YIhnqWv+YN0BGihQCqADBJ4gEBAAAAAAAFCQAAAAUSOAAQAAAAAwAAAG2exrfHLNIRhU4AoMmD9gicepa/5g3QEaKFAKoAMEniAQEAAAAAAAUJAAAABRo4ABAAAAADAAAAbZ7Gt8cs0hGFTgCgyYP2CLp6lr/mDdARooUAqgAwSeIBAQAAAAAABQkAAAAFGjgAIAAAAAMAAACTexvqSF7VRrxsTfT9p4o1hnqWv+YN0BGihQCqADBJ4gEBAAAAAAAFCgAAAAUaLACUAAIAAgAAABTMKEg3FLxFmwetbwFeXygBAgAAAAAABSAAAAAqAgAABRIsAJQAAgACAAAAnHqWv+YN0BGihQCqADBJ4gECAAAAAAAFIAAAACoCAAAFGiwAlAACAAIAAAC6epa/5g3QEaKFAKoAMEniAQIAAAAAAAUgAAAAKgIAAAUSKAAwAAAAAQAAAOXDeD+a971GoLidGBFt3HkBAQAAAAAABQoAAAAFEigAMAEAAAEAAADeR+aRb9lwS5VX1j/088zYAQEAAAAAAAUKAAAAABIkAP8BDwABBQAAAAAABRUAAAAgT5C6f0aEpXZIFpAHAgAAABIYAAQAAAABAgAAAAAABSAAAAAqAgAAABIYAL0BDwABAgAAAAAABSAAAAAgAgAAAQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQAAIAAA=="; - - private readonly ACLProcessor _baseProcessor; - - private readonly string _testDomainName; - private readonly ITestOutputHelper _testOutputHelper; - - public ACLProcessorTest(ITestOutputHelper testOutputHelper) - { - _testOutputHelper = testOutputHelper; - _testDomainName = "TESTLAB.LOCAL"; - _baseProcessor = new ACLProcessor(new LDAPUtils()); - } - - public void Dispose() - { - } - - [Fact] - public void SanityCheck() - { - Assert.True(true); - } - - [Fact] - public void ACLProcessor_IsACLProtected_NullNTSD_ReturnsFalse() - { - var processor = new ACLProcessor(new MockLDAPUtils(), true); - var result = processor.IsACLProtected((byte[])null); - Assert.False(result); - } - - [WindowsOnlyFact] - public void ACLProcessor_TestKnownDataAddMember() - { - var mockLdapUtils = new MockLDAPUtils(); - var mockUtils = new Mock(); - mockUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) - .Returns((string a, string b) => mockLdapUtils.ResolveIDAndType(a, b)); - var sd = new ActiveDirectorySecurityDescriptor(new ActiveDirectorySecurity()); - mockUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(sd); - - var processor = new ACLProcessor(mockUtils.Object, true); - var bytes = Helpers.B64ToBytes(AddMemberSecurityDescriptor); - var result = processor.ProcessACL(bytes, "TESTLAB.LOCAL", Label.Group, false); - - _testOutputHelper.WriteLine(JsonConvert.SerializeObject(result)); - - Assert.Contains(result, - x => x.RightName == EdgeNames.AddSelf && - x.PrincipalSID == "S-1-5-21-3130019616-2776909439-2417379446-2606"); - Assert.Contains(result, - x => x.RightName == EdgeNames.AddMember && - x.PrincipalSID == "S-1-5-21-3130019616-2776909439-2417379446-2119"); - } - - [Fact] - public void ACLProcessor_IsACLProtected_ReturnsTrue() - { - var mockLDAPUtils = new Mock(); - var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - mockSecurityDescriptor.Setup(x => x.AreAccessRulesProtected()).Returns(true); - mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - - var processor = new ACLProcessor(mockLDAPUtils.Object, true); - var bytes = Helpers.B64ToBytes(ProtectedUserNTSecurityDescriptor); - var result = processor.IsACLProtected(bytes); - Assert.True(result); - } - - [Fact] - public void ACLProcessor_IsACLProtected_ReturnsFalse() - { - var mockLDAPUtils = new Mock(); - var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - mockSecurityDescriptor.Setup(m => m.AreAccessRulesProtected()).Returns(false); - mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - - var processor = new ACLProcessor(mockLDAPUtils.Object, true); - var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); - var result = processor.IsACLProtected(bytes); - Assert.False(result); - } - - [Fact] - public void ACLProcessor_ProcessGMSAReaders_NullNTSD_ReturnsNothing() - { - var test = _baseProcessor.ProcessGMSAReaders(null, ""); - Assert.Empty(test); - } - - [Fact] - public void ACLProcess_ProcessGMSAReaders_YieldsCorrectAce() - { - var expectedRightName = "ReadGMSAPassword"; - var expectedSID = "S-1-5-21-3130019616-2776909439-2417379446-500"; - var expectedPrincipalType = Label.User; - var expectedInheritance = false; - - var mockLDAPUtils = new Mock(); - var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - var mockRule = new Mock(MockBehavior.Loose, null); - - mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); - mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); - mockRule.Setup(x => x.IdentityReference()).Returns(expectedSID); - - var collection = new List(); - collection.Add(mockRule.Object); - - mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(collection); - mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) - .Returns(new TypedPrincipal(expectedSID, expectedPrincipalType)); - - var processor = new ACLProcessor(mockLDAPUtils.Object, true); - var bytes = Helpers.B64ToBytes(GMSAProperty); - var result = processor.ProcessGMSAReaders(bytes, _testDomainName).ToArray(); - - Assert.Single(result); - var actual = result.First(); - _testOutputHelper.WriteLine(actual.ToString()); - Assert.Equal(expectedRightName, actual.RightName); - Assert.Equal(expectedSID, actual.PrincipalSID); - Assert.Equal(expectedPrincipalType, actual.PrincipalType); - Assert.Equal(expectedInheritance, actual.IsInherited); - } - - [Fact] - public void ACLProcessor_ProcessGMSAReaders_Null_ACE() - { - var mockLDAPUtils = new Mock(); - var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - var collection = new List(); - collection.Add(null); - - mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(collection); - mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - - var processor = new ACLProcessor(mockLDAPUtils.Object, true); - var bytes = Helpers.B64ToBytes(GMSAProperty); - var result = processor.ProcessGMSAReaders(bytes, _testDomainName).ToArray(); - - Assert.Empty(result); - } - - [Fact] - public void ACLProcessor_ProcessGMSAReaders_Deny_ACE() - { - var mockLDAPUtils = new Mock(); - var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - var mockRule = new Mock(MockBehavior.Loose, null); - var collection = new List(); - - mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Deny); - collection.Add(mockRule.Object); - - mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(collection); - mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - - var processor = new ACLProcessor(mockLDAPUtils.Object, true); - var bytes = Helpers.B64ToBytes(GMSAProperty); - var result = processor.ProcessGMSAReaders(bytes, _testDomainName).ToArray(); - - Assert.Empty(result); - } - - [Fact] - public void ACLProcessor_ProcessGMSAReaders_Null_PrincipalID() - { - var mockLDAPUtils = new Mock(); - var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - var mockRule = new Mock(MockBehavior.Loose, null); - var collection = new List(); - - mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); - mockRule.Setup(x => x.IdentityReference()).Returns((string)null); - collection.Add(mockRule.Object); - - mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(collection); - mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - - var processor = new ACLProcessor(mockLDAPUtils.Object, true); - var bytes = Helpers.B64ToBytes(GMSAProperty); - var result = processor.ProcessGMSAReaders(bytes, _testDomainName).ToArray(); - - Assert.Empty(result); - } - - [Fact] - public void ACLProcessor_ProcessACL_Null_NTSecurityDescriptor() - { - var processor = new ACLProcessor(new MockLDAPUtils(), true); - var result = processor.ProcessACL(null, _testDomainName, Label.User, false).ToArray(); - - Assert.Empty(result); - } - - [Fact] - public void ACLProcessor_ProcessACL_Yields_Owns_ACE() - { - var expectedSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; - var expectedPrincipalType = Label.Group; - - var mockLDAPUtils = new Mock(); - var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - var collection = new List(); - - mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns(expectedSID); - mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) - .Returns(new TypedPrincipal(expectedSID, expectedPrincipalType)); - - var processor = new ACLProcessor(mockLDAPUtils.Object, true); - var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); - var result = processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArray(); - - Assert.Single(result); - var actual = result.First(); - Assert.Equal(actual.PrincipalSID, expectedSID); - Assert.Equal(actual.PrincipalType, expectedPrincipalType); - Assert.Equal(actual.IsInherited, false); - Assert.Equal(actual.RightName, EdgeNames.Owns); - } - - [Fact] - public void ACLProcessor_ProcessACL_Null_SID() - { - var mockLDAPUtils = new Mock(); - var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - var collection = new List(); - - mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); - mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - - var processor = new ACLProcessor(mockLDAPUtils.Object, true); - var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); - var result = processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArray(); - - Assert.Empty(result); - } - - [Fact] - public void ACLProcessor_ProcessACL_Null_ACE() - { - var mockLDAPUtils = new Mock(); - var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - var collection = new List(); - collection.Add(null); - - mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); - mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - - var processor = new ACLProcessor(mockLDAPUtils.Object, true); - var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); - var result = processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArray(); - - Assert.Empty(result); - } - - [Fact] - public void ACLProcessor_ProcessACL_Deny_ACE() - { - var mockLDAPUtils = new Mock(); - var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - var mockRule = new Mock(MockBehavior.Loose, null); - var collection = new List(); - mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Deny); - collection.Add(mockRule.Object); - - mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); - mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - - var processor = new ACLProcessor(mockLDAPUtils.Object, true); - var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); - var result = processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArray(); - - Assert.Empty(result); - } - - [Fact] - public void ACLProcessor_ProcessACL_Unmatched_Inheritance_ACE() - { - var mockLDAPUtils = new Mock(); - var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - var mockRule = new Mock(MockBehavior.Loose, null); - var collection = new List(); - mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); - mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(false); - collection.Add(mockRule.Object); - - mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); - mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - - var processor = new ACLProcessor(mockLDAPUtils.Object, true); - var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); - var result = processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArray(); - - Assert.Empty(result); - } - - [Fact] - public void ACLProcessor_ProcessACL_Null_SID_ACE() - { - var mockLDAPUtils = new Mock(); - var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - var mockRule = new Mock(MockBehavior.Loose, null); - var collection = new List(); - mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); - mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); - mockRule.Setup(x => x.IdentityReference()).Returns((string)null); - collection.Add(mockRule.Object); - - mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); - mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - - var processor = new ACLProcessor(mockLDAPUtils.Object, true); - var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); - var result = processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArray(); - - Assert.Empty(result); - } - - [Fact] - public void ACLProcessor_ProcessACL_GenericAll_Unmatched_Guid() - { - var expectedPrincipalType = Label.Group; - var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; - var unmatchedGuid = new Guid("583991c8-629d-4a07-8a70-74d19d22ac9c"); - - var mockLDAPUtils = new Mock(); - var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - var mockRule = new Mock(MockBehavior.Loose, null); - var collection = new List(); - mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); - mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); - mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); - mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.GenericAll); - mockRule.Setup(x => x.ObjectType()).Returns(unmatchedGuid); - collection.Add(mockRule.Object); - - mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); - mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) - .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); - - var processor = new ACLProcessor(mockLDAPUtils.Object, true); - var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); - var result = processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArray(); - - Assert.Empty(result); - } - - [Fact] - public void ACLProcessor_ProcessACL_GenericAll() - { - var expectedPrincipalType = Label.Group; - var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; - - var mockLDAPUtils = new Mock(); - var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - var mockRule = new Mock(MockBehavior.Loose, null); - var collection = new List(); - mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); - mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); - mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); - mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.GenericAll); - mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); - collection.Add(mockRule.Object); - - mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); - mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) - .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); - - var processor = new ACLProcessor(mockLDAPUtils.Object, true); - var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); - var result = processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArray(); - - Assert.Single(result); - var actual = result.First(); - Assert.Equal(actual.PrincipalType, expectedPrincipalType); - Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); - Assert.Equal(actual.IsInherited, false); - Assert.Equal(actual.RightName, EdgeNames.GenericAll); - } - - [Fact] - public void ACLProcessor_ProcessACL_WriteDacl() - { - var expectedPrincipalType = Label.Group; - var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; - var expectedRightName = ActiveDirectoryRights.WriteDacl; - - var mockLDAPUtils = new Mock(); - var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - var mockRule = new Mock(MockBehavior.Loose, null); - var collection = new List(); - mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); - mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); - mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); - mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(expectedRightName); - mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); - collection.Add(mockRule.Object); - - mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); - mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) - .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); - - var processor = new ACLProcessor(mockLDAPUtils.Object, true); - var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); - var result = processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArray(); - - Assert.Single(result); - var actual = result.First(); - Assert.Equal(actual.PrincipalType, expectedPrincipalType); - Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); - Assert.Equal(actual.IsInherited, false); - Assert.Equal(actual.RightName, expectedRightName.ToString()); - } - - [Fact] - public void ACLProcessor_ProcessACL_WriteOwner() - { - var expectedPrincipalType = Label.Group; - var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; - var expectedRightName = ActiveDirectoryRights.WriteOwner; - - var mockLDAPUtils = new Mock(); - var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - var mockRule = new Mock(MockBehavior.Loose, null); - var collection = new List(); - mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); - mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); - mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); - mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(expectedRightName); - mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); - collection.Add(mockRule.Object); - - mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); - mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) - .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); - - var processor = new ACLProcessor(mockLDAPUtils.Object, true); - var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); - var result = processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArray(); - - Assert.Single(result); - var actual = result.First(); - Assert.Equal(actual.PrincipalType, expectedPrincipalType); - Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); - Assert.Equal(actual.IsInherited, false); - Assert.Equal(actual.RightName, expectedRightName.ToString()); - } - - [Fact] - public void ACLProcessor_ProcessACL_Self() - { - var expectedPrincipalType = Label.Group; - var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; - var expectedRightName = EdgeNames.AddSelf; - - var mockLDAPUtils = new Mock(); - var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - var mockRule = new Mock(MockBehavior.Loose, null); - var collection = new List(); - mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); - mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); - mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); - mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.Self); - mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.WriteMember)); - collection.Add(mockRule.Object); - - mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); - mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) - .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); - - var processor = new ACLProcessor(mockLDAPUtils.Object, true); - var bytes = Helpers.B64ToBytes(AddMemberSecurityDescriptor); - var result = processor.ProcessACL(bytes, _testDomainName, Label.Group, false).ToArray(); - - Assert.Single(result); - var actual = result.First(); - Assert.Equal(actual.PrincipalType, expectedPrincipalType); - Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); - Assert.Equal(actual.IsInherited, false); - Assert.Equal(actual.RightName, expectedRightName); - } - - [Fact] - public void ACLProcessor_ProcessACL_ExtendedRight_Domain_Unmatched() - { - var expectedPrincipalType = Label.Group; - var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; - - var mockLDAPUtils = new Mock(); - var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - var mockRule = new Mock(MockBehavior.Loose, null); - var collection = new List(); - mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); - mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); - mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); - mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); - mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.WriteMember)); - collection.Add(mockRule.Object); - - mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); - mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) - .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); - - var processor = new ACLProcessor(mockLDAPUtils.Object, true); - var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); - var result = processor.ProcessACL(bytes, _testDomainName, Label.Domain, false).ToArray(); - - Assert.Empty(result); - } - - [Fact] - public void ACLProcessor_ProcessACL_ExtendedRight_Domain_DSReplicationGetChanges() - { - var expectedPrincipalType = Label.Group; - var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; - var expectedRightName = EdgeNames.GetChanges; - - var mockLDAPUtils = new Mock(); - var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - var mockRule = new Mock(MockBehavior.Loose, null); - var collection = new List(); - mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); - mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); - mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); - mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); - mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.DSReplicationGetChanges)); - collection.Add(mockRule.Object); - - mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); - mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) - .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); - - var processor = new ACLProcessor(mockLDAPUtils.Object, true); - var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); - var result = processor.ProcessACL(bytes, _testDomainName, Label.Domain, false).ToArray(); - - Assert.Single(result); - var actual = result.First(); - Assert.Equal(actual.PrincipalType, expectedPrincipalType); - Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); - Assert.Equal(actual.IsInherited, false); - Assert.Equal(actual.RightName, expectedRightName); - } - - [Fact] - public void ACLProcessor_ProcessACL_ExtendedRight_Domain_All() - { - var expectedPrincipalType = Label.Group; - var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; - var expectedRightName = EdgeNames.AllExtendedRights; - - var mockLDAPUtils = new Mock(); - var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - var mockRule = new Mock(MockBehavior.Loose, null); - var collection = new List(); - mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); - mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); - mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); - mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); - mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); - collection.Add(mockRule.Object); - - mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); - mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) - .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); - - var processor = new ACLProcessor(mockLDAPUtils.Object, true); - var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); - var result = processor.ProcessACL(bytes, _testDomainName, Label.Domain, false).ToArray(); - - Assert.Single(result); - var actual = result.First(); - Assert.Equal(actual.PrincipalType, expectedPrincipalType); - Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); - Assert.Equal(actual.IsInherited, false); - Assert.Equal(actual.RightName, expectedRightName); - } - - [Fact] - public void ACLProcessor_ProcessACL_ExtendedRight_Domain_DSReplicationGetChangesAll() - { - var expectedPrincipalType = Label.Group; - var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; - var expectedRightName = EdgeNames.GetChangesAll; - - var mockLDAPUtils = new Mock(); - var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - var mockRule = new Mock(MockBehavior.Loose, null); - var collection = new List(); - mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); - mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); - mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); - mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); - mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.DSReplicationGetChangesAll)); - collection.Add(mockRule.Object); - - mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); - mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) - .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); - - var processor = new ACLProcessor(mockLDAPUtils.Object, true); - var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); - var result = processor.ProcessACL(bytes, _testDomainName, Label.Domain, false).ToArray(); - - Assert.Single(result); - var actual = result.First(); - Assert.Equal(actual.PrincipalType, expectedPrincipalType); - Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); - Assert.Equal(actual.IsInherited, false); - Assert.Equal(actual.RightName, expectedRightName); - } - - [Fact] - public void ACLProcessor_ProcessACL_ExtendedRight_User_Unmatched() - { - var expectedPrincipalType = Label.Group; - var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; - var expectedRightName = EdgeNames.GetChangesAll; - var unmatchedGuid = new Guid("583991c8-629d-4a07-8a70-74d19d22ac9c"); - - var mockLDAPUtils = new Mock(); - var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - var mockRule = new Mock(MockBehavior.Loose, null); - var collection = new List(); - mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); - mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); - mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); - mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); - mockRule.Setup(x => x.ObjectType()).Returns(unmatchedGuid); - collection.Add(mockRule.Object); - - mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); - mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) - .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); - - var processor = new ACLProcessor(mockLDAPUtils.Object, true); - var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); - var result = processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArray(); - - Assert.Empty(result); - } - - [Fact] - public void ACLProcessor_ProcessACL_ExtendedRight_User_UserForceChangePassword() - { - var expectedPrincipalType = Label.Group; - var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; - var expectedRightName = EdgeNames.ForceChangePassword; - - var mockLDAPUtils = new Mock(); - var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - var mockRule = new Mock(MockBehavior.Loose, null); - var collection = new List(); - mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); - mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); - mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); - mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); - mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.UserForceChangePassword)); - collection.Add(mockRule.Object); - - mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); - mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) - .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); - - var processor = new ACLProcessor(mockLDAPUtils.Object, true); - var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); - var result = processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArray(); - - Assert.Single(result); - var actual = result.First(); - Assert.Equal(actual.PrincipalType, expectedPrincipalType); - Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); - Assert.Equal(actual.IsInherited, false); - Assert.Equal(actual.RightName, expectedRightName); - } - - [Fact] - public void ACLProcessor_ProcessACL_ExtendedRight_User_All() - { - var expectedPrincipalType = Label.Group; - var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; - var expectedRightName = EdgeNames.AllExtendedRights; - - var mockLDAPUtils = new Mock(); - var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - var mockRule = new Mock(MockBehavior.Loose, null); - var collection = new List(); - mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); - mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); - mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); - mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); - mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); - collection.Add(mockRule.Object); - - mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); - mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) - .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); - - var processor = new ACLProcessor(mockLDAPUtils.Object, true); - var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); - var result = processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArray(); - - Assert.Single(result); - var actual = result.First(); - Assert.Equal(actual.PrincipalType, expectedPrincipalType); - Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); - Assert.Equal(actual.IsInherited, false); - Assert.Equal(actual.RightName, expectedRightName); - } - - [Fact] - public void ACLProcessor_ProcessACL_ExtendedRight_Computer_NoLAPS() - { - var expectedPrincipalType = Label.Group; - var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; - var expectedRightName = EdgeNames.AllExtendedRights; - - var mockLDAPUtils = new Mock(); - var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - var mockRule = new Mock(MockBehavior.Loose, null); - var collection = new List(); - mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); - mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); - mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); - mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); - mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); - collection.Add(mockRule.Object); - - mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); - mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) - .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); - - var processor = new ACLProcessor(mockLDAPUtils.Object, true); - var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); - var result = processor.ProcessACL(bytes, _testDomainName, Label.Computer, false).ToArray(); - - Assert.Empty(result); - } - - [Fact] - public void ACLProcessor_ProcessACL_ExtendedRight_Computer_All() - { - var expectedPrincipalType = Label.Group; - var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; - var expectedRightName = EdgeNames.AllExtendedRights; - - var mockLDAPUtils = new Mock(); - var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - var mockRule = new Mock(MockBehavior.Loose, null); - var collection = new List(); - mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); - mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); - mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); - mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); - mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); - collection.Add(mockRule.Object); - - mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); - mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) - .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); - - var processor = new ACLProcessor(mockLDAPUtils.Object, true); - var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); - var result = processor.ProcessACL(bytes, _testDomainName, Label.Computer, true).ToArray(); - - Assert.Single(result); - var actual = result.First(); - Assert.Equal(actual.PrincipalType, expectedPrincipalType); - Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); - Assert.Equal(actual.IsInherited, false); - Assert.Equal(actual.RightName, expectedRightName); - } - - [Fact(Skip = "Need to populate cache to reach this case")] - public void ACLProcessor_ProcessACL_ExtendedRight_Computer_MappedGuid() - { - } - - [Fact] - public void ACLProcessor_ProcessACL_GenericWrite_Unmatched() - { - var expectedPrincipalType = Label.Group; - var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; - var expectedRightName = EdgeNames.AllExtendedRights; - - var mockLDAPUtils = new Mock(); - var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - var mockRule = new Mock(MockBehavior.Loose, null); - var collection = new List(); - mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); - mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); - mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); - mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.GenericWrite); - mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); - collection.Add(mockRule.Object); - - mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); - mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) - .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); - - var processor = new ACLProcessor(mockLDAPUtils.Object, true); - var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); - var result = processor.ProcessACL(bytes, _testDomainName, Label.Container, true).ToArray(); - - Assert.Empty(result); - } - - [Fact] - public void ACLProcessor_ProcessACL_GenericWrite_User_All() - { - var expectedPrincipalType = Label.Group; - var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; - var expectedRightName = EdgeNames.GenericWrite; - - var mockLDAPUtils = new Mock(); - var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - var mockRule = new Mock(MockBehavior.Loose, null); - var collection = new List(); - mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); - mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); - mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); - mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.GenericWrite); - mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); - collection.Add(mockRule.Object); - - mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); - mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) - .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); - - var processor = new ACLProcessor(mockLDAPUtils.Object, true); - var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); - var result = processor.ProcessACL(bytes, _testDomainName, Label.User, true).ToArray(); - - Assert.Single(result); - var actual = result.First(); - Assert.Equal(actual.PrincipalType, expectedPrincipalType); - Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); - Assert.Equal(actual.IsInherited, false); - Assert.Equal(actual.RightName, expectedRightName); - } - - [Fact] - public void ACLProcessor_ProcessACL_GenericWrite_User_WriteMember() - { - var expectedPrincipalType = Label.Group; - var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; - var expectedRightName = EdgeNames.AddMember; - - var mockLDAPUtils = new Mock(); - var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - var mockRule = new Mock(MockBehavior.Loose, null); - var collection = new List(); - mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); - mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); - mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); - mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.GenericWrite); - mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.WriteMember)); - collection.Add(mockRule.Object); - - mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); - mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) - .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); - - var processor = new ACLProcessor(mockLDAPUtils.Object, true); - var bytes = Helpers.B64ToBytes(AddMemberSecurityDescriptor); - var result = processor.ProcessACL(bytes, _testDomainName, Label.Group, true).ToArray(); - - _testOutputHelper.WriteLine(JsonConvert.SerializeObject(result)); - - Assert.Single(result); - var actual = result.First(); - Assert.Equal(actual.PrincipalType, expectedPrincipalType); - Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); - Assert.Equal(actual.IsInherited, false); - Assert.Equal(actual.RightName, expectedRightName); - } - - [Fact] - public void ACLProcessor_ProcessACL_GenericWrite_Computer_WriteAllowedToAct() - { - var expectedPrincipalType = Label.Group; - var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; - var expectedRightName = EdgeNames.AddAllowedToAct; - - var mockLDAPUtils = new Mock(); - var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - var mockRule = new Mock(MockBehavior.Loose, null); - var collection = new List(); - mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); - mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); - mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); - mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.GenericWrite); - mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.WriteAllowedToAct)); - collection.Add(mockRule.Object); - - mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); - mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) - .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); - - var processor = new ACLProcessor(mockLDAPUtils.Object, true); - var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); - var result = processor.ProcessACL(bytes, _testDomainName, Label.Computer, true).ToArray(); - - Assert.Single(result); - var actual = result.First(); - Assert.Equal(actual.PrincipalType, expectedPrincipalType); - Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); - Assert.Equal(actual.IsInherited, false); - Assert.Equal(actual.RightName, expectedRightName); - } - } -} \ No newline at end of file +// using System; +// using System.Collections.Generic; +// using System.DirectoryServices; +// using System.Linq; +// using System.Security.AccessControl; +// using CommonLibTest.Facades; +// using Moq; +// using Newtonsoft.Json; +// using SharpHoundCommonLib; +// using SharpHoundCommonLib.Enums; +// using SharpHoundCommonLib.OutputTypes; +// using SharpHoundCommonLib.Processors; +// using Xunit; +// using Xunit.Abstractions; +// +// namespace CommonLibTest +// { +// public class ACLProcessorTest : IDisposable +// { +// private const string ProtectedUserNTSecurityDescriptor = +// "AQAEnIgEAAAAAAAAAAAAABQAAAAEAHQEGAAAAAUAPAAQAAAAAwAAAABCFkzAINARp2gAqgBuBSkUzChINxS8RZsHrW8BXl8oAQIAAAAAAAUgAAAAKgIAAAUAPAAQAAAAAwAAAABCFkzAINARp2gAqgBuBSm6epa/5g3QEaKFAKoAMEniAQIAAAAAAAUgAAAAKgIAAAUAPAAQAAAAAwAAABAgIF+ledARkCAAwE/C1M8UzChINxS8RZsHrW8BXl8oAQIAAAAAAAUgAAAAKgIAAAUAPAAQAAAAAwAAABAgIF+ledARkCAAwE/C1M+6epa/5g3QEaKFAKoAMEniAQIAAAAAAAUgAAAAKgIAAAUAPAAQAAAAAwAAAEDCCrypedARkCAAwE/C1M8UzChINxS8RZsHrW8BXl8oAQIAAAAAAAUgAAAAKgIAAAUAPAAQAAAAAwAAAEDCCrypedARkCAAwE/C1M+6epa/5g3QEaKFAKoAMEniAQIAAAAAAAUgAAAAKgIAAAUAPAAQAAAAAwAAAEIvulmiedARkCAAwE/C088UzChINxS8RZsHrW8BXl8oAQIAAAAAAAUgAAAAKgIAAAUAPAAQAAAAAwAAAEIvulmiedARkCAAwE/C08+6epa/5g3QEaKFAKoAMEniAQIAAAAAAAUgAAAAKgIAAAUAPAAQAAAAAwAAAPiIcAPhCtIRtCIAoMlo+TkUzChINxS8RZsHrW8BXl8oAQIAAAAAAAUgAAAAKgIAAAUAPAAQAAAAAwAAAPiIcAPhCtIRtCIAoMlo+Tm6epa/5g3QEaKFAKoAMEniAQIAAAAAAAUgAAAAKgIAAAUAOAAwAAAAAQAAAH96lr/mDdARooUAqgAwSeIBBQAAAAAABRUAAAAgT5C6f0aEpXZIFpAFAgAABQAsABAAAAABAAAAHbGpRq5gWkC36P+KWNRW0gECAAAAAAAFIAAAADACAAAFACwAMAAAAAEAAAAcmrZtIpTREa69AAD4A2fBAQIAAAAAAAUgAAAAMQIAAAUALAAwAAAAAQAAAGK8BVjJvShEpeKFag9MGF4BAgAAAAAABSAAAAAxAgAABQAsAJQAAgACAAAAFMwoSDcUvEWbB61vAV5fKAECAAAAAAAFIAAAACoCAAAFACwAlAACAAIAAAC6epa/5g3QEaKFAKoAMEniAQIAAAAAAAUgAAAAKgIAAAUAKAAAAQAAAQAAAFMacqsvHtARmBkAqgBAUpsBAQAAAAAAAQAAAAAFACgAAAEAAAEAAABTGnKrLx7QEZgZAKoAQFKbAQEAAAAAAAUKAAAABQIoADABAAABAAAA3kfmkW/ZcEuVV9Y/9PPM2AEBAAAAAAAFCgAAAAAAJAC/AQ4AAQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQAAIAAAAAJAC/AQ4AAQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQBwIAAAAAGAC/AQ8AAQIAAAAAAAUgAAAAIAIAAAAAFACUAAIAAQEAAAAAAAULAAAAAAAUAP8BDwABAQAAAAAABRIAAAABBQAAAAAABRUAAAAgT5C6f0aEpXZIFpAAAgAA"; +// +// private const string UnProtectedUserNtSecurityDescriptor = +// "AQAEjJgGAAAAAAAAAAAAABQAAAAEAIQGJwAAAAUAOAAQAAAAAQAAAABCFkzAINARp2gAqgBuBSkBBQAAAAAABRUAAAAgT5C6f0aEpXZIFpApAgAABQA4ABAAAAABAAAAECAgX6V50BGQIADAT8LUzwEFAAAAAAAFFQAAACBPkLp/RoSldkgWkCkCAAAFADgAEAAAAAEAAABAwgq8qXnQEZAgAMBPwtTPAQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQKQIAAAUAOAAQAAAAAQAAAPiIcAPhCtIRtCIAoMlo+TkBBQAAAAAABRUAAAAgT5C6f0aEpXZIFpApAgAABQA4ADAAAAABAAAAf3qWv+YN0BGihQCqADBJ4gEFAAAAAAAFFQAAACBPkLp/RoSldkgWkAUCAAAFACwAEAAAAAEAAAAdsalGrmBaQLfo/4pY1FbSAQIAAAAAAAUgAAAAMAIAAAUALAAwAAAAAQAAAByatm0ilNERrr0AAPgDZ8EBAgAAAAAABSAAAAAxAgAABQAsADAAAAABAAAAYrwFWMm9KESl4oVqD0wYXgECAAAAAAAFIAAAADECAAAFACgAAAEAAAEAAABTGnKrLx7QEZgZAKoAQFKbAQEAAAAAAAEAAAAABQAoAAABAAABAAAAUxpyqy8e0BGYGQCqAEBSmwEBAAAAAAAFCgAAAAUAKAAAAQAAAQAAAFQacqsvHtARmBkAqgBAUpsBAQAAAAAABQoAAAAFACgAAAEAAAEAAABWGnKrLx7QEZgZAKoAQFKbAQEAAAAAAAUKAAAABQAoABAAAAABAAAAQi+6WaJ50BGQIADAT8LTzwEBAAAAAAAFCwAAAAUAKAAQAAAAAQAAAFQBjeT4vNERhwIAwE+5YFABAQAAAAAABQsAAAAFACgAEAAAAAEAAACGuLV3SpTREa69AAD4A2fBAQEAAAAAAAULAAAABQAoABAAAAABAAAAs5VX5FWU0RGuvQAA+ANnwQEBAAAAAAAFCwAAAAUAKAAwAAAAAQAAAIa4tXdKlNERrr0AAPgDZ8EBAQAAAAAABQoAAAAFACgAMAAAAAEAAACylVfkVZTREa69AAD4A2fBAQEAAAAAAAUKAAAABQAoADAAAAABAAAAs5VX5FWU0RGuvQAA+ANnwQEBAAAAAAAFCgAAAAAAJAD/AQ8AAQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQAAIAAAAAGAD/AQ8AAQIAAAAAAAUgAAAAJAIAAAAAFAAAAAIAAQEAAAAAAAULAAAAAAAUAJQAAgABAQAAAAAABQoAAAAAABQA/wEPAAEBAAAAAAAFEgAAAAUSOAAAAQAAAQAAAKr2MREHnNER958AwE/C3NIBBQAAAAAABRUAAAAgT5C6f0aEpXZIFpBKCAAABRI4AAABAAABAAAArfYxEQec0RH3nwDAT8Lc0gEFAAAAAAAFFQAAACBPkLp/RoSldkgWkD8IAAAFEjgAAAEAAAEAAACt9jERB5zREfefAMBPwtzSAQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQSggAAAUaOAAQAAAAAwAAAG2exrfHLNIRhU4AoMmD9giGepa/5g3QEaKFAKoAMEniAQEAAAAAAAUJAAAABRo4ABAAAAADAAAAbZ7Gt8cs0hGFTgCgyYP2CJx6lr/mDdARooUAqgAwSeIBAQAAAAAABQkAAAAFEjgAEAAAAAMAAABtnsa3xyzSEYVOAKDJg/YIunqWv+YN0BGihQCqADBJ4gEBAAAAAAAFCQAAAAUaOAAgAAAAAwAAAJN7G+pIXtVGvGxN9P2nijWGepa/5g3QEaKFAKoAMEniAQEAAAAAAAUKAAAABRosAJQAAgACAAAAFMwoSDcUvEWbB61vAV5fKAECAAAAAAAFIAAAACoCAAAFGiwAlAACAAIAAACcepa/5g3QEaKFAKoAMEniAQIAAAAAAAUgAAAAKgIAAAUSLACUAAIAAgAAALp6lr/mDdARooUAqgAwSeIBAgAAAAAABSAAAAAqAgAABRIoADAAAAABAAAA5cN4P5r3vUaguJ0YEW3ceQEBAAAAAAAFCgAAAAUSKAAwAQAAAQAAAN5H5pFv2XBLlVfWP/TzzNgBAQAAAAAABQoAAAAAEiQA/wEPAAEFAAAAAAAFFQAAACBPkLp/RoSldkgWkAcCAAAAEhgABAAAAAECAAAAAAAFIAAAACoCAAAAEhgAvQEPAAECAAAAAAAFIAAAACACAAABBQAAAAAABRUAAAAgT5C6f0aEpXZIFpAAAgAA"; +// +// private const string GMSAProperty = +// "AQAEgEAAAAAAAAAAAAAAABQAAAAEACwAAQAAAAAAJAD/AQ8AAQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQ9AEAAAECAAAAAAAFIAAAACACAAA\u003d"; +// +// private const string AddMemberSecurityDescriptor = +// "AQAEjGADAAAAAAAAAAAAABQAAAAEAEwDFQAAAAUAOAAIAAAAAQAAAMB5lr/mDdARooUAqgAwSeIBBQAAAAAABRUAAAAgT5C6f0aEpXZIFpAuCgAABQA4ACAAAAABAAAAwHmWv+YN0BGihQCqADBJ4gEFAAAAAAAFFQAAACBPkLp/RoSldkgWkEcIAAAFACwAEAAAAAEAAAAdsalGrmBaQLfo/4pY1FbSAQIAAAAAAAUgAAAAMAIAAAUAKAAAAQAAAQAAAFUacqsvHtARmBkAqgBAUpsBAQAAAAAABQsAAAAAACQA/wEPAAEFAAAAAAAFFQAAACBPkLp/RoSldkgWkAACAAAAABgA/wEPAAECAAAAAAAFIAAAACQCAAAAABQAlAACAAEBAAAAAAAFCgAAAAAAFACUAAIAAQEAAAAAAAULAAAAAAAUAP8BDwABAQAAAAAABRIAAAAFGjgAEAAAAAMAAABtnsa3xyzSEYVOAKDJg/YIhnqWv+YN0BGihQCqADBJ4gEBAAAAAAAFCQAAAAUSOAAQAAAAAwAAAG2exrfHLNIRhU4AoMmD9gicepa/5g3QEaKFAKoAMEniAQEAAAAAAAUJAAAABRo4ABAAAAADAAAAbZ7Gt8cs0hGFTgCgyYP2CLp6lr/mDdARooUAqgAwSeIBAQAAAAAABQkAAAAFGjgAIAAAAAMAAACTexvqSF7VRrxsTfT9p4o1hnqWv+YN0BGihQCqADBJ4gEBAAAAAAAFCgAAAAUaLACUAAIAAgAAABTMKEg3FLxFmwetbwFeXygBAgAAAAAABSAAAAAqAgAABRIsAJQAAgACAAAAnHqWv+YN0BGihQCqADBJ4gECAAAAAAAFIAAAACoCAAAFGiwAlAACAAIAAAC6epa/5g3QEaKFAKoAMEniAQIAAAAAAAUgAAAAKgIAAAUSKAAwAAAAAQAAAOXDeD+a971GoLidGBFt3HkBAQAAAAAABQoAAAAFEigAMAEAAAEAAADeR+aRb9lwS5VX1j/088zYAQEAAAAAAAUKAAAAABIkAP8BDwABBQAAAAAABRUAAAAgT5C6f0aEpXZIFpAHAgAAABIYAAQAAAABAgAAAAAABSAAAAAqAgAAABIYAL0BDwABAgAAAAAABSAAAAAgAgAAAQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQAAIAAA=="; +// +// private readonly ACLProcessor _baseProcessor; +// +// private readonly string _testDomainName; +// private readonly ITestOutputHelper _testOutputHelper; +// +// public ACLProcessorTest(ITestOutputHelper testOutputHelper) +// { +// _testOutputHelper = testOutputHelper; +// _testDomainName = "TESTLAB.LOCAL"; +// _baseProcessor = new ACLProcessor(new LDAPUtils()); +// } +// +// public void Dispose() +// { +// } +// +// [Fact] +// public void SanityCheck() +// { +// Assert.True(true); +// } +// +// [Fact] +// public void ACLProcessor_IsACLProtected_NullNTSD_ReturnsFalse() +// { +// var processor = new ACLProcessor(new MockLDAPUtils(), true); +// var result = processor.IsACLProtected((byte[])null); +// Assert.False(result); +// } +// +// [WindowsOnlyFact] +// public void ACLProcessor_TestKnownDataAddMember() +// { +// var mockLdapUtils = new MockLDAPUtils(); +// var mockUtils = new Mock(); +// mockUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) +// .Returns((string a, string b) => mockLdapUtils.ResolveIDAndType(a, b)); +// var sd = new ActiveDirectorySecurityDescriptor(new ActiveDirectorySecurity()); +// mockUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(sd); +// +// var processor = new ACLProcessor(mockUtils.Object, true); +// var bytes = Helpers.B64ToBytes(AddMemberSecurityDescriptor); +// var result = processor.ProcessACL(bytes, "TESTLAB.LOCAL", Label.Group, false); +// +// _testOutputHelper.WriteLine(JsonConvert.SerializeObject(result)); +// +// Assert.Contains(result, +// x => x.RightName == EdgeNames.AddSelf && +// x.PrincipalSID == "S-1-5-21-3130019616-2776909439-2417379446-2606"); +// Assert.Contains(result, +// x => x.RightName == EdgeNames.AddMember && +// x.PrincipalSID == "S-1-5-21-3130019616-2776909439-2417379446-2119"); +// } +// +// [Fact] +// public void ACLProcessor_IsACLProtected_ReturnsTrue() +// { +// var mockLDAPUtils = new Mock(); +// var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); +// mockSecurityDescriptor.Setup(x => x.AreAccessRulesProtected()).Returns(true); +// mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); +// +// var processor = new ACLProcessor(mockLDAPUtils.Object, true); +// var bytes = Helpers.B64ToBytes(ProtectedUserNTSecurityDescriptor); +// var result = processor.IsACLProtected(bytes); +// Assert.True(result); +// } +// +// [Fact] +// public void ACLProcessor_IsACLProtected_ReturnsFalse() +// { +// var mockLDAPUtils = new Mock(); +// var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); +// mockSecurityDescriptor.Setup(m => m.AreAccessRulesProtected()).Returns(false); +// mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); +// +// var processor = new ACLProcessor(mockLDAPUtils.Object, true); +// var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); +// var result = processor.IsACLProtected(bytes); +// Assert.False(result); +// } +// +// [Fact] +// public void ACLProcessor_ProcessGMSAReaders_NullNTSD_ReturnsNothing() +// { +// var test = _baseProcessor.ProcessGMSAReaders(null, ""); +// Assert.Empty(test); +// } +// +// [Fact] +// public void ACLProcess_ProcessGMSAReaders_YieldsCorrectAce() +// { +// var expectedRightName = "ReadGMSAPassword"; +// var expectedSID = "S-1-5-21-3130019616-2776909439-2417379446-500"; +// var expectedPrincipalType = Label.User; +// var expectedInheritance = false; +// +// var mockLDAPUtils = new Mock(); +// var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); +// var mockRule = new Mock(MockBehavior.Loose, null); +// +// mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); +// mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); +// mockRule.Setup(x => x.IdentityReference()).Returns(expectedSID); +// +// var collection = new List(); +// collection.Add(mockRule.Object); +// +// mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) +// .Returns(collection); +// mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); +// mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) +// .Returns(new TypedPrincipal(expectedSID, expectedPrincipalType)); +// +// var processor = new ACLProcessor(mockLDAPUtils.Object, true); +// var bytes = Helpers.B64ToBytes(GMSAProperty); +// var result = processor.ProcessGMSAReaders(bytes, _testDomainName).ToArray(); +// +// Assert.Single(result); +// var actual = result.First(); +// _testOutputHelper.WriteLine(actual.ToString()); +// Assert.Equal(expectedRightName, actual.RightName); +// Assert.Equal(expectedSID, actual.PrincipalSID); +// Assert.Equal(expectedPrincipalType, actual.PrincipalType); +// Assert.Equal(expectedInheritance, actual.IsInherited); +// } +// +// [Fact] +// public void ACLProcessor_ProcessGMSAReaders_Null_ACE() +// { +// var mockLDAPUtils = new Mock(); +// var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); +// var collection = new List(); +// collection.Add(null); +// +// mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) +// .Returns(collection); +// mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); +// +// var processor = new ACLProcessor(mockLDAPUtils.Object, true); +// var bytes = Helpers.B64ToBytes(GMSAProperty); +// var result = processor.ProcessGMSAReaders(bytes, _testDomainName).ToArray(); +// +// Assert.Empty(result); +// } +// +// [Fact] +// public void ACLProcessor_ProcessGMSAReaders_Deny_ACE() +// { +// var mockLDAPUtils = new Mock(); +// var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); +// var mockRule = new Mock(MockBehavior.Loose, null); +// var collection = new List(); +// +// mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Deny); +// collection.Add(mockRule.Object); +// +// mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) +// .Returns(collection); +// mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); +// +// var processor = new ACLProcessor(mockLDAPUtils.Object, true); +// var bytes = Helpers.B64ToBytes(GMSAProperty); +// var result = processor.ProcessGMSAReaders(bytes, _testDomainName).ToArray(); +// +// Assert.Empty(result); +// } +// +// [Fact] +// public void ACLProcessor_ProcessGMSAReaders_Null_PrincipalID() +// { +// var mockLDAPUtils = new Mock(); +// var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); +// var mockRule = new Mock(MockBehavior.Loose, null); +// var collection = new List(); +// +// mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); +// mockRule.Setup(x => x.IdentityReference()).Returns((string)null); +// collection.Add(mockRule.Object); +// +// mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) +// .Returns(collection); +// mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); +// +// var processor = new ACLProcessor(mockLDAPUtils.Object, true); +// var bytes = Helpers.B64ToBytes(GMSAProperty); +// var result = processor.ProcessGMSAReaders(bytes, _testDomainName).ToArray(); +// +// Assert.Empty(result); +// } +// +// [Fact] +// public void ACLProcessor_ProcessACL_Null_NTSecurityDescriptor() +// { +// var processor = new ACLProcessor(new MockLDAPUtils(), true); +// var result = processor.ProcessACL(null, _testDomainName, Label.User, false).ToArray(); +// +// Assert.Empty(result); +// } +// +// [Fact] +// public void ACLProcessor_ProcessACL_Yields_Owns_ACE() +// { +// var expectedSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; +// var expectedPrincipalType = Label.Group; +// +// var mockLDAPUtils = new Mock(); +// var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); +// var collection = new List(); +// +// mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) +// .Returns(collection); +// mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns(expectedSID); +// mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); +// mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) +// .Returns(new TypedPrincipal(expectedSID, expectedPrincipalType)); +// +// var processor = new ACLProcessor(mockLDAPUtils.Object, true); +// var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); +// var result = processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArray(); +// +// Assert.Single(result); +// var actual = result.First(); +// Assert.Equal(actual.PrincipalSID, expectedSID); +// Assert.Equal(actual.PrincipalType, expectedPrincipalType); +// Assert.Equal(actual.IsInherited, false); +// Assert.Equal(actual.RightName, EdgeNames.Owns); +// } +// +// [Fact] +// public void ACLProcessor_ProcessACL_Null_SID() +// { +// var mockLDAPUtils = new Mock(); +// var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); +// var collection = new List(); +// +// mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) +// .Returns(collection); +// mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); +// mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); +// +// var processor = new ACLProcessor(mockLDAPUtils.Object, true); +// var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); +// var result = processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArray(); +// +// Assert.Empty(result); +// } +// +// [Fact] +// public void ACLProcessor_ProcessACL_Null_ACE() +// { +// var mockLDAPUtils = new Mock(); +// var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); +// var collection = new List(); +// collection.Add(null); +// +// mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) +// .Returns(collection); +// mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); +// mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); +// +// var processor = new ACLProcessor(mockLDAPUtils.Object, true); +// var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); +// var result = processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArray(); +// +// Assert.Empty(result); +// } +// +// [Fact] +// public void ACLProcessor_ProcessACL_Deny_ACE() +// { +// var mockLDAPUtils = new Mock(); +// var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); +// var mockRule = new Mock(MockBehavior.Loose, null); +// var collection = new List(); +// mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Deny); +// collection.Add(mockRule.Object); +// +// mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) +// .Returns(collection); +// mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); +// mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); +// +// var processor = new ACLProcessor(mockLDAPUtils.Object, true); +// var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); +// var result = processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArray(); +// +// Assert.Empty(result); +// } +// +// [Fact] +// public void ACLProcessor_ProcessACL_Unmatched_Inheritance_ACE() +// { +// var mockLDAPUtils = new Mock(); +// var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); +// var mockRule = new Mock(MockBehavior.Loose, null); +// var collection = new List(); +// mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); +// mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(false); +// collection.Add(mockRule.Object); +// +// mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) +// .Returns(collection); +// mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); +// mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); +// +// var processor = new ACLProcessor(mockLDAPUtils.Object, true); +// var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); +// var result = processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArray(); +// +// Assert.Empty(result); +// } +// +// [Fact] +// public void ACLProcessor_ProcessACL_Null_SID_ACE() +// { +// var mockLDAPUtils = new Mock(); +// var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); +// var mockRule = new Mock(MockBehavior.Loose, null); +// var collection = new List(); +// mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); +// mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); +// mockRule.Setup(x => x.IdentityReference()).Returns((string)null); +// collection.Add(mockRule.Object); +// +// mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) +// .Returns(collection); +// mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); +// mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); +// +// var processor = new ACLProcessor(mockLDAPUtils.Object, true); +// var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); +// var result = processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArray(); +// +// Assert.Empty(result); +// } +// +// [Fact] +// public void ACLProcessor_ProcessACL_GenericAll_Unmatched_Guid() +// { +// var expectedPrincipalType = Label.Group; +// var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; +// var unmatchedGuid = new Guid("583991c8-629d-4a07-8a70-74d19d22ac9c"); +// +// var mockLDAPUtils = new Mock(); +// var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); +// var mockRule = new Mock(MockBehavior.Loose, null); +// var collection = new List(); +// mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); +// mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); +// mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); +// mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.GenericAll); +// mockRule.Setup(x => x.ObjectType()).Returns(unmatchedGuid); +// collection.Add(mockRule.Object); +// +// mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) +// .Returns(collection); +// mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); +// mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); +// mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) +// .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); +// +// var processor = new ACLProcessor(mockLDAPUtils.Object, true); +// var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); +// var result = processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArray(); +// +// Assert.Empty(result); +// } +// +// [Fact] +// public void ACLProcessor_ProcessACL_GenericAll() +// { +// var expectedPrincipalType = Label.Group; +// var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; +// +// var mockLDAPUtils = new Mock(); +// var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); +// var mockRule = new Mock(MockBehavior.Loose, null); +// var collection = new List(); +// mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); +// mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); +// mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); +// mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.GenericAll); +// mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); +// collection.Add(mockRule.Object); +// +// mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) +// .Returns(collection); +// mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); +// mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); +// mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) +// .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); +// +// var processor = new ACLProcessor(mockLDAPUtils.Object, true); +// var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); +// var result = processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArray(); +// +// Assert.Single(result); +// var actual = result.First(); +// Assert.Equal(actual.PrincipalType, expectedPrincipalType); +// Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); +// Assert.Equal(actual.IsInherited, false); +// Assert.Equal(actual.RightName, EdgeNames.GenericAll); +// } +// +// [Fact] +// public void ACLProcessor_ProcessACL_WriteDacl() +// { +// var expectedPrincipalType = Label.Group; +// var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; +// var expectedRightName = ActiveDirectoryRights.WriteDacl; +// +// var mockLDAPUtils = new Mock(); +// var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); +// var mockRule = new Mock(MockBehavior.Loose, null); +// var collection = new List(); +// mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); +// mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); +// mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); +// mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(expectedRightName); +// mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); +// collection.Add(mockRule.Object); +// +// mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) +// .Returns(collection); +// mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); +// mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); +// mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) +// .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); +// +// var processor = new ACLProcessor(mockLDAPUtils.Object, true); +// var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); +// var result = processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArray(); +// +// Assert.Single(result); +// var actual = result.First(); +// Assert.Equal(actual.PrincipalType, expectedPrincipalType); +// Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); +// Assert.Equal(actual.IsInherited, false); +// Assert.Equal(actual.RightName, expectedRightName.ToString()); +// } +// +// [Fact] +// public void ACLProcessor_ProcessACL_WriteOwner() +// { +// var expectedPrincipalType = Label.Group; +// var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; +// var expectedRightName = ActiveDirectoryRights.WriteOwner; +// +// var mockLDAPUtils = new Mock(); +// var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); +// var mockRule = new Mock(MockBehavior.Loose, null); +// var collection = new List(); +// mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); +// mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); +// mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); +// mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(expectedRightName); +// mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); +// collection.Add(mockRule.Object); +// +// mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) +// .Returns(collection); +// mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); +// mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); +// mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) +// .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); +// +// var processor = new ACLProcessor(mockLDAPUtils.Object, true); +// var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); +// var result = processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArray(); +// +// Assert.Single(result); +// var actual = result.First(); +// Assert.Equal(actual.PrincipalType, expectedPrincipalType); +// Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); +// Assert.Equal(actual.IsInherited, false); +// Assert.Equal(actual.RightName, expectedRightName.ToString()); +// } +// +// [Fact] +// public void ACLProcessor_ProcessACL_Self() +// { +// var expectedPrincipalType = Label.Group; +// var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; +// var expectedRightName = EdgeNames.AddSelf; +// +// var mockLDAPUtils = new Mock(); +// var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); +// var mockRule = new Mock(MockBehavior.Loose, null); +// var collection = new List(); +// mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); +// mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); +// mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); +// mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.Self); +// mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.WriteMember)); +// collection.Add(mockRule.Object); +// +// mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) +// .Returns(collection); +// mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); +// mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); +// mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) +// .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); +// +// var processor = new ACLProcessor(mockLDAPUtils.Object, true); +// var bytes = Helpers.B64ToBytes(AddMemberSecurityDescriptor); +// var result = processor.ProcessACL(bytes, _testDomainName, Label.Group, false).ToArray(); +// +// Assert.Single(result); +// var actual = result.First(); +// Assert.Equal(actual.PrincipalType, expectedPrincipalType); +// Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); +// Assert.Equal(actual.IsInherited, false); +// Assert.Equal(actual.RightName, expectedRightName); +// } +// +// [Fact] +// public void ACLProcessor_ProcessACL_ExtendedRight_Domain_Unmatched() +// { +// var expectedPrincipalType = Label.Group; +// var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; +// +// var mockLDAPUtils = new Mock(); +// var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); +// var mockRule = new Mock(MockBehavior.Loose, null); +// var collection = new List(); +// mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); +// mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); +// mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); +// mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); +// mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.WriteMember)); +// collection.Add(mockRule.Object); +// +// mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) +// .Returns(collection); +// mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); +// mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); +// mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) +// .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); +// +// var processor = new ACLProcessor(mockLDAPUtils.Object, true); +// var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); +// var result = processor.ProcessACL(bytes, _testDomainName, Label.Domain, false).ToArray(); +// +// Assert.Empty(result); +// } +// +// [Fact] +// public void ACLProcessor_ProcessACL_ExtendedRight_Domain_DSReplicationGetChanges() +// { +// var expectedPrincipalType = Label.Group; +// var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; +// var expectedRightName = EdgeNames.GetChanges; +// +// var mockLDAPUtils = new Mock(); +// var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); +// var mockRule = new Mock(MockBehavior.Loose, null); +// var collection = new List(); +// mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); +// mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); +// mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); +// mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); +// mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.DSReplicationGetChanges)); +// collection.Add(mockRule.Object); +// +// mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) +// .Returns(collection); +// mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); +// mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); +// mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) +// .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); +// +// var processor = new ACLProcessor(mockLDAPUtils.Object, true); +// var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); +// var result = processor.ProcessACL(bytes, _testDomainName, Label.Domain, false).ToArray(); +// +// Assert.Single(result); +// var actual = result.First(); +// Assert.Equal(actual.PrincipalType, expectedPrincipalType); +// Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); +// Assert.Equal(actual.IsInherited, false); +// Assert.Equal(actual.RightName, expectedRightName); +// } +// +// [Fact] +// public void ACLProcessor_ProcessACL_ExtendedRight_Domain_All() +// { +// var expectedPrincipalType = Label.Group; +// var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; +// var expectedRightName = EdgeNames.AllExtendedRights; +// +// var mockLDAPUtils = new Mock(); +// var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); +// var mockRule = new Mock(MockBehavior.Loose, null); +// var collection = new List(); +// mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); +// mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); +// mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); +// mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); +// mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); +// collection.Add(mockRule.Object); +// +// mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) +// .Returns(collection); +// mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); +// mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); +// mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) +// .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); +// +// var processor = new ACLProcessor(mockLDAPUtils.Object, true); +// var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); +// var result = processor.ProcessACL(bytes, _testDomainName, Label.Domain, false).ToArray(); +// +// Assert.Single(result); +// var actual = result.First(); +// Assert.Equal(actual.PrincipalType, expectedPrincipalType); +// Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); +// Assert.Equal(actual.IsInherited, false); +// Assert.Equal(actual.RightName, expectedRightName); +// } +// +// [Fact] +// public void ACLProcessor_ProcessACL_ExtendedRight_Domain_DSReplicationGetChangesAll() +// { +// var expectedPrincipalType = Label.Group; +// var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; +// var expectedRightName = EdgeNames.GetChangesAll; +// +// var mockLDAPUtils = new Mock(); +// var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); +// var mockRule = new Mock(MockBehavior.Loose, null); +// var collection = new List(); +// mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); +// mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); +// mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); +// mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); +// mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.DSReplicationGetChangesAll)); +// collection.Add(mockRule.Object); +// +// mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) +// .Returns(collection); +// mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); +// mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); +// mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) +// .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); +// +// var processor = new ACLProcessor(mockLDAPUtils.Object, true); +// var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); +// var result = processor.ProcessACL(bytes, _testDomainName, Label.Domain, false).ToArray(); +// +// Assert.Single(result); +// var actual = result.First(); +// Assert.Equal(actual.PrincipalType, expectedPrincipalType); +// Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); +// Assert.Equal(actual.IsInherited, false); +// Assert.Equal(actual.RightName, expectedRightName); +// } +// +// [Fact] +// public void ACLProcessor_ProcessACL_ExtendedRight_User_Unmatched() +// { +// var expectedPrincipalType = Label.Group; +// var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; +// var expectedRightName = EdgeNames.GetChangesAll; +// var unmatchedGuid = new Guid("583991c8-629d-4a07-8a70-74d19d22ac9c"); +// +// var mockLDAPUtils = new Mock(); +// var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); +// var mockRule = new Mock(MockBehavior.Loose, null); +// var collection = new List(); +// mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); +// mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); +// mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); +// mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); +// mockRule.Setup(x => x.ObjectType()).Returns(unmatchedGuid); +// collection.Add(mockRule.Object); +// +// mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) +// .Returns(collection); +// mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); +// mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); +// mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) +// .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); +// +// var processor = new ACLProcessor(mockLDAPUtils.Object, true); +// var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); +// var result = processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArray(); +// +// Assert.Empty(result); +// } +// +// [Fact] +// public void ACLProcessor_ProcessACL_ExtendedRight_User_UserForceChangePassword() +// { +// var expectedPrincipalType = Label.Group; +// var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; +// var expectedRightName = EdgeNames.ForceChangePassword; +// +// var mockLDAPUtils = new Mock(); +// var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); +// var mockRule = new Mock(MockBehavior.Loose, null); +// var collection = new List(); +// mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); +// mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); +// mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); +// mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); +// mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.UserForceChangePassword)); +// collection.Add(mockRule.Object); +// +// mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) +// .Returns(collection); +// mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); +// mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); +// mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) +// .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); +// +// var processor = new ACLProcessor(mockLDAPUtils.Object, true); +// var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); +// var result = processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArray(); +// +// Assert.Single(result); +// var actual = result.First(); +// Assert.Equal(actual.PrincipalType, expectedPrincipalType); +// Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); +// Assert.Equal(actual.IsInherited, false); +// Assert.Equal(actual.RightName, expectedRightName); +// } +// +// [Fact] +// public void ACLProcessor_ProcessACL_ExtendedRight_User_All() +// { +// var expectedPrincipalType = Label.Group; +// var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; +// var expectedRightName = EdgeNames.AllExtendedRights; +// +// var mockLDAPUtils = new Mock(); +// var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); +// var mockRule = new Mock(MockBehavior.Loose, null); +// var collection = new List(); +// mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); +// mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); +// mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); +// mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); +// mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); +// collection.Add(mockRule.Object); +// +// mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) +// .Returns(collection); +// mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); +// mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); +// mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) +// .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); +// +// var processor = new ACLProcessor(mockLDAPUtils.Object, true); +// var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); +// var result = processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArray(); +// +// Assert.Single(result); +// var actual = result.First(); +// Assert.Equal(actual.PrincipalType, expectedPrincipalType); +// Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); +// Assert.Equal(actual.IsInherited, false); +// Assert.Equal(actual.RightName, expectedRightName); +// } +// +// [Fact] +// public void ACLProcessor_ProcessACL_ExtendedRight_Computer_NoLAPS() +// { +// var expectedPrincipalType = Label.Group; +// var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; +// var expectedRightName = EdgeNames.AllExtendedRights; +// +// var mockLDAPUtils = new Mock(); +// var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); +// var mockRule = new Mock(MockBehavior.Loose, null); +// var collection = new List(); +// mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); +// mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); +// mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); +// mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); +// mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); +// collection.Add(mockRule.Object); +// +// mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) +// .Returns(collection); +// mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); +// mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); +// mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) +// .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); +// +// var processor = new ACLProcessor(mockLDAPUtils.Object, true); +// var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); +// var result = processor.ProcessACL(bytes, _testDomainName, Label.Computer, false).ToArray(); +// +// Assert.Empty(result); +// } +// +// [Fact] +// public void ACLProcessor_ProcessACL_ExtendedRight_Computer_All() +// { +// var expectedPrincipalType = Label.Group; +// var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; +// var expectedRightName = EdgeNames.AllExtendedRights; +// +// var mockLDAPUtils = new Mock(); +// var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); +// var mockRule = new Mock(MockBehavior.Loose, null); +// var collection = new List(); +// mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); +// mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); +// mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); +// mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); +// mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); +// collection.Add(mockRule.Object); +// +// mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) +// .Returns(collection); +// mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); +// mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); +// mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) +// .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); +// +// var processor = new ACLProcessor(mockLDAPUtils.Object, true); +// var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); +// var result = processor.ProcessACL(bytes, _testDomainName, Label.Computer, true).ToArray(); +// +// Assert.Single(result); +// var actual = result.First(); +// Assert.Equal(actual.PrincipalType, expectedPrincipalType); +// Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); +// Assert.Equal(actual.IsInherited, false); +// Assert.Equal(actual.RightName, expectedRightName); +// } +// +// [Fact(Skip = "Need to populate cache to reach this case")] +// public void ACLProcessor_ProcessACL_ExtendedRight_Computer_MappedGuid() +// { +// } +// +// [Fact] +// public void ACLProcessor_ProcessACL_GenericWrite_Unmatched() +// { +// var expectedPrincipalType = Label.Group; +// var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; +// var expectedRightName = EdgeNames.AllExtendedRights; +// +// var mockLDAPUtils = new Mock(); +// var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); +// var mockRule = new Mock(MockBehavior.Loose, null); +// var collection = new List(); +// mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); +// mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); +// mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); +// mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.GenericWrite); +// mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); +// collection.Add(mockRule.Object); +// +// mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) +// .Returns(collection); +// mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); +// mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); +// mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) +// .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); +// +// var processor = new ACLProcessor(mockLDAPUtils.Object, true); +// var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); +// var result = processor.ProcessACL(bytes, _testDomainName, Label.Container, true).ToArray(); +// +// Assert.Empty(result); +// } +// +// [Fact] +// public void ACLProcessor_ProcessACL_GenericWrite_User_All() +// { +// var expectedPrincipalType = Label.Group; +// var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; +// var expectedRightName = EdgeNames.GenericWrite; +// +// var mockLDAPUtils = new Mock(); +// var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); +// var mockRule = new Mock(MockBehavior.Loose, null); +// var collection = new List(); +// mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); +// mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); +// mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); +// mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.GenericWrite); +// mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); +// collection.Add(mockRule.Object); +// +// mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) +// .Returns(collection); +// mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); +// mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); +// mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) +// .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); +// +// var processor = new ACLProcessor(mockLDAPUtils.Object, true); +// var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); +// var result = processor.ProcessACL(bytes, _testDomainName, Label.User, true).ToArray(); +// +// Assert.Single(result); +// var actual = result.First(); +// Assert.Equal(actual.PrincipalType, expectedPrincipalType); +// Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); +// Assert.Equal(actual.IsInherited, false); +// Assert.Equal(actual.RightName, expectedRightName); +// } +// +// [Fact] +// public void ACLProcessor_ProcessACL_GenericWrite_User_WriteMember() +// { +// var expectedPrincipalType = Label.Group; +// var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; +// var expectedRightName = EdgeNames.AddMember; +// +// var mockLDAPUtils = new Mock(); +// var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); +// var mockRule = new Mock(MockBehavior.Loose, null); +// var collection = new List(); +// mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); +// mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); +// mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); +// mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.GenericWrite); +// mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.WriteMember)); +// collection.Add(mockRule.Object); +// +// mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) +// .Returns(collection); +// mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); +// mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); +// mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) +// .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); +// +// var processor = new ACLProcessor(mockLDAPUtils.Object, true); +// var bytes = Helpers.B64ToBytes(AddMemberSecurityDescriptor); +// var result = processor.ProcessACL(bytes, _testDomainName, Label.Group, true).ToArray(); +// +// _testOutputHelper.WriteLine(JsonConvert.SerializeObject(result)); +// +// Assert.Single(result); +// var actual = result.First(); +// Assert.Equal(actual.PrincipalType, expectedPrincipalType); +// Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); +// Assert.Equal(actual.IsInherited, false); +// Assert.Equal(actual.RightName, expectedRightName); +// } +// +// [Fact] +// public void ACLProcessor_ProcessACL_GenericWrite_Computer_WriteAllowedToAct() +// { +// var expectedPrincipalType = Label.Group; +// var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; +// var expectedRightName = EdgeNames.AddAllowedToAct; +// +// var mockLDAPUtils = new Mock(); +// var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); +// var mockRule = new Mock(MockBehavior.Loose, null); +// var collection = new List(); +// mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); +// mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); +// mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); +// mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.GenericWrite); +// mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.WriteAllowedToAct)); +// collection.Add(mockRule.Object); +// +// mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) +// .Returns(collection); +// mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); +// mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); +// mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) +// .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); +// +// var processor = new ACLProcessor(mockLDAPUtils.Object, true); +// var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); +// var result = processor.ProcessACL(bytes, _testDomainName, Label.Computer, true).ToArray(); +// +// Assert.Single(result); +// var actual = result.First(); +// Assert.Equal(actual.PrincipalType, expectedPrincipalType); +// Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); +// Assert.Equal(actual.IsInherited, false); +// Assert.Equal(actual.RightName, expectedRightName); +// } +// } +// } \ No newline at end of file diff --git a/test/unit/ContainerProcessorTest.cs b/test/unit/ContainerProcessorTest.cs index 35592b14..7fe91990 100644 --- a/test/unit/ContainerProcessorTest.cs +++ b/test/unit/ContainerProcessorTest.cs @@ -1,162 +1,162 @@ -using System; -using System.DirectoryServices.Protocols; -using System.Linq; -using CommonLibTest.Facades; -using Moq; -using SharpHoundCommonLib.Enums; -using SharpHoundCommonLib.OutputTypes; -using SharpHoundCommonLib.Processors; -using Xunit; -using Xunit.Abstractions; - -namespace CommonLibTest -{ - public class ContainerProcessorTest : IDisposable - { - private readonly string _testGpLinkString; - private readonly ITestOutputHelper _testOutputHelper; - - public ContainerProcessorTest(ITestOutputHelper testOutputHelper) - { - _testOutputHelper = testOutputHelper; - _testGpLinkString = - "[LDAP://cn={94DD0260-38B5-497E-8876-10E7A96E80D0},cn=policies,cn=system,DC=testlab,DC=local;0][LDAP://cn={C52F168C-CD05-4487-B405-564934DA8EFF},cn=policies,cn=system,DC=testlab,DC=local;0][LDAP://cn={1E860A30-603A-45C7-A768-26EE74BE6D5D},cn=policies,cn=system,DC=testlab,DC=local;0]"; - } - - public void Dispose() - { - } - - [Fact] - public void ContainerProcessor_ReadContainerGPLinks_IgnoresNull() - { - var processor = new ContainerProcessor(new MockLDAPUtils()); - var test = processor.ReadContainerGPLinks(null); - Assert.Empty(test); - } - - [Fact] - public void ContainerProcessor_ReadContainerGPLinks_UnresolvedGPLink_IsIgnored() - { - var processor = new ContainerProcessor(new MockLDAPUtils()); - //GPLink that doesn't exist - const string s = - "[LDAP://cn={94DD0260-38B5-497E-8876-ABCDEFG},cn=policies,cn=system,DC=testlab,DC=local;0]"; - var test = processor.ReadContainerGPLinks(s); - Assert.Empty(test); - } - - [Fact] - public void ContainerProcessor_ReadContainerGPLinks_ReturnsCorrectValues() - { - var processor = new ContainerProcessor(new MockLDAPUtils()); - var test = processor.ReadContainerGPLinks(_testGpLinkString).ToArray(); - - var expected = new GPLink[] - { - new() - { - GUID = "B39818AF-6349-401A-AE0A-E4972F5BF6D9", - IsEnforced = false - }, - new() - { - GUID = "ACDD64D3-67B3-401F-A6CC-804B3F7B1533", - IsEnforced = false - }, - new() - { - GUID = "C45E9585-4932-4C03-91A8-1856869D49AF", - IsEnforced = false - } - }; - - Assert.Equal(3, test.Length); - Assert.Equal(expected, test); - } - - [Fact] - public void ContainerProcessor_GetContainerChildObjects_ReturnsCorrectData() - { - var mock = new Mock(); - - var searchResults = new MockSearchResultEntry[] - { - //These first 4 should be filtered by our DN filters - new( - "CN=7868d4c8-ac41-4e05-b401-776280e8e9f1,CN=Operations,CN=DomainUpdates,CN=System,DC=testlab,DC=local" - , null, null, Label.Base), - new("CN=Microsoft,CN=Program Data,DC=testlab,DC=local", null, null, Label.Base), - new("CN=Operations,CN=DomainUpdates,CN=System,DC=testlab,DC=local", null, null, Label.Base), - new("CN=User,CN={C52F168C-CD05-4487-B405-564934DA8EFF},CN=Policies,CN=System,DC=testlab,DC=local", null, - null, Label.Base), - //This is a real object in our mock - new("CN=Users,DC=testlab,DC=local", null, "ECAD920E-8EB1-4E31-A80E-DD36367F81F4", Label.Container), - //This object does not exist in our mock - new("CN=Users,DC=testlab,DC=local", null, "ECAD920E-8EB1-4E31-A80E-DD36367F81FD", Label.Container), - //Test null objectid - new("CN=Users,DC=testlab,DC=local", null, null, Label.Container) - }; - - mock.Setup(x => x.QueryLDAP(It.IsAny(), It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny())).Returns(searchResults); - - var processor = new ContainerProcessor(mock.Object); - var test = processor.GetContainerChildObjects(_testGpLinkString).ToArray(); - - var expected = new TypedPrincipal[] - { - new() - { - ObjectIdentifier = "ECAD920E-8EB1-4E31-A80E-DD36367F81F4", - ObjectType = Label.Container - } - }; - - Assert.Single(test); - Assert.Equal(expected, test); - } - - [Fact] - public void ContainerProcessor_ReadBlocksInheritance_ReturnsCorrectValues() - { - var test = ContainerProcessor.ReadBlocksInheritance(null); - var test2 = ContainerProcessor.ReadBlocksInheritance("3"); - var test3 = ContainerProcessor.ReadBlocksInheritance("1"); - - Assert.False(test); - Assert.False(test2); - Assert.True(test3); - } - - [Fact] - public void ContainerProcessor_GetContainingObject_ExpectedResult() - { - var utils = new MockLDAPUtils(); - var proc = new ContainerProcessor(utils); - - var result = proc.GetContainingObject("OU=TESTOU,DC=TESTLAB,DC=LOCAL"); - Assert.Equal(Label.Domain, result.ObjectType); - Assert.Equal("S-1-5-21-3130019616-2776909439-2417379446", result.ObjectIdentifier); - - result = proc.GetContainingObject("CN=PRIMARY,OU=DOMAIN CONTROLLERS,DC=TESTLAB,DC=LOCAL"); - Assert.Equal(Label.OU, result.ObjectType); - Assert.Equal("0DE400CD-2FF3-46E0-8A26-2C917B403C65", result.ObjectIdentifier); - - result = proc.GetContainingObject("CN=ADMINISTRATORS,CN=BUILTIN,DC=TESTLAB,DC=LOCAL"); - Assert.Equal(Label.Domain, result.ObjectType); - Assert.Equal("S-1-5-21-3130019616-2776909439-2417379446", result.ObjectIdentifier); - } - - [Fact] - public void ContainerProcessor_GetContainingObject_BadDN_ReturnsNull() - { - var utils = new MockLDAPUtils(); - var proc = new ContainerProcessor(utils); - - var result = proc.GetContainingObject("abc123"); - Assert.Equal(null, result); - } - } -} \ No newline at end of file +// using System; +// using System.DirectoryServices.Protocols; +// using System.Linq; +// using CommonLibTest.Facades; +// using Moq; +// using SharpHoundCommonLib.Enums; +// using SharpHoundCommonLib.OutputTypes; +// using SharpHoundCommonLib.Processors; +// using Xunit; +// using Xunit.Abstractions; +// +// namespace CommonLibTest +// { +// public class ContainerProcessorTest : IDisposable +// { +// private readonly string _testGpLinkString; +// private readonly ITestOutputHelper _testOutputHelper; +// +// public ContainerProcessorTest(ITestOutputHelper testOutputHelper) +// { +// _testOutputHelper = testOutputHelper; +// _testGpLinkString = +// "[LDAP://cn={94DD0260-38B5-497E-8876-10E7A96E80D0},cn=policies,cn=system,DC=testlab,DC=local;0][LDAP://cn={C52F168C-CD05-4487-B405-564934DA8EFF},cn=policies,cn=system,DC=testlab,DC=local;0][LDAP://cn={1E860A30-603A-45C7-A768-26EE74BE6D5D},cn=policies,cn=system,DC=testlab,DC=local;0]"; +// } +// +// public void Dispose() +// { +// } +// +// [Fact] +// public void ContainerProcessor_ReadContainerGPLinks_IgnoresNull() +// { +// var processor = new ContainerProcessor(new MockLDAPUtils()); +// var test = processor.ReadContainerGPLinks(null); +// Assert.Empty(test); +// } +// +// [Fact] +// public void ContainerProcessor_ReadContainerGPLinks_UnresolvedGPLink_IsIgnored() +// { +// var processor = new ContainerProcessor(new MockLDAPUtils()); +// //GPLink that doesn't exist +// const string s = +// "[LDAP://cn={94DD0260-38B5-497E-8876-ABCDEFG},cn=policies,cn=system,DC=testlab,DC=local;0]"; +// var test = processor.ReadContainerGPLinks(s); +// Assert.Empty(test); +// } +// +// [Fact] +// public void ContainerProcessor_ReadContainerGPLinks_ReturnsCorrectValues() +// { +// var processor = new ContainerProcessor(new MockLDAPUtils()); +// var test = processor.ReadContainerGPLinks(_testGpLinkString).ToArray(); +// +// var expected = new GPLink[] +// { +// new() +// { +// GUID = "B39818AF-6349-401A-AE0A-E4972F5BF6D9", +// IsEnforced = false +// }, +// new() +// { +// GUID = "ACDD64D3-67B3-401F-A6CC-804B3F7B1533", +// IsEnforced = false +// }, +// new() +// { +// GUID = "C45E9585-4932-4C03-91A8-1856869D49AF", +// IsEnforced = false +// } +// }; +// +// Assert.Equal(3, test.Length); +// Assert.Equal(expected, test); +// } +// +// [Fact] +// public void ContainerProcessor_GetContainerChildObjects_ReturnsCorrectData() +// { +// var mock = new Mock(); +// +// var searchResults = new MockSearchResultEntry[] +// { +// //These first 4 should be filtered by our DN filters +// new( +// "CN=7868d4c8-ac41-4e05-b401-776280e8e9f1,CN=Operations,CN=DomainUpdates,CN=System,DC=testlab,DC=local" +// , null, null, Label.Base), +// new("CN=Microsoft,CN=Program Data,DC=testlab,DC=local", null, null, Label.Base), +// new("CN=Operations,CN=DomainUpdates,CN=System,DC=testlab,DC=local", null, null, Label.Base), +// new("CN=User,CN={C52F168C-CD05-4487-B405-564934DA8EFF},CN=Policies,CN=System,DC=testlab,DC=local", null, +// null, Label.Base), +// //This is a real object in our mock +// new("CN=Users,DC=testlab,DC=local", null, "ECAD920E-8EB1-4E31-A80E-DD36367F81F4", Label.Container), +// //This object does not exist in our mock +// new("CN=Users,DC=testlab,DC=local", null, "ECAD920E-8EB1-4E31-A80E-DD36367F81FD", Label.Container), +// //Test null objectid +// new("CN=Users,DC=testlab,DC=local", null, null, Label.Container) +// }; +// +// mock.Setup(x => x.QueryLDAP(It.IsAny(), It.IsAny(), It.IsAny(), +// It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), +// It.IsAny(), It.IsAny())).Returns(searchResults); +// +// var processor = new ContainerProcessor(mock.Object); +// var test = processor.GetContainerChildObjects(_testGpLinkString).ToArray(); +// +// var expected = new TypedPrincipal[] +// { +// new() +// { +// ObjectIdentifier = "ECAD920E-8EB1-4E31-A80E-DD36367F81F4", +// ObjectType = Label.Container +// } +// }; +// +// Assert.Single(test); +// Assert.Equal(expected, test); +// } +// +// [Fact] +// public void ContainerProcessor_ReadBlocksInheritance_ReturnsCorrectValues() +// { +// var test = ContainerProcessor.ReadBlocksInheritance(null); +// var test2 = ContainerProcessor.ReadBlocksInheritance("3"); +// var test3 = ContainerProcessor.ReadBlocksInheritance("1"); +// +// Assert.False(test); +// Assert.False(test2); +// Assert.True(test3); +// } +// +// [Fact] +// public void ContainerProcessor_GetContainingObject_ExpectedResult() +// { +// var utils = new MockLDAPUtils(); +// var proc = new ContainerProcessor(utils); +// +// var result = proc.GetContainingObject("OU=TESTOU,DC=TESTLAB,DC=LOCAL"); +// Assert.Equal(Label.Domain, result.ObjectType); +// Assert.Equal("S-1-5-21-3130019616-2776909439-2417379446", result.ObjectIdentifier); +// +// result = proc.GetContainingObject("CN=PRIMARY,OU=DOMAIN CONTROLLERS,DC=TESTLAB,DC=LOCAL"); +// Assert.Equal(Label.OU, result.ObjectType); +// Assert.Equal("0DE400CD-2FF3-46E0-8A26-2C917B403C65", result.ObjectIdentifier); +// +// result = proc.GetContainingObject("CN=ADMINISTRATORS,CN=BUILTIN,DC=TESTLAB,DC=LOCAL"); +// Assert.Equal(Label.Domain, result.ObjectType); +// Assert.Equal("S-1-5-21-3130019616-2776909439-2417379446", result.ObjectIdentifier); +// } +// +// [Fact] +// public void ContainerProcessor_GetContainingObject_BadDN_ReturnsNull() +// { +// var utils = new MockLDAPUtils(); +// var proc = new ContainerProcessor(utils); +// +// var result = proc.GetContainingObject("abc123"); +// Assert.Equal(null, result); +// } +// } +// } \ No newline at end of file diff --git a/test/unit/DomainTrustProcessorTest.cs b/test/unit/DomainTrustProcessorTest.cs index ad0dc5b2..f5997190 100644 --- a/test/unit/DomainTrustProcessorTest.cs +++ b/test/unit/DomainTrustProcessorTest.cs @@ -1,129 +1,129 @@ -using System; -using System.Collections.Generic; -using System.DirectoryServices.Protocols; -using System.Linq; -using CommonLibTest.Facades; -using Moq; -using SharpHoundCommonLib.Enums; -using SharpHoundCommonLib.Processors; -using Xunit; -using Xunit.Abstractions; - -namespace CommonLibTest -{ - public class DomainTrustProcessorTest - { - private ITestOutputHelper _testOutputHelper; - - public DomainTrustProcessorTest(ITestOutputHelper testOutputHelper) - { - _testOutputHelper = testOutputHelper; - } - - [WindowsOnlyFact] - public void DomainTrustProcessor_EnumerateDomainTrusts_HappyPath() - { - var mockUtils = new Mock(); - var searchResults = new[] - { - new MockSearchResultEntry("CN\u003dexternal.local,CN\u003dSystem,DC\u003dtestlab,DC\u003dlocal", - new Dictionary - { - {"trustdirection", "3"}, - {"trusttype", "2"}, - {"trustattributes", 0x24.ToString()}, - {"cn", "external.local"}, - {"securityidentifier", Helpers.B64ToBytes("AQQAAAAAAAUVAAAA7JjftxhaHTnafGWh")} - }, "", Label.Domain) - }; - - mockUtils.Setup(x => x.QueryLDAP(It.IsAny(), It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny())).Returns(searchResults); - var processor = new DomainTrustProcessor(mockUtils.Object); - var test = processor.EnumerateDomainTrusts("testlab.local").ToArray(); - Assert.Single(test); - var trust = test.First(); - Assert.Equal(TrustDirection.Bidirectional, trust.TrustDirection); - Assert.Equal("EXTERNAL.LOCAL", trust.TargetDomainName); - Assert.Equal("S-1-5-21-3084884204-958224920-2707782874", trust.TargetDomainSid); - Assert.True(trust.IsTransitive); - Assert.Equal(TrustType.ParentChild, trust.TrustType); - Assert.True(trust.SidFilteringEnabled); - } - - [Fact] - public void DomainTrustProcessor_EnumerateDomainTrusts_SadPaths() - { - var mockUtils = new Mock(); - var searchResults = new[] - { - new MockSearchResultEntry("CN\u003dexternal.local,CN\u003dSystem,DC\u003dtestlab,DC\u003dlocal", - new Dictionary - { - {"trustdirection", "3"}, - {"trusttype", "2"}, - {"trustattributes", 0x24.ToString()}, - {"cn", "external.local"}, - {"securityIdentifier", Array.Empty()} - }, "", Label.Domain), - new MockSearchResultEntry("CN\u003dexternal.local,CN\u003dSystem,DC\u003dtestlab,DC\u003dlocal", - new Dictionary - { - {"trustdirection", "3"}, - {"trusttype", "2"}, - {"trustattributes", 0x24.ToString()}, - {"cn", "external.local"}, - {"securityIdentifier", Helpers.B64ToBytes("QQQAAAAAAAUVAAAA7JjftxhaHTnafGWh")} - }, "", Label.Domain), - new MockSearchResultEntry("CN\u003dexternal.local,CN\u003dSystem,DC\u003dtestlab,DC\u003dlocal", - new Dictionary - { - {"trusttype", "2"}, - {"trustattributes", 0x24.ToString()}, - {"cn", "external.local"}, - {"securityIdentifier", Helpers.B64ToBytes("AQQAAAAAAAUVAAAA7JjftxhaHTnafGWh")} - }, "", Label.Domain), - new MockSearchResultEntry("CN\u003dexternal.local,CN\u003dSystem,DC\u003dtestlab,DC\u003dlocal", - new Dictionary - { - {"trustdirection", "3"}, - {"trusttype", "2"}, - {"cn", "external.local"}, - {"securityIdentifier", Helpers.B64ToBytes("AQQAAAAAAAUVAAAA7JjftxhaHTnafGWh")} - }, "", Label.Domain) - }; - - mockUtils.Setup(x => x.QueryLDAP(It.IsAny(), It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny())).Returns(searchResults); - var processor = new DomainTrustProcessor(mockUtils.Object); - var test = processor.EnumerateDomainTrusts("testlab.local"); - Assert.Empty(test); - } - - [Fact] - public void DomainTrustProcessor_TrustAttributesToType() - { - var attrib = TrustAttributes.WithinForest; - var test = DomainTrustProcessor.TrustAttributesToType(attrib); - Assert.Equal(TrustType.ParentChild, test); - - attrib = TrustAttributes.ForestTransitive; - test = DomainTrustProcessor.TrustAttributesToType(attrib); - Assert.Equal(TrustType.Forest, test); - - attrib = TrustAttributes.TreatAsExternal; - test = DomainTrustProcessor.TrustAttributesToType(attrib); - Assert.Equal(TrustType.External, test); - - attrib = TrustAttributes.CrossOrganization; - test = DomainTrustProcessor.TrustAttributesToType(attrib); - Assert.Equal(TrustType.External, test); - - attrib = TrustAttributes.FilterSids; - test = DomainTrustProcessor.TrustAttributesToType(attrib); - Assert.Equal(TrustType.External, test); - } - } -} \ No newline at end of file +// using System; +// using System.Collections.Generic; +// using System.DirectoryServices.Protocols; +// using System.Linq; +// using CommonLibTest.Facades; +// using Moq; +// using SharpHoundCommonLib.Enums; +// using SharpHoundCommonLib.Processors; +// using Xunit; +// using Xunit.Abstractions; +// +// namespace CommonLibTest +// { +// public class DomainTrustProcessorTest +// { +// private ITestOutputHelper _testOutputHelper; +// +// public DomainTrustProcessorTest(ITestOutputHelper testOutputHelper) +// { +// _testOutputHelper = testOutputHelper; +// } +// +// [WindowsOnlyFact] +// public void DomainTrustProcessor_EnumerateDomainTrusts_HappyPath() +// { +// var mockUtils = new Mock(); +// var searchResults = new[] +// { +// new MockSearchResultEntry("CN\u003dexternal.local,CN\u003dSystem,DC\u003dtestlab,DC\u003dlocal", +// new Dictionary +// { +// {"trustdirection", "3"}, +// {"trusttype", "2"}, +// {"trustattributes", 0x24.ToString()}, +// {"cn", "external.local"}, +// {"securityidentifier", Helpers.B64ToBytes("AQQAAAAAAAUVAAAA7JjftxhaHTnafGWh")} +// }, "", Label.Domain) +// }; +// +// mockUtils.Setup(x => x.QueryLDAP(It.IsAny(), It.IsAny(), It.IsAny(), +// It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), +// It.IsAny(), It.IsAny())).Returns(searchResults); +// var processor = new DomainTrustProcessor(mockUtils.Object); +// var test = processor.EnumerateDomainTrusts("testlab.local").ToArray(); +// Assert.Single(test); +// var trust = test.First(); +// Assert.Equal(TrustDirection.Bidirectional, trust.TrustDirection); +// Assert.Equal("EXTERNAL.LOCAL", trust.TargetDomainName); +// Assert.Equal("S-1-5-21-3084884204-958224920-2707782874", trust.TargetDomainSid); +// Assert.True(trust.IsTransitive); +// Assert.Equal(TrustType.ParentChild, trust.TrustType); +// Assert.True(trust.SidFilteringEnabled); +// } +// +// [Fact] +// public void DomainTrustProcessor_EnumerateDomainTrusts_SadPaths() +// { +// var mockUtils = new Mock(); +// var searchResults = new[] +// { +// new MockSearchResultEntry("CN\u003dexternal.local,CN\u003dSystem,DC\u003dtestlab,DC\u003dlocal", +// new Dictionary +// { +// {"trustdirection", "3"}, +// {"trusttype", "2"}, +// {"trustattributes", 0x24.ToString()}, +// {"cn", "external.local"}, +// {"securityIdentifier", Array.Empty()} +// }, "", Label.Domain), +// new MockSearchResultEntry("CN\u003dexternal.local,CN\u003dSystem,DC\u003dtestlab,DC\u003dlocal", +// new Dictionary +// { +// {"trustdirection", "3"}, +// {"trusttype", "2"}, +// {"trustattributes", 0x24.ToString()}, +// {"cn", "external.local"}, +// {"securityIdentifier", Helpers.B64ToBytes("QQQAAAAAAAUVAAAA7JjftxhaHTnafGWh")} +// }, "", Label.Domain), +// new MockSearchResultEntry("CN\u003dexternal.local,CN\u003dSystem,DC\u003dtestlab,DC\u003dlocal", +// new Dictionary +// { +// {"trusttype", "2"}, +// {"trustattributes", 0x24.ToString()}, +// {"cn", "external.local"}, +// {"securityIdentifier", Helpers.B64ToBytes("AQQAAAAAAAUVAAAA7JjftxhaHTnafGWh")} +// }, "", Label.Domain), +// new MockSearchResultEntry("CN\u003dexternal.local,CN\u003dSystem,DC\u003dtestlab,DC\u003dlocal", +// new Dictionary +// { +// {"trustdirection", "3"}, +// {"trusttype", "2"}, +// {"cn", "external.local"}, +// {"securityIdentifier", Helpers.B64ToBytes("AQQAAAAAAAUVAAAA7JjftxhaHTnafGWh")} +// }, "", Label.Domain) +// }; +// +// mockUtils.Setup(x => x.QueryLDAP(It.IsAny(), It.IsAny(), It.IsAny(), +// It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), +// It.IsAny(), It.IsAny())).Returns(searchResults); +// var processor = new DomainTrustProcessor(mockUtils.Object); +// var test = processor.EnumerateDomainTrusts("testlab.local"); +// Assert.Empty(test); +// } +// +// [Fact] +// public void DomainTrustProcessor_TrustAttributesToType() +// { +// var attrib = TrustAttributes.WithinForest; +// var test = DomainTrustProcessor.TrustAttributesToType(attrib); +// Assert.Equal(TrustType.ParentChild, test); +// +// attrib = TrustAttributes.ForestTransitive; +// test = DomainTrustProcessor.TrustAttributesToType(attrib); +// Assert.Equal(TrustType.Forest, test); +// +// attrib = TrustAttributes.TreatAsExternal; +// test = DomainTrustProcessor.TrustAttributesToType(attrib); +// Assert.Equal(TrustType.External, test); +// +// attrib = TrustAttributes.CrossOrganization; +// test = DomainTrustProcessor.TrustAttributesToType(attrib); +// Assert.Equal(TrustType.External, test); +// +// attrib = TrustAttributes.FilterSids; +// test = DomainTrustProcessor.TrustAttributesToType(attrib); +// Assert.Equal(TrustType.External, test); +// } +// } +// } \ No newline at end of file diff --git a/test/unit/Facades/MockLDAPUtils.cs b/test/unit/Facades/MockLDAPUtils.cs index 5bf03336..c0b621fc 100644 --- a/test/unit/Facades/MockLDAPUtils.cs +++ b/test/unit/Facades/MockLDAPUtils.cs @@ -51,6 +51,65 @@ public string[] GetUserGlobalCatalogMatches(string name) }; } + public IAsyncEnumerable> Query(LdapQueryParameters queryParameters, + CancellationToken cancellationToken = new CancellationToken()) { + throw new NotImplementedException(); + } + + public IAsyncEnumerable> PagedQuery(LdapQueryParameters queryParameters, + CancellationToken cancellationToken = new CancellationToken()) { + throw new NotImplementedException(); + } + + public IAsyncEnumerable> RangedRetrieval(string distinguishedName, string attributeName, + CancellationToken cancellationToken = new CancellationToken()) { + throw new NotImplementedException(); + } + + public Task<(bool Success, TypedPrincipal Principal)> ResolveIDAndType(SecurityIdentifier securityIdentifier, string objectDomain) { + throw new NotImplementedException(); + } + + Task<(bool Success, TypedPrincipal Principal)> ILdapUtils.ResolveIDAndType(string identifier, string objectDomain) { + throw new NotImplementedException(); + } + + public Task<(bool Success, TypedPrincipal WellKnownPrincipal)> GetWellKnownPrincipal(string securityIdentifier, string objectDomain) { + throw new NotImplementedException(); + } + + Task<(bool Success, string DomainName)> ILdapUtils.GetDomainNameFromSid(string sid) { + throw new NotImplementedException(); + } + + public Task<(bool Success, string DomainSid)> GetDomainSidFromDomainName(string domainName) { + throw new NotImplementedException(); + } + + public bool GetDomain(string domainName, out Domain domain) { + throw new NotImplementedException(); + } + + public bool GetDomain(out Domain domain) { + throw new NotImplementedException(); + } + + Task<(bool Success, TypedPrincipal Principal)> ILdapUtils.ResolveAccountName(string name, string domain) { + throw new NotImplementedException(); + } + + Task<(bool Success, string SecurityIdentifier)> ILdapUtils.ResolveHostToSid(string host, string domain) { + throw new NotImplementedException(); + } + + public Task<(bool Success, string[] Sids)> GetGlobalCatalogMatches(string name, string domain) { + throw new NotImplementedException(); + } + + public Task<(bool Success, TypedPrincipal Principal)> ResolveCertTemplateByProperty(string propValue, string propName, string domainName) { + throw new NotImplementedException(); + } + public TypedPrincipal ResolveIDAndType(string id, string fallbackDomain) { id = id?.ToUpper(); @@ -749,11 +808,31 @@ public bool ConvertLocalWellKnownPrincipal(SecurityIdentifier sid, string comput return false; } + Task<(bool Success, TypedPrincipal Principal)> ILdapUtils.ResolveDistinguishedName(string distinguishedName) { + throw new NotImplementedException(); + } + public void AddDomainController(string domainControllerSID) { _domainControllers.TryAdd(domainControllerSID, new byte()); } + public IAsyncEnumerable GetWellKnownPrincipalOutput() { + throw new NotImplementedException(); + } + + public void SetLdapConfig(LDAPConfig config) { + throw new NotImplementedException(); + } + + public Task<(bool Success, string Message)> TestLdapConnection(string domain) { + throw new NotImplementedException(); + } + + public Task<(bool Success, string Path)> GetNamingContextPath(string domain, NamingContext context) { + throw new NotImplementedException(); + } + public Domain GetDomain(string domainName = null) { throw new NotImplementedException(); @@ -822,6 +901,10 @@ public TypedPrincipal ResolveAccountName(string name, string domain) } #pragma warning restore CS1998 + Task ILdapUtils.IsDomainController(string computerObjectId, string domainName) { + throw new NotImplementedException(); + } + public TypedPrincipal ResolveDistinguishedName(string dn) { return dn.ToUpper() switch @@ -1059,11 +1142,6 @@ public TypedPrincipal ResolveDistinguishedName(string dn) }; } - public virtual IEnumerable QueryLDAP(LDAPQueryOptions options) - { - throw new NotImplementedException(); - } - public virtual IEnumerable QueryLDAP(string ldapFilter, SearchScope scope, string[] props, CancellationToken cancellationToken, string domainName = null, bool includeAcl = false, bool showDeleted = false, string adsPath = null, @@ -1091,6 +1169,10 @@ public ActiveDirectorySecurityDescriptor MakeSecurityDescriptor() return mockSecurityDescriptor.Object; } + public Task<(bool Success, TypedPrincipal Principal)> ConvertLocalWellKnownPrincipal(SecurityIdentifier sid, string computerDomainSid, string computerDomain) { + throw new NotImplementedException(); + } + public string BuildLdapPath(string dnPath, string domain) { throw new NotImplementedException(); @@ -1118,14 +1200,13 @@ public string GetSchemaPath(string domainName) throw new NotImplementedException(); } - TypedPrincipal ILDAPUtils.ResolveCertTemplateByProperty(string propValue, string propName, string containerDN, string domainName) + public bool IsDomainController(string computerObjectId, string domainName) { throw new NotImplementedException(); } - public bool IsDomainController(string computerObjectId, string domainName) - { - throw new NotImplementedException(); + public void Dispose() { + _forest?.Dispose(); } } } \ No newline at end of file diff --git a/test/unit/Facades/MockSearchResultEntry.cs b/test/unit/Facades/MockSearchResultEntry.cs index 63f7cc97..412f84eb 100644 --- a/test/unit/Facades/MockSearchResultEntry.cs +++ b/test/unit/Facades/MockSearchResultEntry.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Security.Cryptography.X509Certificates; using System.Text; +using System.Threading.Tasks; using SharpHoundCommonLib; using SharpHoundCommonLib.Enums; @@ -26,7 +27,7 @@ public MockSearchResultEntry(string distinguishedName, IDictionary properties, s public string DistinguishedName { get; } - public ResolvedSearchResult ResolveBloodHoundInfo() + public async Task ResolveBloodHoundInfo() { throw new NotImplementedException(); } diff --git a/test/unit/GPOLocalGroupProcessorTest.cs b/test/unit/GPOLocalGroupProcessorTest.cs index 5a0393ee..0bbcef2d 100644 --- a/test/unit/GPOLocalGroupProcessorTest.cs +++ b/test/unit/GPOLocalGroupProcessorTest.cs @@ -1,335 +1,335 @@ -using System.Collections.Generic; -using System.DirectoryServices.Protocols; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Moq; -using SharpHoundCommonLib; -using SharpHoundCommonLib.Enums; -using SharpHoundCommonLib.LDAPQueries; -using SharpHoundCommonLib.OutputTypes; -using SharpHoundCommonLib.Processors; -using Xunit; -using Xunit.Abstractions; - -namespace CommonLibTest -{ - public class GPOLocalGroupProcessorTest - { - private readonly string GpttmplInfContent = @"[Unicode] - Unicode=yes - [Version] - signature=""$CHICAGO$"" - Revision=1 - [Group Membership] - *S-1-5-21-3130019616-2776909439-2417379446-514__Memberof = *S-1-5-32-544 - *S-1-5-21-3130019616-2776909439-2417379446-514__Members = - *S-1-5-32-544__Members = - "; - - private readonly string GpttmplInfContentNoMatch = @"[Unicode] - Unicode=yes - [Version] - signature=""$CHICAGO$"" - Revision=1 - "; - - private readonly string GroupXmlContent = @" - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "; - - private readonly string GroupXmlContentDisabled = @" - - - "; - - private ITestOutputHelper _testOutputHelper; - - public GPOLocalGroupProcessorTest(ITestOutputHelper testOutputHelper) - { - _testOutputHelper = testOutputHelper; - } - - [Fact(Skip = "")] - public async Task GPOLocalGroupProcessor_ReadGPOLocalGroups_Null_GPLink() - { - var mockLDAPUtils = new Mock(); - var processor = new GPOLocalGroupProcessor(mockLDAPUtils.Object); - - var result = await processor.ReadGPOLocalGroups(null, null); - Assert.NotNull(result); - Assert.Empty(result.AffectedComputers); - Assert.Empty(result.DcomUsers); - Assert.Empty(result.RemoteDesktopUsers); - Assert.Empty(result.LocalAdmins); - Assert.Empty(result.PSRemoteUsers); - } - - [Fact(Skip = "")] - public async Task GPOLocalGroupProcessor_ReadGPOLocalGroups_AffectedComputers_0() - { - var mockLDAPUtils = new Mock(); - mockLDAPUtils.Setup(x => x.QueryLDAP( - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny() - )).Returns(new List()); - var processor = new GPOLocalGroupProcessor(mockLDAPUtils.Object); - - var result = await processor.ReadGPOLocalGroups("teapot", null); - Assert.NotNull(result); - Assert.Empty(result.AffectedComputers); - Assert.Empty(result.DcomUsers); - Assert.Empty(result.RemoteDesktopUsers); - Assert.Empty(result.LocalAdmins); - Assert.Empty(result.PSRemoteUsers); - } - - [Fact(Skip = "")] - public async Task GPOLocalGroupProcessor_ReadGPOLocalGroups_Null_Gpcfilesyspath() - { - var mockLDAPUtils = new Mock(); - var mockSearchResultEntry = new Mock(); - mockSearchResultEntry.Setup(x => x.GetSid()).Returns("teapot"); - var mockSearchResults = new List(); - mockSearchResults.Add(mockSearchResultEntry.Object); - mockLDAPUtils.Setup(x => x.QueryLDAP(new LDAPQueryOptions - { - Filter = "(&(samaccounttype=805306369)(!(objectclass=msDS-GroupManagedServiceAccount))(!(objectclass=msDS-ManagedServiceAccount)))", - Scope = SearchScope.Subtree, - Properties = CommonProperties.ObjectSID, - AdsPath = null - })) - .Returns(mockSearchResults.ToArray()); - - var processor = new GPOLocalGroupProcessor(mockLDAPUtils.Object); - var testGPLinkProperty = - "[LDAP:/o=foo/ou=foo Group (ABC123)/cn=foouser (blah)123/dc=somedomain;0;][LDAP:/o=foo/ou=foo Group (ABC123)/cn=foouser (blah)123/dc=someotherdomain;2;]"; - var result = await processor.ReadGPOLocalGroups(testGPLinkProperty, null); - - Assert.NotNull(result); - Assert.Single(result.AffectedComputers); - var actual = result.AffectedComputers.First(); - Assert.Equal(Label.Computer, actual.ObjectType); - Assert.Equal("teapot", actual.ObjectIdentifier); - } - - [Fact] - public async Task GPOLocalGroupProcessor_ReadGPOLocalGroups() - { - var mockLDAPUtils = new Mock(MockBehavior.Strict); - var gpcFileSysPath = Path.GetTempPath(); - - var groupsXmlPath = Path.Join(gpcFileSysPath, "MACHINE", "Preferences", "Groups", "Groups.xml"); - - Path.GetDirectoryName(groupsXmlPath); - Directory.CreateDirectory(Path.GetDirectoryName(groupsXmlPath)); - File.WriteAllText(groupsXmlPath, GroupXmlContent); - - var mockComputerEntry = new Mock(); - mockComputerEntry.Setup(x => x.GetSid()).Returns("teapot"); - var mockComputerResults = new List(); - mockComputerResults.Add(mockComputerEntry.Object); - - var mockGCPFileSysPathEntry = new Mock(); - mockGCPFileSysPathEntry.Setup(x => x.GetProperty(It.IsAny())).Returns(gpcFileSysPath); - var mockGCPFileSysPathResults = new List(); - mockGCPFileSysPathResults.Add(mockGCPFileSysPathEntry.Object); - - mockLDAPUtils.SetupSequence(x => x.QueryLDAP(It.IsAny())) - .Returns(mockComputerResults.ToArray()) - .Returns(mockGCPFileSysPathResults.ToArray()); - - var processor = new GPOLocalGroupProcessor(mockLDAPUtils.Object); - - var testGPLinkProperty = - "[LDAP:/o=foo/ou=foo Group (ABC123)/cn=foouser (blah)123/dc=somedomain;0;][LDAP:/o=foo/ou=foo Group (ABC123)/cn=foouser (blah)123/dc=someotherdomain;2;]"; - var result = await processor.ReadGPOLocalGroups(testGPLinkProperty, null); - - mockLDAPUtils.VerifyAll(); - Assert.NotNull(result); - Assert.Single(result.AffectedComputers); - var actual = result.AffectedComputers.First(); - Assert.Equal(Label.Computer, actual.ObjectType); - Assert.Equal("teapot", actual.ObjectIdentifier); - } - - [Fact] - public async Task GPOLocalGroupProcess_ProcessGPOXMLFile_NoFile() - { - var mockLDAPUtils = new Mock(); - var processor = new GPOLocalGroupProcessor(mockLDAPUtils.Object); - var gpcFileSysPath = Path.Join(Path.GetTempPath(), "made", "up", "path"); - - var actual = processor.ProcessGPOXmlFile(gpcFileSysPath, "somedomain").ToList(); - Assert.NotNull(actual); - Assert.Empty(actual); - } - - [Fact] - public async Task GPOLocalGroupProcess_ProcessGPOXMLFile_Disabled() - { - var mockLDAPUtils = new Mock(); - var gpcFileSysPath = Path.GetTempPath(); - var groupsXmlPath = Path.Join(gpcFileSysPath, "MACHINE", "Preferences", "Groups", "Groups.xml"); - - Directory.CreateDirectory(Path.GetDirectoryName(groupsXmlPath)); - File.WriteAllText(groupsXmlPath, GroupXmlContentDisabled); - - var processor = new GPOLocalGroupProcessor(mockLDAPUtils.Object); - - var actual = processor.ProcessGPOXmlFile(gpcFileSysPath, "somedomain").ToList(); - Assert.NotNull(actual); - Assert.Empty(actual); - } - - [Fact] - public async Task GPOLocalGroupProcessor_ProcessGPOXMLFile() - { - var mockLDAPUtils = new Mock(); - mockLDAPUtils.Setup(x => x.ResolveAccountName(It.IsAny(), It.IsAny())) - .Returns(new TypedPrincipal("S-1-5-21-3130019616-2776909439-2417379446-513", Label.User)); - var gpcFileSysPath = Path.GetTempPath(); - var groupsXmlPath = Path.Join(gpcFileSysPath, "MACHINE", "Preferences", "Groups", "Groups.xml"); - - Directory.CreateDirectory(Path.GetDirectoryName(groupsXmlPath)); - File.WriteAllText(groupsXmlPath, GroupXmlContent); - - var processor = new GPOLocalGroupProcessor(mockLDAPUtils.Object); - var actual = processor.ProcessGPOXmlFile(gpcFileSysPath, "somedomain").ToList(); - - Assert.NotNull(actual); - Assert.NotEmpty(actual); - } - - [Fact] - public async Task GPOLocalGroupProcess_ProcessGPOTemplateFile_NoFile() - { - var mockLDAPUtils = new Mock(); - var processor = new GPOLocalGroupProcessor(mockLDAPUtils.Object); - var gpcFileSysPath = Path.Join(Path.GetTempPath(), "made", "up", "path"); - - var actual = await processor.ProcessGPOTemplateFile(gpcFileSysPath, "somedomain").ToListAsync(); - Assert.NotNull(actual); - Assert.Empty(actual); - } - - [Fact] - public async Task GPOLocalGroupProcess_ProcessGPOTemplateFile_NoMatch() - { - var mockLDAPUtils = new Mock(); - var gpcFileSysPath = Path.GetTempPath(); - var gptTmplPath = Path.Join(gpcFileSysPath, "MACHINE", "Microsoft", "Windows NT", "SecEdit", "GptTmpl.inf"); - - Directory.CreateDirectory(Path.GetDirectoryName(gptTmplPath)); - File.WriteAllText(gptTmplPath, GpttmplInfContentNoMatch); - - var processor = new GPOLocalGroupProcessor(mockLDAPUtils.Object); - - var actual = await processor.ProcessGPOTemplateFile(gpcFileSysPath, "somedomain").ToListAsync(); - Assert.NotNull(actual); - Assert.Empty(actual); - } - - [Fact] - public async Task GPOLocalGroupProcess_ProcessGPOTemplateFile_NullSID() - { - var mockLDAPUtils = new Mock(); - var gpcFileSysPath = Path.GetTempPath(); - var gptTmplPath = Path.Join(gpcFileSysPath, "MACHINE", "Microsoft", "Windows NT", "SecEdit", "GptTmpl.inf"); - - Directory.CreateDirectory(Path.GetDirectoryName(gptTmplPath)); - File.WriteAllText(gptTmplPath, GpttmplInfContent); - - var processor = new GPOLocalGroupProcessor(mockLDAPUtils.Object); - - var actual = await processor.ProcessGPOTemplateFile(gpcFileSysPath, "somedomain").ToListAsync(); - Assert.NotNull(actual); - Assert.NotEmpty(actual); - } - - [Fact] - public async Task GPOLocalGroupProcess_ProcessGPOTemplateFile() - { - var mockLDAPUtils = new Mock(); - mockLDAPUtils.Setup(x => x.ResolveAccountName(It.IsAny(), It.IsAny())) - .Returns(new TypedPrincipal("S-1-5-21-3130019616-2776909439-2417379446-513", Label.User)); - var gpcFileSysPath = Path.GetTempPath(); - var gptTmplPath = Path.Join(gpcFileSysPath, "MACHINE", "Microsoft", "Windows NT", "SecEdit", "GptTmpl.inf"); - - Directory.CreateDirectory(Path.GetDirectoryName(gptTmplPath)); - File.WriteAllText(gptTmplPath, GpttmplInfContent); - - var processor = new GPOLocalGroupProcessor(mockLDAPUtils.Object); - - var actual = await processor.ProcessGPOTemplateFile(gpcFileSysPath, "somedomain").ToListAsync(); - Assert.NotNull(actual); - // Assert.Empty(actual); - } - - [Fact] - public void GPOLocalGroupProcess_GroupAction() - { - var ga = new GPOLocalGroupProcessor.GroupAction(); - var tp = ga.ToTypedPrincipal(); - var str = ga.ToString(); - - Assert.NotNull(tp); - Assert.Equal(new TypedPrincipal(), tp); - Assert.NotNull(str); - Assert.Equal("Action: Add, Target: RestrictedMemberOf, TargetSid: , TargetType: Base, TargetRid: None", - str); - } - } -} \ No newline at end of file +// using System.Collections.Generic; +// using System.DirectoryServices.Protocols; +// using System.IO; +// using System.Linq; +// using System.Threading; +// using System.Threading.Tasks; +// using Moq; +// using SharpHoundCommonLib; +// using SharpHoundCommonLib.Enums; +// using SharpHoundCommonLib.LDAPQueries; +// using SharpHoundCommonLib.OutputTypes; +// using SharpHoundCommonLib.Processors; +// using Xunit; +// using Xunit.Abstractions; +// +// namespace CommonLibTest +// { +// public class GPOLocalGroupProcessorTest +// { +// private readonly string GpttmplInfContent = @"[Unicode] +// Unicode=yes +// [Version] +// signature=""$CHICAGO$"" +// Revision=1 +// [Group Membership] +// *S-1-5-21-3130019616-2776909439-2417379446-514__Memberof = *S-1-5-32-544 +// *S-1-5-21-3130019616-2776909439-2417379446-514__Members = +// *S-1-5-32-544__Members = +// "; +// +// private readonly string GpttmplInfContentNoMatch = @"[Unicode] +// Unicode=yes +// [Version] +// signature=""$CHICAGO$"" +// Revision=1 +// "; +// +// private readonly string GroupXmlContent = @" +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// "; +// +// private readonly string GroupXmlContentDisabled = @" +// +// +// "; +// +// private ITestOutputHelper _testOutputHelper; +// +// public GPOLocalGroupProcessorTest(ITestOutputHelper testOutputHelper) +// { +// _testOutputHelper = testOutputHelper; +// } +// +// [Fact(Skip = "")] +// public async Task GPOLocalGroupProcessor_ReadGPOLocalGroups_Null_GPLink() +// { +// var mockLDAPUtils = new Mock(); +// var processor = new GPOLocalGroupProcessor(mockLDAPUtils.Object); +// +// var result = await processor.ReadGPOLocalGroups(null, null); +// Assert.NotNull(result); +// Assert.Empty(result.AffectedComputers); +// Assert.Empty(result.DcomUsers); +// Assert.Empty(result.RemoteDesktopUsers); +// Assert.Empty(result.LocalAdmins); +// Assert.Empty(result.PSRemoteUsers); +// } +// +// [Fact(Skip = "")] +// public async Task GPOLocalGroupProcessor_ReadGPOLocalGroups_AffectedComputers_0() +// { +// var mockLDAPUtils = new Mock(); +// mockLDAPUtils.Setup(x => x.QueryLDAP( +// It.IsAny(), +// It.IsAny(), +// It.IsAny(), +// It.IsAny(), +// It.IsAny(), +// It.IsAny(), +// It.IsAny(), +// It.IsAny(), +// It.IsAny(), +// It.IsAny(), +// It.IsAny() +// )).Returns(new List()); +// var processor = new GPOLocalGroupProcessor(mockLDAPUtils.Object); +// +// var result = await processor.ReadGPOLocalGroups("teapot", null); +// Assert.NotNull(result); +// Assert.Empty(result.AffectedComputers); +// Assert.Empty(result.DcomUsers); +// Assert.Empty(result.RemoteDesktopUsers); +// Assert.Empty(result.LocalAdmins); +// Assert.Empty(result.PSRemoteUsers); +// } +// +// [Fact(Skip = "")] +// public async Task GPOLocalGroupProcessor_ReadGPOLocalGroups_Null_Gpcfilesyspath() +// { +// var mockLDAPUtils = new Mock(); +// var mockSearchResultEntry = new Mock(); +// mockSearchResultEntry.Setup(x => x.GetSid()).Returns("teapot"); +// var mockSearchResults = new List(); +// mockSearchResults.Add(mockSearchResultEntry.Object); +// mockLDAPUtils.Setup(x => x.QueryLDAP(new LDAPQueryOptions +// { +// Filter = "(&(samaccounttype=805306369)(!(objectclass=msDS-GroupManagedServiceAccount))(!(objectclass=msDS-ManagedServiceAccount)))", +// Scope = SearchScope.Subtree, +// Properties = CommonProperties.ObjectSID, +// AdsPath = null +// })) +// .Returns(mockSearchResults.ToArray()); +// +// var processor = new GPOLocalGroupProcessor(mockLDAPUtils.Object); +// var testGPLinkProperty = +// "[LDAP:/o=foo/ou=foo Group (ABC123)/cn=foouser (blah)123/dc=somedomain;0;][LDAP:/o=foo/ou=foo Group (ABC123)/cn=foouser (blah)123/dc=someotherdomain;2;]"; +// var result = await processor.ReadGPOLocalGroups(testGPLinkProperty, null); +// +// Assert.NotNull(result); +// Assert.Single(result.AffectedComputers); +// var actual = result.AffectedComputers.First(); +// Assert.Equal(Label.Computer, actual.ObjectType); +// Assert.Equal("teapot", actual.ObjectIdentifier); +// } +// +// [Fact] +// public async Task GPOLocalGroupProcessor_ReadGPOLocalGroups() +// { +// var mockLDAPUtils = new Mock(MockBehavior.Strict); +// var gpcFileSysPath = Path.GetTempPath(); +// +// var groupsXmlPath = Path.Join(gpcFileSysPath, "MACHINE", "Preferences", "Groups", "Groups.xml"); +// +// Path.GetDirectoryName(groupsXmlPath); +// Directory.CreateDirectory(Path.GetDirectoryName(groupsXmlPath)); +// File.WriteAllText(groupsXmlPath, GroupXmlContent); +// +// var mockComputerEntry = new Mock(); +// mockComputerEntry.Setup(x => x.GetSid()).Returns("teapot"); +// var mockComputerResults = new List(); +// mockComputerResults.Add(mockComputerEntry.Object); +// +// var mockGCPFileSysPathEntry = new Mock(); +// mockGCPFileSysPathEntry.Setup(x => x.GetProperty(It.IsAny())).Returns(gpcFileSysPath); +// var mockGCPFileSysPathResults = new List(); +// mockGCPFileSysPathResults.Add(mockGCPFileSysPathEntry.Object); +// +// mockLDAPUtils.SetupSequence(x => x.QueryLDAP(It.IsAny())) +// .Returns(mockComputerResults.ToArray()) +// .Returns(mockGCPFileSysPathResults.ToArray()); +// +// var processor = new GPOLocalGroupProcessor(mockLDAPUtils.Object); +// +// var testGPLinkProperty = +// "[LDAP:/o=foo/ou=foo Group (ABC123)/cn=foouser (blah)123/dc=somedomain;0;][LDAP:/o=foo/ou=foo Group (ABC123)/cn=foouser (blah)123/dc=someotherdomain;2;]"; +// var result = await processor.ReadGPOLocalGroups(testGPLinkProperty, null); +// +// mockLDAPUtils.VerifyAll(); +// Assert.NotNull(result); +// Assert.Single(result.AffectedComputers); +// var actual = result.AffectedComputers.First(); +// Assert.Equal(Label.Computer, actual.ObjectType); +// Assert.Equal("teapot", actual.ObjectIdentifier); +// } +// +// [Fact] +// public async Task GPOLocalGroupProcess_ProcessGPOXMLFile_NoFile() +// { +// var mockLDAPUtils = new Mock(); +// var processor = new GPOLocalGroupProcessor(mockLDAPUtils.Object); +// var gpcFileSysPath = Path.Join(Path.GetTempPath(), "made", "up", "path"); +// +// var actual = processor.ProcessGPOXmlFile(gpcFileSysPath, "somedomain").ToList(); +// Assert.NotNull(actual); +// Assert.Empty(actual); +// } +// +// [Fact] +// public async Task GPOLocalGroupProcess_ProcessGPOXMLFile_Disabled() +// { +// var mockLDAPUtils = new Mock(); +// var gpcFileSysPath = Path.GetTempPath(); +// var groupsXmlPath = Path.Join(gpcFileSysPath, "MACHINE", "Preferences", "Groups", "Groups.xml"); +// +// Directory.CreateDirectory(Path.GetDirectoryName(groupsXmlPath)); +// File.WriteAllText(groupsXmlPath, GroupXmlContentDisabled); +// +// var processor = new GPOLocalGroupProcessor(mockLDAPUtils.Object); +// +// var actual = processor.ProcessGPOXmlFile(gpcFileSysPath, "somedomain").ToList(); +// Assert.NotNull(actual); +// Assert.Empty(actual); +// } +// +// [Fact] +// public async Task GPOLocalGroupProcessor_ProcessGPOXMLFile() +// { +// var mockLDAPUtils = new Mock(); +// mockLDAPUtils.Setup(x => x.ResolveAccountName(It.IsAny(), It.IsAny())) +// .Returns(new TypedPrincipal("S-1-5-21-3130019616-2776909439-2417379446-513", Label.User)); +// var gpcFileSysPath = Path.GetTempPath(); +// var groupsXmlPath = Path.Join(gpcFileSysPath, "MACHINE", "Preferences", "Groups", "Groups.xml"); +// +// Directory.CreateDirectory(Path.GetDirectoryName(groupsXmlPath)); +// File.WriteAllText(groupsXmlPath, GroupXmlContent); +// +// var processor = new GPOLocalGroupProcessor(mockLDAPUtils.Object); +// var actual = processor.ProcessGPOXmlFile(gpcFileSysPath, "somedomain").ToList(); +// +// Assert.NotNull(actual); +// Assert.NotEmpty(actual); +// } +// +// [Fact] +// public async Task GPOLocalGroupProcess_ProcessGPOTemplateFile_NoFile() +// { +// var mockLDAPUtils = new Mock(); +// var processor = new GPOLocalGroupProcessor(mockLDAPUtils.Object); +// var gpcFileSysPath = Path.Join(Path.GetTempPath(), "made", "up", "path"); +// +// var actual = await processor.ProcessGPOTemplateFile(gpcFileSysPath, "somedomain").ToListAsync(); +// Assert.NotNull(actual); +// Assert.Empty(actual); +// } +// +// [Fact] +// public async Task GPOLocalGroupProcess_ProcessGPOTemplateFile_NoMatch() +// { +// var mockLDAPUtils = new Mock(); +// var gpcFileSysPath = Path.GetTempPath(); +// var gptTmplPath = Path.Join(gpcFileSysPath, "MACHINE", "Microsoft", "Windows NT", "SecEdit", "GptTmpl.inf"); +// +// Directory.CreateDirectory(Path.GetDirectoryName(gptTmplPath)); +// File.WriteAllText(gptTmplPath, GpttmplInfContentNoMatch); +// +// var processor = new GPOLocalGroupProcessor(mockLDAPUtils.Object); +// +// var actual = await processor.ProcessGPOTemplateFile(gpcFileSysPath, "somedomain").ToListAsync(); +// Assert.NotNull(actual); +// Assert.Empty(actual); +// } +// +// [Fact] +// public async Task GPOLocalGroupProcess_ProcessGPOTemplateFile_NullSID() +// { +// var mockLDAPUtils = new Mock(); +// var gpcFileSysPath = Path.GetTempPath(); +// var gptTmplPath = Path.Join(gpcFileSysPath, "MACHINE", "Microsoft", "Windows NT", "SecEdit", "GptTmpl.inf"); +// +// Directory.CreateDirectory(Path.GetDirectoryName(gptTmplPath)); +// File.WriteAllText(gptTmplPath, GpttmplInfContent); +// +// var processor = new GPOLocalGroupProcessor(mockLDAPUtils.Object); +// +// var actual = await processor.ProcessGPOTemplateFile(gpcFileSysPath, "somedomain").ToListAsync(); +// Assert.NotNull(actual); +// Assert.NotEmpty(actual); +// } +// +// [Fact] +// public async Task GPOLocalGroupProcess_ProcessGPOTemplateFile() +// { +// var mockLDAPUtils = new Mock(); +// mockLDAPUtils.Setup(x => x.ResolveAccountName(It.IsAny(), It.IsAny())) +// .Returns(new TypedPrincipal("S-1-5-21-3130019616-2776909439-2417379446-513", Label.User)); +// var gpcFileSysPath = Path.GetTempPath(); +// var gptTmplPath = Path.Join(gpcFileSysPath, "MACHINE", "Microsoft", "Windows NT", "SecEdit", "GptTmpl.inf"); +// +// Directory.CreateDirectory(Path.GetDirectoryName(gptTmplPath)); +// File.WriteAllText(gptTmplPath, GpttmplInfContent); +// +// var processor = new GPOLocalGroupProcessor(mockLDAPUtils.Object); +// +// var actual = await processor.ProcessGPOTemplateFile(gpcFileSysPath, "somedomain").ToListAsync(); +// Assert.NotNull(actual); +// // Assert.Empty(actual); +// } +// +// [Fact] +// public void GPOLocalGroupProcess_GroupAction() +// { +// var ga = new GPOLocalGroupProcessor.GroupAction(); +// var tp = ga.ToTypedPrincipal(); +// var str = ga.ToString(); +// +// Assert.NotNull(tp); +// Assert.Equal(new TypedPrincipal(), tp); +// Assert.NotNull(str); +// Assert.Equal("Action: Add, Target: RestrictedMemberOf, TargetSid: , TargetType: Base, TargetRid: None", +// str); +// } +// } +// } \ No newline at end of file diff --git a/test/unit/GroupProcessorTest.cs b/test/unit/GroupProcessorTest.cs index 17f4810b..3d23b90b 100644 --- a/test/unit/GroupProcessorTest.cs +++ b/test/unit/GroupProcessorTest.cs @@ -1,130 +1,130 @@ -using System; -using System.Linq; -using CommonLibTest.Facades; -using Moq; -using SharpHoundCommonLib; -using SharpHoundCommonLib.Enums; -using SharpHoundCommonLib.OutputTypes; -using SharpHoundCommonLib.Processors; -using Xunit; -using Xunit.Abstractions; - -namespace CommonLibTest -{ - public class GroupProcessorTest - { - private readonly string _testDomainName; - - private readonly string[] _testMembership = - { - "CN=Domain Admins,CN=Users,DC=testlab,DC=local", - "CN=Enterprise Admins,CN=Users,DC=testlab,DC=local", - "CN=Administrator,CN=Users,DC=testlab,DC=local", - "CN=NonExistent,CN=Users,DC=testlab,DC=local" - }; - - private readonly ITestOutputHelper _testOutputHelper; - private GroupProcessor _baseProcessor; - - public GroupProcessorTest(ITestOutputHelper testOutputHelper) - { - _testOutputHelper = testOutputHelper; - _testDomainName = "TESTLAB.LOCAL"; - _baseProcessor = new GroupProcessor(new LDAPUtils()); - } - - [Fact] - public void GroupProcessor_GetPrimaryGroupInfo_NullPrimaryGroupID_ReturnsNull() - { - var result = GroupProcessor.GetPrimaryGroupInfo(null, null); - Assert.Null(result); - } - - [WindowsOnlyFact] - public void GroupProcessor_GetPrimaryGroupInfo_ReturnsCorrectSID() - { - var result = GroupProcessor.GetPrimaryGroupInfo("513", "S-1-5-21-3130019616-2776909439-2417379446-1105"); - Assert.Equal("S-1-5-21-3130019616-2776909439-2417379446-513", result); - } - - [Fact] - public void GroupProcessor_GetPrimaryGroupInfo_BadSID_ReturnsNull() - { - var result = GroupProcessor.GetPrimaryGroupInfo("513", "ABC123"); - Assert.Null(result); - } - - [Fact] - public void GroupProcessor_ReadGroupMembers_EmptyMembers_DoesRangedRetrieval() - { - var mockUtils = new Mock(); - var expected = new TypedPrincipal[] - { - new() - { - ObjectIdentifier = "S-1-5-21-3130019616-2776909439-2417379446-512", - ObjectType = Label.Group - }, - new() - { - ObjectIdentifier = "S-1-5-21-3130019616-2776909439-2417379446-519", - ObjectType = Label.Group - }, - new() - { - ObjectIdentifier = "S-1-5-21-3130019616-2776909439-2417379446-500", - ObjectType = Label.User - }, - new() - { - ObjectIdentifier = "CN=NONEXISTENT,CN=USERS,DC=TESTLAB,DC=LOCAL", - ObjectType = Label.Base - } - }; - mockUtils.Setup(x => x.DoRangedRetrieval(It.IsAny(), It.IsAny())).Returns(_testMembership); - var processor = new GroupProcessor(mockUtils.Object); - - var results = processor - .ReadGroupMembers("CN=Administrators,CN=Builtin,DC=testlab,DC=local", Array.Empty()).ToArray(); - foreach (var t in results) _testOutputHelper.WriteLine(t.ToString()); - Assert.Equal(4, results.Length); - Assert.Equal(expected, results); - } - - [WindowsOnlyFact] - public void GroupProcessor_ReadGroupMembers_ReturnsCorrectMembers() - { - var utils = new MockLDAPUtils(); - var processor = new GroupProcessor(utils); - var expected = new TypedPrincipal[] - { - new() - { - ObjectIdentifier = "S-1-5-21-3130019616-2776909439-2417379446-512", - ObjectType = Label.Group - }, - new() - { - ObjectIdentifier = "S-1-5-21-3130019616-2776909439-2417379446-519", - ObjectType = Label.Group - }, - new() - { - ObjectIdentifier = "S-1-5-21-3130019616-2776909439-2417379446-500", - ObjectType = Label.User - }, - new() - { - ObjectIdentifier = "CN=NONEXISTENT,CN=USERS,DC=TESTLAB,DC=LOCAL", - ObjectType = Label.Base - } - }; - - var results = processor - .ReadGroupMembers("CN=Administrators,CN=Builtin,DC=testlab,DC=local", _testMembership).ToArray(); - foreach (var t in results) _testOutputHelper.WriteLine(t.ToString()); - Assert.Equal(4, results.Length); - Assert.Equal(expected, results); - } - } -} \ No newline at end of file +// using System; +// using System.Linq; +// using CommonLibTest.Facades; +// using Moq; +// using SharpHoundCommonLib; +// using SharpHoundCommonLib.Enums; +// using SharpHoundCommonLib.OutputTypes; +// using SharpHoundCommonLib.Processors; +// using Xunit; +// using Xunit.Abstractions; +// +// namespace CommonLibTest +// { +// public class GroupProcessorTest +// { +// private readonly string _testDomainName; +// +// private readonly string[] _testMembership = +// { +// "CN=Domain Admins,CN=Users,DC=testlab,DC=local", +// "CN=Enterprise Admins,CN=Users,DC=testlab,DC=local", +// "CN=Administrator,CN=Users,DC=testlab,DC=local", +// "CN=NonExistent,CN=Users,DC=testlab,DC=local" +// }; +// +// private readonly ITestOutputHelper _testOutputHelper; +// private GroupProcessor _baseProcessor; +// +// public GroupProcessorTest(ITestOutputHelper testOutputHelper) +// { +// _testOutputHelper = testOutputHelper; +// _testDomainName = "TESTLAB.LOCAL"; +// _baseProcessor = new GroupProcessor(new LDAPUtils()); +// } +// +// [Fact] +// public void GroupProcessor_GetPrimaryGroupInfo_NullPrimaryGroupID_ReturnsNull() +// { +// var result = GroupProcessor.GetPrimaryGroupInfo(null, null); +// Assert.Null(result); +// } +// +// [WindowsOnlyFact] +// public void GroupProcessor_GetPrimaryGroupInfo_ReturnsCorrectSID() +// { +// var result = GroupProcessor.GetPrimaryGroupInfo("513", "S-1-5-21-3130019616-2776909439-2417379446-1105"); +// Assert.Equal("S-1-5-21-3130019616-2776909439-2417379446-513", result); +// } +// +// [Fact] +// public void GroupProcessor_GetPrimaryGroupInfo_BadSID_ReturnsNull() +// { +// var result = GroupProcessor.GetPrimaryGroupInfo("513", "ABC123"); +// Assert.Null(result); +// } +// +// [Fact] +// public void GroupProcessor_ReadGroupMembers_EmptyMembers_DoesRangedRetrieval() +// { +// var mockUtils = new Mock(); +// var expected = new TypedPrincipal[] +// { +// new() +// { +// ObjectIdentifier = "S-1-5-21-3130019616-2776909439-2417379446-512", +// ObjectType = Label.Group +// }, +// new() +// { +// ObjectIdentifier = "S-1-5-21-3130019616-2776909439-2417379446-519", +// ObjectType = Label.Group +// }, +// new() +// { +// ObjectIdentifier = "S-1-5-21-3130019616-2776909439-2417379446-500", +// ObjectType = Label.User +// }, +// new() +// { +// ObjectIdentifier = "CN=NONEXISTENT,CN=USERS,DC=TESTLAB,DC=LOCAL", +// ObjectType = Label.Base +// } +// }; +// mockUtils.Setup(x => x.DoRangedRetrieval(It.IsAny(), It.IsAny())).Returns(_testMembership); +// var processor = new GroupProcessor(mockUtils.Object); +// +// var results = processor +// .ReadGroupMembers("CN=Administrators,CN=Builtin,DC=testlab,DC=local", Array.Empty()).ToArray(); +// foreach (var t in results) _testOutputHelper.WriteLine(t.ToString()); +// Assert.Equal(4, results.Length); +// Assert.Equal(expected, results); +// } +// +// [WindowsOnlyFact] +// public void GroupProcessor_ReadGroupMembers_ReturnsCorrectMembers() +// { +// var utils = new MockLDAPUtils(); +// var processor = new GroupProcessor(utils); +// var expected = new TypedPrincipal[] +// { +// new() +// { +// ObjectIdentifier = "S-1-5-21-3130019616-2776909439-2417379446-512", +// ObjectType = Label.Group +// }, +// new() +// { +// ObjectIdentifier = "S-1-5-21-3130019616-2776909439-2417379446-519", +// ObjectType = Label.Group +// }, +// new() +// { +// ObjectIdentifier = "S-1-5-21-3130019616-2776909439-2417379446-500", +// ObjectType = Label.User +// }, +// new() +// { +// ObjectIdentifier = "CN=NONEXISTENT,CN=USERS,DC=TESTLAB,DC=LOCAL", +// ObjectType = Label.Base +// } +// }; +// +// var results = processor +// .ReadGroupMembers("CN=Administrators,CN=Builtin,DC=testlab,DC=local", _testMembership).ToArray(); +// foreach (var t in results) _testOutputHelper.WriteLine(t.ToString()); +// Assert.Equal(4, results.Length); +// Assert.Equal(expected, results); +// } +// } +// } \ No newline at end of file diff --git a/test/unit/LDAPPropertyTests.cs b/test/unit/LDAPPropertyTests.cs index e405a18a..39014b8b 100644 --- a/test/unit/LDAPPropertyTests.cs +++ b/test/unit/LDAPPropertyTests.cs @@ -769,78 +769,78 @@ public void LDAPPropertyProcessor_ReadCertTemplateProperties() Assert.Contains("issuancepolicies", keys); } - - [Fact] - public void LDAPPropertyProcessor_ReadIssuancePolicyProperties() - { - var mock = new MockSearchResultEntry("CN\u003d6250993.11BB1AB25A8A65E9FCDF709FCDD5FBC6,CN\u003dOID,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dESC10,DC\u003dLOCAL", - new Dictionary - { - {"domain", "ESC10.LOCAL"}, - {"name", "KEYADMINSOID@ESC10.LOCAL"}, - {"domainsid", "S-1-5-21-3662707843-2053279151-3839588741"}, - {"description", null}, - {"whencreated", 1712567279}, - {"displayname", "KeyAdminsOID"}, - {"certtemplateoid", "1.3.6.1.4.1.311.21.8.4571196.1884641.3293620.10686285.12068043.134.1.30"}, - {"msds-oidtogrouplink", "CN=ENTERPRISE KEY ADMINS,CN=USERS,DC=ESC10,DC=LOCAL"} - , - }, "1E5311A8-E949-4E02-8E08-234ED63200DE", Label.IssuancePolicy); - - var mockLDAPUtils = new MockLDAPUtils(); - var ldapPropertyProcessor = new LDAPPropertyProcessor(mockLDAPUtils); - - - var test = ldapPropertyProcessor.ReadIssuancePolicyProperties(mock); - var keys = test.Props.Keys; - - //These are not common properties - Assert.DoesNotContain("domain", keys); - Assert.DoesNotContain("name", keys); - Assert.DoesNotContain("domainsid", keys); - - Assert.Contains("description", keys); - Assert.Contains("whencreated", keys); - Assert.Contains("displayname", keys); - Assert.Contains("certtemplateoid", keys); - Assert.Contains("oidgrouplink", keys); - } - - [Fact] - public void LDAPPropertyProcessor_ReadIssuancePolicyProperties_NoOIDGroupLink() - { - var mock = new MockSearchResultEntry("CN\u003d6250993.11BB1AB25A8A65E9FCDF709FCDD5FBC6,CN\u003dOID,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dESC10,DC\u003dLOCAL", - new Dictionary - { - {"domain", "ESC10.LOCAL"}, - {"name", "KEYADMINSOID@ESC10.LOCAL"}, - {"domainsid", "S-1-5-21-3662707843-2053279151-3839588741"}, - {"description", null}, - {"whencreated", 1712567279}, - {"displayname", "KeyAdminsOID"}, - {"certtemplateoid", "1.3.6.1.4.1.311.21.8.4571196.1884641.3293620.10686285.12068043.134.1.30"}, - {"msds-oidtogrouplink", null} - , - }, "1E5311A8-E949-4E02-8E08-234ED63200DE", Label.IssuancePolicy); - - var mockLDAPUtils = new MockLDAPUtils(); - var ldapPropertyProcessor = new LDAPPropertyProcessor(mockLDAPUtils); - - - var test = ldapPropertyProcessor.ReadIssuancePolicyProperties(mock); - var keys = test.Props.Keys; - - //These are not common properties - Assert.DoesNotContain("domain", keys); - Assert.DoesNotContain("name", keys); - Assert.DoesNotContain("domainsid", keys); - Assert.DoesNotContain("oidgrouplink", keys); - - Assert.Contains("description", keys); - Assert.Contains("whencreated", keys); - Assert.Contains("displayname", keys); - Assert.Contains("certtemplateoid", keys); - } + // + // [Fact] + // public void LDAPPropertyProcessor_ReadIssuancePolicyProperties() + // { + // var mock = new MockSearchResultEntry("CN\u003d6250993.11BB1AB25A8A65E9FCDF709FCDD5FBC6,CN\u003dOID,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dESC10,DC\u003dLOCAL", + // new Dictionary + // { + // {"domain", "ESC10.LOCAL"}, + // {"name", "KEYADMINSOID@ESC10.LOCAL"}, + // {"domainsid", "S-1-5-21-3662707843-2053279151-3839588741"}, + // {"description", null}, + // {"whencreated", 1712567279}, + // {"displayname", "KeyAdminsOID"}, + // {"certtemplateoid", "1.3.6.1.4.1.311.21.8.4571196.1884641.3293620.10686285.12068043.134.1.30"}, + // {"msds-oidtogrouplink", "CN=ENTERPRISE KEY ADMINS,CN=USERS,DC=ESC10,DC=LOCAL"} + // , + // }, "1E5311A8-E949-4E02-8E08-234ED63200DE", Label.IssuancePolicy); + // + // var mockLDAPUtils = new MockLDAPUtils(); + // var ldapPropertyProcessor = new LDAPPropertyProcessor(mockLDAPUtils); + // + // + // var test = ldapPropertyProcessor.ReadIssuancePolicyProperties(mock); + // var keys = test.Props.Keys; + // + // //These are not common properties + // Assert.DoesNotContain("domain", keys); + // Assert.DoesNotContain("name", keys); + // Assert.DoesNotContain("domainsid", keys); + // + // Assert.Contains("description", keys); + // Assert.Contains("whencreated", keys); + // Assert.Contains("displayname", keys); + // Assert.Contains("certtemplateoid", keys); + // Assert.Contains("oidgrouplink", keys); + // } + // + // [Fact] + // public void LDAPPropertyProcessor_ReadIssuancePolicyProperties_NoOIDGroupLink() + // { + // var mock = new MockSearchResultEntry("CN\u003d6250993.11BB1AB25A8A65E9FCDF709FCDD5FBC6,CN\u003dOID,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dESC10,DC\u003dLOCAL", + // new Dictionary + // { + // {"domain", "ESC10.LOCAL"}, + // {"name", "KEYADMINSOID@ESC10.LOCAL"}, + // {"domainsid", "S-1-5-21-3662707843-2053279151-3839588741"}, + // {"description", null}, + // {"whencreated", 1712567279}, + // {"displayname", "KeyAdminsOID"}, + // {"certtemplateoid", "1.3.6.1.4.1.311.21.8.4571196.1884641.3293620.10686285.12068043.134.1.30"}, + // {"msds-oidtogrouplink", null} + // , + // }, "1E5311A8-E949-4E02-8E08-234ED63200DE", Label.IssuancePolicy); + // + // var mockLDAPUtils = new MockLDAPUtils(); + // var ldapPropertyProcessor = new LDAPPropertyProcessor(mockLDAPUtils); + // + // + // var test = ldapPropertyProcessor.ReadIssuancePolicyProperties(mock); + // var keys = test.Props.Keys; + // + // //These are not common properties + // Assert.DoesNotContain("domain", keys); + // Assert.DoesNotContain("name", keys); + // Assert.DoesNotContain("domainsid", keys); + // Assert.DoesNotContain("oidgrouplink", keys); + // + // Assert.Contains("description", keys); + // Assert.Contains("whencreated", keys); + // Assert.Contains("displayname", keys); + // Assert.Contains("certtemplateoid", keys); + // } [Fact] public void LDAPPropertyProcessor_ParseAllProperties() diff --git a/test/unit/LDAPUtilsTest.cs b/test/unit/LDAPUtilsTest.cs index aecedd97..c9b7b79b 100644 --- a/test/unit/LDAPUtilsTest.cs +++ b/test/unit/LDAPUtilsTest.cs @@ -1,280 +1,281 @@ -using System; -using System.Collections.Generic; -using System.DirectoryServices.ActiveDirectory; -using System.DirectoryServices.Protocols; -using System.Threading; -using CommonLibTest.Facades; -using Moq; -using SharpHoundCommonLib; -using SharpHoundCommonLib.Enums; -using SharpHoundCommonLib.Exceptions; -using Xunit; -using Xunit.Abstractions; - -namespace CommonLibTest -{ - public class LDAPUtilsTest : IDisposable - { - private readonly string _testDomainName; - private readonly string _testForestName; - private readonly ITestOutputHelper _testOutputHelper; - private readonly ILDAPUtils _utils; - - #region Constructor(s) - - public LDAPUtilsTest(ITestOutputHelper testOutputHelper) - { - _testOutputHelper = testOutputHelper; - _testForestName = "PARENT.LOCAL"; - _testDomainName = "TESTLAB.LOCAL"; - _utils = new LDAPUtils(); - // This runs once per test. - } - - #endregion - - #region IDispose Implementation - - public void Dispose() - { - // Tear down (called once per test) - } - - #endregion - - [Fact] - public void SanityCheck() - { - Assert.True(true); - } - - #region Private Members - - #endregion - - #region Creation - - /// - /// - [Fact] - public void GetUserGlobalCatalogMatches_Garbage_ReturnsNull() - { - var test = _utils.GetUserGlobalCatalogMatches("foo"); - _testOutputHelper.WriteLine(test.ToString()); - Assert.NotNull(test); - Assert.Empty(test); - } - - [Fact] - public void ResolveIDAndType_DuplicateSid_ReturnsNull() - { - var test = _utils.ResolveIDAndType("ABC0ACNF", null); - Assert.Null(test); - } - - [Fact] - public void ResolveIDAndType_WellKnownAdministrators_ReturnsConvertedSID() - { - var test = _utils.ResolveIDAndType("S-1-5-32-544", "TESTLAB.LOCAL"); - Assert.NotNull(test); - Assert.Equal(Label.Group, test.ObjectType); - Assert.Equal("TESTLAB.LOCAL-S-1-5-32-544", test.ObjectIdentifier); - } - - [WindowsOnlyFact] - public void GetWellKnownPrincipal_EnterpriseDomainControllers_ReturnsCorrectedSID() - { - var mock = new Mock(); - var mockForest = MockableForest.Construct(_testForestName); - mock.Setup(x => x.GetForest(It.IsAny())).Returns(mockForest); - var result = mock.Object.GetWellKnownPrincipal("S-1-5-9", null, out var typedPrincipal); - Assert.True(result); - Assert.Equal($"{_testForestName}-S-1-5-9", typedPrincipal.ObjectIdentifier); - Assert.Equal(Label.Group, typedPrincipal.ObjectType); - } - - [Fact] - public void BuildLdapPath_BadDomain_ReturnsNull() - { - var mock = new Mock(); - //var mockDomain = MockableDomain.Construct("TESTLAB.LOCAL"); - mock.Setup(x => x.GetDomain(It.IsAny())) - .Returns((Domain)null); - var result = mock.Object.BuildLdapPath("TEST", "ABC"); - Assert.Null(result); - } - - [WindowsOnlyFact] - public void BuildLdapPath_HappyPath() - { - var mock = new Mock(); - var mockDomain = MockableDomain.Construct("TESTLAB.LOCAL"); - mock.Setup(x => x.GetDomain(It.IsAny())) - .Returns(mockDomain); - var result = mock.Object.BuildLdapPath(DirectoryPaths.PKILocation, "ABC"); - Assert.NotNull(result); - Assert.Equal("CN=Public Key Services,CN=Services,CN=Configuration,DC=TESTLAB,DC=LOCAL", result); - } - - [Fact] - public void GetWellKnownPrincipal_NonWellKnown_ReturnsNull() - { - var result = _utils.GetWellKnownPrincipal("S-1-5-21-123456-78910", _testDomainName, out var typedPrincipal); - Assert.False(result); - Assert.Null(typedPrincipal); - } - - [Fact] - public void GetWellKnownPrincipal_WithDomain_ConvertsSID() - { - var result = - _utils.GetWellKnownPrincipal("S-1-5-32-544", _testDomainName, out var typedPrincipal); - Assert.True(result); - Assert.Equal(Label.Group, typedPrincipal.ObjectType); - Assert.Equal($"{_testDomainName}-S-1-5-32-544", typedPrincipal.ObjectIdentifier); - } - - [Fact] - public void DistinguishedNameToDomain_RegularObject_CorrectDomain() - { - var result = SharpHoundCommonLib.Helpers.DistinguishedNameToDomain( - "CN=Account Operators,CN=Builtin,DC=testlab,DC=local"); - Assert.Equal("TESTLAB.LOCAL", result); - - result = SharpHoundCommonLib.Helpers.DistinguishedNameToDomain("DC=testlab,DC=local"); - Assert.Equal("TESTLAB.LOCAL", result); - } - - [Fact] - public void GetDomainRangeSize_BadDomain_ReturnsDefault() - { - var mock = new Mock(); - mock.Setup(x => x.GetDomain(It.IsAny())).Returns((Domain)null); - var result = mock.Object.GetDomainRangeSize(); - Assert.Equal(750, result); - } - - [Fact] - public void GetDomainRangeSize_RespectsDefaultParam() - { - var mock = new Mock(); - mock.Setup(x => x.GetDomain(It.IsAny())).Returns((Domain)null); - - var result = mock.Object.GetDomainRangeSize(null, 1000); - Assert.Equal(1000, result); - } - - [WindowsOnlyFact] - public void GetDomainRangeSize_NoLdapEntry_ReturnsDefault() - { - var mock = new Mock(); - var mockDomain = MockableDomain.Construct("testlab.local"); - mock.Setup(x => x.GetDomain(It.IsAny())).Returns(mockDomain); - mock.Setup(x => x.QueryLDAP(It.IsAny(), It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny())).Returns(new List()); - - var result = mock.Object.GetDomainRangeSize(); - Assert.Equal(750, result); - } - - [WindowsOnlyFact] - public void GetDomainRangeSize_ExpectedResults() - { - var mock = new Mock(); - var mockDomain = MockableDomain.Construct("testlab.local"); - mock.Setup(x => x.GetDomain(It.IsAny())).Returns(mockDomain); - var searchResult = new MockSearchResultEntry("CN=Default Query Policy,CN=Query-Policies,CN=Directory Service,CN=Windows NT,CN=Services,CN=Configuration,DC=testlab,DC=local", new Dictionary - { - {"ldapadminlimits", new[] - { - "MaxPageSize=1250" - }}, - }, "abc123", Label.Base); - - mock.Setup(x => x.QueryLDAP(It.IsAny(), It.IsAny(), null, - It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny())).Returns(new List { searchResult }); - var result = mock.Object.GetDomainRangeSize(); - Assert.Equal(1250, result); - } - - [Fact] - public void DistinguishedNameToDomain_DeletedObjects_CorrectDomain() - { - var result = SharpHoundCommonLib.Helpers.DistinguishedNameToDomain( - @"DC=..Deleted-_msdcs.testlab.local\0ADEL:af1f072f-28d7-4b86-9b87-a408bfc9cb0d,CN=Deleted Objects,DC=testlab,DC=local"); - Assert.Equal("TESTLAB.LOCAL", result); - } - - [Fact] - public void QueryLDAP_With_Exception() - { - var options = new LDAPQueryOptions - { - ThrowException = true - }; - - Assert.Throws( - () => - { - foreach (var sre in _utils.QueryLDAP(null, new SearchScope(), null, new CancellationToken(), null, - false, false, null, false, false, true)) - { - // We shouldn't reach this anyway, and all we care about is if exceptions are bubbling - } - }); - - Assert.Throws( - () => - { - foreach (var sre in _utils.QueryLDAP(options)) - { - // We shouldn't reach this anyway, and all we care about is if exceptions are bubbling - } - }); - } - - [Fact] - public void QueryLDAP_Without_Exception() - { - Exception exception; - - var options = new LDAPQueryOptions - { - ThrowException = false - }; - - exception = Record.Exception( - () => - { - foreach (var sre in _utils.QueryLDAP(null, new SearchScope(), null, new CancellationToken())) - { - // We shouldn't reach this anyway, and all we care about is if exceptions are bubbling - } - }); - Assert.Null(exception); - - exception = Record.Exception( - () => - { - foreach (var sre in _utils.QueryLDAP(options)) - { - // We shouldn't reach this anyway, and all we care about is if exceptions are bubbling - } - }); - Assert.Null(exception); - } - - #endregion - - #region Structural - - #endregion - - - #region Behavioral - - #endregion - } -} \ No newline at end of file +// using System; +// using System.Collections.Generic; +// using System.DirectoryServices.ActiveDirectory; +// using System.DirectoryServices.Protocols; +// using System.Threading; +// using System.Threading.Tasks; +// using CommonLibTest.Facades; +// using Moq; +// using SharpHoundCommonLib; +// using SharpHoundCommonLib.Enums; +// using SharpHoundCommonLib.Exceptions; +// using Xunit; +// using Xunit.Abstractions; +// +// namespace CommonLibTest +// { +// public class LDAPUtilsTest : IDisposable +// { +// private readonly string _testDomainName; +// private readonly string _testForestName; +// private readonly ITestOutputHelper _testOutputHelper; +// private readonly ILdapUtils _utils; +// +// #region Constructor(s) +// +// public LDAPUtilsTest(ITestOutputHelper testOutputHelper) +// { +// _testOutputHelper = testOutputHelper; +// _testForestName = "PARENT.LOCAL"; +// _testDomainName = "TESTLAB.LOCAL"; +// _utils = new LdapUtils(); +// // This runs once per test. +// } +// +// #endregion +// +// #region IDispose Implementation +// +// public void Dispose() +// { +// // Tear down (called once per test) +// } +// +// #endregion +// +// [Fact] +// public void SanityCheck() +// { +// Assert.True(true); +// } +// +// #region Private Members +// +// #endregion +// +// #region Creation +// +// /// +// /// +// [Fact] +// public async Task GetUserGlobalCatalogMatches_Garbage_ReturnsNull() +// { +// var test = await _utils.GetGlobalCatalogMatches("foo"); +// _testOutputHelper.WriteLine(test.ToString()); +// Assert.True(test.Success); +// Assert.Empty(test.Sids); +// } +// +// [Fact] +// public void ResolveIDAndType_DuplicateSid_ReturnsNull() +// { +// var test = _utils.ResolveIDAndType("ABC0ACNF", null); +// Assert.Null(test); +// } +// +// [Fact] +// public void ResolveIDAndType_WellKnownAdministrators_ReturnsConvertedSID() +// { +// var test = _utils.ResolveIDAndType("S-1-5-32-544", "TESTLAB.LOCAL"); +// Assert.NotNull(test); +// Assert.Equal(Label.Group, test.ObjectType); +// Assert.Equal("TESTLAB.LOCAL-S-1-5-32-544", test.ObjectIdentifier); +// } +// +// [WindowsOnlyFact] +// public void GetWellKnownPrincipal_EnterpriseDomainControllers_ReturnsCorrectedSID() +// { +// var mock = new Mock(); +// var mockForest = MockableForest.Construct(_testForestName); +// mock.Setup(x => x.GetForest(It.IsAny())).Returns(mockForest); +// var result = mock.Object.GetWellKnownPrincipal("S-1-5-9", null, out var typedPrincipal); +// Assert.True(result); +// Assert.Equal($"{_testForestName}-S-1-5-9", typedPrincipal.ObjectIdentifier); +// Assert.Equal(Label.Group, typedPrincipal.ObjectType); +// } +// +// [Fact] +// public void BuildLdapPath_BadDomain_ReturnsNull() +// { +// var mock = new Mock(); +// //var mockDomain = MockableDomain.Construct("TESTLAB.LOCAL"); +// mock.Setup(x => x.GetDomain(It.IsAny())) +// .Returns((Domain)null); +// var result = mock.Object.BuildLdapPath("TEST", "ABC"); +// Assert.Null(result); +// } +// +// [WindowsOnlyFact] +// public void BuildLdapPath_HappyPath() +// { +// var mock = new Mock(); +// var mockDomain = MockableDomain.Construct("TESTLAB.LOCAL"); +// mock.Setup(x => x.GetDomain(It.IsAny())) +// .Returns(mockDomain); +// var result = mock.Object.BuildLdapPath(DirectoryPaths.PKILocation, "ABC"); +// Assert.NotNull(result); +// Assert.Equal("CN=Public Key Services,CN=Services,CN=Configuration,DC=TESTLAB,DC=LOCAL", result); +// } +// +// [Fact] +// public void GetWellKnownPrincipal_NonWellKnown_ReturnsNull() +// { +// var result = _utils.GetWellKnownPrincipal("S-1-5-21-123456-78910", _testDomainName, out var typedPrincipal); +// Assert.False(result); +// Assert.Null(typedPrincipal); +// } +// +// [Fact] +// public void GetWellKnownPrincipal_WithDomain_ConvertsSID() +// { +// var result = +// _utils.GetWellKnownPrincipal("S-1-5-32-544", _testDomainName, out var typedPrincipal); +// Assert.True(result); +// Assert.Equal(Label.Group, typedPrincipal.ObjectType); +// Assert.Equal($"{_testDomainName}-S-1-5-32-544", typedPrincipal.ObjectIdentifier); +// } +// +// [Fact] +// public void DistinguishedNameToDomain_RegularObject_CorrectDomain() +// { +// var result = SharpHoundCommonLib.Helpers.DistinguishedNameToDomain( +// "CN=Account Operators,CN=Builtin,DC=testlab,DC=local"); +// Assert.Equal("TESTLAB.LOCAL", result); +// +// result = SharpHoundCommonLib.Helpers.DistinguishedNameToDomain("DC=testlab,DC=local"); +// Assert.Equal("TESTLAB.LOCAL", result); +// } +// +// [Fact] +// public void GetDomainRangeSize_BadDomain_ReturnsDefault() +// { +// var mock = new Mock(); +// mock.Setup(x => x.GetDomain(It.IsAny())).Returns((Domain)null); +// var result = mock.Object.GetDomainRangeSize(); +// Assert.Equal(750, result); +// } +// +// [Fact] +// public void GetDomainRangeSize_RespectsDefaultParam() +// { +// var mock = new Mock(); +// mock.Setup(x => x.GetDomain(It.IsAny())).Returns((Domain)null); +// +// var result = mock.Object.GetDomainRangeSize(null, 1000); +// Assert.Equal(1000, result); +// } +// +// [WindowsOnlyFact] +// public void GetDomainRangeSize_NoLdapEntry_ReturnsDefault() +// { +// var mock = new Mock(); +// var mockDomain = MockableDomain.Construct("testlab.local"); +// mock.Setup(x => x.GetDomain(It.IsAny())).Returns(mockDomain); +// mock.Setup(x => x.QueryLDAP(It.IsAny(), It.IsAny(), It.IsAny(), +// It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), +// It.IsAny(), It.IsAny())).Returns(new List()); +// +// var result = mock.Object.GetDomainRangeSize(); +// Assert.Equal(750, result); +// } +// +// [WindowsOnlyFact] +// public void GetDomainRangeSize_ExpectedResults() +// { +// var mock = new Mock(); +// var mockDomain = MockableDomain.Construct("testlab.local"); +// mock.Setup(x => x.GetDomain(It.IsAny())).Returns(mockDomain); +// var searchResult = new MockSearchResultEntry("CN=Default Query Policy,CN=Query-Policies,CN=Directory Service,CN=Windows NT,CN=Services,CN=Configuration,DC=testlab,DC=local", new Dictionary +// { +// {"ldapadminlimits", new[] +// { +// "MaxPageSize=1250" +// }}, +// }, "abc123", Label.Base); +// +// mock.Setup(x => x.QueryLDAP(It.IsAny(), It.IsAny(), null, +// It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), +// It.IsAny(), It.IsAny())).Returns(new List { searchResult }); +// var result = mock.Object.GetDomainRangeSize(); +// Assert.Equal(1250, result); +// } +// +// [Fact] +// public void DistinguishedNameToDomain_DeletedObjects_CorrectDomain() +// { +// var result = SharpHoundCommonLib.Helpers.DistinguishedNameToDomain( +// @"DC=..Deleted-_msdcs.testlab.local\0ADEL:af1f072f-28d7-4b86-9b87-a408bfc9cb0d,CN=Deleted Objects,DC=testlab,DC=local"); +// Assert.Equal("TESTLAB.LOCAL", result); +// } +// +// [Fact] +// public void QueryLDAP_With_Exception() +// { +// var options = new LDAPQueryOptions +// { +// ThrowException = true +// }; +// +// Assert.Throws( +// () => +// { +// foreach (var sre in _utils.QueryLDAP(null, new SearchScope(), null, new CancellationToken(), null, +// false, false, null, false, false, true)) +// { +// // We shouldn't reach this anyway, and all we care about is if exceptions are bubbling +// } +// }); +// +// Assert.Throws( +// () => +// { +// foreach (var sre in _utils.QueryLDAP(options)) +// { +// // We shouldn't reach this anyway, and all we care about is if exceptions are bubbling +// } +// }); +// } +// +// [Fact] +// public void QueryLDAP_Without_Exception() +// { +// Exception exception; +// +// var options = new LDAPQueryOptions +// { +// ThrowException = false +// }; +// +// exception = Record.Exception( +// () => +// { +// foreach (var sre in _utils.QueryLDAP(null, new SearchScope(), null, new CancellationToken())) +// { +// // We shouldn't reach this anyway, and all we care about is if exceptions are bubbling +// } +// }); +// Assert.Null(exception); +// +// exception = Record.Exception( +// () => +// { +// foreach (var sre in _utils.QueryLDAP(options)) +// { +// // We shouldn't reach this anyway, and all we care about is if exceptions are bubbling +// } +// }); +// Assert.Null(exception); +// } +// +// #endregion +// +// #region Structural +// +// #endregion +// +// +// #region Behavioral +// +// #endregion +// } +// } \ No newline at end of file diff --git a/test/unit/SearchResultEntryTests.cs b/test/unit/SearchResultEntryTests.cs index 8eb59319..d49109eb 100644 --- a/test/unit/SearchResultEntryTests.cs +++ b/test/unit/SearchResultEntryTests.cs @@ -23,10 +23,14 @@ public void Test_GetLabelIssuanceOIDObjects() }; var sre = MockableSearchResultEntry.Construct(attribs, "CN=Test,CN=OID,CN=Public Key Services,CN=Services,CN=Configuration"); - Assert.Equal(Label.IssuancePolicy, sre.GetLabel()); + var success = sre.GetLabel(out var label); + Assert.True(success); + Assert.Equal(Label.IssuancePolicy, label); sre = MockableSearchResultEntry.Construct(attribs, "CN=OID,CN=Public Key Services,CN=Services,CN=Configuration"); - Assert.Equal(Label.Container, sre.GetLabel()); + success = sre.GetLabel(out label); + Assert.True(success); + Assert.Equal(Label.Container, label); } } } \ No newline at end of file From da873bc91d99c6e59c02c697f1987bab71a76ffb Mon Sep 17 00:00:00 2001 From: rvazarkar Date: Mon, 8 Jul 2024 11:52:32 -0400 Subject: [PATCH 36/68] wip: comment out more tests --- test/unit/LDAPPropertyTests.cs | 1862 ++++++++--------- .../unit/UserRightsAssignmentProcessorTest.cs | 132 +- 2 files changed, 997 insertions(+), 997 deletions(-) diff --git a/test/unit/LDAPPropertyTests.cs b/test/unit/LDAPPropertyTests.cs index 39014b8b..0c732437 100644 --- a/test/unit/LDAPPropertyTests.cs +++ b/test/unit/LDAPPropertyTests.cs @@ -1,931 +1,931 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using CommonLibTest.Facades; -using SharpHoundCommonLib; -using SharpHoundCommonLib.Enums; -using SharpHoundCommonLib.OutputTypes; -using SharpHoundCommonLib.Processors; -using Xunit; -using Xunit.Abstractions; - -namespace CommonLibTest -{ - public class LDAPPropertyTests - { - private readonly ITestOutputHelper _testOutputHelper; - - public LDAPPropertyTests(ITestOutputHelper testOutputHelper) - { - _testOutputHelper = testOutputHelper; - } - - [Fact] - public void LDAPPropertyProcessor_ReadDomainProperties_TestGoodData() - { - var mock = new MockSearchResultEntry("DC\u003dtestlab,DC\u003dlocal", new Dictionary - { - {"description", "TESTLAB Domain"}, - {"msds-behavior-version", "6"} - }, "S-1-5-21-3130019616-2776909439-2417379446", Label.Domain); - - var test = LDAPPropertyProcessor.ReadDomainProperties(mock); - Assert.Contains("functionallevel", test.Keys); - Assert.Equal("2012 R2", test["functionallevel"] as string); - Assert.Contains("description", test.Keys); - Assert.Equal("TESTLAB Domain", test["description"] as string); - } - - [Fact] - public void LDAPPropertyProcessor_ReadDomainProperties_TestBadFunctionalLevel() - { - var mock = new MockSearchResultEntry("DC\u003dtestlab,DC\u003dlocal", new Dictionary - { - {"msds-behavior-version", "a"} - }, "S-1-5-21-3130019616-2776909439-2417379446", Label.Domain); - - var test = LDAPPropertyProcessor.ReadDomainProperties(mock); - Assert.Contains("functionallevel", test.Keys); - Assert.Equal("Unknown", test["functionallevel"] as string); - } - - [Fact] - public void LDAPPropertyProcessor_FunctionalLevelToString_TestFunctionalLevels() - { - var expected = new Dictionary - { - {0, "2000 Mixed/Native"}, - {1, "2003 Interim"}, - {2, "2003"}, - {3, "2008"}, - {4, "2008 R2"}, - {5, "2012"}, - {6, "2012 R2"}, - {7, "2016"}, - {-1, "Unknown"} - }; - - foreach (var (key, value) in expected) - Assert.Equal(value, LDAPPropertyProcessor.FunctionalLevelToString(key)); - } - - [Fact] - public void LDAPPropertyProcessor_ReadGPOProperties_TestGoodData() - { - var mock = new MockSearchResultEntry( - "CN\u003d{94DD0260-38B5-497E-8876-10E7A96E80D0},CN\u003dPolicies,CN\u003dSystem,DC\u003dtestlab,DC\u003dlocal", - new Dictionary - { - { - "gpcfilesyspath", - Helpers.B64ToString( - "XFx0ZXN0bGFiLmxvY2FsXFN5c1ZvbFx0ZXN0bGFiLmxvY2FsXFBvbGljaWVzXHs5NEREMDI2MC0zOEI1LTQ5N0UtODg3Ni0xMEU3QTk2RTgwRDB9") - }, - {"description", "Test"} - }, "S-1-5-21-3130019616-2776909439-2417379446", Label.GPO); - - var test = LDAPPropertyProcessor.ReadGPOProperties(mock); - - Assert.Contains("description", test.Keys); - Assert.Equal("Test", test["description"] as string); - Assert.Contains("gpcpath", test.Keys); - Assert.Equal(@"\\TESTLAB.LOCAL\SYSVOL\TESTLAB.LOCAL\POLICIES\{94DD0260-38B5-497E-8876-10E7A96E80D0}", - test["gpcpath"] as string); - } - - [Fact] - public void LDAPPropertyProcessor_ReadOUProperties_TestGoodData() - { - var mock = new MockSearchResultEntry("OU\u003dTestOU,DC\u003dtestlab,DC\u003dlocal", - new Dictionary - { - {"description", "Test"} - }, "2A374493-816A-4193-BEFD-D2F4132C6DCA", Label.OU); - - var test = LDAPPropertyProcessor.ReadOUProperties(mock); - Assert.Contains("description", test.Keys); - Assert.Equal("Test", test["description"] as string); - } - - [Fact] - public void LDAPPropertyProcessor_ReadGroupProperties_TestGoodData() - { - var mock = new MockSearchResultEntry("CN\u003dDomain Admins,CN\u003dUsers,DC\u003dtestlab,DC\u003dlocal", - new Dictionary - { - {"description", "Test"}, - {"admincount", "1"} - }, "S-1-5-21-3130019616-2776909439-2417379446-512", Label.Group); - - var test = LDAPPropertyProcessor.ReadGroupProperties(mock); - Assert.Contains("description", test.Keys); - Assert.Equal("Test", test["description"] as string); - Assert.Contains("admincount", test.Keys); - Assert.True((bool)test["admincount"]); - } - - [Fact] - public void LDAPPropertyProcessor_ReadGroupProperties_TestGoodData_FalseAdminCount() - { - var mock = new MockSearchResultEntry("CN\u003dDomain Admins,CN\u003dUsers,DC\u003dtestlab,DC\u003dlocal", - new Dictionary - { - {"description", "Test"}, - {"admincount", "0"} - }, "S-1-5-21-3130019616-2776909439-2417379446-512", Label.Group); - - var test = LDAPPropertyProcessor.ReadGroupProperties(mock); - Assert.Contains("description", test.Keys); - Assert.Equal("Test", test["description"] as string); - Assert.Contains("admincount", test.Keys); - Assert.False((bool)test["admincount"]); - } - - [Fact] - public void LDAPPropertyProcessor_ReadGroupProperties_NullAdminCount() - { - var mock = new MockSearchResultEntry("CN\u003dDomain Admins,CN\u003dUsers,DC\u003dtestlab,DC\u003dlocal", - new Dictionary - { - {"description", "Test"} - }, "S-1-5-21-3130019616-2776909439-2417379446-512", Label.Group); - - var test = LDAPPropertyProcessor.ReadGroupProperties(mock); - Assert.Contains("description", test.Keys); - Assert.Equal("Test", test["description"] as string); - Assert.Contains("admincount", test.Keys); - Assert.False((bool)test["admincount"]); - } - - [Fact] - public async Task LDAPPropertyProcessor_ReadUserProperties_TestTrustedToAuth() - { - var mock = new MockSearchResultEntry("CN\u003ddfm,CN\u003dUsers,DC\u003dtestlab,DC\u003dlocal", - new Dictionary - { - {"description", "Test"}, - {"useraccountcontrol", 0x1000000.ToString()}, - {"lastlogon", "132673011142753043"}, - {"lastlogontimestamp", "132670318095676525"}, - {"homedirectory", @"\\win10\testdir"}, - { - "serviceprincipalname", new[] - { - "MSSQLSVC\\win10" - } - }, - {"admincount", "1"}, - { - "sidhistory", new[] - { - Helpers.B64ToBytes("AQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQUQQAAA==") - } - }, - {"pwdlastset", "132131667346106691"}, - { - "msds-allowedtodelegateto", new[] - { - "host/primary", - "rdpman/win10" - } - } - }, "S-1-5-21-3130019616-2776909439-2417379446-1101", Label.User); - - var processor = new LDAPPropertyProcessor(new MockLDAPUtils()); - var test = await processor.ReadUserProperties(mock); - var props = test.Props; - var keys = props.Keys; - - Assert.Contains("allowedtodelegate", keys); - var atd = props["allowedtodelegate"] as string[]; - Assert.Equal(2, atd.Length); - Assert.Contains("host/primary", atd); - Assert.Contains("rdpman/win10", atd); - - var atdr = test.AllowedToDelegate; - Assert.Equal(2, atdr.Length); - var expected = new TypedPrincipal[] - { - new() - { - ObjectIdentifier = "S-1-5-21-3130019616-2776909439-2417379446-1001", - ObjectType = Label.Computer - }, - new() - { - ObjectIdentifier = "S-1-5-21-3130019616-2776909439-2417379446-1104", - ObjectType = Label.Computer - } - }; - Assert.Equal(expected, atdr); - } - - [Fact] - public async Task LDAPPropertyProcessor_ReadUserProperties_NullAdminCount() - { - var mock = new MockSearchResultEntry("CN\u003ddfm,CN\u003dUsers,DC\u003dtestlab,DC\u003dlocal", - new Dictionary - { - {"description", "Test"}, - {"useraccountcontrol", "66048"}, - {"lastlogon", "132673011142753043"}, - {"lastlogontimestamp", "132670318095676525"}, - {"homedirectory", @"\\win10\testdir"}, - { - "serviceprincipalname", new[] - { - "MSSQLSVC\\win10" - } - }, - { - "sidhistory", new[] - { - Helpers.B64ToBytes("AQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQUQQAAA==") - } - }, - {"pwdlastset", "132131667346106691"} - }, "S-1-5-21-3130019616-2776909439-2417379446-1101", Label.User); - - var processor = new LDAPPropertyProcessor(new MockLDAPUtils()); - var test = await processor.ReadUserProperties(mock); - var props = test.Props; - var keys = props.Keys; - Assert.Contains("admincount", keys); - Assert.False((bool)props["admincount"]); - } - - [WindowsOnlyFact] - public async Task LDAPPropertyProcessor_ReadUserProperties_HappyPath() - { - var mock = new MockSearchResultEntry("CN\u003ddfm,CN\u003dUsers,DC\u003dtestlab,DC\u003dlocal", - new Dictionary - { - {"description", "Test"}, - {"useraccountcontrol", "66048"}, - {"lastlogon", "132673011142753043"}, - {"lastlogontimestamp", "132670318095676525"}, - {"homedirectory", @"\\win10\testdir"}, - {"mail", "test@testdomain.com"}, - { - "serviceprincipalname", new[] - { - "MSSQLSVC/win10" - } - }, - {"admincount", "1"}, - { - "sidhistory", new[] - { - Helpers.B64ToBytes("AQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQUQQAAA==") - } - }, - {"pwdlastset", "132131667346106691"} - }, "S-1-5-21-3130019616-2776909439-2417379446-1101", Label.User); - - var processor = new LDAPPropertyProcessor(new MockLDAPUtils()); - var test = await processor.ReadUserProperties(mock); - var props = test.Props; - var keys = props.Keys; - - //Random Stuff - Assert.Contains("description", keys); - Assert.Equal("Test", props["description"] as string); - Assert.Contains("admincount", keys); - Assert.True((bool)props["admincount"]); - Assert.Contains("lastlogon", keys); - Assert.Equal(1622827514, (long)props["lastlogon"]); - Assert.Contains("lastlogontimestamp", keys); - Assert.Equal(1622558209, (long)props["lastlogontimestamp"]); - Assert.Contains("pwdlastset", keys); - Assert.Equal(1568693134, (long)props["pwdlastset"]); - Assert.Contains("homedirectory", keys); - Assert.Equal(@"\\win10\testdir", props["homedirectory"] as string); - Assert.Contains("email", keys); - Assert.Equal("test@testdomain.com", props["email"] as string); - - //UAC stuff - Assert.Contains("sensitive", keys); - Assert.False((bool)props["sensitive"]); - Assert.Contains("dontreqpreauth", keys); - Assert.False((bool)props["dontreqpreauth"]); - Assert.Contains("passwordnotreqd", keys); - Assert.False((bool)props["passwordnotreqd"]); - Assert.Contains("unconstraineddelegation", keys); - Assert.False((bool)props["unconstraineddelegation"]); - Assert.Contains("enabled", keys); - Assert.True((bool)props["enabled"]); - Assert.Contains("trustedtoauth", keys); - Assert.False((bool)props["trustedtoauth"]); - - //SPN - Assert.Contains("hasspn", keys); - Assert.True((bool)props["hasspn"]); - Assert.Contains("serviceprincipalnames", keys); - Assert.Contains("MSSQLSVC/win10", props["serviceprincipalnames"] as string[]); - - //SidHistory - Assert.Contains("sidhistory", keys); - var sh = props["sidhistory"] as string[]; - Assert.Single(sh); - Assert.Contains("S-1-5-21-3130019616-2776909439-2417379446-1105", sh); - Assert.Single(test.SidHistory); - Assert.Contains(new TypedPrincipal - { - ObjectIdentifier = "S-1-5-21-3130019616-2776909439-2417379446-1105", - ObjectType = Label.User - }, test.SidHistory); - } - - [Fact] - public async Task LDAPPropertyProcessor_ReadUserProperties_TestBadPaths() - { - var mock = new MockSearchResultEntry("CN\u003ddfm,CN\u003dUsers,DC\u003dtestlab,DC\u003dlocal", - new Dictionary - { - {"description", "Test"}, - {"useraccountcontrol", "abc"}, - {"lastlogon", "132673011142753043"}, - {"lastlogontimestamp", "132670318095676525"}, - {"homedirectory", @"\\win10\testdir"}, - { - "serviceprincipalname", new[] - { - "MSSQLSVC/win10" - } - }, - {"admincount", "c"}, - { - "sidhistory", new[] - { - Array.Empty() - } - }, - {"pwdlastset", "132131667346106691"} - }, "S-1-5-21-3130019616-2776909439-2417379446-1101", Label.User); - - var processor = new LDAPPropertyProcessor(new MockLDAPUtils()); - var test = await processor.ReadUserProperties(mock); - var props = test.Props; - var keys = props.Keys; - - Assert.Contains("sidhistory", keys); - Assert.Empty(props["sidhistory"] as string[]); - Assert.Contains("admincount", keys); - Assert.False((bool)props["admincount"]); - Assert.Contains("sensitive", keys); - Assert.Contains("dontreqpreauth", keys); - Assert.Contains("passwordnotreqd", keys); - Assert.Contains("unconstraineddelegation", keys); - Assert.Contains("pwdneverexpires", keys); - Assert.Contains("enabled", keys); - Assert.Contains("trustedtoauth", keys); - Assert.False((bool)props["trustedtoauth"]); - Assert.False((bool)props["sensitive"]); - Assert.False((bool)props["dontreqpreauth"]); - Assert.False((bool)props["passwordnotreqd"]); - Assert.False((bool)props["unconstraineddelegation"]); - Assert.False((bool)props["pwdneverexpires"]); - Assert.True((bool)props["enabled"]); - } - - [WindowsOnlyFact] - public async Task LDAPPropertyProcessor_ReadComputerProperties_HappyPath() - { - //TODO: Add coverage for allowedtoact - var mock = new MockSearchResultEntry("CN\u003dWIN10,OU\u003dTestOU,DC\u003dtestlab,DC\u003dlocal", - new Dictionary - { - {"description", "Test"}, - {"useraccountcontrol", 0x1001000.ToString()}, - {"lastlogon", "132673011142753043"}, - {"lastlogontimestamp", "132670318095676525"}, - {"operatingsystem", "Windows 10 Enterprise"}, - {"operatingsystemservicepack", "1607"}, - {"mail", "test@testdomain.com"}, - {"admincount", "c"}, - { - "sidhistory", new[] - { - Helpers.B64ToBytes("AQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQUQQAAA==") - } - }, - { - "msds-allowedtodelegateto", new[] - { - "ldap/PRIMARY.testlab.local/testlab.local", - "ldap/PRIMARY.testlab.local", - "ldap/PRIMARY" - } - }, - {"pwdlastset", "132131667346106691"}, - { - "serviceprincipalname", new[] - { - "WSMAN/WIN10", - "WSMAN/WIN10.testlab.local", - "RestrictedKrbHost/WIN10", - "HOST/WIN10", - "RestrictedKrbHost/WIN10.testlab.local", - "HOST/WIN10.testlab.local" - } - } - }, "S-1-5-21-3130019616-2776909439-2417379446-1101", Label.Computer); - - var processor = new LDAPPropertyProcessor(new MockLDAPUtils()); - var test = await processor.ReadComputerProperties(mock); - var props = test.Props; - var keys = props.Keys; - - //UAC - Assert.Contains("enabled", keys); - Assert.Contains("unconstraineddelegation", keys); - Assert.Contains("trustedtoauth", keys); - Assert.Contains("isdc", keys); - Assert.Contains("lastlogon", keys); - Assert.Contains("lastlogontimestamp", keys); - Assert.Contains("pwdlastset", keys); - Assert.True((bool)props["enabled"]); - Assert.False((bool)props["unconstraineddelegation"]); - Assert.True((bool)props["trustedtoauth"]); - Assert.False((bool)props["isdc"]); - - Assert.Contains("lastlogon", keys); - Assert.Equal(1622827514, (long)props["lastlogon"]); - Assert.Contains("lastlogontimestamp", keys); - Assert.Equal(1622558209, (long)props["lastlogontimestamp"]); - Assert.Contains("pwdlastset", keys); - Assert.Equal(1568693134, (long)props["pwdlastset"]); - - //AllowedToDelegate - Assert.Single(test.AllowedToDelegate); - Assert.Contains(new TypedPrincipal - { - ObjectIdentifier = "S-1-5-21-3130019616-2776909439-2417379446-1001", - ObjectType = Label.Computer - }, test.AllowedToDelegate); - - //Other Stuff - Assert.Contains("serviceprincipalnames", keys); - Assert.Equal(6, (props["serviceprincipalnames"] as string[]).Length); - Assert.Contains("operatingsystem", keys); - Assert.Equal("Windows 10 Enterprise 1607", props["operatingsystem"] as string); - Assert.Contains("description", keys); - Assert.Equal("Test", props["description"] as string); - Assert.Contains("email", keys); - Assert.Equal("test@testdomain.com", props["email"] as string); - - //SidHistory - Assert.Contains("sidhistory", keys); - var sh = props["sidhistory"] as string[]; - Assert.Single(sh); - Assert.Contains("S-1-5-21-3130019616-2776909439-2417379446-1105", sh); - Assert.Single(test.SidHistory); - Assert.Contains(new TypedPrincipal - { - ObjectIdentifier = "S-1-5-21-3130019616-2776909439-2417379446-1105", - ObjectType = Label.User - }, test.SidHistory); - } - - [Fact] - public async Task LDAPPropertyProcessor_ReadComputerProperties_TestBadPaths() - { - var mock = new MockSearchResultEntry("CN\u003dWIN10,OU\u003dTestOU,DC\u003dtestlab,DC\u003dlocal", - new Dictionary - { - {"description", "Test"}, - {"useraccountcontrol", "abc"}, - {"lastlogon", "132673011142753043"}, - {"lastlogontimestamp", "132670318095676525"}, - {"operatingsystem", "Windows 10 Enterprise"}, - {"admincount", "c"}, - { - "sidhistory", new[] - { - Array.Empty() - } - }, - { - "msds-allowedToDelegateTo", new[] - { - "ldap/PRIMARY.testlab.local/testlab.local", - "ldap/PRIMARY.testlab.local", - "ldap/PRIMARY" - } - }, - {"pwdlastset", "132131667346106691"}, - { - "serviceprincipalname", new[] - { - "WSMAN/WIN10", - "WSMAN/WIN10.testlab.local", - "RestrictedKrbHost/WIN10", - "HOST/WIN10", - "RestrictedKrbHost/WIN10.testlab.local", - "HOST/WIN10.testlab.local" - } - } - }, "S-1-5-21-3130019616-2776909439-2417379446-1101", Label.Computer); - - var processor = new LDAPPropertyProcessor(new MockLDAPUtils()); - var test = await processor.ReadComputerProperties(mock); - var props = test.Props; - var keys = props.Keys; - - Assert.Contains("unconstraineddelegation", keys); - Assert.Contains("enabled", keys); - Assert.Contains("trustedtoauth", keys); - Assert.False((bool)props["unconstraineddelegation"]); - Assert.True((bool)props["enabled"]); - Assert.False((bool)props["trustedtoauth"]); - Assert.Contains("sidhistory", keys); - Assert.Empty(props["sidhistory"] as string[]); - } - - - [Fact] - public async Task LDAPPropertyProcessor_ReadComputerProperties_TestDumpSMSAPassword() - { - var mock = new MockSearchResultEntry("CN\u003dWIN10,OU\u003dTestOU,DC\u003dtestlab,DC\u003dlocal", - new Dictionary - { - {"description", "Test"}, - {"useraccountcontrol", 0x1001000.ToString()}, - {"lastlogon", "132673011142753043"}, - {"lastlogontimestamp", "132670318095676525"}, - {"operatingsystem", "Windows 10 Enterprise"}, - {"operatingsystemservicepack", "1607"}, - {"admincount", "c"}, - { - "sidhistory", new[] - { - Helpers.B64ToBytes("AQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQUQQAAA==") - } - }, - { - "msds-allowedtodelegateto", new[] - { - "ldap/PRIMARY.testlab.local/testlab.local", - "ldap/PRIMARY.testlab.local", - "ldap/PRIMARY" - } - }, - {"pwdlastset", "132131667346106691"}, - { - "serviceprincipalname", new[] - { - "WSMAN/WIN10", - "WSMAN/WIN10.testlab.local", - "RestrictedKrbHost/WIN10", - "HOST/WIN10", - "RestrictedKrbHost/WIN10.testlab.local", - "HOST/WIN10.testlab.local" - } - }, - { - "msds-hostserviceaccount", new[] - { - "CN=dfm,CN=Users,DC=testlab,DC=local", - "CN=krbtgt,CN=Users,DC=testlab,DC=local" - } - } - }, "S-1-5-21-3130019616-2776909439-2417379446-1101", Label.Computer); - - var processor = new LDAPPropertyProcessor(new MockLDAPUtils()); - var test = await processor.ReadComputerProperties(mock); - - var expected = new TypedPrincipal[] - { - new() - { - ObjectIdentifier = "S-1-5-21-3130019616-2776909439-2417379446-1105", - ObjectType = Label.User - }, - new() - { - ObjectIdentifier = "S-1-5-21-3130019616-2776909439-2417379446-502", - ObjectType = Label.User - } - }; - - var testDumpSMSAPassword = test.DumpSMSAPassword; - Assert.Equal(2, testDumpSMSAPassword.Length); - Assert.Equal(expected, testDumpSMSAPassword); - - } - - [Fact] - public void LDAPPropertyProcessor_ReadRootCAProperties() - { - var mock = new MockSearchResultEntry( - "CN\u003dDUMPSTER-DC01-CA,CN\u003dCERTIFICATION AUTHORITIES,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dDUMPSTER,DC\u003dFIRE", - new Dictionary - { - {"description", null}, - {"domain", "DUMPSTER.FIRE"}, - {"name", "DUMPSTER-DC01-CA@DUMPSTER.FIRE"}, - {"domainsid", "S-1-5-21-2697957641-2271029196-387917394"}, - {"whencreated", 1683986131}, - }, "2F9F3630-F46A-49BF-B186-6629994EBCF9", Label.RootCA); - - var test = LDAPPropertyProcessor.ReadRootCAProperties(mock); - var keys = test.Keys; - - //These are not common properties - Assert.DoesNotContain("domain", keys); - Assert.DoesNotContain("name", keys); - Assert.DoesNotContain("domainsid", keys); - - Assert.Contains("description", keys); - Assert.Contains("whencreated", keys); - } - - [Fact] - public void LDAPPropertyProcessor_ReadAIACAProperties() - { - var mock = new MockSearchResultEntry( - "CN\u003dDUMPSTER-DC01-CA,CN\u003dAIA,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dDUMPSTER,DC\u003dFIRE", - new Dictionary - { - {"description", null}, - {"domain", "DUMPSTER.FIRE"}, - {"name", "DUMPSTER-DC01-CA@DUMPSTER.FIRE"}, - {"domainsid", "S-1-5-21-2697957641-2271029196-387917394"}, - {"whencreated", 1683986131}, - {"hascrosscertificatepair", true}, - }, "2F9F3630-F46A-49BF-B186-6629994EBCF9", Label.AIACA); - - var test = LDAPPropertyProcessor.ReadAIACAProperties(mock); - var keys = test.Keys; - - //These are not common properties - Assert.DoesNotContain("domain", keys); - Assert.DoesNotContain("name", keys); - Assert.DoesNotContain("domainsid", keys); - - Assert.Contains("description", keys); - Assert.Contains("whencreated", keys); - Assert.Contains("crosscertificatepair", keys); - } - - [Fact] - public void LDAPPropertyProcessor_ReadNTAuthStoreProperties() - { - var mock = new MockSearchResultEntry("CN\u003dNTAUTHCERTIFICATES,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dDUMPSTER,DC\u003dFIRE", - new Dictionary - { - {"description", null}, - {"domain", "DUMPSTER.FIRE"}, - {"name", "NTAUTHCERTIFICATES@DUMPSTER.FIRE"}, - {"domainsid", "S-1-5-21-2697957641-2271029196-387917394"}, - {"whencreated", 1683986131}, - }, "2F9F3630-F46A-49BF-B186-6629994EBCF9", Label.NTAuthStore); - - var test = LDAPPropertyProcessor.ReadNTAuthStoreProperties(mock); - var keys = test.Keys; - - //These are not common properties - Assert.DoesNotContain("domain", keys); - Assert.DoesNotContain("name", keys); - Assert.DoesNotContain("domainsid", keys); - - Assert.Contains("description", keys); - Assert.Contains("whencreated", keys); - } - - [Fact] - public void LDAPPropertyProcessor_ReadCertTemplateProperties() - { - var mock = new MockSearchResultEntry("CN\u003dWORKSTATION,CN\u003dCERTIFICATE TEMPLATES,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dEXTERNAL,DC\u003dLOCAL", - new Dictionary - { - {"domain", "EXTERNAL.LOCAL"}, - {"name", "WORKSTATION@EXTERNAL.LOCAL"}, - {"domainsid", "S-1-5-21-3702535222-3822678775-2090119576"}, - {"description", null}, - {"whencreated", 1683986183}, - {"validityperiod", 31536000}, - {"renewalperiod", 3628800}, - {"schemaversion", 2}, - {"displayname", "Workstation Authentication"}, - {"oid", "1.3.6.1.4.1.311.21.8.4571196.1884641.3293620.10686285.12068043.134.1.30"}, - {"enrollmentflag", 32}, - {"requiresmanagerapproval", false}, - {"certificatenameflag", 0x8000000}, - {"ekus", new[] - {"1.3.6.1.5.5.7.3.2"} - }, - {LDAPProperties.CertificateApplicationPolicy, new[] - {"1.3.6.1.5.5.7.3.2"} - }, - {LDAPProperties.CertificatePolicy, new[] - {"1.3.6.1.5.5.7.3.2"} - }, - {"authorizedsignatures", 1}, - {"applicationpolicies", new[] - { "1.3.6.1.4.1.311.20.2.1"} - }, - {"issuancepolicies", new[] - {"1.3.6.1.4.1.311.21.8.4571196.1884641.3293620.10686285.12068043.134.1.400", - "1.3.6.1.4.1.311.21.8.4571196.1884641.3293620.10686285.12068043.134.1.402"} - }, - }, "2F9F3630-F46A-49BF-B186-6629994EBCF9", Label.CertTemplate); - - var test = LDAPPropertyProcessor.ReadCertTemplateProperties(mock); - var keys = test.Keys; - - //These are not common properties - Assert.DoesNotContain("domain", keys); - Assert.DoesNotContain("name", keys); - Assert.DoesNotContain("domainsid", keys); - - Assert.Contains("description", keys); - Assert.Contains("whencreated", keys); - Assert.Contains("validityperiod", keys); - Assert.Contains("renewalperiod", keys); - Assert.Contains("schemaversion", keys); - Assert.Contains("displayname", keys); - Assert.Contains("oid", keys); - Assert.Contains("enrollmentflag", keys); - Assert.Contains("requiresmanagerapproval", keys); - Assert.Contains("certificatenameflag", keys); - Assert.Contains("enrolleesuppliessubject", keys); - Assert.Contains("subjectaltrequireupn", keys); - Assert.Contains("subjectaltrequiredns", keys); - Assert.Contains("subjectaltrequiredomaindns", keys); - Assert.Contains("subjectaltrequireemail", keys); - Assert.Contains("subjectaltrequirespn", keys); - Assert.Contains("subjectrequireemail", keys); - Assert.Contains("ekus", keys); - Assert.Contains("certificateapplicationpolicy", keys); - var hasPolicy = test.TryGetValue("certificatepolicy", out var policies); - Assert.True(hasPolicy); - if (policies is string[] e) - { - Assert.Contains("1.3.6.1.5.5.7.3.2", e); - } - Assert.Contains("authorizedsignatures", keys); - Assert.Contains("applicationpolicies", keys); - Assert.Contains("issuancepolicies", keys); - - } - // - // [Fact] - // public void LDAPPropertyProcessor_ReadIssuancePolicyProperties() - // { - // var mock = new MockSearchResultEntry("CN\u003d6250993.11BB1AB25A8A65E9FCDF709FCDD5FBC6,CN\u003dOID,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dESC10,DC\u003dLOCAL", - // new Dictionary - // { - // {"domain", "ESC10.LOCAL"}, - // {"name", "KEYADMINSOID@ESC10.LOCAL"}, - // {"domainsid", "S-1-5-21-3662707843-2053279151-3839588741"}, - // {"description", null}, - // {"whencreated", 1712567279}, - // {"displayname", "KeyAdminsOID"}, - // {"certtemplateoid", "1.3.6.1.4.1.311.21.8.4571196.1884641.3293620.10686285.12068043.134.1.30"}, - // {"msds-oidtogrouplink", "CN=ENTERPRISE KEY ADMINS,CN=USERS,DC=ESC10,DC=LOCAL"} - // , - // }, "1E5311A8-E949-4E02-8E08-234ED63200DE", Label.IssuancePolicy); - // - // var mockLDAPUtils = new MockLDAPUtils(); - // var ldapPropertyProcessor = new LDAPPropertyProcessor(mockLDAPUtils); - // - // - // var test = ldapPropertyProcessor.ReadIssuancePolicyProperties(mock); - // var keys = test.Props.Keys; - // - // //These are not common properties - // Assert.DoesNotContain("domain", keys); - // Assert.DoesNotContain("name", keys); - // Assert.DoesNotContain("domainsid", keys); - // - // Assert.Contains("description", keys); - // Assert.Contains("whencreated", keys); - // Assert.Contains("displayname", keys); - // Assert.Contains("certtemplateoid", keys); - // Assert.Contains("oidgrouplink", keys); - // } - // - // [Fact] - // public void LDAPPropertyProcessor_ReadIssuancePolicyProperties_NoOIDGroupLink() - // { - // var mock = new MockSearchResultEntry("CN\u003d6250993.11BB1AB25A8A65E9FCDF709FCDD5FBC6,CN\u003dOID,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dESC10,DC\u003dLOCAL", - // new Dictionary - // { - // {"domain", "ESC10.LOCAL"}, - // {"name", "KEYADMINSOID@ESC10.LOCAL"}, - // {"domainsid", "S-1-5-21-3662707843-2053279151-3839588741"}, - // {"description", null}, - // {"whencreated", 1712567279}, - // {"displayname", "KeyAdminsOID"}, - // {"certtemplateoid", "1.3.6.1.4.1.311.21.8.4571196.1884641.3293620.10686285.12068043.134.1.30"}, - // {"msds-oidtogrouplink", null} - // , - // }, "1E5311A8-E949-4E02-8E08-234ED63200DE", Label.IssuancePolicy); - // - // var mockLDAPUtils = new MockLDAPUtils(); - // var ldapPropertyProcessor = new LDAPPropertyProcessor(mockLDAPUtils); - // - // - // var test = ldapPropertyProcessor.ReadIssuancePolicyProperties(mock); - // var keys = test.Props.Keys; - // - // //These are not common properties - // Assert.DoesNotContain("domain", keys); - // Assert.DoesNotContain("name", keys); - // Assert.DoesNotContain("domainsid", keys); - // Assert.DoesNotContain("oidgrouplink", keys); - // - // Assert.Contains("description", keys); - // Assert.Contains("whencreated", keys); - // Assert.Contains("displayname", keys); - // Assert.Contains("certtemplateoid", keys); - // } - - [Fact] - public void LDAPPropertyProcessor_ParseAllProperties() - { - var mock = new MockSearchResultEntry("CN\u003dNTAUTHCERTIFICATES,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dDUMPSTER,DC\u003dFIRE", - new Dictionary - { - {"description", null}, - {"domain", "DUMPSTER.FIRE"}, - {"name", "NTAUTHCERTIFICATES@DUMPSTER.FIRE"}, - {"domainsid", "S-1-5-21-2697957641-2271029196-387917394"}, - {"whencreated", 1683986131}, - }, "2F9F3630-F46A-49BF-B186-6629994EBCF9", Label.NTAuthStore); - - var processor = new LDAPPropertyProcessor(new MockLDAPUtils()); - var props = processor.ParseAllProperties(mock); - var keys = props.Keys; - - //These are reserved properties and so they should be filtered out - Assert.DoesNotContain("description", keys); - Assert.DoesNotContain("whencreated", keys); - Assert.DoesNotContain("name", keys); - - Assert.Contains("domainsid", keys); - Assert.Contains("domain", keys); - } - - [Fact] - public void LDAPPropertyProcessor_ParseAllProperties_NoProperties() - { - var mock = new MockSearchResultEntry("CN\u003dNTAUTHCERTIFICATES,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dDUMPSTER,DC\u003dFIRE", - new Dictionary - { }, "2F9F3630-F46A-49BF-B186-6629994EBCF9", Label.NTAuthStore); - - var processor = new LDAPPropertyProcessor(new MockLDAPUtils()); - var props = processor.ParseAllProperties(mock); - var keys = props.Keys; - - Assert.Empty(keys); - - } - - [Fact] - public void LDAPPropertyProcessor_ParseAllProperties_CollectionCountOne_NullString() - { - var mock = new MockSearchResultEntry("CN\u003dNTAUTHCERTIFICATES,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dDUMPSTER,DC\u003dFIRE", - new Dictionary - {{"domainsid", null} }, "2F9F3630-F46A-49BF-B186-6629994EBCF9", Label.NTAuthStore); - - var processor = new LDAPPropertyProcessor(new MockLDAPUtils()); - var props = processor.ParseAllProperties(mock); - var keys = props.Keys; - - Assert.Empty(keys); - } - - [Fact] - public void LDAPPropertyProcessor_ParseAllProperties_CollectionCountOne_BadPasswordTime() - { - var mock = new MockSearchResultEntry("CN\u003dNTAUTHCERTIFICATES,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dDUMPSTER,DC\u003dFIRE", - new Dictionary - {{"badpasswordtime", "130435290000000000"} }, "2F9F3630-F46A-49BF-B186-6629994EBCF9", Label.NTAuthStore); - - var processor = new LDAPPropertyProcessor(new MockLDAPUtils()); - var props = processor.ParseAllProperties(mock); - var keys = props.Keys; - - Assert.Contains("badpasswordtime", keys); - Assert.Single(keys); - } - - [Fact] - public void LDAPPropertyProcessor_ParseAllProperties_CollectionCountOne_NotBadPasswordTime() - { - var mock = new MockSearchResultEntry("CN\u003dNTAUTHCERTIFICATES,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dDUMPSTER,DC\u003dFIRE", - new Dictionary - {{"domainsid", "S-1-5-21-2697957641-2271029196-387917394"}}, "2F9F3630-F46A-49BF-B186-6629994EBCF9", Label.NTAuthStore); - - var processor = new LDAPPropertyProcessor(new MockLDAPUtils()); - var props = processor.ParseAllProperties(mock); - var keys = props.Keys; - - Assert.Contains("domainsid", keys); - Assert.Single(keys); - } - - } -} +// using System; +// using System.Collections.Generic; +// using System.Threading.Tasks; +// using CommonLibTest.Facades; +// using SharpHoundCommonLib; +// using SharpHoundCommonLib.Enums; +// using SharpHoundCommonLib.OutputTypes; +// using SharpHoundCommonLib.Processors; +// using Xunit; +// using Xunit.Abstractions; +// +// namespace CommonLibTest +// { +// public class LDAPPropertyTests +// { +// private readonly ITestOutputHelper _testOutputHelper; +// +// public LDAPPropertyTests(ITestOutputHelper testOutputHelper) +// { +// _testOutputHelper = testOutputHelper; +// } +// +// [Fact] +// public void LDAPPropertyProcessor_ReadDomainProperties_TestGoodData() +// { +// var mock = new MockSearchResultEntry("DC\u003dtestlab,DC\u003dlocal", new Dictionary +// { +// {"description", "TESTLAB Domain"}, +// {"msds-behavior-version", "6"} +// }, "S-1-5-21-3130019616-2776909439-2417379446", Label.Domain); +// +// var test = LDAPPropertyProcessor.ReadDomainProperties(mock); +// Assert.Contains("functionallevel", test.Keys); +// Assert.Equal("2012 R2", test["functionallevel"] as string); +// Assert.Contains("description", test.Keys); +// Assert.Equal("TESTLAB Domain", test["description"] as string); +// } +// +// [Fact] +// public void LDAPPropertyProcessor_ReadDomainProperties_TestBadFunctionalLevel() +// { +// var mock = new MockSearchResultEntry("DC\u003dtestlab,DC\u003dlocal", new Dictionary +// { +// {"msds-behavior-version", "a"} +// }, "S-1-5-21-3130019616-2776909439-2417379446", Label.Domain); +// +// var test = LDAPPropertyProcessor.ReadDomainProperties(mock); +// Assert.Contains("functionallevel", test.Keys); +// Assert.Equal("Unknown", test["functionallevel"] as string); +// } +// +// [Fact] +// public void LDAPPropertyProcessor_FunctionalLevelToString_TestFunctionalLevels() +// { +// var expected = new Dictionary +// { +// {0, "2000 Mixed/Native"}, +// {1, "2003 Interim"}, +// {2, "2003"}, +// {3, "2008"}, +// {4, "2008 R2"}, +// {5, "2012"}, +// {6, "2012 R2"}, +// {7, "2016"}, +// {-1, "Unknown"} +// }; +// +// foreach (var (key, value) in expected) +// Assert.Equal(value, LDAPPropertyProcessor.FunctionalLevelToString(key)); +// } +// +// [Fact] +// public void LDAPPropertyProcessor_ReadGPOProperties_TestGoodData() +// { +// var mock = new MockSearchResultEntry( +// "CN\u003d{94DD0260-38B5-497E-8876-10E7A96E80D0},CN\u003dPolicies,CN\u003dSystem,DC\u003dtestlab,DC\u003dlocal", +// new Dictionary +// { +// { +// "gpcfilesyspath", +// Helpers.B64ToString( +// "XFx0ZXN0bGFiLmxvY2FsXFN5c1ZvbFx0ZXN0bGFiLmxvY2FsXFBvbGljaWVzXHs5NEREMDI2MC0zOEI1LTQ5N0UtODg3Ni0xMEU3QTk2RTgwRDB9") +// }, +// {"description", "Test"} +// }, "S-1-5-21-3130019616-2776909439-2417379446", Label.GPO); +// +// var test = LDAPPropertyProcessor.ReadGPOProperties(mock); +// +// Assert.Contains("description", test.Keys); +// Assert.Equal("Test", test["description"] as string); +// Assert.Contains("gpcpath", test.Keys); +// Assert.Equal(@"\\TESTLAB.LOCAL\SYSVOL\TESTLAB.LOCAL\POLICIES\{94DD0260-38B5-497E-8876-10E7A96E80D0}", +// test["gpcpath"] as string); +// } +// +// [Fact] +// public void LDAPPropertyProcessor_ReadOUProperties_TestGoodData() +// { +// var mock = new MockSearchResultEntry("OU\u003dTestOU,DC\u003dtestlab,DC\u003dlocal", +// new Dictionary +// { +// {"description", "Test"} +// }, "2A374493-816A-4193-BEFD-D2F4132C6DCA", Label.OU); +// +// var test = LDAPPropertyProcessor.ReadOUProperties(mock); +// Assert.Contains("description", test.Keys); +// Assert.Equal("Test", test["description"] as string); +// } +// +// [Fact] +// public void LDAPPropertyProcessor_ReadGroupProperties_TestGoodData() +// { +// var mock = new MockSearchResultEntry("CN\u003dDomain Admins,CN\u003dUsers,DC\u003dtestlab,DC\u003dlocal", +// new Dictionary +// { +// {"description", "Test"}, +// {"admincount", "1"} +// }, "S-1-5-21-3130019616-2776909439-2417379446-512", Label.Group); +// +// var test = LDAPPropertyProcessor.ReadGroupProperties(mock); +// Assert.Contains("description", test.Keys); +// Assert.Equal("Test", test["description"] as string); +// Assert.Contains("admincount", test.Keys); +// Assert.True((bool)test["admincount"]); +// } +// +// [Fact] +// public void LDAPPropertyProcessor_ReadGroupProperties_TestGoodData_FalseAdminCount() +// { +// var mock = new MockSearchResultEntry("CN\u003dDomain Admins,CN\u003dUsers,DC\u003dtestlab,DC\u003dlocal", +// new Dictionary +// { +// {"description", "Test"}, +// {"admincount", "0"} +// }, "S-1-5-21-3130019616-2776909439-2417379446-512", Label.Group); +// +// var test = LDAPPropertyProcessor.ReadGroupProperties(mock); +// Assert.Contains("description", test.Keys); +// Assert.Equal("Test", test["description"] as string); +// Assert.Contains("admincount", test.Keys); +// Assert.False((bool)test["admincount"]); +// } +// +// [Fact] +// public void LDAPPropertyProcessor_ReadGroupProperties_NullAdminCount() +// { +// var mock = new MockSearchResultEntry("CN\u003dDomain Admins,CN\u003dUsers,DC\u003dtestlab,DC\u003dlocal", +// new Dictionary +// { +// {"description", "Test"} +// }, "S-1-5-21-3130019616-2776909439-2417379446-512", Label.Group); +// +// var test = LDAPPropertyProcessor.ReadGroupProperties(mock); +// Assert.Contains("description", test.Keys); +// Assert.Equal("Test", test["description"] as string); +// Assert.Contains("admincount", test.Keys); +// Assert.False((bool)test["admincount"]); +// } +// +// [Fact] +// public async Task LDAPPropertyProcessor_ReadUserProperties_TestTrustedToAuth() +// { +// var mock = new MockSearchResultEntry("CN\u003ddfm,CN\u003dUsers,DC\u003dtestlab,DC\u003dlocal", +// new Dictionary +// { +// {"description", "Test"}, +// {"useraccountcontrol", 0x1000000.ToString()}, +// {"lastlogon", "132673011142753043"}, +// {"lastlogontimestamp", "132670318095676525"}, +// {"homedirectory", @"\\win10\testdir"}, +// { +// "serviceprincipalname", new[] +// { +// "MSSQLSVC\\win10" +// } +// }, +// {"admincount", "1"}, +// { +// "sidhistory", new[] +// { +// Helpers.B64ToBytes("AQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQUQQAAA==") +// } +// }, +// {"pwdlastset", "132131667346106691"}, +// { +// "msds-allowedtodelegateto", new[] +// { +// "host/primary", +// "rdpman/win10" +// } +// } +// }, "S-1-5-21-3130019616-2776909439-2417379446-1101", Label.User); +// +// var processor = new LDAPPropertyProcessor(new MockLDAPUtils()); +// var test = await processor.ReadUserProperties(mock); +// var props = test.Props; +// var keys = props.Keys; +// +// Assert.Contains("allowedtodelegate", keys); +// var atd = props["allowedtodelegate"] as string[]; +// Assert.Equal(2, atd.Length); +// Assert.Contains("host/primary", atd); +// Assert.Contains("rdpman/win10", atd); +// +// var atdr = test.AllowedToDelegate; +// Assert.Equal(2, atdr.Length); +// var expected = new TypedPrincipal[] +// { +// new() +// { +// ObjectIdentifier = "S-1-5-21-3130019616-2776909439-2417379446-1001", +// ObjectType = Label.Computer +// }, +// new() +// { +// ObjectIdentifier = "S-1-5-21-3130019616-2776909439-2417379446-1104", +// ObjectType = Label.Computer +// } +// }; +// Assert.Equal(expected, atdr); +// } +// +// [Fact] +// public async Task LDAPPropertyProcessor_ReadUserProperties_NullAdminCount() +// { +// var mock = new MockSearchResultEntry("CN\u003ddfm,CN\u003dUsers,DC\u003dtestlab,DC\u003dlocal", +// new Dictionary +// { +// {"description", "Test"}, +// {"useraccountcontrol", "66048"}, +// {"lastlogon", "132673011142753043"}, +// {"lastlogontimestamp", "132670318095676525"}, +// {"homedirectory", @"\\win10\testdir"}, +// { +// "serviceprincipalname", new[] +// { +// "MSSQLSVC\\win10" +// } +// }, +// { +// "sidhistory", new[] +// { +// Helpers.B64ToBytes("AQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQUQQAAA==") +// } +// }, +// {"pwdlastset", "132131667346106691"} +// }, "S-1-5-21-3130019616-2776909439-2417379446-1101", Label.User); +// +// var processor = new LDAPPropertyProcessor(new MockLDAPUtils()); +// var test = await processor.ReadUserProperties(mock); +// var props = test.Props; +// var keys = props.Keys; +// Assert.Contains("admincount", keys); +// Assert.False((bool)props["admincount"]); +// } +// +// [WindowsOnlyFact] +// public async Task LDAPPropertyProcessor_ReadUserProperties_HappyPath() +// { +// var mock = new MockSearchResultEntry("CN\u003ddfm,CN\u003dUsers,DC\u003dtestlab,DC\u003dlocal", +// new Dictionary +// { +// {"description", "Test"}, +// {"useraccountcontrol", "66048"}, +// {"lastlogon", "132673011142753043"}, +// {"lastlogontimestamp", "132670318095676525"}, +// {"homedirectory", @"\\win10\testdir"}, +// {"mail", "test@testdomain.com"}, +// { +// "serviceprincipalname", new[] +// { +// "MSSQLSVC/win10" +// } +// }, +// {"admincount", "1"}, +// { +// "sidhistory", new[] +// { +// Helpers.B64ToBytes("AQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQUQQAAA==") +// } +// }, +// {"pwdlastset", "132131667346106691"} +// }, "S-1-5-21-3130019616-2776909439-2417379446-1101", Label.User); +// +// var processor = new LDAPPropertyProcessor(new MockLDAPUtils()); +// var test = await processor.ReadUserProperties(mock); +// var props = test.Props; +// var keys = props.Keys; +// +// //Random Stuff +// Assert.Contains("description", keys); +// Assert.Equal("Test", props["description"] as string); +// Assert.Contains("admincount", keys); +// Assert.True((bool)props["admincount"]); +// Assert.Contains("lastlogon", keys); +// Assert.Equal(1622827514, (long)props["lastlogon"]); +// Assert.Contains("lastlogontimestamp", keys); +// Assert.Equal(1622558209, (long)props["lastlogontimestamp"]); +// Assert.Contains("pwdlastset", keys); +// Assert.Equal(1568693134, (long)props["pwdlastset"]); +// Assert.Contains("homedirectory", keys); +// Assert.Equal(@"\\win10\testdir", props["homedirectory"] as string); +// Assert.Contains("email", keys); +// Assert.Equal("test@testdomain.com", props["email"] as string); +// +// //UAC stuff +// Assert.Contains("sensitive", keys); +// Assert.False((bool)props["sensitive"]); +// Assert.Contains("dontreqpreauth", keys); +// Assert.False((bool)props["dontreqpreauth"]); +// Assert.Contains("passwordnotreqd", keys); +// Assert.False((bool)props["passwordnotreqd"]); +// Assert.Contains("unconstraineddelegation", keys); +// Assert.False((bool)props["unconstraineddelegation"]); +// Assert.Contains("enabled", keys); +// Assert.True((bool)props["enabled"]); +// Assert.Contains("trustedtoauth", keys); +// Assert.False((bool)props["trustedtoauth"]); +// +// //SPN +// Assert.Contains("hasspn", keys); +// Assert.True((bool)props["hasspn"]); +// Assert.Contains("serviceprincipalnames", keys); +// Assert.Contains("MSSQLSVC/win10", props["serviceprincipalnames"] as string[]); +// +// //SidHistory +// Assert.Contains("sidhistory", keys); +// var sh = props["sidhistory"] as string[]; +// Assert.Single(sh); +// Assert.Contains("S-1-5-21-3130019616-2776909439-2417379446-1105", sh); +// Assert.Single(test.SidHistory); +// Assert.Contains(new TypedPrincipal +// { +// ObjectIdentifier = "S-1-5-21-3130019616-2776909439-2417379446-1105", +// ObjectType = Label.User +// }, test.SidHistory); +// } +// +// [Fact] +// public async Task LDAPPropertyProcessor_ReadUserProperties_TestBadPaths() +// { +// var mock = new MockSearchResultEntry("CN\u003ddfm,CN\u003dUsers,DC\u003dtestlab,DC\u003dlocal", +// new Dictionary +// { +// {"description", "Test"}, +// {"useraccountcontrol", "abc"}, +// {"lastlogon", "132673011142753043"}, +// {"lastlogontimestamp", "132670318095676525"}, +// {"homedirectory", @"\\win10\testdir"}, +// { +// "serviceprincipalname", new[] +// { +// "MSSQLSVC/win10" +// } +// }, +// {"admincount", "c"}, +// { +// "sidhistory", new[] +// { +// Array.Empty() +// } +// }, +// {"pwdlastset", "132131667346106691"} +// }, "S-1-5-21-3130019616-2776909439-2417379446-1101", Label.User); +// +// var processor = new LDAPPropertyProcessor(new MockLDAPUtils()); +// var test = await processor.ReadUserProperties(mock); +// var props = test.Props; +// var keys = props.Keys; +// +// Assert.Contains("sidhistory", keys); +// Assert.Empty(props["sidhistory"] as string[]); +// Assert.Contains("admincount", keys); +// Assert.False((bool)props["admincount"]); +// Assert.Contains("sensitive", keys); +// Assert.Contains("dontreqpreauth", keys); +// Assert.Contains("passwordnotreqd", keys); +// Assert.Contains("unconstraineddelegation", keys); +// Assert.Contains("pwdneverexpires", keys); +// Assert.Contains("enabled", keys); +// Assert.Contains("trustedtoauth", keys); +// Assert.False((bool)props["trustedtoauth"]); +// Assert.False((bool)props["sensitive"]); +// Assert.False((bool)props["dontreqpreauth"]); +// Assert.False((bool)props["passwordnotreqd"]); +// Assert.False((bool)props["unconstraineddelegation"]); +// Assert.False((bool)props["pwdneverexpires"]); +// Assert.True((bool)props["enabled"]); +// } +// +// [WindowsOnlyFact] +// public async Task LDAPPropertyProcessor_ReadComputerProperties_HappyPath() +// { +// //TODO: Add coverage for allowedtoact +// var mock = new MockSearchResultEntry("CN\u003dWIN10,OU\u003dTestOU,DC\u003dtestlab,DC\u003dlocal", +// new Dictionary +// { +// {"description", "Test"}, +// {"useraccountcontrol", 0x1001000.ToString()}, +// {"lastlogon", "132673011142753043"}, +// {"lastlogontimestamp", "132670318095676525"}, +// {"operatingsystem", "Windows 10 Enterprise"}, +// {"operatingsystemservicepack", "1607"}, +// {"mail", "test@testdomain.com"}, +// {"admincount", "c"}, +// { +// "sidhistory", new[] +// { +// Helpers.B64ToBytes("AQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQUQQAAA==") +// } +// }, +// { +// "msds-allowedtodelegateto", new[] +// { +// "ldap/PRIMARY.testlab.local/testlab.local", +// "ldap/PRIMARY.testlab.local", +// "ldap/PRIMARY" +// } +// }, +// {"pwdlastset", "132131667346106691"}, +// { +// "serviceprincipalname", new[] +// { +// "WSMAN/WIN10", +// "WSMAN/WIN10.testlab.local", +// "RestrictedKrbHost/WIN10", +// "HOST/WIN10", +// "RestrictedKrbHost/WIN10.testlab.local", +// "HOST/WIN10.testlab.local" +// } +// } +// }, "S-1-5-21-3130019616-2776909439-2417379446-1101", Label.Computer); +// +// var processor = new LDAPPropertyProcessor(new MockLDAPUtils()); +// var test = await processor.ReadComputerProperties(mock); +// var props = test.Props; +// var keys = props.Keys; +// +// //UAC +// Assert.Contains("enabled", keys); +// Assert.Contains("unconstraineddelegation", keys); +// Assert.Contains("trustedtoauth", keys); +// Assert.Contains("isdc", keys); +// Assert.Contains("lastlogon", keys); +// Assert.Contains("lastlogontimestamp", keys); +// Assert.Contains("pwdlastset", keys); +// Assert.True((bool)props["enabled"]); +// Assert.False((bool)props["unconstraineddelegation"]); +// Assert.True((bool)props["trustedtoauth"]); +// Assert.False((bool)props["isdc"]); +// +// Assert.Contains("lastlogon", keys); +// Assert.Equal(1622827514, (long)props["lastlogon"]); +// Assert.Contains("lastlogontimestamp", keys); +// Assert.Equal(1622558209, (long)props["lastlogontimestamp"]); +// Assert.Contains("pwdlastset", keys); +// Assert.Equal(1568693134, (long)props["pwdlastset"]); +// +// //AllowedToDelegate +// Assert.Single(test.AllowedToDelegate); +// Assert.Contains(new TypedPrincipal +// { +// ObjectIdentifier = "S-1-5-21-3130019616-2776909439-2417379446-1001", +// ObjectType = Label.Computer +// }, test.AllowedToDelegate); +// +// //Other Stuff +// Assert.Contains("serviceprincipalnames", keys); +// Assert.Equal(6, (props["serviceprincipalnames"] as string[]).Length); +// Assert.Contains("operatingsystem", keys); +// Assert.Equal("Windows 10 Enterprise 1607", props["operatingsystem"] as string); +// Assert.Contains("description", keys); +// Assert.Equal("Test", props["description"] as string); +// Assert.Contains("email", keys); +// Assert.Equal("test@testdomain.com", props["email"] as string); +// +// //SidHistory +// Assert.Contains("sidhistory", keys); +// var sh = props["sidhistory"] as string[]; +// Assert.Single(sh); +// Assert.Contains("S-1-5-21-3130019616-2776909439-2417379446-1105", sh); +// Assert.Single(test.SidHistory); +// Assert.Contains(new TypedPrincipal +// { +// ObjectIdentifier = "S-1-5-21-3130019616-2776909439-2417379446-1105", +// ObjectType = Label.User +// }, test.SidHistory); +// } +// +// [Fact] +// public async Task LDAPPropertyProcessor_ReadComputerProperties_TestBadPaths() +// { +// var mock = new MockSearchResultEntry("CN\u003dWIN10,OU\u003dTestOU,DC\u003dtestlab,DC\u003dlocal", +// new Dictionary +// { +// {"description", "Test"}, +// {"useraccountcontrol", "abc"}, +// {"lastlogon", "132673011142753043"}, +// {"lastlogontimestamp", "132670318095676525"}, +// {"operatingsystem", "Windows 10 Enterprise"}, +// {"admincount", "c"}, +// { +// "sidhistory", new[] +// { +// Array.Empty() +// } +// }, +// { +// "msds-allowedToDelegateTo", new[] +// { +// "ldap/PRIMARY.testlab.local/testlab.local", +// "ldap/PRIMARY.testlab.local", +// "ldap/PRIMARY" +// } +// }, +// {"pwdlastset", "132131667346106691"}, +// { +// "serviceprincipalname", new[] +// { +// "WSMAN/WIN10", +// "WSMAN/WIN10.testlab.local", +// "RestrictedKrbHost/WIN10", +// "HOST/WIN10", +// "RestrictedKrbHost/WIN10.testlab.local", +// "HOST/WIN10.testlab.local" +// } +// } +// }, "S-1-5-21-3130019616-2776909439-2417379446-1101", Label.Computer); +// +// var processor = new LDAPPropertyProcessor(new MockLDAPUtils()); +// var test = await processor.ReadComputerProperties(mock); +// var props = test.Props; +// var keys = props.Keys; +// +// Assert.Contains("unconstraineddelegation", keys); +// Assert.Contains("enabled", keys); +// Assert.Contains("trustedtoauth", keys); +// Assert.False((bool)props["unconstraineddelegation"]); +// Assert.True((bool)props["enabled"]); +// Assert.False((bool)props["trustedtoauth"]); +// Assert.Contains("sidhistory", keys); +// Assert.Empty(props["sidhistory"] as string[]); +// } +// +// +// [Fact] +// public async Task LDAPPropertyProcessor_ReadComputerProperties_TestDumpSMSAPassword() +// { +// var mock = new MockSearchResultEntry("CN\u003dWIN10,OU\u003dTestOU,DC\u003dtestlab,DC\u003dlocal", +// new Dictionary +// { +// {"description", "Test"}, +// {"useraccountcontrol", 0x1001000.ToString()}, +// {"lastlogon", "132673011142753043"}, +// {"lastlogontimestamp", "132670318095676525"}, +// {"operatingsystem", "Windows 10 Enterprise"}, +// {"operatingsystemservicepack", "1607"}, +// {"admincount", "c"}, +// { +// "sidhistory", new[] +// { +// Helpers.B64ToBytes("AQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQUQQAAA==") +// } +// }, +// { +// "msds-allowedtodelegateto", new[] +// { +// "ldap/PRIMARY.testlab.local/testlab.local", +// "ldap/PRIMARY.testlab.local", +// "ldap/PRIMARY" +// } +// }, +// {"pwdlastset", "132131667346106691"}, +// { +// "serviceprincipalname", new[] +// { +// "WSMAN/WIN10", +// "WSMAN/WIN10.testlab.local", +// "RestrictedKrbHost/WIN10", +// "HOST/WIN10", +// "RestrictedKrbHost/WIN10.testlab.local", +// "HOST/WIN10.testlab.local" +// } +// }, +// { +// "msds-hostserviceaccount", new[] +// { +// "CN=dfm,CN=Users,DC=testlab,DC=local", +// "CN=krbtgt,CN=Users,DC=testlab,DC=local" +// } +// } +// }, "S-1-5-21-3130019616-2776909439-2417379446-1101", Label.Computer); +// +// var processor = new LDAPPropertyProcessor(new MockLDAPUtils()); +// var test = await processor.ReadComputerProperties(mock); +// +// var expected = new TypedPrincipal[] +// { +// new() +// { +// ObjectIdentifier = "S-1-5-21-3130019616-2776909439-2417379446-1105", +// ObjectType = Label.User +// }, +// new() +// { +// ObjectIdentifier = "S-1-5-21-3130019616-2776909439-2417379446-502", +// ObjectType = Label.User +// } +// }; +// +// var testDumpSMSAPassword = test.DumpSMSAPassword; +// Assert.Equal(2, testDumpSMSAPassword.Length); +// Assert.Equal(expected, testDumpSMSAPassword); +// +// } +// +// [Fact] +// public void LDAPPropertyProcessor_ReadRootCAProperties() +// { +// var mock = new MockSearchResultEntry( +// "CN\u003dDUMPSTER-DC01-CA,CN\u003dCERTIFICATION AUTHORITIES,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dDUMPSTER,DC\u003dFIRE", +// new Dictionary +// { +// {"description", null}, +// {"domain", "DUMPSTER.FIRE"}, +// {"name", "DUMPSTER-DC01-CA@DUMPSTER.FIRE"}, +// {"domainsid", "S-1-5-21-2697957641-2271029196-387917394"}, +// {"whencreated", 1683986131}, +// }, "2F9F3630-F46A-49BF-B186-6629994EBCF9", Label.RootCA); +// +// var test = LDAPPropertyProcessor.ReadRootCAProperties(mock); +// var keys = test.Keys; +// +// //These are not common properties +// Assert.DoesNotContain("domain", keys); +// Assert.DoesNotContain("name", keys); +// Assert.DoesNotContain("domainsid", keys); +// +// Assert.Contains("description", keys); +// Assert.Contains("whencreated", keys); +// } +// +// [Fact] +// public void LDAPPropertyProcessor_ReadAIACAProperties() +// { +// var mock = new MockSearchResultEntry( +// "CN\u003dDUMPSTER-DC01-CA,CN\u003dAIA,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dDUMPSTER,DC\u003dFIRE", +// new Dictionary +// { +// {"description", null}, +// {"domain", "DUMPSTER.FIRE"}, +// {"name", "DUMPSTER-DC01-CA@DUMPSTER.FIRE"}, +// {"domainsid", "S-1-5-21-2697957641-2271029196-387917394"}, +// {"whencreated", 1683986131}, +// {"hascrosscertificatepair", true}, +// }, "2F9F3630-F46A-49BF-B186-6629994EBCF9", Label.AIACA); +// +// var test = LDAPPropertyProcessor.ReadAIACAProperties(mock); +// var keys = test.Keys; +// +// //These are not common properties +// Assert.DoesNotContain("domain", keys); +// Assert.DoesNotContain("name", keys); +// Assert.DoesNotContain("domainsid", keys); +// +// Assert.Contains("description", keys); +// Assert.Contains("whencreated", keys); +// Assert.Contains("crosscertificatepair", keys); +// } +// +// [Fact] +// public void LDAPPropertyProcessor_ReadNTAuthStoreProperties() +// { +// var mock = new MockSearchResultEntry("CN\u003dNTAUTHCERTIFICATES,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dDUMPSTER,DC\u003dFIRE", +// new Dictionary +// { +// {"description", null}, +// {"domain", "DUMPSTER.FIRE"}, +// {"name", "NTAUTHCERTIFICATES@DUMPSTER.FIRE"}, +// {"domainsid", "S-1-5-21-2697957641-2271029196-387917394"}, +// {"whencreated", 1683986131}, +// }, "2F9F3630-F46A-49BF-B186-6629994EBCF9", Label.NTAuthStore); +// +// var test = LDAPPropertyProcessor.ReadNTAuthStoreProperties(mock); +// var keys = test.Keys; +// +// //These are not common properties +// Assert.DoesNotContain("domain", keys); +// Assert.DoesNotContain("name", keys); +// Assert.DoesNotContain("domainsid", keys); +// +// Assert.Contains("description", keys); +// Assert.Contains("whencreated", keys); +// } +// +// [Fact] +// public void LDAPPropertyProcessor_ReadCertTemplateProperties() +// { +// var mock = new MockSearchResultEntry("CN\u003dWORKSTATION,CN\u003dCERTIFICATE TEMPLATES,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dEXTERNAL,DC\u003dLOCAL", +// new Dictionary +// { +// {"domain", "EXTERNAL.LOCAL"}, +// {"name", "WORKSTATION@EXTERNAL.LOCAL"}, +// {"domainsid", "S-1-5-21-3702535222-3822678775-2090119576"}, +// {"description", null}, +// {"whencreated", 1683986183}, +// {"validityperiod", 31536000}, +// {"renewalperiod", 3628800}, +// {"schemaversion", 2}, +// {"displayname", "Workstation Authentication"}, +// {"oid", "1.3.6.1.4.1.311.21.8.4571196.1884641.3293620.10686285.12068043.134.1.30"}, +// {"enrollmentflag", 32}, +// {"requiresmanagerapproval", false}, +// {"certificatenameflag", 0x8000000}, +// {"ekus", new[] +// {"1.3.6.1.5.5.7.3.2"} +// }, +// {LDAPProperties.CertificateApplicationPolicy, new[] +// {"1.3.6.1.5.5.7.3.2"} +// }, +// {LDAPProperties.CertificatePolicy, new[] +// {"1.3.6.1.5.5.7.3.2"} +// }, +// {"authorizedsignatures", 1}, +// {"applicationpolicies", new[] +// { "1.3.6.1.4.1.311.20.2.1"} +// }, +// {"issuancepolicies", new[] +// {"1.3.6.1.4.1.311.21.8.4571196.1884641.3293620.10686285.12068043.134.1.400", +// "1.3.6.1.4.1.311.21.8.4571196.1884641.3293620.10686285.12068043.134.1.402"} +// }, +// }, "2F9F3630-F46A-49BF-B186-6629994EBCF9", Label.CertTemplate); +// +// var test = LDAPPropertyProcessor.ReadCertTemplateProperties(mock); +// var keys = test.Keys; +// +// //These are not common properties +// Assert.DoesNotContain("domain", keys); +// Assert.DoesNotContain("name", keys); +// Assert.DoesNotContain("domainsid", keys); +// +// Assert.Contains("description", keys); +// Assert.Contains("whencreated", keys); +// Assert.Contains("validityperiod", keys); +// Assert.Contains("renewalperiod", keys); +// Assert.Contains("schemaversion", keys); +// Assert.Contains("displayname", keys); +// Assert.Contains("oid", keys); +// Assert.Contains("enrollmentflag", keys); +// Assert.Contains("requiresmanagerapproval", keys); +// Assert.Contains("certificatenameflag", keys); +// Assert.Contains("enrolleesuppliessubject", keys); +// Assert.Contains("subjectaltrequireupn", keys); +// Assert.Contains("subjectaltrequiredns", keys); +// Assert.Contains("subjectaltrequiredomaindns", keys); +// Assert.Contains("subjectaltrequireemail", keys); +// Assert.Contains("subjectaltrequirespn", keys); +// Assert.Contains("subjectrequireemail", keys); +// Assert.Contains("ekus", keys); +// Assert.Contains("certificateapplicationpolicy", keys); +// var hasPolicy = test.TryGetValue("certificatepolicy", out var policies); +// Assert.True(hasPolicy); +// if (policies is string[] e) +// { +// Assert.Contains("1.3.6.1.5.5.7.3.2", e); +// } +// Assert.Contains("authorizedsignatures", keys); +// Assert.Contains("applicationpolicies", keys); +// Assert.Contains("issuancepolicies", keys); +// +// } +// // +// // [Fact] +// // public void LDAPPropertyProcessor_ReadIssuancePolicyProperties() +// // { +// // var mock = new MockSearchResultEntry("CN\u003d6250993.11BB1AB25A8A65E9FCDF709FCDD5FBC6,CN\u003dOID,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dESC10,DC\u003dLOCAL", +// // new Dictionary +// // { +// // {"domain", "ESC10.LOCAL"}, +// // {"name", "KEYADMINSOID@ESC10.LOCAL"}, +// // {"domainsid", "S-1-5-21-3662707843-2053279151-3839588741"}, +// // {"description", null}, +// // {"whencreated", 1712567279}, +// // {"displayname", "KeyAdminsOID"}, +// // {"certtemplateoid", "1.3.6.1.4.1.311.21.8.4571196.1884641.3293620.10686285.12068043.134.1.30"}, +// // {"msds-oidtogrouplink", "CN=ENTERPRISE KEY ADMINS,CN=USERS,DC=ESC10,DC=LOCAL"} +// // , +// // }, "1E5311A8-E949-4E02-8E08-234ED63200DE", Label.IssuancePolicy); +// // +// // var mockLDAPUtils = new MockLDAPUtils(); +// // var ldapPropertyProcessor = new LDAPPropertyProcessor(mockLDAPUtils); +// // +// // +// // var test = ldapPropertyProcessor.ReadIssuancePolicyProperties(mock); +// // var keys = test.Props.Keys; +// // +// // //These are not common properties +// // Assert.DoesNotContain("domain", keys); +// // Assert.DoesNotContain("name", keys); +// // Assert.DoesNotContain("domainsid", keys); +// // +// // Assert.Contains("description", keys); +// // Assert.Contains("whencreated", keys); +// // Assert.Contains("displayname", keys); +// // Assert.Contains("certtemplateoid", keys); +// // Assert.Contains("oidgrouplink", keys); +// // } +// // +// // [Fact] +// // public void LDAPPropertyProcessor_ReadIssuancePolicyProperties_NoOIDGroupLink() +// // { +// // var mock = new MockSearchResultEntry("CN\u003d6250993.11BB1AB25A8A65E9FCDF709FCDD5FBC6,CN\u003dOID,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dESC10,DC\u003dLOCAL", +// // new Dictionary +// // { +// // {"domain", "ESC10.LOCAL"}, +// // {"name", "KEYADMINSOID@ESC10.LOCAL"}, +// // {"domainsid", "S-1-5-21-3662707843-2053279151-3839588741"}, +// // {"description", null}, +// // {"whencreated", 1712567279}, +// // {"displayname", "KeyAdminsOID"}, +// // {"certtemplateoid", "1.3.6.1.4.1.311.21.8.4571196.1884641.3293620.10686285.12068043.134.1.30"}, +// // {"msds-oidtogrouplink", null} +// // , +// // }, "1E5311A8-E949-4E02-8E08-234ED63200DE", Label.IssuancePolicy); +// // +// // var mockLDAPUtils = new MockLDAPUtils(); +// // var ldapPropertyProcessor = new LDAPPropertyProcessor(mockLDAPUtils); +// // +// // +// // var test = ldapPropertyProcessor.ReadIssuancePolicyProperties(mock); +// // var keys = test.Props.Keys; +// // +// // //These are not common properties +// // Assert.DoesNotContain("domain", keys); +// // Assert.DoesNotContain("name", keys); +// // Assert.DoesNotContain("domainsid", keys); +// // Assert.DoesNotContain("oidgrouplink", keys); +// // +// // Assert.Contains("description", keys); +// // Assert.Contains("whencreated", keys); +// // Assert.Contains("displayname", keys); +// // Assert.Contains("certtemplateoid", keys); +// // } +// +// [Fact] +// public void LDAPPropertyProcessor_ParseAllProperties() +// { +// var mock = new MockSearchResultEntry("CN\u003dNTAUTHCERTIFICATES,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dDUMPSTER,DC\u003dFIRE", +// new Dictionary +// { +// {"description", null}, +// {"domain", "DUMPSTER.FIRE"}, +// {"name", "NTAUTHCERTIFICATES@DUMPSTER.FIRE"}, +// {"domainsid", "S-1-5-21-2697957641-2271029196-387917394"}, +// {"whencreated", 1683986131}, +// }, "2F9F3630-F46A-49BF-B186-6629994EBCF9", Label.NTAuthStore); +// +// var processor = new LDAPPropertyProcessor(new MockLDAPUtils()); +// var props = processor.ParseAllProperties(mock); +// var keys = props.Keys; +// +// //These are reserved properties and so they should be filtered out +// Assert.DoesNotContain("description", keys); +// Assert.DoesNotContain("whencreated", keys); +// Assert.DoesNotContain("name", keys); +// +// Assert.Contains("domainsid", keys); +// Assert.Contains("domain", keys); +// } +// +// [Fact] +// public void LDAPPropertyProcessor_ParseAllProperties_NoProperties() +// { +// var mock = new MockSearchResultEntry("CN\u003dNTAUTHCERTIFICATES,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dDUMPSTER,DC\u003dFIRE", +// new Dictionary +// { }, "2F9F3630-F46A-49BF-B186-6629994EBCF9", Label.NTAuthStore); +// +// var processor = new LDAPPropertyProcessor(new MockLDAPUtils()); +// var props = processor.ParseAllProperties(mock); +// var keys = props.Keys; +// +// Assert.Empty(keys); +// +// } +// +// [Fact] +// public void LDAPPropertyProcessor_ParseAllProperties_CollectionCountOne_NullString() +// { +// var mock = new MockSearchResultEntry("CN\u003dNTAUTHCERTIFICATES,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dDUMPSTER,DC\u003dFIRE", +// new Dictionary +// {{"domainsid", null} }, "2F9F3630-F46A-49BF-B186-6629994EBCF9", Label.NTAuthStore); +// +// var processor = new LDAPPropertyProcessor(new MockLDAPUtils()); +// var props = processor.ParseAllProperties(mock); +// var keys = props.Keys; +// +// Assert.Empty(keys); +// } +// +// [Fact] +// public void LDAPPropertyProcessor_ParseAllProperties_CollectionCountOne_BadPasswordTime() +// { +// var mock = new MockSearchResultEntry("CN\u003dNTAUTHCERTIFICATES,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dDUMPSTER,DC\u003dFIRE", +// new Dictionary +// {{"badpasswordtime", "130435290000000000"} }, "2F9F3630-F46A-49BF-B186-6629994EBCF9", Label.NTAuthStore); +// +// var processor = new LDAPPropertyProcessor(new MockLDAPUtils()); +// var props = processor.ParseAllProperties(mock); +// var keys = props.Keys; +// +// Assert.Contains("badpasswordtime", keys); +// Assert.Single(keys); +// } +// +// [Fact] +// public void LDAPPropertyProcessor_ParseAllProperties_CollectionCountOne_NotBadPasswordTime() +// { +// var mock = new MockSearchResultEntry("CN\u003dNTAUTHCERTIFICATES,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dDUMPSTER,DC\u003dFIRE", +// new Dictionary +// {{"domainsid", "S-1-5-21-2697957641-2271029196-387917394"}}, "2F9F3630-F46A-49BF-B186-6629994EBCF9", Label.NTAuthStore); +// +// var processor = new LDAPPropertyProcessor(new MockLDAPUtils()); +// var props = processor.ParseAllProperties(mock); +// var keys = props.Keys; +// +// Assert.Contains("domainsid", keys); +// Assert.Single(keys); +// } +// +// } +// } diff --git a/test/unit/UserRightsAssignmentProcessorTest.cs b/test/unit/UserRightsAssignmentProcessorTest.cs index 3bfca790..b0bc9562 100644 --- a/test/unit/UserRightsAssignmentProcessorTest.cs +++ b/test/unit/UserRightsAssignmentProcessorTest.cs @@ -1,66 +1,66 @@ -using System.Linq; -using System.Threading.Tasks; -using CommonLibTest.Facades; -using CommonLibTest.Facades.LSAMocks.DCMocks; -using CommonLibTest.Facades.LSAMocks.WorkstationMocks; -using Moq; -using Newtonsoft.Json; -using SharpHoundCommonLib.Enums; -using SharpHoundCommonLib.Processors; -using Xunit; -using Xunit.Abstractions; - -namespace CommonLibTest -{ - public class UserRightsAssignmentProcessorTest - { - private readonly ITestOutputHelper _testOutputHelper; - - public UserRightsAssignmentProcessorTest(ITestOutputHelper testOutputHelper) - { - _testOutputHelper = testOutputHelper; - } - - [WindowsOnlyFact] - public async Task UserRightsAssignmentProcessor_TestWorkstation() - { - var mockProcessor = new Mock(new MockLDAPUtils(), null); - var mockLSAPolicy = new MockWorkstationLSAPolicy(); - mockProcessor.Setup(x => x.OpenLSAPolicy(It.IsAny())).Returns(mockLSAPolicy); - var processor = mockProcessor.Object; - var machineDomainSid = $"{Consts.MockDomainSid}-1001"; - var results = await processor.GetUserRightsAssignments("win10.testlab.local", machineDomainSid, "testlab.local", false) - .ToArrayAsync(); - - var privilege = results[0]; - Assert.Equal(LSAPrivileges.RemoteInteractiveLogon, privilege.Privilege); - Assert.Equal(3, results[0].Results.Length); - var adminResult = privilege.Results.First(x => x.ObjectIdentifier.EndsWith("-544")); - Assert.Equal($"{machineDomainSid}-544", adminResult.ObjectIdentifier); - Assert.Equal(Label.LocalGroup, adminResult.ObjectType); - var rdpResult = privilege.Results.First(x => x.ObjectIdentifier.EndsWith("-555")); - Assert.Equal($"{machineDomainSid}-555", rdpResult.ObjectIdentifier); - Assert.Equal(Label.LocalGroup, rdpResult.ObjectType); - } - - [WindowsOnlyFact] - public async Task UserRightsAssignmentProcessor_TestDC() - { - var mockProcessor = new Mock(new MockLDAPUtils(), null); - var mockLSAPolicy = new MockDCLSAPolicy(); - mockProcessor.Setup(x => x.OpenLSAPolicy(It.IsAny())).Returns(mockLSAPolicy); - var processor = mockProcessor.Object; - var machineDomainSid = $"{Consts.MockDomainSid}-1000"; - var results = await processor.GetUserRightsAssignments("primary.testlab.local", machineDomainSid, "testlab.local", true) - .ToArrayAsync(); - - var privilege = results[0]; - _testOutputHelper.WriteLine(JsonConvert.SerializeObject(privilege)); - Assert.Equal(LSAPrivileges.RemoteInteractiveLogon, privilege.Privilege); - Assert.Single(results[0].Results); - var adminResult = privilege.Results.First(x => x.ObjectIdentifier.EndsWith("-544")); - Assert.Equal("TESTLAB.LOCAL-S-1-5-32-544", adminResult.ObjectIdentifier); - Assert.Equal(Label.Group, adminResult.ObjectType); - } - } -} \ No newline at end of file +// using System.Linq; +// using System.Threading.Tasks; +// using CommonLibTest.Facades; +// using CommonLibTest.Facades.LSAMocks.DCMocks; +// using CommonLibTest.Facades.LSAMocks.WorkstationMocks; +// using Moq; +// using Newtonsoft.Json; +// using SharpHoundCommonLib.Enums; +// using SharpHoundCommonLib.Processors; +// using Xunit; +// using Xunit.Abstractions; +// +// namespace CommonLibTest +// { +// public class UserRightsAssignmentProcessorTest +// { +// private readonly ITestOutputHelper _testOutputHelper; +// +// public UserRightsAssignmentProcessorTest(ITestOutputHelper testOutputHelper) +// { +// _testOutputHelper = testOutputHelper; +// } +// +// [WindowsOnlyFact] +// public async Task UserRightsAssignmentProcessor_TestWorkstation() +// { +// var mockProcessor = new Mock(new MockLDAPUtils(), null); +// var mockLSAPolicy = new MockWorkstationLSAPolicy(); +// mockProcessor.Setup(x => x.OpenLSAPolicy(It.IsAny())).Returns(mockLSAPolicy); +// var processor = mockProcessor.Object; +// var machineDomainSid = $"{Consts.MockDomainSid}-1001"; +// var results = await processor.GetUserRightsAssignments("win10.testlab.local", machineDomainSid, "testlab.local", false) +// .ToArrayAsync(); +// +// var privilege = results[0]; +// Assert.Equal(LSAPrivileges.RemoteInteractiveLogon, privilege.Privilege); +// Assert.Equal(3, results[0].Results.Length); +// var adminResult = privilege.Results.First(x => x.ObjectIdentifier.EndsWith("-544")); +// Assert.Equal($"{machineDomainSid}-544", adminResult.ObjectIdentifier); +// Assert.Equal(Label.LocalGroup, adminResult.ObjectType); +// var rdpResult = privilege.Results.First(x => x.ObjectIdentifier.EndsWith("-555")); +// Assert.Equal($"{machineDomainSid}-555", rdpResult.ObjectIdentifier); +// Assert.Equal(Label.LocalGroup, rdpResult.ObjectType); +// } +// +// [WindowsOnlyFact] +// public async Task UserRightsAssignmentProcessor_TestDC() +// { +// var mockProcessor = new Mock(new MockLDAPUtils(), null); +// var mockLSAPolicy = new MockDCLSAPolicy(); +// mockProcessor.Setup(x => x.OpenLSAPolicy(It.IsAny())).Returns(mockLSAPolicy); +// var processor = mockProcessor.Object; +// var machineDomainSid = $"{Consts.MockDomainSid}-1000"; +// var results = await processor.GetUserRightsAssignments("primary.testlab.local", machineDomainSid, "testlab.local", true) +// .ToArrayAsync(); +// +// var privilege = results[0]; +// _testOutputHelper.WriteLine(JsonConvert.SerializeObject(privilege)); +// Assert.Equal(LSAPrivileges.RemoteInteractiveLogon, privilege.Privilege); +// Assert.Single(results[0].Results); +// var adminResult = privilege.Results.First(x => x.ObjectIdentifier.EndsWith("-544")); +// Assert.Equal("TESTLAB.LOCAL-S-1-5-32-544", adminResult.ObjectIdentifier); +// Assert.Equal(Label.Group, adminResult.ObjectType); +// } +// } +// } \ No newline at end of file From 0e9aa086a5f0121d4710ec5661e8e9c493e0adcd Mon Sep 17 00:00:00 2001 From: rvazarkar Date: Mon, 8 Jul 2024 11:53:58 -0400 Subject: [PATCH 37/68] wip: more test comment --- test/unit/ComputerSessionProcessorTest.cs | 462 +++++++++++----------- test/unit/LocalGroupProcessorTest.cs | 220 +++++------ 2 files changed, 341 insertions(+), 341 deletions(-) diff --git a/test/unit/ComputerSessionProcessorTest.cs b/test/unit/ComputerSessionProcessorTest.cs index f5935810..4ea1003b 100644 --- a/test/unit/ComputerSessionProcessorTest.cs +++ b/test/unit/ComputerSessionProcessorTest.cs @@ -1,231 +1,231 @@ -using System; -using System.Threading.Tasks; -using CommonLibTest.Facades; -using Moq; -using Newtonsoft.Json; -using SharpHoundCommonLib; -using SharpHoundCommonLib.OutputTypes; -using SharpHoundCommonLib.Processors; -using SharpHoundRPC.NetAPINative; -using Xunit; -using Xunit.Abstractions; - -namespace CommonLibTest -{ - public class ComputerSessionProcessorTest : IDisposable - { - private readonly string _computerDomain; - private readonly string _computerSid; - private readonly ITestOutputHelper _testOutputHelper; - - public ComputerSessionProcessorTest(ITestOutputHelper testOutputHelper) - { - _testOutputHelper = testOutputHelper; - _computerDomain = "TESTLAB.LOCAL"; - _computerSid = "S-1-5-21-3130019616-2776909439-2417379446-1104"; - } - - #region IDispose Implementation - - public void Dispose() - { - // Tear down (called once per test) - } - - #endregion - - [WindowsOnlyFact] - public async Task ComputerSessionProcessor_ReadUserSessions_FilteringWorks() - { - var mockNativeMethods = new Mock(); - - var apiResult = new NetSessionEnumResults[] - { - new("dfm", "\\\\192.168.92.110"), - new("admin", ""), - new("admin", "\\\\192.168.92.110") - }; - mockNativeMethods.Setup(x => x.NetSessionEnum(It.IsAny())).Returns(apiResult); - - var processor = new ComputerSessionProcessor(new MockLDAPUtils(), "dfm", mockNativeMethods.Object); - var result = await processor.ReadUserSessions("win10", _computerSid, _computerDomain); - Assert.True(result.Collected); - Assert.Empty(result.Results); - } - - [WindowsOnlyFact] - public async Task ComputerSessionProcessor_ReadUserSessions_ResolvesHost() - { - var mockNativeMethods = new Mock(); - var apiResult = new NetSessionEnumResults[] - { - new("admin", "\\\\192.168.1.1") - }; - mockNativeMethods.Setup(x => x.NetSessionEnum(It.IsAny())).Returns(apiResult); - - var expected = new Session[] - { - new() - { - ComputerSID = "S-1-5-21-3130019616-2776909439-2417379446-1104", - UserSID = "S-1-5-21-3130019616-2776909439-2417379446-2116" - } - }; - - var processor = new ComputerSessionProcessor(new MockLDAPUtils(), "dfm", mockNativeMethods.Object); - var result = await processor.ReadUserSessions("win10", _computerSid, _computerDomain); - Assert.True(result.Collected); - Assert.Equal(expected, result.Results); - } - - [WindowsOnlyFact] - public async Task ComputerSessionProcessor_ReadUserSessions_ResolvesLocalHostEquivalent() - { - var mockNativeMethods = new Mock(); - var apiResult = new NetSessionEnumResults[] - { - new("admin", "\\\\127.0.0.1") - }; - mockNativeMethods.Setup(x => x.NetSessionEnum(It.IsAny())).Returns(apiResult); - - var expected = new Session[] - { - new() - { - ComputerSID = _computerSid, - UserSID = "S-1-5-21-3130019616-2776909439-2417379446-2116" - } - }; - - var processor = new ComputerSessionProcessor(new MockLDAPUtils(), "dfm", mockNativeMethods.Object); - var result = await processor.ReadUserSessions("win10", _computerSid, _computerDomain); - Assert.True(result.Collected); - Assert.Equal(expected, result.Results); - } - - [WindowsOnlyFact] - public async Task ComputerSessionProcessor_ReadUserSessions_MultipleMatches_AddsAll() - { - var mockNativeMethods = new Mock(); - var apiResult = new NetSessionEnumResults[] - { - new("administrator", "\\\\127.0.0.1") - }; - mockNativeMethods.Setup(x => x.NetSessionEnum(It.IsAny())).Returns(apiResult); - - var expected = new Session[] - { - new() - { - ComputerSID = _computerSid, - UserSID = "S-1-5-21-3130019616-2776909439-2417379446-500" - }, - new() - { - ComputerSID = _computerSid, - UserSID = "S-1-5-21-3084884204-958224920-2707782874-500" - } - }; - - var processor = new ComputerSessionProcessor(new MockLDAPUtils(), "dfm", mockNativeMethods.Object); - var result = await processor.ReadUserSessions("win10", _computerSid, _computerDomain); - Assert.True(result.Collected); - Assert.Equal(expected, result.Results); - } - - [WindowsOnlyFact] - public async Task ComputerSessionProcessor_ReadUserSessions_NoGCMatch_TriesResolve() - { - var mockNativeMethods = new Mock(); - var apiResult = new NetSessionEnumResults[] - { - new("test", "\\\\127.0.0.1") - }; - mockNativeMethods.Setup(x => x.NetSessionEnum(It.IsAny())).Returns(apiResult); - - var expected = new Session[] - { - new() - { - ComputerSID = _computerSid, - UserSID = "S-1-5-21-3130019616-2776909439-2417379446-1106" - } - }; - - var processor = new ComputerSessionProcessor(new MockLDAPUtils(), "dfm", mockNativeMethods.Object); - var result = await processor.ReadUserSessions("win10", _computerSid, _computerDomain); - Assert.True(result.Collected); - Assert.Equal(expected, result.Results); - } - - [WindowsOnlyFact] - public async Task ComputerSessionProcessor_ReadUserSessions_ComputerAccessDenied_Handled() - { - var mockNativeMethods = new Mock(); - //mockNativeMethods.Setup(x => x.CallSamConnect(ref It.Ref.IsAny, out It.Ref.IsAny, It.IsAny(), ref It.Ref.IsAny)).Returns(NativeMethods.NtStatus.StatusAccessDenied); - mockNativeMethods.Setup(x => x.NetSessionEnum(It.IsAny())) - .Returns(NetAPIEnums.NetAPIStatus.ErrorAccessDenied); - var processor = new ComputerSessionProcessor(new MockLDAPUtils(), "dfm", mockNativeMethods.Object); - var test = await processor.ReadUserSessions("test", "test", "test"); - Assert.False(test.Collected); - Assert.Equal(NetAPIEnums.NetAPIStatus.ErrorAccessDenied.ToString(), test.FailureReason); - } - - [WindowsOnlyFact] - public async Task ComputerSessionProcessor_ReadUserSessionsPrivileged_ComputerAccessDenied_ExceptionCaught() - { - var mockNativeMethods = new Mock(); - //mockNativeMethods.Setup(x => x.CallSamConnect(ref It.Ref.IsAny, out It.Ref.IsAny, It.IsAny(), ref It.Ref.IsAny)).Returns(NativeMethods.NtStatus.StatusAccessDenied); - mockNativeMethods.Setup(x => x.NetWkstaUserEnum(It.IsAny())) - .Returns(NetAPIEnums.NetAPIStatus.ErrorAccessDenied); - var processor = new ComputerSessionProcessor(new MockLDAPUtils(), "dfm", mockNativeMethods.Object); - var test = await processor.ReadUserSessionsPrivileged("test", "test", "test"); - Assert.False(test.Collected); - Assert.Equal(NetAPIEnums.NetAPIStatus.ErrorAccessDenied.ToString(), test.FailureReason); - } - - [WindowsOnlyFact] - public async Task ComputerSessionProcessor_ReadUserSessionsPrivileged_FilteringWorks() - { - var mockNativeMethods = new Mock(); - const string samAccountName = "WIN10"; - - //This is a sample response from a computer in a test environment. The duplicates are intentional - var apiResults = new NetWkstaUserEnumResults[] - { - new("dfm", "TESTLAB"), - new("Administrator", "PRIMARY"), - new("Administrator", ""), - new("WIN10$", "TESTLAB"), - new("WIN10$", "TESTLAB"), - new("WIN10$", "TESTLAB"), - new("WIN10$", "TESTLAB"), - new("JOHN", "WIN10"), - new("SYSTEM", "NT AUTHORITY"), - new("ABC", "TESTLAB") - }; - mockNativeMethods.Setup(x => x.NetWkstaUserEnum(It.IsAny())).Returns(apiResults); - - var expected = new Session[] - { - new() - { - ComputerSID = _computerSid, - UserSID = "S-1-5-21-3130019616-2776909439-2417379446-1105" - }, - new() - { - ComputerSID = _computerSid, - UserSID = "S-1-5-21-3130019616-2776909439-2417379446-500" - } - }; - - var processor = new ComputerSessionProcessor(new MockLDAPUtils(), nativeMethods: mockNativeMethods.Object); - var test = await processor.ReadUserSessionsPrivileged("WIN10.TESTLAB.LOCAL", samAccountName, _computerSid); - Assert.True(test.Collected); - _testOutputHelper.WriteLine(JsonConvert.SerializeObject(test.Results)); - Assert.Equal(2, test.Results.Length); - Assert.Equal(expected, test.Results); - } - } -} \ No newline at end of file +// using System; +// using System.Threading.Tasks; +// using CommonLibTest.Facades; +// using Moq; +// using Newtonsoft.Json; +// using SharpHoundCommonLib; +// using SharpHoundCommonLib.OutputTypes; +// using SharpHoundCommonLib.Processors; +// using SharpHoundRPC.NetAPINative; +// using Xunit; +// using Xunit.Abstractions; +// +// namespace CommonLibTest +// { +// public class ComputerSessionProcessorTest : IDisposable +// { +// private readonly string _computerDomain; +// private readonly string _computerSid; +// private readonly ITestOutputHelper _testOutputHelper; +// +// public ComputerSessionProcessorTest(ITestOutputHelper testOutputHelper) +// { +// _testOutputHelper = testOutputHelper; +// _computerDomain = "TESTLAB.LOCAL"; +// _computerSid = "S-1-5-21-3130019616-2776909439-2417379446-1104"; +// } +// +// #region IDispose Implementation +// +// public void Dispose() +// { +// // Tear down (called once per test) +// } +// +// #endregion +// +// [WindowsOnlyFact] +// public async Task ComputerSessionProcessor_ReadUserSessions_FilteringWorks() +// { +// var mockNativeMethods = new Mock(); +// +// var apiResult = new NetSessionEnumResults[] +// { +// new("dfm", "\\\\192.168.92.110"), +// new("admin", ""), +// new("admin", "\\\\192.168.92.110") +// }; +// mockNativeMethods.Setup(x => x.NetSessionEnum(It.IsAny())).Returns(apiResult); +// +// var processor = new ComputerSessionProcessor(new MockLDAPUtils(), "dfm", mockNativeMethods.Object); +// var result = await processor.ReadUserSessions("win10", _computerSid, _computerDomain); +// Assert.True(result.Collected); +// Assert.Empty(result.Results); +// } +// +// [WindowsOnlyFact] +// public async Task ComputerSessionProcessor_ReadUserSessions_ResolvesHost() +// { +// var mockNativeMethods = new Mock(); +// var apiResult = new NetSessionEnumResults[] +// { +// new("admin", "\\\\192.168.1.1") +// }; +// mockNativeMethods.Setup(x => x.NetSessionEnum(It.IsAny())).Returns(apiResult); +// +// var expected = new Session[] +// { +// new() +// { +// ComputerSID = "S-1-5-21-3130019616-2776909439-2417379446-1104", +// UserSID = "S-1-5-21-3130019616-2776909439-2417379446-2116" +// } +// }; +// +// var processor = new ComputerSessionProcessor(new MockLDAPUtils(), "dfm", mockNativeMethods.Object); +// var result = await processor.ReadUserSessions("win10", _computerSid, _computerDomain); +// Assert.True(result.Collected); +// Assert.Equal(expected, result.Results); +// } +// +// [WindowsOnlyFact] +// public async Task ComputerSessionProcessor_ReadUserSessions_ResolvesLocalHostEquivalent() +// { +// var mockNativeMethods = new Mock(); +// var apiResult = new NetSessionEnumResults[] +// { +// new("admin", "\\\\127.0.0.1") +// }; +// mockNativeMethods.Setup(x => x.NetSessionEnum(It.IsAny())).Returns(apiResult); +// +// var expected = new Session[] +// { +// new() +// { +// ComputerSID = _computerSid, +// UserSID = "S-1-5-21-3130019616-2776909439-2417379446-2116" +// } +// }; +// +// var processor = new ComputerSessionProcessor(new MockLDAPUtils(), "dfm", mockNativeMethods.Object); +// var result = await processor.ReadUserSessions("win10", _computerSid, _computerDomain); +// Assert.True(result.Collected); +// Assert.Equal(expected, result.Results); +// } +// +// [WindowsOnlyFact] +// public async Task ComputerSessionProcessor_ReadUserSessions_MultipleMatches_AddsAll() +// { +// var mockNativeMethods = new Mock(); +// var apiResult = new NetSessionEnumResults[] +// { +// new("administrator", "\\\\127.0.0.1") +// }; +// mockNativeMethods.Setup(x => x.NetSessionEnum(It.IsAny())).Returns(apiResult); +// +// var expected = new Session[] +// { +// new() +// { +// ComputerSID = _computerSid, +// UserSID = "S-1-5-21-3130019616-2776909439-2417379446-500" +// }, +// new() +// { +// ComputerSID = _computerSid, +// UserSID = "S-1-5-21-3084884204-958224920-2707782874-500" +// } +// }; +// +// var processor = new ComputerSessionProcessor(new MockLDAPUtils(), "dfm", mockNativeMethods.Object); +// var result = await processor.ReadUserSessions("win10", _computerSid, _computerDomain); +// Assert.True(result.Collected); +// Assert.Equal(expected, result.Results); +// } +// +// [WindowsOnlyFact] +// public async Task ComputerSessionProcessor_ReadUserSessions_NoGCMatch_TriesResolve() +// { +// var mockNativeMethods = new Mock(); +// var apiResult = new NetSessionEnumResults[] +// { +// new("test", "\\\\127.0.0.1") +// }; +// mockNativeMethods.Setup(x => x.NetSessionEnum(It.IsAny())).Returns(apiResult); +// +// var expected = new Session[] +// { +// new() +// { +// ComputerSID = _computerSid, +// UserSID = "S-1-5-21-3130019616-2776909439-2417379446-1106" +// } +// }; +// +// var processor = new ComputerSessionProcessor(new MockLDAPUtils(), "dfm", mockNativeMethods.Object); +// var result = await processor.ReadUserSessions("win10", _computerSid, _computerDomain); +// Assert.True(result.Collected); +// Assert.Equal(expected, result.Results); +// } +// +// [WindowsOnlyFact] +// public async Task ComputerSessionProcessor_ReadUserSessions_ComputerAccessDenied_Handled() +// { +// var mockNativeMethods = new Mock(); +// //mockNativeMethods.Setup(x => x.CallSamConnect(ref It.Ref.IsAny, out It.Ref.IsAny, It.IsAny(), ref It.Ref.IsAny)).Returns(NativeMethods.NtStatus.StatusAccessDenied); +// mockNativeMethods.Setup(x => x.NetSessionEnum(It.IsAny())) +// .Returns(NetAPIEnums.NetAPIStatus.ErrorAccessDenied); +// var processor = new ComputerSessionProcessor(new MockLDAPUtils(), "dfm", mockNativeMethods.Object); +// var test = await processor.ReadUserSessions("test", "test", "test"); +// Assert.False(test.Collected); +// Assert.Equal(NetAPIEnums.NetAPIStatus.ErrorAccessDenied.ToString(), test.FailureReason); +// } +// +// [WindowsOnlyFact] +// public async Task ComputerSessionProcessor_ReadUserSessionsPrivileged_ComputerAccessDenied_ExceptionCaught() +// { +// var mockNativeMethods = new Mock(); +// //mockNativeMethods.Setup(x => x.CallSamConnect(ref It.Ref.IsAny, out It.Ref.IsAny, It.IsAny(), ref It.Ref.IsAny)).Returns(NativeMethods.NtStatus.StatusAccessDenied); +// mockNativeMethods.Setup(x => x.NetWkstaUserEnum(It.IsAny())) +// .Returns(NetAPIEnums.NetAPIStatus.ErrorAccessDenied); +// var processor = new ComputerSessionProcessor(new MockLDAPUtils(), "dfm", mockNativeMethods.Object); +// var test = await processor.ReadUserSessionsPrivileged("test", "test", "test"); +// Assert.False(test.Collected); +// Assert.Equal(NetAPIEnums.NetAPIStatus.ErrorAccessDenied.ToString(), test.FailureReason); +// } +// +// [WindowsOnlyFact] +// public async Task ComputerSessionProcessor_ReadUserSessionsPrivileged_FilteringWorks() +// { +// var mockNativeMethods = new Mock(); +// const string samAccountName = "WIN10"; +// +// //This is a sample response from a computer in a test environment. The duplicates are intentional +// var apiResults = new NetWkstaUserEnumResults[] +// { +// new("dfm", "TESTLAB"), +// new("Administrator", "PRIMARY"), +// new("Administrator", ""), +// new("WIN10$", "TESTLAB"), +// new("WIN10$", "TESTLAB"), +// new("WIN10$", "TESTLAB"), +// new("WIN10$", "TESTLAB"), +// new("JOHN", "WIN10"), +// new("SYSTEM", "NT AUTHORITY"), +// new("ABC", "TESTLAB") +// }; +// mockNativeMethods.Setup(x => x.NetWkstaUserEnum(It.IsAny())).Returns(apiResults); +// +// var expected = new Session[] +// { +// new() +// { +// ComputerSID = _computerSid, +// UserSID = "S-1-5-21-3130019616-2776909439-2417379446-1105" +// }, +// new() +// { +// ComputerSID = _computerSid, +// UserSID = "S-1-5-21-3130019616-2776909439-2417379446-500" +// } +// }; +// +// var processor = new ComputerSessionProcessor(new MockLDAPUtils(), nativeMethods: mockNativeMethods.Object); +// var test = await processor.ReadUserSessionsPrivileged("WIN10.TESTLAB.LOCAL", samAccountName, _computerSid); +// Assert.True(test.Collected); +// _testOutputHelper.WriteLine(JsonConvert.SerializeObject(test.Results)); +// Assert.Equal(2, test.Results.Length); +// Assert.Equal(expected, test.Results); +// } +// } +// } \ No newline at end of file diff --git a/test/unit/LocalGroupProcessorTest.cs b/test/unit/LocalGroupProcessorTest.cs index 0310fb73..d0725a05 100644 --- a/test/unit/LocalGroupProcessorTest.cs +++ b/test/unit/LocalGroupProcessorTest.cs @@ -1,110 +1,110 @@ -using System; -using System.Linq; -using System.Threading.Tasks; -using CommonLibTest.Facades; -using Moq; -using SharpHoundCommonLib.Enums; -using SharpHoundCommonLib.OutputTypes; -using SharpHoundCommonLib.Processors; -using Xunit; -using Xunit.Abstractions; - -namespace CommonLibTest -{ - public class LocalGroupProcessorTest : IDisposable - { - private readonly ITestOutputHelper _testOutputHelper; - - public LocalGroupProcessorTest(ITestOutputHelper testOutputHelper) - { - _testOutputHelper = testOutputHelper; - } - - public void Dispose() - { - } - - [WindowsOnlyFact] - public async Task LocalGroupProcessor_TestWorkstation() - { - var mockProcessor = new Mock(new MockLDAPUtils(), null); - var mockSamServer = new MockWorkstationSAMServer(); - mockProcessor.Setup(x => x.OpenSamServer(It.IsAny())).Returns(mockSamServer); - var processor = mockProcessor.Object; - var machineDomainSid = $"{Consts.MockWorkstationMachineSid}-1001"; - var results = await processor.GetLocalGroups("win10.testlab.local", machineDomainSid, "TESTLAB.LOCAL", false) - .ToArrayAsync(); - - Assert.Equal(3, results.Length); - var adminGroup = results.First(x => x.ObjectIdentifier.EndsWith("-544")); - Assert.Single(adminGroup.Results); - Assert.Equal($"{machineDomainSid}-544", adminGroup.ObjectIdentifier); - Assert.Equal("S-1-5-21-4243161961-3815211218-2888324771-512", adminGroup.Results[0].ObjectIdentifier); - var rdpGroup = results.First(x => x.ObjectIdentifier.EndsWith("-555")); - Assert.Equal(2, rdpGroup.Results.Length); - Assert.Collection(rdpGroup.Results, - principal => - { - Assert.Equal($"{machineDomainSid}-1003", principal.ObjectIdentifier); - Assert.Equal(Label.LocalGroup, principal.ObjectType); - - }, principal => - { - Assert.Equal($"{machineDomainSid}-544", principal.ObjectIdentifier); - Assert.Equal(Label.LocalGroup, principal.ObjectType); - }); - } - - [WindowsOnlyFact] - public async Task LocalGroupProcessor_TestDomainController() - { - var mockProcessor = new Mock(new MockLDAPUtils(), null); - var mockSamServer = new MockDCSAMServer(); - mockProcessor.Setup(x => x.OpenSamServer(It.IsAny())).Returns(mockSamServer); - var processor = mockProcessor.Object; - var machineDomainSid = $"{Consts.MockWorkstationMachineSid}-1000"; - var results = await processor.GetLocalGroups("primary.testlab.local", machineDomainSid, "TESTLAB.LOCAL", true) - .ToArrayAsync(); - - Assert.Equal(2, results.Length); - var adminGroup = results.First(x => x.ObjectIdentifier.EndsWith("-544")); - Assert.Single(adminGroup.Results); - Assert.Equal("TESTLAB.LOCAL-S-1-5-32-544", adminGroup.ObjectIdentifier); - Assert.Equal("S-1-5-21-4243161961-3815211218-2888324771-512", adminGroup.Results[0].ObjectIdentifier); - } - - [Fact] - public async Task LocalGroupProcessor_ResolveGroupName_NonDC() - { - var mockUtils = new Mock(); - var proc = new LocalGroupProcessor(mockUtils.Object); - - var result = TestPrivateMethod.InstanceMethod(proc, "ResolveGroupName", - new object[] - { - "ADMINISTRATORS", "WIN10.TESTLAB.LOCAL", "S-1-5-32-123-123-500", "TESTLAB.LOCAL", 544, false, false - }); - - Assert.Equal("ADMINISTRATORS@WIN10.TESTLAB.LOCAL", result.PrincipalName); - ; - Assert.Equal("S-1-5-32-123-123-500-544", result.ObjectId); - } - - [Fact] - public async Task LocalGroupProcessor_ResolveGroupName_DC() - { - var mockUtils = new Mock(); - var proc = new LocalGroupProcessor(mockUtils.Object); - - var result = TestPrivateMethod.InstanceMethod(proc, "ResolveGroupName", - new object[] - { - "ADMINISTRATORS", "PRIMARY.TESTLAB.LOCAL", "S-1-5-32-123-123-1000", "TESTLAB.LOCAL", 544, true, true - }); - - Assert.Equal("IGNOREME", result.PrincipalName); - ; - Assert.Equal("TESTLAB.LOCAL-S-1-5-32-544", result.ObjectId); - } - } -} \ No newline at end of file +// using System; +// using System.Linq; +// using System.Threading.Tasks; +// using CommonLibTest.Facades; +// using Moq; +// using SharpHoundCommonLib.Enums; +// using SharpHoundCommonLib.OutputTypes; +// using SharpHoundCommonLib.Processors; +// using Xunit; +// using Xunit.Abstractions; +// +// namespace CommonLibTest +// { +// public class LocalGroupProcessorTest : IDisposable +// { +// private readonly ITestOutputHelper _testOutputHelper; +// +// public LocalGroupProcessorTest(ITestOutputHelper testOutputHelper) +// { +// _testOutputHelper = testOutputHelper; +// } +// +// public void Dispose() +// { +// } +// +// [WindowsOnlyFact] +// public async Task LocalGroupProcessor_TestWorkstation() +// { +// var mockProcessor = new Mock(new MockLDAPUtils(), null); +// var mockSamServer = new MockWorkstationSAMServer(); +// mockProcessor.Setup(x => x.OpenSamServer(It.IsAny())).Returns(mockSamServer); +// var processor = mockProcessor.Object; +// var machineDomainSid = $"{Consts.MockWorkstationMachineSid}-1001"; +// var results = await processor.GetLocalGroups("win10.testlab.local", machineDomainSid, "TESTLAB.LOCAL", false) +// .ToArrayAsync(); +// +// Assert.Equal(3, results.Length); +// var adminGroup = results.First(x => x.ObjectIdentifier.EndsWith("-544")); +// Assert.Single(adminGroup.Results); +// Assert.Equal($"{machineDomainSid}-544", adminGroup.ObjectIdentifier); +// Assert.Equal("S-1-5-21-4243161961-3815211218-2888324771-512", adminGroup.Results[0].ObjectIdentifier); +// var rdpGroup = results.First(x => x.ObjectIdentifier.EndsWith("-555")); +// Assert.Equal(2, rdpGroup.Results.Length); +// Assert.Collection(rdpGroup.Results, +// principal => +// { +// Assert.Equal($"{machineDomainSid}-1003", principal.ObjectIdentifier); +// Assert.Equal(Label.LocalGroup, principal.ObjectType); +// +// }, principal => +// { +// Assert.Equal($"{machineDomainSid}-544", principal.ObjectIdentifier); +// Assert.Equal(Label.LocalGroup, principal.ObjectType); +// }); +// } +// +// [WindowsOnlyFact] +// public async Task LocalGroupProcessor_TestDomainController() +// { +// var mockProcessor = new Mock(new MockLDAPUtils(), null); +// var mockSamServer = new MockDCSAMServer(); +// mockProcessor.Setup(x => x.OpenSamServer(It.IsAny())).Returns(mockSamServer); +// var processor = mockProcessor.Object; +// var machineDomainSid = $"{Consts.MockWorkstationMachineSid}-1000"; +// var results = await processor.GetLocalGroups("primary.testlab.local", machineDomainSid, "TESTLAB.LOCAL", true) +// .ToArrayAsync(); +// +// Assert.Equal(2, results.Length); +// var adminGroup = results.First(x => x.ObjectIdentifier.EndsWith("-544")); +// Assert.Single(adminGroup.Results); +// Assert.Equal("TESTLAB.LOCAL-S-1-5-32-544", adminGroup.ObjectIdentifier); +// Assert.Equal("S-1-5-21-4243161961-3815211218-2888324771-512", adminGroup.Results[0].ObjectIdentifier); +// } +// +// [Fact] +// public async Task LocalGroupProcessor_ResolveGroupName_NonDC() +// { +// var mockUtils = new Mock(); +// var proc = new LocalGroupProcessor(mockUtils.Object); +// +// var result = TestPrivateMethod.InstanceMethod(proc, "ResolveGroupName", +// new object[] +// { +// "ADMINISTRATORS", "WIN10.TESTLAB.LOCAL", "S-1-5-32-123-123-500", "TESTLAB.LOCAL", 544, false, false +// }); +// +// Assert.Equal("ADMINISTRATORS@WIN10.TESTLAB.LOCAL", result.PrincipalName); +// ; +// Assert.Equal("S-1-5-32-123-123-500-544", result.ObjectId); +// } +// +// [Fact] +// public async Task LocalGroupProcessor_ResolveGroupName_DC() +// { +// var mockUtils = new Mock(); +// var proc = new LocalGroupProcessor(mockUtils.Object); +// +// var result = TestPrivateMethod.InstanceMethod(proc, "ResolveGroupName", +// new object[] +// { +// "ADMINISTRATORS", "PRIMARY.TESTLAB.LOCAL", "S-1-5-32-123-123-1000", "TESTLAB.LOCAL", 544, true, true +// }); +// +// Assert.Equal("IGNOREME", result.PrincipalName); +// ; +// Assert.Equal("TESTLAB.LOCAL-S-1-5-32-544", result.ObjectId); +// } +// } +// } \ No newline at end of file From de7bcf8e85d9c4875acec3e0efd97004b61ad889 Mon Sep 17 00:00:00 2001 From: rvazarkar Date: Mon, 8 Jul 2024 12:02:30 -0400 Subject: [PATCH 38/68] wip: break more tests --- test/unit/CommonLibHelperTests.cs | 8 +- test/unit/SPNProcessorsTest.cs | 214 ++++++++++++++-------------- test/unit/SearchResultEntryTests.cs | 72 +++++----- 3 files changed, 147 insertions(+), 147 deletions(-) diff --git a/test/unit/CommonLibHelperTests.cs b/test/unit/CommonLibHelperTests.cs index db95033d..8c7f1d96 100644 --- a/test/unit/CommonLibHelperTests.cs +++ b/test/unit/CommonLibHelperTests.cs @@ -202,12 +202,12 @@ public void ConvertFileTimeToUnixEpoch_Null_NegativeOne() Assert.Equal(-1, result); } - [WindowsOnlyFact] + [Fact] public void ConvertFileTimeToUnixEpoch_WrongFormat_FortmatException() { Exception ex = Assert.Throws(() => SharpHoundCommonLib.Helpers.ConvertFileTimeToUnixEpoch("asdsf")); - Assert.Equal("Input string was not in a correct format.", ex.Message); + Assert.Equal("The input string 'asdsf' was not in a correct format.", ex.Message); } [Fact] @@ -229,12 +229,12 @@ public void ConvertTimestampToUnixEpoch_ValidTimestamp_ValidUnixEpoch() Assert.Equal(d.ToUniversalTime().Date, testDate); } - [WindowsOnlyFact] + [Fact] public void ConvertTimestampToUnixEpoch_InvalidTimestamp_FormatException() { Exception ex = Assert.Throws(() => SharpHoundCommonLib.Helpers.ConvertFileTimeToUnixEpoch("-201adsfasf12180244")); - Assert.Equal("Input string was not in a correct format.", ex.Message); + Assert.Equal("The input string '-201adsfasf12180244' was not in a correct format.", ex.Message); } } } \ No newline at end of file diff --git a/test/unit/SPNProcessorsTest.cs b/test/unit/SPNProcessorsTest.cs index fa83f0e3..03cde9bb 100644 --- a/test/unit/SPNProcessorsTest.cs +++ b/test/unit/SPNProcessorsTest.cs @@ -1,107 +1,107 @@ -using System; -using System.Threading.Tasks; -using CommonLibTest.Facades; -using SharpHoundCommonLib; -using SharpHoundCommonLib.Enums; -using SharpHoundCommonLib.OutputTypes; -using SharpHoundCommonLib.Processors; -using Xunit; - -namespace CommonLibTest -{ - public class SPNProcessorsTest - { - [Fact] - public async Task ReadSPNTargets_SPNLengthZero_YieldBreak() - { - var processor = new SPNProcessors(new MockLDAPUtils()); - var servicePrincipalNames = Array.Empty(); - const string distinguishedName = "cn=policies,cn=system,DC=testlab,DC=local"; - await foreach (var spn in processor.ReadSPNTargets(servicePrincipalNames, distinguishedName)) - Assert.Null(spn); - } - - [Fact] - public async Task ReadSPNTargets_NoPortSupplied_ParsedCorrectly() - { - var processor = new SPNProcessors(new MockLDAPUtils()); - string[] servicePrincipalNames = {"MSSQLSvc/PRIMARY.TESTLAB.LOCAL"}; - const string distinguishedName = "cn=policies,cn=system,DC=testlab,DC=local"; - - var expected = new SPNPrivilege - { - ComputerSID = "S-1-5-21-3130019616-2776909439-2417379446-1001", Port = 1433, - Service = EdgeNames.SQLAdmin - }; - - await foreach (var actual in processor.ReadSPNTargets(servicePrincipalNames, distinguishedName)) - { - Assert.Equal(expected.ComputerSID, actual.ComputerSID); - Assert.Equal(expected.Port, actual.Port); - Assert.Equal(expected.Service, actual.Service); - } - } - - [Fact] - public async Task ReadSPNTargets_BadPortSupplied_ParsedCorrectly() - { - var processor = new SPNProcessors(new MockLDAPUtils()); - string[] servicePrincipalNames = {"MSSQLSvc/PRIMARY.TESTLAB.LOCAL:abcd"}; - const string distinguishedName = "cn=policies,cn=system,DC=testlab,DC=local"; - - var expected = new SPNPrivilege - { - ComputerSID = "S-1-5-21-3130019616-2776909439-2417379446-1001", Port = 1433, - Service = EdgeNames.SQLAdmin - }; - - await foreach (var actual in processor.ReadSPNTargets(servicePrincipalNames, distinguishedName)) - { - Assert.Equal(expected.ComputerSID, actual.ComputerSID); - Assert.Equal(expected.Port, actual.Port); - Assert.Equal(expected.Service, actual.Service); - } - } - - [Fact] - public async void ReadSPNTargets_SuppliedPort_ParsedCorrectly() - { - var processor = new SPNProcessors(new MockLDAPUtils()); - string[] servicePrincipalNames = {"MSSQLSvc/PRIMARY.TESTLAB.LOCAL:2345"}; - const string distinguishedName = "cn=policies,cn=system,DC=testlab,DC=local"; - - var expected = new SPNPrivilege - { - ComputerSID = "S-1-5-21-3130019616-2776909439-2417379446-1001", Port = 2345, - Service = EdgeNames.SQLAdmin - }; - - await foreach (var actual in processor.ReadSPNTargets(servicePrincipalNames, distinguishedName)) - { - Assert.Equal(expected.ComputerSID, actual.ComputerSID); - Assert.Equal(expected.Port, actual.Port); - Assert.Equal(expected.Service, actual.Service); - } - } - - [Fact] - public async void ReadSPNTargets_MissingMssqlSvc_NotRead() - { - var processor = new SPNProcessors(new MockLDAPUtils()); - string[] servicePrincipalNames = {"myhost.redmond.microsoft.com:1433"}; - const string distinguishedName = "CN=Jeff Smith,OU=Sales,DC=Fabrikam,DC=COM"; - await foreach (var spn in processor.ReadSPNTargets(servicePrincipalNames, distinguishedName)) - Assert.Null(spn); - } - - [Fact] - public async void ReadSPNTargets_SPNWithAddressSign_NotRead() - { - var processor = new SPNProcessors(new MockLDAPUtils()); - string[] servicePrincipalNames = {"MSSQLSvc/myhost.redmond.microsoft.com:1433 user@domain"}; - const string distinguishedName = "CN=Jeff Smith,OU=Sales,DC=Fabrikam,DC=COM"; - await foreach (var spn in processor.ReadSPNTargets(servicePrincipalNames, distinguishedName)) - Assert.Null(spn); - } - } -} \ No newline at end of file +// using System; +// using System.Threading.Tasks; +// using CommonLibTest.Facades; +// using SharpHoundCommonLib; +// using SharpHoundCommonLib.Enums; +// using SharpHoundCommonLib.OutputTypes; +// using SharpHoundCommonLib.Processors; +// using Xunit; +// +// namespace CommonLibTest +// { +// public class SPNProcessorsTest +// { +// [Fact] +// public async Task ReadSPNTargets_SPNLengthZero_YieldBreak() +// { +// var processor = new SPNProcessors(new MockLDAPUtils()); +// var servicePrincipalNames = Array.Empty(); +// const string distinguishedName = "cn=policies,cn=system,DC=testlab,DC=local"; +// await foreach (var spn in processor.ReadSPNTargets(servicePrincipalNames, distinguishedName)) +// Assert.Null(spn); +// } +// +// [Fact] +// public async Task ReadSPNTargets_NoPortSupplied_ParsedCorrectly() +// { +// var processor = new SPNProcessors(new MockLDAPUtils()); +// string[] servicePrincipalNames = {"MSSQLSvc/PRIMARY.TESTLAB.LOCAL"}; +// const string distinguishedName = "cn=policies,cn=system,DC=testlab,DC=local"; +// +// var expected = new SPNPrivilege +// { +// ComputerSID = "S-1-5-21-3130019616-2776909439-2417379446-1001", Port = 1433, +// Service = EdgeNames.SQLAdmin +// }; +// +// await foreach (var actual in processor.ReadSPNTargets(servicePrincipalNames, distinguishedName)) +// { +// Assert.Equal(expected.ComputerSID, actual.ComputerSID); +// Assert.Equal(expected.Port, actual.Port); +// Assert.Equal(expected.Service, actual.Service); +// } +// } +// +// [Fact] +// public async Task ReadSPNTargets_BadPortSupplied_ParsedCorrectly() +// { +// var processor = new SPNProcessors(new MockLDAPUtils()); +// string[] servicePrincipalNames = {"MSSQLSvc/PRIMARY.TESTLAB.LOCAL:abcd"}; +// const string distinguishedName = "cn=policies,cn=system,DC=testlab,DC=local"; +// +// var expected = new SPNPrivilege +// { +// ComputerSID = "S-1-5-21-3130019616-2776909439-2417379446-1001", Port = 1433, +// Service = EdgeNames.SQLAdmin +// }; +// +// await foreach (var actual in processor.ReadSPNTargets(servicePrincipalNames, distinguishedName)) +// { +// Assert.Equal(expected.ComputerSID, actual.ComputerSID); +// Assert.Equal(expected.Port, actual.Port); +// Assert.Equal(expected.Service, actual.Service); +// } +// } +// +// [Fact] +// public async void ReadSPNTargets_SuppliedPort_ParsedCorrectly() +// { +// var processor = new SPNProcessors(new MockLDAPUtils()); +// string[] servicePrincipalNames = {"MSSQLSvc/PRIMARY.TESTLAB.LOCAL:2345"}; +// const string distinguishedName = "cn=policies,cn=system,DC=testlab,DC=local"; +// +// var expected = new SPNPrivilege +// { +// ComputerSID = "S-1-5-21-3130019616-2776909439-2417379446-1001", Port = 2345, +// Service = EdgeNames.SQLAdmin +// }; +// +// await foreach (var actual in processor.ReadSPNTargets(servicePrincipalNames, distinguishedName)) +// { +// Assert.Equal(expected.ComputerSID, actual.ComputerSID); +// Assert.Equal(expected.Port, actual.Port); +// Assert.Equal(expected.Service, actual.Service); +// } +// } +// +// [Fact] +// public async void ReadSPNTargets_MissingMssqlSvc_NotRead() +// { +// var processor = new SPNProcessors(new MockLDAPUtils()); +// string[] servicePrincipalNames = {"myhost.redmond.microsoft.com:1433"}; +// const string distinguishedName = "CN=Jeff Smith,OU=Sales,DC=Fabrikam,DC=COM"; +// await foreach (var spn in processor.ReadSPNTargets(servicePrincipalNames, distinguishedName)) +// Assert.Null(spn); +// } +// +// [Fact] +// public async void ReadSPNTargets_SPNWithAddressSign_NotRead() +// { +// var processor = new SPNProcessors(new MockLDAPUtils()); +// string[] servicePrincipalNames = {"MSSQLSvc/myhost.redmond.microsoft.com:1433 user@domain"}; +// const string distinguishedName = "CN=Jeff Smith,OU=Sales,DC=Fabrikam,DC=COM"; +// await foreach (var spn in processor.ReadSPNTargets(servicePrincipalNames, distinguishedName)) +// Assert.Null(spn); +// } +// } +// } \ No newline at end of file diff --git a/test/unit/SearchResultEntryTests.cs b/test/unit/SearchResultEntryTests.cs index d49109eb..2fcaa68a 100644 --- a/test/unit/SearchResultEntryTests.cs +++ b/test/unit/SearchResultEntryTests.cs @@ -1,36 +1,36 @@ -using System.Collections.Generic; -using System.Security.Principal; -using CommonLibTest.Facades; -using SharpHoundCommonLib; -using SharpHoundCommonLib.Enums; -using Xunit; - -namespace CommonLibTest -{ - public class SearchResultEntryTests - { - [WindowsOnlyFact] - public void Test_GetLabelIssuanceOIDObjects() - { - var sid = new SecurityIdentifier("S-1-5-21-3130019616-2776909439-2417379446-500"); - var bsid = new byte[sid.BinaryLength]; - sid.GetBinaryForm(bsid, 0); - var attribs = new Dictionary - { - { "objectsid", bsid}, - { "objectclass", "msPKI-Enterprise-Oid" }, - { "flags", "2" } - }; - - var sre = MockableSearchResultEntry.Construct(attribs, "CN=Test,CN=OID,CN=Public Key Services,CN=Services,CN=Configuration"); - var success = sre.GetLabel(out var label); - Assert.True(success); - Assert.Equal(Label.IssuancePolicy, label); - - sre = MockableSearchResultEntry.Construct(attribs, "CN=OID,CN=Public Key Services,CN=Services,CN=Configuration"); - success = sre.GetLabel(out label); - Assert.True(success); - Assert.Equal(Label.Container, label); - } - } -} \ No newline at end of file +// using System.Collections.Generic; +// using System.Security.Principal; +// using CommonLibTest.Facades; +// using SharpHoundCommonLib; +// using SharpHoundCommonLib.Enums; +// using Xunit; +// +// namespace CommonLibTest +// { +// public class SearchResultEntryTests +// { +// [WindowsOnlyFact] +// public void Test_GetLabelIssuanceOIDObjects() +// { +// var sid = new SecurityIdentifier("S-1-5-21-3130019616-2776909439-2417379446-500"); +// var bsid = new byte[sid.BinaryLength]; +// sid.GetBinaryForm(bsid, 0); +// var attribs = new Dictionary +// { +// { "objectsid", bsid}, +// { "objectclass", "msPKI-Enterprise-Oid" }, +// { "flags", "2" } +// }; +// +// var sre = MockableSearchResultEntry.Construct(attribs, "CN=Test,CN=OID,CN=Public Key Services,CN=Services,CN=Configuration"); +// var success = sre.GetLabel(out var label); +// Assert.True(success); +// Assert.Equal(Label.IssuancePolicy, label); +// +// sre = MockableSearchResultEntry.Construct(attribs, "CN=OID,CN=Public Key Services,CN=Services,CN=Configuration"); +// success = sre.GetLabel(out label); +// Assert.True(success); +// Assert.Equal(Label.Container, label); +// } +// } +// } \ No newline at end of file From ab478e9134274a7934a80db0eeeb7d843c16d2fa Mon Sep 17 00:00:00 2001 From: rvazarkar Date: Tue, 9 Jul 2024 12:33:50 -0400 Subject: [PATCH 39/68] chore: some test updates --- test/unit/ACLProcessorTest.cs | 2032 ++++++++++++++-------------- test/unit/Facades/MockLDAPUtils.cs | 362 ++--- 2 files changed, 1151 insertions(+), 1243 deletions(-) diff --git a/test/unit/ACLProcessorTest.cs b/test/unit/ACLProcessorTest.cs index ca91ebc2..5aa46b76 100644 --- a/test/unit/ACLProcessorTest.cs +++ b/test/unit/ACLProcessorTest.cs @@ -1,1016 +1,1016 @@ -// using System; -// using System.Collections.Generic; -// using System.DirectoryServices; -// using System.Linq; -// using System.Security.AccessControl; -// using CommonLibTest.Facades; -// using Moq; -// using Newtonsoft.Json; -// using SharpHoundCommonLib; -// using SharpHoundCommonLib.Enums; -// using SharpHoundCommonLib.OutputTypes; -// using SharpHoundCommonLib.Processors; -// using Xunit; -// using Xunit.Abstractions; -// -// namespace CommonLibTest -// { -// public class ACLProcessorTest : IDisposable -// { -// private const string ProtectedUserNTSecurityDescriptor = -// "AQAEnIgEAAAAAAAAAAAAABQAAAAEAHQEGAAAAAUAPAAQAAAAAwAAAABCFkzAINARp2gAqgBuBSkUzChINxS8RZsHrW8BXl8oAQIAAAAAAAUgAAAAKgIAAAUAPAAQAAAAAwAAAABCFkzAINARp2gAqgBuBSm6epa/5g3QEaKFAKoAMEniAQIAAAAAAAUgAAAAKgIAAAUAPAAQAAAAAwAAABAgIF+ledARkCAAwE/C1M8UzChINxS8RZsHrW8BXl8oAQIAAAAAAAUgAAAAKgIAAAUAPAAQAAAAAwAAABAgIF+ledARkCAAwE/C1M+6epa/5g3QEaKFAKoAMEniAQIAAAAAAAUgAAAAKgIAAAUAPAAQAAAAAwAAAEDCCrypedARkCAAwE/C1M8UzChINxS8RZsHrW8BXl8oAQIAAAAAAAUgAAAAKgIAAAUAPAAQAAAAAwAAAEDCCrypedARkCAAwE/C1M+6epa/5g3QEaKFAKoAMEniAQIAAAAAAAUgAAAAKgIAAAUAPAAQAAAAAwAAAEIvulmiedARkCAAwE/C088UzChINxS8RZsHrW8BXl8oAQIAAAAAAAUgAAAAKgIAAAUAPAAQAAAAAwAAAEIvulmiedARkCAAwE/C08+6epa/5g3QEaKFAKoAMEniAQIAAAAAAAUgAAAAKgIAAAUAPAAQAAAAAwAAAPiIcAPhCtIRtCIAoMlo+TkUzChINxS8RZsHrW8BXl8oAQIAAAAAAAUgAAAAKgIAAAUAPAAQAAAAAwAAAPiIcAPhCtIRtCIAoMlo+Tm6epa/5g3QEaKFAKoAMEniAQIAAAAAAAUgAAAAKgIAAAUAOAAwAAAAAQAAAH96lr/mDdARooUAqgAwSeIBBQAAAAAABRUAAAAgT5C6f0aEpXZIFpAFAgAABQAsABAAAAABAAAAHbGpRq5gWkC36P+KWNRW0gECAAAAAAAFIAAAADACAAAFACwAMAAAAAEAAAAcmrZtIpTREa69AAD4A2fBAQIAAAAAAAUgAAAAMQIAAAUALAAwAAAAAQAAAGK8BVjJvShEpeKFag9MGF4BAgAAAAAABSAAAAAxAgAABQAsAJQAAgACAAAAFMwoSDcUvEWbB61vAV5fKAECAAAAAAAFIAAAACoCAAAFACwAlAACAAIAAAC6epa/5g3QEaKFAKoAMEniAQIAAAAAAAUgAAAAKgIAAAUAKAAAAQAAAQAAAFMacqsvHtARmBkAqgBAUpsBAQAAAAAAAQAAAAAFACgAAAEAAAEAAABTGnKrLx7QEZgZAKoAQFKbAQEAAAAAAAUKAAAABQIoADABAAABAAAA3kfmkW/ZcEuVV9Y/9PPM2AEBAAAAAAAFCgAAAAAAJAC/AQ4AAQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQAAIAAAAAJAC/AQ4AAQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQBwIAAAAAGAC/AQ8AAQIAAAAAAAUgAAAAIAIAAAAAFACUAAIAAQEAAAAAAAULAAAAAAAUAP8BDwABAQAAAAAABRIAAAABBQAAAAAABRUAAAAgT5C6f0aEpXZIFpAAAgAA"; -// -// private const string UnProtectedUserNtSecurityDescriptor = -// "AQAEjJgGAAAAAAAAAAAAABQAAAAEAIQGJwAAAAUAOAAQAAAAAQAAAABCFkzAINARp2gAqgBuBSkBBQAAAAAABRUAAAAgT5C6f0aEpXZIFpApAgAABQA4ABAAAAABAAAAECAgX6V50BGQIADAT8LUzwEFAAAAAAAFFQAAACBPkLp/RoSldkgWkCkCAAAFADgAEAAAAAEAAABAwgq8qXnQEZAgAMBPwtTPAQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQKQIAAAUAOAAQAAAAAQAAAPiIcAPhCtIRtCIAoMlo+TkBBQAAAAAABRUAAAAgT5C6f0aEpXZIFpApAgAABQA4ADAAAAABAAAAf3qWv+YN0BGihQCqADBJ4gEFAAAAAAAFFQAAACBPkLp/RoSldkgWkAUCAAAFACwAEAAAAAEAAAAdsalGrmBaQLfo/4pY1FbSAQIAAAAAAAUgAAAAMAIAAAUALAAwAAAAAQAAAByatm0ilNERrr0AAPgDZ8EBAgAAAAAABSAAAAAxAgAABQAsADAAAAABAAAAYrwFWMm9KESl4oVqD0wYXgECAAAAAAAFIAAAADECAAAFACgAAAEAAAEAAABTGnKrLx7QEZgZAKoAQFKbAQEAAAAAAAEAAAAABQAoAAABAAABAAAAUxpyqy8e0BGYGQCqAEBSmwEBAAAAAAAFCgAAAAUAKAAAAQAAAQAAAFQacqsvHtARmBkAqgBAUpsBAQAAAAAABQoAAAAFACgAAAEAAAEAAABWGnKrLx7QEZgZAKoAQFKbAQEAAAAAAAUKAAAABQAoABAAAAABAAAAQi+6WaJ50BGQIADAT8LTzwEBAAAAAAAFCwAAAAUAKAAQAAAAAQAAAFQBjeT4vNERhwIAwE+5YFABAQAAAAAABQsAAAAFACgAEAAAAAEAAACGuLV3SpTREa69AAD4A2fBAQEAAAAAAAULAAAABQAoABAAAAABAAAAs5VX5FWU0RGuvQAA+ANnwQEBAAAAAAAFCwAAAAUAKAAwAAAAAQAAAIa4tXdKlNERrr0AAPgDZ8EBAQAAAAAABQoAAAAFACgAMAAAAAEAAACylVfkVZTREa69AAD4A2fBAQEAAAAAAAUKAAAABQAoADAAAAABAAAAs5VX5FWU0RGuvQAA+ANnwQEBAAAAAAAFCgAAAAAAJAD/AQ8AAQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQAAIAAAAAGAD/AQ8AAQIAAAAAAAUgAAAAJAIAAAAAFAAAAAIAAQEAAAAAAAULAAAAAAAUAJQAAgABAQAAAAAABQoAAAAAABQA/wEPAAEBAAAAAAAFEgAAAAUSOAAAAQAAAQAAAKr2MREHnNER958AwE/C3NIBBQAAAAAABRUAAAAgT5C6f0aEpXZIFpBKCAAABRI4AAABAAABAAAArfYxEQec0RH3nwDAT8Lc0gEFAAAAAAAFFQAAACBPkLp/RoSldkgWkD8IAAAFEjgAAAEAAAEAAACt9jERB5zREfefAMBPwtzSAQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQSggAAAUaOAAQAAAAAwAAAG2exrfHLNIRhU4AoMmD9giGepa/5g3QEaKFAKoAMEniAQEAAAAAAAUJAAAABRo4ABAAAAADAAAAbZ7Gt8cs0hGFTgCgyYP2CJx6lr/mDdARooUAqgAwSeIBAQAAAAAABQkAAAAFEjgAEAAAAAMAAABtnsa3xyzSEYVOAKDJg/YIunqWv+YN0BGihQCqADBJ4gEBAAAAAAAFCQAAAAUaOAAgAAAAAwAAAJN7G+pIXtVGvGxN9P2nijWGepa/5g3QEaKFAKoAMEniAQEAAAAAAAUKAAAABRosAJQAAgACAAAAFMwoSDcUvEWbB61vAV5fKAECAAAAAAAFIAAAACoCAAAFGiwAlAACAAIAAACcepa/5g3QEaKFAKoAMEniAQIAAAAAAAUgAAAAKgIAAAUSLACUAAIAAgAAALp6lr/mDdARooUAqgAwSeIBAgAAAAAABSAAAAAqAgAABRIoADAAAAABAAAA5cN4P5r3vUaguJ0YEW3ceQEBAAAAAAAFCgAAAAUSKAAwAQAAAQAAAN5H5pFv2XBLlVfWP/TzzNgBAQAAAAAABQoAAAAAEiQA/wEPAAEFAAAAAAAFFQAAACBPkLp/RoSldkgWkAcCAAAAEhgABAAAAAECAAAAAAAFIAAAACoCAAAAEhgAvQEPAAECAAAAAAAFIAAAACACAAABBQAAAAAABRUAAAAgT5C6f0aEpXZIFpAAAgAA"; -// -// private const string GMSAProperty = -// "AQAEgEAAAAAAAAAAAAAAABQAAAAEACwAAQAAAAAAJAD/AQ8AAQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQ9AEAAAECAAAAAAAFIAAAACACAAA\u003d"; -// -// private const string AddMemberSecurityDescriptor = -// "AQAEjGADAAAAAAAAAAAAABQAAAAEAEwDFQAAAAUAOAAIAAAAAQAAAMB5lr/mDdARooUAqgAwSeIBBQAAAAAABRUAAAAgT5C6f0aEpXZIFpAuCgAABQA4ACAAAAABAAAAwHmWv+YN0BGihQCqADBJ4gEFAAAAAAAFFQAAACBPkLp/RoSldkgWkEcIAAAFACwAEAAAAAEAAAAdsalGrmBaQLfo/4pY1FbSAQIAAAAAAAUgAAAAMAIAAAUAKAAAAQAAAQAAAFUacqsvHtARmBkAqgBAUpsBAQAAAAAABQsAAAAAACQA/wEPAAEFAAAAAAAFFQAAACBPkLp/RoSldkgWkAACAAAAABgA/wEPAAECAAAAAAAFIAAAACQCAAAAABQAlAACAAEBAAAAAAAFCgAAAAAAFACUAAIAAQEAAAAAAAULAAAAAAAUAP8BDwABAQAAAAAABRIAAAAFGjgAEAAAAAMAAABtnsa3xyzSEYVOAKDJg/YIhnqWv+YN0BGihQCqADBJ4gEBAAAAAAAFCQAAAAUSOAAQAAAAAwAAAG2exrfHLNIRhU4AoMmD9gicepa/5g3QEaKFAKoAMEniAQEAAAAAAAUJAAAABRo4ABAAAAADAAAAbZ7Gt8cs0hGFTgCgyYP2CLp6lr/mDdARooUAqgAwSeIBAQAAAAAABQkAAAAFGjgAIAAAAAMAAACTexvqSF7VRrxsTfT9p4o1hnqWv+YN0BGihQCqADBJ4gEBAAAAAAAFCgAAAAUaLACUAAIAAgAAABTMKEg3FLxFmwetbwFeXygBAgAAAAAABSAAAAAqAgAABRIsAJQAAgACAAAAnHqWv+YN0BGihQCqADBJ4gECAAAAAAAFIAAAACoCAAAFGiwAlAACAAIAAAC6epa/5g3QEaKFAKoAMEniAQIAAAAAAAUgAAAAKgIAAAUSKAAwAAAAAQAAAOXDeD+a971GoLidGBFt3HkBAQAAAAAABQoAAAAFEigAMAEAAAEAAADeR+aRb9lwS5VX1j/088zYAQEAAAAAAAUKAAAAABIkAP8BDwABBQAAAAAABRUAAAAgT5C6f0aEpXZIFpAHAgAAABIYAAQAAAABAgAAAAAABSAAAAAqAgAAABIYAL0BDwABAgAAAAAABSAAAAAgAgAAAQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQAAIAAA=="; -// -// private readonly ACLProcessor _baseProcessor; -// -// private readonly string _testDomainName; -// private readonly ITestOutputHelper _testOutputHelper; -// -// public ACLProcessorTest(ITestOutputHelper testOutputHelper) -// { -// _testOutputHelper = testOutputHelper; -// _testDomainName = "TESTLAB.LOCAL"; -// _baseProcessor = new ACLProcessor(new LDAPUtils()); -// } -// -// public void Dispose() -// { -// } -// -// [Fact] -// public void SanityCheck() -// { -// Assert.True(true); -// } -// -// [Fact] -// public void ACLProcessor_IsACLProtected_NullNTSD_ReturnsFalse() -// { -// var processor = new ACLProcessor(new MockLDAPUtils(), true); -// var result = processor.IsACLProtected((byte[])null); -// Assert.False(result); -// } -// -// [WindowsOnlyFact] -// public void ACLProcessor_TestKnownDataAddMember() -// { -// var mockLdapUtils = new MockLDAPUtils(); -// var mockUtils = new Mock(); -// mockUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) -// .Returns((string a, string b) => mockLdapUtils.ResolveIDAndType(a, b)); -// var sd = new ActiveDirectorySecurityDescriptor(new ActiveDirectorySecurity()); -// mockUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(sd); -// -// var processor = new ACLProcessor(mockUtils.Object, true); -// var bytes = Helpers.B64ToBytes(AddMemberSecurityDescriptor); -// var result = processor.ProcessACL(bytes, "TESTLAB.LOCAL", Label.Group, false); -// -// _testOutputHelper.WriteLine(JsonConvert.SerializeObject(result)); -// -// Assert.Contains(result, -// x => x.RightName == EdgeNames.AddSelf && -// x.PrincipalSID == "S-1-5-21-3130019616-2776909439-2417379446-2606"); -// Assert.Contains(result, -// x => x.RightName == EdgeNames.AddMember && -// x.PrincipalSID == "S-1-5-21-3130019616-2776909439-2417379446-2119"); -// } -// -// [Fact] -// public void ACLProcessor_IsACLProtected_ReturnsTrue() -// { -// var mockLDAPUtils = new Mock(); -// var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); -// mockSecurityDescriptor.Setup(x => x.AreAccessRulesProtected()).Returns(true); -// mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); -// -// var processor = new ACLProcessor(mockLDAPUtils.Object, true); -// var bytes = Helpers.B64ToBytes(ProtectedUserNTSecurityDescriptor); -// var result = processor.IsACLProtected(bytes); -// Assert.True(result); -// } -// -// [Fact] -// public void ACLProcessor_IsACLProtected_ReturnsFalse() -// { -// var mockLDAPUtils = new Mock(); -// var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); -// mockSecurityDescriptor.Setup(m => m.AreAccessRulesProtected()).Returns(false); -// mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); -// -// var processor = new ACLProcessor(mockLDAPUtils.Object, true); -// var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); -// var result = processor.IsACLProtected(bytes); -// Assert.False(result); -// } -// -// [Fact] -// public void ACLProcessor_ProcessGMSAReaders_NullNTSD_ReturnsNothing() -// { -// var test = _baseProcessor.ProcessGMSAReaders(null, ""); -// Assert.Empty(test); -// } -// -// [Fact] -// public void ACLProcess_ProcessGMSAReaders_YieldsCorrectAce() -// { -// var expectedRightName = "ReadGMSAPassword"; -// var expectedSID = "S-1-5-21-3130019616-2776909439-2417379446-500"; -// var expectedPrincipalType = Label.User; -// var expectedInheritance = false; -// -// var mockLDAPUtils = new Mock(); -// var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); -// var mockRule = new Mock(MockBehavior.Loose, null); -// -// mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); -// mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); -// mockRule.Setup(x => x.IdentityReference()).Returns(expectedSID); -// -// var collection = new List(); -// collection.Add(mockRule.Object); -// -// mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) -// .Returns(collection); -// mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); -// mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) -// .Returns(new TypedPrincipal(expectedSID, expectedPrincipalType)); -// -// var processor = new ACLProcessor(mockLDAPUtils.Object, true); -// var bytes = Helpers.B64ToBytes(GMSAProperty); -// var result = processor.ProcessGMSAReaders(bytes, _testDomainName).ToArray(); -// -// Assert.Single(result); -// var actual = result.First(); -// _testOutputHelper.WriteLine(actual.ToString()); -// Assert.Equal(expectedRightName, actual.RightName); -// Assert.Equal(expectedSID, actual.PrincipalSID); -// Assert.Equal(expectedPrincipalType, actual.PrincipalType); -// Assert.Equal(expectedInheritance, actual.IsInherited); -// } -// -// [Fact] -// public void ACLProcessor_ProcessGMSAReaders_Null_ACE() -// { -// var mockLDAPUtils = new Mock(); -// var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); -// var collection = new List(); -// collection.Add(null); -// -// mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) -// .Returns(collection); -// mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); -// -// var processor = new ACLProcessor(mockLDAPUtils.Object, true); -// var bytes = Helpers.B64ToBytes(GMSAProperty); -// var result = processor.ProcessGMSAReaders(bytes, _testDomainName).ToArray(); -// -// Assert.Empty(result); -// } -// -// [Fact] -// public void ACLProcessor_ProcessGMSAReaders_Deny_ACE() -// { -// var mockLDAPUtils = new Mock(); -// var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); -// var mockRule = new Mock(MockBehavior.Loose, null); -// var collection = new List(); -// -// mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Deny); -// collection.Add(mockRule.Object); -// -// mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) -// .Returns(collection); -// mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); -// -// var processor = new ACLProcessor(mockLDAPUtils.Object, true); -// var bytes = Helpers.B64ToBytes(GMSAProperty); -// var result = processor.ProcessGMSAReaders(bytes, _testDomainName).ToArray(); -// -// Assert.Empty(result); -// } -// -// [Fact] -// public void ACLProcessor_ProcessGMSAReaders_Null_PrincipalID() -// { -// var mockLDAPUtils = new Mock(); -// var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); -// var mockRule = new Mock(MockBehavior.Loose, null); -// var collection = new List(); -// -// mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); -// mockRule.Setup(x => x.IdentityReference()).Returns((string)null); -// collection.Add(mockRule.Object); -// -// mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) -// .Returns(collection); -// mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); -// -// var processor = new ACLProcessor(mockLDAPUtils.Object, true); -// var bytes = Helpers.B64ToBytes(GMSAProperty); -// var result = processor.ProcessGMSAReaders(bytes, _testDomainName).ToArray(); -// -// Assert.Empty(result); -// } -// -// [Fact] -// public void ACLProcessor_ProcessACL_Null_NTSecurityDescriptor() -// { -// var processor = new ACLProcessor(new MockLDAPUtils(), true); -// var result = processor.ProcessACL(null, _testDomainName, Label.User, false).ToArray(); -// -// Assert.Empty(result); -// } -// -// [Fact] -// public void ACLProcessor_ProcessACL_Yields_Owns_ACE() -// { -// var expectedSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; -// var expectedPrincipalType = Label.Group; -// -// var mockLDAPUtils = new Mock(); -// var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); -// var collection = new List(); -// -// mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) -// .Returns(collection); -// mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns(expectedSID); -// mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); -// mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) -// .Returns(new TypedPrincipal(expectedSID, expectedPrincipalType)); -// -// var processor = new ACLProcessor(mockLDAPUtils.Object, true); -// var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); -// var result = processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArray(); -// -// Assert.Single(result); -// var actual = result.First(); -// Assert.Equal(actual.PrincipalSID, expectedSID); -// Assert.Equal(actual.PrincipalType, expectedPrincipalType); -// Assert.Equal(actual.IsInherited, false); -// Assert.Equal(actual.RightName, EdgeNames.Owns); -// } -// -// [Fact] -// public void ACLProcessor_ProcessACL_Null_SID() -// { -// var mockLDAPUtils = new Mock(); -// var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); -// var collection = new List(); -// -// mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) -// .Returns(collection); -// mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); -// mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); -// -// var processor = new ACLProcessor(mockLDAPUtils.Object, true); -// var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); -// var result = processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArray(); -// -// Assert.Empty(result); -// } -// -// [Fact] -// public void ACLProcessor_ProcessACL_Null_ACE() -// { -// var mockLDAPUtils = new Mock(); -// var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); -// var collection = new List(); -// collection.Add(null); -// -// mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) -// .Returns(collection); -// mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); -// mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); -// -// var processor = new ACLProcessor(mockLDAPUtils.Object, true); -// var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); -// var result = processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArray(); -// -// Assert.Empty(result); -// } -// -// [Fact] -// public void ACLProcessor_ProcessACL_Deny_ACE() -// { -// var mockLDAPUtils = new Mock(); -// var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); -// var mockRule = new Mock(MockBehavior.Loose, null); -// var collection = new List(); -// mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Deny); -// collection.Add(mockRule.Object); -// -// mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) -// .Returns(collection); -// mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); -// mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); -// -// var processor = new ACLProcessor(mockLDAPUtils.Object, true); -// var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); -// var result = processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArray(); -// -// Assert.Empty(result); -// } -// -// [Fact] -// public void ACLProcessor_ProcessACL_Unmatched_Inheritance_ACE() -// { -// var mockLDAPUtils = new Mock(); -// var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); -// var mockRule = new Mock(MockBehavior.Loose, null); -// var collection = new List(); -// mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); -// mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(false); -// collection.Add(mockRule.Object); -// -// mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) -// .Returns(collection); -// mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); -// mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); -// -// var processor = new ACLProcessor(mockLDAPUtils.Object, true); -// var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); -// var result = processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArray(); -// -// Assert.Empty(result); -// } -// -// [Fact] -// public void ACLProcessor_ProcessACL_Null_SID_ACE() -// { -// var mockLDAPUtils = new Mock(); -// var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); -// var mockRule = new Mock(MockBehavior.Loose, null); -// var collection = new List(); -// mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); -// mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); -// mockRule.Setup(x => x.IdentityReference()).Returns((string)null); -// collection.Add(mockRule.Object); -// -// mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) -// .Returns(collection); -// mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); -// mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); -// -// var processor = new ACLProcessor(mockLDAPUtils.Object, true); -// var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); -// var result = processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArray(); -// -// Assert.Empty(result); -// } -// -// [Fact] -// public void ACLProcessor_ProcessACL_GenericAll_Unmatched_Guid() -// { -// var expectedPrincipalType = Label.Group; -// var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; -// var unmatchedGuid = new Guid("583991c8-629d-4a07-8a70-74d19d22ac9c"); -// -// var mockLDAPUtils = new Mock(); -// var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); -// var mockRule = new Mock(MockBehavior.Loose, null); -// var collection = new List(); -// mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); -// mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); -// mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); -// mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.GenericAll); -// mockRule.Setup(x => x.ObjectType()).Returns(unmatchedGuid); -// collection.Add(mockRule.Object); -// -// mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) -// .Returns(collection); -// mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); -// mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); -// mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) -// .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); -// -// var processor = new ACLProcessor(mockLDAPUtils.Object, true); -// var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); -// var result = processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArray(); -// -// Assert.Empty(result); -// } -// -// [Fact] -// public void ACLProcessor_ProcessACL_GenericAll() -// { -// var expectedPrincipalType = Label.Group; -// var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; -// -// var mockLDAPUtils = new Mock(); -// var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); -// var mockRule = new Mock(MockBehavior.Loose, null); -// var collection = new List(); -// mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); -// mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); -// mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); -// mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.GenericAll); -// mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); -// collection.Add(mockRule.Object); -// -// mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) -// .Returns(collection); -// mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); -// mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); -// mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) -// .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); -// -// var processor = new ACLProcessor(mockLDAPUtils.Object, true); -// var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); -// var result = processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArray(); -// -// Assert.Single(result); -// var actual = result.First(); -// Assert.Equal(actual.PrincipalType, expectedPrincipalType); -// Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); -// Assert.Equal(actual.IsInherited, false); -// Assert.Equal(actual.RightName, EdgeNames.GenericAll); -// } -// -// [Fact] -// public void ACLProcessor_ProcessACL_WriteDacl() -// { -// var expectedPrincipalType = Label.Group; -// var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; -// var expectedRightName = ActiveDirectoryRights.WriteDacl; -// -// var mockLDAPUtils = new Mock(); -// var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); -// var mockRule = new Mock(MockBehavior.Loose, null); -// var collection = new List(); -// mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); -// mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); -// mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); -// mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(expectedRightName); -// mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); -// collection.Add(mockRule.Object); -// -// mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) -// .Returns(collection); -// mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); -// mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); -// mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) -// .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); -// -// var processor = new ACLProcessor(mockLDAPUtils.Object, true); -// var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); -// var result = processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArray(); -// -// Assert.Single(result); -// var actual = result.First(); -// Assert.Equal(actual.PrincipalType, expectedPrincipalType); -// Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); -// Assert.Equal(actual.IsInherited, false); -// Assert.Equal(actual.RightName, expectedRightName.ToString()); -// } -// -// [Fact] -// public void ACLProcessor_ProcessACL_WriteOwner() -// { -// var expectedPrincipalType = Label.Group; -// var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; -// var expectedRightName = ActiveDirectoryRights.WriteOwner; -// -// var mockLDAPUtils = new Mock(); -// var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); -// var mockRule = new Mock(MockBehavior.Loose, null); -// var collection = new List(); -// mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); -// mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); -// mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); -// mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(expectedRightName); -// mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); -// collection.Add(mockRule.Object); -// -// mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) -// .Returns(collection); -// mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); -// mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); -// mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) -// .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); -// -// var processor = new ACLProcessor(mockLDAPUtils.Object, true); -// var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); -// var result = processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArray(); -// -// Assert.Single(result); -// var actual = result.First(); -// Assert.Equal(actual.PrincipalType, expectedPrincipalType); -// Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); -// Assert.Equal(actual.IsInherited, false); -// Assert.Equal(actual.RightName, expectedRightName.ToString()); -// } -// -// [Fact] -// public void ACLProcessor_ProcessACL_Self() -// { -// var expectedPrincipalType = Label.Group; -// var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; -// var expectedRightName = EdgeNames.AddSelf; -// -// var mockLDAPUtils = new Mock(); -// var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); -// var mockRule = new Mock(MockBehavior.Loose, null); -// var collection = new List(); -// mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); -// mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); -// mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); -// mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.Self); -// mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.WriteMember)); -// collection.Add(mockRule.Object); -// -// mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) -// .Returns(collection); -// mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); -// mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); -// mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) -// .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); -// -// var processor = new ACLProcessor(mockLDAPUtils.Object, true); -// var bytes = Helpers.B64ToBytes(AddMemberSecurityDescriptor); -// var result = processor.ProcessACL(bytes, _testDomainName, Label.Group, false).ToArray(); -// -// Assert.Single(result); -// var actual = result.First(); -// Assert.Equal(actual.PrincipalType, expectedPrincipalType); -// Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); -// Assert.Equal(actual.IsInherited, false); -// Assert.Equal(actual.RightName, expectedRightName); -// } -// -// [Fact] -// public void ACLProcessor_ProcessACL_ExtendedRight_Domain_Unmatched() -// { -// var expectedPrincipalType = Label.Group; -// var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; -// -// var mockLDAPUtils = new Mock(); -// var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); -// var mockRule = new Mock(MockBehavior.Loose, null); -// var collection = new List(); -// mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); -// mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); -// mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); -// mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); -// mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.WriteMember)); -// collection.Add(mockRule.Object); -// -// mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) -// .Returns(collection); -// mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); -// mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); -// mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) -// .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); -// -// var processor = new ACLProcessor(mockLDAPUtils.Object, true); -// var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); -// var result = processor.ProcessACL(bytes, _testDomainName, Label.Domain, false).ToArray(); -// -// Assert.Empty(result); -// } -// -// [Fact] -// public void ACLProcessor_ProcessACL_ExtendedRight_Domain_DSReplicationGetChanges() -// { -// var expectedPrincipalType = Label.Group; -// var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; -// var expectedRightName = EdgeNames.GetChanges; -// -// var mockLDAPUtils = new Mock(); -// var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); -// var mockRule = new Mock(MockBehavior.Loose, null); -// var collection = new List(); -// mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); -// mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); -// mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); -// mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); -// mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.DSReplicationGetChanges)); -// collection.Add(mockRule.Object); -// -// mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) -// .Returns(collection); -// mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); -// mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); -// mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) -// .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); -// -// var processor = new ACLProcessor(mockLDAPUtils.Object, true); -// var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); -// var result = processor.ProcessACL(bytes, _testDomainName, Label.Domain, false).ToArray(); -// -// Assert.Single(result); -// var actual = result.First(); -// Assert.Equal(actual.PrincipalType, expectedPrincipalType); -// Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); -// Assert.Equal(actual.IsInherited, false); -// Assert.Equal(actual.RightName, expectedRightName); -// } -// -// [Fact] -// public void ACLProcessor_ProcessACL_ExtendedRight_Domain_All() -// { -// var expectedPrincipalType = Label.Group; -// var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; -// var expectedRightName = EdgeNames.AllExtendedRights; -// -// var mockLDAPUtils = new Mock(); -// var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); -// var mockRule = new Mock(MockBehavior.Loose, null); -// var collection = new List(); -// mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); -// mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); -// mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); -// mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); -// mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); -// collection.Add(mockRule.Object); -// -// mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) -// .Returns(collection); -// mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); -// mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); -// mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) -// .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); -// -// var processor = new ACLProcessor(mockLDAPUtils.Object, true); -// var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); -// var result = processor.ProcessACL(bytes, _testDomainName, Label.Domain, false).ToArray(); -// -// Assert.Single(result); -// var actual = result.First(); -// Assert.Equal(actual.PrincipalType, expectedPrincipalType); -// Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); -// Assert.Equal(actual.IsInherited, false); -// Assert.Equal(actual.RightName, expectedRightName); -// } -// -// [Fact] -// public void ACLProcessor_ProcessACL_ExtendedRight_Domain_DSReplicationGetChangesAll() -// { -// var expectedPrincipalType = Label.Group; -// var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; -// var expectedRightName = EdgeNames.GetChangesAll; -// -// var mockLDAPUtils = new Mock(); -// var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); -// var mockRule = new Mock(MockBehavior.Loose, null); -// var collection = new List(); -// mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); -// mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); -// mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); -// mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); -// mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.DSReplicationGetChangesAll)); -// collection.Add(mockRule.Object); -// -// mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) -// .Returns(collection); -// mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); -// mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); -// mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) -// .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); -// -// var processor = new ACLProcessor(mockLDAPUtils.Object, true); -// var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); -// var result = processor.ProcessACL(bytes, _testDomainName, Label.Domain, false).ToArray(); -// -// Assert.Single(result); -// var actual = result.First(); -// Assert.Equal(actual.PrincipalType, expectedPrincipalType); -// Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); -// Assert.Equal(actual.IsInherited, false); -// Assert.Equal(actual.RightName, expectedRightName); -// } -// -// [Fact] -// public void ACLProcessor_ProcessACL_ExtendedRight_User_Unmatched() -// { -// var expectedPrincipalType = Label.Group; -// var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; -// var expectedRightName = EdgeNames.GetChangesAll; -// var unmatchedGuid = new Guid("583991c8-629d-4a07-8a70-74d19d22ac9c"); -// -// var mockLDAPUtils = new Mock(); -// var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); -// var mockRule = new Mock(MockBehavior.Loose, null); -// var collection = new List(); -// mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); -// mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); -// mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); -// mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); -// mockRule.Setup(x => x.ObjectType()).Returns(unmatchedGuid); -// collection.Add(mockRule.Object); -// -// mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) -// .Returns(collection); -// mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); -// mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); -// mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) -// .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); -// -// var processor = new ACLProcessor(mockLDAPUtils.Object, true); -// var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); -// var result = processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArray(); -// -// Assert.Empty(result); -// } -// -// [Fact] -// public void ACLProcessor_ProcessACL_ExtendedRight_User_UserForceChangePassword() -// { -// var expectedPrincipalType = Label.Group; -// var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; -// var expectedRightName = EdgeNames.ForceChangePassword; -// -// var mockLDAPUtils = new Mock(); -// var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); -// var mockRule = new Mock(MockBehavior.Loose, null); -// var collection = new List(); -// mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); -// mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); -// mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); -// mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); -// mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.UserForceChangePassword)); -// collection.Add(mockRule.Object); -// -// mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) -// .Returns(collection); -// mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); -// mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); -// mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) -// .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); -// -// var processor = new ACLProcessor(mockLDAPUtils.Object, true); -// var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); -// var result = processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArray(); -// -// Assert.Single(result); -// var actual = result.First(); -// Assert.Equal(actual.PrincipalType, expectedPrincipalType); -// Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); -// Assert.Equal(actual.IsInherited, false); -// Assert.Equal(actual.RightName, expectedRightName); -// } -// -// [Fact] -// public void ACLProcessor_ProcessACL_ExtendedRight_User_All() -// { -// var expectedPrincipalType = Label.Group; -// var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; -// var expectedRightName = EdgeNames.AllExtendedRights; -// -// var mockLDAPUtils = new Mock(); -// var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); -// var mockRule = new Mock(MockBehavior.Loose, null); -// var collection = new List(); -// mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); -// mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); -// mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); -// mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); -// mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); -// collection.Add(mockRule.Object); -// -// mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) -// .Returns(collection); -// mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); -// mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); -// mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) -// .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); -// -// var processor = new ACLProcessor(mockLDAPUtils.Object, true); -// var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); -// var result = processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArray(); -// -// Assert.Single(result); -// var actual = result.First(); -// Assert.Equal(actual.PrincipalType, expectedPrincipalType); -// Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); -// Assert.Equal(actual.IsInherited, false); -// Assert.Equal(actual.RightName, expectedRightName); -// } -// -// [Fact] -// public void ACLProcessor_ProcessACL_ExtendedRight_Computer_NoLAPS() -// { -// var expectedPrincipalType = Label.Group; -// var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; -// var expectedRightName = EdgeNames.AllExtendedRights; -// -// var mockLDAPUtils = new Mock(); -// var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); -// var mockRule = new Mock(MockBehavior.Loose, null); -// var collection = new List(); -// mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); -// mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); -// mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); -// mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); -// mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); -// collection.Add(mockRule.Object); -// -// mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) -// .Returns(collection); -// mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); -// mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); -// mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) -// .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); -// -// var processor = new ACLProcessor(mockLDAPUtils.Object, true); -// var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); -// var result = processor.ProcessACL(bytes, _testDomainName, Label.Computer, false).ToArray(); -// -// Assert.Empty(result); -// } -// -// [Fact] -// public void ACLProcessor_ProcessACL_ExtendedRight_Computer_All() -// { -// var expectedPrincipalType = Label.Group; -// var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; -// var expectedRightName = EdgeNames.AllExtendedRights; -// -// var mockLDAPUtils = new Mock(); -// var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); -// var mockRule = new Mock(MockBehavior.Loose, null); -// var collection = new List(); -// mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); -// mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); -// mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); -// mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); -// mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); -// collection.Add(mockRule.Object); -// -// mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) -// .Returns(collection); -// mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); -// mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); -// mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) -// .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); -// -// var processor = new ACLProcessor(mockLDAPUtils.Object, true); -// var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); -// var result = processor.ProcessACL(bytes, _testDomainName, Label.Computer, true).ToArray(); -// -// Assert.Single(result); -// var actual = result.First(); -// Assert.Equal(actual.PrincipalType, expectedPrincipalType); -// Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); -// Assert.Equal(actual.IsInherited, false); -// Assert.Equal(actual.RightName, expectedRightName); -// } -// -// [Fact(Skip = "Need to populate cache to reach this case")] -// public void ACLProcessor_ProcessACL_ExtendedRight_Computer_MappedGuid() -// { -// } -// -// [Fact] -// public void ACLProcessor_ProcessACL_GenericWrite_Unmatched() -// { -// var expectedPrincipalType = Label.Group; -// var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; -// var expectedRightName = EdgeNames.AllExtendedRights; -// -// var mockLDAPUtils = new Mock(); -// var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); -// var mockRule = new Mock(MockBehavior.Loose, null); -// var collection = new List(); -// mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); -// mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); -// mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); -// mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.GenericWrite); -// mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); -// collection.Add(mockRule.Object); -// -// mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) -// .Returns(collection); -// mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); -// mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); -// mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) -// .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); -// -// var processor = new ACLProcessor(mockLDAPUtils.Object, true); -// var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); -// var result = processor.ProcessACL(bytes, _testDomainName, Label.Container, true).ToArray(); -// -// Assert.Empty(result); -// } -// -// [Fact] -// public void ACLProcessor_ProcessACL_GenericWrite_User_All() -// { -// var expectedPrincipalType = Label.Group; -// var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; -// var expectedRightName = EdgeNames.GenericWrite; -// -// var mockLDAPUtils = new Mock(); -// var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); -// var mockRule = new Mock(MockBehavior.Loose, null); -// var collection = new List(); -// mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); -// mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); -// mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); -// mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.GenericWrite); -// mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); -// collection.Add(mockRule.Object); -// -// mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) -// .Returns(collection); -// mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); -// mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); -// mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) -// .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); -// -// var processor = new ACLProcessor(mockLDAPUtils.Object, true); -// var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); -// var result = processor.ProcessACL(bytes, _testDomainName, Label.User, true).ToArray(); -// -// Assert.Single(result); -// var actual = result.First(); -// Assert.Equal(actual.PrincipalType, expectedPrincipalType); -// Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); -// Assert.Equal(actual.IsInherited, false); -// Assert.Equal(actual.RightName, expectedRightName); -// } -// -// [Fact] -// public void ACLProcessor_ProcessACL_GenericWrite_User_WriteMember() -// { -// var expectedPrincipalType = Label.Group; -// var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; -// var expectedRightName = EdgeNames.AddMember; -// -// var mockLDAPUtils = new Mock(); -// var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); -// var mockRule = new Mock(MockBehavior.Loose, null); -// var collection = new List(); -// mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); -// mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); -// mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); -// mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.GenericWrite); -// mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.WriteMember)); -// collection.Add(mockRule.Object); -// -// mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) -// .Returns(collection); -// mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); -// mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); -// mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) -// .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); -// -// var processor = new ACLProcessor(mockLDAPUtils.Object, true); -// var bytes = Helpers.B64ToBytes(AddMemberSecurityDescriptor); -// var result = processor.ProcessACL(bytes, _testDomainName, Label.Group, true).ToArray(); -// -// _testOutputHelper.WriteLine(JsonConvert.SerializeObject(result)); -// -// Assert.Single(result); -// var actual = result.First(); -// Assert.Equal(actual.PrincipalType, expectedPrincipalType); -// Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); -// Assert.Equal(actual.IsInherited, false); -// Assert.Equal(actual.RightName, expectedRightName); -// } -// -// [Fact] -// public void ACLProcessor_ProcessACL_GenericWrite_Computer_WriteAllowedToAct() -// { -// var expectedPrincipalType = Label.Group; -// var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; -// var expectedRightName = EdgeNames.AddAllowedToAct; -// -// var mockLDAPUtils = new Mock(); -// var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); -// var mockRule = new Mock(MockBehavior.Loose, null); -// var collection = new List(); -// mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); -// mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); -// mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); -// mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.GenericWrite); -// mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.WriteAllowedToAct)); -// collection.Add(mockRule.Object); -// -// mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) -// .Returns(collection); -// mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); -// mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); -// mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) -// .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); -// -// var processor = new ACLProcessor(mockLDAPUtils.Object, true); -// var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); -// var result = processor.ProcessACL(bytes, _testDomainName, Label.Computer, true).ToArray(); -// -// Assert.Single(result); -// var actual = result.First(); -// Assert.Equal(actual.PrincipalType, expectedPrincipalType); -// Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); -// Assert.Equal(actual.IsInherited, false); -// Assert.Equal(actual.RightName, expectedRightName); -// } -// } -// } \ No newline at end of file +using System; +using System.Collections.Generic; +using System.DirectoryServices; +using System.Linq; +using System.Security.AccessControl; +using System.Threading.Tasks; +using CommonLibTest.Facades; +using Moq; +using Newtonsoft.Json; +using SharpHoundCommonLib; +using SharpHoundCommonLib.Enums; +using SharpHoundCommonLib.OutputTypes; +using SharpHoundCommonLib.Processors; +using Xunit; +using Xunit.Abstractions; + +namespace CommonLibTest +{ + public class ACLProcessorTest : IDisposable + { + private const string ProtectedUserNTSecurityDescriptor = + "AQAEnIgEAAAAAAAAAAAAABQAAAAEAHQEGAAAAAUAPAAQAAAAAwAAAABCFkzAINARp2gAqgBuBSkUzChINxS8RZsHrW8BXl8oAQIAAAAAAAUgAAAAKgIAAAUAPAAQAAAAAwAAAABCFkzAINARp2gAqgBuBSm6epa/5g3QEaKFAKoAMEniAQIAAAAAAAUgAAAAKgIAAAUAPAAQAAAAAwAAABAgIF+ledARkCAAwE/C1M8UzChINxS8RZsHrW8BXl8oAQIAAAAAAAUgAAAAKgIAAAUAPAAQAAAAAwAAABAgIF+ledARkCAAwE/C1M+6epa/5g3QEaKFAKoAMEniAQIAAAAAAAUgAAAAKgIAAAUAPAAQAAAAAwAAAEDCCrypedARkCAAwE/C1M8UzChINxS8RZsHrW8BXl8oAQIAAAAAAAUgAAAAKgIAAAUAPAAQAAAAAwAAAEDCCrypedARkCAAwE/C1M+6epa/5g3QEaKFAKoAMEniAQIAAAAAAAUgAAAAKgIAAAUAPAAQAAAAAwAAAEIvulmiedARkCAAwE/C088UzChINxS8RZsHrW8BXl8oAQIAAAAAAAUgAAAAKgIAAAUAPAAQAAAAAwAAAEIvulmiedARkCAAwE/C08+6epa/5g3QEaKFAKoAMEniAQIAAAAAAAUgAAAAKgIAAAUAPAAQAAAAAwAAAPiIcAPhCtIRtCIAoMlo+TkUzChINxS8RZsHrW8BXl8oAQIAAAAAAAUgAAAAKgIAAAUAPAAQAAAAAwAAAPiIcAPhCtIRtCIAoMlo+Tm6epa/5g3QEaKFAKoAMEniAQIAAAAAAAUgAAAAKgIAAAUAOAAwAAAAAQAAAH96lr/mDdARooUAqgAwSeIBBQAAAAAABRUAAAAgT5C6f0aEpXZIFpAFAgAABQAsABAAAAABAAAAHbGpRq5gWkC36P+KWNRW0gECAAAAAAAFIAAAADACAAAFACwAMAAAAAEAAAAcmrZtIpTREa69AAD4A2fBAQIAAAAAAAUgAAAAMQIAAAUALAAwAAAAAQAAAGK8BVjJvShEpeKFag9MGF4BAgAAAAAABSAAAAAxAgAABQAsAJQAAgACAAAAFMwoSDcUvEWbB61vAV5fKAECAAAAAAAFIAAAACoCAAAFACwAlAACAAIAAAC6epa/5g3QEaKFAKoAMEniAQIAAAAAAAUgAAAAKgIAAAUAKAAAAQAAAQAAAFMacqsvHtARmBkAqgBAUpsBAQAAAAAAAQAAAAAFACgAAAEAAAEAAABTGnKrLx7QEZgZAKoAQFKbAQEAAAAAAAUKAAAABQIoADABAAABAAAA3kfmkW/ZcEuVV9Y/9PPM2AEBAAAAAAAFCgAAAAAAJAC/AQ4AAQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQAAIAAAAAJAC/AQ4AAQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQBwIAAAAAGAC/AQ8AAQIAAAAAAAUgAAAAIAIAAAAAFACUAAIAAQEAAAAAAAULAAAAAAAUAP8BDwABAQAAAAAABRIAAAABBQAAAAAABRUAAAAgT5C6f0aEpXZIFpAAAgAA"; + + private const string UnProtectedUserNtSecurityDescriptor = + "AQAEjJgGAAAAAAAAAAAAABQAAAAEAIQGJwAAAAUAOAAQAAAAAQAAAABCFkzAINARp2gAqgBuBSkBBQAAAAAABRUAAAAgT5C6f0aEpXZIFpApAgAABQA4ABAAAAABAAAAECAgX6V50BGQIADAT8LUzwEFAAAAAAAFFQAAACBPkLp/RoSldkgWkCkCAAAFADgAEAAAAAEAAABAwgq8qXnQEZAgAMBPwtTPAQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQKQIAAAUAOAAQAAAAAQAAAPiIcAPhCtIRtCIAoMlo+TkBBQAAAAAABRUAAAAgT5C6f0aEpXZIFpApAgAABQA4ADAAAAABAAAAf3qWv+YN0BGihQCqADBJ4gEFAAAAAAAFFQAAACBPkLp/RoSldkgWkAUCAAAFACwAEAAAAAEAAAAdsalGrmBaQLfo/4pY1FbSAQIAAAAAAAUgAAAAMAIAAAUALAAwAAAAAQAAAByatm0ilNERrr0AAPgDZ8EBAgAAAAAABSAAAAAxAgAABQAsADAAAAABAAAAYrwFWMm9KESl4oVqD0wYXgECAAAAAAAFIAAAADECAAAFACgAAAEAAAEAAABTGnKrLx7QEZgZAKoAQFKbAQEAAAAAAAEAAAAABQAoAAABAAABAAAAUxpyqy8e0BGYGQCqAEBSmwEBAAAAAAAFCgAAAAUAKAAAAQAAAQAAAFQacqsvHtARmBkAqgBAUpsBAQAAAAAABQoAAAAFACgAAAEAAAEAAABWGnKrLx7QEZgZAKoAQFKbAQEAAAAAAAUKAAAABQAoABAAAAABAAAAQi+6WaJ50BGQIADAT8LTzwEBAAAAAAAFCwAAAAUAKAAQAAAAAQAAAFQBjeT4vNERhwIAwE+5YFABAQAAAAAABQsAAAAFACgAEAAAAAEAAACGuLV3SpTREa69AAD4A2fBAQEAAAAAAAULAAAABQAoABAAAAABAAAAs5VX5FWU0RGuvQAA+ANnwQEBAAAAAAAFCwAAAAUAKAAwAAAAAQAAAIa4tXdKlNERrr0AAPgDZ8EBAQAAAAAABQoAAAAFACgAMAAAAAEAAACylVfkVZTREa69AAD4A2fBAQEAAAAAAAUKAAAABQAoADAAAAABAAAAs5VX5FWU0RGuvQAA+ANnwQEBAAAAAAAFCgAAAAAAJAD/AQ8AAQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQAAIAAAAAGAD/AQ8AAQIAAAAAAAUgAAAAJAIAAAAAFAAAAAIAAQEAAAAAAAULAAAAAAAUAJQAAgABAQAAAAAABQoAAAAAABQA/wEPAAEBAAAAAAAFEgAAAAUSOAAAAQAAAQAAAKr2MREHnNER958AwE/C3NIBBQAAAAAABRUAAAAgT5C6f0aEpXZIFpBKCAAABRI4AAABAAABAAAArfYxEQec0RH3nwDAT8Lc0gEFAAAAAAAFFQAAACBPkLp/RoSldkgWkD8IAAAFEjgAAAEAAAEAAACt9jERB5zREfefAMBPwtzSAQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQSggAAAUaOAAQAAAAAwAAAG2exrfHLNIRhU4AoMmD9giGepa/5g3QEaKFAKoAMEniAQEAAAAAAAUJAAAABRo4ABAAAAADAAAAbZ7Gt8cs0hGFTgCgyYP2CJx6lr/mDdARooUAqgAwSeIBAQAAAAAABQkAAAAFEjgAEAAAAAMAAABtnsa3xyzSEYVOAKDJg/YIunqWv+YN0BGihQCqADBJ4gEBAAAAAAAFCQAAAAUaOAAgAAAAAwAAAJN7G+pIXtVGvGxN9P2nijWGepa/5g3QEaKFAKoAMEniAQEAAAAAAAUKAAAABRosAJQAAgACAAAAFMwoSDcUvEWbB61vAV5fKAECAAAAAAAFIAAAACoCAAAFGiwAlAACAAIAAACcepa/5g3QEaKFAKoAMEniAQIAAAAAAAUgAAAAKgIAAAUSLACUAAIAAgAAALp6lr/mDdARooUAqgAwSeIBAgAAAAAABSAAAAAqAgAABRIoADAAAAABAAAA5cN4P5r3vUaguJ0YEW3ceQEBAAAAAAAFCgAAAAUSKAAwAQAAAQAAAN5H5pFv2XBLlVfWP/TzzNgBAQAAAAAABQoAAAAAEiQA/wEPAAEFAAAAAAAFFQAAACBPkLp/RoSldkgWkAcCAAAAEhgABAAAAAECAAAAAAAFIAAAACoCAAAAEhgAvQEPAAECAAAAAAAFIAAAACACAAABBQAAAAAABRUAAAAgT5C6f0aEpXZIFpAAAgAA"; + + private const string GMSAProperty = + "AQAEgEAAAAAAAAAAAAAAABQAAAAEACwAAQAAAAAAJAD/AQ8AAQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQ9AEAAAECAAAAAAAFIAAAACACAAA\u003d"; + + private const string AddMemberSecurityDescriptor = + "AQAEjGADAAAAAAAAAAAAABQAAAAEAEwDFQAAAAUAOAAIAAAAAQAAAMB5lr/mDdARooUAqgAwSeIBBQAAAAAABRUAAAAgT5C6f0aEpXZIFpAuCgAABQA4ACAAAAABAAAAwHmWv+YN0BGihQCqADBJ4gEFAAAAAAAFFQAAACBPkLp/RoSldkgWkEcIAAAFACwAEAAAAAEAAAAdsalGrmBaQLfo/4pY1FbSAQIAAAAAAAUgAAAAMAIAAAUAKAAAAQAAAQAAAFUacqsvHtARmBkAqgBAUpsBAQAAAAAABQsAAAAAACQA/wEPAAEFAAAAAAAFFQAAACBPkLp/RoSldkgWkAACAAAAABgA/wEPAAECAAAAAAAFIAAAACQCAAAAABQAlAACAAEBAAAAAAAFCgAAAAAAFACUAAIAAQEAAAAAAAULAAAAAAAUAP8BDwABAQAAAAAABRIAAAAFGjgAEAAAAAMAAABtnsa3xyzSEYVOAKDJg/YIhnqWv+YN0BGihQCqADBJ4gEBAAAAAAAFCQAAAAUSOAAQAAAAAwAAAG2exrfHLNIRhU4AoMmD9gicepa/5g3QEaKFAKoAMEniAQEAAAAAAAUJAAAABRo4ABAAAAADAAAAbZ7Gt8cs0hGFTgCgyYP2CLp6lr/mDdARooUAqgAwSeIBAQAAAAAABQkAAAAFGjgAIAAAAAMAAACTexvqSF7VRrxsTfT9p4o1hnqWv+YN0BGihQCqADBJ4gEBAAAAAAAFCgAAAAUaLACUAAIAAgAAABTMKEg3FLxFmwetbwFeXygBAgAAAAAABSAAAAAqAgAABRIsAJQAAgACAAAAnHqWv+YN0BGihQCqADBJ4gECAAAAAAAFIAAAACoCAAAFGiwAlAACAAIAAAC6epa/5g3QEaKFAKoAMEniAQIAAAAAAAUgAAAAKgIAAAUSKAAwAAAAAQAAAOXDeD+a971GoLidGBFt3HkBAQAAAAAABQoAAAAFEigAMAEAAAEAAADeR+aRb9lwS5VX1j/088zYAQEAAAAAAAUKAAAAABIkAP8BDwABBQAAAAAABRUAAAAgT5C6f0aEpXZIFpAHAgAAABIYAAQAAAABAgAAAAAABSAAAAAqAgAAABIYAL0BDwABAgAAAAAABSAAAAAgAgAAAQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQAAIAAA=="; + + private readonly ACLProcessor _baseProcessor; + + private readonly string _testDomainName; + private readonly ITestOutputHelper _testOutputHelper; + + public ACLProcessorTest(ITestOutputHelper testOutputHelper) + { + _testOutputHelper = testOutputHelper; + _testDomainName = "TESTLAB.LOCAL"; + _baseProcessor = new ACLProcessor(new LdapUtils()); + } + + public void Dispose() + { + } + + [Fact] + public void SanityCheck() + { + Assert.True(true); + } + + [Fact] + public void ACLProcessor_IsACLProtected_NullNTSD_ReturnsFalse() + { + var processor = new ACLProcessor(new MockLDAPUtils()); + var result = processor.IsACLProtected((byte[])null); + Assert.False(result); + } + + [WindowsOnlyFact] + public async Task ACLProcessor_TestKnownDataAddMember() + { + var mockLdapUtils = new MockLDAPUtils(); + var mockUtils = new Mock(); + mockUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + .Returns((string a, string b) => mockLdapUtils.ResolveIDAndType(a, b)); + var sd = new ActiveDirectorySecurityDescriptor(new ActiveDirectorySecurity()); + mockUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(sd); + + var processor = new ACLProcessor(mockUtils.Object); + var bytes = Helpers.B64ToBytes(AddMemberSecurityDescriptor); + var result = await processor.ProcessACL(bytes, "TESTLAB.LOCAL", Label.Group, false).ToArrayAsync(); + + _testOutputHelper.WriteLine(JsonConvert.SerializeObject(result)); + + Assert.Contains(result, + x => x.RightName == EdgeNames.AddSelf && + x.PrincipalSID == "S-1-5-21-3130019616-2776909439-2417379446-2606"); + Assert.Contains(result, + x => x.RightName == EdgeNames.AddMember && + x.PrincipalSID == "S-1-5-21-3130019616-2776909439-2417379446-2119"); + } + + [Fact] + public void ACLProcessor_IsACLProtected_ReturnsTrue() + { + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + mockSecurityDescriptor.Setup(x => x.AreAccessRulesProtected()).Returns(true); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Helpers.B64ToBytes(ProtectedUserNTSecurityDescriptor); + var result = processor.IsACLProtected(bytes); + Assert.True(result); + } + + [Fact] + public void ACLProcessor_IsACLProtected_ReturnsFalse() + { + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + mockSecurityDescriptor.Setup(m => m.AreAccessRulesProtected()).Returns(false); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + var result = processor.IsACLProtected(bytes); + Assert.False(result); + } + + [Fact] + public async Task ACLProcessor_ProcessGMSAReaders_NullNTSD_ReturnsNothing() + { + var test =await _baseProcessor.ProcessGMSAReaders(null, "").ToArrayAsync(); + Assert.Empty(test); + } + + [Fact] + public async Task ACLProcess_ProcessGMSAReaders_YieldsCorrectAce() + { + var expectedRightName = EdgeNames.ReadGMSAPassword; + var expectedSID = "S-1-5-21-3130019616-2776909439-2417379446-500"; + var expectedPrincipalType = Label.User; + var expectedInheritance = false; + + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var mockRule = new Mock(MockBehavior.Loose, null); + + mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); + mockRule.Setup(x => x.IdentityReference()).Returns(expectedSID); + + var collection = new List { mockRule.Object }; + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + .ReturnsAsync((true, new TypedPrincipal(expectedSID, expectedPrincipalType))); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Helpers.B64ToBytes(GMSAProperty); + var result = await processor.ProcessGMSAReaders(bytes, _testDomainName).ToArrayAsync(); + + Assert.Single(result); + var actual = result.First(); + _testOutputHelper.WriteLine(actual.ToString()); + Assert.Equal(expectedRightName, actual.RightName); + Assert.Equal(expectedSID, actual.PrincipalSID); + Assert.Equal(expectedPrincipalType, actual.PrincipalType); + Assert.Equal(expectedInheritance, actual.IsInherited); + } + + [Fact] + public async Task ACLProcessor_ProcessGMSAReaders_Null_ACE() + { + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var collection = new List(); + collection.Add(null); + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Helpers.B64ToBytes(GMSAProperty); + var result = await processor.ProcessGMSAReaders(bytes, _testDomainName).ToArrayAsync(); + + Assert.Empty(result); + } + + [Fact] + public async Task ACLProcessor_ProcessGMSAReaders_Deny_ACE() + { + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var mockRule = new Mock(MockBehavior.Loose, null); + var collection = new List(); + + mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Deny); + collection.Add(mockRule.Object); + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Helpers.B64ToBytes(GMSAProperty); + var result = await processor.ProcessGMSAReaders(bytes, _testDomainName).ToArrayAsync(); + + Assert.Empty(result); + } + + [Fact] + public async Task ACLProcessor_ProcessGMSAReaders_Null_PrincipalID() + { + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var mockRule = new Mock(MockBehavior.Loose, null); + var collection = new List(); + + mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + mockRule.Setup(x => x.IdentityReference()).Returns((string)null); + collection.Add(mockRule.Object); + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Helpers.B64ToBytes(GMSAProperty); + var result =await processor.ProcessGMSAReaders(bytes, _testDomainName).ToArrayAsync(); + + Assert.Empty(result); + } + + [Fact] + public async Task ACLProcessor_ProcessACL_Null_NTSecurityDescriptor() + { + var processor = new ACLProcessor(new MockLDAPUtils()); + var result = await processor.ProcessACL(null, _testDomainName, Label.User, false).ToArrayAsync(); + + Assert.Empty(result); + } + + [Fact] + public async Task ACLProcessor_ProcessACL_Yields_Owns_ACE() + { + var expectedSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + var expectedPrincipalType = Label.Group; + + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var collection = new List(); + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns(expectedSID); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + .ReturnsAsync((true, new TypedPrincipal(expectedSID, expectedPrincipalType))); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + var result = await processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArrayAsync(); + + Assert.Single(result); + var actual = result.First(); + Assert.Equal(actual.PrincipalSID, expectedSID); + Assert.Equal(actual.PrincipalType, expectedPrincipalType); + Assert.Equal(actual.IsInherited, false); + Assert.Equal(actual.RightName, EdgeNames.Owns); + } + + [Fact] + public async Task ACLProcessor_ProcessACL_Null_SID() + { + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var collection = new List(); + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + var result = await processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArrayAsync(); + + Assert.Empty(result); + } + // + // [Fact] + // public void ACLProcessor_ProcessACL_Null_ACE() + // { + // var mockLDAPUtils = new Mock(); + // var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + // var collection = new List(); + // collection.Add(null); + // + // mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + // .Returns(collection); + // mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + // mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + // + // var processor = new ACLProcessor(mockLDAPUtils.Object); + // var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + // var result = processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArray(); + // + // Assert.Empty(result); + // } + // + // [Fact] + // public void ACLProcessor_ProcessACL_Deny_ACE() + // { + // var mockLDAPUtils = new Mock(); + // var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + // var mockRule = new Mock(MockBehavior.Loose, null); + // var collection = new List(); + // mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Deny); + // collection.Add(mockRule.Object); + // + // mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + // .Returns(collection); + // mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + // mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + // + // var processor = new ACLProcessor(mockLDAPUtils.Object); + // var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + // var result = processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArray(); + // + // Assert.Empty(result); + // } + // + // [Fact] + // public void ACLProcessor_ProcessACL_Unmatched_Inheritance_ACE() + // { + // var mockLDAPUtils = new Mock(); + // var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + // var mockRule = new Mock(MockBehavior.Loose, null); + // var collection = new List(); + // mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + // mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(false); + // collection.Add(mockRule.Object); + // + // mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + // .Returns(collection); + // mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + // mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + // + // var processor = new ACLProcessor(mockLDAPUtils.Object); + // var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + // var result = processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArray(); + // + // Assert.Empty(result); + // } + // + // [Fact] + // public void ACLProcessor_ProcessACL_Null_SID_ACE() + // { + // var mockLDAPUtils = new Mock(); + // var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + // var mockRule = new Mock(MockBehavior.Loose, null); + // var collection = new List(); + // mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + // mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); + // mockRule.Setup(x => x.IdentityReference()).Returns((string)null); + // collection.Add(mockRule.Object); + // + // mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + // .Returns(collection); + // mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + // mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + // + // var processor = new ACLProcessor(mockLDAPUtils.Object); + // var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + // var result = processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArray(); + // + // Assert.Empty(result); + // } + // + // [Fact] + // public void ACLProcessor_ProcessACL_GenericAll_Unmatched_Guid() + // { + // var expectedPrincipalType = Label.Group; + // var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + // var unmatchedGuid = new Guid("583991c8-629d-4a07-8a70-74d19d22ac9c"); + // + // var mockLDAPUtils = new Mock(); + // var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + // var mockRule = new Mock(MockBehavior.Loose, null); + // var collection = new List(); + // mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + // mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); + // mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); + // mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.GenericAll); + // mockRule.Setup(x => x.ObjectType()).Returns(unmatchedGuid); + // collection.Add(mockRule.Object); + // + // mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + // .Returns(collection); + // mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + // mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + // mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + // .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); + // + // var processor = new ACLProcessor(mockLDAPUtils.Object); + // var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + // var result = processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArray(); + // + // Assert.Empty(result); + // } + // + // [Fact] + // public void ACLProcessor_ProcessACL_GenericAll() + // { + // var expectedPrincipalType = Label.Group; + // var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + // + // var mockLDAPUtils = new Mock(); + // var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + // var mockRule = new Mock(MockBehavior.Loose, null); + // var collection = new List(); + // mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + // mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); + // mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); + // mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.GenericAll); + // mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); + // collection.Add(mockRule.Object); + // + // mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + // .Returns(collection); + // mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + // mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + // mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + // .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); + // + // var processor = new ACLProcessor(mockLDAPUtils.Object); + // var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + // var result = processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArray(); + // + // Assert.Single(result); + // var actual = result.First(); + // Assert.Equal(actual.PrincipalType, expectedPrincipalType); + // Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); + // Assert.Equal(actual.IsInherited, false); + // Assert.Equal(actual.RightName, EdgeNames.GenericAll); + // } + // + // [Fact] + // public void ACLProcessor_ProcessACL_WriteDacl() + // { + // var expectedPrincipalType = Label.Group; + // var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + // var expectedRightName = ActiveDirectoryRights.WriteDacl; + // + // var mockLDAPUtils = new Mock(); + // var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + // var mockRule = new Mock(MockBehavior.Loose, null); + // var collection = new List(); + // mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + // mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); + // mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); + // mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(expectedRightName); + // mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); + // collection.Add(mockRule.Object); + // + // mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + // .Returns(collection); + // mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + // mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + // mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + // .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); + // + // var processor = new ACLProcessor(mockLDAPUtils.Object); + // var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + // var result = processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArray(); + // + // Assert.Single(result); + // var actual = result.First(); + // Assert.Equal(actual.PrincipalType, expectedPrincipalType); + // Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); + // Assert.Equal(actual.IsInherited, false); + // Assert.Equal(actual.RightName, expectedRightName.ToString()); + // } + // + // [Fact] + // public void ACLProcessor_ProcessACL_WriteOwner() + // { + // var expectedPrincipalType = Label.Group; + // var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + // var expectedRightName = ActiveDirectoryRights.WriteOwner; + // + // var mockLDAPUtils = new Mock(); + // var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + // var mockRule = new Mock(MockBehavior.Loose, null); + // var collection = new List(); + // mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + // mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); + // mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); + // mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(expectedRightName); + // mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); + // collection.Add(mockRule.Object); + // + // mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + // .Returns(collection); + // mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + // mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + // mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + // .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); + // + // var processor = new ACLProcessor(mockLDAPUtils.Object); + // var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + // var result = processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArray(); + // + // Assert.Single(result); + // var actual = result.First(); + // Assert.Equal(actual.PrincipalType, expectedPrincipalType); + // Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); + // Assert.Equal(actual.IsInherited, false); + // Assert.Equal(actual.RightName, expectedRightName.ToString()); + // } + // + // [Fact] + // public void ACLProcessor_ProcessACL_Self() + // { + // var expectedPrincipalType = Label.Group; + // var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + // var expectedRightName = EdgeNames.AddSelf; + // + // var mockLDAPUtils = new Mock(); + // var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + // var mockRule = new Mock(MockBehavior.Loose, null); + // var collection = new List(); + // mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + // mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); + // mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); + // mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.Self); + // mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.WriteMember)); + // collection.Add(mockRule.Object); + // + // mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + // .Returns(collection); + // mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + // mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + // mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + // .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); + // + // var processor = new ACLProcessor(mockLDAPUtils.Object); + // var bytes = Helpers.B64ToBytes(AddMemberSecurityDescriptor); + // var result = processor.ProcessACL(bytes, _testDomainName, Label.Group, false).ToArray(); + // + // Assert.Single(result); + // var actual = result.First(); + // Assert.Equal(actual.PrincipalType, expectedPrincipalType); + // Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); + // Assert.Equal(actual.IsInherited, false); + // Assert.Equal(actual.RightName, expectedRightName); + // } + // + // [Fact] + // public void ACLProcessor_ProcessACL_ExtendedRight_Domain_Unmatched() + // { + // var expectedPrincipalType = Label.Group; + // var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + // + // var mockLDAPUtils = new Mock(); + // var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + // var mockRule = new Mock(MockBehavior.Loose, null); + // var collection = new List(); + // mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + // mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); + // mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); + // mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); + // mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.WriteMember)); + // collection.Add(mockRule.Object); + // + // mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + // .Returns(collection); + // mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + // mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + // mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + // .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); + // + // var processor = new ACLProcessor(mockLDAPUtils.Object); + // var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + // var result = processor.ProcessACL(bytes, _testDomainName, Label.Domain, false).ToArray(); + // + // Assert.Empty(result); + // } + // + // [Fact] + // public void ACLProcessor_ProcessACL_ExtendedRight_Domain_DSReplicationGetChanges() + // { + // var expectedPrincipalType = Label.Group; + // var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + // var expectedRightName = EdgeNames.GetChanges; + // + // var mockLDAPUtils = new Mock(); + // var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + // var mockRule = new Mock(MockBehavior.Loose, null); + // var collection = new List(); + // mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + // mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); + // mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); + // mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); + // mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.DSReplicationGetChanges)); + // collection.Add(mockRule.Object); + // + // mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + // .Returns(collection); + // mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + // mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + // mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + // .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); + // + // var processor = new ACLProcessor(mockLDAPUtils.Object); + // var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + // var result = processor.ProcessACL(bytes, _testDomainName, Label.Domain, false).ToArray(); + // + // Assert.Single(result); + // var actual = result.First(); + // Assert.Equal(actual.PrincipalType, expectedPrincipalType); + // Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); + // Assert.Equal(actual.IsInherited, false); + // Assert.Equal(actual.RightName, expectedRightName); + // } + // + // [Fact] + // public void ACLProcessor_ProcessACL_ExtendedRight_Domain_All() + // { + // var expectedPrincipalType = Label.Group; + // var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + // var expectedRightName = EdgeNames.AllExtendedRights; + // + // var mockLDAPUtils = new Mock(); + // var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + // var mockRule = new Mock(MockBehavior.Loose, null); + // var collection = new List(); + // mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + // mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); + // mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); + // mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); + // mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); + // collection.Add(mockRule.Object); + // + // mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + // .Returns(collection); + // mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + // mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + // mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + // .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); + // + // var processor = new ACLProcessor(mockLDAPUtils.Object); + // var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + // var result = processor.ProcessACL(bytes, _testDomainName, Label.Domain, false).ToArray(); + // + // Assert.Single(result); + // var actual = result.First(); + // Assert.Equal(actual.PrincipalType, expectedPrincipalType); + // Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); + // Assert.Equal(actual.IsInherited, false); + // Assert.Equal(actual.RightName, expectedRightName); + // } + // + // [Fact] + // public void ACLProcessor_ProcessACL_ExtendedRight_Domain_DSReplicationGetChangesAll() + // { + // var expectedPrincipalType = Label.Group; + // var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + // var expectedRightName = EdgeNames.GetChangesAll; + // + // var mockLDAPUtils = new Mock(); + // var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + // var mockRule = new Mock(MockBehavior.Loose, null); + // var collection = new List(); + // mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + // mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); + // mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); + // mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); + // mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.DSReplicationGetChangesAll)); + // collection.Add(mockRule.Object); + // + // mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + // .Returns(collection); + // mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + // mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + // mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + // .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); + // + // var processor = new ACLProcessor(mockLDAPUtils.Object); + // var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + // var result = processor.ProcessACL(bytes, _testDomainName, Label.Domain, false).ToArray(); + // + // Assert.Single(result); + // var actual = result.First(); + // Assert.Equal(actual.PrincipalType, expectedPrincipalType); + // Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); + // Assert.Equal(actual.IsInherited, false); + // Assert.Equal(actual.RightName, expectedRightName); + // } + // + // [Fact] + // public void ACLProcessor_ProcessACL_ExtendedRight_User_Unmatched() + // { + // var expectedPrincipalType = Label.Group; + // var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + // var expectedRightName = EdgeNames.GetChangesAll; + // var unmatchedGuid = new Guid("583991c8-629d-4a07-8a70-74d19d22ac9c"); + // + // var mockLDAPUtils = new Mock(); + // var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + // var mockRule = new Mock(MockBehavior.Loose, null); + // var collection = new List(); + // mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + // mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); + // mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); + // mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); + // mockRule.Setup(x => x.ObjectType()).Returns(unmatchedGuid); + // collection.Add(mockRule.Object); + // + // mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + // .Returns(collection); + // mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + // mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + // mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + // .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); + // + // var processor = new ACLProcessor(mockLDAPUtils.Object); + // var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + // var result = processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArray(); + // + // Assert.Empty(result); + // } + // + // [Fact] + // public void ACLProcessor_ProcessACL_ExtendedRight_User_UserForceChangePassword() + // { + // var expectedPrincipalType = Label.Group; + // var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + // var expectedRightName = EdgeNames.ForceChangePassword; + // + // var mockLDAPUtils = new Mock(); + // var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + // var mockRule = new Mock(MockBehavior.Loose, null); + // var collection = new List(); + // mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + // mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); + // mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); + // mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); + // mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.UserForceChangePassword)); + // collection.Add(mockRule.Object); + // + // mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + // .Returns(collection); + // mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + // mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + // mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + // .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); + // + // var processor = new ACLProcessor(mockLDAPUtils.Object); + // var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + // var result = processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArray(); + // + // Assert.Single(result); + // var actual = result.First(); + // Assert.Equal(actual.PrincipalType, expectedPrincipalType); + // Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); + // Assert.Equal(actual.IsInherited, false); + // Assert.Equal(actual.RightName, expectedRightName); + // } + // + // [Fact] + // public void ACLProcessor_ProcessACL_ExtendedRight_User_All() + // { + // var expectedPrincipalType = Label.Group; + // var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + // var expectedRightName = EdgeNames.AllExtendedRights; + // + // var mockLDAPUtils = new Mock(); + // var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + // var mockRule = new Mock(MockBehavior.Loose, null); + // var collection = new List(); + // mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + // mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); + // mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); + // mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); + // mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); + // collection.Add(mockRule.Object); + // + // mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + // .Returns(collection); + // mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + // mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + // mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + // .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); + // + // var processor = new ACLProcessor(mockLDAPUtils.Object); + // var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + // var result = processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArray(); + // + // Assert.Single(result); + // var actual = result.First(); + // Assert.Equal(actual.PrincipalType, expectedPrincipalType); + // Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); + // Assert.Equal(actual.IsInherited, false); + // Assert.Equal(actual.RightName, expectedRightName); + // } + // + // [Fact] + // public void ACLProcessor_ProcessACL_ExtendedRight_Computer_NoLAPS() + // { + // var expectedPrincipalType = Label.Group; + // var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + // var expectedRightName = EdgeNames.AllExtendedRights; + // + // var mockLDAPUtils = new Mock(); + // var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + // var mockRule = new Mock(MockBehavior.Loose, null); + // var collection = new List(); + // mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + // mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); + // mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); + // mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); + // mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); + // collection.Add(mockRule.Object); + // + // mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + // .Returns(collection); + // mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + // mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + // mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + // .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); + // + // var processor = new ACLProcessor(mockLDAPUtils.Object); + // var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + // var result = processor.ProcessACL(bytes, _testDomainName, Label.Computer, false).ToArray(); + // + // Assert.Empty(result); + // } + // + // [Fact] + // public void ACLProcessor_ProcessACL_ExtendedRight_Computer_All() + // { + // var expectedPrincipalType = Label.Group; + // var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + // var expectedRightName = EdgeNames.AllExtendedRights; + // + // var mockLDAPUtils = new Mock(); + // var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + // var mockRule = new Mock(MockBehavior.Loose, null); + // var collection = new List(); + // mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + // mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); + // mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); + // mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); + // mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); + // collection.Add(mockRule.Object); + // + // mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + // .Returns(collection); + // mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + // mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + // mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + // .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); + // + // var processor = new ACLProcessor(mockLDAPUtils.Object); + // var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + // var result = processor.ProcessACL(bytes, _testDomainName, Label.Computer, true).ToArray(); + // + // Assert.Single(result); + // var actual = result.First(); + // Assert.Equal(actual.PrincipalType, expectedPrincipalType); + // Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); + // Assert.Equal(actual.IsInherited, false); + // Assert.Equal(actual.RightName, expectedRightName); + // } + // + // [Fact(Skip = "Need to populate cache to reach this case")] + // public void ACLProcessor_ProcessACL_ExtendedRight_Computer_MappedGuid() + // { + // } + // + // [Fact] + // public void ACLProcessor_ProcessACL_GenericWrite_Unmatched() + // { + // var expectedPrincipalType = Label.Group; + // var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + // var expectedRightName = EdgeNames.AllExtendedRights; + // + // var mockLDAPUtils = new Mock(); + // var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + // var mockRule = new Mock(MockBehavior.Loose, null); + // var collection = new List(); + // mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + // mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); + // mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); + // mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.GenericWrite); + // mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); + // collection.Add(mockRule.Object); + // + // mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + // .Returns(collection); + // mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + // mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + // mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + // .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); + // + // var processor = new ACLProcessor(mockLDAPUtils.Object); + // var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + // var result = processor.ProcessACL(bytes, _testDomainName, Label.Container, true).ToArray(); + // + // Assert.Empty(result); + // } + // + // [Fact] + // public void ACLProcessor_ProcessACL_GenericWrite_User_All() + // { + // var expectedPrincipalType = Label.Group; + // var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + // var expectedRightName = EdgeNames.GenericWrite; + // + // var mockLDAPUtils = new Mock(); + // var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + // var mockRule = new Mock(MockBehavior.Loose, null); + // var collection = new List(); + // mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + // mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); + // mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); + // mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.GenericWrite); + // mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); + // collection.Add(mockRule.Object); + // + // mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + // .Returns(collection); + // mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + // mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + // mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + // .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); + // + // var processor = new ACLProcessor(mockLDAPUtils.Object); + // var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + // var result = processor.ProcessACL(bytes, _testDomainName, Label.User, true).ToArray(); + // + // Assert.Single(result); + // var actual = result.First(); + // Assert.Equal(actual.PrincipalType, expectedPrincipalType); + // Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); + // Assert.Equal(actual.IsInherited, false); + // Assert.Equal(actual.RightName, expectedRightName); + // } + // + // [Fact] + // public void ACLProcessor_ProcessACL_GenericWrite_User_WriteMember() + // { + // var expectedPrincipalType = Label.Group; + // var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + // var expectedRightName = EdgeNames.AddMember; + // + // var mockLDAPUtils = new Mock(); + // var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + // var mockRule = new Mock(MockBehavior.Loose, null); + // var collection = new List(); + // mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + // mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); + // mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); + // mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.GenericWrite); + // mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.WriteMember)); + // collection.Add(mockRule.Object); + // + // mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + // .Returns(collection); + // mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + // mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + // mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + // .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); + // + // var processor = new ACLProcessor(mockLDAPUtils.Object); + // var bytes = Helpers.B64ToBytes(AddMemberSecurityDescriptor); + // var result = processor.ProcessACL(bytes, _testDomainName, Label.Group, true).ToArray(); + // + // _testOutputHelper.WriteLine(JsonConvert.SerializeObject(result)); + // + // Assert.Single(result); + // var actual = result.First(); + // Assert.Equal(actual.PrincipalType, expectedPrincipalType); + // Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); + // Assert.Equal(actual.IsInherited, false); + // Assert.Equal(actual.RightName, expectedRightName); + // } + // + // [Fact] + // public void ACLProcessor_ProcessACL_GenericWrite_Computer_WriteAllowedToAct() + // { + // var expectedPrincipalType = Label.Group; + // var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + // var expectedRightName = EdgeNames.AddAllowedToAct; + // + // var mockLDAPUtils = new Mock(); + // var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + // var mockRule = new Mock(MockBehavior.Loose, null); + // var collection = new List(); + // mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + // mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); + // mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); + // mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.GenericWrite); + // mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.WriteAllowedToAct)); + // collection.Add(mockRule.Object); + // + // mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + // .Returns(collection); + // mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + // mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + // mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + // .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); + // + // var processor = new ACLProcessor(mockLDAPUtils.Object); + // var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + // var result = processor.ProcessACL(bytes, _testDomainName, Label.Computer, true).ToArray(); + // + // Assert.Single(result); + // var actual = result.First(); + // Assert.Equal(actual.PrincipalType, expectedPrincipalType); + // Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); + // Assert.Equal(actual.IsInherited, false); + // Assert.Equal(actual.RightName, expectedRightName); + // } + } +} \ No newline at end of file diff --git a/test/unit/Facades/MockLDAPUtils.cs b/test/unit/Facades/MockLDAPUtils.cs index c0b621fc..749f02cb 100644 --- a/test/unit/Facades/MockLDAPUtils.cs +++ b/test/unit/Facades/MockLDAPUtils.cs @@ -8,6 +8,7 @@ using System.Security.Principal; using System.Threading; using System.Threading.Tasks; +using Castle.Core.Internal; using Moq; using SharpHoundCommonLib; using SharpHoundCommonLib.Enums; @@ -28,92 +29,28 @@ public MockLDAPUtils() _forest = MockableForest.Construct("FOREST.LOCAL"); } - public void SetLDAPConfig(LDAPConfig config) - { - throw new NotImplementedException(); - } - - public bool TestLDAPConfig(string domain) - { - return true; - } - - public string[] GetUserGlobalCatalogMatches(string name) - { - name = name.ToLower(); - return name switch - { - "dfm" => new[] { "S-1-5-21-3130019616-2776909439-2417379446-1105" }, - "administrator" => new[] - {"S-1-5-21-3130019616-2776909439-2417379446-500", "S-1-5-21-3084884204-958224920-2707782874-500"}, - "admin" => new[] { "S-1-5-21-3130019616-2776909439-2417379446-2116" }, - _ => Array.Empty() - }; - } - - public IAsyncEnumerable> Query(LdapQueryParameters queryParameters, + public virtual IAsyncEnumerable> Query(LdapQueryParameters queryParameters, CancellationToken cancellationToken = new CancellationToken()) { throw new NotImplementedException(); } - public IAsyncEnumerable> PagedQuery(LdapQueryParameters queryParameters, + public virtual IAsyncEnumerable> PagedQuery(LdapQueryParameters queryParameters, CancellationToken cancellationToken = new CancellationToken()) { throw new NotImplementedException(); } - public IAsyncEnumerable> RangedRetrieval(string distinguishedName, string attributeName, + public virtual IAsyncEnumerable> RangedRetrieval(string distinguishedName, string attributeName, CancellationToken cancellationToken = new CancellationToken()) { throw new NotImplementedException(); } public Task<(bool Success, TypedPrincipal Principal)> ResolveIDAndType(SecurityIdentifier securityIdentifier, string objectDomain) { - throw new NotImplementedException(); - } - - Task<(bool Success, TypedPrincipal Principal)> ILdapUtils.ResolveIDAndType(string identifier, string objectDomain) { - throw new NotImplementedException(); - } - - public Task<(bool Success, TypedPrincipal WellKnownPrincipal)> GetWellKnownPrincipal(string securityIdentifier, string objectDomain) { - throw new NotImplementedException(); + return ResolveIDAndType(securityIdentifier.Value, objectDomain); } - Task<(bool Success, string DomainName)> ILdapUtils.GetDomainNameFromSid(string sid) { - throw new NotImplementedException(); - } - - public Task<(bool Success, string DomainSid)> GetDomainSidFromDomainName(string domainName) { - throw new NotImplementedException(); - } - - public bool GetDomain(string domainName, out Domain domain) { - throw new NotImplementedException(); - } - - public bool GetDomain(out Domain domain) { - throw new NotImplementedException(); - } - - Task<(bool Success, TypedPrincipal Principal)> ILdapUtils.ResolveAccountName(string name, string domain) { - throw new NotImplementedException(); - } - - Task<(bool Success, string SecurityIdentifier)> ILdapUtils.ResolveHostToSid(string host, string domain) { - throw new NotImplementedException(); - } - - public Task<(bool Success, string[] Sids)> GetGlobalCatalogMatches(string name, string domain) { - throw new NotImplementedException(); - } - - public Task<(bool Success, TypedPrincipal Principal)> ResolveCertTemplateByProperty(string propValue, string propName, string domainName) { - throw new NotImplementedException(); - } - - public TypedPrincipal ResolveIDAndType(string id, string fallbackDomain) - { - id = id?.ToUpper(); - if (GetWellKnownPrincipal(id, fallbackDomain, out var principal)) return principal; + public async Task<(bool Success, TypedPrincipal Principal)> ResolveIDAndType(string identifier, string objectDomain) { + var id = identifier.ToUpper(); + if (await GetWellKnownPrincipal(id, objectDomain) is (true, var principal)) return (true, principal); principal = id switch { @@ -729,152 +666,53 @@ public TypedPrincipal ResolveIDAndType(string id, string fallbackDomain) _ => null }; - return principal; - } - - public Label LookupSidType(string sid, string domain) - { - var result = ResolveIDAndType(sid, domain); - return result.ObjectType; + return (true, principal); } - public Label LookupGuidType(string guid, string domain) - { - var result = ResolveIDAndType(guid, domain); - return result.ObjectType; + public async Task<(bool Success, TypedPrincipal WellKnownPrincipal)> GetWellKnownPrincipal(string securityIdentifier, string objectDomain) { + if (!WellKnownPrincipal.GetWellKnownPrincipal(securityIdentifier, out var commonPrincipal)) return (false, default); + commonPrincipal.ObjectIdentifier = ConvertWellKnownPrincipal(securityIdentifier, objectDomain); + _seenWellKnownPrincipals.TryAdd(commonPrincipal.ObjectIdentifier, securityIdentifier); + return (true, commonPrincipal); } - public string GetDomainNameFromSid(string sid) - { + Task<(bool Success, string DomainName)> ILdapUtils.GetDomainNameFromSid(string sid) { throw new NotImplementedException(); } - public string GetSidFromDomainName(string domainName) - { + public async Task<(bool Success, string DomainSid)> GetDomainSidFromDomainName(string domainName) { if (domainName.Equals("TESTLAB.LOCAL", StringComparison.OrdinalIgnoreCase)) { - return "S-1-5-21-3130019616-2776909439-2417379446"; - } - - return null; - } - - public string ConvertWellKnownPrincipal(string sid, string domain) - { - if (!WellKnownPrincipal.GetWellKnownPrincipal(sid, out _)) return sid; - - if (sid != "S-1-5-9") return $"{domain}-{sid}".ToUpper(); - - var forest = GetForest(domain)?.Name; - return $"{forest}-{sid}".ToUpper(); - } - - public bool GetWellKnownPrincipal(string sid, string domain, out TypedPrincipal commonPrincipal) - { - if (!WellKnownPrincipal.GetWellKnownPrincipal(sid, out commonPrincipal)) return false; - commonPrincipal.ObjectIdentifier = ConvertWellKnownPrincipal(sid, domain); - _seenWellKnownPrincipals.TryAdd(commonPrincipal.ObjectIdentifier, sid); - return true; - } - - public bool ConvertLocalWellKnownPrincipal(SecurityIdentifier sid, string computerDomainSid, string computerDomain, - out TypedPrincipal principal) - { - if (WellKnownPrincipal.GetWellKnownPrincipal(sid.Value, out var common)) - { - //The everyone and auth users principals are special and will be converted to the domain equivalent - if (sid.Value is "S-1-1-0" or "S-1-5-11") - { - GetWellKnownPrincipal(sid.Value, computerDomain, out principal); - return true; - } - - //Use the computer object id + the RID of the sid we looked up to create our new principal - principal = new TypedPrincipal - { - ObjectIdentifier = $"{computerDomainSid}-{sid.Rid()}", - ObjectType = common.ObjectType switch - { - Label.User => Label.LocalUser, - Label.Group => Label.LocalGroup, - _ => common.ObjectType - } - }; - - return true; + return (true, "S-1-5-21-3130019616-2776909439-2417379446"); } - principal = null; - return false; + return (false, default); } - Task<(bool Success, TypedPrincipal Principal)> ILdapUtils.ResolveDistinguishedName(string distinguishedName) { - throw new NotImplementedException(); - } - - public void AddDomainController(string domainControllerSID) - { - _domainControllers.TryAdd(domainControllerSID, new byte()); - } - - public IAsyncEnumerable GetWellKnownPrincipalOutput() { - throw new NotImplementedException(); - } - - public void SetLdapConfig(LDAPConfig config) { - throw new NotImplementedException(); - } - - public Task<(bool Success, string Message)> TestLdapConnection(string domain) { - throw new NotImplementedException(); - } - - public Task<(bool Success, string Path)> GetNamingContextPath(string domain, NamingContext context) { + public bool GetDomain(string domainName, out Domain domain) { throw new NotImplementedException(); } - public Domain GetDomain(string domainName = null) - { + public bool GetDomain(out Domain domain) { throw new NotImplementedException(); } - public IEnumerable GetWellKnownPrincipalOutput(string domain) - { - foreach (var wkp in _seenWellKnownPrincipals) + public async Task<(bool Success, TypedPrincipal Principal)> ResolveAccountName(string name, string domain) { + return (true, name.ToUpper() switch { - WellKnownPrincipal.GetWellKnownPrincipal(wkp.Value, out var principal); - OutputBase output = principal.ObjectType switch - { - Label.User => new User(), - Label.Computer => new Computer(), - Label.Group => new Group(), - Label.GPO => new GPO(), - Label.Domain => new SharpHoundCommonLib.OutputTypes.Domain(), - Label.OU => new OU(), - Label.Container => new Container(), - _ => throw new ArgumentOutOfRangeException() - }; - - output.Properties.Add("name", principal.ObjectIdentifier); - output.ObjectIdentifier = wkp.Key; - yield return output; - } - - var entdc = GetBaseEnterpriseDC(); - entdc.Members = _domainControllers.Select(x => new TypedPrincipal(x.Key, Label.Computer)).ToArray(); - yield return entdc; - } - - public virtual IEnumerable DoRangedRetrieval(string distinguishedName, string attributeName) - { - throw new NotImplementedException(); + "ADMINISTRATOR" => new TypedPrincipal( + "S-1-5-21-3130019616-2776909439-2417379446-500", Label.User), + "DFM" => new TypedPrincipal( + "S-1-5-21-3130019616-2776909439-2417379446-1105", Label.User), + "TEST" => new TypedPrincipal("S-1-5-21-3130019616-2776909439-2417379446-1106", Label.User), + _ => null + }); } -#pragma warning disable CS1998 - public async Task ResolveHostToSid(string hostname, string domain) - { - var h = SharpHoundCommonLib.Helpers.StripServicePrincipalName(hostname); - return h.ToUpper() switch + public async Task<(bool Success, string SecurityIdentifier)> ResolveHostToSid(string host, string domain) { + var h = SharpHoundCommonLib.Helpers.StripServicePrincipalName(host); + + var resolved = h.ToUpper() switch { "192.168.1.1" => "S-1-5-21-3130019616-2776909439-2417379446-1104", "PRIMARY" => "S-1-5-21-3130019616-2776909439-2417379446-1001", @@ -883,31 +721,40 @@ public async Task ResolveHostToSid(string hostname, string domain) "WIN10.TESTLAB.LOCAL" => "S-1-5-21-3130019616-2776909439-2417379446-1104", _ => null }; + + return (resolved != null, resolved); } -#pragma warning restore CS1998 -#pragma warning disable CS1998 - public TypedPrincipal ResolveAccountName(string name, string domain) - { - return name.ToUpper() switch + public async Task<(bool Success, string[] Sids)> GetGlobalCatalogMatches(string name, string domain) { + name = name.ToLower(); + var results = name switch { - "ADMINISTRATOR" => new TypedPrincipal( - "S-1-5-21-3130019616-2776909439-2417379446-500", Label.User), - "DFM" => new TypedPrincipal( - "S-1-5-21-3130019616-2776909439-2417379446-1105", Label.User), - "TEST" => new TypedPrincipal("S-1-5-21-3130019616-2776909439-2417379446-1106", Label.User), - _ => null + "dfm" => new[] { "S-1-5-21-3130019616-2776909439-2417379446-1105" }, + "administrator" => new[] + {"S-1-5-21-3130019616-2776909439-2417379446-500", "S-1-5-21-3084884204-958224920-2707782874-500"}, + "admin" => new[] { "S-1-5-21-3130019616-2776909439-2417379446-2116" }, + _ => Array.Empty() }; + + return (!results.IsNullOrEmpty(), results); } -#pragma warning restore CS1998 - Task ILdapUtils.IsDomainController(string computerObjectId, string domainName) { + public Task<(bool Success, TypedPrincipal Principal)> ResolveCertTemplateByProperty(string propValue, string propName, string domainName) { throw new NotImplementedException(); } - public TypedPrincipal ResolveDistinguishedName(string dn) + public string ConvertWellKnownPrincipal(string sid, string domain) { - return dn.ToUpper() switch + if (!WellKnownPrincipal.GetWellKnownPrincipal(sid, out _)) return sid; + + if (sid != "S-1-5-9") return $"{domain}-{sid}".ToUpper(); + + var forest = GetForest(domain)?.Name; + return $"{forest}-{sid}".ToUpper(); + } + + public async Task<(bool Success, TypedPrincipal Principal)> ResolveDistinguishedName(string distinguishedName) { + var result = distinguishedName.ToUpper() switch { "CN=REPLICATOR,CN=BUILTIN,DC=TESTLAB,DC=LOCAL" => new TypedPrincipal("TESTLAB.LOCAL-S-1-5-32-552", Label.Group), @@ -1140,21 +987,64 @@ public TypedPrincipal ResolveDistinguishedName(string dn) "CN=ENTERPRISE KEY ADMINS,CN=USERS,DC=ESC10,DC=LOCAL" => new TypedPrincipal("S-1-5-21-3662707843-2053279151-3839588741-527", Label.Group), _ => null, }; + + return (result != null, result); } - public virtual IEnumerable QueryLDAP(string ldapFilter, SearchScope scope, string[] props, - CancellationToken cancellationToken, - string domainName = null, bool includeAcl = false, bool showDeleted = false, string adsPath = null, - bool globalCatalog = false, bool skipCache = false, bool throwException = false) + public void AddDomainController(string domainControllerSID) { + _domainControllers.TryAdd(domainControllerSID, new byte()); + } + + public IAsyncEnumerable GetWellKnownPrincipalOutput() { + throw new NotImplementedException(); + } + + public void SetLdapConfig(LDAPConfig config) { throw new NotImplementedException(); } - public virtual IEnumerable QueryLDAP(string ldapFilter, SearchScope scope, string[] props, - string domainName = null, - bool includeAcl = false, bool showDeleted = false, string adsPath = null, bool globalCatalog = false, - bool skipCache = false, bool throwException = false) + public Task<(bool Success, string Message)> TestLdapConnection(string domain) { + throw new NotImplementedException(); + } + + public Task<(bool Success, string Path)> GetNamingContextPath(string domain, NamingContext context) { + throw new NotImplementedException(); + } + + public Domain GetDomain(string domainName = null) + { + throw new NotImplementedException(); + } + + public IEnumerable GetWellKnownPrincipalOutput(string domain) { + foreach (var wkp in _seenWellKnownPrincipals) + { + WellKnownPrincipal.GetWellKnownPrincipal(wkp.Value, out var principal); + OutputBase output = principal.ObjectType switch + { + Label.User => new User(), + Label.Computer => new Computer(), + Label.Group => new Group(), + Label.GPO => new GPO(), + Label.Domain => new SharpHoundCommonLib.OutputTypes.Domain(), + Label.OU => new OU(), + Label.Container => new Container(), + _ => throw new ArgumentOutOfRangeException() + }; + + output.Properties.Add("name", principal.ObjectIdentifier); + output.ObjectIdentifier = wkp.Key; + yield return output; + } + + var entdc = GetBaseEnterpriseDC(); + entdc.Members = _domainControllers.Select(x => new TypedPrincipal(x.Key, Label.Computer)).ToArray(); + yield return entdc; + } + + Task ILdapUtils.IsDomainController(string computerObjectId, string domainName) { throw new NotImplementedException(); } @@ -1169,8 +1059,31 @@ public ActiveDirectorySecurityDescriptor MakeSecurityDescriptor() return mockSecurityDescriptor.Object; } - public Task<(bool Success, TypedPrincipal Principal)> ConvertLocalWellKnownPrincipal(SecurityIdentifier sid, string computerDomainSid, string computerDomain) { - throw new NotImplementedException(); + public async Task<(bool Success, TypedPrincipal Principal)> ConvertLocalWellKnownPrincipal(SecurityIdentifier sid, string computerDomainSid, string computerDomain) { + if (WellKnownPrincipal.GetWellKnownPrincipal(sid.Value, out var common)) + { + //The everyone and auth users principals are special and will be converted to the domain equivalent + if (sid.Value is "S-1-1-0" or "S-1-5-11") + { + return await GetWellKnownPrincipal(sid.Value, computerDomain); + } + + //Use the computer object id + the RID of the sid we looked up to create our new principal + var principal = new TypedPrincipal + { + ObjectIdentifier = $"{computerDomainSid}-{sid.Rid()}", + ObjectType = common.ObjectType switch + { + Label.User => Label.LocalUser, + Label.Group => Label.LocalGroup, + _ => common.ObjectType + } + }; + + return (true, principal); + } + + return (false, default); } public string BuildLdapPath(string dnPath, string domain) @@ -1184,12 +1097,7 @@ private Group GetBaseEnterpriseDC() g.Properties.Add("name", "ENTERPRISE DOMAIN CONTROLLERS@TESTLAB.LOCAL".ToUpper()); return g; } - - public TypedPrincipal ResolveCertTemplateByCN(string cn, string containerDN, string domainName) - { - throw new NotImplementedException(); - } - + public string GetConfigurationPath(string domainName) { throw new NotImplementedException(); @@ -1206,7 +1114,7 @@ public bool IsDomainController(string computerObjectId, string domainName) } public void Dispose() { - _forest?.Dispose(); + } } } \ No newline at end of file From be570f31b5488d88f8cfeb73385fa519e2a90cbd Mon Sep 17 00:00:00 2001 From: rvazarkar Date: Tue, 9 Jul 2024 12:40:42 -0400 Subject: [PATCH 40/68] wip: fix ACLProcessorTest.cs --- test/unit/ACLProcessorTest.cs | 1472 ++++++++++++++++----------------- 1 file changed, 736 insertions(+), 736 deletions(-) diff --git a/test/unit/ACLProcessorTest.cs b/test/unit/ACLProcessorTest.cs index 5aa46b76..4501631a 100644 --- a/test/unit/ACLProcessorTest.cs +++ b/test/unit/ACLProcessorTest.cs @@ -276,741 +276,741 @@ public async Task ACLProcessor_ProcessACL_Null_SID() Assert.Empty(result); } - // - // [Fact] - // public void ACLProcessor_ProcessACL_Null_ACE() - // { - // var mockLDAPUtils = new Mock(); - // var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - // var collection = new List(); - // collection.Add(null); - // - // mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - // .Returns(collection); - // mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); - // mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - // - // var processor = new ACLProcessor(mockLDAPUtils.Object); - // var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); - // var result = processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArray(); - // - // Assert.Empty(result); - // } - // - // [Fact] - // public void ACLProcessor_ProcessACL_Deny_ACE() - // { - // var mockLDAPUtils = new Mock(); - // var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - // var mockRule = new Mock(MockBehavior.Loose, null); - // var collection = new List(); - // mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Deny); - // collection.Add(mockRule.Object); - // - // mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - // .Returns(collection); - // mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); - // mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - // - // var processor = new ACLProcessor(mockLDAPUtils.Object); - // var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); - // var result = processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArray(); - // - // Assert.Empty(result); - // } - // - // [Fact] - // public void ACLProcessor_ProcessACL_Unmatched_Inheritance_ACE() - // { - // var mockLDAPUtils = new Mock(); - // var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - // var mockRule = new Mock(MockBehavior.Loose, null); - // var collection = new List(); - // mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); - // mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(false); - // collection.Add(mockRule.Object); - // - // mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - // .Returns(collection); - // mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); - // mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - // - // var processor = new ACLProcessor(mockLDAPUtils.Object); - // var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); - // var result = processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArray(); - // - // Assert.Empty(result); - // } - // - // [Fact] - // public void ACLProcessor_ProcessACL_Null_SID_ACE() - // { - // var mockLDAPUtils = new Mock(); - // var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - // var mockRule = new Mock(MockBehavior.Loose, null); - // var collection = new List(); - // mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); - // mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); - // mockRule.Setup(x => x.IdentityReference()).Returns((string)null); - // collection.Add(mockRule.Object); - // - // mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - // .Returns(collection); - // mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); - // mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - // - // var processor = new ACLProcessor(mockLDAPUtils.Object); - // var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); - // var result = processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArray(); - // - // Assert.Empty(result); - // } - // - // [Fact] - // public void ACLProcessor_ProcessACL_GenericAll_Unmatched_Guid() - // { - // var expectedPrincipalType = Label.Group; - // var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; - // var unmatchedGuid = new Guid("583991c8-629d-4a07-8a70-74d19d22ac9c"); - // - // var mockLDAPUtils = new Mock(); - // var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - // var mockRule = new Mock(MockBehavior.Loose, null); - // var collection = new List(); - // mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); - // mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); - // mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); - // mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.GenericAll); - // mockRule.Setup(x => x.ObjectType()).Returns(unmatchedGuid); - // collection.Add(mockRule.Object); - // - // mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - // .Returns(collection); - // mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); - // mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - // mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) - // .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); - // - // var processor = new ACLProcessor(mockLDAPUtils.Object); - // var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); - // var result = processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArray(); - // - // Assert.Empty(result); - // } - // - // [Fact] - // public void ACLProcessor_ProcessACL_GenericAll() - // { - // var expectedPrincipalType = Label.Group; - // var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; - // - // var mockLDAPUtils = new Mock(); - // var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - // var mockRule = new Mock(MockBehavior.Loose, null); - // var collection = new List(); - // mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); - // mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); - // mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); - // mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.GenericAll); - // mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); - // collection.Add(mockRule.Object); - // - // mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - // .Returns(collection); - // mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); - // mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - // mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) - // .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); - // - // var processor = new ACLProcessor(mockLDAPUtils.Object); - // var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); - // var result = processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArray(); - // - // Assert.Single(result); - // var actual = result.First(); - // Assert.Equal(actual.PrincipalType, expectedPrincipalType); - // Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); - // Assert.Equal(actual.IsInherited, false); - // Assert.Equal(actual.RightName, EdgeNames.GenericAll); - // } - // - // [Fact] - // public void ACLProcessor_ProcessACL_WriteDacl() - // { - // var expectedPrincipalType = Label.Group; - // var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; - // var expectedRightName = ActiveDirectoryRights.WriteDacl; - // - // var mockLDAPUtils = new Mock(); - // var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - // var mockRule = new Mock(MockBehavior.Loose, null); - // var collection = new List(); - // mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); - // mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); - // mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); - // mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(expectedRightName); - // mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); - // collection.Add(mockRule.Object); - // - // mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - // .Returns(collection); - // mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); - // mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - // mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) - // .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); - // - // var processor = new ACLProcessor(mockLDAPUtils.Object); - // var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); - // var result = processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArray(); - // - // Assert.Single(result); - // var actual = result.First(); - // Assert.Equal(actual.PrincipalType, expectedPrincipalType); - // Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); - // Assert.Equal(actual.IsInherited, false); - // Assert.Equal(actual.RightName, expectedRightName.ToString()); - // } - // - // [Fact] - // public void ACLProcessor_ProcessACL_WriteOwner() - // { - // var expectedPrincipalType = Label.Group; - // var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; - // var expectedRightName = ActiveDirectoryRights.WriteOwner; - // - // var mockLDAPUtils = new Mock(); - // var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - // var mockRule = new Mock(MockBehavior.Loose, null); - // var collection = new List(); - // mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); - // mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); - // mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); - // mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(expectedRightName); - // mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); - // collection.Add(mockRule.Object); - // - // mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - // .Returns(collection); - // mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); - // mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - // mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) - // .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); - // - // var processor = new ACLProcessor(mockLDAPUtils.Object); - // var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); - // var result = processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArray(); - // - // Assert.Single(result); - // var actual = result.First(); - // Assert.Equal(actual.PrincipalType, expectedPrincipalType); - // Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); - // Assert.Equal(actual.IsInherited, false); - // Assert.Equal(actual.RightName, expectedRightName.ToString()); - // } - // - // [Fact] - // public void ACLProcessor_ProcessACL_Self() - // { - // var expectedPrincipalType = Label.Group; - // var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; - // var expectedRightName = EdgeNames.AddSelf; - // - // var mockLDAPUtils = new Mock(); - // var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - // var mockRule = new Mock(MockBehavior.Loose, null); - // var collection = new List(); - // mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); - // mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); - // mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); - // mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.Self); - // mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.WriteMember)); - // collection.Add(mockRule.Object); - // - // mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - // .Returns(collection); - // mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); - // mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - // mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) - // .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); - // - // var processor = new ACLProcessor(mockLDAPUtils.Object); - // var bytes = Helpers.B64ToBytes(AddMemberSecurityDescriptor); - // var result = processor.ProcessACL(bytes, _testDomainName, Label.Group, false).ToArray(); - // - // Assert.Single(result); - // var actual = result.First(); - // Assert.Equal(actual.PrincipalType, expectedPrincipalType); - // Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); - // Assert.Equal(actual.IsInherited, false); - // Assert.Equal(actual.RightName, expectedRightName); - // } - // - // [Fact] - // public void ACLProcessor_ProcessACL_ExtendedRight_Domain_Unmatched() - // { - // var expectedPrincipalType = Label.Group; - // var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; - // - // var mockLDAPUtils = new Mock(); - // var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - // var mockRule = new Mock(MockBehavior.Loose, null); - // var collection = new List(); - // mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); - // mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); - // mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); - // mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); - // mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.WriteMember)); - // collection.Add(mockRule.Object); - // - // mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - // .Returns(collection); - // mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); - // mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - // mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) - // .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); - // - // var processor = new ACLProcessor(mockLDAPUtils.Object); - // var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); - // var result = processor.ProcessACL(bytes, _testDomainName, Label.Domain, false).ToArray(); - // - // Assert.Empty(result); - // } - // - // [Fact] - // public void ACLProcessor_ProcessACL_ExtendedRight_Domain_DSReplicationGetChanges() - // { - // var expectedPrincipalType = Label.Group; - // var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; - // var expectedRightName = EdgeNames.GetChanges; - // - // var mockLDAPUtils = new Mock(); - // var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - // var mockRule = new Mock(MockBehavior.Loose, null); - // var collection = new List(); - // mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); - // mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); - // mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); - // mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); - // mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.DSReplicationGetChanges)); - // collection.Add(mockRule.Object); - // - // mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - // .Returns(collection); - // mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); - // mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - // mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) - // .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); - // - // var processor = new ACLProcessor(mockLDAPUtils.Object); - // var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); - // var result = processor.ProcessACL(bytes, _testDomainName, Label.Domain, false).ToArray(); - // - // Assert.Single(result); - // var actual = result.First(); - // Assert.Equal(actual.PrincipalType, expectedPrincipalType); - // Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); - // Assert.Equal(actual.IsInherited, false); - // Assert.Equal(actual.RightName, expectedRightName); - // } - // - // [Fact] - // public void ACLProcessor_ProcessACL_ExtendedRight_Domain_All() - // { - // var expectedPrincipalType = Label.Group; - // var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; - // var expectedRightName = EdgeNames.AllExtendedRights; - // - // var mockLDAPUtils = new Mock(); - // var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - // var mockRule = new Mock(MockBehavior.Loose, null); - // var collection = new List(); - // mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); - // mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); - // mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); - // mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); - // mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); - // collection.Add(mockRule.Object); - // - // mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - // .Returns(collection); - // mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); - // mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - // mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) - // .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); - // - // var processor = new ACLProcessor(mockLDAPUtils.Object); - // var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); - // var result = processor.ProcessACL(bytes, _testDomainName, Label.Domain, false).ToArray(); - // - // Assert.Single(result); - // var actual = result.First(); - // Assert.Equal(actual.PrincipalType, expectedPrincipalType); - // Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); - // Assert.Equal(actual.IsInherited, false); - // Assert.Equal(actual.RightName, expectedRightName); - // } - // - // [Fact] - // public void ACLProcessor_ProcessACL_ExtendedRight_Domain_DSReplicationGetChangesAll() - // { - // var expectedPrincipalType = Label.Group; - // var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; - // var expectedRightName = EdgeNames.GetChangesAll; - // - // var mockLDAPUtils = new Mock(); - // var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - // var mockRule = new Mock(MockBehavior.Loose, null); - // var collection = new List(); - // mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); - // mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); - // mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); - // mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); - // mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.DSReplicationGetChangesAll)); - // collection.Add(mockRule.Object); - // - // mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - // .Returns(collection); - // mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); - // mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - // mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) - // .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); - // - // var processor = new ACLProcessor(mockLDAPUtils.Object); - // var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); - // var result = processor.ProcessACL(bytes, _testDomainName, Label.Domain, false).ToArray(); - // - // Assert.Single(result); - // var actual = result.First(); - // Assert.Equal(actual.PrincipalType, expectedPrincipalType); - // Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); - // Assert.Equal(actual.IsInherited, false); - // Assert.Equal(actual.RightName, expectedRightName); - // } - // - // [Fact] - // public void ACLProcessor_ProcessACL_ExtendedRight_User_Unmatched() - // { - // var expectedPrincipalType = Label.Group; - // var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; - // var expectedRightName = EdgeNames.GetChangesAll; - // var unmatchedGuid = new Guid("583991c8-629d-4a07-8a70-74d19d22ac9c"); - // - // var mockLDAPUtils = new Mock(); - // var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - // var mockRule = new Mock(MockBehavior.Loose, null); - // var collection = new List(); - // mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); - // mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); - // mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); - // mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); - // mockRule.Setup(x => x.ObjectType()).Returns(unmatchedGuid); - // collection.Add(mockRule.Object); - // - // mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - // .Returns(collection); - // mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); - // mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - // mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) - // .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); - // - // var processor = new ACLProcessor(mockLDAPUtils.Object); - // var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); - // var result = processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArray(); - // - // Assert.Empty(result); - // } - // - // [Fact] - // public void ACLProcessor_ProcessACL_ExtendedRight_User_UserForceChangePassword() - // { - // var expectedPrincipalType = Label.Group; - // var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; - // var expectedRightName = EdgeNames.ForceChangePassword; - // - // var mockLDAPUtils = new Mock(); - // var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - // var mockRule = new Mock(MockBehavior.Loose, null); - // var collection = new List(); - // mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); - // mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); - // mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); - // mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); - // mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.UserForceChangePassword)); - // collection.Add(mockRule.Object); - // - // mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - // .Returns(collection); - // mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); - // mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - // mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) - // .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); - // - // var processor = new ACLProcessor(mockLDAPUtils.Object); - // var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); - // var result = processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArray(); - // - // Assert.Single(result); - // var actual = result.First(); - // Assert.Equal(actual.PrincipalType, expectedPrincipalType); - // Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); - // Assert.Equal(actual.IsInherited, false); - // Assert.Equal(actual.RightName, expectedRightName); - // } - // - // [Fact] - // public void ACLProcessor_ProcessACL_ExtendedRight_User_All() - // { - // var expectedPrincipalType = Label.Group; - // var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; - // var expectedRightName = EdgeNames.AllExtendedRights; - // - // var mockLDAPUtils = new Mock(); - // var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - // var mockRule = new Mock(MockBehavior.Loose, null); - // var collection = new List(); - // mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); - // mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); - // mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); - // mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); - // mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); - // collection.Add(mockRule.Object); - // - // mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - // .Returns(collection); - // mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); - // mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - // mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) - // .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); - // - // var processor = new ACLProcessor(mockLDAPUtils.Object); - // var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); - // var result = processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArray(); - // - // Assert.Single(result); - // var actual = result.First(); - // Assert.Equal(actual.PrincipalType, expectedPrincipalType); - // Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); - // Assert.Equal(actual.IsInherited, false); - // Assert.Equal(actual.RightName, expectedRightName); - // } - // - // [Fact] - // public void ACLProcessor_ProcessACL_ExtendedRight_Computer_NoLAPS() - // { - // var expectedPrincipalType = Label.Group; - // var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; - // var expectedRightName = EdgeNames.AllExtendedRights; - // - // var mockLDAPUtils = new Mock(); - // var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - // var mockRule = new Mock(MockBehavior.Loose, null); - // var collection = new List(); - // mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); - // mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); - // mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); - // mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); - // mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); - // collection.Add(mockRule.Object); - // - // mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - // .Returns(collection); - // mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); - // mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - // mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) - // .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); - // - // var processor = new ACLProcessor(mockLDAPUtils.Object); - // var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); - // var result = processor.ProcessACL(bytes, _testDomainName, Label.Computer, false).ToArray(); - // - // Assert.Empty(result); - // } - // - // [Fact] - // public void ACLProcessor_ProcessACL_ExtendedRight_Computer_All() - // { - // var expectedPrincipalType = Label.Group; - // var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; - // var expectedRightName = EdgeNames.AllExtendedRights; - // - // var mockLDAPUtils = new Mock(); - // var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - // var mockRule = new Mock(MockBehavior.Loose, null); - // var collection = new List(); - // mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); - // mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); - // mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); - // mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); - // mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); - // collection.Add(mockRule.Object); - // - // mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - // .Returns(collection); - // mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); - // mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - // mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) - // .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); - // - // var processor = new ACLProcessor(mockLDAPUtils.Object); - // var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); - // var result = processor.ProcessACL(bytes, _testDomainName, Label.Computer, true).ToArray(); - // - // Assert.Single(result); - // var actual = result.First(); - // Assert.Equal(actual.PrincipalType, expectedPrincipalType); - // Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); - // Assert.Equal(actual.IsInherited, false); - // Assert.Equal(actual.RightName, expectedRightName); - // } - // - // [Fact(Skip = "Need to populate cache to reach this case")] - // public void ACLProcessor_ProcessACL_ExtendedRight_Computer_MappedGuid() - // { - // } - // - // [Fact] - // public void ACLProcessor_ProcessACL_GenericWrite_Unmatched() - // { - // var expectedPrincipalType = Label.Group; - // var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; - // var expectedRightName = EdgeNames.AllExtendedRights; - // - // var mockLDAPUtils = new Mock(); - // var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - // var mockRule = new Mock(MockBehavior.Loose, null); - // var collection = new List(); - // mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); - // mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); - // mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); - // mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.GenericWrite); - // mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); - // collection.Add(mockRule.Object); - // - // mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - // .Returns(collection); - // mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); - // mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - // mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) - // .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); - // - // var processor = new ACLProcessor(mockLDAPUtils.Object); - // var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); - // var result = processor.ProcessACL(bytes, _testDomainName, Label.Container, true).ToArray(); - // - // Assert.Empty(result); - // } - // - // [Fact] - // public void ACLProcessor_ProcessACL_GenericWrite_User_All() - // { - // var expectedPrincipalType = Label.Group; - // var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; - // var expectedRightName = EdgeNames.GenericWrite; - // - // var mockLDAPUtils = new Mock(); - // var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - // var mockRule = new Mock(MockBehavior.Loose, null); - // var collection = new List(); - // mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); - // mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); - // mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); - // mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.GenericWrite); - // mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); - // collection.Add(mockRule.Object); - // - // mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - // .Returns(collection); - // mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); - // mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - // mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) - // .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); - // - // var processor = new ACLProcessor(mockLDAPUtils.Object); - // var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); - // var result = processor.ProcessACL(bytes, _testDomainName, Label.User, true).ToArray(); - // - // Assert.Single(result); - // var actual = result.First(); - // Assert.Equal(actual.PrincipalType, expectedPrincipalType); - // Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); - // Assert.Equal(actual.IsInherited, false); - // Assert.Equal(actual.RightName, expectedRightName); - // } - // - // [Fact] - // public void ACLProcessor_ProcessACL_GenericWrite_User_WriteMember() - // { - // var expectedPrincipalType = Label.Group; - // var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; - // var expectedRightName = EdgeNames.AddMember; - // - // var mockLDAPUtils = new Mock(); - // var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - // var mockRule = new Mock(MockBehavior.Loose, null); - // var collection = new List(); - // mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); - // mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); - // mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); - // mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.GenericWrite); - // mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.WriteMember)); - // collection.Add(mockRule.Object); - // - // mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - // .Returns(collection); - // mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); - // mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - // mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) - // .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); - // - // var processor = new ACLProcessor(mockLDAPUtils.Object); - // var bytes = Helpers.B64ToBytes(AddMemberSecurityDescriptor); - // var result = processor.ProcessACL(bytes, _testDomainName, Label.Group, true).ToArray(); - // - // _testOutputHelper.WriteLine(JsonConvert.SerializeObject(result)); - // - // Assert.Single(result); - // var actual = result.First(); - // Assert.Equal(actual.PrincipalType, expectedPrincipalType); - // Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); - // Assert.Equal(actual.IsInherited, false); - // Assert.Equal(actual.RightName, expectedRightName); - // } - // - // [Fact] - // public void ACLProcessor_ProcessACL_GenericWrite_Computer_WriteAllowedToAct() - // { - // var expectedPrincipalType = Label.Group; - // var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; - // var expectedRightName = EdgeNames.AddAllowedToAct; - // - // var mockLDAPUtils = new Mock(); - // var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - // var mockRule = new Mock(MockBehavior.Loose, null); - // var collection = new List(); - // mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); - // mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); - // mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); - // mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.GenericWrite); - // mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.WriteAllowedToAct)); - // collection.Add(mockRule.Object); - // - // mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - // .Returns(collection); - // mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); - // mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - // mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) - // .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); - // - // var processor = new ACLProcessor(mockLDAPUtils.Object); - // var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); - // var result = processor.ProcessACL(bytes, _testDomainName, Label.Computer, true).ToArray(); - // - // Assert.Single(result); - // var actual = result.First(); - // Assert.Equal(actual.PrincipalType, expectedPrincipalType); - // Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); - // Assert.Equal(actual.IsInherited, false); - // Assert.Equal(actual.RightName, expectedRightName); - // } + + [Fact] + public async Task ACLProcessor_ProcessACL_Null_ACE() + { + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var collection = new List(); + collection.Add(null); + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + var result = await processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArrayAsync(); + + Assert.Empty(result); + } + + [Fact] + public async Task ACLProcessor_ProcessACL_Deny_ACE() + { + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var mockRule = new Mock(MockBehavior.Loose, null); + var collection = new List(); + mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Deny); + collection.Add(mockRule.Object); + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + var result = await processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArrayAsync(); + + Assert.Empty(result); + } + + [Fact] + public async Task ACLProcessor_ProcessACL_Unmatched_Inheritance_ACE() + { + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var mockRule = new Mock(MockBehavior.Loose, null); + var collection = new List(); + mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(false); + collection.Add(mockRule.Object); + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + var result = await processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArrayAsync(); + + Assert.Empty(result); + } + + [Fact] + public async Task ACLProcessor_ProcessACL_Null_SID_ACE() + { + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var mockRule = new Mock(MockBehavior.Loose, null); + var collection = new List(); + mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); + mockRule.Setup(x => x.IdentityReference()).Returns((string)null); + collection.Add(mockRule.Object); + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + var result = await processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArrayAsync(); + + Assert.Empty(result); + } + + [Fact] + public async Task ACLProcessor_ProcessACL_GenericAll_Unmatched_Guid() + { + var expectedPrincipalType = Label.Group; + var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + var unmatchedGuid = new Guid("583991c8-629d-4a07-8a70-74d19d22ac9c"); + + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var mockRule = new Mock(MockBehavior.Loose, null); + var collection = new List(); + mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); + mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); + mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.GenericAll); + mockRule.Setup(x => x.ObjectType()).Returns(unmatchedGuid); + collection.Add(mockRule.Object); + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + .ReturnsAsync((true,new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + var result = await processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArrayAsync(); + + Assert.Empty(result); + } + + [Fact] + public async Task ACLProcessor_ProcessACL_GenericAll() + { + var expectedPrincipalType = Label.Group; + var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var mockRule = new Mock(MockBehavior.Loose, null); + var collection = new List(); + mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); + mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); + mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.GenericAll); + mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); + collection.Add(mockRule.Object); + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + .ReturnsAsync((true, new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + var result = await processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArrayAsync(); + + Assert.Single(result); + var actual = result.First(); + Assert.Equal(actual.PrincipalType, expectedPrincipalType); + Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); + Assert.Equal(actual.IsInherited, false); + Assert.Equal(actual.RightName, EdgeNames.GenericAll); + } + + [Fact] + public async Task ACLProcessor_ProcessACL_WriteDacl() + { + var expectedPrincipalType = Label.Group; + var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + var expectedRightName = ActiveDirectoryRights.WriteDacl; + + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var mockRule = new Mock(MockBehavior.Loose, null); + var collection = new List(); + mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); + mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); + mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(expectedRightName); + mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); + collection.Add(mockRule.Object); + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + .ReturnsAsync((true, new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + var result = await processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArrayAsync(); + + Assert.Single(result); + var actual = result.First(); + Assert.Equal(actual.PrincipalType, expectedPrincipalType); + Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); + Assert.Equal(actual.IsInherited, false); + Assert.Equal(actual.RightName, expectedRightName.ToString()); + } + + [Fact] + public async Task ACLProcessor_ProcessACL_WriteOwner() + { + var expectedPrincipalType = Label.Group; + var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + var expectedRightName = ActiveDirectoryRights.WriteOwner; + + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var mockRule = new Mock(MockBehavior.Loose, null); + var collection = new List(); + mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); + mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); + mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(expectedRightName); + mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); + collection.Add(mockRule.Object); + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + .ReturnsAsync((true, new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + var result = await processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArrayAsync(); + + Assert.Single(result); + var actual = result.First(); + Assert.Equal(actual.PrincipalType, expectedPrincipalType); + Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); + Assert.Equal(actual.IsInherited, false); + Assert.Equal(actual.RightName, expectedRightName.ToString()); + } + + [Fact] + public async Task ACLProcessor_ProcessACL_Self() + { + var expectedPrincipalType = Label.Group; + var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + var expectedRightName = EdgeNames.AddSelf; + + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var mockRule = new Mock(MockBehavior.Loose, null); + var collection = new List(); + mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); + mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); + mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.Self); + mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.WriteMember)); + collection.Add(mockRule.Object); + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + .ReturnsAsync((true, new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Helpers.B64ToBytes(AddMemberSecurityDescriptor); + var result = await processor.ProcessACL(bytes, _testDomainName, Label.Group, false).ToArrayAsync(); + + Assert.Single(result); + var actual = result.First(); + Assert.Equal(actual.PrincipalType, expectedPrincipalType); + Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); + Assert.Equal(actual.IsInherited, false); + Assert.Equal(actual.RightName, expectedRightName); + } + + [Fact] + public async Task ACLProcessor_ProcessACL_ExtendedRight_Domain_Unmatched() + { + var expectedPrincipalType = Label.Group; + var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var mockRule = new Mock(MockBehavior.Loose, null); + var collection = new List(); + mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); + mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); + mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); + mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.WriteMember)); + collection.Add(mockRule.Object); + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + .ReturnsAsync((true, new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + var result = await processor.ProcessACL(bytes, _testDomainName, Label.Domain, false).ToArrayAsync(); + + Assert.Empty(result); + } + + [Fact] + public async Task ACLProcessor_ProcessACL_ExtendedRight_Domain_DSReplicationGetChanges() + { + var expectedPrincipalType = Label.Group; + var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + var expectedRightName = EdgeNames.GetChanges; + + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var mockRule = new Mock(MockBehavior.Loose, null); + var collection = new List(); + mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); + mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); + mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); + mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.DSReplicationGetChanges)); + collection.Add(mockRule.Object); + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + .ReturnsAsync((true,new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + var result = await processor.ProcessACL(bytes, _testDomainName, Label.Domain, false).ToArrayAsync(); + + Assert.Single(result); + var actual = result.First(); + Assert.Equal(actual.PrincipalType, expectedPrincipalType); + Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); + Assert.Equal(actual.IsInherited, false); + Assert.Equal(actual.RightName, expectedRightName); + } + + [Fact] + public async Task ACLProcessor_ProcessACL_ExtendedRight_Domain_All() + { + var expectedPrincipalType = Label.Group; + var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + var expectedRightName = EdgeNames.AllExtendedRights; + + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var mockRule = new Mock(MockBehavior.Loose, null); + var collection = new List(); + mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); + mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); + mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); + mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); + collection.Add(mockRule.Object); + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + .ReturnsAsync((true, new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + var result = await processor.ProcessACL(bytes, _testDomainName, Label.Domain, false).ToArrayAsync(); + + Assert.Single(result); + var actual = result.First(); + Assert.Equal(actual.PrincipalType, expectedPrincipalType); + Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); + Assert.Equal(actual.IsInherited, false); + Assert.Equal(actual.RightName, expectedRightName); + } + + [Fact] + public async Task ACLProcessor_ProcessACL_ExtendedRight_Domain_DSReplicationGetChangesAll() + { + var expectedPrincipalType = Label.Group; + var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + var expectedRightName = EdgeNames.GetChangesAll; + + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var mockRule = new Mock(MockBehavior.Loose, null); + var collection = new List(); + mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); + mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); + mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); + mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.DSReplicationGetChangesAll)); + collection.Add(mockRule.Object); + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + .ReturnsAsync((true, new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + var result = await processor.ProcessACL(bytes, _testDomainName, Label.Domain, false).ToArrayAsync(); + + Assert.Single(result); + var actual = result.First(); + Assert.Equal(actual.PrincipalType, expectedPrincipalType); + Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); + Assert.Equal(actual.IsInherited, false); + Assert.Equal(actual.RightName, expectedRightName); + } + + [Fact] + public async Task ACLProcessor_ProcessACL_ExtendedRight_User_Unmatched() + { + var expectedPrincipalType = Label.Group; + var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + var expectedRightName = EdgeNames.GetChangesAll; + var unmatchedGuid = new Guid("583991c8-629d-4a07-8a70-74d19d22ac9c"); + + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var mockRule = new Mock(MockBehavior.Loose, null); + var collection = new List(); + mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); + mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); + mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); + mockRule.Setup(x => x.ObjectType()).Returns(unmatchedGuid); + collection.Add(mockRule.Object); + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + .ReturnsAsync((true,new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + var result = await processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArrayAsync(); + + Assert.Empty(result); + } + + [Fact] + public async Task ACLProcessor_ProcessACL_ExtendedRight_User_UserForceChangePassword() + { + var expectedPrincipalType = Label.Group; + var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + var expectedRightName = EdgeNames.ForceChangePassword; + + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var mockRule = new Mock(MockBehavior.Loose, null); + var collection = new List(); + mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); + mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); + mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); + mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.UserForceChangePassword)); + collection.Add(mockRule.Object); + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + .ReturnsAsync((true,new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + var result = await processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArrayAsync(); + + Assert.Single(result); + var actual = result.First(); + Assert.Equal(actual.PrincipalType, expectedPrincipalType); + Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); + Assert.Equal(actual.IsInherited, false); + Assert.Equal(actual.RightName, expectedRightName); + } + + [Fact] + public async Task ACLProcessor_ProcessACL_ExtendedRight_User_All() + { + var expectedPrincipalType = Label.Group; + var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + var expectedRightName = EdgeNames.AllExtendedRights; + + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var mockRule = new Mock(MockBehavior.Loose, null); + var collection = new List(); + mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); + mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); + mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); + mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); + collection.Add(mockRule.Object); + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + .ReturnsAsync((true,new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + var result = await processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArrayAsync(); + + Assert.Single(result); + var actual = result.First(); + Assert.Equal(actual.PrincipalType, expectedPrincipalType); + Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); + Assert.Equal(actual.IsInherited, false); + Assert.Equal(actual.RightName, expectedRightName); + } + + [Fact] + public async Task ACLProcessor_ProcessACL_ExtendedRight_Computer_NoLAPS() + { + var expectedPrincipalType = Label.Group; + var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + var expectedRightName = EdgeNames.AllExtendedRights; + + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var mockRule = new Mock(MockBehavior.Loose, null); + var collection = new List(); + mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); + mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); + mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); + mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); + collection.Add(mockRule.Object); + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + .ReturnsAsync((true, new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + var result = await processor.ProcessACL(bytes, _testDomainName, Label.Computer, false).ToArrayAsync(); + + Assert.Empty(result); + } + + [Fact] + public async Task ACLProcessor_ProcessACL_ExtendedRight_Computer_All() + { + var expectedPrincipalType = Label.Group; + var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + var expectedRightName = EdgeNames.AllExtendedRights; + + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var mockRule = new Mock(MockBehavior.Loose, null); + var collection = new List(); + mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); + mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); + mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); + mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); + collection.Add(mockRule.Object); + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + .ReturnsAsync((true,new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + var result = await processor.ProcessACL(bytes, _testDomainName, Label.Computer, true).ToArrayAsync(); + + Assert.Single(result); + var actual = result.First(); + Assert.Equal(actual.PrincipalType, expectedPrincipalType); + Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); + Assert.Equal(actual.IsInherited, false); + Assert.Equal(actual.RightName, expectedRightName); + } + + [Fact(Skip = "Need to populate cache to reach this case")] + public async Task ACLProcessor_ProcessACL_ExtendedRight_Computer_MappedGuid() + { + } + + [Fact] + public async Task ACLProcessor_ProcessACL_GenericWrite_Unmatched() + { + var expectedPrincipalType = Label.Group; + var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + var expectedRightName = EdgeNames.AllExtendedRights; + + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var mockRule = new Mock(MockBehavior.Loose, null); + var collection = new List(); + mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); + mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); + mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.GenericWrite); + mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); + collection.Add(mockRule.Object); + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + .ReturnsAsync((true,new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + var result = await processor.ProcessACL(bytes, _testDomainName, Label.Container, true).ToArrayAsync(); + + Assert.Empty(result); + } + + [Fact] + public async Task ACLProcessor_ProcessACL_GenericWrite_User_All() + { + var expectedPrincipalType = Label.Group; + var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + var expectedRightName = EdgeNames.GenericWrite; + + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var mockRule = new Mock(MockBehavior.Loose, null); + var collection = new List(); + mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); + mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); + mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.GenericWrite); + mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); + collection.Add(mockRule.Object); + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + .ReturnsAsync((true,new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + var result = await processor.ProcessACL(bytes, _testDomainName, Label.User, true).ToArrayAsync(); + + Assert.Single(result); + var actual = result.First(); + Assert.Equal(actual.PrincipalType, expectedPrincipalType); + Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); + Assert.Equal(actual.IsInherited, false); + Assert.Equal(actual.RightName, expectedRightName); + } + + [Fact] + public async Task ACLProcessor_ProcessACL_GenericWrite_User_WriteMember() + { + var expectedPrincipalType = Label.Group; + var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + var expectedRightName = EdgeNames.AddMember; + + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var mockRule = new Mock(MockBehavior.Loose, null); + var collection = new List(); + mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); + mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); + mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.GenericWrite); + mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.WriteMember)); + collection.Add(mockRule.Object); + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + .ReturnsAsync((true,new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Helpers.B64ToBytes(AddMemberSecurityDescriptor); + var result = await processor.ProcessACL(bytes, _testDomainName, Label.Group, true).ToArrayAsync(); + + _testOutputHelper.WriteLine(JsonConvert.SerializeObject(result)); + + Assert.Single(result); + var actual = result.First(); + Assert.Equal(actual.PrincipalType, expectedPrincipalType); + Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); + Assert.Equal(actual.IsInherited, false); + Assert.Equal(actual.RightName, expectedRightName); + } + + [Fact] + public async Task ACLProcessor_ProcessACL_GenericWrite_Computer_WriteAllowedToAct() + { + var expectedPrincipalType = Label.Group; + var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + var expectedRightName = EdgeNames.AddAllowedToAct; + + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var mockRule = new Mock(MockBehavior.Loose, null); + var collection = new List(); + mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); + mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); + mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.GenericWrite); + mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.WriteAllowedToAct)); + collection.Add(mockRule.Object); + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + .ReturnsAsync((true,new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + var result = await processor.ProcessACL(bytes, _testDomainName, Label.Computer, true).ToArrayAsync(); + + Assert.Single(result); + var actual = result.First(); + Assert.Equal(actual.PrincipalType, expectedPrincipalType); + Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); + Assert.Equal(actual.IsInherited, false); + Assert.Equal(actual.RightName, expectedRightName); + } } } \ No newline at end of file From b346631f9e00c62d05ce07eaf1852a1ecdc33385 Mon Sep 17 00:00:00 2001 From: rvazarkar Date: Tue, 9 Jul 2024 12:47:15 -0400 Subject: [PATCH 41/68] chore: fix test --- test/unit/ACLProcessorTest.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/unit/ACLProcessorTest.cs b/test/unit/ACLProcessorTest.cs index 4501631a..b9a7128a 100644 --- a/test/unit/ACLProcessorTest.cs +++ b/test/unit/ACLProcessorTest.cs @@ -3,6 +3,7 @@ using System.DirectoryServices; using System.Linq; using System.Security.AccessControl; +using System.Threading; using System.Threading.Tasks; using CommonLibTest.Facades; using Moq; @@ -65,6 +66,9 @@ public async Task ACLProcessor_TestKnownDataAddMember() { var mockLdapUtils = new MockLDAPUtils(); var mockUtils = new Mock(); + var mockData = new[] { LdapResult.Fail() }; + mockUtils.Setup(x => x.Query(It.IsAny(), It.IsAny())) + .Returns(mockData.ToAsyncEnumerable()); mockUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) .Returns((string a, string b) => mockLdapUtils.ResolveIDAndType(a, b)); var sd = new ActiveDirectorySecurityDescriptor(new ActiveDirectorySecurity()); From 8ec0cae55d9550c5059138dc1cf22d158b1cce2d Mon Sep 17 00:00:00 2001 From: rvazarkar Date: Tue, 9 Jul 2024 12:53:55 -0400 Subject: [PATCH 42/68] chore: some pragma fixes --- test/unit/ACLProcessorTest.cs | 40 +++++++++++++++--------------- test/unit/Facades/MockLDAPUtils.cs | 4 +-- 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/test/unit/ACLProcessorTest.cs b/test/unit/ACLProcessorTest.cs index b9a7128a..f65880f8 100644 --- a/test/unit/ACLProcessorTest.cs +++ b/test/unit/ACLProcessorTest.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.DirectoryServices; using System.Linq; using System.Security.AccessControl; @@ -17,6 +18,7 @@ namespace CommonLibTest { + [SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")] public class ACLProcessorTest : IDisposable { private const string ProtectedUserNTSecurityDescriptor = @@ -165,9 +167,8 @@ public async Task ACLProcessor_ProcessGMSAReaders_Null_ACE() { var mockLDAPUtils = new Mock(); var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - var collection = new List(); - collection.Add(null); - + var collection = new List { null }; + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(collection); mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); @@ -258,7 +259,7 @@ public async Task ACLProcessor_ProcessACL_Yields_Owns_ACE() var actual = result.First(); Assert.Equal(actual.PrincipalSID, expectedSID); Assert.Equal(actual.PrincipalType, expectedPrincipalType); - Assert.Equal(actual.IsInherited, false); + Assert.False(actual.IsInherited); Assert.Equal(actual.RightName, EdgeNames.Owns); } @@ -286,9 +287,8 @@ public async Task ACLProcessor_ProcessACL_Null_ACE() { var mockLDAPUtils = new Mock(); var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - var collection = new List(); - collection.Add(null); - + var collection = new List { null }; + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(collection); mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); @@ -434,7 +434,7 @@ public async Task ACLProcessor_ProcessACL_GenericAll() var actual = result.First(); Assert.Equal(actual.PrincipalType, expectedPrincipalType); Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); - Assert.Equal(actual.IsInherited, false); + Assert.False(actual.IsInherited); Assert.Equal(actual.RightName, EdgeNames.GenericAll); } @@ -471,7 +471,7 @@ public async Task ACLProcessor_ProcessACL_WriteDacl() var actual = result.First(); Assert.Equal(actual.PrincipalType, expectedPrincipalType); Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); - Assert.Equal(actual.IsInherited, false); + Assert.False(actual.IsInherited); Assert.Equal(actual.RightName, expectedRightName.ToString()); } @@ -508,7 +508,7 @@ public async Task ACLProcessor_ProcessACL_WriteOwner() var actual = result.First(); Assert.Equal(actual.PrincipalType, expectedPrincipalType); Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); - Assert.Equal(actual.IsInherited, false); + Assert.False(actual.IsInherited); Assert.Equal(actual.RightName, expectedRightName.ToString()); } @@ -545,7 +545,7 @@ public async Task ACLProcessor_ProcessACL_Self() var actual = result.First(); Assert.Equal(actual.PrincipalType, expectedPrincipalType); Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); - Assert.Equal(actual.IsInherited, false); + Assert.False(actual.IsInherited); Assert.Equal(actual.RightName, expectedRightName); } @@ -613,7 +613,7 @@ public async Task ACLProcessor_ProcessACL_ExtendedRight_Domain_DSReplicationGetC var actual = result.First(); Assert.Equal(actual.PrincipalType, expectedPrincipalType); Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); - Assert.Equal(actual.IsInherited, false); + Assert.False(actual.IsInherited); Assert.Equal(actual.RightName, expectedRightName); } @@ -650,7 +650,7 @@ public async Task ACLProcessor_ProcessACL_ExtendedRight_Domain_All() var actual = result.First(); Assert.Equal(actual.PrincipalType, expectedPrincipalType); Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); - Assert.Equal(actual.IsInherited, false); + Assert.False(actual.IsInherited); Assert.Equal(actual.RightName, expectedRightName); } @@ -687,7 +687,7 @@ public async Task ACLProcessor_ProcessACL_ExtendedRight_Domain_DSReplicationGetC var actual = result.First(); Assert.Equal(actual.PrincipalType, expectedPrincipalType); Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); - Assert.Equal(actual.IsInherited, false); + Assert.False(actual.IsInherited); Assert.Equal(actual.RightName, expectedRightName); } @@ -757,7 +757,7 @@ public async Task ACLProcessor_ProcessACL_ExtendedRight_User_UserForceChangePass var actual = result.First(); Assert.Equal(actual.PrincipalType, expectedPrincipalType); Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); - Assert.Equal(actual.IsInherited, false); + Assert.False(actual.IsInherited); Assert.Equal(actual.RightName, expectedRightName); } @@ -794,7 +794,7 @@ public async Task ACLProcessor_ProcessACL_ExtendedRight_User_All() var actual = result.First(); Assert.Equal(actual.PrincipalType, expectedPrincipalType); Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); - Assert.Equal(actual.IsInherited, false); + Assert.False(actual.IsInherited); Assert.Equal(actual.RightName, expectedRightName); } @@ -863,7 +863,7 @@ public async Task ACLProcessor_ProcessACL_ExtendedRight_Computer_All() var actual = result.First(); Assert.Equal(actual.PrincipalType, expectedPrincipalType); Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); - Assert.Equal(actual.IsInherited, false); + Assert.False(actual.IsInherited); Assert.Equal(actual.RightName, expectedRightName); } @@ -937,7 +937,7 @@ public async Task ACLProcessor_ProcessACL_GenericWrite_User_All() var actual = result.First(); Assert.Equal(actual.PrincipalType, expectedPrincipalType); Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); - Assert.Equal(actual.IsInherited, false); + Assert.False(actual.IsInherited); Assert.Equal(actual.RightName, expectedRightName); } @@ -976,7 +976,7 @@ public async Task ACLProcessor_ProcessACL_GenericWrite_User_WriteMember() var actual = result.First(); Assert.Equal(actual.PrincipalType, expectedPrincipalType); Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); - Assert.Equal(actual.IsInherited, false); + Assert.False(actual.IsInherited); Assert.Equal(actual.RightName, expectedRightName); } @@ -1013,7 +1013,7 @@ public async Task ACLProcessor_ProcessACL_GenericWrite_Computer_WriteAllowedToAc var actual = result.First(); Assert.Equal(actual.PrincipalType, expectedPrincipalType); Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); - Assert.Equal(actual.IsInherited, false); + Assert.False(actual.IsInherited); Assert.Equal(actual.RightName, expectedRightName); } } diff --git a/test/unit/Facades/MockLDAPUtils.cs b/test/unit/Facades/MockLDAPUtils.cs index 749f02cb..2fc741d3 100644 --- a/test/unit/Facades/MockLDAPUtils.cs +++ b/test/unit/Facades/MockLDAPUtils.cs @@ -1,9 +1,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.DirectoryServices.ActiveDirectory; -using System.DirectoryServices.Protocols; using System.Linq; using System.Security.Principal; using System.Threading; @@ -13,8 +11,8 @@ using SharpHoundCommonLib; using SharpHoundCommonLib.Enums; using SharpHoundCommonLib.OutputTypes; -using SharpHoundRPC.Wrappers; using Domain = System.DirectoryServices.ActiveDirectory.Domain; +#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously namespace CommonLibTest.Facades { From fbc281b543335e07284d9d0d894718cd04a936c3 Mon Sep 17 00:00:00 2001 From: rvazarkar Date: Tue, 9 Jul 2024 12:59:00 -0400 Subject: [PATCH 43/68] chore: more mock fixes --- test/unit/ACLProcessorTest.cs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/test/unit/ACLProcessorTest.cs b/test/unit/ACLProcessorTest.cs index f65880f8..843968f0 100644 --- a/test/unit/ACLProcessorTest.cs +++ b/test/unit/ACLProcessorTest.cs @@ -251,6 +251,10 @@ public async Task ACLProcessor_ProcessACL_Yields_Owns_ACE() mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) .ReturnsAsync((true, new TypedPrincipal(expectedSID, expectedPrincipalType))); + var mockData = new[] { LdapResult.Fail() }; + mockLDAPUtils.Setup(x => x.Query(It.IsAny(), It.IsAny())) + .Returns(mockData.ToAsyncEnumerable()); + var processor = new ACLProcessor(mockLDAPUtils.Object); var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); var result = await processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArrayAsync(); @@ -394,6 +398,9 @@ public async Task ACLProcessor_ProcessACL_GenericAll_Unmatched_Guid() mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) .ReturnsAsync((true,new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); + var mockData = new[] { LdapResult.Fail() }; + mockLDAPUtils.Setup(x => x.Query(It.IsAny(), It.IsAny())) + .Returns(mockData.ToAsyncEnumerable()); var processor = new ACLProcessor(mockLDAPUtils.Object); var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); @@ -425,6 +432,9 @@ public async Task ACLProcessor_ProcessACL_GenericAll() mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) .ReturnsAsync((true, new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); + var mockData = new[] { LdapResult.Fail() }; + mockLDAPUtils.Setup(x => x.Query(It.IsAny(), It.IsAny())) + .Returns(mockData.ToAsyncEnumerable()); var processor = new ACLProcessor(mockLDAPUtils.Object); var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); @@ -462,6 +472,9 @@ public async Task ACLProcessor_ProcessACL_WriteDacl() mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) .ReturnsAsync((true, new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); + var mockData = new[] { LdapResult.Fail() }; + mockLDAPUtils.Setup(x => x.Query(It.IsAny(), It.IsAny())) + .Returns(mockData.ToAsyncEnumerable()); var processor = new ACLProcessor(mockLDAPUtils.Object); var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); @@ -499,6 +512,9 @@ public async Task ACLProcessor_ProcessACL_WriteOwner() mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) .ReturnsAsync((true, new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); + var mockData = new[] { LdapResult.Fail() }; + mockLDAPUtils.Setup(x => x.Query(It.IsAny(), It.IsAny())) + .Returns(mockData.ToAsyncEnumerable()); var processor = new ACLProcessor(mockLDAPUtils.Object); var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); @@ -536,6 +552,9 @@ public async Task ACLProcessor_ProcessACL_Self() mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) .ReturnsAsync((true, new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); + var mockData = new[] { LdapResult.Fail() }; + mockLDAPUtils.Setup(x => x.Query(It.IsAny(), It.IsAny())) + .Returns(mockData.ToAsyncEnumerable()); var processor = new ACLProcessor(mockLDAPUtils.Object); var bytes = Helpers.B64ToBytes(AddMemberSecurityDescriptor); @@ -572,6 +591,9 @@ public async Task ACLProcessor_ProcessACL_ExtendedRight_Domain_Unmatched() mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) .ReturnsAsync((true, new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); + var mockData = new[] { LdapResult.Fail() }; + mockLDAPUtils.Setup(x => x.Query(It.IsAny(), It.IsAny())) + .Returns(mockData.ToAsyncEnumerable()); var processor = new ACLProcessor(mockLDAPUtils.Object); var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); @@ -678,6 +700,9 @@ public async Task ACLProcessor_ProcessACL_ExtendedRight_Domain_DSReplicationGetC mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) .ReturnsAsync((true, new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); + var mockData = new[] { LdapResult.Fail() }; + mockLDAPUtils.Setup(x => x.Query(It.IsAny(), It.IsAny())) + .Returns(mockData.ToAsyncEnumerable()); var processor = new ACLProcessor(mockLDAPUtils.Object); var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); From c7f61699028ad03b11ee78c07baaa6ee40d0eb6a Mon Sep 17 00:00:00 2001 From: rvazarkar Date: Tue, 9 Jul 2024 13:40:34 -0400 Subject: [PATCH 44/68] wip: fix computer session tests, mark as available on any platform --- test/unit/CertAbuseProcessorTest.cs | 1 + test/unit/ComputerSessionProcessorTest.cs | 462 +++++++++++----------- test/unit/Facades/MockLDAPUtils.cs | 6 +- 3 files changed, 235 insertions(+), 234 deletions(-) diff --git a/test/unit/CertAbuseProcessorTest.cs b/test/unit/CertAbuseProcessorTest.cs index 6ecf8a0c..4b025297 100644 --- a/test/unit/CertAbuseProcessorTest.cs +++ b/test/unit/CertAbuseProcessorTest.cs @@ -11,6 +11,7 @@ namespace CommonLibTest { + //TODO: Make these tests work public class CertAbuseProcessorTest : IDisposable { private const string CASecurityFixture = diff --git a/test/unit/ComputerSessionProcessorTest.cs b/test/unit/ComputerSessionProcessorTest.cs index 4ea1003b..ec860c83 100644 --- a/test/unit/ComputerSessionProcessorTest.cs +++ b/test/unit/ComputerSessionProcessorTest.cs @@ -1,231 +1,231 @@ -// using System; -// using System.Threading.Tasks; -// using CommonLibTest.Facades; -// using Moq; -// using Newtonsoft.Json; -// using SharpHoundCommonLib; -// using SharpHoundCommonLib.OutputTypes; -// using SharpHoundCommonLib.Processors; -// using SharpHoundRPC.NetAPINative; -// using Xunit; -// using Xunit.Abstractions; -// -// namespace CommonLibTest -// { -// public class ComputerSessionProcessorTest : IDisposable -// { -// private readonly string _computerDomain; -// private readonly string _computerSid; -// private readonly ITestOutputHelper _testOutputHelper; -// -// public ComputerSessionProcessorTest(ITestOutputHelper testOutputHelper) -// { -// _testOutputHelper = testOutputHelper; -// _computerDomain = "TESTLAB.LOCAL"; -// _computerSid = "S-1-5-21-3130019616-2776909439-2417379446-1104"; -// } -// -// #region IDispose Implementation -// -// public void Dispose() -// { -// // Tear down (called once per test) -// } -// -// #endregion -// -// [WindowsOnlyFact] -// public async Task ComputerSessionProcessor_ReadUserSessions_FilteringWorks() -// { -// var mockNativeMethods = new Mock(); -// -// var apiResult = new NetSessionEnumResults[] -// { -// new("dfm", "\\\\192.168.92.110"), -// new("admin", ""), -// new("admin", "\\\\192.168.92.110") -// }; -// mockNativeMethods.Setup(x => x.NetSessionEnum(It.IsAny())).Returns(apiResult); -// -// var processor = new ComputerSessionProcessor(new MockLDAPUtils(), "dfm", mockNativeMethods.Object); -// var result = await processor.ReadUserSessions("win10", _computerSid, _computerDomain); -// Assert.True(result.Collected); -// Assert.Empty(result.Results); -// } -// -// [WindowsOnlyFact] -// public async Task ComputerSessionProcessor_ReadUserSessions_ResolvesHost() -// { -// var mockNativeMethods = new Mock(); -// var apiResult = new NetSessionEnumResults[] -// { -// new("admin", "\\\\192.168.1.1") -// }; -// mockNativeMethods.Setup(x => x.NetSessionEnum(It.IsAny())).Returns(apiResult); -// -// var expected = new Session[] -// { -// new() -// { -// ComputerSID = "S-1-5-21-3130019616-2776909439-2417379446-1104", -// UserSID = "S-1-5-21-3130019616-2776909439-2417379446-2116" -// } -// }; -// -// var processor = new ComputerSessionProcessor(new MockLDAPUtils(), "dfm", mockNativeMethods.Object); -// var result = await processor.ReadUserSessions("win10", _computerSid, _computerDomain); -// Assert.True(result.Collected); -// Assert.Equal(expected, result.Results); -// } -// -// [WindowsOnlyFact] -// public async Task ComputerSessionProcessor_ReadUserSessions_ResolvesLocalHostEquivalent() -// { -// var mockNativeMethods = new Mock(); -// var apiResult = new NetSessionEnumResults[] -// { -// new("admin", "\\\\127.0.0.1") -// }; -// mockNativeMethods.Setup(x => x.NetSessionEnum(It.IsAny())).Returns(apiResult); -// -// var expected = new Session[] -// { -// new() -// { -// ComputerSID = _computerSid, -// UserSID = "S-1-5-21-3130019616-2776909439-2417379446-2116" -// } -// }; -// -// var processor = new ComputerSessionProcessor(new MockLDAPUtils(), "dfm", mockNativeMethods.Object); -// var result = await processor.ReadUserSessions("win10", _computerSid, _computerDomain); -// Assert.True(result.Collected); -// Assert.Equal(expected, result.Results); -// } -// -// [WindowsOnlyFact] -// public async Task ComputerSessionProcessor_ReadUserSessions_MultipleMatches_AddsAll() -// { -// var mockNativeMethods = new Mock(); -// var apiResult = new NetSessionEnumResults[] -// { -// new("administrator", "\\\\127.0.0.1") -// }; -// mockNativeMethods.Setup(x => x.NetSessionEnum(It.IsAny())).Returns(apiResult); -// -// var expected = new Session[] -// { -// new() -// { -// ComputerSID = _computerSid, -// UserSID = "S-1-5-21-3130019616-2776909439-2417379446-500" -// }, -// new() -// { -// ComputerSID = _computerSid, -// UserSID = "S-1-5-21-3084884204-958224920-2707782874-500" -// } -// }; -// -// var processor = new ComputerSessionProcessor(new MockLDAPUtils(), "dfm", mockNativeMethods.Object); -// var result = await processor.ReadUserSessions("win10", _computerSid, _computerDomain); -// Assert.True(result.Collected); -// Assert.Equal(expected, result.Results); -// } -// -// [WindowsOnlyFact] -// public async Task ComputerSessionProcessor_ReadUserSessions_NoGCMatch_TriesResolve() -// { -// var mockNativeMethods = new Mock(); -// var apiResult = new NetSessionEnumResults[] -// { -// new("test", "\\\\127.0.0.1") -// }; -// mockNativeMethods.Setup(x => x.NetSessionEnum(It.IsAny())).Returns(apiResult); -// -// var expected = new Session[] -// { -// new() -// { -// ComputerSID = _computerSid, -// UserSID = "S-1-5-21-3130019616-2776909439-2417379446-1106" -// } -// }; -// -// var processor = new ComputerSessionProcessor(new MockLDAPUtils(), "dfm", mockNativeMethods.Object); -// var result = await processor.ReadUserSessions("win10", _computerSid, _computerDomain); -// Assert.True(result.Collected); -// Assert.Equal(expected, result.Results); -// } -// -// [WindowsOnlyFact] -// public async Task ComputerSessionProcessor_ReadUserSessions_ComputerAccessDenied_Handled() -// { -// var mockNativeMethods = new Mock(); -// //mockNativeMethods.Setup(x => x.CallSamConnect(ref It.Ref.IsAny, out It.Ref.IsAny, It.IsAny(), ref It.Ref.IsAny)).Returns(NativeMethods.NtStatus.StatusAccessDenied); -// mockNativeMethods.Setup(x => x.NetSessionEnum(It.IsAny())) -// .Returns(NetAPIEnums.NetAPIStatus.ErrorAccessDenied); -// var processor = new ComputerSessionProcessor(new MockLDAPUtils(), "dfm", mockNativeMethods.Object); -// var test = await processor.ReadUserSessions("test", "test", "test"); -// Assert.False(test.Collected); -// Assert.Equal(NetAPIEnums.NetAPIStatus.ErrorAccessDenied.ToString(), test.FailureReason); -// } -// -// [WindowsOnlyFact] -// public async Task ComputerSessionProcessor_ReadUserSessionsPrivileged_ComputerAccessDenied_ExceptionCaught() -// { -// var mockNativeMethods = new Mock(); -// //mockNativeMethods.Setup(x => x.CallSamConnect(ref It.Ref.IsAny, out It.Ref.IsAny, It.IsAny(), ref It.Ref.IsAny)).Returns(NativeMethods.NtStatus.StatusAccessDenied); -// mockNativeMethods.Setup(x => x.NetWkstaUserEnum(It.IsAny())) -// .Returns(NetAPIEnums.NetAPIStatus.ErrorAccessDenied); -// var processor = new ComputerSessionProcessor(new MockLDAPUtils(), "dfm", mockNativeMethods.Object); -// var test = await processor.ReadUserSessionsPrivileged("test", "test", "test"); -// Assert.False(test.Collected); -// Assert.Equal(NetAPIEnums.NetAPIStatus.ErrorAccessDenied.ToString(), test.FailureReason); -// } -// -// [WindowsOnlyFact] -// public async Task ComputerSessionProcessor_ReadUserSessionsPrivileged_FilteringWorks() -// { -// var mockNativeMethods = new Mock(); -// const string samAccountName = "WIN10"; -// -// //This is a sample response from a computer in a test environment. The duplicates are intentional -// var apiResults = new NetWkstaUserEnumResults[] -// { -// new("dfm", "TESTLAB"), -// new("Administrator", "PRIMARY"), -// new("Administrator", ""), -// new("WIN10$", "TESTLAB"), -// new("WIN10$", "TESTLAB"), -// new("WIN10$", "TESTLAB"), -// new("WIN10$", "TESTLAB"), -// new("JOHN", "WIN10"), -// new("SYSTEM", "NT AUTHORITY"), -// new("ABC", "TESTLAB") -// }; -// mockNativeMethods.Setup(x => x.NetWkstaUserEnum(It.IsAny())).Returns(apiResults); -// -// var expected = new Session[] -// { -// new() -// { -// ComputerSID = _computerSid, -// UserSID = "S-1-5-21-3130019616-2776909439-2417379446-1105" -// }, -// new() -// { -// ComputerSID = _computerSid, -// UserSID = "S-1-5-21-3130019616-2776909439-2417379446-500" -// } -// }; -// -// var processor = new ComputerSessionProcessor(new MockLDAPUtils(), nativeMethods: mockNativeMethods.Object); -// var test = await processor.ReadUserSessionsPrivileged("WIN10.TESTLAB.LOCAL", samAccountName, _computerSid); -// Assert.True(test.Collected); -// _testOutputHelper.WriteLine(JsonConvert.SerializeObject(test.Results)); -// Assert.Equal(2, test.Results.Length); -// Assert.Equal(expected, test.Results); -// } -// } -// } \ No newline at end of file +using System; +using System.Threading.Tasks; +using CommonLibTest.Facades; +using Moq; +using Newtonsoft.Json; +using SharpHoundCommonLib; +using SharpHoundCommonLib.OutputTypes; +using SharpHoundCommonLib.Processors; +using SharpHoundRPC.NetAPINative; +using Xunit; +using Xunit.Abstractions; + +namespace CommonLibTest +{ + public class ComputerSessionProcessorTest : IDisposable + { + private readonly string _computerDomain; + private readonly string _computerSid; + private readonly ITestOutputHelper _testOutputHelper; + + public ComputerSessionProcessorTest(ITestOutputHelper testOutputHelper) + { + _testOutputHelper = testOutputHelper; + _computerDomain = "TESTLAB.LOCAL"; + _computerSid = "S-1-5-21-3130019616-2776909439-2417379446-1104"; + } + + #region IDispose Implementation + + public void Dispose() + { + // Tear down (called once per test) + } + + #endregion + + [Fact] + public async Task ComputerSessionProcessor_ReadUserSessions_FilteringWorks() + { + var mockNativeMethods = new Mock(); + + var apiResult = new NetSessionEnumResults[] + { + new("dfm", "\\\\192.168.92.110"), + new("admin", ""), + new("admin", "\\\\192.168.92.110") + }; + mockNativeMethods.Setup(x => x.NetSessionEnum(It.IsAny())).Returns(apiResult); + + var processor = new ComputerSessionProcessor(new MockLDAPUtils(), "dfm", mockNativeMethods.Object); + var result = await processor.ReadUserSessions("win10", _computerSid, _computerDomain); + Assert.True(result.Collected); + Assert.Empty(result.Results); + } + + [Fact] + public async Task ComputerSessionProcessor_ReadUserSessions_ResolvesHost() + { + var mockNativeMethods = new Mock(); + var apiResult = new NetSessionEnumResults[] + { + new("admin", "\\\\192.168.1.1") + }; + mockNativeMethods.Setup(x => x.NetSessionEnum(It.IsAny())).Returns(apiResult); + + var expected = new Session[] + { + new() + { + ComputerSID = "S-1-5-21-3130019616-2776909439-2417379446-1104", + UserSID = "S-1-5-21-3130019616-2776909439-2417379446-2116" + } + }; + + var processor = new ComputerSessionProcessor(new MockLDAPUtils(), "dfm", mockNativeMethods.Object); + var result = await processor.ReadUserSessions("win10", _computerSid, _computerDomain); + Assert.True(result.Collected); + Assert.Equal(expected, result.Results); + } + + [Fact] + public async Task ComputerSessionProcessor_ReadUserSessions_ResolvesLocalHostEquivalent() + { + var mockNativeMethods = new Mock(); + var apiResult = new NetSessionEnumResults[] + { + new("admin", "\\\\127.0.0.1") + }; + mockNativeMethods.Setup(x => x.NetSessionEnum(It.IsAny())).Returns(apiResult); + + var expected = new Session[] + { + new() + { + ComputerSID = _computerSid, + UserSID = "S-1-5-21-3130019616-2776909439-2417379446-2116" + } + }; + + var processor = new ComputerSessionProcessor(new MockLDAPUtils(), "dfm", mockNativeMethods.Object); + var result = await processor.ReadUserSessions("win10", _computerSid, _computerDomain); + Assert.True(result.Collected); + Assert.Equal(expected, result.Results); + } + + [Fact] + public async Task ComputerSessionProcessor_ReadUserSessions_MultipleMatches_AddsAll() + { + var mockNativeMethods = new Mock(); + var apiResult = new NetSessionEnumResults[] + { + new("administrator", "\\\\127.0.0.1") + }; + mockNativeMethods.Setup(x => x.NetSessionEnum(It.IsAny())).Returns(apiResult); + + var expected = new Session[] + { + new() + { + ComputerSID = _computerSid, + UserSID = "S-1-5-21-3130019616-2776909439-2417379446-500" + }, + new() + { + ComputerSID = _computerSid, + UserSID = "S-1-5-21-3084884204-958224920-2707782874-500" + } + }; + + var processor = new ComputerSessionProcessor(new MockLDAPUtils(), "dfm", mockNativeMethods.Object); + var result = await processor.ReadUserSessions("win10", _computerSid, _computerDomain); + Assert.True(result.Collected); + Assert.Equal(expected, result.Results); + } + + [Fact] + public async Task ComputerSessionProcessor_ReadUserSessions_NoGCMatch_TriesResolve() + { + var mockNativeMethods = new Mock(); + var apiResult = new NetSessionEnumResults[] + { + new("test", "\\\\127.0.0.1") + }; + mockNativeMethods.Setup(x => x.NetSessionEnum(It.IsAny())).Returns(apiResult); + + var expected = new Session[] + { + new() + { + ComputerSID = _computerSid, + UserSID = "S-1-5-21-3130019616-2776909439-2417379446-1106" + } + }; + + var processor = new ComputerSessionProcessor(new MockLDAPUtils(), "dfm", mockNativeMethods.Object); + var result = await processor.ReadUserSessions("win10", _computerSid, _computerDomain); + Assert.True(result.Collected); + Assert.Equal(expected, result.Results); + } + + [Fact] + public async Task ComputerSessionProcessor_ReadUserSessions_ComputerAccessDenied_Handled() + { + var mockNativeMethods = new Mock(); + //mockNativeMethods.Setup(x => x.CallSamConnect(ref It.Ref.IsAny, out It.Ref.IsAny, It.IsAny(), ref It.Ref.IsAny)).Returns(NativeMethods.NtStatus.StatusAccessDenied); + mockNativeMethods.Setup(x => x.NetSessionEnum(It.IsAny())) + .Returns(NetAPIEnums.NetAPIStatus.ErrorAccessDenied); + var processor = new ComputerSessionProcessor(new MockLDAPUtils(), "dfm", mockNativeMethods.Object); + var test = await processor.ReadUserSessions("test", "test", "test"); + Assert.False(test.Collected); + Assert.Equal(NetAPIEnums.NetAPIStatus.ErrorAccessDenied.ToString(), test.FailureReason); + } + + [Fact] + public async Task ComputerSessionProcessor_ReadUserSessionsPrivileged_ComputerAccessDenied_ExceptionCaught() + { + var mockNativeMethods = new Mock(); + //mockNativeMethods.Setup(x => x.CallSamConnect(ref It.Ref.IsAny, out It.Ref.IsAny, It.IsAny(), ref It.Ref.IsAny)).Returns(NativeMethods.NtStatus.StatusAccessDenied); + mockNativeMethods.Setup(x => x.NetWkstaUserEnum(It.IsAny())) + .Returns(NetAPIEnums.NetAPIStatus.ErrorAccessDenied); + var processor = new ComputerSessionProcessor(new MockLDAPUtils(), "dfm", mockNativeMethods.Object); + var test = await processor.ReadUserSessionsPrivileged("test", "test", "test"); + Assert.False(test.Collected); + Assert.Equal(NetAPIEnums.NetAPIStatus.ErrorAccessDenied.ToString(), test.FailureReason); + } + + [Fact] + public async Task ComputerSessionProcessor_ReadUserSessionsPrivileged_FilteringWorks() + { + var mockNativeMethods = new Mock(); + const string samAccountName = "WIN10"; + + //This is a sample response from a computer in a test environment. The duplicates are intentional + var apiResults = new NetWkstaUserEnumResults[] + { + new("dfm", "TESTLAB"), + new("Administrator", "PRIMARY"), + new("Administrator", ""), + new("WIN10$", "TESTLAB"), + new("WIN10$", "TESTLAB"), + new("WIN10$", "TESTLAB"), + new("WIN10$", "TESTLAB"), + new("JOHN", "WIN10"), + new("SYSTEM", "NT AUTHORITY"), + new("ABC", "TESTLAB") + }; + mockNativeMethods.Setup(x => x.NetWkstaUserEnum(It.IsAny())).Returns(apiResults); + + var expected = new Session[] + { + new() + { + ComputerSID = _computerSid, + UserSID = "S-1-5-21-3130019616-2776909439-2417379446-1105" + }, + new() + { + ComputerSID = _computerSid, + UserSID = "S-1-5-21-3130019616-2776909439-2417379446-500" + } + }; + + var processor = new ComputerSessionProcessor(new MockLDAPUtils(), nativeMethods: mockNativeMethods.Object, currentUserName:"ADMINISTRATOR"); + var test = await processor.ReadUserSessionsPrivileged("WIN10.TESTLAB.LOCAL", samAccountName, _computerSid); + Assert.True(test.Collected); + _testOutputHelper.WriteLine(JsonConvert.SerializeObject(test.Results)); + Assert.Equal(2, test.Results.Length); + Assert.Equal(expected, test.Results); + } + } +} \ No newline at end of file diff --git a/test/unit/Facades/MockLDAPUtils.cs b/test/unit/Facades/MockLDAPUtils.cs index 2fc741d3..d56f6a36 100644 --- a/test/unit/Facades/MockLDAPUtils.cs +++ b/test/unit/Facades/MockLDAPUtils.cs @@ -696,15 +696,15 @@ public bool GetDomain(out Domain domain) { } public async Task<(bool Success, TypedPrincipal Principal)> ResolveAccountName(string name, string domain) { - return (true, name.ToUpper() switch - { + var res = name.ToUpper() switch { "ADMINISTRATOR" => new TypedPrincipal( "S-1-5-21-3130019616-2776909439-2417379446-500", Label.User), "DFM" => new TypedPrincipal( "S-1-5-21-3130019616-2776909439-2417379446-1105", Label.User), "TEST" => new TypedPrincipal("S-1-5-21-3130019616-2776909439-2417379446-1106", Label.User), _ => null - }); + }; + return (res != null, res); } public async Task<(bool Success, string SecurityIdentifier)> ResolveHostToSid(string host, string domain) { From 9f9e96e894fc8b074a19e2a71dc16b0c65226c8b Mon Sep 17 00:00:00 2001 From: rvazarkar Date: Tue, 9 Jul 2024 13:46:36 -0400 Subject: [PATCH 45/68] wip: fix container tests --- test/unit/ContainerProcessorTest.cs | 329 ++++++++++++++-------------- test/unit/Facades/MockLDAPUtils.cs | 2 +- 2 files changed, 168 insertions(+), 163 deletions(-) diff --git a/test/unit/ContainerProcessorTest.cs b/test/unit/ContainerProcessorTest.cs index 7fe91990..9a67c0cf 100644 --- a/test/unit/ContainerProcessorTest.cs +++ b/test/unit/ContainerProcessorTest.cs @@ -1,162 +1,167 @@ -// using System; -// using System.DirectoryServices.Protocols; -// using System.Linq; -// using CommonLibTest.Facades; -// using Moq; -// using SharpHoundCommonLib.Enums; -// using SharpHoundCommonLib.OutputTypes; -// using SharpHoundCommonLib.Processors; -// using Xunit; -// using Xunit.Abstractions; -// -// namespace CommonLibTest -// { -// public class ContainerProcessorTest : IDisposable -// { -// private readonly string _testGpLinkString; -// private readonly ITestOutputHelper _testOutputHelper; -// -// public ContainerProcessorTest(ITestOutputHelper testOutputHelper) -// { -// _testOutputHelper = testOutputHelper; -// _testGpLinkString = -// "[LDAP://cn={94DD0260-38B5-497E-8876-10E7A96E80D0},cn=policies,cn=system,DC=testlab,DC=local;0][LDAP://cn={C52F168C-CD05-4487-B405-564934DA8EFF},cn=policies,cn=system,DC=testlab,DC=local;0][LDAP://cn={1E860A30-603A-45C7-A768-26EE74BE6D5D},cn=policies,cn=system,DC=testlab,DC=local;0]"; -// } -// -// public void Dispose() -// { -// } -// -// [Fact] -// public void ContainerProcessor_ReadContainerGPLinks_IgnoresNull() -// { -// var processor = new ContainerProcessor(new MockLDAPUtils()); -// var test = processor.ReadContainerGPLinks(null); -// Assert.Empty(test); -// } -// -// [Fact] -// public void ContainerProcessor_ReadContainerGPLinks_UnresolvedGPLink_IsIgnored() -// { -// var processor = new ContainerProcessor(new MockLDAPUtils()); -// //GPLink that doesn't exist -// const string s = -// "[LDAP://cn={94DD0260-38B5-497E-8876-ABCDEFG},cn=policies,cn=system,DC=testlab,DC=local;0]"; -// var test = processor.ReadContainerGPLinks(s); -// Assert.Empty(test); -// } -// -// [Fact] -// public void ContainerProcessor_ReadContainerGPLinks_ReturnsCorrectValues() -// { -// var processor = new ContainerProcessor(new MockLDAPUtils()); -// var test = processor.ReadContainerGPLinks(_testGpLinkString).ToArray(); -// -// var expected = new GPLink[] -// { -// new() -// { -// GUID = "B39818AF-6349-401A-AE0A-E4972F5BF6D9", -// IsEnforced = false -// }, -// new() -// { -// GUID = "ACDD64D3-67B3-401F-A6CC-804B3F7B1533", -// IsEnforced = false -// }, -// new() -// { -// GUID = "C45E9585-4932-4C03-91A8-1856869D49AF", -// IsEnforced = false -// } -// }; -// -// Assert.Equal(3, test.Length); -// Assert.Equal(expected, test); -// } -// -// [Fact] -// public void ContainerProcessor_GetContainerChildObjects_ReturnsCorrectData() -// { -// var mock = new Mock(); -// -// var searchResults = new MockSearchResultEntry[] -// { -// //These first 4 should be filtered by our DN filters -// new( -// "CN=7868d4c8-ac41-4e05-b401-776280e8e9f1,CN=Operations,CN=DomainUpdates,CN=System,DC=testlab,DC=local" -// , null, null, Label.Base), -// new("CN=Microsoft,CN=Program Data,DC=testlab,DC=local", null, null, Label.Base), -// new("CN=Operations,CN=DomainUpdates,CN=System,DC=testlab,DC=local", null, null, Label.Base), -// new("CN=User,CN={C52F168C-CD05-4487-B405-564934DA8EFF},CN=Policies,CN=System,DC=testlab,DC=local", null, -// null, Label.Base), -// //This is a real object in our mock -// new("CN=Users,DC=testlab,DC=local", null, "ECAD920E-8EB1-4E31-A80E-DD36367F81F4", Label.Container), -// //This object does not exist in our mock -// new("CN=Users,DC=testlab,DC=local", null, "ECAD920E-8EB1-4E31-A80E-DD36367F81FD", Label.Container), -// //Test null objectid -// new("CN=Users,DC=testlab,DC=local", null, null, Label.Container) -// }; -// -// mock.Setup(x => x.QueryLDAP(It.IsAny(), It.IsAny(), It.IsAny(), -// It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), -// It.IsAny(), It.IsAny())).Returns(searchResults); -// -// var processor = new ContainerProcessor(mock.Object); -// var test = processor.GetContainerChildObjects(_testGpLinkString).ToArray(); -// -// var expected = new TypedPrincipal[] -// { -// new() -// { -// ObjectIdentifier = "ECAD920E-8EB1-4E31-A80E-DD36367F81F4", -// ObjectType = Label.Container -// } -// }; -// -// Assert.Single(test); -// Assert.Equal(expected, test); -// } -// -// [Fact] -// public void ContainerProcessor_ReadBlocksInheritance_ReturnsCorrectValues() -// { -// var test = ContainerProcessor.ReadBlocksInheritance(null); -// var test2 = ContainerProcessor.ReadBlocksInheritance("3"); -// var test3 = ContainerProcessor.ReadBlocksInheritance("1"); -// -// Assert.False(test); -// Assert.False(test2); -// Assert.True(test3); -// } -// -// [Fact] -// public void ContainerProcessor_GetContainingObject_ExpectedResult() -// { -// var utils = new MockLDAPUtils(); -// var proc = new ContainerProcessor(utils); -// -// var result = proc.GetContainingObject("OU=TESTOU,DC=TESTLAB,DC=LOCAL"); -// Assert.Equal(Label.Domain, result.ObjectType); -// Assert.Equal("S-1-5-21-3130019616-2776909439-2417379446", result.ObjectIdentifier); -// -// result = proc.GetContainingObject("CN=PRIMARY,OU=DOMAIN CONTROLLERS,DC=TESTLAB,DC=LOCAL"); -// Assert.Equal(Label.OU, result.ObjectType); -// Assert.Equal("0DE400CD-2FF3-46E0-8A26-2C917B403C65", result.ObjectIdentifier); -// -// result = proc.GetContainingObject("CN=ADMINISTRATORS,CN=BUILTIN,DC=TESTLAB,DC=LOCAL"); -// Assert.Equal(Label.Domain, result.ObjectType); -// Assert.Equal("S-1-5-21-3130019616-2776909439-2417379446", result.ObjectIdentifier); -// } -// -// [Fact] -// public void ContainerProcessor_GetContainingObject_BadDN_ReturnsNull() -// { -// var utils = new MockLDAPUtils(); -// var proc = new ContainerProcessor(utils); -// -// var result = proc.GetContainingObject("abc123"); -// Assert.Equal(null, result); -// } -// } -// } \ No newline at end of file +using System; +using System.DirectoryServices.Protocols; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using CommonLibTest.Facades; +using Moq; +using SharpHoundCommonLib; +using SharpHoundCommonLib.Enums; +using SharpHoundCommonLib.OutputTypes; +using SharpHoundCommonLib.Processors; +using Xunit; +using Xunit.Abstractions; + +namespace CommonLibTest +{ + public class ContainerProcessorTest : IDisposable + { + private readonly string _testGpLinkString; + private readonly ITestOutputHelper _testOutputHelper; + + public ContainerProcessorTest(ITestOutputHelper testOutputHelper) + { + _testOutputHelper = testOutputHelper; + _testGpLinkString = + "[LDAP://cn={94DD0260-38B5-497E-8876-10E7A96E80D0},cn=policies,cn=system,DC=testlab,DC=local;0][LDAP://cn={C52F168C-CD05-4487-B405-564934DA8EFF},cn=policies,cn=system,DC=testlab,DC=local;0][LDAP://cn={1E860A30-603A-45C7-A768-26EE74BE6D5D},cn=policies,cn=system,DC=testlab,DC=local;0]"; + } + + public void Dispose() + { + } + + [Fact] + public async Task ContainerProcessor_ReadContainerGPLinks_IgnoresNull() + { + var processor = new ContainerProcessor(new MockLDAPUtils()); + var test = await processor.ReadContainerGPLinks(null).ToArrayAsync(); + Assert.Empty(test); + } + + [Fact] + public async Task ContainerProcessor_ReadContainerGPLinks_UnresolvedGPLink_IsIgnored() + { + var processor = new ContainerProcessor(new MockLDAPUtils()); + //GPLink that doesn't exist + const string s = + "[LDAP://cn={94DD0260-38B5-497E-8876-ABCDEFG},cn=policies,cn=system,DC=testlab,DC=local;0]"; + var test = await processor.ReadContainerGPLinks(s).ToArrayAsync(); + Assert.Empty(test); + } + + [Fact] + public async Task ContainerProcessor_ReadContainerGPLinks_ReturnsCorrectValues() + { + var processor = new ContainerProcessor(new MockLDAPUtils()); + var test = await processor.ReadContainerGPLinks(_testGpLinkString).ToArrayAsync(); + + var expected = new GPLink[] + { + new() + { + GUID = "B39818AF-6349-401A-AE0A-E4972F5BF6D9", + IsEnforced = false + }, + new() + { + GUID = "ACDD64D3-67B3-401F-A6CC-804B3F7B1533", + IsEnforced = false + }, + new() + { + GUID = "C45E9585-4932-4C03-91A8-1856869D49AF", + IsEnforced = false + } + }; + + Assert.Equal(3, test.Length); + Assert.Equal(expected, test); + } + + [Fact] + public async Task ContainerProcessor_GetContainerChildObjects_ReturnsCorrectData() + { + var mock = new Mock(); + + var searchResults = new[] + { + //These first 4 should be filtered by our DN filters + LdapResult.Ok(new MockSearchResultEntry( + "CN=7868d4c8-ac41-4e05-b401-776280e8e9f1,CN=Operations,CN=DomainUpdates,CN=System,DC=testlab,DC=local" + , null, null, Label.Base)), + LdapResult.Ok(new MockSearchResultEntry("CN=Microsoft,CN=Program Data,DC=testlab,DC=local", null, null, Label.Base)), + LdapResult.Ok(new MockSearchResultEntry("CN=Operations,CN=DomainUpdates,CN=System,DC=testlab,DC=local", null, null, Label.Base)), + LdapResult.Ok(new MockSearchResultEntry("CN=User,CN={C52F168C-CD05-4487-B405-564934DA8EFF},CN=Policies,CN=System,DC=testlab,DC=local", null, + null, Label.Base)), + //This is a real object in our mock + LdapResult.Ok(new MockSearchResultEntry("CN=Users,DC=testlab,DC=local", null, "ECAD920E-8EB1-4E31-A80E-DD36367F81F4", Label.Container)), + //This object does not exist in our mock + LdapResult.Ok(new MockSearchResultEntry("CN=Users,DC=testlab,DC=local", null, "ECAD920E-8EB1-4E31-A80E-DD36367F81FD", Label.Container)), + //Test null objectid + LdapResult.Ok(new MockSearchResultEntry("CN=Users,DC=testlab,DC=local", null, null, Label.Container)) + }; + + mock.Setup(x => x.Query(It.IsAny(), It.IsAny())).Returns(searchResults.ToAsyncEnumerable); + + var processor = new ContainerProcessor(mock.Object); + var test = await processor.GetContainerChildObjects(_testGpLinkString).ToArrayAsync(); + + var expected = new TypedPrincipal[] + { + new() + { + ObjectIdentifier = "ECAD920E-8EB1-4E31-A80E-DD36367F81F4", + ObjectType = Label.Container + } + }; + + Assert.Equal(expected, test); + Assert.Single(test); + + } + + [Fact] + public async Task ContainerProcessor_ReadBlocksInheritance_ReturnsCorrectValues() + { + var test = ContainerProcessor.ReadBlocksInheritance(null); + var test2 = ContainerProcessor.ReadBlocksInheritance("3"); + var test3 = ContainerProcessor.ReadBlocksInheritance("1"); + + Assert.False(test); + Assert.False(test2); + Assert.True(test3); + } + + [Fact] + public async Task ContainerProcessor_GetContainingObject_ExpectedResult() + { + var utils = new MockLDAPUtils(); + var proc = new ContainerProcessor(utils); + + var (success, result) = await proc.GetContainingObject("OU=TESTOU,DC=TESTLAB,DC=LOCAL"); + Assert.Equal(Label.Domain, result.ObjectType); + Assert.Equal("S-1-5-21-3130019616-2776909439-2417379446", result.ObjectIdentifier); + Assert.True(success); + + (success, result) = await proc.GetContainingObject("CN=PRIMARY,OU=DOMAIN CONTROLLERS,DC=TESTLAB,DC=LOCAL"); + Assert.Equal(Label.OU, result.ObjectType); + Assert.Equal("0DE400CD-2FF3-46E0-8A26-2C917B403C65", result.ObjectIdentifier); + Assert.True(success); + + (success, result) = await proc.GetContainingObject("CN=ADMINISTRATORS,CN=BUILTIN,DC=TESTLAB,DC=LOCAL"); + Assert.Equal(Label.Domain, result.ObjectType); + Assert.Equal("S-1-5-21-3130019616-2776909439-2417379446", result.ObjectIdentifier); + Assert.True(success); + } + + [Fact] + public async Task ContainerProcessor_GetContainingObject_BadDN_ReturnsNull() + { + var utils = new MockLDAPUtils(); + var proc = new ContainerProcessor(utils); + + var (success, result) = await proc.GetContainingObject("abc123"); + Assert.False(success); + } + } +} \ No newline at end of file diff --git a/test/unit/Facades/MockLDAPUtils.cs b/test/unit/Facades/MockLDAPUtils.cs index d56f6a36..c13e3fbd 100644 --- a/test/unit/Facades/MockLDAPUtils.cs +++ b/test/unit/Facades/MockLDAPUtils.cs @@ -664,7 +664,7 @@ public virtual IAsyncEnumerable> RangedRetrieval(string distingui _ => null }; - return (true, principal); + return (principal != null, principal); } public async Task<(bool Success, TypedPrincipal WellKnownPrincipal)> GetWellKnownPrincipal(string securityIdentifier, string objectDomain) { From 2b84698ac7e6e07cf79ce4beedb9870b194b336f Mon Sep 17 00:00:00 2001 From: rvazarkar Date: Tue, 9 Jul 2024 13:49:21 -0400 Subject: [PATCH 46/68] wip: fix domain trust tests --- test/unit/DomainTrustProcessorTest.cs | 257 +++++++++++++------------- 1 file changed, 128 insertions(+), 129 deletions(-) diff --git a/test/unit/DomainTrustProcessorTest.cs b/test/unit/DomainTrustProcessorTest.cs index f5997190..ba9641af 100644 --- a/test/unit/DomainTrustProcessorTest.cs +++ b/test/unit/DomainTrustProcessorTest.cs @@ -1,129 +1,128 @@ -// using System; -// using System.Collections.Generic; -// using System.DirectoryServices.Protocols; -// using System.Linq; -// using CommonLibTest.Facades; -// using Moq; -// using SharpHoundCommonLib.Enums; -// using SharpHoundCommonLib.Processors; -// using Xunit; -// using Xunit.Abstractions; -// -// namespace CommonLibTest -// { -// public class DomainTrustProcessorTest -// { -// private ITestOutputHelper _testOutputHelper; -// -// public DomainTrustProcessorTest(ITestOutputHelper testOutputHelper) -// { -// _testOutputHelper = testOutputHelper; -// } -// -// [WindowsOnlyFact] -// public void DomainTrustProcessor_EnumerateDomainTrusts_HappyPath() -// { -// var mockUtils = new Mock(); -// var searchResults = new[] -// { -// new MockSearchResultEntry("CN\u003dexternal.local,CN\u003dSystem,DC\u003dtestlab,DC\u003dlocal", -// new Dictionary -// { -// {"trustdirection", "3"}, -// {"trusttype", "2"}, -// {"trustattributes", 0x24.ToString()}, -// {"cn", "external.local"}, -// {"securityidentifier", Helpers.B64ToBytes("AQQAAAAAAAUVAAAA7JjftxhaHTnafGWh")} -// }, "", Label.Domain) -// }; -// -// mockUtils.Setup(x => x.QueryLDAP(It.IsAny(), It.IsAny(), It.IsAny(), -// It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), -// It.IsAny(), It.IsAny())).Returns(searchResults); -// var processor = new DomainTrustProcessor(mockUtils.Object); -// var test = processor.EnumerateDomainTrusts("testlab.local").ToArray(); -// Assert.Single(test); -// var trust = test.First(); -// Assert.Equal(TrustDirection.Bidirectional, trust.TrustDirection); -// Assert.Equal("EXTERNAL.LOCAL", trust.TargetDomainName); -// Assert.Equal("S-1-5-21-3084884204-958224920-2707782874", trust.TargetDomainSid); -// Assert.True(trust.IsTransitive); -// Assert.Equal(TrustType.ParentChild, trust.TrustType); -// Assert.True(trust.SidFilteringEnabled); -// } -// -// [Fact] -// public void DomainTrustProcessor_EnumerateDomainTrusts_SadPaths() -// { -// var mockUtils = new Mock(); -// var searchResults = new[] -// { -// new MockSearchResultEntry("CN\u003dexternal.local,CN\u003dSystem,DC\u003dtestlab,DC\u003dlocal", -// new Dictionary -// { -// {"trustdirection", "3"}, -// {"trusttype", "2"}, -// {"trustattributes", 0x24.ToString()}, -// {"cn", "external.local"}, -// {"securityIdentifier", Array.Empty()} -// }, "", Label.Domain), -// new MockSearchResultEntry("CN\u003dexternal.local,CN\u003dSystem,DC\u003dtestlab,DC\u003dlocal", -// new Dictionary -// { -// {"trustdirection", "3"}, -// {"trusttype", "2"}, -// {"trustattributes", 0x24.ToString()}, -// {"cn", "external.local"}, -// {"securityIdentifier", Helpers.B64ToBytes("QQQAAAAAAAUVAAAA7JjftxhaHTnafGWh")} -// }, "", Label.Domain), -// new MockSearchResultEntry("CN\u003dexternal.local,CN\u003dSystem,DC\u003dtestlab,DC\u003dlocal", -// new Dictionary -// { -// {"trusttype", "2"}, -// {"trustattributes", 0x24.ToString()}, -// {"cn", "external.local"}, -// {"securityIdentifier", Helpers.B64ToBytes("AQQAAAAAAAUVAAAA7JjftxhaHTnafGWh")} -// }, "", Label.Domain), -// new MockSearchResultEntry("CN\u003dexternal.local,CN\u003dSystem,DC\u003dtestlab,DC\u003dlocal", -// new Dictionary -// { -// {"trustdirection", "3"}, -// {"trusttype", "2"}, -// {"cn", "external.local"}, -// {"securityIdentifier", Helpers.B64ToBytes("AQQAAAAAAAUVAAAA7JjftxhaHTnafGWh")} -// }, "", Label.Domain) -// }; -// -// mockUtils.Setup(x => x.QueryLDAP(It.IsAny(), It.IsAny(), It.IsAny(), -// It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), -// It.IsAny(), It.IsAny())).Returns(searchResults); -// var processor = new DomainTrustProcessor(mockUtils.Object); -// var test = processor.EnumerateDomainTrusts("testlab.local"); -// Assert.Empty(test); -// } -// -// [Fact] -// public void DomainTrustProcessor_TrustAttributesToType() -// { -// var attrib = TrustAttributes.WithinForest; -// var test = DomainTrustProcessor.TrustAttributesToType(attrib); -// Assert.Equal(TrustType.ParentChild, test); -// -// attrib = TrustAttributes.ForestTransitive; -// test = DomainTrustProcessor.TrustAttributesToType(attrib); -// Assert.Equal(TrustType.Forest, test); -// -// attrib = TrustAttributes.TreatAsExternal; -// test = DomainTrustProcessor.TrustAttributesToType(attrib); -// Assert.Equal(TrustType.External, test); -// -// attrib = TrustAttributes.CrossOrganization; -// test = DomainTrustProcessor.TrustAttributesToType(attrib); -// Assert.Equal(TrustType.External, test); -// -// attrib = TrustAttributes.FilterSids; -// test = DomainTrustProcessor.TrustAttributesToType(attrib); -// Assert.Equal(TrustType.External, test); -// } -// } -// } \ No newline at end of file +using System; +using System.Collections.Generic; +using System.DirectoryServices.Protocols; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using CommonLibTest.Facades; +using Moq; +using SharpHoundCommonLib; +using SharpHoundCommonLib.Enums; +using SharpHoundCommonLib.Processors; +using Xunit; +using Xunit.Abstractions; + +namespace CommonLibTest +{ + public class DomainTrustProcessorTest + { + private ITestOutputHelper _testOutputHelper; + + public DomainTrustProcessorTest(ITestOutputHelper testOutputHelper) + { + _testOutputHelper = testOutputHelper; + } + + [WindowsOnlyFact] + public async Task DomainTrustProcessor_EnumerateDomainTrusts_HappyPath() + { + var mockUtils = new Mock(); + var searchResults = new[] + { + LdapResult.Ok(new MockSearchResultEntry("CN\u003dexternal.local,CN\u003dSystem,DC\u003dtestlab,DC\u003dlocal", + new Dictionary + { + {"trustdirection", "3"}, + {"trusttype", "2"}, + {"trustattributes", 0x24.ToString()}, + {"cn", "external.local"}, + {"securityidentifier", Helpers.B64ToBytes("AQQAAAAAAAUVAAAA7JjftxhaHTnafGWh")} + }, "", Label.Domain)) + }; + + mockUtils.Setup(x => x.Query(It.IsAny(), It.IsAny())).Returns(searchResults.ToAsyncEnumerable); + var processor = new DomainTrustProcessor(mockUtils.Object); + var test = await processor.EnumerateDomainTrusts("testlab.local").ToArrayAsync(); + Assert.Single(test); + var trust = test.First(); + Assert.Equal(TrustDirection.Bidirectional, trust.TrustDirection); + Assert.Equal("EXTERNAL.LOCAL", trust.TargetDomainName); + Assert.Equal("S-1-5-21-3084884204-958224920-2707782874", trust.TargetDomainSid); + Assert.True(trust.IsTransitive); + Assert.Equal(TrustType.ParentChild, trust.TrustType); + Assert.True(trust.SidFilteringEnabled); + } + + [Fact] + public async Task DomainTrustProcessor_EnumerateDomainTrusts_SadPaths() + { + var mockUtils = new Mock(); + var searchResults = new[] + { + LdapResult.Ok(new MockSearchResultEntry("CN\u003dexternal.local,CN\u003dSystem,DC\u003dtestlab,DC\u003dlocal", + new Dictionary + { + {"trustdirection", "3"}, + {"trusttype", "2"}, + {"trustattributes", 0x24.ToString()}, + {"cn", "external.local"}, + {"securityIdentifier", Array.Empty()} + }, "", Label.Domain)), + LdapResult.Ok(new MockSearchResultEntry("CN\u003dexternal.local,CN\u003dSystem,DC\u003dtestlab,DC\u003dlocal", + new Dictionary + { + {"trustdirection", "3"}, + {"trusttype", "2"}, + {"trustattributes", 0x24.ToString()}, + {"cn", "external.local"}, + {"securityIdentifier", Helpers.B64ToBytes("QQQAAAAAAAUVAAAA7JjftxhaHTnafGWh")} + }, "", Label.Domain)), + LdapResult.Ok(new MockSearchResultEntry("CN\u003dexternal.local,CN\u003dSystem,DC\u003dtestlab,DC\u003dlocal", + new Dictionary + { + {"trusttype", "2"}, + {"trustattributes", 0x24.ToString()}, + {"cn", "external.local"}, + {"securityIdentifier", Helpers.B64ToBytes("AQQAAAAAAAUVAAAA7JjftxhaHTnafGWh")} + }, "", Label.Domain)), + LdapResult.Ok(new MockSearchResultEntry("CN\u003dexternal.local,CN\u003dSystem,DC\u003dtestlab,DC\u003dlocal", + new Dictionary + { + {"trustdirection", "3"}, + {"trusttype", "2"}, + {"cn", "external.local"}, + {"securityIdentifier", Helpers.B64ToBytes("AQQAAAAAAAUVAAAA7JjftxhaHTnafGWh")} + }, "", Label.Domain)) + }; + + mockUtils.Setup(x => x.Query(It.IsAny(), It.IsAny())).Returns(searchResults.ToAsyncEnumerable); + var processor = new DomainTrustProcessor(mockUtils.Object); + var test = await processor.EnumerateDomainTrusts("testlab.local").ToArrayAsync(); + Assert.Empty(test); + } + + [Fact] + public void DomainTrustProcessor_TrustAttributesToType() + { + var attrib = TrustAttributes.WithinForest; + var test = DomainTrustProcessor.TrustAttributesToType(attrib); + Assert.Equal(TrustType.ParentChild, test); + + attrib = TrustAttributes.ForestTransitive; + test = DomainTrustProcessor.TrustAttributesToType(attrib); + Assert.Equal(TrustType.Forest, test); + + attrib = TrustAttributes.TreatAsExternal; + test = DomainTrustProcessor.TrustAttributesToType(attrib); + Assert.Equal(TrustType.External, test); + + attrib = TrustAttributes.CrossOrganization; + test = DomainTrustProcessor.TrustAttributesToType(attrib); + Assert.Equal(TrustType.External, test); + + attrib = TrustAttributes.FilterSids; + test = DomainTrustProcessor.TrustAttributesToType(attrib); + Assert.Equal(TrustType.External, test); + } + } +} \ No newline at end of file From d680705c44c741200e788d190971196b7079bb17 Mon Sep 17 00:00:00 2001 From: anemeth Date: Tue, 9 Jul 2024 11:07:38 -0700 Subject: [PATCH 47/68] Bringing tests back online WIP --- test/unit/LocalGroupProcessorTest.cs | 224 +++++++++--------- test/unit/SPNProcessorsTest.cs | 214 ++++++++--------- test/unit/SearchResultEntryTests.cs | 72 +++--- .../unit/UserRightsAssignmentProcessorTest.cs | 132 +++++------ 4 files changed, 323 insertions(+), 319 deletions(-) diff --git a/test/unit/LocalGroupProcessorTest.cs b/test/unit/LocalGroupProcessorTest.cs index d0725a05..57f5d88a 100644 --- a/test/unit/LocalGroupProcessorTest.cs +++ b/test/unit/LocalGroupProcessorTest.cs @@ -1,110 +1,114 @@ -// using System; -// using System.Linq; -// using System.Threading.Tasks; -// using CommonLibTest.Facades; -// using Moq; -// using SharpHoundCommonLib.Enums; -// using SharpHoundCommonLib.OutputTypes; -// using SharpHoundCommonLib.Processors; -// using Xunit; -// using Xunit.Abstractions; -// -// namespace CommonLibTest -// { -// public class LocalGroupProcessorTest : IDisposable -// { -// private readonly ITestOutputHelper _testOutputHelper; -// -// public LocalGroupProcessorTest(ITestOutputHelper testOutputHelper) -// { -// _testOutputHelper = testOutputHelper; -// } -// -// public void Dispose() -// { -// } -// -// [WindowsOnlyFact] -// public async Task LocalGroupProcessor_TestWorkstation() -// { -// var mockProcessor = new Mock(new MockLDAPUtils(), null); -// var mockSamServer = new MockWorkstationSAMServer(); -// mockProcessor.Setup(x => x.OpenSamServer(It.IsAny())).Returns(mockSamServer); -// var processor = mockProcessor.Object; -// var machineDomainSid = $"{Consts.MockWorkstationMachineSid}-1001"; -// var results = await processor.GetLocalGroups("win10.testlab.local", machineDomainSid, "TESTLAB.LOCAL", false) -// .ToArrayAsync(); -// -// Assert.Equal(3, results.Length); -// var adminGroup = results.First(x => x.ObjectIdentifier.EndsWith("-544")); -// Assert.Single(adminGroup.Results); -// Assert.Equal($"{machineDomainSid}-544", adminGroup.ObjectIdentifier); -// Assert.Equal("S-1-5-21-4243161961-3815211218-2888324771-512", adminGroup.Results[0].ObjectIdentifier); -// var rdpGroup = results.First(x => x.ObjectIdentifier.EndsWith("-555")); -// Assert.Equal(2, rdpGroup.Results.Length); -// Assert.Collection(rdpGroup.Results, -// principal => -// { -// Assert.Equal($"{machineDomainSid}-1003", principal.ObjectIdentifier); -// Assert.Equal(Label.LocalGroup, principal.ObjectType); -// -// }, principal => -// { -// Assert.Equal($"{machineDomainSid}-544", principal.ObjectIdentifier); -// Assert.Equal(Label.LocalGroup, principal.ObjectType); -// }); -// } -// -// [WindowsOnlyFact] -// public async Task LocalGroupProcessor_TestDomainController() -// { -// var mockProcessor = new Mock(new MockLDAPUtils(), null); -// var mockSamServer = new MockDCSAMServer(); -// mockProcessor.Setup(x => x.OpenSamServer(It.IsAny())).Returns(mockSamServer); -// var processor = mockProcessor.Object; -// var machineDomainSid = $"{Consts.MockWorkstationMachineSid}-1000"; -// var results = await processor.GetLocalGroups("primary.testlab.local", machineDomainSid, "TESTLAB.LOCAL", true) -// .ToArrayAsync(); -// -// Assert.Equal(2, results.Length); -// var adminGroup = results.First(x => x.ObjectIdentifier.EndsWith("-544")); -// Assert.Single(adminGroup.Results); -// Assert.Equal("TESTLAB.LOCAL-S-1-5-32-544", adminGroup.ObjectIdentifier); -// Assert.Equal("S-1-5-21-4243161961-3815211218-2888324771-512", adminGroup.Results[0].ObjectIdentifier); -// } -// -// [Fact] -// public async Task LocalGroupProcessor_ResolveGroupName_NonDC() -// { -// var mockUtils = new Mock(); -// var proc = new LocalGroupProcessor(mockUtils.Object); -// -// var result = TestPrivateMethod.InstanceMethod(proc, "ResolveGroupName", -// new object[] -// { -// "ADMINISTRATORS", "WIN10.TESTLAB.LOCAL", "S-1-5-32-123-123-500", "TESTLAB.LOCAL", 544, false, false -// }); -// -// Assert.Equal("ADMINISTRATORS@WIN10.TESTLAB.LOCAL", result.PrincipalName); -// ; -// Assert.Equal("S-1-5-32-123-123-500-544", result.ObjectId); -// } -// -// [Fact] -// public async Task LocalGroupProcessor_ResolveGroupName_DC() -// { -// var mockUtils = new Mock(); -// var proc = new LocalGroupProcessor(mockUtils.Object); -// -// var result = TestPrivateMethod.InstanceMethod(proc, "ResolveGroupName", -// new object[] -// { -// "ADMINISTRATORS", "PRIMARY.TESTLAB.LOCAL", "S-1-5-32-123-123-1000", "TESTLAB.LOCAL", 544, true, true -// }); -// -// Assert.Equal("IGNOREME", result.PrincipalName); -// ; -// Assert.Equal("TESTLAB.LOCAL-S-1-5-32-544", result.ObjectId); -// } -// } -// } \ No newline at end of file +using System; +using System.Linq; +using System.Threading.Tasks; +using CommonLibTest.Facades; +using Moq; +using SharpHoundCommonLib.Enums; +using SharpHoundCommonLib.OutputTypes; +using SharpHoundCommonLib.Processors; +using Xunit; +using Xunit.Abstractions; + +namespace CommonLibTest +{ + public class LocalGroupProcessorTest : IDisposable + { + private readonly ITestOutputHelper _testOutputHelper; + + public LocalGroupProcessorTest(ITestOutputHelper testOutputHelper) + { + _testOutputHelper = testOutputHelper; + } + + public void Dispose() + { + } + + [WindowsOnlyFact] + public async Task LocalGroupProcessor_TestWorkstation() + { + var mockProcessor = new Mock(new MockLDAPUtils(), null); + var mockSamServer = new MockWorkstationSAMServer(); + mockProcessor.Setup(x => x.OpenSamServer(It.IsAny())).Returns(mockSamServer); + var processor = mockProcessor.Object; + var machineDomainSid = $"{Consts.MockWorkstationMachineSid}-1001"; + var results = await processor.GetLocalGroups("win10.testlab.local", machineDomainSid, "TESTLAB.LOCAL", false) + .ToArrayAsync(); + + Assert.Equal(3, results.Length); + var adminGroup = results.First(x => x.ObjectIdentifier.EndsWith("-544")); + Assert.Single(adminGroup.Results); + Assert.Equal($"{machineDomainSid}-544", adminGroup.ObjectIdentifier); + Assert.Equal("S-1-5-21-4243161961-3815211218-2888324771-512", adminGroup.Results[0].ObjectIdentifier); + var rdpGroup = results.First(x => x.ObjectIdentifier.EndsWith("-555")); + Assert.Equal(2, rdpGroup.Results.Length); + Assert.Collection(rdpGroup.Results, + principal => + { + Assert.Equal($"{machineDomainSid}-1003", principal.ObjectIdentifier); + Assert.Equal(Label.LocalGroup, principal.ObjectType); + + }, principal => + { + Assert.Equal($"{machineDomainSid}-544", principal.ObjectIdentifier); + Assert.Equal(Label.LocalGroup, principal.ObjectType); + }); + } + + [WindowsOnlyFact] + public async Task LocalGroupProcessor_TestDomainController() + { + var mockProcessor = new Mock(new MockLDAPUtils(), null); + var mockSamServer = new MockDCSAMServer(); + mockProcessor.Setup(x => x.OpenSamServer(It.IsAny())).Returns(mockSamServer); + var processor = mockProcessor.Object; + var machineDomainSid = $"{Consts.MockWorkstationMachineSid}-1000"; + var results = await processor.GetLocalGroups("primary.testlab.local", machineDomainSid, "TESTLAB.LOCAL", true) + .ToArrayAsync(); + + Assert.Equal(2, results.Length); + var adminGroup = results.First(x => x.ObjectIdentifier.EndsWith("-544")); + Assert.Single(adminGroup.Results); + Assert.Equal("TESTLAB.LOCAL-S-1-5-32-544", adminGroup.ObjectIdentifier); + Assert.Equal("S-1-5-21-4243161961-3815211218-2888324771-512", adminGroup.Results[0].ObjectIdentifier); + } + + [Fact] + public async Task LocalGroupProcessor_ResolveGroupName_NonDC() + { + var mockUtils = new Mock(); + var proc = new LocalGroupProcessor(mockUtils.Object); + + var resultTask = TestPrivateMethod.InstanceMethod>(proc, "ResolveGroupName", + new object[] + { + "ADMINISTRATORS", "WIN10.TESTLAB.LOCAL", "S-1-5-32-123-123-500", "TESTLAB.LOCAL", 544, false, false + }); + + var result = await resultTask; + + Assert.Equal("ADMINISTRATORS@WIN10.TESTLAB.LOCAL", result.PrincipalName); + ; + Assert.Equal("S-1-5-32-123-123-500-544", result.ObjectId); + } + + [Fact] + public async Task LocalGroupProcessor_ResolveGroupName_DC() + { + var mockUtils = new Mock(); + var proc = new LocalGroupProcessor(mockUtils.Object); + + var resultTask = TestPrivateMethod.InstanceMethod>(proc, "ResolveGroupName", + new object[] + { + "ADMINISTRATORS", "PRIMARY.TESTLAB.LOCAL", "S-1-5-32-123-123-1000", "TESTLAB.LOCAL", 544, true, true + }); + + var result = await resultTask; + + Assert.Equal("IGNOREME", result.PrincipalName); + ; + Assert.Equal("TESTLAB.LOCAL-S-1-5-32-544", result.ObjectId); + } + } +} \ No newline at end of file diff --git a/test/unit/SPNProcessorsTest.cs b/test/unit/SPNProcessorsTest.cs index 03cde9bb..fa83f0e3 100644 --- a/test/unit/SPNProcessorsTest.cs +++ b/test/unit/SPNProcessorsTest.cs @@ -1,107 +1,107 @@ -// using System; -// using System.Threading.Tasks; -// using CommonLibTest.Facades; -// using SharpHoundCommonLib; -// using SharpHoundCommonLib.Enums; -// using SharpHoundCommonLib.OutputTypes; -// using SharpHoundCommonLib.Processors; -// using Xunit; -// -// namespace CommonLibTest -// { -// public class SPNProcessorsTest -// { -// [Fact] -// public async Task ReadSPNTargets_SPNLengthZero_YieldBreak() -// { -// var processor = new SPNProcessors(new MockLDAPUtils()); -// var servicePrincipalNames = Array.Empty(); -// const string distinguishedName = "cn=policies,cn=system,DC=testlab,DC=local"; -// await foreach (var spn in processor.ReadSPNTargets(servicePrincipalNames, distinguishedName)) -// Assert.Null(spn); -// } -// -// [Fact] -// public async Task ReadSPNTargets_NoPortSupplied_ParsedCorrectly() -// { -// var processor = new SPNProcessors(new MockLDAPUtils()); -// string[] servicePrincipalNames = {"MSSQLSvc/PRIMARY.TESTLAB.LOCAL"}; -// const string distinguishedName = "cn=policies,cn=system,DC=testlab,DC=local"; -// -// var expected = new SPNPrivilege -// { -// ComputerSID = "S-1-5-21-3130019616-2776909439-2417379446-1001", Port = 1433, -// Service = EdgeNames.SQLAdmin -// }; -// -// await foreach (var actual in processor.ReadSPNTargets(servicePrincipalNames, distinguishedName)) -// { -// Assert.Equal(expected.ComputerSID, actual.ComputerSID); -// Assert.Equal(expected.Port, actual.Port); -// Assert.Equal(expected.Service, actual.Service); -// } -// } -// -// [Fact] -// public async Task ReadSPNTargets_BadPortSupplied_ParsedCorrectly() -// { -// var processor = new SPNProcessors(new MockLDAPUtils()); -// string[] servicePrincipalNames = {"MSSQLSvc/PRIMARY.TESTLAB.LOCAL:abcd"}; -// const string distinguishedName = "cn=policies,cn=system,DC=testlab,DC=local"; -// -// var expected = new SPNPrivilege -// { -// ComputerSID = "S-1-5-21-3130019616-2776909439-2417379446-1001", Port = 1433, -// Service = EdgeNames.SQLAdmin -// }; -// -// await foreach (var actual in processor.ReadSPNTargets(servicePrincipalNames, distinguishedName)) -// { -// Assert.Equal(expected.ComputerSID, actual.ComputerSID); -// Assert.Equal(expected.Port, actual.Port); -// Assert.Equal(expected.Service, actual.Service); -// } -// } -// -// [Fact] -// public async void ReadSPNTargets_SuppliedPort_ParsedCorrectly() -// { -// var processor = new SPNProcessors(new MockLDAPUtils()); -// string[] servicePrincipalNames = {"MSSQLSvc/PRIMARY.TESTLAB.LOCAL:2345"}; -// const string distinguishedName = "cn=policies,cn=system,DC=testlab,DC=local"; -// -// var expected = new SPNPrivilege -// { -// ComputerSID = "S-1-5-21-3130019616-2776909439-2417379446-1001", Port = 2345, -// Service = EdgeNames.SQLAdmin -// }; -// -// await foreach (var actual in processor.ReadSPNTargets(servicePrincipalNames, distinguishedName)) -// { -// Assert.Equal(expected.ComputerSID, actual.ComputerSID); -// Assert.Equal(expected.Port, actual.Port); -// Assert.Equal(expected.Service, actual.Service); -// } -// } -// -// [Fact] -// public async void ReadSPNTargets_MissingMssqlSvc_NotRead() -// { -// var processor = new SPNProcessors(new MockLDAPUtils()); -// string[] servicePrincipalNames = {"myhost.redmond.microsoft.com:1433"}; -// const string distinguishedName = "CN=Jeff Smith,OU=Sales,DC=Fabrikam,DC=COM"; -// await foreach (var spn in processor.ReadSPNTargets(servicePrincipalNames, distinguishedName)) -// Assert.Null(spn); -// } -// -// [Fact] -// public async void ReadSPNTargets_SPNWithAddressSign_NotRead() -// { -// var processor = new SPNProcessors(new MockLDAPUtils()); -// string[] servicePrincipalNames = {"MSSQLSvc/myhost.redmond.microsoft.com:1433 user@domain"}; -// const string distinguishedName = "CN=Jeff Smith,OU=Sales,DC=Fabrikam,DC=COM"; -// await foreach (var spn in processor.ReadSPNTargets(servicePrincipalNames, distinguishedName)) -// Assert.Null(spn); -// } -// } -// } \ No newline at end of file +using System; +using System.Threading.Tasks; +using CommonLibTest.Facades; +using SharpHoundCommonLib; +using SharpHoundCommonLib.Enums; +using SharpHoundCommonLib.OutputTypes; +using SharpHoundCommonLib.Processors; +using Xunit; + +namespace CommonLibTest +{ + public class SPNProcessorsTest + { + [Fact] + public async Task ReadSPNTargets_SPNLengthZero_YieldBreak() + { + var processor = new SPNProcessors(new MockLDAPUtils()); + var servicePrincipalNames = Array.Empty(); + const string distinguishedName = "cn=policies,cn=system,DC=testlab,DC=local"; + await foreach (var spn in processor.ReadSPNTargets(servicePrincipalNames, distinguishedName)) + Assert.Null(spn); + } + + [Fact] + public async Task ReadSPNTargets_NoPortSupplied_ParsedCorrectly() + { + var processor = new SPNProcessors(new MockLDAPUtils()); + string[] servicePrincipalNames = {"MSSQLSvc/PRIMARY.TESTLAB.LOCAL"}; + const string distinguishedName = "cn=policies,cn=system,DC=testlab,DC=local"; + + var expected = new SPNPrivilege + { + ComputerSID = "S-1-5-21-3130019616-2776909439-2417379446-1001", Port = 1433, + Service = EdgeNames.SQLAdmin + }; + + await foreach (var actual in processor.ReadSPNTargets(servicePrincipalNames, distinguishedName)) + { + Assert.Equal(expected.ComputerSID, actual.ComputerSID); + Assert.Equal(expected.Port, actual.Port); + Assert.Equal(expected.Service, actual.Service); + } + } + + [Fact] + public async Task ReadSPNTargets_BadPortSupplied_ParsedCorrectly() + { + var processor = new SPNProcessors(new MockLDAPUtils()); + string[] servicePrincipalNames = {"MSSQLSvc/PRIMARY.TESTLAB.LOCAL:abcd"}; + const string distinguishedName = "cn=policies,cn=system,DC=testlab,DC=local"; + + var expected = new SPNPrivilege + { + ComputerSID = "S-1-5-21-3130019616-2776909439-2417379446-1001", Port = 1433, + Service = EdgeNames.SQLAdmin + }; + + await foreach (var actual in processor.ReadSPNTargets(servicePrincipalNames, distinguishedName)) + { + Assert.Equal(expected.ComputerSID, actual.ComputerSID); + Assert.Equal(expected.Port, actual.Port); + Assert.Equal(expected.Service, actual.Service); + } + } + + [Fact] + public async void ReadSPNTargets_SuppliedPort_ParsedCorrectly() + { + var processor = new SPNProcessors(new MockLDAPUtils()); + string[] servicePrincipalNames = {"MSSQLSvc/PRIMARY.TESTLAB.LOCAL:2345"}; + const string distinguishedName = "cn=policies,cn=system,DC=testlab,DC=local"; + + var expected = new SPNPrivilege + { + ComputerSID = "S-1-5-21-3130019616-2776909439-2417379446-1001", Port = 2345, + Service = EdgeNames.SQLAdmin + }; + + await foreach (var actual in processor.ReadSPNTargets(servicePrincipalNames, distinguishedName)) + { + Assert.Equal(expected.ComputerSID, actual.ComputerSID); + Assert.Equal(expected.Port, actual.Port); + Assert.Equal(expected.Service, actual.Service); + } + } + + [Fact] + public async void ReadSPNTargets_MissingMssqlSvc_NotRead() + { + var processor = new SPNProcessors(new MockLDAPUtils()); + string[] servicePrincipalNames = {"myhost.redmond.microsoft.com:1433"}; + const string distinguishedName = "CN=Jeff Smith,OU=Sales,DC=Fabrikam,DC=COM"; + await foreach (var spn in processor.ReadSPNTargets(servicePrincipalNames, distinguishedName)) + Assert.Null(spn); + } + + [Fact] + public async void ReadSPNTargets_SPNWithAddressSign_NotRead() + { + var processor = new SPNProcessors(new MockLDAPUtils()); + string[] servicePrincipalNames = {"MSSQLSvc/myhost.redmond.microsoft.com:1433 user@domain"}; + const string distinguishedName = "CN=Jeff Smith,OU=Sales,DC=Fabrikam,DC=COM"; + await foreach (var spn in processor.ReadSPNTargets(servicePrincipalNames, distinguishedName)) + Assert.Null(spn); + } + } +} \ No newline at end of file diff --git a/test/unit/SearchResultEntryTests.cs b/test/unit/SearchResultEntryTests.cs index 2fcaa68a..d49109eb 100644 --- a/test/unit/SearchResultEntryTests.cs +++ b/test/unit/SearchResultEntryTests.cs @@ -1,36 +1,36 @@ -// using System.Collections.Generic; -// using System.Security.Principal; -// using CommonLibTest.Facades; -// using SharpHoundCommonLib; -// using SharpHoundCommonLib.Enums; -// using Xunit; -// -// namespace CommonLibTest -// { -// public class SearchResultEntryTests -// { -// [WindowsOnlyFact] -// public void Test_GetLabelIssuanceOIDObjects() -// { -// var sid = new SecurityIdentifier("S-1-5-21-3130019616-2776909439-2417379446-500"); -// var bsid = new byte[sid.BinaryLength]; -// sid.GetBinaryForm(bsid, 0); -// var attribs = new Dictionary -// { -// { "objectsid", bsid}, -// { "objectclass", "msPKI-Enterprise-Oid" }, -// { "flags", "2" } -// }; -// -// var sre = MockableSearchResultEntry.Construct(attribs, "CN=Test,CN=OID,CN=Public Key Services,CN=Services,CN=Configuration"); -// var success = sre.GetLabel(out var label); -// Assert.True(success); -// Assert.Equal(Label.IssuancePolicy, label); -// -// sre = MockableSearchResultEntry.Construct(attribs, "CN=OID,CN=Public Key Services,CN=Services,CN=Configuration"); -// success = sre.GetLabel(out label); -// Assert.True(success); -// Assert.Equal(Label.Container, label); -// } -// } -// } \ No newline at end of file +using System.Collections.Generic; +using System.Security.Principal; +using CommonLibTest.Facades; +using SharpHoundCommonLib; +using SharpHoundCommonLib.Enums; +using Xunit; + +namespace CommonLibTest +{ + public class SearchResultEntryTests + { + [WindowsOnlyFact] + public void Test_GetLabelIssuanceOIDObjects() + { + var sid = new SecurityIdentifier("S-1-5-21-3130019616-2776909439-2417379446-500"); + var bsid = new byte[sid.BinaryLength]; + sid.GetBinaryForm(bsid, 0); + var attribs = new Dictionary + { + { "objectsid", bsid}, + { "objectclass", "msPKI-Enterprise-Oid" }, + { "flags", "2" } + }; + + var sre = MockableSearchResultEntry.Construct(attribs, "CN=Test,CN=OID,CN=Public Key Services,CN=Services,CN=Configuration"); + var success = sre.GetLabel(out var label); + Assert.True(success); + Assert.Equal(Label.IssuancePolicy, label); + + sre = MockableSearchResultEntry.Construct(attribs, "CN=OID,CN=Public Key Services,CN=Services,CN=Configuration"); + success = sre.GetLabel(out label); + Assert.True(success); + Assert.Equal(Label.Container, label); + } + } +} \ No newline at end of file diff --git a/test/unit/UserRightsAssignmentProcessorTest.cs b/test/unit/UserRightsAssignmentProcessorTest.cs index b0bc9562..3bfca790 100644 --- a/test/unit/UserRightsAssignmentProcessorTest.cs +++ b/test/unit/UserRightsAssignmentProcessorTest.cs @@ -1,66 +1,66 @@ -// using System.Linq; -// using System.Threading.Tasks; -// using CommonLibTest.Facades; -// using CommonLibTest.Facades.LSAMocks.DCMocks; -// using CommonLibTest.Facades.LSAMocks.WorkstationMocks; -// using Moq; -// using Newtonsoft.Json; -// using SharpHoundCommonLib.Enums; -// using SharpHoundCommonLib.Processors; -// using Xunit; -// using Xunit.Abstractions; -// -// namespace CommonLibTest -// { -// public class UserRightsAssignmentProcessorTest -// { -// private readonly ITestOutputHelper _testOutputHelper; -// -// public UserRightsAssignmentProcessorTest(ITestOutputHelper testOutputHelper) -// { -// _testOutputHelper = testOutputHelper; -// } -// -// [WindowsOnlyFact] -// public async Task UserRightsAssignmentProcessor_TestWorkstation() -// { -// var mockProcessor = new Mock(new MockLDAPUtils(), null); -// var mockLSAPolicy = new MockWorkstationLSAPolicy(); -// mockProcessor.Setup(x => x.OpenLSAPolicy(It.IsAny())).Returns(mockLSAPolicy); -// var processor = mockProcessor.Object; -// var machineDomainSid = $"{Consts.MockDomainSid}-1001"; -// var results = await processor.GetUserRightsAssignments("win10.testlab.local", machineDomainSid, "testlab.local", false) -// .ToArrayAsync(); -// -// var privilege = results[0]; -// Assert.Equal(LSAPrivileges.RemoteInteractiveLogon, privilege.Privilege); -// Assert.Equal(3, results[0].Results.Length); -// var adminResult = privilege.Results.First(x => x.ObjectIdentifier.EndsWith("-544")); -// Assert.Equal($"{machineDomainSid}-544", adminResult.ObjectIdentifier); -// Assert.Equal(Label.LocalGroup, adminResult.ObjectType); -// var rdpResult = privilege.Results.First(x => x.ObjectIdentifier.EndsWith("-555")); -// Assert.Equal($"{machineDomainSid}-555", rdpResult.ObjectIdentifier); -// Assert.Equal(Label.LocalGroup, rdpResult.ObjectType); -// } -// -// [WindowsOnlyFact] -// public async Task UserRightsAssignmentProcessor_TestDC() -// { -// var mockProcessor = new Mock(new MockLDAPUtils(), null); -// var mockLSAPolicy = new MockDCLSAPolicy(); -// mockProcessor.Setup(x => x.OpenLSAPolicy(It.IsAny())).Returns(mockLSAPolicy); -// var processor = mockProcessor.Object; -// var machineDomainSid = $"{Consts.MockDomainSid}-1000"; -// var results = await processor.GetUserRightsAssignments("primary.testlab.local", machineDomainSid, "testlab.local", true) -// .ToArrayAsync(); -// -// var privilege = results[0]; -// _testOutputHelper.WriteLine(JsonConvert.SerializeObject(privilege)); -// Assert.Equal(LSAPrivileges.RemoteInteractiveLogon, privilege.Privilege); -// Assert.Single(results[0].Results); -// var adminResult = privilege.Results.First(x => x.ObjectIdentifier.EndsWith("-544")); -// Assert.Equal("TESTLAB.LOCAL-S-1-5-32-544", adminResult.ObjectIdentifier); -// Assert.Equal(Label.Group, adminResult.ObjectType); -// } -// } -// } \ No newline at end of file +using System.Linq; +using System.Threading.Tasks; +using CommonLibTest.Facades; +using CommonLibTest.Facades.LSAMocks.DCMocks; +using CommonLibTest.Facades.LSAMocks.WorkstationMocks; +using Moq; +using Newtonsoft.Json; +using SharpHoundCommonLib.Enums; +using SharpHoundCommonLib.Processors; +using Xunit; +using Xunit.Abstractions; + +namespace CommonLibTest +{ + public class UserRightsAssignmentProcessorTest + { + private readonly ITestOutputHelper _testOutputHelper; + + public UserRightsAssignmentProcessorTest(ITestOutputHelper testOutputHelper) + { + _testOutputHelper = testOutputHelper; + } + + [WindowsOnlyFact] + public async Task UserRightsAssignmentProcessor_TestWorkstation() + { + var mockProcessor = new Mock(new MockLDAPUtils(), null); + var mockLSAPolicy = new MockWorkstationLSAPolicy(); + mockProcessor.Setup(x => x.OpenLSAPolicy(It.IsAny())).Returns(mockLSAPolicy); + var processor = mockProcessor.Object; + var machineDomainSid = $"{Consts.MockDomainSid}-1001"; + var results = await processor.GetUserRightsAssignments("win10.testlab.local", machineDomainSid, "testlab.local", false) + .ToArrayAsync(); + + var privilege = results[0]; + Assert.Equal(LSAPrivileges.RemoteInteractiveLogon, privilege.Privilege); + Assert.Equal(3, results[0].Results.Length); + var adminResult = privilege.Results.First(x => x.ObjectIdentifier.EndsWith("-544")); + Assert.Equal($"{machineDomainSid}-544", adminResult.ObjectIdentifier); + Assert.Equal(Label.LocalGroup, adminResult.ObjectType); + var rdpResult = privilege.Results.First(x => x.ObjectIdentifier.EndsWith("-555")); + Assert.Equal($"{machineDomainSid}-555", rdpResult.ObjectIdentifier); + Assert.Equal(Label.LocalGroup, rdpResult.ObjectType); + } + + [WindowsOnlyFact] + public async Task UserRightsAssignmentProcessor_TestDC() + { + var mockProcessor = new Mock(new MockLDAPUtils(), null); + var mockLSAPolicy = new MockDCLSAPolicy(); + mockProcessor.Setup(x => x.OpenLSAPolicy(It.IsAny())).Returns(mockLSAPolicy); + var processor = mockProcessor.Object; + var machineDomainSid = $"{Consts.MockDomainSid}-1000"; + var results = await processor.GetUserRightsAssignments("primary.testlab.local", machineDomainSid, "testlab.local", true) + .ToArrayAsync(); + + var privilege = results[0]; + _testOutputHelper.WriteLine(JsonConvert.SerializeObject(privilege)); + Assert.Equal(LSAPrivileges.RemoteInteractiveLogon, privilege.Privilege); + Assert.Single(results[0].Results); + var adminResult = privilege.Results.First(x => x.ObjectIdentifier.EndsWith("-544")); + Assert.Equal("TESTLAB.LOCAL-S-1-5-32-544", adminResult.ObjectIdentifier); + Assert.Equal(Label.Group, adminResult.ObjectType); + } + } +} \ No newline at end of file From bc790723804516fd6df60314b3db39a8acd020ce Mon Sep 17 00:00:00 2001 From: rvazarkar Date: Tue, 9 Jul 2024 14:40:50 -0400 Subject: [PATCH 48/68] wip: fix gpolocalgroup processor tests --- .../Processors/GPOLocalGroupProcessor.cs | 22 + test/unit/GPOLocalGroupProcessorTest.cs | 671 +++++++++--------- 2 files changed, 358 insertions(+), 335 deletions(-) diff --git a/src/CommonLib/Processors/GPOLocalGroupProcessor.cs b/src/CommonLib/Processors/GPOLocalGroupProcessor.cs index e6b754d3..040f1a69 100644 --- a/src/CommonLib/Processors/GPOLocalGroupProcessor.cs +++ b/src/CommonLib/Processors/GPOLocalGroupProcessor.cs @@ -502,6 +502,28 @@ public TypedPrincipal ToTypedPrincipal() { }; } + protected bool Equals(GroupAction other) { + return Action == other.Action && Target == other.Target && TargetSid == other.TargetSid && TargetType == other.TargetType && TargetRid == other.TargetRid; + } + + public override bool Equals(object obj) { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((GroupAction)obj); + } + + public override int GetHashCode() { + unchecked { + var hashCode = (int)Action; + hashCode = (hashCode * 397) ^ (int)Target; + hashCode = (hashCode * 397) ^ (TargetSid != null ? TargetSid.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (int)TargetType; + hashCode = (hashCode * 397) ^ (int)TargetRid; + return hashCode; + } + } + public override string ToString() { return $"{nameof(Action)}: {Action}, {nameof(Target)}: {Target}, {nameof(TargetSid)}: {TargetSid}, {nameof(TargetType)}: {TargetType}, {nameof(TargetRid)}: {TargetRid}"; diff --git a/test/unit/GPOLocalGroupProcessorTest.cs b/test/unit/GPOLocalGroupProcessorTest.cs index 0bbcef2d..18af16bc 100644 --- a/test/unit/GPOLocalGroupProcessorTest.cs +++ b/test/unit/GPOLocalGroupProcessorTest.cs @@ -1,335 +1,336 @@ -// using System.Collections.Generic; -// using System.DirectoryServices.Protocols; -// using System.IO; -// using System.Linq; -// using System.Threading; -// using System.Threading.Tasks; -// using Moq; -// using SharpHoundCommonLib; -// using SharpHoundCommonLib.Enums; -// using SharpHoundCommonLib.LDAPQueries; -// using SharpHoundCommonLib.OutputTypes; -// using SharpHoundCommonLib.Processors; -// using Xunit; -// using Xunit.Abstractions; -// -// namespace CommonLibTest -// { -// public class GPOLocalGroupProcessorTest -// { -// private readonly string GpttmplInfContent = @"[Unicode] -// Unicode=yes -// [Version] -// signature=""$CHICAGO$"" -// Revision=1 -// [Group Membership] -// *S-1-5-21-3130019616-2776909439-2417379446-514__Memberof = *S-1-5-32-544 -// *S-1-5-21-3130019616-2776909439-2417379446-514__Members = -// *S-1-5-32-544__Members = -// "; -// -// private readonly string GpttmplInfContentNoMatch = @"[Unicode] -// Unicode=yes -// [Version] -// signature=""$CHICAGO$"" -// Revision=1 -// "; -// -// private readonly string GroupXmlContent = @" -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// "; -// -// private readonly string GroupXmlContentDisabled = @" -// -// -// "; -// -// private ITestOutputHelper _testOutputHelper; -// -// public GPOLocalGroupProcessorTest(ITestOutputHelper testOutputHelper) -// { -// _testOutputHelper = testOutputHelper; -// } -// -// [Fact(Skip = "")] -// public async Task GPOLocalGroupProcessor_ReadGPOLocalGroups_Null_GPLink() -// { -// var mockLDAPUtils = new Mock(); -// var processor = new GPOLocalGroupProcessor(mockLDAPUtils.Object); -// -// var result = await processor.ReadGPOLocalGroups(null, null); -// Assert.NotNull(result); -// Assert.Empty(result.AffectedComputers); -// Assert.Empty(result.DcomUsers); -// Assert.Empty(result.RemoteDesktopUsers); -// Assert.Empty(result.LocalAdmins); -// Assert.Empty(result.PSRemoteUsers); -// } -// -// [Fact(Skip = "")] -// public async Task GPOLocalGroupProcessor_ReadGPOLocalGroups_AffectedComputers_0() -// { -// var mockLDAPUtils = new Mock(); -// mockLDAPUtils.Setup(x => x.QueryLDAP( -// It.IsAny(), -// It.IsAny(), -// It.IsAny(), -// It.IsAny(), -// It.IsAny(), -// It.IsAny(), -// It.IsAny(), -// It.IsAny(), -// It.IsAny(), -// It.IsAny(), -// It.IsAny() -// )).Returns(new List()); -// var processor = new GPOLocalGroupProcessor(mockLDAPUtils.Object); -// -// var result = await processor.ReadGPOLocalGroups("teapot", null); -// Assert.NotNull(result); -// Assert.Empty(result.AffectedComputers); -// Assert.Empty(result.DcomUsers); -// Assert.Empty(result.RemoteDesktopUsers); -// Assert.Empty(result.LocalAdmins); -// Assert.Empty(result.PSRemoteUsers); -// } -// -// [Fact(Skip = "")] -// public async Task GPOLocalGroupProcessor_ReadGPOLocalGroups_Null_Gpcfilesyspath() -// { -// var mockLDAPUtils = new Mock(); -// var mockSearchResultEntry = new Mock(); -// mockSearchResultEntry.Setup(x => x.GetSid()).Returns("teapot"); -// var mockSearchResults = new List(); -// mockSearchResults.Add(mockSearchResultEntry.Object); -// mockLDAPUtils.Setup(x => x.QueryLDAP(new LDAPQueryOptions -// { -// Filter = "(&(samaccounttype=805306369)(!(objectclass=msDS-GroupManagedServiceAccount))(!(objectclass=msDS-ManagedServiceAccount)))", -// Scope = SearchScope.Subtree, -// Properties = CommonProperties.ObjectSID, -// AdsPath = null -// })) -// .Returns(mockSearchResults.ToArray()); -// -// var processor = new GPOLocalGroupProcessor(mockLDAPUtils.Object); -// var testGPLinkProperty = -// "[LDAP:/o=foo/ou=foo Group (ABC123)/cn=foouser (blah)123/dc=somedomain;0;][LDAP:/o=foo/ou=foo Group (ABC123)/cn=foouser (blah)123/dc=someotherdomain;2;]"; -// var result = await processor.ReadGPOLocalGroups(testGPLinkProperty, null); -// -// Assert.NotNull(result); -// Assert.Single(result.AffectedComputers); -// var actual = result.AffectedComputers.First(); -// Assert.Equal(Label.Computer, actual.ObjectType); -// Assert.Equal("teapot", actual.ObjectIdentifier); -// } -// -// [Fact] -// public async Task GPOLocalGroupProcessor_ReadGPOLocalGroups() -// { -// var mockLDAPUtils = new Mock(MockBehavior.Strict); -// var gpcFileSysPath = Path.GetTempPath(); -// -// var groupsXmlPath = Path.Join(gpcFileSysPath, "MACHINE", "Preferences", "Groups", "Groups.xml"); -// -// Path.GetDirectoryName(groupsXmlPath); -// Directory.CreateDirectory(Path.GetDirectoryName(groupsXmlPath)); -// File.WriteAllText(groupsXmlPath, GroupXmlContent); -// -// var mockComputerEntry = new Mock(); -// mockComputerEntry.Setup(x => x.GetSid()).Returns("teapot"); -// var mockComputerResults = new List(); -// mockComputerResults.Add(mockComputerEntry.Object); -// -// var mockGCPFileSysPathEntry = new Mock(); -// mockGCPFileSysPathEntry.Setup(x => x.GetProperty(It.IsAny())).Returns(gpcFileSysPath); -// var mockGCPFileSysPathResults = new List(); -// mockGCPFileSysPathResults.Add(mockGCPFileSysPathEntry.Object); -// -// mockLDAPUtils.SetupSequence(x => x.QueryLDAP(It.IsAny())) -// .Returns(mockComputerResults.ToArray()) -// .Returns(mockGCPFileSysPathResults.ToArray()); -// -// var processor = new GPOLocalGroupProcessor(mockLDAPUtils.Object); -// -// var testGPLinkProperty = -// "[LDAP:/o=foo/ou=foo Group (ABC123)/cn=foouser (blah)123/dc=somedomain;0;][LDAP:/o=foo/ou=foo Group (ABC123)/cn=foouser (blah)123/dc=someotherdomain;2;]"; -// var result = await processor.ReadGPOLocalGroups(testGPLinkProperty, null); -// -// mockLDAPUtils.VerifyAll(); -// Assert.NotNull(result); -// Assert.Single(result.AffectedComputers); -// var actual = result.AffectedComputers.First(); -// Assert.Equal(Label.Computer, actual.ObjectType); -// Assert.Equal("teapot", actual.ObjectIdentifier); -// } -// -// [Fact] -// public async Task GPOLocalGroupProcess_ProcessGPOXMLFile_NoFile() -// { -// var mockLDAPUtils = new Mock(); -// var processor = new GPOLocalGroupProcessor(mockLDAPUtils.Object); -// var gpcFileSysPath = Path.Join(Path.GetTempPath(), "made", "up", "path"); -// -// var actual = processor.ProcessGPOXmlFile(gpcFileSysPath, "somedomain").ToList(); -// Assert.NotNull(actual); -// Assert.Empty(actual); -// } -// -// [Fact] -// public async Task GPOLocalGroupProcess_ProcessGPOXMLFile_Disabled() -// { -// var mockLDAPUtils = new Mock(); -// var gpcFileSysPath = Path.GetTempPath(); -// var groupsXmlPath = Path.Join(gpcFileSysPath, "MACHINE", "Preferences", "Groups", "Groups.xml"); -// -// Directory.CreateDirectory(Path.GetDirectoryName(groupsXmlPath)); -// File.WriteAllText(groupsXmlPath, GroupXmlContentDisabled); -// -// var processor = new GPOLocalGroupProcessor(mockLDAPUtils.Object); -// -// var actual = processor.ProcessGPOXmlFile(gpcFileSysPath, "somedomain").ToList(); -// Assert.NotNull(actual); -// Assert.Empty(actual); -// } -// -// [Fact] -// public async Task GPOLocalGroupProcessor_ProcessGPOXMLFile() -// { -// var mockLDAPUtils = new Mock(); -// mockLDAPUtils.Setup(x => x.ResolveAccountName(It.IsAny(), It.IsAny())) -// .Returns(new TypedPrincipal("S-1-5-21-3130019616-2776909439-2417379446-513", Label.User)); -// var gpcFileSysPath = Path.GetTempPath(); -// var groupsXmlPath = Path.Join(gpcFileSysPath, "MACHINE", "Preferences", "Groups", "Groups.xml"); -// -// Directory.CreateDirectory(Path.GetDirectoryName(groupsXmlPath)); -// File.WriteAllText(groupsXmlPath, GroupXmlContent); -// -// var processor = new GPOLocalGroupProcessor(mockLDAPUtils.Object); -// var actual = processor.ProcessGPOXmlFile(gpcFileSysPath, "somedomain").ToList(); -// -// Assert.NotNull(actual); -// Assert.NotEmpty(actual); -// } -// -// [Fact] -// public async Task GPOLocalGroupProcess_ProcessGPOTemplateFile_NoFile() -// { -// var mockLDAPUtils = new Mock(); -// var processor = new GPOLocalGroupProcessor(mockLDAPUtils.Object); -// var gpcFileSysPath = Path.Join(Path.GetTempPath(), "made", "up", "path"); -// -// var actual = await processor.ProcessGPOTemplateFile(gpcFileSysPath, "somedomain").ToListAsync(); -// Assert.NotNull(actual); -// Assert.Empty(actual); -// } -// -// [Fact] -// public async Task GPOLocalGroupProcess_ProcessGPOTemplateFile_NoMatch() -// { -// var mockLDAPUtils = new Mock(); -// var gpcFileSysPath = Path.GetTempPath(); -// var gptTmplPath = Path.Join(gpcFileSysPath, "MACHINE", "Microsoft", "Windows NT", "SecEdit", "GptTmpl.inf"); -// -// Directory.CreateDirectory(Path.GetDirectoryName(gptTmplPath)); -// File.WriteAllText(gptTmplPath, GpttmplInfContentNoMatch); -// -// var processor = new GPOLocalGroupProcessor(mockLDAPUtils.Object); -// -// var actual = await processor.ProcessGPOTemplateFile(gpcFileSysPath, "somedomain").ToListAsync(); -// Assert.NotNull(actual); -// Assert.Empty(actual); -// } -// -// [Fact] -// public async Task GPOLocalGroupProcess_ProcessGPOTemplateFile_NullSID() -// { -// var mockLDAPUtils = new Mock(); -// var gpcFileSysPath = Path.GetTempPath(); -// var gptTmplPath = Path.Join(gpcFileSysPath, "MACHINE", "Microsoft", "Windows NT", "SecEdit", "GptTmpl.inf"); -// -// Directory.CreateDirectory(Path.GetDirectoryName(gptTmplPath)); -// File.WriteAllText(gptTmplPath, GpttmplInfContent); -// -// var processor = new GPOLocalGroupProcessor(mockLDAPUtils.Object); -// -// var actual = await processor.ProcessGPOTemplateFile(gpcFileSysPath, "somedomain").ToListAsync(); -// Assert.NotNull(actual); -// Assert.NotEmpty(actual); -// } -// -// [Fact] -// public async Task GPOLocalGroupProcess_ProcessGPOTemplateFile() -// { -// var mockLDAPUtils = new Mock(); -// mockLDAPUtils.Setup(x => x.ResolveAccountName(It.IsAny(), It.IsAny())) -// .Returns(new TypedPrincipal("S-1-5-21-3130019616-2776909439-2417379446-513", Label.User)); -// var gpcFileSysPath = Path.GetTempPath(); -// var gptTmplPath = Path.Join(gpcFileSysPath, "MACHINE", "Microsoft", "Windows NT", "SecEdit", "GptTmpl.inf"); -// -// Directory.CreateDirectory(Path.GetDirectoryName(gptTmplPath)); -// File.WriteAllText(gptTmplPath, GpttmplInfContent); -// -// var processor = new GPOLocalGroupProcessor(mockLDAPUtils.Object); -// -// var actual = await processor.ProcessGPOTemplateFile(gpcFileSysPath, "somedomain").ToListAsync(); -// Assert.NotNull(actual); -// // Assert.Empty(actual); -// } -// -// [Fact] -// public void GPOLocalGroupProcess_GroupAction() -// { -// var ga = new GPOLocalGroupProcessor.GroupAction(); -// var tp = ga.ToTypedPrincipal(); -// var str = ga.ToString(); -// -// Assert.NotNull(tp); -// Assert.Equal(new TypedPrincipal(), tp); -// Assert.NotNull(str); -// Assert.Equal("Action: Add, Target: RestrictedMemberOf, TargetSid: , TargetType: Base, TargetRid: None", -// str); -// } -// } -// } \ No newline at end of file +using System; +using System.Collections.Generic; +using System.DirectoryServices.Protocols; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using CommonLibTest.Facades; +using Moq; +using SharpHoundCommonLib; +using SharpHoundCommonLib.Enums; +using SharpHoundCommonLib.LDAPQueries; +using SharpHoundCommonLib.OutputTypes; +using SharpHoundCommonLib.Processors; +using Xunit; +using Xunit.Abstractions; + +namespace CommonLibTest +{ + public class GPOLocalGroupProcessorTest + { + private readonly string GpttmplInfContent = @"[Unicode] + Unicode=yes + [Version] + signature=""$CHICAGO$"" + Revision=1 + [Group Membership] + *S-1-5-21-3130019616-2776909439-2417379446-514__Memberof = *S-1-5-32-544 + *S-1-5-21-3130019616-2776909439-2417379446-514__Members = + *S-1-5-32-544__Members = + "; + + private readonly string GpttmplInfContentNoMatch = @"[Unicode] + Unicode=yes + [Version] + signature=""$CHICAGO$"" + Revision=1 + "; + + private readonly string GroupXmlContent = @" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + "; + + private readonly string GroupXmlContentDisabled = @" + + + "; + + private ITestOutputHelper _testOutputHelper; + + public GPOLocalGroupProcessorTest(ITestOutputHelper testOutputHelper) + { + _testOutputHelper = testOutputHelper; + } + + [Fact] + public async Task GPOLocalGroupProcessor_ReadGPOLocalGroups_Null_GPLink() + { + var mockLDAPUtils = new Mock(); + var processor = new GPOLocalGroupProcessor(mockLDAPUtils.Object); + + var result = await processor.ReadGPOLocalGroups(null, null); + Assert.NotNull(result); + Assert.Empty(result.AffectedComputers); + Assert.Empty(result.DcomUsers); + Assert.Empty(result.RemoteDesktopUsers); + Assert.Empty(result.LocalAdmins); + Assert.Empty(result.PSRemoteUsers); + } + + [Fact] + public async Task GPOLocalGroupProcessor_ReadGPOLocalGroups_AffectedComputers_0() + { + var mockLDAPUtils = new Mock(); + mockLDAPUtils.Setup(x => x.Query(It.IsAny(), It.IsAny())).Returns(Array.Empty>().ToAsyncEnumerable); + var processor = new GPOLocalGroupProcessor(mockLDAPUtils.Object); + + var result = await processor.ReadGPOLocalGroups("teapot", null); + Assert.NotNull(result); + Assert.Empty(result.AffectedComputers); + Assert.Empty(result.DcomUsers); + Assert.Empty(result.RemoteDesktopUsers); + Assert.Empty(result.LocalAdmins); + Assert.Empty(result.PSRemoteUsers); + } + + [Fact] + public async Task GPOLocalGroupProcessor_ReadGPOLocalGroups_Null_Gpcfilesyspath() + { + var mockLDAPUtils = new Mock(); + var mockSearchResultEntry = new Mock(); + mockSearchResultEntry.Setup(x => x.GetSid()).Returns("teapot"); + var mockResult = LdapResult.Ok(mockSearchResultEntry.Object); + var mockSearchResults = new List> { mockResult }; + mockLDAPUtils + .Setup(x => x.Query( + It.Is(y => + y.LDAPFilter.Equals(new LDAPFilter().AddComputersNoMSAs().GetFilter()) && y.Attributes.Equals(CommonProperties.ObjectSID)), + It.IsAny())).Returns(mockSearchResults.ToAsyncEnumerable); + + mockLDAPUtils + .Setup(x => x.Query( + It.Is(y => + y.LDAPFilter.Equals(new LDAPFilter().AddAllObjects().GetFilter())), + It.IsAny())).Returns(Array.Empty>().ToAsyncEnumerable); + + var processor = new GPOLocalGroupProcessor(mockLDAPUtils.Object); + var testGPLinkProperty = + "[LDAP:/o=foo/ou=foo Group (ABC123)/cn=foouser (blah)123/dc=somedomain;0;][LDAP:/o=foo/ou=foo Group (ABC123)/cn=foouser (blah)123/dc=someotherdomain;2;]"; + var result = await processor.ReadGPOLocalGroups(testGPLinkProperty, null); + + Assert.NotNull(result); + Assert.Single(result.AffectedComputers); + var actual = result.AffectedComputers.First(); + Assert.Equal(Label.Computer, actual.ObjectType); + Assert.Equal("teapot", actual.ObjectIdentifier); + } + + [Fact] + public async Task GPOLocalGroupProcessor_ReadGPOLocalGroups() + { + var mockLDAPUtils = new Mock(MockBehavior.Loose); + var gpcFileSysPath = Path.GetTempPath(); + + var groupsXmlPath = Path.Join(gpcFileSysPath, "MACHINE", "Preferences", "Groups", "Groups.xml"); + + Path.GetDirectoryName(groupsXmlPath); + Directory.CreateDirectory(Path.GetDirectoryName(groupsXmlPath)); + File.WriteAllText(groupsXmlPath, GroupXmlContent); + + var mockComputerEntry = new Mock(); + mockComputerEntry.Setup(x => x.GetSid()).Returns("teapot"); + var mockComputerResults = new List>(); + mockComputerResults.Add(LdapResult.Ok(mockComputerEntry.Object)); + + var mockGCPFileSysPathEntry = new Mock(); + mockGCPFileSysPathEntry.Setup(x => x.GetProperty(It.IsAny())).Returns(gpcFileSysPath); + var mockGCPFileSysPathResults = new List> { LdapResult.Ok(mockGCPFileSysPathEntry.Object) }; + + mockLDAPUtils.SetupSequence(x => x.Query(It.IsAny(), It.IsAny())) + .Returns(mockComputerResults.ToAsyncEnumerable) + .Returns(mockGCPFileSysPathResults.ToAsyncEnumerable) + .Returns(Array.Empty>().ToAsyncEnumerable); + + var processor = new GPOLocalGroupProcessor(mockLDAPUtils.Object); + + var testGPLinkProperty = + "[LDAP:/o=foo/ou=foo Group (ABC123)/cn=foouser (blah)123/dc=somedomain;0;][LDAP:/o=foo/ou=foo Group (ABC123)/cn=foouser (blah)123/dc=someotherdomain;2;]"; + var result = await processor.ReadGPOLocalGroups(testGPLinkProperty, null); + + mockLDAPUtils.VerifyAll(); + Assert.NotNull(result); + Assert.Single(result.AffectedComputers); + var actual = result.AffectedComputers.First(); + Assert.Equal(Label.Computer, actual.ObjectType); + Assert.Equal("teapot", actual.ObjectIdentifier); + } + + [Fact] + public async Task GPOLocalGroupProcess_ProcessGPOXMLFile_NoFile() + { + var mockLDAPUtils = new Mock(); + var processor = new GPOLocalGroupProcessor(mockLDAPUtils.Object); + var gpcFileSysPath = Path.Join(Path.GetTempPath(), "made", "up", "path"); + + var actual = await processor.ProcessGPOXmlFile(gpcFileSysPath, "somedomain").ToArrayAsync(); + Assert.NotNull(actual); + Assert.Empty(actual); + } + + [Fact] + public async Task GPOLocalGroupProcess_ProcessGPOXMLFile_Disabled() + { + var mockLDAPUtils = new Mock(); + var gpcFileSysPath = Path.GetTempPath(); + var groupsXmlPath = Path.Join(gpcFileSysPath, "MACHINE", "Preferences", "Groups", "Groups.xml"); + + Directory.CreateDirectory(Path.GetDirectoryName(groupsXmlPath)); + File.WriteAllText(groupsXmlPath, GroupXmlContentDisabled); + + var processor = new GPOLocalGroupProcessor(mockLDAPUtils.Object); + + var actual = await processor.ProcessGPOXmlFile(gpcFileSysPath, "somedomain").ToArrayAsync(); + Assert.NotNull(actual); + Assert.Empty(actual); + } + + [Fact] + public async Task GPOLocalGroupProcessor_ProcessGPOXMLFile() + { + var mockLDAPUtils = new Mock(); + mockLDAPUtils.Setup(x => x.ResolveAccountName(It.IsAny(), It.IsAny())) + .ReturnsAsync((true, new TypedPrincipal("S-1-5-21-3130019616-2776909439-2417379446-513", Label.User))); + var gpcFileSysPath = Path.GetTempPath(); + var groupsXmlPath = Path.Join(gpcFileSysPath, "MACHINE", "Preferences", "Groups", "Groups.xml"); + + Directory.CreateDirectory(Path.GetDirectoryName(groupsXmlPath)); + File.WriteAllText(groupsXmlPath, GroupXmlContent); + + var processor = new GPOLocalGroupProcessor(mockLDAPUtils.Object); + var actual = await processor.ProcessGPOXmlFile(gpcFileSysPath, "somedomain").ToArrayAsync(); + + Assert.NotNull(actual); + Assert.NotEmpty(actual); + } + + [Fact] + public async Task GPOLocalGroupProcess_ProcessGPOTemplateFile_NoFile() + { + var mockLDAPUtils = new Mock(); + var processor = new GPOLocalGroupProcessor(mockLDAPUtils.Object); + var gpcFileSysPath = Path.Join(Path.GetTempPath(), "made", "up", "path"); + + var actual = await processor.ProcessGPOTemplateFile(gpcFileSysPath, "somedomain").ToListAsync(); + Assert.NotNull(actual); + Assert.Empty(actual); + } + + [Fact] + public async Task GPOLocalGroupProcess_ProcessGPOTemplateFile_NoMatch() + { + var mockLDAPUtils = new Mock(); + var gpcFileSysPath = Path.GetTempPath(); + var gptTmplPath = Path.Join(gpcFileSysPath, "MACHINE", "Microsoft", "Windows NT", "SecEdit", "GptTmpl.inf"); + + Directory.CreateDirectory(Path.GetDirectoryName(gptTmplPath)); + File.WriteAllText(gptTmplPath, GpttmplInfContentNoMatch); + + var processor = new GPOLocalGroupProcessor(mockLDAPUtils.Object); + + var actual = await processor.ProcessGPOTemplateFile(gpcFileSysPath, "somedomain").ToListAsync(); + Assert.NotNull(actual); + Assert.Empty(actual); + } + + [Fact] + public async Task GPOLocalGroupProcess_ProcessGPOTemplateFile_NullSID() + { + var mockLDAPUtils = new MockLDAPUtils(); + var gpcFileSysPath = Path.GetTempPath(); + var gptTmplPath = Path.Join(gpcFileSysPath, "MACHINE", "Microsoft", "Windows NT", "SecEdit", "GptTmpl.inf"); + + Directory.CreateDirectory(Path.GetDirectoryName(gptTmplPath)); + File.WriteAllText(gptTmplPath, GpttmplInfContent); + + var processor = new GPOLocalGroupProcessor(mockLDAPUtils); + + var actual = await processor.ProcessGPOTemplateFile(gpcFileSysPath, "somedomain").ToListAsync(); + Assert.NotNull(actual); + Assert.NotEmpty(actual); + } + + [Fact] + public async Task GPOLocalGroupProcess_ProcessGPOTemplateFile() + { + var mockLDAPUtils = new Mock(); + mockLDAPUtils.Setup(x => x.ResolveAccountName(It.IsAny(), It.IsAny())) + .ReturnsAsync((true,new TypedPrincipal("S-1-5-21-3130019616-2776909439-2417379446-513", Label.User))); + var gpcFileSysPath = Path.GetTempPath(); + var gptTmplPath = Path.Join(gpcFileSysPath, "MACHINE", "Microsoft", "Windows NT", "SecEdit", "GptTmpl.inf"); + + Directory.CreateDirectory(Path.GetDirectoryName(gptTmplPath)); + File.WriteAllText(gptTmplPath, GpttmplInfContent); + + var processor = new GPOLocalGroupProcessor(mockLDAPUtils.Object); + + var actual = await processor.ProcessGPOTemplateFile(gpcFileSysPath, "somedomain").ToListAsync(); + Assert.NotNull(actual); + Assert.NotEmpty(actual); + var expected = new GPOLocalGroupProcessor.GroupAction() { + Action = GPOLocalGroupProcessor.GroupActionOperation.Add, + Target = GPOLocalGroupProcessor.GroupActionTarget.RestrictedMember, + TargetSid = "S-1-5-21-3130019616-2776909439-2417379446-513", + TargetRid = GPOLocalGroupProcessor.LocalGroupRids.Administrators, + TargetType = Label.User + }; + Assert.Contains(expected, actual); + } + + [Fact] + public void GPOLocalGroupProcess_GroupAction() + { + var ga = new GPOLocalGroupProcessor.GroupAction(); + var tp = ga.ToTypedPrincipal(); + var str = ga.ToString(); + + Assert.NotNull(tp); + Assert.Equal(new TypedPrincipal(), tp); + Assert.NotNull(str); + Assert.Equal("Action: Add, Target: RestrictedMemberOf, TargetSid: , TargetType: Base, TargetRid: None", + str); + } + } +} \ No newline at end of file From b350347d20c51af064a2c296a99ba4ac33d47222 Mon Sep 17 00:00:00 2001 From: rvazarkar Date: Tue, 9 Jul 2024 15:46:39 -0400 Subject: [PATCH 49/68] wip: fix group processor tests --- test/unit/GroupProcessorTest.cs | 271 +++++++++++++++++--------------- 1 file changed, 141 insertions(+), 130 deletions(-) diff --git a/test/unit/GroupProcessorTest.cs b/test/unit/GroupProcessorTest.cs index 3d23b90b..53cc86d3 100644 --- a/test/unit/GroupProcessorTest.cs +++ b/test/unit/GroupProcessorTest.cs @@ -1,130 +1,141 @@ -// using System; -// using System.Linq; -// using CommonLibTest.Facades; -// using Moq; -// using SharpHoundCommonLib; -// using SharpHoundCommonLib.Enums; -// using SharpHoundCommonLib.OutputTypes; -// using SharpHoundCommonLib.Processors; -// using Xunit; -// using Xunit.Abstractions; -// -// namespace CommonLibTest -// { -// public class GroupProcessorTest -// { -// private readonly string _testDomainName; -// -// private readonly string[] _testMembership = -// { -// "CN=Domain Admins,CN=Users,DC=testlab,DC=local", -// "CN=Enterprise Admins,CN=Users,DC=testlab,DC=local", -// "CN=Administrator,CN=Users,DC=testlab,DC=local", -// "CN=NonExistent,CN=Users,DC=testlab,DC=local" -// }; -// -// private readonly ITestOutputHelper _testOutputHelper; -// private GroupProcessor _baseProcessor; -// -// public GroupProcessorTest(ITestOutputHelper testOutputHelper) -// { -// _testOutputHelper = testOutputHelper; -// _testDomainName = "TESTLAB.LOCAL"; -// _baseProcessor = new GroupProcessor(new LDAPUtils()); -// } -// -// [Fact] -// public void GroupProcessor_GetPrimaryGroupInfo_NullPrimaryGroupID_ReturnsNull() -// { -// var result = GroupProcessor.GetPrimaryGroupInfo(null, null); -// Assert.Null(result); -// } -// -// [WindowsOnlyFact] -// public void GroupProcessor_GetPrimaryGroupInfo_ReturnsCorrectSID() -// { -// var result = GroupProcessor.GetPrimaryGroupInfo("513", "S-1-5-21-3130019616-2776909439-2417379446-1105"); -// Assert.Equal("S-1-5-21-3130019616-2776909439-2417379446-513", result); -// } -// -// [Fact] -// public void GroupProcessor_GetPrimaryGroupInfo_BadSID_ReturnsNull() -// { -// var result = GroupProcessor.GetPrimaryGroupInfo("513", "ABC123"); -// Assert.Null(result); -// } -// -// [Fact] -// public void GroupProcessor_ReadGroupMembers_EmptyMembers_DoesRangedRetrieval() -// { -// var mockUtils = new Mock(); -// var expected = new TypedPrincipal[] -// { -// new() -// { -// ObjectIdentifier = "S-1-5-21-3130019616-2776909439-2417379446-512", -// ObjectType = Label.Group -// }, -// new() -// { -// ObjectIdentifier = "S-1-5-21-3130019616-2776909439-2417379446-519", -// ObjectType = Label.Group -// }, -// new() -// { -// ObjectIdentifier = "S-1-5-21-3130019616-2776909439-2417379446-500", -// ObjectType = Label.User -// }, -// new() -// { -// ObjectIdentifier = "CN=NONEXISTENT,CN=USERS,DC=TESTLAB,DC=LOCAL", -// ObjectType = Label.Base -// } -// }; -// mockUtils.Setup(x => x.DoRangedRetrieval(It.IsAny(), It.IsAny())).Returns(_testMembership); -// var processor = new GroupProcessor(mockUtils.Object); -// -// var results = processor -// .ReadGroupMembers("CN=Administrators,CN=Builtin,DC=testlab,DC=local", Array.Empty()).ToArray(); -// foreach (var t in results) _testOutputHelper.WriteLine(t.ToString()); -// Assert.Equal(4, results.Length); -// Assert.Equal(expected, results); -// } -// -// [WindowsOnlyFact] -// public void GroupProcessor_ReadGroupMembers_ReturnsCorrectMembers() -// { -// var utils = new MockLDAPUtils(); -// var processor = new GroupProcessor(utils); -// var expected = new TypedPrincipal[] -// { -// new() -// { -// ObjectIdentifier = "S-1-5-21-3130019616-2776909439-2417379446-512", -// ObjectType = Label.Group -// }, -// new() -// { -// ObjectIdentifier = "S-1-5-21-3130019616-2776909439-2417379446-519", -// ObjectType = Label.Group -// }, -// new() -// { -// ObjectIdentifier = "S-1-5-21-3130019616-2776909439-2417379446-500", -// ObjectType = Label.User -// }, -// new() -// { -// ObjectIdentifier = "CN=NONEXISTENT,CN=USERS,DC=TESTLAB,DC=LOCAL", -// ObjectType = Label.Base -// } -// }; -// -// var results = processor -// .ReadGroupMembers("CN=Administrators,CN=Builtin,DC=testlab,DC=local", _testMembership).ToArray(); -// foreach (var t in results) _testOutputHelper.WriteLine(t.ToString()); -// Assert.Equal(4, results.Length); -// Assert.Equal(expected, results); -// } -// } -// } \ No newline at end of file +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using CommonLibTest.Facades; +using Moq; +using SharpHoundCommonLib; +using SharpHoundCommonLib.Enums; +using SharpHoundCommonLib.OutputTypes; +using SharpHoundCommonLib.Processors; +using Xunit; +using Xunit.Abstractions; + +namespace CommonLibTest +{ + public class GroupProcessorTest + { + private readonly string _testDomainName; + + private readonly Result[] _testMembershipReturn = + { + Result.Ok("CN=Domain Admins,CN=Users,DC=testlab,DC=local"), + Result.Ok("CN=Enterprise Admins,CN=Users,DC=testlab,DC=local"), + Result.Ok("CN=Administrator,CN=Users,DC=testlab,DC=local"), + Result.Ok("CN=NonExistent,CN=Users,DC=testlab,DC=local") + }; + + private readonly string[] _testMembership = + { + "CN=Domain Admins,CN=Users,DC=testlab,DC=local", + "CN=Enterprise Admins,CN=Users,DC=testlab,DC=local", + "CN=Administrator,CN=Users,DC=testlab,DC=local", + "CN=NonExistent,CN=Users,DC=testlab,DC=local" + }; + + + private readonly ITestOutputHelper _testOutputHelper; + private GroupProcessor _baseProcessor; + + public GroupProcessorTest(ITestOutputHelper testOutputHelper) + { + _testOutputHelper = testOutputHelper; + _testDomainName = "TESTLAB.LOCAL"; + _baseProcessor = new GroupProcessor(new LdapUtils()); + } + + [Fact] + public async Task GroupProcessor_GetPrimaryGroupInfo_NullPrimaryGroupID_ReturnsNull() + { + var result = GroupProcessor.GetPrimaryGroupInfo(null, null); + Assert.Null(result); + } + + [WindowsOnlyFact] + public async Task GroupProcessor_GetPrimaryGroupInfo_ReturnsCorrectSID() + { + var result = GroupProcessor.GetPrimaryGroupInfo("513", "S-1-5-21-3130019616-2776909439-2417379446-1105"); + Assert.Equal("S-1-5-21-3130019616-2776909439-2417379446-513", result); + } + + [Fact] + public async Task GroupProcessor_GetPrimaryGroupInfo_BadSID_ReturnsNull() + { + var result = GroupProcessor.GetPrimaryGroupInfo("513", "ABC123"); + Assert.Null(result); + } + + [Fact] + public async Task GroupProcessor_ReadGroupMembers_EmptyMembers_DoesRangedRetrieval() + { + var mockUtils = new Mock(); + var expected = new TypedPrincipal[] + { + new() + { + ObjectIdentifier = "S-1-5-21-3130019616-2776909439-2417379446-512", + ObjectType = Label.Group + }, + new() + { + ObjectIdentifier = "S-1-5-21-3130019616-2776909439-2417379446-519", + ObjectType = Label.Group + }, + new() + { + ObjectIdentifier = "S-1-5-21-3130019616-2776909439-2417379446-500", + ObjectType = Label.User + }, + new() + { + ObjectIdentifier = "CN=NONEXISTENT,CN=USERS,DC=TESTLAB,DC=LOCAL", + ObjectType = Label.Base + } + }; + mockUtils.Setup(x => x.RangedRetrieval(It.IsAny(), It.IsAny(), It.IsAny())).Returns(_testMembershipReturn.ToAsyncEnumerable()); + var processor = new GroupProcessor(mockUtils.Object); + + var results = await processor + .ReadGroupMembers("CN=Administrators,CN=Builtin,DC=testlab,DC=local", Array.Empty()).ToArrayAsync(); + foreach (var t in results) _testOutputHelper.WriteLine(t.ToString()); + Assert.Equal(4, results.Length); + Assert.Equal(expected, results); + } + + [WindowsOnlyFact] + public async Task GroupProcessor_ReadGroupMembers_ReturnsCorrectMembers() + { + var utils = new MockLDAPUtils(); + var processor = new GroupProcessor(utils); + var expected = new TypedPrincipal[] + { + new() + { + ObjectIdentifier = "S-1-5-21-3130019616-2776909439-2417379446-512", + ObjectType = Label.Group + }, + new() + { + ObjectIdentifier = "S-1-5-21-3130019616-2776909439-2417379446-519", + ObjectType = Label.Group + }, + new() + { + ObjectIdentifier = "S-1-5-21-3130019616-2776909439-2417379446-500", + ObjectType = Label.User + }, + new() + { + ObjectIdentifier = "CN=NONEXISTENT,CN=USERS,DC=TESTLAB,DC=LOCAL", + ObjectType = Label.Base + } + }; + + var results = await processor + .ReadGroupMembers("CN=Administrators,CN=Builtin,DC=testlab,DC=local", _testMembership).ToArrayAsync(); + foreach (var t in results) _testOutputHelper.WriteLine(t.ToString()); + Assert.Equal(4, results.Length); + Assert.Equal(expected, results); + } + } +} \ No newline at end of file From b5b1a9fc067e7234f2d69d2e090c3afe67af57a5 Mon Sep 17 00:00:00 2001 From: rvazarkar Date: Tue, 9 Jul 2024 15:49:18 -0400 Subject: [PATCH 50/68] wip: uncomment tests --- test/unit/LDAPPropertyTests.cs | 1862 ++++++++++++++++---------------- 1 file changed, 931 insertions(+), 931 deletions(-) diff --git a/test/unit/LDAPPropertyTests.cs b/test/unit/LDAPPropertyTests.cs index 0c732437..39014b8b 100644 --- a/test/unit/LDAPPropertyTests.cs +++ b/test/unit/LDAPPropertyTests.cs @@ -1,931 +1,931 @@ -// using System; -// using System.Collections.Generic; -// using System.Threading.Tasks; -// using CommonLibTest.Facades; -// using SharpHoundCommonLib; -// using SharpHoundCommonLib.Enums; -// using SharpHoundCommonLib.OutputTypes; -// using SharpHoundCommonLib.Processors; -// using Xunit; -// using Xunit.Abstractions; -// -// namespace CommonLibTest -// { -// public class LDAPPropertyTests -// { -// private readonly ITestOutputHelper _testOutputHelper; -// -// public LDAPPropertyTests(ITestOutputHelper testOutputHelper) -// { -// _testOutputHelper = testOutputHelper; -// } -// -// [Fact] -// public void LDAPPropertyProcessor_ReadDomainProperties_TestGoodData() -// { -// var mock = new MockSearchResultEntry("DC\u003dtestlab,DC\u003dlocal", new Dictionary -// { -// {"description", "TESTLAB Domain"}, -// {"msds-behavior-version", "6"} -// }, "S-1-5-21-3130019616-2776909439-2417379446", Label.Domain); -// -// var test = LDAPPropertyProcessor.ReadDomainProperties(mock); -// Assert.Contains("functionallevel", test.Keys); -// Assert.Equal("2012 R2", test["functionallevel"] as string); -// Assert.Contains("description", test.Keys); -// Assert.Equal("TESTLAB Domain", test["description"] as string); -// } -// -// [Fact] -// public void LDAPPropertyProcessor_ReadDomainProperties_TestBadFunctionalLevel() -// { -// var mock = new MockSearchResultEntry("DC\u003dtestlab,DC\u003dlocal", new Dictionary -// { -// {"msds-behavior-version", "a"} -// }, "S-1-5-21-3130019616-2776909439-2417379446", Label.Domain); -// -// var test = LDAPPropertyProcessor.ReadDomainProperties(mock); -// Assert.Contains("functionallevel", test.Keys); -// Assert.Equal("Unknown", test["functionallevel"] as string); -// } -// -// [Fact] -// public void LDAPPropertyProcessor_FunctionalLevelToString_TestFunctionalLevels() -// { -// var expected = new Dictionary -// { -// {0, "2000 Mixed/Native"}, -// {1, "2003 Interim"}, -// {2, "2003"}, -// {3, "2008"}, -// {4, "2008 R2"}, -// {5, "2012"}, -// {6, "2012 R2"}, -// {7, "2016"}, -// {-1, "Unknown"} -// }; -// -// foreach (var (key, value) in expected) -// Assert.Equal(value, LDAPPropertyProcessor.FunctionalLevelToString(key)); -// } -// -// [Fact] -// public void LDAPPropertyProcessor_ReadGPOProperties_TestGoodData() -// { -// var mock = new MockSearchResultEntry( -// "CN\u003d{94DD0260-38B5-497E-8876-10E7A96E80D0},CN\u003dPolicies,CN\u003dSystem,DC\u003dtestlab,DC\u003dlocal", -// new Dictionary -// { -// { -// "gpcfilesyspath", -// Helpers.B64ToString( -// "XFx0ZXN0bGFiLmxvY2FsXFN5c1ZvbFx0ZXN0bGFiLmxvY2FsXFBvbGljaWVzXHs5NEREMDI2MC0zOEI1LTQ5N0UtODg3Ni0xMEU3QTk2RTgwRDB9") -// }, -// {"description", "Test"} -// }, "S-1-5-21-3130019616-2776909439-2417379446", Label.GPO); -// -// var test = LDAPPropertyProcessor.ReadGPOProperties(mock); -// -// Assert.Contains("description", test.Keys); -// Assert.Equal("Test", test["description"] as string); -// Assert.Contains("gpcpath", test.Keys); -// Assert.Equal(@"\\TESTLAB.LOCAL\SYSVOL\TESTLAB.LOCAL\POLICIES\{94DD0260-38B5-497E-8876-10E7A96E80D0}", -// test["gpcpath"] as string); -// } -// -// [Fact] -// public void LDAPPropertyProcessor_ReadOUProperties_TestGoodData() -// { -// var mock = new MockSearchResultEntry("OU\u003dTestOU,DC\u003dtestlab,DC\u003dlocal", -// new Dictionary -// { -// {"description", "Test"} -// }, "2A374493-816A-4193-BEFD-D2F4132C6DCA", Label.OU); -// -// var test = LDAPPropertyProcessor.ReadOUProperties(mock); -// Assert.Contains("description", test.Keys); -// Assert.Equal("Test", test["description"] as string); -// } -// -// [Fact] -// public void LDAPPropertyProcessor_ReadGroupProperties_TestGoodData() -// { -// var mock = new MockSearchResultEntry("CN\u003dDomain Admins,CN\u003dUsers,DC\u003dtestlab,DC\u003dlocal", -// new Dictionary -// { -// {"description", "Test"}, -// {"admincount", "1"} -// }, "S-1-5-21-3130019616-2776909439-2417379446-512", Label.Group); -// -// var test = LDAPPropertyProcessor.ReadGroupProperties(mock); -// Assert.Contains("description", test.Keys); -// Assert.Equal("Test", test["description"] as string); -// Assert.Contains("admincount", test.Keys); -// Assert.True((bool)test["admincount"]); -// } -// -// [Fact] -// public void LDAPPropertyProcessor_ReadGroupProperties_TestGoodData_FalseAdminCount() -// { -// var mock = new MockSearchResultEntry("CN\u003dDomain Admins,CN\u003dUsers,DC\u003dtestlab,DC\u003dlocal", -// new Dictionary -// { -// {"description", "Test"}, -// {"admincount", "0"} -// }, "S-1-5-21-3130019616-2776909439-2417379446-512", Label.Group); -// -// var test = LDAPPropertyProcessor.ReadGroupProperties(mock); -// Assert.Contains("description", test.Keys); -// Assert.Equal("Test", test["description"] as string); -// Assert.Contains("admincount", test.Keys); -// Assert.False((bool)test["admincount"]); -// } -// -// [Fact] -// public void LDAPPropertyProcessor_ReadGroupProperties_NullAdminCount() -// { -// var mock = new MockSearchResultEntry("CN\u003dDomain Admins,CN\u003dUsers,DC\u003dtestlab,DC\u003dlocal", -// new Dictionary -// { -// {"description", "Test"} -// }, "S-1-5-21-3130019616-2776909439-2417379446-512", Label.Group); -// -// var test = LDAPPropertyProcessor.ReadGroupProperties(mock); -// Assert.Contains("description", test.Keys); -// Assert.Equal("Test", test["description"] as string); -// Assert.Contains("admincount", test.Keys); -// Assert.False((bool)test["admincount"]); -// } -// -// [Fact] -// public async Task LDAPPropertyProcessor_ReadUserProperties_TestTrustedToAuth() -// { -// var mock = new MockSearchResultEntry("CN\u003ddfm,CN\u003dUsers,DC\u003dtestlab,DC\u003dlocal", -// new Dictionary -// { -// {"description", "Test"}, -// {"useraccountcontrol", 0x1000000.ToString()}, -// {"lastlogon", "132673011142753043"}, -// {"lastlogontimestamp", "132670318095676525"}, -// {"homedirectory", @"\\win10\testdir"}, -// { -// "serviceprincipalname", new[] -// { -// "MSSQLSVC\\win10" -// } -// }, -// {"admincount", "1"}, -// { -// "sidhistory", new[] -// { -// Helpers.B64ToBytes("AQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQUQQAAA==") -// } -// }, -// {"pwdlastset", "132131667346106691"}, -// { -// "msds-allowedtodelegateto", new[] -// { -// "host/primary", -// "rdpman/win10" -// } -// } -// }, "S-1-5-21-3130019616-2776909439-2417379446-1101", Label.User); -// -// var processor = new LDAPPropertyProcessor(new MockLDAPUtils()); -// var test = await processor.ReadUserProperties(mock); -// var props = test.Props; -// var keys = props.Keys; -// -// Assert.Contains("allowedtodelegate", keys); -// var atd = props["allowedtodelegate"] as string[]; -// Assert.Equal(2, atd.Length); -// Assert.Contains("host/primary", atd); -// Assert.Contains("rdpman/win10", atd); -// -// var atdr = test.AllowedToDelegate; -// Assert.Equal(2, atdr.Length); -// var expected = new TypedPrincipal[] -// { -// new() -// { -// ObjectIdentifier = "S-1-5-21-3130019616-2776909439-2417379446-1001", -// ObjectType = Label.Computer -// }, -// new() -// { -// ObjectIdentifier = "S-1-5-21-3130019616-2776909439-2417379446-1104", -// ObjectType = Label.Computer -// } -// }; -// Assert.Equal(expected, atdr); -// } -// -// [Fact] -// public async Task LDAPPropertyProcessor_ReadUserProperties_NullAdminCount() -// { -// var mock = new MockSearchResultEntry("CN\u003ddfm,CN\u003dUsers,DC\u003dtestlab,DC\u003dlocal", -// new Dictionary -// { -// {"description", "Test"}, -// {"useraccountcontrol", "66048"}, -// {"lastlogon", "132673011142753043"}, -// {"lastlogontimestamp", "132670318095676525"}, -// {"homedirectory", @"\\win10\testdir"}, -// { -// "serviceprincipalname", new[] -// { -// "MSSQLSVC\\win10" -// } -// }, -// { -// "sidhistory", new[] -// { -// Helpers.B64ToBytes("AQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQUQQAAA==") -// } -// }, -// {"pwdlastset", "132131667346106691"} -// }, "S-1-5-21-3130019616-2776909439-2417379446-1101", Label.User); -// -// var processor = new LDAPPropertyProcessor(new MockLDAPUtils()); -// var test = await processor.ReadUserProperties(mock); -// var props = test.Props; -// var keys = props.Keys; -// Assert.Contains("admincount", keys); -// Assert.False((bool)props["admincount"]); -// } -// -// [WindowsOnlyFact] -// public async Task LDAPPropertyProcessor_ReadUserProperties_HappyPath() -// { -// var mock = new MockSearchResultEntry("CN\u003ddfm,CN\u003dUsers,DC\u003dtestlab,DC\u003dlocal", -// new Dictionary -// { -// {"description", "Test"}, -// {"useraccountcontrol", "66048"}, -// {"lastlogon", "132673011142753043"}, -// {"lastlogontimestamp", "132670318095676525"}, -// {"homedirectory", @"\\win10\testdir"}, -// {"mail", "test@testdomain.com"}, -// { -// "serviceprincipalname", new[] -// { -// "MSSQLSVC/win10" -// } -// }, -// {"admincount", "1"}, -// { -// "sidhistory", new[] -// { -// Helpers.B64ToBytes("AQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQUQQAAA==") -// } -// }, -// {"pwdlastset", "132131667346106691"} -// }, "S-1-5-21-3130019616-2776909439-2417379446-1101", Label.User); -// -// var processor = new LDAPPropertyProcessor(new MockLDAPUtils()); -// var test = await processor.ReadUserProperties(mock); -// var props = test.Props; -// var keys = props.Keys; -// -// //Random Stuff -// Assert.Contains("description", keys); -// Assert.Equal("Test", props["description"] as string); -// Assert.Contains("admincount", keys); -// Assert.True((bool)props["admincount"]); -// Assert.Contains("lastlogon", keys); -// Assert.Equal(1622827514, (long)props["lastlogon"]); -// Assert.Contains("lastlogontimestamp", keys); -// Assert.Equal(1622558209, (long)props["lastlogontimestamp"]); -// Assert.Contains("pwdlastset", keys); -// Assert.Equal(1568693134, (long)props["pwdlastset"]); -// Assert.Contains("homedirectory", keys); -// Assert.Equal(@"\\win10\testdir", props["homedirectory"] as string); -// Assert.Contains("email", keys); -// Assert.Equal("test@testdomain.com", props["email"] as string); -// -// //UAC stuff -// Assert.Contains("sensitive", keys); -// Assert.False((bool)props["sensitive"]); -// Assert.Contains("dontreqpreauth", keys); -// Assert.False((bool)props["dontreqpreauth"]); -// Assert.Contains("passwordnotreqd", keys); -// Assert.False((bool)props["passwordnotreqd"]); -// Assert.Contains("unconstraineddelegation", keys); -// Assert.False((bool)props["unconstraineddelegation"]); -// Assert.Contains("enabled", keys); -// Assert.True((bool)props["enabled"]); -// Assert.Contains("trustedtoauth", keys); -// Assert.False((bool)props["trustedtoauth"]); -// -// //SPN -// Assert.Contains("hasspn", keys); -// Assert.True((bool)props["hasspn"]); -// Assert.Contains("serviceprincipalnames", keys); -// Assert.Contains("MSSQLSVC/win10", props["serviceprincipalnames"] as string[]); -// -// //SidHistory -// Assert.Contains("sidhistory", keys); -// var sh = props["sidhistory"] as string[]; -// Assert.Single(sh); -// Assert.Contains("S-1-5-21-3130019616-2776909439-2417379446-1105", sh); -// Assert.Single(test.SidHistory); -// Assert.Contains(new TypedPrincipal -// { -// ObjectIdentifier = "S-1-5-21-3130019616-2776909439-2417379446-1105", -// ObjectType = Label.User -// }, test.SidHistory); -// } -// -// [Fact] -// public async Task LDAPPropertyProcessor_ReadUserProperties_TestBadPaths() -// { -// var mock = new MockSearchResultEntry("CN\u003ddfm,CN\u003dUsers,DC\u003dtestlab,DC\u003dlocal", -// new Dictionary -// { -// {"description", "Test"}, -// {"useraccountcontrol", "abc"}, -// {"lastlogon", "132673011142753043"}, -// {"lastlogontimestamp", "132670318095676525"}, -// {"homedirectory", @"\\win10\testdir"}, -// { -// "serviceprincipalname", new[] -// { -// "MSSQLSVC/win10" -// } -// }, -// {"admincount", "c"}, -// { -// "sidhistory", new[] -// { -// Array.Empty() -// } -// }, -// {"pwdlastset", "132131667346106691"} -// }, "S-1-5-21-3130019616-2776909439-2417379446-1101", Label.User); -// -// var processor = new LDAPPropertyProcessor(new MockLDAPUtils()); -// var test = await processor.ReadUserProperties(mock); -// var props = test.Props; -// var keys = props.Keys; -// -// Assert.Contains("sidhistory", keys); -// Assert.Empty(props["sidhistory"] as string[]); -// Assert.Contains("admincount", keys); -// Assert.False((bool)props["admincount"]); -// Assert.Contains("sensitive", keys); -// Assert.Contains("dontreqpreauth", keys); -// Assert.Contains("passwordnotreqd", keys); -// Assert.Contains("unconstraineddelegation", keys); -// Assert.Contains("pwdneverexpires", keys); -// Assert.Contains("enabled", keys); -// Assert.Contains("trustedtoauth", keys); -// Assert.False((bool)props["trustedtoauth"]); -// Assert.False((bool)props["sensitive"]); -// Assert.False((bool)props["dontreqpreauth"]); -// Assert.False((bool)props["passwordnotreqd"]); -// Assert.False((bool)props["unconstraineddelegation"]); -// Assert.False((bool)props["pwdneverexpires"]); -// Assert.True((bool)props["enabled"]); -// } -// -// [WindowsOnlyFact] -// public async Task LDAPPropertyProcessor_ReadComputerProperties_HappyPath() -// { -// //TODO: Add coverage for allowedtoact -// var mock = new MockSearchResultEntry("CN\u003dWIN10,OU\u003dTestOU,DC\u003dtestlab,DC\u003dlocal", -// new Dictionary -// { -// {"description", "Test"}, -// {"useraccountcontrol", 0x1001000.ToString()}, -// {"lastlogon", "132673011142753043"}, -// {"lastlogontimestamp", "132670318095676525"}, -// {"operatingsystem", "Windows 10 Enterprise"}, -// {"operatingsystemservicepack", "1607"}, -// {"mail", "test@testdomain.com"}, -// {"admincount", "c"}, -// { -// "sidhistory", new[] -// { -// Helpers.B64ToBytes("AQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQUQQAAA==") -// } -// }, -// { -// "msds-allowedtodelegateto", new[] -// { -// "ldap/PRIMARY.testlab.local/testlab.local", -// "ldap/PRIMARY.testlab.local", -// "ldap/PRIMARY" -// } -// }, -// {"pwdlastset", "132131667346106691"}, -// { -// "serviceprincipalname", new[] -// { -// "WSMAN/WIN10", -// "WSMAN/WIN10.testlab.local", -// "RestrictedKrbHost/WIN10", -// "HOST/WIN10", -// "RestrictedKrbHost/WIN10.testlab.local", -// "HOST/WIN10.testlab.local" -// } -// } -// }, "S-1-5-21-3130019616-2776909439-2417379446-1101", Label.Computer); -// -// var processor = new LDAPPropertyProcessor(new MockLDAPUtils()); -// var test = await processor.ReadComputerProperties(mock); -// var props = test.Props; -// var keys = props.Keys; -// -// //UAC -// Assert.Contains("enabled", keys); -// Assert.Contains("unconstraineddelegation", keys); -// Assert.Contains("trustedtoauth", keys); -// Assert.Contains("isdc", keys); -// Assert.Contains("lastlogon", keys); -// Assert.Contains("lastlogontimestamp", keys); -// Assert.Contains("pwdlastset", keys); -// Assert.True((bool)props["enabled"]); -// Assert.False((bool)props["unconstraineddelegation"]); -// Assert.True((bool)props["trustedtoauth"]); -// Assert.False((bool)props["isdc"]); -// -// Assert.Contains("lastlogon", keys); -// Assert.Equal(1622827514, (long)props["lastlogon"]); -// Assert.Contains("lastlogontimestamp", keys); -// Assert.Equal(1622558209, (long)props["lastlogontimestamp"]); -// Assert.Contains("pwdlastset", keys); -// Assert.Equal(1568693134, (long)props["pwdlastset"]); -// -// //AllowedToDelegate -// Assert.Single(test.AllowedToDelegate); -// Assert.Contains(new TypedPrincipal -// { -// ObjectIdentifier = "S-1-5-21-3130019616-2776909439-2417379446-1001", -// ObjectType = Label.Computer -// }, test.AllowedToDelegate); -// -// //Other Stuff -// Assert.Contains("serviceprincipalnames", keys); -// Assert.Equal(6, (props["serviceprincipalnames"] as string[]).Length); -// Assert.Contains("operatingsystem", keys); -// Assert.Equal("Windows 10 Enterprise 1607", props["operatingsystem"] as string); -// Assert.Contains("description", keys); -// Assert.Equal("Test", props["description"] as string); -// Assert.Contains("email", keys); -// Assert.Equal("test@testdomain.com", props["email"] as string); -// -// //SidHistory -// Assert.Contains("sidhistory", keys); -// var sh = props["sidhistory"] as string[]; -// Assert.Single(sh); -// Assert.Contains("S-1-5-21-3130019616-2776909439-2417379446-1105", sh); -// Assert.Single(test.SidHistory); -// Assert.Contains(new TypedPrincipal -// { -// ObjectIdentifier = "S-1-5-21-3130019616-2776909439-2417379446-1105", -// ObjectType = Label.User -// }, test.SidHistory); -// } -// -// [Fact] -// public async Task LDAPPropertyProcessor_ReadComputerProperties_TestBadPaths() -// { -// var mock = new MockSearchResultEntry("CN\u003dWIN10,OU\u003dTestOU,DC\u003dtestlab,DC\u003dlocal", -// new Dictionary -// { -// {"description", "Test"}, -// {"useraccountcontrol", "abc"}, -// {"lastlogon", "132673011142753043"}, -// {"lastlogontimestamp", "132670318095676525"}, -// {"operatingsystem", "Windows 10 Enterprise"}, -// {"admincount", "c"}, -// { -// "sidhistory", new[] -// { -// Array.Empty() -// } -// }, -// { -// "msds-allowedToDelegateTo", new[] -// { -// "ldap/PRIMARY.testlab.local/testlab.local", -// "ldap/PRIMARY.testlab.local", -// "ldap/PRIMARY" -// } -// }, -// {"pwdlastset", "132131667346106691"}, -// { -// "serviceprincipalname", new[] -// { -// "WSMAN/WIN10", -// "WSMAN/WIN10.testlab.local", -// "RestrictedKrbHost/WIN10", -// "HOST/WIN10", -// "RestrictedKrbHost/WIN10.testlab.local", -// "HOST/WIN10.testlab.local" -// } -// } -// }, "S-1-5-21-3130019616-2776909439-2417379446-1101", Label.Computer); -// -// var processor = new LDAPPropertyProcessor(new MockLDAPUtils()); -// var test = await processor.ReadComputerProperties(mock); -// var props = test.Props; -// var keys = props.Keys; -// -// Assert.Contains("unconstraineddelegation", keys); -// Assert.Contains("enabled", keys); -// Assert.Contains("trustedtoauth", keys); -// Assert.False((bool)props["unconstraineddelegation"]); -// Assert.True((bool)props["enabled"]); -// Assert.False((bool)props["trustedtoauth"]); -// Assert.Contains("sidhistory", keys); -// Assert.Empty(props["sidhistory"] as string[]); -// } -// -// -// [Fact] -// public async Task LDAPPropertyProcessor_ReadComputerProperties_TestDumpSMSAPassword() -// { -// var mock = new MockSearchResultEntry("CN\u003dWIN10,OU\u003dTestOU,DC\u003dtestlab,DC\u003dlocal", -// new Dictionary -// { -// {"description", "Test"}, -// {"useraccountcontrol", 0x1001000.ToString()}, -// {"lastlogon", "132673011142753043"}, -// {"lastlogontimestamp", "132670318095676525"}, -// {"operatingsystem", "Windows 10 Enterprise"}, -// {"operatingsystemservicepack", "1607"}, -// {"admincount", "c"}, -// { -// "sidhistory", new[] -// { -// Helpers.B64ToBytes("AQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQUQQAAA==") -// } -// }, -// { -// "msds-allowedtodelegateto", new[] -// { -// "ldap/PRIMARY.testlab.local/testlab.local", -// "ldap/PRIMARY.testlab.local", -// "ldap/PRIMARY" -// } -// }, -// {"pwdlastset", "132131667346106691"}, -// { -// "serviceprincipalname", new[] -// { -// "WSMAN/WIN10", -// "WSMAN/WIN10.testlab.local", -// "RestrictedKrbHost/WIN10", -// "HOST/WIN10", -// "RestrictedKrbHost/WIN10.testlab.local", -// "HOST/WIN10.testlab.local" -// } -// }, -// { -// "msds-hostserviceaccount", new[] -// { -// "CN=dfm,CN=Users,DC=testlab,DC=local", -// "CN=krbtgt,CN=Users,DC=testlab,DC=local" -// } -// } -// }, "S-1-5-21-3130019616-2776909439-2417379446-1101", Label.Computer); -// -// var processor = new LDAPPropertyProcessor(new MockLDAPUtils()); -// var test = await processor.ReadComputerProperties(mock); -// -// var expected = new TypedPrincipal[] -// { -// new() -// { -// ObjectIdentifier = "S-1-5-21-3130019616-2776909439-2417379446-1105", -// ObjectType = Label.User -// }, -// new() -// { -// ObjectIdentifier = "S-1-5-21-3130019616-2776909439-2417379446-502", -// ObjectType = Label.User -// } -// }; -// -// var testDumpSMSAPassword = test.DumpSMSAPassword; -// Assert.Equal(2, testDumpSMSAPassword.Length); -// Assert.Equal(expected, testDumpSMSAPassword); -// -// } -// -// [Fact] -// public void LDAPPropertyProcessor_ReadRootCAProperties() -// { -// var mock = new MockSearchResultEntry( -// "CN\u003dDUMPSTER-DC01-CA,CN\u003dCERTIFICATION AUTHORITIES,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dDUMPSTER,DC\u003dFIRE", -// new Dictionary -// { -// {"description", null}, -// {"domain", "DUMPSTER.FIRE"}, -// {"name", "DUMPSTER-DC01-CA@DUMPSTER.FIRE"}, -// {"domainsid", "S-1-5-21-2697957641-2271029196-387917394"}, -// {"whencreated", 1683986131}, -// }, "2F9F3630-F46A-49BF-B186-6629994EBCF9", Label.RootCA); -// -// var test = LDAPPropertyProcessor.ReadRootCAProperties(mock); -// var keys = test.Keys; -// -// //These are not common properties -// Assert.DoesNotContain("domain", keys); -// Assert.DoesNotContain("name", keys); -// Assert.DoesNotContain("domainsid", keys); -// -// Assert.Contains("description", keys); -// Assert.Contains("whencreated", keys); -// } -// -// [Fact] -// public void LDAPPropertyProcessor_ReadAIACAProperties() -// { -// var mock = new MockSearchResultEntry( -// "CN\u003dDUMPSTER-DC01-CA,CN\u003dAIA,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dDUMPSTER,DC\u003dFIRE", -// new Dictionary -// { -// {"description", null}, -// {"domain", "DUMPSTER.FIRE"}, -// {"name", "DUMPSTER-DC01-CA@DUMPSTER.FIRE"}, -// {"domainsid", "S-1-5-21-2697957641-2271029196-387917394"}, -// {"whencreated", 1683986131}, -// {"hascrosscertificatepair", true}, -// }, "2F9F3630-F46A-49BF-B186-6629994EBCF9", Label.AIACA); -// -// var test = LDAPPropertyProcessor.ReadAIACAProperties(mock); -// var keys = test.Keys; -// -// //These are not common properties -// Assert.DoesNotContain("domain", keys); -// Assert.DoesNotContain("name", keys); -// Assert.DoesNotContain("domainsid", keys); -// -// Assert.Contains("description", keys); -// Assert.Contains("whencreated", keys); -// Assert.Contains("crosscertificatepair", keys); -// } -// -// [Fact] -// public void LDAPPropertyProcessor_ReadNTAuthStoreProperties() -// { -// var mock = new MockSearchResultEntry("CN\u003dNTAUTHCERTIFICATES,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dDUMPSTER,DC\u003dFIRE", -// new Dictionary -// { -// {"description", null}, -// {"domain", "DUMPSTER.FIRE"}, -// {"name", "NTAUTHCERTIFICATES@DUMPSTER.FIRE"}, -// {"domainsid", "S-1-5-21-2697957641-2271029196-387917394"}, -// {"whencreated", 1683986131}, -// }, "2F9F3630-F46A-49BF-B186-6629994EBCF9", Label.NTAuthStore); -// -// var test = LDAPPropertyProcessor.ReadNTAuthStoreProperties(mock); -// var keys = test.Keys; -// -// //These are not common properties -// Assert.DoesNotContain("domain", keys); -// Assert.DoesNotContain("name", keys); -// Assert.DoesNotContain("domainsid", keys); -// -// Assert.Contains("description", keys); -// Assert.Contains("whencreated", keys); -// } -// -// [Fact] -// public void LDAPPropertyProcessor_ReadCertTemplateProperties() -// { -// var mock = new MockSearchResultEntry("CN\u003dWORKSTATION,CN\u003dCERTIFICATE TEMPLATES,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dEXTERNAL,DC\u003dLOCAL", -// new Dictionary -// { -// {"domain", "EXTERNAL.LOCAL"}, -// {"name", "WORKSTATION@EXTERNAL.LOCAL"}, -// {"domainsid", "S-1-5-21-3702535222-3822678775-2090119576"}, -// {"description", null}, -// {"whencreated", 1683986183}, -// {"validityperiod", 31536000}, -// {"renewalperiod", 3628800}, -// {"schemaversion", 2}, -// {"displayname", "Workstation Authentication"}, -// {"oid", "1.3.6.1.4.1.311.21.8.4571196.1884641.3293620.10686285.12068043.134.1.30"}, -// {"enrollmentflag", 32}, -// {"requiresmanagerapproval", false}, -// {"certificatenameflag", 0x8000000}, -// {"ekus", new[] -// {"1.3.6.1.5.5.7.3.2"} -// }, -// {LDAPProperties.CertificateApplicationPolicy, new[] -// {"1.3.6.1.5.5.7.3.2"} -// }, -// {LDAPProperties.CertificatePolicy, new[] -// {"1.3.6.1.5.5.7.3.2"} -// }, -// {"authorizedsignatures", 1}, -// {"applicationpolicies", new[] -// { "1.3.6.1.4.1.311.20.2.1"} -// }, -// {"issuancepolicies", new[] -// {"1.3.6.1.4.1.311.21.8.4571196.1884641.3293620.10686285.12068043.134.1.400", -// "1.3.6.1.4.1.311.21.8.4571196.1884641.3293620.10686285.12068043.134.1.402"} -// }, -// }, "2F9F3630-F46A-49BF-B186-6629994EBCF9", Label.CertTemplate); -// -// var test = LDAPPropertyProcessor.ReadCertTemplateProperties(mock); -// var keys = test.Keys; -// -// //These are not common properties -// Assert.DoesNotContain("domain", keys); -// Assert.DoesNotContain("name", keys); -// Assert.DoesNotContain("domainsid", keys); -// -// Assert.Contains("description", keys); -// Assert.Contains("whencreated", keys); -// Assert.Contains("validityperiod", keys); -// Assert.Contains("renewalperiod", keys); -// Assert.Contains("schemaversion", keys); -// Assert.Contains("displayname", keys); -// Assert.Contains("oid", keys); -// Assert.Contains("enrollmentflag", keys); -// Assert.Contains("requiresmanagerapproval", keys); -// Assert.Contains("certificatenameflag", keys); -// Assert.Contains("enrolleesuppliessubject", keys); -// Assert.Contains("subjectaltrequireupn", keys); -// Assert.Contains("subjectaltrequiredns", keys); -// Assert.Contains("subjectaltrequiredomaindns", keys); -// Assert.Contains("subjectaltrequireemail", keys); -// Assert.Contains("subjectaltrequirespn", keys); -// Assert.Contains("subjectrequireemail", keys); -// Assert.Contains("ekus", keys); -// Assert.Contains("certificateapplicationpolicy", keys); -// var hasPolicy = test.TryGetValue("certificatepolicy", out var policies); -// Assert.True(hasPolicy); -// if (policies is string[] e) -// { -// Assert.Contains("1.3.6.1.5.5.7.3.2", e); -// } -// Assert.Contains("authorizedsignatures", keys); -// Assert.Contains("applicationpolicies", keys); -// Assert.Contains("issuancepolicies", keys); -// -// } -// // -// // [Fact] -// // public void LDAPPropertyProcessor_ReadIssuancePolicyProperties() -// // { -// // var mock = new MockSearchResultEntry("CN\u003d6250993.11BB1AB25A8A65E9FCDF709FCDD5FBC6,CN\u003dOID,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dESC10,DC\u003dLOCAL", -// // new Dictionary -// // { -// // {"domain", "ESC10.LOCAL"}, -// // {"name", "KEYADMINSOID@ESC10.LOCAL"}, -// // {"domainsid", "S-1-5-21-3662707843-2053279151-3839588741"}, -// // {"description", null}, -// // {"whencreated", 1712567279}, -// // {"displayname", "KeyAdminsOID"}, -// // {"certtemplateoid", "1.3.6.1.4.1.311.21.8.4571196.1884641.3293620.10686285.12068043.134.1.30"}, -// // {"msds-oidtogrouplink", "CN=ENTERPRISE KEY ADMINS,CN=USERS,DC=ESC10,DC=LOCAL"} -// // , -// // }, "1E5311A8-E949-4E02-8E08-234ED63200DE", Label.IssuancePolicy); -// // -// // var mockLDAPUtils = new MockLDAPUtils(); -// // var ldapPropertyProcessor = new LDAPPropertyProcessor(mockLDAPUtils); -// // -// // -// // var test = ldapPropertyProcessor.ReadIssuancePolicyProperties(mock); -// // var keys = test.Props.Keys; -// // -// // //These are not common properties -// // Assert.DoesNotContain("domain", keys); -// // Assert.DoesNotContain("name", keys); -// // Assert.DoesNotContain("domainsid", keys); -// // -// // Assert.Contains("description", keys); -// // Assert.Contains("whencreated", keys); -// // Assert.Contains("displayname", keys); -// // Assert.Contains("certtemplateoid", keys); -// // Assert.Contains("oidgrouplink", keys); -// // } -// // -// // [Fact] -// // public void LDAPPropertyProcessor_ReadIssuancePolicyProperties_NoOIDGroupLink() -// // { -// // var mock = new MockSearchResultEntry("CN\u003d6250993.11BB1AB25A8A65E9FCDF709FCDD5FBC6,CN\u003dOID,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dESC10,DC\u003dLOCAL", -// // new Dictionary -// // { -// // {"domain", "ESC10.LOCAL"}, -// // {"name", "KEYADMINSOID@ESC10.LOCAL"}, -// // {"domainsid", "S-1-5-21-3662707843-2053279151-3839588741"}, -// // {"description", null}, -// // {"whencreated", 1712567279}, -// // {"displayname", "KeyAdminsOID"}, -// // {"certtemplateoid", "1.3.6.1.4.1.311.21.8.4571196.1884641.3293620.10686285.12068043.134.1.30"}, -// // {"msds-oidtogrouplink", null} -// // , -// // }, "1E5311A8-E949-4E02-8E08-234ED63200DE", Label.IssuancePolicy); -// // -// // var mockLDAPUtils = new MockLDAPUtils(); -// // var ldapPropertyProcessor = new LDAPPropertyProcessor(mockLDAPUtils); -// // -// // -// // var test = ldapPropertyProcessor.ReadIssuancePolicyProperties(mock); -// // var keys = test.Props.Keys; -// // -// // //These are not common properties -// // Assert.DoesNotContain("domain", keys); -// // Assert.DoesNotContain("name", keys); -// // Assert.DoesNotContain("domainsid", keys); -// // Assert.DoesNotContain("oidgrouplink", keys); -// // -// // Assert.Contains("description", keys); -// // Assert.Contains("whencreated", keys); -// // Assert.Contains("displayname", keys); -// // Assert.Contains("certtemplateoid", keys); -// // } -// -// [Fact] -// public void LDAPPropertyProcessor_ParseAllProperties() -// { -// var mock = new MockSearchResultEntry("CN\u003dNTAUTHCERTIFICATES,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dDUMPSTER,DC\u003dFIRE", -// new Dictionary -// { -// {"description", null}, -// {"domain", "DUMPSTER.FIRE"}, -// {"name", "NTAUTHCERTIFICATES@DUMPSTER.FIRE"}, -// {"domainsid", "S-1-5-21-2697957641-2271029196-387917394"}, -// {"whencreated", 1683986131}, -// }, "2F9F3630-F46A-49BF-B186-6629994EBCF9", Label.NTAuthStore); -// -// var processor = new LDAPPropertyProcessor(new MockLDAPUtils()); -// var props = processor.ParseAllProperties(mock); -// var keys = props.Keys; -// -// //These are reserved properties and so they should be filtered out -// Assert.DoesNotContain("description", keys); -// Assert.DoesNotContain("whencreated", keys); -// Assert.DoesNotContain("name", keys); -// -// Assert.Contains("domainsid", keys); -// Assert.Contains("domain", keys); -// } -// -// [Fact] -// public void LDAPPropertyProcessor_ParseAllProperties_NoProperties() -// { -// var mock = new MockSearchResultEntry("CN\u003dNTAUTHCERTIFICATES,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dDUMPSTER,DC\u003dFIRE", -// new Dictionary -// { }, "2F9F3630-F46A-49BF-B186-6629994EBCF9", Label.NTAuthStore); -// -// var processor = new LDAPPropertyProcessor(new MockLDAPUtils()); -// var props = processor.ParseAllProperties(mock); -// var keys = props.Keys; -// -// Assert.Empty(keys); -// -// } -// -// [Fact] -// public void LDAPPropertyProcessor_ParseAllProperties_CollectionCountOne_NullString() -// { -// var mock = new MockSearchResultEntry("CN\u003dNTAUTHCERTIFICATES,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dDUMPSTER,DC\u003dFIRE", -// new Dictionary -// {{"domainsid", null} }, "2F9F3630-F46A-49BF-B186-6629994EBCF9", Label.NTAuthStore); -// -// var processor = new LDAPPropertyProcessor(new MockLDAPUtils()); -// var props = processor.ParseAllProperties(mock); -// var keys = props.Keys; -// -// Assert.Empty(keys); -// } -// -// [Fact] -// public void LDAPPropertyProcessor_ParseAllProperties_CollectionCountOne_BadPasswordTime() -// { -// var mock = new MockSearchResultEntry("CN\u003dNTAUTHCERTIFICATES,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dDUMPSTER,DC\u003dFIRE", -// new Dictionary -// {{"badpasswordtime", "130435290000000000"} }, "2F9F3630-F46A-49BF-B186-6629994EBCF9", Label.NTAuthStore); -// -// var processor = new LDAPPropertyProcessor(new MockLDAPUtils()); -// var props = processor.ParseAllProperties(mock); -// var keys = props.Keys; -// -// Assert.Contains("badpasswordtime", keys); -// Assert.Single(keys); -// } -// -// [Fact] -// public void LDAPPropertyProcessor_ParseAllProperties_CollectionCountOne_NotBadPasswordTime() -// { -// var mock = new MockSearchResultEntry("CN\u003dNTAUTHCERTIFICATES,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dDUMPSTER,DC\u003dFIRE", -// new Dictionary -// {{"domainsid", "S-1-5-21-2697957641-2271029196-387917394"}}, "2F9F3630-F46A-49BF-B186-6629994EBCF9", Label.NTAuthStore); -// -// var processor = new LDAPPropertyProcessor(new MockLDAPUtils()); -// var props = processor.ParseAllProperties(mock); -// var keys = props.Keys; -// -// Assert.Contains("domainsid", keys); -// Assert.Single(keys); -// } -// -// } -// } +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using CommonLibTest.Facades; +using SharpHoundCommonLib; +using SharpHoundCommonLib.Enums; +using SharpHoundCommonLib.OutputTypes; +using SharpHoundCommonLib.Processors; +using Xunit; +using Xunit.Abstractions; + +namespace CommonLibTest +{ + public class LDAPPropertyTests + { + private readonly ITestOutputHelper _testOutputHelper; + + public LDAPPropertyTests(ITestOutputHelper testOutputHelper) + { + _testOutputHelper = testOutputHelper; + } + + [Fact] + public void LDAPPropertyProcessor_ReadDomainProperties_TestGoodData() + { + var mock = new MockSearchResultEntry("DC\u003dtestlab,DC\u003dlocal", new Dictionary + { + {"description", "TESTLAB Domain"}, + {"msds-behavior-version", "6"} + }, "S-1-5-21-3130019616-2776909439-2417379446", Label.Domain); + + var test = LDAPPropertyProcessor.ReadDomainProperties(mock); + Assert.Contains("functionallevel", test.Keys); + Assert.Equal("2012 R2", test["functionallevel"] as string); + Assert.Contains("description", test.Keys); + Assert.Equal("TESTLAB Domain", test["description"] as string); + } + + [Fact] + public void LDAPPropertyProcessor_ReadDomainProperties_TestBadFunctionalLevel() + { + var mock = new MockSearchResultEntry("DC\u003dtestlab,DC\u003dlocal", new Dictionary + { + {"msds-behavior-version", "a"} + }, "S-1-5-21-3130019616-2776909439-2417379446", Label.Domain); + + var test = LDAPPropertyProcessor.ReadDomainProperties(mock); + Assert.Contains("functionallevel", test.Keys); + Assert.Equal("Unknown", test["functionallevel"] as string); + } + + [Fact] + public void LDAPPropertyProcessor_FunctionalLevelToString_TestFunctionalLevels() + { + var expected = new Dictionary + { + {0, "2000 Mixed/Native"}, + {1, "2003 Interim"}, + {2, "2003"}, + {3, "2008"}, + {4, "2008 R2"}, + {5, "2012"}, + {6, "2012 R2"}, + {7, "2016"}, + {-1, "Unknown"} + }; + + foreach (var (key, value) in expected) + Assert.Equal(value, LDAPPropertyProcessor.FunctionalLevelToString(key)); + } + + [Fact] + public void LDAPPropertyProcessor_ReadGPOProperties_TestGoodData() + { + var mock = new MockSearchResultEntry( + "CN\u003d{94DD0260-38B5-497E-8876-10E7A96E80D0},CN\u003dPolicies,CN\u003dSystem,DC\u003dtestlab,DC\u003dlocal", + new Dictionary + { + { + "gpcfilesyspath", + Helpers.B64ToString( + "XFx0ZXN0bGFiLmxvY2FsXFN5c1ZvbFx0ZXN0bGFiLmxvY2FsXFBvbGljaWVzXHs5NEREMDI2MC0zOEI1LTQ5N0UtODg3Ni0xMEU3QTk2RTgwRDB9") + }, + {"description", "Test"} + }, "S-1-5-21-3130019616-2776909439-2417379446", Label.GPO); + + var test = LDAPPropertyProcessor.ReadGPOProperties(mock); + + Assert.Contains("description", test.Keys); + Assert.Equal("Test", test["description"] as string); + Assert.Contains("gpcpath", test.Keys); + Assert.Equal(@"\\TESTLAB.LOCAL\SYSVOL\TESTLAB.LOCAL\POLICIES\{94DD0260-38B5-497E-8876-10E7A96E80D0}", + test["gpcpath"] as string); + } + + [Fact] + public void LDAPPropertyProcessor_ReadOUProperties_TestGoodData() + { + var mock = new MockSearchResultEntry("OU\u003dTestOU,DC\u003dtestlab,DC\u003dlocal", + new Dictionary + { + {"description", "Test"} + }, "2A374493-816A-4193-BEFD-D2F4132C6DCA", Label.OU); + + var test = LDAPPropertyProcessor.ReadOUProperties(mock); + Assert.Contains("description", test.Keys); + Assert.Equal("Test", test["description"] as string); + } + + [Fact] + public void LDAPPropertyProcessor_ReadGroupProperties_TestGoodData() + { + var mock = new MockSearchResultEntry("CN\u003dDomain Admins,CN\u003dUsers,DC\u003dtestlab,DC\u003dlocal", + new Dictionary + { + {"description", "Test"}, + {"admincount", "1"} + }, "S-1-5-21-3130019616-2776909439-2417379446-512", Label.Group); + + var test = LDAPPropertyProcessor.ReadGroupProperties(mock); + Assert.Contains("description", test.Keys); + Assert.Equal("Test", test["description"] as string); + Assert.Contains("admincount", test.Keys); + Assert.True((bool)test["admincount"]); + } + + [Fact] + public void LDAPPropertyProcessor_ReadGroupProperties_TestGoodData_FalseAdminCount() + { + var mock = new MockSearchResultEntry("CN\u003dDomain Admins,CN\u003dUsers,DC\u003dtestlab,DC\u003dlocal", + new Dictionary + { + {"description", "Test"}, + {"admincount", "0"} + }, "S-1-5-21-3130019616-2776909439-2417379446-512", Label.Group); + + var test = LDAPPropertyProcessor.ReadGroupProperties(mock); + Assert.Contains("description", test.Keys); + Assert.Equal("Test", test["description"] as string); + Assert.Contains("admincount", test.Keys); + Assert.False((bool)test["admincount"]); + } + + [Fact] + public void LDAPPropertyProcessor_ReadGroupProperties_NullAdminCount() + { + var mock = new MockSearchResultEntry("CN\u003dDomain Admins,CN\u003dUsers,DC\u003dtestlab,DC\u003dlocal", + new Dictionary + { + {"description", "Test"} + }, "S-1-5-21-3130019616-2776909439-2417379446-512", Label.Group); + + var test = LDAPPropertyProcessor.ReadGroupProperties(mock); + Assert.Contains("description", test.Keys); + Assert.Equal("Test", test["description"] as string); + Assert.Contains("admincount", test.Keys); + Assert.False((bool)test["admincount"]); + } + + [Fact] + public async Task LDAPPropertyProcessor_ReadUserProperties_TestTrustedToAuth() + { + var mock = new MockSearchResultEntry("CN\u003ddfm,CN\u003dUsers,DC\u003dtestlab,DC\u003dlocal", + new Dictionary + { + {"description", "Test"}, + {"useraccountcontrol", 0x1000000.ToString()}, + {"lastlogon", "132673011142753043"}, + {"lastlogontimestamp", "132670318095676525"}, + {"homedirectory", @"\\win10\testdir"}, + { + "serviceprincipalname", new[] + { + "MSSQLSVC\\win10" + } + }, + {"admincount", "1"}, + { + "sidhistory", new[] + { + Helpers.B64ToBytes("AQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQUQQAAA==") + } + }, + {"pwdlastset", "132131667346106691"}, + { + "msds-allowedtodelegateto", new[] + { + "host/primary", + "rdpman/win10" + } + } + }, "S-1-5-21-3130019616-2776909439-2417379446-1101", Label.User); + + var processor = new LDAPPropertyProcessor(new MockLDAPUtils()); + var test = await processor.ReadUserProperties(mock); + var props = test.Props; + var keys = props.Keys; + + Assert.Contains("allowedtodelegate", keys); + var atd = props["allowedtodelegate"] as string[]; + Assert.Equal(2, atd.Length); + Assert.Contains("host/primary", atd); + Assert.Contains("rdpman/win10", atd); + + var atdr = test.AllowedToDelegate; + Assert.Equal(2, atdr.Length); + var expected = new TypedPrincipal[] + { + new() + { + ObjectIdentifier = "S-1-5-21-3130019616-2776909439-2417379446-1001", + ObjectType = Label.Computer + }, + new() + { + ObjectIdentifier = "S-1-5-21-3130019616-2776909439-2417379446-1104", + ObjectType = Label.Computer + } + }; + Assert.Equal(expected, atdr); + } + + [Fact] + public async Task LDAPPropertyProcessor_ReadUserProperties_NullAdminCount() + { + var mock = new MockSearchResultEntry("CN\u003ddfm,CN\u003dUsers,DC\u003dtestlab,DC\u003dlocal", + new Dictionary + { + {"description", "Test"}, + {"useraccountcontrol", "66048"}, + {"lastlogon", "132673011142753043"}, + {"lastlogontimestamp", "132670318095676525"}, + {"homedirectory", @"\\win10\testdir"}, + { + "serviceprincipalname", new[] + { + "MSSQLSVC\\win10" + } + }, + { + "sidhistory", new[] + { + Helpers.B64ToBytes("AQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQUQQAAA==") + } + }, + {"pwdlastset", "132131667346106691"} + }, "S-1-5-21-3130019616-2776909439-2417379446-1101", Label.User); + + var processor = new LDAPPropertyProcessor(new MockLDAPUtils()); + var test = await processor.ReadUserProperties(mock); + var props = test.Props; + var keys = props.Keys; + Assert.Contains("admincount", keys); + Assert.False((bool)props["admincount"]); + } + + [WindowsOnlyFact] + public async Task LDAPPropertyProcessor_ReadUserProperties_HappyPath() + { + var mock = new MockSearchResultEntry("CN\u003ddfm,CN\u003dUsers,DC\u003dtestlab,DC\u003dlocal", + new Dictionary + { + {"description", "Test"}, + {"useraccountcontrol", "66048"}, + {"lastlogon", "132673011142753043"}, + {"lastlogontimestamp", "132670318095676525"}, + {"homedirectory", @"\\win10\testdir"}, + {"mail", "test@testdomain.com"}, + { + "serviceprincipalname", new[] + { + "MSSQLSVC/win10" + } + }, + {"admincount", "1"}, + { + "sidhistory", new[] + { + Helpers.B64ToBytes("AQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQUQQAAA==") + } + }, + {"pwdlastset", "132131667346106691"} + }, "S-1-5-21-3130019616-2776909439-2417379446-1101", Label.User); + + var processor = new LDAPPropertyProcessor(new MockLDAPUtils()); + var test = await processor.ReadUserProperties(mock); + var props = test.Props; + var keys = props.Keys; + + //Random Stuff + Assert.Contains("description", keys); + Assert.Equal("Test", props["description"] as string); + Assert.Contains("admincount", keys); + Assert.True((bool)props["admincount"]); + Assert.Contains("lastlogon", keys); + Assert.Equal(1622827514, (long)props["lastlogon"]); + Assert.Contains("lastlogontimestamp", keys); + Assert.Equal(1622558209, (long)props["lastlogontimestamp"]); + Assert.Contains("pwdlastset", keys); + Assert.Equal(1568693134, (long)props["pwdlastset"]); + Assert.Contains("homedirectory", keys); + Assert.Equal(@"\\win10\testdir", props["homedirectory"] as string); + Assert.Contains("email", keys); + Assert.Equal("test@testdomain.com", props["email"] as string); + + //UAC stuff + Assert.Contains("sensitive", keys); + Assert.False((bool)props["sensitive"]); + Assert.Contains("dontreqpreauth", keys); + Assert.False((bool)props["dontreqpreauth"]); + Assert.Contains("passwordnotreqd", keys); + Assert.False((bool)props["passwordnotreqd"]); + Assert.Contains("unconstraineddelegation", keys); + Assert.False((bool)props["unconstraineddelegation"]); + Assert.Contains("enabled", keys); + Assert.True((bool)props["enabled"]); + Assert.Contains("trustedtoauth", keys); + Assert.False((bool)props["trustedtoauth"]); + + //SPN + Assert.Contains("hasspn", keys); + Assert.True((bool)props["hasspn"]); + Assert.Contains("serviceprincipalnames", keys); + Assert.Contains("MSSQLSVC/win10", props["serviceprincipalnames"] as string[]); + + //SidHistory + Assert.Contains("sidhistory", keys); + var sh = props["sidhistory"] as string[]; + Assert.Single(sh); + Assert.Contains("S-1-5-21-3130019616-2776909439-2417379446-1105", sh); + Assert.Single(test.SidHistory); + Assert.Contains(new TypedPrincipal + { + ObjectIdentifier = "S-1-5-21-3130019616-2776909439-2417379446-1105", + ObjectType = Label.User + }, test.SidHistory); + } + + [Fact] + public async Task LDAPPropertyProcessor_ReadUserProperties_TestBadPaths() + { + var mock = new MockSearchResultEntry("CN\u003ddfm,CN\u003dUsers,DC\u003dtestlab,DC\u003dlocal", + new Dictionary + { + {"description", "Test"}, + {"useraccountcontrol", "abc"}, + {"lastlogon", "132673011142753043"}, + {"lastlogontimestamp", "132670318095676525"}, + {"homedirectory", @"\\win10\testdir"}, + { + "serviceprincipalname", new[] + { + "MSSQLSVC/win10" + } + }, + {"admincount", "c"}, + { + "sidhistory", new[] + { + Array.Empty() + } + }, + {"pwdlastset", "132131667346106691"} + }, "S-1-5-21-3130019616-2776909439-2417379446-1101", Label.User); + + var processor = new LDAPPropertyProcessor(new MockLDAPUtils()); + var test = await processor.ReadUserProperties(mock); + var props = test.Props; + var keys = props.Keys; + + Assert.Contains("sidhistory", keys); + Assert.Empty(props["sidhistory"] as string[]); + Assert.Contains("admincount", keys); + Assert.False((bool)props["admincount"]); + Assert.Contains("sensitive", keys); + Assert.Contains("dontreqpreauth", keys); + Assert.Contains("passwordnotreqd", keys); + Assert.Contains("unconstraineddelegation", keys); + Assert.Contains("pwdneverexpires", keys); + Assert.Contains("enabled", keys); + Assert.Contains("trustedtoauth", keys); + Assert.False((bool)props["trustedtoauth"]); + Assert.False((bool)props["sensitive"]); + Assert.False((bool)props["dontreqpreauth"]); + Assert.False((bool)props["passwordnotreqd"]); + Assert.False((bool)props["unconstraineddelegation"]); + Assert.False((bool)props["pwdneverexpires"]); + Assert.True((bool)props["enabled"]); + } + + [WindowsOnlyFact] + public async Task LDAPPropertyProcessor_ReadComputerProperties_HappyPath() + { + //TODO: Add coverage for allowedtoact + var mock = new MockSearchResultEntry("CN\u003dWIN10,OU\u003dTestOU,DC\u003dtestlab,DC\u003dlocal", + new Dictionary + { + {"description", "Test"}, + {"useraccountcontrol", 0x1001000.ToString()}, + {"lastlogon", "132673011142753043"}, + {"lastlogontimestamp", "132670318095676525"}, + {"operatingsystem", "Windows 10 Enterprise"}, + {"operatingsystemservicepack", "1607"}, + {"mail", "test@testdomain.com"}, + {"admincount", "c"}, + { + "sidhistory", new[] + { + Helpers.B64ToBytes("AQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQUQQAAA==") + } + }, + { + "msds-allowedtodelegateto", new[] + { + "ldap/PRIMARY.testlab.local/testlab.local", + "ldap/PRIMARY.testlab.local", + "ldap/PRIMARY" + } + }, + {"pwdlastset", "132131667346106691"}, + { + "serviceprincipalname", new[] + { + "WSMAN/WIN10", + "WSMAN/WIN10.testlab.local", + "RestrictedKrbHost/WIN10", + "HOST/WIN10", + "RestrictedKrbHost/WIN10.testlab.local", + "HOST/WIN10.testlab.local" + } + } + }, "S-1-5-21-3130019616-2776909439-2417379446-1101", Label.Computer); + + var processor = new LDAPPropertyProcessor(new MockLDAPUtils()); + var test = await processor.ReadComputerProperties(mock); + var props = test.Props; + var keys = props.Keys; + + //UAC + Assert.Contains("enabled", keys); + Assert.Contains("unconstraineddelegation", keys); + Assert.Contains("trustedtoauth", keys); + Assert.Contains("isdc", keys); + Assert.Contains("lastlogon", keys); + Assert.Contains("lastlogontimestamp", keys); + Assert.Contains("pwdlastset", keys); + Assert.True((bool)props["enabled"]); + Assert.False((bool)props["unconstraineddelegation"]); + Assert.True((bool)props["trustedtoauth"]); + Assert.False((bool)props["isdc"]); + + Assert.Contains("lastlogon", keys); + Assert.Equal(1622827514, (long)props["lastlogon"]); + Assert.Contains("lastlogontimestamp", keys); + Assert.Equal(1622558209, (long)props["lastlogontimestamp"]); + Assert.Contains("pwdlastset", keys); + Assert.Equal(1568693134, (long)props["pwdlastset"]); + + //AllowedToDelegate + Assert.Single(test.AllowedToDelegate); + Assert.Contains(new TypedPrincipal + { + ObjectIdentifier = "S-1-5-21-3130019616-2776909439-2417379446-1001", + ObjectType = Label.Computer + }, test.AllowedToDelegate); + + //Other Stuff + Assert.Contains("serviceprincipalnames", keys); + Assert.Equal(6, (props["serviceprincipalnames"] as string[]).Length); + Assert.Contains("operatingsystem", keys); + Assert.Equal("Windows 10 Enterprise 1607", props["operatingsystem"] as string); + Assert.Contains("description", keys); + Assert.Equal("Test", props["description"] as string); + Assert.Contains("email", keys); + Assert.Equal("test@testdomain.com", props["email"] as string); + + //SidHistory + Assert.Contains("sidhistory", keys); + var sh = props["sidhistory"] as string[]; + Assert.Single(sh); + Assert.Contains("S-1-5-21-3130019616-2776909439-2417379446-1105", sh); + Assert.Single(test.SidHistory); + Assert.Contains(new TypedPrincipal + { + ObjectIdentifier = "S-1-5-21-3130019616-2776909439-2417379446-1105", + ObjectType = Label.User + }, test.SidHistory); + } + + [Fact] + public async Task LDAPPropertyProcessor_ReadComputerProperties_TestBadPaths() + { + var mock = new MockSearchResultEntry("CN\u003dWIN10,OU\u003dTestOU,DC\u003dtestlab,DC\u003dlocal", + new Dictionary + { + {"description", "Test"}, + {"useraccountcontrol", "abc"}, + {"lastlogon", "132673011142753043"}, + {"lastlogontimestamp", "132670318095676525"}, + {"operatingsystem", "Windows 10 Enterprise"}, + {"admincount", "c"}, + { + "sidhistory", new[] + { + Array.Empty() + } + }, + { + "msds-allowedToDelegateTo", new[] + { + "ldap/PRIMARY.testlab.local/testlab.local", + "ldap/PRIMARY.testlab.local", + "ldap/PRIMARY" + } + }, + {"pwdlastset", "132131667346106691"}, + { + "serviceprincipalname", new[] + { + "WSMAN/WIN10", + "WSMAN/WIN10.testlab.local", + "RestrictedKrbHost/WIN10", + "HOST/WIN10", + "RestrictedKrbHost/WIN10.testlab.local", + "HOST/WIN10.testlab.local" + } + } + }, "S-1-5-21-3130019616-2776909439-2417379446-1101", Label.Computer); + + var processor = new LDAPPropertyProcessor(new MockLDAPUtils()); + var test = await processor.ReadComputerProperties(mock); + var props = test.Props; + var keys = props.Keys; + + Assert.Contains("unconstraineddelegation", keys); + Assert.Contains("enabled", keys); + Assert.Contains("trustedtoauth", keys); + Assert.False((bool)props["unconstraineddelegation"]); + Assert.True((bool)props["enabled"]); + Assert.False((bool)props["trustedtoauth"]); + Assert.Contains("sidhistory", keys); + Assert.Empty(props["sidhistory"] as string[]); + } + + + [Fact] + public async Task LDAPPropertyProcessor_ReadComputerProperties_TestDumpSMSAPassword() + { + var mock = new MockSearchResultEntry("CN\u003dWIN10,OU\u003dTestOU,DC\u003dtestlab,DC\u003dlocal", + new Dictionary + { + {"description", "Test"}, + {"useraccountcontrol", 0x1001000.ToString()}, + {"lastlogon", "132673011142753043"}, + {"lastlogontimestamp", "132670318095676525"}, + {"operatingsystem", "Windows 10 Enterprise"}, + {"operatingsystemservicepack", "1607"}, + {"admincount", "c"}, + { + "sidhistory", new[] + { + Helpers.B64ToBytes("AQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQUQQAAA==") + } + }, + { + "msds-allowedtodelegateto", new[] + { + "ldap/PRIMARY.testlab.local/testlab.local", + "ldap/PRIMARY.testlab.local", + "ldap/PRIMARY" + } + }, + {"pwdlastset", "132131667346106691"}, + { + "serviceprincipalname", new[] + { + "WSMAN/WIN10", + "WSMAN/WIN10.testlab.local", + "RestrictedKrbHost/WIN10", + "HOST/WIN10", + "RestrictedKrbHost/WIN10.testlab.local", + "HOST/WIN10.testlab.local" + } + }, + { + "msds-hostserviceaccount", new[] + { + "CN=dfm,CN=Users,DC=testlab,DC=local", + "CN=krbtgt,CN=Users,DC=testlab,DC=local" + } + } + }, "S-1-5-21-3130019616-2776909439-2417379446-1101", Label.Computer); + + var processor = new LDAPPropertyProcessor(new MockLDAPUtils()); + var test = await processor.ReadComputerProperties(mock); + + var expected = new TypedPrincipal[] + { + new() + { + ObjectIdentifier = "S-1-5-21-3130019616-2776909439-2417379446-1105", + ObjectType = Label.User + }, + new() + { + ObjectIdentifier = "S-1-5-21-3130019616-2776909439-2417379446-502", + ObjectType = Label.User + } + }; + + var testDumpSMSAPassword = test.DumpSMSAPassword; + Assert.Equal(2, testDumpSMSAPassword.Length); + Assert.Equal(expected, testDumpSMSAPassword); + + } + + [Fact] + public void LDAPPropertyProcessor_ReadRootCAProperties() + { + var mock = new MockSearchResultEntry( + "CN\u003dDUMPSTER-DC01-CA,CN\u003dCERTIFICATION AUTHORITIES,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dDUMPSTER,DC\u003dFIRE", + new Dictionary + { + {"description", null}, + {"domain", "DUMPSTER.FIRE"}, + {"name", "DUMPSTER-DC01-CA@DUMPSTER.FIRE"}, + {"domainsid", "S-1-5-21-2697957641-2271029196-387917394"}, + {"whencreated", 1683986131}, + }, "2F9F3630-F46A-49BF-B186-6629994EBCF9", Label.RootCA); + + var test = LDAPPropertyProcessor.ReadRootCAProperties(mock); + var keys = test.Keys; + + //These are not common properties + Assert.DoesNotContain("domain", keys); + Assert.DoesNotContain("name", keys); + Assert.DoesNotContain("domainsid", keys); + + Assert.Contains("description", keys); + Assert.Contains("whencreated", keys); + } + + [Fact] + public void LDAPPropertyProcessor_ReadAIACAProperties() + { + var mock = new MockSearchResultEntry( + "CN\u003dDUMPSTER-DC01-CA,CN\u003dAIA,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dDUMPSTER,DC\u003dFIRE", + new Dictionary + { + {"description", null}, + {"domain", "DUMPSTER.FIRE"}, + {"name", "DUMPSTER-DC01-CA@DUMPSTER.FIRE"}, + {"domainsid", "S-1-5-21-2697957641-2271029196-387917394"}, + {"whencreated", 1683986131}, + {"hascrosscertificatepair", true}, + }, "2F9F3630-F46A-49BF-B186-6629994EBCF9", Label.AIACA); + + var test = LDAPPropertyProcessor.ReadAIACAProperties(mock); + var keys = test.Keys; + + //These are not common properties + Assert.DoesNotContain("domain", keys); + Assert.DoesNotContain("name", keys); + Assert.DoesNotContain("domainsid", keys); + + Assert.Contains("description", keys); + Assert.Contains("whencreated", keys); + Assert.Contains("crosscertificatepair", keys); + } + + [Fact] + public void LDAPPropertyProcessor_ReadNTAuthStoreProperties() + { + var mock = new MockSearchResultEntry("CN\u003dNTAUTHCERTIFICATES,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dDUMPSTER,DC\u003dFIRE", + new Dictionary + { + {"description", null}, + {"domain", "DUMPSTER.FIRE"}, + {"name", "NTAUTHCERTIFICATES@DUMPSTER.FIRE"}, + {"domainsid", "S-1-5-21-2697957641-2271029196-387917394"}, + {"whencreated", 1683986131}, + }, "2F9F3630-F46A-49BF-B186-6629994EBCF9", Label.NTAuthStore); + + var test = LDAPPropertyProcessor.ReadNTAuthStoreProperties(mock); + var keys = test.Keys; + + //These are not common properties + Assert.DoesNotContain("domain", keys); + Assert.DoesNotContain("name", keys); + Assert.DoesNotContain("domainsid", keys); + + Assert.Contains("description", keys); + Assert.Contains("whencreated", keys); + } + + [Fact] + public void LDAPPropertyProcessor_ReadCertTemplateProperties() + { + var mock = new MockSearchResultEntry("CN\u003dWORKSTATION,CN\u003dCERTIFICATE TEMPLATES,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dEXTERNAL,DC\u003dLOCAL", + new Dictionary + { + {"domain", "EXTERNAL.LOCAL"}, + {"name", "WORKSTATION@EXTERNAL.LOCAL"}, + {"domainsid", "S-1-5-21-3702535222-3822678775-2090119576"}, + {"description", null}, + {"whencreated", 1683986183}, + {"validityperiod", 31536000}, + {"renewalperiod", 3628800}, + {"schemaversion", 2}, + {"displayname", "Workstation Authentication"}, + {"oid", "1.3.6.1.4.1.311.21.8.4571196.1884641.3293620.10686285.12068043.134.1.30"}, + {"enrollmentflag", 32}, + {"requiresmanagerapproval", false}, + {"certificatenameflag", 0x8000000}, + {"ekus", new[] + {"1.3.6.1.5.5.7.3.2"} + }, + {LDAPProperties.CertificateApplicationPolicy, new[] + {"1.3.6.1.5.5.7.3.2"} + }, + {LDAPProperties.CertificatePolicy, new[] + {"1.3.6.1.5.5.7.3.2"} + }, + {"authorizedsignatures", 1}, + {"applicationpolicies", new[] + { "1.3.6.1.4.1.311.20.2.1"} + }, + {"issuancepolicies", new[] + {"1.3.6.1.4.1.311.21.8.4571196.1884641.3293620.10686285.12068043.134.1.400", + "1.3.6.1.4.1.311.21.8.4571196.1884641.3293620.10686285.12068043.134.1.402"} + }, + }, "2F9F3630-F46A-49BF-B186-6629994EBCF9", Label.CertTemplate); + + var test = LDAPPropertyProcessor.ReadCertTemplateProperties(mock); + var keys = test.Keys; + + //These are not common properties + Assert.DoesNotContain("domain", keys); + Assert.DoesNotContain("name", keys); + Assert.DoesNotContain("domainsid", keys); + + Assert.Contains("description", keys); + Assert.Contains("whencreated", keys); + Assert.Contains("validityperiod", keys); + Assert.Contains("renewalperiod", keys); + Assert.Contains("schemaversion", keys); + Assert.Contains("displayname", keys); + Assert.Contains("oid", keys); + Assert.Contains("enrollmentflag", keys); + Assert.Contains("requiresmanagerapproval", keys); + Assert.Contains("certificatenameflag", keys); + Assert.Contains("enrolleesuppliessubject", keys); + Assert.Contains("subjectaltrequireupn", keys); + Assert.Contains("subjectaltrequiredns", keys); + Assert.Contains("subjectaltrequiredomaindns", keys); + Assert.Contains("subjectaltrequireemail", keys); + Assert.Contains("subjectaltrequirespn", keys); + Assert.Contains("subjectrequireemail", keys); + Assert.Contains("ekus", keys); + Assert.Contains("certificateapplicationpolicy", keys); + var hasPolicy = test.TryGetValue("certificatepolicy", out var policies); + Assert.True(hasPolicy); + if (policies is string[] e) + { + Assert.Contains("1.3.6.1.5.5.7.3.2", e); + } + Assert.Contains("authorizedsignatures", keys); + Assert.Contains("applicationpolicies", keys); + Assert.Contains("issuancepolicies", keys); + + } + // + // [Fact] + // public void LDAPPropertyProcessor_ReadIssuancePolicyProperties() + // { + // var mock = new MockSearchResultEntry("CN\u003d6250993.11BB1AB25A8A65E9FCDF709FCDD5FBC6,CN\u003dOID,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dESC10,DC\u003dLOCAL", + // new Dictionary + // { + // {"domain", "ESC10.LOCAL"}, + // {"name", "KEYADMINSOID@ESC10.LOCAL"}, + // {"domainsid", "S-1-5-21-3662707843-2053279151-3839588741"}, + // {"description", null}, + // {"whencreated", 1712567279}, + // {"displayname", "KeyAdminsOID"}, + // {"certtemplateoid", "1.3.6.1.4.1.311.21.8.4571196.1884641.3293620.10686285.12068043.134.1.30"}, + // {"msds-oidtogrouplink", "CN=ENTERPRISE KEY ADMINS,CN=USERS,DC=ESC10,DC=LOCAL"} + // , + // }, "1E5311A8-E949-4E02-8E08-234ED63200DE", Label.IssuancePolicy); + // + // var mockLDAPUtils = new MockLDAPUtils(); + // var ldapPropertyProcessor = new LDAPPropertyProcessor(mockLDAPUtils); + // + // + // var test = ldapPropertyProcessor.ReadIssuancePolicyProperties(mock); + // var keys = test.Props.Keys; + // + // //These are not common properties + // Assert.DoesNotContain("domain", keys); + // Assert.DoesNotContain("name", keys); + // Assert.DoesNotContain("domainsid", keys); + // + // Assert.Contains("description", keys); + // Assert.Contains("whencreated", keys); + // Assert.Contains("displayname", keys); + // Assert.Contains("certtemplateoid", keys); + // Assert.Contains("oidgrouplink", keys); + // } + // + // [Fact] + // public void LDAPPropertyProcessor_ReadIssuancePolicyProperties_NoOIDGroupLink() + // { + // var mock = new MockSearchResultEntry("CN\u003d6250993.11BB1AB25A8A65E9FCDF709FCDD5FBC6,CN\u003dOID,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dESC10,DC\u003dLOCAL", + // new Dictionary + // { + // {"domain", "ESC10.LOCAL"}, + // {"name", "KEYADMINSOID@ESC10.LOCAL"}, + // {"domainsid", "S-1-5-21-3662707843-2053279151-3839588741"}, + // {"description", null}, + // {"whencreated", 1712567279}, + // {"displayname", "KeyAdminsOID"}, + // {"certtemplateoid", "1.3.6.1.4.1.311.21.8.4571196.1884641.3293620.10686285.12068043.134.1.30"}, + // {"msds-oidtogrouplink", null} + // , + // }, "1E5311A8-E949-4E02-8E08-234ED63200DE", Label.IssuancePolicy); + // + // var mockLDAPUtils = new MockLDAPUtils(); + // var ldapPropertyProcessor = new LDAPPropertyProcessor(mockLDAPUtils); + // + // + // var test = ldapPropertyProcessor.ReadIssuancePolicyProperties(mock); + // var keys = test.Props.Keys; + // + // //These are not common properties + // Assert.DoesNotContain("domain", keys); + // Assert.DoesNotContain("name", keys); + // Assert.DoesNotContain("domainsid", keys); + // Assert.DoesNotContain("oidgrouplink", keys); + // + // Assert.Contains("description", keys); + // Assert.Contains("whencreated", keys); + // Assert.Contains("displayname", keys); + // Assert.Contains("certtemplateoid", keys); + // } + + [Fact] + public void LDAPPropertyProcessor_ParseAllProperties() + { + var mock = new MockSearchResultEntry("CN\u003dNTAUTHCERTIFICATES,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dDUMPSTER,DC\u003dFIRE", + new Dictionary + { + {"description", null}, + {"domain", "DUMPSTER.FIRE"}, + {"name", "NTAUTHCERTIFICATES@DUMPSTER.FIRE"}, + {"domainsid", "S-1-5-21-2697957641-2271029196-387917394"}, + {"whencreated", 1683986131}, + }, "2F9F3630-F46A-49BF-B186-6629994EBCF9", Label.NTAuthStore); + + var processor = new LDAPPropertyProcessor(new MockLDAPUtils()); + var props = processor.ParseAllProperties(mock); + var keys = props.Keys; + + //These are reserved properties and so they should be filtered out + Assert.DoesNotContain("description", keys); + Assert.DoesNotContain("whencreated", keys); + Assert.DoesNotContain("name", keys); + + Assert.Contains("domainsid", keys); + Assert.Contains("domain", keys); + } + + [Fact] + public void LDAPPropertyProcessor_ParseAllProperties_NoProperties() + { + var mock = new MockSearchResultEntry("CN\u003dNTAUTHCERTIFICATES,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dDUMPSTER,DC\u003dFIRE", + new Dictionary + { }, "2F9F3630-F46A-49BF-B186-6629994EBCF9", Label.NTAuthStore); + + var processor = new LDAPPropertyProcessor(new MockLDAPUtils()); + var props = processor.ParseAllProperties(mock); + var keys = props.Keys; + + Assert.Empty(keys); + + } + + [Fact] + public void LDAPPropertyProcessor_ParseAllProperties_CollectionCountOne_NullString() + { + var mock = new MockSearchResultEntry("CN\u003dNTAUTHCERTIFICATES,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dDUMPSTER,DC\u003dFIRE", + new Dictionary + {{"domainsid", null} }, "2F9F3630-F46A-49BF-B186-6629994EBCF9", Label.NTAuthStore); + + var processor = new LDAPPropertyProcessor(new MockLDAPUtils()); + var props = processor.ParseAllProperties(mock); + var keys = props.Keys; + + Assert.Empty(keys); + } + + [Fact] + public void LDAPPropertyProcessor_ParseAllProperties_CollectionCountOne_BadPasswordTime() + { + var mock = new MockSearchResultEntry("CN\u003dNTAUTHCERTIFICATES,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dDUMPSTER,DC\u003dFIRE", + new Dictionary + {{"badpasswordtime", "130435290000000000"} }, "2F9F3630-F46A-49BF-B186-6629994EBCF9", Label.NTAuthStore); + + var processor = new LDAPPropertyProcessor(new MockLDAPUtils()); + var props = processor.ParseAllProperties(mock); + var keys = props.Keys; + + Assert.Contains("badpasswordtime", keys); + Assert.Single(keys); + } + + [Fact] + public void LDAPPropertyProcessor_ParseAllProperties_CollectionCountOne_NotBadPasswordTime() + { + var mock = new MockSearchResultEntry("CN\u003dNTAUTHCERTIFICATES,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dDUMPSTER,DC\u003dFIRE", + new Dictionary + {{"domainsid", "S-1-5-21-2697957641-2271029196-387917394"}}, "2F9F3630-F46A-49BF-B186-6629994EBCF9", Label.NTAuthStore); + + var processor = new LDAPPropertyProcessor(new MockLDAPUtils()); + var props = processor.ParseAllProperties(mock); + var keys = props.Keys; + + Assert.Contains("domainsid", keys); + Assert.Single(keys); + } + + } +} From 886239c66ab35ed1c596c6e5bfa3304b9fc8a6a7 Mon Sep 17 00:00:00 2001 From: rvazarkar Date: Tue, 9 Jul 2024 15:58:02 -0400 Subject: [PATCH 51/68] fix: resolvelabel mistake --- src/CommonLib/LdapUtils.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CommonLib/LdapUtils.cs b/src/CommonLib/LdapUtils.cs index c8461f61..aec35cdc 100644 --- a/src/CommonLib/LdapUtils.cs +++ b/src/CommonLib/LdapUtils.cs @@ -1558,7 +1558,7 @@ internal static bool ResolveLabel(string objectIdentifier, string distinguishedN if (distinguishedName.StartsWith(DirectoryPaths.OIDContainerLocation, StringComparison.InvariantCultureIgnoreCase)) type = Label.Container; - if (flags == 2) + else if (flags == 2) { type = Label.IssuancePolicy; } From 8496778444120253321855cce026f31dc93f9973 Mon Sep 17 00:00:00 2001 From: anemeth Date: Tue, 9 Jul 2024 13:14:34 -0700 Subject: [PATCH 52/68] LdapUtilsTest first pass --- test/unit/LDAPUtilsTest.cs | 563 +++++++++++++++++++------------------ 1 file changed, 282 insertions(+), 281 deletions(-) diff --git a/test/unit/LDAPUtilsTest.cs b/test/unit/LDAPUtilsTest.cs index c9b7b79b..78b95a33 100644 --- a/test/unit/LDAPUtilsTest.cs +++ b/test/unit/LDAPUtilsTest.cs @@ -1,281 +1,282 @@ -// using System; -// using System.Collections.Generic; -// using System.DirectoryServices.ActiveDirectory; -// using System.DirectoryServices.Protocols; -// using System.Threading; -// using System.Threading.Tasks; -// using CommonLibTest.Facades; -// using Moq; -// using SharpHoundCommonLib; -// using SharpHoundCommonLib.Enums; -// using SharpHoundCommonLib.Exceptions; -// using Xunit; -// using Xunit.Abstractions; -// -// namespace CommonLibTest -// { -// public class LDAPUtilsTest : IDisposable -// { -// private readonly string _testDomainName; -// private readonly string _testForestName; -// private readonly ITestOutputHelper _testOutputHelper; -// private readonly ILdapUtils _utils; -// -// #region Constructor(s) -// -// public LDAPUtilsTest(ITestOutputHelper testOutputHelper) -// { -// _testOutputHelper = testOutputHelper; -// _testForestName = "PARENT.LOCAL"; -// _testDomainName = "TESTLAB.LOCAL"; -// _utils = new LdapUtils(); -// // This runs once per test. -// } -// -// #endregion -// -// #region IDispose Implementation -// -// public void Dispose() -// { -// // Tear down (called once per test) -// } -// -// #endregion -// -// [Fact] -// public void SanityCheck() -// { -// Assert.True(true); -// } -// -// #region Private Members -// -// #endregion -// -// #region Creation -// -// /// -// /// -// [Fact] -// public async Task GetUserGlobalCatalogMatches_Garbage_ReturnsNull() -// { -// var test = await _utils.GetGlobalCatalogMatches("foo"); -// _testOutputHelper.WriteLine(test.ToString()); -// Assert.True(test.Success); -// Assert.Empty(test.Sids); -// } -// -// [Fact] -// public void ResolveIDAndType_DuplicateSid_ReturnsNull() -// { -// var test = _utils.ResolveIDAndType("ABC0ACNF", null); -// Assert.Null(test); -// } -// -// [Fact] -// public void ResolveIDAndType_WellKnownAdministrators_ReturnsConvertedSID() -// { -// var test = _utils.ResolveIDAndType("S-1-5-32-544", "TESTLAB.LOCAL"); -// Assert.NotNull(test); -// Assert.Equal(Label.Group, test.ObjectType); -// Assert.Equal("TESTLAB.LOCAL-S-1-5-32-544", test.ObjectIdentifier); -// } -// -// [WindowsOnlyFact] -// public void GetWellKnownPrincipal_EnterpriseDomainControllers_ReturnsCorrectedSID() -// { -// var mock = new Mock(); -// var mockForest = MockableForest.Construct(_testForestName); -// mock.Setup(x => x.GetForest(It.IsAny())).Returns(mockForest); -// var result = mock.Object.GetWellKnownPrincipal("S-1-5-9", null, out var typedPrincipal); -// Assert.True(result); -// Assert.Equal($"{_testForestName}-S-1-5-9", typedPrincipal.ObjectIdentifier); -// Assert.Equal(Label.Group, typedPrincipal.ObjectType); -// } -// -// [Fact] -// public void BuildLdapPath_BadDomain_ReturnsNull() -// { -// var mock = new Mock(); -// //var mockDomain = MockableDomain.Construct("TESTLAB.LOCAL"); -// mock.Setup(x => x.GetDomain(It.IsAny())) -// .Returns((Domain)null); -// var result = mock.Object.BuildLdapPath("TEST", "ABC"); -// Assert.Null(result); -// } -// -// [WindowsOnlyFact] -// public void BuildLdapPath_HappyPath() -// { -// var mock = new Mock(); -// var mockDomain = MockableDomain.Construct("TESTLAB.LOCAL"); -// mock.Setup(x => x.GetDomain(It.IsAny())) -// .Returns(mockDomain); -// var result = mock.Object.BuildLdapPath(DirectoryPaths.PKILocation, "ABC"); -// Assert.NotNull(result); -// Assert.Equal("CN=Public Key Services,CN=Services,CN=Configuration,DC=TESTLAB,DC=LOCAL", result); -// } -// -// [Fact] -// public void GetWellKnownPrincipal_NonWellKnown_ReturnsNull() -// { -// var result = _utils.GetWellKnownPrincipal("S-1-5-21-123456-78910", _testDomainName, out var typedPrincipal); -// Assert.False(result); -// Assert.Null(typedPrincipal); -// } -// -// [Fact] -// public void GetWellKnownPrincipal_WithDomain_ConvertsSID() -// { -// var result = -// _utils.GetWellKnownPrincipal("S-1-5-32-544", _testDomainName, out var typedPrincipal); -// Assert.True(result); -// Assert.Equal(Label.Group, typedPrincipal.ObjectType); -// Assert.Equal($"{_testDomainName}-S-1-5-32-544", typedPrincipal.ObjectIdentifier); -// } -// -// [Fact] -// public void DistinguishedNameToDomain_RegularObject_CorrectDomain() -// { -// var result = SharpHoundCommonLib.Helpers.DistinguishedNameToDomain( -// "CN=Account Operators,CN=Builtin,DC=testlab,DC=local"); -// Assert.Equal("TESTLAB.LOCAL", result); -// -// result = SharpHoundCommonLib.Helpers.DistinguishedNameToDomain("DC=testlab,DC=local"); -// Assert.Equal("TESTLAB.LOCAL", result); -// } -// -// [Fact] -// public void GetDomainRangeSize_BadDomain_ReturnsDefault() -// { -// var mock = new Mock(); -// mock.Setup(x => x.GetDomain(It.IsAny())).Returns((Domain)null); -// var result = mock.Object.GetDomainRangeSize(); -// Assert.Equal(750, result); -// } -// -// [Fact] -// public void GetDomainRangeSize_RespectsDefaultParam() -// { -// var mock = new Mock(); -// mock.Setup(x => x.GetDomain(It.IsAny())).Returns((Domain)null); -// -// var result = mock.Object.GetDomainRangeSize(null, 1000); -// Assert.Equal(1000, result); -// } -// -// [WindowsOnlyFact] -// public void GetDomainRangeSize_NoLdapEntry_ReturnsDefault() -// { -// var mock = new Mock(); -// var mockDomain = MockableDomain.Construct("testlab.local"); -// mock.Setup(x => x.GetDomain(It.IsAny())).Returns(mockDomain); -// mock.Setup(x => x.QueryLDAP(It.IsAny(), It.IsAny(), It.IsAny(), -// It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), -// It.IsAny(), It.IsAny())).Returns(new List()); -// -// var result = mock.Object.GetDomainRangeSize(); -// Assert.Equal(750, result); -// } -// -// [WindowsOnlyFact] -// public void GetDomainRangeSize_ExpectedResults() -// { -// var mock = new Mock(); -// var mockDomain = MockableDomain.Construct("testlab.local"); -// mock.Setup(x => x.GetDomain(It.IsAny())).Returns(mockDomain); -// var searchResult = new MockSearchResultEntry("CN=Default Query Policy,CN=Query-Policies,CN=Directory Service,CN=Windows NT,CN=Services,CN=Configuration,DC=testlab,DC=local", new Dictionary -// { -// {"ldapadminlimits", new[] -// { -// "MaxPageSize=1250" -// }}, -// }, "abc123", Label.Base); -// -// mock.Setup(x => x.QueryLDAP(It.IsAny(), It.IsAny(), null, -// It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), -// It.IsAny(), It.IsAny())).Returns(new List { searchResult }); -// var result = mock.Object.GetDomainRangeSize(); -// Assert.Equal(1250, result); -// } -// -// [Fact] -// public void DistinguishedNameToDomain_DeletedObjects_CorrectDomain() -// { -// var result = SharpHoundCommonLib.Helpers.DistinguishedNameToDomain( -// @"DC=..Deleted-_msdcs.testlab.local\0ADEL:af1f072f-28d7-4b86-9b87-a408bfc9cb0d,CN=Deleted Objects,DC=testlab,DC=local"); -// Assert.Equal("TESTLAB.LOCAL", result); -// } -// -// [Fact] -// public void QueryLDAP_With_Exception() -// { -// var options = new LDAPQueryOptions -// { -// ThrowException = true -// }; -// -// Assert.Throws( -// () => -// { -// foreach (var sre in _utils.QueryLDAP(null, new SearchScope(), null, new CancellationToken(), null, -// false, false, null, false, false, true)) -// { -// // We shouldn't reach this anyway, and all we care about is if exceptions are bubbling -// } -// }); -// -// Assert.Throws( -// () => -// { -// foreach (var sre in _utils.QueryLDAP(options)) -// { -// // We shouldn't reach this anyway, and all we care about is if exceptions are bubbling -// } -// }); -// } -// -// [Fact] -// public void QueryLDAP_Without_Exception() -// { -// Exception exception; -// -// var options = new LDAPQueryOptions -// { -// ThrowException = false -// }; -// -// exception = Record.Exception( -// () => -// { -// foreach (var sre in _utils.QueryLDAP(null, new SearchScope(), null, new CancellationToken())) -// { -// // We shouldn't reach this anyway, and all we care about is if exceptions are bubbling -// } -// }); -// Assert.Null(exception); -// -// exception = Record.Exception( -// () => -// { -// foreach (var sre in _utils.QueryLDAP(options)) -// { -// // We shouldn't reach this anyway, and all we care about is if exceptions are bubbling -// } -// }); -// Assert.Null(exception); -// } -// -// #endregion -// -// #region Structural -// -// #endregion -// -// -// #region Behavioral -// -// #endregion -// } -// } \ No newline at end of file +using System; +using System.Collections.Generic; +using System.DirectoryServices.ActiveDirectory; +using System.DirectoryServices.Protocols; +using System.Threading; +using System.Threading.Tasks; +using CommonLibTest.Facades; +using Moq; +using SharpHoundCommonLib; +using SharpHoundCommonLib.Enums; +using SharpHoundCommonLib.Exceptions; +using Xunit; +using Xunit.Abstractions; + +namespace CommonLibTest +{ + public class LDAPUtilsTest : IDisposable + { + private readonly string _testDomainName; + private readonly string _testForestName; + private readonly ITestOutputHelper _testOutputHelper; + private readonly ILdapUtils _utils; + + #region Constructor(s) + + public LDAPUtilsTest(ITestOutputHelper testOutputHelper) + { + _testOutputHelper = testOutputHelper; + _testForestName = "PARENT.LOCAL"; + _testDomainName = "TESTLAB.LOCAL"; + _utils = new LdapUtils(); + // This runs once per test. + } + + #endregion + + #region IDispose Implementation + + public void Dispose() + { + // Tear down (called once per test) + } + + #endregion + + [Fact] + public void SanityCheck() + { + Assert.True(true); + } + + #region Private Members + + #endregion + + #region Creation + + /// + /// + [Fact] + public async Task GetUserGlobalCatalogMatches_Garbage_ReturnsNull() + { + var test = await _utils.GetGlobalCatalogMatches("foo", "bar"); + _testOutputHelper.WriteLine(test.ToString()); + Assert.True(test.Success); + Assert.Empty(test.Sids); + } + + [Fact] + public void ResolveIDAndType_DuplicateSid_ReturnsNull() + { + var test = _utils.ResolveIDAndType("ABC0ACNF", null); + Assert.Null(test); + } + + [Fact] + public async void ResolveIDAndType_WellKnownAdministrators_ReturnsConvertedSID() + { + var test = await _utils.ResolveIDAndType("S-1-5-32-544", "TESTLAB.LOCAL"); + Assert.True(test.Success); + Assert.NotNull(test.Principal); + Assert.Equal(Label.Group, test.Principal.ObjectType); + Assert.Equal("TESTLAB.LOCAL-S-1-5-32-544", test.Principal.ObjectIdentifier); + } + + [WindowsOnlyFact] + public async void GetWellKnownPrincipal_EnterpriseDomainControllers_ReturnsCorrectedSID() + { + var mock = new Mock(); + var mockForest = MockableForest.Construct(_testForestName); + mock.Setup(x => x.GetForest(It.IsAny())).Returns(mockForest); + var result = await mock.Object.GetWellKnownPrincipal("S-1-5-9", null); + Assert.True(result.Success); + Assert.Equal($"{_testForestName}-S-1-5-9", result.WellKnownPrincipal.ObjectIdentifier); + Assert.Equal(Label.Group, result.WellKnownPrincipal.ObjectType); + } + + [Fact] + public void BuildLdapPath_BadDomain_ReturnsNull() + { + var mock = new Mock(); + //var mockDomain = MockableDomain.Construct("TESTLAB.LOCAL"); + mock.Setup(x => x.GetDomain(It.IsAny())) + .Returns((Domain)null); + var result = mock.Object.BuildLdapPath("TEST", "ABC"); + Assert.Null(result); + } + + [WindowsOnlyFact] + public void BuildLdapPath_HappyPath() + { + var mock = new Mock(); + var mockDomain = MockableDomain.Construct("TESTLAB.LOCAL"); + mock.Setup(x => x.GetDomain(It.IsAny())) + .Returns(mockDomain); + var result = mock.Object.BuildLdapPath(DirectoryPaths.PKILocation, "ABC"); + Assert.NotNull(result); + Assert.Equal("CN=Public Key Services,CN=Services,CN=Configuration,DC=TESTLAB,DC=LOCAL", result); + } + + [Fact] + public async void GetWellKnownPrincipal_NonWellKnown_ReturnsNull() + { + var result = await _utils.GetWellKnownPrincipal("S-1-5-21-123456-78910", _testDomainName); + Assert.False(result.Success); + Assert.Null(result.WellKnownPrincipal); + } + + [Fact] + public async void GetWellKnownPrincipal_WithDomain_ConvertsSID() + { + var result = + await _utils.GetWellKnownPrincipal("S-1-5-32-544", _testDomainName); + Assert.True(result.Success); + Assert.Equal(Label.Group, result.WellKnownPrincipal.ObjectType); + Assert.Equal($"{_testDomainName}-S-1-5-32-544", result.WellKnownPrincipal.ObjectIdentifier); + } + + [Fact] + public void DistinguishedNameToDomain_RegularObject_CorrectDomain() + { + var result = SharpHoundCommonLib.Helpers.DistinguishedNameToDomain( + "CN=Account Operators,CN=Builtin,DC=testlab,DC=local"); + Assert.Equal("TESTLAB.LOCAL", result); + + result = SharpHoundCommonLib.Helpers.DistinguishedNameToDomain("DC=testlab,DC=local"); + Assert.Equal("TESTLAB.LOCAL", result); + } + + [Fact] + public void GetDomainRangeSize_BadDomain_ReturnsDefault() + { + var mock = new Mock(); + mock.Setup(x => x.GetDomain(It.IsAny())).Returns((Domain)null); + var result = mock.Object.GetDomainRangeSize(); + Assert.Equal(750, result); + } + + [Fact] + public void GetDomainRangeSize_RespectsDefaultParam() + { + var mock = new Mock(); + mock.Setup(x => x.GetDomain(It.IsAny())).Returns((Domain)null); + + var result = mock.Object.GetDomainRangeSize(null, 1000); + Assert.Equal(1000, result); + } + + [WindowsOnlyFact] + public void GetDomainRangeSize_NoLdapEntry_ReturnsDefault() + { + var mock = new Mock(); + var mockDomain = MockableDomain.Construct("testlab.local"); + mock.Setup(x => x.GetDomain(It.IsAny())).Returns(mockDomain); + mock.Setup(x => x.Query(It.IsAny(), It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny())).Returns(new List()); + + var result = mock.Object.GetDomainRangeSize(); + Assert.Equal(750, result); + } + + [WindowsOnlyFact] + public void GetDomainRangeSize_ExpectedResults() + { + var mock = new Mock(); + var mockDomain = MockableDomain.Construct("testlab.local"); + mock.Setup(x => x.GetDomain(It.IsAny())).Returns(mockDomain); + var searchResult = new MockSearchResultEntry("CN=Default Query Policy,CN=Query-Policies,CN=Directory Service,CN=Windows NT,CN=Services,CN=Configuration,DC=testlab,DC=local", new Dictionary + { + {"ldapadminlimits", new[] + { + "MaxPageSize=1250" + }}, + }, "abc123", Label.Base); + + mock.Setup(x => x.Query(It.IsAny(), It.IsAny(), null, + It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny())).Returns(new List { searchResult }); + var result = mock.Object.GetDomainRangeSize(); + Assert.Equal(1250, result); + } + + [Fact] + public void DistinguishedNameToDomain_DeletedObjects_CorrectDomain() + { + var result = SharpHoundCommonLib.Helpers.DistinguishedNameToDomain( + @"DC=..Deleted-_msdcs.testlab.local\0ADEL:af1f072f-28d7-4b86-9b87-a408bfc9cb0d,CN=Deleted Objects,DC=testlab,DC=local"); + Assert.Equal("TESTLAB.LOCAL", result); + } + + [Fact] + public void QueryLDAP_With_Exception() + { + var options = new LDAPQueryOptions + { + ThrowException = true + }; + + Assert.Throws( + async () => + { + await foreach (var sre in _utils.Query(null, new SearchScope(), null, new CancellationToken(), null, + false, false, null, false, false, true)) + { + // We shouldn't reach this anyway, and all we care about is if exceptions are bubbling + } + }); + + Assert.Throws( + async () => + { + await foreach (var sre in _utils.Query(options)) + { + // We shouldn't reach this anyway, and all we care about is if exceptions are bubbling + } + }); + } + + [Fact] + public void QueryLDAP_Without_Exception() + { + Exception exception; + + var options = new LDAPQueryOptions + { + ThrowException = false + }; + + exception = Record.Exception( + async () => + { + await foreach (var sre in _utils.Query(null, new SearchScope(), null, new CancellationToken())) + { + // We shouldn't reach this anyway, and all we care about is if exceptions are bubbling + } + }); + Assert.Null(exception); + + exception = Record.Exception( + async () => + { + await foreach (var sre in _utils.Query(options)) + { + // We shouldn't reach this anyway, and all we care about is if exceptions are bubbling + } + }); + Assert.Null(exception); + } + + #endregion + + #region Structural + + #endregion + + + #region Behavioral + + #endregion + } +} \ No newline at end of file From 1dcccde21781cca090f8748c1a844ca742369bf3 Mon Sep 17 00:00:00 2001 From: rvazarkar Date: Tue, 9 Jul 2024 16:41:25 -0400 Subject: [PATCH 53/68] wip: ldap utils tests --- test/unit/LDAPUtilsTest.cs | 130 ------------------------------------- 1 file changed, 130 deletions(-) diff --git a/test/unit/LDAPUtilsTest.cs b/test/unit/LDAPUtilsTest.cs index 78b95a33..e6889d57 100644 --- a/test/unit/LDAPUtilsTest.cs +++ b/test/unit/LDAPUtilsTest.cs @@ -49,12 +49,6 @@ public void SanityCheck() Assert.True(true); } - #region Private Members - - #endregion - - #region Creation - /// /// [Fact] @@ -147,60 +141,6 @@ public void DistinguishedNameToDomain_RegularObject_CorrectDomain() Assert.Equal("TESTLAB.LOCAL", result); } - [Fact] - public void GetDomainRangeSize_BadDomain_ReturnsDefault() - { - var mock = new Mock(); - mock.Setup(x => x.GetDomain(It.IsAny())).Returns((Domain)null); - var result = mock.Object.GetDomainRangeSize(); - Assert.Equal(750, result); - } - - [Fact] - public void GetDomainRangeSize_RespectsDefaultParam() - { - var mock = new Mock(); - mock.Setup(x => x.GetDomain(It.IsAny())).Returns((Domain)null); - - var result = mock.Object.GetDomainRangeSize(null, 1000); - Assert.Equal(1000, result); - } - - [WindowsOnlyFact] - public void GetDomainRangeSize_NoLdapEntry_ReturnsDefault() - { - var mock = new Mock(); - var mockDomain = MockableDomain.Construct("testlab.local"); - mock.Setup(x => x.GetDomain(It.IsAny())).Returns(mockDomain); - mock.Setup(x => x.Query(It.IsAny(), It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny())).Returns(new List()); - - var result = mock.Object.GetDomainRangeSize(); - Assert.Equal(750, result); - } - - [WindowsOnlyFact] - public void GetDomainRangeSize_ExpectedResults() - { - var mock = new Mock(); - var mockDomain = MockableDomain.Construct("testlab.local"); - mock.Setup(x => x.GetDomain(It.IsAny())).Returns(mockDomain); - var searchResult = new MockSearchResultEntry("CN=Default Query Policy,CN=Query-Policies,CN=Directory Service,CN=Windows NT,CN=Services,CN=Configuration,DC=testlab,DC=local", new Dictionary - { - {"ldapadminlimits", new[] - { - "MaxPageSize=1250" - }}, - }, "abc123", Label.Base); - - mock.Setup(x => x.Query(It.IsAny(), It.IsAny(), null, - It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny())).Returns(new List { searchResult }); - var result = mock.Object.GetDomainRangeSize(); - Assert.Equal(1250, result); - } - [Fact] public void DistinguishedNameToDomain_DeletedObjects_CorrectDomain() { @@ -208,75 +148,5 @@ public void DistinguishedNameToDomain_DeletedObjects_CorrectDomain() @"DC=..Deleted-_msdcs.testlab.local\0ADEL:af1f072f-28d7-4b86-9b87-a408bfc9cb0d,CN=Deleted Objects,DC=testlab,DC=local"); Assert.Equal("TESTLAB.LOCAL", result); } - - [Fact] - public void QueryLDAP_With_Exception() - { - var options = new LDAPQueryOptions - { - ThrowException = true - }; - - Assert.Throws( - async () => - { - await foreach (var sre in _utils.Query(null, new SearchScope(), null, new CancellationToken(), null, - false, false, null, false, false, true)) - { - // We shouldn't reach this anyway, and all we care about is if exceptions are bubbling - } - }); - - Assert.Throws( - async () => - { - await foreach (var sre in _utils.Query(options)) - { - // We shouldn't reach this anyway, and all we care about is if exceptions are bubbling - } - }); - } - - [Fact] - public void QueryLDAP_Without_Exception() - { - Exception exception; - - var options = new LDAPQueryOptions - { - ThrowException = false - }; - - exception = Record.Exception( - async () => - { - await foreach (var sre in _utils.Query(null, new SearchScope(), null, new CancellationToken())) - { - // We shouldn't reach this anyway, and all we care about is if exceptions are bubbling - } - }); - Assert.Null(exception); - - exception = Record.Exception( - async () => - { - await foreach (var sre in _utils.Query(options)) - { - // We shouldn't reach this anyway, and all we care about is if exceptions are bubbling - } - }); - Assert.Null(exception); - } - - #endregion - - #region Structural - - #endregion - - - #region Behavioral - - #endregion } } \ No newline at end of file From 4a03e01cfcc8882f2f4b9d3e9b4013073f514139 Mon Sep 17 00:00:00 2001 From: Rohan Vazarkar Date: Mon, 22 Jul 2024 14:34:02 -0400 Subject: [PATCH 54/68] IDirectoryObject Refactor (#135) * wip: implement IDirectoryObject for cleanliness and testability * wip: move some stuff around, add a bunch of label tests * chore: add some more tests and fix some subtle bugs * wip: standardize ldap casing, move producer generation into commonlib * wip: add docs to ldap utils * fix: BED-4370 - use lastlogontimestamp in addition to pwdlastset for computer availability * chore: more tests * ACL Inheritance Tracking (#133) * fix: include inheritance id into hash * feat: add inheritancehashes property to containers/ous/domains * chore: add tests and fix some old ones * chore: small update * chore: add back deleted tests * wip: patch some tests * fix: don't try to release a connection if its null * fix: use value instead of an array reference * chore: rename entry in wrapper for clarity * chore: properly use DefaultIfEmpty consistently * chore: refactor out function for readability * chore: remove warns * chore: remove old commented code --- src/CommonLib/ConnectionPoolManager.cs | 19 +- src/CommonLib/DirectoryEntryExtensions.cs | 134 -- .../DirectoryObjects/DirectoryEntryWrapper.cs | 188 ++ .../DirectoryObjectExtensions.cs | 71 + .../DirectoryObjects/IDirectoryObject.cs | 20 + .../SearchResultEntryWrapper.cs | 203 ++ ...llectionMethods.cs => CollectionMethod.cs} | 2 +- src/CommonLib/Enums/DirectoryPaths.cs | 2 +- src/CommonLib/Enums/LDAPProperties.cs | 1 + src/CommonLib/Enums/ObjectClass.cs | 15 + src/CommonLib/Extensions.cs | 15 +- src/CommonLib/Helpers.cs | 149 +- src/CommonLib/ILdapUtils.cs | 126 +- .../{LDAPConfig.cs => LdapConfig.cs} | 2 +- src/CommonLib/LdapConnectionPool.cs | 8 +- src/CommonLib/LdapConnectionWrapper.cs | 14 +- src/CommonLib/LdapProducerQueryGenerator.cs | 151 ++ .../CommonFilters.cs | 0 .../CommonPaths.cs | 0 .../CommonProperties.cs | 3 +- .../LdapFilter.cs} | 38 +- src/CommonLib/LdapUtils.cs | 659 +++--- src/CommonLib/OutputTypes/ACE.cs | 26 +- src/CommonLib/OutputTypes/Computer.cs | 2 +- src/CommonLib/OutputTypes/Container.cs | 1 + src/CommonLib/OutputTypes/Domain.cs | 2 + src/CommonLib/OutputTypes/OU.cs | 1 + src/CommonLib/Processors/ACLProcessor.cs | 459 ++-- .../Processors/ComputerAvailability.cs | 58 +- .../Processors/ContainerProcessor.cs | 40 +- .../Processors/DomainTrustProcessor.cs | 29 +- .../Processors/GPOLocalGroupProcessor.cs | 24 +- src/CommonLib/Processors/GroupProcessor.cs | 12 +- ...yProcessor.cs => LdapPropertyProcessor.cs} | 393 ++-- src/CommonLib/Processors/SPNProcessors.cs | 13 +- src/CommonLib/SearchResultEntryExtensions.cs | 233 -- src/CommonLib/SearchResultEntryWrapper.cs | 288 --- src/CommonLib/SecurityDescriptor.cs | 6 + test/unit/ACLProcessorTest.cs | 1992 +++++++++-------- test/unit/CertAbuseProcessorTest.cs | 20 +- test/unit/CommonLibHelperTests.cs | 136 +- test/unit/ComputerAvailabilityTests.cs | 10 +- test/unit/ComputerSessionProcessorTest.cs | 16 +- test/unit/ContainerProcessorTest.cs | 30 +- test/unit/DirectoryObjectTests.cs | 320 +++ test/unit/DomainTrustProcessorTest.cs | 32 +- test/unit/Facades/MockDirectoryObject.cs | 166 ++ .../{MockLDAPUtils.cs => MockLdapUtils.cs} | 18 +- test/unit/Facades/MockSearchResultEntry.cs | 142 -- test/unit/GPOLocalGroupProcessorTest.cs | 167 +- test/unit/GroupProcessorTest.cs | 13 +- test/unit/LDAPFilterTest.cs | 8 +- test/unit/LDAPUtilsTest.cs | 200 +- ...PPropertyTests.cs => LdapPropertyTests.cs} | 350 +-- test/unit/LocalGroupProcessorTest.cs | 8 +- test/unit/SPNProcessorsTest.cs | 12 +- test/unit/SearchResultEntryTests.cs | 36 - test/unit/TestLogger.cs | 2 + .../unit/UserRightsAssignmentProcessorTest.cs | 4 +- test/unit/{Helpers.cs => Utils.cs} | 12 +- test/unit/WellKnownPrincipalTest.cs | 12 - 61 files changed, 3839 insertions(+), 3274 deletions(-) delete mode 100644 src/CommonLib/DirectoryEntryExtensions.cs create mode 100644 src/CommonLib/DirectoryObjects/DirectoryEntryWrapper.cs create mode 100644 src/CommonLib/DirectoryObjects/DirectoryObjectExtensions.cs create mode 100644 src/CommonLib/DirectoryObjects/IDirectoryObject.cs create mode 100644 src/CommonLib/DirectoryObjects/SearchResultEntryWrapper.cs rename src/CommonLib/Enums/{CollectionMethods.cs => CollectionMethod.cs} (96%) create mode 100644 src/CommonLib/Enums/ObjectClass.cs rename src/CommonLib/{LDAPConfig.cs => LdapConfig.cs} (97%) create mode 100644 src/CommonLib/LdapProducerQueryGenerator.cs rename src/CommonLib/{LDAPQueries => LdapQueries}/CommonFilters.cs (100%) rename src/CommonLib/{LDAPQueries => LdapQueries}/CommonPaths.cs (100%) rename src/CommonLib/{LDAPQueries => LdapQueries}/CommonProperties.cs (98%) rename src/CommonLib/{LDAPQueries/LDAPFilter.cs => LdapQueries/LdapFilter.cs} (88%) rename src/CommonLib/Processors/{LDAPPropertyProcessor.cs => LdapPropertyProcessor.cs} (74%) delete mode 100644 src/CommonLib/SearchResultEntryExtensions.cs delete mode 100644 src/CommonLib/SearchResultEntryWrapper.cs create mode 100644 test/unit/DirectoryObjectTests.cs create mode 100644 test/unit/Facades/MockDirectoryObject.cs rename test/unit/Facades/{MockLDAPUtils.cs => MockLdapUtils.cs} (99%) delete mode 100644 test/unit/Facades/MockSearchResultEntry.cs rename test/unit/{LDAPPropertyTests.cs => LdapPropertyTests.cs} (72%) delete mode 100644 test/unit/SearchResultEntryTests.cs rename test/unit/{Helpers.cs => Utils.cs} (69%) diff --git a/src/CommonLib/ConnectionPoolManager.cs b/src/CommonLib/ConnectionPoolManager.cs index 95fc9615..f4f76719 100644 --- a/src/CommonLib/ConnectionPoolManager.cs +++ b/src/CommonLib/ConnectionPoolManager.cs @@ -9,20 +9,23 @@ namespace SharpHoundCommonLib { public class ConnectionPoolManager : IDisposable{ private readonly ConcurrentDictionary _pools = new(); - private readonly LDAPConfig _ldapConfig; + private readonly LdapConfig _ldapConfig; private readonly string[] _translateNames = { "Administrator", "admin" }; private readonly ConcurrentDictionary _resolvedIdentifiers = new(StringComparer.OrdinalIgnoreCase); private readonly ILogger _log; private readonly PortScanner _portScanner; - public ConnectionPoolManager(LDAPConfig config, ILogger log = null, PortScanner scanner = null) { + public ConnectionPoolManager(LdapConfig config, ILogger log = null, PortScanner scanner = null) { _ldapConfig = config; _log = log ?? Logging.LogProvider.CreateLogger("ConnectionPoolManager"); _portScanner = scanner ?? new PortScanner(); } public void ReleaseConnection(LdapConnectionWrapper connectionWrapper, bool connectionFaulted = false) { - //I dont think this is possible, but at least account for it + if (connectionWrapper == null) { + return; + } + //I don't think this is possible, but at least account for it if (!_pools.TryGetValue(connectionWrapper.PoolIdentifier, out var pool)) { _log.LogWarning("Could not find pool for {Identifier}", connectionWrapper.PoolIdentifier); connectionWrapper.Connection.Dispose(); @@ -84,11 +87,8 @@ private bool GetDomainSidFromDomainName(string domainName, out string domainSid) if (Cache.GetDomainSidMapping(domainName, out domainSid)) return true; try { - var entry = new DirectoryEntry($"LDAP://{domainName}"); - //Force load objectsid into the object cache - entry.RefreshCache(new[] { "objectSid" }); - var sid = entry.GetSid(); - if (sid != null) { + var entry = new DirectoryEntry($"LDAP://{domainName}").ToDirectoryObject(); + if (entry.TryGetSecurityIdentifier(out var sid)) { Cache.AddDomainSidMapping(domainName, sid); domainSid = sid; return true; @@ -100,8 +100,7 @@ private bool GetDomainSidFromDomainName(string domainName, out string domainSid) if (LdapUtils.GetDomain(domainName, _ldapConfig, out var domainObject)) try { - domainSid = domainObject.GetDirectoryEntry().GetSid(); - if (domainSid != null) { + if (domainObject.GetDirectoryEntry().ToDirectoryObject().TryGetSecurityIdentifier(out domainSid)) { Cache.AddDomainSidMapping(domainName, domainSid); return true; } diff --git a/src/CommonLib/DirectoryEntryExtensions.cs b/src/CommonLib/DirectoryEntryExtensions.cs deleted file mode 100644 index 3469b926..00000000 --- a/src/CommonLib/DirectoryEntryExtensions.cs +++ /dev/null @@ -1,134 +0,0 @@ -using System; -using System.Collections.Generic; -using System.DirectoryServices; -using System.Security.Principal; -using System.Text; -using SharpHoundCommonLib.Enums; -using SharpHoundCommonLib.LDAPQueries; -using SharpHoundCommonLib.OutputTypes; - -namespace SharpHoundCommonLib { - - public static class DirectoryEntryExtensions { - public static string GetProperty(this DirectoryEntry entry, string propertyName) { - try { - if (!entry.Properties.Contains(propertyName)) - entry.RefreshCache(new[] { propertyName }); - - if (!entry.Properties.Contains(propertyName)) - return null; - } - catch { - return null; - } - - var s = entry.Properties[propertyName][0]; - return s switch - { - string st => st, - _ => null - }; - } - - public static string[] GetPropertyAsArray(this DirectoryEntry entry, string propertyName) { - try { - if (!entry.Properties.Contains(propertyName)) - entry.RefreshCache(new[] { propertyName }); - - if (!entry.Properties.Contains(propertyName)) - return null; - } - catch { - return null; - } - - var dest = new List(); - foreach (var val in entry.Properties[propertyName]) { - if (val is string s) { - dest.Add(s); - } - } - - return dest.ToArray(); - } - - public static bool GetTypedPrincipal(this DirectoryEntry entry, out TypedPrincipal principal) { - var identifier = entry.GetObjectIdentifier(); - var success = entry.GetLabel(out var label); - principal = new TypedPrincipal(identifier, label); - return (success && !string.IsNullOrWhiteSpace(identifier)); - } - - public static string GetObjectIdentifier(this DirectoryEntry entry) { - return entry.GetSid() ?? entry.GetGuid(); - } - - public static string GetSid(this DirectoryEntry entry) - { - try - { - if (!entry.Properties.Contains(LDAPProperties.ObjectSID)) - entry.RefreshCache(new[] { LDAPProperties.ObjectSID }); - - if (!entry.Properties.Contains(LDAPProperties.ObjectSID)) - return null; - } - catch - { - return null; - } - - var s = entry.Properties[LDAPProperties.ObjectSID][0]; - return s switch - { - byte[] b => new SecurityIdentifier(b, 0).ToString(), - string st => new SecurityIdentifier(Encoding.ASCII.GetBytes(st), 0).ToString(), - _ => null - }; - } - - public static string GetGuid(this DirectoryEntry entry) - { - try - { - //Attempt to refresh the props first - if (!entry.Properties.Contains(LDAPProperties.ObjectGUID)) - entry.RefreshCache(new[] { LDAPProperties.ObjectGUID }); - - if (!entry.Properties.Contains(LDAPProperties.ObjectGUID)) - return null; - } - catch - { - return null; - } - - var s = entry.Properties[LDAPProperties.ObjectGUID][0]; - return s switch - { - byte[] b => new Guid(b).ToString(), - string st => st, - _ => null - }; - } - - - public static bool GetLabel(this DirectoryEntry entry, out Label type) { - try { - entry.RefreshCache(CommonProperties.TypeResolutionProps); - } - catch { - //pass - } - - var flagString = entry.GetProperty(LDAPProperties.Flags); - if (!int.TryParse(flagString, out var flags)) { - flags = 0; - } - - return LdapUtils.ResolveLabel(entry.GetObjectIdentifier(), entry.GetProperty(LDAPProperties.DistinguishedName), - entry.GetProperty(LDAPProperties.SAMAccountType), - entry.GetPropertyAsArray(LDAPProperties.SAMAccountType), flags, out type); - } - } -} \ No newline at end of file diff --git a/src/CommonLib/DirectoryObjects/DirectoryEntryWrapper.cs b/src/CommonLib/DirectoryObjects/DirectoryEntryWrapper.cs new file mode 100644 index 00000000..9a608127 --- /dev/null +++ b/src/CommonLib/DirectoryObjects/DirectoryEntryWrapper.cs @@ -0,0 +1,188 @@ +using System; +using System.Collections.Generic; +using System.DirectoryServices; +using System.Security.Cryptography.X509Certificates; +using System.Security.Principal; +using System.Text; + +namespace SharpHoundCommonLib; + +public class DirectoryEntryWrapper : IDirectoryObject { + private readonly DirectoryEntry _entry; + + public DirectoryEntryWrapper(DirectoryEntry entry) { + _entry = entry; + } + + public bool TryGetDistinguishedName(out string value) { + return TryGetProperty(LDAPProperties.DistinguishedName, out value); + } + + private bool CheckCache(string propertyName) { + try { + if (!_entry.Properties.Contains(propertyName)) + _entry.RefreshCache(new[] { propertyName }); + + return _entry.Properties.Contains(propertyName); + } + catch { + return false; + } + } + + public bool TryGetProperty(string propertyName, out string value) { + value = string.Empty; + if (!CheckCache(propertyName)) { + return false; + } + + var s = _entry.Properties[propertyName].Value; + value = s switch { + string st => st, + int i => i.ToString(), + _ => null + }; + + return value != null; + } + + public bool TryGetByteProperty(string propertyName, out byte[] value) { + value = Array.Empty(); + if (!CheckCache(propertyName)) { + return false; + } + + var prop = _entry.Properties[propertyName].Value; + if (prop is not byte[] b) return false; + value = b; + return true; + } + + public bool TryGetArrayProperty(string propertyName, out string[] value) { + value = Array.Empty(); + if (!CheckCache(propertyName)) { + return false; + } + + var dest = new List(); + foreach (var val in _entry.Properties[propertyName]) { + if (val is string s) { + dest.Add(s); + } + } + + value = dest.ToArray(); + return true; + } + + public bool TryGetByteArrayProperty(string propertyName, out byte[][] value) { + value = Array.Empty(); + if (!CheckCache(propertyName)) { + return false; + } + + var raw = _entry.Properties[propertyName].Value; + if (raw is not byte[][] b) { + return false; + } + value = b; + return true; + } + + public bool TryGetIntProperty(string propertyName, out int value) { + value = 0; + if (!CheckCache(propertyName)) return false; + + if (!TryGetProperty(propertyName, out var s)) { + return false; + } + + return int.TryParse(s, out value); + } + + public bool TryGetCertificateArrayProperty(string propertyName, out X509Certificate2[] value) { + value = Array.Empty(); + if (!TryGetByteArrayProperty(propertyName, out var bytes)) { + return false; + } + + if (bytes.Length == 0) { + return true; + } + + var result = new List(); + + foreach (var b in bytes) { + try { + var cert = new X509Certificate2(b); + result.Add(cert); + } + catch { + //pass + } + } + + value = result.ToArray(); + return true; + } + + public bool TryGetSecurityIdentifier(out string securityIdentifier) { + securityIdentifier = string.Empty; + if (!CheckCache(LDAPProperties.ObjectSID)) { + return false; + } + + var raw = _entry.Properties[LDAPProperties.ObjectSID][0]; + try { + securityIdentifier = raw switch { + byte[] b => new SecurityIdentifier(b, 0).ToString(), + string st => new SecurityIdentifier(Encoding.ASCII.GetBytes(st), 0).ToString(), + _ => default + }; + + return securityIdentifier != default; + } + catch { + return false; + } + } + + public bool TryGetGuid(out string guid) { + guid = string.Empty; + if (!TryGetByteProperty(LDAPProperties.ObjectGUID, out var raw)) { + return false; + } + + try { + guid = new Guid(raw).ToString().ToUpper(); + return true; + } catch { + return false; + } + } + + public string GetProperty(string propertyName) { + CheckCache(propertyName); + return _entry.Properties[propertyName].Value as string; + } + + public byte[] GetByteProperty(string propertyName) { + CheckCache(propertyName); + return _entry.Properties[propertyName].Value as byte[]; + } + + public int PropertyCount(string propertyName) { + if (!CheckCache(propertyName)) { + return 0; + } + + var prop = _entry.Properties[propertyName]; + return prop.Count; + + } + + public IEnumerable PropertyNames() { + foreach (var property in _entry.Properties.PropertyNames) + yield return property.ToString().ToLower(); + } +} \ No newline at end of file diff --git a/src/CommonLib/DirectoryObjects/DirectoryObjectExtensions.cs b/src/CommonLib/DirectoryObjects/DirectoryObjectExtensions.cs new file mode 100644 index 00000000..170e7c33 --- /dev/null +++ b/src/CommonLib/DirectoryObjects/DirectoryObjectExtensions.cs @@ -0,0 +1,71 @@ +using System; +using System.Linq; +using SharpHoundCommonLib.Enums; + +namespace SharpHoundCommonLib.DirectoryObjects; + +public static class DirectoryObjectExtensions { + public static bool IsMSA(this IDirectoryObject directoryObject) { + if (!directoryObject.TryGetArrayProperty(LDAPProperties.ObjectClass, out var classes)) { + return false; + } + + return classes.Contains(ObjectClass.MSAClass, StringComparer.InvariantCultureIgnoreCase); + } + + public static bool IsGMSA(this IDirectoryObject directoryObject) { + if (!directoryObject.TryGetArrayProperty(LDAPProperties.ObjectClass, out var classes)) { + return false; + } + + return classes.Contains(ObjectClass.GMSAClass, StringComparer.InvariantCultureIgnoreCase); + } + + public static bool GetObjectIdentifier(this IDirectoryObject directoryObject, out string objectIdentifier) { + if (directoryObject.TryGetSecurityIdentifier(out objectIdentifier) && !string.IsNullOrWhiteSpace(objectIdentifier)) { + return true; + } + + return directoryObject.TryGetGuid(out objectIdentifier) && !string.IsNullOrWhiteSpace(objectIdentifier); + } + + public static bool GetLabel(this IDirectoryObject directoryObject, out Label type) { + type = Label.Base; + if (!directoryObject.GetObjectIdentifier(out var objectIdentifier)) { + return false; + } + + if (!directoryObject.TryGetIntProperty(LDAPProperties.Flags, out var flags)) { + flags = 0; + } + + directoryObject.TryGetDistinguishedName(out var distinguishedName); + directoryObject.TryGetProperty(LDAPProperties.SAMAccountType, out var samAccountType); + directoryObject.TryGetArrayProperty(LDAPProperties.ObjectClass, out var objectClasses); + + return LdapUtils.ResolveLabel(objectIdentifier, distinguishedName, samAccountType, objectClasses, flags, + out type); + } + + public static bool IsDeleted(this IDirectoryObject directoryObject) { + if (!directoryObject.TryGetProperty(LDAPProperties.IsDeleted, out var deleted)) { + return false; + } + + return bool.TryParse(deleted, out var isDeleted) && isDeleted; + } + + public static bool HasLAPS(this IDirectoryObject directoryObject) { + if (directoryObject.TryGetIntProperty(LDAPProperties.LAPSExpirationTime, out var lapsExpiration) && + lapsExpiration > 0) { + return true; + } + + if (directoryObject.TryGetIntProperty(LDAPProperties.LegacyLAPSExpirationTime, out var legacyLapsExpiration) && + legacyLapsExpiration > 0) { + return true; + } + + return false; + } +} \ No newline at end of file diff --git a/src/CommonLib/DirectoryObjects/IDirectoryObject.cs b/src/CommonLib/DirectoryObjects/IDirectoryObject.cs new file mode 100644 index 00000000..a3582e75 --- /dev/null +++ b/src/CommonLib/DirectoryObjects/IDirectoryObject.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using System.Security.Cryptography.X509Certificates; + +namespace SharpHoundCommonLib; + +public interface IDirectoryObject { + bool TryGetDistinguishedName(out string value); + bool TryGetProperty(string propertyName, out string value); + bool TryGetByteProperty(string propertyName, out byte[] value); + bool TryGetArrayProperty(string propertyName, out string[] value); + bool TryGetByteArrayProperty(string propertyName, out byte[][] value); + bool TryGetIntProperty(string propertyName, out int value); + bool TryGetCertificateArrayProperty(string propertyName, out X509Certificate2[] value); + bool TryGetSecurityIdentifier(out string securityIdentifier); + bool TryGetGuid(out string guid); + string GetProperty(string propertyName); + byte[] GetByteProperty(string propertyName); + int PropertyCount(string propertyName); + IEnumerable PropertyNames(); +} \ No newline at end of file diff --git a/src/CommonLib/DirectoryObjects/SearchResultEntryWrapper.cs b/src/CommonLib/DirectoryObjects/SearchResultEntryWrapper.cs new file mode 100644 index 00000000..b72b7e40 --- /dev/null +++ b/src/CommonLib/DirectoryObjects/SearchResultEntryWrapper.cs @@ -0,0 +1,203 @@ +using System; +using System.Collections.Generic; +using System.DirectoryServices.Protocols; +using System.Linq; +using System.Security.Cryptography.X509Certificates; +using System.Security.Principal; + +namespace SharpHoundCommonLib; + +public class SearchResultEntryWrapper : IDirectoryObject { + private readonly SearchResultEntry _entry; + + public SearchResultEntryWrapper(SearchResultEntry entry) { + _entry = entry; + } + + public bool TryGetDistinguishedName(out string value) { + return TryGetProperty(LDAPProperties.DistinguishedName, out value); + } + + public bool TryGetProperty(string propertyName, out string value) { + value = string.Empty; + if (!_entry.Attributes.Contains(propertyName)) + return false; + + var collection = _entry.Attributes[propertyName]; + //Use GetValues to auto-convert to the proper type + var lookups = collection.GetValues(typeof(string)); + if (lookups.Length == 0) + return false; + + if (lookups[0] is not string prop || prop.Length == 0) + return false; + + value = prop; + return true; + } + + public bool TryGetByteProperty(string propertyName, out byte[] value) { + value = Array.Empty(); + if (!_entry.Attributes.Contains(propertyName)) + return false; + + var collection = _entry.Attributes[propertyName]; + var lookups = collection.GetValues(typeof(byte[])); + + if (lookups.Length == 0) + return false; + + if (lookups[0] is not byte[] bytes || bytes.Length == 0) + return false; + + value = bytes; + return true; + } + + public bool TryGetArrayProperty(string propertyName, out string[] value) { + value = Array.Empty(); + if (!_entry.Attributes.Contains(propertyName)) + return false; + + var values = _entry.Attributes[propertyName]; + var strings = values.GetValues(typeof(string)); + + if (strings.Length == 0) return true; + if (strings is not string[] result) return false; + + value = result; + return true; + } + + public bool TryGetByteArrayProperty(string propertyName, out byte[][] value) { + value = Array.Empty(); + if (!_entry.Attributes.Contains(propertyName)) + return false; + + var values = _entry.Attributes[propertyName]; + var bytes = values.GetValues(typeof(byte[])); + + if (bytes is not byte[][] result) return false; + value = result; + return true; + } + + public bool TryGetIntProperty(string propertyName, out int value) { + if (!TryGetProperty(propertyName, out var raw)) { + value = 0; + return false; + } + + return int.TryParse(raw, out value); + } + + public bool TryGetCertificateArrayProperty(string propertyName, out X509Certificate2[] value) { + value = Array.Empty(); + + if (!TryGetByteArrayProperty(propertyName, out var bytes)) { + return false; + } + + if (bytes.Length == 0) { + return true; + } + + var result = new List(); + + foreach (var b in bytes) { + try { + var cert = new X509Certificate2(b); + result.Add(cert); + } catch { + //pass + } + } + + value = result.ToArray(); + return true; + } + + public bool TryGetSecurityIdentifier(out string securityIdentifier) { + securityIdentifier = string.Empty; + if (!_entry.Attributes.Contains(LDAPProperties.ObjectSID)) return false; + + object[] s; + try { + s = _entry.Attributes[LDAPProperties.ObjectSID].GetValues(typeof(byte[])); + } catch (NotSupportedException) { + return false; + } + + if (s.Length == 0) + return false; + + if (s[0] is not byte[] sidBytes || sidBytes.Length == 0) + return false; + + try { + var sid = new SecurityIdentifier(sidBytes, 0); + securityIdentifier = sid.Value.ToUpper(); + return true; + } catch { + return false; + } + } + + public bool TryGetGuid(out string guid) { + guid = string.Empty; + if (!TryGetByteProperty(LDAPProperties.ObjectGUID, out var raw)) { + return false; + } + + try { + guid = new Guid(raw).ToString().ToUpper(); + return true; + } catch { + return false; + } + } + + public string GetProperty(string propertyName) { + if (!_entry.Attributes.Contains(propertyName)) + return null; + + var collection = _entry.Attributes[propertyName]; + //Use GetValues to auto-convert to the proper type + var lookups = collection.GetValues(typeof(string)); + if (lookups.Length == 0) + return null; + + if (lookups[0] is not string prop || prop.Length == 0) + return null; + + return prop; + } + + public byte[] GetByteProperty(string propertyName) { + if (!_entry.Attributes.Contains(propertyName)) + return null; + + var collection = _entry.Attributes[propertyName]; + var lookups = collection.GetValues(typeof(byte[])); + + if (lookups.Length == 0) + return Array.Empty(); + + if (lookups[0] is not byte[] bytes || bytes.Length == 0) + return Array.Empty(); + + return bytes; + } + + public int PropertyCount(string propertyName) { + if (!_entry.Attributes.Contains(propertyName)) return 0; + var prop = _entry.Attributes[propertyName]; + return prop.Count; + } + + public IEnumerable PropertyNames() { + if (_entry.Attributes.AttributeNames != null) + foreach (var property in _entry.Attributes.AttributeNames) + yield return property.ToString().ToLower(); + } +} \ No newline at end of file diff --git a/src/CommonLib/Enums/CollectionMethods.cs b/src/CommonLib/Enums/CollectionMethod.cs similarity index 96% rename from src/CommonLib/Enums/CollectionMethods.cs rename to src/CommonLib/Enums/CollectionMethod.cs index d4ebf61e..0b5959e1 100644 --- a/src/CommonLib/Enums/CollectionMethods.cs +++ b/src/CommonLib/Enums/CollectionMethod.cs @@ -3,7 +3,7 @@ namespace SharpHoundCommonLib.Enums { [Flags] - public enum ResolvedCollectionMethod + public enum CollectionMethod { None = 0, Group = 1, diff --git a/src/CommonLib/Enums/DirectoryPaths.cs b/src/CommonLib/Enums/DirectoryPaths.cs index d30e19e4..878feef2 100644 --- a/src/CommonLib/Enums/DirectoryPaths.cs +++ b/src/CommonLib/Enums/DirectoryPaths.cs @@ -1,6 +1,6 @@ namespace SharpHoundCommonLib.Enums { - public class DirectoryPaths + public static class DirectoryPaths { public const string EnterpriseCALocation = "CN=Enrollment Services,CN=Public Key Services,CN=Services"; public const string RootCALocation = "CN=Certification Authorities,CN=Public Key Services,CN=Services"; diff --git a/src/CommonLib/Enums/LDAPProperties.cs b/src/CommonLib/Enums/LDAPProperties.cs index 57b0d685..60212251 100644 --- a/src/CommonLib/Enums/LDAPProperties.cs +++ b/src/CommonLib/Enums/LDAPProperties.cs @@ -78,5 +78,6 @@ public static class LDAPProperties public const string NetbiosName = "netbiosName"; public const string DnsRoot = "dnsroot"; public const string ServerName = "servername"; + public const string OU = "ou"; } } diff --git a/src/CommonLib/Enums/ObjectClass.cs b/src/CommonLib/Enums/ObjectClass.cs new file mode 100644 index 00000000..f8bab0fc --- /dev/null +++ b/src/CommonLib/Enums/ObjectClass.cs @@ -0,0 +1,15 @@ +namespace SharpHoundCommonLib.Enums; + +public static class ObjectClass { + public const string GroupPolicyContainerClass = "groupPolicyContainer"; + public const string OrganizationalUnitClass = "organizationalUnit"; + public const string DomainClass = "domain"; + public const string ContainerClass = "container"; + public const string ConfigurationClass = "configuration"; + public const string PKICertificateTemplateClass = "pKICertificateTemplate"; + public const string PKIEnrollmentServiceClass = "pKIEnrollmentService"; + public const string CertificationAuthorityClass = "certificationAuthority"; + public const string OIDContainerClass = "msPKI-Enterprise-Oid"; + public const string GMSAClass = "msds-groupmanagedserviceaccount"; + public const string MSAClass = "msds-managedserviceaccount"; +} \ No newline at end of file diff --git a/src/CommonLib/Extensions.cs b/src/CommonLib/Extensions.cs index 4128fb58..f8252796 100644 --- a/src/CommonLib/Extensions.cs +++ b/src/CommonLib/Extensions.cs @@ -1,4 +1,5 @@ using System; +using System.DirectoryServices; using System.Linq; using System.Security.Principal; using Microsoft.Extensions.Logging; @@ -45,9 +46,9 @@ public static string LdapValue(this Guid s) /// /// /// - public static bool IsComputerCollectionSet(this ResolvedCollectionMethod methods) - { - return (methods & ResolvedCollectionMethod.ComputerOnly) != 0; + public static bool IsComputerCollectionSet(this CollectionMethod methods) { + const CollectionMethod test = CollectionMethod.ComputerOnly | CollectionMethod.LoggedOn; + return (methods & test) != 0; } /// @@ -55,9 +56,9 @@ public static bool IsComputerCollectionSet(this ResolvedCollectionMethod methods /// /// /// - public static bool IsLocalGroupCollectionSet(this ResolvedCollectionMethod methods) + public static bool IsLocalGroupCollectionSet(this CollectionMethod methods) { - return (methods & ResolvedCollectionMethod.LocalGroups) != 0; + return (methods & CollectionMethod.LocalGroups) != 0; } /// @@ -71,5 +72,9 @@ public static int Rid(this SecurityIdentifier securityIdentifier) var rid = int.Parse(value.Substring(value.LastIndexOf("-", StringComparison.Ordinal) + 1)); return rid; } + + public static IDirectoryObject ToDirectoryObject(this DirectoryEntry entry) { + return new DirectoryEntryWrapper(entry); + } } } \ No newline at end of file diff --git a/src/CommonLib/Helpers.cs b/src/CommonLib/Helpers.cs index c59da55e..f3400cdb 100644 --- a/src/CommonLib/Helpers.cs +++ b/src/CommonLib/Helpers.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.DirectoryServices; using System.Globalization; using System.Linq; using System.Security.Principal; @@ -13,52 +12,45 @@ using SharpHoundCommonLib.Processors; using Microsoft.Win32; -namespace SharpHoundCommonLib -{ - public static class Helpers - { - private static readonly HashSet Groups = new() {"268435456", "268435457", "536870912", "536870913"}; - private static readonly HashSet Computers = new() {"805306369"}; - private static readonly HashSet Users = new() {"805306368"}; +namespace SharpHoundCommonLib { + public static class Helpers { + private static readonly HashSet Groups = new() { "268435456", "268435457", "536870912", "536870913" }; + private static readonly HashSet Computers = new() { "805306369" }; + private static readonly HashSet Users = new() { "805306368" }; private static readonly Regex DCReplaceRegex = new("DC=", RegexOptions.IgnoreCase | RegexOptions.Compiled); private static readonly Regex SPNRegex = new(@".*\/.*", RegexOptions.Compiled); private static readonly DateTime EpochDiff = new(1970, 1, 1); - private static readonly string[] FilteredSids = - { + + private static readonly string[] FilteredSids = { "S-1-5-2", "S-1-5-3", "S-1-5-4", "S-1-5-6", "S-1-5-7", "S-1-2", "S-1-2-0", "S-1-5-18", "S-1-5-19", "S-1-5-20", "S-1-0-0", "S-1-0", "S-1-2-1" }; - public static string RemoveDistinguishedNamePrefix(string distinguishedName) - { - if (!distinguishedName.Contains(",")) - { + public static string RemoveDistinguishedNamePrefix(string distinguishedName) { + if (!distinguishedName.Contains(",")) { return ""; } - if (distinguishedName.IndexOf("DC=", StringComparison.OrdinalIgnoreCase) < 0) - { + + if (distinguishedName.IndexOf("DC=", StringComparison.OrdinalIgnoreCase) < 0) { return ""; } //Start at the first instance of a comma, and continue to loop while we still have commas. If we get -1, it means we ran out of commas. //This allows us to cleanly iterate over all indexes of commas in our DNs and find the first non-escaped one - for (var i = distinguishedName.IndexOf(','); i > -1; i = distinguishedName.IndexOf(',', i + 1)) - { + for (var i = distinguishedName.IndexOf(','); i > -1; i = distinguishedName.IndexOf(',', i + 1)) { //If theres a comma at the beginning of the DN, something screwy is going on. Just ignore it - if (i == 0) - { + if (i == 0) { continue; } //This indicates an escaped comma, which we should not use to split a DN - if (distinguishedName[i-1] == '\\') - { + if (distinguishedName[i - 1] == '\\') { continue; } - + //This is an unescaped comma, so snip our DN from this comma onwards and return this as the cleaned distinguished name - return distinguishedName.Substring(i + 1); + return distinguishedName.Substring(i + 1); } return ""; @@ -71,11 +63,9 @@ public static string RemoveDistinguishedNamePrefix(string distinguishedName) /// /// /// - public static IEnumerable SplitGPLinkProperty(string linkProp, bool filterDisabled = true) - { + public static IEnumerable SplitGPLinkProperty(string linkProp, bool filterDisabled = true) { foreach (var link in linkProp.Split(']', '[') - .Where(x => x.StartsWith("LDAP", StringComparison.OrdinalIgnoreCase))) - { + .Where(x => x.StartsWith("LDAP", StringComparison.OrdinalIgnoreCase))) { var s = link.Split(';'); var dn = s[0].Substring(s[0].IndexOf("CN=", StringComparison.OrdinalIgnoreCase)); var status = s[1]; @@ -85,8 +75,7 @@ public static IEnumerable SplitGPLinkProperty(string linkProp, boo if (status is "3" or "1") continue; - yield return new ParsedGPLink - { + yield return new ParsedGPLink { Status = status.TrimStart().TrimEnd(), DistinguishedName = dn.TrimStart().TrimEnd() }; @@ -98,8 +87,7 @@ public static IEnumerable SplitGPLinkProperty(string linkProp, boo /// /// /// Label value representing type - public static Label SamAccountTypeToType(string samAccountType) - { + public static Label SamAccountTypeToType(string samAccountType) { if (Groups.Contains(samAccountType)) return Label.Group; @@ -117,8 +105,7 @@ public static Label SamAccountTypeToType(string samAccountType) /// /// String security identifier to convert /// String representation to use in LDAP filters - public static string ConvertSidToHexSid(string sid) - { + public static string ConvertSidToHexSid(string sid) { var securityIdentifier = new SecurityIdentifier(sid); var sidBytes = new byte[securityIdentifier.BinaryLength]; securityIdentifier.GetBinaryForm(sidBytes, 0); @@ -132,8 +119,7 @@ public static string ConvertSidToHexSid(string sid) /// /// /// - public static string ConvertGuidToHexGuid(string guid) - { + public static string ConvertGuidToHexGuid(string guid) { var guidObj = new Guid(guid); var guidBytes = guidObj.ToByteArray(); var output = $"\\{BitConverter.ToString(guidBytes).Replace('-', '\\')}"; @@ -145,19 +131,15 @@ public static string ConvertGuidToHexGuid(string guid) /// /// Distinguished Name to extract domain from /// String representing the domain name of this object - public static string DistinguishedNameToDomain(string distinguishedName) - { + public static string DistinguishedNameToDomain(string distinguishedName) { int idx; - if (distinguishedName.ToUpper().Contains("DELETED OBJECTS")) - { + if (distinguishedName.ToUpper().Contains("DELETED OBJECTS")) { idx = distinguishedName.IndexOf("DC=", 3, StringComparison.Ordinal); - } - else - { + } else { idx = distinguishedName.IndexOf("DC=", - StringComparison.CurrentCultureIgnoreCase); + StringComparison.CurrentCultureIgnoreCase); } - + if (idx < 0) return null; @@ -165,7 +147,7 @@ public static string DistinguishedNameToDomain(string distinguishedName) temp = DCReplaceRegex.Replace(temp, "").Replace(",", ".").ToUpper(); return temp; } - + /// /// Converts a domain name to a distinguished name using simple string substitution /// @@ -180,8 +162,7 @@ public static string DomainNameToDistinguishedName(string domainName) { /// /// Raw service principal name /// Stripped service principal name with (hopefully) just the hostname - public static string StripServicePrincipalName(string target) - { + public static string StripServicePrincipalName(string target) { return SPNRegex.IsMatch(target) ? target.Split('/')[1].Split(':')[0] : target; } @@ -190,8 +171,7 @@ public static string StripServicePrincipalName(string target) /// /// /// - public static string Base64(string input) - { + public static string Base64(string input) { var plainBytes = Encoding.UTF8.GetBytes(input); return Convert.ToBase64String(plainBytes); } @@ -201,8 +181,7 @@ public static string Base64(string input) /// /// /// - public static long ConvertFileTimeToUnixEpoch(string ldapTime) - { + public static long ConvertFileTimeToUnixEpoch(string ldapTime) { if (ldapTime == null) return -1; @@ -212,12 +191,9 @@ public static long ConvertFileTimeToUnixEpoch(string ldapTime) long toReturn; - try - { - toReturn = (long) Math.Floor(DateTime.FromFileTimeUtc(time).Subtract(EpochDiff).TotalSeconds); - } - catch - { + try { + toReturn = (long)Math.Floor(DateTime.FromFileTimeUtc(time).Subtract(EpochDiff).TotalSeconds); + } catch { toReturn = -1; } @@ -229,15 +205,11 @@ public static long ConvertFileTimeToUnixEpoch(string ldapTime) /// /// /// - public static long ConvertTimestampToUnixEpoch(string ldapTime) - { - try - { + public static long ConvertTimestampToUnixEpoch(string ldapTime) { + try { var dt = DateTime.ParseExact(ldapTime, "yyyyMMddHHmmss.0K", CultureInfo.CurrentCulture); - return (long) dt.Subtract(EpochDiff).TotalSeconds; - } - catch - { + return (long)dt.Subtract(EpochDiff).TotalSeconds; + } catch { return 0; } } @@ -247,22 +219,20 @@ public static long ConvertTimestampToUnixEpoch(string ldapTime) /// /// /// - public static long ConvertLdapTimeToLong(string ldapTime) - { + public static long ConvertLdapTimeToLong(string ldapTime) { if (ldapTime == null) return -1; var time = long.Parse(ldapTime); return time; } - + /// /// Removes some commonly seen SIDs that have no use in the schema /// /// /// - internal static string PreProcessSID(string sid) - { + internal static string PreProcessSID(string sid) { sid = sid?.ToUpper(); if (sid != null) //Ignore Local System/Creator Owner/Principal Self @@ -270,9 +240,8 @@ internal static string PreProcessSID(string sid) return null; } - - public static bool IsSidFiltered(string sid) - { + + public static bool IsSidFiltered(string sid) { //Uppercase just in case we get a lowercase s sid = sid.ToUpper(); if (sid.StartsWith("S-1-5-80") || sid.StartsWith("S-1-5-82") || @@ -285,38 +254,28 @@ public static bool IsSidFiltered(string sid) return false; } - public static RegistryResult GetRegistryKeyData(string target, string subkey, string subvalue, ILogger log) - { + public static RegistryResult GetRegistryKeyData(string target, string subkey, string subvalue, ILogger log) { var data = new RegistryResult(); - - try - { + + try { var baseKey = OpenRemoteRegistry(target); var value = baseKey.GetValue(subkey, subvalue); data.Value = value; data.Collected = true; - } - catch (IOException e) - { + } catch (IOException e) { log.LogError(e, "Error getting data from registry for {Target}: {RegSubKey}:{RegValue}", target, subkey, subvalue); data.FailureReason = "Target machine was not found or not connectable"; - } - catch (SecurityException e) - { + } catch (SecurityException e) { log.LogError(e, "Error getting data from registry for {Target}: {RegSubKey}:{RegValue}", target, subkey, subvalue); data.FailureReason = "User does not have the proper permissions to perform this operation"; - } - catch (UnauthorizedAccessException e) - { + } catch (UnauthorizedAccessException e) { log.LogError(e, "Error getting data from registry for {Target}: {RegSubKey}:{RegValue}", target, subkey, subvalue); data.FailureReason = "User does not have the necessary registry rights"; - } - catch (Exception e) - { + } catch (Exception e) { log.LogError(e, "Error getting data from registry for {Target}: {RegSubKey}:{RegValue}", target, subkey, subvalue); data.FailureReason = e.Message; @@ -324,9 +283,8 @@ public static RegistryResult GetRegistryKeyData(string target, string subkey, st return data; } - - public static IRegistryKey OpenRemoteRegistry(string target) - { + + public static IRegistryKey OpenRemoteRegistry(string target) { var key = new SHRegistryKey(RegistryHive.LocalMachine, target); return key; } @@ -339,8 +297,7 @@ public static IRegistryKey OpenRemoteRegistry(string target) }; } - public class ParsedGPLink - { + public class ParsedGPLink { public string DistinguishedName { get; set; } public string Status { get; set; } } diff --git a/src/CommonLib/ILdapUtils.cs b/src/CommonLib/ILdapUtils.cs index bf15cac2..0732a1f8 100644 --- a/src/CommonLib/ILdapUtils.cs +++ b/src/CommonLib/ILdapUtils.cs @@ -8,43 +8,163 @@ namespace SharpHoundCommonLib { public interface ILdapUtils : IDisposable { - IAsyncEnumerable> Query(LdapQueryParameters queryParameters, + /// + /// Performs a non-paged LDAP query. + /// + /// Parameters for the LDAP query + /// Optional cancellation token to support early exit + /// An IEnumerable containing Result objects containing Directory Objects + IAsyncEnumerable> Query(LdapQueryParameters queryParameters, CancellationToken cancellationToken = new()); - IAsyncEnumerable> PagedQuery(LdapQueryParameters queryParameters, + /// + /// Performs a LDAP query with paging support. + /// + /// Parameters for the LDAP query + /// Optional cancellation token to support early exit + /// An IEnumerable containing Result objects containing Directory Objects + IAsyncEnumerable> PagedQuery(LdapQueryParameters queryParameters, CancellationToken cancellationToken = new()); + /// + /// Performs a ranged retrieval operation + /// + /// The base distinguished name to search on + /// The attribute being retrieved + /// A cancellation token for early exit + /// An IEnumerable of result objects containing string results from the query IAsyncEnumerable> RangedRetrieval(string distinguishedName, string attributeName, CancellationToken cancellationToken = new()); + /// + /// Attempts to resolve a SecurityIdentifier to its corresponding TypedPrincipal + /// + /// SecurityIdentifier object to resolve + /// The domain the object belongs too + /// A tuple containing success state as well as the resolved principal if successful Task<(bool Success, TypedPrincipal Principal)> ResolveIDAndType(SecurityIdentifier securityIdentifier, string objectDomain); + /// + /// Attempts to resolve an object identifier to its corresponding TypedPrincipal + /// + /// String identifier for an object, usually a guid or sid + /// The domain the object belongs too + /// A tuple containing success state as well as the resolved principal if successful Task<(bool Success, TypedPrincipal Principal)> ResolveIDAndType(string identifier, string objectDomain); + /// + /// Attempts to resolve a security identifier to its corresponding well known principal + /// + /// + /// + /// A tuple containing success state as well as the resolved principal if successful Task<(bool Success, TypedPrincipal WellKnownPrincipal)> GetWellKnownPrincipal( string securityIdentifier, string objectDomain); + /// + /// Attempts to resolve the domain name for a security identifier. + /// + /// String security identifier for an object + /// A tuple containing success state as well as the resolved domain name if successful Task<(bool Success, string DomainName)> GetDomainNameFromSid(string sid); + /// + /// Attempts to resolve the sid for a domain given its name + /// + /// The domain name to resolve + /// A tuple containing success state as well as the resolved domain sid if successful Task<(bool Success, string DomainSid)> GetDomainSidFromDomainName(string domainName); + /// + /// Attempts to retrieve the Domain object for the specified domain + /// + /// The domain name to retrieve the Domain object for + /// The domain object + /// True if the domain was found, false if not bool GetDomain(string domainName, out System.DirectoryServices.ActiveDirectory.Domain domain); + /// + /// Attempts to retrieve the Domain object for the user's current domain + /// + /// The Domain object + /// True if the domain was found, false if not bool GetDomain(out System.DirectoryServices.ActiveDirectory.Domain domain); + /// + /// Attempts to resolve an account name to its corresponding typed principal + /// + /// The account name to resolve + /// The domain to resolve the account in + /// A tuple containing success state as well as the resolved TypedPrincipal if successful Task<(bool Success, TypedPrincipal Principal)> ResolveAccountName(string name, string domain); + /// + /// Attempts to resolve a host to its corresponding security identifier in AD + /// + /// The hostname to resolve. Will accept an IP or a hostname + /// The domain to lookup the account in + /// A tuple containing success state as well as the resolved computer sid if successful Task<(bool Success, string SecurityIdentifier)> ResolveHostToSid(string host, string domain); + /// + /// Attempts to look up possible matches for a user in the global catalog + /// + /// The name of the account to look up + /// The domain to connect to a global catalog for + /// A tuple containing success state as well as all potential account matches in the global catalog Task<(bool Success, string[] Sids)> GetGlobalCatalogMatches(string name, string domain); + /// + /// Attempts to resolve a certificate template by a specific property + /// + /// The value of the property being matched + /// The name of the property being matched + /// The domain to lookup the certificate template in + /// A tuple containing success state as well as the resolved certificate template if successful Task<(bool Success, TypedPrincipal Principal)> ResolveCertTemplateByProperty(string propValue, string propName, string domainName); + /// + /// Makes a new security descriptor object. This is a testing shim + /// + /// An ActiveDirectorySecurityDescriptor object ActiveDirectorySecurityDescriptor MakeSecurityDescriptor(); + /// + /// Attempts to convert a local-to-computer well known principal + /// + /// The security identifier to convert + /// The sid of the computer in the domain + /// The domain of the computer + /// A tuple containing success state as well as the resolved principal if successful Task<(bool Success, TypedPrincipal Principal)> ConvertLocalWellKnownPrincipal(SecurityIdentifier sid, string computerDomainSid, string computerDomain); + /// + /// Attempts to determine if a computer sid corresponds to a domain controller + /// + /// The sid of the computer being tested + /// The domain to lookup the computer + /// True if the SID is a domain controller, false if not or if the object is not found Task IsDomainController(string computerObjectId, string domainName); + /// + /// Attempts to resolve a distinguished name to its corresponding principal + /// + /// The distinguished name to resolve + /// A tuple containing success state as well as the resolved principal sid if successful Task<(bool Success, TypedPrincipal Principal)> ResolveDistinguishedName(string distinguishedName); void AddDomainController(string domainControllerSID); IAsyncEnumerable GetWellKnownPrincipalOutput(); - void SetLdapConfig(LDAPConfig config); + /// + /// Sets the ldap config for this utils instance. Will dispose if any existing ldap connections when set + /// + /// The new ldap config + void SetLdapConfig(LdapConfig config); + /// + /// Tests if a LDAP connection can be made successfully to a domain + /// + /// The domain to test + /// A tuple containing success state as well as a message if unsuccessful Task<(bool Success, string Message)> TestLdapConnection(string domain); + /// + /// Attempts to get the distinguished name corresponding to a specific naming context for a domain + /// + /// The domain to get the context for + /// The naming context being retrieved + /// A tuple containing success state as well as the resolved distinguished name if successful Task<(bool Success, string Path)> GetNamingContextPath(string domain, NamingContext context); } } \ No newline at end of file diff --git a/src/CommonLib/LDAPConfig.cs b/src/CommonLib/LdapConfig.cs similarity index 97% rename from src/CommonLib/LDAPConfig.cs rename to src/CommonLib/LdapConfig.cs index c4cdd9ff..e59bae71 100644 --- a/src/CommonLib/LDAPConfig.cs +++ b/src/CommonLib/LdapConfig.cs @@ -2,7 +2,7 @@ namespace SharpHoundCommonLib { - public class LDAPConfig + public class LdapConfig { public string Username { get; set; } = null; public string Password { get; set; } = null; diff --git a/src/CommonLib/LdapConnectionPool.cs b/src/CommonLib/LdapConnectionPool.cs index b21d38cf..b7913034 100644 --- a/src/CommonLib/LdapConnectionPool.cs +++ b/src/CommonLib/LdapConnectionPool.cs @@ -19,12 +19,12 @@ public class LdapConnectionPool : IDisposable{ private readonly SemaphoreSlim _semaphore; private readonly string _identifier; private readonly string _poolIdentifier; - private readonly LDAPConfig _ldapConfig; + private readonly LdapConfig _ldapConfig; private readonly ILogger _log; private readonly PortScanner _portScanner; private readonly NativeMethods _nativeMethods; - public LdapConnectionPool(string identifier, string poolIdentifier, LDAPConfig config, int maxConnections = 10, PortScanner scanner = null, NativeMethods nativeMethods = null, ILogger log = null) { + public LdapConnectionPool(string identifier, string poolIdentifier, LdapConfig config, int maxConnections = 10, PortScanner scanner = null, NativeMethods nativeMethods = null, ILogger log = null) { _connections = new ConcurrentBag(); _globalCatalogConnection = new ConcurrentBag(); _semaphore = new SemaphoreSlim(maxConnections, maxConnections); @@ -303,7 +303,7 @@ private bool TestLdapConnection(LdapConnection connection, out LdapConnectionTes try { //Do an initial search request to get the rootDSE //This ldap filter is equivalent to (objectclass=*) - var searchRequest = CreateSearchRequest("", new LDAPFilter().AddAllObjects().GetFilter(), + var searchRequest = CreateSearchRequest("", new LdapFilter().AddAllObjects().GetFilter(), SearchScope.Base, null); response = (SearchResponse)connection.SendRequest(searchRequest); @@ -335,7 +335,7 @@ private bool TestLdapConnection(LdapConnection connection, out LdapConnectionTes private class LdapConnectionTestResult { public string Message { get; set; } - public ISearchResultEntry SearchResultEntry { get; set; } + public IDirectoryObject SearchResultEntry { get; set; } public int ErrorCode { get; set; } } diff --git a/src/CommonLib/LdapConnectionWrapper.cs b/src/CommonLib/LdapConnectionWrapper.cs index 0482603d..17314518 100644 --- a/src/CommonLib/LdapConnectionWrapper.cs +++ b/src/CommonLib/LdapConnectionWrapper.cs @@ -5,7 +5,7 @@ namespace SharpHoundCommonLib { public class LdapConnectionWrapper { public LdapConnection Connection { get; private set; } - private readonly ISearchResultEntry _searchResultEntry; + private readonly IDirectoryObject _rootDseEntry; private string _domainSearchBase; private string _configurationSearchBase; private string _schemaSearchBase; @@ -14,10 +14,10 @@ public class LdapConnectionWrapper { public readonly bool GlobalCatalog; public readonly string PoolIdentifier; - public LdapConnectionWrapper(LdapConnection connection, ISearchResultEntry entry, bool globalCatalog, + public LdapConnectionWrapper(LdapConnection connection, IDirectoryObject rootDseEntry, bool globalCatalog, string poolIdentifier) { Connection = connection; - _searchResultEntry = entry; + _rootDseEntry = rootDseEntry; Guid = new Guid().ToString(); GlobalCatalog = globalCatalog; PoolIdentifier = poolIdentifier; @@ -28,7 +28,7 @@ public string GetServer() { return _server; } - _server = _searchResultEntry.GetProperty(LDAPProperties.DNSHostName); + _server = _rootDseEntry.GetProperty(LDAPProperties.DNSHostName); return _server; } @@ -39,10 +39,10 @@ public bool GetSearchBase(NamingContext context, out string searchBase) { } searchBase = context switch { - NamingContext.Default => _searchResultEntry.GetProperty(LDAPProperties.DefaultNamingContext), + NamingContext.Default => _rootDseEntry.GetProperty(LDAPProperties.DefaultNamingContext), NamingContext.Configuration => - _searchResultEntry.GetProperty(LDAPProperties.ConfigurationNamingContext), - NamingContext.Schema => _searchResultEntry.GetProperty(LDAPProperties.SchemaNamingContext), + _rootDseEntry.GetProperty(LDAPProperties.ConfigurationNamingContext), + NamingContext.Schema => _rootDseEntry.GetProperty(LDAPProperties.SchemaNamingContext), _ => throw new ArgumentOutOfRangeException(nameof(context), context, null) }; diff --git a/src/CommonLib/LdapProducerQueryGenerator.cs b/src/CommonLib/LdapProducerQueryGenerator.cs new file mode 100644 index 00000000..ec38f28c --- /dev/null +++ b/src/CommonLib/LdapProducerQueryGenerator.cs @@ -0,0 +1,151 @@ +using System.Collections.Generic; +using System.Linq; +using SharpHoundCommonLib.Enums; +using SharpHoundCommonLib.LDAPQueries; + +namespace SharpHoundCommonLib; + +public class LdapProducerQueryGenerator { + public static GeneratedLdapParameters GenerateDefaultPartitionParameters(CollectionMethod methods) { + var filter = new LdapFilter(); + var properties = new List(); + + properties.AddRange(CommonProperties.BaseQueryProps); + properties.AddRange(CommonProperties.TypeResolutionProps); + + if (methods.HasFlag(CollectionMethod.ObjectProps) || methods.HasFlag(CollectionMethod.ACL) || methods.HasFlag(CollectionMethod.Container)) { + filter = filter.AddComputers().AddDomains().AddUsers().AddContainers().AddGPOs().AddOUs().AddGroups(); + + if (methods.HasFlag(CollectionMethod.Container)) { + properties.AddRange(CommonProperties.ContainerProps); + } + + if (methods.HasFlag(CollectionMethod.Group)) { + properties.AddRange(CommonProperties.GroupResolutionProps); + } + + if (methods.HasFlag(CollectionMethod.ACL)) { + properties.AddRange(CommonProperties.ACLProps); + } + + if (methods.IsComputerCollectionSet()) { + properties.AddRange(CommonProperties.ComputerMethodProps); + } + + if (methods.HasFlag(CollectionMethod.Trusts)) { + properties.AddRange(CommonProperties.DomainTrustProps); + } + + if (methods.HasFlag(CollectionMethod.GPOLocalGroup)) + properties.AddRange(CommonProperties.GPOLocalGroupProps); + + if (methods.HasFlag(CollectionMethod.SPNTargets)) + properties.AddRange(CommonProperties.SPNTargetProps); + + if (methods.HasFlag(CollectionMethod.DCRegistry)) + properties.AddRange(CommonProperties.ComputerMethodProps); + + if (methods.HasFlag(CollectionMethod.SPNTargets)) { + properties.AddRange(CommonProperties.SPNTargetProps); + } + + return new GeneratedLdapParameters { + Filter = filter, + Attributes = properties.Distinct().ToArray() + }; + } + + if (methods.HasFlag(CollectionMethod.Group)) { + filter = filter.AddGroups(); + properties.AddRange(CommonProperties.GroupResolutionProps); + } + + if (methods.IsComputerCollectionSet()) { + filter = filter.AddComputers(); + properties.AddRange(CommonProperties.ComputerMethodProps); + } + + if (methods.HasFlag(CollectionMethod.Trusts)) { + filter = filter.AddDomains(); + properties.AddRange(CommonProperties.ComputerMethodProps); + } + + if (methods.HasFlag(CollectionMethod.SPNTargets)) { + filter = filter.AddUsers(CommonFilters.NeedsSPN); + properties.AddRange(CommonProperties.SPNTargetProps); + } + + if (methods.HasFlag(CollectionMethod.GPOLocalGroup)) { + filter = filter.AddOUs(); + properties.AddRange(CommonProperties.GPOLocalGroupProps); + } + + if (methods.HasFlag(CollectionMethod.DCRegistry)) { + filter = filter.AddComputers(CommonFilters.DomainControllers); + properties.AddRange(CommonProperties.ComputerMethodProps); + } + + return new GeneratedLdapParameters { + Filter = filter, + Attributes = properties.Distinct().ToArray() + }; + } + + public static GeneratedLdapParameters GenerateConfigurationPartitionParameters(CollectionMethod methods) { + var filter = new LdapFilter(); + var properties = new List(); + + properties.AddRange(CommonProperties.BaseQueryProps); + properties.AddRange(CommonProperties.TypeResolutionProps); + + if (methods.HasFlag(CollectionMethod.ACL) || methods.HasFlag(CollectionMethod.ObjectProps) || + methods.HasFlag(CollectionMethod.Container) || methods.HasFlag(CollectionMethod.CertServices)) { + filter = filter.AddContainers().AddConfiguration().AddCertificateTemplates().AddCertificateAuthorities() + .AddEnterpriseCertificationAuthorities().AddIssuancePolicies(); + + if (methods.HasFlag(CollectionMethod.ObjectProps)) + { + properties.AddRange(CommonProperties.ObjectPropsProps); + } + + if (methods.HasFlag(CollectionMethod.ACL)) { + properties.AddRange(CommonProperties.ACLProps); + } + + if (methods.HasFlag(CollectionMethod.Container)) { + properties.AddRange(CommonProperties.ContainerProps); + } + + if (methods.HasFlag(CollectionMethod.CertServices)) { + properties.AddRange(CommonProperties.CertAbuseProps); + properties.AddRange(CommonProperties.ObjectPropsProps); + properties.AddRange(CommonProperties.ContainerProps); + properties.AddRange(CommonProperties.ACLProps); + } + + if (methods.HasFlag(CollectionMethod.CARegistry)) { + properties.AddRange(CommonProperties.CertAbuseProps); + } + + return new GeneratedLdapParameters { + Filter = filter, + Attributes = properties.Distinct().ToArray() + }; + } + + if (methods.HasFlag(CollectionMethod.CARegistry)) { + filter = filter.AddEnterpriseCertificationAuthorities(); + properties.AddRange(CommonProperties.CertAbuseProps); + } + + return new GeneratedLdapParameters { + Filter = filter, + Attributes = properties.Distinct().ToArray() + }; + } +} + +public class GeneratedLdapParameters { + public string[] Attributes { get; set; } + public LdapFilter Filter { get; set; } +} \ No newline at end of file diff --git a/src/CommonLib/LDAPQueries/CommonFilters.cs b/src/CommonLib/LdapQueries/CommonFilters.cs similarity index 100% rename from src/CommonLib/LDAPQueries/CommonFilters.cs rename to src/CommonLib/LdapQueries/CommonFilters.cs diff --git a/src/CommonLib/LDAPQueries/CommonPaths.cs b/src/CommonLib/LdapQueries/CommonPaths.cs similarity index 100% rename from src/CommonLib/LDAPQueries/CommonPaths.cs rename to src/CommonLib/LdapQueries/CommonPaths.cs diff --git a/src/CommonLib/LDAPQueries/CommonProperties.cs b/src/CommonLib/LdapQueries/CommonProperties.cs similarity index 98% rename from src/CommonLib/LDAPQueries/CommonProperties.cs rename to src/CommonLib/LdapQueries/CommonProperties.cs index 6cda11de..34661a00 100644 --- a/src/CommonLib/LDAPQueries/CommonProperties.cs +++ b/src/CommonLib/LdapQueries/CommonProperties.cs @@ -30,7 +30,8 @@ public static class CommonProperties public static readonly string[] ComputerMethodProps = { LDAPProperties.SAMAccountName, LDAPProperties.DistinguishedName, LDAPProperties.DNSHostName, - LDAPProperties.SAMAccountType, LDAPProperties.OperatingSystem, LDAPProperties.PasswordLastSet + LDAPProperties.SAMAccountType, LDAPProperties.OperatingSystem, LDAPProperties.PasswordLastSet, + LDAPProperties.LastLogonTimestamp }; public static readonly string[] ACLProps = diff --git a/src/CommonLib/LDAPQueries/LDAPFilter.cs b/src/CommonLib/LdapQueries/LdapFilter.cs similarity index 88% rename from src/CommonLib/LDAPQueries/LDAPFilter.cs rename to src/CommonLib/LdapQueries/LdapFilter.cs index 44402c7f..58bb4fbb 100644 --- a/src/CommonLib/LDAPQueries/LDAPFilter.cs +++ b/src/CommonLib/LdapQueries/LdapFilter.cs @@ -6,7 +6,7 @@ namespace SharpHoundCommonLib.LDAPQueries /// /// A class used to more easily build LDAP filters based on the common filters used by SharpHound /// - public class LDAPFilter + public class LdapFilter { private readonly List _filterParts = new(); private readonly List _mandatory = new(); @@ -49,7 +49,7 @@ private static string BuildString(string baseFilter, params string[] conditions) /// /// /// - public LDAPFilter AddAllObjects(params string[] conditions) + public LdapFilter AddAllObjects(params string[] conditions) { _filterParts.Add(BuildString("(objectclass=*)", conditions)); @@ -61,7 +61,7 @@ public LDAPFilter AddAllObjects(params string[] conditions) /// /// /// - public LDAPFilter AddUsers(params string[] conditions) + public LdapFilter AddUsers(params string[] conditions) { _filterParts.Add(BuildString("(samaccounttype=805306368)", conditions)); @@ -73,7 +73,7 @@ public LDAPFilter AddUsers(params string[] conditions) /// /// /// - public LDAPFilter AddGroups(params string[] conditions) + public LdapFilter AddGroups(params string[] conditions) { _filterParts.Add(BuildString( "(|(samaccounttype=268435456)(samaccounttype=268435457)(samaccounttype=536870912)(samaccounttype=536870913))", @@ -87,7 +87,7 @@ public LDAPFilter AddGroups(params string[] conditions) /// /// /// - public LDAPFilter AddPrimaryGroups(params string[] conditions) + public LdapFilter AddPrimaryGroups(params string[] conditions) { _filterParts.Add(BuildString("(primarygroupid=*)", conditions)); @@ -99,7 +99,7 @@ public LDAPFilter AddPrimaryGroups(params string[] conditions) /// /// /// - public LDAPFilter AddGPOs(params string[] conditions) + public LdapFilter AddGPOs(params string[] conditions) { _filterParts.Add(BuildString("(&(objectcategory=groupPolicyContainer)(flags=*))", conditions)); @@ -111,7 +111,7 @@ public LDAPFilter AddGPOs(params string[] conditions) /// /// /// - public LDAPFilter AddOUs(params string[] conditions) + public LdapFilter AddOUs(params string[] conditions) { _filterParts.Add(BuildString("(objectcategory=organizationalUnit)", conditions)); @@ -123,7 +123,7 @@ public LDAPFilter AddOUs(params string[] conditions) /// /// /// - public LDAPFilter AddDomains(params string[] conditions) + public LdapFilter AddDomains(params string[] conditions) { _filterParts.Add(BuildString("(objectclass=domain)", conditions)); @@ -135,7 +135,7 @@ public LDAPFilter AddDomains(params string[] conditions) /// /// /// - public LDAPFilter AddContainers(params string[] conditions) + public LdapFilter AddContainers(params string[] conditions) { _filterParts.Add(BuildString("(objectClass=container)", conditions)); @@ -147,7 +147,7 @@ public LDAPFilter AddContainers(params string[] conditions) /// /// /// - public LDAPFilter AddConfiguration(params string[] conditions) + public LdapFilter AddConfiguration(params string[] conditions) { _filterParts.Add(BuildString("(objectClass=configuration)", conditions)); @@ -161,7 +161,7 @@ public LDAPFilter AddConfiguration(params string[] conditions) /// /// /// - public LDAPFilter AddComputers(params string[] conditions) + public LdapFilter AddComputers(params string[] conditions) { _filterParts.Add(BuildString("(samaccounttype=805306369)", conditions)); return this; @@ -172,7 +172,7 @@ public LDAPFilter AddComputers(params string[] conditions) /// /// /// - public LDAPFilter AddCertificateTemplates(params string[] conditions) + public LdapFilter AddCertificateTemplates(params string[] conditions) { _filterParts.Add(BuildString("(objectclass=pKICertificateTemplate)", conditions)); return this; @@ -183,7 +183,7 @@ public LDAPFilter AddCertificateTemplates(params string[] conditions) /// /// /// - public LDAPFilter AddCertificateAuthorities(params string[] conditions) + public LdapFilter AddCertificateAuthorities(params string[] conditions) { _filterParts.Add(BuildString("(|(objectClass=certificationAuthority)(objectClass=pkiEnrollmentService))", conditions)); @@ -195,7 +195,7 @@ public LDAPFilter AddCertificateAuthorities(params string[] conditions) /// /// /// - public LDAPFilter AddEnterpriseCertificationAuthorities(params string[] conditions) + public LdapFilter AddEnterpriseCertificationAuthorities(params string[] conditions) { _filterParts.Add(BuildString("(objectCategory=pKIEnrollmentService)", conditions)); return this; @@ -206,7 +206,7 @@ public LDAPFilter AddEnterpriseCertificationAuthorities(params string[] conditio /// /// /// - public LDAPFilter AddIssuancePolicies(params string[] conditions) + public LdapFilter AddIssuancePolicies(params string[] conditions) { _filterParts.Add(BuildString("(objectClass=msPKI-Enterprise-Oid)", conditions)); return this; @@ -217,7 +217,7 @@ public LDAPFilter AddIssuancePolicies(params string[] conditions) /// /// /// - public LDAPFilter AddSchemaID(params string[] conditions) + public LdapFilter AddSchemaID(params string[] conditions) { _filterParts.Add(BuildString("(schemaidguid=*)", conditions)); return this; @@ -228,7 +228,7 @@ public LDAPFilter AddSchemaID(params string[] conditions) /// /// /// - public LDAPFilter AddComputersNoMSAs(params string[] conditions) + public LdapFilter AddComputersNoMSAs(params string[] conditions) { _filterParts.Add(BuildString("(&(samaccounttype=805306369)(!(objectclass=msDS-GroupManagedServiceAccount))(!(objectclass=msDS-ManagedServiceAccount)))", conditions)); return this; @@ -240,7 +240,7 @@ public LDAPFilter AddComputersNoMSAs(params string[] conditions) /// LDAP Filter to add to query /// If true, filter will be AND otherwise OR /// - public LDAPFilter AddFilter(string filter, bool enforce) + public LdapFilter AddFilter(string filter, bool enforce) { if (enforce) _mandatory.Add(FixFilter(filter)); @@ -277,7 +277,7 @@ public string GetFilter() public IEnumerable GetFilterList() { - return _filterParts; + return _filterParts.Distinct(); } } } \ No newline at end of file diff --git a/src/CommonLib/LdapUtils.cs b/src/CommonLib/LdapUtils.cs index aec35cdc..8877120a 100644 --- a/src/CommonLib/LdapUtils.cs +++ b/src/CommonLib/LdapUtils.cs @@ -15,6 +15,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; +using SharpHoundCommonLib.DirectoryObjects; using SharpHoundCommonLib.Enums; using SharpHoundCommonLib.LDAPQueries; using SharpHoundCommonLib.OutputTypes; @@ -37,7 +38,8 @@ public class LdapUtils : ILdapUtils { private static readonly ConcurrentDictionary SeenWellKnownPrincipals = new(); - private readonly ConcurrentDictionary _hostResolutionMap = new(StringComparer.OrdinalIgnoreCase); + private readonly ConcurrentDictionary + _hostResolutionMap = new(StringComparer.OrdinalIgnoreCase); private readonly ConcurrentDictionary _distinguishedNameCache = new(StringComparer.OrdinalIgnoreCase); @@ -46,10 +48,10 @@ private static readonly ConcurrentDictionary private readonly PortScanner _portScanner; private readonly NativeMethods _nativeMethods; private readonly string _nullCacheKey = Guid.NewGuid().ToString(); - private readonly Regex _sidRegex = new(@"^(S-\d+-\d+-\d+-\d+-\d+-\d+)-\d+$"); + private static readonly Regex SIDRegex = new(@"^(S-\d+-\d+-\d+-\d+-\d+-\d+)(-\d+)?$"); private readonly string[] _translateNames = { "Administrator", "admin" }; - private LDAPConfig _ldapConfig = new(); + private LdapConfig _ldapConfig = new(); private ConnectionPoolManager _connectionPool; @@ -84,7 +86,7 @@ public LdapUtils(NativeMethods nativeMethods = null, PortScanner scanner = null, _nativeMethods = nativeMethods ?? new NativeMethods(); _portScanner = scanner ?? new PortScanner(); _log = log ?? Logging.LogProvider.CreateLogger("LDAPUtils"); - _connectionPool = new ConnectionPoolManager(_ldapConfig, scanner:_portScanner); + _connectionPool = new ConnectionPoolManager(_ldapConfig, scanner: _portScanner); } public async IAsyncEnumerable> RangedRetrieval(string distinguishedName, @@ -128,14 +130,12 @@ public async IAsyncEnumerable> RangedRetrieval(string distinguish SearchResponse response = null; try { response = (SearchResponse)connectionWrapper.Connection.SendRequest(searchRequest); - } - catch (LdapException le) when (le.ErrorCode == (int)ResultCode.Busy && busyRetryCount < MaxRetries) { + } catch (LdapException le) when (le.ErrorCode == (int)ResultCode.Busy && busyRetryCount < MaxRetries) { busyRetryCount++; var backoffDelay = GetNextBackoff(busyRetryCount); await Task.Delay(backoffDelay, cancellationToken); - } - catch (LdapException le) when (le.ErrorCode == (int)LdapErrorCodes.ServerDown && - queryRetryCount < MaxRetries) { + } catch (LdapException le) when (le.ErrorCode == (int)LdapErrorCodes.ServerDown && + queryRetryCount < MaxRetries) { queryRetryCount++; _connectionPool.ReleaseConnection(connectionWrapper, true); for (var retryCount = 0; retryCount < MaxRetries; retryCount++) { @@ -159,15 +159,15 @@ await _connectionPool.GetLdapConnection(domain, distinguishedName); tempResult = LdapResult.Fail( - "RangedRetrieval - Failed to get a new connection after ServerDown.", queryParameters, le.ErrorCode); + "RangedRetrieval - Failed to get a new connection after ServerDown.", + queryParameters, le.ErrorCode); } } - } - catch (LdapException le) { + } catch (LdapException le) { tempResult = LdapResult.Fail( - $"Caught unrecoverable ldap exception: {le.Message} (ServerMessage: {le.ServerErrorMessage}) (ErrorCode: {le.ErrorCode})", queryParameters, le.ErrorCode); - } - catch (Exception e) { + $"Caught unrecoverable ldap exception: {le.Message} (ServerMessage: {le.ServerErrorMessage}) (ErrorCode: {le.ErrorCode})", + queryParameters, le.ErrorCode); + } catch (Exception e) { tempResult = LdapResult.Fail($"Caught unrecoverable exception: {e.Message}", queryParameters); } @@ -177,10 +177,10 @@ await _connectionPool.GetLdapConnection(domain, if (tempResult != null) { if (tempResult.ErrorCode == (int)LdapErrorCodes.ServerDown) { _connectionPool.ReleaseConnection(connectionWrapper, true); - } - else { + } else { _connectionPool.ReleaseConnection(connectionWrapper); } + yield return tempResult; yield break; } @@ -207,18 +207,17 @@ await _connectionPool.GetLdapConnection(domain, currentRange = $"{attributeName};range={index}-{index + step}"; searchRequest.Attributes.Clear(); searchRequest.Attributes.Add(currentRange); - } - else { + } else { //I dont know what can cause a RR to have multiple entries, but its nothing good. Break out _connectionPool.ReleaseConnection(connectionWrapper); yield break; } } - + _connectionPool.ReleaseConnection(connectionWrapper); } - public async IAsyncEnumerable> Query(LdapQueryParameters queryParameters, + public async IAsyncEnumerable> Query(LdapQueryParameters queryParameters, [EnumeratorCancellation] CancellationToken cancellationToken = new()) { var setupResult = await SetupLdapQuery(queryParameters); @@ -238,7 +237,7 @@ public async IAsyncEnumerable> Query(LdapQueryPar var queryRetryCount = 0; var busyRetryCount = 0; - LdapResult tempResult = null; + LdapResult tempResult = null; var querySuccess = false; SearchResponse response = null; while (!cancellationToken.IsCancellationRequested) { @@ -248,19 +247,16 @@ public async IAsyncEnumerable> Query(LdapQueryPar if (response != null) { querySuccess = true; - } - else if (queryRetryCount == MaxRetries) { + } else if (queryRetryCount == MaxRetries) { tempResult = - LdapResult.Fail($"Failed to get a response after {MaxRetries} attempts", + LdapResult.Fail($"Failed to get a response after {MaxRetries} attempts", queryParameters); - } - else { + } else { queryRetryCount++; continue; } - } - catch (LdapException le) when (le.ErrorCode == (int)LdapErrorCodes.ServerDown && - queryRetryCount < MaxRetries) { + } catch (LdapException le) when (le.ErrorCode == (int)LdapErrorCodes.ServerDown && + queryRetryCount < MaxRetries) { /* * A ServerDown exception indicates that our connection is no longer valid for one of many reasons. * We'll want to release our connection back to the pool, but dispose it. We need a new connection, @@ -278,7 +274,8 @@ public async IAsyncEnumerable> Query(LdapQueryPar await _connectionPool.GetLdapConnection(queryParameters.DomainName, queryParameters.GlobalCatalog); if (success) { - _log.LogDebug("Query - Recovered from ServerDown successfully, connection made to {NewServer}", + _log.LogDebug( + "Query - Recovered from ServerDown successfully, connection made to {NewServer}", newConnectionWrapper.GetServer()); connectionWrapper = newConnectionWrapper; break; @@ -289,12 +286,11 @@ await _connectionPool.GetLdapConnection(queryParameters.DomainName, _log.LogError("Query - Failed to get a new connection after ServerDown.\n{Info}", queryParameters.GetQueryInfo()); tempResult = - LdapResult.Fail( + LdapResult.Fail( "Query - Failed to get a new connection after ServerDown.", queryParameters); } } - } - catch (LdapException le) when (le.ErrorCode == (int)ResultCode.Busy && busyRetryCount < MaxRetries) { + } catch (LdapException le) when (le.ErrorCode == (int)ResultCode.Busy && busyRetryCount < MaxRetries) { /* * If we get a busy error, we want to do an exponential backoff, but maintain the current connection * The expectation is that given enough time, the server should stop being busy and service our query appropriately @@ -302,15 +298,13 @@ await _connectionPool.GetLdapConnection(queryParameters.DomainName, busyRetryCount++; var backoffDelay = GetNextBackoff(busyRetryCount); await Task.Delay(backoffDelay, cancellationToken); - } - catch (LdapException le) { - tempResult = LdapResult.Fail( + } catch (LdapException le) { + tempResult = LdapResult.Fail( $"Query - Caught unrecoverable ldap exception: {le.Message} (ServerMessage: {le.ServerErrorMessage}) (ErrorCode: {le.ErrorCode})", queryParameters); - } - catch (Exception e) { + } catch (Exception e) { tempResult = - LdapResult.Fail($"Query - Caught unrecoverable exception: {e.Message}", + LdapResult.Fail($"Query - Caught unrecoverable exception: {e.Message}", queryParameters); } @@ -318,10 +312,10 @@ await _connectionPool.GetLdapConnection(queryParameters.DomainName, if (tempResult != null) { if (tempResult.ErrorCode == (int)LdapErrorCodes.ServerDown) { _connectionPool.ReleaseConnection(connectionWrapper, true); - } - else { + } else { _connectionPool.ReleaseConnection(connectionWrapper); } + yield return tempResult; yield break; } @@ -331,14 +325,14 @@ await _connectionPool.GetLdapConnection(queryParameters.DomainName, break; } } - + _connectionPool.ReleaseConnection(connectionWrapper); foreach (SearchResultEntry entry in response.Entries) { - yield return LdapResult.Ok(new SearchResultEntryWrapper(entry, this)); + yield return LdapResult.Ok(new SearchResultEntryWrapper(entry)); } } - public async IAsyncEnumerable> PagedQuery(LdapQueryParameters queryParameters, + public async IAsyncEnumerable> PagedQuery(LdapQueryParameters queryParameters, [EnumeratorCancellation] CancellationToken cancellationToken = new()) { var setupResult = await SetupLdapQuery(queryParameters); @@ -362,7 +356,7 @@ public async IAsyncEnumerable> PagedQuery(LdapQue PageResultResponseControl pageResponse = null; var busyRetryCount = 0; var queryRetryCount = 0; - LdapResult tempResult = null; + LdapResult tempResult = null; while (!cancellationToken.IsCancellationRequested) { SearchResponse response = null; @@ -373,17 +367,14 @@ public async IAsyncEnumerable> PagedQuery(LdapQue pageResponse = (PageResultResponseControl)response.Controls .Where(x => x is PageResultResponseControl).DefaultIfEmpty(null).FirstOrDefault(); queryRetryCount = 0; - } - else if (queryRetryCount == MaxRetries) { - tempResult = LdapResult.Fail( + } else if (queryRetryCount == MaxRetries) { + tempResult = LdapResult.Fail( $"PagedQuery - Failed to get a response after {MaxRetries} attempts", queryParameters); - } - else { + } else { queryRetryCount++; } - } - catch (LdapException le) when (le.ErrorCode == (int)LdapErrorCodes.ServerDown) { + } catch (LdapException le) when (le.ErrorCode == (int)LdapErrorCodes.ServerDown) { /* * If we dont have a servername, we're not going to be able to re-establish a connection here. Page cookies are only valid for the server they were generated on. Bail out. */ @@ -403,8 +394,9 @@ public async IAsyncEnumerable> PagedQuery(LdapQue for (var retryCount = 0; retryCount < MaxRetries; retryCount++) { var backoffDelay = GetNextBackoff(retryCount); await Task.Delay(backoffDelay, cancellationToken); - var (success, ldapConnectionWrapperNew, message) = await _connectionPool.GetLdapConnectionForServer( - queryParameters.DomainName, serverName, queryParameters.GlobalCatalog); + var (success, ldapConnectionWrapperNew, message) = + await _connectionPool.GetLdapConnectionForServer( + queryParameters.DomainName, serverName, queryParameters.GlobalCatalog); if (success) { _log.LogDebug("PagedQuery - Recovered from ServerDown successfully"); @@ -416,12 +408,11 @@ public async IAsyncEnumerable> PagedQuery(LdapQue _log.LogError("PagedQuery - Failed to get a new connection after ServerDown.\n{Info}", queryParameters.GetQueryInfo()); tempResult = - LdapResult.Fail("Failed to get a new connection after serverdown", + LdapResult.Fail("Failed to get a new connection after serverdown", queryParameters, le.ErrorCode); } } - } - catch (LdapException le) when (le.ErrorCode == (int)ResultCode.Busy && busyRetryCount < MaxRetries) { + } catch (LdapException le) when (le.ErrorCode == (int)ResultCode.Busy && busyRetryCount < MaxRetries) { /* * If we get a busy error, we want to do an exponential backoff, but maintain the current connection * The expectation is that given enough time, the server should stop being busy and service our query appropriately @@ -429,25 +420,23 @@ public async IAsyncEnumerable> PagedQuery(LdapQue busyRetryCount++; var backoffDelay = GetNextBackoff(busyRetryCount); await Task.Delay(backoffDelay, cancellationToken); - } - catch (LdapException le) { - tempResult = LdapResult.Fail( + } catch (LdapException le) { + tempResult = LdapResult.Fail( $"PagedQuery - Caught unrecoverable ldap exception: {le.Message} (ServerMessage: {le.ServerErrorMessage}) (ErrorCode: {le.ErrorCode})", queryParameters, le.ErrorCode); - } - catch (Exception e) { + } catch (Exception e) { tempResult = - LdapResult.Fail($"PagedQuery - Caught unrecoverable exception: {e.Message}", + LdapResult.Fail($"PagedQuery - Caught unrecoverable exception: {e.Message}", queryParameters); } if (tempResult != null) { if (tempResult.ErrorCode == (int)LdapErrorCodes.ServerDown) { _connectionPool.ReleaseConnection(connectionWrapper, true); - } - else { + } else { _connectionPool.ReleaseConnection(connectionWrapper); } + yield return tempResult; yield break; } @@ -468,7 +457,7 @@ public async IAsyncEnumerable> PagedQuery(LdapQue yield break; } - yield return LdapResult.Ok(new SearchResultEntryWrapper(entry, this)); + yield return LdapResult.Ok(new SearchResultEntryWrapper(entry)); } if (pageResponse.Cookie.Length == 0 || response.Entries.Count == 0 || @@ -476,12 +465,13 @@ public async IAsyncEnumerable> PagedQuery(LdapQue _connectionPool.ReleaseConnection(connectionWrapper); yield break; } - + pageControl.Cookie = pageResponse.Cookie; } } - public async Task<(bool Success, TypedPrincipal Principal)> ResolveIDAndType(SecurityIdentifier securityIdentifier, + public async Task<(bool Success, TypedPrincipal Principal)> ResolveIDAndType( + SecurityIdentifier securityIdentifier, string objectDomain) { return await ResolveIDAndType(securityIdentifier.Value, objectDomain); } @@ -520,22 +510,22 @@ public async IAsyncEnumerable> PagedQuery(LdapQue DomainName = tempDomain, LDAPFilter = CommonFilters.SpecificSID(sid), Attributes = CommonProperties.TypeResolutionProps - }).DefaultIfEmpty(LdapResult.Fail()).FirstOrDefaultAsync(); + }).DefaultIfEmpty(LdapResult.Fail()).FirstOrDefaultAsync(); if (result.IsSuccess) { - type = result.Value.GetLabel(); - Cache.AddType(sid, type); - return (true, type); + if (result.Value.GetLabel(out type)) { + Cache.AddType(sid, type); + return (true, type); + } } try { - var entry = new DirectoryEntry($"LDAP://"); + var entry = CreateDirectoryEntry($"LDAP://"); if (entry.GetLabel(out type)) { Cache.AddType(sid, type); return (true, type); } - } - catch { + } catch { //pass } @@ -543,14 +533,13 @@ public async IAsyncEnumerable> PagedQuery(LdapQue try { var principal = Principal.FindByIdentity(ctx, IdentityType.Sid, sid); if (principal != null) { - var entry = (DirectoryEntry)principal.GetUnderlyingObject(); + var entry = ((DirectoryEntry)principal.GetUnderlyingObject()).ToDirectoryObject(); if (entry.GetLabel(out type)) { Cache.AddType(sid, type); return (true, type); } } - } - catch { + } catch { //pass } } @@ -567,22 +556,20 @@ public async IAsyncEnumerable> PagedQuery(LdapQue DomainName = domain, LDAPFilter = CommonFilters.SpecificGUID(guid), Attributes = CommonProperties.TypeResolutionProps - }).DefaultIfEmpty(LdapResult.Fail()).FirstOrDefaultAsync(); + }).DefaultIfEmpty(LdapResult.Fail()).FirstOrDefaultAsync(); - if (result.IsSuccess) { - type = result.Value.GetLabel(); + if (result.IsSuccess && result.Value.GetLabel(out type)) { Cache.AddType(guid, type); return (true, type); } try { - var entry = new DirectoryEntry($"LDAP://"); + var entry = CreateDirectoryEntry($"LDAP://"); if (entry.GetLabel(out type)) { Cache.AddType(guid, type); return (true, type); } - } - catch { + } catch { //pass } @@ -590,14 +577,13 @@ public async IAsyncEnumerable> PagedQuery(LdapQue try { var principal = Principal.FindByIdentity(ctx, IdentityType.Guid, guid); if (principal != null) { - var entry = (DirectoryEntry)principal.GetUnderlyingObject(); + var entry = ((DirectoryEntry)principal.GetUnderlyingObject()).ToDirectoryObject(); if (entry.GetLabel(out type)) { Cache.AddType(guid, type); return (true, type); } } - } - catch { + } catch { //pass } } @@ -611,7 +597,8 @@ public async IAsyncEnumerable> PagedQuery(LdapQue return (false, null); } - var (newIdentifier, newDomain) = await GetWellKnownPrincipalObjectIdentifier(securityIdentifier, objectDomain); + var (newIdentifier, newDomain) = + await GetWellKnownPrincipalObjectIdentifier(securityIdentifier, objectDomain); wellKnownPrincipal.ObjectIdentifier = newIdentifier; SeenWellKnownPrincipals.TryAdd(wellKnownPrincipal.ObjectIdentifier, new ResolvedWellKnownPrincipal { @@ -640,11 +627,12 @@ public async IAsyncEnumerable> PagedQuery(LdapQue return ($"{forest}-{securityIdentifier}".ToUpper(), forest); } - _log.LogWarning("Failed to get a forest name for domain {Domain}, unable to resolve enterprise DC sid", domain); + _log.LogWarning("Failed to get a forest name for domain {Domain}, unable to resolve enterprise DC sid", + domain); return ($"UNKNOWN-{securityIdentifier}", "UNKNOWN"); } - private async Task<(bool Success, string ForestName)> GetForest(string domain) { + public virtual async Task<(bool Success, string ForestName)> GetForest(string domain) { if (DomainToForestCache.TryGetValue(domain, out var cachedForest)) { return (true, cachedForest); } @@ -654,8 +642,7 @@ public async IAsyncEnumerable> PagedQuery(LdapQue var forestName = domainObject.Forest.Name.ToUpper(); DomainToForestCache.TryAdd(domain, forestName); return (true, forestName); - } - catch { + } catch { //pass } } @@ -674,15 +661,13 @@ public async IAsyncEnumerable> PagedQuery(LdapQue Attributes = new[] { LDAPProperties.RootDomainNamingContext }, SearchScope = SearchScope.Base, DomainName = domain, - LDAPFilter = new LDAPFilter().AddAllObjects().GetFilter(), + LDAPFilter = new LdapFilter().AddAllObjects().GetFilter(), }; var result = await Query(queryParameters).FirstAsync(); - if (result.IsSuccess) { - var rdn = result.Value.GetProperty(LDAPProperties.RootDomainNamingContext); - if (!string.IsNullOrEmpty(rdn)) { - return (true, Helpers.DistinguishedNameToDomain(rdn).ToUpper()); - } + if (result.IsSuccess && + result.Value.TryGetProperty(LDAPProperties.RootDomainNamingContext, out var rootNamingContext)) { + return (true, Helpers.DistinguishedNameToDomain(rootNamingContext).ToUpper()); } return (false, null); @@ -699,17 +684,14 @@ private bool CreateSearchRequest(LdapQueryParameters queryParameters, string basePath; if (!string.IsNullOrWhiteSpace(queryParameters.SearchBase)) { basePath = queryParameters.SearchBase; - } - else if (!connectionWrapper.GetSearchBase(queryParameters.NamingContext, out basePath)) { + } else if (!connectionWrapper.GetSearchBase(queryParameters.NamingContext, out basePath)) { string tempPath; if (CallDsGetDcName(queryParameters.DomainName, out var info) && info != null) { tempPath = Helpers.DomainNameToDistinguishedName(info.Value.DomainName); connectionWrapper.SaveContext(queryParameters.NamingContext, basePath); - } - else if (GetDomain(queryParameters.DomainName, out var domainObject)) { + } else if (GetDomain(queryParameters.DomainName, out var domainObject)) { tempPath = Helpers.DomainNameToDistinguishedName(domainObject.Name); - } - else { + } else { searchRequest = null; return false; } @@ -805,9 +787,8 @@ private SearchRequest CreateSearchRequest(string distinguishedName, string ldapF string domainSid; try { domainSid = new SecurityIdentifier(sid).AccountDomainSid?.Value.ToUpper(); - } - catch { - var match = _sidRegex.Match(sid); + } catch { + var match = SIDRegex.Match(sid); domainSid = match.Success ? match.Groups[1].Value : null; } @@ -820,15 +801,12 @@ private SearchRequest CreateSearchRequest(string distinguishedName, string ldapF } try { - var entry = new DirectoryEntry($"LDAP://"); - entry.RefreshCache(new[] { LDAPProperties.DistinguishedName }); - var dn = entry.GetProperty(LDAPProperties.DistinguishedName); - if (!string.IsNullOrWhiteSpace(dn)) { + var entry = CreateDirectoryEntry($"LDAP://"); + if (entry.TryGetDistinguishedName(out var dn)) { Cache.AddDomainSidMapping(domainSid, Helpers.DistinguishedNameToDomain(dn)); return (true, Helpers.DistinguishedNameToDomain(dn)); } - } - catch { + } catch { //pass } @@ -847,8 +825,7 @@ private SearchRequest CreateSearchRequest(string distinguishedName, string ldapF return (true, Helpers.DistinguishedNameToDomain(dn)); } } - } - catch { + } catch { //pass } } @@ -865,34 +842,34 @@ private SearchRequest CreateSearchRequest(string distinguishedName, string ldapF DomainName = domain.Name, Attributes = new[] { LDAPProperties.DistinguishedName }, GlobalCatalog = true, - LDAPFilter = new LDAPFilter().AddDomains(CommonFilters.SpecificSID(domainSid)).GetFilter() - }).DefaultIfEmpty(LdapResult.Fail()).FirstOrDefaultAsync(); + LDAPFilter = new LdapFilter().AddDomains(CommonFilters.SpecificSID(domainSid)).GetFilter() + }).DefaultIfEmpty(LdapResult.Fail()).FirstOrDefaultAsync(); - if (result.IsSuccess) { - return (true, Helpers.DistinguishedNameToDomain(result.Value.DistinguishedName)); + if (result.IsSuccess && result.Value.TryGetDistinguishedName(out var distinguishedName)) { + return (true, Helpers.DistinguishedNameToDomain(distinguishedName)); } result = await Query(new LdapQueryParameters { DomainName = domain.Name, Attributes = new[] { LDAPProperties.DistinguishedName }, GlobalCatalog = true, - LDAPFilter = new LDAPFilter().AddFilter("(objectclass=trusteddomain)", true) + LDAPFilter = new LdapFilter().AddFilter("(objectclass=trusteddomain)", true) .AddFilter($"(securityidentifier={Helpers.ConvertSidToHexSid(domainSid)})", true).GetFilter() - }).DefaultIfEmpty(LdapResult.Fail()).FirstOrDefaultAsync(); + }).DefaultIfEmpty(LdapResult.Fail()).FirstOrDefaultAsync(); - if (result.IsSuccess) { - return (true, Helpers.DistinguishedNameToDomain(result.Value.DistinguishedName)); + if (result.IsSuccess && result.Value.TryGetDistinguishedName(out distinguishedName)) { + return (true, Helpers.DistinguishedNameToDomain(distinguishedName)); } result = await Query(new LdapQueryParameters { DomainName = domain.Name, Attributes = new[] { LDAPProperties.DistinguishedName }, - LDAPFilter = new LDAPFilter().AddFilter("(objectclass=domaindns)", true) + LDAPFilter = new LdapFilter().AddFilter("(objectclass=domaindns)", true) .AddFilter(CommonFilters.SpecificSID(domainSid), true).GetFilter() - }).DefaultIfEmpty(LdapResult.Fail()).FirstOrDefaultAsync(); + }).DefaultIfEmpty(LdapResult.Fail()).FirstOrDefaultAsync(); - if (result.IsSuccess) { - return (true, Helpers.DistinguishedNameToDomain(result.Value.DistinguishedName)); + if (result.IsSuccess && result.Value.TryGetDistinguishedName(out distinguishedName)) { + return (true, Helpers.DistinguishedNameToDomain(distinguishedName)); } return (false, string.Empty); @@ -902,29 +879,25 @@ private SearchRequest CreateSearchRequest(string distinguishedName, string ldapF if (Cache.GetDomainSidMapping(domainName, out var domainSid)) return (true, domainSid); try { - var entry = new DirectoryEntry($"LDAP://{domainName}"); + var entry = CreateDirectoryEntry($"LDAP://{domainName}"); //Force load objectsid into the object cache - entry.RefreshCache(new[] { "objectSid" }); - var sid = entry.GetSid(); - if (sid != null) { + if (entry.TryGetSecurityIdentifier(out var sid)) { Cache.AddDomainSidMapping(domainName, sid); domainSid = sid; return (true, domainSid); } - } - catch { + } catch { //we expect this to fail sometimes } if (GetDomain(domainName, out var domainObject)) try { - domainSid = domainObject.GetDirectoryEntry().GetSid(); - if (domainSid != null) { + var entry = domainObject.GetDirectoryEntry().ToDirectoryObject(); + if (entry.TryGetSecurityIdentifier(out domainSid)) { Cache.AddDomainSidMapping(domainName, domainSid); return (true, domainSid); } - } - catch { + } catch { //we expect this to fail sometimes (not sure why, but better safe than sorry) } @@ -935,24 +908,20 @@ private SearchRequest CreateSearchRequest(string distinguishedName, string ldapF domainSid = sid.AccountDomainSid.ToString(); Cache.AddDomainSidMapping(domainName, domainSid); return (true, domainSid); - } - catch { + } catch { //We expect this to fail if the username doesn't exist in the domain } var result = await Query(new LdapQueryParameters() { DomainName = domainName, Attributes = new[] { LDAPProperties.ObjectSID }, - LDAPFilter = new LDAPFilter().AddFilter(CommonFilters.DomainControllers, true).GetFilter() - }).DefaultIfEmpty(LdapResult.Fail()).FirstOrDefaultAsync(); + LDAPFilter = new LdapFilter().AddFilter(CommonFilters.DomainControllers, true).GetFilter() + }).DefaultIfEmpty(LdapResult.Fail()).FirstOrDefaultAsync(); - if (result.IsSuccess) { - var sid = result.Value.GetSid(); - if (!string.IsNullOrEmpty(sid)) { - domainSid = new SecurityIdentifier(sid).AccountDomainSid.Value; - Cache.AddDomainSidMapping(domainName, domainSid); - return (true, domainSid); - } + if (result.IsSuccess && result.Value.TryGetSecurityIdentifier(out var securityIdentifier)) { + domainSid = new SecurityIdentifier(securityIdentifier).AccountDomainSid.Value; + Cache.AddDomainSidMapping(domainName, domainSid); + return (true, domainSid); } return (false, string.Empty); @@ -986,14 +955,13 @@ public bool GetDomain(string domainName, out Domain domain) { if (domain == null) return false; DomainCache.TryAdd(cacheKey, domain); return true; - } - catch (Exception e) { + } catch (Exception e) { _log.LogDebug(e, "GetDomain call failed for domain name {Name}", domainName); return false; } } - public static bool GetDomain(string domainName, LDAPConfig ldapConfig, out Domain domain) { + public static bool GetDomain(string domainName, LdapConfig ldapConfig, out Domain domain) { if (DomainCache.TryGetValue(domainName, out domain)) return true; try { @@ -1013,9 +981,9 @@ public static bool GetDomain(string domainName, LDAPConfig ldapConfig, out Domai if (domain == null) return false; DomainCache.TryAdd(domainName, domain); return true; - } - catch (Exception e) { - Logging.Logger.LogDebug("Static GetDomain call failed for domain {DomainName}: {Error}", domainName, e.Message); + } catch (Exception e) { + Logging.Logger.LogDebug("Static GetDomain call failed for domain {DomainName}: {Error}", domainName, + e.Message); return false; } } @@ -1040,8 +1008,7 @@ public bool GetDomain(out Domain domain) { domain = Domain.GetDomain(context); DomainCache.TryAdd(cacheKey, domain); return true; - } - catch (Exception e) { + } catch (Exception e) { _log.LogDebug(e, "GetDomain call failed for blank domain"); return false; } @@ -1062,16 +1029,12 @@ public bool GetDomain(out Domain domain) { DomainName = domain, Attributes = CommonProperties.TypeResolutionProps, LDAPFilter = $"(samaccountname={name})" - }).DefaultIfEmpty(LdapResult.Fail()).FirstOrDefaultAsync(); - - if (result.IsSuccess) { - type = result.Value.GetLabel(); - id = result.Value.GetObjectIdentifier(); + }).DefaultIfEmpty(LdapResult.Fail()).FirstOrDefaultAsync(); - if (!string.IsNullOrWhiteSpace(id)) { - Cache.AddPrefixedValue(name, domain, id); - Cache.AddType(id, type); - } + if (result.IsSuccess && result.Value.GetObjectIdentifier(out id)) { + result.Value.GetLabel(out type); + Cache.AddPrefixedValue(name, domain, id); + Cache.AddType(id, type); var (tempID, _) = await GetWellKnownPrincipalObjectIdentifier(id, domain); return (true, new TypedPrincipal(tempID, type)); @@ -1089,11 +1052,10 @@ public bool GetDomain(out Domain domain) { if (_hostResolutionMap.TryGetValue(strippedHost, out var sid)) return (true, sid); - //Immediately start with NetWekstaGetInfo as its our most reliable indicator if successful - var workstationInfo = await GetWorkstationInfo(strippedHost); - if (workstationInfo.HasValue) { - var tempName = workstationInfo.Value.ComputerName; - var tempDomain = workstationInfo.Value.LanGroup; + //Immediately start with NetWkstaGetInfo as it's our most reliable indicator if successful + if (await GetWorkstationInfo(strippedHost) is (true, var workstationInfo)) { + var tempName = workstationInfo.ComputerName; + var tempDomain = workstationInfo.LanGroup; if (string.IsNullOrWhiteSpace(tempDomain)) { tempDomain = domain; @@ -1137,8 +1099,7 @@ public bool GetDomain(out Domain domain) { _hostResolutionMap.TryAdd(strippedHost, result.Principal.ObjectIdentifier); return (true, result.Principal.ObjectIdentifier); } - } - else { + } else { //Format: WIN10 (probably a netbios name) var result = await ResolveAccountName($"{strippedHost}$", domain); if (result.Success) { @@ -1164,8 +1125,7 @@ public bool GetDomain(out Domain domain) { _hostResolutionMap.TryAdd(strippedHost, result.Principal.ObjectIdentifier); return (true, result.Principal.ObjectIdentifier); } - } - catch { + } catch { //pass } @@ -1177,14 +1137,14 @@ public bool GetDomain(out Domain domain) { /// /// /// - private async Task GetWorkstationInfo(string hostname) { + private async Task<(bool Success, NetAPIStructs.WorkstationInfo100 Info)> GetWorkstationInfo(string hostname) { if (!await _portScanner.CheckPort(hostname)) - return null; + return (false, default); var result = _nativeMethods.CallNetWkstaGetInfo(hostname); - if (result.IsSuccess) return result.Value; + if (result.IsSuccess) return (true, result.Value); - return null; + return (false, default); } public async Task<(bool Success, string[] Sids)> GetGlobalCatalogMatches(string name, string domain) { @@ -1198,15 +1158,15 @@ public bool GetDomain(out Domain domain) { DomainName = domain, Attributes = new[] { LDAPProperties.ObjectSID }, GlobalCatalog = true, - LDAPFilter = new LDAPFilter().AddUsers($"(samaccountname={name})").GetFilter() + LDAPFilter = new LdapFilter().AddUsers($"(samaccountname={name})").GetFilter() })) { - if (result.IsSuccess) { - var sid = result.Value.GetSid(); - if (!string.IsNullOrWhiteSpace(sid)) { - sids.Add(sid); + if (result.IsSuccess && result.Value.TryGetSecurityIdentifier(out var sid)) { + if (await GetWellKnownPrincipal(sid, domain) is (true, var principal)) { + sids.Add(principal.ObjectIdentifier); + } else { + sids.Add(sid); } - } - else { + } else { return (false, Array.Empty()); } } @@ -1217,7 +1177,8 @@ public bool GetDomain(out Domain domain) { public async Task<(bool Success, TypedPrincipal Principal)> ResolveCertTemplateByProperty(string propertyValue, string propertyName, string domainName) { - var filter = new LDAPFilter().AddCertificateTemplates().AddFilter($"({propertyName}={propertyValue})", true); + var filter = new LdapFilter().AddCertificateTemplates() + .AddFilter($"({propertyName}={propertyValue})", true); var result = await Query(new LdapQueryParameters { DomainName = domainName, Attributes = CommonProperties.TypeResolutionProps, @@ -1225,7 +1186,7 @@ public bool GetDomain(out Domain domain) { NamingContext = NamingContext.Configuration, RelativeSearchBase = DirectoryPaths.CertTemplateLocation, LDAPFilter = filter.GetFilter(), - }).DefaultIfEmpty(LdapResult.Fail()).FirstOrDefaultAsync(); + }).DefaultIfEmpty(LdapResult.Fail()).FirstOrDefaultAsync(); if (!result.IsSuccess) { _log.LogWarning( @@ -1234,8 +1195,11 @@ public bool GetDomain(out Domain domain) { return (false, null); } - var entry = result.Value; - return (true, new TypedPrincipal(entry.GetGuid(), Label.CertTemplate)); + if (result.Value.TryGetGuid(out var guid)) { + return (true, new TypedPrincipal(guid, Label.CertTemplate)); + } + + return (false, default); } /// @@ -1272,8 +1236,7 @@ private static bool RequestNETBIOSNameFromComputer(string server, string domain, } remoteEndpoint = new IPEndPoint(address, 137); - } - catch { + } catch { //Failed to resolve an IP, so return null netbios = null; return false; @@ -1292,13 +1255,11 @@ private static bool RequestNETBIOSNameFromComputer(string server, string domain, netbios = null; return false; - } - catch (SocketException) { + } catch (SocketException) { netbios = null; return false; } - } - finally { + } finally { //Make sure we close the socket if its open requestSocket.Close(); } @@ -1312,7 +1273,8 @@ public ActiveDirectorySecurityDescriptor MakeSecurityDescriptor() { return new ActiveDirectorySecurityDescriptor(new ActiveDirectorySecurity()); } - public async Task<(bool Success, TypedPrincipal Principal)> ConvertLocalWellKnownPrincipal(SecurityIdentifier sid, + public async Task<(bool Success, TypedPrincipal Principal)> ConvertLocalWellKnownPrincipal( + SecurityIdentifier sid, string computerDomainSid, string computerDomain) { if (!WellKnownPrincipal.GetWellKnownPrincipal(sid.Value, out var common)) return (false, null); //The everyone and auth users principals are special and will be converted to the domain equivalent @@ -1335,14 +1297,14 @@ public ActiveDirectorySecurityDescriptor MakeSecurityDescriptor() { public async Task IsDomainController(string computerObjectId, string domainName) { var resDomain = await GetDomainNameFromSid(domainName) is (false, var tempDomain) ? tempDomain : domainName; - var filter = new LDAPFilter().AddFilter(CommonFilters.SpecificSID(computerObjectId), true) + var filter = new LdapFilter().AddFilter(CommonFilters.SpecificSID(computerObjectId), true) .AddFilter(CommonFilters.DomainControllers, true); var result = await Query(new LdapQueryParameters() { DomainName = resDomain, Attributes = CommonProperties.ObjectID, LDAPFilter = filter.GetFilter(), - }).DefaultIfEmpty(null).FirstOrDefaultAsync(); - return result is { IsSuccess: true }; + }).DefaultIfEmpty(LdapResult.Fail()).FirstOrDefaultAsync(); + return result.IsSuccess; } public async Task<(bool Success, TypedPrincipal Principal)> ResolveDistinguishedName(string distinguishedName) { @@ -1356,22 +1318,18 @@ public async Task IsDomainController(string computerObjectId, string domai Attributes = CommonProperties.TypeResolutionProps, SearchBase = distinguishedName, SearchScope = SearchScope.Base, - LDAPFilter = new LDAPFilter().AddAllObjects().GetFilter() - }).DefaultIfEmpty(null).FirstOrDefaultAsync(); + LDAPFilter = new LdapFilter().AddAllObjects().GetFilter() + }).DefaultIfEmpty(LdapResult.Fail()).FirstOrDefaultAsync(); - if (result is { IsSuccess: true }) { + if (result.IsSuccess && result.Value.GetObjectIdentifier(out var id)) { var entry = result.Value; - var id = entry.GetObjectIdentifier(); - if (id == null) { - return (false, default); - } if (await GetWellKnownPrincipal(id, domain) is (true, var wellKnownPrincipal)) { _distinguishedNameCache.TryAdd(distinguishedName, wellKnownPrincipal); return (true, wellKnownPrincipal); } - var type = entry.GetLabel(); + entry.GetLabel(out var type); principal = new TypedPrincipal(id, type); _distinguishedNameCache.TryAdd(distinguishedName, principal); return (true, principal); @@ -1379,31 +1337,37 @@ public async Task IsDomainController(string computerObjectId, string domai using (var ctx = new PrincipalContext(ContextType.Domain)) { try { - var lookupPrincipal = Principal.FindByIdentity(ctx, IdentityType.DistinguishedName, distinguishedName); - if (lookupPrincipal != null && - ((DirectoryEntry)lookupPrincipal.GetUnderlyingObject()).GetTypedPrincipal(out principal)) { - return (true, principal); + var lookupPrincipal = + Principal.FindByIdentity(ctx, IdentityType.DistinguishedName, distinguishedName); + if (lookupPrincipal != null) { + var entry = ((DirectoryEntry)lookupPrincipal.GetUnderlyingObject()).ToDirectoryObject(); + if (entry.GetObjectIdentifier(out var identifier) && entry.GetLabel(out var label)) { + if (await GetWellKnownPrincipal(identifier, domain) is (true, var wellKnownPrincipal)) { + _distinguishedNameCache.TryAdd(distinguishedName, wellKnownPrincipal); + return (true, wellKnownPrincipal); + } + + principal = new TypedPrincipal(identifier, label); + _distinguishedNameCache.TryAdd(distinguishedName, principal); + return (true, new TypedPrincipal(identifier, label)); + } } return (false, default); - } - catch { + } catch { return (false, default); } } } - - public void AddDomainController(string domainControllerSID) - { + + public void AddDomainController(string domainControllerSID) { DomainControllers.TryAdd(domainControllerSID, new byte()); } public async IAsyncEnumerable GetWellKnownPrincipalOutput() { - foreach (var wkp in SeenWellKnownPrincipals) - { + foreach (var wkp in SeenWellKnownPrincipals) { WellKnownPrincipal.GetWellKnownPrincipal(wkp.Value.WkpId, out var principal); - OutputBase output = principal.ObjectType switch - { + OutputBase output = principal.ObjectType switch { Label.User => new User(), Label.Computer => new Computer(), Label.Group => new OutputTypes.Group(), @@ -1417,16 +1381,16 @@ public async IAsyncEnumerable GetWellKnownPrincipalOutput() { output.Properties.Add("name", $"{principal.ObjectIdentifier}@{wkp.Value.DomainName}".ToUpper()); if (await GetDomainSidFromDomainName(wkp.Value.DomainName) is (true, var sid)) { - output.Properties.Add("domainsid", sid); + output.Properties.Add("domainsid", sid); } - + output.Properties.Add("domain", wkp.Value.DomainName.ToUpper()); output.ObjectIdentifier = wkp.Key; yield return output; } } - public void SetLdapConfig(LDAPConfig config) { + public void SetLdapConfig(LdapConfig config) { _ldapConfig = config; _connectionPool.Dispose(); _connectionPool = new ConnectionPoolManager(_ldapConfig, scanner: _portScanner); @@ -1443,7 +1407,7 @@ public void SetLdapConfig(LDAPConfig config) { return (true, searchBase); } } - + var property = context switch { NamingContext.Default => LDAPProperties.DefaultNamingContext, NamingContext.Configuration => LDAPProperties.ConfigurationNamingContext, @@ -1453,33 +1417,27 @@ public void SetLdapConfig(LDAPConfig config) { try { var entry = CreateDirectoryEntry($"LDAP://{domain}/RootDSE"); - entry.RefreshCache(new[] { property }); - var searchBase = entry.GetProperty(property); - if (!string.IsNullOrWhiteSpace(searchBase)) { + if (entry.TryGetProperty(property, out var searchBase)) { return (true, searchBase); } - } - catch { + } catch { //pass } if (GetDomain(domain, out var domainObj)) { try { - var entry = domainObj.GetDirectoryEntry(); - entry.RefreshCache(new[] { property }); - var searchBase = entry.GetProperty(property); - if (!string.IsNullOrWhiteSpace(searchBase)) { + var entry = domainObj.GetDirectoryEntry().ToDirectoryObject(); + if (entry.TryGetProperty(property, out var searchBase)) { return (true, searchBase); } - } - catch { + } catch { //pass } var name = domainObj.Name; if (!string.IsNullOrWhiteSpace(name)) { var tempPath = Helpers.DomainNameToDistinguishedName(name); - + var searchBase = context switch { NamingContext.Configuration => $"CN=Configuration,{tempPath}", NamingContext.Schema => $"CN=Schema,CN=Configuration,{tempPath}", @@ -1494,28 +1452,31 @@ public void SetLdapConfig(LDAPConfig config) { return (false, default); } - private DirectoryEntry CreateDirectoryEntry(string path) { + private IDirectoryObject CreateDirectoryEntry(string path) { if (_ldapConfig.Username != null) { - return new DirectoryEntry(path, _ldapConfig.Username, _ldapConfig.Password); + return new DirectoryEntry(path, _ldapConfig.Username, _ldapConfig.Password).ToDirectoryObject(); } - return new DirectoryEntry(path); + return new DirectoryEntry(path).ToDirectoryObject(); } + public void Dispose() { _connectionPool?.Dispose(); } - - internal static bool ResolveLabel(string objectIdentifier, string distinguishedName, string samAccountType, string[] objectClasses, int flags, out Label type) { + + internal static bool ResolveLabel(string objectIdentifier, string distinguishedName, string samAccountType, + string[] objectClasses, int flags, out Label type) { type = Label.Base; - if (objectIdentifier != null && WellKnownPrincipal.GetWellKnownPrincipal(objectIdentifier, out var principal)) { + if (objectIdentifier != null && + WellKnownPrincipal.GetWellKnownPrincipal(objectIdentifier, out var principal)) { type = principal.ObjectType; return true; } - + //Override GMSA/MSA account to treat them as users for the graph - if (objectClasses != null && (objectClasses.Contains(MSAClass, StringComparer.OrdinalIgnoreCase) || - objectClasses.Contains(GMSAClass, StringComparer.OrdinalIgnoreCase))) - { + if (objectClasses != null && + (objectClasses.Contains(ObjectClass.MSAClass, StringComparer.OrdinalIgnoreCase) || + objectClasses.Contains(ObjectClass.GMSAClass, StringComparer.OrdinalIgnoreCase))) { type = Label.User; return true; } @@ -1528,38 +1489,39 @@ internal static bool ResolveLabel(string objectIdentifier, string distinguishedN } } - if (objectClasses == null) { + if (objectClasses == null || objectClasses.Length == 0) { type = Label.Base; return false; } - - if (objectClasses.Contains(GroupPolicyContainerClass, StringComparer.InvariantCultureIgnoreCase)) + + if (objectClasses.Contains(ObjectClass.GroupPolicyContainerClass, StringComparer.OrdinalIgnoreCase)) type = Label.GPO; - else if (objectClasses.Contains(OrganizationalUnitClass, StringComparer.InvariantCultureIgnoreCase)) + else if (objectClasses.Contains(ObjectClass.OrganizationalUnitClass, StringComparer.OrdinalIgnoreCase)) type = Label.OU; - else if (objectClasses.Contains(DomainClass, StringComparer.InvariantCultureIgnoreCase)) + else if (objectClasses.Contains(ObjectClass.DomainClass, StringComparer.OrdinalIgnoreCase)) type = Label.Domain; - else if (objectClasses.Contains(ContainerClass, StringComparer.InvariantCultureIgnoreCase)) + else if (objectClasses.Contains(ObjectClass.ContainerClass, StringComparer.OrdinalIgnoreCase)) type = Label.Container; - else if (objectClasses.Contains(ConfigurationClass, StringComparer.InvariantCultureIgnoreCase)) + else if (objectClasses.Contains(ObjectClass.ConfigurationClass, StringComparer.OrdinalIgnoreCase)) type = Label.Configuration; - else if (objectClasses.Contains(PKICertificateTemplateClass, StringComparer.InvariantCultureIgnoreCase)) + else if (objectClasses.Contains(ObjectClass.PKICertificateTemplateClass, StringComparer.OrdinalIgnoreCase)) type = Label.CertTemplate; - else if (objectClasses.Contains(PKIEnrollmentServiceClass, StringComparer.InvariantCultureIgnoreCase)) + else if (objectClasses.Contains(ObjectClass.PKIEnrollmentServiceClass, StringComparer.OrdinalIgnoreCase)) type = Label.EnterpriseCA; - else if (objectClasses.Contains(CertificationAuthorityClass, StringComparer.InvariantCultureIgnoreCase)) { - if (distinguishedName.Contains(DirectoryPaths.RootCALocation)) + else if (objectClasses.Contains(ObjectClass.CertificationAuthorityClass, + StringComparer.OrdinalIgnoreCase)) { + if (distinguishedName.IndexOf(DirectoryPaths.RootCALocation, StringComparison.OrdinalIgnoreCase) > 0) type = Label.RootCA; - if (distinguishedName.Contains(DirectoryPaths.AIACALocation)) + if (distinguishedName.IndexOf(DirectoryPaths.AIACALocation, StringComparison.OrdinalIgnoreCase) > 0) type = Label.AIACA; - if (distinguishedName.Contains(DirectoryPaths.NTAuthStoreLocation)) + if (distinguishedName.IndexOf(DirectoryPaths.NTAuthStoreLocation, StringComparison.OrdinalIgnoreCase) > + 0) type = Label.NTAuthStore; - }else if (objectClasses.Contains(OIDContainerClass, StringComparer.InvariantCultureIgnoreCase)) { + } else if (objectClasses.Contains(ObjectClass.OIDContainerClass, StringComparer.OrdinalIgnoreCase)) { if (distinguishedName.StartsWith(DirectoryPaths.OIDContainerLocation, - StringComparison.InvariantCultureIgnoreCase)) + StringComparison.OrdinalIgnoreCase)) type = Label.Container; - else if (flags == 2) - { + else if (flags == 2) { type = Label.IssuancePolicy; } } @@ -1567,16 +1529,163 @@ internal static bool ResolveLabel(string objectIdentifier, string distinguishedN return type != Label.Base; } - private const string GroupPolicyContainerClass = "groupPolicyContainer"; - private const string OrganizationalUnitClass = "organizationalUnit"; - private const string DomainClass = "domain"; - private const string ContainerClass = "container"; - private const string ConfigurationClass = "configuration"; - private const string PKICertificateTemplateClass = "pKICertificateTemplate"; - private const string PKIEnrollmentServiceClass = "pKIEnrollmentService"; - private const string CertificationAuthorityClass = "certificationAuthority"; - private const string OIDContainerClass = "msPKI-Enterprise-Oid"; - private const string GMSAClass = "msds-groupmanagedserviceaccount"; - private const string MSAClass = "msds-managedserviceaccount"; + public static async Task<(bool Success, ResolvedSearchResult ResolvedResult)> ResolveSearchResult( + IDirectoryObject directoryObject, ILdapUtils utils) { + if (!directoryObject.GetObjectIdentifier(out var objectIdentifier)) { + return (false, default); + } + + var res = new ResolvedSearchResult { + ObjectId = objectIdentifier + }; + + //If the object is deleted, we can short circuit the rest of this logic as we don't really care about anything else + if (directoryObject.IsDeleted()) { + res.Deleted = true; + return (true, res); + } + + if (directoryObject.TryGetIntProperty(LDAPProperties.UserAccountControl, out var rawUac)) { + var flags = (UacFlags)rawUac; + if (flags.HasFlag(UacFlags.ServerTrustAccount)) { + res.IsDomainController = true; + utils.AddDomainController(objectIdentifier); + } + } + + string domain; + + if (directoryObject.TryGetDistinguishedName(out var distinguishedName)) { + domain = Helpers.DistinguishedNameToDomain(distinguishedName); + } else { + if (objectIdentifier.StartsWith("S-1-5") && + await utils.GetDomainNameFromSid(objectIdentifier) is (true, var domainName)) { + domain = domainName; + } else { + return (false, default); + } + } + + string domainSid; + var match = SIDRegex.Match(objectIdentifier); + if (match.Success) { + domainSid = match.Groups[1].Value; + } else if (await utils.GetDomainSidFromDomainName(domain) is (true, var sid)) { + domainSid = sid; + } else { + Logging.Logger.LogWarning("Failed to resolve domain sid for object {Identifier}", objectIdentifier); + domainSid = null; + } + + res.Domain = domain; + res.DomainSid = domainSid; + + if (WellKnownPrincipal.GetWellKnownPrincipal(objectIdentifier, out var wellKnownPrincipal)) { + res.DisplayName = $"{wellKnownPrincipal.ObjectIdentifier}@{domain}"; + res.ObjectType = wellKnownPrincipal.ObjectType; + if (await utils.GetWellKnownPrincipal(objectIdentifier, domain) is (true, var convertedPrincipal)) { + res.ObjectId = convertedPrincipal.ObjectIdentifier; + } + + return (true, res); + } + + if (!directoryObject.GetLabel(out var label)) { + if (await utils.ResolveIDAndType(objectIdentifier, domain) is (true, var typedPrincipal)) { + label = typedPrincipal.ObjectType; + } + } + + if (directoryObject.IsMSA() || directoryObject.IsGMSA()) { + label = Label.User; + } + + res.ObjectType = label; + + directoryObject.TryGetProperty(LDAPProperties.SAMAccountName, out var samAccountName); + + switch (label) { + case Label.User: + case Label.Group: + case Label.Base: + res.DisplayName = $"{samAccountName}@{domain}"; + break; + case Label.Computer: { + var shortName = samAccountName?.TrimEnd('$'); + if (directoryObject.TryGetProperty(LDAPProperties.DNSHostName, out var dns)) { + res.DisplayName = dns; + } else if (!string.IsNullOrWhiteSpace(shortName)) { + res.DisplayName = $"{shortName}.{domain}"; + } else if (directoryObject.TryGetProperty(LDAPProperties.CanonicalName, + out var canonicalName)) { + res.DisplayName = $"{canonicalName}.{domain}"; + } else if (directoryObject.TryGetProperty(LDAPProperties.Name, out var name)) { + res.DisplayName = $"{name}.{domain}"; + } else { + res.DisplayName = $"UNKNOWN.{domain}"; + } + + break; + } + case Label.GPO: + case Label.IssuancePolicy: { + if (directoryObject.TryGetProperty(LDAPProperties.DisplayName, out var displayName)) { + res.DisplayName = $"{displayName}@{domain}"; + } else if (directoryObject.TryGetProperty(LDAPProperties.CanonicalName, + out var canonicalName)) { + res.DisplayName = $"{canonicalName}@{domain}"; + } else { + res.DisplayName = $"UNKNOWN@{domain}"; + } + + break; + } + case Label.Domain: + res.DisplayName = domain; + break; + case Label.OU: { + if (directoryObject.TryGetProperty(LDAPProperties.Name, out var name)) { + res.DisplayName = $"{name}@{domain}"; + } else if (directoryObject.TryGetProperty(LDAPProperties.OU, out var ou)) { + res.DisplayName = $"{ou}@{domain}"; + } else { + res.DisplayName = $"UNKNOWN@{domain}"; + } + + break; + } + case Label.Container: { + if (directoryObject.TryGetProperty(LDAPProperties.Name, out var name)) { + res.DisplayName = $"{name}@{domain}"; + } else if (directoryObject.TryGetProperty(LDAPProperties.CanonicalName, + out var canonicalName)) { + res.DisplayName = $"{canonicalName}@{domain}"; + } else { + res.DisplayName = $"UNKNOWN@{domain}"; + } + + break; + } + case Label.Configuration: + case Label.RootCA: + case Label.AIACA: + case Label.NTAuthStore: + case Label.EnterpriseCA: + case Label.CertTemplate: { + if (directoryObject.TryGetProperty(LDAPProperties.Name, out var name)) { + res.DisplayName = $"{name}@{domain}"; + } else { + res.DisplayName = $"UNKNOWN@{domain}"; + } + + break; + } + default: + throw new ArgumentOutOfRangeException(); + } + + res.DisplayName = res.DisplayName.ToUpper(); + return (true, res); + } } } \ No newline at end of file diff --git a/src/CommonLib/OutputTypes/ACE.cs b/src/CommonLib/OutputTypes/ACE.cs index 649442b5..9d62d88b 100644 --- a/src/CommonLib/OutputTypes/ACE.cs +++ b/src/CommonLib/OutputTypes/ACE.cs @@ -1,39 +1,33 @@ using SharpHoundCommonLib.Enums; -namespace SharpHoundCommonLib.OutputTypes -{ - public class ACE - { +namespace SharpHoundCommonLib.OutputTypes { + public class ACE { public string PrincipalSID { get; set; } public Label PrincipalType { get; set; } public string RightName { get; set; } public bool IsInherited { get; set; } + public string InheritanceHash { get; set; } - public override string ToString() - { + public override string ToString() { return $"{PrincipalType} {PrincipalSID} - {RightName} {(IsInherited ? "" : "Not")} Inherited"; } - protected bool Equals(ACE other) - { + protected bool Equals(ACE other) { return PrincipalSID == other.PrincipalSID && PrincipalType == other.PrincipalType && RightName == other.RightName && IsInherited == other.IsInherited; } - public override bool Equals(object obj) - { + public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) return false; if (ReferenceEquals(this, obj)) return true; if (obj.GetType() != GetType()) return false; - return Equals((ACE) obj); + return Equals((ACE)obj); } - public override int GetHashCode() - { - unchecked - { + public override int GetHashCode() { + unchecked { var hashCode = PrincipalSID != null ? PrincipalSID.GetHashCode() : 0; - hashCode = (hashCode * 397) ^ (int) PrincipalType; + hashCode = (hashCode * 397) ^ (int)PrincipalType; hashCode = (hashCode * 397) ^ (RightName != null ? RightName.GetHashCode() : 0); hashCode = (hashCode * 397) ^ IsInherited.GetHashCode(); return hashCode; diff --git a/src/CommonLib/OutputTypes/Computer.cs b/src/CommonLib/OutputTypes/Computer.cs index a39a2903..2fbcfcf5 100644 --- a/src/CommonLib/OutputTypes/Computer.cs +++ b/src/CommonLib/OutputTypes/Computer.cs @@ -35,7 +35,7 @@ public class ComputerStatus public string Error { get; set; } public static string NonWindowsOS => "NonWindowsOS"; - public static string OldPwd => "PwdLastSetOutOfRange"; + public static string NotActive => "NotActive"; public static string PortNotOpen => "PortNotOpen"; public static string Success => "Success"; diff --git a/src/CommonLib/OutputTypes/Container.cs b/src/CommonLib/OutputTypes/Container.cs index 09f8d95b..d72cfe27 100644 --- a/src/CommonLib/OutputTypes/Container.cs +++ b/src/CommonLib/OutputTypes/Container.cs @@ -5,5 +5,6 @@ namespace SharpHoundCommonLib.OutputTypes public class Container : OutputBase { public TypedPrincipal[] ChildObjects { get; set; } = Array.Empty(); + public string[] InheritanceHashes { get; set; } = Array.Empty(); } } \ No newline at end of file diff --git a/src/CommonLib/OutputTypes/Domain.cs b/src/CommonLib/OutputTypes/Domain.cs index 0bad9c4c..d6621fe3 100644 --- a/src/CommonLib/OutputTypes/Domain.cs +++ b/src/CommonLib/OutputTypes/Domain.cs @@ -8,5 +8,7 @@ public class Domain : OutputBase public TypedPrincipal[] ChildObjects { get; set; } = Array.Empty(); public DomainTrust[] Trusts { get; set; } = Array.Empty(); public GPLink[] Links { get; set; } = Array.Empty(); + public string[] InheritanceHashes { get; set; } = Array.Empty(); + public string ForestRootIdentifier { get; set; } } } \ No newline at end of file diff --git a/src/CommonLib/OutputTypes/OU.cs b/src/CommonLib/OutputTypes/OU.cs index 2bb3e55f..87f1f9d8 100644 --- a/src/CommonLib/OutputTypes/OU.cs +++ b/src/CommonLib/OutputTypes/OU.cs @@ -7,5 +7,6 @@ public class OU : OutputBase public ResultingGPOChanges GPOChanges = new(); public GPLink[] Links { get; set; } = Array.Empty(); public TypedPrincipal[] ChildObjects { get; set; } = Array.Empty(); + public string[] InheritanceHashes { get; set; } = Array.Empty(); } } \ No newline at end of file diff --git a/src/CommonLib/Processors/ACLProcessor.cs b/src/CommonLib/Processors/ACLProcessor.cs index d20e4b22..dd563827 100644 --- a/src/CommonLib/Processors/ACLProcessor.cs +++ b/src/CommonLib/Processors/ACLProcessor.cs @@ -2,48 +2,47 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.DirectoryServices; +using System.Linq; using System.Security.AccessControl; +using System.Security.Cryptography; using System.Security.Principal; +using System.Text; using System.Threading.Tasks; using Microsoft.Extensions.Logging; +using SharpHoundCommonLib.DirectoryObjects; using SharpHoundCommonLib.Enums; using SharpHoundCommonLib.OutputTypes; using SearchScope = System.DirectoryServices.Protocols.SearchScope; -namespace SharpHoundCommonLib.Processors -{ - public class ACLProcessor - { +namespace SharpHoundCommonLib.Processors { + public class ACLProcessor { private static readonly Dictionary BaseGuids; private static readonly ConcurrentDictionary GuidMap = new(); private readonly ILogger _log; private readonly ILdapUtils _utils; private static readonly HashSet BuiltDomainCaches = new(StringComparer.OrdinalIgnoreCase); - - static ACLProcessor() - { + + static ACLProcessor() { //Create a dictionary with the base GUIDs of each object type - BaseGuids = new Dictionary - { - {Label.User, "bf967aba-0de6-11d0-a285-00aa003049e2"}, - {Label.Computer, "bf967a86-0de6-11d0-a285-00aa003049e2"}, - {Label.Group, "bf967a9c-0de6-11d0-a285-00aa003049e2"}, - {Label.Domain, "19195a5a-6da0-11d0-afd3-00c04fd930c9"}, - {Label.GPO, "f30e3bc2-9ff0-11d1-b603-0000f80367c1"}, - {Label.OU, "bf967aa5-0de6-11d0-a285-00aa003049e2"}, - {Label.Container, "bf967a8b-0de6-11d0-a285-00aa003049e2"}, - {Label.Configuration, "bf967a87-0de6-11d0-a285-00aa003049e2"}, - {Label.RootCA, "3fdfee50-47f4-11d1-a9c3-0000f80367c1"}, - {Label.AIACA, "3fdfee50-47f4-11d1-a9c3-0000f80367c1"}, - {Label.EnterpriseCA, "ee4aa692-3bba-11d2-90cc-00c04fd91ab1"}, - {Label.NTAuthStore, "3fdfee50-47f4-11d1-a9c3-0000f80367c1"}, - {Label.CertTemplate, "e5209ca2-3bba-11d2-90cc-00c04fd91ab1"}, - {Label.IssuancePolicy, "37cfd85c-6719-4ad8-8f9e-8678ba627563"} + BaseGuids = new Dictionary { + { Label.User, "bf967aba-0de6-11d0-a285-00aa003049e2" }, + { Label.Computer, "bf967a86-0de6-11d0-a285-00aa003049e2" }, + { Label.Group, "bf967a9c-0de6-11d0-a285-00aa003049e2" }, + { Label.Domain, "19195a5a-6da0-11d0-afd3-00c04fd930c9" }, + { Label.GPO, "f30e3bc2-9ff0-11d1-b603-0000f80367c1" }, + { Label.OU, "bf967aa5-0de6-11d0-a285-00aa003049e2" }, + { Label.Container, "bf967a8b-0de6-11d0-a285-00aa003049e2" }, + { Label.Configuration, "bf967a87-0de6-11d0-a285-00aa003049e2" }, + { Label.RootCA, "3fdfee50-47f4-11d1-a9c3-0000f80367c1" }, + { Label.AIACA, "3fdfee50-47f4-11d1-a9c3-0000f80367c1" }, + { Label.EnterpriseCA, "ee4aa692-3bba-11d2-90cc-00c04fd91ab1" }, + { Label.NTAuthStore, "3fdfee50-47f4-11d1-a9c3-0000f80367c1" }, + { Label.CertTemplate, "e5209ca2-3bba-11d2-90cc-00c04fd91ab1" }, + { Label.IssuancePolicy, "37cfd85c-6719-4ad8-8f9e-8678ba627563" } }; } - public ACLProcessor(ILdapUtils utils, ILogger log = null) - { + public ACLProcessor(ILdapUtils utils, ILogger log = null) { _utils = utils; _log = log ?? Logging.LogProvider.CreateLogger("ACLProc"); } @@ -55,21 +54,20 @@ public ACLProcessor(ILdapUtils utils, ILogger log = null) private async Task BuildGuidCache(string domain) { BuiltDomainCaches.Add(domain); await foreach (var result in _utils.Query(new LdapQueryParameters() { - DomainName = domain, - LDAPFilter = "(schemaIDGUID=*)", - NamingContext = NamingContext.Schema, - Attributes = new[] {LDAPProperties.SchemaIDGUID, LDAPProperties.Name}, - })) - { + DomainName = domain, + LDAPFilter = "(schemaIDGUID=*)", + NamingContext = NamingContext.Schema, + Attributes = new[] { LDAPProperties.SchemaIDGUID, LDAPProperties.Name }, + })) { if (result.IsSuccess) { - var name = result.Value.GetProperty(LDAPProperties.Name)?.ToLower(); - var guid = result.Value.GetGuid(); - if (name == null || guid == null) { + if (!result.Value.TryGetProperty(LDAPProperties.Name, out var name) || + !result.Value.TryGetGuid(out var guid)) { continue; } + name = name.ToLower(); if (name is LDAPProperties.LAPSPassword or LDAPProperties.LegacyLAPSPassword) { - GuidMap.TryAdd(guid, name); + GuidMap.TryAdd(guid, name); } } } @@ -80,10 +78,12 @@ private async Task BuildGuidCache(string domain) { /// /// /// - public bool IsACLProtected(ISearchResultEntry entry) - { - var ntsd = entry.GetByteProperty(LDAPProperties.SecurityDescriptor); - return IsACLProtected(ntsd); + public bool IsACLProtected(IDirectoryObject entry) { + if (entry.TryGetByteProperty(LDAPProperties.SecurityDescriptor, out var ntSecurityDescriptor)) { + return IsACLProtected(ntSecurityDescriptor); + } + + return false; } /// @@ -91,8 +91,7 @@ public bool IsACLProtected(ISearchResultEntry entry) /// /// /// - public bool IsACLProtected(byte[] ntSecurityDescriptor) - { + public bool IsACLProtected(byte[] ntSecurityDescriptor) { if (ntSecurityDescriptor == null) return false; @@ -108,9 +107,11 @@ public bool IsACLProtected(byte[] ntSecurityDescriptor) /// /// /// - public IAsyncEnumerable ProcessACL(ResolvedSearchResult result, ISearchResultEntry searchResult) - { - var descriptor = searchResult.GetByteProperty(LDAPProperties.SecurityDescriptor); + public IAsyncEnumerable ProcessACL(ResolvedSearchResult result, IDirectoryObject searchResult) { + if (!searchResult.TryGetByteProperty(LDAPProperties.SecurityDescriptor, out var descriptor)) { + return AsyncEnumerable.Empty(); + } + var domain = result.Domain; var type = result.ObjectType; var hasLaps = searchResult.HasLAPS(); @@ -119,6 +120,85 @@ public IAsyncEnumerable ProcessACL(ResolvedSearchResult result, ISearchResu return ProcessACL(descriptor, domain, type, hasLaps, name); } + internal static string CalculateInheritanceHash(string identityReference, ActiveDirectoryRights rights, + string aceType, string inheritedObjectType) { + var hash = identityReference + rights + aceType + inheritedObjectType; + using (var md5 = MD5.Create()) { + var bytes = md5.ComputeHash(Encoding.UTF8.GetBytes(hash)); + var builder = new StringBuilder(); + foreach (var b in bytes) { + builder.Append(b.ToString("x2")); + } + + return builder.ToString(); + } + } + + /// + /// Helper function to get inherited ACE hashes using CommonLib types + /// + /// + /// + /// + public IEnumerable GetInheritedAceHashes(IDirectoryObject directoryObject, + ResolvedSearchResult resolvedSearchResult) { + if (directoryObject.TryGetByteProperty(LDAPProperties.SecurityDescriptor, out var value)) { + return GetInheritedAceHashes(value, resolvedSearchResult.DisplayName); + } + + return Array.Empty(); + } + + /// + /// Gets the hashes for all aces that are pushing inheritance down the tree for later comparison + /// + /// + /// + /// + public IEnumerable GetInheritedAceHashes(byte[] ntSecurityDescriptor, string objectName = "") { + if (ntSecurityDescriptor == null) { + yield break; + } + + var descriptor = _utils.MakeSecurityDescriptor(); + try { + descriptor.SetSecurityDescriptorBinaryForm(ntSecurityDescriptor); + } catch (OverflowException) { + _log.LogWarning( + "Security descriptor on object {Name} exceeds maximum allowable length. Unable to process", + objectName); + yield break; + } + + foreach (var ace in descriptor.GetAccessRules(true, true, typeof(SecurityIdentifier))) { + //Skip all null/deny/inherited aces + if (ace == null || ace.AccessControlType() == AccessControlType.Deny || ace.IsInherited()) { + continue; + } + + var ir = ace.IdentityReference(); + var principalSid = Helpers.PreProcessSID(ir); + + //Skip aces for filtered principals + if (principalSid == null) { + _log.LogTrace("Pre-Process excluded SID {SID} on {Name}", ir ?? "null", objectName); + continue; + } + + var iFlags = ace.InheritanceFlags; + if (iFlags == InheritanceFlags.None) { + continue; + } + + var aceRights = ace.ActiveDirectoryRights(); + //Lowercase this just in case. As far as I know it should always come back that way anyways, but better safe than sorry + var aceType = ace.ObjectType().ToString().ToLower(); + var inheritanceType = ace.InheritedObjectType(); + + yield return CalculateInheritanceHash(ir, aceRights, aceType, inheritanceType); + } + } + /// /// Read's the ntSecurityDescriptor from a SearchResultEntry and processes the ACEs in the ACL, filtering out ACEs that /// BloodHound is not interested in @@ -131,24 +211,20 @@ public IAsyncEnumerable ProcessACL(ResolvedSearchResult result, ISearchResu /// public async IAsyncEnumerable ProcessACL(byte[] ntSecurityDescriptor, string objectDomain, Label objectType, - bool hasLaps, string objectName = "") - { + bool hasLaps, string objectName = "") { if (!BuiltDomainCaches.Contains(objectDomain)) { await BuildGuidCache(objectDomain); } - if (ntSecurityDescriptor == null) - { + + if (ntSecurityDescriptor == null) { _log.LogDebug("Security Descriptor is null for {Name}", objectName); yield break; } var descriptor = _utils.MakeSecurityDescriptor(); - try - { + try { descriptor.SetSecurityDescriptorBinaryForm(ntSecurityDescriptor); - } - catch (OverflowException) - { + } catch (OverflowException) { _log.LogWarning( "Security descriptor on object {Name} exceeds maximum allowable length. Unable to process", objectName); @@ -157,39 +233,31 @@ public async IAsyncEnumerable ProcessACL(byte[] ntSecurityDescriptor, strin var ownerSid = Helpers.PreProcessSID(descriptor.GetOwner(typeof(SecurityIdentifier))); - if (ownerSid != null) - { + if (ownerSid != null) { if (await _utils.ResolveIDAndType(ownerSid, objectDomain) is (true, var resolvedOwner)) { - yield return new ACE - { + yield return new ACE { PrincipalType = resolvedOwner.ObjectType, PrincipalSID = resolvedOwner.ObjectIdentifier, RightName = EdgeNames.Owns, IsInherited = false }; } - } - else - { + } else { _log.LogDebug("Owner is null for {Name}", objectName); } - foreach (var ace in descriptor.GetAccessRules(true, true, typeof(SecurityIdentifier))) - { - if (ace == null) - { + foreach (var ace in descriptor.GetAccessRules(true, true, typeof(SecurityIdentifier))) { + if (ace == null) { _log.LogTrace("Skipping null ACE for {Name}", objectName); continue; } - if (ace.AccessControlType() == AccessControlType.Deny) - { + if (ace.AccessControlType() == AccessControlType.Deny) { _log.LogTrace("Skipping deny ACE for {Name}", objectName); continue; } - if (!ace.IsAceInheritedFrom(BaseGuids[objectType])) - { + if (!ace.IsAceInheritedFrom(BaseGuids[objectType])) { _log.LogTrace("Skipping ACE with unmatched GUID/inheritance for {Name}", objectName); continue; } @@ -197,8 +265,7 @@ public async IAsyncEnumerable ProcessACL(byte[] ntSecurityDescriptor, strin var ir = ace.IdentityReference(); var principalSid = Helpers.PreProcessSID(ir); - if (principalSid == null) - { + if (principalSid == null) { _log.LogTrace("Pre-Process excluded SID {SID} on {Name}", ir ?? "null", objectName); continue; } @@ -213,21 +280,25 @@ public async IAsyncEnumerable ProcessACL(byte[] ntSecurityDescriptor, strin var aceType = ace.ObjectType().ToString().ToLower(); var inherited = ace.IsInherited(); + var aceInheritanceHash = ""; + if (inherited) { + aceInheritanceHash = CalculateInheritanceHash(ir, aceRights, aceType, ace.InheritedObjectType()); + } + GuidMap.TryGetValue(aceType, out var mappedGuid); _log.LogTrace("Processing ACE with rights {Rights} and guid {GUID} on object {Name}", aceRights, aceType, objectName); //GenericAll applies to every object - if (aceRights.HasFlag(ActiveDirectoryRights.GenericAll)) - { + if (aceRights.HasFlag(ActiveDirectoryRights.GenericAll)) { if (aceType is ACEGuids.AllGuid or "") - yield return new ACE - { + yield return new ACE { PrincipalType = resolvedPrincipal.ObjectType, PrincipalSID = resolvedPrincipal.ObjectIdentifier, IsInherited = inherited, - RightName = EdgeNames.GenericAll + RightName = EdgeNames.GenericAll, + InheritanceHash = aceInheritanceHash }; //This is a special case. If we don't continue here, every other ACE will match because GenericAll includes all other permissions continue; @@ -235,21 +306,21 @@ public async IAsyncEnumerable ProcessACL(byte[] ntSecurityDescriptor, strin //WriteDACL and WriteOwner are always useful no matter what the object type is as well because they enable all other attacks if (aceRights.HasFlag(ActiveDirectoryRights.WriteDacl)) - yield return new ACE - { + yield return new ACE { PrincipalType = resolvedPrincipal.ObjectType, PrincipalSID = resolvedPrincipal.ObjectIdentifier, IsInherited = inherited, - RightName = EdgeNames.WriteDacl + RightName = EdgeNames.WriteDacl, + InheritanceHash = aceInheritanceHash }; if (aceRights.HasFlag(ActiveDirectoryRights.WriteOwner)) - yield return new ACE - { + yield return new ACE { PrincipalType = resolvedPrincipal.ObjectType, PrincipalSID = resolvedPrincipal.ObjectIdentifier, IsInherited = inherited, - RightName = EdgeNames.WriteOwner + RightName = EdgeNames.WriteOwner, + InheritanceHash = aceInheritanceHash }; //Cool ACE courtesy of @rookuu. Allows a principal to add itself to a group and no one else @@ -257,238 +328,226 @@ public async IAsyncEnumerable ProcessACL(byte[] ntSecurityDescriptor, strin !aceRights.HasFlag(ActiveDirectoryRights.WriteProperty) && !aceRights.HasFlag(ActiveDirectoryRights.GenericWrite) && objectType == Label.Group && aceType == ACEGuids.WriteMember) - yield return new ACE - { + yield return new ACE { PrincipalType = resolvedPrincipal.ObjectType, PrincipalSID = resolvedPrincipal.ObjectIdentifier, IsInherited = inherited, - RightName = EdgeNames.AddSelf + RightName = EdgeNames.AddSelf, + InheritanceHash = aceInheritanceHash }; //Process object type specific ACEs. Extended rights apply to users, domains, computers, and cert templates - if (aceRights.HasFlag(ActiveDirectoryRights.ExtendedRight)) - { - if (objectType == Label.Domain) - { + if (aceRights.HasFlag(ActiveDirectoryRights.ExtendedRight)) { + if (objectType == Label.Domain) { if (aceType == ACEGuids.DSReplicationGetChanges) - yield return new ACE - { + yield return new ACE { PrincipalType = resolvedPrincipal.ObjectType, PrincipalSID = resolvedPrincipal.ObjectIdentifier, IsInherited = inherited, - RightName = EdgeNames.GetChanges + RightName = EdgeNames.GetChanges, + InheritanceHash = aceInheritanceHash }; else if (aceType == ACEGuids.DSReplicationGetChangesAll) - yield return new ACE - { + yield return new ACE { PrincipalType = resolvedPrincipal.ObjectType, PrincipalSID = resolvedPrincipal.ObjectIdentifier, IsInherited = inherited, - RightName = EdgeNames.GetChangesAll + RightName = EdgeNames.GetChangesAll, + InheritanceHash = aceInheritanceHash }; else if (aceType == ACEGuids.DSReplicationGetChangesInFilteredSet) - yield return new ACE - { + yield return new ACE { PrincipalType = resolvedPrincipal.ObjectType, PrincipalSID = resolvedPrincipal.ObjectIdentifier, IsInherited = inherited, - RightName = EdgeNames.GetChangesInFilteredSet + RightName = EdgeNames.GetChangesInFilteredSet, + InheritanceHash = aceInheritanceHash }; else if (aceType is ACEGuids.AllGuid or "") - yield return new ACE - { + yield return new ACE { PrincipalType = resolvedPrincipal.ObjectType, PrincipalSID = resolvedPrincipal.ObjectIdentifier, IsInherited = inherited, - RightName = EdgeNames.AllExtendedRights + RightName = EdgeNames.AllExtendedRights, + InheritanceHash = aceInheritanceHash }; - } - else if (objectType == Label.User) - { + } else if (objectType == Label.User) { if (aceType == ACEGuids.UserForceChangePassword) - yield return new ACE - { + yield return new ACE { PrincipalType = resolvedPrincipal.ObjectType, PrincipalSID = resolvedPrincipal.ObjectIdentifier, IsInherited = inherited, - RightName = EdgeNames.ForceChangePassword + RightName = EdgeNames.ForceChangePassword, + InheritanceHash = aceInheritanceHash }; else if (aceType is ACEGuids.AllGuid or "") - yield return new ACE - { + yield return new ACE { PrincipalType = resolvedPrincipal.ObjectType, PrincipalSID = resolvedPrincipal.ObjectIdentifier, IsInherited = inherited, - RightName = EdgeNames.AllExtendedRights + RightName = EdgeNames.AllExtendedRights, + InheritanceHash = aceInheritanceHash }; - } - else if (objectType == Label.Computer) - { + } else if (objectType == Label.Computer) { //ReadLAPSPassword is only applicable if the computer actually has LAPS. Check the world readable property ms-mcs-admpwdexpirationtime - if (hasLaps) - { + if (hasLaps) { if (aceType is ACEGuids.AllGuid or "") - yield return new ACE - { + yield return new ACE { PrincipalType = resolvedPrincipal.ObjectType, PrincipalSID = resolvedPrincipal.ObjectIdentifier, IsInherited = inherited, - RightName = EdgeNames.AllExtendedRights + RightName = EdgeNames.AllExtendedRights, + InheritanceHash = aceInheritanceHash }; else if (mappedGuid is LDAPProperties.LegacyLAPSPassword or LDAPProperties.LAPSPassword) - yield return new ACE - { + yield return new ACE { PrincipalType = resolvedPrincipal.ObjectType, PrincipalSID = resolvedPrincipal.ObjectIdentifier, IsInherited = inherited, - RightName = EdgeNames.ReadLAPSPassword + RightName = EdgeNames.ReadLAPSPassword, + InheritanceHash = aceInheritanceHash }; } - } - else if (objectType == Label.CertTemplate) - { + } else if (objectType == Label.CertTemplate) { if (aceType is ACEGuids.AllGuid or "") - yield return new ACE - { + yield return new ACE { PrincipalType = resolvedPrincipal.ObjectType, PrincipalSID = resolvedPrincipal.ObjectIdentifier, IsInherited = inherited, - RightName = EdgeNames.AllExtendedRights + RightName = EdgeNames.AllExtendedRights, + InheritanceHash = aceInheritanceHash }; else if (aceType is ACEGuids.Enroll) - yield return new ACE - { + yield return new ACE { PrincipalType = resolvedPrincipal.ObjectType, PrincipalSID = resolvedPrincipal.ObjectIdentifier, IsInherited = inherited, - RightName = EdgeNames.Enroll + RightName = EdgeNames.Enroll, + InheritanceHash = aceInheritanceHash }; } } //GenericWrite encapsulates WriteProperty, so process them in tandem to avoid duplicate edges if (aceRights.HasFlag(ActiveDirectoryRights.GenericWrite) || - aceRights.HasFlag(ActiveDirectoryRights.WriteProperty)) - { - if (objectType is Label.User - or Label.Group - or Label.Computer - or Label.GPO - or Label.CertTemplate - or Label.RootCA - or Label.EnterpriseCA - or Label.AIACA - or Label.NTAuthStore + aceRights.HasFlag(ActiveDirectoryRights.WriteProperty)) { + if (objectType is Label.User + or Label.Group + or Label.Computer + or Label.GPO + or Label.CertTemplate + or Label.RootCA + or Label.EnterpriseCA + or Label.AIACA + or Label.NTAuthStore or Label.IssuancePolicy) if (aceType is ACEGuids.AllGuid or "") - yield return new ACE - { + yield return new ACE { PrincipalType = resolvedPrincipal.ObjectType, PrincipalSID = resolvedPrincipal.ObjectIdentifier, IsInherited = inherited, - RightName = EdgeNames.GenericWrite + RightName = EdgeNames.GenericWrite, + InheritanceHash = aceInheritanceHash }; if (objectType == Label.User && aceType == ACEGuids.WriteSPN) - yield return new ACE - { + yield return new ACE { PrincipalType = resolvedPrincipal.ObjectType, PrincipalSID = resolvedPrincipal.ObjectIdentifier, IsInherited = inherited, - RightName = EdgeNames.WriteSPN + RightName = EdgeNames.WriteSPN, + InheritanceHash = aceInheritanceHash }; else if (objectType == Label.Computer && aceType == ACEGuids.WriteAllowedToAct) - yield return new ACE - { + yield return new ACE { PrincipalType = resolvedPrincipal.ObjectType, PrincipalSID = resolvedPrincipal.ObjectIdentifier, IsInherited = inherited, - RightName = EdgeNames.AddAllowedToAct + RightName = EdgeNames.AddAllowedToAct, + InheritanceHash = aceInheritanceHash }; else if (objectType == Label.Computer && aceType == ACEGuids.UserAccountRestrictions) - yield return new ACE - { + yield return new ACE { PrincipalType = resolvedPrincipal.ObjectType, PrincipalSID = resolvedPrincipal.ObjectIdentifier, IsInherited = inherited, - RightName = EdgeNames.WriteAccountRestrictions + RightName = EdgeNames.WriteAccountRestrictions, + InheritanceHash = aceInheritanceHash }; else if (objectType == Label.Group && aceType == ACEGuids.WriteMember) - yield return new ACE - { + yield return new ACE { PrincipalType = resolvedPrincipal.ObjectType, PrincipalSID = resolvedPrincipal.ObjectIdentifier, IsInherited = inherited, - RightName = EdgeNames.AddMember + RightName = EdgeNames.AddMember, + InheritanceHash = aceInheritanceHash }; else if (objectType is Label.User or Label.Computer && aceType == ACEGuids.AddKeyPrincipal) - yield return new ACE - { + yield return new ACE { PrincipalType = resolvedPrincipal.ObjectType, PrincipalSID = resolvedPrincipal.ObjectIdentifier, IsInherited = inherited, - RightName = EdgeNames.AddKeyCredentialLink + RightName = EdgeNames.AddKeyCredentialLink, + InheritanceHash = aceInheritanceHash }; - else if (objectType is Label.CertTemplate) - { + else if (objectType is Label.CertTemplate) { if (aceType == ACEGuids.PKIEnrollmentFlag) - yield return new ACE - { + yield return new ACE { PrincipalType = resolvedPrincipal.ObjectType, PrincipalSID = resolvedPrincipal.ObjectIdentifier, IsInherited = inherited, - RightName = EdgeNames.WritePKIEnrollmentFlag + RightName = EdgeNames.WritePKIEnrollmentFlag, + InheritanceHash = aceInheritanceHash }; else if (aceType == ACEGuids.PKINameFlag) - yield return new ACE - { + yield return new ACE { PrincipalType = resolvedPrincipal.ObjectType, PrincipalSID = resolvedPrincipal.ObjectIdentifier, IsInherited = inherited, - RightName = EdgeNames.WritePKINameFlag + RightName = EdgeNames.WritePKINameFlag, + InheritanceHash = aceInheritanceHash }; } } // EnterpriseCA rights - if (objectType == Label.EnterpriseCA) - { + if (objectType == Label.EnterpriseCA) { if (aceType is ACEGuids.Enroll) - yield return new ACE - { + yield return new ACE { PrincipalType = resolvedPrincipal.ObjectType, PrincipalSID = resolvedPrincipal.ObjectIdentifier, IsInherited = inherited, - RightName = EdgeNames.Enroll + RightName = EdgeNames.Enroll, + InheritanceHash = aceInheritanceHash }; var cARights = (CertificationAuthorityRights)aceRights; // TODO: These if statements are also present in ProcessRegistryEnrollmentPermissions. Move to shared location. if ((cARights & CertificationAuthorityRights.ManageCA) != 0) - yield return new ACE - { + yield return new ACE { PrincipalType = resolvedPrincipal.ObjectType, PrincipalSID = resolvedPrincipal.ObjectIdentifier, IsInherited = inherited, - RightName = EdgeNames.ManageCA + RightName = EdgeNames.ManageCA, + InheritanceHash = aceInheritanceHash }; if ((cARights & CertificationAuthorityRights.ManageCertificates) != 0) - yield return new ACE - { + yield return new ACE { PrincipalType = resolvedPrincipal.ObjectType, PrincipalSID = resolvedPrincipal.ObjectIdentifier, IsInherited = inherited, - RightName = EdgeNames.ManageCertificates + RightName = EdgeNames.ManageCertificates, + InheritanceHash = aceInheritanceHash }; if ((cARights & CertificationAuthorityRights.Enroll) != 0) - yield return new ACE - { + yield return new ACE { PrincipalType = resolvedPrincipal.ObjectType, PrincipalSID = resolvedPrincipal.ObjectIdentifier, IsInherited = inherited, - RightName = EdgeNames.Enroll + RightName = EdgeNames.Enroll, + InheritanceHash = aceInheritanceHash }; } } @@ -501,9 +560,11 @@ or Label.NTAuthStore /// /// public IAsyncEnumerable ProcessGMSAReaders(ResolvedSearchResult resolvedSearchResult, - ISearchResultEntry searchResultEntry) - { - var descriptor = searchResultEntry.GetByteProperty(LDAPProperties.GroupMSAMembership); + IDirectoryObject searchResultEntry) { + if (!searchResultEntry.TryGetByteProperty(LDAPProperties.GroupMSAMembership, out var descriptor)) { + return AsyncEnumerable.Empty(); + } + var domain = resolvedSearchResult.Domain; var name = resolvedSearchResult.DisplayName; @@ -516,8 +577,7 @@ public IAsyncEnumerable ProcessGMSAReaders(ResolvedSearchResult resolvedSea /// /// /// - public IAsyncEnumerable ProcessGMSAReaders(byte[] groupMSAMembership, string objectDomain) - { + public IAsyncEnumerable ProcessGMSAReaders(byte[] groupMSAMembership, string objectDomain) { return ProcessGMSAReaders(groupMSAMembership, "", objectDomain); } @@ -529,36 +589,29 @@ public IAsyncEnumerable ProcessGMSAReaders(byte[] groupMSAMembership, strin /// /// /// - public async IAsyncEnumerable ProcessGMSAReaders(byte[] groupMSAMembership, string objectName, string objectDomain) - { - if (groupMSAMembership == null) - { + public async IAsyncEnumerable ProcessGMSAReaders(byte[] groupMSAMembership, string objectName, + string objectDomain) { + if (groupMSAMembership == null) { _log.LogDebug("GMSA bytes are null for {Name}", objectName); yield break; } var descriptor = _utils.MakeSecurityDescriptor(); - try - { + try { descriptor.SetSecurityDescriptorBinaryForm(groupMSAMembership); - } - catch (OverflowException) - { + } catch (OverflowException) { _log.LogWarning("GMSA ACL length on object {Name} exceeds allowable length. Unable to process", objectName); } - foreach (var ace in descriptor.GetAccessRules(true, true, typeof(SecurityIdentifier))) - { - if (ace == null) - { + foreach (var ace in descriptor.GetAccessRules(true, true, typeof(SecurityIdentifier))) { + if (ace == null) { _log.LogTrace("Skipping null GMSA ACE for {Name}", objectName); continue; } - if (ace.AccessControlType() == AccessControlType.Deny) - { + if (ace.AccessControlType() == AccessControlType.Deny) { _log.LogTrace("Skipping deny GMSA ACE for {Name}", objectName); continue; } @@ -566,8 +619,7 @@ public async IAsyncEnumerable ProcessGMSAReaders(byte[] groupMSAMembership, var ir = ace.IdentityReference(); var principalSid = Helpers.PreProcessSID(ir); - if (principalSid == null) - { + if (principalSid == null) { _log.LogTrace("Pre-Process excluded SID {SID} on {Name}", ir ?? "null", objectName); continue; } @@ -576,8 +628,7 @@ public async IAsyncEnumerable ProcessGMSAReaders(byte[] groupMSAMembership, var (success, resolvedPrincipal) = await _utils.ResolveIDAndType(principalSid, objectDomain); if (success) { - yield return new ACE - { + yield return new ACE { RightName = EdgeNames.ReadGMSAPassword, PrincipalType = resolvedPrincipal.ObjectType, PrincipalSID = resolvedPrincipal.ObjectIdentifier, diff --git a/src/CommonLib/Processors/ComputerAvailability.cs b/src/CommonLib/Processors/ComputerAvailability.cs index 8159bb6c..bc30d2bf 100644 --- a/src/CommonLib/Processors/ComputerAvailability.cs +++ b/src/CommonLib/Processors/ComputerAvailability.cs @@ -47,13 +47,14 @@ public ComputerAvailability(PortScanner scanner, int timeout = 500, int computer /// /// /// - public Task IsComputerAvailable(ResolvedSearchResult result, ISearchResultEntry entry) + public Task IsComputerAvailable(ResolvedSearchResult result, IDirectoryObject entry) { var name = result.DisplayName; var os = entry.GetProperty(LDAPProperties.OperatingSystem); var pwdlastset = entry.GetProperty(LDAPProperties.PasswordLastSet); - - return IsComputerAvailable(name, os, pwdlastset); + var lastLogon = entry.GetProperty(LDAPProperties.LastLogonTimestamp); + + return IsComputerAvailable(name, os, pwdlastset, lastLogon); } /// @@ -65,9 +66,10 @@ public Task IsComputerAvailable(ResolvedSearchResult result, ISe /// The computer to check availability for /// The LDAP operatingsystem attribute value /// The LDAP pwdlastset attribute value + /// The LDAP lastlogontimestamp attribute value /// A ComputerStatus object that represents the availability of the computer public async Task IsComputerAvailable(string computerName, string operatingSystem, - string pwdLastSet) + string pwdLastSet, string lastLogon) { if (operatingSystem != null && !operatingSystem.StartsWith("Windows", StringComparison.OrdinalIgnoreCase)) { @@ -86,28 +88,22 @@ await SendComputerStatus(new CSVComputerStatus }; } - if (!_skipPasswordCheck) + if (!_skipPasswordCheck && !IsComputerActive(pwdLastSet, lastLogon)) { - var passwordLastSet = Helpers.ConvertLdapTimeToLong(pwdLastSet); - var threshold = DateTime.Now.AddDays(_computerExpiryDays * -1).ToFileTimeUtc(); - - if (passwordLastSet < threshold) + _log.LogDebug( + "{ComputerName} is not available because password last set and lastlogontimestamp are out of range", + computerName); + await SendComputerStatus(new CSVComputerStatus + { + Status = ComputerStatus.NotActive, + Task = "ComputerAvailability", + ComputerName = computerName + }); + return new ComputerStatus { - _log.LogDebug( - "{ComputerName} is not available because password last set {PwdLastSet} is out of range", - computerName, passwordLastSet); - await SendComputerStatus(new CSVComputerStatus - { - Status = ComputerStatus.OldPwd, - Task = "ComputerAvailability", - ComputerName = computerName - }); - return new ComputerStatus - { - Connectable = false, - Error = ComputerStatus.OldPwd - }; - } + Connectable = false, + Error = ComputerStatus.NotActive + }; } if (_skipPortScan) @@ -150,6 +146,20 @@ await SendComputerStatus(new CSVComputerStatus }; } + /// + /// Checks if a computer's passwordlastset/lastlogontimestamp attributes are within a certain range + /// + /// + /// + /// + private bool IsComputerActive(string pwdLastSet, string lastLogonTimestamp) { + var passwordLastSet = Helpers.ConvertLdapTimeToLong(pwdLastSet); + var lastLogonTimeStamp = Helpers.ConvertLdapTimeToLong(lastLogonTimestamp); + var threshold = DateTime.Now.AddDays(_computerExpiryDays * -1).ToFileTimeUtc(); + + return passwordLastSet >= threshold || lastLogonTimeStamp >= threshold; + } + private async Task SendComputerStatus(CSVComputerStatus status) { if (ComputerStatusEvent is not null) await ComputerStatusEvent.Invoke(status); diff --git a/src/CommonLib/Processors/ContainerProcessor.cs b/src/CommonLib/Processors/ContainerProcessor.cs index 79022dc9..267abd7f 100644 --- a/src/CommonLib/Processors/ContainerProcessor.cs +++ b/src/CommonLib/Processors/ContainerProcessor.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; using System.DirectoryServices.Protocols; +using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.Logging; +using SharpHoundCommonLib.DirectoryObjects; using SharpHoundCommonLib.Enums; using SharpHoundCommonLib.LDAPQueries; using SharpHoundCommonLib.OutputTypes; @@ -35,9 +37,13 @@ private static bool IsDistinguishedNameFiltered(string distinguishedName) /// /// /// - public async Task<(bool Success, TypedPrincipal principal)> GetContainingObject(ISearchResultEntry entry) + public async Task<(bool Success, TypedPrincipal principal)> GetContainingObject(IDirectoryObject entry) { - return await GetContainingObject(entry.DistinguishedName); + if (entry.TryGetDistinguishedName(out var dn)) { + return await GetContainingObject(dn); + } + + return (false, default); } /// @@ -71,12 +77,14 @@ private static bool IsDistinguishedNameFiltered(string distinguishedName) /// /// public IAsyncEnumerable GetContainerChildObjects(ResolvedSearchResult result, - ISearchResultEntry entry) + IDirectoryObject entry) { var name = result.DisplayName; - var dn = entry.DistinguishedName; - - return GetContainerChildObjects(dn, name); + if (entry.TryGetDistinguishedName(out var dn)) { + return GetContainerChildObjects(dn, name); + } + + return AsyncEnumerable.Empty(); } /// @@ -87,7 +95,7 @@ public IAsyncEnumerable GetContainerChildObjects(ResolvedSearchR /// public async IAsyncEnumerable GetContainerChildObjects(string distinguishedName, string containerName = "") { - var filter = new LDAPFilter().AddComputers().AddUsers().AddGroups().AddOUs().AddContainers(); + var filter = new LdapFilter().AddComputers().AddUsers().AddGroups().AddOUs().AddContainers(); filter.AddCertificateAuthorities().AddCertificateTemplates().AddEnterpriseCertificationAuthorities(); await foreach (var childEntryResult in _utils.Query(new LdapQueryParameters { DomainName = Helpers.DistinguishedNameToDomain(distinguishedName), @@ -102,17 +110,13 @@ public async IAsyncEnumerable GetContainerChildObjects(string di } var childEntry = childEntryResult.Value; - var dn = childEntry.DistinguishedName; - if (IsDistinguishedNameFiltered(dn)) - { + if (!childEntry.TryGetDistinguishedName(out var dn) || IsDistinguishedNameFiltered(dn)) { _log.LogTrace("Skipping filtered child {Child} for {Container}", dn, containerName); continue; } - var id = childEntry.GetObjectIdentifier(); - if (id == null) - { - _log.LogTrace("Got null ID for {ChildDN} under {Container}", childEntry.DistinguishedName, + if (!childEntry.GetObjectIdentifier(out var id)) { + _log.LogTrace("Got null ID for {ChildDN} under {Container}", dn, containerName); continue; } @@ -124,11 +128,13 @@ public async IAsyncEnumerable GetContainerChildObjects(string di } } - public IAsyncEnumerable ReadContainerGPLinks(ResolvedSearchResult result, ISearchResultEntry entry) + public IAsyncEnumerable ReadContainerGPLinks(ResolvedSearchResult result, IDirectoryObject entry) { - var links = entry.GetProperty(LDAPProperties.GPLink); + if (entry.TryGetProperty(LDAPProperties.GPLink, out var links)) { + return ReadContainerGPLinks(links); + } - return ReadContainerGPLinks(links); + return AsyncEnumerable.Empty(); } /// diff --git a/src/CommonLib/Processors/DomainTrustProcessor.cs b/src/CommonLib/Processors/DomainTrustProcessor.cs index 7f64b987..d9c9d3bc 100644 --- a/src/CommonLib/Processors/DomainTrustProcessor.cs +++ b/src/CommonLib/Processors/DomainTrustProcessor.cs @@ -38,9 +38,7 @@ public async IAsyncEnumerable EnumerateDomainTrusts(string domain) var entry = result.Value; var trust = new DomainTrust(); - var targetSidBytes = entry.GetByteProperty(LDAPProperties.SecurityIdentifier); - if (targetSidBytes == null || targetSidBytes.Length == 0) - { + if (!entry.TryGetByteProperty(LDAPProperties.SecurityIdentifier, out var targetSidBytes) || targetSidBytes.Length == 0) { _log.LogTrace("Trust sid is null or empty for target: {Domain}", domain); continue; } @@ -58,33 +56,26 @@ public async IAsyncEnumerable EnumerateDomainTrusts(string domain) trust.TargetDomainSid = sid; - if (int.TryParse(entry.GetProperty(LDAPProperties.TrustDirection), out var td)) - { - trust.TrustDirection = (TrustDirection) td; - } - else - { + if (!entry.TryGetIntProperty(LDAPProperties.TrustDirection, out var td)) { _log.LogTrace("Failed to convert trustdirection for target: {Domain}", domain); continue; } - + trust.TrustDirection = (TrustDirection) td; + TrustAttributes attributes; - if (int.TryParse(entry.GetProperty(LDAPProperties.TrustAttributes), out var ta)) - { - attributes = (TrustAttributes) ta; - } - else - { + if (!entry.TryGetIntProperty(LDAPProperties.TrustAttributes, out var ta)) { _log.LogTrace("Failed to convert trustattributes for target: {Domain}", domain); continue; } + + attributes = (TrustAttributes) ta; trust.IsTransitive = !attributes.HasFlag(TrustAttributes.NonTransitive); - var name = entry.GetProperty(LDAPProperties.CanonicalName)?.ToUpper(); - if (name != null) - trust.TargetDomainName = name; + if (entry.TryGetProperty(LDAPProperties.CanonicalName, out var cn)) { + trust.TargetDomainName = cn.ToUpper(); + } trust.SidFilteringEnabled = attributes.HasFlag(TrustAttributes.FilterSids); trust.TrustType = TrustAttributesToType(attributes); diff --git a/src/CommonLib/Processors/GPOLocalGroupProcessor.cs b/src/CommonLib/Processors/GPOLocalGroupProcessor.cs index 040f1a69..eadf1e54 100644 --- a/src/CommonLib/Processors/GPOLocalGroupProcessor.cs +++ b/src/CommonLib/Processors/GPOLocalGroupProcessor.cs @@ -49,10 +49,12 @@ public GPOLocalGroupProcessor(ILdapUtils utils, ILogger log = null) { _log = log ?? Logging.LogProvider.CreateLogger("GPOLocalGroupProc"); } - public Task ReadGPOLocalGroups(ISearchResultEntry entry) { - var links = entry.GetProperty(LDAPProperties.GPLink); - var dn = entry.DistinguishedName; - return ReadGPOLocalGroups(links, dn); + public Task ReadGPOLocalGroups(IDirectoryObject entry) { + if (entry.TryGetProperty(LDAPProperties.GPLink, out var links) && entry.TryGetDistinguishedName(out var dn)) { + return ReadGPOLocalGroups(links, dn); + } + + return default; } public async Task ReadGPOLocalGroups(string gpLink, string distinguishedName) { @@ -65,7 +67,7 @@ public async Task ReadGPOLocalGroups(string gpLink, string // Its cheaper to fetch the affected computers from LDAP first and then process the GPLinks var affectedComputers = new List(); await foreach (var result in _utils.Query(new LdapQueryParameters() { - LDAPFilter = new LDAPFilter().AddComputersNoMSAs().GetFilter(), + LDAPFilter = new LdapFilter().AddComputersNoMSAs().GetFilter(), Attributes = CommonProperties.ObjectSID, SearchBase = distinguishedName })) { @@ -74,8 +76,7 @@ public async Task ReadGPOLocalGroups(string gpLink, string } var entry = result.Value; - var sid = entry.GetSid(); - if (string.IsNullOrWhiteSpace(sid)) { + if (!entry.TryGetSecurityIdentifier(out var sid)) { continue; } @@ -115,18 +116,17 @@ public async Task ReadGPOLocalGroups(string gpLink, string var gpoDomain = Helpers.DistinguishedNameToDomain(linkDn); var result = await _utils.Query(new LdapQueryParameters() { - LDAPFilter = new LDAPFilter().AddAllObjects().GetFilter(), + LDAPFilter = new LdapFilter().AddAllObjects().GetFilter(), SearchScope = SearchScope.Base, Attributes = CommonProperties.GPCFileSysPath, SearchBase = linkDn - }).DefaultIfEmpty(null).FirstOrDefaultAsync(); + }).DefaultIfEmpty(LdapResult.Fail()).FirstOrDefaultAsync(); - if (result is not { IsSuccess: true }) { + if (!result.IsSuccess) { continue; } - var filePath = result.Value.GetProperty(LDAPProperties.GPCFileSYSPath); - if (string.IsNullOrWhiteSpace(filePath)) { + if (!result.Value.TryGetProperty(LDAPProperties.GPCFileSYSPath, out var filePath)) { GpoActionCache.TryAdd(linkDn, actions); continue; } diff --git a/src/CommonLib/Processors/GroupProcessor.cs b/src/CommonLib/Processors/GroupProcessor.cs index bfcbbbb7..7fc5a2ce 100644 --- a/src/CommonLib/Processors/GroupProcessor.cs +++ b/src/CommonLib/Processors/GroupProcessor.cs @@ -19,13 +19,13 @@ public GroupProcessor(ILdapUtils utils, ILogger log = null) _log = log ?? Logging.LogProvider.CreateLogger("GroupProc"); } - public IAsyncEnumerable ReadGroupMembers(ResolvedSearchResult result, ISearchResultEntry entry) + public IAsyncEnumerable ReadGroupMembers(ResolvedSearchResult result, IDirectoryObject entry) { - var members = entry.GetArrayProperty(LDAPProperties.Members); - var name = result.DisplayName; - var dn = entry.DistinguishedName; - - return ReadGroupMembers(dn, members, name); + if (entry.TryGetArrayProperty(LDAPProperties.Members, out var members) && entry.TryGetDistinguishedName(out var dn)) { + return ReadGroupMembers(dn, members, result.DisplayName); + } + + return AsyncEnumerable.Empty(); } /// diff --git a/src/CommonLib/Processors/LDAPPropertyProcessor.cs b/src/CommonLib/Processors/LdapPropertyProcessor.cs similarity index 74% rename from src/CommonLib/Processors/LDAPPropertyProcessor.cs rename to src/CommonLib/Processors/LdapPropertyProcessor.cs index 9437fb15..c9dd882f 100644 --- a/src/CommonLib/Processors/LDAPPropertyProcessor.cs +++ b/src/CommonLib/Processors/LdapPropertyProcessor.cs @@ -11,11 +11,10 @@ using SharpHoundCommonLib.Enums; using SharpHoundCommonLib.LDAPQueries; using SharpHoundCommonLib.OutputTypes; +// ReSharper disable StringLiteralTypo -namespace SharpHoundCommonLib.Processors -{ - public class LDAPPropertyProcessor - { +namespace SharpHoundCommonLib.Processors { + public class LdapPropertyProcessor { private static readonly string[] ReservedAttributes = CommonProperties.TypeResolutionProps .Concat(CommonProperties.BaseQueryProps).Concat(CommonProperties.GroupResolutionProps) .Concat(CommonProperties.ComputerMethodProps).Concat(CommonProperties.ACLProps) @@ -25,22 +24,21 @@ public class LDAPPropertyProcessor private readonly ILdapUtils _utils; - public LDAPPropertyProcessor(ILdapUtils utils) - { + public LdapPropertyProcessor(ILdapUtils utils) { _utils = utils; } - private static Dictionary GetCommonProps(ISearchResultEntry entry) - { - return new Dictionary - { - { - "description", entry.GetProperty(LDAPProperties.Description) - }, - { - "whencreated", Helpers.ConvertTimestampToUnixEpoch(entry.GetProperty(LDAPProperties.WhenCreated)) - } - }; + private static Dictionary GetCommonProps(IDirectoryObject entry) { + var ret = new Dictionary(); + if (entry.TryGetProperty(LDAPProperties.Description, out var description)) { + ret["description"] = description; + } + + if (entry.TryGetProperty(LDAPProperties.WhenCreated, out var wc)) { + ret["whencreated"] = Helpers.ConvertTimestampToUnixEpoch(wc); + } + + return ret; } /// @@ -48,13 +46,14 @@ private static Dictionary GetCommonProps(ISearchResultEntry entr /// /// /// - public static Dictionary ReadDomainProperties(ISearchResultEntry entry) - { + public static Dictionary ReadDomainProperties(IDirectoryObject entry) { var props = GetCommonProps(entry); - if (!int.TryParse(entry.GetProperty(LDAPProperties.DomainFunctionalLevel), out var level)) level = -1; + if (!entry.TryGetIntProperty(LDAPProperties.DomainFunctionalLevel, out var functionalLevel)) { + functionalLevel = -1; + } - props.Add("functionallevel", FunctionalLevelToString(level)); + props.Add("functionallevel", FunctionalLevelToString(functionalLevel)); return props; } @@ -64,10 +63,8 @@ public static Dictionary ReadDomainProperties(ISearchResultEntry /// /// /// - public static string FunctionalLevelToString(int level) - { - var functionalLevel = level switch - { + public static string FunctionalLevelToString(int level) { + var functionalLevel = level switch { 0 => "2000 Mixed/Native", 1 => "2003 Interim", 2 => "2003", @@ -87,10 +84,10 @@ public static string FunctionalLevelToString(int level) /// /// /// - public static Dictionary ReadGPOProperties(ISearchResultEntry entry) - { + public static Dictionary ReadGPOProperties(IDirectoryObject entry) { var props = GetCommonProps(entry); - props.Add("gpcpath", entry.GetProperty(LDAPProperties.GPCFileSYSPath)?.ToUpper()); + entry.TryGetProperty(LDAPProperties.GPCFileSYSPath, out var path); + props.Add("gpcpath", path.ToUpper()); return props; } @@ -99,8 +96,7 @@ public static Dictionary ReadGPOProperties(ISearchResultEntry en /// /// /// - public static Dictionary ReadOUProperties(ISearchResultEntry entry) - { + public static Dictionary ReadOUProperties(IDirectoryObject entry) { var props = GetCommonProps(entry); return props; } @@ -110,21 +106,10 @@ public static Dictionary ReadOUProperties(ISearchResultEntry ent /// /// /// - public static Dictionary ReadGroupProperties(ISearchResultEntry entry) - { + public static Dictionary ReadGroupProperties(IDirectoryObject entry) { var props = GetCommonProps(entry); - - var ac = entry.GetProperty(LDAPProperties.AdminCount); - if (ac != null) - { - var a = int.Parse(ac); - props.Add("admincount", a != 0); - } - else - { - props.Add("admincount", false); - } - + entry.TryGetIntProperty(LDAPProperties.AdminCount, out var ac); + props.Add("admincount", ac != 0); return props; } @@ -133,27 +118,29 @@ public static Dictionary ReadGroupProperties(ISearchResultEntry /// /// /// - public static Dictionary ReadContainerProperties(ISearchResultEntry entry) - { + public static Dictionary ReadContainerProperties(IDirectoryObject entry) { var props = GetCommonProps(entry); return props; } + public Task + ReadUserProperties(IDirectoryObject entry, ResolvedSearchResult searchResult) { + return ReadUserProperties(entry, searchResult.Domain); + } + /// /// Reads specific LDAP properties related to Users /// /// + /// /// - public async Task ReadUserProperties(ISearchResultEntry entry) - { + public async Task ReadUserProperties(IDirectoryObject entry, string domain) { var userProps = new UserProperties(); var props = GetCommonProps(entry); var uacFlags = (UacFlags)0; - var uac = entry.GetProperty(LDAPProperties.UserAccountControl); - if (int.TryParse(uac, out var flag)) - { - uacFlags = (UacFlags)flag; + if (entry.TryGetIntProperty(LDAPProperties.UserAccountControl, out var uac)) { + uacFlags = (UacFlags)uac; } props.Add("sensitive", uacFlags.HasFlag(UacFlags.NotDelegated)); @@ -164,23 +151,18 @@ public async Task ReadUserProperties(ISearchResultEntry entry) props.Add("enabled", !uacFlags.HasFlag(UacFlags.AccountDisable)); props.Add("trustedtoauth", uacFlags.HasFlag(UacFlags.TrustedToAuthForDelegation)); - var domain = Helpers.DistinguishedNameToDomain(entry.DistinguishedName); - var comps = new List(); - if (uacFlags.HasFlag(UacFlags.TrustedToAuthForDelegation)) - { - var delegates = entry.GetArrayProperty(LDAPProperties.AllowedToDelegateTo); + if (uacFlags.HasFlag(UacFlags.TrustedToAuthForDelegation) && + entry.TryGetArrayProperty(LDAPProperties.AllowedToDelegateTo, out var delegates)) { props.Add("allowedtodelegate", delegates); - foreach (var d in delegates) - { + foreach (var d in delegates) { if (d == null) continue; var resolvedHost = await _utils.ResolveHostToSid(d, domain); if (resolvedHost.Success && resolvedHost.SecurityIdentifier.Contains("S-1")) - comps.Add(new TypedPrincipal - { + comps.Add(new TypedPrincipal { ObjectIdentifier = resolvedHost.SecurityIdentifier, ObjectType = Label.Computer }); @@ -189,12 +171,24 @@ public async Task ReadUserProperties(ISearchResultEntry entry) userProps.AllowedToDelegate = comps.Distinct().ToArray(); - props.Add("lastlogon", Helpers.ConvertFileTimeToUnixEpoch(entry.GetProperty(LDAPProperties.LastLogon))); - props.Add("lastlogontimestamp", - Helpers.ConvertFileTimeToUnixEpoch(entry.GetProperty(LDAPProperties.LastLogonTimestamp))); + if (!entry.TryGetProperty(LDAPProperties.LastLogon, out var lastLogon)) { + lastLogon = null; + } + + props.Add("lastlogon", Helpers.ConvertFileTimeToUnixEpoch(lastLogon)); + + if (!entry.TryGetProperty(LDAPProperties.LastLogonTimestamp, out var lastLogonTimeStamp)) { + lastLogonTimeStamp = null; + } + + props.Add("lastlogontimestamp", Helpers.ConvertFileTimeToUnixEpoch(lastLogonTimeStamp)); + + if (!entry.TryGetProperty(LDAPProperties.PasswordLastSet, out var passwordLastSet)) { + passwordLastSet = null; + } props.Add("pwdlastset", - Helpers.ConvertFileTimeToUnixEpoch(entry.GetProperty(LDAPProperties.PasswordLastSet))); - var spn = entry.GetArrayProperty(LDAPProperties.ServicePrincipalNames); + Helpers.ConvertFileTimeToUnixEpoch(passwordLastSet)); + entry.TryGetArrayProperty(LDAPProperties.ServicePrincipalNames, out var spn); props.Add("serviceprincipalnames", spn); props.Add("hasspn", spn.Length > 0); props.Add("displayname", entry.GetProperty(LDAPProperties.DisplayName)); @@ -207,31 +201,17 @@ public async Task ReadUserProperties(ISearchResultEntry entry) props.Add("sfupassword", entry.GetProperty(LDAPProperties.MsSFU30Password)); props.Add("logonscript", entry.GetProperty(LDAPProperties.ScriptPath)); - var ac = entry.GetProperty(LDAPProperties.AdminCount); - if (ac != null) - { - if (int.TryParse(ac, out var parsed)) - props.Add("admincount", parsed != 0); - else - props.Add("admincount", false); - } - else - { - props.Add("admincount", false); - } + entry.TryGetIntProperty(LDAPProperties.AdminCount, out var ac); + props.Add("admincount", ac != 0); - var sh = entry.GetByteArrayProperty(LDAPProperties.SIDHistory); + entry.TryGetByteArrayProperty(LDAPProperties.SIDHistory, out var sh); var sidHistoryList = new List(); var sidHistoryPrincipals = new List(); - foreach (var sid in sh) - { + foreach (var sid in sh) { string sSid; - try - { + try { sSid = new SecurityIdentifier(sid, 0).Value; - } - catch - { + } catch { continue; } @@ -250,44 +230,43 @@ public async Task ReadUserProperties(ISearchResultEntry entry) return userProps; } + public Task ReadComputerProperties(IDirectoryObject entry, + ResolvedSearchResult searchResult) { + return ReadComputerProperties(entry, searchResult.Domain); + } + /// /// Reads specific LDAP properties related to Computers /// /// + /// /// - public async Task ReadComputerProperties(ISearchResultEntry entry) - { + public async Task ReadComputerProperties(IDirectoryObject entry, string domain) { var compProps = new ComputerProperties(); var props = GetCommonProps(entry); var flags = (UacFlags)0; - var uac = entry.GetProperty(LDAPProperties.UserAccountControl); - if (int.TryParse(uac, out var flag)) - { - flags = (UacFlags)flag; + if (entry.TryGetIntProperty(LDAPProperties.UserAccountControl, out var uac)) { + flags = (UacFlags)uac; } props.Add("enabled", !flags.HasFlag(UacFlags.AccountDisable)); props.Add("unconstraineddelegation", flags.HasFlag(UacFlags.TrustedForDelegation)); props.Add("trustedtoauth", flags.HasFlag(UacFlags.TrustedToAuthForDelegation)); props.Add("isdc", flags.HasFlag(UacFlags.ServerTrustAccount)); - - var domain = Helpers.DistinguishedNameToDomain(entry.DistinguishedName); - + var comps = new List(); - if (flags.HasFlag(UacFlags.TrustedToAuthForDelegation)) - { - var delegates = entry.GetArrayProperty(LDAPProperties.AllowedToDelegateTo); + if (flags.HasFlag(UacFlags.TrustedToAuthForDelegation) && + entry.TryGetArrayProperty(LDAPProperties.AllowedToDelegateTo, out var delegates)) { props.Add("allowedtodelegate", delegates); - foreach (var d in delegates) - { - var hname = d.Contains("/") ? d.Split('/')[1] : d; - hname = hname.Split(':')[0]; - var resolvedHost = await _utils.ResolveHostToSid(hname, domain); + foreach (var d in delegates) { + if (d == null) + continue; + + var resolvedHost = await _utils.ResolveHostToSid(d, domain); if (resolvedHost.Success && resolvedHost.SecurityIdentifier.Contains("S-1")) - comps.Add(new TypedPrincipal - { + comps.Add(new TypedPrincipal { ObjectIdentifier = resolvedHost.SecurityIdentifier, ObjectType = Label.Computer }); @@ -297,13 +276,10 @@ public async Task ReadComputerProperties(ISearchResultEntry compProps.AllowedToDelegate = comps.Distinct().ToArray(); var allowedToActPrincipals = new List(); - var rawAllowedToAct = entry.GetByteProperty(LDAPProperties.AllowedToActOnBehalfOfOtherIdentity); - if (rawAllowedToAct != null) - { + if (entry.TryGetByteProperty(LDAPProperties.AllowedToActOnBehalfOfOtherIdentity, out var rawAllowedToAct)) { var sd = _utils.MakeSecurityDescriptor(); sd.SetSecurityDescriptorBinaryForm(rawAllowedToAct, AccessControlSections.Access); - foreach (var rule in sd.GetAccessRules(true, true, typeof(SecurityIdentifier))) - { + foreach (var rule in sd.GetAccessRules(true, true, typeof(SecurityIdentifier))) { if (await _utils.ResolveIDAndType(rule.IdentityReference(), domain) is (true, var res)) allowedToActPrincipals.Add(res); } @@ -316,7 +292,8 @@ public async Task ReadComputerProperties(ISearchResultEntry Helpers.ConvertFileTimeToUnixEpoch(entry.GetProperty(LDAPProperties.LastLogonTimestamp))); props.Add("pwdlastset", Helpers.ConvertFileTimeToUnixEpoch(entry.GetProperty(LDAPProperties.PasswordLastSet))); - props.Add("serviceprincipalnames", entry.GetArrayProperty(LDAPProperties.ServicePrincipalNames)); + entry.TryGetArrayProperty(LDAPProperties.ServicePrincipalNames, out var spn); + props.Add("serviceprincipalnames", spn); props.Add("email", entry.GetProperty(LDAPProperties.Email)); var os = entry.GetProperty(LDAPProperties.OperatingSystem); var sp = entry.GetProperty(LDAPProperties.ServicePack); @@ -325,18 +302,14 @@ public async Task ReadComputerProperties(ISearchResultEntry props.Add("operatingsystem", os); - var sh = entry.GetByteArrayProperty(LDAPProperties.SIDHistory); + entry.TryGetByteArrayProperty(LDAPProperties.SIDHistory, out var sh); var sidHistoryList = new List(); var sidHistoryPrincipals = new List(); - foreach (var sid in sh) - { + foreach (var sid in sh) { string sSid; - try - { + try { sSid = new SecurityIdentifier(sid, 0).Value; - } - catch - { + } catch { continue; } @@ -350,12 +323,9 @@ public async Task ReadComputerProperties(ISearchResultEntry props.Add("sidhistory", sidHistoryList.ToArray()); - var hsa = entry.GetArrayProperty(LDAPProperties.HostServiceAccount); var smsaPrincipals = new List(); - if (hsa != null) - { - foreach (var dn in hsa) - { + if (entry.TryGetArrayProperty(LDAPProperties.HostServiceAccount, out var hsa)) { + foreach (var dn in hsa) { if (await _utils.ResolveDistinguishedName(dn) is (true, var resolvedPrincipal)) smsaPrincipals.Add(resolvedPrincipal); } @@ -373,14 +343,11 @@ public async Task ReadComputerProperties(ISearchResultEntry /// /// /// Returns a dictionary with the common properties of the RootCA - public static Dictionary ReadRootCAProperties(ISearchResultEntry entry) - { + public static Dictionary ReadRootCAProperties(IDirectoryObject entry) { var props = GetCommonProps(entry); // Certificate - var rawCertificate = entry.GetByteProperty(LDAPProperties.CACertificate); - if (rawCertificate != null) - { + if (entry.TryGetByteProperty(LDAPProperties.CACertificate, out var rawCertificate)) { var cert = new ParsedCertificate(rawCertificate); props.Add("certthumbprint", cert.Thumbprint); props.Add("certname", cert.Name); @@ -388,7 +355,7 @@ public static Dictionary ReadRootCAProperties(ISearchResultEntry props.Add("hasbasicconstraints", cert.HasBasicConstraints); props.Add("basicconstraintpathlength", cert.BasicConstraintPathLength); } - + return props; } @@ -397,19 +364,16 @@ public static Dictionary ReadRootCAProperties(ISearchResultEntry /// /// /// Returns a dictionary with the common properties and the crosscertificatepair property of the AICA - public static Dictionary ReadAIACAProperties(ISearchResultEntry entry) - { + public static Dictionary ReadAIACAProperties(IDirectoryObject entry) { var props = GetCommonProps(entry); - var crossCertificatePair = entry.GetByteArrayProperty((LDAPProperties.CrossCertificatePair)); + entry.TryGetByteArrayProperty(LDAPProperties.CrossCertificatePair, out var crossCertificatePair); var hasCrossCertificatePair = crossCertificatePair.Length > 0; props.Add("crosscertificatepair", crossCertificatePair); props.Add("hascrosscertificatepair", hasCrossCertificatePair); // Certificate - var rawCertificate = entry.GetByteProperty(LDAPProperties.CACertificate); - if (rawCertificate != null) - { + if (entry.TryGetByteProperty(LDAPProperties.CACertificate, out var rawCertificate)) { var cert = new ParsedCertificate(rawCertificate); props.Add("certthumbprint", cert.Thumbprint); props.Add("certname", cert.Name); @@ -421,17 +385,14 @@ public static Dictionary ReadAIACAProperties(ISearchResultEntry return props; } - public static Dictionary ReadEnterpriseCAProperties(ISearchResultEntry entry) - { + public static Dictionary ReadEnterpriseCAProperties(IDirectoryObject entry) { var props = GetCommonProps(entry); - if (entry.GetIntProperty("flags", out var flags)) props.Add("flags", (PKICertificateAuthorityFlags)flags); + if (entry.TryGetIntProperty("flags", out var flags)) props.Add("flags", (PKICertificateAuthorityFlags)flags); props.Add("caname", entry.GetProperty(LDAPProperties.Name)); props.Add("dnshostname", entry.GetProperty(LDAPProperties.DNSHostName)); // Certificate - var rawCertificate = entry.GetByteProperty(LDAPProperties.CACertificate); - if (rawCertificate != null) - { + if (entry.TryGetByteProperty(LDAPProperties.CACertificate, out var rawCertificate)) { var cert = new ParsedCertificate(rawCertificate); props.Add("certthumbprint", cert.Thumbprint); props.Add("certname", cert.Name); @@ -448,8 +409,7 @@ public static Dictionary ReadEnterpriseCAProperties(ISearchResul /// /// /// Returns a dictionary with the common properties of the NTAuthStore - public static Dictionary ReadNTAuthStoreProperties(ISearchResultEntry entry) - { + public static Dictionary ReadNTAuthStoreProperties(IDirectoryObject entry) { var props = GetCommonProps(entry); return props; } @@ -459,21 +419,19 @@ public static Dictionary ReadNTAuthStoreProperties(ISearchResult /// /// /// Returns a dictionary associated with the CertTemplate properties that were read - public static Dictionary ReadCertTemplateProperties(ISearchResultEntry entry) - { + public static Dictionary ReadCertTemplateProperties(IDirectoryObject entry) { var props = GetCommonProps(entry); props.Add("validityperiod", ConvertPKIPeriod(entry.GetByteProperty(LDAPProperties.PKIExpirationPeriod))); props.Add("renewalperiod", ConvertPKIPeriod(entry.GetByteProperty(LDAPProperties.PKIOverlappedPeriod))); - if (entry.GetIntProperty(LDAPProperties.TemplateSchemaVersion, out var schemaVersion)) + if (entry.TryGetIntProperty(LDAPProperties.TemplateSchemaVersion, out var schemaVersion)) props.Add("schemaversion", schemaVersion); props.Add("displayname", entry.GetProperty(LDAPProperties.DisplayName)); props.Add("oid", entry.GetProperty(LDAPProperties.CertTemplateOID)); - if (entry.GetIntProperty(LDAPProperties.PKIEnrollmentFlag, out var enrollmentFlagsRaw)) - { + if (entry.TryGetIntProperty(LDAPProperties.PKIEnrollmentFlag, out var enrollmentFlagsRaw)) { var enrollmentFlags = (PKIEnrollmentFlag)enrollmentFlagsRaw; props.Add("enrollmentflag", enrollmentFlags); @@ -481,8 +439,7 @@ public static Dictionary ReadCertTemplateProperties(ISearchResul props.Add("nosecurityextension", enrollmentFlags.HasFlag(PKIEnrollmentFlag.NO_SECURITY_EXTENSION)); } - if (entry.GetIntProperty(LDAPProperties.PKINameFlag, out var nameFlagsRaw)) - { + if (entry.TryGetIntProperty(LDAPProperties.PKINameFlag, out var nameFlagsRaw)) { var nameFlags = (PKICertificateNameFlag)nameFlagsRaw; props.Add("certificatenameflag", nameFlags); @@ -502,50 +459,51 @@ public static Dictionary ReadCertTemplateProperties(ISearchResul nameFlags.HasFlag(PKICertificateNameFlag.SUBJECT_REQUIRE_EMAIL)); } - var ekus = entry.GetArrayProperty(LDAPProperties.ExtendedKeyUsage); + entry.TryGetArrayProperty(LDAPProperties.ExtendedKeyUsage, out var ekus); props.Add("ekus", ekus); - var certificateApplicationPolicy = entry.GetArrayProperty(LDAPProperties.CertificateApplicationPolicy); + entry.TryGetArrayProperty(LDAPProperties.CertificateApplicationPolicy, out var certificateApplicationPolicy); props.Add("certificateapplicationpolicy", certificateApplicationPolicy); - var certificatePolicy = entry.GetArrayProperty(LDAPProperties.CertificatePolicy); + entry.TryGetArrayProperty(LDAPProperties.CertificatePolicy, out var certificatePolicy); props.Add("certificatepolicy", certificatePolicy); - if (entry.GetIntProperty(LDAPProperties.NumSignaturesRequired, out var authorizedSignatures)) + if (entry.TryGetIntProperty(LDAPProperties.NumSignaturesRequired, out var authorizedSignatures)) props.Add("authorizedsignatures", authorizedSignatures); var hasUseLegacyProvider = false; - if (entry.GetIntProperty(LDAPProperties.PKIPrivateKeyFlag, out var privateKeyFlagsRaw)) - { + if (entry.TryGetIntProperty(LDAPProperties.PKIPrivateKeyFlag, out var privateKeyFlagsRaw)) { var privateKeyFlags = (PKIPrivateKeyFlag)privateKeyFlagsRaw; hasUseLegacyProvider = privateKeyFlags.HasFlag(PKIPrivateKeyFlag.USE_LEGACY_PROVIDER); } - props.Add("applicationpolicies", ParseCertTemplateApplicationPolicies(entry.GetArrayProperty(LDAPProperties.ApplicationPolicies), schemaVersion, hasUseLegacyProvider)); - props.Add("issuancepolicies", entry.GetArrayProperty(LDAPProperties.IssuancePolicies)); + entry.TryGetArrayProperty(LDAPProperties.ApplicationPolicies, out var appPolicies); + + props.Add("applicationpolicies", + ParseCertTemplateApplicationPolicies(appPolicies, + schemaVersion, hasUseLegacyProvider)); + entry.TryGetArrayProperty(LDAPProperties.IssuancePolicies, out var issuancePolicies); + props.Add("issuancepolicies", issuancePolicies); // Construct effectiveekus var effectiveekus = schemaVersion == 1 & ekus.Length > 0 ? ekus : certificateApplicationPolicy; props.Add("effectiveekus", effectiveekus); // Construct authenticationenabled - var authenticationEnabled = effectiveekus.Intersect(Helpers.AuthenticationOIDs).Any() | effectiveekus.Length == 0; + var authenticationEnabled = + effectiveekus.Intersect(Helpers.AuthenticationOIDs).Any() | effectiveekus.Length == 0; props.Add("authenticationenabled", authenticationEnabled); return props; } - public async Task ReadIssuancePolicyProperties(ISearchResultEntry entry) - { + public async Task ReadIssuancePolicyProperties(IDirectoryObject entry) { var ret = new IssuancePolicyProperties(); var props = GetCommonProps(entry); props.Add("displayname", entry.GetProperty(LDAPProperties.DisplayName)); props.Add("certtemplateoid", entry.GetProperty(LDAPProperties.CertTemplateOID)); - var link = entry.GetProperty(LDAPProperties.OIDGroupLink); - if (!string.IsNullOrEmpty(link)) - { - if (await _utils.ResolveDistinguishedName(link) is (true, var linkedGroup)) - { + if (entry.TryGetProperty(LDAPProperties.OIDGroupLink, out var link)) { + if (await _utils.ResolveDistinguishedName(link) is (true, var linkedGroup)) { props.Add("oidgrouplink", linkedGroup.ObjectIdentifier); ret.GroupLink = linkedGroup; } @@ -560,28 +518,22 @@ public async Task ReadIssuancePolicyProperties(ISearch /// format using a best guess /// /// - public Dictionary ParseAllProperties(ISearchResultEntry entry) - { + public Dictionary ParseAllProperties(IDirectoryObject entry) { var props = new Dictionary(); var type = typeof(LDAPProperties); - var reserved = type.GetFields(BindingFlags.Static | BindingFlags.Public).Select(x => x.GetValue(null).ToString()).ToArray(); + var reserved = type.GetFields(BindingFlags.Static | BindingFlags.Public) + .Select(x => x.GetValue(null).ToString()).ToArray(); - foreach (var property in entry.PropertyNames()) - { - if (ReservedAttributes.Contains(property, StringComparer.OrdinalIgnoreCase)) + foreach (var property in entry.PropertyNames()) { + if (reserved.Contains(property, StringComparer.OrdinalIgnoreCase)) continue; - var collCount = entry.PropCount(property); + var collCount = entry.PropertyCount(property); if (collCount == 0) continue; - if (collCount == 1) - { - var testBytes = entry.GetByteProperty(property); - - if (testBytes == null || testBytes.Length == 0) continue; - + if (collCount == 1) { var testString = entry.GetProperty(property); if (!string.IsNullOrEmpty(testString)) @@ -589,15 +541,10 @@ public Dictionary ParseAllProperties(ISearchResultEntry entry) props.Add(property, Helpers.ConvertFileTimeToUnixEpoch(testString)); else props.Add(property, BestGuessConvert(testString)); - } - else - { - var arrBytes = entry.GetByteArrayProperty(property); - if (arrBytes.Length == 0) - continue; - - var arr = entry.GetArrayProperty(property); - if (arr.Length > 0) props.Add(property, arr.Select(BestGuessConvert).ToArray()); + } else { + if (entry.TryGetArrayProperty(property, out var arr) && arr.Length > 0) { + props.Add(property, arr.Select(BestGuessConvert).ToArray()); + } } } @@ -610,25 +557,23 @@ public Dictionary ParseAllProperties(ISearchResultEntry entry) /// /// /// - private static string[] ParseCertTemplateApplicationPolicies(string[] applicationPolicies, int schemaVersion, bool hasUseLegacyProvider) - { + private static string[] ParseCertTemplateApplicationPolicies(string[] applicationPolicies, int schemaVersion, + bool hasUseLegacyProvider) { if (applicationPolicies == null || applicationPolicies.Length == 0 || schemaVersion == 1 || schemaVersion == 2 - || (schemaVersion == 4 && hasUseLegacyProvider)) - { + || (schemaVersion == 4 && hasUseLegacyProvider)) { return applicationPolicies; - } - else - { + } else { // Format: "Name`Type`Value`Name`Type`Value`..." // (https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-crtd/c55ec697-be3f-4117-8316-8895e4399237) // Return the Value of Name = "msPKI-RA-Application-Policies" entries var entries = applicationPolicies[0].Split('`'); return Enumerable.Range(0, entries.Length / 3) .Select(i => entries.Skip(i * 3).Take(3).ToArray()) - .Where(parts => parts.Length == 3 && parts[0].Equals(LDAPProperties.ApplicationPolicies, StringComparison.OrdinalIgnoreCase)) + .Where(parts => parts.Length == 3 && parts[0].Equals(LDAPProperties.ApplicationPolicies, + StringComparison.OrdinalIgnoreCase)) .Select(parts => parts[2]) .ToArray(); } @@ -639,8 +584,7 @@ private static string[] ParseCertTemplateApplicationPolicies(string[] applicatio /// /// /// - private static object BestGuessConvert(string property) - { + private static object BestGuessConvert(string property) { //Parse boolean values if (bool.TryParse(property, out var boolResult)) return boolResult; @@ -663,56 +607,47 @@ private static object BestGuessConvert(string property) /// https://www.sysadmins.lv/blog-en/how-to-convert-pkiexirationperiod-and-pkioverlapperiod-active-directory-attributes.aspx /// /// Returns a string representing the time period associated with the input byte array in a human readable form - private static string ConvertPKIPeriod(byte[] bytes) - { + private static string ConvertPKIPeriod(byte[] bytes) { if (bytes == null || bytes.Length == 0) return "Unknown"; - try - { + try { Array.Reverse(bytes); var temp = BitConverter.ToString(bytes).Replace("-", ""); var value = Convert.ToInt64(temp, 16) * -.0000001; - if (value % 31536000 == 0 && value / 31536000 >= 1) - { + if (value % 31536000 == 0 && value / 31536000 >= 1) { if (value / 31536000 == 1) return "1 year"; return $"{value / 31536000} years"; } - if (value % 2592000 == 0 && value / 2592000 >= 1) - { + if (value % 2592000 == 0 && value / 2592000 >= 1) { if (value / 2592000 == 1) return "1 month"; return $"{value / 2592000} months"; } - if (value % 604800 == 0 && value / 604800 >= 1) - { + if (value % 604800 == 0 && value / 604800 >= 1) { if (value / 604800 == 1) return "1 week"; return $"{value / 604800} weeks"; } - if (value % 86400 == 0 && value / 86400 >= 1) - { + if (value % 86400 == 0 && value / 86400 >= 1) { if (value / 86400 == 1) return "1 day"; return $"{value / 86400} days"; } - if (value % 3600 == 0 && value / 3600 >= 1) - { + if (value % 3600 == 0 && value / 3600 >= 1) { if (value / 3600 == 1) return "1 hour"; return $"{value / 3600} hours"; } return ""; - } - catch (Exception) - { + } catch (Exception) { return "Unknown"; } } @@ -723,8 +658,7 @@ private static string ConvertPKIPeriod(byte[] bytes) [Flags] [SuppressMessage("ReSharper", "UnusedMember.Local")] [SuppressMessage("ReSharper", "InconsistentNaming")] - private enum IsTextUnicodeFlags - { + private enum IsTextUnicodeFlags { IS_TEXT_UNICODE_ASCII16 = 0x0001, IS_TEXT_UNICODE_REVERSE_ASCII16 = 0x0010, @@ -749,16 +683,14 @@ private enum IsTextUnicodeFlags } } - public class ParsedCertificate - { + public class ParsedCertificate { public string Thumbprint { get; set; } public string Name { get; set; } public string[] Chain { get; set; } = Array.Empty(); public bool HasBasicConstraints { get; set; } = false; public int BasicConstraintPathLength { get; set; } - public ParsedCertificate(byte[] rawCertificate) - { + public ParsedCertificate(byte[] rawCertificate) { var parsedCertificate = new X509Certificate2(rawCertificate); Thumbprint = parsedCertificate.Thumbprint; var name = parsedCertificate.FriendlyName; @@ -775,11 +707,9 @@ public ParsedCertificate(byte[] rawCertificate) // Extensions X509ExtensionCollection extensions = parsedCertificate.Extensions; List certificateExtensions = new List(); - foreach (X509Extension extension in extensions) - { + foreach (X509Extension extension in extensions) { CertificateExtension certificateExtension = new CertificateExtension(extension); - switch (certificateExtension.Oid.Value) - { + switch (certificateExtension.Oid.Value) { case CAExtensionTypes.BasicConstraints: X509BasicConstraintsExtension ext = (X509BasicConstraintsExtension)extension; HasBasicConstraints = ext.HasPathLengthConstraint; @@ -790,15 +720,13 @@ public ParsedCertificate(byte[] rawCertificate) } } - public class UserProperties - { + public class UserProperties { public Dictionary Props { get; set; } = new(); public TypedPrincipal[] AllowedToDelegate { get; set; } = Array.Empty(); public TypedPrincipal[] SidHistory { get; set; } = Array.Empty(); } - public class ComputerProperties - { + public class ComputerProperties { public Dictionary Props { get; set; } = new(); public TypedPrincipal[] AllowedToDelegate { get; set; } = Array.Empty(); public TypedPrincipal[] AllowedToAct { get; set; } = Array.Empty(); @@ -806,9 +734,8 @@ public class ComputerProperties public TypedPrincipal[] DumpSMSAPassword { get; set; } = Array.Empty(); } - public class IssuancePolicyProperties - { + public class IssuancePolicyProperties { public Dictionary Props { get; set; } = new(); public TypedPrincipal GroupLink { get; set; } = new TypedPrincipal(); } -} +} \ No newline at end of file diff --git a/src/CommonLib/Processors/SPNProcessors.cs b/src/CommonLib/Processors/SPNProcessors.cs index 13af5a81..57b401e6 100644 --- a/src/CommonLib/Processors/SPNProcessors.cs +++ b/src/CommonLib/Processors/SPNProcessors.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using Microsoft.Extensions.Logging; using SharpHoundCommonLib.Enums; using SharpHoundCommonLib.OutputTypes; @@ -18,13 +19,13 @@ public SPNProcessors(ILdapUtils utils, ILogger log = null) } public IAsyncEnumerable ReadSPNTargets(ResolvedSearchResult result, - ISearchResultEntry entry) + IDirectoryObject entry) { - var members = entry.GetArrayProperty(LDAPProperties.ServicePrincipalNames); - var name = result.DisplayName; - var dn = entry.DistinguishedName; - - return ReadSPNTargets(members, dn, name); + if (entry.TryGetArrayProperty(LDAPProperties.ServicePrincipalNames, out var members) && entry.TryGetDistinguishedName(out var dn)) { + return ReadSPNTargets(members, dn, result.DisplayName); + } + + return AsyncEnumerable.Empty(); } public async IAsyncEnumerable ReadSPNTargets(string[] servicePrincipalNames, diff --git a/src/CommonLib/SearchResultEntryExtensions.cs b/src/CommonLib/SearchResultEntryExtensions.cs deleted file mode 100644 index 264f6544..00000000 --- a/src/CommonLib/SearchResultEntryExtensions.cs +++ /dev/null @@ -1,233 +0,0 @@ -using System; -using System.DirectoryServices.Protocols; -using System.Linq; -using System.Security.Cryptography.X509Certificates; -using System.Security.Principal; -using System.Text; -using SharpHoundCommonLib.Enums; - -namespace SharpHoundCommonLib { - - public static class SearchResultEntryExtensions { - /// - /// Extension method to determine the BloodHound type of a SearchResultEntry using LDAP properties - /// Requires ldap properties objectsid, samaccounttype, objectclass - /// - /// - /// - public static bool GetLabel(this SearchResultEntry entry, out Label type) - { - if (!entry.GetPropertyAsInt(LDAPProperties.Flags, out var flags)) { - flags = 0; - } - - return LdapUtils.ResolveLabel(entry.GetObjectIdentifier(), entry.DistinguishedName, - entry.GetProperty(LDAPProperties.SAMAccountType), entry.GetPropertyAsArray(LDAPProperties.ObjectClass), - flags, out type); - } - - /// - /// Gets the specified property as a string from the SearchResultEntry - /// - /// - /// The LDAP name of the property you want to get - /// The string value of the property if it exists or null - public static string GetProperty(this SearchResultEntry entry, string property) - { - if (!entry.Attributes.Contains(property)) - return null; - - var collection = entry.Attributes[property]; - //Use GetValues to auto-convert to the proper type - var lookups = collection.GetValues(typeof(string)); - if (lookups.Length == 0) - return null; - - if (lookups[0] is not string prop || prop.Length == 0) - return null; - - return prop; - } - - /// - /// Get's the string representation of the "objectguid" property from the SearchResultEntry - /// - /// - /// The string representation of the object's GUID if possible, otherwise null - public static string GetGuid(this SearchResultEntry entry) - { - if (entry.Attributes.Contains(LDAPProperties.ObjectGUID)) - { - var guidBytes = entry.GetPropertyAsBytes(LDAPProperties.ObjectGUID); - - return new Guid(guidBytes).ToString().ToUpper(); - } - - return null; - } - - /// - /// Gets the "objectsid" property as a string from the SearchResultEntry - /// - /// - /// The string representation of the object's SID if possible, otherwise null - public static string GetSid(this SearchResultEntry entry) - { - if (!entry.Attributes.Contains(LDAPProperties.ObjectSID)) return null; - - object[] s; - try - { - s = entry.Attributes[LDAPProperties.ObjectSID].GetValues(typeof(byte[])); - } - catch (NotSupportedException) - { - return null; - } - - if (s.Length == 0) - return null; - - if (s[0] is not byte[] sidBytes || sidBytes.Length == 0) - return null; - - try - { - var sid = new SecurityIdentifier(sidBytes, 0); - return sid.Value.ToUpper(); - } - catch (ArgumentNullException) - { - return null; - } - } - - /// - /// Gets the specified property as a string array from the SearchResultEntry - /// - /// - /// The LDAP name of the property you want to get - /// The specified property as an array of strings if possible, else an empty array - public static string[] GetPropertyAsArray(this SearchResultEntry entry, string property) - { - if (!entry.Attributes.Contains(property)) - return Array.Empty(); - - var values = entry.Attributes[property]; - var strings = values.GetValues(typeof(string)); - - return strings is not string[] result ? Array.Empty() : result; - } - - /// - /// Gets the specified property as an array of byte arrays from the SearchResultEntry - /// Used for SIDHistory - /// - /// - /// The LDAP name of the property you want to get - /// The specified property as an array of bytes if possible, else an empty array - public static byte[][] GetPropertyAsArrayOfBytes(this SearchResultEntry entry, string property) - { - if (!entry.Attributes.Contains(property)) - return Array.Empty(); - - var values = entry.Attributes[property]; - var bytes = values.GetValues(typeof(byte[])); - - return bytes is not byte[][] result ? Array.Empty() : result; - } - - /// - /// Gets the specified property as a byte array - /// - /// - /// The LDAP name of the property you want to get - /// An array of bytes if possible, else null - public static byte[] GetPropertyAsBytes(this SearchResultEntry searchResultEntry, string property) - { - if (!searchResultEntry.Attributes.Contains(property)) - return null; - - var collection = searchResultEntry.Attributes[property]; - var lookups = collection.GetValues(typeof(byte[])); - - if (lookups.Length == 0) - return Array.Empty(); - - if (lookups[0] is not byte[] bytes || bytes.Length == 0) - return Array.Empty(); - - return bytes; - } - - /// - /// Gets the specified property as an int - /// - /// - /// - /// - /// - public static bool GetPropertyAsInt(this SearchResultEntry entry, string property, out int value) - { - var prop = entry.GetProperty(property); - if (prop != null) return int.TryParse(prop, out value); - value = 0; - return false; - } - - /// - /// Gets the specified property as an array of X509 certificates. - /// - /// - /// - /// - public static X509Certificate2[] GetPropertyAsArrayOfCertificates(this SearchResultEntry searchResultEntry, - string property) - { - if (!searchResultEntry.Attributes.Contains(property)) - return null; - - return searchResultEntry.GetPropertyAsArrayOfBytes(property).Select(x => new X509Certificate2(x)).ToArray(); - } - - - /// - /// Attempts to get the unique object identifier as used by BloodHound for the Search Result Entry. Tries to get - /// objectsid first, and then objectguid next. - /// - /// - /// String representation of the entry's object identifier or null - public static string GetObjectIdentifier(this SearchResultEntry entry) - { - return entry.GetSid() ?? entry.GetGuid(); - } - - /// - /// Checks the isDeleted LDAP property to determine if an entry has been deleted from the directory - /// - /// - /// - public static bool IsDeleted(this SearchResultEntry entry) - { - var deleted = entry.GetProperty(LDAPProperties.IsDeleted); - return bool.TryParse(deleted, out var isDeleted) && isDeleted; - } - - /// - /// Helper function to print attributes of a SearchResultEntry - /// - /// - public static string PrintEntry(this SearchResultEntry searchResultEntry) - { - var sb = new StringBuilder(); - if (searchResultEntry.Attributes.AttributeNames == null) return sb.ToString(); - foreach (var propertyName in searchResultEntry.Attributes.AttributeNames) - { - var property = propertyName.ToString(); - sb.Append(property).Append("\t").Append(searchResultEntry.GetProperty(property)).Append("\n"); - } - - return sb.ToString(); - } - } -} \ No newline at end of file diff --git a/src/CommonLib/SearchResultEntryWrapper.cs b/src/CommonLib/SearchResultEntryWrapper.cs deleted file mode 100644 index cc64c1fd..00000000 --- a/src/CommonLib/SearchResultEntryWrapper.cs +++ /dev/null @@ -1,288 +0,0 @@ -using System; -using System.Collections.Generic; -using System.DirectoryServices.Protocols; -using System.Linq; -using System.Runtime.InteropServices; -using System.Security.Cryptography.X509Certificates; -using System.Security.Principal; -using System.Threading.Tasks; -using Microsoft.Extensions.Logging; -using SharpHoundCommonLib.Enums; - -namespace SharpHoundCommonLib -{ - public interface ISearchResultEntry - { - string DistinguishedName { get; } - Task ResolveBloodHoundInfo(); - string GetProperty(string propertyName); - byte[] GetByteProperty(string propertyName); - string[] GetArrayProperty(string propertyName); - byte[][] GetByteArrayProperty(string propertyName); - bool GetIntProperty(string propertyName, out int value); - X509Certificate2[] GetCertificateArrayProperty(string propertyName); - string GetObjectIdentifier(); - bool IsDeleted(); - Label GetLabel(); - string GetSid(); - string GetGuid(); - int PropCount(string prop); - IEnumerable PropertyNames(); - bool IsMSA(); - bool IsGMSA(); - bool HasLAPS(); - } - - public class SearchResultEntryWrapper : ISearchResultEntry - { - private const string GMSAClass = "msds-groupmanagedserviceaccount"; - private const string MSAClass = "msds-managedserviceaccount"; - private readonly SearchResultEntry _entry; - private readonly ILogger _log; - private readonly ILdapUtils _utils; - - public SearchResultEntryWrapper(SearchResultEntry entry, ILdapUtils utils = null, ILogger log = null) - { - _entry = entry; - _utils = utils ?? new LdapUtils(); - _log = log ?? Logging.LogProvider.CreateLogger("SearchResultWrapper"); - } - - public string DistinguishedName => _entry.DistinguishedName; - - public async Task ResolveBloodHoundInfo() - { - var res = new ResolvedSearchResult(); - - var objectId = GetObjectIdentifier(); - if (objectId == null) - { - _log.LogWarning("ObjectIdentifier is null for {DN}", DistinguishedName); - return null; - } - - var uac = _entry.GetProperty(LDAPProperties.UserAccountControl); - if (int.TryParse(uac, out var flag)) - { - var flags = (UacFlags) flag; - if (flags.HasFlag(UacFlags.ServerTrustAccount)) - { - _log.LogTrace("Marked {SID} as a domain controller", objectId); - res.IsDomainController = true; - _utils.AddDomainController(objectId); - } - } - - //Try to resolve the domain - var distinguishedName = DistinguishedName; - string itemDomain; - if (distinguishedName == null) - { - if (objectId.StartsWith("S-1-") && await _utils.GetDomainNameFromSid(objectId) is (true, var domain)) - { - itemDomain = domain; - } - else - { - _log.LogWarning("Failed to resolve domain for {ObjectID}", objectId); - return null; - } - } - else - { - itemDomain = Helpers.DistinguishedNameToDomain(distinguishedName); - } - - _log.LogTrace("Resolved domain for {SID} to {Domain}", objectId, itemDomain); - - res.ObjectId = objectId; - res.Domain = itemDomain; - if (IsDeleted()) - { - res.Deleted = IsDeleted(); - _log.LogTrace("{SID} is tombstoned, skipping rest of resolution", objectId); - return res; - } - - if (WellKnownPrincipal.GetWellKnownPrincipal(objectId, out var wkPrincipal)) - { - if (await _utils.GetDomainSidFromDomainName(itemDomain) is (true, var sid)) - res.DomainSid = sid; - res.DisplayName = $"{wkPrincipal.ObjectIdentifier}@{itemDomain}"; - res.ObjectType = wkPrincipal.ObjectType; - if (await _utils.GetWellKnownPrincipal(objectId, itemDomain) is (true, var principal)) - res.ObjectId = principal.ObjectIdentifier; - - _log.LogTrace("Resolved {DN} to wkp {ObjectID}", DistinguishedName, res.ObjectId); - return res; - } - - if (objectId.StartsWith("S-1-")) - try - { - res.DomainSid = new SecurityIdentifier(objectId).AccountDomainSid.Value; - } - catch - { - if (await _utils.GetDomainSidFromDomainName(itemDomain) is (true, var sid)) - res.DomainSid = sid; - } - else - if (await _utils.GetDomainSidFromDomainName(itemDomain) is (true, var sid)) - res.DomainSid = sid; - - var samAccountName = GetProperty(LDAPProperties.SAMAccountName); - - var itemType = GetLabel(); - res.ObjectType = itemType; - - if (IsGMSA() || IsMSA()) - { - res.ObjectType = Label.User; - itemType = Label.User; - } - - _log.LogTrace("Resolved type for {SID} to {Label}", objectId, itemType); - - switch (itemType) - { - case Label.User: - case Label.Group: - case Label.Base: - res.DisplayName = $"{samAccountName}@{itemDomain}"; - break; - case Label.Computer: - { - var shortName = samAccountName?.TrimEnd('$'); - var dns = GetProperty(LDAPProperties.DNSHostName); - var cn = GetProperty(LDAPProperties.CanonicalName); - - if (dns != null) - res.DisplayName = dns; - else if (shortName == null && cn == null) - res.DisplayName = $"UNKNOWN.{itemDomain}"; - else if (shortName != null) - res.DisplayName = $"{shortName}.{itemDomain}"; - else - res.DisplayName = $"{cn}.{itemDomain}"; - - break; - } - case Label.GPO: - case Label.IssuancePolicy: - { - var cn = GetProperty(LDAPProperties.CanonicalName); - var displayName = GetProperty(LDAPProperties.DisplayName); - res.DisplayName = string.IsNullOrEmpty(displayName) ? $"{cn}@{itemDomain}" : $"{GetProperty(LDAPProperties.DisplayName)}@{itemDomain}"; - break; - } - case Label.Domain: - res.DisplayName = itemDomain; - break; - case Label.OU: - case Label.Container: - case Label.Configuration: - case Label.RootCA: - case Label.AIACA: - case Label.NTAuthStore: - case Label.EnterpriseCA: - case Label.CertTemplate: - res.DisplayName = $"{GetProperty(LDAPProperties.Name)}@{itemDomain}"; - break; - default: - throw new ArgumentOutOfRangeException(); - } - - return res; - } - - public string GetProperty(string propertyName) - { - return _entry.GetProperty(propertyName); - } - - public byte[] GetByteProperty(string propertyName) - { - return _entry.GetPropertyAsBytes(propertyName); - } - - public string[] GetArrayProperty(string propertyName) - { - return _entry.GetPropertyAsArray(propertyName); - } - - public byte[][] GetByteArrayProperty(string propertyName) - { - return _entry.GetPropertyAsArrayOfBytes(propertyName); - } - - public bool GetIntProperty(string propertyName, out int value) - { - return _entry.GetPropertyAsInt(propertyName, out value); - } - - public X509Certificate2[] GetCertificateArrayProperty(string propertyName) - { - return _entry.GetPropertyAsArrayOfCertificates(propertyName); - } - - public string GetObjectIdentifier() - { - return _entry.GetObjectIdentifier(); - } - - public bool IsDeleted() - { - return _entry.IsDeleted(); - } - - public Label GetLabel() - { - _entry.GetLabel(out var label); - return label; - } - - public string GetSid() - { - return _entry.GetSid(); - } - - public string GetGuid() - { - return _entry.GetGuid(); - } - - public int PropCount(string prop) - { - var coll = _entry.Attributes[prop]; - return coll.Count; - } - - public IEnumerable PropertyNames() - { - foreach (var property in _entry.Attributes.AttributeNames) yield return property.ToString().ToLower(); - } - - public bool IsMSA() - { - var classes = GetArrayProperty(LDAPProperties.ObjectClass); - return classes.Contains(MSAClass, StringComparer.InvariantCultureIgnoreCase); - } - - public bool IsGMSA() - { - var classes = GetArrayProperty(LDAPProperties.ObjectClass); - return classes.Contains(GMSAClass, StringComparer.InvariantCultureIgnoreCase); - } - - public bool HasLAPS() - { - return GetProperty(LDAPProperties.LAPSExpirationTime) != null || GetProperty(LDAPProperties.LegacyLAPSExpirationTime) != null; - } - - public SearchResultEntry GetEntry() - { - return _entry; - } - } -} diff --git a/src/CommonLib/SecurityDescriptor.cs b/src/CommonLib/SecurityDescriptor.cs index 73ef33f9..3ef96569 100644 --- a/src/CommonLib/SecurityDescriptor.cs +++ b/src/CommonLib/SecurityDescriptor.cs @@ -15,6 +15,8 @@ public ActiveDirectoryRuleDescriptor(ActiveDirectoryAccessRule inner) _inner = inner; } + public virtual InheritanceFlags InheritanceFlags => _inner.InheritanceFlags; + public virtual AccessControlType AccessControlType() { return _inner.AccessControlType; @@ -30,6 +32,10 @@ public virtual bool IsInherited() return _inner.IsInherited; } + public virtual string InheritedObjectType() { + return _inner.InheritedObjectType.ToString(); + } + public virtual bool IsAceInheritedFrom(string guid) { //Check if the ace is inherited diff --git a/test/unit/ACLProcessorTest.cs b/test/unit/ACLProcessorTest.cs index 843968f0..f002a389 100644 --- a/test/unit/ACLProcessorTest.cs +++ b/test/unit/ACLProcessorTest.cs @@ -16,11 +16,9 @@ using Xunit; using Xunit.Abstractions; -namespace CommonLibTest -{ +namespace CommonLibTest { [SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")] - public class ACLProcessorTest : IDisposable - { + public class ACLProcessorTest : IDisposable { private const string ProtectedUserNTSecurityDescriptor = "AQAEnIgEAAAAAAAAAAAAABQAAAAEAHQEGAAAAAUAPAAQAAAAAwAAAABCFkzAINARp2gAqgBuBSkUzChINxS8RZsHrW8BXl8oAQIAAAAAAAUgAAAAKgIAAAUAPAAQAAAAAwAAAABCFkzAINARp2gAqgBuBSm6epa/5g3QEaKFAKoAMEniAQIAAAAAAAUgAAAAKgIAAAUAPAAQAAAAAwAAABAgIF+ledARkCAAwE/C1M8UzChINxS8RZsHrW8BXl8oAQIAAAAAAAUgAAAAKgIAAAUAPAAQAAAAAwAAABAgIF+ledARkCAAwE/C1M+6epa/5g3QEaKFAKoAMEniAQIAAAAAAAUgAAAAKgIAAAUAPAAQAAAAAwAAAEDCCrypedARkCAAwE/C1M8UzChINxS8RZsHrW8BXl8oAQIAAAAAAAUgAAAAKgIAAAUAPAAQAAAAAwAAAEDCCrypedARkCAAwE/C1M+6epa/5g3QEaKFAKoAMEniAQIAAAAAAAUgAAAAKgIAAAUAPAAQAAAAAwAAAEIvulmiedARkCAAwE/C088UzChINxS8RZsHrW8BXl8oAQIAAAAAAAUgAAAAKgIAAAUAPAAQAAAAAwAAAEIvulmiedARkCAAwE/C08+6epa/5g3QEaKFAKoAMEniAQIAAAAAAAUgAAAAKgIAAAUAPAAQAAAAAwAAAPiIcAPhCtIRtCIAoMlo+TkUzChINxS8RZsHrW8BXl8oAQIAAAAAAAUgAAAAKgIAAAUAPAAQAAAAAwAAAPiIcAPhCtIRtCIAoMlo+Tm6epa/5g3QEaKFAKoAMEniAQIAAAAAAAUgAAAAKgIAAAUAOAAwAAAAAQAAAH96lr/mDdARooUAqgAwSeIBBQAAAAAABRUAAAAgT5C6f0aEpXZIFpAFAgAABQAsABAAAAABAAAAHbGpRq5gWkC36P+KWNRW0gECAAAAAAAFIAAAADACAAAFACwAMAAAAAEAAAAcmrZtIpTREa69AAD4A2fBAQIAAAAAAAUgAAAAMQIAAAUALAAwAAAAAQAAAGK8BVjJvShEpeKFag9MGF4BAgAAAAAABSAAAAAxAgAABQAsAJQAAgACAAAAFMwoSDcUvEWbB61vAV5fKAECAAAAAAAFIAAAACoCAAAFACwAlAACAAIAAAC6epa/5g3QEaKFAKoAMEniAQIAAAAAAAUgAAAAKgIAAAUAKAAAAQAAAQAAAFMacqsvHtARmBkAqgBAUpsBAQAAAAAAAQAAAAAFACgAAAEAAAEAAABTGnKrLx7QEZgZAKoAQFKbAQEAAAAAAAUKAAAABQIoADABAAABAAAA3kfmkW/ZcEuVV9Y/9PPM2AEBAAAAAAAFCgAAAAAAJAC/AQ4AAQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQAAIAAAAAJAC/AQ4AAQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQBwIAAAAAGAC/AQ8AAQIAAAAAAAUgAAAAIAIAAAAAFACUAAIAAQEAAAAAAAULAAAAAAAUAP8BDwABAQAAAAAABRIAAAABBQAAAAAABRUAAAAgT5C6f0aEpXZIFpAAAgAA"; @@ -38,37 +36,32 @@ public class ACLProcessorTest : IDisposable private readonly string _testDomainName; private readonly ITestOutputHelper _testOutputHelper; - public ACLProcessorTest(ITestOutputHelper testOutputHelper) - { + public ACLProcessorTest(ITestOutputHelper testOutputHelper) { _testOutputHelper = testOutputHelper; _testDomainName = "TESTLAB.LOCAL"; _baseProcessor = new ACLProcessor(new LdapUtils()); } - public void Dispose() - { + public void Dispose() { } [Fact] - public void SanityCheck() - { + public void SanityCheck() { Assert.True(true); } [Fact] - public void ACLProcessor_IsACLProtected_NullNTSD_ReturnsFalse() - { - var processor = new ACLProcessor(new MockLDAPUtils()); + public void ACLProcessor_IsACLProtected_NullNTSD_ReturnsFalse() { + var processor = new ACLProcessor(new MockLdapUtils()); var result = processor.IsACLProtected((byte[])null); Assert.False(result); } [WindowsOnlyFact] - public async Task ACLProcessor_TestKnownDataAddMember() - { - var mockLdapUtils = new MockLDAPUtils(); + public async Task ACLProcessor_TestKnownDataAddMember() { + var mockLdapUtils = new MockLdapUtils(); var mockUtils = new Mock(); - var mockData = new[] { LdapResult.Fail() }; + var mockData = new[] { LdapResult.Fail() }; mockUtils.Setup(x => x.Query(It.IsAny(), It.IsAny())) .Returns(mockData.ToAsyncEnumerable()); mockUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) @@ -77,7 +70,7 @@ public async Task ACLProcessor_TestKnownDataAddMember() mockUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(sd); var processor = new ACLProcessor(mockUtils.Object); - var bytes = Helpers.B64ToBytes(AddMemberSecurityDescriptor); + var bytes = Utils.B64ToBytes(AddMemberSecurityDescriptor); var result = await processor.ProcessACL(bytes, "TESTLAB.LOCAL", Label.Group, false).ToArrayAsync(); _testOutputHelper.WriteLine(JsonConvert.SerializeObject(result)); @@ -89,957 +82,1016 @@ public async Task ACLProcessor_TestKnownDataAddMember() x => x.RightName == EdgeNames.AddMember && x.PrincipalSID == "S-1-5-21-3130019616-2776909439-2417379446-2119"); } - - [Fact] - public void ACLProcessor_IsACLProtected_ReturnsTrue() - { - var mockLDAPUtils = new Mock(); - var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - mockSecurityDescriptor.Setup(x => x.AreAccessRulesProtected()).Returns(true); - mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - - var processor = new ACLProcessor(mockLDAPUtils.Object); - var bytes = Helpers.B64ToBytes(ProtectedUserNTSecurityDescriptor); - var result = processor.IsACLProtected(bytes); - Assert.True(result); - } - - [Fact] - public void ACLProcessor_IsACLProtected_ReturnsFalse() - { - var mockLDAPUtils = new Mock(); - var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - mockSecurityDescriptor.Setup(m => m.AreAccessRulesProtected()).Returns(false); - mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - - var processor = new ACLProcessor(mockLDAPUtils.Object); - var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); - var result = processor.IsACLProtected(bytes); - Assert.False(result); - } - - [Fact] - public async Task ACLProcessor_ProcessGMSAReaders_NullNTSD_ReturnsNothing() - { - var test =await _baseProcessor.ProcessGMSAReaders(null, "").ToArrayAsync(); - Assert.Empty(test); - } - - [Fact] - public async Task ACLProcess_ProcessGMSAReaders_YieldsCorrectAce() - { - var expectedRightName = EdgeNames.ReadGMSAPassword; - var expectedSID = "S-1-5-21-3130019616-2776909439-2417379446-500"; - var expectedPrincipalType = Label.User; - var expectedInheritance = false; - - var mockLDAPUtils = new Mock(); - var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - var mockRule = new Mock(MockBehavior.Loose, null); - - mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); - mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); - mockRule.Setup(x => x.IdentityReference()).Returns(expectedSID); - - var collection = new List { mockRule.Object }; - - mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(collection); - mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) - .ReturnsAsync((true, new TypedPrincipal(expectedSID, expectedPrincipalType))); - - var processor = new ACLProcessor(mockLDAPUtils.Object); - var bytes = Helpers.B64ToBytes(GMSAProperty); - var result = await processor.ProcessGMSAReaders(bytes, _testDomainName).ToArrayAsync(); - - Assert.Single(result); - var actual = result.First(); - _testOutputHelper.WriteLine(actual.ToString()); - Assert.Equal(expectedRightName, actual.RightName); - Assert.Equal(expectedSID, actual.PrincipalSID); - Assert.Equal(expectedPrincipalType, actual.PrincipalType); - Assert.Equal(expectedInheritance, actual.IsInherited); - } - - [Fact] - public async Task ACLProcessor_ProcessGMSAReaders_Null_ACE() - { - var mockLDAPUtils = new Mock(); - var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - var collection = new List { null }; - - mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(collection); - mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - - var processor = new ACLProcessor(mockLDAPUtils.Object); - var bytes = Helpers.B64ToBytes(GMSAProperty); - var result = await processor.ProcessGMSAReaders(bytes, _testDomainName).ToArrayAsync(); - - Assert.Empty(result); - } - - [Fact] - public async Task ACLProcessor_ProcessGMSAReaders_Deny_ACE() - { - var mockLDAPUtils = new Mock(); - var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - var mockRule = new Mock(MockBehavior.Loose, null); - var collection = new List(); - - mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Deny); - collection.Add(mockRule.Object); - - mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(collection); - mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - - var processor = new ACLProcessor(mockLDAPUtils.Object); - var bytes = Helpers.B64ToBytes(GMSAProperty); - var result = await processor.ProcessGMSAReaders(bytes, _testDomainName).ToArrayAsync(); - - Assert.Empty(result); - } - - [Fact] - public async Task ACLProcessor_ProcessGMSAReaders_Null_PrincipalID() - { - var mockLDAPUtils = new Mock(); - var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - var mockRule = new Mock(MockBehavior.Loose, null); - var collection = new List(); - - mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); - mockRule.Setup(x => x.IdentityReference()).Returns((string)null); - collection.Add(mockRule.Object); - - mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(collection); - mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - - var processor = new ACLProcessor(mockLDAPUtils.Object); - var bytes = Helpers.B64ToBytes(GMSAProperty); - var result =await processor.ProcessGMSAReaders(bytes, _testDomainName).ToArrayAsync(); - - Assert.Empty(result); - } - - [Fact] - public async Task ACLProcessor_ProcessACL_Null_NTSecurityDescriptor() - { - var processor = new ACLProcessor(new MockLDAPUtils()); - var result = await processor.ProcessACL(null, _testDomainName, Label.User, false).ToArrayAsync(); - - Assert.Empty(result); - } - - [Fact] - public async Task ACLProcessor_ProcessACL_Yields_Owns_ACE() - { - var expectedSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; - var expectedPrincipalType = Label.Group; - - var mockLDAPUtils = new Mock(); - var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - var collection = new List(); - - mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns(expectedSID); - mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) - .ReturnsAsync((true, new TypedPrincipal(expectedSID, expectedPrincipalType))); - - var mockData = new[] { LdapResult.Fail() }; - mockLDAPUtils.Setup(x => x.Query(It.IsAny(), It.IsAny())) - .Returns(mockData.ToAsyncEnumerable()); - - var processor = new ACLProcessor(mockLDAPUtils.Object); - var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); - var result = await processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArrayAsync(); - - Assert.Single(result); - var actual = result.First(); - Assert.Equal(actual.PrincipalSID, expectedSID); - Assert.Equal(actual.PrincipalType, expectedPrincipalType); - Assert.False(actual.IsInherited); - Assert.Equal(actual.RightName, EdgeNames.Owns); - } - - [Fact] - public async Task ACLProcessor_ProcessACL_Null_SID() - { - var mockLDAPUtils = new Mock(); - var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - var collection = new List(); - - mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); - mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - - var processor = new ACLProcessor(mockLDAPUtils.Object); - var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); - var result = await processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArrayAsync(); - - Assert.Empty(result); - } - - [Fact] - public async Task ACLProcessor_ProcessACL_Null_ACE() - { - var mockLDAPUtils = new Mock(); - var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - var collection = new List { null }; - - mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); - mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - - var processor = new ACLProcessor(mockLDAPUtils.Object); - var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); - var result = await processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArrayAsync(); - - Assert.Empty(result); - } - - [Fact] - public async Task ACLProcessor_ProcessACL_Deny_ACE() - { - var mockLDAPUtils = new Mock(); - var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - var mockRule = new Mock(MockBehavior.Loose, null); - var collection = new List(); - mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Deny); - collection.Add(mockRule.Object); - - mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); - mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - - var processor = new ACLProcessor(mockLDAPUtils.Object); - var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); - var result = await processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArrayAsync(); - - Assert.Empty(result); - } - - [Fact] - public async Task ACLProcessor_ProcessACL_Unmatched_Inheritance_ACE() - { - var mockLDAPUtils = new Mock(); - var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - var mockRule = new Mock(MockBehavior.Loose, null); - var collection = new List(); - mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); - mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(false); - collection.Add(mockRule.Object); - - mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); - mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - - var processor = new ACLProcessor(mockLDAPUtils.Object); - var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); - var result = await processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArrayAsync(); - - Assert.Empty(result); - } - - [Fact] - public async Task ACLProcessor_ProcessACL_Null_SID_ACE() - { - var mockLDAPUtils = new Mock(); - var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - var mockRule = new Mock(MockBehavior.Loose, null); - var collection = new List(); - mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); - mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); - mockRule.Setup(x => x.IdentityReference()).Returns((string)null); - collection.Add(mockRule.Object); - - mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); - mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - - var processor = new ACLProcessor(mockLDAPUtils.Object); - var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); - var result = await processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArrayAsync(); - - Assert.Empty(result); - } - - [Fact] - public async Task ACLProcessor_ProcessACL_GenericAll_Unmatched_Guid() - { - var expectedPrincipalType = Label.Group; - var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; - var unmatchedGuid = new Guid("583991c8-629d-4a07-8a70-74d19d22ac9c"); - - var mockLDAPUtils = new Mock(); - var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - var mockRule = new Mock(MockBehavior.Loose, null); - var collection = new List(); - mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); - mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); - mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); - mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.GenericAll); - mockRule.Setup(x => x.ObjectType()).Returns(unmatchedGuid); - collection.Add(mockRule.Object); - - mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); - mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) - .ReturnsAsync((true,new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); - var mockData = new[] { LdapResult.Fail() }; - mockLDAPUtils.Setup(x => x.Query(It.IsAny(), It.IsAny())) - .Returns(mockData.ToAsyncEnumerable()); - - var processor = new ACLProcessor(mockLDAPUtils.Object); - var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); - var result = await processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArrayAsync(); - - Assert.Empty(result); - } - - [Fact] - public async Task ACLProcessor_ProcessACL_GenericAll() - { - var expectedPrincipalType = Label.Group; - var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; - - var mockLDAPUtils = new Mock(); - var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - var mockRule = new Mock(MockBehavior.Loose, null); - var collection = new List(); - mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); - mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); - mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); - mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.GenericAll); - mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); - collection.Add(mockRule.Object); - - mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); - mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) - .ReturnsAsync((true, new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); - var mockData = new[] { LdapResult.Fail() }; - mockLDAPUtils.Setup(x => x.Query(It.IsAny(), It.IsAny())) - .Returns(mockData.ToAsyncEnumerable()); - - var processor = new ACLProcessor(mockLDAPUtils.Object); - var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); - var result = await processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArrayAsync(); - - Assert.Single(result); - var actual = result.First(); - Assert.Equal(actual.PrincipalType, expectedPrincipalType); - Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); - Assert.False(actual.IsInherited); - Assert.Equal(actual.RightName, EdgeNames.GenericAll); - } - - [Fact] - public async Task ACLProcessor_ProcessACL_WriteDacl() - { - var expectedPrincipalType = Label.Group; - var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; - var expectedRightName = ActiveDirectoryRights.WriteDacl; - - var mockLDAPUtils = new Mock(); - var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - var mockRule = new Mock(MockBehavior.Loose, null); - var collection = new List(); - mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); - mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); - mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); - mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(expectedRightName); - mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); - collection.Add(mockRule.Object); - - mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); - mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) - .ReturnsAsync((true, new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); - var mockData = new[] { LdapResult.Fail() }; - mockLDAPUtils.Setup(x => x.Query(It.IsAny(), It.IsAny())) - .Returns(mockData.ToAsyncEnumerable()); - - var processor = new ACLProcessor(mockLDAPUtils.Object); - var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); - var result = await processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArrayAsync(); - - Assert.Single(result); - var actual = result.First(); - Assert.Equal(actual.PrincipalType, expectedPrincipalType); - Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); - Assert.False(actual.IsInherited); - Assert.Equal(actual.RightName, expectedRightName.ToString()); - } - - [Fact] - public async Task ACLProcessor_ProcessACL_WriteOwner() - { - var expectedPrincipalType = Label.Group; - var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; - var expectedRightName = ActiveDirectoryRights.WriteOwner; - - var mockLDAPUtils = new Mock(); - var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - var mockRule = new Mock(MockBehavior.Loose, null); - var collection = new List(); - mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); - mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); - mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); - mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(expectedRightName); - mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); - collection.Add(mockRule.Object); - - mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); - mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) - .ReturnsAsync((true, new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); - var mockData = new[] { LdapResult.Fail() }; - mockLDAPUtils.Setup(x => x.Query(It.IsAny(), It.IsAny())) - .Returns(mockData.ToAsyncEnumerable()); - - var processor = new ACLProcessor(mockLDAPUtils.Object); - var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); - var result = await processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArrayAsync(); - - Assert.Single(result); - var actual = result.First(); - Assert.Equal(actual.PrincipalType, expectedPrincipalType); - Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); - Assert.False(actual.IsInherited); - Assert.Equal(actual.RightName, expectedRightName.ToString()); - } - - [Fact] - public async Task ACLProcessor_ProcessACL_Self() - { - var expectedPrincipalType = Label.Group; - var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; - var expectedRightName = EdgeNames.AddSelf; - - var mockLDAPUtils = new Mock(); - var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - var mockRule = new Mock(MockBehavior.Loose, null); - var collection = new List(); - mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); - mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); - mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); - mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.Self); - mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.WriteMember)); - collection.Add(mockRule.Object); - - mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); - mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) - .ReturnsAsync((true, new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); - var mockData = new[] { LdapResult.Fail() }; - mockLDAPUtils.Setup(x => x.Query(It.IsAny(), It.IsAny())) - .Returns(mockData.ToAsyncEnumerable()); - - var processor = new ACLProcessor(mockLDAPUtils.Object); - var bytes = Helpers.B64ToBytes(AddMemberSecurityDescriptor); - var result = await processor.ProcessACL(bytes, _testDomainName, Label.Group, false).ToArrayAsync(); - - Assert.Single(result); - var actual = result.First(); - Assert.Equal(actual.PrincipalType, expectedPrincipalType); - Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); - Assert.False(actual.IsInherited); - Assert.Equal(actual.RightName, expectedRightName); - } - - [Fact] - public async Task ACLProcessor_ProcessACL_ExtendedRight_Domain_Unmatched() - { - var expectedPrincipalType = Label.Group; - var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; - - var mockLDAPUtils = new Mock(); - var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - var mockRule = new Mock(MockBehavior.Loose, null); - var collection = new List(); - mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); - mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); - mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); - mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); - mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.WriteMember)); - collection.Add(mockRule.Object); - - mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); - mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) - .ReturnsAsync((true, new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); - var mockData = new[] { LdapResult.Fail() }; - mockLDAPUtils.Setup(x => x.Query(It.IsAny(), It.IsAny())) - .Returns(mockData.ToAsyncEnumerable()); - - var processor = new ACLProcessor(mockLDAPUtils.Object); - var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); - var result = await processor.ProcessACL(bytes, _testDomainName, Label.Domain, false).ToArrayAsync(); - - Assert.Empty(result); - } - - [Fact] - public async Task ACLProcessor_ProcessACL_ExtendedRight_Domain_DSReplicationGetChanges() - { - var expectedPrincipalType = Label.Group; - var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; - var expectedRightName = EdgeNames.GetChanges; - - var mockLDAPUtils = new Mock(); - var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - var mockRule = new Mock(MockBehavior.Loose, null); - var collection = new List(); - mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); - mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); - mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); - mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); - mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.DSReplicationGetChanges)); - collection.Add(mockRule.Object); - - mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); - mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) - .ReturnsAsync((true,new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); - - var processor = new ACLProcessor(mockLDAPUtils.Object); - var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); - var result = await processor.ProcessACL(bytes, _testDomainName, Label.Domain, false).ToArrayAsync(); - - Assert.Single(result); - var actual = result.First(); - Assert.Equal(actual.PrincipalType, expectedPrincipalType); - Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); - Assert.False(actual.IsInherited); - Assert.Equal(actual.RightName, expectedRightName); - } - - [Fact] - public async Task ACLProcessor_ProcessACL_ExtendedRight_Domain_All() - { - var expectedPrincipalType = Label.Group; - var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; - var expectedRightName = EdgeNames.AllExtendedRights; - - var mockLDAPUtils = new Mock(); - var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - var mockRule = new Mock(MockBehavior.Loose, null); - var collection = new List(); - mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); - mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); - mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); - mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); - mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); - collection.Add(mockRule.Object); - - mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); - mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) - .ReturnsAsync((true, new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); - - var processor = new ACLProcessor(mockLDAPUtils.Object); - var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); - var result = await processor.ProcessACL(bytes, _testDomainName, Label.Domain, false).ToArrayAsync(); - - Assert.Single(result); - var actual = result.First(); - Assert.Equal(actual.PrincipalType, expectedPrincipalType); - Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); - Assert.False(actual.IsInherited); - Assert.Equal(actual.RightName, expectedRightName); - } - - [Fact] - public async Task ACLProcessor_ProcessACL_ExtendedRight_Domain_DSReplicationGetChangesAll() - { - var expectedPrincipalType = Label.Group; - var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; - var expectedRightName = EdgeNames.GetChangesAll; - - var mockLDAPUtils = new Mock(); - var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - var mockRule = new Mock(MockBehavior.Loose, null); - var collection = new List(); - mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); - mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); - mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); - mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); - mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.DSReplicationGetChangesAll)); - collection.Add(mockRule.Object); - - mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); - mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) - .ReturnsAsync((true, new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); - var mockData = new[] { LdapResult.Fail() }; - mockLDAPUtils.Setup(x => x.Query(It.IsAny(), It.IsAny())) - .Returns(mockData.ToAsyncEnumerable()); - - var processor = new ACLProcessor(mockLDAPUtils.Object); - var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); - var result = await processor.ProcessACL(bytes, _testDomainName, Label.Domain, false).ToArrayAsync(); - - Assert.Single(result); - var actual = result.First(); - Assert.Equal(actual.PrincipalType, expectedPrincipalType); - Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); - Assert.False(actual.IsInherited); - Assert.Equal(actual.RightName, expectedRightName); - } - - [Fact] - public async Task ACLProcessor_ProcessACL_ExtendedRight_User_Unmatched() - { - var expectedPrincipalType = Label.Group; - var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; - var expectedRightName = EdgeNames.GetChangesAll; - var unmatchedGuid = new Guid("583991c8-629d-4a07-8a70-74d19d22ac9c"); - - var mockLDAPUtils = new Mock(); - var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - var mockRule = new Mock(MockBehavior.Loose, null); - var collection = new List(); - mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); - mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); - mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); - mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); - mockRule.Setup(x => x.ObjectType()).Returns(unmatchedGuid); - collection.Add(mockRule.Object); - - mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); - mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) - .ReturnsAsync((true,new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); - - var processor = new ACLProcessor(mockLDAPUtils.Object); - var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); - var result = await processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArrayAsync(); - - Assert.Empty(result); - } - - [Fact] - public async Task ACLProcessor_ProcessACL_ExtendedRight_User_UserForceChangePassword() - { - var expectedPrincipalType = Label.Group; - var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; - var expectedRightName = EdgeNames.ForceChangePassword; - - var mockLDAPUtils = new Mock(); - var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - var mockRule = new Mock(MockBehavior.Loose, null); - var collection = new List(); - mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); - mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); - mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); - mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); - mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.UserForceChangePassword)); - collection.Add(mockRule.Object); - - mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); - mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) - .ReturnsAsync((true,new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); - - var processor = new ACLProcessor(mockLDAPUtils.Object); - var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); - var result = await processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArrayAsync(); - - Assert.Single(result); - var actual = result.First(); - Assert.Equal(actual.PrincipalType, expectedPrincipalType); - Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); - Assert.False(actual.IsInherited); - Assert.Equal(actual.RightName, expectedRightName); - } - - [Fact] - public async Task ACLProcessor_ProcessACL_ExtendedRight_User_All() - { - var expectedPrincipalType = Label.Group; - var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; - var expectedRightName = EdgeNames.AllExtendedRights; - - var mockLDAPUtils = new Mock(); - var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - var mockRule = new Mock(MockBehavior.Loose, null); - var collection = new List(); - mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); - mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); - mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); - mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); - mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); - collection.Add(mockRule.Object); - - mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); - mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) - .ReturnsAsync((true,new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); - - var processor = new ACLProcessor(mockLDAPUtils.Object); - var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); - var result = await processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArrayAsync(); - - Assert.Single(result); - var actual = result.First(); - Assert.Equal(actual.PrincipalType, expectedPrincipalType); - Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); - Assert.False(actual.IsInherited); - Assert.Equal(actual.RightName, expectedRightName); - } - - [Fact] - public async Task ACLProcessor_ProcessACL_ExtendedRight_Computer_NoLAPS() - { - var expectedPrincipalType = Label.Group; - var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; - var expectedRightName = EdgeNames.AllExtendedRights; - - var mockLDAPUtils = new Mock(); - var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - var mockRule = new Mock(MockBehavior.Loose, null); - var collection = new List(); - mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); - mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); - mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); - mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); - mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); - collection.Add(mockRule.Object); - - mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); - mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) - .ReturnsAsync((true, new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); - - var processor = new ACLProcessor(mockLDAPUtils.Object); - var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); - var result = await processor.ProcessACL(bytes, _testDomainName, Label.Computer, false).ToArrayAsync(); - - Assert.Empty(result); - } - - [Fact] - public async Task ACLProcessor_ProcessACL_ExtendedRight_Computer_All() - { - var expectedPrincipalType = Label.Group; - var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; - var expectedRightName = EdgeNames.AllExtendedRights; - - var mockLDAPUtils = new Mock(); - var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - var mockRule = new Mock(MockBehavior.Loose, null); - var collection = new List(); - mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); - mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); - mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); - mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); - mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); - collection.Add(mockRule.Object); - - mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); - mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) - .ReturnsAsync((true,new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); - - var processor = new ACLProcessor(mockLDAPUtils.Object); - var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); - var result = await processor.ProcessACL(bytes, _testDomainName, Label.Computer, true).ToArrayAsync(); - - Assert.Single(result); - var actual = result.First(); - Assert.Equal(actual.PrincipalType, expectedPrincipalType); - Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); - Assert.False(actual.IsInherited); - Assert.Equal(actual.RightName, expectedRightName); - } - - [Fact(Skip = "Need to populate cache to reach this case")] - public async Task ACLProcessor_ProcessACL_ExtendedRight_Computer_MappedGuid() - { - } - - [Fact] - public async Task ACLProcessor_ProcessACL_GenericWrite_Unmatched() - { - var expectedPrincipalType = Label.Group; - var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; - var expectedRightName = EdgeNames.AllExtendedRights; - - var mockLDAPUtils = new Mock(); - var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - var mockRule = new Mock(MockBehavior.Loose, null); - var collection = new List(); - mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); - mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); - mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); - mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.GenericWrite); - mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); - collection.Add(mockRule.Object); - - mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); - mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) - .ReturnsAsync((true,new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); - - var processor = new ACLProcessor(mockLDAPUtils.Object); - var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); - var result = await processor.ProcessACL(bytes, _testDomainName, Label.Container, true).ToArrayAsync(); - - Assert.Empty(result); - } - - [Fact] - public async Task ACLProcessor_ProcessACL_GenericWrite_User_All() - { - var expectedPrincipalType = Label.Group; - var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; - var expectedRightName = EdgeNames.GenericWrite; - - var mockLDAPUtils = new Mock(); - var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - var mockRule = new Mock(MockBehavior.Loose, null); - var collection = new List(); - mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); - mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); - mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); - mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.GenericWrite); - mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); - collection.Add(mockRule.Object); - - mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); - mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) - .ReturnsAsync((true,new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); - - var processor = new ACLProcessor(mockLDAPUtils.Object); - var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); - var result = await processor.ProcessACL(bytes, _testDomainName, Label.User, true).ToArrayAsync(); - - Assert.Single(result); - var actual = result.First(); - Assert.Equal(actual.PrincipalType, expectedPrincipalType); - Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); - Assert.False(actual.IsInherited); - Assert.Equal(actual.RightName, expectedRightName); - } - - [Fact] - public async Task ACLProcessor_ProcessACL_GenericWrite_User_WriteMember() - { - var expectedPrincipalType = Label.Group; - var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; - var expectedRightName = EdgeNames.AddMember; - - var mockLDAPUtils = new Mock(); - var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - var mockRule = new Mock(MockBehavior.Loose, null); - var collection = new List(); - mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); - mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); - mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); - mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.GenericWrite); - mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.WriteMember)); - collection.Add(mockRule.Object); - - mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); - mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) - .ReturnsAsync((true,new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); - - var processor = new ACLProcessor(mockLDAPUtils.Object); - var bytes = Helpers.B64ToBytes(AddMemberSecurityDescriptor); - var result = await processor.ProcessACL(bytes, _testDomainName, Label.Group, true).ToArrayAsync(); - - _testOutputHelper.WriteLine(JsonConvert.SerializeObject(result)); - - Assert.Single(result); - var actual = result.First(); - Assert.Equal(actual.PrincipalType, expectedPrincipalType); - Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); - Assert.False(actual.IsInherited); - Assert.Equal(actual.RightName, expectedRightName); - } - - [Fact] - public async Task ACLProcessor_ProcessACL_GenericWrite_Computer_WriteAllowedToAct() - { - var expectedPrincipalType = Label.Group; - var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; - var expectedRightName = EdgeNames.AddAllowedToAct; - - var mockLDAPUtils = new Mock(); - var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); - var mockRule = new Mock(MockBehavior.Loose, null); - var collection = new List(); - mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); - mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); - mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); - mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.GenericWrite); - mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.WriteAllowedToAct)); - collection.Add(mockRule.Object); - - mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); - mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) - .ReturnsAsync((true,new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); - - var processor = new ACLProcessor(mockLDAPUtils.Object); - var bytes = Helpers.B64ToBytes(UnProtectedUserNtSecurityDescriptor); - var result = await processor.ProcessACL(bytes, _testDomainName, Label.Computer, true).ToArrayAsync(); - - Assert.Single(result); - var actual = result.First(); - Assert.Equal(actual.PrincipalType, expectedPrincipalType); - Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); - Assert.False(actual.IsInherited); - Assert.Equal(actual.RightName, expectedRightName); - } + + [Fact] + public void ACLProcessor_IsACLProtected_ReturnsTrue() { + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + mockSecurityDescriptor.Setup(x => x.AreAccessRulesProtected()).Returns(true); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Utils.B64ToBytes(ProtectedUserNTSecurityDescriptor); + var result = processor.IsACLProtected(bytes); + Assert.True(result); + } + + [Fact] + public void ACLProcessor_IsACLProtected_ReturnsFalse() { + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + mockSecurityDescriptor.Setup(m => m.AreAccessRulesProtected()).Returns(false); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Utils.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + var result = processor.IsACLProtected(bytes); + Assert.False(result); + } + + [Fact] + public async Task ACLProcessor_ProcessGMSAReaders_NullNTSD_ReturnsNothing() { + var test = await _baseProcessor.ProcessGMSAReaders(null, "").ToArrayAsync(); + Assert.Empty(test); + } + + [Fact] + public async Task ACLProcess_ProcessGMSAReaders_YieldsCorrectAce() { + var expectedRightName = EdgeNames.ReadGMSAPassword; + var expectedSID = "S-1-5-21-3130019616-2776909439-2417379446-500"; + var expectedPrincipalType = Label.User; + var expectedInheritance = false; + + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var mockRule = new Mock(MockBehavior.Loose, null); + + mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); + mockRule.Setup(x => x.IdentityReference()).Returns(expectedSID); + + var collection = new List { mockRule.Object }; + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + .ReturnsAsync((true, new TypedPrincipal(expectedSID, expectedPrincipalType))); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Utils.B64ToBytes(GMSAProperty); + var result = await processor.ProcessGMSAReaders(bytes, _testDomainName).ToArrayAsync(); + + Assert.Single(result); + var actual = result.First(); + _testOutputHelper.WriteLine(actual.ToString()); + Assert.Equal(expectedRightName, actual.RightName); + Assert.Equal(expectedSID, actual.PrincipalSID); + Assert.Equal(expectedPrincipalType, actual.PrincipalType); + Assert.Equal(expectedInheritance, actual.IsInherited); + } + + [Fact] + public async Task ACLProcessor_ProcessGMSAReaders_Null_ACE() { + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var collection = new List { null }; + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Utils.B64ToBytes(GMSAProperty); + var result = await processor.ProcessGMSAReaders(bytes, _testDomainName).ToArrayAsync(); + + Assert.Empty(result); + } + + [Fact] + public async Task ACLProcessor_ProcessGMSAReaders_Deny_ACE() { + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var mockRule = new Mock(MockBehavior.Loose, null); + var collection = new List(); + + mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Deny); + collection.Add(mockRule.Object); + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Utils.B64ToBytes(GMSAProperty); + var result = await processor.ProcessGMSAReaders(bytes, _testDomainName).ToArrayAsync(); + + Assert.Empty(result); + } + + [Fact] + public async Task ACLProcessor_ProcessGMSAReaders_Null_PrincipalID() { + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var mockRule = new Mock(MockBehavior.Loose, null); + var collection = new List(); + + mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + mockRule.Setup(x => x.IdentityReference()).Returns((string)null); + collection.Add(mockRule.Object); + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Utils.B64ToBytes(GMSAProperty); + var result = await processor.ProcessGMSAReaders(bytes, _testDomainName).ToArrayAsync(); + + Assert.Empty(result); + } + + [Fact] + public async Task ACLProcessor_ProcessACL_Null_NTSecurityDescriptor() { + var processor = new ACLProcessor(new MockLdapUtils()); + var result = await processor.ProcessACL(null, _testDomainName, Label.User, false).ToArrayAsync(); + + Assert.Empty(result); + } + + [Fact] + public async Task ACLProcessor_ProcessACL_Yields_Owns_ACE() { + var expectedSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + var expectedPrincipalType = Label.Group; + + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var collection = new List(); + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns(expectedSID); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + .ReturnsAsync((true, new TypedPrincipal(expectedSID, expectedPrincipalType))); + + var mockData = new[] { LdapResult.Fail() }; + mockLDAPUtils.Setup(x => x.Query(It.IsAny(), It.IsAny())) + .Returns(mockData.ToAsyncEnumerable()); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Utils.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + var result = await processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArrayAsync(); + + Assert.Single(result); + var actual = result.First(); + Assert.Equal(actual.PrincipalSID, expectedSID); + Assert.Equal(actual.PrincipalType, expectedPrincipalType); + Assert.False(actual.IsInherited); + Assert.Equal(actual.RightName, EdgeNames.Owns); + } + + [Fact] + public async Task ACLProcessor_ProcessACL_Null_SID() { + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var collection = new List(); + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Utils.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + var result = await processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArrayAsync(); + + Assert.Empty(result); + } + + [Fact] + public async Task ACLProcessor_ProcessACL_Null_ACE() { + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var collection = new List { null }; + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Utils.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + var result = await processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArrayAsync(); + + Assert.Empty(result); + } + + [Fact] + public async Task ACLProcessor_ProcessACL_Deny_ACE() { + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var mockRule = new Mock(MockBehavior.Loose, null); + var collection = new List(); + mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Deny); + collection.Add(mockRule.Object); + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Utils.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + var result = await processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArrayAsync(); + + Assert.Empty(result); + } + + [Fact] + public async Task ACLProcessor_ProcessACL_Unmatched_Inheritance_ACE() { + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var mockRule = new Mock(MockBehavior.Loose, null); + var collection = new List(); + mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(false); + collection.Add(mockRule.Object); + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Utils.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + var result = await processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArrayAsync(); + + Assert.Empty(result); + } + + [Fact] + public async Task ACLProcessor_ProcessACL_Null_SID_ACE() { + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var mockRule = new Mock(MockBehavior.Loose, null); + var collection = new List(); + mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); + mockRule.Setup(x => x.IdentityReference()).Returns((string)null); + collection.Add(mockRule.Object); + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Utils.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + var result = await processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArrayAsync(); + + Assert.Empty(result); + } + + [Fact] + public async Task ACLProcessor_ProcessACL_GenericAll_Unmatched_Guid() { + var expectedPrincipalType = Label.Group; + var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + var unmatchedGuid = new Guid("583991c8-629d-4a07-8a70-74d19d22ac9c"); + + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var mockRule = new Mock(MockBehavior.Loose, null); + var collection = new List(); + mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); + mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); + mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.GenericAll); + mockRule.Setup(x => x.ObjectType()).Returns(unmatchedGuid); + collection.Add(mockRule.Object); + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + .ReturnsAsync((true, new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); + var mockData = new[] { LdapResult.Fail() }; + mockLDAPUtils.Setup(x => x.Query(It.IsAny(), It.IsAny())) + .Returns(mockData.ToAsyncEnumerable()); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Utils.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + var result = await processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArrayAsync(); + + Assert.Empty(result); + } + + [Fact] + public async Task ACLProcessor_ProcessACL_GenericAll() { + var expectedPrincipalType = Label.Group; + var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var mockRule = new Mock(MockBehavior.Loose, null); + var collection = new List(); + mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); + mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); + mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.GenericAll); + mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); + collection.Add(mockRule.Object); + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + .ReturnsAsync((true, new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); + var mockData = new[] { LdapResult.Fail() }; + mockLDAPUtils.Setup(x => x.Query(It.IsAny(), It.IsAny())) + .Returns(mockData.ToAsyncEnumerable()); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Utils.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + var result = await processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArrayAsync(); + + Assert.Single(result); + var actual = result.First(); + Assert.Equal(actual.PrincipalType, expectedPrincipalType); + Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); + Assert.False(actual.IsInherited); + Assert.Equal(actual.RightName, EdgeNames.GenericAll); + } + + [Fact] + public async Task ACLProcessor_ProcessACL_WriteDacl() { + var expectedPrincipalType = Label.Group; + var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + var expectedRightName = ActiveDirectoryRights.WriteDacl; + + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var mockRule = new Mock(MockBehavior.Loose, null); + var collection = new List(); + mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); + mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); + mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(expectedRightName); + mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); + collection.Add(mockRule.Object); + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + .ReturnsAsync((true, new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); + var mockData = new[] { LdapResult.Fail() }; + mockLDAPUtils.Setup(x => x.Query(It.IsAny(), It.IsAny())) + .Returns(mockData.ToAsyncEnumerable()); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Utils.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + var result = await processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArrayAsync(); + + Assert.Single(result); + var actual = result.First(); + Assert.Equal(actual.PrincipalType, expectedPrincipalType); + Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); + Assert.False(actual.IsInherited); + Assert.Equal(actual.RightName, expectedRightName.ToString()); + } + + [Fact] + public async Task ACLProcessor_ProcessACL_WriteOwner() { + var expectedPrincipalType = Label.Group; + var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + var expectedRightName = ActiveDirectoryRights.WriteOwner; + + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var mockRule = new Mock(MockBehavior.Loose, null); + var collection = new List(); + mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); + mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); + mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(expectedRightName); + mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); + collection.Add(mockRule.Object); + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + .ReturnsAsync((true, new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); + var mockData = new[] { LdapResult.Fail() }; + mockLDAPUtils.Setup(x => x.Query(It.IsAny(), It.IsAny())) + .Returns(mockData.ToAsyncEnumerable()); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Utils.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + var result = await processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArrayAsync(); + + Assert.Single(result); + var actual = result.First(); + Assert.Equal(actual.PrincipalType, expectedPrincipalType); + Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); + Assert.False(actual.IsInherited); + Assert.Equal(actual.RightName, expectedRightName.ToString()); + } + + [Fact] + public async Task ACLProcessor_ProcessACL_Self() { + var expectedPrincipalType = Label.Group; + var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + var expectedRightName = EdgeNames.AddSelf; + + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var mockRule = new Mock(MockBehavior.Loose, null); + var collection = new List(); + mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); + mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); + mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.Self); + mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.WriteMember)); + collection.Add(mockRule.Object); + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + .ReturnsAsync((true, new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); + var mockData = new[] { LdapResult.Fail() }; + mockLDAPUtils.Setup(x => x.Query(It.IsAny(), It.IsAny())) + .Returns(mockData.ToAsyncEnumerable()); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Utils.B64ToBytes(AddMemberSecurityDescriptor); + var result = await processor.ProcessACL(bytes, _testDomainName, Label.Group, false).ToArrayAsync(); + + Assert.Single(result); + var actual = result.First(); + Assert.Equal(actual.PrincipalType, expectedPrincipalType); + Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); + Assert.False(actual.IsInherited); + Assert.Equal(actual.RightName, expectedRightName); + } + + [Fact] + public async Task ACLProcessor_ProcessACL_ExtendedRight_Domain_Unmatched() { + var expectedPrincipalType = Label.Group; + var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var mockRule = new Mock(MockBehavior.Loose, null); + var collection = new List(); + mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); + mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); + mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); + mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.WriteMember)); + collection.Add(mockRule.Object); + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + .ReturnsAsync((true, new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); + var mockData = new[] { LdapResult.Fail() }; + mockLDAPUtils.Setup(x => x.Query(It.IsAny(), It.IsAny())) + .Returns(mockData.ToAsyncEnumerable()); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Utils.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + var result = await processor.ProcessACL(bytes, _testDomainName, Label.Domain, false).ToArrayAsync(); + + Assert.Empty(result); + } + + [Fact] + public async Task ACLProcessor_ProcessACL_ExtendedRight_Domain_DSReplicationGetChanges() { + var expectedPrincipalType = Label.Group; + var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + var expectedRightName = EdgeNames.GetChanges; + + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var mockRule = new Mock(MockBehavior.Loose, null); + var collection = new List(); + mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); + mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); + mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); + mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.DSReplicationGetChanges)); + collection.Add(mockRule.Object); + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + .ReturnsAsync((true, new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); + mockLDAPUtils.Setup(x => x.Query(It.IsAny(), It.IsAny())) + .Returns(Array.Empty>().ToAsyncEnumerable); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Utils.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + var result = await processor.ProcessACL(bytes, _testDomainName, Label.Domain, false).ToArrayAsync(); + + Assert.Single(result); + var actual = result.First(); + Assert.Equal(actual.PrincipalType, expectedPrincipalType); + Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); + Assert.False(actual.IsInherited); + Assert.Equal(actual.RightName, expectedRightName); + } + + [Fact] + public async Task ACLProcessor_ProcessACL_ExtendedRight_Domain_All() { + var expectedPrincipalType = Label.Group; + var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + var expectedRightName = EdgeNames.AllExtendedRights; + + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var mockRule = new Mock(MockBehavior.Loose, null); + var collection = new List(); + mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); + mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); + mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); + mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); + collection.Add(mockRule.Object); + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + .ReturnsAsync((true, new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); + mockLDAPUtils.Setup(x => x.Query(It.IsAny(), It.IsAny())) + .Returns(Array.Empty>().ToAsyncEnumerable); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Utils.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + var result = await processor.ProcessACL(bytes, _testDomainName, Label.Domain, false).ToArrayAsync(); + + Assert.Single(result); + var actual = result.First(); + Assert.Equal(actual.PrincipalType, expectedPrincipalType); + Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); + Assert.False(actual.IsInherited); + Assert.Equal(actual.RightName, expectedRightName); + } + + [Fact] + public async Task ACLProcessor_ProcessACL_ExtendedRight_Domain_DSReplicationGetChangesAll() { + var expectedPrincipalType = Label.Group; + var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + var expectedRightName = EdgeNames.GetChangesAll; + + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var mockRule = new Mock(MockBehavior.Loose, null); + var collection = new List(); + mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); + mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); + mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); + mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.DSReplicationGetChangesAll)); + collection.Add(mockRule.Object); + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + .ReturnsAsync((true, new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); + var mockData = new[] { LdapResult.Fail() }; + mockLDAPUtils.Setup(x => x.Query(It.IsAny(), It.IsAny())) + .Returns(mockData.ToAsyncEnumerable()); + mockLDAPUtils.Setup(x => x.Query(It.IsAny(), It.IsAny())) + .Returns(Array.Empty>().ToAsyncEnumerable); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Utils.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + var result = await processor.ProcessACL(bytes, _testDomainName, Label.Domain, false).ToArrayAsync(); + + Assert.Single(result); + var actual = result.First(); + Assert.Equal(actual.PrincipalType, expectedPrincipalType); + Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); + Assert.False(actual.IsInherited); + Assert.Equal(actual.RightName, expectedRightName); + } + + [Fact] + public async Task ACLProcessor_ProcessACL_ExtendedRight_User_Unmatched() { + var expectedPrincipalType = Label.Group; + var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + var unmatchedGuid = new Guid("583991c8-629d-4a07-8a70-74d19d22ac9c"); + + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var mockRule = new Mock(MockBehavior.Loose, null); + var collection = new List(); + mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); + mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); + mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); + mockRule.Setup(x => x.ObjectType()).Returns(unmatchedGuid); + collection.Add(mockRule.Object); + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + .ReturnsAsync((true, new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); + mockLDAPUtils.Setup(x => x.Query(It.IsAny(), It.IsAny())) + .Returns(Array.Empty>().ToAsyncEnumerable); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Utils.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + var result = await processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArrayAsync(); + + Assert.Empty(result); + } + + [Fact] + public async Task ACLProcessor_ProcessACL_ExtendedRight_User_UserForceChangePassword() { + var expectedPrincipalType = Label.Group; + var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + var expectedRightName = EdgeNames.ForceChangePassword; + + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var mockRule = new Mock(MockBehavior.Loose, null); + var collection = new List(); + mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); + mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); + mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); + mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.UserForceChangePassword)); + collection.Add(mockRule.Object); + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + .ReturnsAsync((true, new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); + mockLDAPUtils.Setup(x => x.Query(It.IsAny(), It.IsAny())) + .Returns(Array.Empty>().ToAsyncEnumerable); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Utils.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + var result = await processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArrayAsync(); + + Assert.Single(result); + var actual = result.First(); + Assert.Equal(actual.PrincipalType, expectedPrincipalType); + Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); + Assert.False(actual.IsInherited); + Assert.Equal(actual.RightName, expectedRightName); + } + + [Fact] + public async Task ACLProcessor_ProcessACL_ExtendedRight_User_All() { + var expectedPrincipalType = Label.Group; + var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + var expectedRightName = EdgeNames.AllExtendedRights; + + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var mockRule = new Mock(MockBehavior.Loose, null); + var collection = new List(); + mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); + mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); + mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); + mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); + collection.Add(mockRule.Object); + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + .ReturnsAsync((true, new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); + mockLDAPUtils.Setup(x => x.Query(It.IsAny(), It.IsAny())) + .Returns(Array.Empty>().ToAsyncEnumerable); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Utils.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + var result = await processor.ProcessACL(bytes, _testDomainName, Label.User, false).ToArrayAsync(); + + Assert.Single(result); + var actual = result.First(); + Assert.Equal(actual.PrincipalType, expectedPrincipalType); + Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); + Assert.False(actual.IsInherited); + Assert.Equal(actual.RightName, expectedRightName); + } + + [Fact] + public async Task ACLProcessor_ProcessACL_ExtendedRight_Computer_NoLAPS() { + var expectedPrincipalType = Label.Group; + var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var mockRule = new Mock(MockBehavior.Loose, null); + var collection = new List(); + mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); + mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); + mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); + mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); + collection.Add(mockRule.Object); + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + .ReturnsAsync((true, new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); + mockLDAPUtils.Setup(x => x.Query(It.IsAny(), It.IsAny())) + .Returns(Array.Empty>().ToAsyncEnumerable); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Utils.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + var result = await processor.ProcessACL(bytes, _testDomainName, Label.Computer, false).ToArrayAsync(); + + Assert.Empty(result); + } + + [Fact] + public async Task ACLProcessor_ProcessACL_ExtendedRight_Computer_All() { + var expectedPrincipalType = Label.Group; + var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + var expectedRightName = EdgeNames.AllExtendedRights; + + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var mockRule = new Mock(MockBehavior.Loose, null); + var collection = new List(); + mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); + mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); + mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.ExtendedRight); + mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); + collection.Add(mockRule.Object); + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + .ReturnsAsync((true, new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); + mockLDAPUtils.Setup(x => x.Query(It.IsAny(), It.IsAny())) + .Returns(Array.Empty>().ToAsyncEnumerable); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Utils.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + var result = await processor.ProcessACL(bytes, _testDomainName, Label.Computer, true).ToArrayAsync(); + + Assert.Single(result); + var actual = result.First(); + Assert.Equal(actual.PrincipalType, expectedPrincipalType); + Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); + Assert.False(actual.IsInherited); + Assert.Equal(actual.RightName, expectedRightName); + } + + [Fact] + public async Task ACLProcessor_ProcessACL_GenericWrite_Unmatched() { + var expectedPrincipalType = Label.Group; + var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var mockRule = new Mock(MockBehavior.Loose, null); + var collection = new List(); + mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); + mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); + mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.GenericWrite); + mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); + collection.Add(mockRule.Object); + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + .ReturnsAsync((true, new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); + mockLDAPUtils.Setup(x => x.Query(It.IsAny(), It.IsAny())) + .Returns(Array.Empty>().ToAsyncEnumerable); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Utils.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + var result = await processor.ProcessACL(bytes, _testDomainName, Label.Container, true).ToArrayAsync(); + + Assert.Empty(result); + } + + [Fact] + public async Task ACLProcessor_ProcessACL_GenericWrite_User_All() { + var expectedPrincipalType = Label.Group; + var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + var expectedRightName = EdgeNames.GenericWrite; + + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var mockRule = new Mock(MockBehavior.Loose, null); + var collection = new List(); + mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); + mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); + mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.GenericWrite); + mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.AllGuid)); + collection.Add(mockRule.Object); + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + .ReturnsAsync((true, new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); + mockLDAPUtils.Setup(x => x.Query(It.IsAny(), It.IsAny())) + .Returns(Array.Empty>().ToAsyncEnumerable); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Utils.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + var result = await processor.ProcessACL(bytes, _testDomainName, Label.User, true).ToArrayAsync(); + + Assert.Single(result); + var actual = result.First(); + Assert.Equal(actual.PrincipalType, expectedPrincipalType); + Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); + Assert.False(actual.IsInherited); + Assert.Equal(actual.RightName, expectedRightName); + } + + [Fact] + public async Task ACLProcessor_ProcessACL_GenericWrite_User_WriteMember() { + var expectedPrincipalType = Label.Group; + var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + var expectedRightName = EdgeNames.AddMember; + + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var mockRule = new Mock(MockBehavior.Loose, null); + var collection = new List(); + mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); + mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); + mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.GenericWrite); + mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.WriteMember)); + collection.Add(mockRule.Object); + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + .ReturnsAsync((true, new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); + mockLDAPUtils.Setup(x => x.Query(It.IsAny(), It.IsAny())) + .Returns(Array.Empty>().ToAsyncEnumerable); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Utils.B64ToBytes(AddMemberSecurityDescriptor); + var result = await processor.ProcessACL(bytes, _testDomainName, Label.Group, true).ToArrayAsync(); + + _testOutputHelper.WriteLine(JsonConvert.SerializeObject(result)); + + Assert.Single(result); + var actual = result.First(); + Assert.Equal(actual.PrincipalType, expectedPrincipalType); + Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); + Assert.False(actual.IsInherited); + Assert.Equal(actual.RightName, expectedRightName); + } + + [Fact] + public async Task ACLProcessor_ProcessACL_GenericWrite_Computer_WriteAllowedToAct() { + var expectedPrincipalType = Label.Group; + var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + var expectedRightName = EdgeNames.AddAllowedToAct; + + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var mockRule = new Mock(MockBehavior.Loose, null); + var collection = new List(); + mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); + mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); + mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.GenericWrite); + mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.WriteAllowedToAct)); + collection.Add(mockRule.Object); + + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) + .ReturnsAsync((true, new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType))); + mockLDAPUtils.Setup(x => x.Query(It.IsAny(), It.IsAny())) + .Returns(Array.Empty>().ToAsyncEnumerable); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var bytes = Utils.B64ToBytes(UnProtectedUserNtSecurityDescriptor); + var result = await processor.ProcessACL(bytes, _testDomainName, Label.Computer, true).ToArrayAsync(); + + Assert.Single(result); + var actual = result.First(); + Assert.Equal(actual.PrincipalType, expectedPrincipalType); + Assert.Equal(actual.PrincipalSID, expectedPrincipalSID); + Assert.False(actual.IsInherited); + Assert.Equal(actual.RightName, expectedRightName); + } + + [Fact] + public void GetInheritedAceHashes_NullSD_Empty() { + var proc = new ACLProcessor(new MockLdapUtils()); + var result = proc.GetInheritedAceHashes(null).ToArray(); + Assert.Empty(result); + } + + [Fact] + public void GetInheritedAceHashes_HappyPath() { + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + var mockRule = new Mock(MockBehavior.Loose, null); + const string expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + var collection = new List(); + mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); + mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); + mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.GenericWrite); + mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.WriteAllowedToAct)); + mockRule.Setup(x => x.IsInherited()).Returns(true); + mockRule.Setup(x => x.InheritanceFlags).Returns(InheritanceFlags.ContainerInherit); + collection.Add(mockRule.Object); + mockRule = new Mock(MockBehavior.Loose, null); + mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); + mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); + mockRule.Setup(x => x.IdentityReference()).Returns(expectedPrincipalSID); + mockRule.Setup(x => x.ActiveDirectoryRights()).Returns(ActiveDirectoryRights.GenericWrite); + mockRule.Setup(x => x.ObjectType()).Returns(new Guid(ACEGuids.WriteAllowedToAct)); + mockRule.Setup(x => x.IsInherited()).Returns(false); + mockRule.Setup(x => x.InheritanceFlags).Returns(InheritanceFlags.ContainerInherit); + collection.Add(mockRule.Object); + mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(collection); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + var processor = new ACLProcessor(mockLDAPUtils.Object); + var result = processor.GetInheritedAceHashes(Array.Empty()).ToArray(); + Assert.Single(result); + } + + [Fact] + public void Test_ACLInheritanceHashSame() { + const string expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; + var g = new Guid().ToString(); + var result1 = ACLProcessor.CalculateInheritanceHash(expectedPrincipalSID, + ActiveDirectoryRights.GenericWrite, new Guid(ACEGuids.WriteAllowedToAct).ToString(), g); + var result2 = ACLProcessor.CalculateInheritanceHash(expectedPrincipalSID, + ActiveDirectoryRights.GenericWrite, new Guid(ACEGuids.WriteAllowedToAct).ToString(), g); + + Assert.Equal(result1, result2); + } + + [Fact] + public void Test_ACLProcessor_IsACLProtected_Protected() { + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + mockSecurityDescriptor.Setup(x => x.AreAccessRulesProtected()).Returns(true); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var result = processor.IsACLProtected(Array.Empty()); + Assert.True(result); + } + + [Fact] + public void Test_ACLProcessor_IsACLProtected_NotProtected() { + var mockLDAPUtils = new Mock(); + var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); + mockSecurityDescriptor.Setup(x => x.AreAccessRulesProtected()).Returns(false); + mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + + var processor = new ACLProcessor(mockLDAPUtils.Object); + var result = processor.IsACLProtected(Array.Empty()); + Assert.False(result); + } } } \ No newline at end of file diff --git a/test/unit/CertAbuseProcessorTest.cs b/test/unit/CertAbuseProcessorTest.cs index 4b025297..3a5ce818 100644 --- a/test/unit/CertAbuseProcessorTest.cs +++ b/test/unit/CertAbuseProcessorTest.cs @@ -1,31 +1,19 @@ using System; -using System.DirectoryServices; -using CommonLibTest.Facades; -using Moq; -using Newtonsoft.Json; -using SharpHoundCommonLib; -using SharpHoundCommonLib.Enums; -using SharpHoundCommonLib.Processors; -using Xunit; using Xunit.Abstractions; -namespace CommonLibTest -{ +namespace CommonLibTest { //TODO: Make these tests work - public class CertAbuseProcessorTest : IDisposable - { + public class CertAbuseProcessorTest : IDisposable { private const string CASecurityFixture = "AQAUhCABAAAwAQAAFAAAAEQAAAACADAAAgAAAALAFAD//wAAAQEAAAAAAAEAAAAAAsAUAP//AAABAQAAAAAABQcAAAACANwABwAAAAADGAABAAAAAQIAAAAAAAUgAAAAIAIAAAADGAACAAAAAQIAAAAAAAUgAAAAIAIAAAADJAABAAAAAQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQAAIAAAADJAACAAAAAQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQAAIAAAADJAABAAAAAQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQBwIAAAADJAACAAAAAQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQBwIAAAADFAAAAgAAAQEAAAAAAAULAAAAAQIAAAAAAAUgAAAAIAIAAAECAAAAAAAFIAAAACACAAA="; private readonly ITestOutputHelper _testOutputHelper; - public CertAbuseProcessorTest(ITestOutputHelper testOutputHelper) - { + public CertAbuseProcessorTest(ITestOutputHelper testOutputHelper) { _testOutputHelper = testOutputHelper; } - public void Dispose() - { + public void Dispose() { } // [Fact] diff --git a/test/unit/CommonLibHelperTests.cs b/test/unit/CommonLibHelperTests.cs index 8c7f1d96..2c054eed 100644 --- a/test/unit/CommonLibHelperTests.cs +++ b/test/unit/CommonLibHelperTests.cs @@ -1,38 +1,35 @@ using System; using System.Text; +using SharpHoundCommonLib; using SharpHoundCommonLib.Enums; using Xunit; -namespace CommonLibTest -{ - public class CommonLibHelperTest - { +namespace CommonLibTest { + public class CommonLibHelperTest { [Fact] - public void RemoveDistinguishedNamePrefix_ExpectedResult() - { + public void RemoveDistinguishedNamePrefix_ExpectedResult() { var dn = "CN=Jeff Smith,OU=Sales,DC=Fabrikam,DC=COM"; - var result = SharpHoundCommonLib.Helpers.RemoveDistinguishedNamePrefix(dn); + var result = Helpers.RemoveDistinguishedNamePrefix(dn); Assert.Equal("OU=Sales,DC=Fabrikam,DC=COM", result); - result = SharpHoundCommonLib.Helpers.RemoveDistinguishedNamePrefix( + result = Helpers.RemoveDistinguishedNamePrefix( "CN=Administrator,CN=Users,DC=testlab,DC=local"); Assert.Equal("CN=Users,DC=testlab,DC=local", result); - result = SharpHoundCommonLib.Helpers.RemoveDistinguishedNamePrefix( + result = Helpers.RemoveDistinguishedNamePrefix( "CN=Litware,OU=Docs\\, Adatum,DC=Fabrikam,DC=COM"); Assert.Equal("OU=Docs\\, Adatum,DC=Fabrikam,DC=COM", result); - result = SharpHoundCommonLib.Helpers.RemoveDistinguishedNamePrefix( + result = Helpers.RemoveDistinguishedNamePrefix( "OU=Test\\, OU,OU=Test,DC=Fabrikam,DC=COM"); Assert.Equal("OU=Test,DC=Fabrikam,DC=COM", result); } [Fact] - public void SplitGPLinkProperty_ValidPropFilterEnabled_ExpectedResult() - { + public void SplitGPLinkProperty_ValidPropFilterEnabled_ExpectedResult() { var isPropFilterEnabled = false; //TODO: Ari, proper test string? var testGPLinkProperty = "[LDAP:/o=foo/ou=foo Group (ABC123)/cn=foouser (blah)123; SIP:foouser@example.co.uk; smtp:foouser@sub1.example.co.uk; smtp:foouser@sub2.example.co.uk; SMTP:foouser@example.co.uk][]"; - var res = SharpHoundCommonLib.Helpers.SplitGPLinkProperty(testGPLinkProperty, isPropFilterEnabled); + var res = Helpers.SplitGPLinkProperty(testGPLinkProperty, isPropFilterEnabled); foreach (var parsedGPLink in res) Assert.Equal("cn=foouser (blah)123", parsedGPLink.DistinguishedName); @@ -40,14 +37,13 @@ public void SplitGPLinkProperty_ValidPropFilterEnabled_ExpectedResult() } [Fact] - public void SplitGPLinkProperty_ValidPropFilterDisabled_ExpectedResult() - { + public void SplitGPLinkProperty_ValidPropFilterDisabled_ExpectedResult() { var isPropFilterEnabled = false; //TODO: Ari, proper test string? var testGPLinkProperty = "[LDAP:/o=foo/ou=foo Group (ABC123)/cn=foouser (blah)123; SIP:foouser@example.co.uk; smtp:foouser@sub1.example.co.uk; smtp:foouser@sub2.example.co.uk; SMTP:foouser@example.co.uk][]"; - var res = SharpHoundCommonLib.Helpers.SplitGPLinkProperty(testGPLinkProperty, isPropFilterEnabled); + var res = Helpers.SplitGPLinkProperty(testGPLinkProperty, isPropFilterEnabled); foreach (var parsedGPLink in res) Assert.Equal("cn=foouser (blah)123", parsedGPLink.DistinguishedName); @@ -56,14 +52,13 @@ public void SplitGPLinkProperty_ValidPropFilterDisabled_ExpectedResult() /// [Fact] - public void SplitGPLinkProperty_PropWithUnsupportedDelimiter_FilterEnabled_ExpectedResult() - { + public void SplitGPLinkProperty_PropWithUnsupportedDelimiter_FilterEnabled_ExpectedResult() { var isPropFilterEnabled = true; //TODO: Ari, proper test string? var testGPLinkProperty = "[LDAP:/o=foo/ou=foo Group (ABC123)/cn=foouser (blah)123; DC=somedomainName; SIP:foouser@example.co.uk; smtp:foouser@sub1.example.co.uk; smtp:foouser@sub2.example.co.uk; SMTP:foouser@example.co.uk][]"; - var res = SharpHoundCommonLib.Helpers.SplitGPLinkProperty(testGPLinkProperty, isPropFilterEnabled); + var res = Helpers.SplitGPLinkProperty(testGPLinkProperty, isPropFilterEnabled); foreach (var parsedGPLink in res) Assert.Equal("cn=foouser (blah)123", parsedGPLink.DistinguishedName); @@ -71,20 +66,17 @@ public void SplitGPLinkProperty_PropWithUnsupportedDelimiter_FilterEnabled_Expec } [Fact] - public void SplitGPLinkProperty_InValidPropFilterDisabled_ExpectedResult() - { + public void SplitGPLinkProperty_InValidPropFilterDisabled_ExpectedResult() { var isPropFilterEnabled = false; //TODO: Ari, proper test string? var testGPLinkProperty = "/*obviously wrong data*/"; - var res = SharpHoundCommonLib.Helpers.SplitGPLinkProperty(testGPLinkProperty, isPropFilterEnabled); + var res = Helpers.SplitGPLinkProperty(testGPLinkProperty, isPropFilterEnabled); Assert.Empty(res); } [Fact] - public void SamAccountTypeToType_ValidString_CorrectLabel() - { - var accountTypeLookup = new (string accountType, Label label)[] - { + public void SamAccountTypeToType_ValidString_CorrectLabel() { + var accountTypeLookup = new (string accountType, Label label)[] { (accountType: "268435456", label: Label.Group), (accountType: "268435457", label: Label.Group), (accountType: "536870912", label: Label.Group), @@ -93,17 +85,15 @@ public void SamAccountTypeToType_ValidString_CorrectLabel() (accountType: "805306368", Label.User) }; - foreach (var e in accountTypeLookup) - { - var result = SharpHoundCommonLib.Helpers.SamAccountTypeToType(e.accountType); + foreach (var e in accountTypeLookup) { + var result = Helpers.SamAccountTypeToType(e.accountType); Assert.Equal(result, e.label); } } [Fact] - public void SamAccountTypeToType_InValidString_CorrectLabel() - { - var result = SharpHoundCommonLib.Helpers.SamAccountTypeToType("nonsense_^&^^&(*^*^*&(&^&(^*AAAA"); + public void SamAccountTypeToType_InValidString_CorrectLabel() { + var result = Helpers.SamAccountTypeToType("nonsense_^&^^&(*^*^*&(&^&(^*AAAA"); Assert.Equal(Label.Base, result); } @@ -120,12 +110,11 @@ public void SamAccountTypeToType_InValidString_CorrectLabel() // } [Fact] - public void ConvertGuidToHexGuid_ValidStringGuid_ValidHex() - { + public void ConvertGuidToHexGuid_ValidStringGuid_ValidHex() { // Atmoic conversion test. Add as many variants as needed to increase confidence. var guid = Guid.NewGuid(); - var hexString = SharpHoundCommonLib.Helpers.ConvertGuidToHexGuid(guid.ToString()); + var hexString = Helpers.ConvertGuidToHexGuid(guid.ToString()); // We recreate part of thr operation here to test parity. If you the function under test changes this code then the test may fail and indicate drift in the code away from expected behavior. var output = $"\\{BitConverter.ToString(guid.ToByteArray()).Replace('-', '\\')}"; @@ -133,96 +122,85 @@ public void ConvertGuidToHexGuid_ValidStringGuid_ValidHex() } [Fact] - public void DistinguishedNameToDomain_ValidDistinguishedName_ExpectedDomainValue() - { + public void DistinguishedNameToDomain_ValidDistinguishedName_ExpectedDomainValue() { var expected = "FABRIKAM.COM"; var actual = - SharpHoundCommonLib.Helpers.DistinguishedNameToDomain("CN=Jeff Smith,OU=Sales,DC=Fabrikam,DC=COM"); + Helpers.DistinguishedNameToDomain("CN=Jeff Smith,OU=Sales,DC=Fabrikam,DC=COM"); Assert.Equal(expected, actual); } [Fact] - public void DistinguishedNameToDomain_InValidDistinguishedName_ReturnsNull() - { + public void DistinguishedNameToDomain_InValidDistinguishedName_ReturnsNull() { var testDCQuery = "[LDAP:/o=foo/ou=foo Group (ABC123)/cn=foouser (blah)123; DX=wjatvar][]"; - var actual = SharpHoundCommonLib.Helpers.DistinguishedNameToDomain(testDCQuery); + var actual = Helpers.DistinguishedNameToDomain(testDCQuery); Assert.Null(actual); } [Fact] - public void StripServicePrincipalName_ValidServicePrincipal_ExpectedHostName() - { + public void StripServicePrincipalName_ValidServicePrincipal_ExpectedHostName() { var testString = "www/WEB-SERVER-01.adsec.local"; var expected = "WEB-SERVER-01.adsec.local"; - var actual = SharpHoundCommonLib.Helpers.StripServicePrincipalName(testString); + var actual = Helpers.StripServicePrincipalName(testString); Assert.Equal(expected, actual); } [Fact] - public void StripServicePrincipalName_InValidServicePrincipal_ExpectedHostName() - { + public void StripServicePrincipalName_InValidServicePrincipal_ExpectedHostName() { var testString = "234234f___bb4::fadfs"; var expected = "234234f___bb4::fadfs"; - var actual = SharpHoundCommonLib.Helpers.StripServicePrincipalName(testString); + var actual = Helpers.StripServicePrincipalName(testString); Assert.Equal(expected, actual); } [Fact] - public void StripServicePrincipalName_EmptyHost_Valid() - { + public void StripServicePrincipalName_EmptyHost_Valid() { var testString = "MSSQLSvc/:1433"; var expected = ""; - var actual = SharpHoundCommonLib.Helpers.StripServicePrincipalName(testString); + var actual = Helpers.StripServicePrincipalName(testString); Assert.Equal(expected, actual); } [Fact] - public void B64ToBytes_String_ValidBase64String() - { + public void B64ToBytes_String_ValidBase64String() { var testString = "obviously nonsense"; var exampleBytes = Encoding.UTF8.GetBytes(testString); var compareString = Convert.ToBase64String(exampleBytes); - var result = SharpHoundCommonLib.Helpers.Base64(testString); + var result = Helpers.Base64(testString); Assert.Equal(compareString, result); } [Fact] - public void ConvertFileTimeToUnixEpoch_ValidFileTime_ValidUnixEpoch() - { + public void ConvertFileTimeToUnixEpoch_ValidFileTime_ValidUnixEpoch() { var testFileTime = "132260149842749745"; - var result = SharpHoundCommonLib.Helpers.ConvertFileTimeToUnixEpoch(testFileTime); + var result = Helpers.ConvertFileTimeToUnixEpoch(testFileTime); var expected = 1581541384; Assert.Equal(expected, result); } [Fact] - public void ConvertFileTimeToUnixEpoch_Null_NegativeOne() - { - var result = SharpHoundCommonLib.Helpers.ConvertFileTimeToUnixEpoch(null); + public void ConvertFileTimeToUnixEpoch_Null_NegativeOne() { + var result = Helpers.ConvertFileTimeToUnixEpoch(null); Assert.Equal(-1, result); } [Fact] - public void ConvertFileTimeToUnixEpoch_WrongFormat_FortmatException() - { + public void ConvertFileTimeToUnixEpoch_WrongFormat_FortmatException() { Exception ex = - Assert.Throws(() => SharpHoundCommonLib.Helpers.ConvertFileTimeToUnixEpoch("asdsf")); + Assert.Throws(() => Helpers.ConvertFileTimeToUnixEpoch("asdsf")); Assert.Equal("The input string 'asdsf' was not in a correct format.", ex.Message); } [Fact] - public void ConvertFileTimeToUnixEpoch_BadInput_CastExceptionReturnsNegativeOne() - { - var result = SharpHoundCommonLib.Helpers.ConvertFileTimeToUnixEpoch("-3242432"); + public void ConvertFileTimeToUnixEpoch_BadInput_CastExceptionReturnsNegativeOne() { + var result = Helpers.ConvertFileTimeToUnixEpoch("-3242432"); Assert.Equal(-1, result); } [Fact] - public void ConvertTimestampToUnixEpoch_ValidTimestamp_ValidUnixEpoch() - { + public void ConvertTimestampToUnixEpoch_ValidTimestamp_ValidUnixEpoch() { var d = DateTime.Parse("2021-06-21T00:00:00"); var result = - SharpHoundCommonLib.Helpers.ConvertFileTimeToUnixEpoch(d.ToFileTimeUtc().ToString()); // get the epoch + Helpers.ConvertFileTimeToUnixEpoch(d.ToFileTimeUtc().ToString()); // get the epoch var dateTimeOffset = DateTimeOffset.FromUnixTimeSeconds(result); // create an offset from the epoch var testDate = dateTimeOffset.UtcDateTime; @@ -230,11 +208,27 @@ public void ConvertTimestampToUnixEpoch_ValidTimestamp_ValidUnixEpoch() } [Fact] - public void ConvertTimestampToUnixEpoch_InvalidTimestamp_FormatException() - { + public void ConvertTimestampToUnixEpoch_InvalidTimestamp_FormatException() { Exception ex = Assert.Throws(() => - SharpHoundCommonLib.Helpers.ConvertFileTimeToUnixEpoch("-201adsfasf12180244")); + Helpers.ConvertFileTimeToUnixEpoch("-201adsfasf12180244")); Assert.Equal("The input string '-201adsfasf12180244' was not in a correct format.", ex.Message); } + + [Fact] + public void DistinguishedNameToDomain_RegularObject_CorrectDomain() { + var result = Helpers.DistinguishedNameToDomain( + "CN=Account Operators,CN=Builtin,DC=testlab,DC=local"); + Assert.Equal("TESTLAB.LOCAL", result); + + result = Helpers.DistinguishedNameToDomain("DC=testlab,DC=local"); + Assert.Equal("TESTLAB.LOCAL", result); + } + + [Fact] + public void DistinguishedNameToDomain_DeletedObjects_CorrectDomain() { + var result = Helpers.DistinguishedNameToDomain( + @"DC=..Deleted-_msdcs.testlab.local\0ADEL:af1f072f-28d7-4b86-9b87-a408bfc9cb0d,CN=Deleted Objects,DC=testlab,DC=local"); + Assert.Equal("TESTLAB.LOCAL", result); + } } } \ No newline at end of file diff --git a/test/unit/ComputerAvailabilityTests.cs b/test/unit/ComputerAvailabilityTests.cs index 96fbda24..2a4dd9e7 100644 --- a/test/unit/ComputerAvailabilityTests.cs +++ b/test/unit/ComputerAvailabilityTests.cs @@ -36,7 +36,7 @@ public void Dispose() public async Task ComputerAvailability_IsComputerAvailable_BadOperatingSystem_ReturnsFalse() { var processor = new ComputerAvailability(); - var test = await processor.IsComputerAvailable("test", "Linux Mint 1.0", "132682398326125518"); + var test = await processor.IsComputerAvailable("test", "Linux Mint 1.0", "132682398326125518", "132682398326125518"); Assert.False(test.Connectable); Assert.Equal(ComputerStatus.NonWindowsOS, test.Error); @@ -50,10 +50,10 @@ public async Task ComputerAvailability_IsComputerAvailable_OldPwdLastSet_Returns //Create a date 91 days ago. Our threshold for pwdlastset is 90 days var n = DateTime.Now.AddDays(-91) - new DateTime(1601, 01, 01, 0, 0, 0, DateTimeKind.Utc); - var test = await processor.IsComputerAvailable("test", "Windows 10 Enterprise", n.Ticks.ToString()); + var test = await processor.IsComputerAvailable("test", "Windows 10 Enterprise", n.Ticks.ToString(), n.Ticks.ToString()); Assert.False(test.Connectable); - Assert.Equal(ComputerStatus.OldPwd, test.Error); + Assert.Equal(ComputerStatus.NotActive, test.Error); } [Fact] @@ -64,7 +64,7 @@ public async Task ComputerAvailability_IsComputerAvailable_PortClosed_ReturnsFal //Create a date 5 days ago var n = DateTime.Now.AddDays(-5) - new DateTime(1601, 01, 01, 0, 0, 0, DateTimeKind.Utc); - var test = await processor.IsComputerAvailable("test", "Windows 10 Enterprise", n.Ticks.ToString()); + var test = await processor.IsComputerAvailable("test", "Windows 10 Enterprise", n.Ticks.ToString(), n.Ticks.ToString()); Assert.False(test.Connectable); Assert.Equal(ComputerStatus.PortNotOpen, test.Error); @@ -78,7 +78,7 @@ public async Task ComputerAvailability_IsComputerAvailable_PortOpen_ReturnsTrue( //Create a date 5 days ago var n = DateTime.Now.AddDays(-5) - new DateTime(1601, 01, 01, 0, 0, 0, DateTimeKind.Utc); - var test = await processor.IsComputerAvailable("test", "Windows 10 Enterprise", n.Ticks.ToString()); + var test = await processor.IsComputerAvailable("test", "Windows 10 Enterprise", n.Ticks.ToString(), n.Ticks.ToString()); Assert.True(test.Connectable); } diff --git a/test/unit/ComputerSessionProcessorTest.cs b/test/unit/ComputerSessionProcessorTest.cs index ec860c83..58f009df 100644 --- a/test/unit/ComputerSessionProcessorTest.cs +++ b/test/unit/ComputerSessionProcessorTest.cs @@ -47,7 +47,7 @@ public async Task ComputerSessionProcessor_ReadUserSessions_FilteringWorks() }; mockNativeMethods.Setup(x => x.NetSessionEnum(It.IsAny())).Returns(apiResult); - var processor = new ComputerSessionProcessor(new MockLDAPUtils(), "dfm", mockNativeMethods.Object); + var processor = new ComputerSessionProcessor(new MockLdapUtils(), "dfm", mockNativeMethods.Object); var result = await processor.ReadUserSessions("win10", _computerSid, _computerDomain); Assert.True(result.Collected); Assert.Empty(result.Results); @@ -72,7 +72,7 @@ public async Task ComputerSessionProcessor_ReadUserSessions_ResolvesHost() } }; - var processor = new ComputerSessionProcessor(new MockLDAPUtils(), "dfm", mockNativeMethods.Object); + var processor = new ComputerSessionProcessor(new MockLdapUtils(), "dfm", mockNativeMethods.Object); var result = await processor.ReadUserSessions("win10", _computerSid, _computerDomain); Assert.True(result.Collected); Assert.Equal(expected, result.Results); @@ -97,7 +97,7 @@ public async Task ComputerSessionProcessor_ReadUserSessions_ResolvesLocalHostEqu } }; - var processor = new ComputerSessionProcessor(new MockLDAPUtils(), "dfm", mockNativeMethods.Object); + var processor = new ComputerSessionProcessor(new MockLdapUtils(), "dfm", mockNativeMethods.Object); var result = await processor.ReadUserSessions("win10", _computerSid, _computerDomain); Assert.True(result.Collected); Assert.Equal(expected, result.Results); @@ -127,7 +127,7 @@ public async Task ComputerSessionProcessor_ReadUserSessions_MultipleMatches_Adds } }; - var processor = new ComputerSessionProcessor(new MockLDAPUtils(), "dfm", mockNativeMethods.Object); + var processor = new ComputerSessionProcessor(new MockLdapUtils(), "dfm", mockNativeMethods.Object); var result = await processor.ReadUserSessions("win10", _computerSid, _computerDomain); Assert.True(result.Collected); Assert.Equal(expected, result.Results); @@ -152,7 +152,7 @@ public async Task ComputerSessionProcessor_ReadUserSessions_NoGCMatch_TriesResol } }; - var processor = new ComputerSessionProcessor(new MockLDAPUtils(), "dfm", mockNativeMethods.Object); + var processor = new ComputerSessionProcessor(new MockLdapUtils(), "dfm", mockNativeMethods.Object); var result = await processor.ReadUserSessions("win10", _computerSid, _computerDomain); Assert.True(result.Collected); Assert.Equal(expected, result.Results); @@ -165,7 +165,7 @@ public async Task ComputerSessionProcessor_ReadUserSessions_ComputerAccessDenied //mockNativeMethods.Setup(x => x.CallSamConnect(ref It.Ref.IsAny, out It.Ref.IsAny, It.IsAny(), ref It.Ref.IsAny)).Returns(NativeMethods.NtStatus.StatusAccessDenied); mockNativeMethods.Setup(x => x.NetSessionEnum(It.IsAny())) .Returns(NetAPIEnums.NetAPIStatus.ErrorAccessDenied); - var processor = new ComputerSessionProcessor(new MockLDAPUtils(), "dfm", mockNativeMethods.Object); + var processor = new ComputerSessionProcessor(new MockLdapUtils(), "dfm", mockNativeMethods.Object); var test = await processor.ReadUserSessions("test", "test", "test"); Assert.False(test.Collected); Assert.Equal(NetAPIEnums.NetAPIStatus.ErrorAccessDenied.ToString(), test.FailureReason); @@ -178,7 +178,7 @@ public async Task ComputerSessionProcessor_ReadUserSessionsPrivileged_ComputerAc //mockNativeMethods.Setup(x => x.CallSamConnect(ref It.Ref.IsAny, out It.Ref.IsAny, It.IsAny(), ref It.Ref.IsAny)).Returns(NativeMethods.NtStatus.StatusAccessDenied); mockNativeMethods.Setup(x => x.NetWkstaUserEnum(It.IsAny())) .Returns(NetAPIEnums.NetAPIStatus.ErrorAccessDenied); - var processor = new ComputerSessionProcessor(new MockLDAPUtils(), "dfm", mockNativeMethods.Object); + var processor = new ComputerSessionProcessor(new MockLdapUtils(), "dfm", mockNativeMethods.Object); var test = await processor.ReadUserSessionsPrivileged("test", "test", "test"); Assert.False(test.Collected); Assert.Equal(NetAPIEnums.NetAPIStatus.ErrorAccessDenied.ToString(), test.FailureReason); @@ -220,7 +220,7 @@ public async Task ComputerSessionProcessor_ReadUserSessionsPrivileged_FilteringW } }; - var processor = new ComputerSessionProcessor(new MockLDAPUtils(), nativeMethods: mockNativeMethods.Object, currentUserName:"ADMINISTRATOR"); + var processor = new ComputerSessionProcessor(new MockLdapUtils(), nativeMethods: mockNativeMethods.Object, currentUserName:"ADMINISTRATOR"); var test = await processor.ReadUserSessionsPrivileged("WIN10.TESTLAB.LOCAL", samAccountName, _computerSid); Assert.True(test.Collected); _testOutputHelper.WriteLine(JsonConvert.SerializeObject(test.Results)); diff --git a/test/unit/ContainerProcessorTest.cs b/test/unit/ContainerProcessorTest.cs index 9a67c0cf..5c375442 100644 --- a/test/unit/ContainerProcessorTest.cs +++ b/test/unit/ContainerProcessorTest.cs @@ -33,7 +33,7 @@ public void Dispose() [Fact] public async Task ContainerProcessor_ReadContainerGPLinks_IgnoresNull() { - var processor = new ContainerProcessor(new MockLDAPUtils()); + var processor = new ContainerProcessor(new MockLdapUtils()); var test = await processor.ReadContainerGPLinks(null).ToArrayAsync(); Assert.Empty(test); } @@ -41,7 +41,7 @@ public async Task ContainerProcessor_ReadContainerGPLinks_IgnoresNull() [Fact] public async Task ContainerProcessor_ReadContainerGPLinks_UnresolvedGPLink_IsIgnored() { - var processor = new ContainerProcessor(new MockLDAPUtils()); + var processor = new ContainerProcessor(new MockLdapUtils()); //GPLink that doesn't exist const string s = "[LDAP://cn={94DD0260-38B5-497E-8876-ABCDEFG},cn=policies,cn=system,DC=testlab,DC=local;0]"; @@ -52,7 +52,7 @@ public async Task ContainerProcessor_ReadContainerGPLinks_UnresolvedGPLink_IsIgn [Fact] public async Task ContainerProcessor_ReadContainerGPLinks_ReturnsCorrectValues() { - var processor = new ContainerProcessor(new MockLDAPUtils()); + var processor = new ContainerProcessor(new MockLdapUtils()); var test = await processor.ReadContainerGPLinks(_testGpLinkString).ToArrayAsync(); var expected = new GPLink[] @@ -81,24 +81,24 @@ public async Task ContainerProcessor_ReadContainerGPLinks_ReturnsCorrectValues() [Fact] public async Task ContainerProcessor_GetContainerChildObjects_ReturnsCorrectData() { - var mock = new Mock(); + var mock = new Mock(); var searchResults = new[] { //These first 4 should be filtered by our DN filters - LdapResult.Ok(new MockSearchResultEntry( + LdapResult.Ok(new MockDirectoryObject( "CN=7868d4c8-ac41-4e05-b401-776280e8e9f1,CN=Operations,CN=DomainUpdates,CN=System,DC=testlab,DC=local" - , null, null, Label.Base)), - LdapResult.Ok(new MockSearchResultEntry("CN=Microsoft,CN=Program Data,DC=testlab,DC=local", null, null, Label.Base)), - LdapResult.Ok(new MockSearchResultEntry("CN=Operations,CN=DomainUpdates,CN=System,DC=testlab,DC=local", null, null, Label.Base)), - LdapResult.Ok(new MockSearchResultEntry("CN=User,CN={C52F168C-CD05-4487-B405-564934DA8EFF},CN=Policies,CN=System,DC=testlab,DC=local", null, - null, Label.Base)), + , null, null,null)), + LdapResult.Ok(new MockDirectoryObject("CN=Microsoft,CN=Program Data,DC=testlab,DC=local", null, null,null)), + LdapResult.Ok(new MockDirectoryObject("CN=Operations,CN=DomainUpdates,CN=System,DC=testlab,DC=local", null, null,null)), + LdapResult.Ok(new MockDirectoryObject("CN=User,CN={C52F168C-CD05-4487-B405-564934DA8EFF},CN=Policies,CN=System,DC=testlab,DC=local", null, + null,null)), //This is a real object in our mock - LdapResult.Ok(new MockSearchResultEntry("CN=Users,DC=testlab,DC=local", null, "ECAD920E-8EB1-4E31-A80E-DD36367F81F4", Label.Container)), + LdapResult.Ok(new MockDirectoryObject("CN=Users,DC=testlab,DC=local", null, "","ECAD920E-8EB1-4E31-A80E-DD36367F81F4")), //This object does not exist in our mock - LdapResult.Ok(new MockSearchResultEntry("CN=Users,DC=testlab,DC=local", null, "ECAD920E-8EB1-4E31-A80E-DD36367F81FD", Label.Container)), + LdapResult.Ok(new MockDirectoryObject("CN=Users,DC=testlab,DC=local", null, "","ECAD920E-8EB1-4E31-A80E-DD36367F81FD")), //Test null objectid - LdapResult.Ok(new MockSearchResultEntry("CN=Users,DC=testlab,DC=local", null, null, Label.Container)) + LdapResult.Ok(new MockDirectoryObject("CN=Users,DC=testlab,DC=local", null, null, "")) }; mock.Setup(x => x.Query(It.IsAny(), It.IsAny())).Returns(searchResults.ToAsyncEnumerable); @@ -135,7 +135,7 @@ public async Task ContainerProcessor_ReadBlocksInheritance_ReturnsCorrectValues( [Fact] public async Task ContainerProcessor_GetContainingObject_ExpectedResult() { - var utils = new MockLDAPUtils(); + var utils = new MockLdapUtils(); var proc = new ContainerProcessor(utils); var (success, result) = await proc.GetContainingObject("OU=TESTOU,DC=TESTLAB,DC=LOCAL"); @@ -157,7 +157,7 @@ public async Task ContainerProcessor_GetContainingObject_ExpectedResult() [Fact] public async Task ContainerProcessor_GetContainingObject_BadDN_ReturnsNull() { - var utils = new MockLDAPUtils(); + var utils = new MockLdapUtils(); var proc = new ContainerProcessor(utils); var (success, result) = await proc.GetContainingObject("abc123"); diff --git a/test/unit/DirectoryObjectTests.cs b/test/unit/DirectoryObjectTests.cs new file mode 100644 index 00000000..9c3f930f --- /dev/null +++ b/test/unit/DirectoryObjectTests.cs @@ -0,0 +1,320 @@ +using System; +using System.Collections.Generic; +using System.Security.Principal; +using CommonLibTest.Facades; +using SharpHoundCommonLib; +using SharpHoundCommonLib.DirectoryObjects; +using SharpHoundCommonLib.Enums; +using Xunit; + +namespace CommonLibTest { + public class DirectoryObjectTests { + [Fact] + public void Test_GetLabelIssuanceOIDObjects() { + var attribs = new Dictionary { + { LDAPProperties.ObjectClass, new[] { "msPKI-Enterprise-Oid" } }, + { LDAPProperties.Flags, "2" } + }; + + var mock = new MockDirectoryObject("CN=Test,CN=OID,CN=Public Key Services,CN=Services,CN=Configuration", + attribs, "S-1-5-21-3130019616-2776909439-2417379446-500", ""); + + var success = mock.GetLabel(out var label); + Assert.True(success); + Assert.Equal(Label.IssuancePolicy, label); + + mock = new MockDirectoryObject("CN=OID,CN=Public Key Services,CN=Services,CN=Configuration", + attribs, "S-1-5-21-3130019616-2776909439-2417379446-500", ""); + success = mock.GetLabel(out label); + Assert.True(success); + Assert.Equal(Label.Container, label); + } + + [Fact] + public void Test_HasLaps() { + var attribs = new Dictionary { + { LDAPProperties.LegacyLAPSExpirationTime, 12345 }, + }; + + var mock = new MockDirectoryObject("abc", attribs, "", ""); + Assert.True(mock.HasLAPS()); + + mock.Properties = new Dictionary { + { LDAPProperties.LAPSExpirationTime, 12345 }, + }; + + Assert.True(mock.HasLAPS()); + + mock.Properties = new Dictionary { + { LDAPProperties.Flags, 0 } + }; + + Assert.False(mock.HasLAPS()); + } + + [Fact] + public void Test_IsDeleted() { + var attribs = new Dictionary { + { LDAPProperties.IsDeleted, "true" }, + }; + + var mock = new MockDirectoryObject("abc", attribs, "", ""); + Assert.True(mock.IsDeleted()); + + mock.Properties = new Dictionary { + { LDAPProperties.IsDeleted, false }, + }; + Assert.False(mock.IsDeleted()); + + mock.Properties = new Dictionary(); + Assert.False(mock.IsDeleted()); + } + + [Fact] + public void Test_IsMSA() { + var attribs = new Dictionary { + { LDAPProperties.ObjectClass, new[] { "top", "msds-managedserviceaccount" } }, + }; + + var mock = new MockDirectoryObject("abc", attribs, "", ""); + Assert.True(mock.IsMSA()); + Assert.False(mock.IsGMSA()); + + mock.Properties = new Dictionary { + { LDAPProperties.ObjectClass, new[] { "top" } }, + }; + + Assert.False(mock.IsMSA()); + Assert.False(mock.IsGMSA()); + + mock.Properties = new Dictionary(); + Assert.False(mock.IsGMSA()); + Assert.False(mock.IsMSA()); + } + + [Fact] + public void Test_IsGMSA() { + var attribs = new Dictionary { + { LDAPProperties.ObjectClass, new[] { "top", "msds-groupmanagedserviceaccount" } }, + }; + + var mock = new MockDirectoryObject("abc", attribs, "", ""); + Assert.True(mock.IsGMSA()); + Assert.False(mock.IsMSA()); + + mock.Properties = new Dictionary { + { LDAPProperties.ObjectClass, new[] { "top" } }, + }; + + Assert.False(mock.IsGMSA()); + Assert.False(mock.IsMSA()); + + mock.Properties = new Dictionary(); + Assert.False(mock.IsGMSA()); + Assert.False(mock.IsMSA()); + } + + [Fact] + public void Test_GetLabel_BadObjectID() { + var attribs = new Dictionary { + { LDAPProperties.ObjectClass, new[] { "top", "msds-groupmanagedserviceaccount" } }, + }; + + var mock = new MockDirectoryObject("abc", attribs, "", ""); + Assert.False(mock.GetLabel(out var label)); + Assert.Equal(Label.Base, label); + } + + [Fact] + public void Test_GetLabel_WellKnownAdministratorsObject() { + var attribs = new Dictionary() { + { LDAPProperties.ObjectClass, new[] { "top" } }, + { LDAPProperties.Flags, "2" }, + { LDAPProperties.SAMAccountType, "805306368" } + }; + + var mock = new MockDirectoryObject("CN=Administrators,CN=BuiltIn,DC=Testlab,DC=Local", attribs, + "S-1-5-32-544", new Guid().ToString()); + + Assert.True(mock.GetLabel(out var label)); + Assert.Equal(Label.Group, label); + } + + [Fact] + public void Test_GetLabel_Computer_Objects() { + var attribs = new Dictionary { + { LDAPProperties.ObjectClass, new[] { "top", "msds-groupmanagedserviceaccount" } }, + { LDAPProperties.Flags, "2" }, + { LDAPProperties.SAMAccountType, "805306369" } + }; + + var mock = new MockDirectoryObject("abc", attribs, + "123456", new Guid().ToString()); + + Assert.True(mock.GetLabel(out var label)); + Assert.Equal(Label.User, label); + + mock.Properties = new Dictionary { + { LDAPProperties.ObjectClass, new[] { "top", "msds-managedserviceaccount" } }, + { LDAPProperties.Flags, "2" }, + { LDAPProperties.SAMAccountType, "805306369" } + }; + + Assert.True(mock.GetLabel(out label)); + Assert.Equal(Label.User, label); + + mock.Properties = new Dictionary { + { LDAPProperties.ObjectClass, new[] { "top", "computer" } }, + { LDAPProperties.Flags, "2" }, + { LDAPProperties.SAMAccountType, "805306369" } + }; + + Assert.True(mock.GetLabel(out label)); + Assert.Equal(Label.Computer, label); + } + + [Fact] + public void Test_GetLabel_UserObjects() { + var attribs = new Dictionary { + { LDAPProperties.ObjectClass, new[] { "top", "person" } }, + { LDAPProperties.SAMAccountType, "805306368" } + }; + + var mock = new MockDirectoryObject("abc", attribs, + "123456", new Guid().ToString()); + Assert.True(mock.GetLabel(out var label)); + Assert.Equal(Label.User, label); + } + + [Fact] + public void Test_GetLabel_GPOObjects() { + var attribs = new Dictionary { + { LDAPProperties.ObjectClass, new[] { "top", ObjectClass.GroupPolicyContainerClass } }, + }; + + var mock = new MockDirectoryObject("abc", attribs, + "123456", new Guid().ToString()); + Assert.True(mock.GetLabel(out var label)); + Assert.Equal(Label.GPO, label); + } + + [Fact] + public void Test_GetLabel_GroupObjects() { + var attribs = new Dictionary { + { LDAPProperties.ObjectClass, new[] { "top" } }, + { LDAPProperties.SAMAccountType, "268435456" } + }; + + var mock = new MockDirectoryObject("abc", attribs, + "123456", new Guid().ToString()); + Assert.True(mock.GetLabel(out var label)); + Assert.Equal(Label.Group, label); + + mock.Properties = new Dictionary { + { LDAPProperties.ObjectClass, new[] { "top" } }, + { LDAPProperties.SAMAccountType, "268435457" } + }; + + Assert.True(mock.GetLabel(out label)); + Assert.Equal(Label.Group, label); + } + + [Fact] + public void Test_GetLabel_DomainObjects() { + var attribs = new Dictionary { + { LDAPProperties.ObjectClass, new[] { "top", ObjectClass.DomainClass } }, + }; + + var mock = new MockDirectoryObject("abc", attribs, + "123456", new Guid().ToString()); + Assert.True(mock.GetLabel(out var label)); + Assert.Equal(Label.Domain, label); + } + + [Fact] + public void Test_GetLabel_ContainerObjects() { + var attribs = new Dictionary { + { LDAPProperties.ObjectClass, new[] { "top", ObjectClass.ContainerClass } }, + }; + + var mock = new MockDirectoryObject("abc", attribs, + "123456", new Guid().ToString()); + Assert.True(mock.GetLabel(out var label)); + Assert.Equal(Label.Container, label); + } + + [Fact] + public void Test_GetLabel_ConfigurationObjects() { + var attribs = new Dictionary { + { LDAPProperties.ObjectClass, new[] { "top", ObjectClass.ConfigurationClass } }, + }; + + var mock = new MockDirectoryObject("abc", attribs, + "123456", new Guid().ToString()); + Assert.True(mock.GetLabel(out var label)); + Assert.Equal(Label.Configuration, label); + } + + [Fact] + public void Test_GetLabel_CertTemplateObjects() { + var attribs = new Dictionary { + { LDAPProperties.ObjectClass, new[] { "top", ObjectClass.PKICertificateTemplateClass } }, + }; + + var mock = new MockDirectoryObject("abc", attribs, + "123456", new Guid().ToString()); + Assert.True(mock.GetLabel(out var label)); + Assert.Equal(Label.CertTemplate, label); + } + + [Fact] + public void Test_GetLabel_EnterpriseCAObjects() { + var attribs = new Dictionary { + { LDAPProperties.ObjectClass, new[] { "top", ObjectClass.PKIEnrollmentServiceClass } }, + }; + + var mock = new MockDirectoryObject("abc", attribs, + "123456", new Guid().ToString()); + Assert.True(mock.GetLabel(out var label)); + Assert.Equal(Label.EnterpriseCA, label); + } + + [Fact] + public void Test_GetLabel_CertificationAuthorityObjects() { + var attribs = new Dictionary { + { LDAPProperties.ObjectClass, new[] { "top", ObjectClass.CertificationAuthorityClass } }, + }; + + var mock = new MockDirectoryObject($"CN=Test,{DirectoryPaths.RootCALocation.ToUpper()},DC=Testlab,DC=local", + attribs, + "123456", new Guid().ToString()); + Assert.True(mock.GetLabel(out var label)); + Assert.Equal(Label.RootCA, label); + + mock.DistinguishedName = $"CN=Test,{DirectoryPaths.AIACALocation.ToUpper()},DC=Testlab,DC=local"; + Assert.True(mock.GetLabel(out label)); + Assert.Equal(Label.AIACA, label); + + mock.DistinguishedName = $"CN=Test,{DirectoryPaths.NTAuthStoreLocation.ToUpper()},DC=Testlab,DC=local"; + Assert.True(mock.GetLabel(out label)); + Assert.Equal(Label.NTAuthStore, label); + } + + [Fact] + public void Test_GetLabel_NoLabel() { + var attribs = new Dictionary { + { LDAPProperties.ObjectClass, new[] { "top" } }, + }; + + var mock = new MockDirectoryObject($"CN=Test,{DirectoryPaths.RootCALocation.ToUpper()},DC=Testlab,DC=local", + attribs, + "123456", new Guid().ToString()); + Assert.False(mock.GetLabel(out var label)); + Assert.Equal(Label.Base, label); + + mock.Properties = new Dictionary(); + Assert.False(mock.GetLabel(out label)); + Assert.Equal(Label.Base, label); + } + } +} \ No newline at end of file diff --git a/test/unit/DomainTrustProcessorTest.cs b/test/unit/DomainTrustProcessorTest.cs index ba9641af..c31326d0 100644 --- a/test/unit/DomainTrustProcessorTest.cs +++ b/test/unit/DomainTrustProcessorTest.cs @@ -26,18 +26,18 @@ public DomainTrustProcessorTest(ITestOutputHelper testOutputHelper) [WindowsOnlyFact] public async Task DomainTrustProcessor_EnumerateDomainTrusts_HappyPath() { - var mockUtils = new Mock(); + var mockUtils = new Mock(); var searchResults = new[] { - LdapResult.Ok(new MockSearchResultEntry("CN\u003dexternal.local,CN\u003dSystem,DC\u003dtestlab,DC\u003dlocal", + LdapResult.Ok(new MockDirectoryObject("CN\u003dexternal.local,CN\u003dSystem,DC\u003dtestlab,DC\u003dlocal", new Dictionary { {"trustdirection", "3"}, {"trusttype", "2"}, {"trustattributes", 0x24.ToString()}, {"cn", "external.local"}, - {"securityidentifier", Helpers.B64ToBytes("AQQAAAAAAAUVAAAA7JjftxhaHTnafGWh")} - }, "", Label.Domain)) + {"securityidentifier", Utils.B64ToBytes("AQQAAAAAAAUVAAAA7JjftxhaHTnafGWh")} + }, "","")) }; mockUtils.Setup(x => x.Query(It.IsAny(), It.IsAny())).Returns(searchResults.ToAsyncEnumerable); @@ -56,10 +56,10 @@ public async Task DomainTrustProcessor_EnumerateDomainTrusts_HappyPath() [Fact] public async Task DomainTrustProcessor_EnumerateDomainTrusts_SadPaths() { - var mockUtils = new Mock(); + var mockUtils = new Mock(); var searchResults = new[] { - LdapResult.Ok(new MockSearchResultEntry("CN\u003dexternal.local,CN\u003dSystem,DC\u003dtestlab,DC\u003dlocal", + LdapResult.Ok(new MockDirectoryObject("CN\u003dexternal.local,CN\u003dSystem,DC\u003dtestlab,DC\u003dlocal", new Dictionary { {"trustdirection", "3"}, @@ -67,32 +67,32 @@ public async Task DomainTrustProcessor_EnumerateDomainTrusts_SadPaths() {"trustattributes", 0x24.ToString()}, {"cn", "external.local"}, {"securityIdentifier", Array.Empty()} - }, "", Label.Domain)), - LdapResult.Ok(new MockSearchResultEntry("CN\u003dexternal.local,CN\u003dSystem,DC\u003dtestlab,DC\u003dlocal", + }, "","")), + LdapResult.Ok(new MockDirectoryObject("CN\u003dexternal.local,CN\u003dSystem,DC\u003dtestlab,DC\u003dlocal", new Dictionary { {"trustdirection", "3"}, {"trusttype", "2"}, {"trustattributes", 0x24.ToString()}, {"cn", "external.local"}, - {"securityIdentifier", Helpers.B64ToBytes("QQQAAAAAAAUVAAAA7JjftxhaHTnafGWh")} - }, "", Label.Domain)), - LdapResult.Ok(new MockSearchResultEntry("CN\u003dexternal.local,CN\u003dSystem,DC\u003dtestlab,DC\u003dlocal", + {"securityIdentifier", Utils.B64ToBytes("QQQAAAAAAAUVAAAA7JjftxhaHTnafGWh")} + }, "","")), + LdapResult.Ok(new MockDirectoryObject("CN\u003dexternal.local,CN\u003dSystem,DC\u003dtestlab,DC\u003dlocal", new Dictionary { {"trusttype", "2"}, {"trustattributes", 0x24.ToString()}, {"cn", "external.local"}, - {"securityIdentifier", Helpers.B64ToBytes("AQQAAAAAAAUVAAAA7JjftxhaHTnafGWh")} - }, "", Label.Domain)), - LdapResult.Ok(new MockSearchResultEntry("CN\u003dexternal.local,CN\u003dSystem,DC\u003dtestlab,DC\u003dlocal", + {"securityIdentifier", Utils.B64ToBytes("AQQAAAAAAAUVAAAA7JjftxhaHTnafGWh")} + }, "","")), + LdapResult.Ok(new MockDirectoryObject("CN\u003dexternal.local,CN\u003dSystem,DC\u003dtestlab,DC\u003dlocal", new Dictionary { {"trustdirection", "3"}, {"trusttype", "2"}, {"cn", "external.local"}, - {"securityIdentifier", Helpers.B64ToBytes("AQQAAAAAAAUVAAAA7JjftxhaHTnafGWh")} - }, "", Label.Domain)) + {"securityIdentifier", Utils.B64ToBytes("AQQAAAAAAAUVAAAA7JjftxhaHTnafGWh")} + }, "","")) }; mockUtils.Setup(x => x.Query(It.IsAny(), It.IsAny())).Returns(searchResults.ToAsyncEnumerable); diff --git a/test/unit/Facades/MockDirectoryObject.cs b/test/unit/Facades/MockDirectoryObject.cs new file mode 100644 index 00000000..e0f2ccdd --- /dev/null +++ b/test/unit/Facades/MockDirectoryObject.cs @@ -0,0 +1,166 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using SharpHoundCommonLib; +using SharpHoundCommonLib.Enums; + +namespace CommonLibTest.Facades; + +public class MockDirectoryObject : IDirectoryObject { + private readonly string _objectSID; + private readonly string _objectGuid; + public IDictionary Properties { get; set; } + public string DistinguishedName { get; set; } + + public MockDirectoryObject(string distinguishedName, IDictionary properties, string sid, string guid) { + DistinguishedName = distinguishedName; + Properties = properties; + _objectSID = sid; + _objectGuid = guid; + } + + public bool TryGetDistinguishedName(out string value) { + value = DistinguishedName; + return !string.IsNullOrWhiteSpace(DistinguishedName); + } + + public bool TryGetProperty(string propertyName, out string value) { + if (!Properties.Contains(propertyName)) { + value = default; + return false; + } + + var temp = Properties[propertyName]; + + switch (temp) { + case string s: + value = s; + return true; + case int i: + value = i.ToString(); + return true; + default: + value = default; + return false; + } + } + + public bool TryGetByteProperty(string propertyName, out byte[] value) { + if (!Properties.Contains(propertyName)) { + value = default; + return false; + } + + switch (Properties[propertyName]) { + case string prop: + value = Encoding.ASCII.GetBytes(prop); + return true; + case byte[] b: + value = b; + return true; + default: + value = default; + return false; + } + } + + public bool TryGetArrayProperty(string propertyName, out string[] value) { + if (!Properties.Contains(propertyName)) { + value = Array.Empty(); + return false; + } + + var temp = Properties[propertyName]; + if (temp.IsArray()) { + value = temp as string[]; + return true; + } + + value = Array.Empty(); + return false; + } + + public bool TryGetByteArrayProperty(string propertyName, out byte[][] value) { + if (!Properties.Contains(propertyName)) { + value = Array.Empty(); + return false; + } + + if (Properties[propertyName] is byte[][] b) { + value = b; + return true; + } + + value = default; + return false; + } + + public bool TryGetIntProperty(string propertyName, out int value) { + if (!Properties.Contains(propertyName)) { + value = default; + return false; + } + + switch (Properties[propertyName]) { + case int i: + value = i; + return true; + case string s when int.TryParse(s, out var val): + value = val; + return true; + default: + value = 0; + return false; + } + } + + public bool TryGetCertificateArrayProperty(string propertyName, out X509Certificate2[] value) { + if (!TryGetByteArrayProperty(propertyName, out var b)) { + value = Array.Empty(); + return false; + } + + value = b.Select(x => new X509Certificate2(x)).ToArray(); + return true; + } + + public bool TryGetSecurityIdentifier(out string securityIdentifier) { + securityIdentifier = _objectSID; + return true; + } + + public bool TryGetGuid(out string guid) { + guid = _objectGuid; + return true; + } + + public string GetProperty(string propertyName) { + return Properties[propertyName] as string; + } + + public byte[] GetByteProperty(string propertyName) { + return Properties[propertyName] as byte[]; + } + + public int PropertyCount(string propertyName) { + if (!Properties.Contains(propertyName)) { + return 0; + } + + var property = Properties[propertyName]; + if (property.IsArray()) + { + var cast = property as string[]; + return cast?.Length ?? 0; + } + + return 1; + } + + public IEnumerable PropertyNames() { + foreach (var property in Properties.Keys) yield return property.ToString().ToLower(); + } +} \ No newline at end of file diff --git a/test/unit/Facades/MockLDAPUtils.cs b/test/unit/Facades/MockLdapUtils.cs similarity index 99% rename from test/unit/Facades/MockLDAPUtils.cs rename to test/unit/Facades/MockLdapUtils.cs index c13e3fbd..a10cda45 100644 --- a/test/unit/Facades/MockLDAPUtils.cs +++ b/test/unit/Facades/MockLdapUtils.cs @@ -16,23 +16,23 @@ namespace CommonLibTest.Facades { - public class MockLDAPUtils : ILdapUtils + public class MockLdapUtils : ILdapUtils { private readonly ConcurrentDictionary _domainControllers = new(); private readonly Forest _forest; private readonly ConcurrentDictionary _seenWellKnownPrincipals = new(); - public MockLDAPUtils() + public MockLdapUtils() { _forest = MockableForest.Construct("FOREST.LOCAL"); } - public virtual IAsyncEnumerable> Query(LdapQueryParameters queryParameters, + public virtual IAsyncEnumerable> Query(LdapQueryParameters queryParameters, CancellationToken cancellationToken = new CancellationToken()) { throw new NotImplementedException(); } - public virtual IAsyncEnumerable> PagedQuery(LdapQueryParameters queryParameters, + public virtual IAsyncEnumerable> PagedQuery(LdapQueryParameters queryParameters, CancellationToken cancellationToken = new CancellationToken()) { throw new NotImplementedException(); } @@ -674,8 +674,12 @@ public virtual IAsyncEnumerable> RangedRetrieval(string distingui return (true, commonPrincipal); } - Task<(bool Success, string DomainName)> ILdapUtils.GetDomainNameFromSid(string sid) { - throw new NotImplementedException(); + async Task<(bool Success, string DomainName)> ILdapUtils.GetDomainNameFromSid(string sid) { + if (sid.StartsWith("S-1-5-21-3130019616-2776909439-2417379446", StringComparison.OrdinalIgnoreCase)) { + return (true, "TESTLAB.LOCAL"); + } + + return (false, default); } public async Task<(bool Success, string DomainSid)> GetDomainSidFromDomainName(string domainName) { @@ -998,7 +1002,7 @@ public IAsyncEnumerable GetWellKnownPrincipalOutput() { throw new NotImplementedException(); } - public void SetLdapConfig(LDAPConfig config) { + public void SetLdapConfig(LdapConfig config) { throw new NotImplementedException(); } diff --git a/test/unit/Facades/MockSearchResultEntry.cs b/test/unit/Facades/MockSearchResultEntry.cs deleted file mode 100644 index 412f84eb..00000000 --- a/test/unit/Facades/MockSearchResultEntry.cs +++ /dev/null @@ -1,142 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Security.Cryptography.X509Certificates; -using System.Text; -using System.Threading.Tasks; -using SharpHoundCommonLib; -using SharpHoundCommonLib.Enums; - -namespace CommonLibTest.Facades -{ - public class MockSearchResultEntry : ISearchResultEntry - { - private readonly string _objectId; - private readonly Label _objectType; - private readonly IDictionary _properties; - - public MockSearchResultEntry(string distinguishedName, IDictionary properties, string objectId, - Label objectType) - { - DistinguishedName = distinguishedName; - _properties = properties; - _objectId = objectId; - _objectType = objectType; - } - - public string DistinguishedName { get; } - - public async Task ResolveBloodHoundInfo() - { - throw new NotImplementedException(); - } - - public string GetProperty(string propertyName) - { - return _properties[propertyName] as string; - } - - public byte[] GetByteProperty(string propertyName) - { - if (!_properties.Contains(propertyName)) - return null; - - if (_properties[propertyName] is string prop) - { - return Encoding.ASCII.GetBytes(prop); - } - - return _properties[propertyName] as byte[]; - } - - public string[] GetArrayProperty(string propertyName) - { - if (!_properties.Contains(propertyName)) - return Array.Empty(); - - var value = _properties[propertyName]; - if (value.IsArray()) - return value as string[]; - - return new [] { (value ?? "").ToString() }; - } - - public byte[][] GetByteArrayProperty(string propertyName) - { - if (!_properties.Contains(propertyName)) - return Array.Empty(); - - var property = _properties[propertyName] as byte[][]; - return property; - } - - public bool GetIntProperty(string propertyName, out int value) - { - value = _properties[propertyName] is int ? (int)_properties[propertyName] : 0; - return true; - } - - public X509Certificate2[] GetCertificateArrayProperty(string propertyName) - { - return GetByteArrayProperty(propertyName).Select(x => new X509Certificate2(x)).ToArray(); - } - - public string GetObjectIdentifier() - { - return _objectId; - } - - public bool IsDeleted() - { - throw new NotImplementedException(); - } - - public Label GetLabel() - { - return _objectType; - } - - public string GetSid() - { - return _objectId; - } - - public string GetGuid() - { - return _objectId; - } - - public int PropCount(string prop) - { - var property = _properties[prop]; - if (property.IsArray()) - { - var cast = property as string[]; - return cast?.Length ?? 0; - } - - return 1; - } - - public IEnumerable PropertyNames() - { - foreach (var property in _properties.Keys) yield return property.ToString().ToLower(); - } - - public bool IsMSA() - { - throw new NotImplementedException(); - } - - public bool IsGMSA() - { - throw new NotImplementedException(); - } - - public bool HasLAPS() - { - throw new NotImplementedException(); - } - } -} \ No newline at end of file diff --git a/test/unit/GPOLocalGroupProcessorTest.cs b/test/unit/GPOLocalGroupProcessorTest.cs index 18af16bc..84643074 100644 --- a/test/unit/GPOLocalGroupProcessorTest.cs +++ b/test/unit/GPOLocalGroupProcessorTest.cs @@ -15,10 +15,8 @@ using Xunit; using Xunit.Abstractions; -namespace CommonLibTest -{ - public class GPOLocalGroupProcessorTest - { +namespace CommonLibTest { + public class GPOLocalGroupProcessorTest { private readonly string GpttmplInfContent = @"[Unicode] Unicode=yes [Version] @@ -90,14 +88,12 @@ [Group Membership] private ITestOutputHelper _testOutputHelper; - public GPOLocalGroupProcessorTest(ITestOutputHelper testOutputHelper) - { + public GPOLocalGroupProcessorTest(ITestOutputHelper testOutputHelper) { _testOutputHelper = testOutputHelper; } [Fact] - public async Task GPOLocalGroupProcessor_ReadGPOLocalGroups_Null_GPLink() - { + public async Task GPOLocalGroupProcessor_ReadGPOLocalGroups_Null_GPLink() { var mockLDAPUtils = new Mock(); var processor = new GPOLocalGroupProcessor(mockLDAPUtils.Object); @@ -109,14 +105,14 @@ public async Task GPOLocalGroupProcessor_ReadGPOLocalGroups_Null_GPLink() Assert.Empty(result.LocalAdmins); Assert.Empty(result.PSRemoteUsers); } - + [Fact] - public async Task GPOLocalGroupProcessor_ReadGPOLocalGroups_AffectedComputers_0() - { + public async Task GPOLocalGroupProcessor_ReadGPOLocalGroups_AffectedComputers_0() { var mockLDAPUtils = new Mock(); - mockLDAPUtils.Setup(x => x.Query(It.IsAny(), It.IsAny())).Returns(Array.Empty>().ToAsyncEnumerable); + mockLDAPUtils.Setup(x => x.Query(It.IsAny(), It.IsAny())) + .Returns(Array.Empty>().ToAsyncEnumerable); var processor = new GPOLocalGroupProcessor(mockLDAPUtils.Object); - + var result = await processor.ReadGPOLocalGroups("teapot", null); Assert.NotNull(result); Assert.Empty(result.AffectedComputers); @@ -125,71 +121,74 @@ public async Task GPOLocalGroupProcessor_ReadGPOLocalGroups_AffectedComputers_0( Assert.Empty(result.LocalAdmins); Assert.Empty(result.PSRemoteUsers); } - + [Fact] - public async Task GPOLocalGroupProcessor_ReadGPOLocalGroups_Null_Gpcfilesyspath() - { + public async Task GPOLocalGroupProcessor_ReadGPOLocalGroups_Null_Gpcfilesyspath() { var mockLDAPUtils = new Mock(); - var mockSearchResultEntry = new Mock(); - mockSearchResultEntry.Setup(x => x.GetSid()).Returns("teapot"); - var mockResult = LdapResult.Ok(mockSearchResultEntry.Object); - var mockSearchResults = new List> { mockResult }; + var mockSearchResultEntry = new Mock(); + var sid = "teapot"; + mockSearchResultEntry.Setup(x => x.TryGetSecurityIdentifier(out sid)).Returns(true); + var mockResult = LdapResult.Ok(mockSearchResultEntry.Object); + var mockSearchResults = new List> { mockResult }; mockLDAPUtils .Setup(x => x.Query( It.Is(y => - y.LDAPFilter.Equals(new LDAPFilter().AddComputersNoMSAs().GetFilter()) && y.Attributes.Equals(CommonProperties.ObjectSID)), + y.LDAPFilter.Equals(new LdapFilter().AddComputersNoMSAs().GetFilter()) && + y.Attributes.Equals(CommonProperties.ObjectSID)), It.IsAny())).Returns(mockSearchResults.ToAsyncEnumerable); - + mockLDAPUtils .Setup(x => x.Query( It.Is(y => - y.LDAPFilter.Equals(new LDAPFilter().AddAllObjects().GetFilter())), - It.IsAny())).Returns(Array.Empty>().ToAsyncEnumerable); - + y.LDAPFilter.Equals(new LdapFilter().AddAllObjects().GetFilter())), + It.IsAny())) + .Returns(Array.Empty>().ToAsyncEnumerable); + var processor = new GPOLocalGroupProcessor(mockLDAPUtils.Object); var testGPLinkProperty = "[LDAP:/o=foo/ou=foo Group (ABC123)/cn=foouser (blah)123/dc=somedomain;0;][LDAP:/o=foo/ou=foo Group (ABC123)/cn=foouser (blah)123/dc=someotherdomain;2;]"; var result = await processor.ReadGPOLocalGroups(testGPLinkProperty, null); - + Assert.NotNull(result); Assert.Single(result.AffectedComputers); var actual = result.AffectedComputers.First(); Assert.Equal(Label.Computer, actual.ObjectType); Assert.Equal("teapot", actual.ObjectIdentifier); } - + [Fact] - public async Task GPOLocalGroupProcessor_ReadGPOLocalGroups() - { + public async Task GPOLocalGroupProcessor_ReadGPOLocalGroups() { var mockLDAPUtils = new Mock(MockBehavior.Loose); var gpcFileSysPath = Path.GetTempPath(); - + var groupsXmlPath = Path.Join(gpcFileSysPath, "MACHINE", "Preferences", "Groups", "Groups.xml"); - + Path.GetDirectoryName(groupsXmlPath); Directory.CreateDirectory(Path.GetDirectoryName(groupsXmlPath)); File.WriteAllText(groupsXmlPath, GroupXmlContent); - - var mockComputerEntry = new Mock(); - mockComputerEntry.Setup(x => x.GetSid()).Returns("teapot"); - var mockComputerResults = new List>(); - mockComputerResults.Add(LdapResult.Ok(mockComputerEntry.Object)); - - var mockGCPFileSysPathEntry = new Mock(); - mockGCPFileSysPathEntry.Setup(x => x.GetProperty(It.IsAny())).Returns(gpcFileSysPath); - var mockGCPFileSysPathResults = new List> { LdapResult.Ok(mockGCPFileSysPathEntry.Object) }; + + var mockComputerEntry = new Mock(); + var sid = "teapot"; + mockComputerEntry.Setup(x => x.TryGetSecurityIdentifier(out sid)).Returns(true); + var mockComputerResults = new List>(); + mockComputerResults.Add(LdapResult.Ok(mockComputerEntry.Object)); + + var mockGCPFileSysPathEntry = new Mock(); + mockGCPFileSysPathEntry.Setup(x => x.TryGetProperty(It.IsAny(), out gpcFileSysPath)).Returns(true); + var mockGCPFileSysPathResults = new List> + { LdapResult.Ok(mockGCPFileSysPathEntry.Object) }; mockLDAPUtils.SetupSequence(x => x.Query(It.IsAny(), It.IsAny())) .Returns(mockComputerResults.ToAsyncEnumerable) .Returns(mockGCPFileSysPathResults.ToAsyncEnumerable) - .Returns(Array.Empty>().ToAsyncEnumerable); - + .Returns(Array.Empty>().ToAsyncEnumerable); + var processor = new GPOLocalGroupProcessor(mockLDAPUtils.Object); - + var testGPLinkProperty = "[LDAP:/o=foo/ou=foo Group (ABC123)/cn=foouser (blah)123/dc=somedomain;0;][LDAP:/o=foo/ou=foo Group (ABC123)/cn=foouser (blah)123/dc=someotherdomain;2;]"; var result = await processor.ReadGPOLocalGroups(testGPLinkProperty, null); - + mockLDAPUtils.VerifyAll(); Assert.NotNull(result); Assert.Single(result.AffectedComputers); @@ -197,115 +196,108 @@ public async Task GPOLocalGroupProcessor_ReadGPOLocalGroups() Assert.Equal(Label.Computer, actual.ObjectType); Assert.Equal("teapot", actual.ObjectIdentifier); } - + [Fact] - public async Task GPOLocalGroupProcess_ProcessGPOXMLFile_NoFile() - { + public async Task GPOLocalGroupProcess_ProcessGPOXMLFile_NoFile() { var mockLDAPUtils = new Mock(); var processor = new GPOLocalGroupProcessor(mockLDAPUtils.Object); var gpcFileSysPath = Path.Join(Path.GetTempPath(), "made", "up", "path"); - + var actual = await processor.ProcessGPOXmlFile(gpcFileSysPath, "somedomain").ToArrayAsync(); Assert.NotNull(actual); Assert.Empty(actual); } - + [Fact] - public async Task GPOLocalGroupProcess_ProcessGPOXMLFile_Disabled() - { + public async Task GPOLocalGroupProcess_ProcessGPOXMLFile_Disabled() { var mockLDAPUtils = new Mock(); var gpcFileSysPath = Path.GetTempPath(); var groupsXmlPath = Path.Join(gpcFileSysPath, "MACHINE", "Preferences", "Groups", "Groups.xml"); - + Directory.CreateDirectory(Path.GetDirectoryName(groupsXmlPath)); File.WriteAllText(groupsXmlPath, GroupXmlContentDisabled); - + var processor = new GPOLocalGroupProcessor(mockLDAPUtils.Object); - + var actual = await processor.ProcessGPOXmlFile(gpcFileSysPath, "somedomain").ToArrayAsync(); Assert.NotNull(actual); Assert.Empty(actual); } - + [Fact] - public async Task GPOLocalGroupProcessor_ProcessGPOXMLFile() - { + public async Task GPOLocalGroupProcessor_ProcessGPOXMLFile() { var mockLDAPUtils = new Mock(); mockLDAPUtils.Setup(x => x.ResolveAccountName(It.IsAny(), It.IsAny())) .ReturnsAsync((true, new TypedPrincipal("S-1-5-21-3130019616-2776909439-2417379446-513", Label.User))); var gpcFileSysPath = Path.GetTempPath(); var groupsXmlPath = Path.Join(gpcFileSysPath, "MACHINE", "Preferences", "Groups", "Groups.xml"); - + Directory.CreateDirectory(Path.GetDirectoryName(groupsXmlPath)); File.WriteAllText(groupsXmlPath, GroupXmlContent); - + var processor = new GPOLocalGroupProcessor(mockLDAPUtils.Object); var actual = await processor.ProcessGPOXmlFile(gpcFileSysPath, "somedomain").ToArrayAsync(); - + Assert.NotNull(actual); Assert.NotEmpty(actual); } - + [Fact] - public async Task GPOLocalGroupProcess_ProcessGPOTemplateFile_NoFile() - { + public async Task GPOLocalGroupProcess_ProcessGPOTemplateFile_NoFile() { var mockLDAPUtils = new Mock(); var processor = new GPOLocalGroupProcessor(mockLDAPUtils.Object); var gpcFileSysPath = Path.Join(Path.GetTempPath(), "made", "up", "path"); - + var actual = await processor.ProcessGPOTemplateFile(gpcFileSysPath, "somedomain").ToListAsync(); Assert.NotNull(actual); Assert.Empty(actual); } - + [Fact] - public async Task GPOLocalGroupProcess_ProcessGPOTemplateFile_NoMatch() - { + public async Task GPOLocalGroupProcess_ProcessGPOTemplateFile_NoMatch() { var mockLDAPUtils = new Mock(); var gpcFileSysPath = Path.GetTempPath(); var gptTmplPath = Path.Join(gpcFileSysPath, "MACHINE", "Microsoft", "Windows NT", "SecEdit", "GptTmpl.inf"); - + Directory.CreateDirectory(Path.GetDirectoryName(gptTmplPath)); File.WriteAllText(gptTmplPath, GpttmplInfContentNoMatch); - + var processor = new GPOLocalGroupProcessor(mockLDAPUtils.Object); - + var actual = await processor.ProcessGPOTemplateFile(gpcFileSysPath, "somedomain").ToListAsync(); Assert.NotNull(actual); Assert.Empty(actual); } - + [Fact] - public async Task GPOLocalGroupProcess_ProcessGPOTemplateFile_NullSID() - { - var mockLDAPUtils = new MockLDAPUtils(); + public async Task GPOLocalGroupProcess_ProcessGPOTemplateFile_NullSID() { + var mockLDAPUtils = new MockLdapUtils(); var gpcFileSysPath = Path.GetTempPath(); var gptTmplPath = Path.Join(gpcFileSysPath, "MACHINE", "Microsoft", "Windows NT", "SecEdit", "GptTmpl.inf"); - + Directory.CreateDirectory(Path.GetDirectoryName(gptTmplPath)); File.WriteAllText(gptTmplPath, GpttmplInfContent); - + var processor = new GPOLocalGroupProcessor(mockLDAPUtils); - + var actual = await processor.ProcessGPOTemplateFile(gpcFileSysPath, "somedomain").ToListAsync(); Assert.NotNull(actual); Assert.NotEmpty(actual); } - + [Fact] - public async Task GPOLocalGroupProcess_ProcessGPOTemplateFile() - { + public async Task GPOLocalGroupProcess_ProcessGPOTemplateFile() { var mockLDAPUtils = new Mock(); mockLDAPUtils.Setup(x => x.ResolveAccountName(It.IsAny(), It.IsAny())) - .ReturnsAsync((true,new TypedPrincipal("S-1-5-21-3130019616-2776909439-2417379446-513", Label.User))); + .ReturnsAsync((true, new TypedPrincipal("S-1-5-21-3130019616-2776909439-2417379446-513", Label.User))); var gpcFileSysPath = Path.GetTempPath(); var gptTmplPath = Path.Join(gpcFileSysPath, "MACHINE", "Microsoft", "Windows NT", "SecEdit", "GptTmpl.inf"); - + Directory.CreateDirectory(Path.GetDirectoryName(gptTmplPath)); File.WriteAllText(gptTmplPath, GpttmplInfContent); - + var processor = new GPOLocalGroupProcessor(mockLDAPUtils.Object); - + var actual = await processor.ProcessGPOTemplateFile(gpcFileSysPath, "somedomain").ToListAsync(); Assert.NotNull(actual); Assert.NotEmpty(actual); @@ -318,14 +310,13 @@ public async Task GPOLocalGroupProcess_ProcessGPOTemplateFile() }; Assert.Contains(expected, actual); } - + [Fact] - public void GPOLocalGroupProcess_GroupAction() - { + public void GPOLocalGroupProcess_GroupAction() { var ga = new GPOLocalGroupProcessor.GroupAction(); var tp = ga.ToTypedPrincipal(); var str = ga.ToString(); - + Assert.NotNull(tp); Assert.Equal(new TypedPrincipal(), tp); Assert.NotNull(str); diff --git a/test/unit/GroupProcessorTest.cs b/test/unit/GroupProcessorTest.cs index 53cc86d3..c8f621e7 100644 --- a/test/unit/GroupProcessorTest.cs +++ b/test/unit/GroupProcessorTest.cs @@ -15,8 +15,6 @@ namespace CommonLibTest { public class GroupProcessorTest { - private readonly string _testDomainName; - private readonly Result[] _testMembershipReturn = { Result.Ok("CN=Domain Admins,CN=Users,DC=testlab,DC=local"), @@ -40,26 +38,25 @@ public class GroupProcessorTest public GroupProcessorTest(ITestOutputHelper testOutputHelper) { _testOutputHelper = testOutputHelper; - _testDomainName = "TESTLAB.LOCAL"; _baseProcessor = new GroupProcessor(new LdapUtils()); } [Fact] - public async Task GroupProcessor_GetPrimaryGroupInfo_NullPrimaryGroupID_ReturnsNull() + public void GroupProcessor_GetPrimaryGroupInfo_NullPrimaryGroupID_ReturnsNull() { var result = GroupProcessor.GetPrimaryGroupInfo(null, null); Assert.Null(result); } [WindowsOnlyFact] - public async Task GroupProcessor_GetPrimaryGroupInfo_ReturnsCorrectSID() + public void GroupProcessor_GetPrimaryGroupInfo_ReturnsCorrectSID() { var result = GroupProcessor.GetPrimaryGroupInfo("513", "S-1-5-21-3130019616-2776909439-2417379446-1105"); Assert.Equal("S-1-5-21-3130019616-2776909439-2417379446-513", result); } [Fact] - public async Task GroupProcessor_GetPrimaryGroupInfo_BadSID_ReturnsNull() + public void GroupProcessor_GetPrimaryGroupInfo_BadSID_ReturnsNull() { var result = GroupProcessor.GetPrimaryGroupInfo("513", "ABC123"); Assert.Null(result); @@ -68,7 +65,7 @@ public async Task GroupProcessor_GetPrimaryGroupInfo_BadSID_ReturnsNull() [Fact] public async Task GroupProcessor_ReadGroupMembers_EmptyMembers_DoesRangedRetrieval() { - var mockUtils = new Mock(); + var mockUtils = new Mock(); var expected = new TypedPrincipal[] { new() @@ -105,7 +102,7 @@ public async Task GroupProcessor_ReadGroupMembers_EmptyMembers_DoesRangedRetriev [WindowsOnlyFact] public async Task GroupProcessor_ReadGroupMembers_ReturnsCorrectMembers() { - var utils = new MockLDAPUtils(); + var utils = new MockLdapUtils(); var processor = new GroupProcessor(utils); var expected = new TypedPrincipal[] { diff --git a/test/unit/LDAPFilterTest.cs b/test/unit/LDAPFilterTest.cs index 7c534486..ad9f0f8a 100644 --- a/test/unit/LDAPFilterTest.cs +++ b/test/unit/LDAPFilterTest.cs @@ -25,7 +25,7 @@ public void Dispose() [Fact] public void LDAPFilter_CreateNewFilter_FilterNotNull() { - var test = new LDAPFilter(); + var test = new LdapFilter(); Assert.NotNull(test); } @@ -36,7 +36,7 @@ public void LDAPFilter_CreateNewFilter_FilterNotNull() [Fact] public void LDAPFilter_GroupFilter_FilterCorrect() { - var test = new LDAPFilter(); + var test = new LdapFilter(); test.AddGroups(); var filter = test.GetFilter(); _testOutputHelper.WriteLine(filter); @@ -48,7 +48,7 @@ public void LDAPFilter_GroupFilter_FilterCorrect() [Fact] public void LDAPFilter_GroupFilter_ExtraFilter_FilterCorrect() { - var test = new LDAPFilter(); + var test = new LdapFilter(); test.AddGroups("objectclass=*"); var filter = test.GetFilter(); _testOutputHelper.WriteLine(filter); @@ -60,7 +60,7 @@ public void LDAPFilter_GroupFilter_ExtraFilter_FilterCorrect() [Fact] public void LDAPFilter_GetFilterList() { - var test = new LDAPFilter().AddUsers().AddComputers(); + var test = new LdapFilter().AddUsers().AddComputers(); IEnumerable filters = test.GetFilterList(); int i = 0; diff --git a/test/unit/LDAPUtilsTest.cs b/test/unit/LDAPUtilsTest.cs index e6889d57..c69e4bae 100644 --- a/test/unit/LDAPUtilsTest.cs +++ b/test/unit/LDAPUtilsTest.cs @@ -1,21 +1,16 @@ using System; using System.Collections.Generic; using System.DirectoryServices.ActiveDirectory; -using System.DirectoryServices.Protocols; -using System.Threading; using System.Threading.Tasks; using CommonLibTest.Facades; using Moq; using SharpHoundCommonLib; using SharpHoundCommonLib.Enums; -using SharpHoundCommonLib.Exceptions; using Xunit; using Xunit.Abstractions; -namespace CommonLibTest -{ - public class LDAPUtilsTest : IDisposable - { +namespace CommonLibTest { + public class LDAPUtilsTest : IDisposable { private readonly string _testDomainName; private readonly string _testForestName; private readonly ITestOutputHelper _testOutputHelper; @@ -23,8 +18,7 @@ public class LDAPUtilsTest : IDisposable #region Constructor(s) - public LDAPUtilsTest(ITestOutputHelper testOutputHelper) - { + public LDAPUtilsTest(ITestOutputHelper testOutputHelper) { _testOutputHelper = testOutputHelper; _testForestName = "PARENT.LOCAL"; _testDomainName = "TESTLAB.LOCAL"; @@ -36,24 +30,21 @@ public LDAPUtilsTest(ITestOutputHelper testOutputHelper) #region IDispose Implementation - public void Dispose() - { + public void Dispose() { // Tear down (called once per test) } #endregion [Fact] - public void SanityCheck() - { + public void SanityCheck() { Assert.True(true); } /// /// [Fact] - public async Task GetUserGlobalCatalogMatches_Garbage_ReturnsNull() - { + public async Task GetUserGlobalCatalogMatches_Garbage_ReturnsNull() { var test = await _utils.GetGlobalCatalogMatches("foo", "bar"); _testOutputHelper.WriteLine(test.ToString()); Assert.True(test.Success); @@ -61,15 +52,13 @@ public async Task GetUserGlobalCatalogMatches_Garbage_ReturnsNull() } [Fact] - public void ResolveIDAndType_DuplicateSid_ReturnsNull() - { - var test = _utils.ResolveIDAndType("ABC0ACNF", null); - Assert.Null(test); + public async Task ResolveIDAndType_DuplicateSid_ReturnsNull() { + var test = await _utils.ResolveIDAndType("ABC0ACNF", null); + Assert.False(test.Success); } [Fact] - public async void ResolveIDAndType_WellKnownAdministrators_ReturnsConvertedSID() - { + public async void ResolveIDAndType_WellKnownAdministrators_ReturnsConvertedSID() { var test = await _utils.ResolveIDAndType("S-1-5-32-544", "TESTLAB.LOCAL"); Assert.True(test.Success); Assert.NotNull(test.Principal); @@ -77,12 +66,11 @@ public async void ResolveIDAndType_WellKnownAdministrators_ReturnsConvertedSID() Assert.Equal("TESTLAB.LOCAL-S-1-5-32-544", test.Principal.ObjectIdentifier); } - [WindowsOnlyFact] + [Fact] public async void GetWellKnownPrincipal_EnterpriseDomainControllers_ReturnsCorrectedSID() { var mock = new Mock(); - var mockForest = MockableForest.Construct(_testForestName); - mock.Setup(x => x.GetForest(It.IsAny())).Returns(mockForest); + mock.Setup(x => x.GetForest(It.IsAny())).ReturnsAsync((true, _testForestName)); var result = await mock.Object.GetWellKnownPrincipal("S-1-5-9", null); Assert.True(result.Success); Assert.Equal($"{_testForestName}-S-1-5-9", result.WellKnownPrincipal.ObjectIdentifier); @@ -90,39 +78,14 @@ public async void GetWellKnownPrincipal_EnterpriseDomainControllers_ReturnsCorre } [Fact] - public void BuildLdapPath_BadDomain_ReturnsNull() - { - var mock = new Mock(); - //var mockDomain = MockableDomain.Construct("TESTLAB.LOCAL"); - mock.Setup(x => x.GetDomain(It.IsAny())) - .Returns((Domain)null); - var result = mock.Object.BuildLdapPath("TEST", "ABC"); - Assert.Null(result); - } - - [WindowsOnlyFact] - public void BuildLdapPath_HappyPath() - { - var mock = new Mock(); - var mockDomain = MockableDomain.Construct("TESTLAB.LOCAL"); - mock.Setup(x => x.GetDomain(It.IsAny())) - .Returns(mockDomain); - var result = mock.Object.BuildLdapPath(DirectoryPaths.PKILocation, "ABC"); - Assert.NotNull(result); - Assert.Equal("CN=Public Key Services,CN=Services,CN=Configuration,DC=TESTLAB,DC=LOCAL", result); - } - - [Fact] - public async void GetWellKnownPrincipal_NonWellKnown_ReturnsNull() - { + public async void GetWellKnownPrincipal_NonWellKnown_ReturnsNull() { var result = await _utils.GetWellKnownPrincipal("S-1-5-21-123456-78910", _testDomainName); Assert.False(result.Success); Assert.Null(result.WellKnownPrincipal); } [Fact] - public async void GetWellKnownPrincipal_WithDomain_ConvertsSID() - { + public async void GetWellKnownPrincipal_WithDomain_ConvertsSID() { var result = await _utils.GetWellKnownPrincipal("S-1-5-32-544", _testDomainName); Assert.True(result.Success); @@ -131,22 +94,133 @@ public async void GetWellKnownPrincipal_WithDomain_ConvertsSID() } [Fact] - public void DistinguishedNameToDomain_RegularObject_CorrectDomain() - { - var result = SharpHoundCommonLib.Helpers.DistinguishedNameToDomain( - "CN=Account Operators,CN=Builtin,DC=testlab,DC=local"); - Assert.Equal("TESTLAB.LOCAL", result); + public async Task Test_ResolveSearchResult_BadObjectID() { + var utils = new MockLdapUtils(); + var attribs = new Dictionary { + { LDAPProperties.ObjectClass, new[] { "top", "person" } }, + { LDAPProperties.SAMAccountType, "805306368" } + }; + + var mock = new MockDirectoryObject("abc", attribs, + "", ""); + var (success, _) = await LdapUtils.ResolveSearchResult(mock, utils); + Assert.False(success); + } - result = SharpHoundCommonLib.Helpers.DistinguishedNameToDomain("DC=testlab,DC=local"); - Assert.Equal("TESTLAB.LOCAL", result); + [Fact] + public async Task Test_ResolveSearchResult_DeletedObject() { + var utils = new MockLdapUtils(); + var attribs = new Dictionary { + { LDAPProperties.IsDeleted, "true" }, + }; + + var guid = new Guid().ToString(); + + var mock = new MockDirectoryObject("abc", attribs, + "", guid); + var (success, resolved) = await LdapUtils.ResolveSearchResult(mock, utils); + Assert.True(success); + Assert.Equal(guid, resolved.ObjectId); + Assert.True(resolved.Deleted); } [Fact] - public void DistinguishedNameToDomain_DeletedObjects_CorrectDomain() - { - var result = SharpHoundCommonLib.Helpers.DistinguishedNameToDomain( - @"DC=..Deleted-_msdcs.testlab.local\0ADEL:af1f072f-28d7-4b86-9b87-a408bfc9cb0d,CN=Deleted Objects,DC=testlab,DC=local"); - Assert.Equal("TESTLAB.LOCAL", result); + public async Task Test_ResolveSearchResult_DCObject() { + var utils = new MockLdapUtils(); + var attribs = new Dictionary { + { LDAPProperties.SAMAccountType, "805306369" }, { + LDAPProperties.UserAccountControl, + ((int)(UacFlags.ServerTrustAccount | UacFlags.WorkstationTrustAccount)).ToString() + }, + { LDAPProperties.DNSHostName, "primary.testlab.local" } + }; + var guid = new Guid().ToString(); + const string sid = "S-1-5-21-3130019616-2776909439-2417379446-1001"; + const string dn = "CN=PRIMARY,OU=DOMAIN CONTROLLERS,DC=TESTLAB,DC=LOCAL"; + + var mock = new MockDirectoryObject(dn, attribs, sid, guid); + + var (success, result) = await LdapUtils.ResolveSearchResult(mock, utils); + Assert.True(success); + Assert.Equal(sid, result.ObjectId); + Assert.Equal(Label.Computer, result.ObjectType); + Assert.True(result.IsDomainController); + Assert.Equal("PRIMARY.TESTLAB.LOCAL", result.DisplayName); + Assert.Equal("S-1-5-21-3130019616-2776909439-2417379446", result.DomainSid); + Assert.Equal("TESTLAB.LOCAL", result.Domain); + Assert.False(result.Deleted); + + mock.DistinguishedName = ""; + + (success, result) = await LdapUtils.ResolveSearchResult(mock, utils); + Assert.True(success); + Assert.Equal(sid, result.ObjectId); + Assert.Equal(Label.Computer, result.ObjectType); + Assert.True(result.IsDomainController); + Assert.Equal("PRIMARY.TESTLAB.LOCAL", result.DisplayName); + Assert.Equal("S-1-5-21-3130019616-2776909439-2417379446", result.DomainSid); + Assert.Equal("TESTLAB.LOCAL", result.Domain); + Assert.False(result.Deleted); + + mock.Properties.Remove(LDAPProperties.DNSHostName); + mock.Properties[LDAPProperties.CanonicalName] = "PRIMARY"; + (success, result) = await LdapUtils.ResolveSearchResult(mock, utils); + Assert.True(success); + Assert.Equal(sid, result.ObjectId); + Assert.Equal(Label.Computer, result.ObjectType); + Assert.True(result.IsDomainController); + Assert.Equal("PRIMARY.TESTLAB.LOCAL", result.DisplayName); + Assert.Equal("S-1-5-21-3130019616-2776909439-2417379446", result.DomainSid); + Assert.Equal("TESTLAB.LOCAL", result.Domain); + Assert.False(result.Deleted); + + mock.Properties.Remove(LDAPProperties.CanonicalName); + mock.Properties[LDAPProperties.Name] = "PRIMARY"; + (success, result) = await LdapUtils.ResolveSearchResult(mock, utils); + Assert.True(success); + Assert.Equal(sid, result.ObjectId); + Assert.Equal(Label.Computer, result.ObjectType); + Assert.True(result.IsDomainController); + Assert.Equal("PRIMARY.TESTLAB.LOCAL", result.DisplayName); + Assert.Equal("S-1-5-21-3130019616-2776909439-2417379446", result.DomainSid); + Assert.Equal("TESTLAB.LOCAL", result.Domain); + Assert.False(result.Deleted); + + mock.Properties.Remove(LDAPProperties.Name); + (success, result) = await LdapUtils.ResolveSearchResult(mock, utils); + Assert.True(success); + Assert.Equal(sid, result.ObjectId); + Assert.Equal(Label.Computer, result.ObjectType); + Assert.True(result.IsDomainController); + Assert.Equal("UNKNOWN.TESTLAB.LOCAL", result.DisplayName); + Assert.Equal("S-1-5-21-3130019616-2776909439-2417379446", result.DomainSid); + Assert.Equal("TESTLAB.LOCAL", result.Domain); + Assert.False(result.Deleted); + } + + [Fact] + public async Task Test_ResolveSearchResult_MSAGMSA() { + var utils = new MockLdapUtils(); + var attribs = new Dictionary { + { LDAPProperties.ObjectClass, new[] { "top", ObjectClass.MSAClass } }, + { LDAPProperties.SAMAccountType, "805306369" }, + { LDAPProperties.SAMAccountName, "TESTMSA$" } + }; + + const string sid = "S-1-5-21-3130019616-2776909439-2417379446-2105"; + const string dn = "CN=TESTMSA,CN=MANAGED SERVICE ACCOUNTS,DC=TESTLAB,DC=LOCAL"; + var guid = new Guid().ToString(); + + var mock = new MockDirectoryObject(dn, attribs, sid, guid); + + var (success, result) = await LdapUtils.ResolveSearchResult(mock, utils); + Assert.True(success); + Assert.Equal(sid, result.ObjectId); + Assert.Equal(Label.User, result.ObjectType); + Assert.Equal("TESTMSA$@TESTLAB.LOCAL", result.DisplayName); + Assert.Equal("S-1-5-21-3130019616-2776909439-2417379446", result.DomainSid); + Assert.Equal("TESTLAB.LOCAL", result.Domain); + Assert.False(result.Deleted); } } } \ No newline at end of file diff --git a/test/unit/LDAPPropertyTests.cs b/test/unit/LdapPropertyTests.cs similarity index 72% rename from test/unit/LDAPPropertyTests.cs rename to test/unit/LdapPropertyTests.cs index 39014b8b..30c35cd4 100644 --- a/test/unit/LDAPPropertyTests.cs +++ b/test/unit/LdapPropertyTests.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; using CommonLibTest.Facades; using SharpHoundCommonLib; @@ -8,14 +10,15 @@ using SharpHoundCommonLib.Processors; using Xunit; using Xunit.Abstractions; +// ReSharper disable StringLiteralTypo namespace CommonLibTest { - public class LDAPPropertyTests + public class LdapPropertyTests { private readonly ITestOutputHelper _testOutputHelper; - public LDAPPropertyTests(ITestOutputHelper testOutputHelper) + public LdapPropertyTests(ITestOutputHelper testOutputHelper) { _testOutputHelper = testOutputHelper; } @@ -23,13 +26,13 @@ public LDAPPropertyTests(ITestOutputHelper testOutputHelper) [Fact] public void LDAPPropertyProcessor_ReadDomainProperties_TestGoodData() { - var mock = new MockSearchResultEntry("DC\u003dtestlab,DC\u003dlocal", new Dictionary + var mock = new MockDirectoryObject("DC\u003dtestlab,DC\u003dlocal", new Dictionary { {"description", "TESTLAB Domain"}, {"msds-behavior-version", "6"} - }, "S-1-5-21-3130019616-2776909439-2417379446", Label.Domain); + }, "S-1-5-21-3130019616-2776909439-2417379446",""); - var test = LDAPPropertyProcessor.ReadDomainProperties(mock); + var test = LdapPropertyProcessor.ReadDomainProperties(mock); Assert.Contains("functionallevel", test.Keys); Assert.Equal("2012 R2", test["functionallevel"] as string); Assert.Contains("description", test.Keys); @@ -39,12 +42,12 @@ public void LDAPPropertyProcessor_ReadDomainProperties_TestGoodData() [Fact] public void LDAPPropertyProcessor_ReadDomainProperties_TestBadFunctionalLevel() { - var mock = new MockSearchResultEntry("DC\u003dtestlab,DC\u003dlocal", new Dictionary + var mock = new MockDirectoryObject("DC\u003dtestlab,DC\u003dlocal", new Dictionary { {"msds-behavior-version", "a"} - }, "S-1-5-21-3130019616-2776909439-2417379446", Label.Domain); + }, "S-1-5-21-3130019616-2776909439-2417379446",""); - var test = LDAPPropertyProcessor.ReadDomainProperties(mock); + var test = LdapPropertyProcessor.ReadDomainProperties(mock); Assert.Contains("functionallevel", test.Keys); Assert.Equal("Unknown", test["functionallevel"] as string); } @@ -66,25 +69,25 @@ public void LDAPPropertyProcessor_FunctionalLevelToString_TestFunctionalLevels() }; foreach (var (key, value) in expected) - Assert.Equal(value, LDAPPropertyProcessor.FunctionalLevelToString(key)); + Assert.Equal(value, LdapPropertyProcessor.FunctionalLevelToString(key)); } [Fact] public void LDAPPropertyProcessor_ReadGPOProperties_TestGoodData() { - var mock = new MockSearchResultEntry( + var mock = new MockDirectoryObject( "CN\u003d{94DD0260-38B5-497E-8876-10E7A96E80D0},CN\u003dPolicies,CN\u003dSystem,DC\u003dtestlab,DC\u003dlocal", new Dictionary { { "gpcfilesyspath", - Helpers.B64ToString( + Utils.B64ToString( "XFx0ZXN0bGFiLmxvY2FsXFN5c1ZvbFx0ZXN0bGFiLmxvY2FsXFBvbGljaWVzXHs5NEREMDI2MC0zOEI1LTQ5N0UtODg3Ni0xMEU3QTk2RTgwRDB9") }, {"description", "Test"} - }, "S-1-5-21-3130019616-2776909439-2417379446", Label.GPO); + }, "S-1-5-21-3130019616-2776909439-2417379446",""); - var test = LDAPPropertyProcessor.ReadGPOProperties(mock); + var test = LdapPropertyProcessor.ReadGPOProperties(mock); Assert.Contains("description", test.Keys); Assert.Equal("Test", test["description"] as string); @@ -96,13 +99,13 @@ public void LDAPPropertyProcessor_ReadGPOProperties_TestGoodData() [Fact] public void LDAPPropertyProcessor_ReadOUProperties_TestGoodData() { - var mock = new MockSearchResultEntry("OU\u003dTestOU,DC\u003dtestlab,DC\u003dlocal", + var mock = new MockDirectoryObject("OU\u003dTestOU,DC\u003dtestlab,DC\u003dlocal", new Dictionary { {"description", "Test"} - }, "2A374493-816A-4193-BEFD-D2F4132C6DCA", Label.OU); + },"", "2A374493-816A-4193-BEFD-D2F4132C6DCA"); - var test = LDAPPropertyProcessor.ReadOUProperties(mock); + var test = LdapPropertyProcessor.ReadOUProperties(mock); Assert.Contains("description", test.Keys); Assert.Equal("Test", test["description"] as string); } @@ -110,14 +113,14 @@ public void LDAPPropertyProcessor_ReadOUProperties_TestGoodData() [Fact] public void LDAPPropertyProcessor_ReadGroupProperties_TestGoodData() { - var mock = new MockSearchResultEntry("CN\u003dDomain Admins,CN\u003dUsers,DC\u003dtestlab,DC\u003dlocal", + var mock = new MockDirectoryObject("CN\u003dDomain Admins,CN\u003dUsers,DC\u003dtestlab,DC\u003dlocal", new Dictionary { {"description", "Test"}, {"admincount", "1"} - }, "S-1-5-21-3130019616-2776909439-2417379446-512", Label.Group); + }, "S-1-5-21-3130019616-2776909439-2417379446-512",""); - var test = LDAPPropertyProcessor.ReadGroupProperties(mock); + var test = LdapPropertyProcessor.ReadGroupProperties(mock); Assert.Contains("description", test.Keys); Assert.Equal("Test", test["description"] as string); Assert.Contains("admincount", test.Keys); @@ -127,14 +130,14 @@ public void LDAPPropertyProcessor_ReadGroupProperties_TestGoodData() [Fact] public void LDAPPropertyProcessor_ReadGroupProperties_TestGoodData_FalseAdminCount() { - var mock = new MockSearchResultEntry("CN\u003dDomain Admins,CN\u003dUsers,DC\u003dtestlab,DC\u003dlocal", + var mock = new MockDirectoryObject("CN\u003dDomain Admins,CN\u003dUsers,DC\u003dtestlab,DC\u003dlocal", new Dictionary { {"description", "Test"}, {"admincount", "0"} - }, "S-1-5-21-3130019616-2776909439-2417379446-512", Label.Group); + }, "S-1-5-21-3130019616-2776909439-2417379446-512",""); - var test = LDAPPropertyProcessor.ReadGroupProperties(mock); + var test = LdapPropertyProcessor.ReadGroupProperties(mock); Assert.Contains("description", test.Keys); Assert.Equal("Test", test["description"] as string); Assert.Contains("admincount", test.Keys); @@ -144,13 +147,13 @@ public void LDAPPropertyProcessor_ReadGroupProperties_TestGoodData_FalseAdminCou [Fact] public void LDAPPropertyProcessor_ReadGroupProperties_NullAdminCount() { - var mock = new MockSearchResultEntry("CN\u003dDomain Admins,CN\u003dUsers,DC\u003dtestlab,DC\u003dlocal", + var mock = new MockDirectoryObject("CN\u003dDomain Admins,CN\u003dUsers,DC\u003dtestlab,DC\u003dlocal", new Dictionary { {"description", "Test"} - }, "S-1-5-21-3130019616-2776909439-2417379446-512", Label.Group); + }, "S-1-5-21-3130019616-2776909439-2417379446-512",""); - var test = LDAPPropertyProcessor.ReadGroupProperties(mock); + var test = LdapPropertyProcessor.ReadGroupProperties(mock); Assert.Contains("description", test.Keys); Assert.Equal("Test", test["description"] as string); Assert.Contains("admincount", test.Keys); @@ -160,13 +163,13 @@ public void LDAPPropertyProcessor_ReadGroupProperties_NullAdminCount() [Fact] public async Task LDAPPropertyProcessor_ReadUserProperties_TestTrustedToAuth() { - var mock = new MockSearchResultEntry("CN\u003ddfm,CN\u003dUsers,DC\u003dtestlab,DC\u003dlocal", + var mock = new MockDirectoryObject("CN\u003ddfm,CN\u003dUsers,DC\u003dtestlab,DC\u003dlocal", new Dictionary { {"description", "Test"}, {"useraccountcontrol", 0x1000000.ToString()}, - {"lastlogon", "132673011142753043"}, - {"lastlogontimestamp", "132670318095676525"}, + {LDAPProperties.LastLogon, "132673011142753043"}, + {LDAPProperties.LastLogonTimestamp, "132670318095676525"}, {"homedirectory", @"\\win10\testdir"}, { "serviceprincipalname", new[] @@ -178,7 +181,7 @@ public async Task LDAPPropertyProcessor_ReadUserProperties_TestTrustedToAuth() { "sidhistory", new[] { - Helpers.B64ToBytes("AQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQUQQAAA==") + Utils.B64ToBytes("AQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQUQQAAA==") } }, {"pwdlastset", "132131667346106691"}, @@ -189,10 +192,10 @@ public async Task LDAPPropertyProcessor_ReadUserProperties_TestTrustedToAuth() "rdpman/win10" } } - }, "S-1-5-21-3130019616-2776909439-2417379446-1101", Label.User); + }, "S-1-5-21-3130019616-2776909439-2417379446-1101", ""); - var processor = new LDAPPropertyProcessor(new MockLDAPUtils()); - var test = await processor.ReadUserProperties(mock); + var processor = new LdapPropertyProcessor(new MockLdapUtils()); + var test = await processor.ReadUserProperties(mock, "testlab.local"); var props = test.Props; var keys = props.Keys; @@ -223,7 +226,7 @@ public async Task LDAPPropertyProcessor_ReadUserProperties_TestTrustedToAuth() [Fact] public async Task LDAPPropertyProcessor_ReadUserProperties_NullAdminCount() { - var mock = new MockSearchResultEntry("CN\u003ddfm,CN\u003dUsers,DC\u003dtestlab,DC\u003dlocal", + var mock = new MockDirectoryObject("CN\u003ddfm,CN\u003dUsers,DC\u003dtestlab,DC\u003dlocal", new Dictionary { {"description", "Test"}, @@ -240,14 +243,14 @@ public async Task LDAPPropertyProcessor_ReadUserProperties_NullAdminCount() { "sidhistory", new[] { - Helpers.B64ToBytes("AQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQUQQAAA==") + Utils.B64ToBytes("AQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQUQQAAA==") } }, {"pwdlastset", "132131667346106691"} - }, "S-1-5-21-3130019616-2776909439-2417379446-1101", Label.User); + }, "S-1-5-21-3130019616-2776909439-2417379446-1101",""); - var processor = new LDAPPropertyProcessor(new MockLDAPUtils()); - var test = await processor.ReadUserProperties(mock); + var processor = new LdapPropertyProcessor(new MockLdapUtils()); + var test = await processor.ReadUserProperties(mock, "testlab.local"); var props = test.Props; var keys = props.Keys; Assert.Contains("admincount", keys); @@ -257,7 +260,7 @@ public async Task LDAPPropertyProcessor_ReadUserProperties_NullAdminCount() [WindowsOnlyFact] public async Task LDAPPropertyProcessor_ReadUserProperties_HappyPath() { - var mock = new MockSearchResultEntry("CN\u003ddfm,CN\u003dUsers,DC\u003dtestlab,DC\u003dlocal", + var mock = new MockDirectoryObject("CN\u003ddfm,CN\u003dUsers,DC\u003dtestlab,DC\u003dlocal", new Dictionary { {"description", "Test"}, @@ -276,14 +279,14 @@ public async Task LDAPPropertyProcessor_ReadUserProperties_HappyPath() { "sidhistory", new[] { - Helpers.B64ToBytes("AQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQUQQAAA==") + Utils.B64ToBytes("AQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQUQQAAA==") } }, {"pwdlastset", "132131667346106691"} - }, "S-1-5-21-3130019616-2776909439-2417379446-1101", Label.User); + }, "S-1-5-21-3130019616-2776909439-2417379446-1101",""); - var processor = new LDAPPropertyProcessor(new MockLDAPUtils()); - var test = await processor.ReadUserProperties(mock); + var processor = new LdapPropertyProcessor(new MockLdapUtils()); + var test = await processor.ReadUserProperties(mock, "testlab.local"); var props = test.Props; var keys = props.Keys; @@ -339,7 +342,7 @@ public async Task LDAPPropertyProcessor_ReadUserProperties_HappyPath() [Fact] public async Task LDAPPropertyProcessor_ReadUserProperties_TestBadPaths() { - var mock = new MockSearchResultEntry("CN\u003ddfm,CN\u003dUsers,DC\u003dtestlab,DC\u003dlocal", + var mock = new MockDirectoryObject("CN\u003ddfm,CN\u003dUsers,DC\u003dtestlab,DC\u003dlocal", new Dictionary { {"description", "Test"}, @@ -361,10 +364,10 @@ public async Task LDAPPropertyProcessor_ReadUserProperties_TestBadPaths() } }, {"pwdlastset", "132131667346106691"} - }, "S-1-5-21-3130019616-2776909439-2417379446-1101", Label.User); + }, "S-1-5-21-3130019616-2776909439-2417379446-1101",""); - var processor = new LDAPPropertyProcessor(new MockLDAPUtils()); - var test = await processor.ReadUserProperties(mock); + var processor = new LdapPropertyProcessor(new MockLdapUtils()); + var test = await processor.ReadUserProperties(mock, "testlab.local"); var props = test.Props; var keys = props.Keys; @@ -392,7 +395,7 @@ public async Task LDAPPropertyProcessor_ReadUserProperties_TestBadPaths() public async Task LDAPPropertyProcessor_ReadComputerProperties_HappyPath() { //TODO: Add coverage for allowedtoact - var mock = new MockSearchResultEntry("CN\u003dWIN10,OU\u003dTestOU,DC\u003dtestlab,DC\u003dlocal", + var mock = new MockDirectoryObject("CN\u003dWIN10,OU\u003dTestOU,DC\u003dtestlab,DC\u003dlocal", new Dictionary { {"description", "Test"}, @@ -406,7 +409,7 @@ public async Task LDAPPropertyProcessor_ReadComputerProperties_HappyPath() { "sidhistory", new[] { - Helpers.B64ToBytes("AQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQUQQAAA==") + Utils.B64ToBytes("AQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQUQQAAA==") } }, { @@ -429,10 +432,10 @@ public async Task LDAPPropertyProcessor_ReadComputerProperties_HappyPath() "HOST/WIN10.testlab.local" } } - }, "S-1-5-21-3130019616-2776909439-2417379446-1101", Label.Computer); + }, "S-1-5-21-3130019616-2776909439-2417379446-1101",""); - var processor = new LDAPPropertyProcessor(new MockLDAPUtils()); - var test = await processor.ReadComputerProperties(mock); + var processor = new LdapPropertyProcessor(new MockLdapUtils()); + var test = await processor.ReadComputerProperties(mock, "testlab.local"); var props = test.Props; var keys = props.Keys; @@ -490,7 +493,7 @@ public async Task LDAPPropertyProcessor_ReadComputerProperties_HappyPath() [Fact] public async Task LDAPPropertyProcessor_ReadComputerProperties_TestBadPaths() { - var mock = new MockSearchResultEntry("CN\u003dWIN10,OU\u003dTestOU,DC\u003dtestlab,DC\u003dlocal", + var mock = new MockDirectoryObject("CN\u003dWIN10,OU\u003dTestOU,DC\u003dtestlab,DC\u003dlocal", new Dictionary { {"description", "Test"}, @@ -525,10 +528,10 @@ public async Task LDAPPropertyProcessor_ReadComputerProperties_TestBadPaths() "HOST/WIN10.testlab.local" } } - }, "S-1-5-21-3130019616-2776909439-2417379446-1101", Label.Computer); + }, "S-1-5-21-3130019616-2776909439-2417379446-1101", ""); - var processor = new LDAPPropertyProcessor(new MockLDAPUtils()); - var test = await processor.ReadComputerProperties(mock); + var processor = new LdapPropertyProcessor(new MockLdapUtils()); + var test = await processor.ReadComputerProperties(mock, "testlab.local"); var props = test.Props; var keys = props.Keys; @@ -546,7 +549,7 @@ public async Task LDAPPropertyProcessor_ReadComputerProperties_TestBadPaths() [Fact] public async Task LDAPPropertyProcessor_ReadComputerProperties_TestDumpSMSAPassword() { - var mock = new MockSearchResultEntry("CN\u003dWIN10,OU\u003dTestOU,DC\u003dtestlab,DC\u003dlocal", + var mock = new MockDirectoryObject("CN\u003dWIN10,OU\u003dTestOU,DC\u003dtestlab,DC\u003dlocal", new Dictionary { {"description", "Test"}, @@ -559,7 +562,7 @@ public async Task LDAPPropertyProcessor_ReadComputerProperties_TestDumpSMSAPassw { "sidhistory", new[] { - Helpers.B64ToBytes("AQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQUQQAAA==") + Utils.B64ToBytes("AQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQUQQAAA==") } }, { @@ -589,10 +592,10 @@ public async Task LDAPPropertyProcessor_ReadComputerProperties_TestDumpSMSAPassw "CN=krbtgt,CN=Users,DC=testlab,DC=local" } } - }, "S-1-5-21-3130019616-2776909439-2417379446-1101", Label.Computer); + }, "S-1-5-21-3130019616-2776909439-2417379446-1101", ""); - var processor = new LDAPPropertyProcessor(new MockLDAPUtils()); - var test = await processor.ReadComputerProperties(mock); + var processor = new LdapPropertyProcessor(new MockLdapUtils()); + var test = await processor.ReadComputerProperties(mock, "testlab.local"); var expected = new TypedPrincipal[] { @@ -617,7 +620,7 @@ public async Task LDAPPropertyProcessor_ReadComputerProperties_TestDumpSMSAPassw [Fact] public void LDAPPropertyProcessor_ReadRootCAProperties() { - var mock = new MockSearchResultEntry( + var mock = new MockDirectoryObject( "CN\u003dDUMPSTER-DC01-CA,CN\u003dCERTIFICATION AUTHORITIES,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dDUMPSTER,DC\u003dFIRE", new Dictionary { @@ -626,9 +629,9 @@ public void LDAPPropertyProcessor_ReadRootCAProperties() {"name", "DUMPSTER-DC01-CA@DUMPSTER.FIRE"}, {"domainsid", "S-1-5-21-2697957641-2271029196-387917394"}, {"whencreated", 1683986131}, - }, "2F9F3630-F46A-49BF-B186-6629994EBCF9", Label.RootCA); + }, "","2F9F3630-F46A-49BF-B186-6629994EBCF9"); - var test = LDAPPropertyProcessor.ReadRootCAProperties(mock); + var test = LdapPropertyProcessor.ReadRootCAProperties(mock); var keys = test.Keys; //These are not common properties @@ -636,14 +639,17 @@ public void LDAPPropertyProcessor_ReadRootCAProperties() Assert.DoesNotContain("name", keys); Assert.DoesNotContain("domainsid", keys); - Assert.Contains("description", keys); Assert.Contains("whencreated", keys); } [Fact] - public void LDAPPropertyProcessor_ReadAIACAProperties() - { - var mock = new MockSearchResultEntry( + public void LDAPPropertyProcessor_ReadAIACAProperties() { + var ecdsa = ECDsa.Create(); + var req = new CertificateRequest("cn=foobar", ecdsa, HashAlgorithmName.SHA256); + var cert = req.CreateSelfSigned(DateTimeOffset.Now, DateTimeOffset.Now.AddYears(5)); + + var bytes = cert.Export(X509ContentType.Cert, "abc"); + var mock = new MockDirectoryObject( "CN\u003dDUMPSTER-DC01-CA,CN\u003dAIA,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dDUMPSTER,DC\u003dFIRE", new Dictionary { @@ -653,9 +659,10 @@ public void LDAPPropertyProcessor_ReadAIACAProperties() {"domainsid", "S-1-5-21-2697957641-2271029196-387917394"}, {"whencreated", 1683986131}, {"hascrosscertificatepair", true}, - }, "2F9F3630-F46A-49BF-B186-6629994EBCF9", Label.AIACA); + {LDAPProperties.CACertificate, bytes} + }, "","2F9F3630-F46A-49BF-B186-6629994EBCF9"); - var test = LDAPPropertyProcessor.ReadAIACAProperties(mock); + var test = LdapPropertyProcessor.ReadAIACAProperties(mock); var keys = test.Keys; //These are not common properties @@ -663,15 +670,19 @@ public void LDAPPropertyProcessor_ReadAIACAProperties() Assert.DoesNotContain("name", keys); Assert.DoesNotContain("domainsid", keys); - Assert.Contains("description", keys); Assert.Contains("whencreated", keys); Assert.Contains("crosscertificatepair", keys); + Assert.Contains("certthumbprint", keys); + Assert.Contains("certname", keys); + Assert.Contains("certchain", keys); + Assert.Contains("hasbasicconstraints", keys); + Assert.Contains("basicconstraintpathlength", keys); } [Fact] public void LDAPPropertyProcessor_ReadNTAuthStoreProperties() { - var mock = new MockSearchResultEntry("CN\u003dNTAUTHCERTIFICATES,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dDUMPSTER,DC\u003dFIRE", + var mock = new MockDirectoryObject("CN\u003dNTAUTHCERTIFICATES,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dDUMPSTER,DC\u003dFIRE", new Dictionary { {"description", null}, @@ -679,9 +690,9 @@ public void LDAPPropertyProcessor_ReadNTAuthStoreProperties() {"name", "NTAUTHCERTIFICATES@DUMPSTER.FIRE"}, {"domainsid", "S-1-5-21-2697957641-2271029196-387917394"}, {"whencreated", 1683986131}, - }, "2F9F3630-F46A-49BF-B186-6629994EBCF9", Label.NTAuthStore); + }, "","2F9F3630-F46A-49BF-B186-6629994EBCF9"); - var test = LDAPPropertyProcessor.ReadNTAuthStoreProperties(mock); + var test = LdapPropertyProcessor.ReadNTAuthStoreProperties(mock); var keys = test.Keys; //These are not common properties @@ -689,14 +700,13 @@ public void LDAPPropertyProcessor_ReadNTAuthStoreProperties() Assert.DoesNotContain("name", keys); Assert.DoesNotContain("domainsid", keys); - Assert.Contains("description", keys); Assert.Contains("whencreated", keys); } [Fact] public void LDAPPropertyProcessor_ReadCertTemplateProperties() { - var mock = new MockSearchResultEntry("CN\u003dWORKSTATION,CN\u003dCERTIFICATE TEMPLATES,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dEXTERNAL,DC\u003dLOCAL", + var mock = new MockDirectoryObject("CN\u003dWORKSTATION,CN\u003dCERTIFICATE TEMPLATES,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dEXTERNAL,DC\u003dLOCAL", new Dictionary { {"domain", "EXTERNAL.LOCAL"}, @@ -706,32 +716,33 @@ public void LDAPPropertyProcessor_ReadCertTemplateProperties() {"whencreated", 1683986183}, {"validityperiod", 31536000}, {"renewalperiod", 3628800}, - {"schemaversion", 2}, + {LDAPProperties.TemplateSchemaVersion, 2}, {"displayname", "Workstation Authentication"}, {"oid", "1.3.6.1.4.1.311.21.8.4571196.1884641.3293620.10686285.12068043.134.1.30"}, - {"enrollmentflag", 32}, + {LDAPProperties.PKIEnrollmentFlag, 32}, {"requiresmanagerapproval", false}, - {"certificatenameflag", 0x8000000}, + {LDAPProperties.PKINameFlag, 0x8000000}, {"ekus", new[] - {"1.3.6.1.5.5.7.3.2"} + {"1.3.6.1.5.5.7.3.2"} }, {LDAPProperties.CertificateApplicationPolicy, new[] - {"1.3.6.1.5.5.7.3.2"} + {"1.3.6.1.5.5.7.3.2"} }, {LDAPProperties.CertificatePolicy, new[] {"1.3.6.1.5.5.7.3.2"} }, - {"authorizedsignatures", 1}, + {LDAPProperties.NumSignaturesRequired, 1}, {"applicationpolicies", new[] - { "1.3.6.1.4.1.311.20.2.1"} + { "1.3.6.1.4.1.311.20.2.1"} }, {"issuancepolicies", new[] - {"1.3.6.1.4.1.311.21.8.4571196.1884641.3293620.10686285.12068043.134.1.400", - "1.3.6.1.4.1.311.21.8.4571196.1884641.3293620.10686285.12068043.134.1.402"} + {"1.3.6.1.4.1.311.21.8.4571196.1884641.3293620.10686285.12068043.134.1.400", + "1.3.6.1.4.1.311.21.8.4571196.1884641.3293620.10686285.12068043.134.1.402"} }, - }, "2F9F3630-F46A-49BF-B186-6629994EBCF9", Label.CertTemplate); + {LDAPProperties.PKIPrivateKeyFlag, 256}, + }, "","2F9F3630-F46A-49BF-B186-6629994EBCF9"); - var test = LDAPPropertyProcessor.ReadCertTemplateProperties(mock); + var test = LdapPropertyProcessor.ReadCertTemplateProperties(mock); var keys = test.Keys; //These are not common properties @@ -739,7 +750,6 @@ public void LDAPPropertyProcessor_ReadCertTemplateProperties() Assert.DoesNotContain("name", keys); Assert.DoesNotContain("domainsid", keys); - Assert.Contains("description", keys); Assert.Contains("whencreated", keys); Assert.Contains("validityperiod", keys); Assert.Contains("renewalperiod", keys); @@ -769,83 +779,75 @@ public void LDAPPropertyProcessor_ReadCertTemplateProperties() Assert.Contains("issuancepolicies", keys); } - // - // [Fact] - // public void LDAPPropertyProcessor_ReadIssuancePolicyProperties() - // { - // var mock = new MockSearchResultEntry("CN\u003d6250993.11BB1AB25A8A65E9FCDF709FCDD5FBC6,CN\u003dOID,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dESC10,DC\u003dLOCAL", - // new Dictionary - // { - // {"domain", "ESC10.LOCAL"}, - // {"name", "KEYADMINSOID@ESC10.LOCAL"}, - // {"domainsid", "S-1-5-21-3662707843-2053279151-3839588741"}, - // {"description", null}, - // {"whencreated", 1712567279}, - // {"displayname", "KeyAdminsOID"}, - // {"certtemplateoid", "1.3.6.1.4.1.311.21.8.4571196.1884641.3293620.10686285.12068043.134.1.30"}, - // {"msds-oidtogrouplink", "CN=ENTERPRISE KEY ADMINS,CN=USERS,DC=ESC10,DC=LOCAL"} - // , - // }, "1E5311A8-E949-4E02-8E08-234ED63200DE", Label.IssuancePolicy); - // - // var mockLDAPUtils = new MockLDAPUtils(); - // var ldapPropertyProcessor = new LDAPPropertyProcessor(mockLDAPUtils); - // - // - // var test = ldapPropertyProcessor.ReadIssuancePolicyProperties(mock); - // var keys = test.Props.Keys; - // - // //These are not common properties - // Assert.DoesNotContain("domain", keys); - // Assert.DoesNotContain("name", keys); - // Assert.DoesNotContain("domainsid", keys); - // - // Assert.Contains("description", keys); - // Assert.Contains("whencreated", keys); - // Assert.Contains("displayname", keys); - // Assert.Contains("certtemplateoid", keys); - // Assert.Contains("oidgrouplink", keys); - // } - // - // [Fact] - // public void LDAPPropertyProcessor_ReadIssuancePolicyProperties_NoOIDGroupLink() - // { - // var mock = new MockSearchResultEntry("CN\u003d6250993.11BB1AB25A8A65E9FCDF709FCDD5FBC6,CN\u003dOID,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dESC10,DC\u003dLOCAL", - // new Dictionary - // { - // {"domain", "ESC10.LOCAL"}, - // {"name", "KEYADMINSOID@ESC10.LOCAL"}, - // {"domainsid", "S-1-5-21-3662707843-2053279151-3839588741"}, - // {"description", null}, - // {"whencreated", 1712567279}, - // {"displayname", "KeyAdminsOID"}, - // {"certtemplateoid", "1.3.6.1.4.1.311.21.8.4571196.1884641.3293620.10686285.12068043.134.1.30"}, - // {"msds-oidtogrouplink", null} - // , - // }, "1E5311A8-E949-4E02-8E08-234ED63200DE", Label.IssuancePolicy); - // - // var mockLDAPUtils = new MockLDAPUtils(); - // var ldapPropertyProcessor = new LDAPPropertyProcessor(mockLDAPUtils); - // - // - // var test = ldapPropertyProcessor.ReadIssuancePolicyProperties(mock); - // var keys = test.Props.Keys; - // - // //These are not common properties - // Assert.DoesNotContain("domain", keys); - // Assert.DoesNotContain("name", keys); - // Assert.DoesNotContain("domainsid", keys); - // Assert.DoesNotContain("oidgrouplink", keys); - // - // Assert.Contains("description", keys); - // Assert.Contains("whencreated", keys); - // Assert.Contains("displayname", keys); - // Assert.Contains("certtemplateoid", keys); - // } + + [Fact] + public async Task LDAPPropertyProcessor_ReadIssuancePolicyProperties() + { + var mock = new MockDirectoryObject("CN\u003d6250993.11BB1AB25A8A65E9FCDF709FCDD5FBC6,CN\u003dOID,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dESC10,DC\u003dLOCAL", + new Dictionary + { + {LDAPProperties.Description, null}, + {LDAPProperties.WhenCreated, 1712567279}, + {LDAPProperties.DisplayName, "KeyAdminsOID"}, + {LDAPProperties.CertTemplateOID, "1.3.6.1.4.1.311.21.8.4571196.1884641.3293620.10686285.12068043.134.1.30"}, + {LDAPProperties.OIDGroupLink, "CN=ENTERPRISE KEY ADMINS,CN=USERS,DC=ESC10,DC=LOCAL"} + , + }, "","1E5311A8-E949-4E02-8E08-234ED63200DE"); + + var mockLDAPUtils = new MockLdapUtils(); + var ldapPropertyProcessor = new LdapPropertyProcessor(mockLDAPUtils); + + + var test = await ldapPropertyProcessor.ReadIssuancePolicyProperties(mock); + var keys = test.Props.Keys; + + //These are not common properties + Assert.DoesNotContain("domain", keys); + Assert.DoesNotContain("name", keys); + Assert.DoesNotContain("domainsid", keys); + + Assert.Contains("whencreated", keys); + Assert.Contains("displayname", keys); + Assert.Contains("certtemplateoid", keys); + Assert.Contains("oidgrouplink", keys); + } + + [Fact] + public async Task LDAPPropertyProcessor_ReadIssuancePolicyProperties_NoOIDGroupLink() + { + var mock = new MockDirectoryObject("CN\u003d6250993.11BB1AB25A8A65E9FCDF709FCDD5FBC6,CN\u003dOID,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dESC10,DC\u003dLOCAL", + new Dictionary + { + {LDAPProperties.Description, null}, + {LDAPProperties.WhenCreated, 1712567279}, + {LDAPProperties.DisplayName, "KeyAdminsOID"}, + {LDAPProperties.CertTemplateOID, "1.3.6.1.4.1.311.21.8.4571196.1884641.3293620.10686285.12068043.134.1.30"}, + {LDAPProperties.OIDGroupLink, null} + , + }, "","1E5311A8-E949-4E02-8E08-234ED63200DE"); + + var mockLDAPUtils = new MockLdapUtils(); + var ldapPropertyProcessor = new LdapPropertyProcessor(mockLDAPUtils); + + var test = await ldapPropertyProcessor.ReadIssuancePolicyProperties(mock); + var keys = test.Props.Keys; + + //These are not common properties + Assert.DoesNotContain("domain", keys); + Assert.DoesNotContain("name", keys); + Assert.DoesNotContain("domainsid", keys); + Assert.DoesNotContain("oidgrouplink", keys); + + //Assert.Contains("description", keys); + Assert.Contains("whencreated", keys); + Assert.Contains("displayname", keys); + Assert.Contains("certtemplateoid", keys); + } [Fact] public void LDAPPropertyProcessor_ParseAllProperties() { - var mock = new MockSearchResultEntry("CN\u003dNTAUTHCERTIFICATES,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dDUMPSTER,DC\u003dFIRE", + var mock = new MockDirectoryObject("CN\u003dNTAUTHCERTIFICATES,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dDUMPSTER,DC\u003dFIRE", new Dictionary { {"description", null}, @@ -853,9 +855,9 @@ public void LDAPPropertyProcessor_ParseAllProperties() {"name", "NTAUTHCERTIFICATES@DUMPSTER.FIRE"}, {"domainsid", "S-1-5-21-2697957641-2271029196-387917394"}, {"whencreated", 1683986131}, - }, "2F9F3630-F46A-49BF-B186-6629994EBCF9", Label.NTAuthStore); + }, "","2F9F3630-F46A-49BF-B186-6629994EBCF9"); - var processor = new LDAPPropertyProcessor(new MockLDAPUtils()); + var processor = new LdapPropertyProcessor(new MockLdapUtils()); var props = processor.ParseAllProperties(mock); var keys = props.Keys; @@ -871,11 +873,11 @@ public void LDAPPropertyProcessor_ParseAllProperties() [Fact] public void LDAPPropertyProcessor_ParseAllProperties_NoProperties() { - var mock = new MockSearchResultEntry("CN\u003dNTAUTHCERTIFICATES,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dDUMPSTER,DC\u003dFIRE", + var mock = new MockDirectoryObject("CN\u003dNTAUTHCERTIFICATES,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dDUMPSTER,DC\u003dFIRE", new Dictionary - { }, "2F9F3630-F46A-49BF-B186-6629994EBCF9", Label.NTAuthStore); + { }, "","2F9F3630-F46A-49BF-B186-6629994EBCF9"); - var processor = new LDAPPropertyProcessor(new MockLDAPUtils()); + var processor = new LdapPropertyProcessor(new MockLdapUtils()); var props = processor.ParseAllProperties(mock); var keys = props.Keys; @@ -886,11 +888,11 @@ public void LDAPPropertyProcessor_ParseAllProperties_NoProperties() [Fact] public void LDAPPropertyProcessor_ParseAllProperties_CollectionCountOne_NullString() { - var mock = new MockSearchResultEntry("CN\u003dNTAUTHCERTIFICATES,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dDUMPSTER,DC\u003dFIRE", + var mock = new MockDirectoryObject("CN\u003dNTAUTHCERTIFICATES,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dDUMPSTER,DC\u003dFIRE", new Dictionary - {{"domainsid", null} }, "2F9F3630-F46A-49BF-B186-6629994EBCF9", Label.NTAuthStore); + {{"domainsid", null} }, "","2F9F3630-F46A-49BF-B186-6629994EBCF9"); - var processor = new LDAPPropertyProcessor(new MockLDAPUtils()); + var processor = new LdapPropertyProcessor(new MockLdapUtils()); var props = processor.ParseAllProperties(mock); var keys = props.Keys; @@ -900,11 +902,11 @@ public void LDAPPropertyProcessor_ParseAllProperties_CollectionCountOne_NullStri [Fact] public void LDAPPropertyProcessor_ParseAllProperties_CollectionCountOne_BadPasswordTime() { - var mock = new MockSearchResultEntry("CN\u003dNTAUTHCERTIFICATES,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dDUMPSTER,DC\u003dFIRE", + var mock = new MockDirectoryObject("CN\u003dNTAUTHCERTIFICATES,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dDUMPSTER,DC\u003dFIRE", new Dictionary - {{"badpasswordtime", "130435290000000000"} }, "2F9F3630-F46A-49BF-B186-6629994EBCF9", Label.NTAuthStore); + {{"badpasswordtime", "130435290000000000"} }, "","2F9F3630-F46A-49BF-B186-6629994EBCF9"); - var processor = new LDAPPropertyProcessor(new MockLDAPUtils()); + var processor = new LdapPropertyProcessor(new MockLdapUtils()); var props = processor.ParseAllProperties(mock); var keys = props.Keys; @@ -915,11 +917,11 @@ public void LDAPPropertyProcessor_ParseAllProperties_CollectionCountOne_BadPassw [Fact] public void LDAPPropertyProcessor_ParseAllProperties_CollectionCountOne_NotBadPasswordTime() { - var mock = new MockSearchResultEntry("CN\u003dNTAUTHCERTIFICATES,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dDUMPSTER,DC\u003dFIRE", + var mock = new MockDirectoryObject("CN\u003dNTAUTHCERTIFICATES,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dDUMPSTER,DC\u003dFIRE", new Dictionary - {{"domainsid", "S-1-5-21-2697957641-2271029196-387917394"}}, "2F9F3630-F46A-49BF-B186-6629994EBCF9", Label.NTAuthStore); + {{"domainsid", "S-1-5-21-2697957641-2271029196-387917394"}}, "","2F9F3630-F46A-49BF-B186-6629994EBCF9"); - var processor = new LDAPPropertyProcessor(new MockLDAPUtils()); + var processor = new LdapPropertyProcessor(new MockLdapUtils()); var props = processor.ParseAllProperties(mock); var keys = props.Keys; diff --git a/test/unit/LocalGroupProcessorTest.cs b/test/unit/LocalGroupProcessorTest.cs index 57f5d88a..e12cf85b 100644 --- a/test/unit/LocalGroupProcessorTest.cs +++ b/test/unit/LocalGroupProcessorTest.cs @@ -27,7 +27,7 @@ public void Dispose() [WindowsOnlyFact] public async Task LocalGroupProcessor_TestWorkstation() { - var mockProcessor = new Mock(new MockLDAPUtils(), null); + var mockProcessor = new Mock(new MockLdapUtils(), null); var mockSamServer = new MockWorkstationSAMServer(); mockProcessor.Setup(x => x.OpenSamServer(It.IsAny())).Returns(mockSamServer); var processor = mockProcessor.Object; @@ -58,7 +58,7 @@ public async Task LocalGroupProcessor_TestWorkstation() [WindowsOnlyFact] public async Task LocalGroupProcessor_TestDomainController() { - var mockProcessor = new Mock(new MockLDAPUtils(), null); + var mockProcessor = new Mock(new MockLdapUtils(), null); var mockSamServer = new MockDCSAMServer(); mockProcessor.Setup(x => x.OpenSamServer(It.IsAny())).Returns(mockSamServer); var processor = mockProcessor.Object; @@ -76,7 +76,7 @@ public async Task LocalGroupProcessor_TestDomainController() [Fact] public async Task LocalGroupProcessor_ResolveGroupName_NonDC() { - var mockUtils = new Mock(); + var mockUtils = new Mock(); var proc = new LocalGroupProcessor(mockUtils.Object); var resultTask = TestPrivateMethod.InstanceMethod>(proc, "ResolveGroupName", @@ -95,7 +95,7 @@ public async Task LocalGroupProcessor_ResolveGroupName_NonDC() [Fact] public async Task LocalGroupProcessor_ResolveGroupName_DC() { - var mockUtils = new Mock(); + var mockUtils = new Mock(); var proc = new LocalGroupProcessor(mockUtils.Object); var resultTask = TestPrivateMethod.InstanceMethod>(proc, "ResolveGroupName", diff --git a/test/unit/SPNProcessorsTest.cs b/test/unit/SPNProcessorsTest.cs index fa83f0e3..fd124202 100644 --- a/test/unit/SPNProcessorsTest.cs +++ b/test/unit/SPNProcessorsTest.cs @@ -14,7 +14,7 @@ public class SPNProcessorsTest [Fact] public async Task ReadSPNTargets_SPNLengthZero_YieldBreak() { - var processor = new SPNProcessors(new MockLDAPUtils()); + var processor = new SPNProcessors(new MockLdapUtils()); var servicePrincipalNames = Array.Empty(); const string distinguishedName = "cn=policies,cn=system,DC=testlab,DC=local"; await foreach (var spn in processor.ReadSPNTargets(servicePrincipalNames, distinguishedName)) @@ -24,7 +24,7 @@ public async Task ReadSPNTargets_SPNLengthZero_YieldBreak() [Fact] public async Task ReadSPNTargets_NoPortSupplied_ParsedCorrectly() { - var processor = new SPNProcessors(new MockLDAPUtils()); + var processor = new SPNProcessors(new MockLdapUtils()); string[] servicePrincipalNames = {"MSSQLSvc/PRIMARY.TESTLAB.LOCAL"}; const string distinguishedName = "cn=policies,cn=system,DC=testlab,DC=local"; @@ -45,7 +45,7 @@ public async Task ReadSPNTargets_NoPortSupplied_ParsedCorrectly() [Fact] public async Task ReadSPNTargets_BadPortSupplied_ParsedCorrectly() { - var processor = new SPNProcessors(new MockLDAPUtils()); + var processor = new SPNProcessors(new MockLdapUtils()); string[] servicePrincipalNames = {"MSSQLSvc/PRIMARY.TESTLAB.LOCAL:abcd"}; const string distinguishedName = "cn=policies,cn=system,DC=testlab,DC=local"; @@ -66,7 +66,7 @@ public async Task ReadSPNTargets_BadPortSupplied_ParsedCorrectly() [Fact] public async void ReadSPNTargets_SuppliedPort_ParsedCorrectly() { - var processor = new SPNProcessors(new MockLDAPUtils()); + var processor = new SPNProcessors(new MockLdapUtils()); string[] servicePrincipalNames = {"MSSQLSvc/PRIMARY.TESTLAB.LOCAL:2345"}; const string distinguishedName = "cn=policies,cn=system,DC=testlab,DC=local"; @@ -87,7 +87,7 @@ public async void ReadSPNTargets_SuppliedPort_ParsedCorrectly() [Fact] public async void ReadSPNTargets_MissingMssqlSvc_NotRead() { - var processor = new SPNProcessors(new MockLDAPUtils()); + var processor = new SPNProcessors(new MockLdapUtils()); string[] servicePrincipalNames = {"myhost.redmond.microsoft.com:1433"}; const string distinguishedName = "CN=Jeff Smith,OU=Sales,DC=Fabrikam,DC=COM"; await foreach (var spn in processor.ReadSPNTargets(servicePrincipalNames, distinguishedName)) @@ -97,7 +97,7 @@ public async void ReadSPNTargets_MissingMssqlSvc_NotRead() [Fact] public async void ReadSPNTargets_SPNWithAddressSign_NotRead() { - var processor = new SPNProcessors(new MockLDAPUtils()); + var processor = new SPNProcessors(new MockLdapUtils()); string[] servicePrincipalNames = {"MSSQLSvc/myhost.redmond.microsoft.com:1433 user@domain"}; const string distinguishedName = "CN=Jeff Smith,OU=Sales,DC=Fabrikam,DC=COM"; await foreach (var spn in processor.ReadSPNTargets(servicePrincipalNames, distinguishedName)) diff --git a/test/unit/SearchResultEntryTests.cs b/test/unit/SearchResultEntryTests.cs deleted file mode 100644 index d49109eb..00000000 --- a/test/unit/SearchResultEntryTests.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Collections.Generic; -using System.Security.Principal; -using CommonLibTest.Facades; -using SharpHoundCommonLib; -using SharpHoundCommonLib.Enums; -using Xunit; - -namespace CommonLibTest -{ - public class SearchResultEntryTests - { - [WindowsOnlyFact] - public void Test_GetLabelIssuanceOIDObjects() - { - var sid = new SecurityIdentifier("S-1-5-21-3130019616-2776909439-2417379446-500"); - var bsid = new byte[sid.BinaryLength]; - sid.GetBinaryForm(bsid, 0); - var attribs = new Dictionary - { - { "objectsid", bsid}, - { "objectclass", "msPKI-Enterprise-Oid" }, - { "flags", "2" } - }; - - var sre = MockableSearchResultEntry.Construct(attribs, "CN=Test,CN=OID,CN=Public Key Services,CN=Services,CN=Configuration"); - var success = sre.GetLabel(out var label); - Assert.True(success); - Assert.Equal(Label.IssuancePolicy, label); - - sre = MockableSearchResultEntry.Construct(attribs, "CN=OID,CN=Public Key Services,CN=Services,CN=Configuration"); - success = sre.GetLabel(out label); - Assert.True(success); - Assert.Equal(Label.Container, label); - } - } -} \ No newline at end of file diff --git a/test/unit/TestLogger.cs b/test/unit/TestLogger.cs index 8f15398f..53a10259 100644 --- a/test/unit/TestLogger.cs +++ b/test/unit/TestLogger.cs @@ -1,9 +1,11 @@ using System; +using System.Diagnostics.CodeAnalysis; using Microsoft.Extensions.Logging; using Xunit.Abstractions; namespace CommonLibTest { + [ExcludeFromCodeCoverage] public class TestLogger : ILogger { private readonly LogLevel _level; diff --git a/test/unit/UserRightsAssignmentProcessorTest.cs b/test/unit/UserRightsAssignmentProcessorTest.cs index 3bfca790..b4d4488c 100644 --- a/test/unit/UserRightsAssignmentProcessorTest.cs +++ b/test/unit/UserRightsAssignmentProcessorTest.cs @@ -24,7 +24,7 @@ public UserRightsAssignmentProcessorTest(ITestOutputHelper testOutputHelper) [WindowsOnlyFact] public async Task UserRightsAssignmentProcessor_TestWorkstation() { - var mockProcessor = new Mock(new MockLDAPUtils(), null); + var mockProcessor = new Mock(new MockLdapUtils(), null); var mockLSAPolicy = new MockWorkstationLSAPolicy(); mockProcessor.Setup(x => x.OpenLSAPolicy(It.IsAny())).Returns(mockLSAPolicy); var processor = mockProcessor.Object; @@ -46,7 +46,7 @@ public async Task UserRightsAssignmentProcessor_TestWorkstation() [WindowsOnlyFact] public async Task UserRightsAssignmentProcessor_TestDC() { - var mockProcessor = new Mock(new MockLDAPUtils(), null); + var mockProcessor = new Mock(new MockLdapUtils(), null); var mockLSAPolicy = new MockDCLSAPolicy(); mockProcessor.Setup(x => x.OpenLSAPolicy(It.IsAny())).Returns(mockLSAPolicy); var processor = mockProcessor.Object; diff --git a/test/unit/Helpers.cs b/test/unit/Utils.cs similarity index 69% rename from test/unit/Helpers.cs rename to test/unit/Utils.cs index fa3f1482..84f9799e 100644 --- a/test/unit/Helpers.cs +++ b/test/unit/Utils.cs @@ -8,7 +8,7 @@ namespace CommonLibTest { - public class Helpers + public static class Utils { internal static byte[] B64ToBytes(string base64) { @@ -24,16 +24,6 @@ internal static string B64ToString(string base64) internal static class Extensions { - internal static async Task ToArrayAsync(this IAsyncEnumerable items, - CancellationToken cancellationToken = default) - { - var results = new List(); - await foreach (var item in items.WithCancellation(cancellationToken) - .ConfigureAwait(false)) - results.Add(item); - return results.ToArray(); - } - internal static bool IsArray(this object obj) { var valueType = obj?.GetType(); diff --git a/test/unit/WellKnownPrincipalTest.cs b/test/unit/WellKnownPrincipalTest.cs index 73c3c419..d49a3f25 100644 --- a/test/unit/WellKnownPrincipalTest.cs +++ b/test/unit/WellKnownPrincipalTest.cs @@ -6,14 +6,6 @@ namespace CommonLibTest { - public struct WKP - { - public string SID { get; set; } - public string Name { get; set; } - - public string Description { get; set; } - } - public class WellKnownPrincipalTest : IDisposable { #region Constructor(s) @@ -21,8 +13,6 @@ public class WellKnownPrincipalTest : IDisposable public WellKnownPrincipalTest(ITestOutputHelper testOutputHelper) { _testOutputHelper = testOutputHelper; - _testDomainName = "TESTLAB.LOCAL"; - _testForestName = "FOREST.LOCAL"; } #endregion @@ -131,8 +121,6 @@ private static (string sid, string name, Label label)[] GetWellKnownPrincipals() #region Private Members private readonly ITestOutputHelper _testOutputHelper; - private readonly string _testDomainName; - private readonly string _testForestName; #endregion From 7757f2fa76c7d90aee6ae11a820bd5ac4e26eec9 Mon Sep 17 00:00:00 2001 From: rvazarkar Date: Mon, 22 Jul 2024 14:48:49 -0400 Subject: [PATCH 55/68] chore: delete backup file --- src/CommonLib/LDAPUtilsBackup.cs | 2261 ------------------------------ 1 file changed, 2261 deletions(-) delete mode 100644 src/CommonLib/LDAPUtilsBackup.cs diff --git a/src/CommonLib/LDAPUtilsBackup.cs b/src/CommonLib/LDAPUtilsBackup.cs deleted file mode 100644 index f606bb89..00000000 --- a/src/CommonLib/LDAPUtilsBackup.cs +++ /dev/null @@ -1,2261 +0,0 @@ -// using System; -// using System.Collections.Concurrent; -// using System.Collections.Generic; -// using System.Diagnostics; -// using System.DirectoryServices; -// using System.DirectoryServices.ActiveDirectory; -// using System.DirectoryServices.Protocols; -// using System.Linq; -// using System.Net; -// using System.Net.Sockets; -// using System.Security.Principal; -// using System.Text; -// using System.Threading; -// using System.Threading.Tasks; -// using Microsoft.Extensions.Logging; -// using SharpHoundCommonLib.Enums; -// using SharpHoundCommonLib.Exceptions; -// using SharpHoundCommonLib.LDAPQueries; -// using SharpHoundCommonLib.OutputTypes; -// using SharpHoundCommonLib.Processors; -// using SharpHoundRPC.NetAPINative; -// using Domain = System.DirectoryServices.ActiveDirectory.Domain; -// using SearchScope = System.DirectoryServices.Protocols.SearchScope; -// using SecurityMasks = System.DirectoryServices.Protocols.SecurityMasks; -// -// namespace SharpHoundCommonLib -// { -// public class LDAPUtils : ILDAPUtils -// { -// private const string NullCacheKey = "UNIQUENULL"; -// -// // The following byte stream contains the necessary message to request a NetBios name from a machine -// // http://web.archive.org/web/20100409111218/http://msdn.microsoft.com/en-us/library/system.net.sockets.socket.aspx -// private static readonly byte[] NameRequest = -// { -// 0x80, 0x94, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, -// 0x00, 0x00, 0x00, 0x00, 0x20, 0x43, 0x4b, 0x41, -// 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, -// 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, -// 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, -// 0x41, 0x41, 0x41, 0x41, 0x41, 0x00, 0x00, 0x21, -// 0x00, 0x01 -// }; -// -// -// private static readonly ConcurrentDictionary -// SeenWellKnownPrincipals = new(); -// -// private static readonly ConcurrentDictionary DomainControllers = new(); -// private static readonly ConcurrentDictionary CachedDomainInfo = new(StringComparer.OrdinalIgnoreCase); -// -// private readonly ConcurrentDictionary _domainCache = new(); -// private static readonly TimeSpan MinBackoffDelay = TimeSpan.FromSeconds(2); -// private static readonly TimeSpan MaxBackoffDelay = TimeSpan.FromSeconds(20); -// private const int BackoffDelayMultiplier = 2; -// private const int MaxRetries = 3; -// -// private readonly ConcurrentDictionary _hostResolutionMap = new(); -// private readonly ConcurrentDictionary _ldapConnections = new(); -// private readonly ConcurrentDictionary _ldapRangeSizeCache = new(); -// private readonly ILogger _log; -// private readonly NativeMethods _nativeMethods; -// private readonly ConcurrentDictionary _netbiosCache = new(); -// private readonly PortScanner _portScanner; -// private LDAPConfig _ldapConfig = new(); -// private readonly ManualResetEvent _connectionResetEvent = new(false); -// private readonly object _lockObj = new(); -// -// -// /// -// /// Creates a new instance of LDAP Utils with defaults -// /// -// public LDAPUtils() -// { -// _nativeMethods = new NativeMethods(); -// _portScanner = new PortScanner(); -// _log = Logging.LogProvider.CreateLogger("LDAPUtils"); -// } -// -// /// -// /// Creates a new instance of LDAP utils and allows overriding implementations -// /// -// /// -// /// -// /// -// public LDAPUtils(NativeMethods nativeMethods = null, PortScanner scanner = null, ILogger log = null) -// { -// _nativeMethods = nativeMethods ?? new NativeMethods(); -// _portScanner = scanner ?? new PortScanner(); -// _log = log ?? Logging.LogProvider.CreateLogger("LDAPUtils"); -// } -// -// /// -// /// Sets the configuration for LDAP queries -// /// -// /// -// /// -// public void SetLDAPConfig(LDAPConfig config) -// { -// _ldapConfig = config ?? throw new ArgumentNullException(nameof(config), "LDAP Configuration can not be null"); -// //Close out any existing LDAP connections to request a new incoming config -// foreach (var kv in _ldapConnections) -// { -// kv.Value.Connection.Dispose(); -// } -// -// _ldapConnections.Clear(); -// } -// -// /// -// /// Turns a sid into a well known principal ID. -// /// -// /// -// /// -// /// -// /// True if a well known principal was identified, false if not -// public bool GetWellKnownPrincipal(string sid, string domain, out TypedPrincipal commonPrincipal) -// { -// if (!WellKnownPrincipal.GetWellKnownPrincipal(sid, out commonPrincipal)) return false; -// var tempDomain = domain ?? GetDomain()?.Name ?? "UNKNOWN"; -// commonPrincipal.ObjectIdentifier = ConvertWellKnownPrincipal(sid, tempDomain); -// SeenWellKnownPrincipals.TryAdd(commonPrincipal.ObjectIdentifier, new ResolvedWellKnownPrincipal -// { -// DomainName = domain, -// WkpId = sid -// }); -// return true; -// } -// -// public bool ConvertLocalWellKnownPrincipal(SecurityIdentifier sid, string computerDomainSid, -// string computerDomain, out TypedPrincipal principal) -// { -// if (WellKnownPrincipal.GetWellKnownPrincipal(sid.Value, out var common)) -// { -// //The everyone and auth users principals are special and will be converted to the domain equivalent -// if (sid.Value is "S-1-1-0" or "S-1-5-11") -// { -// GetWellKnownPrincipal(sid.Value, computerDomain, out principal); -// return true; -// } -// -// //Use the computer object id + the RID of the sid we looked up to create our new principal -// principal = new TypedPrincipal -// { -// ObjectIdentifier = $"{computerDomainSid}-{sid.Rid()}", -// ObjectType = common.ObjectType switch -// { -// Label.User => Label.LocalUser, -// Label.Group => Label.LocalGroup, -// _ => common.ObjectType -// } -// }; -// -// return true; -// } -// -// principal = null; -// return false; -// } -// -// /// -// /// Adds a SID to an internal list of domain controllers -// /// -// /// -// public void AddDomainController(string domainControllerSID) -// { -// DomainControllers.TryAdd(domainControllerSID, new byte()); -// } -// -// /// -// /// Gets output objects for currently observed well known principals -// /// -// /// -// /// -// public IEnumerable GetWellKnownPrincipalOutput(string domain) -// { -// foreach (var wkp in SeenWellKnownPrincipals) -// { -// WellKnownPrincipal.GetWellKnownPrincipal(wkp.Value.WkpId, out var principal); -// OutputBase output = principal.ObjectType switch -// { -// Label.User => new User(), -// Label.Computer => new Computer(), -// Label.Group => new Group(), -// Label.GPO => new GPO(), -// Label.Domain => new OutputTypes.Domain(), -// Label.OU => new OU(), -// Label.Container => new Container(), -// Label.Configuration => new Container(), -// _ => throw new ArgumentOutOfRangeException() -// }; -// -// output.Properties.Add("name", $"{principal.ObjectIdentifier}@{wkp.Value.DomainName}".ToUpper()); -// var domainSid = GetSidFromDomainName(wkp.Value.DomainName); -// output.Properties.Add("domainsid", domainSid); -// output.Properties.Add("domain", wkp.Value.DomainName.ToUpper()); -// output.ObjectIdentifier = wkp.Key; -// yield return output; -// } -// -// var entdc = GetBaseEnterpriseDC(domain); -// entdc.Members = DomainControllers.Select(x => new TypedPrincipal(x.Key, Label.Computer)).ToArray(); -// yield return entdc; -// } -// -// /// -// /// Converts a -// /// -// /// -// /// -// /// -// public string ConvertWellKnownPrincipal(string sid, string domain) -// { -// if (!WellKnownPrincipal.GetWellKnownPrincipal(sid, out _)) return sid; -// -// if (sid != "S-1-5-9") return $"{domain}-{sid}".ToUpper(); -// -// var forest = GetForest(domain)?.Name; -// if (forest == null) _log.LogWarning("Error getting forest, ENTDC sid is likely incorrect"); -// return $"{forest ?? "UNKNOWN"}-{sid}".ToUpper(); -// } -// -// /// -// /// Queries the global catalog to get potential SID matches for a username in the forest -// /// -// /// -// /// -// public string[] GetUserGlobalCatalogMatches(string name) -// { -// var tempName = name.ToLower(); -// if (Cache.GetGCCache(tempName, out var sids)) -// return sids; -// -// var query = new LDAPFilter().AddUsers($"samaccountname={tempName}").GetFilter(); -// var results = QueryLDAP(query, SearchScope.Subtree, new[] { "objectsid" }, globalCatalog: true) -// .Select(x => x.GetSid()).Where(x => x != null).ToArray(); -// Cache.AddGCCache(tempName, results); -// return results; -// } -// -// /// -// /// Uses an LDAP lookup to attempt to find the Label for a given SID -// /// Will also convert to a well known principal ID if needed -// /// -// /// -// /// -// /// -// public TypedPrincipal ResolveIDAndType(string id, string fallbackDomain) -// { -// //This is a duplicated SID object which is weird and makes things unhappy. Throw it out -// if (id.Contains("0ACNF")) -// return null; -// -// if (GetWellKnownPrincipal(id, fallbackDomain, out var principal)) -// return principal; -// -// var type = id.StartsWith("S-") ? LookupSidType(id, fallbackDomain) : LookupGuidType(id, fallbackDomain); -// return new TypedPrincipal(id, type); -// } -// -// public TypedPrincipal ResolveCertTemplateByProperty(string propValue, string propertyName, string containerDN, -// string domainName) -// { -// var filter = new LDAPFilter().AddCertificateTemplates().AddFilter(propertyName + "=" + propValue, true); -// var res = QueryLDAP(filter.GetFilter(), SearchScope.OneLevel, -// CommonProperties.TypeResolutionProps, adsPath: containerDN, domainName: domainName); -// -// if (res == null) -// { -// _log.LogWarning( -// "Could not find certificate template with '{propertyName}:{propValue}' under {containerDN}; null result", -// propertyName, propValue, containerDN); -// return null; -// } -// -// List resList = new List(res); -// if (resList.Count == 0) -// { -// _log.LogWarning( -// "Could not find certificate template with '{propertyName}:{propValue}' under {containerDN}; empty list", -// propertyName, propValue, containerDN); -// return null; -// } -// -// if (resList.Count > 1) -// { -// _log.LogWarning( -// "Found more than one certificate template with '{propertyName}:{propValue}' under {containerDN}", -// propertyName, propValue, containerDN); -// return null; -// } -// -// ISearchResultEntry searchResultEntry = resList.FirstOrDefault(); -// return new TypedPrincipal(searchResultEntry.GetGuid(), Label.CertTemplate); -// } -// -// /// -// /// Attempts to lookup the Label for a sid -// /// -// /// -// /// -// /// -// public Label LookupSidType(string sid, string domain) -// { -// if (Cache.GetIDType(sid, out var type)) -// return type; -// -// var rDomain = GetDomainNameFromSid(sid) ?? domain; -// -// var result = -// QueryLDAP(CommonFilters.SpecificSID(sid), SearchScope.Subtree, CommonProperties.TypeResolutionProps, -// rDomain) -// .DefaultIfEmpty(null).FirstOrDefault(); -// -// type = result?.GetLabel() ?? Label.Base; -// Cache.AddType(sid, type); -// return type; -// } -// -// /// -// /// Attempts to lookup the Label for a GUID -// /// -// /// -// /// -// /// -// public Label LookupGuidType(string guid, string domain) -// { -// if (Cache.GetIDType(guid, out var type)) -// return type; -// -// var hex = Helpers.ConvertGuidToHexGuid(guid); -// if (hex == null) -// return Label.Base; -// -// var result = -// QueryLDAP($"(objectguid={hex})", SearchScope.Subtree, CommonProperties.TypeResolutionProps, domain) -// .DefaultIfEmpty(null).FirstOrDefault(); -// -// type = result?.GetLabel() ?? Label.Base; -// Cache.AddType(guid, type); -// return type; -// } -// -// /// -// /// Attempts to find the domain associated with a SID -// /// -// /// -// /// -// public string GetDomainNameFromSid(string sid) -// { -// try -// { -// var parsedSid = new SecurityIdentifier(sid); -// var domainSid = parsedSid.AccountDomainSid?.Value.ToUpper(); -// if (domainSid == null) -// return null; -// -// _log.LogDebug("Resolving sid {DomainSid}", domainSid); -// -// if (Cache.GetDomainSidMapping(domainSid, out var domain)) -// return domain; -// -// _log.LogDebug("No cache hit for {DomainSid}", domainSid); -// domain = GetDomainNameFromSidLdap(domainSid); -// _log.LogDebug("Resolved to {Domain}", domain); -// -// //Cache both to and from so we can use this later -// if (domain != null) -// { -// Cache.AddDomainSidMapping(domainSid, domain); -// Cache.AddDomainSidMapping(domain, domainSid); -// } -// -// return domain; -// } -// catch -// { -// return null; -// } -// } -// -// /// -// /// Attempts to get the SID associated with a domain name -// /// -// /// -// /// -// public string GetSidFromDomainName(string domainName) -// { -// var tempDomainName = NormalizeDomainName(domainName); -// if (tempDomainName == null) -// return null; -// if (Cache.GetDomainSidMapping(tempDomainName, out var sid)) return sid; -// -// var domainObj = GetDomain(tempDomainName); -// -// if (domainObj != null) -// sid = domainObj.GetDirectoryEntry().GetSid(); -// else -// sid = null; -// -// if (sid != null) -// { -// Cache.AddDomainSidMapping(sid, tempDomainName); -// Cache.AddDomainSidMapping(tempDomainName, sid); -// if (tempDomainName != domainName) -// { -// Cache.AddDomainSidMapping(domainName, sid); -// } -// } -// -// return sid; -// } -// -// // Saving this code for an eventual async implementation -// // public async IAsyncEnumerable DoRangedRetrievalAsync(string distinguishedName, string attributeName) -// // { -// // var domainName = Helpers.DistinguishedNameToDomain(distinguishedName); -// // LdapConnection conn; -// // try -// // { -// // conn = await CreateLDAPConnection(domainName, authType: _ldapConfig.AuthType); -// // } -// // catch -// // { -// // yield break; -// // } -// // -// // if (conn == null) -// // yield break; -// // -// // var index = 0; -// // var step = 0; -// // var currentRange = $"{attributeName};range={index}-*"; -// // var complete = false; -// // -// // var searchRequest = CreateSearchRequest($"{attributeName}=*", SearchScope.Base, new[] {currentRange}, -// // domainName, distinguishedName); -// // -// // var backoffDelay = MinBackoffDelay; -// // var retryCount = 0; -// // -// // while (true) -// // { -// // DirectoryResponse searchResult; -// // try -// // { -// // searchResult = await Task.Factory.FromAsync(conn.BeginSendRequest, conn.EndSendRequest, -// // searchRequest, -// // PartialResultProcessing.NoPartialResultSupport, null); -// // } -// // catch (LdapException le) when (le.ErrorCode == 51 && retryCount < MaxRetries) -// // { -// // //Allow three retries with a backoff on each one if we get a "Server is Busy" error -// // retryCount++; -// // await Task.Delay(backoffDelay); -// // backoffDelay = TimeSpan.FromSeconds(Math.Min( -// // backoffDelay.TotalSeconds * BackoffDelayMultiplier.TotalSeconds, MaxBackoffDelay.TotalSeconds)); -// // continue; -// // } -// // catch (Exception e) -// // { -// // _log.LogWarning(e,"Caught exception during ranged retrieval for {DN}", distinguishedName); -// // yield break; -// // } -// // -// // if (searchResult is SearchResponse response && response.Entries.Count == 1) -// // { -// // var entry = response.Entries[0]; -// // var attributeNames = entry?.Attributes?.AttributeNames; -// // if (attributeNames != null) -// // { -// // foreach (string attr in attributeNames) -// // { -// // //Set our current range to the name of the attribute, which will tell us how far we are in "paging" -// // currentRange = attr; -// // //Check if the string has the * character in it. If it does, we've reached the end of this search -// // complete = currentRange.IndexOf("*", 0, StringComparison.Ordinal) > 0; -// // //Set our step to the number of attributes that came back. -// // step = entry.Attributes[currentRange].Count; -// // } -// // } -// // -// // -// // foreach (string val in entry.Attributes[currentRange].GetValues(typeof(string))) -// // { -// // yield return val; -// // index++; -// // } -// // -// // if (complete) yield break; -// // -// // currentRange = $"{attributeName};range={index}-{index + step}"; -// // searchRequest.Attributes.Clear(); -// // searchRequest.Attributes.Add(currentRange); -// // } -// // else -// // { -// // yield break; -// // } -// // } -// // } -// -// /// -// /// Performs Attribute Ranged Retrieval -// /// https://docs.microsoft.com/en-us/windows/win32/adsi/attribute-range-retrieval -// /// The function self-determines the range and internally handles the maximum step allowed by the server -// /// -// /// -// /// -// /// -// public IEnumerable DoRangedRetrieval(string distinguishedName, string attributeName) -// { -// var domainName = Helpers.DistinguishedNameToDomain(distinguishedName); -// var task = Task.Run(() => CreateLDAPConnectionWrapper(domainName, authType: _ldapConfig.AuthType)); -// -// LdapConnectionWrapper connWrapper; -// -// try -// { -// connWrapper = task.ConfigureAwait(false).GetAwaiter().GetResult(); -// } -// catch -// { -// yield break; -// } -// -// if (connWrapper.Connection == null) -// yield break; -// -// var conn = connWrapper.Connection; -// -// var index = 0; -// var step = 0; -// var baseString = $"{attributeName}"; -// //Example search string: member;range=0-1000 -// var currentRange = $"{baseString};range={index}-*"; -// var complete = false; -// -// var searchRequest = CreateSearchRequest($"{attributeName}=*", SearchScope.Base, new[] { currentRange }, -// connWrapper.DomainInfo, distinguishedName); -// -// if (searchRequest == null) -// yield break; -// -// var backoffDelay = MinBackoffDelay; -// var retryCount = 0; -// -// while (true) -// { -// SearchResponse response; -// try -// { -// response = (SearchResponse)conn.SendRequest(searchRequest); -// } -// catch (LdapException le) when (le.ErrorCode == (int)LdapErrorCodes.Busy && retryCount < MaxRetries) -// { -// //Allow three retries with a backoff on each one if we get a "Server is Busy" error -// retryCount++; -// Thread.Sleep(backoffDelay); -// backoffDelay = GetNextBackoff(retryCount); -// continue; -// } -// catch (Exception e) -// { -// _log.LogError(e, "Error doing ranged retrieval for {Attribute} on {Dn}", attributeName, -// distinguishedName); -// yield break; -// } -// -// //If we ever get more than one response from here, something is horribly wrong -// if (response?.Entries.Count == 1) -// { -// var entry = response.Entries[0]; -// //Process the attribute we get back to determine a few things -// foreach (string attr in entry.Attributes.AttributeNames) -// { -// //Set our current range to the name of the attribute, which will tell us how far we are in "paging" -// currentRange = attr; -// //Check if the string has the * character in it. If it does, we've reached the end of this search -// complete = currentRange.IndexOf("*", 0, StringComparison.Ordinal) > 0; -// //Set our step to the number of attributes that came back. -// step = entry.Attributes[currentRange].Count; -// } -// -// foreach (string val in entry.Attributes[currentRange].GetValues(typeof(string))) -// { -// yield return val; -// index++; -// } -// -// if (complete) yield break; -// -// currentRange = $"{baseString};range={index}-{index + step}"; -// searchRequest.Attributes.Clear(); -// searchRequest.Attributes.Add(currentRange); -// } -// else -// { -// //Something went wrong here. -// yield break; -// } -// } -// } -// -// /// -// /// Takes a host in most applicable forms from AD and attempts to resolve it into a SID. -// /// -// /// -// /// -// /// -// public async Task ResolveHostToSid(string hostname, string domain) -// { -// var strippedHost = Helpers.StripServicePrincipalName(hostname).ToUpper().TrimEnd('$'); -// if (string.IsNullOrEmpty(strippedHost)) -// { -// return null; -// } -// -// if (_hostResolutionMap.TryGetValue(strippedHost, out var sid)) return sid; -// -// var normalDomain = NormalizeDomainName(domain); -// -// string tempName; -// string tempDomain = null; -// -// //Step 1: Handle non-IP address values -// if (!IPAddress.TryParse(strippedHost, out _)) -// { -// // Format: ABC.TESTLAB.LOCAL -// if (strippedHost.Contains(".")) -// { -// var split = strippedHost.Split('.'); -// tempName = split[0]; -// tempDomain = string.Join(".", split.Skip(1).ToArray()); -// } -// // Format: WINDOWS -// else -// { -// tempName = strippedHost; -// tempDomain = normalDomain; -// } -// -// // Add $ to the end of the name to match how computers are stored in AD -// tempName = $"{tempName}$".ToUpper(); -// var principal = ResolveAccountName(tempName, tempDomain); -// sid = principal?.ObjectIdentifier; -// if (sid != null) -// { -// _hostResolutionMap.TryAdd(strippedHost, sid); -// return sid; -// } -// } -// -// //Step 2: Try NetWkstaGetInfo -// //Next we'll try calling NetWkstaGetInfo in hopes of getting the NETBIOS name directly from the computer -// //We'll use the hostname that we started with instead of the one from our previous step -// var workstationInfo = await GetWorkstationInfo(strippedHost); -// if (workstationInfo.HasValue) -// { -// tempName = workstationInfo.Value.ComputerName; -// tempDomain = workstationInfo.Value.LanGroup; -// -// if (string.IsNullOrEmpty(tempDomain)) -// tempDomain = normalDomain; -// -// if (!string.IsNullOrEmpty(tempName)) -// { -// //Append the $ to indicate this is a computer -// tempName = $"{tempName}$".ToUpper(); -// var principal = ResolveAccountName(tempName, tempDomain); -// sid = principal?.ObjectIdentifier; -// if (sid != null) -// { -// _hostResolutionMap.TryAdd(strippedHost, sid); -// return sid; -// } -// } -// } -// -// //Step 3: Socket magic -// // Attempt to request the NETBIOS name of the computer directly -// if (RequestNETBIOSNameFromComputer(strippedHost, normalDomain, out tempName)) -// { -// tempDomain ??= normalDomain; -// tempName = $"{tempName}$".ToUpper(); -// -// var principal = ResolveAccountName(tempName, tempDomain); -// sid = principal?.ObjectIdentifier; -// if (sid != null) -// { -// _hostResolutionMap.TryAdd(strippedHost, sid); -// return sid; -// } -// } -// -// //Try DNS resolution next -// string resolvedHostname; -// try -// { -// resolvedHostname = (await Dns.GetHostEntryAsync(strippedHost)).HostName; -// } -// catch -// { -// resolvedHostname = null; -// } -// -// if (resolvedHostname != null) -// { -// var splitName = resolvedHostname.Split('.'); -// tempName = $"{splitName[0]}$".ToUpper(); -// tempDomain = string.Join(".", splitName.Skip(1)); -// -// var principal = ResolveAccountName(tempName, tempDomain); -// sid = principal?.ObjectIdentifier; -// if (sid != null) -// { -// _hostResolutionMap.TryAdd(strippedHost, sid); -// return sid; -// } -// } -// -// //If we get here, everything has failed, and life is very sad. -// tempName = strippedHost; -// tempDomain = normalDomain; -// -// if (tempName.Contains(".")) -// { -// _hostResolutionMap.TryAdd(strippedHost, tempName); -// return tempName; -// } -// -// tempName = $"{tempName}.{tempDomain}"; -// _hostResolutionMap.TryAdd(strippedHost, tempName); -// return tempName; -// } -// -// /// -// /// Attempts to convert a bare account name (usually from session enumeration) to its corresponding ID and object type -// /// -// /// -// /// -// /// -// public TypedPrincipal ResolveAccountName(string name, string domain) -// { -// if (string.IsNullOrWhiteSpace(name)) -// return null; -// -// if (Cache.GetPrefixedValue(name, domain, out var id) && Cache.GetIDType(id, out var type)) -// return new TypedPrincipal -// { -// ObjectIdentifier = id, -// ObjectType = type -// }; -// -// var d = NormalizeDomainName(domain); -// var result = QueryLDAP($"(samaccountname={name})", SearchScope.Subtree, -// CommonProperties.TypeResolutionProps, -// d).DefaultIfEmpty(null).FirstOrDefault(); -// -// if (result == null) -// { -// _log.LogDebug("ResolveAccountName - unable to get result for {Name}", name); -// return null; -// } -// -// type = result.GetLabel(); -// id = result.GetObjectIdentifier(); -// -// if (id == null) -// { -// _log.LogDebug("ResolveAccountName - could not retrieve ID on {DN} for {Name}", result.DistinguishedName, -// name); -// return null; -// } -// -// Cache.AddPrefixedValue(name, domain, id); -// Cache.AddType(id, type); -// -// id = ConvertWellKnownPrincipal(id, domain); -// -// return new TypedPrincipal -// { -// ObjectIdentifier = id, -// ObjectType = type -// }; -// } -// -// /// -// /// Attempts to convert a distinguishedname to its corresponding ID and object type. -// /// -// /// DistinguishedName -// /// A TypedPrincipal object with the SID and Label -// public TypedPrincipal ResolveDistinguishedName(string dn) -// { -// if (Cache.GetConvertedValue(dn, out var id) && Cache.GetIDType(id, out var type)) -// return new TypedPrincipal -// { -// ObjectIdentifier = id, -// ObjectType = type -// }; -// -// var domain = Helpers.DistinguishedNameToDomain(dn); -// var result = QueryLDAP("(objectclass=*)", SearchScope.Base, CommonProperties.TypeResolutionProps, domain, -// adsPath: dn) -// .DefaultIfEmpty(null).FirstOrDefault(); -// -// if (result == null) -// { -// _log.LogDebug("ResolveDistinguishedName - No result for {DN}", dn); -// return null; -// } -// -// id = result.GetObjectIdentifier(); -// if (id == null) -// { -// _log.LogDebug("ResolveDistinguishedName - could not retrieve object identifier from {DN}", dn); -// return null; -// } -// -// if (GetWellKnownPrincipal(id, domain, out var principal)) return principal; -// -// type = result.GetLabel(); -// -// Cache.AddConvertedValue(dn, id); -// Cache.AddType(id, type); -// -// id = ConvertWellKnownPrincipal(id, domain); -// -// return new TypedPrincipal -// { -// ObjectIdentifier = id, -// ObjectType = type -// }; -// } -// -// /// -// /// Queries LDAP using LDAPQueryOptions -// /// -// /// -// /// -// public IEnumerable QueryLDAP(LDAPQueryOptions options) -// { -// return QueryLDAP( -// options.Filter, -// options.Scope, -// options.Properties, -// options.CancellationToken, -// options.DomainName, -// options.IncludeAcl, -// options.ShowDeleted, -// options.AdsPath, -// options.GlobalCatalog, -// options.SkipCache, -// options.ThrowException -// ); -// } -// -// /// -// /// Performs an LDAP query using the parameters specified by the user. -// /// -// /// LDAP filter -// /// SearchScope to query -// /// LDAP properties to fetch for each object -// /// Cancellation Token -// /// Include the DACL and Owner values in the NTSecurityDescriptor -// /// Include deleted objects -// /// Domain to query -// /// ADS path to limit the query too -// /// Use the global catalog instead of the regular LDAP server -// /// -// /// Skip the connection cache and force a new connection. You must dispose of this connection -// /// yourself. -// /// -// /// Throw exceptions rather than logging the errors directly -// /// All LDAP search results matching the specified parameters -// /// -// /// Thrown when an error occurs during LDAP query (only when throwException = true) -// /// -// public IEnumerable QueryLDAP(string ldapFilter, SearchScope scope, -// string[] props, CancellationToken cancellationToken, string domainName = null, bool includeAcl = false, -// bool showDeleted = false, string adsPath = null, bool globalCatalog = false, bool skipCache = false, -// bool throwException = false) -// { -// var queryParams = SetupLDAPQueryFilter( -// ldapFilter, scope, props, includeAcl, domainName, includeAcl, adsPath, globalCatalog, skipCache); -// -// if (queryParams.Exception != null) -// { -// _log.LogWarning("Failed to setup LDAP Query Filter: {Message}", queryParams.Exception.Message); -// if (throwException) -// throw new LDAPQueryException("Failed to setup LDAP Query Filter", queryParams.Exception); -// yield break; -// } -// -// var conn = queryParams.Connection; -// var request = queryParams.SearchRequest; -// var pageControl = queryParams.PageControl; -// -// PageResultResponseControl pageResponse = null; -// var backoffDelay = MinBackoffDelay; -// var retryCount = 0; -// while (true) -// { -// if (cancellationToken.IsCancellationRequested) -// yield break; -// -// SearchResponse response; -// try -// { -// _log.LogTrace("Sending LDAP request for {Filter}", ldapFilter); -// response = (SearchResponse)conn.SendRequest(request); -// if (response != null) -// pageResponse = (PageResultResponseControl)response.Controls -// .Where(x => x is PageResultResponseControl).DefaultIfEmpty(null).FirstOrDefault(); -// } -// catch (LdapException le) when (le.ErrorCode == (int)LdapErrorCodes.ServerDown && -// retryCount < MaxRetries) -// { -// /*A ServerDown exception indicates that our connection is no longer valid for one of many reasons. -// However, this function is generally called by multiple threads, so we need to be careful in recreating -// the connection. Using a semaphore, we can ensure that only one thread is actually recreating the connection -// while the other threads that hit the ServerDown exception simply wait. The initial caller will hold the semaphore -// and do a backoff delay before trying to make a new connection which will replace the existing connection in the -// _ldapConnections cache. Other threads will retrieve the new connection from the cache instead of making a new one -// This minimizes overhead of new connections while still fixing our core problem.*/ -// -// //Always increment retry count -// retryCount++; -// -// //Attempt to acquire a lock -// if (Monitor.TryEnter(_lockObj)) -// { -// //If we've acquired the lock, we want to immediately signal our reset event so everyone else waits -// _connectionResetEvent.Reset(); -// try -// { -// //Sleep for our backoff -// Thread.Sleep(backoffDelay); -// //Explicitly skip the cache so we don't get the same connection back -// conn = CreateNewConnection(domainName, globalCatalog, true).Connection; -// if (conn == null) -// { -// _log.LogError( -// "Unable to create replacement ldap connection for ServerDown exception. Breaking loop"); -// yield break; -// } -// -// _log.LogInformation("Created new LDAP connection after receiving ServerDown from server"); -// } -// finally -// { -// //Reset our event + release the lock -// _connectionResetEvent.Set(); -// Monitor.Exit(_lockObj); -// } -// } -// else -// { -// //If someone else is holding the reset event, we want to just wait and then pull the newly created connection out of the cache -// //This event will be released after the first entrant thread is done making a new connection -// //The thread.sleep is to prevent a potential, very unlikely race -// Thread.Sleep(50); -// _connectionResetEvent.WaitOne(); -// conn = CreateNewConnection(domainName, globalCatalog).Connection; -// } -// -// backoffDelay = GetNextBackoff(retryCount); -// continue; -// } -// catch (LdapException le) when (le.ErrorCode == (int)LdapErrorCodes.Busy && retryCount < MaxRetries) -// { -// retryCount++; -// backoffDelay = GetNextBackoff(retryCount); -// continue; -// } -// catch (LdapException le) -// { -// if (le.ErrorCode != (int)LdapErrorCodes.LocalError) -// { -// if (throwException) -// { -// throw new LDAPQueryException( -// $"LDAP Exception in Loop: {le.ErrorCode}. {le.ServerErrorMessage}. {le.Message}. Filter: {ldapFilter}. Domain: {domainName}", -// le); -// } -// -// _log.LogWarning(le, -// "LDAP Exception in Loop: {ErrorCode}. {ServerErrorMessage}. {Message}. Filter: {Filter}. Domain: {Domain}", -// le.ErrorCode, le.ServerErrorMessage, le.Message, ldapFilter, domainName); -// } -// -// yield break; -// } -// catch (Exception e) -// { -// _log.LogWarning(e, "Exception in LDAP loop for {Filter} and {Domain}", ldapFilter, domainName); -// if (throwException) -// throw new LDAPQueryException($"Exception in LDAP loop for {ldapFilter} and {domainName}", e); -// -// yield break; -// } -// -// if (cancellationToken.IsCancellationRequested) -// yield break; -// -// if (response == null || pageResponse == null) -// continue; -// -// foreach (SearchResultEntry entry in response.Entries) -// { -// if (cancellationToken.IsCancellationRequested) -// yield break; -// -// yield return new SearchResultEntryWrapper(entry, this); -// } -// -// if (pageResponse.Cookie.Length == 0 || response.Entries.Count == 0 || -// cancellationToken.IsCancellationRequested) -// yield break; -// -// pageControl.Cookie = pageResponse.Cookie; -// } -// } -// -// private LdapConnectionWrapper CreateNewConnection(string domainName = null, bool globalCatalog = false, -// bool skipCache = false) -// { -// var task = Task.Run(() => CreateLDAPConnectionWrapper(domainName, skipCache, _ldapConfig.AuthType, globalCatalog)); -// -// try -// { -// return task.ConfigureAwait(false).GetAwaiter().GetResult(); -// } -// catch -// { -// return null; -// } -// } -// -// /// -// /// Performs an LDAP query using the parameters specified by the user. -// /// -// /// LDAP filter -// /// SearchScope to query -// /// LDAP properties to fetch for each object -// /// Include the DACL and Owner values in the NTSecurityDescriptor -// /// Include deleted objects -// /// Domain to query -// /// ADS path to limit the query too -// /// Use the global catalog instead of the regular LDAP server -// /// -// /// Skip the connection cache and force a new connection. You must dispose of this connection -// /// yourself. -// /// -// /// Throw exceptions rather than logging the errors directly -// /// All LDAP search results matching the specified parameters -// /// -// /// Thrown when an error occurs during LDAP query (only when throwException = true) -// /// -// public virtual IEnumerable QueryLDAP(string ldapFilter, SearchScope scope, -// string[] props, string domainName = null, bool includeAcl = false, bool showDeleted = false, -// string adsPath = null, bool globalCatalog = false, bool skipCache = false, bool throwException = false) -// { -// return QueryLDAP(ldapFilter, scope, props, new CancellationToken(), domainName, includeAcl, showDeleted, -// adsPath, globalCatalog, skipCache, throwException); -// } -// -// private static TimeSpan GetNextBackoff(int retryCount) -// { -// return TimeSpan.FromSeconds(Math.Min( -// MinBackoffDelay.TotalSeconds * Math.Pow(BackoffDelayMultiplier, retryCount), -// MaxBackoffDelay.TotalSeconds)); -// } -// -// /// -// /// Gets the forest associated with a domain. -// /// If no domain is provided, defaults to current domain -// /// -// /// -// /// -// public virtual Forest GetForest(string domainName = null) -// { -// try -// { -// if (domainName == null && _ldapConfig.Username == null) -// return Forest.GetCurrentForest(); -// -// var domain = GetDomain(domainName); -// return domain?.Forest; -// } -// catch -// { -// return null; -// } -// } -// -// /// -// /// Creates a new ActiveDirectorySecurityDescriptor -// /// Function created for testing purposes -// /// -// /// -// public ActiveDirectorySecurityDescriptor MakeSecurityDescriptor() -// { -// return new ActiveDirectorySecurityDescriptor(new ActiveDirectorySecurity()); -// } -// -// public string BuildLdapPath(string dnPath, string domainName) -// { -// //Check our cached info for a fast check -// if (CachedDomainInfo.TryGetValue(domainName, out var info)) -// { -// return $"{dnPath},{info.DomainSearchBase}"; -// } -// var domain = GetDomain(domainName)?.Name; -// if (domain == null) -// return null; -// -// var adPath = $"{dnPath},DC={domain.Replace(".", ",DC=")}"; -// return adPath; -// } -// -// /// -// /// Tests the current LDAP config to ensure its valid by pulling a domain object -// /// -// /// True if connection was successful, else false -// public bool TestLDAPConfig(string domain) -// { -// var filter = new LDAPFilter(); -// filter.AddDomains(); -// -// _log.LogTrace("Testing LDAP connection for domain {Domain}", domain); -// var result = QueryLDAP(filter.GetFilter(), SearchScope.Subtree, CommonProperties.ObjectID, domain, -// throwException: true) -// .DefaultIfEmpty(null).FirstOrDefault(); -// _log.LogTrace("Result object from LDAP connection test is {DN}", result?.DistinguishedName ?? "null"); -// return result != null; -// } -// -// /// -// /// Gets the domain object associated with the specified domain name. -// /// Defaults to current domain if none specified -// /// -// /// -// /// -// public virtual Domain GetDomain(string domainName = null) -// { -// var cacheKey = domainName ?? NullCacheKey; -// if (_domainCache.TryGetValue(cacheKey, out var domain)) return domain; -// -// try -// { -// DirectoryContext context; -// if (_ldapConfig.Username != null) -// context = domainName != null -// ? new DirectoryContext(DirectoryContextType.Domain, domainName, _ldapConfig.Username, -// _ldapConfig.Password) -// : new DirectoryContext(DirectoryContextType.Domain, _ldapConfig.Username, -// _ldapConfig.Password); -// else -// context = domainName != null -// ? new DirectoryContext(DirectoryContextType.Domain, domainName) -// : new DirectoryContext(DirectoryContextType.Domain); -// -// domain = Domain.GetDomain(context); -// } -// catch (Exception e) -// { -// _log.LogDebug(e, "GetDomain call failed at {StackTrace}", new StackFrame()); -// domain = null; -// } -// -// _domainCache.TryAdd(cacheKey, domain); -// return domain; -// } -// -// /// -// /// Setup LDAP query for filter -// /// -// /// LDAP filter -// /// SearchScope to query -// /// LDAP properties to fetch for each object -// /// Include the DACL and Owner values in the NTSecurityDescriptor -// /// Domain to query -// /// Include deleted objects -// /// ADS path to limit the query too -// /// Use the global catalog instead of the regular LDAP server -// /// -// /// Skip the connection cache and force a new connection. You must dispose of this connection -// /// yourself. -// /// -// /// Tuple of LdapConnection, SearchRequest, PageResultRequestControl and LDAPQueryException -// // ReSharper disable once MemberCanBePrivate.Global -// internal LDAPQueryParams SetupLDAPQueryFilter( -// string ldapFilter, -// SearchScope scope, string[] props, bool includeAcl = false, string domainName = null, -// bool showDeleted = false, -// string adsPath = null, bool globalCatalog = false, bool skipCache = false) -// { -// _log.LogTrace("Creating ldap connection for {Target} with filter {Filter}", -// globalCatalog ? "Global Catalog" : "DC", ldapFilter); -// var task = Task.Run(() => CreateLDAPConnectionWrapper(domainName, skipCache, _ldapConfig.AuthType, globalCatalog)); -// -// var queryParams = new LDAPQueryParams(); -// -// LdapConnectionWrapper connWrapper; -// try -// { -// connWrapper = task.ConfigureAwait(false).GetAwaiter().GetResult(); -// } -// catch (NoLdapDataException) -// { -// var errorString = -// $"Successfully connected via LDAP to {domainName ?? "Default Domain"} but no data received. This is most likely due to permissions or using kerberos authentication across trusts."; -// queryParams.Exception = new LDAPQueryException(errorString, null); -// return queryParams; -// } -// catch (LdapAuthenticationException e) -// { -// var errorString = -// $"Failed to connect via LDAP to {domainName ?? "Default Domain"}: Authentication is invalid"; -// queryParams.Exception = new LDAPQueryException(errorString, e.InnerException); -// return queryParams; -// } -// catch (LdapConnectionException e) -// { -// var errorString = -// $"Failed to connect via LDAP to {domainName ?? "Default Domain"}: {e.InnerException.Message} (Code: {e.ErrorCode}"; -// queryParams.Exception = new LDAPQueryException(errorString, e.InnerException); -// return queryParams; -// } -// -// var conn = connWrapper.Connection; -// -// //If we get a null connection, something went wrong, but we don't have an error to go with it for whatever reason -// if (conn == null) -// { -// var errorString = -// $"LDAP connection is null for filter {ldapFilter} and domain {domainName ?? "Default Domain"}"; -// queryParams.Exception = new LDAPQueryException(errorString); -// return queryParams; -// } -// -// SearchRequest request; -// -// try -// { -// request = CreateSearchRequest(ldapFilter, scope, props, connWrapper.DomainInfo, adsPath, showDeleted); -// } -// catch (LDAPQueryException ldapQueryException) -// { -// queryParams.Exception = ldapQueryException; -// return queryParams; -// } -// -// if (request == null) -// { -// var errorString = -// $"Search request is null for filter {ldapFilter} and domain {domainName ?? "Default Domain"}"; -// queryParams.Exception = new LDAPQueryException(errorString); -// return queryParams; -// } -// -// var pageControl = new PageResultRequestControl(500); -// request.Controls.Add(pageControl); -// -// if (includeAcl) -// request.Controls.Add(new SecurityDescriptorFlagControl -// { -// SecurityMasks = SecurityMasks.Dacl | SecurityMasks.Owner -// }); -// -// queryParams.Connection = conn; -// queryParams.SearchRequest = request; -// queryParams.PageControl = pageControl; -// -// return queryParams; -// } -// -// private Group GetBaseEnterpriseDC(string domain) -// { -// var forest = GetForest(domain)?.Name; -// if (forest == null) _log.LogWarning("Error getting forest, ENTDC sid is likely incorrect"); -// var g = new Group { ObjectIdentifier = $"{forest}-S-1-5-9".ToUpper() }; -// g.Properties.Add("name", $"ENTERPRISE DOMAIN CONTROLLERS@{forest ?? "UNKNOWN"}".ToUpper()); -// g.Properties.Add("domainsid", GetSidFromDomainName(forest)); -// g.Properties.Add("domain", forest); -// return g; -// } -// -// /// -// /// Updates the config for querying LDAP -// /// -// /// -// public void UpdateLDAPConfig(LDAPConfig config) -// { -// _ldapConfig = config; -// } -// -// private string GetDomainNameFromSidLdap(string sid) -// { -// var hexSid = Helpers.ConvertSidToHexSid(sid); -// -// if (hexSid == null) -// return null; -// -// //Search using objectsid first -// var result = -// QueryLDAP($"(&(objectclass=domain)(objectsid={hexSid}))", SearchScope.Subtree, -// new[] { "distinguishedname" }, globalCatalog: true).DefaultIfEmpty(null).FirstOrDefault(); -// -// if (result != null) -// { -// var domainName = Helpers.DistinguishedNameToDomain(result.DistinguishedName); -// return domainName; -// } -// -// //Try trusteddomain objects with the securityidentifier attribute -// result = -// QueryLDAP($"(&(objectclass=trusteddomain)(securityidentifier={sid}))", SearchScope.Subtree, -// new[] { "cn" }, globalCatalog: true).DefaultIfEmpty(null).FirstOrDefault(); -// -// if (result != null) -// { -// var domainName = result.GetProperty(LDAPProperties.CanonicalName); -// return domainName; -// } -// -// //We didn't find anything so just return null -// return null; -// } -// -// /// -// /// Uses a socket and a set of bytes to request the NETBIOS name from a remote computer -// /// -// /// -// /// -// /// -// /// -// private static bool RequestNETBIOSNameFromComputer(string server, string domain, out string netbios) -// { -// var receiveBuffer = new byte[1024]; -// var requestSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); -// try -// { -// //Set receive timeout to 1 second -// requestSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, 1000); -// EndPoint remoteEndpoint; -// -// //We need to create an endpoint to bind too. If its an IP, just use that. -// if (IPAddress.TryParse(server, out var parsedAddress)) -// remoteEndpoint = new IPEndPoint(parsedAddress, 137); -// else -// //If its not an IP, we're going to try and resolve it from DNS -// try -// { -// IPAddress address; -// if (server.Contains(".")) -// address = Dns -// .GetHostAddresses(server).First(x => x.AddressFamily == AddressFamily.InterNetwork); -// else -// address = Dns.GetHostAddresses($"{server}.{domain}")[0]; -// -// if (address == null) -// { -// netbios = null; -// return false; -// } -// -// remoteEndpoint = new IPEndPoint(address, 137); -// } -// catch -// { -// //Failed to resolve an IP, so return null -// netbios = null; -// return false; -// } -// -// var originEndpoint = new IPEndPoint(IPAddress.Any, 0); -// requestSocket.Bind(originEndpoint); -// -// try -// { -// requestSocket.SendTo(NameRequest, remoteEndpoint); -// var receivedByteCount = requestSocket.ReceiveFrom(receiveBuffer, ref remoteEndpoint); -// if (receivedByteCount >= 90) -// { -// netbios = new ASCIIEncoding().GetString(receiveBuffer, 57, 16).Trim('\0', ' '); -// return true; -// } -// -// netbios = null; -// return false; -// } -// catch (SocketException) -// { -// netbios = null; -// return false; -// } -// } -// finally -// { -// //Make sure we close the socket if its open -// requestSocket.Close(); -// } -// } -// -// /// -// /// Calls the NetWkstaGetInfo API on a hostname -// /// -// /// -// /// -// private async Task GetWorkstationInfo(string hostname) -// { -// if (!await _portScanner.CheckPort(hostname)) -// return null; -// -// var result = NetAPIMethods.NetWkstaGetInfo(hostname); -// if (result.IsSuccess) return result.Value; -// -// return null; -// } -// -// /// -// /// Creates a SearchRequest object for use in querying LDAP. -// /// -// /// LDAP filter -// /// SearchScope to query -// /// LDAP properties to fetch for each object -// /// Domain info object which is created alongside the LDAP connection -// /// ADS path to limit the query too -// /// Include deleted objects in results -// /// A built SearchRequest -// private SearchRequest CreateSearchRequest(string filter, SearchScope scope, string[] attributes, -// DomainInfo domainInfo, string adsPath = null, bool showDeleted = false) -// { -// var adPath = adsPath?.Replace("LDAP://", "") ?? domainInfo.DomainSearchBase; -// -// var request = new SearchRequest(adPath, filter, scope, attributes); -// request.Controls.Add(new SearchOptionsControl(SearchOption.DomainScope)); -// if (showDeleted) -// request.Controls.Add(new ShowDeletedControl()); -// -// return request; -// } -// -// private LdapConnection CreateConnectionHelper(string directoryIdentifier, bool ssl, AuthType authType, bool globalCatalog) -// { -// var port = globalCatalog ? _ldapConfig.GetGCPort(ssl) : _ldapConfig.GetPort(ssl); -// var identifier = new LdapDirectoryIdentifier(directoryIdentifier, port, false, false); -// var connection = new LdapConnection(identifier) { Timeout = new TimeSpan(0, 0, 5, 0) }; -// SetupLdapConnection(connection, true, authType); -// return connection; -// } -// -// private static void CheckAndThrowException(LdapException ldapException) -// { -// //A null error code with success false indicates that we successfully created a connection but got no data back, this is generally because our AuthType isn't compatible. -// //AuthType Kerberos will only work across trusts in very specific scenarios. Alternatively, we don't have read rights. -// //Throw this exception for clients to handle -// if (ldapException.ErrorCode is (int)LdapErrorCodes.KerberosAuthType or (int)ResultCode.InsufficientAccessRights) -// { -// throw new NoLdapDataException(ldapException.ErrorCode); -// } -// -// //We shouldn't ever hit this in theory, but we'll error out if its the case -// if (ldapException.ErrorCode is (int)ResultCode.InappropriateAuthentication) -// { -// throw new LdapAuthenticationException(ldapException); -// } -// -// //Any other error we dont have specific ways to handle -// if (ldapException.ErrorCode != (int)ResultCode.Unavailable && ldapException.ErrorCode != (int)ResultCode.Busy) -// { -// throw new LdapConnectionException(ldapException); -// } -// } -// -// private string ResolveDomainToFullName(string domain) -// { -// if (string.IsNullOrEmpty(domain)) -// { -// return GetDomain()?.Name.ToUpper().Trim(); -// } -// -// if (CachedDomainInfo.TryGetValue(domain.ToUpper(), out var info)) -// { -// return info.DomainFQDN; -// } -// -// return GetDomain(domain)?.Name.ToUpper().Trim(); -// } -// -// /// -// /// Creates an LDAP connection with appropriate options based off the ldap configuration. Caches connections -// /// -// /// The domain to connect too -// /// Skip the connection cache -// /// Auth type to use. Defaults to Kerberos. Use Negotiate for netonly/cross trust(forest) scenarios -// /// Use global catalog or not -// /// A connected LDAP connection or null -// -// private async Task CreateLDAPConnectionWrapper(string domainName = null, bool skipCache = false, -// AuthType authType = AuthType.Kerberos, bool globalCatalog = false) -// { -// // Step 1: If domain passed in is non-null, skip this step -// // - Call GetDomain with a null domain to get the user's current domain -// // Step 2: Take domain passed in to the function or resolved from step 1 -// // - Try an ldap connection on SSL -// // - If ServerUnavailable - Try an ldap connection on non-SSL -// // Step 3: Pass the domain to GetDomain to resolve to a better name (potentially) -// // - If we get a better name, repeat step 2 with the new name -// // Step 4: -// // - Use GetDomain to get a domain object along with a list of domain controllers -// // - Try the primary domain controller on both ssl/non-ssl -// // - Loop over domain controllers and try each on ssl/non-ssl -// -// //If a server has been manually specified, we should never get past this block for opsec reasons -// if (_ldapConfig.Server != null) -// { -// _log.LogInformation("Server is set via config, attempting to create ldap connection to {Server}", _ldapConfig.Server); -// if (!skipCache) -// { -// if (GetCachedConnection(_ldapConfig.Server, globalCatalog, out var conn)) -// { -// return conn; -// } -// } -// -// var singleServerConn = CreateLDAPConnection(_ldapConfig.Server, authType, globalCatalog); -// if (singleServerConn == null) { -// return new LdapConnectionWrapper -// { -// Connection = null, -// DomainInfo = null -// }; -// } -// -// var cacheKey = new LDAPConnectionCacheKey(_ldapConfig.Server, globalCatalog); -// _ldapConnections.AddOrUpdate(cacheKey, singleServerConn, (_, ldapConnection) => -// { -// ldapConnection.Connection.Dispose(); -// return singleServerConn; -// }); -// return singleServerConn; -// } -// -// //Take the incoming domain name and Upper/Trim it. If the name is null, we'll have to use GetDomain to figure out the user's domain context -// var domain = domainName?.ToUpper().Trim() ?? ResolveDomainToFullName(domainName); -// -// //If our domain is STILL null, we're not going to get anything reliable, so exit out -// if (domain == null) -// { -// _log.LogWarning("Initial domain name for new LDAP connection is null and/or unresolvable. Unable to create a new connection"); -// return new LdapConnectionWrapper -// { -// Connection = null, -// DomainInfo = null -// }; -// } -// -// if (!skipCache) -// { -// if (GetCachedConnection(domain, globalCatalog, out var conn)) -// { -// return conn; -// } -// } -// -// _log.LogInformation("No cached LDAP connection found for {Domain}, attempting a new connection", domain); -// -// var connectionWrapper = CreateLDAPConnection(domain, authType, globalCatalog); -// //If our connection isn't null, it means we have a good connection -// if (connectionWrapper != null) -// { -// var cacheKey = new LDAPConnectionCacheKey(domain, globalCatalog); -// _ldapConnections.AddOrUpdate(cacheKey, connectionWrapper, (_, ldapConnection) => -// { -// ldapConnection.Connection.Dispose(); -// return connectionWrapper; -// }); -// return connectionWrapper; -// } -// -// //If our incoming domain name wasn't null, try to re-resolve the name for a better potential match and then retry -// if (domainName != null) -// { -// var newDomain = ResolveDomainToFullName(domainName); -// if (!string.IsNullOrEmpty(newDomain) && !newDomain.Equals(domain, StringComparison.OrdinalIgnoreCase)) -// { -// //Set our domain name to the newly resolved value for future steps -// domain = newDomain; -// if (!skipCache) -// { -// //Check our cache again, maybe the new name works -// if (GetCachedConnection(domain, globalCatalog, out var conn)) -// { -// return conn; -// } -// } -// -// connectionWrapper = CreateLDAPConnection(domain, authType, globalCatalog); -// //If our connection isn't null, it means we have a good connection -// if (connectionWrapper != null) -// { -// var cacheKey = new LDAPConnectionCacheKey(domain, globalCatalog); -// _ldapConnections.AddOrUpdate(cacheKey, connectionWrapper, (_, ldapConnection) => -// { -// ldapConnection.Connection.Dispose(); -// return connectionWrapper; -// }); -// return connectionWrapper; -// } -// } -// } -// -// //Next step, look for domain controllers -// var domainObj = GetDomain(domain); -// if (domainObj?.Name == null) -// { -// return null; -// } -// -// //Start with the PDC of the domain and see if we can connect -// var pdc = domainObj.PdcRoleOwner.Name; -// connectionWrapper = await CreateLDAPConnectionWithPortCheck(pdc, authType, globalCatalog); -// if (connectionWrapper != null) -// { -// var cacheKey = new LDAPConnectionCacheKey(domain, globalCatalog); -// _ldapConnections.AddOrUpdate(cacheKey, connectionWrapper, (_, ldapConnection) => -// { -// ldapConnection.Connection.Dispose(); -// return connectionWrapper; -// }); -// return connectionWrapper; -// } -// -// //Loop over all other domain controllers and see if we can make a good connection to any -// foreach (DomainController dc in domainObj.DomainControllers) -// { -// connectionWrapper = await CreateLDAPConnectionWithPortCheck(dc.Name, authType, globalCatalog); -// if (connectionWrapper != null) -// { -// var cacheKey = new LDAPConnectionCacheKey(domain, globalCatalog); -// _ldapConnections.AddOrUpdate(cacheKey, connectionWrapper, (_, ldapConnection) => -// { -// ldapConnection.Connection.Dispose(); -// return connectionWrapper; -// }); -// return connectionWrapper; -// } -// } -// -// return new LdapConnectionWrapper() -// { -// Connection = null, -// DomainInfo = null -// }; -// } -// -// private bool GetCachedConnection(string domain, bool globalCatalog, out LdapConnectionWrapper connectionWrapper) -// { -// var domainName = domain; -// if (CachedDomainInfo.TryGetValue(domain.ToUpper(), out var resolved)) -// { -// domainName = resolved.DomainFQDN; -// } -// var key = new LDAPConnectionCacheKey(domainName, globalCatalog); -// return _ldapConnections.TryGetValue(key, out connectionWrapper); -// } -// -// private async Task CreateLDAPConnectionWithPortCheck(string target, AuthType authType, bool globalCatalog) -// { -// if (globalCatalog) -// { -// if (await _portScanner.CheckPort(target, _ldapConfig.GetGCPort(true)) || (!_ldapConfig.ForceSSL && -// await _portScanner.CheckPort(target, _ldapConfig.GetGCPort(false)))) -// { -// return CreateLDAPConnection(target, authType, true); -// } -// } -// else -// { -// if (await _portScanner.CheckPort(target, _ldapConfig.GetPort(true)) || (!_ldapConfig.ForceSSL && await _portScanner.CheckPort(target, _ldapConfig.GetPort(false)))) -// { -// return CreateLDAPConnection(target, authType, false); -// } -// } -// -// return null; -// } -// -// private LdapConnectionWrapper CreateLDAPConnection(string target, AuthType authType, bool globalCatalog) -// { -// //Lets build a new connection -// //Always try SSL first -// var connection = CreateConnectionHelper(target, true, authType, globalCatalog); -// var connectionResult = TestConnection(connection); -// DomainInfo info; -// -// if (connectionResult.Success) -// { -// var domain = connectionResult.DomainInfo.DomainFQDN; -// if (!CachedDomainInfo.ContainsKey(domain)) -// { -// var baseDomainInfo = connectionResult.DomainInfo; -// baseDomainInfo.DomainSID = GetDomainSidFromConnection(connection, baseDomainInfo); -// baseDomainInfo.DomainNetbiosName = GetDomainNetbiosName(connection, baseDomainInfo); -// _log.LogInformation("Got info for domain: {info}", baseDomainInfo); -// CachedDomainInfo.TryAdd(baseDomainInfo.DomainFQDN, baseDomainInfo); -// CachedDomainInfo.TryAdd(baseDomainInfo.DomainNetbiosName, baseDomainInfo); -// CachedDomainInfo.TryAdd(baseDomainInfo.DomainSID, baseDomainInfo); -// if (!string.IsNullOrEmpty(baseDomainInfo.DomainSID)) -// { -// Cache.AddDomainSidMapping(baseDomainInfo.DomainFQDN, baseDomainInfo.DomainSID); -// Cache.AddDomainSidMapping(baseDomainInfo.DomainSID, baseDomainInfo.DomainFQDN); -// if (!string.IsNullOrEmpty(baseDomainInfo.DomainNetbiosName)) -// { -// Cache.AddDomainSidMapping(baseDomainInfo.DomainNetbiosName, baseDomainInfo.DomainSID); -// } -// } -// -// if (!string.IsNullOrEmpty(baseDomainInfo.DomainNetbiosName)) -// { -// _netbiosCache.TryAdd(baseDomainInfo.DomainFQDN, baseDomainInfo.DomainNetbiosName); -// } -// -// info = baseDomainInfo; -// } -// else -// { -// CachedDomainInfo.TryGetValue(domain, out info); -// } -// return new LdapConnectionWrapper -// { -// Connection = connection, -// DomainInfo = info -// }; -// } -// -// CheckAndThrowException(connectionResult.Exception); -// -// //If we're not allowing fallbacks to LDAP from LDAPS, just return here -// if (_ldapConfig.ForceSSL) -// { -// return null; -// } -// //If we get to this point, it means we have an unsuccessful connection, but our error code doesn't indicate an outright failure -// //Try a new connection without SSL -// connection = CreateConnectionHelper(target, false, authType, globalCatalog); -// -// connectionResult = TestConnection(connection); -// -// if (connectionResult.Success) -// { -// var domain = connectionResult.DomainInfo.DomainFQDN; -// if (!CachedDomainInfo.ContainsKey(domain.ToUpper())) -// { -// var baseDomainInfo = connectionResult.DomainInfo; -// baseDomainInfo.DomainSID = GetDomainSidFromConnection(connection, baseDomainInfo); -// baseDomainInfo.DomainNetbiosName = GetDomainNetbiosName(connection, baseDomainInfo); -// CachedDomainInfo.TryAdd(baseDomainInfo.DomainFQDN, baseDomainInfo); -// CachedDomainInfo.TryAdd(baseDomainInfo.DomainNetbiosName, baseDomainInfo); -// CachedDomainInfo.TryAdd(baseDomainInfo.DomainSID, baseDomainInfo); -// -// if (!string.IsNullOrEmpty(baseDomainInfo.DomainSID)) -// { -// Cache.AddDomainSidMapping(baseDomainInfo.DomainFQDN, baseDomainInfo.DomainSID); -// } -// -// if (!string.IsNullOrEmpty(baseDomainInfo.DomainNetbiosName)) -// { -// Cache.AddDomainSidMapping(baseDomainInfo.DomainNetbiosName, baseDomainInfo.DomainSID); -// } -// -// info = baseDomainInfo; -// }else -// { -// CachedDomainInfo.TryGetValue(domain, out info); -// } -// return new LdapConnectionWrapper -// { -// Connection = connection, -// DomainInfo = info -// }; -// } -// -// CheckAndThrowException(connectionResult.Exception); -// return null; -// } -// -// private LdapConnectionTestResult TestConnection(LdapConnection connection) -// { -// try -// { -// //Attempt an initial bind. If this fails, likely auth is invalid, or its not a valid target -// connection.Bind(); -// } -// catch (LdapException e) -// { -// connection.Dispose(); -// return new LdapConnectionTestResult(false, e, null, null); -// } -// -// try -// { -// //Do an initial search request to get the rootDSE -// //This ldap filter is equivalent to (objectclass=*) -// var searchRequest = new SearchRequest("", new LDAPFilter().AddAllObjects().GetFilter(), -// SearchScope.Base, null); -// searchRequest.Controls.Add(new SearchOptionsControl(SearchOption.DomainScope)); -// -// var response = (SearchResponse)connection.SendRequest(searchRequest); -// if (response?.Entries == null) -// { -// connection.Dispose(); -// return new LdapConnectionTestResult(false, null, null, null); -// } -// -// if (response.Entries.Count == 0) -// { -// connection.Dispose(); -// return new LdapConnectionTestResult(false, new LdapException((int)LdapErrorCodes.KerberosAuthType), null, null); -// } -// -// var entry = response.Entries[0]; -// var baseDN = entry.GetProperty(LDAPProperties.RootDomainNamingContext).ToUpper().Trim(); -// var configurationDN = entry.GetProperty(LDAPProperties.ConfigurationNamingContext).ToUpper().Trim(); -// var domainname = Helpers.DistinguishedNameToDomain(baseDN).ToUpper().Trim(); -// var servername = entry.GetProperty(LDAPProperties.ServerName); -// var compName = servername.Substring(0, servername.IndexOf(',')).Substring(3).Trim(); -// var fullServerName = $"{compName}.{domainname}".ToUpper().Trim(); -// -// return new LdapConnectionTestResult(true, null, new DomainInfo -// { -// DomainConfigurationPath = configurationDN, -// DomainSearchBase = baseDN, -// DomainFQDN = domainname -// }, fullServerName); -// } -// catch (LdapException e) -// { -// try -// { -// connection.Dispose(); -// } -// catch -// { -// //pass -// } -// return new LdapConnectionTestResult(false, e, null, null); -// } -// } -// -// public class LdapConnectionTestResult -// { -// public bool Success { get; set; } -// public LdapException Exception { get; set; } -// public DomainInfo DomainInfo { get; set; } -// public string ServerName { get; set; } -// -// public LdapConnectionTestResult(bool success, LdapException e, DomainInfo info, string server) -// { -// Success = success; -// Exception = e; -// DomainInfo = info; -// ServerName = server; -// } -// } -// -// private string GetDomainNetbiosName(LdapConnection connection, DomainInfo info) -// { -// try -// { -// var searchRequest = new SearchRequest($"CN=Partitions,{info.DomainConfigurationPath}", -// "(&(nETBIOSName=*)(dnsRoot=*))", -// SearchScope.Subtree, new[] { LDAPProperties.NetbiosName, LDAPProperties.DnsRoot }); -// -// var response = (SearchResponse)connection.SendRequest(searchRequest); -// if (response == null || response.Entries.Count == 0) -// { -// return ""; -// } -// -// foreach (SearchResultEntry entry in response.Entries) -// { -// var root = entry.GetProperty(LDAPProperties.DnsRoot); -// var netbios = entry.GetProperty(LDAPProperties.NetbiosName); -// _log.LogInformation(root); -// _log.LogInformation(netbios); -// -// if (root.ToUpper().Equals(info.DomainFQDN)) -// { -// return netbios.ToUpper(); -// } -// } -// -// return ""; -// } -// catch (LdapException e) -// { -// _log.LogWarning("Failed grabbing netbios name from ldap for {domain}: {e}", info.DomainFQDN, e); -// return ""; -// } -// } -// -// private string GetDomainSidFromConnection(LdapConnection connection, DomainInfo info) -// { -// try -// { -// //This ldap filter searches for domain controllers -// //Searches for any accounts with a UAC value inclusive of 8192 bitwise -// //8192 is the flag for SERVER_TRUST_ACCOUNT, which is set only on Domain Controllers -// var searchRequest = new SearchRequest(info.DomainSearchBase, -// "(&(objectCategory=computer)(userAccountControl:1.2.840.113556.1.4.803:=8192))", -// SearchScope.Subtree, new[] { "objectsid"}); -// -// var response = (SearchResponse)connection.SendRequest(searchRequest); -// if (response == null || response.Entries.Count == 0) -// { -// return ""; -// } -// -// var entry = response.Entries[0]; -// var sid = entry.GetSid(); -// return sid.Substring(0, sid.LastIndexOf('-')).ToUpper(); -// } -// catch (LdapException) -// { -// _log.LogWarning("Failed grabbing domainsid from ldap for {domain}", info.DomainFQDN); -// return ""; -// } -// } -// -// private void SetupLdapConnection(LdapConnection connection, bool ssl, AuthType authType) -// { -// //These options are important! -// connection.SessionOptions.ProtocolVersion = 3; -// //Referral chasing does not work with paged searches -// connection.SessionOptions.ReferralChasing = ReferralChasingOptions.None; -// if (ssl) -// { -// connection.SessionOptions.SecureSocketLayer = true; -// } -// -// if (_ldapConfig.DisableSigning) -// { -// connection.SessionOptions.Sealing = false; -// connection.SessionOptions.Signing = false; -// } -// -// if (_ldapConfig.DisableCertVerification) -// connection.SessionOptions.VerifyServerCertificate = (_, _) => true; -// -// if (_ldapConfig.Username != null) -// { -// var cred = new NetworkCredential(_ldapConfig.Username, _ldapConfig.Password); -// connection.Credential = cred; -// } -// -// connection.AuthType = authType; -// } -// -// /// -// /// Normalizes a domain name to its full DNS name -// /// -// /// -// /// -// internal string NormalizeDomainName(string domain) -// { -// if (domain == null) -// return null; -// -// var resolved = domain; -// -// if (resolved.Contains(".")) -// return domain.ToUpper(); -// -// resolved = ResolveDomainNetbiosToDns(domain) ?? domain; -// -// return resolved.ToUpper(); -// } -// -// /// -// /// Turns a domain Netbios name into its FQDN using the DsGetDcName function (TESTLAB -> TESTLAB.LOCAL) -// /// -// /// -// /// -// internal string ResolveDomainNetbiosToDns(string domainName) -// { -// var key = domainName.ToUpper(); -// if (_netbiosCache.TryGetValue(key, out var flatName)) -// return flatName; -// -// var domain = GetDomain(domainName); -// if (domain != null) -// { -// _netbiosCache.TryAdd(key, domain.Name); -// return domain.Name; -// } -// -// var computerName = _ldapConfig.Server; -// -// var dci = _nativeMethods.CallDsGetDcName(computerName, domainName); -// if (dci.IsSuccess) -// { -// flatName = dci.Value.DomainName; -// _netbiosCache.TryAdd(key, flatName); -// return flatName; -// } -// -// return domainName.ToUpper(); -// } -// -// /// -// /// Gets the range retrieval limit for a domain -// /// -// /// -// /// -// /// -// public int GetDomainRangeSize(string domainName = null, int defaultRangeSize = 750) -// { -// var domainPath = DomainNameToDistinguishedName(domainName); -// //Default to a page size of 750 for safety -// if (domainPath == null) -// { -// _log.LogDebug("Unable to resolve domain {Domain} to distinguishedname to get page size", -// domainName ?? "current domain"); -// return defaultRangeSize; -// } -// -// if (_ldapRangeSizeCache.TryGetValue(domainPath.ToUpper(), out var parsedPageSize)) -// { -// return parsedPageSize; -// } -// -// var configPath = CommonPaths.CreateDNPath(CommonPaths.QueryPolicyPath, domainPath); -// var enumerable = QueryLDAP("(objectclass=*)", SearchScope.Base, null, adsPath: configPath); -// var config = enumerable.DefaultIfEmpty(null).FirstOrDefault(); -// var pageSize = config?.GetArrayProperty(LDAPProperties.LdapAdminLimits) -// .FirstOrDefault(x => x.StartsWith("MaxPageSize", StringComparison.OrdinalIgnoreCase)); -// if (pageSize == null) -// { -// _log.LogDebug("No LDAPAdminLimits object found for {Domain}", domainName); -// _ldapRangeSizeCache.TryAdd(domainPath.ToUpper(), defaultRangeSize); -// return defaultRangeSize; -// } -// -// if (int.TryParse(pageSize.Split('=').Last(), out parsedPageSize)) -// { -// _ldapRangeSizeCache.TryAdd(domainPath.ToUpper(), parsedPageSize); -// _log.LogInformation("Found page size {PageSize} for {Domain}", parsedPageSize, -// domainName ?? "current domain"); -// return parsedPageSize; -// } -// -// _log.LogDebug("Failed to parse pagesize for {Domain}, returning default", domainName ?? "current domain"); -// -// _ldapRangeSizeCache.TryAdd(domainPath.ToUpper(), defaultRangeSize); -// return defaultRangeSize; -// } -// -// private string DomainNameToDistinguishedName(string domain) -// { -// var resolvedDomain = GetDomain(domain)?.Name ?? domain; -// return resolvedDomain == null ? null : $"DC={resolvedDomain.Replace(".", ",DC=")}"; -// } -// -// private class ResolvedWellKnownPrincipal -// { -// public string DomainName { get; set; } -// public string WkpId { get; set; } -// } -// -// public string GetConfigurationPath(string domainName = null) -// { -// string path = domainName == null -// ? "LDAP://RootDSE" -// : $"LDAP://{NormalizeDomainName(domainName)}/RootDSE"; -// -// DirectoryEntry rootDse; -// if (_ldapConfig.Username != null) -// rootDse = new DirectoryEntry(path, _ldapConfig.Username, _ldapConfig.Password); -// else -// rootDse = new DirectoryEntry(path); -// -// return $"{rootDse.Properties["configurationNamingContext"]?[0]}"; -// } -// -// public string GetSchemaPath(string domainName) -// { -// string path = domainName == null -// ? "LDAP://RootDSE" -// : $"LDAP://{NormalizeDomainName(domainName)}/RootDSE"; -// -// DirectoryEntry rootDse; -// if (_ldapConfig.Username != null) -// rootDse = new DirectoryEntry(path, _ldapConfig.Username, _ldapConfig.Password); -// else -// rootDse = new DirectoryEntry(path); -// -// return $"{rootDse.Properties["schemaNamingContext"]?[0]}"; -// } -// -// public bool IsDomainController(string computerObjectId, string domainName) -// { -// var filter = new LDAPFilter().AddFilter(LDAPProperties.ObjectSID + "=" + computerObjectId, true) -// .AddFilter(CommonFilters.DomainControllers, true); -// var res = QueryLDAP(filter.GetFilter(), SearchScope.Subtree, -// CommonProperties.ObjectID, domainName: domainName); -// if (res.Count() > 0) -// return true; -// return false; -// } -// } -// } - - -// using System.Collections.Generic; -// using System.DirectoryServices.ActiveDirectory; -// using System.DirectoryServices.Protocols; -// using System.Security.Principal; -// using System.Threading; -// using System.Threading.Tasks; -// using SharpHoundCommonLib.Enums; -// using SharpHoundCommonLib.OutputTypes; -// using SharpHoundRPC.Wrappers; -// using Domain = System.DirectoryServices.ActiveDirectory.Domain; -// -// namespace SharpHoundCommonLib -// { -// /// -// /// Struct representing options to create an LDAP query -// /// -// public struct LDAPQueryOptions -// { -// public string Filter; -// public SearchScope Scope; -// public string[] Properties; -// public CancellationToken CancellationToken; -// public string DomainName; -// public bool IncludeAcl; -// public bool ShowDeleted; -// public string AdsPath; -// public bool GlobalCatalog; -// public bool SkipCache; -// public bool ThrowException; -// } -// -// public interface ILDAPUtils -// { -// void SetLDAPConfig(LDAPConfig config); -// bool TestLDAPConfig(string domain); -// string[] GetUserGlobalCatalogMatches(string name); -// TypedPrincipal ResolveIDAndType(string id, string fallbackDomain); -// TypedPrincipal ResolveCertTemplateByProperty(string propValue, string propName, string containerDN, string domainName); -// Label LookupSidType(string sid, string domain); -// Label LookupGuidType(string guid, string domain); -// string GetDomainNameFromSid(string sid); -// string GetSidFromDomainName(string domainName); -// string ConvertWellKnownPrincipal(string sid, string domain); -// bool GetWellKnownPrincipal(string sid, string domain, out TypedPrincipal commonPrincipal); -// -// bool ConvertLocalWellKnownPrincipal(SecurityIdentifier sid, string computerDomainSid, string computerDomain, -// out TypedPrincipal principal); -// Domain GetDomain(string domainName = null); -// void AddDomainController(string domainControllerSID); -// IEnumerable GetWellKnownPrincipalOutput(string domain); -// -// /// -// /// Performs Attribute Ranged Retrieval -// /// https://docs.microsoft.com/en-us/windows/win32/adsi/attribute-range-retrieval -// /// The function self-determines the range and internally handles the maximum step allowed by the server -// /// -// /// -// /// -// /// -// IEnumerable DoRangedRetrieval(string distinguishedName, string attributeName); -// -// /// -// /// Takes a host in most applicable forms from AD and attempts to resolve it into a SID. -// /// -// /// -// /// -// /// -// Task ResolveHostToSid(string hostname, string domain); -// -// /// -// /// Attempts to convert a bare account name (usually from session enumeration) to its corresponding ID and object type -// /// -// /// -// /// -// /// -// TypedPrincipal ResolveAccountName(string name, string domain); -// -// /// -// /// Attempts to convert a distinguishedname to its corresponding ID and object type. -// /// -// /// DistinguishedName -// /// A TypedPrincipal object with the SID and Label -// TypedPrincipal ResolveDistinguishedName(string dn); -// -// /// -// /// Performs an LDAP query using the parameters specified by the user. -// /// -// /// LDAP query options -// /// All LDAP search results matching the specified parameters -// IEnumerable QueryLDAP(LDAPQueryOptions options); -// -// /// -// /// Performs an LDAP query using the parameters specified by the user. -// /// -// /// LDAP filter -// /// SearchScope to query -// /// LDAP properties to fetch for each object -// /// Cancellation Token -// /// Include the DACL and Owner values in the NTSecurityDescriptor -// /// Include deleted objects -// /// Domain to query -// /// ADS path to limit the query too -// /// Use the global catalog instead of the regular LDAP server -// /// -// /// Skip the connection cache and force a new connection. You must dispose of this connection -// /// yourself. -// /// -// /// Throw exceptions rather than logging the errors directly -// /// All LDAP search results matching the specified parameters -// IEnumerable QueryLDAP(string ldapFilter, SearchScope scope, -// string[] props, CancellationToken cancellationToken, string domainName = null, bool includeAcl = false, -// bool showDeleted = false, string adsPath = null, bool globalCatalog = false, bool skipCache = false, -// bool throwException = false); -// -// /// -// /// Performs an LDAP query using the parameters specified by the user. -// /// -// /// LDAP filter -// /// SearchScope to query -// /// LDAP properties to fetch for each object -// /// Include the DACL and Owner values in the NTSecurityDescriptor -// /// Include deleted objects -// /// Domain to query -// /// ADS path to limit the query too -// /// Use the global catalog instead of the regular LDAP server -// /// -// /// Skip the connection cache and force a new connection. You must dispose of this connection -// /// yourself. -// /// -// /// Throw exceptions rather than logging the errors directly -// /// All LDAP search results matching the specified parameters -// IEnumerable QueryLDAP(string ldapFilter, SearchScope scope, -// string[] props, string domainName = null, bool includeAcl = false, bool showDeleted = false, -// string adsPath = null, bool globalCatalog = false, bool skipCache = false, bool throwException = false); -// -// Forest GetForest(string domainName = null); -// string GetConfigurationPath(string domainName); -// string GetSchemaPath(string domainName); -// -// ActiveDirectorySecurityDescriptor MakeSecurityDescriptor(); -// string BuildLdapPath(string dnPath, string domain); -// bool IsDomainController(string computerObjectId, string domainName); -// } -// } \ No newline at end of file From f06234236f3465ffbc509e8913f1882236cde076 Mon Sep 17 00:00:00 2001 From: rvazarkar Date: Mon, 22 Jul 2024 15:11:19 -0400 Subject: [PATCH 56/68] chore: supress/fix nits --- test/unit/ContainerProcessorTest.cs | 2 +- test/unit/Facades/LSAMocks/DCMocks/MockDCLSAPolicy.cs | 2 ++ .../LSAMocks/WorkstationMocks/MockWorkstationLSAPolicy.cs | 2 ++ test/unit/Facades/MockLdapUtils.cs | 2 ++ test/unit/Facades/MockableDomain.cs | 4 +++- test/unit/Facades/MockableForest.cs | 4 +++- .../Facades/SAMMocks/DCMocks/MockDCAliasAdministrators.cs | 2 ++ test/unit/Facades/SAMMocks/DCMocks/MockDCAliasUsers.cs | 2 ++ test/unit/Facades/SAMMocks/DCMocks/MockDCSAMServer.cs | 2 ++ .../WorkstationMocks/MockWorkstationAliasAdministrators.cs | 2 ++ .../SAMMocks/WorkstationMocks/MockWorkstationAliasRDP.cs | 2 ++ .../WorkstationMocks/MockWorkstationAliasTestGroup.cs | 2 ++ .../SAMMocks/WorkstationMocks/MockWorkstationSAMServer.cs | 4 +++- 13 files changed, 28 insertions(+), 4 deletions(-) diff --git a/test/unit/ContainerProcessorTest.cs b/test/unit/ContainerProcessorTest.cs index 5c375442..a4111bca 100644 --- a/test/unit/ContainerProcessorTest.cs +++ b/test/unit/ContainerProcessorTest.cs @@ -121,7 +121,7 @@ public async Task ContainerProcessor_GetContainerChildObjects_ReturnsCorrectData } [Fact] - public async Task ContainerProcessor_ReadBlocksInheritance_ReturnsCorrectValues() + public void ContainerProcessor_ReadBlocksInheritance_ReturnsCorrectValues() { var test = ContainerProcessor.ReadBlocksInheritance(null); var test2 = ContainerProcessor.ReadBlocksInheritance("3"); diff --git a/test/unit/Facades/LSAMocks/DCMocks/MockDCLSAPolicy.cs b/test/unit/Facades/LSAMocks/DCMocks/MockDCLSAPolicy.cs index c6f3c3c5..bdc2fb8f 100644 --- a/test/unit/Facades/LSAMocks/DCMocks/MockDCLSAPolicy.cs +++ b/test/unit/Facades/LSAMocks/DCMocks/MockDCLSAPolicy.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Security.Principal; using SharpHoundRPC; using SharpHoundRPC.Shared; @@ -7,6 +8,7 @@ namespace CommonLibTest.Facades.LSAMocks.DCMocks { + [SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")] public class MockDCLSAPolicy : ILSAPolicy { public Result<(string Name, string Sid)> GetLocalDomainInformation() diff --git a/test/unit/Facades/LSAMocks/WorkstationMocks/MockWorkstationLSAPolicy.cs b/test/unit/Facades/LSAMocks/WorkstationMocks/MockWorkstationLSAPolicy.cs index 95424ea2..924d7575 100644 --- a/test/unit/Facades/LSAMocks/WorkstationMocks/MockWorkstationLSAPolicy.cs +++ b/test/unit/Facades/LSAMocks/WorkstationMocks/MockWorkstationLSAPolicy.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Security.Principal; using SharpHoundRPC; using SharpHoundRPC.Shared; @@ -7,6 +8,7 @@ namespace CommonLibTest.Facades.LSAMocks.WorkstationMocks { + [SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")] public class MockWorkstationLSAPolicy : ILSAPolicy { public Result<(string Name, string Sid)> GetLocalDomainInformation() diff --git a/test/unit/Facades/MockLdapUtils.cs b/test/unit/Facades/MockLdapUtils.cs index a10cda45..964a416a 100644 --- a/test/unit/Facades/MockLdapUtils.cs +++ b/test/unit/Facades/MockLdapUtils.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.DirectoryServices.ActiveDirectory; using System.Linq; using System.Security.Principal; @@ -16,6 +17,7 @@ namespace CommonLibTest.Facades { + [SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")] public class MockLdapUtils : ILdapUtils { private readonly ConcurrentDictionary _domainControllers = new(); diff --git a/test/unit/Facades/MockableDomain.cs b/test/unit/Facades/MockableDomain.cs index 059918d1..c28bd093 100644 --- a/test/unit/Facades/MockableDomain.cs +++ b/test/unit/Facades/MockableDomain.cs @@ -1,7 +1,9 @@ -using System.DirectoryServices.ActiveDirectory; +using System.Diagnostics.CodeAnalysis; +using System.DirectoryServices.ActiveDirectory; namespace CommonLibTest.Facades { + [SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")] public class MockableDomain { public static Domain Construct(string domainName) diff --git a/test/unit/Facades/MockableForest.cs b/test/unit/Facades/MockableForest.cs index 9893e633..4c421ef2 100644 --- a/test/unit/Facades/MockableForest.cs +++ b/test/unit/Facades/MockableForest.cs @@ -1,7 +1,9 @@ -using System.DirectoryServices.ActiveDirectory; +using System.Diagnostics.CodeAnalysis; +using System.DirectoryServices.ActiveDirectory; namespace CommonLibTest.Facades { + [SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")] public class MockableForest { public static Forest Construct(string forestDnsName) diff --git a/test/unit/Facades/SAMMocks/DCMocks/MockDCAliasAdministrators.cs b/test/unit/Facades/SAMMocks/DCMocks/MockDCAliasAdministrators.cs index 15a18ddc..80f3db1a 100644 --- a/test/unit/Facades/SAMMocks/DCMocks/MockDCAliasAdministrators.cs +++ b/test/unit/Facades/SAMMocks/DCMocks/MockDCAliasAdministrators.cs @@ -1,10 +1,12 @@ using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Security.Principal; using SharpHoundRPC; using SharpHoundRPC.Wrappers; namespace CommonLibTest.Facades { + [SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")] public class MockDCAliasAdministrators : ISAMAlias { public Result> GetMembers() diff --git a/test/unit/Facades/SAMMocks/DCMocks/MockDCAliasUsers.cs b/test/unit/Facades/SAMMocks/DCMocks/MockDCAliasUsers.cs index b15c0e59..5b0cb58c 100644 --- a/test/unit/Facades/SAMMocks/DCMocks/MockDCAliasUsers.cs +++ b/test/unit/Facades/SAMMocks/DCMocks/MockDCAliasUsers.cs @@ -1,10 +1,12 @@ using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Security.Principal; using SharpHoundRPC; using SharpHoundRPC.Wrappers; namespace CommonLibTest.Facades { + [SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")] public class MockDCAliasUsers : ISAMAlias { public Result> GetMembers() diff --git a/test/unit/Facades/SAMMocks/DCMocks/MockDCSAMServer.cs b/test/unit/Facades/SAMMocks/DCMocks/MockDCSAMServer.cs index 5e090c19..bf0e5352 100644 --- a/test/unit/Facades/SAMMocks/DCMocks/MockDCSAMServer.cs +++ b/test/unit/Facades/SAMMocks/DCMocks/MockDCSAMServer.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Security.Principal; using SharpHoundRPC; using SharpHoundRPC.SAMRPCNative; @@ -8,6 +9,7 @@ namespace CommonLibTest.Facades { + [SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")] public class MockDCSAMServer : ISAMServer { public bool IsNull { get; } diff --git a/test/unit/Facades/SAMMocks/WorkstationMocks/MockWorkstationAliasAdministrators.cs b/test/unit/Facades/SAMMocks/WorkstationMocks/MockWorkstationAliasAdministrators.cs index 2c4d50a5..d9e1dcea 100644 --- a/test/unit/Facades/SAMMocks/WorkstationMocks/MockWorkstationAliasAdministrators.cs +++ b/test/unit/Facades/SAMMocks/WorkstationMocks/MockWorkstationAliasAdministrators.cs @@ -1,10 +1,12 @@ using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Security.Principal; using SharpHoundRPC; using SharpHoundRPC.Wrappers; namespace CommonLibTest.Facades { + [SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")] public class MockWorkstationAliasAdministrators : ISAMAlias { public Result> GetMembers() diff --git a/test/unit/Facades/SAMMocks/WorkstationMocks/MockWorkstationAliasRDP.cs b/test/unit/Facades/SAMMocks/WorkstationMocks/MockWorkstationAliasRDP.cs index 11fe80ae..e6647307 100644 --- a/test/unit/Facades/SAMMocks/WorkstationMocks/MockWorkstationAliasRDP.cs +++ b/test/unit/Facades/SAMMocks/WorkstationMocks/MockWorkstationAliasRDP.cs @@ -1,10 +1,12 @@ using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Security.Principal; using SharpHoundRPC; using SharpHoundRPC.Wrappers; namespace CommonLibTest.Facades { + [SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")] public class MockWorkstationAliasRDP : ISAMAlias { public Result> GetMembers() diff --git a/test/unit/Facades/SAMMocks/WorkstationMocks/MockWorkstationAliasTestGroup.cs b/test/unit/Facades/SAMMocks/WorkstationMocks/MockWorkstationAliasTestGroup.cs index 8eaeb8fc..09164c6b 100644 --- a/test/unit/Facades/SAMMocks/WorkstationMocks/MockWorkstationAliasTestGroup.cs +++ b/test/unit/Facades/SAMMocks/WorkstationMocks/MockWorkstationAliasTestGroup.cs @@ -1,10 +1,12 @@ using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Security.Principal; using SharpHoundRPC; using SharpHoundRPC.Wrappers; namespace CommonLibTest.Facades { + [SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")] public class MockWorkstationAliasTestGroup : ISAMAlias { public Result> GetMembers() diff --git a/test/unit/Facades/SAMMocks/WorkstationMocks/MockWorkstationSAMServer.cs b/test/unit/Facades/SAMMocks/WorkstationMocks/MockWorkstationSAMServer.cs index 8f076b19..f89827bc 100644 --- a/test/unit/Facades/SAMMocks/WorkstationMocks/MockWorkstationSAMServer.cs +++ b/test/unit/Facades/SAMMocks/WorkstationMocks/MockWorkstationSAMServer.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Security.Principal; using SharpHoundRPC; using SharpHoundRPC.SAMRPCNative; @@ -8,6 +9,7 @@ namespace CommonLibTest.Facades { + [SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")] public class MockWorkstationSAMServer : ISAMServer { public bool IsNull { get; } @@ -23,7 +25,7 @@ public class MockWorkstationSAMServer : ISAMServer public Result LookupDomain(string name) { - throw new System.NotImplementedException(); + throw new NotImplementedException(); } public Result GetMachineSid(string testName = null) From 5d8905c8ba8bb046700221c7c7c9d138d0fa79a6 Mon Sep 17 00:00:00 2001 From: Rohan Vazarkar Date: Tue, 23 Jul 2024 12:36:15 -0400 Subject: [PATCH 57/68] Replace System.Linq.Async with custom implementations (#137) * chore: remove system.linq.async, replace with custom functions for file size issues * chore: add some tests covering new async enumerables * chore: minor fixes * chore: add overload for FirstOrDefaultAsync * chore: add some more tests --- src/CommonLib/AsyncEnumerable.cs | 23 ++++ .../SearchResultEntryWrapper.cs | 2 +- src/CommonLib/Extensions.cs | 114 ++++++++++++++++-- src/CommonLib/ILdapUtils.cs | 2 + src/CommonLib/LdapUtils.cs | 8 +- src/CommonLib/SharpHoundCommonLib.csproj | 1 - test/unit/AsyncEnumerableTests.cs | 92 ++++++++++++++ test/unit/Facades/MockLdapUtils.cs | 10 +- test/unit/Utils.cs | 50 ++++++++ 9 files changed, 286 insertions(+), 16 deletions(-) create mode 100644 src/CommonLib/AsyncEnumerable.cs create mode 100644 test/unit/AsyncEnumerableTests.cs diff --git a/src/CommonLib/AsyncEnumerable.cs b/src/CommonLib/AsyncEnumerable.cs new file mode 100644 index 00000000..57d74c50 --- /dev/null +++ b/src/CommonLib/AsyncEnumerable.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace SharpHoundCommonLib; + +public static class AsyncEnumerable { + public static IAsyncEnumerable Empty() => EmptyAsyncEnumerable.Instance; + + private sealed class EmptyAsyncEnumerable : IAsyncEnumerable { + public static readonly EmptyAsyncEnumerable Instance = new(); + private readonly IAsyncEnumerator _enumerator = new EmptyAsyncEnumerator(); + public IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = new CancellationToken()) { + return _enumerator; + } + } + + private sealed class EmptyAsyncEnumerator : IAsyncEnumerator { + public ValueTask DisposeAsync() => default; + public ValueTask MoveNextAsync() => new(false); + public T Current => default; + } +} \ No newline at end of file diff --git a/src/CommonLib/DirectoryObjects/SearchResultEntryWrapper.cs b/src/CommonLib/DirectoryObjects/SearchResultEntryWrapper.cs index b72b7e40..0dc0a151 100644 --- a/src/CommonLib/DirectoryObjects/SearchResultEntryWrapper.cs +++ b/src/CommonLib/DirectoryObjects/SearchResultEntryWrapper.cs @@ -15,7 +15,7 @@ public SearchResultEntryWrapper(SearchResultEntry entry) { } public bool TryGetDistinguishedName(out string value) { - return TryGetProperty(LDAPProperties.DistinguishedName, out value); + return TryGetProperty(LDAPProperties.DistinguishedName, out value) && !string.IsNullOrWhiteSpace(value); } public bool TryGetProperty(string propertyName, out string value) { diff --git a/src/CommonLib/Extensions.cs b/src/CommonLib/Extensions.cs index f8252796..c7086a62 100644 --- a/src/CommonLib/Extensions.cs +++ b/src/CommonLib/Extensions.cs @@ -1,7 +1,10 @@ using System; +using System.Collections.Generic; using System.DirectoryServices; using System.Linq; using System.Security.Principal; +using System.Threading; +using System.Threading.Tasks; using Microsoft.Extensions.Logging; using SharpHoundCommonLib.Enums; @@ -16,14 +19,109 @@ static Extensions() Log = Logging.LogProvider.CreateLogger("Extensions"); } - // internal static async Task> ToListAsync(this IAsyncEnumerable items) - // { - // var results = new List(); - // await foreach (var item in items - // .ConfigureAwait(false)) - // results.Add(item); - // return results; - // } + public static async Task> ToListAsync(this IAsyncEnumerable items) + { + if (items == null) { + return new List(); + } + var results = new List(); + await foreach (var item in items + .ConfigureAwait(false)) + results.Add(item); + return results; + } + + public static async Task ToArrayAsync(this IAsyncEnumerable items) + { + if (items == null) { + return Array.Empty(); + } + var results = new List(); + await foreach (var item in items + .ConfigureAwait(false)) + results.Add(item); + return results.ToArray(); + } + + public static async Task FirstOrDefaultAsync(this IAsyncEnumerable source, + CancellationToken cancellationToken = default) { + if (source == null) { + return default; + } + + await using (var enumerator = source.GetAsyncEnumerator(cancellationToken)) { + var first = await enumerator.MoveNextAsync() ? enumerator.Current : default; + return first; + } + } + + public static async Task FirstOrDefaultAsync(this IAsyncEnumerable source, T defaultValue, + CancellationToken cancellationToken = default) { + if (source == null) { + return defaultValue; + } + + await using (var enumerator = source.GetAsyncEnumerator(cancellationToken)) { + var first = await enumerator.MoveNextAsync() ? enumerator.Current : defaultValue; + return first; + } + } + + public static IAsyncEnumerable DefaultIfEmpty(this IAsyncEnumerable source, + T defaultValue, CancellationToken cancellationToken = default) { + return new DefaultIfEmptyAsyncEnumerable(source, defaultValue); + } + + private sealed class DefaultIfEmptyAsyncEnumerable : IAsyncEnumerable { + private readonly DefaultIfEmptyAsyncEnumerator _enumerator; + public DefaultIfEmptyAsyncEnumerable(IAsyncEnumerable source, T defaultValue) { + _enumerator = new DefaultIfEmptyAsyncEnumerator(source, defaultValue); + } + public IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = new CancellationToken()) { + return _enumerator; + } + } + + private sealed class DefaultIfEmptyAsyncEnumerator : IAsyncEnumerator { + private readonly IAsyncEnumerable _source; + private readonly T _defaultValue; + private T _current; + private bool _enumeratorDisposed; + + private IAsyncEnumerator _enumerator; + + public DefaultIfEmptyAsyncEnumerator(IAsyncEnumerable source, T defaultValue) { + _source = source; + _defaultValue = defaultValue; + } + + public async ValueTask DisposeAsync() { + _enumeratorDisposed = true; + if (_enumerator != null) { + await _enumerator.DisposeAsync().ConfigureAwait(false); + _enumerator = null; + } + } + + public async ValueTask MoveNextAsync() { + if (_enumeratorDisposed) { + return false; + } + _enumerator ??= _source.GetAsyncEnumerator(); + + if (await _enumerator.MoveNextAsync().ConfigureAwait(false)) { + _current = _enumerator.Current; + return true; + } + + _current = _defaultValue; + await DisposeAsync().ConfigureAwait(false); + return true; + } + + public T Current => _current; + } + public static string LdapValue(this SecurityIdentifier s) { diff --git a/src/CommonLib/ILdapUtils.cs b/src/CommonLib/ILdapUtils.cs index 0732a1f8..9f0bbc42 100644 --- a/src/CommonLib/ILdapUtils.cs +++ b/src/CommonLib/ILdapUtils.cs @@ -88,6 +88,8 @@ IAsyncEnumerable> RangedRetrieval(string distinguishedName, /// The Domain object /// True if the domain was found, false if not bool GetDomain(out System.DirectoryServices.ActiveDirectory.Domain domain); + + Task<(bool Success, string ForestName)> GetForest(string domain); /// /// Attempts to resolve an account name to its corresponding typed principal /// diff --git a/src/CommonLib/LdapUtils.cs b/src/CommonLib/LdapUtils.cs index 8877120a..57146120 100644 --- a/src/CommonLib/LdapUtils.cs +++ b/src/CommonLib/LdapUtils.cs @@ -664,7 +664,7 @@ await _connectionPool.GetLdapConnectionForServer( LDAPFilter = new LdapFilter().AddAllObjects().GetFilter(), }; - var result = await Query(queryParameters).FirstAsync(); + var result = await Query(queryParameters).DefaultIfEmpty(LdapResult.Fail()).FirstOrDefaultAsync(); if (result.IsSuccess && result.Value.TryGetProperty(LDAPProperties.RootDomainNamingContext, out var rootNamingContext)) { return (true, Helpers.DistinguishedNameToDomain(rootNamingContext).ToUpper()); @@ -1296,6 +1296,9 @@ public ActiveDirectorySecurityDescriptor MakeSecurityDescriptor() { } public async Task IsDomainController(string computerObjectId, string domainName) { + if (DomainControllers.ContainsKey(computerObjectId)) { + return true; + } var resDomain = await GetDomainNameFromSid(domainName) is (false, var tempDomain) ? tempDomain : domainName; var filter = new LdapFilter().AddFilter(CommonFilters.SpecificSID(computerObjectId), true) .AddFilter(CommonFilters.DomainControllers, true); @@ -1304,6 +1307,9 @@ public async Task IsDomainController(string computerObjectId, string domai Attributes = CommonProperties.ObjectID, LDAPFilter = filter.GetFilter(), }).DefaultIfEmpty(LdapResult.Fail()).FirstOrDefaultAsync(); + if (result.IsSuccess) { + DomainControllers.TryAdd(computerObjectId, new byte()); + } return result.IsSuccess; } diff --git a/src/CommonLib/SharpHoundCommonLib.csproj b/src/CommonLib/SharpHoundCommonLib.csproj index d11f23a7..f8e12c2f 100644 --- a/src/CommonLib/SharpHoundCommonLib.csproj +++ b/src/CommonLib/SharpHoundCommonLib.csproj @@ -21,7 +21,6 @@ - diff --git a/test/unit/AsyncEnumerableTests.cs b/test/unit/AsyncEnumerableTests.cs new file mode 100644 index 00000000..33664030 --- /dev/null +++ b/test/unit/AsyncEnumerableTests.cs @@ -0,0 +1,92 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using SharpHoundCommonLib; +using Xunit; + +namespace CommonLibTest; + +public class AsyncEnumerableTests { + [Fact] + public async Task AsyncEnumerable_DefaultIfEmpty_Empty() { + var enumerable = AsyncEnumerable.Empty().DefaultIfEmpty(1); + var e = enumerable.GetAsyncEnumerator(); + var res = await e.MoveNextAsync(); + Assert.True(res); + Assert.Equal(1, e.Current); + Assert.False(await e.MoveNextAsync()); + } + + [Fact] + public async Task AsyncEnumerable_FirstOrDefault() { + var enumerable = AsyncEnumerable.Empty(); + var res = await enumerable.FirstOrDefaultAsync(); + Assert.Equal(0, res); + } + + [Fact] + public async Task AsyncEnumerable_FirstOrDefault_WithDefault() { + var enumerable = AsyncEnumerable.Empty(); + var res = await enumerable.FirstOrDefaultAsync(10); + Assert.Equal(10, res); + } + + [Fact] + public async Task AsyncEnumerable_CombinedOperators() { + var enumerable = AsyncEnumerable.Empty(); + var res = await enumerable.DefaultIfEmpty("abc").FirstOrDefaultAsync(); + Assert.Equal("abc", res); + } + + [Fact] + public async Task AsyncEnumerable_ToAsyncEnumerable() { + var collection = new[] { + "a", "b", "c" + }; + + var test = collection.ToAsyncEnumerable(); + + var index = 0; + await foreach (var item in test) { + Assert.Equal(collection[index], item); + index++; + } + } + + [Fact] + public async Task AsyncEnumerable_FirstOrDefaultFunction() { + var test = await TestFunc().FirstOrDefaultAsync(); + Assert.Equal("a", test); + } + + [Fact] + public async Task AsyncEnumerable_CombinedFunction() { + var test = await TestFunc().DefaultIfEmpty("d").FirstOrDefaultAsync(); + Assert.Equal("a", test); + } + + [Fact] + public async Task AsyncEnumerable_FirstOrDefaultEmptyFunction() { + var test = await EmptyFunc().FirstOrDefaultAsync(); + Assert.Null(test); + } + + [Fact] + public async Task AsyncEnumerable_CombinedEmptyFunction() { + var test = await EmptyFunc().DefaultIfEmpty("d").FirstOrDefaultAsync(); + Assert.Equal("d", test); + } + + private async IAsyncEnumerable TestFunc() { + var collection = new[] { + "a", "b", "c" + }; + + foreach (var i in collection) { + yield return i; + } + } + + private async IAsyncEnumerable EmptyFunc() { + yield break; + } +} \ No newline at end of file diff --git a/test/unit/Facades/MockLdapUtils.cs b/test/unit/Facades/MockLdapUtils.cs index 964a416a..6b854a57 100644 --- a/test/unit/Facades/MockLdapUtils.cs +++ b/test/unit/Facades/MockLdapUtils.cs @@ -671,7 +671,7 @@ public virtual IAsyncEnumerable> RangedRetrieval(string distingui public async Task<(bool Success, TypedPrincipal WellKnownPrincipal)> GetWellKnownPrincipal(string securityIdentifier, string objectDomain) { if (!WellKnownPrincipal.GetWellKnownPrincipal(securityIdentifier, out var commonPrincipal)) return (false, default); - commonPrincipal.ObjectIdentifier = ConvertWellKnownPrincipal(securityIdentifier, objectDomain); + commonPrincipal.ObjectIdentifier = await ConvertWellKnownPrincipal(securityIdentifier, objectDomain); _seenWellKnownPrincipals.TryAdd(commonPrincipal.ObjectIdentifier, securityIdentifier); return (true, commonPrincipal); } @@ -747,13 +747,13 @@ public bool GetDomain(out Domain domain) { throw new NotImplementedException(); } - public string ConvertWellKnownPrincipal(string sid, string domain) + public async Task ConvertWellKnownPrincipal(string sid, string domain) { if (!WellKnownPrincipal.GetWellKnownPrincipal(sid, out _)) return sid; if (sid != "S-1-5-9") return $"{domain}-{sid}".ToUpper(); - var forest = GetForest(domain)?.Name; + var (success, forest) = await GetForest(domain); return $"{forest}-{sid}".ToUpper(); } @@ -1052,9 +1052,9 @@ Task ILdapUtils.IsDomainController(string computerObjectId, string domainN throw new NotImplementedException(); } - public Forest GetForest(string domainName = null) + public async Task<(bool Success, string ForestName)> GetForest(string domainName = null) { - return _forest; + return (true, _forest.Name); } public ActiveDirectorySecurityDescriptor MakeSecurityDescriptor() diff --git a/test/unit/Utils.cs b/test/unit/Utils.cs index 84f9799e..10e9d90a 100644 --- a/test/unit/Utils.cs +++ b/test/unit/Utils.cs @@ -24,6 +24,15 @@ internal static string B64ToString(string base64) internal static class Extensions { + public static async Task ToArrayAsync(this IAsyncEnumerable items) + { + var results = new List(); + await foreach (var item in items + .ConfigureAwait(false)) + results.Add(item); + return results.ToArray(); + } + internal static bool IsArray(this object obj) { var valueType = obj?.GetType(); @@ -31,6 +40,47 @@ internal static bool IsArray(this object obj) return false; return valueType.IsArray; } + + internal static IAsyncEnumerable ToAsyncEnumerable(this IEnumerable source) { + return source switch { + ICollection collection => new IAsyncEnumerableCollectionAdapter(collection), + _ => null + }; + } + + private sealed class IAsyncEnumerableCollectionAdapter : IAsyncEnumerable { + private readonly IAsyncEnumerator _enumerator; + + public IAsyncEnumerableCollectionAdapter(ICollection source) { + _enumerator = new IAsyncEnumeratorCollectionAdapter(source); + } + public IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = new CancellationToken()) { + return _enumerator; + } + } + + private sealed class IAsyncEnumeratorCollectionAdapter : IAsyncEnumerator { + private readonly IEnumerable _source; + private IEnumerator _enumerator; + + public IAsyncEnumeratorCollectionAdapter(ICollection source) { + _source = source; + } + + public ValueTask DisposeAsync() { + _enumerator = null; + return ValueTask.CompletedTask; + } + + public ValueTask MoveNextAsync() { + if (_enumerator == null) { + _enumerator = _source.GetEnumerator(); + } + return ValueTask.FromResult(_enumerator.MoveNext()); + } + + public T Current => _enumerator.Current; + } } public sealed class WindowsOnlyFact : FactAttribute From 4b7c7399ec1525d93cdadfd4f4ed6a36cbaf3d09 Mon Sep 17 00:00:00 2001 From: rvazarkar Date: Tue, 23 Jul 2024 17:31:04 -0400 Subject: [PATCH 58/68] chore: fix incorrect log --- src/CommonLib/LdapUtils.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CommonLib/LdapUtils.cs b/src/CommonLib/LdapUtils.cs index 57146120..539085b2 100644 --- a/src/CommonLib/LdapUtils.cs +++ b/src/CommonLib/LdapUtils.cs @@ -1191,7 +1191,7 @@ public bool GetDomain(out Domain domain) { if (!result.IsSuccess) { _log.LogWarning( "Could not find certificate template with {PropertyName}:{PropertyValue}: {Error}", - propertyName, propertyName, result.Error); + propertyName, propertyValue, result.Error); return (false, null); } From 7d9a9baa1322167d0f461897ad8c2c8ba1be1983 Mon Sep 17 00:00:00 2001 From: rvazarkar Date: Tue, 23 Jul 2024 17:51:54 -0400 Subject: [PATCH 59/68] chore: drop log verbosity for reg key opening --- src/CommonLib/Helpers.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/CommonLib/Helpers.cs b/src/CommonLib/Helpers.cs index f3400cdb..63d4c466 100644 --- a/src/CommonLib/Helpers.cs +++ b/src/CommonLib/Helpers.cs @@ -264,19 +264,19 @@ public static RegistryResult GetRegistryKeyData(string target, string subkey, st data.Collected = true; } catch (IOException e) { - log.LogError(e, "Error getting data from registry for {Target}: {RegSubKey}:{RegValue}", + log.LogDebug(e, "Error getting data from registry for {Target}: {RegSubKey}:{RegValue}", target, subkey, subvalue); data.FailureReason = "Target machine was not found or not connectable"; } catch (SecurityException e) { - log.LogError(e, "Error getting data from registry for {Target}: {RegSubKey}:{RegValue}", + log.LogDebug(e, "Error getting data from registry for {Target}: {RegSubKey}:{RegValue}", target, subkey, subvalue); data.FailureReason = "User does not have the proper permissions to perform this operation"; } catch (UnauthorizedAccessException e) { - log.LogError(e, "Error getting data from registry for {Target}: {RegSubKey}:{RegValue}", + log.LogDebug(e, "Error getting data from registry for {Target}: {RegSubKey}:{RegValue}", target, subkey, subvalue); data.FailureReason = "User does not have the necessary registry rights"; } catch (Exception e) { - log.LogError(e, "Error getting data from registry for {Target}: {RegSubKey}:{RegValue}", + log.LogDebug(e, "Error getting data from registry for {Target}: {RegSubKey}:{RegValue}", target, subkey, subvalue); data.FailureReason = e.Message; } From cab3c1573743f190ac7b24a8ef31106cd3be6b2a Mon Sep 17 00:00:00 2001 From: rvazarkar Date: Wed, 24 Jul 2024 14:28:26 -0400 Subject: [PATCH 60/68] chore: some logging updates --- src/CommonLib/LdapUtils.cs | 2 +- src/CommonLib/Processors/ACLProcessor.cs | 70 +++--- .../Processors/CertAbuseProcessor.cs | 21 +- .../Processors/ComputerAvailability.cs | 11 +- .../Processors/ComputerSessionProcessor.cs | 203 +++++++----------- .../Processors/ContainerProcessor.cs | 10 +- .../Processors/DomainTrustProcessor.cs | 3 +- src/CommonLib/Processors/GroupProcessor.cs | 61 +++--- src/CommonLib/Processors/RegistryResult.cs | 6 +- src/CommonLib/Processors/SPNProcessors.cs | 47 ++-- 10 files changed, 183 insertions(+), 251 deletions(-) diff --git a/src/CommonLib/LdapUtils.cs b/src/CommonLib/LdapUtils.cs index 539085b2..a7e0bf63 100644 --- a/src/CommonLib/LdapUtils.cs +++ b/src/CommonLib/LdapUtils.cs @@ -1277,7 +1277,7 @@ public ActiveDirectorySecurityDescriptor MakeSecurityDescriptor() { SecurityIdentifier sid, string computerDomainSid, string computerDomain) { if (!WellKnownPrincipal.GetWellKnownPrincipal(sid.Value, out var common)) return (false, null); - //The everyone and auth users principals are special and will be converted to the domain equivalent + //The "Everyone" and "Authenticated Users" principals are special and will be converted to the domain equivalent if (sid.Value is "S-1-1-0" or "S-1-5-11") { return await GetWellKnownPrincipal(sid.Value, computerDomain); } diff --git a/src/CommonLib/Processors/ACLProcessor.cs b/src/CommonLib/Processors/ACLProcessor.cs index dd563827..9eb72744 100644 --- a/src/CommonLib/Processors/ACLProcessor.cs +++ b/src/CommonLib/Processors/ACLProcessor.cs @@ -2,7 +2,6 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.DirectoryServices; -using System.Linq; using System.Security.AccessControl; using System.Security.Cryptography; using System.Security.Principal; @@ -12,7 +11,6 @@ using SharpHoundCommonLib.DirectoryObjects; using SharpHoundCommonLib.Enums; using SharpHoundCommonLib.OutputTypes; -using SearchScope = System.DirectoryServices.Protocols.SearchScope; namespace SharpHoundCommonLib.Processors { public class ACLProcessor { @@ -53,7 +51,7 @@ public ACLProcessor(ILdapUtils utils, ILogger log = null) { /// private async Task BuildGuidCache(string domain) { BuiltDomainCaches.Add(domain); - await foreach (var result in _utils.Query(new LdapQueryParameters() { + await foreach (var result in _utils.Query(new LdapQueryParameters { DomainName = domain, LDAPFilter = "(schemaIDGUID=*)", NamingContext = NamingContext.Schema, @@ -67,8 +65,11 @@ private async Task BuildGuidCache(string domain) { name = name.ToLower(); if (name is LDAPProperties.LAPSPassword or LDAPProperties.LegacyLAPSPassword) { + _log.LogDebug("Found GUID for ACL Right {Name}: {Guid} in domain {Domain}", name, guid, domain); GuidMap.TryAdd(guid, name); } + } else { + _log.LogDebug("Error while building GUID cache for {Domain}: {Message}", domain, result.Error); } } } @@ -123,6 +124,10 @@ public IAsyncEnumerable ProcessACL(ResolvedSearchResult result, IDirectoryO internal static string CalculateInheritanceHash(string identityReference, ActiveDirectoryRights rights, string aceType, string inheritedObjectType) { var hash = identityReference + rights + aceType + inheritedObjectType; + /* + * We're using MD5 because its fast and this data isn't cryptographically important. + * Additionally, the chances of a collision in our data size is miniscule and irrelevant. + */ using (var md5 = MD5.Create()) { var bytes = md5.ComputeHash(Encoding.UTF8.GetBytes(hash)); var builder = new StringBuilder(); @@ -159,7 +164,8 @@ public IEnumerable GetInheritedAceHashes(byte[] ntSecurityDescriptor, st if (ntSecurityDescriptor == null) { yield break; } - + + _log.LogDebug("Processing Inherited ACE hashes for {Name}", objectName); var descriptor = _utils.MakeSecurityDescriptor(); try { descriptor.SetSecurityDescriptorBinaryForm(ntSecurityDescriptor); @@ -181,7 +187,6 @@ public IEnumerable GetInheritedAceHashes(byte[] ntSecurityDescriptor, st //Skip aces for filtered principals if (principalSid == null) { - _log.LogTrace("Pre-Process excluded SID {SID} on {Name}", ir ?? "null", objectName); continue; } @@ -200,8 +205,8 @@ public IEnumerable GetInheritedAceHashes(byte[] ntSecurityDescriptor, st } /// - /// Read's the ntSecurityDescriptor from a SearchResultEntry and processes the ACEs in the ACL, filtering out ACEs that - /// BloodHound is not interested in + /// Read's a raw ntSecurityDescriptor and processes the ACEs in the ACL, filtering out ACEs that + /// BloodHound is not interested in as well as principals we don't care about /// /// /// @@ -230,7 +235,8 @@ public async IAsyncEnumerable ProcessACL(byte[] ntSecurityDescriptor, strin objectName); yield break; } - + + _log.LogDebug("Processing ACL for {ObjectName}", objectName); var ownerSid = Helpers.PreProcessSID(descriptor.GetOwner(typeof(SecurityIdentifier))); if (ownerSid != null) { @@ -241,38 +247,35 @@ public async IAsyncEnumerable ProcessACL(byte[] ntSecurityDescriptor, strin RightName = EdgeNames.Owns, IsInherited = false }; + } else { + _log.LogTrace("Failed to resolve owner for {Name}", objectName); + yield return new ACE { + PrincipalType = Label.Base, + PrincipalSID = ownerSid, + RightName = EdgeNames.Owns, + IsInherited = false + }; } - } else { - _log.LogDebug("Owner is null for {Name}", objectName); } - + foreach (var ace in descriptor.GetAccessRules(true, true, typeof(SecurityIdentifier))) { - if (ace == null) { - _log.LogTrace("Skipping null ACE for {Name}", objectName); - continue; - } - - if (ace.AccessControlType() == AccessControlType.Deny) { - _log.LogTrace("Skipping deny ACE for {Name}", objectName); - continue; - } - - if (!ace.IsAceInheritedFrom(BaseGuids[objectType])) { - _log.LogTrace("Skipping ACE with unmatched GUID/inheritance for {Name}", objectName); + if (ace == null || ace.AccessControlType() == AccessControlType.Deny || !ace.IsAceInheritedFrom(BaseGuids[objectType])) { continue; } var ir = ace.IdentityReference(); var principalSid = Helpers.PreProcessSID(ir); + //Preprocess returns null if this is an ignored sid if (principalSid == null) { - _log.LogTrace("Pre-Process excluded SID {SID} on {Name}", ir ?? "null", objectName); continue; } var (success, resolvedPrincipal) = await _utils.ResolveIDAndType(principalSid, objectDomain); if (!success) { - _log.LogDebug("Failed to resolve type for principal {Sid}", principalSid); + _log.LogTrace("Failed to resolve type for principal {Sid} on ACE for {Object}", principalSid, objectName); + resolvedPrincipal.ObjectIdentifier = principalSid; + resolvedPrincipal.ObjectType = Label.Base; } var aceRights = ace.ActiveDirectoryRights(); @@ -602,17 +605,12 @@ public async IAsyncEnumerable ProcessGMSAReaders(byte[] groupMSAMembership, } catch (OverflowException) { _log.LogWarning("GMSA ACL length on object {Name} exceeds allowable length. Unable to process", objectName); + yield break; } - - + + _log.LogDebug("Processing GMSA Readers for {ObjectName}", objectName); foreach (var ace in descriptor.GetAccessRules(true, true, typeof(SecurityIdentifier))) { - if (ace == null) { - _log.LogTrace("Skipping null GMSA ACE for {Name}", objectName); - continue; - } - - if (ace.AccessControlType() == AccessControlType.Deny) { - _log.LogTrace("Skipping deny GMSA ACE for {Name}", objectName); + if (ace == null || ace.AccessControlType() == AccessControlType.Deny) { continue; } @@ -620,14 +618,12 @@ public async IAsyncEnumerable ProcessGMSAReaders(byte[] groupMSAMembership, var principalSid = Helpers.PreProcessSID(ir); if (principalSid == null) { - _log.LogTrace("Pre-Process excluded SID {SID} on {Name}", ir ?? "null", objectName); continue; } _log.LogTrace("Processing GMSA ACE with principal {Principal}", principalSid); - var (success, resolvedPrincipal) = await _utils.ResolveIDAndType(principalSid, objectDomain); - if (success) { + if (await _utils.ResolveIDAndType(principalSid, objectDomain) is (true, var resolvedPrincipal)) { yield return new ACE { RightName = EdgeNames.ReadGMSAPassword, PrincipalType = resolvedPrincipal.ObjectType, diff --git a/src/CommonLib/Processors/CertAbuseProcessor.cs b/src/CommonLib/Processors/CertAbuseProcessor.cs index 3a03e944..667a4218 100644 --- a/src/CommonLib/Processors/CertAbuseProcessor.cs +++ b/src/CommonLib/Processors/CertAbuseProcessor.cs @@ -57,15 +57,14 @@ public async Task ProcessRegistryEnrollmentPermissions(str descriptor.SetSecurityDescriptorBinaryForm(aceData.Value as byte[], AccessControlSections.All); var ownerSid = Helpers.PreProcessSID(descriptor.GetOwner(typeof(SecurityIdentifier))); - var (success,computerDomain) = await _utils.GetDomainNameFromSid(computerObjectId); - var isDomainController = await _utils.IsDomainController(computerObjectId, computerDomain); + var isDomainController = await _utils.IsDomainController(computerObjectId, objectDomain); var machineSid = await GetMachineSid(computerName, computerObjectId); var aces = new List(); - if (ownerSid != null) - { - if (await GetRegistryPrincipal(new SecurityIdentifier(ownerSid), computerDomain, computerName, + if (ownerSid != null) { + var processed = new SecurityIdentifier(ownerSid); + if (await GetRegistryPrincipal(processed, objectDomain, computerName, isDomainController, computerObjectId, machineSid) is (true, var resolvedOwner)) { aces.Add(new ACE { @@ -74,8 +73,15 @@ public async Task ProcessRegistryEnrollmentPermissions(str RightName = EdgeNames.Owns, IsInherited = false }); + } else { + aces.Add(new ACE + { + PrincipalType = Label.Base, + PrincipalSID = processed.Value, + RightName = EdgeNames.Owns, + IsInherited = false + }); } - } else { @@ -96,7 +102,8 @@ public async Task ProcessRegistryEnrollmentPermissions(str var (getDomainSuccess, principalDomain) = await _utils.GetDomainNameFromSid(principalSid); if (!getDomainSuccess) { - + //Fallback to computer's domain in case we cant resolve the principal domain + principalDomain = objectDomain; } var (resSuccess, resolvedPrincipal) = await GetRegistryPrincipal(new SecurityIdentifier(principalSid), principalDomain, computerName, isDomainController, computerObjectId, machineSid); if (!resSuccess) diff --git a/src/CommonLib/Processors/ComputerAvailability.cs b/src/CommonLib/Processors/ComputerAvailability.cs index bc30d2bf..e87103a5 100644 --- a/src/CommonLib/Processors/ComputerAvailability.cs +++ b/src/CommonLib/Processors/ComputerAvailability.cs @@ -73,7 +73,7 @@ public async Task IsComputerAvailable(string computerName, strin { if (operatingSystem != null && !operatingSystem.StartsWith("Windows", StringComparison.OrdinalIgnoreCase)) { - _log.LogDebug("{ComputerName} is not available because operating system {OperatingSystem} is not valid", + _log.LogTrace("{ComputerName} is not available because operating system {OperatingSystem} is not valid", computerName, operatingSystem); await SendComputerStatus(new CSVComputerStatus { @@ -90,7 +90,7 @@ await SendComputerStatus(new CSVComputerStatus if (!_skipPasswordCheck && !IsComputerActive(pwdLastSet, lastLogon)) { - _log.LogDebug( + _log.LogTrace( "{ComputerName} is not available because password last set and lastlogontimestamp are out of range", computerName); await SendComputerStatus(new CSVComputerStatus @@ -112,11 +112,10 @@ await SendComputerStatus(new CSVComputerStatus Connectable = true, Error = null }; - - + if (!await _scanner.CheckPort(computerName, timeout: _scanTimeout)) { - _log.LogDebug("{ComputerName} is not available because port 445 is unavailable", computerName); + _log.LogTrace("{ComputerName} is not available because port 445 is unavailable", computerName); await SendComputerStatus(new CSVComputerStatus { Status = ComputerStatus.PortNotOpen, @@ -130,7 +129,7 @@ await SendComputerStatus(new CSVComputerStatus }; } - _log.LogDebug("{ComputerName} is available for enumeration", computerName); + _log.LogTrace("{ComputerName} is available for enumeration", computerName); await SendComputerStatus(new CSVComputerStatus { diff --git a/src/CommonLib/Processors/ComputerSessionProcessor.cs b/src/CommonLib/Processors/ComputerSessionProcessor.cs index e7659ba1..e0db075e 100644 --- a/src/CommonLib/Processors/ComputerSessionProcessor.cs +++ b/src/CommonLib/Processors/ComputerSessionProcessor.cs @@ -8,11 +8,10 @@ using Microsoft.Extensions.Logging; using Microsoft.Win32; using SharpHoundCommonLib.OutputTypes; +using SharpHoundRPC.NetAPINative; -namespace SharpHoundCommonLib.Processors -{ - public class ComputerSessionProcessor - { +namespace SharpHoundCommonLib.Processors { + public class ComputerSessionProcessor { public delegate Task ComputerStatusDelegate(CSVComputerStatus status); private static readonly Regex SidRegex = new(@"S-1-5-21-[0-9]+-[0-9]+-[0-9]+-[0-9]+$", RegexOptions.Compiled); @@ -24,8 +23,9 @@ public class ComputerSessionProcessor private readonly string _localAdminUsername; private readonly string _localAdminPassword; - public ComputerSessionProcessor(ILdapUtils utils, string currentUserName = null, NativeMethods nativeMethods = null, ILogger log = null, bool doLocalAdminSessionEnum = false, string localAdminUsername = null, string localAdminPassword = null) - { + public ComputerSessionProcessor(ILdapUtils utils, string currentUserName = null, + NativeMethods nativeMethods = null, ILogger log = null, bool doLocalAdminSessionEnum = false, + string localAdminUsername = null, string localAdminPassword = null) { _utils = utils; _nativeMethods = nativeMethods ?? new NativeMethods(); _currentUserName = currentUserName ?? WindowsIdentity.GetCurrent().Name.Split('\\')[1]; @@ -46,48 +46,44 @@ public ComputerSessionProcessor(ILdapUtils utils, string currentUserName = null, /// /// public async Task ReadUserSessions(string computerName, string computerSid, - string computerDomain) - { + string computerDomain) { var ret = new SessionAPIResult(); - SharpHoundRPC.NetAPINative.NetAPIResult> result; + NetAPIResult> result; + + _log.LogDebug("Running NetSessionEnum for {ObjectName}", computerName); - if (_doLocalAdminSessionEnum) - { + if (_doLocalAdminSessionEnum) { // If we are authenticating using a local admin, we need to impersonate for this - using (new Impersonator(_localAdminUsername, ".", _localAdminPassword, LogonType.LOGON32_LOGON_NEW_CREDENTIALS, LogonProvider.LOGON32_PROVIDER_WINNT50)) - { + using (new Impersonator(_localAdminUsername, ".", _localAdminPassword, + LogonType.LOGON32_LOGON_NEW_CREDENTIALS, LogonProvider.LOGON32_PROVIDER_WINNT50)) { result = _nativeMethods.NetSessionEnum(computerName); } - if (result.IsFailed) - { + if (result.IsFailed) { // Fall back to default User - _log.LogDebug("NetSessionEnum failed on {ComputerName} with local admin credentials: {Status}. Fallback to default user.", computerName, result.Status); + _log.LogDebug( + "NetSessionEnum failed on {ComputerName} with local admin credentials: {Status}. Fallback to default user.", + computerName, result.Status); result = _nativeMethods.NetSessionEnum(computerName); } - } - else - { + } else { result = _nativeMethods.NetSessionEnum(computerName); } - if (result.IsFailed) - { - await SendComputerStatus(new CSVComputerStatus - { + if (result.IsFailed) { + await SendComputerStatus(new CSVComputerStatus { Status = result.Status.ToString(), Task = "NetSessionEnum", ComputerName = computerName }); - _log.LogDebug("NetSessionEnum failed on {ComputerName}: {Status}", computerName, result.Status); + _log.LogTrace("NetSessionEnum failed on {ComputerName}: {Status}", computerName, result.Status); ret.Collected = false; ret.FailureReason = result.Status.ToString(); return ret; } - _log.LogDebug("NetSessionEnum succeeded on {ComputerName}", computerName); - await SendComputerStatus(new CSVComputerStatus - { + _log.LogTrace("NetSessionEnum succeeded on {ComputerName}", computerName); + await SendComputerStatus(new CSVComputerStatus { Status = CSVComputerStatus.StatusSuccess, Task = "NetSessionEnum", ComputerName = computerName @@ -96,8 +92,7 @@ await SendComputerStatus(new CSVComputerStatus ret.Collected = true; var results = new List(); - foreach (var sesInfo in result.Value) - { + foreach (var sesInfo in result.Value) { var username = sesInfo.Username; var computerSessionName = sesInfo.ComputerName; @@ -105,18 +100,14 @@ await SendComputerStatus(new CSVComputerStatus computerSessionName, computerName); //Filter out blank/null cnames/usernames - if (string.IsNullOrWhiteSpace(computerSessionName) || string.IsNullOrWhiteSpace(username)) - { - _log.LogTrace("Skipping NetSessionEnum entry with null session/user"); + if (string.IsNullOrWhiteSpace(computerSessionName) || string.IsNullOrWhiteSpace(username)) { continue; } //Filter out blank usernames, computer accounts, the user we're doing enumeration with, and anonymous logons if (username.EndsWith("$") || username.Equals(_currentUserName, StringComparison.CurrentCultureIgnoreCase) || - username.Equals("anonymous logon", StringComparison.CurrentCultureIgnoreCase)) - { - _log.LogTrace("Skipping NetSessionEnum entry for {Username}", username); + username.Equals("anonymous logon", StringComparison.CurrentCultureIgnoreCase)) { continue; } @@ -132,24 +123,18 @@ await SendComputerStatus(new CSVComputerStatus resolvedComputerSID = tempSid; //Throw out this data if we couldn't resolve it successfully. - if (resolvedComputerSID == null || !resolvedComputerSID.StartsWith("S-1")) - { - _log.LogTrace("Unable to resolve {ComputerSessionName} to real SID", computerSessionName); + if (resolvedComputerSID == null || !resolvedComputerSID.StartsWith("S-1")) { continue; } var (matchSuccess, sids) = await _utils.GetGlobalCatalogMatches(username, computerDomain); - if (matchSuccess) - { + if (matchSuccess) { results.AddRange( - sids.Select(s => new Session {ComputerSID = resolvedComputerSID, UserSID = s})); - } - else - { + sids.Select(s => new Session { ComputerSID = resolvedComputerSID, UserSID = s })); + } else { var res = await _utils.ResolveAccountName(username, computerDomain); if (res.Success) - results.Add(new Session - { + results.Add(new Session { ComputerSID = resolvedComputerSID, UserSID = res.Principal.ObjectIdentifier }); @@ -170,48 +155,45 @@ await SendComputerStatus(new CSVComputerStatus /// /// public async Task ReadUserSessionsPrivileged(string computerName, - string computerSamAccountName, string computerSid) - { + string computerSamAccountName, string computerSid) { var ret = new SessionAPIResult(); - SharpHoundRPC.NetAPINative.NetAPIResult> result; + NetAPIResult> + result; + + _log.LogDebug("Running NetWkstaUserEnum for {ObjectName}", computerName); - if (_doLocalAdminSessionEnum) - { + if (_doLocalAdminSessionEnum) { // If we are authenticating using a local admin, we need to impersonate for this - using (new Impersonator(_localAdminUsername, ".", _localAdminPassword, LogonType.LOGON32_LOGON_NEW_CREDENTIALS, LogonProvider.LOGON32_PROVIDER_WINNT50)) - { + using (new Impersonator(_localAdminUsername, ".", _localAdminPassword, + LogonType.LOGON32_LOGON_NEW_CREDENTIALS, LogonProvider.LOGON32_PROVIDER_WINNT50)) { result = _nativeMethods.NetWkstaUserEnum(computerName); } - if (result.IsFailed) - { + if (result.IsFailed) { // Fall back to default User - _log.LogDebug("NetWkstaUserEnum failed on {ComputerName} with local admin credentials: {Status}. Fallback to default user.", computerName, result.Status); + _log.LogDebug( + "NetWkstaUserEnum failed on {ComputerName} with local admin credentials: {Status}. Fallback to default user.", + computerName, result.Status); result = _nativeMethods.NetWkstaUserEnum(computerName); } - } - else - { + } else { result = _nativeMethods.NetWkstaUserEnum(computerName); } - if (result.IsFailed) - { - await SendComputerStatus(new CSVComputerStatus - { + if (result.IsFailed) { + await SendComputerStatus(new CSVComputerStatus { Status = result.Status.ToString(), Task = "NetWkstaUserEnum", ComputerName = computerName }); - _log.LogDebug("NetWkstaUserEnum failed on {ComputerName}: {Status}", computerName, result.Status); + _log.LogTrace("NetWkstaUserEnum failed on {ComputerName}: {Status}", computerName, result.Status); ret.Collected = false; ret.FailureReason = result.Status.ToString(); return ret; } - _log.LogDebug("NetWkstaUserEnum succeeded on {ComputerName}", computerName); - await SendComputerStatus(new CSVComputerStatus - { + _log.LogTrace("NetWkstaUserEnum succeeded on {ComputerName}", computerName); + await SendComputerStatus(new CSVComputerStatus { Status = result.Status.ToString(), Task = "NetWkstaUserEnum", ComputerName = computerName @@ -220,52 +202,31 @@ await SendComputerStatus(new CSVComputerStatus ret.Collected = true; var results = new List(); - foreach (var wkstaUserInfo in result.Value) - { + foreach (var wkstaUserInfo in result.Value) { var domain = wkstaUserInfo.LogonDomain; var username = wkstaUserInfo.Username; - - _log.LogTrace("NetWkstaUserEnum entry: {Username}@{Domain} from {ComputerName}", username, domain, - computerName); - - //These are local computer accounts. - if (domain.Equals(computerSamAccountName, StringComparison.CurrentCultureIgnoreCase)) - { - _log.LogTrace("Skipping local entry {Username}@{Domain}", username, domain); + + //If we dont have a domain or the domain has a space, ignore this object as it's unusable for us + if (string.IsNullOrWhiteSpace(domain) || domain.Contains(" ")) { continue; } - //Filter out empty usernames and computer sessions - if (string.IsNullOrWhiteSpace(username) || username.EndsWith("$", StringComparison.Ordinal)) - { - _log.LogTrace("Skipping null or computer session"); + //These are local computer accounts. + if (domain.Equals(computerSamAccountName, StringComparison.CurrentCultureIgnoreCase)) { continue; } - //If we dont have a domain, ignore this object - if (string.IsNullOrWhiteSpace(domain)) - { - _log.LogTrace("Skipping null/empty domain"); + //Filter out empty usernames and computer sessions + if (string.IsNullOrWhiteSpace(username) || username.EndsWith("$", StringComparison.Ordinal)) { continue; } - //Any domain with a space is unusable. It'll be things like NT Authority or Font Driver - if (domain.Contains(" ")) - { - _log.LogTrace("Skipping domain with space: {Domain}", domain); - continue; + if (await _utils.ResolveAccountName(username, domain) is (true, var res)) { + results.Add(res); } - - var (success, res) = await _utils.ResolveAccountName(username, domain); - if (!success) - continue; - - _log.LogTrace("Resolved NetWkstaUserEnum entry: {SID}", res.ObjectIdentifier); - results.Add(res); } - ret.Results = results.Select(x => new Session - { + ret.Results = results.Select(x => new Session { ComputerSID = computerSid, UserSID = x.ObjectIdentifier }).ToArray(); @@ -274,40 +235,37 @@ await SendComputerStatus(new CSVComputerStatus } public async Task ReadUserSessionsRegistry(string computerName, string computerDomain, - string computerSid) - { + string computerSid) { var ret = new SessionAPIResult(); + + _log.LogDebug("Running RegSessionEnum for {ObjectName}", computerName); RegistryKey key = null; - try - { + try { var task = OpenRegistryKey(computerName, RegistryHive.Users); - - if (await Task.WhenAny(task, Task.Delay(10000)) != task) - { + + if (await Task.WhenAny(task, Task.Delay(10000)) != task) { _log.LogDebug("Hit timeout on registry enum on {Server}. Abandoning registry enum", computerName); ret.Collected = false; ret.FailureReason = "Timeout"; - await SendComputerStatus(new CSVComputerStatus - { + await SendComputerStatus(new CSVComputerStatus { Status = "Timeout", Task = "RegistrySessionEnum", ComputerName = computerName }); return ret; } - + key = task.Result; ret.Collected = true; - await SendComputerStatus(new CSVComputerStatus - { + await SendComputerStatus(new CSVComputerStatus { Status = CSVComputerStatus.StatusSuccess, Task = "RegistrySessionEnum", ComputerName = computerName }); - _log.LogDebug("Registry session enum succeeded on {ComputerName}", computerName); + _log.LogTrace("Registry session enum succeeded on {ComputerName}", computerName); var results = new List(); foreach (var subkey in key.GetSubKeyNames()) { if (!SidRegex.IsMatch(subkey)) { @@ -325,12 +283,9 @@ await SendComputerStatus(new CSVComputerStatus ret.Results = results.ToArray(); return ret; - } - catch (Exception e) - { - _log.LogDebug("Registry session enum failed on {ComputerName}: {Status}", computerName, e.Message); - await SendComputerStatus(new CSVComputerStatus - { + } catch (Exception e) { + _log.LogTrace("Registry session enum failed on {ComputerName}: {Status}", computerName, e.Message); + await SendComputerStatus(new CSVComputerStatus { Status = e.Message, Task = "RegistrySessionEnum", ComputerName = computerName @@ -338,21 +293,17 @@ await SendComputerStatus(new CSVComputerStatus ret.Collected = false; ret.FailureReason = e.Message; return ret; - } - finally - { + } finally { key?.Dispose(); } } - private Task OpenRegistryKey(string computerName, RegistryHive hive) - { + private static Task OpenRegistryKey(string computerName, RegistryHive hive) { return Task.Run(() => RegistryKey.OpenRemoteBaseKey(hive, computerName)); } - private async Task SendComputerStatus(CSVComputerStatus status) - { + private async Task SendComputerStatus(CSVComputerStatus status) { if (ComputerStatusEvent is not null) await ComputerStatusEvent.Invoke(status); } } -} +} \ No newline at end of file diff --git a/src/CommonLib/Processors/ContainerProcessor.cs b/src/CommonLib/Processors/ContainerProcessor.cs index 267abd7f..fcad05c7 100644 --- a/src/CommonLib/Processors/ContainerProcessor.cs +++ b/src/CommonLib/Processors/ContainerProcessor.cs @@ -40,6 +40,7 @@ private static bool IsDistinguishedNameFiltered(string distinguishedName) public async Task<(bool Success, TypedPrincipal principal)> GetContainingObject(IDirectoryObject entry) { if (entry.TryGetDistinguishedName(out var dn)) { + _log.LogTrace("Reading containing object for {DN}", dn); return await GetContainingObject(dn); } @@ -56,15 +57,12 @@ private static bool IsDistinguishedNameFiltered(string distinguishedName) { var containerDn = Helpers.RemoveDistinguishedNamePrefix(distinguishedName); + //If the container is the builtin container, we want to redirect the containing object to the domain of the object if (containerDn.StartsWith("CN=BUILTIN", StringComparison.OrdinalIgnoreCase)) { + //This is always safe var domain = Helpers.DistinguishedNameToDomain(distinguishedName); - var (success, domainSid) = await _utils.GetDomainSidFromDomainName(domain); - if (success) { - return (true, new TypedPrincipal(domainSid, Label.Domain)); - } - - return (false, default); + return (true, new TypedPrincipal(domain, Label.Domain)); } return await _utils.ResolveDistinguishedName(containerDn); diff --git a/src/CommonLib/Processors/DomainTrustProcessor.cs b/src/CommonLib/Processors/DomainTrustProcessor.cs index d9c9d3bc..52c89786 100644 --- a/src/CommonLib/Processors/DomainTrustProcessor.cs +++ b/src/CommonLib/Processors/DomainTrustProcessor.cs @@ -26,6 +26,7 @@ public DomainTrustProcessor(ILdapUtils utils, ILogger log = null) /// public async IAsyncEnumerable EnumerateDomainTrusts(string domain) { + _log.LogDebug("Running trust enumeration for {Domain}", domain); await foreach (var result in _utils.Query(new LdapQueryParameters { LDAPFilter = CommonFilters.TrustedDomains, Attributes = CommonProperties.DomainTrustProps, @@ -39,7 +40,7 @@ public async IAsyncEnumerable EnumerateDomainTrusts(string domain) var entry = result.Value; var trust = new DomainTrust(); if (!entry.TryGetByteProperty(LDAPProperties.SecurityIdentifier, out var targetSidBytes) || targetSidBytes.Length == 0) { - _log.LogTrace("Trust sid is null or empty for target: {Domain}", domain); + _log.LogDebug("Trust sid is null or empty for target: {Domain}", domain); continue; } diff --git a/src/CommonLib/Processors/GroupProcessor.cs b/src/CommonLib/Processors/GroupProcessor.cs index 7fc5a2ce..8df10b30 100644 --- a/src/CommonLib/Processors/GroupProcessor.cs +++ b/src/CommonLib/Processors/GroupProcessor.cs @@ -6,25 +6,22 @@ using SharpHoundCommonLib.Enums; using SharpHoundCommonLib.OutputTypes; -namespace SharpHoundCommonLib.Processors -{ - public class GroupProcessor - { +namespace SharpHoundCommonLib.Processors { + public class GroupProcessor { private readonly ILogger _log; private readonly ILdapUtils _utils; - public GroupProcessor(ILdapUtils utils, ILogger log = null) - { + public GroupProcessor(ILdapUtils utils, ILogger log = null) { _utils = utils; _log = log ?? Logging.LogProvider.CreateLogger("GroupProc"); } - - public IAsyncEnumerable ReadGroupMembers(ResolvedSearchResult result, IDirectoryObject entry) - { - if (entry.TryGetArrayProperty(LDAPProperties.Members, out var members) && entry.TryGetDistinguishedName(out var dn)) { + + public IAsyncEnumerable ReadGroupMembers(ResolvedSearchResult result, IDirectoryObject entry) { + if (entry.TryGetArrayProperty(LDAPProperties.Members, out var members) && + entry.TryGetDistinguishedName(out var dn)) { return ReadGroupMembers(dn, members, result.DisplayName); } - + return AsyncEnumerable.Empty(); } @@ -36,41 +33,37 @@ public IAsyncEnumerable ReadGroupMembers(ResolvedSearchResult re /// /// public async IAsyncEnumerable ReadGroupMembers(string distinguishedName, string[] members, - string objectName = "") - { + string objectName = "") { + _log.LogDebug("Running Group Membership Enumeration for {ObjectName}", objectName); // If our returned array has a length of 0, one of two things is happening // The first possibility we'll look at is we need to use ranged retrieval, because AD will not return // more than a certain number of items. If we get nothing back from this, then the group is empty - if (members.Length == 0) - { - _log.LogTrace("Member property for {ObjectName} is empty, trying range retrieval", + if (members.Length == 0) { + _log.LogDebug("Member property for {ObjectName} is empty, trying range retrieval", objectName); - await foreach (var result in _utils.RangedRetrieval(distinguishedName, "member")) - { + await foreach (var result in _utils.RangedRetrieval(distinguishedName, "member")) { if (!result.IsSuccess) { + _log.LogDebug("Failure during ranged retrieval for {ObjectName}: {Message}", objectName, result.Error); yield break; } var member = result.Value; _log.LogTrace("Got member {DN} for {ObjectName} from ranged retrieval", member, objectName); - if (await _utils.ResolveDistinguishedName(member) is (true, var res) && !Helpers.IsSidFiltered(res.ObjectIdentifier)) { + if (await _utils.ResolveDistinguishedName(member) is (true, var res) && + !Helpers.IsSidFiltered(res.ObjectIdentifier)) { yield return res; - } - else { + } else { yield return new TypedPrincipal(member.ToUpper(), Label.Base); } } - } - else - { + } else { //If we're here, we just read the data directly and life is good - foreach (var member in members) - { + foreach (var member in members) { _log.LogTrace("Got member {DN} for {ObjectName}", member, objectName); - if (await _utils.ResolveDistinguishedName(member) is (true, var res) && !Helpers.IsSidFiltered(res.ObjectIdentifier)) { + if (await _utils.ResolveDistinguishedName(member) is (true, var res) && + !Helpers.IsSidFiltered(res.ObjectIdentifier)) { yield return res; - } - else { + } else { yield return new TypedPrincipal(member.ToUpper(), Label.Base); } } @@ -83,22 +76,18 @@ public async IAsyncEnumerable ReadGroupMembers(string distinguis /// /// /// - public static string GetPrimaryGroupInfo(string primaryGroupId, string objectId) - { + public static string GetPrimaryGroupInfo(string primaryGroupId, string objectId) { if (primaryGroupId == null) return null; if (objectId == null) return null; - try - { + try { var domainSid = new SecurityIdentifier(objectId).AccountDomainSid.Value; var primaryGroupSid = $"{domainSid}-{primaryGroupId}"; return primaryGroupSid; - } - catch - { + } catch { return null; } } diff --git a/src/CommonLib/Processors/RegistryResult.cs b/src/CommonLib/Processors/RegistryResult.cs index 205e2346..18ec9d9d 100644 --- a/src/CommonLib/Processors/RegistryResult.cs +++ b/src/CommonLib/Processors/RegistryResult.cs @@ -1,9 +1,7 @@ using SharpHoundCommonLib.OutputTypes; -namespace SharpHoundCommonLib.Processors -{ - public class RegistryResult : APIResult - { +namespace SharpHoundCommonLib.Processors { + public class RegistryResult : APIResult { public object Value { get; set; } } } \ No newline at end of file diff --git a/src/CommonLib/Processors/SPNProcessors.cs b/src/CommonLib/Processors/SPNProcessors.cs index 57b401e6..87661eab 100644 --- a/src/CommonLib/Processors/SPNProcessors.cs +++ b/src/CommonLib/Processors/SPNProcessors.cs @@ -4,54 +4,48 @@ using SharpHoundCommonLib.Enums; using SharpHoundCommonLib.OutputTypes; -namespace SharpHoundCommonLib.Processors -{ - public class SPNProcessors - { +namespace SharpHoundCommonLib.Processors { + public class SPNProcessors { private const string MSSQLSPNString = "mssqlsvc"; private readonly ILogger _log; private readonly ILdapUtils _utils; - public SPNProcessors(ILdapUtils utils, ILogger log = null) - { + public SPNProcessors(ILdapUtils utils, ILogger log = null) { _utils = utils; _log = log ?? Logging.LogProvider.CreateLogger("SPNProc"); } public IAsyncEnumerable ReadSPNTargets(ResolvedSearchResult result, - IDirectoryObject entry) - { - if (entry.TryGetArrayProperty(LDAPProperties.ServicePrincipalNames, out var members) && entry.TryGetDistinguishedName(out var dn)) { - return ReadSPNTargets(members, dn, result.DisplayName); + IDirectoryObject entry) { + if (entry.TryGetArrayProperty(LDAPProperties.ServicePrincipalNames, out var members) && + entry.TryGetDistinguishedName(out var dn)) { + return ReadSPNTargets(members, dn, result.DisplayName); } - + return AsyncEnumerable.Empty(); } public async IAsyncEnumerable ReadSPNTargets(string[] servicePrincipalNames, - string distinguishedName, string objectName = "") - { - if (servicePrincipalNames.Length == 0) - { + string distinguishedName, string objectName = "") { + if (servicePrincipalNames.Length == 0) { _log.LogTrace("SPN Array is empty for {Name}", objectName); yield break; } + + _log.LogDebug("Processing SPN targets for {ObjectName}", objectName); var domain = Helpers.DistinguishedNameToDomain(distinguishedName); - foreach (var spn in servicePrincipalNames) - { + foreach (var spn in servicePrincipalNames) { //This SPN format isn't useful for us right now (username@domain) - if (spn.Contains("@")) - { + if (spn.Contains("@")) { _log.LogTrace("Skipping spn without @ {SPN} for {Name}", spn, objectName); continue; } _log.LogTrace("Processing SPN {SPN} for {Name}", spn, objectName); - if (spn.ToLower().Contains(MSSQLSPNString)) - { + if (spn.ToLower().Contains(MSSQLSPNString)) { _log.LogTrace("Matched SQL SPN {SPN} for {Name}", spn, objectName); var port = 1433; @@ -59,15 +53,14 @@ public async IAsyncEnumerable ReadSPNTargets(string[] servicePrinc if (!int.TryParse(spn.Split(':')[1], out port)) port = 1433; - var host = await _utils.ResolveHostToSid(spn, domain); - _log.LogTrace("Resolved {SPN} to {Hostname}", spn, host); - if (host.Success && host.SecurityIdentifier.StartsWith("S-1-")) - yield return new SPNPrivilege - { - ComputerSID = host.SecurityIdentifier, + if (await _utils.ResolveHostToSid(spn, domain) is (true, var host) && host.StartsWith("S-1")) { + _log.LogTrace("Resolved {SPN} to {Hostname}", spn, host); + yield return new SPNPrivilege { + ComputerSID = host, Port = port, Service = EdgeNames.SQLAdmin }; + } } } } From 1b7652c3ffa2518ef4a3d2305c3e9fd79e1cbc1b Mon Sep 17 00:00:00 2001 From: rvazarkar Date: Wed, 24 Jul 2024 14:52:40 -0400 Subject: [PATCH 61/68] chore: revert bad change --- src/CommonLib/Processors/ContainerProcessor.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/CommonLib/Processors/ContainerProcessor.cs b/src/CommonLib/Processors/ContainerProcessor.cs index fcad05c7..4539d721 100644 --- a/src/CommonLib/Processors/ContainerProcessor.cs +++ b/src/CommonLib/Processors/ContainerProcessor.cs @@ -62,7 +62,11 @@ private static bool IsDistinguishedNameFiltered(string distinguishedName) { //This is always safe var domain = Helpers.DistinguishedNameToDomain(distinguishedName); - return (true, new TypedPrincipal(domain, Label.Domain)); + if (await _utils.GetDomainSidFromDomainName(domain) is (true, var domainSid)) { + return (true, new TypedPrincipal(domainSid, Label.Domain)); + } + + return (false, default); } return await _utils.ResolveDistinguishedName(containerDn); From 42b0b42e210c7f896b6fbea4d65408b93b7e714a Mon Sep 17 00:00:00 2001 From: rvazarkar Date: Thu, 25 Jul 2024 10:02:28 -0400 Subject: [PATCH 62/68] chore: cleanups --- src/CommonLib/Enums/DataTypes.cs | 12 ------------ src/CommonLib/Processors/LdapPropertyProcessor.cs | 11 +++-------- 2 files changed, 3 insertions(+), 20 deletions(-) delete mode 100644 src/CommonLib/Enums/DataTypes.cs diff --git a/src/CommonLib/Enums/DataTypes.cs b/src/CommonLib/Enums/DataTypes.cs deleted file mode 100644 index 4c3222c8..00000000 --- a/src/CommonLib/Enums/DataTypes.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace SharpHoundCommonLib.Enums -{ - public static class DataTypes - { - public const string Users = "users"; - public const string Groups = "groups"; - public const string Computers = "computers"; - public const string Domains = "domains"; - public const string GPOs = "gpos"; - public const string OUs = "ous"; - } -} \ No newline at end of file diff --git a/src/CommonLib/Processors/LdapPropertyProcessor.cs b/src/CommonLib/Processors/LdapPropertyProcessor.cs index c9dd882f..784b091b 100644 --- a/src/CommonLib/Processors/LdapPropertyProcessor.cs +++ b/src/CommonLib/Processors/LdapPropertyProcessor.cs @@ -20,7 +20,7 @@ public class LdapPropertyProcessor { .Concat(CommonProperties.ComputerMethodProps).Concat(CommonProperties.ACLProps) .Concat(CommonProperties.ObjectPropsProps).Concat(CommonProperties.ContainerProps) .Concat(CommonProperties.SPNTargetProps).Concat(CommonProperties.DomainTrustProps) - .Concat(CommonProperties.GPOLocalGroupProps).ToArray(); + .Concat(CommonProperties.GPOLocalGroupProps).Concat(CommonProperties.CertAbuseProps).ToArray(); private readonly ILdapUtils _utils; @@ -520,13 +520,8 @@ public async Task ReadIssuancePolicyProperties(IDirect /// public Dictionary ParseAllProperties(IDirectoryObject entry) { var props = new Dictionary(); - - var type = typeof(LDAPProperties); - var reserved = type.GetFields(BindingFlags.Static | BindingFlags.Public) - .Select(x => x.GetValue(null).ToString()).ToArray(); - foreach (var property in entry.PropertyNames()) { - if (reserved.Contains(property, StringComparer.OrdinalIgnoreCase)) + if (ReservedAttributes.Contains(property, StringComparer.OrdinalIgnoreCase)) continue; var collCount = entry.PropertyCount(property); @@ -537,7 +532,7 @@ public Dictionary ParseAllProperties(IDirectoryObject entry) { var testString = entry.GetProperty(property); if (!string.IsNullOrEmpty(testString)) - if (property == "badpasswordtime") + if (property.Equals("badpasswordtime", StringComparison.OrdinalIgnoreCase)) props.Add(property, Helpers.ConvertFileTimeToUnixEpoch(testString)); else props.Add(property, BestGuessConvert(testString)); From ba5420ea13144b4040edab76f01f8c5da3d4de7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20B=C3=BClow=20Knudsen?= <12843299+JonasBK@users.noreply.github.com> Date: Thu, 25 Jul 2024 19:49:50 +0200 Subject: [PATCH 63/68] feat: collector version number meta tag (#139) --- src/CommonLib/OutputTypes/MetaTag.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/CommonLib/OutputTypes/MetaTag.cs b/src/CommonLib/OutputTypes/MetaTag.cs index c11a4653..91158035 100644 --- a/src/CommonLib/OutputTypes/MetaTag.cs +++ b/src/CommonLib/OutputTypes/MetaTag.cs @@ -9,5 +9,6 @@ public class MetaTag [DataMember(Name = "type")] public string DataType { get; set; } [DataMember(Name = "count")] public long Count { get; set; } [DataMember(Name = "version")] public int Version { get; set; } + [DataMember(Name = "collectorversion")] public string CollectorVersion { get; set; } } } \ No newline at end of file From ec0725650e7a9a048a80034c50a9a2d8b80c0622 Mon Sep 17 00:00:00 2001 From: rvazarkar Date: Thu, 25 Jul 2024 14:03:25 -0400 Subject: [PATCH 64/68] chore: add some missing properties --- src/CommonLib/Enums/LDAPProperties.cs | 1 + src/CommonLib/LdapQueries/CommonProperties.cs | 4 ++++ src/CommonLib/Processors/SPNProcessors.cs | 11 ++++------- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/CommonLib/Enums/LDAPProperties.cs b/src/CommonLib/Enums/LDAPProperties.cs index 60212251..8b13359b 100644 --- a/src/CommonLib/Enums/LDAPProperties.cs +++ b/src/CommonLib/Enums/LDAPProperties.cs @@ -79,5 +79,6 @@ public static class LDAPProperties public const string DnsRoot = "dnsroot"; public const string ServerName = "servername"; public const string OU = "ou"; + public const string ProfilePath = "profilepath"; } } diff --git a/src/CommonLib/LdapQueries/CommonProperties.cs b/src/CommonLib/LdapQueries/CommonProperties.cs index 34661a00..54a59549 100644 --- a/src/CommonLib/LdapQueries/CommonProperties.cs +++ b/src/CommonLib/LdapQueries/CommonProperties.cs @@ -88,5 +88,9 @@ public static class CommonProperties LDAPProperties.CertificateApplicationPolicy, LDAPProperties.CertificatePolicy, LDAPProperties.IssuancePolicies, LDAPProperties.CrossCertificatePair, LDAPProperties.ApplicationPolicies, LDAPProperties.PKIPrivateKeyFlag, LDAPProperties.OIDGroupLink }; + + public static readonly string[] StealthProperties = { + LDAPProperties.HomeDirectory, LDAPProperties.ScriptPath, LDAPProperties.ProfilePath + }; } } \ No newline at end of file diff --git a/src/CommonLib/Processors/SPNProcessors.cs b/src/CommonLib/Processors/SPNProcessors.cs index 87661eab..b05221c2 100644 --- a/src/CommonLib/Processors/SPNProcessors.cs +++ b/src/CommonLib/Processors/SPNProcessors.cs @@ -17,16 +17,15 @@ public SPNProcessors(ILdapUtils utils, ILogger log = null) { public IAsyncEnumerable ReadSPNTargets(ResolvedSearchResult result, IDirectoryObject entry) { - if (entry.TryGetArrayProperty(LDAPProperties.ServicePrincipalNames, out var members) && - entry.TryGetDistinguishedName(out var dn)) { - return ReadSPNTargets(members, dn, result.DisplayName); + if (entry.TryGetArrayProperty(LDAPProperties.ServicePrincipalNames, out var members)) { + return ReadSPNTargets(members, result.Domain, result.DisplayName); } return AsyncEnumerable.Empty(); } public async IAsyncEnumerable ReadSPNTargets(string[] servicePrincipalNames, - string distinguishedName, string objectName = "") { + string domainName, string objectName = "") { if (servicePrincipalNames.Length == 0) { _log.LogTrace("SPN Array is empty for {Name}", objectName); yield break; @@ -34,8 +33,6 @@ public async IAsyncEnumerable ReadSPNTargets(string[] servicePrinc _log.LogDebug("Processing SPN targets for {ObjectName}", objectName); - var domain = Helpers.DistinguishedNameToDomain(distinguishedName); - foreach (var spn in servicePrincipalNames) { //This SPN format isn't useful for us right now (username@domain) if (spn.Contains("@")) { @@ -53,7 +50,7 @@ public async IAsyncEnumerable ReadSPNTargets(string[] servicePrinc if (!int.TryParse(spn.Split(':')[1], out port)) port = 1433; - if (await _utils.ResolveHostToSid(spn, domain) is (true, var host) && host.StartsWith("S-1")) { + if (await _utils.ResolveHostToSid(spn, domainName) is (true, var host) && host.StartsWith("S-1")) { _log.LogTrace("Resolved {SPN} to {Hostname}", spn, host); yield return new SPNPrivilege { ComputerSID = host, From d5e4bada0dbe0fc74b8ce02d79b66fde867b3345 Mon Sep 17 00:00:00 2001 From: rvazarkar Date: Thu, 25 Jul 2024 16:10:59 -0400 Subject: [PATCH 65/68] chore: visibility changes + remove old comment --- .../Exceptions/LDAPQueryException.cs | 19 ------------------- .../Exceptions/LdapAuthenticationException.cs | 2 +- .../Exceptions/LdapConnectionException.cs | 2 +- .../Exceptions/NoLdapDataException.cs | 2 +- src/CommonLib/LdapConnectionPool.cs | 6 +----- 5 files changed, 4 insertions(+), 27 deletions(-) delete mode 100644 src/CommonLib/Exceptions/LDAPQueryException.cs diff --git a/src/CommonLib/Exceptions/LDAPQueryException.cs b/src/CommonLib/Exceptions/LDAPQueryException.cs deleted file mode 100644 index b463a51e..00000000 --- a/src/CommonLib/Exceptions/LDAPQueryException.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; - -namespace SharpHoundCommonLib.Exceptions -{ - public class LDAPQueryException : Exception - { - public LDAPQueryException() - { - } - - public LDAPQueryException(string message) : base(message) - { - } - - public LDAPQueryException(string message, Exception inner) : base(message, inner) - { - } - } -} \ No newline at end of file diff --git a/src/CommonLib/Exceptions/LdapAuthenticationException.cs b/src/CommonLib/Exceptions/LdapAuthenticationException.cs index a628d959..ca3be839 100644 --- a/src/CommonLib/Exceptions/LdapAuthenticationException.cs +++ b/src/CommonLib/Exceptions/LdapAuthenticationException.cs @@ -3,7 +3,7 @@ namespace SharpHoundCommonLib.Exceptions { - public class LdapAuthenticationException : Exception + internal class LdapAuthenticationException : Exception { public readonly LdapException LdapException; public LdapAuthenticationException(LdapException exception) : base("Error authenticating to LDAP", exception) diff --git a/src/CommonLib/Exceptions/LdapConnectionException.cs b/src/CommonLib/Exceptions/LdapConnectionException.cs index 3d56c9e5..3bce86c4 100644 --- a/src/CommonLib/Exceptions/LdapConnectionException.cs +++ b/src/CommonLib/Exceptions/LdapConnectionException.cs @@ -3,7 +3,7 @@ namespace SharpHoundCommonLib.Exceptions { - public class LdapConnectionException : Exception + internal class LdapConnectionException : Exception { public int ErrorCode { get; } public LdapConnectionException(LdapException innerException) : base("Failed during ldap connection tests", innerException) diff --git a/src/CommonLib/Exceptions/NoLdapDataException.cs b/src/CommonLib/Exceptions/NoLdapDataException.cs index 628a0b1b..771f99da 100644 --- a/src/CommonLib/Exceptions/NoLdapDataException.cs +++ b/src/CommonLib/Exceptions/NoLdapDataException.cs @@ -2,7 +2,7 @@ namespace SharpHoundCommonLib.Exceptions { - public class NoLdapDataException : Exception + internal class NoLdapDataException : Exception { public NoLdapDataException() { diff --git a/src/CommonLib/LdapConnectionPool.cs b/src/CommonLib/LdapConnectionPool.cs index b7913034..69f44da7 100644 --- a/src/CommonLib/LdapConnectionPool.cs +++ b/src/CommonLib/LdapConnectionPool.cs @@ -364,8 +364,4 @@ private SearchRequest CreateSearchRequest(string distinguishedName, string ldapF return searchRequest; } } -} - -//TESTLAB -//TESTLAB.LOCAL -//PRIMARY.TESTLAB.LOCAL \ No newline at end of file +} \ No newline at end of file From f80bc17f8fdaad580501664ec42357bc2d487bd0 Mon Sep 17 00:00:00 2001 From: Hoshea <39255806+Argentix03@users.noreply.github.com> Date: Fri, 26 Jul 2024 16:47:12 +0300 Subject: [PATCH 66/68] Update WellKnownPrincipal.cs (#140) * Update WellKnownPrincipal.cs The correct type for this is user. this small thing made a huge confusion for some people today :( i think this was caused from copy pasting s-1-5-15 the one above it in a fix long ago. do not confuse IUSR type with IIS_IUSRS https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/manage/understand-security-identifiers * Update WellKnownPrincipalTest.cs --- src/CommonLib/WellKnownPrincipal.cs | 4 ++-- test/unit/WellKnownPrincipalTest.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/CommonLib/WellKnownPrincipal.cs b/src/CommonLib/WellKnownPrincipal.cs index 1d1ae363..46574c0f 100644 --- a/src/CommonLib/WellKnownPrincipal.cs +++ b/src/CommonLib/WellKnownPrincipal.cs @@ -44,7 +44,7 @@ public static bool GetWellKnownPrincipal(string sid, out TypedPrincipal commonPr "S-1-5-13" => new TypedPrincipal("Terminal Server Users", Label.Group), "S-1-5-14" => new TypedPrincipal("Remote Interactive Logon", Label.Group), "S-1-5-15" => new TypedPrincipal("This Organization", Label.Group), - "S-1-5-17" => new TypedPrincipal("IUSR", Label.Group), + "S-1-5-17" => new TypedPrincipal("IUSR", Label.User), "S-1-5-18" => new TypedPrincipal("Local System", Label.User), "S-1-5-19" => new TypedPrincipal("Local Service", Label.User), "S-1-5-20" => new TypedPrincipal("Network Service", Label.User), @@ -88,4 +88,4 @@ public static bool GetWellKnownPrincipal(string sid, out TypedPrincipal commonPr return commonPrincipal != null; } } -} \ No newline at end of file +} diff --git a/test/unit/WellKnownPrincipalTest.cs b/test/unit/WellKnownPrincipalTest.cs index d49a3f25..f8043c78 100644 --- a/test/unit/WellKnownPrincipalTest.cs +++ b/test/unit/WellKnownPrincipalTest.cs @@ -78,7 +78,7 @@ private static (string sid, string name, Label label)[] GetWellKnownPrincipals() ("S-1-5-13", "Terminal Server Users", Label.Group), ("S-1-5-14", "Remote Interactive Logon", Label.Group), ("S-1-5-15", "This Organization", Label.Group), - ("S-1-5-17", "IUSR", Label.Group), + ("S-1-5-17", "IUSR", Label.User), ("S-1-5-18", "Local System", Label.User), ("S-1-5-19", "Local Service", Label.User), ("S-1-5-20", "Network Service", Label.User), @@ -148,4 +148,4 @@ public void GetWellKnownPrincipal_NonWellKnown_ReturnsNull() #endregion } -} \ No newline at end of file +} From 8652c1af633c3cf2639cb6c6af384ff297c4b7c0 Mon Sep 17 00:00:00 2001 From: rvazarkar Date: Fri, 26 Jul 2024 09:52:38 -0400 Subject: [PATCH 67/68] chore: update Dockerfile chore: set default values for MockDirectoryObject --- Dockerfile | 2 +- test/unit/Facades/MockDirectoryObject.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 460809de..d641ee89 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ FROM mono:6.12.0 # Install .NET SDK -ENV DOTNET_VERSION=5.0 +ENV DOTNET_VERSION=7.0 RUN curl -sSL https://dot.net/v1/dotnet-install.sh \ | bash -s -- -Channel $DOTNET_VERSION -InstallDir /usr/share/dotnet \ diff --git a/test/unit/Facades/MockDirectoryObject.cs b/test/unit/Facades/MockDirectoryObject.cs index e0f2ccdd..db8d69fc 100644 --- a/test/unit/Facades/MockDirectoryObject.cs +++ b/test/unit/Facades/MockDirectoryObject.cs @@ -15,7 +15,7 @@ public class MockDirectoryObject : IDirectoryObject { public IDictionary Properties { get; set; } public string DistinguishedName { get; set; } - public MockDirectoryObject(string distinguishedName, IDictionary properties, string sid, string guid) { + public MockDirectoryObject(string distinguishedName, IDictionary properties, string sid = "", string guid = "") { DistinguishedName = distinguishedName; Properties = properties; _objectSID = sid; From b114fb9dbffbeec446e571f5385747969a4f0c29 Mon Sep 17 00:00:00 2001 From: rvazarkar Date: Fri, 26 Jul 2024 12:43:41 -0400 Subject: [PATCH 68/68] chore: change version to 4.0.0 --- src/CommonLib/SharpHoundCommonLib.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CommonLib/SharpHoundCommonLib.csproj b/src/CommonLib/SharpHoundCommonLib.csproj index f8e12c2f..3becbab7 100644 --- a/src/CommonLib/SharpHoundCommonLib.csproj +++ b/src/CommonLib/SharpHoundCommonLib.csproj @@ -9,7 +9,7 @@ Common library for C# BloodHound enumeration tasks GPL-3.0-only https://github.com/BloodHoundAD/SharpHoundCommon - 4.0.0-rc1 + 4.0.0 SharpHoundCommonLib SharpHoundCommonLib