SIGRed: vulnerabilidad crítica en el servidor DNS de Windows (CVE-2020-1350)

Los investigadores de Checkpoint se propusieron encontrar una vulnerabilidad mediante la cual comprometer un dominio de Windows, preferiblemente sin necesidad previa de autenticación, y siguieron la senda de la explotación de protocolos como ya pasó con Eternalblue con RDP o BlueKeep con RDP. En este caso se centraron en el servidor DNS presente en la mayoría de los controladores de dominio y el resultado fue el descubrimiento de un desbordamiento de enteros o integer overflow (que deriva en un Heap-Based Buffer Overflow) a la hora de parsear peticiones de tipo Signature (RR SIG records): hablamos de la vulnerabilidad CVE-2020-1350 llamada también SIGRed con un CVSS de 10.0 y que afecta a las versiones de Windows Server 2003 a 2019.

Unas pocas premisas

Partamos de unas pocas premisas antes de empezar a ver la descripción de la vulnerabilidad:
  • DNS opera en el puerto 53 UDP o TCP
  • un solo mensaje DNS (respuesta/petición) está limitado a 512 bytes en UDP y 65,535 bytes en TCP.
  • DNS es jerárquico y descentralizado por naturaleza. Esto significa que cuando un servidor DNS no conoce la respuesta a una consulta que recibe, la consulta se reenvía a un servidor DNS que se encuentra sobre él en la jerarquía
  • En Windows, el cliente DNS y el servidor DNS se implementan en dos módulos diferentes:
    •  Cliente DNS: dnsapi.dll es responsable de la resolución de DNS.
    •  Servidor DNS: dns.exe es responsable de responder las consultas DNS en Windows Server, en el que está instalada la funcionalidad DNS.

Descripción de la vulnerabilidad

La vulnerabilidad se encuentra en el servidor DNS/dns.exe:
1. dns.exe implementa una función de parseo para cada tipo de respuesta soportada
2. Uno de los tipos de respuesta admitidos es para una consulta SIG. La función del handler para el tipo de respuesta SIG es dns.exe!SigWireRead
3. La función responsable de asignar memoria para el RR (Resource Record) RR_AllocateEx espera que sus parámetros se pasen en registros de 16 bits, ya que solo usa la parte dx de rdx y la parte cx de rcx.
4. Si podemos hacer que se muestre un resultado mayor de 65.535 bytes (el valor máximo para un entero de 16 bits) se produce un desbordamiento y esta dirección de memoria asignada se pasa como un búfer de destino para memcpy, lo que lleva a un heap buffer overflow.

Provocando la vulnerabilidad

Como hemos dicho al principio DNS sobre UDP tiene un límite de tamaño de 512 bytes (o 4,096 bytes si el servidor admite Mecanismos de extensión para DNS0 o EDNS0). En cualquier caso, eso no es suficiente para desencadenar la vulnerabilidad.
Pero, ¿qué sucede si por algún motivo un servidor envía una respuesta superior a 4.096 bytes? Por ejemplo, una respuesta larga TXT o un nombre de host que se puede resolver en varias direcciones IP.

De acuerdo con el DNS RFC 5966:
"En ausencia de EDNS0, el comportamiento normal de cualquier servidor DNS que necesite enviar una respuesta UDP que exceda el límite de 512 bytes es que el servidor trunque la respuesta para que se ajuste dentro de ese límite y luego establezca el indicador TC en el encabezado de respuesta. Cuando el cliente recibe dicha respuesta, toma el flag TC como una indicación de que debería reintentar a través de TCP."

Por lo tanto, podemos establecer el flag TC (truncamiento) en nuestra respuesta, lo que hace que el servidor DNS de Windows de destino inicie una nueva conexión TCP a nuestro servidor de nombres malicioso, y podemos pasar un mensaje de más de 4,096 bytes. ¿Pero cuanto más grande?

Como los dos primeros bytes del mensaje representan su longitud, el tamaño máximo de un mensaje en DNS sobre TCP se representa como 16 bits y, por lo tanto, está limitado a 64 KB.


Pero incluso un mensaje de longitud 65.535 no es lo suficientemente grande como para desencadenar la vulnerabilidad, ya que la longitud del mensaje incluye los encabezados y la consulta original. Esta sobrecarga no se tiene en cuenta al calcular el tamaño que se pasa a RR_AllocateEx.

