locked
How to determine if a user that created a process doesn't belong to Administrators group? RRS feed

  • Question

  • I have a service that checks whether a user which started a process with a particular PId belongs to an Administrators group. My first version of code was the following

    HANDLE hProcess=OpenProcess(PROCESS_QUERY_INFORMATION,0,PId);	//PId is a target process id
    if(hProcess){
    	HANDLE hToken;
    	if(OpenProcessToken(hProcess,TOKEN_READ,&hToken)){
    		BOOL bAdmin=0;
    		if(CheckTokenMembership(hToken,pAdminSID,&bAdmin)){	//pAdminSID is a previously generated SID for Administrators group as made here http://msdn.microsoft.com/en-us/library/windows/desktop/aa376389
    			if(!bAdmin){	//The user which started the process is not in Administrators group
    				…
    			}
    		}
    		CloseHandle(hToken);
    	}
    	CloseHandle(hProcess);
    }

    But CheckTokenMembership() was returning ERROR_NO_IMPERSONATION_TOKEN. Then I thought what it would be a good idea to add some impersonation features to my code

    HANDLE hProcess=OpenProcess(PROCESS_QUERY_INFORMATION,0,PId);
    if(hProcess){
    	HANDLE hToken;
    	if(OpenProcessToken(hProcess,TOKEN_READ | TOKEN_IMPERSONATE | TOKEN_DUPLICATE,&hToken)){
    		if(ImpersonateLoggedOnUser(hToken)){
    			BOOL bAdmin=0;
    			if(CheckTokenMembership(hToken,pAdminSID,&bAdmin)){
    				…
    			}
    			RevertToSelf();
    		}
    		CloseHandle(hToken);
    	}
    	CloseHandle(hProcess);
    }

    But it doesn't work! I even tried different combinations of flags for OpenProcessToken(), but I still get ERROR_NO_IMPERSONATION_TOKEN in CheckTokenMembership() or 0x5 in ImpersonateLoggedOnUser().

    How can I check token membership then?

    The service is running in Windows 7 under standard service account.


    If I fall I will arise on my way to paradise

    Monday, July 10, 2017 11:28 AM

Answers

  • I did some tweaking of the MS sample code.  On Win8.1 it successfully identified members of the local administrators group. The service that called the code was running as LocalSystem. I tested it on a process running as as a regular user, and on processes running as an Administrator both with and without elevation.  I did not test it on XP.

    BOOL IsUserInAdminGroup(LONG ProcessId)
    {
    	BOOL fInAdminGroup = FALSE;
    	DWORD dwError = ERROR_SUCCESS;
    	HANDLE hToken = NULL;
    	HANDLE hTokenToCheck = NULL, hLinkedToken = NULL;
    	DWORD cbSize = 0;
    	HANDLE hProcess = NULL;
    	//OSVERSIONINFO osver = { sizeof(osver) };
    
    	hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, ProcessId);
    
    	if (!hProcess)
    	{
    		dwError = GetLastError();
    		goto Cleanup;
    	}
    
    	// Open the primary access token of the process for query and duplicate.
    	if (!OpenProcessToken(hProcess, TOKEN_QUERY | TOKEN_DUPLICATE,
    		&hToken))
    	{
    		dwError = GetLastError();
    		goto Cleanup;
    	}
    
    	// Determine whether system is running Windows Vista or later operating 
    	// systems (major version >= 6) because they support linked tokens, but 
    	// previous versions (major version < 6) do not.
    	//if (!GetVersionEx(&osver))
    	//{
    	//    dwError = GetLastError();
    	//    goto Cleanup;
    	//}
    
    	if (IsWindowsVistaOrGreater()) //osver.dwMajorVersion >= 6
    	{
    		// Running Windows Vista or later (major version >= 6). 
    		// Determine token type: limited, elevated, or default. 
    		TOKEN_ELEVATION_TYPE elevType;
    		if (!GetTokenInformation(hToken, TokenElevationType, &elevType,
    			sizeof(elevType), &cbSize))
    		{
    			dwError = GetLastError();
    			goto Cleanup;
    		}
    
    		// If limited, get the linked elevated token for further check.
    		if (TokenElevationTypeLimited == elevType)
    		{
    			if (!GetTokenInformation(hToken, TokenLinkedToken, &hLinkedToken,
    				sizeof(hLinkedToken), &cbSize))
    			{
    				dwError = GetLastError();
    				goto Cleanup;
    			}
    
    			if (!DuplicateToken(hLinkedToken, SecurityImpersonation, &hTokenToCheck))
    			{
    				dwError = GetLastError();
    				goto Cleanup;
    			}
    
    		}
    	}
    
    	// CheckTokenMembership requires an impersonation token. If we just got a 
    	// linked token, it already is an impersonation token.  If we did not get 
    	// a linked token, duplicate the original into an impersonation token for 
    	// CheckTokenMembership.
    	if (!hTokenToCheck)
    	{
    		if (!DuplicateToken(hToken, SecurityImpersonation, &hTokenToCheck))
    		{
    			dwError = GetLastError();
    			goto Cleanup;
    		}
    	}
    
    	// Create the SID corresponding to the Administrators group.
    	BYTE adminSID[SECURITY_MAX_SID_SIZE];
    	cbSize = sizeof(adminSID);
    	if (!CreateWellKnownSid(WinBuiltinAdministratorsSid, NULL, &adminSID,
    		&cbSize))
    	{
    		dwError = GetLastError();
    		goto Cleanup;
    	}
    
    	// Check if the token to be checked contains admin SID.
    	// http://msdn.microsoft.com/en-us/library/aa379596(VS.85).aspx:
    	// To determine whether a SID is enabled in a token, that is, whether it 
    	// has the SE_GROUP_ENABLED attribute, call CheckTokenMembership.
    
    	BOOL bImpersonate = ImpersonateLoggedOnUser(hTokenToCheck);
    
    	if (!CheckTokenMembership(hTokenToCheck, &adminSID, &fInAdminGroup))
    	{
    		dwError = GetLastError();
    		goto Cleanup;
    	}
    
    Cleanup:
    	// Centralized cleanup for all allocated resources.
    
    	if (bImpersonate)
    		RevertToSelf();
    
    	if (hToken)
    	{
    		CloseHandle(hToken);
    		hToken = NULL;
    	}
    	if(hLinkedToken)
    	{
    		CloseHandle(hLinkedToken);
    		hLinkedToken = NULL;
    	}
    	if (hTokenToCheck)
    	{
    		CloseHandle(hTokenToCheck);
    		hTokenToCheck = NULL;
    	}
    	if (hProcess)
    	{
    		CloseHandle(hProcess);
    		hProcess = NULL;
    	}
    
    	// Throw the error if something failed in the function.
    	if (ERROR_SUCCESS != dwError)
    	{
    		throw dwError;
    	}
    
    	return fInAdminGroup;
    }



    • Edited by RLWA32 Monday, July 10, 2017 6:04 PM clarified comment
    • Marked as answer by apixosoft Monday, July 10, 2017 8:35 PM
    Monday, July 10, 2017 4:09 PM

