diff --git a/changelogs/fragments/runas-become-system-privileges.yml b/changelogs/fragments/runas-become-system-privileges.yml new file mode 100644 index 00000000000..37562ca6927 --- /dev/null +++ b/changelogs/fragments/runas-become-system-privileges.yml @@ -0,0 +1,2 @@ +bugfixes: +- runas - create a new token when running as ``SYSTEM`` to ensure it has the full privileges assigned to that account diff --git a/lib/ansible/module_utils/csharp/Ansible.Become.cs b/lib/ansible/module_utils/csharp/Ansible.Become.cs index 25233165332..a7434a765e9 100644 --- a/lib/ansible/module_utils/csharp/Ansible.Become.cs +++ b/lib/ansible/module_utils/csharp/Ansible.Become.cs @@ -339,7 +339,11 @@ namespace Ansible.Become // 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. - systemToken = GetPrimaryTokenForUser(new SecurityIdentifier("S-1-5-18"), new List() { "SeTcbPrivilege" }); + // 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"; + systemToken = GetPrimaryTokenForUser(new SecurityIdentifier("S-1-5-18"), + new List() { "SeTcbPrivilege" }, mostPrivileges); if (systemToken != null) { try @@ -425,7 +429,8 @@ namespace Ansible.Become return userTokens; } - private static SafeNativeHandle GetPrimaryTokenForUser(SecurityIdentifier sid, List requiredPrivileges = null) + private static SafeNativeHandle GetPrimaryTokenForUser(SecurityIdentifier sid, + List requiredPrivileges = null, bool mostPrivileges = false) { // According to CreateProcessWithTokenW we require a token with // TOKEN_QUERY, TOKEN_DUPLICATE and TOKEN_ASSIGN_PRIMARY @@ -435,6 +440,9 @@ 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 @@ -443,10 +451,15 @@ namespace Ansible.Become if (tokenLogonType == NativeHelpers.SECURITY_LOGON_TYPE.Network) continue; + 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) { - List actualPrivileges = TokenUtil.GetTokenPrivileges(hToken).Select(x => x.Name).ToList(); int missing = requiredPrivileges.Where(x => !actualPrivileges.Contains(x)).Count(); if (missing > 0) continue; @@ -455,16 +468,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, SecurityImpersonationLevel.Anonymous, - TokenType.Primary); + 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 (!mostPrivileges) + break; } - return null; + return userToken; } private static SafeNativeHandle GetS4UTokenForUser(SecurityIdentifier sid, LogonType logonType)