< Summary

Class:SharpHoundCommonLib.Processors.ComputerSessionProcessor
Assembly:SharpHoundCommonLib
File(s):D:\a\SharpHoundCommon\SharpHoundCommon\src\CommonLib\Processors\ComputerSessionProcessor.cs
Covered lines:142
Uncovered lines:75
Coverable lines:217
Total lines:328
Line coverage:65.4% (142 of 217)
Covered branches:75
Total branches:118
Branch coverage:63.5% (75 of 118)

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.cctor()100%100%
.ctor(...)66.66%60100%
ReadUserSessions()84%500100%
ReadUserSessionsPrivileged()85.71%280100%
ReadUserSessionsRegistry()0%2200%
OpenRegistryKey(...)100%100%
SendComputerStatus()75%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;
 11using SharpHoundRPC;
 12using SharpHoundRPC.NetAPINative;
 13
 14namespace SharpHoundCommonLib.Processors {
 15    public class ComputerSessionProcessor {
 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
 1027        public ComputerSessionProcessor(ILdapUtils utils,
 1028            NativeMethods nativeMethods = null, ILogger log = null, string currentUserName = null,
 1029            bool doLocalAdminSessionEnum = false,
 2030            string localAdminUsername = null, string localAdminPassword = null) {
 1031            _utils = utils;
 1032            _nativeMethods = nativeMethods ?? new NativeMethods();
 1033            _currentUserName = currentUserName ?? WindowsIdentity.GetCurrent().Name.Split('\\')[1];
 1034            _log = log ?? Logging.LogProvider.CreateLogger("CompSessions");
 1035            _doLocalAdminSessionEnum = doLocalAdminSessionEnum;
 1036            _localAdminUsername = localAdminUsername;
 1037            _localAdminPassword = localAdminPassword;
 1038        }
 39
 40        public event ComputerStatusDelegate ComputerStatusEvent;
 41
 42        /// <summary>
 43        ///     Uses the NetSessionEnum Win32 API call to get network sessions from a remote computer.
 44        ///     These are usually from SMB share accesses or other network sessions of the sort
 45        /// </summary>
 46        /// <param name="computerName"></param>
 47        /// <param name="computerSid"></param>
 48        /// <param name="computerDomain"></param>
 49        /// <param name="timeout"></param>
 50        /// <returns></returns>
 51        public async Task<SessionAPIResult> ReadUserSessions(string computerName, string computerSid,
 752            string computerDomain, TimeSpan timeout = default) {
 53
 1354            if (timeout == default) {
 655                timeout = TimeSpan.FromMinutes(2);
 656            }
 757            var ret = new SessionAPIResult();
 58
 759            _log.LogDebug("Running NetSessionEnum for {ObjectName}", computerName);
 60
 1461            var result = await Task.Run(() => {
 762                NetAPIResult<IEnumerable<NetSessionEnumResults>> result;
 763                if (_doLocalAdminSessionEnum) {
 764                    // If we are authenticating using a local admin, we need to impersonate for this
 065                    using (new Impersonator(_localAdminUsername, ".", _localAdminPassword,
 066                               LogonType.LOGON32_LOGON_NEW_CREDENTIALS, LogonProvider.LOGON32_PROVIDER_WINNT50)) {
 067                        result = _nativeMethods.NetSessionEnum(computerName);
 068                    }
 769
 070                    if (result.IsFailed) {
 771                        // Fall back to default User
 072                        _log.LogDebug(
 073                            "NetSessionEnum failed on {ComputerName} with local admin credentials: {Status}. Fallback to
 074                            computerName, result.Status);
 075                        result = _nativeMethods.NetSessionEnum(computerName);
 076                    }
 777                } else {
 778                    result = _nativeMethods.NetSessionEnum(computerName);
 779                }
 780
 781                return result;
 1482            }).TimeoutAfter(timeout);
 83
 984            if (result.IsFailed) {
 285                await SendComputerStatus(new CSVComputerStatus {
 286                    Status = result.GetErrorStatus(),
 287                    Task = "NetSessionEnum",
 288                    ComputerName = computerName
 289                });
 290                _log.LogTrace("NetSessionEnum failed on {ComputerName}: {Status}", computerName, result.Status);
 291                ret.Collected = false;
 292                ret.FailureReason = result.Status.ToString();
 293                return ret;
 94            }
 95
 596            _log.LogDebug("NetSessionEnum succeeded on {ComputerName}", computerName);
 597            await SendComputerStatus(new CSVComputerStatus {
 598                Status = CSVComputerStatus.StatusSuccess,
 599                Task = "NetSessionEnum",
 5100                ComputerName = computerName
 5101            });
 102
 5103            ret.Collected = true;
 5104            var results = new List<Session>();
 105
 36106            foreach (var sesInfo in result.Value) {
 7107                var username = sesInfo.Username;
 7108                var computerSessionName = sesInfo.ComputerName;
 109
 7110                _log.LogTrace("NetSessionEnum Entry: {Username}@{ComputerSessionName} from {ComputerName}", username,
 7111                    computerSessionName, computerName);
 112
 113                //Filter out blank/null cnames/usernames
 8114                if (string.IsNullOrWhiteSpace(computerSessionName) || string.IsNullOrWhiteSpace(username)) {
 1115                    continue;
 116                }
 117
 118                //Filter out blank usernames, computer accounts, the user we're doing enumeration with, and anonymous lo
 6119                if (username.EndsWith("$") ||
 6120                    username.Equals(_currentUserName, StringComparison.CurrentCultureIgnoreCase) ||
 7121                    username.Equals("anonymous logon", StringComparison.CurrentCultureIgnoreCase)) {
 1122                    continue;
 123                }
 124
 125                // Remove leading slashes for unc paths
 5126                computerSessionName = computerSessionName.TrimStart('\\');
 127
 5128                string resolvedComputerSID = null;
 129                //Resolve "localhost" equivalents to the computer sid
 5130                if (computerSessionName is "[::1]" or "127.0.0.1")
 3131                    resolvedComputerSID = computerSid;
 2132                else if (await _utils.ResolveHostToSid(computerSessionName, computerDomain) is (true, var tempSid))
 133                    //Attempt to resolve the host name to a SID
 1134                    resolvedComputerSID = tempSid;
 135
 136                //Throw out this data if we couldn't resolve it successfully.
 6137                if (resolvedComputerSID == null || !resolvedComputerSID.StartsWith("S-1")) {
 1138                    continue;
 139                }
 140
 4141                var (matchSuccess, sids) = await _utils.GetGlobalCatalogMatches(username, computerDomain);
 7142                if (matchSuccess) {
 3143                    results.AddRange(
 7144                        sids.Select(s => new Session { ComputerSID = resolvedComputerSID, UserSID = s }));
 4145                } else {
 1146                    var res = await _utils.ResolveAccountName(username, computerDomain);
 1147                    if (res.Success)
 1148                        results.Add(new Session {
 1149                            ComputerSID = resolvedComputerSID,
 1150                            UserSID = res.Principal.ObjectIdentifier
 1151                        });
 1152                }
 4153            }
 154
 5155            ret.Results = results.ToArray();
 156
 5157            return ret;
 7158        }
 159
 160        /// <summary>
 161        ///     Uses the privileged win32 API, NetWkstaUserEnum, to return the logged on users on a remote computer.
 162        ///     Requires administrator rights on the target system
 163        /// </summary>
 164        /// <param name="computerName"></param>
 165        /// <param name="computerSamAccountName"></param>
 166        /// <param name="computerSid"></param>
 167        /// <param name="timeout"></param>
 168        /// <returns></returns>
 169        public async Task<SessionAPIResult> ReadUserSessionsPrivileged(string computerName,
 3170            string computerSamAccountName, string computerSid, TimeSpan timeout = default) {
 3171            var ret = new SessionAPIResult();
 5172            if (timeout == default) {
 2173                timeout = TimeSpan.FromMinutes(2);
 2174            }
 175
 3176            _log.LogDebug("Running NetWkstaUserEnum for {ObjectName}", computerName);
 177
 6178            var result = await Task.Run(() => {
 3179                NetAPIResult<IEnumerable<NetWkstaUserEnumResults>>
 3180                    result;
 3181                if (_doLocalAdminSessionEnum) {
 3182                    // If we are authenticating using a local admin, we need to impersonate for this
 0183                    using (new Impersonator(_localAdminUsername, ".", _localAdminPassword,
 0184                               LogonType.LOGON32_LOGON_NEW_CREDENTIALS, LogonProvider.LOGON32_PROVIDER_WINNT50)) {
 0185                        result = _nativeMethods.NetWkstaUserEnum(computerName);
 0186                    }
 3187
 0188                    if (result.IsFailed) {
 3189                        // Fall back to default User
 0190                        _log.LogDebug(
 0191                            "NetWkstaUserEnum failed on {ComputerName} with local admin credentials: {Status}. Fallback 
 0192                            computerName, result.Status);
 0193                        result = _nativeMethods.NetWkstaUserEnum(computerName);
 0194                    }
 3195                } else {
 3196                    result = _nativeMethods.NetWkstaUserEnum(computerName);
 3197                }
 3198
 3199                return result;
 6200            }).TimeoutAfter(timeout);
 201
 5202            if (result.IsFailed) {
 2203                await SendComputerStatus(new CSVComputerStatus {
 2204                    Status = result.GetErrorStatus(),
 2205                    Task = "NetWkstaUserEnum",
 2206                    ComputerName = computerName
 2207                });
 2208                _log.LogTrace("NetWkstaUserEnum failed on {ComputerName}: {Status}", computerName, result.Status);
 2209                ret.Collected = false;
 2210                ret.FailureReason = result.Status.ToString();
 2211                return ret;
 212            }
 213
 1214            _log.LogTrace("NetWkstaUserEnum succeeded on {ComputerName}", computerName);
 1215            await SendComputerStatus(new CSVComputerStatus {
 1216                Status = result.Status.ToString(),
 1217                Task = "NetWkstaUserEnum",
 1218                ComputerName = computerName
 1219            });
 220
 1221            ret.Collected = true;
 222
 1223            var results = new List<TypedPrincipal>();
 33224            foreach (var wkstaUserInfo in result.Value) {
 10225                var domain = wkstaUserInfo.LogonDomain;
 10226                var username = wkstaUserInfo.Username;
 227
 228                //If we dont have a domain or the domain has a space, ignore this object as it's unusable for us
 12229                if (string.IsNullOrWhiteSpace(domain) || domain.Contains(" ")) {
 2230                    continue;
 231                }
 232
 233                //These are local computer accounts.
 9234                if (domain.Equals(computerSamAccountName, StringComparison.CurrentCultureIgnoreCase)) {
 1235                    continue;
 236                }
 237
 238                //Filter out empty usernames and computer sessions
 11239                if (string.IsNullOrWhiteSpace(username) || username.EndsWith("$", StringComparison.Ordinal)) {
 4240                    continue;
 241                }
 242
 5243                if (await _utils.ResolveAccountName(username, domain) is (true, var res)) {
 2244                    results.Add(res);
 2245                }
 3246            }
 247
 3248            ret.Results = results.Select(x => new Session {
 3249                ComputerSID = computerSid,
 3250                UserSID = x.ObjectIdentifier
 3251            }).ToArray();
 252
 1253            return ret;
 3254        }
 255
 256        public async Task<SessionAPIResult> ReadUserSessionsRegistry(string computerName, string computerDomain,
 0257            string computerSid) {
 0258            var ret = new SessionAPIResult();
 259
 0260            _log.LogDebug("Running RegSessionEnum for {ObjectName}", computerName);
 261
 0262            RegistryKey key = null;
 263
 0264            try {
 0265                var task = OpenRegistryKey(computerName, RegistryHive.Users);
 266
 0267                if (await Task.WhenAny(task, Task.Delay(10000)) != task) {
 0268                    _log.LogDebug("Hit timeout on registry enum on {Server}. Abandoning registry enum", computerName);
 0269                    ret.Collected = false;
 0270                    ret.FailureReason = "Timeout";
 0271                    await SendComputerStatus(new CSVComputerStatus {
 0272                        Status = "Timeout",
 0273                        Task = "RegistrySessionEnum",
 0274                        ComputerName = computerName
 0275                    });
 0276                    return ret;
 277                }
 278
 0279                key = task.Result;
 280
 0281                ret.Collected = true;
 0282                await SendComputerStatus(new CSVComputerStatus {
 0283                    Status = CSVComputerStatus.StatusSuccess,
 0284                    Task = "RegistrySessionEnum",
 0285                    ComputerName = computerName
 0286                });
 0287                _log.LogTrace("Registry session enum succeeded on {ComputerName}", computerName);
 0288                var results = new List<Session>();
 0289                foreach (var subkey in key.GetSubKeyNames()) {
 0290                    if (!SidRegex.IsMatch(subkey)) {
 0291                        continue;
 292                    }
 293
 0294                    if (await _utils.ResolveIDAndType(subkey, computerDomain) is (true, var principal)) {
 0295                        results.Add(new Session() {
 0296                            ComputerSID = computerSid,
 0297                            UserSID = principal.ObjectIdentifier
 0298                        });
 0299                    }
 0300                }
 301
 0302                ret.Results = results.ToArray();
 303
 0304                return ret;
 0305            } catch (Exception e) {
 0306                _log.LogTrace("Registry session enum failed on {ComputerName}: {Status}", computerName, e.Message);
 0307                await SendComputerStatus(new CSVComputerStatus {
 0308                    Status = e.Message,
 0309                    Task = "RegistrySessionEnum",
 0310                    ComputerName = computerName
 0311                });
 0312                ret.Collected = false;
 0313                ret.FailureReason = e.Message;
 0314                return ret;
 0315            } finally {
 0316                key?.Dispose();
 0317            }
 0318        }
 319
 0320        private static Task<RegistryKey> OpenRegistryKey(string computerName, RegistryHive hive) {
 0321            return Task.Run(() => RegistryKey.OpenRemoteBaseKey(hive, computerName));
 0322        }
 323
 10324        private async Task SendComputerStatus(CSVComputerStatus status) {
 12325            if (ComputerStatusEvent is not null) await ComputerStatusEvent.Invoke(status);
 10326        }
 327    }
 328}