diff --git a/src/CommonLib/Ntlm/HttpClientFactory.cs b/src/CommonLib/Ntlm/HttpClientFactory.cs deleted file mode 100644 index 74b8e6f5b..000000000 --- a/src/CommonLib/Ntlm/HttpClientFactory.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using System.Net; -using System.Net.Http; - -namespace SharpHoundCommonLib.Ntlm; - -public interface IHttpClientFactory { - HttpClient CreateUnauthenticatedClient(); - HttpClient CreateAuthenticatedHttpClient(Uri Url, string authPackage = "Kerberos"); -} - -public class HttpClientFactory : IHttpClientFactory { - public HttpClient CreateUnauthenticatedClient() { - var handler = new HttpClientHandler { - ServerCertificateCustomValidationCallback = (httpRequestMessage, cert, cetChain, policyErrors) => true, - UseDefaultCredentials = false - }; - - return new HttpClient(handler); - } - - public HttpClient CreateAuthenticatedHttpClient(Uri Url, string authPackage = "Kerberos") { - var handler = new HttpClientHandler { - Credentials = new CredentialCache() { - { Url, authPackage, CredentialCache.DefaultNetworkCredentials } - }, - - PreAuthenticate = true, - ServerCertificateCustomValidationCallback = - (httpRequestMessage, cert, cetChain, policyErrors) => { return true; }, - }; - - return new HttpClient(handler); - } -} \ No newline at end of file diff --git a/src/CommonLib/Ntlm/HttpNtlmAuthenticationService.cs b/src/CommonLib/Ntlm/HttpNtlmAuthenticationService.cs index cd0a7b935..ac36bcae8 100644 --- a/src/CommonLib/Ntlm/HttpNtlmAuthenticationService.cs +++ b/src/CommonLib/Ntlm/HttpNtlmAuthenticationService.cs @@ -15,14 +15,14 @@ namespace SharpHoundCommonLib.Ntlm; /// public class HttpNtlmAuthenticationService { private readonly ILogger _logger; - private readonly IHttpClientFactory _httpClientFactory; + private readonly INtlmHttpClientFactory _ntlmHttpClientFactory; private readonly AdaptiveTimeout _getSupportedNTLMAuthSchemesAdaptiveTimeout; private readonly AdaptiveTimeout _ntlmAuthAdaptiveTimeout; private readonly AdaptiveTimeout _authWithChannelBindingAdaptiveTimeout; - public HttpNtlmAuthenticationService(IHttpClientFactory httpClientFactory, ILogger logger = null) { + public HttpNtlmAuthenticationService(INtlmHttpClientFactory ntlmHttpClientFactory, ILogger logger = null) { _logger = logger ?? Logging.LogProvider.CreateLogger(nameof(HttpNtlmAuthenticationService)); - _httpClientFactory = httpClientFactory; + _ntlmHttpClientFactory = ntlmHttpClientFactory ?? throw new ArgumentNullException(nameof(ntlmHttpClientFactory)); _getSupportedNTLMAuthSchemesAdaptiveTimeout = new AdaptiveTimeout(maxTimeout: TimeSpan.FromMinutes(2), Logging.LogProvider.CreateLogger(nameof(GetSupportedNtlmAuthSchemesAsync))); _ntlmAuthAdaptiveTimeout = new AdaptiveTimeout(maxTimeout: TimeSpan.FromMinutes(2), Logging.LogProvider.CreateLogger(nameof(NtlmAuthenticationHandler.PerformNtlmAuthenticationAsync))); _authWithChannelBindingAdaptiveTimeout = new AdaptiveTimeout(maxTimeout: TimeSpan.FromMinutes(2), Logging.LogProvider.CreateLogger(nameof(AuthWithBadChannelBindingsAsync))); @@ -57,7 +57,7 @@ public async Task EnsureRequiresAuth(Uri url, bool? useBadChannelBindings) { } private async Task GetSupportedNtlmAuthSchemesAsync(Uri url) { - var httpClient = _httpClientFactory.CreateUnauthenticatedClient(); + var httpClient = _ntlmHttpClientFactory.CreateUnauthenticatedClient(); using var getRequest = new HttpRequestMessage(HttpMethod.Get, url); var result = await _getSupportedNTLMAuthSchemesAdaptiveTimeout.ExecuteWithTimeout(async (timeoutToken) => { @@ -105,7 +105,7 @@ internal string[] ExtractAuthSchemes(HttpResponseMessage response) { } private async Task AuthWithBadChannelBindingsAsync(Uri url, string authScheme, NtlmAuthenticationHandler ntlmAuth = null) { - var httpClient = _httpClientFactory.CreateUnauthenticatedClient(); + var httpClient = _ntlmHttpClientFactory.CreateUnauthenticatedClient(); var transport = new HttpTransport(httpClient, url, authScheme, _logger); var ntlmAuthHandler = ntlmAuth ?? new NtlmAuthenticationHandler($"HTTP/{url.Host}"); @@ -143,18 +143,7 @@ private async Task AuthWithBadChannelBindingsAsync(Uri url, string authScheme, N } private async Task AuthWithChannelBindingAsync(Uri url, string authScheme) { - var handler = new HttpClientHandler { - ServerCertificateCustomValidationCallback = (httpRequestMessage, cert, cetChain, policyErrors) => true, - }; - - var credentialCache = new CredentialCache { - { url, authScheme, CredentialCache.DefaultNetworkCredentials } - }; - - handler.Credentials = credentialCache; - handler.PreAuthenticate = true; - - using var client = new HttpClient(handler); + using var client = _ntlmHttpClientFactory.CreateAuthenticatedHttpClient(url, authScheme); var result = await _authWithChannelBindingAdaptiveTimeout.ExecuteWithTimeout(async (timeoutToken) => { try { @@ -217,4 +206,4 @@ public AuthNotRequiredException() { public AuthNotRequiredException(string message) : base(message) { } -} \ No newline at end of file +} diff --git a/src/CommonLib/Ntlm/NtlmHttpClientFactory.cs b/src/CommonLib/Ntlm/NtlmHttpClientFactory.cs new file mode 100644 index 000000000..cb5e917d6 --- /dev/null +++ b/src/CommonLib/Ntlm/NtlmHttpClientFactory.cs @@ -0,0 +1,62 @@ +using System; +using System.Net; +using System.Net.Http; +using System.Security.Authentication; + +namespace SharpHoundCommonLib.Ntlm; + +public interface INtlmHttpClientFactory { + HttpClient CreateUnauthenticatedClient(); + HttpClient CreateAuthenticatedHttpClient(Uri Url, string authPackage = "Kerberos"); +} + +public class NtlmHttpClientFactory : INtlmHttpClientFactory { + private readonly SslProtocols _sslProtocols; + + /// + /// Creates an HttpClientFactory whose handlers will negotiate TLS using OS/framework defaults. + /// + public NtlmHttpClientFactory() : this(SslProtocols.None) { } + + /// + /// Creates an HttpClientFactory whose handlers will restrict TLS negotiation to the specified protocols. + /// Use this overload when a specific set of legacy protocols must be supported for a target service, + /// rather than setting process-wide. + /// + /// + /// The SSL/TLS protocols to allow. Pass to defer to OS/framework defaults. + /// + public NtlmHttpClientFactory(SslProtocols sslProtocols) { + _sslProtocols = sslProtocols; + } + + public HttpClient CreateUnauthenticatedClient() { + var handler = new HttpClientHandler { + ServerCertificateCustomValidationCallback = + (httpRequestMessage, cert, cetChain, policyErrors) => true, + UseDefaultCredentials = false + }; + + if (_sslProtocols != SslProtocols.None) + handler.SslProtocols = _sslProtocols; + + return new HttpClient(handler); + } + + public HttpClient CreateAuthenticatedHttpClient(Uri Url, string authPackage = "Kerberos") { + var handler = new HttpClientHandler { + Credentials = new CredentialCache() { + { Url, authPackage, CredentialCache.DefaultNetworkCredentials } + }, + + PreAuthenticate = true, + ServerCertificateCustomValidationCallback = + (httpRequestMessage, cert, cetChain, policyErrors) => true, + }; + + if (_sslProtocols != SslProtocols.None) + handler.SslProtocols = _sslProtocols; + + return new HttpClient(handler); + } +} \ No newline at end of file diff --git a/src/CommonLib/Processors/CAEnrollmentProcessor.cs b/src/CommonLib/Processors/CAEnrollmentProcessor.cs index 753e33215..a45c12afb 100644 --- a/src/CommonLib/Processors/CAEnrollmentProcessor.cs +++ b/src/CommonLib/Processors/CAEnrollmentProcessor.cs @@ -7,6 +7,7 @@ using System.Net; using System.Net.Http; using System.Net.Sockets; +using System.Security.Authentication; using System.Threading.Tasks; namespace SharpHoundCommonLib.Processors { @@ -18,13 +19,11 @@ public class CAEnrollmentProcessor { private readonly string _caName; private readonly ILogger _logger; - public CAEnrollmentProcessor(string caDnsHostname, string caName, ILogger log = null) { - ServicePointManager.SecurityProtocol |= - SecurityProtocolType.Ssl3 - | SecurityProtocolType.Tls12 - | SecurityProtocolType.Tls11 - | SecurityProtocolType.Tls; + // TLS1.3 is not available in .Net Framework 4.7.2, but the enum can still be assigned. + private const SslProtocols CaEnrollmentSslProtocols = + SslProtocols.Ssl3 | SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12 | (SslProtocols)12288; + public CAEnrollmentProcessor(string caDnsHostname, string caName, ILogger log = null) { _caDnsHostname = caDnsHostname; _caName = caName; _logger = log ?? Logging.LogProvider.CreateLogger("CAEnrollmentProcessor"); @@ -48,7 +47,7 @@ await Task.WhenAll( } catch (Exception ex) { _logger.LogError(ex, "An error occurred while scanning enrollment endpoints"); } - + endpoints = TagEndpoints(endpoints).ToList(); return endpoints; @@ -59,7 +58,7 @@ private IEnumerable> TagEndpoints(IEnumerable>> private async Task> GetNtlmEndpoint(Uri url, bool? useBadChannelBinding, CAEnrollmentEndpointType type, CAEnrollmentEndpointScanResult scanResult) { var authService = new HttpNtlmAuthenticationService( - new HttpClientFactory() + new NtlmHttpClientFactory(CaEnrollmentSslProtocols) ); var output = new CAEnrollmentEndpoint(url, type, scanResult); @@ -228,4 +227,4 @@ private async Task> GetNtlmEndpoint(Uri url, boo } } } -} \ No newline at end of file +} diff --git a/test/unit/HttpNtlmAuthenticationServiceTest.cs b/test/unit/HttpNtlmAuthenticationServiceTest.cs index e78d654df..f5825e138 100644 --- a/test/unit/HttpNtlmAuthenticationServiceTest.cs +++ b/test/unit/HttpNtlmAuthenticationServiceTest.cs @@ -25,7 +25,7 @@ public void Dispose() { [Fact] public void HttpNtlmAuthenticationService_ExtractAuthSchemes_AuthNotRequiredException() { - var service = new HttpNtlmAuthenticationService(new HttpClientFactory(), null); + var service = new HttpNtlmAuthenticationService(new NtlmHttpClientFactory(), null); var httpResponseMessage = new HttpResponseMessage { StatusCode = HttpStatusCode.OK, }; @@ -37,7 +37,7 @@ public void HttpNtlmAuthenticationService_ExtractAuthSchemes_AuthNotRequiredExce [Fact] public void HttpNtlmAuthenticationService_ExtractAuthSchemes_HttpForbiddenException() { - var service = new HttpNtlmAuthenticationService(new HttpClientFactory(), null); + var service = new HttpNtlmAuthenticationService(new NtlmHttpClientFactory(), null); var httpResponseMessage = new HttpResponseMessage { StatusCode = HttpStatusCode.Forbidden, }; @@ -49,7 +49,7 @@ public void HttpNtlmAuthenticationService_ExtractAuthSchemes_HttpForbiddenExcept [Fact] public void HttpNtlmAuthenticationService_ExtractAuthSchemes_HttpServerErrorException() { - var service = new HttpNtlmAuthenticationService(new HttpClientFactory(), null); + var service = new HttpNtlmAuthenticationService(new NtlmHttpClientFactory(), null); var httpResponseMessage = new HttpResponseMessage { StatusCode = HttpStatusCode.InternalServerError, }; @@ -61,7 +61,7 @@ public void HttpNtlmAuthenticationService_ExtractAuthSchemes_HttpServerErrorExce [Fact] public void HttpNtlmAuthenticationService_ExtractAuthSchemes_Success() { - var service = new HttpNtlmAuthenticationService(new HttpClientFactory(), null); + var service = new HttpNtlmAuthenticationService(new NtlmHttpClientFactory(), null); var httpResponseMessage = new HttpResponseMessage(); httpResponseMessage.StatusCode = HttpStatusCode.Accepted; httpResponseMessage.Headers.WwwAuthenticate.Add(