(一)Access Token

Access Token:与特定的windows账户关联,账户环境下启动的所有进程都会获得该令牌的副本,进程中的线程默认获得这个令牌。

访问令牌(Access tokens)

访问令牌是描述进程或线程的安全上下文的对象。令牌中的信息包括与进程或线程关联的用户帐户的标识和特权信息。当用户登录时,系统通过将用户密码与安全数据库(如域认证中的NTDS或本地认证中的SAM文件)中存储的信息进行比较来验证用户密码。如果密码经过验证,则系统将生成访问令牌。代表该用户执行的每个进程都有此访问令牌的副本。(通常我们在输入密码登陆进入Windows界面时就是一个生成访问令牌的过程)

当线程与安全对象进行交互或尝试执行需要特权的系统任务时,系统使用访问令牌来标识用户。访问令牌包含以下信息:

  • 用户帐户的安全标识符(SID)(关SID是什么后续章节会详细介绍)

  • 用户所在组的SID

  • 标识当前登录会话(logon session)的登录SID

  • 用户或用户组拥有的特权列表

  • 所有者SID

  • 主要组的SID

  • 用户创建安全对象而不指定安全描述符时系统使用的默认DACL

  • 访问令牌的来源

  • 令牌是主要令牌(内核分配的令牌)还是模拟令牌(模拟而来的令牌)

  • 受限制SID的可选列表(如UAC存在时会针对某些账户删除某些特权)

  • 当前的模拟级别

  • 其他数据

Access tokens产生的大致过程为:

  1. 使用凭据(用户密码)进行认证

  2. 当前登录会话的Session创建

  3. Windows此时会返回用户sid和用户组sid

  4. LSA(Local Security Authority)创建一个Token

  5. 依据该token创建进程、线程(如果CreaetProcess时自己指定了 Token, LSA会用该Token, 否则就继承父进程Token进行运行)

Windows Access Token分两种:

  1. 主令牌(Primary token)

  2. 模拟令牌(Impersonation token)

每个进程都有一个主要令牌,用于描述与该进程关联的用户帐户的安全上下文。默认情况下,当进程的线程与安全对象进行交互时,系统使用主令牌。

此外,线程可以模拟客户端帐户。模拟允许线程使用客户端的安全上下文与安全对象进行交互。模拟客户端的线程同时具有主令牌和模拟令牌。(出现这种情况是因为服务操作是在寄宿进程中执行,在默认的情况下,服务操作是否具有足够的权限访问某个资源(比如文件)取决于执行寄宿进程Windows帐号的权限设置,而与作为客户端的Windows帐号无关。在有多情况下,我们希望服务操作执行在基于客户端的安全上下文中执行,以解决执行服务进行的帐号权限不足的问题。简单来说就是我们希望不同客户端来访问服务时,服务可以模拟客户端的身份去访问服务,而不是用自己的主进程Token身份去访问。)

使用OpenProcessToken函数可检索进程的主令牌的句柄。使用OpenThreadToken函数检索线程的模拟令牌的句柄。

给出一个C++代码示例来看看如何使用API,这段代码主要用于查看当前运行的账户是否在Administrators组内。

C++
// ConsoleApplication1.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <iostream>
#include <windows.h> 
#include <vector>

bool is_administrator() {
    HANDLE access_token;
    DWORD buffer_size = 0;
    PSID admin_SID;
    TOKEN_GROUPS* group_token = NULL;
    SID_IDENTIFIER_AUTHORITY NT_authority = SECURITY_NT_AUTHORITY;

    if (!OpenProcessToken(GetCurrentProcess(), TOKEN_READ, &access_token))
        return false;

    GetTokenInformation(
        access_token,
        TokenGroups,
        group_token,
        0,
        &buffer_size
    );

    std::vector<char> buffer(buffer_size);

    group_token =
        reinterpret_cast<TOKEN_GROUPS*>(&buffer[0]);

    bool succeeded = GetTokenInformation(
        access_token,
        TokenGroups,
        group_token,
        buffer_size,
        &buffer_size
    );

    CloseHandle(access_token);
    if (!succeeded)
        return false;

    if (!AllocateAndInitializeSid(
        &NT_authority,
        2,
        SECURITY_BUILTIN_DOMAIN_RID,
        DOMAIN_ALIAS_RID_ADMINS,
        0, 0, 0, 0, 0, 0,
        &admin_SID
    ))
    {
        return false;
    }

    bool found = false;
    for (int i = 0; !found && i < group_token->GroupCount; i++)
        found = EqualSid(admin_SID, group_token->Groups[i].Sid);
    FreeSid(admin_SID);
    return found;
}

