Skip to content

Commit cf50418

Browse files
authored
BED-5562 changing LDAP InvalidOperationException to debug (#202)
* fix: changing LDAP InvalidOperationException to debug * chore: added unit testing for DCLdapProcessor.cs Authenticate exception handling * chore: fix github build warnings
1 parent a8d2ae5 commit cf50418

5 files changed

Lines changed: 191 additions & 11 deletions

File tree

src/CommonLib/Ntlm/LdapTransport.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ private void ThrowIfHandleNull() {
3333
}
3434
}
3535

36-
public void InitializeConnectionAsync(int timeout = -1) {
36+
public virtual void InitializeConnectionAsync(int timeout = -1) {
3737
if (_ldap == null) {
3838
_ldap = new LdapConnection();
3939
try {

src/CommonLib/Ntlm/NtlmAuthenticationHandler.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public NtlmAuthenticationHandler(string targetService, ILogger logger = null) {
2828
};
2929
}
3030

31-
public async Task<object> PerformNtlmAuthenticationAsync(INtlmTransport transport) {
31+
public virtual async Task<object> PerformNtlmAuthenticationAsync(INtlmTransport transport) {
3232
using var context = new SspiContext(
3333
null,
3434
null,

src/CommonLib/Processors/DCLdapProcessor.cs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -171,12 +171,12 @@ public virtual async Task<bool> TestLdapsPort() {
171171
/// <param name="endpoint"></param>
172172
/// <param name="options"></param>
173173
/// <returns></returns>
174-
protected internal virtual async Task<bool> Authenticate(Uri endpoint, LdapAuthOptions options) {
174+
protected internal virtual async Task<bool> Authenticate(Uri endpoint, LdapAuthOptions options, NtlmAuthenticationHandler ntlmAuth = null, LdapTransport ldapTransport = null) {
175175
var host = endpoint.Host;
176-
var auth = new NtlmAuthenticationHandler($"LDAP/{host.ToUpper()}") {
176+
var auth = ntlmAuth ?? new NtlmAuthenticationHandler($"LDAP/{host.ToUpper()}") {
177177
Options = options
178178
};
179-
var transport = new LdapTransport(_log, endpoint);
179+
var transport = ldapTransport ?? new LdapTransport(_log, endpoint);
180180

181181
try {
182182
transport.InitializeConnectionAsync(_ldapTimeout);
@@ -214,7 +214,10 @@ protected internal virtual async Task<bool> Authenticate(Uri endpoint, LdapAuthO
214214
ex.ServerErrorMessage);
215215
break;
216216
}
217-
} catch (Exception ex) {
217+
} catch (InvalidOperationException ex) {
218+
_log.LogDebug("LDAP InvalidOperationException: {message}", ex.Message);
219+
} catch (Exception ex)
220+
{
218221
_log.LogError("An unhandled error occurred during the LDAP test: {ex}", ex);
219222
}
220223

test/unit/DCLdapProcessorTest.cs

Lines changed: 147 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Diagnostics.CodeAnalysis;
4+
using System.Linq;
45
using System.Runtime.CompilerServices;
56
using System.Threading.Tasks;
7+
using CommonLibTest.Facades;
8+
using Microsoft.Extensions.Logging;
69
using Moq;
710
using SharpHoundCommonLib;
11+
using SharpHoundCommonLib.Enums;
12+
using SharpHoundCommonLib.Ntlm;
813
using SharpHoundCommonLib.Processors;
914
using SharpHoundRPC;
1015
using Xunit;
@@ -15,6 +20,8 @@ namespace CommonLibTest {
1520
public class DCLdapProcessorTest : IDisposable {
1621

1722
private readonly ITestOutputHelper _testOutputHelper;
23+
private readonly string SEC_E_UNSUPPORTED_FUNCTION = "80090302";
24+
private readonly string SEC_E_BAD_BINDINGS = "80090346";
1825

1926
public DCLdapProcessorTest(ITestOutputHelper testOutputHelper) {
2027
_testOutputHelper = testOutputHelper;
@@ -27,7 +34,7 @@ public void Dispose() {
2734
public async Task DCLdapProcessor_Scan() {
2835
var mockProcessor = new Mock<DCLdapProcessor>(It.IsAny<int>(), "primary.testlab.local", null);
2936

30-
mockProcessor.Setup(x => x.Authenticate(It.IsAny<Uri>(), It.IsAny<LdapAuthOptions>())).ReturnsAsync(false);
37+
mockProcessor.Setup(x => x.Authenticate(It.IsAny<Uri>(), It.IsAny<LdapAuthOptions>(),null, null)).ReturnsAsync(false);
3138

3239
mockProcessor.Setup(x => x.TestLdapPort()).ReturnsAsync(true);
3340
mockProcessor.Setup(x => x.TestLdapsPort()).ReturnsAsync(true);
@@ -54,7 +61,7 @@ public async Task DCLdapProcessor_Scan() {
5461
public async Task DCLdapProcessor_Scan_Failed() {
5562
var mockProcessor = new Mock<DCLdapProcessor>(It.IsAny<int>(), "primary.testlab.local", null);
5663

57-
mockProcessor.Setup(x => x.Authenticate(It.IsAny<Uri>(), It.IsAny<LdapAuthOptions>())).Throws(new Exception("Error"));
64+
mockProcessor.Setup(x => x.Authenticate(It.IsAny<Uri>(), It.IsAny<LdapAuthOptions>(), null, null)).Throws(new Exception("Error"));
5865

5966
mockProcessor.Setup(x => x.TestLdapPort()).ReturnsAsync(true);
6067
mockProcessor.Setup(x => x.TestLdapsPort()).ReturnsAsync(true);
@@ -83,7 +90,7 @@ public async Task DCLdapProcessor_Scan_Failed() {
8390
public async Task DCLdapProcessor_CheckScan_Timeout() {
8491
var mockProcessor = new Mock<DCLdapProcessor>(2, "primary.testlab.local", null);
8592

86-
mockProcessor.Setup(x => x.Authenticate(It.IsAny<Uri>(), It.IsAny<LdapAuthOptions>())).ReturnsAsync(() => {
93+
mockProcessor.Setup(x => x.Authenticate(It.IsAny<Uri>(), It.IsAny<LdapAuthOptions>(), null, null)).ReturnsAsync(() => {
8794
Task.Delay(100).Wait();
8895
return false;
8996
});
@@ -111,7 +118,7 @@ public async Task DCLdapProcessor_CheckScan_Timeout() {
111118
public async Task DCLdapProcessor_CheckIsNtlmSigningRequired()
112119
{
113120
var mockProcessor = new Mock<DCLdapProcessor>(It.IsAny<int>(), "primary.testlab.local", null);
114-
mockProcessor.Setup(x => x.Authenticate(It.IsAny<Uri>(), It.IsAny<LdapAuthOptions>())).ReturnsAsync(false);
121+
mockProcessor.Setup(x => x.Authenticate(It.IsAny<Uri>(), It.IsAny<LdapAuthOptions>(),null, null)).ReturnsAsync(false);
115122
var processor = mockProcessor.Object;
116123
var result = await processor.CheckIsNtlmSigningRequired();
117124
Assert.True(result.IsSuccess);
@@ -122,11 +129,146 @@ public async Task DCLdapProcessor_CheckIsNtlmSigningRequired()
122129
public async Task DCLdapProcessor_CheckIsNtlmSigningRequired_Exception()
123130
{
124131
var mockProcessor = new Mock<DCLdapProcessor>(It.IsAny<int>(), "primary.testlab.local", null);
125-
mockProcessor.Setup(x => x.Authenticate(It.IsAny<Uri>(), It.IsAny<LdapAuthOptions>())).Throws(new Exception("Error"));
132+
mockProcessor.Setup(x => x.Authenticate(It.IsAny<Uri>(), It.IsAny<LdapAuthOptions>(), null, null)).Throws(new Exception("Error"));
126133
var processor = mockProcessor.Object;
127134
var result = await processor.CheckIsNtlmSigningRequired();
128135
Assert.True(result.IsFailed);
129136
Assert.Contains("CheckIsNtlmSigningRequired failed: System.Exception: Error", result.Error);
130137
}
138+
139+
[Fact]
140+
public async Task DCLdapProcessor_Authenticate_InvalidCredentialsException_SEC_E_UNSUPPORTED_FUNCTION()
141+
{
142+
var exception = "ErrorTest";
143+
var endpoint = "http://primary.testlab.local/";
144+
var expected = $"LDAP endpoint '{endpoint}' does not support NTLM";
145+
146+
var mockLogger = new Mock<ILogger<DCLdapProcessor>>();
147+
var mockLdapTransport = new Mock<LdapTransport>(null, It.IsAny<Uri>());
148+
mockLdapTransport.Setup(x => x.InitializeConnectionAsync(It.IsAny<int>())).Throws(new LdapNativeException("Error", (int)LdapErrorCodes.InvalidCredentials, SEC_E_UNSUPPORTED_FUNCTION));
149+
var processor = new DCLdapProcessor(It.IsAny<int>(), "primary.testlab.local", mockLogger.Object);
150+
var result = await processor.Authenticate(new Uri(endpoint), It.IsAny<LdapAuthOptions>(), null, mockLdapTransport.Object);
151+
Assert.False(result);
152+
mockLogger.VerifyLogContains(LogLevel.Debug, expected);
153+
}
154+
155+
[Fact]
156+
public async Task DCLdapProcessor_Authenticate_InvalidCredentialsException_SEC_E_BAD_BINDINGS()
157+
{
158+
var exception = "ErrorTest";
159+
var endpoint = "http://primary.testlab.local/";
160+
var expected = $"Bad bindings with the LDAPS endpoint '{endpoint}'. Server error: {SEC_E_BAD_BINDINGS}";
161+
162+
var mockLogger = new Mock<ILogger<DCLdapProcessor>>();
163+
var mockLdapTransport = new Mock<LdapTransport>(null, It.IsAny<Uri>());
164+
mockLdapTransport.Setup(x => x.InitializeConnectionAsync(It.IsAny<int>())).Throws(new LdapNativeException("Error", (int)LdapErrorCodes.InvalidCredentials, SEC_E_BAD_BINDINGS));
165+
var processor = new DCLdapProcessor(It.IsAny<int>(), "primary.testlab.local", mockLogger.Object);
166+
var result = await processor.Authenticate(new Uri(endpoint), It.IsAny<LdapAuthOptions>(), null, mockLdapTransport.Object);
167+
Assert.False(result);
168+
mockLogger.VerifyLogContains(LogLevel.Debug, expected);
169+
}
170+
171+
[Fact]
172+
public async Task DCLdapProcessor_Authenticate_InvalidCredentialsException_Unhandled()
173+
{
174+
var exception = "ErrorTest";
175+
var endpoint = "http://primary.testlab.local/";
176+
var expected = $"Unhandled LDAP InvalidCred error code during LDAP test: SharpHoundCommonLib.Ntlm.LdapNativeException: {exception}. LDAP error code: {(int)LdapErrorCodes.InvalidCredentials}.";
177+
178+
var mockLogger = new Mock<ILogger<DCLdapProcessor>>();
179+
var mockLdapTransport = new Mock<LdapTransport>(null, It.IsAny<Uri>());
180+
mockLdapTransport.Setup(x => x.InitializeConnectionAsync(It.IsAny<int>())).Throws(new LdapNativeException(exception, (int)LdapErrorCodes.InvalidCredentials, "80090347"));
181+
var processor = new DCLdapProcessor(It.IsAny<int>(), "primary.testlab.local", mockLogger.Object);
182+
var result = await processor.Authenticate(new Uri(endpoint), It.IsAny<LdapAuthOptions>(), null, mockLdapTransport.Object);
183+
Assert.False(result);
184+
mockLogger.VerifyLogContains(LogLevel.Error, expected);
185+
}
186+
187+
[Fact]
188+
public async Task DCLdapProcessor_Authenticate_StrongAuthRequiredException()
189+
{
190+
var exception = "ErrorTest";
191+
var endpoint = "http://primary.testlab.local/";
192+
var expected = $"LDAP requires signing. Endpoint: {endpoint}";
193+
194+
var mockLogger = new Mock<ILogger<DCLdapProcessor>>();
195+
var mockLdapTransport = new Mock<LdapTransport>(null, It.IsAny<Uri>());
196+
mockLdapTransport.Setup(x => x.InitializeConnectionAsync(It.IsAny<int>())).Throws(new LdapNativeException(exception, (int)LdapErrorCodes.StrongAuthRequired, null));
197+
var processor = new DCLdapProcessor(It.IsAny<int>(), "primary.testlab.local", mockLogger.Object);
198+
var result = await processor.Authenticate(new Uri(endpoint), It.IsAny<LdapAuthOptions>(), null, mockLdapTransport.Object);
199+
Assert.False(result);
200+
mockLogger.VerifyLog(LogLevel.Debug, expected);
201+
}
202+
203+
[Fact]
204+
public async Task DCLdapProcessor_Authenticate_ServerDownException()
205+
{
206+
var exception = "ErrorTest";
207+
var endpoint = "http://primary.testlab.local/";
208+
var expected = $"LDAP endpoint '{endpoint}' not accessible";
209+
210+
var mockLogger = new Mock<ILogger<DCLdapProcessor>>();
211+
var mockLdapTransport = new Mock<LdapTransport>(null, It.IsAny<Uri>());
212+
mockLdapTransport.Setup(x => x.InitializeConnectionAsync(It.IsAny<int>())).Throws(new LdapNativeException(exception, (int)LdapErrorCodes.ServerDown));
213+
var processor = new DCLdapProcessor(It.IsAny<int>(), "primary.testlab.local", mockLogger.Object);
214+
var result = await processor.Authenticate(new Uri(endpoint), It.IsAny<LdapAuthOptions>(), null, mockLdapTransport.Object);
215+
Assert.False(result);
216+
mockLogger.VerifyLog(LogLevel.Debug, expected);
217+
}
218+
219+
[Fact]
220+
public async Task DCLdapProcessor_Authenticate_LdapUnhandledException()
221+
{
222+
var endpoint = "http://primary.testlab.local/";
223+
var exception = "ErrorTest";
224+
var expected = $"Unhandled LdapException error code during LDAP test: SharpHoundCommonLib.Ntlm.LdapNativeException: {exception}. LDAP error code: {(int)LdapErrorCodes.LocalError}";
225+
226+
var mockLogger = new Mock<ILogger<DCLdapProcessor>>();
227+
var mockLdapTransport = new Mock<LdapTransport>(null, It.IsAny<Uri>());
228+
mockLdapTransport.Setup(x => x.InitializeConnectionAsync(It.IsAny<int>())).Throws(new LdapNativeException(exception, (int)LdapErrorCodes.LocalError));
229+
var processor = new DCLdapProcessor(It.IsAny<int>(), "primary.testlab.local", mockLogger.Object);
230+
var result = await processor.Authenticate(new Uri(endpoint), It.IsAny<LdapAuthOptions>(), null, mockLdapTransport.Object);
231+
Assert.False(result);
232+
mockLogger.VerifyLogContains(LogLevel.Error, expected);
233+
}
234+
235+
[Fact]
236+
public async Task DCLdapProcessor_Authenticate_InvalidOperationException()
237+
{
238+
var endpoint = "http://primary.testlab.local/";
239+
var exception = "Server did return a challenge";
240+
var expected = $"LDAP InvalidOperationException: {exception}";
241+
242+
var mockLogger = new Mock<ILogger<DCLdapProcessor>>();
243+
var mockLdapTransport = new Mock<LdapTransport>(null, It.IsAny<Uri>());
244+
var mockAuthenticator = new Mock<NtlmAuthenticationHandler>(It.IsAny<string>(), null);
245+
mockLdapTransport.Setup(x => x.InitializeConnectionAsync(It.IsAny<int>())).Verifiable();
246+
mockAuthenticator.Setup(x => x.PerformNtlmAuthenticationAsync(It.IsAny<INtlmTransport>()))
247+
.Throws(new InvalidOperationException(exception));
248+
var processor = new DCLdapProcessor(It.IsAny<int>(), "primary.testlab.local", mockLogger.Object);
249+
var result = await processor.Authenticate(new Uri(endpoint), It.IsAny<LdapAuthOptions>(), mockAuthenticator.Object, mockLdapTransport.Object);
250+
Assert.False(result);
251+
mockLogger.VerifyLog(LogLevel.Debug, expected);
252+
}
253+
254+
[Fact]
255+
public async Task DCLdapProcessor_Authenticate_UnhandledException()
256+
{
257+
var endpoint = "http://primary.testlab.local/";
258+
var exception = "Unhandled exception";
259+
var expected = $"An unhandled error occurred during the LDAP test: System.Exception: {exception}";
260+
261+
var mockLogger = new Mock<ILogger<DCLdapProcessor>>();
262+
var mockLdapTransport = new Mock<LdapTransport>(null, It.IsAny<Uri>());
263+
var mockAuthenticator = new Mock<NtlmAuthenticationHandler>(It.IsAny<string>(), null);
264+
mockLdapTransport.Setup(x => x.InitializeConnectionAsync(It.IsAny<int>())).Verifiable();
265+
mockAuthenticator.Setup(x => x.PerformNtlmAuthenticationAsync(It.IsAny<INtlmTransport>()))
266+
.Throws(new Exception(exception));
267+
var processor = new DCLdapProcessor(It.IsAny<int>(), "primary.testlab.local", mockLogger.Object);
268+
var result = await processor.Authenticate(new Uri(endpoint), It.IsAny<LdapAuthOptions>(), mockAuthenticator.Object, mockLdapTransport.Object);
269+
Assert.False(result);
270+
mockLogger.VerifyLogContains(LogLevel.Error, expected);
271+
}
272+
131273
}
132274
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
using System;
2+
using System.Linq;
3+
using Microsoft.Extensions.Logging;
4+
using Moq;
5+
6+
namespace CommonLibTest.Facades;
7+
8+
public static class MockExtentions
9+
{
10+
public static void VerifyLogContains<T>(this Mock<ILogger<T>> mockLogger, LogLevel logLevel, params string[] expected)
11+
{
12+
mockLogger.Verify(
13+
x => x.Log(
14+
logLevel,
15+
It.IsAny<EventId>(),
16+
It.Is<It.IsAnyType>((o, t) =>
17+
expected.All(s => o.ToString().Contains(s, StringComparison.OrdinalIgnoreCase))),
18+
It.IsAny<Exception>(),
19+
It.IsAny<Func<It.IsAnyType, Exception?, string>>()),
20+
Times.Once);
21+
}
22+
23+
public static void VerifyLog<T>(this Mock<ILogger<T>> mockLogger, LogLevel logLevel, string expectedMessage)
24+
{
25+
mockLogger.Verify(
26+
x => x.Log(
27+
logLevel,
28+
It.IsAny<EventId>(),
29+
It.Is<It.IsAnyType>((o, t) =>
30+
string.Equals(expectedMessage, o.ToString(), StringComparison.InvariantCultureIgnoreCase)),
31+
It.IsAny<Exception>(),
32+
It.IsAny<Func<It.IsAnyType, Exception?, string>>()),
33+
Times.Once);
34+
}
35+
}

0 commit comments

Comments
 (0)