4

When UAC is enabled, and you log in with an administrative account, you get two tokens:

  • the elevated token; this has the Administrators group enabled, is high integrity (i.e., the mandatory integrity label SID is S-1-16-12288) and has elevation type TokenElevationTypeFull.

  • the limited token; this has the Administrators group disabled, is medium integrity (S-1-16-8192) and has elevation type TokenElevationTypeLimited.

Do these three factors always match up in this way? That is, does the kernel require that only tokens with the Administrators group enabled can have high integrity and/or TokenElevationTypeFull?

Are there any circumstances under which a process will not have Administrators privilege but will be running with high integrity and/or TokenElevationTypeFull?

(Rationale for the question: the answer affects the ways in which a programmer can safely test for elevated privileges. For example, it came up here.)

Community
  • 1
  • 1
Harry Johnston
  • 33,445
  • 6
  • 56
  • 142

1 Answers1

4

No, the kernel does not require that the integrity level and elevation type of a token match up with the status of the Administrators group. This means that a process having a high integrity level, or TokenElevationTypeFull, does not necessarily have administrator access.

In particular, note that using runas /trustlevel:0x20000 from an administrative command prompt will result in a process that does not have administrator privilege but nonetheless runs with high integrity and (if UAC is enabled) will have TokenElevationTypeFull. (As discovered here.) I believe this represents a bug in runas.

This sample code demonstrates the behaviour; if run with admin privilege, it launches a subprocess with the administrators group (and all privileges except SeChangeNotifyPrivilege) disabled but which is still running with high integrity and TokenElevationTypeFull.

#include <Windows.h>
#include <Sddl.h>

#include <stdio.h>

PSID admins_sid;

void get_membership(HANDLE token)
{
    BOOL is_enabled;
    HANDLE itoken;

    if (!DuplicateToken(token, SecurityIdentification, &itoken))
    {
        printf("DuplicateToken: %u\n", GetLastError());
        return;
    }

    if (!CheckTokenMembership(itoken, admins_sid, &is_enabled))
    {
        printf("CheckTokenMembership: %u\n", GetLastError());
        CloseHandle(itoken);
        return;
    }

    CloseHandle(itoken);

    printf("Administrators group enabled: %u\n", is_enabled);
    return;
}

void get_integrity(HANDLE token)
{
    char buffer[4096];
    char * stringsid;

    TOKEN_MANDATORY_LABEL *token_mandatory_label = (TOKEN_MANDATORY_LABEL *)buffer;
    DWORD dw;

    if (!GetTokenInformation(token, TokenIntegrityLevel, buffer, sizeof(buffer), &dw))
    {
        printf("GetTokenInformation: %u\n", GetLastError());
        return;
    }

    if (!ConvertSidToStringSidA(token_mandatory_label->Label.Sid, &stringsid))
    {
        printf("ConvertSidToStringSid: %u\n", GetLastError());
        return;
    }

    printf("SID: %s\n", stringsid);
}

void get_elevation(HANDLE token)
{
    TOKEN_ELEVATION_TYPE elevation;
    DWORD dw;

    if (!GetTokenInformation(token, 
        TokenElevationType, &elevation, sizeof(elevation), &dw))
    {
        printf("GetTokenInformation: %u\n", GetLastError());
        return;
    }

    printf("Elevation type : %u\n", (DWORD)elevation);
}   

void test(void)
{
    HANDLE token1, token2;
    SID_AND_ATTRIBUTES sids_to_disable;
    STARTUPINFOA si = {sizeof(STARTUPINFOA)};
    PROCESS_INFORMATION pi;

    if (!OpenProcessToken(GetCurrentProcess(), MAXIMUM_ALLOWED, &token1))
    {
        printf("OpenProcessToken: %u\n", GetLastError());
        return;
    }

    printf("token1:\n");
    get_membership(token1);
    get_integrity(token1);
    get_elevation(token1);

    sids_to_disable.Attributes = 0;
    sids_to_disable.Sid = admins_sid;

    if (!CreateRestrictedToken(token1, 
        DISABLE_MAX_PRIVILEGE, 1, &sids_to_disable, 0, NULL, 0, NULL, &token2))
    {
        printf("CreateRestrictedToken: %u\n", GetLastError());
        return;
    }

    printf("token2:\n");
    get_membership(token2);
    get_integrity(token2);
    get_elevation(token2);

    if (!CreateProcessAsUserA(token2, 
        NULL, "cmd", NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi))
    {
        printf("CreateProcessAsUser: %u\n", GetLastError());
        return;
    }
}

