Una explicación sencilla sobre SAD DNS y por qué es un desastre (o una bendición)

Sergio De Los Santos    16 noviembre, 2020
Una explicación sencilla sobre SAD DNS y por qué es un desastre (o una bendición)

En 2008, Kaminsky sacudió los cimientos de internet. Un fallo de diseño en DNS permitía falsear respuestas y enviar a una víctima a donde el atacante quisiera. 12 años después, se ha encontrado una fórmula similar muy interesante para poder envenenar la caché de los servidores DNS. Peor, si cabe, que la de Kaminsky. Fundamentalmente porque no necesita estar en la misma red de la víctima y porque ha sido anunciada cuando todavía muchos servidores, sistemas operativos y programas no están parcheados. Veamos cómo funciona de forma comprensible.

Para poder falsear una petición DNS y devolverle al cliente una mentira, el atacante debe saber el TxID (transaction ID) y el puerto origen UDP. Esto supone una entropía de 32 bits (adivinar dos campos de 16 bits). SAD DNS consiste (básicamente, porque el paper es muy complejo) en inferir el puerto UDP a través de un ingenioso método que se vale de los mensajes de vuelta de error ICMP. Si se deduce el puerto, esto deja de nuevo una entropía de solo el TxID de 16 bits, asumible para un ataque. Una vez se tengan estos dos datos, se construye el paquete y se bombardea al servidor de nombres.

¿Cómo inferir el puerto UDP abierto?

Los preliminares necesarios son que por el funcionamiento de UDP, el servidor abre unos puertos UDP de respuesta por los que se comunica con otros servidores de nombres. Conocer esos puertos efímeros que abre en sus comunicaciones es vital porque junto con el TxID suponen todo lo que necesita un atacante para falsear la respuesta. O sea, si un servidor (resolver o forwarder) hace una pregunta a otro servidor, espera un TxID y UDP concretos en su respuesta. Y sea quien sea quien le devuelva un paquete con esos datos, lo tomará como una verdad absoluta. Se le podrá engañar con una resolución de dominio-IP falsa. Solo hace falta que el atacante conozca en este caso el puerto UDP abierto, deduzca el TxID por fuerza bruta y bombardee a su vítima.

Cuando contactas con un puerto UDP y le preguntas si está abierto o no, los servidores devuelven un mensaje de “puerto cerrado” en ICMP. Para no saturar con respuestas, tienen un límite global de 1000 por segundo. Un límite global significa que da igual si le preguntan 100 o 10 servidores a la vez, para todos tiene 1000 respuestas en un segundo ante preguntas de puerto abierto, por ejemplo. Esto, que se hizo para no saturar el sistema, es lo que en realidad causa todo este problema.

El límite global es 1000 en Linux, 200 en Windows y FreeBSD y 250 en MacOS. Y en realidad, todo el paper se basa en “denunciar” esta fórmula de límite global fijo. Debe ser revisado porque ya se ha alertado con anterioridad de los peligros de esto, pero nunca con un ataque y aplicación tan prácticas. También importante porque no solo el DNS, sino que QUIC y HTTP/3, basados en UDP, pueden ser vulnerables. El ataque es complejo y en cada paso existen detalles que mitigar, pero fundamentalmente los pasos básicos son (con potenciales inexactitudes en aras de la simplicidad):

  • Enviar 1000 sondas UDP al resolvedor víctima con IPs de origen falseadas probando 1000 puertos. En realidad son lotes de 50 cada 20 ms para superar otro límite de respuestas por IP que tiene el sistema operativo Linux.
  • Si los 1000 puertos están cerrados, la víctima devolverá (a las IPs falseadas) 1000 paquetes ICMP de error indicando que no está abierto el puerto. Si está abierto, no pasa nada, lo descarta la aplicación correspondiente en ese puerto. No importa que el atacante no vea los ICMP respuesta (llegan a las IPs falseada). Lo que importa es ver cuánto del límite global de 1000 respuestas por segundo “se gasta” en esa tanda.
  •  Antes de dejar pasar ese segundo, el atacante consulta un puerto UDP cualquiera que sepa cerrado y si el servidor le devuelve un ICMP de “puerto cerrado”… es que no se había gastado los 1000 ICPM de “error puerto cerrado” y por tanto… ¡en ese rango de 1000 había al menos un puerto abierto! Bingo. Como el límite de respuestas ICMP es global, una sola respuesta de puerto cerrado significa que no se gastó el límite de 1000 repuestas de “puerto cerrado” por segundo. Alguno había abierto. Esta consulta la hace desde su IP real, no falseada, para recibir de verdad (o no) la respuesta.

