深入剖析Windows本地提权(基础概念篇)
1.前言
Windows的提权是在攻防对抗中绕不开的话题,本文针对Windows操作系统的本地提权手段原理以及方式进行梳理介绍,提权是后渗透重要的一环节,在权限较低的情况下,站在攻击者的视角进行内部网络安全测试、系统安全测试、应用安全测试等方面会受到限制
考虑到在Windows操作系统中提权手段的丰富性和多样性,本文划分为两部分,分别是基础概念篇和应用篇,基础概念篇介绍Windows提权相关的前置知识,包括SID/安全描述符/令牌等。 应用篇则从权限等级角度切入,介绍以从常规用户到管理员用户,从服务账户到SYSTEM,以及从常规用户到SYSTEM三类情形的Win提权姿势,应用篇的阅读没有前后顺序,可根据读者自身所需进行全文阅读或某方向内容的阅读。
xxxxxx
2.Windows提权相关基础概念
2.1 安全标识符SID
安全标识符(Security Identifiers,SID),是标识用户、组和计算机帐户的唯一的号码。每个帐户都有一个由权威机构(例如Windows域控制器)颁发的唯一SID,并存储在安全数据库中。每次用户登录时,系统都会从数据库中检索该用户的SID,并将其放在该用户的访问令牌中。当SID用作用户或组的唯一标识符时,就不能再使用它来标识另一个用户或组。
MSDN提供了常见的SID列表
S-1-5-18 (LocalSystem)
S-1-5-19 (LocalService)
S-1-5-20 (NetworkService)
S-1-5-32-544 (Administrators)
S-1-5-32-545 (Users)
S-1-5-32-550 (PrintOperators)
SID简化的结构如下
首先第一位S
是默认位,第二位R
代表SID
的版本号,第三位A
代表主标识值,后面的若干位SA
则代表子标识值,其中SA1
到SAn-1
代表域相关的标识值,SAn
则是RID
(relative identifier),代表相对标识值
针对上面S-1-5-21-1004336348-1177238915-682003330-512
进行解析,可以知道
SID
的版本号是1
- 主标识值是
5
,代表NT
- 域相关的子标识值是
21-1004336348-1177238915-682003330
RID
是512
,代表Domain Admins
这里我们可以使用windbg来查看进程的Token,Token中包含了用户SID以及所属组SID的信息
2.2 访问权限
访问权限是对对象执行特定操作需要的权限。例如,FILE_READ_DATA
这个访问权限代表了读取文件的权限。
访问权限分为两种类型:
-
特定访问权限: 执行单个操作的权限,如调试进程需要
SeDebugPrivilege
的权限,这个权限和需要操作的对象无关,只和操作行为本身有关系。具体可以参考官方文档 -
一般访问权限: 一种泛型访问权限,和对象的
ACL
有关,如上文中的FILE_READ_DATA
权限。
在Windows系统中,当我们访问某个Object
(对象)的时候,由ACL
(访问控制表)判断当前账户/账户组所拥有的权限是否允许我们执行相应的操作,ACL
涉及到了多个概念,包括ACE
/DACL
/SACL
等,这些概念的涵义如下
- ACL: Access Control List,用来表示用户(组)权限的列表,包括DACL和SACL
- ACE: Access Control Entry,ACL中的访问控制实体,一个ACL可能包含0到多个 ACE
- DACL: 当一个进程尝试访问一个安全对象的时候,系统检查对象的DACL中的ACE
注:DACL就是ACE链表,一种数据结构,每一个节点存放着访问权限等信息(这种链表查找起来比数组快)来决定是否赋予访问权限。如果对象没有DACL,系统赋予完全的访问权限
- SACL: System Access Control List,用来记录对安全对象访问的日志
当我们对某个文件进行访问时,系统将做以下判断:
1.如果没有DACL,系统将允许访问
2.如果存在DACL,但没有ACE,系统将拒绝所有访问
3.如果存在DACL,也存在ACE,那么会按照每个ACE指定允许或拒绝
下面我们同样通过windbg来获得内核对象的安全描述符,安全描述符指针放置在内核对象头部的字段中。
在windbg中启动内核调试,使用!sd
可以查看安全描述符信息,这里借用一张图
可以看到该DACL存在3个ACE,ACE的类型为
ACCESS_ALLOWED_ACE_TYPE
,Mask
是权限掩码,用来指定对应的权限。
因此图中第一条ACE代表的意思是允许SID为S-1-5-32-544
的对象能够对该对象做0x001fffff
对应的操作(Mask
的权限位根据不同对象类型有不同含义),具体的权限位可以参考下图
其中Object-specific access rights
代表和对象类型相关的访问权限,如FILE_READ_DATA
对应的则是0x1
, 属于文件这种对象类型的访问权限。
2.2.1 特殊访问权限
2.3 本地账户
本地帐户存储在本地服务器上。与提权相关的的本地账户包括管理员账户,默认本地系统帐户,以及服务账户
2.3.1 管理员账户(Administrators)
每台计算机都有一个管理员账户,管理员帐户是在Windows安装过程中创建的第一个帐户。
管理员帐户可以完全控制本地计算机上的文件、目录、服务和其他资源。管理员帐户可以创建其他本地用户、分配用户权限和分配权限。
默认管理员帐户无法删除或锁定,但可以重命名或禁用。
在Windows 10
和Windows Server 2016
中,Windows 安装程序禁用内置管理员帐户并创建另一个本地帐户,该帐户是管理员组的成员。
2.3.2 默认本地系统账户
SYSTEM 帐户由操作系统和在Windows下运行的服务使用。Windows 操作系统中有许多服务和进程需要能够在内部登录,例如在 Windows 安装期间。
SYSTEM 帐户就是为此目的而设计的。它是一个内部帐户,不会显示在用户管理器中,并且无法添加到任何组中。
默认情况下,SYSTEM 帐户被授予对NTFS卷上所有文件的完全控制权限。因此 SYSTEM 帐户包含管理员帐户所具有的功能和权限。
2.3.3 网络服务账户
NETWORK SERVICE
是服务控制管理器(SCM)使用的预定义本地帐户, 以这个账户运行的服务,允许把访问凭据提交给远程的计算机。
2.3.4 本地服务账户
LOCAL SERVICE
帐户是服务控制管理器使用的预定义本地帐户,它在本地计算机上具有最低权限,并在网络上提供匿名凭据。
类似于Sqlserver、IIS等都属于本地服务账户, 本地服务账户具有SeImpersonatePrivilege
这个特殊访问权限,至于为何强调该权限,下一篇会展开叙述。
2.4 访问令牌
Windows Token
又叫Access Token
(访问令牌),它是一个描述进程或者线程安全上下文的一个对象。不同的用户登录计算机后,都会生成一个Access Token
,这个Token在用户创建进程或者线程时会被使用并且不断的拷贝,这也就解释了A用户创建一个进程而该进程也不会有B用户的权限。
关于令牌的解释可以在MSDN中找到:
https://docs.microsoft.com/zh-cn/windows/win32/secauthz/access-tokens?redirectedfrom=MSDN
2.4.1 访问令牌组成
令牌分为如下两类:
- Primary Token (主令牌)
- Impersonation令牌 (模拟令牌)
注:当用户注销后,系统将会使主令牌切换为模拟令牌,而模拟令牌不会被清除,只有在重启机器后才会清除
主令牌是由windows内核创建并分配给进程的默认访问令牌,每一个进程有一个主令牌,它描述了与当前进程相关的用户帐户的安全上下文。
此外,线程可以模拟客户端帐户。模拟允许线程使用客户端的安全上下文与安全对象进行交互。模拟客户端的线程同时具有主令牌和模拟令牌,笔者理解为线程可以以客户端的身份和服务端交互并且是有模拟服务端的身份的权限
令牌的组成主要有如下部分:
- TokenID (标识
token
的唯一ID) - Privileges (当前
token
的权限,具体可查看官方文档) - UserAndGroups (当前用户及所属的组,实际是一个
SID
数组) - RestrictedSids (如果该值不为空,则表示令牌属于受限令牌,同样是一个
SID
数组) - TokenType (当前令牌的类型)
- ImpersonationLevel (模拟令牌的等级)
令牌的创建过程如下:
- 使用凭据(用户密码)进行认证
- 登录Session创建
- Windows返回用户sid和用户组sid
- LSA(Local Security Authority)创建一个Token
- 依据该token创建进程、线程(如果CreaetProcess时自己指定了 Token, LSA会用该Token, 否则就继承父进程Token进行运行)
2.4.2 令牌模拟级别
官方说明如下:
https://docs.microsoft.com/zh-cn/windows/win32/secauthz/impersonation-levels
模拟级别通过如下所示的SECURITY_IMPERSONATION_LEVEL
枚举表示
1: typedef enum _SECURITY_IMPERSONATION_LEVEL {
2: SecurityAnonymous,
3: SecurityIdentification,
4: SecurityImpersonation,
5: SecurityDelegation
6: } SECURITY_IMPERSONATION_LEVEL, *PSECURITY_IMPERSONATION_LEVEL;
模拟级别 | 说明 |
---|---|
SecurityAnonymous | 无法获取有关客户端的标识信息且无法模拟客户端; |
SecurityIdentification | 可以获取有关客户端的信息(如安全标识符和特权)但是无法模拟客户端 |
SecurityImpersonation | 可以在本地模拟客户端但无法在远程系统上模拟客户端 |
SecurityDelegation | 可以在本地和远程系统上模拟客户端 |
文档中给出了三个通过用户身份创建进程的函数:
函数 | 需要特权 | 输入 |
---|---|---|
CreateProcessWithLogon | null | 域/用户名/密码 |
CreateProcessWithToken | SeImpersonatePrivilege | Primary令牌 |
CreateProcessAsUser | SeAssignPrimaryTokenPrivilege和SeIncreaseQuotaPrivilege | Primary令牌 |
从这三个Win API中我们可以很容易的发现,当拥有SeAssignPrimaryToken
或者SeImpersonate
权限时,我们可以通过模拟Primary令牌的方式来创建新进程从而提升权限(前提是令牌具有Impersonation
和Delegation
级别),因此如果想通过模拟令牌的方式来进行提权,一个非常重要的前提就是用户具有SeImpersonate
权限
2.4.3 如何获取令牌
Win API中提供了OpenProcessToken/openThreadToken
等函数用来打开某个进程或者线程的访问令牌,其函数原型如下:
BOOL OpenProcessToken(
HANDLE ProcessHandle, //访问令牌已打开的进程的句柄
DWORD DesiredAccess,
PHANDLE TokenHandle //指向句柄的指针,该句柄在函数返回时标识新打开的访问令牌。
);
BOOL OpenThreadToken(
HANDLE ThreadHandle, //打开访问令牌的线程的句柄。
DWORD DesiredAccess,
BOOL OpenAsSelf,
PHANDLE TokenHandle //指向接收新打开的访问令牌句柄的变量的指针
);
通过OpenThreadToken/OpenProcessToken函数来获取访问令牌。
在这里,模拟令牌和主令牌之间是可以通过DuplicateTokenEx
函数互相转换的
DuplicateTokenEx
我们在最后调用CreateProcessWithToken/CreateThreadWithToken
时所传递的令牌必须要是主令牌,而我们一般获得的令牌都是模拟令牌,因此中间过程需要由DuplicateTokenEx
函数来进行转化
文档中给出了DuplicateTokenEx
创建主令牌的典型场景,服务器应用程序创建一个线程,该线程调用其中一个模拟函数(例如 ImpersonateNamedPipeClient)来模拟客户端。模拟线程然后调用OpenThreadToken
函数来获取自己的令牌,该令牌是模拟令牌。该线程在对DuplicateTokenEx
的调用中指定此模拟令牌,并指定TokenPrimary标志。然后DuplicateTokenEx
函数创建一个主令牌
因此,一个模拟令牌的过程大概是:
- 1.OpenProcess(获取目标进程上下文),返回的句柄将用于步骤2的参数
- 2.OpenProcessToken(获得进程访问令牌的句柄),通过该函数获得对应进程的令牌句柄
- 3.DuplicateTokenEx(创建一个主令牌),设置访问令牌模拟级别并复制一个令牌句柄
- 4.CreateProcessWithTokenW(创建进程),模拟主令牌的权限创建对应进程
因此当我们拥有SeImpersonatePrivilege
权限时便可以通过对进程爆破的方式找到满足如下条件的进程:
- 进程运行用户是SYSTEM
- 令牌级别至少是Impersonation级别
- 攻击者运行的权限至少拥有SeImpersonatePrivilege
这其实就是Token Kidnapping
技术,相关介绍
https://msrc-blog.microsoft.com/2009/04/14/token-kidnapping/
不过如果不是Administrator身份通过这种方式爆破来提权的成功性很低,这里只是提供了一种思路
3.从常规用户到管理员账户(BypassUAC)
这一方面的实现已经是经久不衰的话题,这里笔者要讨论的可能也猜到了,就是BypassUAC,当然在这里可能不只有BypassUAC实现了从常规用户到管理员用户,但目前的主流手段仍然是BypassUAC
UAC是微软Microsoft Windows Vista
以后版本引入的一种安全机制。其原理是通知用户是否对应用程序使用硬盘驱动器和系统文件授权,以达到帮助阻止恶意程序(有时也称为“恶意软件”)损坏系统的效果。
通过UAC,应用程序和任务可始终在非管理员帐户的安全上下文中运行,除非管理员特别授予管理员级别的系统访问权限。UAC 可以阻止未经授权的应用程序自动进行安装,并防止无意中更改系统设置。
下图清晰描述了如何根据是否启用UAC以及应用程序是否具有UAC清单来运行应用
在开启了UAC之后,如果用户是标准用户, Windows会给用户分配一个标准Access Token
而如果用户以管理员权限登陆,会生成两份访问令牌,一份是完整的管理员访问令牌(Full Access Token),一份是标准用户令牌,令牌相关知识已经在前文提到,这里就不在过多赘述
在绝大多数BypassUAC的实现中,笔者认为利用方式主要可以分为两大类:
1、各类UAC白名单程序的DLL劫持(Dll Hijack)
2、各类提升权限的COM接口利用(Elevated COM interface)
这里首先以第二种方式为例,结合实际BypassUAC组件来分析如何从常规用户到管理员账户进行提权
3.1 UAC的逆向分析
首先我们需要知道的是在任务管理器中使用管理员身份启动的进程的父进程是explorer,但是在explorer中KERNELBASE!CreateProcessW
位置下断点,使用管理员权限运行程序,并不会断下,而正常启动程序则可以正常断下来,这说明elevated的程序很有可能并非是由explorer拉起的。
而explorer本身也并非一个SYSTEM权限的进程,如果提权过程是由explorer过程发起,因此也不可能达到提权的目的。为此我们先在KERNELBASE!CreateProcessW
下入断点随后正常启动一个程序,查看调用栈分析:
由文件名可知该函数在
windows_storage.dll
中,进行反编译查看该函数的实现过程:发现
CInvokeCreateProcessVerb::CallCreateProcess
实际上会去调用AicLaunchAdminProcess
我们跟进这个函数:
可以看到这里实际上进行了RPC的通信,我们根据绑定句柄的UUID值可以找到相应的接口,这里使用Rpcview进行查看:
其实我们在服务中也可以看到appinfo的服务,对应的描述和UAC的实现非常类似
这里得到了对应接口的DLL为appinfo.dll
,我们拖入到IDA进行分析该DLL:
我们发现在该dll中的导出函数多以使用RPCRT4较多:
RPC Functions(Remote Procedure Call)使得一个程序可以调用另一计算机的子程序
本地过程调用(LPC,Local Procedure Call)则是在本机进程间进行通讯。
而在之前的了解中已经知道在UAC验证中是使用AiLaunchAdminProcess
这个API实现的,根据接口地址找到一个对应函数RAiLaunchAdminProcess
,在该函数的实现中找到了AiLaunchAdminProcess
:
其中封装了CreateProcessAsUser函数来进行提权操作,因此其实在整个UAC提升权限的过程中是appinfo服务完成了提升权限的处理
3.2 定位可利用的BypassUAC
到这里当然还没有结束,我们知道在UAC过程中会弹出认证框,因此我们还需要对整个弹窗流程进行分析:
弹窗的整体流程是
RAiLaunchAdminProcess -> AiCheckLUA -> AiLaunchConsentUI
其中根据名称很容易想到AiLaunchConsentUI
应该就是实现弹窗的过程,因此我们再继续跟进该函数进行分析:
swprintf_s(Buffer, 0x3Dui64, L"consent.exe %u %u %p", CurrentProcessId, phkResult, a2);
然后调用AiLaunchProcess
来启动consent.exe
,也就是真正绘制uac窗口的程序:
由此我们得知consent.exe
程序是用来绘制uac窗口的程序,因此将该程序进行逆向分析,通过分析解决如下问题
- 在consent绘制的uac窗口上,我们可以看到要进行权限提升的程序的路径,命令行等等相关信息,consent是如何获取这些信息的?
consent的命令行中传入了父进程的pid(appinfo服务的进程pid),一个结构体长度以及一个指向结构体的指针,随后consent调用
NtReadVirtualMemory
从父进程的内存中读取结构体的内容,这个结构体中就包含了需要特权提升的进程信息。
- consent程序如何将用户的操作反馈回给appinfo服务进程?
同样通过调用NtWriteVirtualMemory
写内存的方式从而传递到appinfo服务的内存当中
通过分析找到了决定是否弹窗的关键函数为:
跟进发现里面对COM组件进行了判断,判断依据主要是COM组件的Elevation属性:
继续返回到appinfo.dll
中查看逻辑,我们已经知道BypassUAC最主要也就是两种方式:
1、各类UAC白名单程序的DLL劫持(Dll Hijack)
2、各类提升权限的COM接口利用(Elevated COM interface)
找到了对应判断第一种的函数AiIsEXESafeToAutoApprove
:
继续跟进该函数:
此时得到了官方支持的具有
autoElevate
的可执行文件名单,也就是UAC白名单3.3 UAC是如何进行权限提升的
谈到这里,可能还是得回到consent程序中来看,其实整个UAC的过程就是appinfo和consent横跳的过程,接CuiCheckElevationAutoApprovalMedium
判断完成之后通过LABLE_78
调用NtQueryInformationToken
来获取一个权限提升的令牌
不过在此之前,判断是通过CuiGetTokenForApp
拿到已经是高权限的TokenHandle
,由于逆向水平不高,就没有继续跟进该函数进一步底层分析
之后通过NtDuplicateObject
和NtWriteVirtualMemory
将提升权限后的令牌写回至appinfo服务进程中
至此,UAC的整个过程应该也就差不多完成,最后来一个整体的总结
请求进程将要请求的进程cmdline和进程路径通过LPC接口传递给appinfo的RAiLuanchAdminProcess
函数,该函数首先验证路径是否在白名单中,并将结果传递给consent.exe
进程,该进程验证被请求的进程签名以及发起者的权限是否符合要求,然后决定是否弹出UAC框让用户进行确认。这个UAC框会创建新的安全桌面,屏蔽之前的界面。同时这个UAC框进程是SYSTEM权限进程,其他普通进程也无法和其进行通信交互。用户确认之后,会调用CreateProcessAsUser
函数以管理员权限启动请求的进程。
3.4 ICMLuaUtil方式提权
COM提升名称(COM Elevation Moniker)技术允许运行在用户帐户控制(UAC)下的应用程序用提升权限的方法来激活COM类,以此提升COM接口权限。
我们可以从UACME项目来查看对应的利用方式,该组件对应项目中实现的具体函数名称为:ucmCMLuaUtilShellExecMethod
UACME项目到目前为止总结了60多种绕过UAC的方式,并且列出具备auto-elevate能力的UAC白名单程序或接口。
该函数的源码如下:
NTSTATUS ucmCMLuaUtilShellExecMethod(
_In_ LPWSTR lpszExecutable
)
{
NTSTATUS MethodResult = STATUS_ACCESS_DENIED;
HRESULT r = E_FAIL, hr_init;
BOOL bApprove = FALSE;
ICMLuaUtil* CMLuaUtil = NULL;
hr_init = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
do {
//
// Potential fix check.
//
if (supIsConsentApprovedInterface(T_CLSID_CMSTPLUA, &bApprove)) {
if (bApprove == FALSE) {
MethodResult = STATUS_NOINTERFACE;
break;
}
}
r = ucmAllocateElevatedObject(
T_CLSID_CMSTPLUA,
&IID_ICMLuaUtil,
CLSCTX_LOCAL_SERVER,
(void**)&CMLuaUtil);
if (r != S_OK)
break;
if (CMLuaUtil == NULL) {
r = E_OUTOFMEMORY;
break;
}
r = CMLuaUtil->lpVtbl->ShellExec(CMLuaUtil,
lpszExecutable,
NULL,
NULL,
SEE_MASK_DEFAULT,
SW_SHOW);
if (SUCCEEDED(r))
MethodResult = STATUS_SUCCESS;
} while (FALSE);
if (CMLuaUtil != NULL) {
CMLuaUtil->lpVtbl->Release(CMLuaUtil);
}
if (hr_init == S_OK)
CoUninitialize();
return MethodResult;
}
结合源码可知这里利用的是CMSTPLUA组件
中ICMLuaUtil
接口,来搜索对应的CLSID,对应{3E5FC7F9-9A51-4367-9063-A120244FBEC7}
在前文我们已经了解UAC的整个过程,知道在consent程序会调用
CuiIsCOMClassAutoApprovable
函数判断是否存在自动权限提升的COM组件,其中判断的标志就是搜寻elevation
属性中是否为auto Approval
而该组件是具有该属性的,因此如果想要利用COM组件提权,必须满足的条件之一就是Elevation属性中的Enabled跟Auto Approval为True
这里我们使用的是ICMLuaUtil接口,因此继续分析cmlua.dll
我们主要来看该DLL的虚函数表是否有能够利用的函数
函数原型如下:
__int64 __fastcall CCMLuaUtil::ShellExec(
CCMLuaUtil *this,
const unsigned __int16 *a2,
const unsigned __int16 *a3,
const unsigned __int16 *a4,
unsigned int a5,
unsigned int a6)
/*
pExecInfo.lpFile = a2;
pExecInfo.lpParameters = a3;
pExecInfo.lpDirectory = a4;
pExecInfo.fMask = a5;
pExecInfo.nShow = a6;
*/
因此当我们创建并实例化该接口时,调用接口的ShellExec
方法创建指定进程从而BypassUAC
这里我们通过C#写了一个利用该组件提权的程序,其核心代码如下:
当编译完成运行时却发现程序依然还是会弹出UAC框进行验证,这是因为如果执行COM提升名称(COM Elevation Moniker)代码的程序身份是不可信的,则会触发UAC弹窗,这里我们的程序并没有经过验证
默认能够绕过UAC的文件或者程序需要满足如下三个条件:
- 程序配置为自动提升权限,以管理员权限执行
- 程序包含签名
- 从受信任的目录(c:windowssystem32)执行
因此我们要利用系统的可信进程去进行调用,可以选择的有rundll32.exe、explorer.exe等,只需要把创建COM组件的代码以及执行你想执行的命令的代码,放到可信任进程里面去执行,这样就可以BypassUAC。
对应的第一种方式就是生成DLL文件,将实现都放在DLLmain中,调用rundll32.exe
或者cmstp.exe
来执行该DLL文件,不过考虑到会有文件落地,并且rundll32.exe
本身已经被很多AV监控,因此该方法不是首选方法。
第二种方法引出另一个技术,叫MasqueradePEB
进程的信息,包括命令行参数、图像位置、加载的模块等存储在进程控制块中,并且该结构是可以在用户空间访问和修改的,通过修改PEB进程控制块来欺骗系统认为该进程是一个合法进程
注意在为自己的进程修改进程控制块过程中不需要SeDebugPrivilege权限
而UAC在判断系统进程是否可信,判断依据是PEB结构,因此在使用COM组件提权之前我们将进程信息伪装成可信程序,例如c:windowsexplorer.exe
等就能够BypassUAC
当调用PEBMasq.MasqueradePEB
伪装成explorer.exe
后
我们执行该程序发现现在并不会弹出UAC框并且成功获得管理员权限的cmd进程:
3.5 利用白名单程序实现BypassUAC
利用白名单程序的本质实际上是劫持注册表,这种方法主要是通过寻找autoElevated
属性为true的程序,修改其注册表shellopencommand
的值,改成我们想要执行的paylaod,在该值中指明的字段会在这类程序运行时自动执行,类似于默认程序打开,当你以后运行该程序时,这个command命令都会自动执行。
这里我们以Windows 10中Fodhelper.exe
为例,该程序是一个autoelevate
程序
使用ProcessMonitor监控打开该进程后该进程的相关操作:
启动
fodhelper.exe
时,会在注册表中执行以下检查:
HKCU:SoftwareClassesms-settingsshellopencommand
HKCU:SoftwareClassesms-settingsshellopencommandDelegateExecute
HKCU:SoftwareClassesms-settingsshellopencommand(default)
由于这些注册表项不存在,用户可以在注册表中创建此结构,以便操纵fodhelper以绕过用户帐户控制 (UAC) 执行具有更高权限的命令。
当再次运行fodhelper后将执行命令并打开提升的 PowerShell 会话:
但是在利用过程中值得注意的是需要确保
DelegateExecute
项是存在的,否则也无法成功BypassUAC
如何寻找具有autoPriv的白名单
其实这一类程序都满足一定的条件:
- 可执行文件必须经过
Windows Publisher
的数字签名,Windows Publisher
是用于对Windows附带的所有代码进行签名的证书(仅由 Microsoft进行签名是不够的,因此Windows未附带的Microsoft软件不包括在内); - 可执行文件必须位于其中一个为数不多的“安全”目录中。安全目录是指标准用户无法修改的目录,并且它们包括 %SystemRoot%System32(例如,WindowsSystem32)及其大多数子目录、%SystemRoot%Ehome,以及 %ProgramFiles%下的少许目录(其中包括
Windows Defender
和Windows notepad
)。
这里可以直接使用命令:
strings.exe -s *.exe | findstr /i "autoElevate"
来获取manifest文件中申明autoElevate
的程序,也可以通过UACME项目中uacinfo.exe
来获得相关程序信息
4. 从服务账户到SYSTEM
之所以需要单独划分为从服务账户到SYSTEM权限,主要是因为在服务账户中通常具有SeImpersonatePrivilege
权限,即使是IIS、Sqlserver用户等也同样具有该权限,在渗透测试中从web端或者数据库来打点也是极为常见的,因此如何进行存在SeImpersonatePrivilege
权限的提权也是一直研究的热点。
以下用户拥有SeImpersonatePrivilege
权限:
- 本地管理员账户(不包括管理员组普通账户)和本地服务帐户
- 由SCM启动的服务
服务账户在Windows权限模型中本身就拥有很高的权限,所以微软不认为这是一个漏洞
讨论到这里,首先需要介绍的就是目前使用最广泛的Potato系列,其实所有的Potato系列本质上都是relay,如果是远程提权则是relay,用于本地也可以称为是reflection
4.2 HotPotato
2016年1月中旬,来自FoxGlove Security安全团队的breenmachine在博客中介绍了一种被称为Hot Potato的漏洞利用技术。在默认配置下,Hot Potato能够利用Windows操作系统的已知缺陷来获取本地计算机的最高控制权限,受影响的操作系统包括Windows 7/8/10 和 Windows Server 2008/2012
有关详细的原理讲述可以参考原作者的文章,在这里的核心思想是通过NTLM中继(基于HTTP->SMB中继)和NBNS欺骗。
在此之前我们需要知道windows解析域名的顺序是:
Hosts
DNS (cache / server)
LLMNR
NBNS
1.LLMNR
LLMNR 是一种基于协议域名系统(DNS)数据包的格式,使得两者的IPv4和IPv6的主机进行名称解析为同一本地链路上的主机,因此也称作多播DNS。监听的端口为UDP/5355,支持IPv4和IPv6 ,并且在Linux上也实现了此协议。其解析名称的特点为端到端,IPv4 的广播地址为224.0.0.252
,IPv6的广播地址为FF02:0:0:0:0:0:1:3
或FF02::1:3
当局域网中的DNS服务器不可用时,DNS客户端会使用LLMNR本地链路多播名称解析来解析本地网段上的主机的名称,直到网络连接恢复正常为止。
LLMNR 进行名称解析的过程为:
1.检查本地 NetBIOS 缓存
2.如果缓存中没有则会像当前子网域发送广播
3.当前子网域的其他主机收到并检查广播包,如果没有主机响应则请求失败
也就是说LLMNR并不需要一个服务器,而是采用广播包的形式,去询问DNS,跟ARP很像,因此也存在类似arp投毒等问题的出现
举个简单的例子:
当我们通过net use尝试去建立一个不存在IPC链接或者是尝试和攻击机建立IPC链接的时候:
当本机在Hosts文件里面没有找到,通过DNS解析失败。就会通过LLMNR协议进行广播,前文已经说过LLMNR的广播地址对应的就是224.0.0.252
2.NBNS
全称是NetBIOS Name Service
NetBIOS 协议进行名称解析的过程如下:
检查本地 NetBIOS 缓存
如果缓存中没有请求的名称且已配置了 WINS 服务器,接下来则会向 WINS 服务器发出请求
如果没有配置 WINS 服务器或 WINS 服务器无响应则会向当前子网域发送广播
如果发送广播后无任何主机响应则会读取本地的 lmhosts 文件
需要注意的是这里和ARP类似,会在本地所有主机进行广播,因此当攻击者相应这条广播消息时,发送方便会认为自己查找的目标就是这个响应者
NBNS包有1个2字节的TXID
字段,必须进行请求响应
的匹配,在这里我们假定没有权限进行流量的监听,也就不知道是在哪一个端口进行通信,但由于是通过UDP进行传输,因此可以通过1-65535之间进行泛洪猜测。
前文提到如果网络中有DNS记录,此时就不会用到NBNS协议,作者使用称为UDP端口耗尽
的技术来强制系统上的所有DNS查找失败,当请求已经没有可用的UDP资源时,DNS查找失败便会通过NBNS进行广播查询
3.设置假的WPAD服务器
wpad全称是Web Proxy Auto-Discovery Protocol
,通过让浏览器自动发现代理服务器,定位代理配置文件PAC(在下文也叫做PAC文件或者wpad.dat),下载编译并运行,最终自动使用代理访问网络。
它在本地网络上搜索名为wpad的计算机以找到该文件。然后执行以下步骤:
1.如果配置了DHCP服务器,则客户端从DHCP服务器中检索wpad.dat文件(如果成功,则执行步骤4)。
2.wpad.corpdomain.com查询被发送到DNS服务器以查找分发Wpad配置的设备。(如果成功,则执行第4步)。
3.发送WPAD的LLMNR或NBNS查询(如果成功,请转到第4步,否则无法使用代理)
4.下载wpad.dat并使用它。
在下面的流量捕获中,机器以广播方式发送NBNS数据包,请求wpad.dat:
在 Windows 中,系统默认会通过访问URLhttp://wpad/wpad.dat
自动尝试检测网络代理设置配置,并且适用于某些Windows服务,例如Windows更新
当然是http://wpad/wpad.dat
不会存在于所有网络上,因为主机名wpad不一定存在于DNS名称服务器中。然而,正如我们在上面看到的,我们可以使用NBNS欺骗来伪造主机名
凭借欺骗NBNS响应的能力,我们可以将 NBNS 欺骗器定位在127.0.0.1。我们用主机WPAD或WPAD.DOMAIN.TLD的NBNS响应数据包淹没目标机器(我们自己的机器),我们说WPAD主机的IP地址为127.0.0.1。
同时,我们在127.0.0.1本地运行一个HTTP服务器。当它收到对http://wpad/wpad.dat
的请求时,它会响应如下:
使得所有流量都通过127.0.0.1上运行的服务器重定向
NTLM中继
NTLM中继是一种众所周知但经常被误解的针对 Windows NTLM 身份验证的攻击。NTLM 协议容易受到中间人攻击。如果攻击者可以欺骗用户尝试使用 NTLM 对其机器进行身份验证,他可以将该身份验证尝试中继到另一台机器
这种攻击的旧版本让受害者尝试使用带有NTLM身份验证的SMB协议向攻击者进行身份验证。然后攻击者会将这些凭据转发回受害者的计算机,并使用类似“psexec”的技术获得远程访问权限。
微软通过使用已经在进行中的挑战来禁止相同协议的NTLM身份验证来修补这个问题。这意味着从一台主机到其自身的SMB->SMB NTLM中继
将不再起作用。然而,跨协议攻击,如HTTP->SMB
仍然可以中继成功
在Potato漏洞利用中,所有HTTP请求都通过302重定向重定向到 http://localhost/GETHASHESxxxxx
,其中xxxxx对应一个唯一标识符。请求http://localhost/GETHASHESxxxxx
响应NTLM身份验证的 401 请求。
这样我们就可以将所有NTLM凭据中继到本地SMB侦听器以创建运行用户定义命令的新系统服务。
当有问题的HTTP请求来自高权限帐户时,例如,当它是来自Windows更新服务的请求时,那么将以“NT AUTHORITYSYSTEM”权限运行
最原始的Potato其本质就是一个NTLM Relay
,最后附上一张整体的流程图
局限性
Microsoft通过使用已经在进行中的质询来禁止相同协议的NTLM身份验证来修补此问题 (MS16-075)。这意味着从一台主机到其自身的SMB->SMB NTLM
中继将不再起作用。MS16-077 WPAD名称解析将不使用NetBIOS
并且在请求 PAC 文件时不发送凭据,因此WAPD Attack已修补。
4.3 Rotten Potato
相比于Hotpotato利用NTLM Relay中级到SMB,最后调用svcctl.CreateServiceW
创建指定的服务,Rotten Potato
主要利用远程过程调用(RPC)通过CoGetInstanceFromIStorage
进行NTLM中继,在本地协商得到NT AUTHORITYSYSTEM
帐户的安全令牌,在模拟安全令牌后调用CreateProcessWithToken
等API来使用指定的令牌来创建进程
public static void BootstrapComMarshal()
{
IStorage stg = ComUtils.CreateStorage();
// Use a known local system service COM server, in this cast BITSv1
Guid clsid = new Guid("4991d34b-80a1-4291-83b6-3328366b9097");
TestClass c = new TestClass(stg, String.Format("{0}[{1}]", "127.0.0.1", 6666)); // ip and port
MULTI_QI[] qis = new MULTI_QI[1];
qis[0].pIID = ComUtils.IID_IUnknownPtr;
qis[0].pItf = null;
qis[0].hr = 0;
CoGetInstanceFromIStorage(null, ref clsid, null, CLSCTX.CLSCTX_LOCAL_SERVER, c, 1,qis);
}
在这样一段代码中,CoGetInstanceFromIStorage
调用尝试从调用者指定的位置获取指定对象的实例
这里我们告诉COM我们想要一个BITS
对象的实例,我们想要从 127.0.0.1端口 6666加载它,注意TestClass
实际上是一个IStorage对象的实例,
因此对应的COM组件会和本地6666端口进行通信,同时监听该端口,如果我们以正确的方式回复,我们就会让以SYSTEM
帐户运行的COM组件尝试向我们NTLM
身份验证。
这里作者使用非常巧妙的一点来避免不同版本的Windows RPC报文的问题,即将我们在TCP端口6666上从COM接收到的任何数据包中继回TCP端口135
上的本地 Windows RPC 监听器
。由于我们收到的这些数据包是有效RPC对话的一部分,无论我们使用的是什么版本的Windows都会做出适当的反应。然后,我们可以使用从 135上的Windows RPC
收到的这些数据包作为我们对COM的回复的模板。
来看一下对应Poc产生的数据情况
我们收到的第一个数据包(数据包7)是在端口6666上传入的(来自与本地端口对话的COM)。接下来,我们将相同的数据包(数据包9)中继到
TCP 135
上的 RPC。然后在数据包11中,我们从RPC(TCP 135)收到回复,在数据包13中,我们将该回复中继到COM。
重复上述过程,直到进行NTLM身份验证
NTLM中继和令牌协商
左边蓝色的是COM组件在TCP端口6666上发送给我们的数据包。右边红色的是我们将使用从这些数据包中提取的数据进行的Windows API调用。
为了使用 NTLM 身份验证在本地协商安全令牌,必须首先调用函数AcquireCredentialsHandle
来获取我们需要的数据结构的句柄。
其中
SECPKG_CRED_BOTH
代表着验证传入凭据或使用本地凭据准备传出令牌。此标志启用其他两个标志,hCred
指向CredHandle结构的指针,用于接收凭证句柄。
接下来,我们调用AcceptSecurityContext
,该函数的输入将是NTLM类型 1(协商)消息。输出将是NTLM类型(挑战)消息,该消息被发送回尝试进行身份验证的客户端,在本例中为DCOM。
在 RPC 和 COM 之间中继了几个数据包后,最终 COM 将尝试通过发送 NTLM type1(协商)消息来尝试向我们发起 NTLM 身份验证,此时我们将从该数据包中提取的 NTLM type1(协商)消息作为输入传递给AcquireCredentialsHandle
函数:
之前我们将NTLM type1(协商)数据包转发到端口 135 上的 RPC,现在它会回复一个NTLM的挑战包,但是我们不能简单地将这个数据包转发回 COM,分别看一下135端口RPC产生的挑战包和本地6666端口发送的挑战包:
请注意突出显示的字段“NTLM Server Challenge”及其下方的字段“Reserved”,它们的值不同,前文提到了AcceptSecurityContext
该调用的输出是一条 NTLM (挑战)消息,因此在这里我们需要将得到的输出结果进行替换,这样才会使得对AcceptSecurityContext
的成功调用
当我们将修改后的 NTLM type 2(协商)数据包转发到 COM,其中Challenge/Reserved
字段与“AcceptSecurityContext”的输出相匹配,COM组件会向我们发送回NTLM type 3(身份验证)数据包,我们使用它来对AcceptSecurityContext
进行最终调用。
最后我们以一张图来总结一下整个Rotten Potato提权的大体流程:
- 1.使用
CoGetInstanceFromIStorage
API 调用欺骗 RPC 对代理进行身份验证。在此调用中指定了代理 IP/端口。 -
- RPC 向代理发送NTLM 协商包。
- 3.代理依赖的NTLM协商到RPC在端口135,被用作模板。同时,调用
AcceptSecurityContext
以强制进行本地身份验证。 - 4&5 得到
RPC 135
和AcceptSecurityContext
的NTLM 挑战包后进行部分替换以匹配本地协商并转发到RPC,也就是到步骤6 - 7&8 RPC对使用
AcceptSecurityContext
得到的挑战包进行相应 - 9 模拟令牌创建进程,实现提权
局限
在https://decoder.cloud/2018/10/29/no-more-rotten-juicy-potato提到了关于相关更新的修复,其中主要为两点:
-
DCOM 不与我们的本地侦听器交谈,即现在DCOM组件不会和除135端口以外的其他端口进行通信,这意味着无法通过6666端口充当中间人
-
将数据包发送到我们控制下侦听端口 135 的主机,然后将数据转发到我们本地的 COM 侦听器是行不通的。问题是在这种情况下,客户端不会协商本地身份验证。
因此,此技术不适用于 >= Windows 10 1809 和 Windows Server 2019 的版本
4.4 Pipepotato(BadPotato)
其实Pipepotato
也就是Printspoofer
利用,有关该提权利用方式以及原理介绍可以参考笔者之前的文章PrintSpoofer提权原理探究,在这里再次对其进行部分引用。
回到前面遇到的问题上,我们知道在Win 10中已经做出了调整,利用IStorage COM组件只允许和135端口进行通信,意味着中间人攻击已经失效,我们无法进行重放,因此Pipepotato作者把目光放到了管道上,而笔者个人认为利用管道模拟的方式更加简洁并且高效
管道可以有两种类型:
- 匿名管道 – 匿名管道通常在父进程和子进程之间传输数据。它们通常用于在子进程与其父进程之间重定向标准输入和输出。
- 命名管道 – 命名管道可以在不相关的进程之间传输数据,前提是管道的权限授予对客户端进程的适当访问权限。
命名管道服务器线程可以调用ImpersonateNamedPipeClient
函数来假定连接到管道客户端的用户的访问令牌。例如,命名管道服务器可以提供对管道服务器具有特权访问权限的数据库或文件系统的访问。当管道客户端向服务器发送请求时,服务器模拟客户端并尝试访问受保护的数据库。然后系统会根据客户端的安全级别授予或拒绝服务器的访问权限。当服务器完成时,它使用RevertToSelf
函数恢复其原始安全令牌。
该模拟级别决定了在模拟客户端服务器可以执行的操作。默认情况下,服务器在 SecurityImpersonation模拟级别进行模拟。
该利用的核心原理是通过打印机漏洞强迫运行Spooler服务的任何主机通过Kerberos或者NTLM向攻击者选择的目标发起身份认证请求,这里可以强迫一个特权进程来访问我们的恶意管道,当我们拥有SecurityImpersonation
权限时便可以通过模拟管道安全上下文的方式,来模拟该特权进程,得到该特权进程的模拟令牌,通过将其转化为主令牌的方式最终调用CreateProcessWithToken
等方式来以SYSTEM
权限运行程序
已有较为完善的工具:
https://github.com/itm4n/PrintSpoofer
该手段由于并不是微软承认的一个漏洞,因此实际上还是比较实用和不受限制的,但是由于近年来在PrintNightmare爆发之后,很多企业会选择关闭spoolss服务,因此使得Printerbug失效,从而导致pipePotato的失效
4.5 PetitPotam提权
从名字上看该方式并不是传统的Potato,但是从利用手法上,笔者仍然认为其本质也可以通过模拟管道客户端进而实现提权,并且PetitPotam更多用于域横向渗透中,同时也能够用于本地提权
MS-EFSR里面有个函数EfsRpcOpenFileRaw
long EfsRpcOpenFileRaw(
[in] handle_t binding_h,
[out] PEXIMPORT_CONTEXT_HANDLE* hContext,
[in, string] wchar_t* FileName,
[in] long Flags
);
他的作用是打开服务器上的加密对象以进行备份或还原,服务器上的加密对象由FileName参数指定,FileName的类型是UncPath。
当指定格式为\IPC$
的时候,lsass.exe服务就会去访问\IPpipesrvsrv
有关于该协议的完成IDL文件:
https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-efsr/4a25b8e1-fd90-41b6-9301-62ed71334436
这里是通过调用RPC接口的方式实现的
Microsoft 远程过程调用 (RPC) 定义了一种用于创建分布式客户端/服务器程序的强大技术。RPC 运行时存根和库管理大多数与网络协议和通信相关的进程。这使您能够专注于应用程序的细节而不是网络的细节。
有关RPC编程以及介绍将会放在下一部分漏洞挖掘的时候进行重点展示,相关内容可以参考MSDN:
https://docs.microsoft.com/en-us/windows/win32/rpc/rpc-start-page
MS-EFSR的调用有pipelsarpc
和pipeefsrpc
两种方法,其中
pipelsarpc的服务器接口必须是UUID[c681d488-d850-11d0-8c52-00c04fd90f7e]
pipeefsrpc的服务器接口必须是UUID [df1941c5-fe89-4e79-bf10-463657acf44d]
但是通过efsrpc
的方式并不支持,也就是说无法以普通用户的身份来调用该接口的方法,而pipelsarpc
则可以,因此在构造过程中我们通常使用pipelsarpc
接口
结合管道实现提权
类似于printspoofer,前文我们提到当指定格式为\IPC$
的时候,lsass.exe服务就会去访问\IPpipesrvsrv
,而这个管道我们是无法创建的,因此我们同样需要使用UNC路径的一些trick
,如果能够使得lsass.exe
访问我们自己创建的恶意管道,在拥有SeImpersonatePrivilege
的情况下,便可以模拟管道客户端安全上下文的方式,以lsass.exe
所属用户的权限创建进程,而该进程是系统权限(SYSTEM)
事实上,我们确实可以通过构造类似这样的UNC路径欺骗系统:
\\127.0.0.1/pipe/crispr\C$\x
可以看到,通过利用特定UNC路径成功欺骗lsass.exe
进程去连接指定的管道,因此为了实现本地提权,我们只需要创建这样一个特定管道去模拟RPC客户端安全上下文即可
鉴于目前利用PetitPotam实现本地提权的工具较少,笔者实现了利用PetitPotam
进行本地提权:
https://github.com/crisprss/PetitPotam
遍历了可以利用的函数,最后发现如下几种接口函数用于本地提权:
1.EfsRpcOpenFileRaw (fixed with CVE-2021-36942)
2: EfsRpcEncryptFileSrv_Downlevel
3: EfsRpcDecryptFileSrv_Downlevel
4: EfsRpcQueryUsersOnFile_Downlevel
5: EfsRpcQueryRecoveryAgents_Downlevel
6: EfsRpcRemoveUsersFromFile_Downlevel
7: EfsRpcAddUsersToFile_Downlevel
不过没有使用
CreateProcessAsUserW
因此在创建进程过程中并不会以interactive
的方式有兴趣的师傅可以对此进行优化,笔者在后期也会进行相关优化
5.从常规用户到SYSTEM
在一般的提权文章中很少会有关于从普通用户提权到SYSTEM,究其原因笔者认为,一方面是因为类似这样的提权比较频繁的出现在win sys内核漏洞中,另一方面是因为利用协议层或者逻辑层面而言去利用进而提权也是非常巧妙的,很具技巧性和机遇性
而在很多提权的思路中,利用与文件相关的操作进行提权的手法是笔者认为非常巧妙的,在这里笔者主要通过对于一个提权手法的展现来探讨一些这些非常规提权手段
5.1 CVE-2020-0787
该漏洞的原因主要是出于Windows 后台智能传输服务
程序员和系统管理员使用后台智能传输服务 (BITS) 从 HTTP Web 服务器和 SMB 文件共享下载文件或将文件上传到 HTTP Web 服务器。BITS 会考虑传输成本和网络使用情况,尽量减少对用户前台工作的影响。BITS 还处理网络中断、暂停和自动恢复传输,即使在重新启动后也是如此。BITS 包括用于创建和管理传输的 PowerShell cmdlet 以及 BitsAdmin 命令行实用程序。
后台智能传输服务公开了几个 COM 对象,我们可以使用OleViewDotNet
列出这些对象以及相关信息
在这里,我们将重点介绍后台智能传输 (BIT) 控制类1.0和
Legacy BIT 控制类
及其主要接口,分别为IBackgroundCopyManager和IBackgroundCopyMgr
MSDN中给出了关于BITS的使用
https://docs.microsoft.com/en-us/windows/win32/bits/bits-dot-net
以下步骤显示了如何使用后台智能传输服务 (BITS)接口执行文件传输
连接到 BITS 服务
创建转移作业
将文件添加到作业
开始工作
确定 BITS 是否成功传输文件
完成工作
在BIT Legacy Background Intelligent Transfer Control Class
中我们可以通过:
CoCreateInstance(CLSID_69AD4AEE-51BE-439B-A92C-86AE490E8B30) -> IBackgroundCopyQMgr*
|__ IBackgroundCopyQMgr::CreateGroup() -> IBackgroundCopyGroup*
|__ IBackgroundCopyGroup::CreateJob() -> IBackgroundCopyJob1*
|__ IBackgroundCopyJob1::AddFiles(FILESETINFO)
|__ IBackgroundCopyJob1::Resume()
|__ IBackgroundCopyJob1::Complete()
- 创建BIT 控制类的实例通过
CoCreateInstance()
得到请求指向IBackgroundCopyQMgr
接口的指针 - 利用该指针创建一个组
IBackgroundCopyQMgr::CreateGroup()
- 通过调用创建一个作业
IBackgroundCopyGroup::CreateJob()
以获取指向IBackgroundCopyJob1
接口的指针 - 通过调用将文件添加到作业中,该调IBackgroundCopyJob1::AddFiles()
- 最后,由于作业是SUSPENDED状态,必须在作业状态为
TRANSFERRED
时调用IBackgroundCopyJob1::Resume()和IBackgroundCopyJob1::Complete()
而重点就在IBackgroundCopyGroup
的文档差异上,在MSDN提供的参考文档中提供了13种方法,而使用OleViewDotNet
查看时却发现存在15中方法:
我们知道相应的头文件是
Qmgr.h
,因此我们直接定位到头文件中进行查看:我们可以看到两个未公开的方法:
QueryNewJobInterface()和SetNotificationPointer()
作者进行了如下的操作,其实就是通过QueryNewJobInterface
指向的这个未知接口来替换之前IBackgroundCopyJob1
完成Resume和Complete操作
从作者给出的Poc上也能清晰地知道其操作,对于Complete的实现也是同样
然后使用Process Monitor
来观察,看到该服务在目标目录中创建了一个 TMP 文件,并尝试打开作为参数给出的本地文件
一旦我们调用该
Resume()
函数,该服务就会开始读取目标文件\127.0.0.1C$WindowsSystem32driversetchosts
并将其内容写入TMP文件C:TempBITF046.tmp
最后,TMP 文件被重命名为test.txt调用MoveFileEx(),并且此时并不会模拟当前用户,而是以服务用户本身的权限进行操作,即SYSTEM,这是漏洞所在
然而一旦涉及到文件写和读的操作,便一定涉及到文件符号链接和挂载的问题,而这些问题就有可能导致我们能进行任意文件的读和写
文件操作
James Forshaw(@tiraniddo)在NTFS文件系统和Windows操作系统内部完成了开创性工作,他在发表了许多相关的文章(symlinks、hardlinks、NTPathConversion、DirCreate2FileRead、FileWrite2EoP、AccessModeMismatch
),并在Infiltrate和SyScan。他提出了几种滥用Windows文件系统和路径解析功能的技术(大致总结如下),并在开源的symboliclink-testing-tools工具包和NtApiDotNet库中实现了这些技术。
在这里介绍两种与该漏洞相关的文件操作
文件锁
oplock是一种可以放置在文件上的锁,当其他进程想要访问该文件时,它可以被告知—同时延迟这些进程的访问,以便锁定进程可以在解除锁之前让文件处于适当的状态。oplocks最初是为通过SMB缓存客户端-服务器文件访问而设计的,可以通过调用文件句柄上的特定控制代码设置oplock。
你可以通过锁定一个试图打开的文件或目录来轻松地赢得与进程的竞争。
SetOpLock工具可以让你创建这些锁,并阻止对文件或目录的访问,直到你按回车键释放锁。它让你在读、写和放行oplock之间进行选择。
可以看到这个文件已经被击中,并且只有我们释放后才有机会读取文件的内容:
对象符号链接管理器
虽然NTFS确实提供了文件系统的符号链接,但在Windows上,无权限的用户不能在文件系统上创建符号链接:它需要SeCreateSymbolicLinkPrivilege
,默认情况下,只有管理员才能获得该权限。
不过,无权限的用户可以在Windows的 “对象管理器 “中创建符号链接,顾名思义,它可以管理进程、部分和文件等对象。对象管理器使用符号链接,例如驱动器字母和命名管道与相应设备相关联。用户可以在可写对象目录中创建对象符号链接,如RPC CONTROL
,这些符号链接可以指向任意路径—包括文件系统中的路径—无论该路径当前是否存在。
当与NTFS连接点结合时,对象符号链接会做一些有趣的事情。事实上,一个无权限的用户可以将一个挂载点与该目录中的对象管理器符号连接起来,解析到RPC CONTROL目录
因此我们可以通过这种方式使得C:tmpb2test1.txt解析为C:tmpb3test.txt
需要注意的是我们必须对这些文件拥有可写权限,并且第一个文件需要是空文件,而第二个文件要是存在的文件
回到上文,既然我们知道了当调用Resume()
函数,该服务就会开始读取目标文件并且写入TMP文件,那如何利用有关文件的操作来进行任意文件写入?
如何利用
这里直接介绍,这个想法是为服务提供一个文件夹的路径,该文件夹最初将用作另一个“物理”目录的连接点。我们使用本地文件创建一个新作业以“下载”并在 TMP 文件上设置 Oplock。恢复作业后,该服务将开始写入TMP文件,同时模拟RPC客户端并将命中 Oplock。然后我们需要做的就是将挂载点切换到对象目录并创建两个符号链接。TMP文件将指向我们拥有的任何文件,“本地”文件将指向文件System32夹中的新 DLL 文件。最后,在释放 Oplock 后,服务将继续写入原始 TMP 文件,但它将通过我们的两个符号链接执行最后的移动操作。
1.首先我们创建如下的目录结构:
<DIR> C:workspace
|__ <DIR> bait
|__ <DIR> mountpoint
|__ FakeDll.dll
mountpoint目录的目的是从连接到bait目录切换到连接到RPC Control对象
目录。FakeDll.dll是我们想要移动到受限位置的文件,例如C:WindowsSystem32
2.创建挂载点
从C:workspacemountpoint
创建一个挂载点到C:workspacebait
,之所以创建这个挂载点是由于我们要确定tmp文件的确切名称,切换挂载点后这样可以在这个文件夹(bait)当中找到名为 BIT*.tmp 的文件。
3.创建一个新的工作
我们将使用Legacy Control Class
提供的接口来创建具有以下参数的新作业。
Target URL: \127.0.0.1C$WindowsSystem32driversetchosts
Local file: C:workspacemountpointtest.txt
由于之前创建的结点,本地文件的真实路径将是C:workspacebaittest.txt
4.找到TMP文件并设置一个Oplock
将文件添加到作业队列时,该服务会立即创建一个 TMP 文件。由于它有一个“随机”名称,我们必须列出目录的内容bait才能找到它。在这里,我们应该找到一个像BIT1337.tmp. 一旦我们有了名字,我们就可以在文件上设置一个Oplock。
5.job继续,等待Oplock
一旦作业恢复,服务将打开TMP文件进行写入并触发 Oplock。这种技术允许我们暂停操作
6.此时切换挂载点
在这一步之前:
TMP file = C:workspacemountpointBIT1337.tmp -> C:workspacebaitBIT1337.tmp
Local file = C:workspacemountpointtest.txt -> C:workspacebaittest.txt
我们切换挂载点并创建符号链接,在切换之前首先
- 删除了mountpoint文件夹下面的挂载点 ( ReparsePoint::DeleteMountPoint 删除了挂载点)
- 重新将mountpoint挂载到 RPC Control 上 ( ReparsePoint::CreateMountPoint 创建了新的挂载点)
C:workspacemountpoint -> RPC Control
Symlink #1: RPC ControlBIT1337.tmp -> C:workspaceFakeDll.dll
Symlink #2: RPC Controltest.txt -> C:WindowsSystem32FakeDll.dll
在这一步之后:
TMP file = C:workspacemountpointBIT1337.tmp -> C:workspaceFakeDll.dll
Local file = C:workspacemountpointtest.txt -> C:WindowsSystem32FakeDll.dll
因此本来该服务是将C:workspacemountpointBIT1337.tmp
调用MoveFileEx()
函数重命名为C:workspacemountpointtest.txt
,而由于重新挂载并且创建符号链接,服务其实是将C:workspaceFakeDll.dll
调用MoveFileEx()
重命名为C:WindowsSystem32FakeDll.dll
,因此,我们的 DLL 将被移动到该System32文件夹中。
进行本地提权
最终使用了UsoDllLoader获得SYSTEMshell,这是作者发现的一个基于DLL劫持的方式进行的本地提权,在Windows10之后,微软提供了一种名叫Update Session Orchestrator
的服务。通过这个服务能够作为一个普通用户去用COM和系统服务通信,并开始一个"update scan" 去进行更新,作者发现usoclient.exe可以实现更新的目的,并且他在开始更新的时候还会尝试去加载一个不存在的DLLwindowscoredeviceinfo.dll
,因此我们利用任意文件写入的方式,可以写入我们编译好的恶意DLL到C:WindowsSystem32windowscoredeviceinfo.dll
中实现提权。
详细思路和原理可以参考作者的文章:
https://itm4n.github.io/usodllloader-part1/
https://itm4n.github.io/usodllloader-part2/
6.总结
由于Windows本地提权的方式太过繁杂,笔者在这里主要以介绍近年国内外最为火热和范围最广的提权方式,并且尝试从不同角度来探讨提权的多样性,从NTLM Relay、RPC接口方法、管道模拟客户端、BypassUAC、以及利用系统缺陷导致以系统权限实现文件的相关读写来进行提权,如有不当之处还请指正,在下一篇文章中笔者将通过披露最近挖掘的相关提权漏洞来分享漏洞挖掘经历
参考文章:
https://itm4n.github.io/cve-2020-0787-windows-bits-eop/
https://googleprojectzero.blogspot.com/2018/04/windows-exploitation-tricks-exploiting.html
https://foxglovesecurity.com/2016/09/26/rotten-potato-privilege-escalation-from-service-accounts-to-system/
https://www.sentinelone.com/labs/relaying-potatoes-another-unexpected-privilege-escalation-vulnerability-in-windows-rpc-protocol/
https://troopers.de/downloads/troopers19/TROOPERS19_AD_Abusing_privileged_file_operations.pdf
https://docs.microsoft.com/en-us/windows/security/identity-protection/access-control/local-accounts
https://decoder.cloud/2020/05/04/from-network-service-to-system/