@ -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 )