Así, en tandas de 1000 consultas por segundo y comprobando si gasta o no el límite de paquetes de error puerto cerrado, el atacante puede ir deduciendo qué puertos están abiertos. En poco tiempo tendrá mapeados los puertos abiertos del servidor. Obviamente el atacante lo combina con búsquedas “inteligentes” binarias para optimizar, dividiendo los rangos de puertos “potencialmente abiertos” en cada tanda para ir más rápido y dar así con el dato concreto.

Los investigadores también tuvieron que eliminar el “ruido” de otros puertos abiertos o escaneos que se estén haciendo al sistema mientras lo realiza el atacante, y en el paper aclaran algunas fórmulas para conseguirlo.

Más fallos y problemas

Todo viene de una tormenta perfecta de errores en implementación UDP, en la implementación del límite de 1000 respuestas… La explicación de arriba es “simplista” porque los investigadores detectaron otros problemas de implementación que a veces incluso les beneficiaban y otras consistían en ligeras variaciones del ataque.

Porque el fallo no está solo en la implementación del límite global ICMP. Tampoco se libra la implementación de UDP en sí misma. Según el RFC, en un solo socket UDP, las aplicaciones pueden recibir en un mismo socket conexiones de diferentes IPs fuente. Las comprobaciones de a quién se le da qué, lo dejan en el RFC en manos de la aplicación que maneje la conexión entrante. Esto, que se supone que aplicaría a los servidores (son sockets que reciben), también aplica a los clientes. Así según los experimentos en el paper también aplica al cliente UDP que abre puertos para hacer consultas y, por tanto, facilita mucho el ataque, que permite escanear puertos “abiertos” para consultas con cualquier dirección IP de fuente.

Y algo muy importante: ¿qué pasa si en la implementación de UDP la aplicación marca como “privado” un puerto UDP de respuesta de forma que solo el que inicia la conexión puede conectarse a él (y otros no puedan ver si está abierto o cerrado)? Esto supondría un problema para el primer paso del ataque en el que se falsifican las IPs de origen y agiliza el proceso. Abrir puertos “públicos” o “privados” depende del servidor DNS. Y solo BIND lo hace bien. Dnsmask, Unbound, no. En estos casos no se pueden falsificar las IP de las rachas (las que se usan para gastar el límite global y que te da igual recibir o no) sino que solo se puede falsificar las rachas con una sola IP origen. Esto haría el escaneo más lento. Pero no hay problema. Si no es así y los puertos son privados, también hay un fallo en Linux para “mitigarlo”. La comprobación del “límite global” se realiza antes de la cuenta del límite por IP. Esto, que en principio se hizo así porque comprobar el límite global es más rápido que el límite por IP, permite en realidad que no se tarde tanto y la técnica siga siendo válida aun con los puertos privados.

El paper sigue con recomendaciones para forwardes, resolvers… todo un repaso profundo a la seguridad DNS.

¿Soluciones?

Linux ya tiene preparado un parche, pero hay mucha tela de cortar. Desde el DNSSEC, que siempre se recomienda pero nunca termina de despegar, hasta desactivar las respuestas de ICMP… cosa que puede ser compleja. El parche del Kernel hará que ahora no haya un valor fijo de 1000 respuestas por segundo, sino aleatoria de entre 500 y 2000. El atacante así no podrá hacer sus cábalas correctamente para saber si en un segundo se ha gastado ese límite y deducir puertos UDP abiertos.

Parece que el origen absoluto del problema es de implementación, no el diseño. En este RFC se describe ese límite de ratio de respuesta, y se deja abierto a un número. Elegirlo fijo y en 1000 como se hizo en el Kernel en 2014 es parte del problema.

Por cierto, con este script de BlueCatLabs programado cada minuto, se podrá mitigar el problema en un servidor DNS haciendo a mano lo que hará el parche para SAD DNS.

Esperemos entonces parches para todos: los sistemas operativos y los principales servidores DNS. Muchos servidores públicos ya están parcheados pero muchos más no. Resulta particularmente interesante en este ataque que es muy limpio para el atacante, no necesita estar en la red de la víctima. Puede hacerlo todo desde fuera y confundir a los servidores. Un desastre. O una bendición. Porque de esta, se arreglarán bastantes “cabos sueltos” en el protocolo UDP y DNS.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *