本文结合POC源码,研究Potato家族本地提权细节

Feature or vulnerability

该提权手法的前提是拥有SeImpersonatePrivilegeSeAssignPrimaryTokenPrivilege权限,以下用户拥有SeImpersonatePrivilege权限(而只有更高权限的账户比如SYSTEM才有SeAssignPrimaryTokenPrivilege权限):

  • 本地管理员账户(不包括管理员组普通账户)和本地服务帐户
  • 由SCM启动的服务

P.s. 本机测试时即使在本地策略中授予管理员组普通用户SeImpersonatePrivilege特权,在cmd.exe中whoami /priv也不显示该特权,且无法利用;而SeAssignPrimaryTokenPrivilege特权则可以正常授予普通用户

Windows服务的登录账户

  1. Local System(NT AUTHORITY\System)
    • It has the highest level of permissions on the local system.
    • If the client and the server are both in a domain, then the Local System account uses the PC account (hostname$) to login on the remote computer.
    • If the client or the server is not in a domain, then the Local System account uses ANONYMOUS LOGON.
  2. Network Service(NT AUTHORITY\Network Service)
    • It has permissions as an unpriviledge normal user on the local system.
    • When accessing the network, it behaves the same as the Local System account.
  3. Local Service(NT AUTHORITY\Local Service)
    • It has permissions as an unpriviledge normal user on the local system.
    • It always uses ANONYMOUS LOGON, whether a computer is in a domain or not.

也就是说该提权是

  • Administrator -> SYSTEM
  • Service -> SYSTEM

服务账户在Windows权限模型中本身就拥有很高的权限,所以微软不认为这是一个漏洞

但理论还得结合实际,实际渗透时是很有用的。常见场景下,拿到IIS的WebShell,或通过SQLi执行xp_cmdshell,此时手里的服务账户在进行操作时等同于是个低权限账户,而使用该提权手法可以直接获取SYSTEM权限

SeImpersonate & SeAssignPrimaryToken Privilege

if you have SeAssignPrimaryToken or SeImpersonatePrivilege, you are SYSTEM

Windows的token是描述安全上下文的对象,用户登录系统后就会生成token,创建新进程或新线程时这个token会被不断拷贝

Token成员:

用户账户的(SID)
用户所属的组的SID
用于标识当前登陆会话的登陆SID
用户或用户组所拥有的权限列表
所有者SID
所有者组的SID
访问控制列表
访问令牌的来源
主令牌/模拟令牌
限制SID的可选列表
模拟等级:
       Anonymous: server无法模拟或识别client
       Identification: 可识别client的身份和特权,不能模拟
       Impersonation: 可在本地系统模拟
       Delegation: 可在远程系统上模拟
C:\WINDOWS\system32>whoami /priv

PRIVILEGES INFORMATION
----------------------

Privilege Name                  Description                                 State  
=============================== =========================================== =======
SeAssignPrimaryTokenPrivilege   Replace a process level token               Enabled
SeImpersonatePrivilege          Impersonate a client after authentication   Enabled

CreateProcessWithTokenW签名

WINADVAPI
_Must_inspect_result_ BOOL
WINAPI
CreateProcessWithTokenW(
    _In_        HANDLE hToken,
    _In_        DWORD dwLogonFlags,
    _In_opt_    LPCWSTR lpApplicationName,
    _Inout_opt_ LPWSTR lpCommandLine,
    _In_        DWORD dwCreationFlags,
    _In_opt_    LPVOID lpEnvironment,
    _In_opt_    LPCWSTR lpCurrentDirectory,
    _In_        LPSTARTUPINFOW lpStartupInfo,
    _Out_       LPPROCESS_INFORMATION lpProcessInformation
      );

当用户具有SeImpersonatePrivilege特权,则可以调用CreateProcessWithTokenW以某个Token的权限启动新进程

CreateProcessAsUserW签名

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

当用户具有SeAssignPrimaryTokenPrivilege特权,则可以调用CreateProcessAsUserW以hToken权限启动新进程

为什么会有一系列Impersonate函数,微软本意是让高权限服务端可以模拟低权限客户端来执行操作以提高安全性,但被攻击者反向使用了

How to get a high-privilege token

Potato家族使用了一系列的手段

Origin Potato

repo: https://github.com/foxglovesec/Potato