int main(int argc, char ** argv)
{
    {
        SID_IDENTIFIER_AUTHORITY SIDAuth = SECURITY_NT_AUTHORITY;
        if(! AllocateAndInitializeSid( &SIDAuth, 2,
                         SECURITY_BUILTIN_DOMAIN_RID,
                         DOMAIN_ALIAS_RID_ADMINS,
                         0, 0, 0, 0, 0, 0,
                         &admins_sid) ) 
        {
            printf( "AllocateAndInitializeSid: %u\n", GetLastError());
            return 1;
        }
    }

    test();
    return 0;
}

Output when run from an elevated command prompt:

token1:
Administrators group enabled: 1
SID: S-1-16-12288
Elevation type : 2
token2:
Administrators group enabled: 0
SID: S-1-16-12288
Elevation type : 2

If you run the sample code again from the child process, you can confirm that the child process did retain these properties:

token1:
Administrators group enabled: 0
SID: S-1-16-12288
Elevation type : 2

If UAC is disabled, the elevation type is TokenElevationTypeDefault but otherwise the outcome is the same:

token1:
Administrators group enabled: 1
SID: S-1-16-12288
Elevation type : 1
token2:
Administrators group enabled: 0
SID: S-1-16-12288
Elevation type : 1

As expected, the limited token looks like this:

token1:
Administrators group enabled: 0
SID: S-1-16-8192
Elevation type : 3

Or if you're logged in as a non-admin user, whether UAC is enabled or not:

token1:
Administrators group enabled: 0
SID: S-1-16-8192
Elevation type : 1

(All tests run on Windows 7 SP1 x64.)

riQQ
  • 4,188
  • 4
  • 19
  • 33
Harry Johnston
  • 33,445
  • 6
  • 56
  • 142
  • This crashes for me in Windows 10 x64. The error code is `STATUS_DLL_INIT_FAILED`. It happens just as conhost.exe executes, but before it gets to create a new window. I checked that both the child cmd.exe and conhost.exe processes have the Administrators group set for deny only and high integrity. It could be a bug in Windows 10, or indicative of an assumption somewhere that a high integrity process has administrator rights. My build of Windows 10 is out of date (10.0.10074); it may work in a newer build. – Eryk Sun Jun 22 '15 at 00:50
  • @eryksun: you mean the child instance of cmd.exe crashes? Sounds like something to do with the desktop/window station permissions. (Does it still crash if you don't use CREATE_NEW_CONSOLE, letting it inherit the existing console instead?) I've got an up-to-date Windows 10 install at home, if I have time I'll try it out tonight. – Harry Johnston Jun 22 '15 at 01:04
  • For sure it's due to creating a new console. Like I said, the child cmd.exe fails (probably kernelbase's initialization, but I haven't checked) as the new instance of conhost.exe is starting up. If I remove `CREATE_NEW_CONSOLE` and wait on the process handle it's fine. – Eryk Sun Jun 22 '15 at 02:39
  • Except in both Windows 7 and 10 trying to run `whoami /groups` fails with `ERROR: Access is denied.` and `ERROR: Unable to get group membership information.` – Eryk Sun Jun 22 '15 at 02:40
  • @eryksun: yes, I see `whoami /groups` fail too. I think it's related to the default permissions assigned to the process object: they give access to Administrators rather than to the user account. When you use the limited token that UAC generates, the default permissions grant access to the user object. I guess to make this all work properly I would need to explicitly set an appropriate default DACL via SetTokenInformation with the TokenDefaultDacl option. (That might be responsible for the crash on Windows 10, too.) – Harry Johnston Jun 22 '15 at 04:02
  • `whoami /groups` fails for me when it calls `OpenProcessToken` requesting `TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES | TOKEN_ADJUST_GROUPS` access. Like you say the token's DACL is missing the ACE that grants all access to the user's SID. The token's security descriptor grants all access to SYSTEM and Administrators. Also, for the user's logon SID it grants `READ_CONTROL | TOKEN_QUERY_SOURCE | TOKEN_QUERY | TOKEN_IMPERSONATE | TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY`, but no `ADJUST` rights. – Eryk Sun Jun 22 '15 at 05:27
  • Did you get a chance to try this in Windows 10? – Eryk Sun Jun 24 '15 at 00:47
  • No, I pretty much lost interest once I realized the default DACL was probably responsible - I figure the IPC between conhost and the new process is breaking due to inadequate permissions. I don't think it would be too difficult to write code that duplicates UAC, just fiddly, and it wasn't really the point of the question. – Harry Johnston Jun 24 '15 at 01:11