summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJordan Borean <jborean93@gmail.com>2024-11-12 00:16:22 +0100
committerGitHub <noreply@github.com>2024-11-12 00:16:22 +0100
commit3befdd3d151e66a7b17cbe49e31d158903191a76 (patch)
treec8a00cafd1d216f7480986431997eded3df5f13d
parentansible-test - align 2025 connection defaults to CI matrix (#84291) (diff)
downloadansible-3befdd3d151e66a7b17cbe49e31d158903191a76.tar.xz
ansible-3befdd3d151e66a7b17cbe49e31d158903191a76.zip
Fix runas become SYSTEM logic (#84280)
Fixes the logic when attempting to become the SYSTEM user using the runas plugin. It was incorrectly assumed that calling LogonUser with the SYSTEM username would produce a new token with all the privileges but instead it creates a copy of the existing token. This reverts the logic back to the original process and adds in new logic to avoid any tokens that are restricted from creating new processes.
-rw-r--r--changelogs/fragments/become-runas-system-deux.yml3
-rw-r--r--lib/ansible/module_utils/csharp/Ansible.AccessToken.cs51
-rw-r--r--lib/ansible/module_utils/csharp/Ansible.Become.cs104
3 files changed, 123 insertions, 35 deletions
diff --git a/changelogs/fragments/become-runas-system-deux.yml b/changelogs/fragments/become-runas-system-deux.yml
new file mode 100644
index 0000000000..e8b17f92a4
--- /dev/null
+++ b/changelogs/fragments/become-runas-system-deux.yml
@@ -0,0 +1,3 @@
+bugfixes:
+ - >-
+ runas become - Fix up become logic to still get the SYSTEM token with the most privileges when running as SYSTEM.
diff --git a/lib/ansible/module_utils/csharp/Ansible.AccessToken.cs b/lib/ansible/module_utils/csharp/Ansible.AccessToken.cs
index 49fba4e5e7..a7959efb30 100644
--- a/lib/ansible/module_utils/csharp/Ansible.AccessToken.cs
+++ b/lib/ansible/module_utils/csharp/Ansible.AccessToken.cs
@@ -339,19 +339,47 @@ namespace Ansible.AccessToken
public static IEnumerable<SafeNativeHandle> EnumerateUserTokens(SecurityIdentifier sid,
TokenAccessLevels access = TokenAccessLevels.Query)
{
+ return EnumerateUserTokens(sid, access, (p, h) => true);
+ }
+
+ public static IEnumerable<SafeNativeHandle> EnumerateUserTokens(
+ SecurityIdentifier sid,
+ TokenAccessLevels access,
+ Func<System.Diagnostics.Process, SafeNativeHandle, bool> processFilter)
+ {
+ // We always need the Query access level so we can query the TokenUser
+ access |= TokenAccessLevels.Query;
+
foreach (System.Diagnostics.Process process in System.Diagnostics.Process.GetProcesses())
{
- // We always need the Query access level so we can query the TokenUser
using (process)
- using (SafeNativeHandle hToken = TryOpenAccessToken(process, access | TokenAccessLevels.Query))
+ using (SafeNativeHandle processHandle = NativeMethods.OpenProcess(ProcessAccessFlags.QueryInformation, false, (UInt32)process.Id))
{
- if (hToken == null)
+ if (processHandle.IsInvalid)
+ {
continue;
+ }
- if (!sid.Equals(GetTokenUser(hToken)))
+ if (!processFilter(process, processHandle))
+ {
continue;
+ }
+
+ SafeNativeHandle accessToken;
+ if (!NativeMethods.OpenProcessToken(processHandle, access, out accessToken))
+ {
+ continue;
+ }
+
+ using (accessToken)
+ {
+ if (!sid.Equals(GetTokenUser(accessToken)))
+ {
+ continue;
+ }
- yield return hToken;
+ yield return accessToken;
+ }
}
}
}
@@ -440,18 +468,5 @@ namespace Ansible.AccessToken
for (int i = 0; i < array.Length; i++, ptrOffset = IntPtr.Add(ptrOffset, Marshal.SizeOf(typeof(T))))
array[i] = (T)Marshal.PtrToStructure(ptrOffset, typeof(T));
}
-
- private static SafeNativeHandle TryOpenAccessToken(System.Diagnostics.Process process, TokenAccessLevels access)
- {
- try
- {
- using (SafeNativeHandle hProcess = OpenProcess(process.Id, ProcessAccessFlags.QueryInformation, false))
- return OpenProcessToken(hProcess, access);
- }
- catch (Win32Exception)
- {
- return null;
- }
- }
}
}
diff --git a/lib/ansible/module_utils/csharp/Ansible.Become.cs b/lib/ansible/module_utils/csharp/Ansible.Become.cs
index 68d4d11d7a..08b73d404b 100644
--- a/lib/ansible/module_utils/csharp/Ansible.Become.cs
+++ b/lib/ansible/module_utils/csharp/Ansible.Become.cs
@@ -93,10 +93,21 @@ namespace Ansible.Become
CachedRemoteInteractive,
CachedUnlock
}
+
+ [Flags]
+ public enum ProcessChildProcessPolicyFlags
+ {
+ None = 0x0,
+ NoChildProcessCreation = 0x1,
+ AuditNoChildProcessCreation = 0x2,
+ AllowSecureProcessCreation = 0x4,
+ }
}
internal class NativeMethods
{
+ public const int ProcessChildProcessPolicy = 13;
+
[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool AllocateLocallyUniqueId(
out Luid Luid);
@@ -116,6 +127,13 @@ namespace Ansible.Become
[DllImport("kernel32.dll")]
public static extern UInt32 GetCurrentThreadId();
+ [DllImport("kernel32.dll", SetLastError = true)]
+ public static extern bool GetProcessMitigationPolicy(
+ SafeNativeHandle hProcess,
+ int MitigationPolicy,
+ ref NativeHelpers.ProcessChildProcessPolicyFlags lpBuffer,
+ IntPtr dwLength);
+
[DllImport("user32.dll", SetLastError = true)]
public static extern NoopSafeHandle GetProcessWindowStation();
@@ -217,6 +235,7 @@ namespace Ansible.Become
};
private static int WINDOWS_STATION_ALL_ACCESS = 0x000F037F;
private static int DESKTOP_RIGHTS_ALL_ACCESS = 0x000F01FF;
+ private static bool _getProcessMitigationPolicySupported = true;
public static Result CreateProcessAsUser(string username, string password, string command)
{
@@ -333,12 +352,13 @@ namespace Ansible.Become
// Grant access to the current Windows Station and Desktop to the become user
GrantAccessToWindowStationAndDesktop(account);
- // Try and impersonate a SYSTEM token. We need the SeTcbPrivilege for
- // - LogonUser for a service SID
- // - S4U logon
- // - Token elevation
+ // Try and impersonate a SYSTEM token, we need a SYSTEM token to either become a well known service
+ // account or have administrative rights on the become access token.
+ // If we ultimately are becoming the SYSTEM account we want the token with the most privileges available.
+ // https://github.com/ansible/ansible/issues/71453
+ bool usedForProcess = becomeSid == "S-1-5-18";
systemToken = GetPrimaryTokenForUser(new SecurityIdentifier("S-1-5-18"),
- new List<string>() { "SeTcbPrivilege" });
+ new List<string>() { "SeTcbPrivilege" }, usedForProcess);
if (systemToken != null)
{
try
@@ -356,9 +376,11 @@ namespace Ansible.Become
try
{
+ if (becomeSid == "S-1-5-18")
+ userTokens.Add(systemToken);
// Cannot use String.IsEmptyOrNull() as an empty string is an account that doesn't have a pass.
// We only use S4U if no password was defined or it was null
- if (!SERVICE_SIDS.Contains(becomeSid) && password == null && logonType != LogonType.NewCredentials)
+ else if (!SERVICE_SIDS.Contains(becomeSid) && password == null && logonType != LogonType.NewCredentials)
{
// If no password was specified, try and duplicate an existing token for that user or use S4U to
// generate one without network credentials
@@ -381,11 +403,6 @@ namespace Ansible.Become
string domain = null;
switch (becomeSid)
{
- case "S-1-5-18":
- logonType = LogonType.Service;
- domain = "NT AUTHORITY";
- username = "SYSTEM";
- break;
case "S-1-5-19":
logonType = LogonType.Service;
domain = "NT AUTHORITY";
@@ -427,8 +444,10 @@ namespace Ansible.Become
return userTokens;
}
- private static SafeNativeHandle GetPrimaryTokenForUser(SecurityIdentifier sid,
- List<string> requiredPrivileges = null)
+ private static SafeNativeHandle GetPrimaryTokenForUser(
+ SecurityIdentifier sid,
+ List<string> requiredPrivileges = null,
+ bool usedForProcess = false)
{
// According to CreateProcessWithTokenW we require a token with
// TOKEN_QUERY, TOKEN_DUPLICATE and TOKEN_ASSIGN_PRIMARY
@@ -438,7 +457,19 @@ namespace Ansible.Become
TokenAccessLevels.AssignPrimary |
TokenAccessLevels.Impersonate;
- foreach (SafeNativeHandle hToken in TokenUtil.EnumerateUserTokens(sid, dwAccess))
+ SafeNativeHandle userToken = null;
+ int privilegeCount = 0;
+
+ // If we are using this token for the process, we need to check the
+ // process mitigation policy allows child processes to be created.
+ var processFilter = usedForProcess
+ ? (Func<System.Diagnostics.Process, SafeNativeHandle, bool>)((p, t) =>
+ {
+ return GetProcessChildProcessPolicyFlags(t) == NativeHelpers.ProcessChildProcessPolicyFlags.None;
+ })
+ : ((p, t) => true);
+
+ foreach (SafeNativeHandle hToken in TokenUtil.EnumerateUserTokens(sid, dwAccess, processFilter))
{
// Filter out any Network logon tokens, using become with that is useless when S4U
// can give us a Batch logon
@@ -448,6 +479,10 @@ namespace Ansible.Become
List<string> actualPrivileges = TokenUtil.GetTokenPrivileges(hToken).Select(x => x.Name).ToList();
+ // If the token has less or the same number of privileges than the current token, skip it.
+ if (usedForProcess && privilegeCount >= actualPrivileges.Count)
+ continue;
+
// Check that the required privileges are on the token
if (requiredPrivileges != null)
{
@@ -459,16 +494,22 @@ namespace Ansible.Become
// Duplicate the token to convert it to a primary token with the access level required.
try
{
- return TokenUtil.DuplicateToken(hToken, TokenAccessLevels.MaximumAllowed,
+ userToken = TokenUtil.DuplicateToken(hToken, TokenAccessLevels.MaximumAllowed,
SecurityImpersonationLevel.Anonymous, TokenType.Primary);
+ privilegeCount = actualPrivileges.Count;
}
catch (Process.Win32Exception)
{
continue;
}
+
+ // If we don't care about getting the token with the most privileges, escape the loop as we already
+ // have a token.
+ if (!usedForProcess)
+ break;
}
- return null;
+ return userToken;
}
private static SafeNativeHandle GetS4UTokenForUser(SecurityIdentifier sid, LogonType logonType)
@@ -581,6 +622,35 @@ namespace Ansible.Become
return null;
}
+ private static NativeHelpers.ProcessChildProcessPolicyFlags GetProcessChildProcessPolicyFlags(SafeNativeHandle processHandle)
+ {
+ // Because this is only used to check the policy, we ignore any
+ // errors and pretend that the policy is None.
+ NativeHelpers.ProcessChildProcessPolicyFlags policy = NativeHelpers.ProcessChildProcessPolicyFlags.None;
+
+ if (_getProcessMitigationPolicySupported)
+ {
+ try
+ {
+ if (NativeMethods.GetProcessMitigationPolicy(
+ processHandle,
+ NativeMethods.ProcessChildProcessPolicy,
+ ref policy,
+ (IntPtr)4))
+ {
+ return policy;
+ }
+ }
+ catch (EntryPointNotFoundException)
+ {
+ // If the function is not available, we won't try to call it again
+ _getProcessMitigationPolicySupported = false;
+ }
+ }
+
+ return policy;
+ }
+
private static NativeHelpers.SECURITY_LOGON_TYPE GetTokenLogonType(SafeNativeHandle hToken)
{
TokenStatistics stats = TokenUtil.GetTokenStatistics(hToken);
@@ -637,4 +707,4 @@ namespace Ansible.Become
{ }
}
}
-}
+} \ No newline at end of file