mirror of
https://github.com/ansible-collections/community.general.git
synced 2025-04-24 11:21:25 -07:00
minor become/runas cleanup (#32564)
* removed/blobified unused PInvoke stuff * added try/finally around impersonation to ensure RevertToSelf is called in all cases * added a few explanatory comments
This commit is contained in:
parent
16e98c8c5b
commit
8ecc7bc4a1
1 changed files with 106 additions and 143 deletions
|
@ -204,13 +204,8 @@ namespace Ansible
|
||||||
public IntPtr lpReserved;
|
public IntPtr lpReserved;
|
||||||
public IntPtr lpDesktop;
|
public IntPtr lpDesktop;
|
||||||
public IntPtr lpTitle;
|
public IntPtr lpTitle;
|
||||||
public Int32 dwX;
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 28)]
|
||||||
public Int32 dwY;
|
public byte[] _data1;
|
||||||
public Int32 dwXSize;
|
|
||||||
public Int32 dwYSize;
|
|
||||||
public Int32 dwXCountChars;
|
|
||||||
public Int32 dwYCountChars;
|
|
||||||
public Int32 dwFillAttribute;
|
|
||||||
public Int32 dwFlags;
|
public Int32 dwFlags;
|
||||||
public Int16 wShowWindow;
|
public Int16 wShowWindow;
|
||||||
public Int16 cbReserved2;
|
public Int16 cbReserved2;
|
||||||
|
@ -257,17 +252,6 @@ namespace Ansible
|
||||||
public SID_AND_ATTRIBUTES User;
|
public SID_AND_ATTRIBUTES User;
|
||||||
}
|
}
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
|
||||||
public struct IO_COUNTERS
|
|
||||||
{
|
|
||||||
public UInt64 ReadOperationCount;
|
|
||||||
public UInt64 WriteOperationCount;
|
|
||||||
public UInt64 OtherOperationCount;
|
|
||||||
public UInt64 ReadTransferCount;
|
|
||||||
public UInt64 WriteTransferCount;
|
|
||||||
public UInt64 OtherTransferCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
public struct JOBOBJECT_BASIC_LIMIT_INFORMATION
|
public struct JOBOBJECT_BASIC_LIMIT_INFORMATION
|
||||||
{
|
{
|
||||||
|
@ -283,14 +267,13 @@ namespace Ansible
|
||||||
}
|
}
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
public struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION
|
public class JOBOBJECT_EXTENDED_LIMIT_INFORMATION
|
||||||
{
|
{
|
||||||
public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation;
|
public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation = new JOBOBJECT_BASIC_LIMIT_INFORMATION();
|
||||||
public IO_COUNTERS IoInfo;
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst=48)]
|
||||||
public UIntPtr ProcessMemoryLimit;
|
public byte[] IO_COUNTERS_BLOB;
|
||||||
public UIntPtr JobMemoryLimit;
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst=4)]
|
||||||
public UIntPtr PeakProcessMemoryUsed;
|
public UIntPtr[] LIMIT_BLOB;
|
||||||
public UIntPtr PeakJobMemoryUsed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Flags]
|
[Flags]
|
||||||
|
@ -305,8 +288,6 @@ namespace Ansible
|
||||||
CREATE_BREAKAWAY_FROM_JOB = 0x01000000,
|
CREATE_BREAKAWAY_FROM_JOB = 0x01000000,
|
||||||
CREATE_DEFAULT_ERROR_MODE = 0x04000000,
|
CREATE_DEFAULT_ERROR_MODE = 0x04000000,
|
||||||
CREATE_NEW_CONSOLE = 0x00000010,
|
CREATE_NEW_CONSOLE = 0x00000010,
|
||||||
CREATE_NEW_PROCESS_GROUP = 0x00000200,
|
|
||||||
CREATE_SEPARATE_WOW_VDM = 0x00000800,
|
|
||||||
CREATE_SUSPENDED = 0x00000004,
|
CREATE_SUSPENDED = 0x00000004,
|
||||||
CREATE_UNICODE_ENVIRONMENT = 0x00000400,
|
CREATE_UNICODE_ENVIRONMENT = 0x00000400,
|
||||||
EXTENDED_STARTUPINFO_PRESENT = 0x00080000
|
EXTENDED_STARTUPINFO_PRESENT = 0x00080000
|
||||||
|
@ -339,9 +320,6 @@ namespace Ansible
|
||||||
public enum LogonProvider
|
public enum LogonProvider
|
||||||
{
|
{
|
||||||
LOGON32_PROVIDER_DEFAULT = 0,
|
LOGON32_PROVIDER_DEFAULT = 0,
|
||||||
LOGON32_PROVIDER_WINNT35 = 1,
|
|
||||||
LOGON32_PROVIDER_WINNT40 = 2,
|
|
||||||
LOGON32_PROVIDER_WINNT50 = 3
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum TokenInformationClass
|
public enum TokenInformationClass
|
||||||
|
@ -363,28 +341,12 @@ namespace Ansible
|
||||||
[Flags]
|
[Flags]
|
||||||
public enum ProcessAccessFlags : uint
|
public enum ProcessAccessFlags : uint
|
||||||
{
|
{
|
||||||
PROCESS_ALL_ACCESS = 0x001F0FFF,
|
|
||||||
PROCESS_TERMINATE = 0x00000001,
|
|
||||||
PROCESS_CREATE_THREAD = 0x00000002,
|
|
||||||
PROCESS_VM_OPERATION = 0x00000008,
|
|
||||||
PROCESS_VM_READ = 0x00000010,
|
|
||||||
PROCESS_VM_WRITE = 0x00000020,
|
|
||||||
PROCESS_DUP_HANDLE = 0x00000040,
|
|
||||||
PROCESS_CREATE_PROCESS = 0x000000080,
|
|
||||||
PROCESS_SET_QUOTA = 0x00000100,
|
|
||||||
PROCESS_SET_INFORMATION = 0x00000200,
|
|
||||||
PROCESS_QUERY_INFORMATION = 0x00000400,
|
PROCESS_QUERY_INFORMATION = 0x00000400,
|
||||||
PROCESS_SUSPEND_RESUME = 0x00000800,
|
|
||||||
PROCESS_QUERY_LIMITED_INFORMATION = 0x00001000,
|
|
||||||
SYNCHRONIZE = 0x00100000
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum SECURITY_IMPERSONATION_LEVEL
|
public enum SECURITY_IMPERSONATION_LEVEL
|
||||||
{
|
{
|
||||||
SecurityAnoynmous,
|
|
||||||
SecurityIdentification,
|
|
||||||
SecurityImpersonation,
|
SecurityImpersonation,
|
||||||
SecurityDelegation
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum TOKEN_TYPE
|
public enum TOKEN_TYPE
|
||||||
|
@ -395,13 +357,7 @@ namespace Ansible
|
||||||
|
|
||||||
enum JobObjectInfoType
|
enum JobObjectInfoType
|
||||||
{
|
{
|
||||||
AssociateCompletionPortInformation = 7,
|
|
||||||
BasicLimitInformation = 2,
|
|
||||||
BasicUIRestrictions = 4,
|
|
||||||
EndOfJobTimeInformation = 6,
|
|
||||||
ExtendedLimitInformation = 9,
|
ExtendedLimitInformation = 9,
|
||||||
SecurityLimitInformation = 5,
|
|
||||||
GroupInformation = 11
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Flags]
|
[Flags]
|
||||||
|
@ -455,8 +411,8 @@ namespace Ansible
|
||||||
private static extern bool SetInformationJobObject(
|
private static extern bool SetInformationJobObject(
|
||||||
IntPtr hJob,
|
IntPtr hJob,
|
||||||
JobObjectInfoType JobObjectInfoClass,
|
JobObjectInfoType JobObjectInfoClass,
|
||||||
IntPtr lpJobObjectInfo,
|
JOBOBJECT_EXTENDED_LIMIT_INFORMATION lpJobObjectInfo,
|
||||||
UInt32 cbJobObjectInfoLength);
|
int cbJobObjectInfoLength);
|
||||||
|
|
||||||
[DllImport("kernel32.dll", SetLastError = true)]
|
[DllImport("kernel32.dll", SetLastError = true)]
|
||||||
private static extern bool AssignProcessToJobObject(
|
private static extern bool AssignProcessToJobObject(
|
||||||
|
@ -475,17 +431,11 @@ namespace Ansible
|
||||||
if (handle == IntPtr.Zero)
|
if (handle == IntPtr.Zero)
|
||||||
throw new Win32Exception("CreateJobObject() failed");
|
throw new Win32Exception("CreateJobObject() failed");
|
||||||
|
|
||||||
JOBOBJECT_BASIC_LIMIT_INFORMATION jobInfo = new JOBOBJECT_BASIC_LIMIT_INFORMATION();
|
|
||||||
jobInfo.LimitFlags = LimitFlags.JOB_OBJECT_LIMIT_BREAKAWAY_OK | LimitFlags.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
|
|
||||||
|
|
||||||
JOBOBJECT_EXTENDED_LIMIT_INFORMATION extendedJobInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION();
|
JOBOBJECT_EXTENDED_LIMIT_INFORMATION extendedJobInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION();
|
||||||
extendedJobInfo.BasicLimitInformation = jobInfo;
|
// on OSs that support nested jobs, one of the jobs must allow breakaway for async to work properly under WinRM
|
||||||
|
extendedJobInfo.BasicLimitInformation.LimitFlags = LimitFlags.JOB_OBJECT_LIMIT_BREAKAWAY_OK | LimitFlags.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
|
||||||
|
|
||||||
int length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
|
if (!SetInformationJobObject(handle, JobObjectInfoType.ExtendedLimitInformation, extendedJobInfo, Marshal.SizeOf(extendedJobInfo)))
|
||||||
IntPtr pExtendedJobInfo = Marshal.AllocHGlobal(length);
|
|
||||||
Marshal.StructureToPtr(extendedJobInfo, pExtendedJobInfo, false);
|
|
||||||
|
|
||||||
if (!SetInformationJobObject(handle, JobObjectInfoType.ExtendedLimitInformation, pExtendedJobInfo, (UInt32)length))
|
|
||||||
throw new Win32Exception("SetInformationJobObject() failed");
|
throw new Win32Exception("SetInformationJobObject() failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -628,8 +578,6 @@ namespace Ansible
|
||||||
{
|
{
|
||||||
SecurityIdentifier account = GetBecomeSid(username);
|
SecurityIdentifier account = GetBecomeSid(username);
|
||||||
|
|
||||||
CreationFlags startup_flags = CreationFlags.CREATE_UNICODE_ENVIRONMENT | CreationFlags.CREATE_BREAKAWAY_FROM_JOB | CreationFlags.CREATE_SUSPENDED;
|
|
||||||
|
|
||||||
STARTUPINFOEX si = new STARTUPINFOEX();
|
STARTUPINFOEX si = new STARTUPINFOEX();
|
||||||
si.startupInfo.dwFlags = (int)StartupInfoFlags.USESTDHANDLES;
|
si.startupInfo.dwFlags = (int)StartupInfoFlags.USESTDHANDLES;
|
||||||
|
|
||||||
|
@ -665,6 +613,9 @@ namespace Ansible
|
||||||
// Create the environment block if set
|
// Create the environment block if set
|
||||||
IntPtr lpEnvironment = IntPtr.Zero;
|
IntPtr lpEnvironment = IntPtr.Zero;
|
||||||
|
|
||||||
|
// To support async + become, we have to do some job magic later, which requires both breakaway and starting suspended
|
||||||
|
CreationFlags startup_flags = CreationFlags.CREATE_UNICODE_ENVIRONMENT | CreationFlags.CREATE_BREAKAWAY_FROM_JOB | CreationFlags.CREATE_SUSPENDED;
|
||||||
|
|
||||||
PROCESS_INFORMATION pi = new PROCESS_INFORMATION();
|
PROCESS_INFORMATION pi = new PROCESS_INFORMATION();
|
||||||
|
|
||||||
// Get the user tokens to try running processes with
|
// Get the user tokens to try running processes with
|
||||||
|
@ -761,98 +712,110 @@ namespace Ansible
|
||||||
GrantAccessToWindowStationAndDesktop(account);
|
GrantAccessToWindowStationAndDesktop(account);
|
||||||
string account_sid = account.ToString();
|
string account_sid = account.ToString();
|
||||||
bool impersonated = false;
|
bool impersonated = false;
|
||||||
IntPtr hSystemTokenDup = IntPtr.Zero;
|
|
||||||
|
|
||||||
// Try to get SYSTEM token handle so we can impersonate to get full admin token
|
try
|
||||||
IntPtr hSystemToken = GetSystemUserHandle();
|
|
||||||
if (hSystemToken == IntPtr.Zero && service_sids.Contains(account_sid))
|
|
||||||
{
|
{
|
||||||
// We need the SYSTEM token if we want to become one of those accounts, fail here
|
IntPtr hSystemTokenDup = IntPtr.Zero;
|
||||||
throw new Win32Exception("Failed to get token for NT AUTHORITY\\SYSTEM");
|
|
||||||
}
|
|
||||||
else if (hSystemToken != IntPtr.Zero)
|
|
||||||
{
|
|
||||||
// We have the token, need to duplicate and impersonate
|
|
||||||
bool dupResult = DuplicateTokenEx(
|
|
||||||
hSystemToken,
|
|
||||||
TokenAccessLevels.MaximumAllowed,
|
|
||||||
IntPtr.Zero,
|
|
||||||
SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation,
|
|
||||||
TOKEN_TYPE.TokenPrimary,
|
|
||||||
out hSystemTokenDup);
|
|
||||||
int lastError = Marshal.GetLastWin32Error();
|
|
||||||
CloseHandle(hSystemToken);
|
|
||||||
|
|
||||||
if (!dupResult && service_sids.Contains(account_sid))
|
// Try to get SYSTEM token handle so we can impersonate to get full admin token
|
||||||
throw new Win32Exception(lastError, "Failed to duplicate token for NT AUTHORITY\\SYSTEM");
|
IntPtr hSystemToken = GetSystemUserHandle();
|
||||||
else if (dupResult && account_sid != "S-1-5-18")
|
if (hSystemToken == IntPtr.Zero && service_sids.Contains(account_sid))
|
||||||
{
|
{
|
||||||
if (ImpersonateLoggedOnUser(hSystemTokenDup))
|
// We need the SYSTEM token if we want to become one of those accounts, fail here
|
||||||
impersonated = true;
|
throw new Win32Exception("Failed to get token for NT AUTHORITY\\SYSTEM");
|
||||||
else if (service_sids.Contains(account_sid))
|
|
||||||
throw new Win32Exception("Failed to impersonate as SYSTEM account");
|
|
||||||
}
|
}
|
||||||
}
|
else if (hSystemToken != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
// We have the token, need to duplicate and impersonate
|
||||||
|
bool dupResult = DuplicateTokenEx(
|
||||||
|
hSystemToken,
|
||||||
|
TokenAccessLevels.MaximumAllowed,
|
||||||
|
IntPtr.Zero,
|
||||||
|
SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation,
|
||||||
|
TOKEN_TYPE.TokenPrimary,
|
||||||
|
out hSystemTokenDup);
|
||||||
|
int lastError = Marshal.GetLastWin32Error();
|
||||||
|
CloseHandle(hSystemToken);
|
||||||
|
|
||||||
LogonType logonType;
|
if (!dupResult && service_sids.Contains(account_sid))
|
||||||
string domain = null;
|
throw new Win32Exception(lastError, "Failed to duplicate token for NT AUTHORITY\\SYSTEM");
|
||||||
if (service_sids.Contains(account_sid))
|
else if (dupResult && account_sid != "S-1-5-18")
|
||||||
{
|
{
|
||||||
logonType = LogonType.LOGON32_LOGON_SERVICE;
|
if (ImpersonateLoggedOnUser(hSystemTokenDup))
|
||||||
domain = "NT AUTHORITY";
|
impersonated = true;
|
||||||
password = null;
|
else if (service_sids.Contains(account_sid))
|
||||||
switch (account_sid)
|
throw new Win32Exception("Failed to impersonate as SYSTEM account");
|
||||||
{
|
}
|
||||||
case "S-1-5-18":
|
// If SYSTEM impersonation failed but we're trying to become a regular user, just proceed;
|
||||||
tokens.Add(hSystemTokenDup);
|
// might get a limited token in UAC-enabled cases, but better than nothing...
|
||||||
return tokens;
|
|
||||||
case "S-1-5-19":
|
|
||||||
username = "LocalService";
|
|
||||||
break;
|
|
||||||
case "S-1-5-20":
|
|
||||||
username = "NetworkService";
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else
|
LogonType logonType;
|
||||||
{
|
string domain = null;
|
||||||
// We are trying to become a local or domain account
|
|
||||||
logonType = LogonType.LOGON32_LOGON_INTERACTIVE;
|
if (service_sids.Contains(account_sid))
|
||||||
if (username.Contains(@"\"))
|
|
||||||
{
|
{
|
||||||
var user_split = username.Split(Convert.ToChar(@"\"));
|
// We're using a well-known service account, do a service logon instead of interactive
|
||||||
domain = user_split[0];
|
logonType = LogonType.LOGON32_LOGON_SERVICE;
|
||||||
username = user_split[1];
|
domain = "NT AUTHORITY";
|
||||||
|
password = null;
|
||||||
|
switch (account_sid)
|
||||||
|
{
|
||||||
|
case "S-1-5-18":
|
||||||
|
tokens.Add(hSystemTokenDup);
|
||||||
|
return tokens;
|
||||||
|
case "S-1-5-19":
|
||||||
|
username = "LocalService";
|
||||||
|
break;
|
||||||
|
case "S-1-5-20":
|
||||||
|
username = "NetworkService";
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (username.Contains("@"))
|
|
||||||
domain = null;
|
|
||||||
else
|
else
|
||||||
domain = ".";
|
{
|
||||||
}
|
// We are trying to become a local or domain account
|
||||||
|
logonType = LogonType.LOGON32_LOGON_INTERACTIVE;
|
||||||
|
if (username.Contains(@"\"))
|
||||||
|
{
|
||||||
|
var user_split = username.Split(Convert.ToChar(@"\"));
|
||||||
|
domain = user_split[0];
|
||||||
|
username = user_split[1];
|
||||||
|
}
|
||||||
|
else if (username.Contains("@"))
|
||||||
|
domain = null;
|
||||||
|
else
|
||||||
|
domain = ".";
|
||||||
|
}
|
||||||
|
|
||||||
IntPtr hToken = IntPtr.Zero;
|
IntPtr hToken = IntPtr.Zero;
|
||||||
if (!LogonUser(
|
if (!LogonUser(
|
||||||
username,
|
username,
|
||||||
domain,
|
domain,
|
||||||
password,
|
password,
|
||||||
logonType,
|
logonType,
|
||||||
LogonProvider.LOGON32_PROVIDER_DEFAULT,
|
LogonProvider.LOGON32_PROVIDER_DEFAULT,
|
||||||
out hToken))
|
out hToken))
|
||||||
|
{
|
||||||
|
throw new Win32Exception("LogonUser failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!service_sids.Contains(account_sid))
|
||||||
|
{
|
||||||
|
// Try and get the elevated token for local/domain account
|
||||||
|
IntPtr hTokenElevated = GetElevatedToken(hToken);
|
||||||
|
tokens.Add(hTokenElevated);
|
||||||
|
}
|
||||||
|
|
||||||
|
// add the original token as a fallback
|
||||||
|
tokens.Add(hToken);
|
||||||
|
}
|
||||||
|
finally
|
||||||
{
|
{
|
||||||
throw new Win32Exception("LogonUser failed");
|
if (impersonated)
|
||||||
|
RevertToSelf();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!service_sids.Contains(account_sid))
|
|
||||||
{
|
|
||||||
// Try and get the elevated token for local/domain account
|
|
||||||
IntPtr hTokenElevated = GetElevatedToken(hToken);
|
|
||||||
tokens.Add(hTokenElevated);
|
|
||||||
}
|
|
||||||
tokens.Add(hToken);
|
|
||||||
|
|
||||||
if (impersonated)
|
|
||||||
RevertToSelf();
|
|
||||||
|
|
||||||
return tokens;
|
return tokens;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue