长亭百川云 - 文章详情

主机安全技术剖析- Windows本地提权(应用篇)

crispr

107

2022-03-25

在长亭牧云(CloudWalker)团队,有那么一群研究员,他们孜孜不倦、持续探索,专治攻防场景下各类“疑难杂症”。
2022虎虎生风,长亭科技特开辟「主机安全技术专栏」,分享最实用、最深入的安全研究。

导读

Windows 的提权是在攻防对抗中绕不开的话题,目前长亭牧云(CloudWalker)主机安全管理平台已支持通用性的的 windows 提权检测,更多交流或者投递简历,欢迎联系邮箱:jingyuan.chen@chaitin.com

长亭科技「主机安全技术专栏」开辟3期内容分享Windows 提权。本期是应用篇,从权限等级角度切入,介绍两类Win提权姿势:从常规用户到管理员用户、从服务账户到 SYSTEM,分享近年国内外最为火热和范围最广的提权方式。应用篇没有前后阅读顺序,读者可根据自身需求选择阅读内容。

目录

1. 从常规用户到管理员账户(BypassUAC)

1.1 UAC的逆向分析
1.2 定位可利用的BypassUAC
1.3 UAC是如何进行权限提升的
1.4 ICMLuaUtil方式提权
1.5 利用白名单程序实现BypassUAC

2. 从服务账户到SYSTEM

2.1 HotPotato
2.2 Rotten Potato
2.3 Pipepotato(BadPotato)
2.4 PetitPotam提权

了解Windows 操作系统的本地提权手段原理以及方式,指路【基础概念篇】https://mp.weixin.qq.com/s/GuCCdVWV2KjzTB2Br1T28A

0x01 从常规用户到管理员账户(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组件来分析如何从常规用户到管理员账户进行提权

1.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服务完成了提升权限的处理

1.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白名单

1.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函数以管理员权限启动请求的进程。

1.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:\windows\system32)执行
因此我们要利用系统的可信进程去进行调用,可以选择的有rundll32.exe、explorer.exe等,只需要把创建COM组件的代码以及执行你想执行的命令的代码,放到可信任进程里面去执行,这样就可以BypassUAC。
对应的第一种方式就是生成DLL文件,将实现都放在DLLmain中,调用rundll32.exe或者cmstp.exe来执行该DLL文件,不过考虑到会有文件落地,并且rundll32.exe本身已经被很多AV监控,因此该方法不是首选方法。
第二种方法引出另一个技术,叫MasqueradePEB
进程的信息,包括命令行参数、图像位置、加载的模块等存储在进程控制块中,并且该结构是可以在用户空间访问和修改的,通过修改PEB进程控制块来欺骗系统认为该进程是一个合法进程
注意在为自己的进程修改进程控制块过程中不需要SeDebugPrivilege权限
而UAC在判断系统进程是否可信,判断依据是PEB结构,因此在使用COM组件提权之前我们将进程信息伪装成可信程序,例如c:\windows\explorer.exe等就能够BypassUAC
当调用PEBMasq.MasqueradePEB伪装成explorer.exe后

我们执行该程序发现现在并不会弹出UAC框并且成功获得管理员权限的cmd进程:

1.5 利用白名单程序实现BypassUAC

利用白名单程序的本质实际上是劫持注册表,这种方法主要是通过寻找autoElevated属性为true的程序,修改其注册表\shell\open\command的值,改成我们想要执行的paylaod,在该值中指明的字段会在这类程序运行时自动执行,类似于默认程序打开,当你以后运行该程序时,这个command命令都会自动执行。
这里我们以Windows 10中Fodhelper.exe为例,该程序是一个autoelevate程序
使用ProcessMonitor监控打开该进程后该进程的相关操作:

启动fodhelper.exe时,会在注册表中执行以下检查:
HKCU:\Software\Classes\ms-settings\shell\open\command
HKCU:\Software\Classes\ms-settings\shell\open\command\DelegateExecute
HKCU:\Software\Classes\ms-settings\shell\open\command(default)
由于这些注册表项不存在,用户可以在注册表中创建此结构,以便操纵fodhelper以绕过用户帐户控制 (UAC) 执行具有更高权限的命令。

当再次运行fodhelper后将执行命令并打开提升的 PowerShell 会话:

但是在利用过程中值得注意的是需要确保DelegateExecute项是存在的,否则也无法成功BypassUAC
如何寻找具有autoPriv的白名单
其实这一类程序都满足一定的条件:

  1. 可执行文件必须经过Windows Publisher的数字签名,Windows Publisher是用于对Windows附带的所有代码进行签名的证书(仅由 Microsoft进行签名是不够的,因此Windows未附带的Microsoft软件不包括在内);
  2. 可执行文件必须位于其中一个为数不多的“安全”目录中。安全目录是指标准用户无法修改的目录,并且它们包括 %SystemRoot%\System32(例如,\Windows\System32)及其大多数子目录、%SystemRoot%\Ehome,以及 %ProgramFiles%下的少许目录(其中包括Windows Defender和Windows notepad)。
    这里可以直接使用命令:
    strings.exe -s *.exe | findstr /i "autoElevate"
    来获取manifest文件中申明autoElevate的程序,也可以通过UACME项目中uacinfo.exe来获得相关程序信息

