Do high-integrity tokens *have* to have the Administrators group enabled?

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.

Addendum October 2022: I suspect that the bug in runas is that it only allows you to specify certain integrity levels, and gets them wrong! Medium integrity is 0x2000, not 0x20000.

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

Leave a Comment