最初的Potato是WPAD或LLMNR/NBNS投毒(细节部分还需要使用DNS exhaust的手段来使DNS解析失败从而走广播LLMNR/NBNS),让某些高权限系统服务请求自己监听的端口,并要求NTLM认证,然后relay到本地的SMB listener

这个提权其实跟今天要讲的关系不大,因为它本质是个跨协议(HTTP -> SMB)的reflection NTLM relay

一方面relay攻击对有SMB签名的系统无效,且之后微软通过在lsass中缓存来缓解relay回自身的攻击

RottenPotato & JuicyPotato

repo: https://github.com/ohpe/juicy-potato

这两种不同于初始的Potato,它是通过DCOM call来使服务向攻击者监听的端口发起连接并进行NTLM认证

Rotten Potato和Juicy Potato几乎是同样的原理,后者在前者的基础上完善,所以后文细节部分就以JuicyPotato来讲

When a DCOM object is passed to an out of process COM server the object reference is marshalled in an OBJREF stream. For marshal-by-reference this results in an OBJREF_STANDARD stream being generated which provides enough information to the server to locate the original object and bind to it. Along with the identity for the object is a list of RPC binding strings (containing a TowerId and a string). This can be abused to connect to an arbitrary TCP port when an unmarshal occurs by specifying the tower as NCACN_IP_TCP and a string in the form “host[port]”. When the object resolver tries to bind the RPC port it will make a TCP connection to the specified address and if needed will try and do authentication based on the security bindings.

If we specify the NTLM authentication service in the bindings then the authentication will use basic NTLM. We just need to get a privileged COM service to unmarshal the object, we could do this on a per-service basis by finding an appropriate DCOM call which takes an object, however we can do it generically by abusing the activation service which takes a marshalled IStorage object and do it against any system service (such as BITS).

从project-zero扒来的图

JuicyPotato通过传递BITS的CLSID和IStorage对象实例给CoGetInstanceFromIStorage函数,使rpcss激活BITS服务,随后rpcss的DCOM OXID resolver会解析序列化数据中的OBJREF拿到DUALSTRINGARRAY字段,该字段指定了host[port]格式的location,绑定对象时会向其中的host[port]发起DCE/RPC(Distributed Computing Environment)请求。这个host[port]由攻击者监听的,如果攻击者要求NTLM身份验证,高权限服务就会发送net-NTLM进行认证

看到这里是不是觉得好像和Potato差不多,依然是NTLM relay的套路,只是换了种让高权限服务请求我们的方式。其实JuicyPotato后面的操作也不同,它拿到net-NTLM后会通过SSPI的AcceptSecurityContext函数进行本地NTLM协商,最终拿到一个高权限的impersonation级别token,然后通过CreateProcessWithTokenW来启动新进程

另外,我们知道NTLM是个嵌入协议,DCOM调用发送的是DCE/RPC协议。我们只需要处理内部的NTLM SSP部分,所以该POC在本地NTLM协商的同时还会同时relay到本机的RPC 135端口来获取当前系统合法的RPC报文,后面的过程就只需要替换RPC报文中的NTLM SSP部分即可

DCOM协议文档:https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-dcom/4a893f3d-bd29-48cd-9f43-d9777a4415b0

PrintSpoofer (or PipePotato or BadPotato)

你问我它为啥有三个名字?最初公开POC的老外叫它PrintSpoofer,之后360的paper叫它PipePotato,然后GitHub一个国人的POC又叫它BadPotato。尊重第一个公开POC的作者,后文叫它PrintSpoofer

该POC是2020.5公开的,它是通过Windows named pipe的一个API: ImpersonateNamedPipeClient来模拟高权限客户端的token(还有类似的ImpersonatedLoggedOnUserRpcImpersonateClient函数),调用该函数后会更改当前线程的安全上下文(其实已经不是什么新技术了)

The ImpersonateNamedPipeClient function allows the server end of a named pipe to impersonate the client end. When this function is called, the named-pipe file system changes the thread of the calling process to start impersonating the security context of the last message read from the pipe. Only the server end of the pipe can call this function.

这个POC有趣的地方在于,它利用了打印机组件路径检查的BUG,使SYSTEM权限服务能连接到攻击者创建的named pipe

spoolsv.exe服务有一个公开的RPC服务,里面有以下函数

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)

