-
Notifications
You must be signed in to change notification settings - Fork 45
/
ComputerSessionProcessor.cs
357 lines (311 loc) · 14.7 KB
/
ComputerSessionProcessor.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Principal;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Impersonate;
using Microsoft.Extensions.Logging;
using Microsoft.Win32;
using SharpHoundCommonLib.OutputTypes;
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);
private readonly string _currentUserName;
private readonly ILogger _log;
private readonly NativeMethods _nativeMethods;
private readonly ILDAPUtils _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)
{
_utils = utils;
_nativeMethods = nativeMethods ?? new NativeMethods();
_currentUserName = currentUserName ?? WindowsIdentity.GetCurrent().Name.Split('\\')[1];
_log = log ?? Logging.LogProvider.CreateLogger("CompSessions");
_doLocalAdminSessionEnum = doLocalAdminSessionEnum;
_localAdminUsername = localAdminUsername;
_localAdminPassword = localAdminPassword;
}
public event ComputerStatusDelegate ComputerStatusEvent;
/// <summary>
/// Uses the NetSessionEnum Win32 API call to get network sessions from a remote computer.
/// These are usually from SMB share accesses or other network sessions of the sort
/// </summary>
/// <param name="computerName"></param>
/// <param name="computerSid"></param>
/// <param name="computerDomain"></param>
/// <returns></returns>
public async Task<SessionAPIResult> ReadUserSessions(string computerName, string computerSid,
string computerDomain)
{
var ret = new SessionAPIResult();
SharpHoundRPC.NetAPINative.NetAPIResult<IEnumerable<SharpHoundRPC.NetAPINative.NetSessionEnumResults>> result;
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))
{
result = _nativeMethods.NetSessionEnum(computerName);
}
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);
result = _nativeMethods.NetSessionEnum(computerName);
}
}
else
{
result = _nativeMethods.NetSessionEnum(computerName);
}
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);
ret.Collected = false;
ret.FailureReason = result.Status.ToString();
return ret;
}
_log.LogDebug("NetSessionEnum succeeded on {ComputerName}", computerName);
await SendComputerStatus(new CSVComputerStatus
{
Status = CSVComputerStatus.StatusSuccess,
Task = "NetSessionEnum",
ComputerName = computerName
});
ret.Collected = true;
var results = new List<Session>();
foreach (var sesInfo in result.Value)
{
var username = sesInfo.Username;
var computerSessionName = sesInfo.ComputerName;
_log.LogTrace("NetSessionEnum Entry: {Username}@{ComputerSessionName} from {ComputerName}", username,
computerSessionName, computerName);
//Filter out blank/null cnames/usernames
if (string.IsNullOrWhiteSpace(computerSessionName) || string.IsNullOrWhiteSpace(username))
{
_log.LogTrace("Skipping NetSessionEnum entry with null session/user");
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);
continue;
}
// Remove leading slashes for unc paths
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
//Attempt to resolve the host name to a SID
resolvedComputerSID = await _utils.ResolveHostToSid(computerSessionName, computerDomain);
//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);
continue;
}
var matches = _utils.GetUserGlobalCatalogMatches(username);
if (matches.Length > 0)
{
results.AddRange(
matches.Select(s => new Session {ComputerSID = resolvedComputerSID, UserSID = s}));
}
else
{
var res = _utils.ResolveAccountName(username, computerDomain);
if (res != null)
results.Add(new Session
{
ComputerSID = resolvedComputerSID,
UserSID = res.ObjectIdentifier
});
}
}
ret.Results = results.ToArray();
return ret;
}
/// <summary>
/// Uses the privileged win32 API, NetWkstaUserEnum, to return the logged on users on a remote computer.
/// Requires administrator rights on the target system
/// </summary>
/// <param name="computerName"></param>
/// <param name="computerSamAccountName"></param>
/// <param name="computerSid"></param>
/// <returns></returns>
public async Task<SessionAPIResult> ReadUserSessionsPrivileged(string computerName,
string computerSamAccountName, string computerSid)
{
var ret = new SessionAPIResult();
SharpHoundRPC.NetAPINative.NetAPIResult<IEnumerable<SharpHoundRPC.NetAPINative.NetWkstaUserEnumResults>> result;
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))
{
result = _nativeMethods.NetWkstaUserEnum(computerName);
}
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);
result = _nativeMethods.NetWkstaUserEnum(computerName);
}
}
else
{
result = _nativeMethods.NetWkstaUserEnum(computerName);
}
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);
ret.Collected = false;
ret.FailureReason = result.Status.ToString();
return ret;
}
_log.LogDebug("NetWkstaUserEnum succeeded on {ComputerName}", computerName);
await SendComputerStatus(new CSVComputerStatus
{
Status = result.Status.ToString(),
Task = "NetWkstaUserEnum",
ComputerName = computerName
});
ret.Collected = true;
var results = new List<TypedPrincipal>();
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);
continue;
}
//Filter out empty usernames and computer sessions
if (string.IsNullOrWhiteSpace(username) || username.EndsWith("$", StringComparison.Ordinal))
{
_log.LogTrace("Skipping null or computer session");
continue;
}
//If we dont have a domain, ignore this object
if (string.IsNullOrWhiteSpace(domain))
{
_log.LogTrace("Skipping null/empty domain");
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;
}
var res = _utils.ResolveAccountName(username, domain);
if (res == null)
continue;
_log.LogTrace("Resolved NetWkstaUserEnum entry: {SID}", res.ObjectIdentifier);
results.Add(res);
}
ret.Results = results.Select(x => new Session
{
ComputerSID = computerSid,
UserSID = x.ObjectIdentifier
}).ToArray();
return ret;
}
public async Task<SessionAPIResult> ReadUserSessionsRegistry(string computerName, string computerDomain,
string computerSid)
{
var ret = new SessionAPIResult();
RegistryKey key = null;
try
{
var task = OpenRegistryKey(computerName, RegistryHive.Users);
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
{
Status = "Timeout",
Task = "RegistrySessionEnum",
ComputerName = computerName
});
return ret;
}
key = task.Result;
ret.Collected = true;
await SendComputerStatus(new CSVComputerStatus
{
Status = CSVComputerStatus.StatusSuccess,
Task = "RegistrySessionEnum",
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
{
ComputerSID = computerSid,
UserSID = x.ObjectIdentifier
})
.ToArray();
return ret;
}
catch (Exception e)
{
_log.LogDebug("Registry session enum failed on {ComputerName}: {Status}", computerName, e.Message);
await SendComputerStatus(new CSVComputerStatus
{
Status = e.Message,
Task = "RegistrySessionEnum",
ComputerName = computerName
});
ret.Collected = false;
ret.FailureReason = e.Message;
return ret;
}
finally
{
key?.Dispose();
}
}
private Task<RegistryKey> OpenRegistryKey(string computerName, RegistryHive hive)
{
return Task.Run(() => RegistryKey.OpenRemoteBaseKey(hive, computerName));
}
private async Task SendComputerStatus(CSVComputerStatus status)
{
if (ComputerStatusEvent is not null) await ComputerStatusEvent.Invoke(status);
}
}
}