From b5ae8a382baca04442ad82e654ab6e35379844c3 Mon Sep 17 00:00:00 2001 From: Jordan Borean Date: Fri, 13 Sep 2024 14:25:58 +1000 Subject: [PATCH] runas - create new SYTEM token for become (#83827) Instead of re-using the token used in impersonation, this change will create a new token for the SYSTEM account as returned by LogonUser. The benefits of this is that the token will contain the full privileges for the SYSTEM account rather than potentially one that has restricted privileges we used during impersonation. It should also help avoid problems on Windows that fails on status 0x0000016F when the impersonated token during become was from a process that is restricted from creating sub processes. --- changelogs/fragments/become-runas-system.yml | 4 ++ .../module_utils/csharp/Ansible.Become.cs | 39 +++++++------------ 2 files changed, 18 insertions(+), 25 deletions(-) create mode 100644 changelogs/fragments/become-runas-system.yml diff --git a/changelogs/fragments/become-runas-system.yml b/changelogs/fragments/become-runas-system.yml new file mode 100644 index 00000000000..2ad7e465628 --- /dev/null +++ b/changelogs/fragments/become-runas-system.yml @@ -0,0 +1,4 @@ +bugfixes: + - -> + runas become - Generate new token for the SYSTEM token to use for become. This should result in the full SYSTEM + token being used and problems starting the process that fails with ``The process creation has been blocked``. diff --git a/lib/ansible/module_utils/csharp/Ansible.Become.cs b/lib/ansible/module_utils/csharp/Ansible.Become.cs index d3bb1564fa6..68d4d11d7a5 100644 --- a/lib/ansible/module_utils/csharp/Ansible.Become.cs +++ b/lib/ansible/module_utils/csharp/Ansible.Become.cs @@ -333,13 +333,12 @@ 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 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 mostPrivileges = becomeSid == "S-1-5-18"; + // Try and impersonate a SYSTEM token. We need the SeTcbPrivilege for + // - LogonUser for a service SID + // - S4U logon + // - Token elevation systemToken = GetPrimaryTokenForUser(new SecurityIdentifier("S-1-5-18"), - new List() { "SeTcbPrivilege" }, mostPrivileges); + new List() { "SeTcbPrivilege" }); if (systemToken != null) { try @@ -357,11 +356,9 @@ 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 - else if (!SERVICE_SIDS.Contains(becomeSid) && password == null && logonType != LogonType.NewCredentials) + 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 @@ -384,6 +381,11 @@ 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"; @@ -426,7 +428,7 @@ namespace Ansible.Become } private static SafeNativeHandle GetPrimaryTokenForUser(SecurityIdentifier sid, - List requiredPrivileges = null, bool mostPrivileges = false) + List requiredPrivileges = null) { // According to CreateProcessWithTokenW we require a token with // TOKEN_QUERY, TOKEN_DUPLICATE and TOKEN_ASSIGN_PRIMARY @@ -436,9 +438,6 @@ namespace Ansible.Become TokenAccessLevels.AssignPrimary | TokenAccessLevels.Impersonate; - SafeNativeHandle userToken = null; - int privilegeCount = 0; - foreach (SafeNativeHandle hToken in TokenUtil.EnumerateUserTokens(sid, dwAccess)) { // Filter out any Network logon tokens, using become with that is useless when S4U @@ -449,10 +448,6 @@ namespace Ansible.Become List 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 (mostPrivileges && privilegeCount >= actualPrivileges.Count) - continue; - // Check that the required privileges are on the token if (requiredPrivileges != null) { @@ -464,22 +459,16 @@ namespace Ansible.Become // Duplicate the token to convert it to a primary token with the access level required. try { - userToken = TokenUtil.DuplicateToken(hToken, TokenAccessLevels.MaximumAllowed, + return 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 (!mostPrivileges) - break; } - return userToken; + return null; } private static SafeNativeHandle GetS4UTokenForUser(SecurityIdentifier sid, LogonType logonType)