This page looks best with JavaScript enabled

CVE-2020-1337: mis dos centavos

 ·  ✍️ neofito

En enero de este mismo año, Peleg Hadar (@peleghd) y Tomer Bar reportaron a Microsoft, sin liberar públicamente los detalles, una vulnerabilidad en el servicio de impresión de Windows que permitiría la elevación de privilegios en los sistemas Windows 7 en adelante. Los correspondientes parches fueron liberados por Microsoft el 12 de mayo y el CVE asignado a la vulnerabilidad fue el (CVE-2020-1048).

El mismo día en que fueron liberadas las actualizaciones Alex Ionescu (@aionescu) y Yarden Shafir (@yarden_shafir), los cuales también encontraron dicha vulnerabilidad, publicaron los detalles de su investigación en el artículo PrintDemon: Print Spooler Privilege Escalation, Persistence & Stealth (CVE-2020-1048 & more). Aprovechando este conocimiento y después de algunas pruebas logré encontrar un bypass para el parche de Microsoft utilizando junction points y symlinks (todo el mérito para James Forshaw @tiraniddo).

Mi reporte, con fecha 19 de mayo de 2020, fué marcado por Microsoft como duplicado dado que la causa raíz al parecer ya había sido reportada previamente. A la vulnerabilidad se le asignó el CVE-2020-1337, el cual comparto con otros investigadores.

En este artículo trataré brevemente de detallar el proceso de explotación de la vulnerabilidad CVE-2020-1048 y el bypass que logré encontrar, incluyendo referencias de interés de obligada lectura.

CVE-2020-1048

Como ya he comentado, el 12 de mayo de este mismo año Microsoft publicó las actualizaciones de seguridad para el CVE-2020-1048, una vulnerabilidad que permitiría una escalada de privilegios aprovechando el servicio de impresión de Windows:

An elevation of privilege vulnerability exists when the Windows Print Spooler service improperly allows arbitrary writing to the file system. An attacker who successfully exploited this vulnerability could run arbitrary code with elevated system privileges. An attacker could then install programs; view, change, or delete data; or create new accounts with full user rights.

To exploit this vulnerability, an attacker would have to log on to an affected system and run a specially crafted script or application.

The update addresses the vulnerability by correcting how the Windows Print Spooler Component writes to the file system.

Yarden Shafir y Alex Ionescu publicaron su artículo PrintDemon: Print Spooler Privilege Escalation, Persistence & Stealth (CVE-2020-1048 & more) detallando el origen de la vulnerabilidad y dando algunas pistas para su explotación sin liberar una PoC totalmente funcional, al menos no utilizándola “as is”.

El proceso de explotación pasa por crear un puerto local de impresora un tanto especial para, posteriormente, crear una impresora virtual asignándole dicho puerto y utilizando un driver genérico. Lo especial del puerto local asignado a la impresora consiste en hacer que apunte a un fichero en un path interesante, por ejemplo C:\Windows\system32\evil.dll, dado que durante el proceso de creación del puerto Windows no comprueba si el usuario tiene permisos de escritura en dicha ruta. Si posteriormente desde el contexto de un usuario estándar lanzamos una tarea de impresión utilizando la impresora virtual, la tarea devolverá un error al intentar crear el fichero dado que no tenemos los permisos necesarios para imprimir en dicha ubicación; el servicio de impresión ejecuta el proceso impersonando al usuario que lanzó la tarea, así que hasta aquí todo normal.

Nótese que para apuntar el puerto local a un fichero en C:\Windows\system32 y posteriormente poder crear la impresora virtual con este puerto y un driver genérico deberemos utilizar PowerShell o hacerlo mediante código, ya que si intentamos utilizar el asistente de creación de impresoras el proceso nos devolverá un error de Acceso denegado al intentar crear el puerto, dado que en este caso si se comprueban correctamente los permisos incluso antes de “crear” realmente el fichero.

Acceso denegado al crear un puerto local

El artículo, cuya lectura es absolutamente recomendada, detalla la causa de esta discordancia además de desmenuzar el proceso llevado a cabo por el servicio de impresión de Windows al lanzar una tarea de impresión. Durante este proceso se generan en el directorio C:\Windows\system32\spool\PRINTERS los siguientes ficheros:

  • FPnnnnn.SPL ó nnnnn.SPL que básicamente contiene lo que será impreso.
  • FPnnnnn.SHD ó nnnnn.SHD que contiene los metadatos que definen la tarea de impresión.

