diff --git a/src/CommonLib/Impersonate.cs b/src/CommonLib/Impersonate.cs new file mode 100644 index 00000000..fe222c54 --- /dev/null +++ b/src/CommonLib/Impersonate.cs @@ -0,0 +1,199 @@ +//credit to Phillip Allan-Harding (Twitter @phillipharding) for this library. +using System; +using System.ComponentModel; +using System.Runtime.InteropServices; +using System.Security.Principal; +using System.Xml.Linq; + +namespace Impersonate +{ + public enum LogonType + { + LOGON32_LOGON_INTERACTIVE = 2, + LOGON32_LOGON_NETWORK = 3, + LOGON32_LOGON_BATCH = 4, + LOGON32_LOGON_SERVICE = 5, + LOGON32_LOGON_UNLOCK = 7, + LOGON32_LOGON_NETWORK_CLEARTEXT = 8, // Win2K or higher + LOGON32_LOGON_NEW_CREDENTIALS = 9 // Win2K or higher + }; + + public enum LogonProvider + { + LOGON32_PROVIDER_DEFAULT = 0, + LOGON32_PROVIDER_WINNT35 = 1, + LOGON32_PROVIDER_WINNT40 = 2, + LOGON32_PROVIDER_WINNT50 = 3 + }; + + public enum ImpersonationLevel + { + SecurityAnonymous = 0, + SecurityIdentification = 1, + SecurityImpersonation = 2, + SecurityDelegation = 3 + } + + class Win32NativeMethods + { + [DllImport("advapi32.dll", SetLastError = true)] + public static extern int LogonUser(string lpszUserName, + string lpszDomain, + string lpszPassword, + int dwLogonType, + int dwLogonProvider, + ref IntPtr phToken); + + [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)] + public static extern int DuplicateToken(IntPtr hToken, + int impersonationLevel, + ref IntPtr hNewToken); + + [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)] + public static extern bool RevertToSelf(); + + [DllImport("kernel32.dll", CharSet = CharSet.Auto)] + public static extern bool CloseHandle(IntPtr handle); + } + + /// + /// Allows code to be executed under the security context of a specified user account. + /// + /// + /// + /// Implements IDispose, so can be used via a using-directive or method calls; + /// ... + /// + /// var imp = new Impersonator( "myUsername", "myDomainname", "myPassword" ); + /// imp.UndoImpersonation(); + /// + /// ... + /// + /// var imp = new Impersonator(); + /// imp.Impersonate("myUsername", "myDomainname", "myPassword"); + /// imp.UndoImpersonation(); + /// + /// ... + /// + /// using ( new Impersonator( "myUsername", "myDomainname", "myPassword" ) ) + /// { + /// ... + /// 1 + /// ... + /// } + /// + /// ... + /// + public class Impersonator : IDisposable + { + private WindowsImpersonationContext _wic; + + /// + /// Begins impersonation with the given credentials, Logon type and Logon provider. + /// + /// Name of the user. + /// Name of the domain. + /// The password. + ///< param name="logonType">Type of the logon. + /// The logon provider. + public Impersonator(string userName, string domainName, string password, LogonType logonType, LogonProvider logonProvider) + { + Impersonate(userName, domainName, password, logonType, logonProvider); + } + + /// + /// Begins impersonation with the given credentials. + /// + /// Name of the user. + /// Name of the domain. + /// The password. + public Impersonator(string userName, string domainName, string password) + { + Impersonate(userName, domainName, password, LogonType.LOGON32_LOGON_INTERACTIVE, LogonProvider.LOGON32_PROVIDER_DEFAULT); + } + + /// + /// Initializes a new instance of the class. + /// + public Impersonator() + { } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + UndoImpersonation(); + } + + /// + /// Impersonates the specified user account. + /// + /// Name of the user. + /// Name of the domain. + /// The password. + public void Impersonate(string userName, string domainName, string password) + { + Impersonate(userName, domainName, password, LogonType.LOGON32_LOGON_INTERACTIVE, LogonProvider.LOGON32_PROVIDER_DEFAULT); + } + + /// + /// Impersonates the specified user account. + /// + /// Name of the user. + /// Name of the domain. + /// The password. + ///< param name="logonType">Type of the logon. + /// The logon provider. + public void Impersonate(string userName, string domainName, string password, LogonType logonType, LogonProvider logonProvider) + { + UndoImpersonation(); + + IntPtr logonToken = IntPtr.Zero; + IntPtr logonTokenDuplicate = IntPtr.Zero; + try + { + // revert to the application pool identity, saving the identity of the current requestor + _wic = WindowsIdentity.Impersonate(IntPtr.Zero); + + // do logon & impersonate + if (Win32NativeMethods.LogonUser(userName, + domainName, + password, + (int)logonType, + (int)logonProvider, + ref logonToken) != 0) + { + if (Win32NativeMethods.DuplicateToken(logonToken, (int)ImpersonationLevel.SecurityImpersonation, ref logonTokenDuplicate) != 0) + { + var wi = new WindowsIdentity(logonTokenDuplicate); + wi.Impersonate(); // discard the returned identity context (which is the context of the application pool) + } + else + throw new Win32Exception(Marshal.GetLastWin32Error()); + } + else + throw new Win32Exception(Marshal.GetLastWin32Error()); + } + finally + { + if (logonToken != IntPtr.Zero) + Win32NativeMethods.CloseHandle(logonToken); + + if (logonTokenDuplicate != IntPtr.Zero) + Win32NativeMethods.CloseHandle(logonTokenDuplicate); + } + } + + /// + /// Stops impersonation. + /// + private void UndoImpersonation() + { + // restore saved requestor identity + if (_wic != null) + _wic.Undo(); + _wic = null; + } + } +} diff --git a/src/CommonLib/Processors/ComputerSessionProcessor.cs b/src/CommonLib/Processors/ComputerSessionProcessor.cs index 45ca81a5..3e4010f0 100644 --- a/src/CommonLib/Processors/ComputerSessionProcessor.cs +++ b/src/CommonLib/Processors/ComputerSessionProcessor.cs @@ -1,15 +1,22 @@ using System; using System.Collections.Generic; +using System.DirectoryServices.ActiveDirectory; +using System.Drawing.Text; using System.Linq; +using System.Runtime.InteropServices; +using System.Runtime.Remoting.Contexts; 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); @@ -19,14 +26,19 @@ public class ComputerSessionProcessor 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) + 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; @@ -43,8 +55,29 @@ public async Task ReadUserSessions(string computerName, string string computerDomain) { var ret = new SessionAPIResult(); + SharpHoundRPC.NetAPINative.NetAPIResult> 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); + } - var result = _nativeMethods.NetSessionEnum(computerName); if (result.IsFailed) { await SendComputerStatus(new CSVComputerStatus @@ -148,7 +181,28 @@ public async Task ReadUserSessionsPrivileged(string computerNa string computerSamAccountName, string computerSid) { var ret = new SessionAPIResult(); - var result = _nativeMethods.NetWkstaUserEnum(computerName); + SharpHoundRPC.NetAPINative.NetAPIResult> 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) { @@ -232,7 +286,6 @@ public async Task ReadUserSessionsRegistry(string computerName string computerSid) { var ret = new SessionAPIResult(); - RegistryKey key = null; try @@ -306,4 +359,4 @@ private async Task SendComputerStatus(CSVComputerStatus status) if (ComputerStatusEvent is not null) await ComputerStatusEvent.Invoke(status); } } -} \ No newline at end of file +}