int main()
{
    bool ret;
    ret = is_administrator();
    if (ret) {
        printf("Yes, you are administrator!");
    }    
}

其他的API还有很多,可以使用以下功能来操作访问令牌。

函数

描述

更改访问令牌中的组信息。

启用或禁用访问令牌中的特权。它不会授予新特权或撤销现有特权。

确定是否在指定的访问令牌中启用了指定的SID。

创建一个新令牌,它是现有令牌的受限版本。受限令牌可以具有禁用的SID、已删除的特权以及受限的SID列表。

创建一个新的模拟令牌,该令牌复制现有令牌。

创建一个新的主要令牌或模拟令牌,该令牌可复制现有令牌。

检索有关令牌的信息。

确定令牌是否具有限制SID列表。

检索进程的主要访问令牌的句柄。

检索线程的模拟访问令牌的句柄。

分配或删除线程的模拟令牌。

更改令牌的所有者、主要组或默认DACL。

访问令牌功能使用以下结构来描述访问令牌的各个部分:

结构体

描述

标识访问令牌的信息。

系统在线程创建的新对象的安全描述符中使用的默认DACL。

指定访问令牌中的SID和组SID的属性。

新对象的安全描述符的默认所有者SID。

新对象的安全描述符的默认主要组SID。

与访问令牌关联的特权。还确定是否启用了特权。

访问令牌的来源。

与访问令牌关联的统计信息。

与访问令牌关联的用户的SID。

访问令牌功能使用以下枚举类型:

枚举类型

指定

标识正在设置或从访问令牌检索的信息的类型。

将访问令牌标识为主要令牌或模拟令牌。

受限令牌

一个常见的受限令牌如下图所示,这是一个未过UAC的用户,通常来说Windows在启动时LSA将尝试识别用户是否是特权组的成员,或者是否已使用与IsTokenRestricted函数类似的功能授予敏感特权。存在限制的SID将生成低特权的新访问令牌。

成为管理员之后便会增加大量权限:

也可以使用Process Explorer查看进安全信息:

受限令牌是已由CreateRestrictedToken函数修改的主令牌或模拟访问令牌。在受限令牌的安全上下文中运行的进程或模拟线程在访问安全对象或执行特权操作方面的能力受到限制。 CreateRestrictedToken函数可以通过以下方式限制令牌:

  • 从令牌中删除某些特权。

  • 将“仅拒绝”属性应用于令牌中的SID,以便它们不能用于访问受保护的对象。(相关内容可以产看下一节的SID属性)

  • 限制SID列表,这可以限制受限令牌拥有者对安全对象的访问。

当系统检查令牌对安全对象的访问时,系统将使用限制SID列表。当受限制的进程或线程尝试访问安全对象时,系统执行两项访问检查:一项使用令牌启用的SID,另一项使用限制SID列表。仅当两个访问检查都允许所请求的访问权限时,才授予访问权限。

可以在对CreateProcessAsUser函数的调用中使用受限制的主令牌。通常调用CreateProcessAsUser的进程必须具有SE_ASSIGNPRIMARYTOKEN_NAME特权,该特权通常仅由系统代码或LocalSystem帐户中运行的服务保留。但是,如果CreateProcessAsUser调用指定了调用者主令牌的受限令牌,则不需要此特权。这使普通应用程序可以创建受限进程。

还可以在ImpersonateLoggedOnUser函数中使用受限制的主要或模拟令牌。

若要确定令牌是否具有限制SID列表,可以调用IsTokenRestricted函数。

注意:使用受限令牌的应用程序应在默认桌面以外的其他桌面上运行受限应用程序, 防止受限制的应用程序使用SendMessagePostMessage攻击默认桌面上不受限制的应用程序。

Access Token中的SID属性

访问令牌中的每个用户和组安全标识符(SID)具有一组属性,这些属性控制系统在访问检查中如何使用SID。下表列出了控制访问检查的属性。

