From 9b8fbe6a1bcf5e6b3d3836db40f998c27e3354b7 Mon Sep 17 00:00:00 2001
From: LuemmelSec <58529760+LuemmelSec@users.noreply.github.com>
Date: Tue, 14 Feb 2023 13:26:49 +0100
Subject: [PATCH] Session Enumeration as local Admin User
---
src/CommonLib/Impersonate.cs | 199 ++++++++++++++++++
.../Processors/ComputerSessionProcessor.cs | 65 +++++-
2 files changed, 258 insertions(+), 6 deletions(-)
create mode 100644 src/CommonLib/Impersonate.cs
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 >
+ ///< param name="logonType">Type of the logon.
+ /// The logon provider. param >
+ 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. param >
+ 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. param >
+ 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 >
+ ///< param name="logonType">Type of the logon.
+ /// The logon provider. param >
+ 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
+}