< Summary

Class:SharpHoundCommonLib.Processors.ComputerSessionProcessor
Assembly:SharpHoundCommonLib
File(s):D:\a\SharpHoundCommon\SharpHoundCommon\src\CommonLib\Processors\ComputerSessionProcessor.cs
Covered lines:146
Uncovered lines:80
Coverable lines:226
Total lines:357
Line coverage:64.6% (146 of 226)
Covered branches:56
Total branches:84
Branch coverage:66.6% (56 of 84)

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.cctor()100%100%
.ctor(...)83.33%60100%
ReadUserSessions()84.21%38086.41%
ReadUserSessionsPrivileged()75%24083.82%
ReadUserSessionsRegistry()0%1200%
OpenRegistryKey(...)100%100%
SendComputerStatus()25%40100%

File(s)

D:\a\SharpHoundCommon\SharpHoundCommon\src\CommonLib\Processors\ComputerSessionProcessor.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.Linq;
 4using System.Security.Principal;
 5using System.Text.RegularExpressions;
 6using System.Threading.Tasks;
 7using Impersonate;
 8using Microsoft.Extensions.Logging;
 9using Microsoft.Win32;
 10using SharpHoundCommonLib.OutputTypes;
 11
 12namespace SharpHoundCommonLib.Processors
 13{
 14    public class ComputerSessionProcessor
 15    {
 16        public delegate Task ComputerStatusDelegate(CSVComputerStatus status);
 17
 018        private static readonly Regex SidRegex = new(@"S-1-5-21-[0-9]+-[0-9]+-[0-9]+-[0-9]+$", RegexOptions.Compiled);
 19        private readonly string _currentUserName;
 20        private readonly ILogger _log;
 21        private readonly NativeMethods _nativeMethods;
 22        private readonly ILDAPUtils _utils;
 23        private readonly bool _doLocalAdminSessionEnum;
 24        private readonly string _localAdminUsername;
 25        private readonly string _localAdminPassword;
 26
 827        public ComputerSessionProcessor(ILDAPUtils utils, string currentUserName = null, NativeMethods nativeMethods = n
 828        {
 829            _utils = utils;
 830            _nativeMethods = nativeMethods ?? new NativeMethods();
 831            _currentUserName = currentUserName ?? WindowsIdentity.GetCurrent().Name.Split('\\')[1];
 832            _log = log ?? Logging.LogProvider.CreateLogger("CompSessions");
 833            _doLocalAdminSessionEnum = doLocalAdminSessionEnum;
 834            _localAdminUsername = localAdminUsername;
 835            _localAdminPassword = localAdminPassword;
 836        }
 37
 38        public event ComputerStatusDelegate ComputerStatusEvent;
 39
 40        /// <summary>
 41        ///     Uses the NetSessionEnum Win32 API call to get network sessions from a remote computer.
 42        ///     These are usually from SMB share accesses or other network sessions of the sort
 43        /// </summary>
 44        /// <param name="computerName"></param>
 45        /// <param name="computerSid"></param>
 46        /// <param name="computerDomain"></param>
 47        /// <returns></returns>
 48        public async Task<SessionAPIResult> ReadUserSessions(string computerName, string computerSid,
 49            string computerDomain)
 650        {
 651            var ret = new SessionAPIResult();
 52            SharpHoundRPC.NetAPINative.NetAPIResult<IEnumerable<SharpHoundRPC.NetAPINative.NetSessionEnumResults>> resul
 53
 654            if (_doLocalAdminSessionEnum)
 055            {
 56                // If we are authenticating using a local admin, we need to impersonate for this
 57                Impersonator Impersonate;
 058                using (Impersonate = new Impersonator(_localAdminUsername, ".", _localAdminPassword, LogonType.LOGON32_L
 059                {
 060                    result = _nativeMethods.NetSessionEnum(computerName);
 061                }
 62
 063                if (result.IsFailed)
 064                {
 65                    // Fall back to default User
 066                    _log.LogDebug("NetSessionEnum failed on {ComputerName} with local admin credentials: {Status}. Fallb
 067                    result = _nativeMethods.NetSessionEnum(computerName);
 068                }
 069            }
 70            else
 671            {
 672                result = _nativeMethods.NetSessionEnum(computerName);
 673            }
 74
 675            if (result.IsFailed)
 176            {
 177                await SendComputerStatus(new CSVComputerStatus
 178                {
 179                    Status = result.Status.ToString(),
 180                    Task = "NetSessionEnum",
 181                    ComputerName = computerName
 182                });
 183                _log.LogDebug("NetSessionEnum failed on {ComputerName}: {Status}", computerName, result.Status);
 184                ret.Collected = false;
 185                ret.FailureReason = result.Status.ToString();
 186                return ret;
 87            }
 88
 589            _log.LogDebug("NetSessionEnum succeeded on {ComputerName}", computerName);
 590            await SendComputerStatus(new CSVComputerStatus
 591            {
 592                Status = CSVComputerStatus.StatusSuccess,
 593                Task = "NetSessionEnum",
 594                ComputerName = computerName
 595            });
 96
 597            ret.Collected = true;
 598            var results = new List<Session>();
 99
 29100            foreach (var sesInfo in result.Value)
 7101            {
 7102                var username = sesInfo.Username;
 7103                var computerSessionName = sesInfo.ComputerName;
 104
 7105                _log.LogTrace("NetSessionEnum Entry: {Username}@{ComputerSessionName} from {ComputerName}", username,
 7106                    computerSessionName, computerName);
 107
 108                //Filter out blank/null cnames/usernames
 7109                if (string.IsNullOrWhiteSpace(computerSessionName) || string.IsNullOrWhiteSpace(username))
 1110                {
 1111                    _log.LogTrace("Skipping NetSessionEnum entry with null session/user");
 1112                    continue;
 113                }
 114
 115                //Filter out blank usernames, computer accounts, the user we're doing enumeration with, and anonymous lo
 6116                if (username.EndsWith("$") ||
 6117                    username.Equals(_currentUserName, StringComparison.CurrentCultureIgnoreCase) ||
 6118                    username.Equals("anonymous logon", StringComparison.CurrentCultureIgnoreCase))
 1119                {
 1120                    _log.LogTrace("Skipping NetSessionEnum entry for {Username}", username);
 1121                    continue;
 122                }
 123
 124                // Remove leading slashes for unc paths
 5125                computerSessionName = computerSessionName.TrimStart('\\');
 126
 5127                string resolvedComputerSID = null;
 128
 129                //Resolve "localhost" equivalents to the computer sid
 5130                if (computerSessionName is "[::1]" or "127.0.0.1")
 3131                    resolvedComputerSID = computerSid;
 132                else
 133                    //Attempt to resolve the host name to a SID
 2134                    resolvedComputerSID = await _utils.ResolveHostToSid(computerSessionName, computerDomain);
 135
 136                //Throw out this data if we couldn't resolve it successfully.
 5137                if (resolvedComputerSID == null || !resolvedComputerSID.StartsWith("S-1"))
 1138                {
 1139                    _log.LogTrace("Unable to resolve {ComputerSessionName} to real SID", computerSessionName);
 1140                    continue;
 141                }
 142
 4143                var matches = _utils.GetUserGlobalCatalogMatches(username);
 4144                if (matches.Length > 0)
 3145                {
 3146                    results.AddRange(
 7147                        matches.Select(s => new Session {ComputerSID = resolvedComputerSID, UserSID = s}));
 3148                }
 149                else
 1150                {
 1151                    var res = _utils.ResolveAccountName(username, computerDomain);
 1152                    if (res != null)
 1153                        results.Add(new Session
 1154                        {
 1155                            ComputerSID = resolvedComputerSID,
 1156                            UserSID = res.ObjectIdentifier
 1157                        });
 1158                }
 4159            }
 160
 5161            ret.Results = results.ToArray();
 162
 5163            return ret;
 6164        }
 165
 166        /// <summary>
 167        ///     Uses the privileged win32 API, NetWkstaUserEnum, to return the logged on users on a remote computer.
 168        ///     Requires administrator rights on the target system
 169        /// </summary>
 170        /// <param name="computerName"></param>
 171        /// <param name="computerSamAccountName"></param>
 172        /// <param name="computerSid"></param>
 173        /// <returns></returns>
 174        public async Task<SessionAPIResult> ReadUserSessionsPrivileged(string computerName,
 175            string computerSamAccountName, string computerSid)
 2176        {
 2177            var ret = new SessionAPIResult();
 178            SharpHoundRPC.NetAPINative.NetAPIResult<IEnumerable<SharpHoundRPC.NetAPINative.NetWkstaUserEnumResults>> res
 179
 2180            if (_doLocalAdminSessionEnum)
 0181            {
 182                // If we are authenticating using a local admin, we need to impersonate for this
 183                Impersonator Impersonate;
 0184                using (Impersonate = new Impersonator(_localAdminUsername, ".", _localAdminPassword, LogonType.LOGON32_L
 0185                {
 0186                    result = _nativeMethods.NetWkstaUserEnum(computerName);
 0187                }
 188
 0189                if (result.IsFailed)
 0190                {
 191                    // Fall back to default User
 0192                    _log.LogDebug("NetWkstaUserEnum failed on {ComputerName} with local admin credentials: {Status}. Fal
 0193                    result = _nativeMethods.NetWkstaUserEnum(computerName);
 0194                }
 0195            }
 196            else
 2197            {
 2198                result = _nativeMethods.NetWkstaUserEnum(computerName);
 2199            }
 200
 2201            if (result.IsFailed)
 1202            {
 1203                await SendComputerStatus(new CSVComputerStatus
 1204                {
 1205                    Status = result.Status.ToString(),
 1206                    Task = "NetWkstaUserEnum",
 1207                    ComputerName = computerName
 1208                });
 1209                _log.LogDebug("NetWkstaUserEnum failed on {ComputerName}: {Status}", computerName, result.Status);
 1210                ret.Collected = false;
 1211                ret.FailureReason = result.Status.ToString();
 1212                return ret;
 213            }
 214
 1215            _log.LogDebug("NetWkstaUserEnum succeeded on {ComputerName}", computerName);
 1216            await SendComputerStatus(new CSVComputerStatus
 1217            {
 1218                Status = result.Status.ToString(),
 1219                Task = "NetWkstaUserEnum",
 1220                ComputerName = computerName
 1221            });
 222
 1223            ret.Collected = true;
 224
 1225            var results = new List<TypedPrincipal>();
 23226            foreach (var wkstaUserInfo in result.Value)
 10227            {
 10228                var domain = wkstaUserInfo.LogonDomain;
 10229                var username = wkstaUserInfo.Username;
 230
 10231                _log.LogTrace("NetWkstaUserEnum entry: {Username}@{Domain} from {ComputerName}", username, domain,
 10232                    computerName);
 233
 234                //These are local computer accounts.
 10235                if (domain.Equals(computerSamAccountName, StringComparison.CurrentCultureIgnoreCase))
 1236                {
 1237                    _log.LogTrace("Skipping local entry {Username}@{Domain}", username, domain);
 1238                    continue;
 239                }
 240
 241                //Filter out empty usernames and computer sessions
 9242                if (string.IsNullOrWhiteSpace(username) || username.EndsWith("$", StringComparison.Ordinal))
 4243                {
 4244                    _log.LogTrace("Skipping null or computer session");
 4245                    continue;
 246                }
 247
 248                //If we dont have a domain, ignore this object
 5249                if (string.IsNullOrWhiteSpace(domain))
 1250                {
 1251                    _log.LogTrace("Skipping null/empty domain");
 1252                    continue;
 253                }
 254
 255                //Any domain with a space is unusable. It'll be things like NT Authority or Font Driver
 4256                if (domain.Contains(" "))
 1257                {
 1258                    _log.LogTrace("Skipping domain with space: {Domain}", domain);
 1259                    continue;
 260                }
 261
 3262                var res = _utils.ResolveAccountName(username, domain);
 3263                if (res == null)
 1264                    continue;
 265
 2266                _log.LogTrace("Resolved NetWkstaUserEnum entry: {SID}", res.ObjectIdentifier);
 2267                results.Add(res);
 2268            }
 269
 3270            ret.Results = results.Select(x => new Session
 3271            {
 3272                ComputerSID = computerSid,
 3273                UserSID = x.ObjectIdentifier
 3274            }).ToArray();
 275
 1276            return ret;
 2277        }
 278
 279        public async Task<SessionAPIResult> ReadUserSessionsRegistry(string computerName, string computerDomain,
 280            string computerSid)
 0281        {
 0282            var ret = new SessionAPIResult();
 283
 0284            RegistryKey key = null;
 285
 286            try
 0287            {
 0288                var task = OpenRegistryKey(computerName, RegistryHive.Users);
 289
 0290                if (await Task.WhenAny(task, Task.Delay(10000)) != task)
 0291                {
 0292                    _log.LogDebug("Hit timeout on registry enum on {Server}. Abandoning registry enum", computerName);
 0293                    ret.Collected = false;
 0294                    ret.FailureReason = "Timeout";
 0295                    await SendComputerStatus(new CSVComputerStatus
 0296                    {
 0297                        Status = "Timeout",
 0298                        Task = "RegistrySessionEnum",
 0299                        ComputerName = computerName
 0300                    });
 0301                    return ret;
 302                }
 303
 0304                key = task.Result;
 305
 0306                ret.Collected = true;
 0307                await SendComputerStatus(new CSVComputerStatus
 0308                {
 0309                    Status = CSVComputerStatus.StatusSuccess,
 0310                    Task = "RegistrySessionEnum",
 0311                    ComputerName = computerName
 0312                });
 0313                _log.LogDebug("Registry session enum succeeded on {ComputerName}", computerName);
 0314                ret.Results = key.GetSubKeyNames()
 0315                    .Where(subkey => SidRegex.IsMatch(subkey))
 0316                    .Select(x => _utils.ResolveIDAndType(x, computerDomain))
 0317                    .Where(x => x != null)
 0318                    .Select(x =>
 0319                        new Session
 0320                        {
 0321                            ComputerSID = computerSid,
 0322                            UserSID = x.ObjectIdentifier
 0323                        })
 0324                    .ToArray();
 325
 0326                return ret;
 327            }
 0328            catch (Exception e)
 0329            {
 0330                _log.LogDebug("Registry session enum failed on {ComputerName}: {Status}", computerName, e.Message);
 0331                await SendComputerStatus(new CSVComputerStatus
 0332                {
 0333                    Status = e.Message,
 0334                    Task = "RegistrySessionEnum",
 0335                    ComputerName = computerName
 0336                });
 0337                ret.Collected = false;
 0338                ret.FailureReason = e.Message;
 0339                return ret;
 340            }
 341            finally
 0342            {
 0343                key?.Dispose();
 0344            }
 0345        }
 346
 347        private Task<RegistryKey> OpenRegistryKey(string computerName, RegistryHive hive)
 0348        {
 0349            return Task.Run(() => RegistryKey.OpenRemoteBaseKey(hive, computerName));
 0350        }
 351
 352        private async Task SendComputerStatus(CSVComputerStatus status)
 8353        {
 8354            if (ComputerStatusEvent is not null) await ComputerStatusEvent.Invoke(status);
 8355        }
 356    }
 357}