From 076f41df747ae9d04a49e12e5933fd87c55aec7c Mon Sep 17 00:00:00 2001 From: Rohan Vazarkar Date: Mon, 9 Dec 2024 10:41:05 -0500 Subject: [PATCH 1/5] feat: initial NTLM commit --- src/CommonLib/Enums/CollectionMethod.cs | 12 +- src/CommonLib/Enums/LdapErrorCodes.cs | 4 +- src/CommonLib/Enums/LdapOption.cs | 14 + src/CommonLib/Enums/LdapOptionValue.cs | 15 + .../Enums/LdapSupportedSaslMechansims.cs | 16 + src/CommonLib/LdapConnectionPool.cs | 2 +- src/CommonLib/LdapProducerQueryGenerator.cs | 4 +- src/CommonLib/LdapUtils.cs | 224 +++--- src/CommonLib/Ntlm/HttpClientFactory.cs | 45 ++ .../Ntlm/HttpNtlmAuthenticationService.cs | 205 ++++++ src/CommonLib/Ntlm/HttpTransport.cs | 69 ++ src/CommonLib/Ntlm/INtlmTransport.cs | 10 + src/CommonLib/Ntlm/LdapException.cs | 16 + src/CommonLib/Ntlm/LdapNative.cs | 346 +++++++++ src/CommonLib/Ntlm/LdapTransport.cs | 119 +++ .../Ntlm/NtlmAuthenticationHandler.cs | 64 ++ src/CommonLib/OutputTypes/APIResult.cs | 46 +- src/CommonLib/OutputTypes/CAHttpEndpoint.cs | 67 ++ src/CommonLib/OutputTypes/Computer.cs | 56 +- src/CommonLib/OutputTypes/EnterpriseCA.cs | 1 + src/CommonLib/OutputTypes/NtlmSession.cs | 113 +++ .../Processors/CAEnrollmentProcessor.cs | 235 ++++++ .../Processors/ComputerSessionProcessor.cs | 2 +- src/CommonLib/Processors/DCLdapProcessor.cs | 291 ++++++++ src/CommonLib/Processors/EventLogProcessor.cs | 332 +++++++++ src/CommonLib/Processors/SmbProcessor.cs | 429 +++++++++++ .../UserRightsAssignmentProcessor.cs | 2 +- .../Processors/WebClientServiceProcessor.cs | 99 +++ src/CommonLib/Properties/launchSettings.json | 8 + src/CommonLib/SharpHoundCommonLib.csproj | 4 +- .../ThirdParty/PSOpenAD/Authentication.cs | 515 +++++++++++++ src/CommonLib/ThirdParty/PSOpenAD/SSPI.cs | 676 ++++++++++++++++++ 32 files changed, 3941 insertions(+), 100 deletions(-) create mode 100644 src/CommonLib/Enums/LdapOption.cs create mode 100644 src/CommonLib/Enums/LdapOptionValue.cs create mode 100644 src/CommonLib/Enums/LdapSupportedSaslMechansims.cs create mode 100644 src/CommonLib/Ntlm/HttpClientFactory.cs create mode 100644 src/CommonLib/Ntlm/HttpNtlmAuthenticationService.cs create mode 100644 src/CommonLib/Ntlm/HttpTransport.cs create mode 100644 src/CommonLib/Ntlm/INtlmTransport.cs create mode 100644 src/CommonLib/Ntlm/LdapException.cs create mode 100644 src/CommonLib/Ntlm/LdapNative.cs create mode 100644 src/CommonLib/Ntlm/LdapTransport.cs create mode 100644 src/CommonLib/Ntlm/NtlmAuthenticationHandler.cs create mode 100644 src/CommonLib/OutputTypes/CAHttpEndpoint.cs create mode 100644 src/CommonLib/OutputTypes/NtlmSession.cs create mode 100644 src/CommonLib/Processors/CAEnrollmentProcessor.cs create mode 100644 src/CommonLib/Processors/DCLdapProcessor.cs create mode 100644 src/CommonLib/Processors/EventLogProcessor.cs create mode 100644 src/CommonLib/Processors/SmbProcessor.cs create mode 100644 src/CommonLib/Processors/WebClientServiceProcessor.cs create mode 100644 src/CommonLib/Properties/launchSettings.json create mode 100644 src/CommonLib/ThirdParty/PSOpenAD/Authentication.cs create mode 100644 src/CommonLib/ThirdParty/PSOpenAD/SSPI.cs diff --git a/src/CommonLib/Enums/CollectionMethod.cs b/src/CommonLib/Enums/CollectionMethod.cs index 0b5959e1..76bbd402 100644 --- a/src/CommonLib/Enums/CollectionMethod.cs +++ b/src/CommonLib/Enums/CollectionMethod.cs @@ -25,10 +25,14 @@ public enum CollectionMethod CARegistry = 1 << 16, DCRegistry = 1 << 17, CertServices = 1 << 18, + LdapServices = 1 << 19, + WebClientService = 1 << 21, + SmbInfo = 1 << 22, + EventLogs = 1 << 23, LocalGroups = DCOM | RDP | LocalAdmin | PSRemote, - ComputerOnly = LocalGroups | Session | UserRights | CARegistry | DCRegistry, - DCOnly = ACL | Container | Group | ObjectProps | Trusts | GPOLocalGroup | CertServices, - Default = Group | Session | Trusts | ACL | ObjectProps | LocalGroups | SPNTargets | Container | CertServices, - All = Default | LoggedOn | GPOLocalGroup | UserRights | CARegistry | DCRegistry + ComputerOnly = LocalGroups | Session | UserRights | CARegistry | DCRegistry | WebClientService | SmbInfo | EventLogs, + DCOnly = ACL | Container | Group | ObjectProps | Trusts | GPOLocalGroup | CertServices | LdapServices | SmbInfo, + Default = Group | Session | Trusts | ACL | ObjectProps | LocalGroups | SPNTargets | Container | CertServices | LdapServices | SmbInfo, + All = Default | LoggedOn | GPOLocalGroup | UserRights | CARegistry | DCRegistry | WebClientService | LdapServices | EventLogs } } \ No newline at end of file diff --git a/src/CommonLib/Enums/LdapErrorCodes.cs b/src/CommonLib/Enums/LdapErrorCodes.cs index becf71c4..65a52ac6 100644 --- a/src/CommonLib/Enums/LdapErrorCodes.cs +++ b/src/CommonLib/Enums/LdapErrorCodes.cs @@ -3,10 +3,12 @@ public enum LdapErrorCodes : int { Success = 0, + StrongAuthRequired = 8, + SaslBindInProgress = 14, InvalidCredentials = 49, Busy = 51, ServerDown = 81, LocalError = 82, - KerberosAuthType = 83 + KerberosAuthType = 83, } } \ No newline at end of file diff --git a/src/CommonLib/Enums/LdapOption.cs b/src/CommonLib/Enums/LdapOption.cs new file mode 100644 index 00000000..084cbff2 --- /dev/null +++ b/src/CommonLib/Enums/LdapOption.cs @@ -0,0 +1,14 @@ +namespace SharpHoundCommonLib.Enums +{ + public enum LdapOption : int + { + Ssl = 0x0A, + ProtocolVersion = 0x11, + ResultCode = 0x31, + ServerError = 0x33, + ServerCertificate = 0x81, + Sign = 0x95, + Encrypt = 0x96, + Timeout = 0x5002, + } +} diff --git a/src/CommonLib/Enums/LdapOptionValue.cs b/src/CommonLib/Enums/LdapOptionValue.cs new file mode 100644 index 00000000..857c9b92 --- /dev/null +++ b/src/CommonLib/Enums/LdapOptionValue.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SharpHoundCommonLib.Enums +{ + public enum LdapOptionValue : int + { + Off = 0, + On = 1, + Version3 = 3, + }; +} diff --git a/src/CommonLib/Enums/LdapSupportedSaslMechansims.cs b/src/CommonLib/Enums/LdapSupportedSaslMechansims.cs new file mode 100644 index 00000000..c2f84fbc --- /dev/null +++ b/src/CommonLib/Enums/LdapSupportedSaslMechansims.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SharpHoundCommonLib.Enums +{ + public static class LdapSupportedSaslMechansims + { + public const string GSSAPI = "GSSAPI"; + public const string GSS_SPNEGO = "GSS-SPNEGO"; + public const string EXTERNAL = "EXTERNAL"; + public const string DIGEST_MD5 = "DIGEST_MD5"; + } +} diff --git a/src/CommonLib/LdapConnectionPool.cs b/src/CommonLib/LdapConnectionPool.cs index fe0cb5a7..3f6ee8ab 100644 --- a/src/CommonLib/LdapConnectionPool.cs +++ b/src/CommonLib/LdapConnectionPool.cs @@ -19,7 +19,7 @@ namespace SharpHoundCommonLib { internal class LdapConnectionPool : IDisposable{ private readonly ConcurrentBag _connections; private readonly ConcurrentBag _globalCatalogConnection; - private readonly SemaphoreSlim _semaphore; + private readonly SemaphoreSlim _semaphore = null; private readonly string _identifier; private readonly string _poolIdentifier; private readonly LdapConfig _ldapConfig; diff --git a/src/CommonLib/LdapProducerQueryGenerator.cs b/src/CommonLib/LdapProducerQueryGenerator.cs index 0d6580f8..fb0be38b 100644 --- a/src/CommonLib/LdapProducerQueryGenerator.cs +++ b/src/CommonLib/LdapProducerQueryGenerator.cs @@ -46,7 +46,7 @@ public static GeneratedLdapParameters GenerateDefaultPartitionParameters(Collect if (methods.HasFlag(CollectionMethod.SPNTargets)) properties.AddRange(CommonProperties.SPNTargetProps); - if (methods.HasFlag(CollectionMethod.DCRegistry)) + if (methods.HasFlag(CollectionMethod.DCRegistry) || methods.HasFlag(CollectionMethod.LdapServices)) properties.AddRange(CommonProperties.ComputerMethodProps); if (methods.HasFlag(CollectionMethod.SPNTargets)) { @@ -79,7 +79,7 @@ public static GeneratedLdapParameters GenerateDefaultPartitionParameters(Collect properties.AddRange(CommonProperties.GPOLocalGroupProps); } - if (methods.HasFlag(CollectionMethod.DCRegistry)) { + if (methods.HasFlag(CollectionMethod.DCRegistry) || methods.HasFlag(CollectionMethod.LdapServices)) { filter = filter.AddComputers(CommonFilters.DomainControllers); properties.AddRange(CommonProperties.ComputerMethodProps); } diff --git a/src/CommonLib/LdapUtils.cs b/src/CommonLib/LdapUtils.cs index a9166aff..68123258 100644 --- a/src/CommonLib/LdapUtils.cs +++ b/src/CommonLib/LdapUtils.cs @@ -1143,8 +1143,10 @@ internal static bool ResolveLabel(string objectIdentifier, string distinguishedN } public static async Task<(bool Success, ResolvedSearchResult ResolvedResult)> ResolveSearchResult( - IDirectoryObject directoryObject, ILdapUtils utils) { - if (!directoryObject.GetObjectIdentifier(out var objectIdentifier)) { + IDirectoryObject directoryObject, ILdapUtils utils) + { + if (!directoryObject.GetObjectIdentifier(out var objectIdentifier)) + { return (false, default); } @@ -1153,14 +1155,17 @@ internal static bool ResolveLabel(string objectIdentifier, string distinguishedN }; //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()) { + if (directoryObject.IsDeleted()) + { res.Deleted = true; return (true, res); } - if (directoryObject.TryGetLongProperty(LDAPProperties.UserAccountControl, out var rawUac)) { + if (directoryObject.TryGetLongProperty(LDAPProperties.UserAccountControl, out var rawUac)) + { var flags = (UacFlags)rawUac; - if (flags.HasFlag(UacFlags.ServerTrustAccount)) { + if (flags.HasFlag(UacFlags.ServerTrustAccount)) + { res.IsDomainController = true; utils.AddDomainController(objectIdentifier); } @@ -1168,24 +1173,35 @@ internal static bool ResolveLabel(string objectIdentifier, string distinguishedN string domain; - if (directoryObject.TryGetDistinguishedName(out var distinguishedName)) { + if (directoryObject.TryGetDistinguishedName(out var distinguishedName)) + { domain = Helpers.DistinguishedNameToDomain(distinguishedName); - } else { + } + else + { if (objectIdentifier.StartsWith("S-1-5") && - await utils.GetDomainNameFromSid(objectIdentifier) is (true, var domainName)) { + await utils.GetDomainNameFromSid(objectIdentifier) is (true, var domainName)) + { domain = domainName; - } else { + } + else + { return (false, default); } } string domainSid; var match = SIDRegex.Match(objectIdentifier); - if (match.Success) { + if (match.Success) + { domainSid = match.Groups[1].Value; - } else if (await utils.GetDomainSidFromDomainName(domain) is (true, var sid)) { + } + else if (await utils.GetDomainSidFromDomainName(domain) is (true, var sid)) + { domainSid = sid; - } else { + } + else + { Logging.Logger.LogWarning("Failed to resolve domain sid for object {Identifier}", objectIdentifier); domainSid = null; } @@ -1193,112 +1209,160 @@ await utils.GetDomainNameFromSid(objectIdentifier) is (true, var domainName)) { res.Domain = domain; res.DomainSid = domainSid; - if (WellKnownPrincipal.GetWellKnownPrincipal(objectIdentifier, out var wellKnownPrincipal)) { + 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)) { + 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)) { + res.ObjectType = await ComputeLabel(directoryObject, objectIdentifier, domain, utils); + + directoryObject.TryGetProperty(LDAPProperties.SAMAccountName, out var samAccountName); + res.DisplayName = ComputeDisplayName(directoryObject, domain, res.ObjectType, samAccountName); + return (true, res); + } + + private static async Task