No ahondaré en la estructura de el fichero SHD, ya que conocerla no resulta imprescindible para entender el procedimiento de explotación llevado a cabo (consultar los siguientes repositorios). Como resumen, cuando se inicia el servicio de impresión de Windows éste comprueba si existe algún fichero shadow (.SHD) en el directorio del spooler (normalmente C:\Windows\system32\spool\PRINTERS) analizando su contenido y lanzando la impresión del fichero de spool (.SPL) asociado y, lo que es más interesante, ejecutando el proceso directamente como SYSTEM sin impersonar al usuario que lanzó inicialmente la tarea. Ahora sí, con todas estas piezas, ya podemos montar una prueba de concepto funcional.

Utilizando Visual Studio para abrir el proyecto una vez descargado desde el repositorio, el primer paso será modificar en printserver\pserver.c el fichero destino utilizado como puerto local de la impresora:

1
    LPWSTR g_PortName = L"c:\\windows\\system32\\evil.dll";

Modificaremos también la declaración de las variables hPrinter y hMonitor asignándoles un valor por defecto igual a NULL:

1
2
    HANDLE hPrinter = NULL;
    HANDLE hMonitor = NULL;

Posteriormente agregaremos el fichero de cabecera strsafe.h y el siguiente fragmento código, adaptado a partir de una respuesta en stackoverflow, y que se encargará de copiar el contenido de la DLL maliciosa en una cadena de texto y almacenarla en memoria:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
    FILE* f;
    wchar_t wDirPath[MAX_PATH];
    DWORD dRet;

    dRet = GetCurrentDirectory(MAX_PATH, wDirPath);
    if (dRet == 0)
    {
        printf("GetCurrentDirectory failed (%d)\n", GetLastError());
        goto CleanupPath;
    }
    wcscat_s(wDirPath, sizeof(wDirPath), L"\\evil.dll");

    _wfopen_s(&f, wDirPath, L"rb");
    fseek(f, 0, SEEK_END);
    long fsize = ftell(f);
    fseek(f, 0, SEEK_SET);

    char* string = malloc(fsize + 1);
    fread(string, 1, fsize, f);
    fclose(f);

    printf("[.] Reading evil.dll: %d bytes\n", fsize);

Por último indicar que será necesario tener instalado el Windows Driver Kit para que el proyecto compile correctamente, ya que depende de la librería ntdllp.lib.

Ahora, en un sistema Windows 10, sin la actualización correspondiente al CVE-2020-1048 instalada, utilizaremos el binario printserver.exe generado colocando en el mismo directorio un fichero de nombre evil.dll desde el contexto de un usuario estándar:

Ejecución del binario printserver modificado

La ejecución del binario se quedará esperando nuestra interacción y así lo dejaremos. Si ahora observamos las impresoras disponibles en el sistema advertiremos la existencia de un nuevo dispositivo, PrintDemon, que tiene un documento pausado en la cola:

Impresora PrintDemon con un documento pausado

Necesitaremos reiniciar el servicio de impresión (spooler), y dado que estamos utilizando un usuario estándar y no contamos con los permisos suficientes reiniciaremos el sistema para lograrlo. Una vez arrancado accederemos a la cola de la impresora maliciosa y reanudaremos las tareas de impresión:

Reanudaremos el documento pausado

Como resultado habremos conseguido plantar nuestro fichero en un directorio privilegiado:

Fichero malicioso en system32

Podemos utilizar esta vulnerabilidad junto con la DLL maliciosa desarrollada también por Yarden Shafir y Alex Ionescu en su artículo Faxing Your Way to SYSTEM — Part Two, publicado el 30 de abril, para realizar un ataque de tipo DLL hijacking y escalar privilegios en el sistema. No es el objetivo del presente artículo detallar esa parte del ataque pero puede consultarse el siguiente hilo en twitter para hacerse una idea del proceso completo a seguir.

Unos días despues de la publicación del código de PrintDemon, concretamente el 15 de mayo, BC Security (@BCSecurity1), conocido por ser el encargado del mantenimiento del conocido framework Empire, liberó una versión del exploit desarrollada en PowerShell. Seguramente sea mi culpa pero lo cierto es que no he sido capaz de hacerlo funcionar correctamente.

