Skip to content

Commit 3086355

Browse files
ktstraderrvazarkar
andauthored
fix: added timeout wrapper to SmbProcessor.cs (#188)
* fix: added timeout wrapper to SmbProcessor.cs * fix: added timeout wrapper to DCLdapProcessor.cs * remove empty space * chore: adding SendComputerStatus and send fail if Timeout Task fail * chore: version bump * chore: test smbProcessor timeout * chore: fix test * chore: test DCLdapProcessor timeouts * chore: ??? * chore: fixed DCLdapProcessor tests \o/ * chore: fixing up some bits * fix: split logic for dc ldap processor * chore: fix test * chore: fix test? * chore: use it.isany * chore: bump version * chore: remove bad code * chore: already bumped the version, it should be on 4.2.3 * chore: add todo --------- Co-authored-by: Rohan Vazarkar <rvazarkar@specterops.io>
1 parent 217481e commit 3086355

5 files changed

Lines changed: 255 additions & 59 deletions

File tree

src/CommonLib/Processors/DCLdapProcessor.cs

Lines changed: 71 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System;
77
using System.Diagnostics.CodeAnalysis;
88
using System.Threading.Tasks;
9+
using SharpHoundRPC;
910
using SharpHoundRPC.PortScanner;
1011

1112
namespace SharpHoundCommonLib.Processors;
@@ -25,39 +26,88 @@ public class DCLdapProcessor {
2526
private readonly int _ldapTimeout;
2627
private readonly Uri _ldapEndpoint;
2728
private readonly Uri _ldapSslEndpoint;
29+
public delegate Task ComputerStatusDelegate(CSVComputerStatus status);
2830

2931
private readonly string SEC_E_UNSUPPORTED_FUNCTION = "80090302";
3032
private readonly string SEC_E_BAD_BINDINGS = "80090346";
3133

3234

33-
public DCLdapProcessor(int portScanTimeout, string dcHostname, ILogger log) {
34-
_log = log;
35+
public DCLdapProcessor(int portScanTimeout, string dcHostname, ILogger log = null) {
36+
_log = log ?? Logging.LogProvider.CreateLogger("DCLdapProcessor");
3537
_scanner = new PortScanner();
3638
_portScanTimeout = portScanTimeout;
3739
_ldapTimeout = portScanTimeout / 1000;
3840
_ldapEndpoint = new Uri($"ldap://{dcHostname}:389");
3941
_ldapSslEndpoint = new Uri($"ldaps://{dcHostname}:636");
4042
}
43+
44+
public event ComputerStatusDelegate ComputerStatusEvent;
4145

42-
public async Task<LdapService> Scan() {
46+
public async Task<LdapService> Scan(string computerName, TimeSpan timeout = default) {
47+
if (timeout == default) {
48+
timeout = TimeSpan.FromMinutes(2);
49+
}
50+
4351
var hasLdap = await TestLdapPort();
4452
var hasLdaps = await TestLdapsPort();
45-
APIResult<bool> isSigningRequired = new(),
53+
SharpHoundRPC.Result<bool> isSigningRequired = new(),
4654
isChannelBindingDisabled = new();
4755

4856
if (hasLdap) {
49-
isSigningRequired = await CheckIsNtlmSigningRequired();
57+
isSigningRequired = await Task.Run(CheckIsNtlmSigningRequired).TimeoutAfter(timeout);
5058
}
5159

5260
if (hasLdaps) {
53-
isChannelBindingDisabled = await CheckIsChannelBindingDisabled();
61+
isChannelBindingDisabled = await Task.Run(CheckIsChannelBindingDisabled).TimeoutAfter(timeout);
62+
}
63+
64+
if (isSigningRequired.IsFailed) {
65+
await SendComputerStatus(new CSVComputerStatus {
66+
Status = isSigningRequired.Error,
67+
Task = "DCLdapIsSigningRequired",
68+
ComputerName = computerName
69+
});
70+
_log.LogTrace("DCLdapScan failed on IsSigningRequired for {ComputerName}: {Status}", computerName, isSigningRequired.Status);
71+
} else {
72+
await SendComputerStatus(new CSVComputerStatus {
73+
Status = CSVComputerStatus.StatusSuccess,
74+
Task = "DCLdapIsSigningRequired",
75+
ComputerName = computerName
76+
});
5477
}
5578

79+
if (isChannelBindingDisabled.IsFailed) {
80+
await SendComputerStatus(new CSVComputerStatus {
81+
Status = isChannelBindingDisabled.Error,
82+
Task = "DCLdapIsChannelBindingDisabled",
83+
ComputerName = computerName
84+
});
85+
_log.LogTrace("DCLdapScan failed on IsChannelBindingDisabled for {ComputerName}: {Status}", computerName, isSigningRequired.Status);
86+
} else {
87+
await SendComputerStatus(new CSVComputerStatus {
88+
Status = CSVComputerStatus.StatusSuccess,
89+
Task = "DCLdapIsChannelBindingDisabled",
90+
ComputerName = computerName
91+
});
92+
}
93+
5694
return new LdapService(
5795
hasLdap,
5896
hasLdaps,
59-
isSigningRequired,
60-
isChannelBindingDisabled
97+
new APIResult<bool>
98+
{
99+
Collected = isSigningRequired.IsSuccess,
100+
FailureReason = isSigningRequired.Error,
101+
Result = isSigningRequired.Value,
102+
103+
},
104+
new APIResult<bool>
105+
{
106+
Collected = isChannelBindingDisabled.IsSuccess,
107+
FailureReason = isChannelBindingDisabled.Error,
108+
Result = isChannelBindingDisabled.Value,
109+
110+
}
61111
);
62112
}
63113

@@ -66,25 +116,26 @@ public async Task<LdapService> Scan() {
66116
/// </summary>
67117
/// <returns>bool</returns>
68118
[ExcludeFromCodeCoverage]
69-
public async Task<bool> TestLdapPort() {
119+
public virtual async Task<bool> TestLdapPort() {
70120
return await _scanner.CheckPort(_ldapEndpoint.Host, _ldapEndpoint.Port, _portScanTimeout);
71121
}
72122

73123
[ExcludeFromCodeCoverage]
74-
public async Task<bool> TestLdapsPort() {
124+
public virtual async Task<bool> TestLdapsPort() {
75125
return await _scanner.CheckPort(_ldapSslEndpoint.Host, _ldapSslEndpoint.Port, _portScanTimeout);
76126
}
77127

78-
public async Task<APIResult<bool>> CheckIsNtlmSigningRequired() {
128+
public virtual async Task<SharpHoundRPC.Result<bool>> CheckIsNtlmSigningRequired() {
79129
try {
80130
var options = new LdapAuthOptions() {
81131
Signing = false
82132
};
83133
var accessibleWithoutSigning = await Authenticate(_ldapEndpoint, options);
84134

85-
return APIResult<bool>.Success(accessibleWithoutSigning == false);
135+
return SharpHoundRPC.Result<bool>.Ok(accessibleWithoutSigning == false);
136+
86137
} catch (Exception ex) {
87-
return APIResult<bool>.Failure($"CheckIsNtlmSigningRequired failed: {ex}");
138+
return SharpHoundRPC.Result<bool>.Fail($"CheckIsNtlmSigningRequired failed: {ex}");
88139
}
89140
}
90141

@@ -96,7 +147,7 @@ public async Task<APIResult<bool>> CheckIsNtlmSigningRequired() {
96147
// 3) Correct bindings to ensure NTLM auth is enabled
97148
// However, as of right now we only do #2. We can't do #1 right now since the
98149
// Window's SSPI APIs (InitSecurityContext) always add channel bindings.
99-
public async Task<APIResult<bool>> CheckIsChannelBindingDisabled() {
150+
public virtual async Task<SharpHoundRPC.Result<bool>> CheckIsChannelBindingDisabled() {
100151
try {
101152
// 1) Can we connect with *invalid* bindings
102153

@@ -107,10 +158,10 @@ public async Task<APIResult<bool>> CheckIsChannelBindingDisabled() {
107158
Signing = false,
108159
Bindings = bindings
109160
});
161+
return SharpHoundRPC.Result<bool>.Ok(accessibleWithNoBindings);
110162

111-
return APIResult<bool>.Success(accessibleWithNoBindings);
112163
} catch (Exception ex) {
113-
return APIResult<bool>.Failure($"CheckIsNtlmSigningRequired failed: {ex}");
164+
return SharpHoundRPC.Result<bool>.Fail($"CheckIsNtlmSigningRequired failed: {ex}");
114165
}
115166
}
116167

@@ -169,4 +220,8 @@ private async Task<bool> Authenticate(Uri endpoint, LdapAuthOptions options) {
169220

170221
return false;
171222
}
223+
224+
private async Task SendComputerStatus(CSVComputerStatus status) {
225+
if (ComputerStatusEvent is not null) await ComputerStatusEvent.Invoke(status);
226+
}
172227
}

src/CommonLib/Processors/SmbProcessor.cs

Lines changed: 80 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Net.Sockets;
66
using System.Text;
77
using System.Threading.Tasks;
8+
using SharpHoundRPC;
89

910
namespace SharpHoundCommonLib.Processors {
1011
/// <summary>
@@ -16,43 +17,85 @@ namespace SharpHoundCommonLib.Processors {
1617
/// </summary>
1718
/// <param name="timeoutMs"></param>
1819
/// <param name="log"></param>
19-
public class SmbProcessor(int timeoutMs, ILogger log = null) {
20-
private readonly ILogger _log = log ?? Logging.LogProvider.CreateLogger("SmbProcessor");
21-
22-
public async Task<APIResult<SmbInfo>> Scan(string host) {
23-
var scanner = new SmbScanner();
24-
var result = await scanner.Scan(host, 445, timeoutMs);
25-
26-
if (result.Success && result.Info != null) {
27-
var info = new SmbInfo() {
28-
SigningEnabled = result.Info.SmbSigning,
29-
OsVersion = result.Info.OsVersion,
30-
OsBuild = result.Info.OsBuildNumber.ToString(),
31-
DnsComputerName = result.Info.DnsComputerName,
32-
};
33-
34-
return APIResult<SmbInfo>.Success(info);
35-
} else {
36-
return APIResult<SmbInfo>.Failure(result.ErrorMessage ?? "Unknown error");
20+
public class SmbProcessor
21+
{
22+
//TODO: Have this class take in our portscanner class and use that
23+
public delegate Task ComputerStatusDelegate(CSVComputerStatus status);
24+
private readonly ILogger _log;
25+
private readonly SmbScanner _smbScanner;
26+
private readonly int _timeoutMs;
27+
28+
public SmbProcessor(int timeoutMs, SmbScanner smbScanner = null, ILogger log = null)
29+
{
30+
_timeoutMs = timeoutMs;
31+
_smbScanner = smbScanner ?? new SmbScanner();
32+
_log = log ?? Logging.LogProvider.CreateLogger("SmbProcessor");
33+
}
34+
35+
public event ComputerStatusDelegate ComputerStatusEvent;
36+
public virtual async Task<APIResult<SmbInfo>> Scan(string host, TimeSpan timeout = default) {
37+
if (timeout == default) {
38+
timeout = TimeSpan.FromMinutes(2);
3739
}
40+
41+
var result = await Task.Run(() => _smbScanner.Scan(host, 445, _timeoutMs)).TimeoutAfter(timeout);
42+
43+
if (result.IsFailed) {
44+
await SendComputerStatus(new CSVComputerStatus {
45+
Status = result.Error,
46+
Task = "SmbScan",
47+
ComputerName = host
48+
});
49+
_log.LogTrace("SmbScan failed on {ComputerName}: {Status}", host, result.Status);
50+
return APIResult<SmbInfo>.Failure(result.Status.ToString());
51+
}
52+
53+
if (result.Value.Info == null)
54+
{
55+
await SendComputerStatus(new CSVComputerStatus {
56+
Status = result.Error ?? "Unknown error",
57+
Task = "SmbScan",
58+
ComputerName = host
59+
});
60+
_log.LogTrace("SmbScan failed on {ComputerName}: {Status}", host, result.Status);
61+
return APIResult<SmbInfo>.Failure(result.Error ?? "Unknown error");
62+
}
63+
64+
_log.LogDebug("SmbScan succeeded on {ComputerName}", host);
65+
await SendComputerStatus(new CSVComputerStatus {
66+
Status = CSVComputerStatus.StatusSuccess,
67+
Task = "SmbScan",
68+
ComputerName = host
69+
});
70+
71+
var info = new SmbInfo() {
72+
SigningEnabled = result.Value.Info.SmbSigning,
73+
OsVersion = result.Value.Info.OsVersion,
74+
OsBuild = result.Value.Info.OsBuildNumber.ToString(),
75+
DnsComputerName = result.Value.Info.DnsComputerName,
76+
};
77+
78+
return APIResult<SmbInfo>.Success(info);
79+
80+
}
81+
82+
private async Task SendComputerStatus(CSVComputerStatus status) {
83+
if (ComputerStatusEvent is not null) await ComputerStatusEvent.Invoke(status);
3884
}
3985
}
4086

41-
4287
public enum SmbVersion {
4388
Unknown,
4489
SMBv1,
4590
SMBv2
4691
}
4792

48-
public class SmbScanResult {
49-
public SmbScanResult(string host) {
93+
public class SmbScanInfo {
94+
public SmbScanInfo(string host) {
5095
Host = host;
5196
}
5297

5398
public string Host { get; set; }
54-
public bool Success { get; set; }
55-
public string ErrorMessage { get; set; }
5699
public NTLMInfo Info { get; set; }
57100
public SmbVersion SmbVersion { get; set; }
58101
}
@@ -150,9 +193,8 @@ public static NTLMInfo FromBytes(byte[] buf) {
150193
}
151194

152195
public class SmbScanner {
153-
public async Task<SmbScanResult> Scan(string host, int port, int timeoutMs = 10000) {
154-
var result = new SmbScanResult(host) {
155-
Success = false,
196+
public virtual async Task<SharpHoundRPC.Result<SmbScanInfo>> Scan(string host, int port, int timeoutMs = 10000) {
197+
var scanInfo = new SmbScanInfo(host) {
156198
SmbVersion = SmbVersion.Unknown
157199
};
158200

@@ -162,8 +204,7 @@ public async Task<SmbScanResult> Scan(string host, int port, int timeoutMs = 100
162204
smbClient = await ConnectAsync(host, port, timeoutMs);
163205

164206
if (!smbClient.Connected) {
165-
result.ErrorMessage = "SMBInfo can't connect!";
166-
return result;
207+
return SharpHoundRPC.Result<SmbScanInfo>.Fail("SMBInfo can't connect!");
167208
}
168209

169210
var smbClientStream = smbClient.GetStream();
@@ -199,14 +240,13 @@ public async Task<SmbScanResult> Scan(string host, int port, int timeoutMs = 100
199240
}
200241

201242
if (ss.Length >= 2) {
202-
result.Info = NTLMInfo.FromBytes(smbClientReceive);
203-
result.Info.NativeOs = ss[0];
204-
result.Info.NativeLanManager = ss[1];
205-
result.Info.SmbSigning = signingEnabled;
243+
scanInfo.Info = NTLMInfo.FromBytes(smbClientReceive);
244+
scanInfo.Info.NativeOs = ss[0];
245+
scanInfo.Info.NativeLanManager = ss[1];
246+
scanInfo.Info.SmbSigning = signingEnabled;
206247
}
207248

208-
result.SmbVersion = SmbVersion.SMBv1;
209-
result.Success = true;
249+
scanInfo.SmbVersion = SmbVersion.SMBv1;
210250
} catch {
211251
// If SMBv1 fails, try SMBv2 with a new connection
212252
if (smbClient != null) {
@@ -221,8 +261,7 @@ public async Task<SmbScanResult> Scan(string host, int port, int timeoutMs = 100
221261
if (BitConverter.ToString([
222262
smbClientReceive[4], smbClientReceive[5], smbClientReceive[6], smbClientReceive[7]
223263
]).ToLower() == "ff-53-4d-42") {
224-
result.ErrorMessage = "Could not connect with SMBv2";
225-
return result;
264+
return SharpHoundRPC.Result<SmbScanInfo>.Fail("Could not connect with SMBv2");
226265
}
227266

228267
var signingEnabled = BitConverter.ToString([smbClientReceive[70]]) == "03";
@@ -233,19 +272,18 @@ public async Task<SmbScanResult> Scan(string host, int port, int timeoutMs = 100
233272
smbClientReceive = await SendStreamAsync(smbClientStream, GetNTLMSSPNegotiatev2Data(smbPackets),
234273
operationCts.Token);
235274

236-
result.Info = NTLMInfo.FromBytes(smbClientReceive);
237-
result.Info.SmbSigning = smbPackets.SMB_Signing;
275+
scanInfo.Info = NTLMInfo.FromBytes(smbClientReceive);
276+
scanInfo.Info.SmbSigning = smbPackets.SMB_Signing;
238277

239-
result.SmbVersion = SmbVersion.SMBv2;
240-
result.Success = true;
278+
scanInfo.SmbVersion = SmbVersion.SMBv2;
241279
}
242280
} catch (Exception ex) {
243-
result.ErrorMessage = ex.Message;
281+
return SharpHoundRPC.Result<SmbScanInfo>.Fail(ex.Message);
244282
} finally {
245283
smbClient?.Close();
246284
}
247285

248-
return result;
286+
return SharpHoundRPC.Result<SmbScanInfo>.Ok(scanInfo);
249287
}
250288

251289
private static async Task<TcpClient> ConnectAsync(string host, int port, int timeoutMs) {

src/CommonLib/SharpHoundCommonLib.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
<PackageDescription>Common library for C# BloodHound enumeration tasks</PackageDescription>
1010
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
1111
<RepositoryUrl>https://github.com/BloodHoundAD/SharpHoundCommon</RepositoryUrl>
12-
<Version>4.2.2</Version>
12+
<Version>4.2.3</Version>
1313
<AssemblyName>SharpHoundCommonLib</AssemblyName>
1414
<RootNamespace>SharpHoundCommonLib</RootNamespace>
1515
</PropertyGroup>

0 commit comments

Comments
 (0)