pszLocalMachine参数需要传递UNC路径,传递\\127.0.0.1时,服务器会访问\\127.0.0.1\pipe\spoolss,但这个管道已经被系统注册了,如果我们传递\\127.0.0.1\pipe则因为路径检查而报错

但当传递\\127.0.0.1/pipe/foo时,校验路径时会认为127.0.0.1/pipe/foo是主机名,随后在连接named pipe时会对参数做标准化,将/转化为\,于是就会连接\\127.0.0.1\pipe\foo\pipe\spoolss,攻击者就可以注册这个named pipe从而窃取client的token


这个POC启动新进程是使用CreateProcessAsUser而不是CreateProcessWithToken

作者使用AsUser而不是WithToken的原因和我猜的一样,用CreateProcessAsUserW是为了能在当前console执行,做到interactive。我测试时就发现了传递给CreateProcessWithTokenlpEnvironment参数似乎被忽略了,永远会启动新console,作者在issue里说这是个bug

只有前面调用ImpersonateNamedPipeClient时需要SeImpersonatePrivilege特权,调用成功线程切换到SYSTEM安全上下文,此时调用CreateProcessAsUserW时caller和authticator是相同的,就不需要SeAssignPrimaryTokenPrivilege权限

理论是这样,但实际上我在Windows Server 2012 r2的DC上测试时,域内LocalSystem登录账户是hostname$,该账户没有SeAssignPrimaryTokenPrivilege,EXP返回1314 error(提给作者的issue: https://github.com/itm4n/PrintSpoofer/issues/1)

之后作者增加了一层check,当CreateProcessAsUser失败后会fallback回CreateProcessWithToken,不过CreateProcessWithToken无法做到interactive

RoguePotato

repo: https://github.com/antonioCoco/RoguePotato

这个也是利用了命名管道

微软修补后,高版本Windows DCOM解析器不允许OBJREF中的DUALSTRINGARRAY字段指定端口号。为了绕过这个限制并能做本地令牌协商,作者在一台远程主机上的135端口做流量转发,将其转回受害者本机端口,并写了一个恶意RPC OXID解析器

RPC支持的协议

https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rpce/472083a9-56f1-4d81-a208-d18aef68c101

RPC transport RPC protocol sequence string
SMB ncacn_np (see section 2.1.1.2)
TCP/IP (both IPv4 and IPv6) ncacn_ip_tcp (see section 2.1.1.1)
UDP ncadg_ip_udp (see section 2.1.2.1)
SPX ncacn_spx (see section 2.1.1.3)
IPX ncadg_ipx (see section 2.1.2.2)
NetBIOS over IPX ncacn_nb_ipx (see section 2.1.1.4)
NetBIOS over TCP ncacn_nb_tcp (see section 2.1.1.5)
NetBIOS over NetBEUI ncacn_nb_nb (see section 2.1.1.6)
AppleTalk ncacn_at_dsp (see section 2.1.1.7)
RPC over HTTP ncacn_http (see section 2.1.1.8)

但作者实践过程中发现ncacn_ip_tcp返回的是识别令牌,之后受到PrintSpoofer启发,使用ncacn_np:localhost/pipe/roguepotato[\pipe\epmapper]让RPCSS连接

不出网的情况下就只能在内网打下一台,相比之下有些局限

作者paper:https://decoder.cloud/2020/05/11/no-more-juicypotato-old-story-welcome-roguepotato/

SweetPotato

repo: https://github.com/CCob/SweetPotato

COM/WinRM/Spoolsv的集合版,也就是Juicy/PrintSpoofer

WinRM的方法是作者文章https://decoder.cloud/2019/12/06/we-thought-they-were-potatoes-but-they-were-beans/中提到的,当WinRM在当前系统未启用时,攻击者监听本机5985端口,BITS服务会向WinRM 5985发起NTLM认证

Details

我希望掌握POC的细节,所以会结合代码分析PrintSpooferJuicyPotato内部的:

  • CreateProcessWithTokenW & CreateProcessAsUserW
  • Named pipe ImpersonateNamedPipeClient
  • 触发DCOM call -- CoGetInstanceFromIStorage
  • SSPI本地NTLM协商 -- AcceptSecurityContext

为了突出重点,后面代码会删除错误处理,参数处理等,只保留骨干

PrintSpoofer

点击收藏 | 1 关注 | 3
  • 动动手指,沙发就是你的了!
登录 后跟帖