Por otra parte, el pasado 5 de agosto Peleg Hadar (@peleghd) y Tomer Bar liberaron los detalles de su investigación acerca del CVE-2020-1048 en sendas charlas en la Black Hat y DEF CON (video, slides y whitepaper) de este año. El exploit, junto con material adicional, puede encontrarse en su repositorio de github.

Mi bypass para el CVE-2020-1048

Primero eliminaremos la impresora e instalaremos el parche correspondiente iniciando nuevamente el proceso de creación de la impresora utilizando ahora PowerShell dado que nos permitirá obtener un resultado mas esclarecedor:

Error al agregar el puerto para la impresora

Tal como puede apreciarse en la captura obtenemos un error de acceso denegado al intentar crear el puerto para la impresora. Las modificaciones introducidas por la actualización de seguridad realizan correctamente una comprobación de permisos en el directorio indicado como destino, deteniendo el proceso en este punto; pero si le damos una vuelta, seguro que se nos ocurre algún bypass…

Durante el proceso de creación del puerto local de la impresora Windows comprueba si el usuario tiene permisos de escritura en el directorio indicado así que tendremos que utilizar algún truco para eludir esta comprobación. En este caso utilizaremos los siguientes comandos de PowerShell para probar nuestra idea.

En primer lugar agregaremos un driver genérico y crearemos un directorio que posteriormente utilizaremos como destino para la impresión:

Agregamos un driver y creamos un directorio destino

Seguidamente crearemos un puerto local apuntando a un fichero en ese directorio y confirmaremos que se haya creado correctamente:

Agregamos un puerto para la impresora

El proceso se completará correctamente dado que nuestro usuario estándar tiene control total tanto sobre el directorio como sobre su contenido. Por último agregaremos la impresora virtual utilizando el driver genérico y el puerto local recién creado:

Creación de la impresora virtual

En este momento pondremos la impresora virtual en pausa:

Pausando la impresora virtual

Ahora mandaremos a imprimir un documento de prueba de forma que se quedará pausado en la cola de la impresora virtual:

Documento pausado en la cola de la impresora virtual

Tal y como hicimos anteriormente, ahora será necesario reiniciar el servicio de impresión, y dado que estamos utilizando un usuario estándar y no contamos con los permisos suficientes nuevamente reiniciaremos el sistema para lograrlo.

Hasta aquí todo normal pero ahora, ¿qué podemos hacer para conseguir escribir en un directorio privilegiado con la configuración que hemos desplegado hasta el momento? La respuesta viene de la mano de James Forshaw y su suite de herramientas symboliclink-testing-tools.

Sin ahondar demasiado en los detalles (recomiendo consultar la siguiente presentación) lo que haremos será generar un junction point para el directorio donde apunta nuestro puerto local, C:\Users\me\test, para posteriormente mediante otro tipo de enlace simbólico apuntar el fichero dentro de nuestro directorio, C:\Users\me\test\foobar, a C:\windows\system32\evil.dll:

Preparando el entorno utilizando symlinks

A continuación accederemos a la cola de la impresora maliciosa y reanudaremos la impresión, comprobando posteriormente que se ha creado el fichero, C:\windows\system32\evil.dll, aunque con un contenido inservible:

Confirmación de la creación del fichero

Una vez confirmado el bypass y para demostrar el proceso de explotación he desarrollado una PoC en C# utilizando el paquete NtApiDotNet publicado por James Forshaw para el uso mediante programación de enlaces simbólicos y los paquetes IlMerge 3.0.40 y MSBuild.ILMerge.Task para obtener un binario autocontenido. Después de molestar un poco y tras alguna que otra prueba puedo decir que el código es funcional.

El binario, si se indica como primer parámetro el primer paso en la secuencia de explotación, recibe como segundo parámetro la ruta donde se encuentra el fichero malicioso que será plantado en C:\Windows\system32; finalizada su ejecución nos indica que debemos reiniciar el sistema:

BinaryPlanting.exe paso 1

Una vez reiniciado el sistema ejecutaremos el segundo paso, indicando en este caso como segundo parámetro el nombre con el que el contenido del fichero indicado en el primer paso será creado en el directorio C:\Windows\system32:

BinaryPlanting.exe paso 2

Como resultado habremos conseguido copiar una DLL maliciosa en el directorio C:\Windows\system32 lo que nos permitiría llevar a cabo un ataque de DLL hijacking para elevar privilegios en un sistema vulnerable.

Enlaces de interés


hiveminds_es
ESCRITO POR
neofito
Hive Minds