All replies

  • The following sample code was taken from Microsoft's CppUACSelfElevation sample.  I modified it slightly to use the IsWindowsVistaOrGreater function from Versionhelpers.h.

    //
    //   FUNCTION: IsUserInAdminGroup()
    //
    //   PURPOSE: The function checks whether the primary access token of the 
    //   process belongs to user account that is a member of the local 
    //   Administrators group, even if it currently is not elevated.
    //
    //   RETURN VALUE: Returns TRUE if the primary access token of the process 
    //   belongs to user account that is a member of the local Administrators 
    //   group. Returns FALSE if the token does not.
    //
    //   EXCEPTION: If this function fails, it throws a C++ DWORD exception which 
    //   contains the Win32 error code of the failure.
    //
    //   EXAMPLE CALL:
    //     try 
    //     {
    //         if (IsUserInAdminGroup())
    //             wprintf (L"User is a member of the Administrators group\n");
    //         else
    //             wprintf (L"User is not a member of the Administrators group\n");
    //     }
    //     catch (DWORD dwError)
    //     {
    //         wprintf(L"IsUserInAdminGroup failed w/err %lu\n", dwError);
    //     }
    //
    BOOL IsUserInAdminGroup()
    {
        BOOL fInAdminGroup = FALSE;
        DWORD dwError = ERROR_SUCCESS;
        HANDLE hToken = NULL;
        HANDLE hTokenToCheck = NULL;
        DWORD cbSize = 0;
        //OSVERSIONINFO osver = { sizeof(osver) };
    
        // Open the primary access token of the process for query and duplicate.
        if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_DUPLICATE, 
            &hToken))
        {
            dwError = GetLastError();
            goto Cleanup;
        }
    
        // Determine whether system is running Windows Vista or later operating 
        // systems (major version >= 6) because they support linked tokens, but 
        // previous versions (major version < 6) do not.
        //if (!GetVersionEx(&osver))
        //{
        //    dwError = GetLastError();
        //    goto Cleanup;
        //}
    
        if (IsWindowsVistaOrGreater()) //osver.dwMajorVersion >= 6
        {
            // Running Windows Vista or later (major version >= 6). 
            // Determine token type: limited, elevated, or default. 
            TOKEN_ELEVATION_TYPE elevType;
            if (!GetTokenInformation(hToken, TokenElevationType, &elevType, 
                sizeof(elevType), &cbSize))
            {
                dwError = GetLastError();
                goto Cleanup;
            }
    
            // If limited, get the linked elevated token for further check.
            if (TokenElevationTypeLimited == elevType)
            {
                if (!GetTokenInformation(hToken, TokenLinkedToken, &hTokenToCheck, 
                    sizeof(hTokenToCheck), &cbSize))
                {
                    dwError = GetLastError();
                    goto Cleanup;
                }
            }
        }
        
        // CheckTokenMembership requires an impersonation token. If we just got a 
        // linked token, it already is an impersonation token.  If we did not get 
        // a linked token, duplicate the original into an impersonation token for 
        // CheckTokenMembership.
        if (!hTokenToCheck)
        {
            if (!DuplicateToken(hToken, SecurityIdentification, &hTokenToCheck))
            {
                dwError = GetLastError();
                goto Cleanup;
            }
        }
    
        // Create the SID corresponding to the Administrators group.
        BYTE adminSID[SECURITY_MAX_SID_SIZE];
        cbSize = sizeof(adminSID);
        if (!CreateWellKnownSid(WinBuiltinAdministratorsSid, NULL, &adminSID,  
            &cbSize))
        {
            dwError = GetLastError();
            goto Cleanup;
        }
    
        // Check if the token to be checked contains admin SID.
        // http://msdn.microsoft.com/en-us/library/aa379596(VS.85).aspx:
        // To determine whether a SID is enabled in a token, that is, whether it 
        // has the SE_GROUP_ENABLED attribute, call CheckTokenMembership.
        if (!CheckTokenMembership(hTokenToCheck, &adminSID, &fInAdminGroup)) 
        {
            dwError = GetLastError();
            goto Cleanup;
        }
    
    Cleanup:
        // Centralized cleanup for all allocated resources.
        if (hToken)
        {
            CloseHandle(hToken);
            hToken = NULL;
        }
        if (hTokenToCheck)
        {
            CloseHandle(hTokenToCheck);
            hTokenToCheck = NULL;
        }
    
        // Throw the error if something failed in the function.
        if (ERROR_SUCCESS != dwError)
        {
            throw dwError;
        }
    
        return fInAdminGroup;
    }
    

    Monday, July 10, 2017 11:55 AM
  • The problem stills the same: ERROR_NO_IMPERSONATION_TOKEN in CheckTokenMembership(). Maybe it's because your sample doesn't use another process but GetCurrentProcess() instead?

    If I fall I will arise on my way to paradise

    Monday, July 10, 2017 12:32 PM
  • The MS sample code calls DuplicateToken if necessary to create an impersonation token.
    Monday, July 10, 2017 12:44 PM
  • The MS sample code calls DuplicateToken if necessary to create an impersonation token.
    It calls DuplicateToken() only when original token was not limited, or when OS is < Vista, because
    If we just got a linked token, it already is an impersonation token
    In my situation execution goes through GetTokenInformation(TokenLinkedToken), which HAVE to return an impersonation token, but… CheckTokenMembership() fails again. Maybe it's because my program is a service, or something else I don't know?

    If I fall I will arise on my way to paradise

    • Edited by apixosoft Monday, July 10, 2017 1:04 PM
    Monday, July 10, 2017 1:00 PM
  • One thing about the code you posted is that if you are already impersonating you can pass NULL as the first parameter to CheckTokenMembership.  The docs say "If TokenHandle is NULL, CheckTokenMembership uses the impersonation token of the calling thread."
    Monday, July 10, 2017 1:04 PM
  • I've already changed mine code to yours. But why your one doesn't want to work correctly? Everything seems to be ok according to its description.

    P.S. Mail notifications stopped working again?


    If I fall I will arise on my way to paradise

    • Edited by apixosoft Monday, July 10, 2017 1:12 PM
    Monday, July 10, 2017 1:12 PM
  • One thing about the code you posted is that if you are already impersonating you can pass NULL as the first parameter to CheckTokenMembership.  The docs say "If TokenHandle is NULL, CheckTokenMembership uses the impersonation token of the calling thread."
    Also useless, because now CheckTokenMembership(NULL,…) returns "access denied".

    If I fall I will arise on my way to paradise

    Monday, July 10, 2017 1:31 PM
  • I did some tweaking of the MS sample code.  On Win8.1 it successfully identified members of the local administrators group. The service that called the code was running as LocalSystem. I tested it on a process running as as a regular user, and on processes running as an Administrator both with and without elevation.  I did not test it on XP.

    BOOL IsUserInAdminGroup(LONG ProcessId)
    {
    	BOOL fInAdminGroup = FALSE;
    	DWORD dwError = ERROR_SUCCESS;
    	HANDLE hToken = NULL;
    	HANDLE hTokenToCheck = NULL, hLinkedToken = NULL;
    	DWORD cbSize = 0;
    	HANDLE hProcess = NULL;
    	//OSVERSIONINFO osver = { sizeof(osver) };
    
    	hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, ProcessId);
    
    	if (!hProcess)
    	{
    		dwError = GetLastError();
    		goto Cleanup;
    	}
    
    	// Open the primary access token of the process for query and duplicate.
    	if (!OpenProcessToken(hProcess, TOKEN_QUERY | TOKEN_DUPLICATE,
    		&hToken))
    	{
    		dwError = GetLastError();
    		goto Cleanup;
    	}
    
    	// Determine whether system is running Windows Vista or later operating 
    	// systems (major version >= 6) because they support linked tokens, but 
    	// previous versions (major version < 6) do not.
    	//if (!GetVersionEx(&osver))
    	//{
    	//    dwError = GetLastError();
    	//    goto Cleanup;
    	//}
    
    	if (IsWindowsVistaOrGreater()) //osver.dwMajorVersion >= 6
    	{
    		// Running Windows Vista or later (major version >= 6). 
    		// Determine token type: limited, elevated, or default. 
    		TOKEN_ELEVATION_TYPE elevType;
    		if (!GetTokenInformation(hToken, TokenElevationType, &elevType,
    			sizeof(elevType), &cbSize))
    		{
    			dwError = GetLastError();
    			goto Cleanup;
    		}
    
    		// If limited, get the linked elevated token for further check.
    		if (TokenElevationTypeLimited == elevType)
    		{
    			if (!GetTokenInformation(hToken, TokenLinkedToken, &hLinkedToken,
    				sizeof(hLinkedToken), &cbSize))
    			{
    				dwError = GetLastError();
    				goto Cleanup;
    			}
    
    			if (!DuplicateToken(hLinkedToken, SecurityImpersonation, &hTokenToCheck))
    			{
    				dwError = GetLastError();
    				goto Cleanup;
    			}
    
    		}
    	}
    
    	// CheckTokenMembership requires an impersonation token. If we just got a 
    	// linked token, it already is an impersonation token.  If we did not get 
    	// a linked token, duplicate the original into an impersonation token for 
    	// CheckTokenMembership.
    	if (!hTokenToCheck)
    	{
    		if (!DuplicateToken(hToken, SecurityImpersonation, &hTokenToCheck))
    		{
    			dwError = GetLastError();
    			goto Cleanup;
    		}
    	}
    
    	// Create the SID corresponding to the Administrators group.
    	BYTE adminSID[SECURITY_MAX_SID_SIZE];
    	cbSize = sizeof(adminSID);
    	if (!CreateWellKnownSid(WinBuiltinAdministratorsSid, NULL, &adminSID,
    		&cbSize))
    	{
    		dwError = GetLastError();
    		goto Cleanup;
    	}
    
    	// Check if the token to be checked contains admin SID.
    	// http://msdn.microsoft.com/en-us/library/aa379596(VS.85).aspx:
    	// To determine whether a SID is enabled in a token, that is, whether it 
    	// has the SE_GROUP_ENABLED attribute, call CheckTokenMembership.
    
    	BOOL bImpersonate = ImpersonateLoggedOnUser(hTokenToCheck);
    
    	if (!CheckTokenMembership(hTokenToCheck, &adminSID, &fInAdminGroup))
    	{
    		dwError = GetLastError();
    		goto Cleanup;
    	}
    
    Cleanup:
    	// Centralized cleanup for all allocated resources.
    
    	if (bImpersonate)
    		RevertToSelf();
    
    	if (hToken)
    	{
    		CloseHandle(hToken);
    		hToken = NULL;
    	}
    	if(hLinkedToken)
    	{
    		CloseHandle(hLinkedToken);
    		hLinkedToken = NULL;
    	}
    	if (hTokenToCheck)
    	{
    		CloseHandle(hTokenToCheck);
    		hTokenToCheck = NULL;
    	}
    	if (hProcess)
    	{
    		CloseHandle(hProcess);
    		hProcess = NULL;
    	}
    
    	// Throw the error if something failed in the function.
    	if (ERROR_SUCCESS != dwError)
    	{
    		throw dwError;
    	}
    
    	return fInAdminGroup;
    }



    • Edited by RLWA32 Monday, July 10, 2017 6:04 PM clarified comment
    • Marked as answer by apixosoft Monday, July 10, 2017 8:35 PM
    Monday, July 10, 2017 4:09 PM
  • Thank you very much! And excuse me that I didn't write in time.

    Desperate to find a solution, I decided to experiment and duplicated a linked token. Unbelievable, but it worked! I don't know why the documentation lies that "If we just got a linked token, it already is an impersonation token", but anyway even linked token *HAVE* to be duplicated.

    Thank you again for your endeavours!


    If I fall I will arise on my way to paradise

    Monday, July 10, 2017 8:35 PM