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.
pull/83953/head
Jordan Borean 2 months ago committed by GitHub
parent 33d4ba8fa2
commit b5ae8a382b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -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``.

@ -333,13 +333,12 @@ namespace Ansible.Become
// Grant access to the current Windows Station and Desktop to the become user // Grant access to the current Windows Station and Desktop to the become user
GrantAccessToWindowStationAndDesktop(account); GrantAccessToWindowStationAndDesktop(account);
// Try and impersonate a SYSTEM token, we need a SYSTEM token to either become a well known service // Try and impersonate a SYSTEM token. We need the SeTcbPrivilege for
// account or have administrative rights on the become access token. // - LogonUser for a service SID
// If we ultimately are becoming the SYSTEM account we want the token with the most privileges available. // - S4U logon
// https://github.com/ansible/ansible/issues/71453 // - Token elevation
bool mostPrivileges = becomeSid == "S-1-5-18";
systemToken = GetPrimaryTokenForUser(new SecurityIdentifier("S-1-5-18"), systemToken = GetPrimaryTokenForUser(new SecurityIdentifier("S-1-5-18"),
new List<string>() { "SeTcbPrivilege" }, mostPrivileges); new List<string>() { "SeTcbPrivilege" });
if (systemToken != null) if (systemToken != null)
{ {
try try
@ -357,11 +356,9 @@ namespace Ansible.Become
try 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. // 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 // 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 // If no password was specified, try and duplicate an existing token for that user or use S4U to
// generate one without network credentials // generate one without network credentials
@ -384,6 +381,11 @@ namespace Ansible.Become
string domain = null; string domain = null;
switch (becomeSid) switch (becomeSid)
{ {
case "S-1-5-18":
logonType = LogonType.Service;
domain = "NT AUTHORITY";
username = "SYSTEM";
break;
case "S-1-5-19": case "S-1-5-19":
logonType = LogonType.Service; logonType = LogonType.Service;
domain = "NT AUTHORITY"; domain = "NT AUTHORITY";
@ -426,7 +428,7 @@ namespace Ansible.Become
} }
private static SafeNativeHandle GetPrimaryTokenForUser(SecurityIdentifier sid, private static SafeNativeHandle GetPrimaryTokenForUser(SecurityIdentifier sid,
List<string> requiredPrivileges = null, bool mostPrivileges = false) List<string> requiredPrivileges = null)
{ {
// According to CreateProcessWithTokenW we require a token with // According to CreateProcessWithTokenW we require a token with
// TOKEN_QUERY, TOKEN_DUPLICATE and TOKEN_ASSIGN_PRIMARY // TOKEN_QUERY, TOKEN_DUPLICATE and TOKEN_ASSIGN_PRIMARY
@ -436,9 +438,6 @@ namespace Ansible.Become
TokenAccessLevels.AssignPrimary | TokenAccessLevels.AssignPrimary |
TokenAccessLevels.Impersonate; TokenAccessLevels.Impersonate;
SafeNativeHandle userToken = null;
int privilegeCount = 0;
foreach (SafeNativeHandle hToken in TokenUtil.EnumerateUserTokens(sid, dwAccess)) foreach (SafeNativeHandle hToken in TokenUtil.EnumerateUserTokens(sid, dwAccess))
{ {
// Filter out any Network logon tokens, using become with that is useless when S4U // Filter out any Network logon tokens, using become with that is useless when S4U
@ -449,10 +448,6 @@ namespace Ansible.Become
List<string> actualPrivileges = TokenUtil.GetTokenPrivileges(hToken).Select(x => x.Name).ToList(); 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 (mostPrivileges && privilegeCount >= actualPrivileges.Count)
continue;
// Check that the required privileges are on the token // Check that the required privileges are on the token
if (requiredPrivileges != null) 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. // Duplicate the token to convert it to a primary token with the access level required.
try try
{ {
userToken = TokenUtil.DuplicateToken(hToken, TokenAccessLevels.MaximumAllowed, return TokenUtil.DuplicateToken(hToken, TokenAccessLevels.MaximumAllowed,
SecurityImpersonationLevel.Anonymous, TokenType.Primary); SecurityImpersonationLevel.Anonymous, TokenType.Primary);
privilegeCount = actualPrivileges.Count;
} }
catch (Process.Win32Exception) catch (Process.Win32Exception)
{ {
continue; 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) private static SafeNativeHandle GetS4UTokenForUser(SecurityIdentifier sid, LogonType logonType)

Loading…
Cancel
Save