0x02 从服务账户到SYSTEM

之所以需要单独划分为从服务账户到SYSTEM权限,主要是因为在服务账户中通常具有SeImpersonatePrivilege权限,即使是IIS、Sqlserver用户等也同样具有该权限,在渗透测试中从web端或者数据库来打点也是极为常见的,因此如何进行存在SeImpersonatePrivilege权限的提权也是一直研究的热点。
以下用户拥有SeImpersonatePrivilege权限:
• 本地管理员账户(不包括管理员组普通账户)和本地服务帐户
• 由SCM启动的服务
服务账户在Windows权限模型中本身就拥有很高的权限,所以微软不认为这是一个漏洞
讨论到这里,首先需要介绍的就是目前使用最广泛的Potato系列,其实所有的Potato系列本质上都是relay,如果是远程提权则是relay,用于本地也可以称为是reflection

2.1 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 AUTHORITY\SYSTEM”权限运行
最原始的Potato其本质就是一个NTLM Relay,最后附上一张整体的流程图

局限性

Microsoft通过使用已经在进行中的质询来禁止相同协议的NTLM身份验证来修补此问题 (MS16-075)。这意味着从一台主机到其自身的SMB->SMB NTLM中继将不再起作用。MS16-077 WPAD名称解析将不使用NetBIOS 并且在请求 PAC 文件时不发送凭据,因此WAPD Attack已修补。

2.2 Rotten Potato

相比于Hotpotato利用NTLM Relay中继到SMB,最后调用svcctl.CreateServiceW创建指定的服务,Rotten Potato主要利用远程过程调用(RPC)通过CoGetInstanceFromIStorage进行NTLM中继,在本地协商得到NT AUTHORITY\SYSTEM帐户的安全令牌,在模拟安全令牌后调用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 Challenge,因此在这里我们需要将得到的输出结果进行替换,这样才会使得对AcceptSecurityContext的成功调用
当我们将修改后的 NTLM type 2(协商)数据包转发到 COM,其中Challenge/Reserved字段与“AcceptSecurityContext”的输出相匹配,COM组件会向我们发送回NTLM type 3(身份验证)数据包,我们使用它来对AcceptSecurityContext进行最终调用。

最后我们以一张图来总结一下整个Rotten Potato提权的大体流程:

• 1 使用CoGetInstanceFromIStorageAPI 调用欺骗 RPC 对代理进行身份验证。在此调用中指定了代理 IP/端口。
• 2 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 的版本

2.3 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的失效

2.4 PetitPotam提权

从名字上看该方式并不是传统的Potato,但是从利用手法上,笔者仍然认为其本质也可以通过模拟管道客户端进而实现提权,并且PetitPotam更多用于域横向渗透中,同时也能够用于本地提权(LPE)

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。
当指定格式为\IP\C$的时候,lsass.exe服务就会去访问\IP\pipe\srvsrv

有关于该协议的完成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的调用有\pipe\lsarpc和\pipe\efsrpc两种方法,其中
\pipe\lsarpc的服务器接口必须是UUID[c681d488-d850-11d0-8c52-00c04fd90f7e]
\pipe\efsrpc的服务器接口必须是UUID [df1941c5-fe89-4e79-bf10-463657acf44d]
但是通过efsrpc的方式并不支持,也就是说无法以普通用户的身份来调用该接口的方法,而\pipe\lsarpc则可以,因此在构造过程中我们通常使用\pipe\lsarpc接口

结合管道实现提权

类似于printspoofer,前文我们提到当指定格式为\IP\C$的时候,lsass.exe服务就会去访问\IP\pipe\srvsrv,而这个管道我们是无法创建的,因此我们同样需要使用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的方式

0x03 总结

由于Windows本地提权的方式太过繁杂,笔者在这里主要以介绍近年国内外最为火热和范围最广的提权方式,并且尝试从不同角度来探讨提权的多样性,从NTLM Relay、RPC接口方法、管道模拟客户端、BypassUAC、以及利用系统缺陷导致以系统权限实现文件的相关读写来进行提权,如有不当之处还请指正,在下一篇文章中笔者将通过披露最近挖掘的相关提权漏洞来分享漏洞挖掘经历,不过提权基于named pipe,略显鸡肋,在此只是抛砖引玉分享一些思路供大家参考

相关实现:
https://github.com/crisprss/PetitPotam
参考文章:
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

相关推荐
关注或联系我们
添加百川云公众号,移动管理云安全产品
咨询热线:
4000-327-707
百川公众号
百川公众号
百川云客服
百川云客服

Copyright ©2024 北京长亭科技有限公司
icon
京ICP备 2024055124号-2