属性

描述

SE_GROUP_ENABLED

启用具有此属性的SID进行访问检查。当系统执行访问检查时,它将检查适用于访问令牌中已启用的SID之一的允许访问和拒绝访问的访问控制项(ACE)。除非设置了SE_GROUP_USE_FOR_DENY_ONLY属性,否则在访问检查期间将忽略不具有此属性的SID。

SE_GROUP_USE_FOR_DENY_ONLY

具有此属性的SID是仅拒绝的SID。当系统执行访问检查时,它会检查适用于SID的拒绝访问的ACE,但是会忽略SID允许访问的ACE。如果设置了此属性,则不会设置SE_GROUP_ENABLED属性,并且无法重新启用SID

要设置或清除组SID的SE_GROUP_ENABLED属性,可使用AdjustTokenGroups函数。不能禁用具有SE_GROUP_MANDATORY属性的组SID。不能使用AdjustTokenGroups禁用访问令牌的用户SID。

若要确定令牌中是否启用了SID,即它是否具有SE_GROUP_ENABLED属性,可以调用CheckTokenMembership函数。

若要设置SID的SE_GROUP_USE_FOR_DENY_ONLY属性,可以在调用CreateRestrictedToken函数时指定的拒绝专用SID列表中包含的该SID。 CreateRestrictedToken可以将SE_GROUP_USE_FOR_DENY_ONLY属性应用于任何SID,包括用户SID和具有SE_GROUP_MANDATORY属性的组SID。但不能从SID中删除仅拒绝属性,也不能使用AdjustTokenGroups在仅拒绝SID上设置SE_GROUP_ENABLED属性。

要获取SID的属性,可以使用TokenGroups值调用GetTokenInformation函数。该函数返回一个SID_AND_ATTRIBUTES结构数组,该结构标识组SID及其属性。

访问令牌对象的访问权限

除非应用程序有权更改对象的访问控制列表,否则该应用程序不能更改该对象的访问控制列表。这些权限由对象的访问令牌中的安全描述符控制。有关安全性的更多信息,请参见访问控制模型

要获取或设置访问令牌的安全描述符,请调用GetKernelObjectSecuritySetKernelObjectSecurity函数。

当调用OpenProcessTokenOpenThreadToken函数以获取访问令牌的句柄时,系统将根据令牌的安全描述符中的DACL检查请求的访问权限。

以下是访问令牌对象的有效访问权限:

  • DELETEREAD_CONTROLWRITE_DACWRITE_OWNER标准访问权限。访问令牌不支持SYNCHRONIZE标准访问权限。

  • 获得或设置对象的安全描述符中的SACLACCESS_SYSTEM_SECURITY权限。

  • 下表列出了访问令牌的特定访问权限:

  • 含义

    TOKEN_ADJUST_DEFAULT

    更改访问令牌的默认所有者,主要组或DACL是必需的。

    TOKEN_ADJUST_GROUPS

    需要在访问令牌中调整组的属性

    TOKEN_ADJUST_PRIVILEGES

    启用或禁用访问令牌中的特权

    TOKEN_ADJUST_SESSIONID

    调整访问令牌的会话ID。必须具有SE_TCB_NAME特权。

    TOKEN_ASSIGN_PRIMARY

    将主令牌附加到进程。要完成此任务,还需要SE_ASSIGNPRIMARYTOKEN_NAME特权。

    TOKEN_DUPLICATE

    复制访问令牌

    TOKEN_EXECUTE

    结合STANDARD_RIGHTS_EXECUTETOKEN_IMPERSONATE

    TOKEN_IMPERSONATE

    将模拟访问令牌附加到进程

    TOKEN_QUERY

    查询访问令牌

    TOKEN_QUERY_SOURCE

    查询访问令牌的来源

    TOKEN_READ

    结合STANDARD_RIGHTS_READTOKEN_QUERY

    TOKEN_WRITE

    合并STANDARD_RIGHTS_WRITETOKEN_ADJUST_PRIVILEGESTOKEN_ADJUST_GROUPSTOKEN_ADJUST_DEFAULT

    TOKEN_ALL_ACCESS

    合并令牌的所有可能的访问权限

最后更新于