Por otro lado, para exprimir tanta información como sea posible en los 512 bytes, los nombres DNS pueden (y a menudo DEBEN) comprimirse ... En este caso, el nombre DNS de la respuesta se codifica como 0xc0 0x0c. La parte c0 tiene los dos bits más significativos establecidos, lo que indica que los siguientes 6 + 8 bits son un puntero a algún lugar anterior en el mensaje. En este caso, esto apunta a la posición 12 (= 0x0c) dentro del paquete, que está inmediatamente después del encabezado DNS.

¿Qué hay en el offset 0x0c(12) en el comienzo del paquete? El nombre o FQDN.


En conclusión podemos usar el byte "mágico" 0xc0 para referenciar cadenas desde dentro del paquete.

Veamos la fórmula que calcula el tamaño que se pasa a RR_AllocateEx:

[Name_PacketNameToCountNameEx result] + [0x14] + [The Signature field’s length (rdi–rax)]

El propósito de Name_PacketNameToCountNameEx es calcular el tamaño de un campo de nombre, teniendo en cuenta la compresión del puntero. Tener una primitiva que nos permita aumentar el tamaño de la asignación en una gran cantidad, cuando solo la representamos con dos bytes, es exactamente lo que necesitamos.

Podemos usar la compresión del puntero en el campo Signer's Name. Sin embargo, simplemente especificando 0xc00c como el Signer's name no causaría el desbordamiento, ya que el nombre de dominio consultado ya está presente en la consulta, y el tamaño de la sobrecarga se resta del valor asignado. ¿Pero qué hay de 0xc00d? La única restricción que tenemos que cumplir es que nuestra cadena encodeada es válida (que termina con 0x0000), y podemos hacerlo fácilmente porque tenemos un campo sin restricciones de caracteres: el valor de signature.

Por ejemplo para el dominio 41414141.fun, 0xc00d apunta al primer carácter del dominio ("4"). El valor ordinal de este carácter se usa como el tamaño de la cadena sin comprimir ("4" representa el valor 0x34 (52)). La agregación del tamaño de esta cadena sin comprimir, con la cantidad máxima de datos que podemos ajustar en el campo Firma (hasta 65,535, dependiendo de la consulta original), da como resultado un valor superior a 65,535 bytes, lo que provoca el desbordamiento.

Explotando la vulnerabilidad desde el navegador

DNS se puede transportar a través de TCP y el servidor DNS de Windows admite este tipo de conexión. Podemos abusar de las funciones de "Connection Reuse" y "Pipelining" enviando una petición HTTP POST al servidor DNS de destino (https://target-dns:53/) con el binary data, que contiene otra consulta DNS "smuggled" en los datos POST, que se consultarán por separado.

El payload HTTP consiste en lo siguiente:

- Cabeceras de peticiones HTTP que no controlamos (User-Agent, Referer, etc.).
- "Relleno" para que la primera consulta DNS tenga una longitud adecuada (0x504f) dentro de los datos POST.
- Nuestra consulta DNS "smuggled" dentro de los datos POST.


Llevando la explotación a la práctica

El binario dns.exe se compila con Control Flow Guard (CFG), lo que significa que el enfoque tradicional de sobrescribir un puntero de función en la memoria no es suficiente para explotar este error. Para tener éxito necesitamos encontrar primitivas que nos brinden las siguientes capacidades: write-what-where (para sobrescribir con precisión una dirección de retorno en la pila) e infoleak (para leakear direcciones de memoria, como la pila).

Contramedidas

Se recomienda encarecidamente parchear los servidores DNS de Windows afectados para evitar la explotación de esta vulnerabilidad:

https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2020-1350

Según Shodan hay más de 2,000 servidores DNS Windows con el puerto 53 abierto en Internet. Rapid7 con su proyecto Sonar han identificado más de 41,000...


En cualquier caso hay que tener en cuenta que CVE-2020-1350 no requiere una conexión directa al puerto 53. Si se suplanta/realiza un phishing sobre alguien, basta un clic y el servidor DNS interno recibirá la petición maliciosa.

Como solución temporal, hasta que se aplique el parche, se sugiere establecer la longitud máxima de un mensaje DNS (sobre TCP) en 0xFF00, lo que debería eliminar la vulnerabilidad. Se puede hacer ejecutando los siguientes comandos:

reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\DNS\Parameters" /v "TcpReceivePacketSize" /t REG_DWORD /d 0xFF00 /f
net stop DNS && net start DNS


Fuente: https://research.checkpoint.com/2020/resolving-your-way-into-domain-admin-exploiting-a-17-year-old-bug-in-windows-dns-servers/

1 comentarios :