A tale of a DNS packet (CVE-2016-2776)

Introducción

Desde hace muchos años BIND es el servidor de DNS más usado en Internet, es el sistema estándar de resolución de nombres en plataformas UNIX y es utilizado en 10 de los 13 Servidores Raíz del Sistema de nombres de dominio de Internet. En pocas palabras es una de las principales funciones de la red Internet.

Sobre este contexto no todos los dias se ve una vulnerabilidad (CVE-2016-2776) calificada como ALTA en uno de los servicios mas críticos de internet (https://kb.isc.org/article/AA-01419/0).

Las pruebas realizadas por ISC (Internet Systems Consortium) descubrieron una condición de error crítica al momento de construir una respuesta. Ademas un advisory en la base de conocimiento de ISC reconoce que un atacante puede explotar la vulnerabilidad de forma remota y probablemente por esto marca un scoring alto de gravedad.

Decidimos pasar una cierta cantidad de horas investigando la causa principal de este error con el fin de ver si había algo más que una denegación de servicio.


Identificando las modificaciones

Siguiendo la tradición de tener errores en el software necesario para la supervivencia de la humanidad, salió CVE-2016-2776. Sin detalle alguno del problema en ningún lado, ni cual era el misterioso "Specifically Constructed Request", salimos  a ver que es lo que se había modificado en el repositorio de Bind9.
En el diff del fix, el cambio más interesante se encontraba en dns_message_renderbegin()


Comparando a simple vista podemos intuir que existía un comportamiento indefinido cuando r.length < msg->reserved daba FALSE. Sin embargo al ver la modificación realizada donde r.length - DNS_MESSAGE_HEADERLEN < msg->reserved es TRUE podemos considerar este contexto del programa modificado cuando se daba la siguiente condición:


01  not (r.length < msg->reserved) and (r.length - DNS_MESSAGE_HEADERLEN < msg->reserved)
02  (r.length >= msg->reserved) and (r.length - DNS_MESSAGE_HEADERLEN < msg >reserved)
03  r.length - DNS_MESSAGE_HEADERLEN < msg->reserved <= r.length


Entonces, veamos qué podemos hacer para que eso ocurra. Si miramos dns_message_renderbegin() notamos que r.length describe el espacio disponible en isc_buffer_t buffer, que es donde la respuesta del server sera escrita. Esto se calcula como buffer->length - buffer->used. De acuerdo a cómo construyamos la query, podemos asegurar que r.length sea un valor conocido dado que va a ser igual al tamaño máximo que la respuesta puede tener y no metimos nada aun porque recién empezamos a construirla (después de todo estamos en dns_message_render***BEGIN***()). En nuestro caso, podemos asegurar que sea 512, que es el tamaño máximo estándar de una respuesta DNS por UDP.
Con eso y sabiendo que DNS_MESSAGE_HEADERLEN es simplemente una constante de valor 12, si logramos que 500 < msg->reserved <= 512, podemos crear el contexto que motivó el fix de esa validación.


Entonces, que es msg->reserved? En la librería lib/dns/message.c, podemos ver que es una variable que indica cuántos bytes se desean reservar en msg->buffer para un posterior uso y solo se manipula con las funciones dns_message_renderreserve() y dns_message_renderrelease(). Lo interesante de esto, es lo que hace para lograr su propósito. Podemos ver que dns_message_rendersection() modifica el estado interno de msg->buffer, precisamente msg->buffer->length, con la humilde intención de que posteriores manipulaciones sobre ese buffer crean que su tamaño real es menor (clever boy).




El famoso buffer.c Si lograste seguirnos hasta acá vas a estar cuestionando lo siguiente:

  • Que hace la implementación de la lib que manipula isc_buffer_t
  • Quién es entonces el famoso buffer.c
Cada función expuesta hace gran cantidad de aserciones sobre isc_buffer_t para asegurarse de que no está haciendo mal las cosas y termine potencialmente corrompiendo memoria. Hay que tener en cuenta cuidadosamente el resto del estado de isc_buffer_t para recién después cambiarlo. Según el CVE publicado se describe que una aserción se dispara desde buffer.c, claramente existe un contexto en donde msg->reserved termina dejando invalida a la estructura isc_buffer_t y aborta al proceso en un posterior llamado a alguna función de buffer.c.



Haciendo la POC Ya convencidos de que msg->reserved está mal cuando 500 < msg->reserved <= 512, nos queda ver como podemos manipular esta variable a gusto del comensal. Trackeando el uso de dns_message_renderreserve() en lib/dns/message.c encontramos que msg->reserved es utilizada para trackear cuantos bytes serán necesarios para escribir los Additional RR (OPT, TSIG y SIG(0)) una vez que se termina de renderizar la respuesta en dns_message_renderend(). La forma más directa que encontramos de manipular un Additional RR incluido en la respuesta es enviar una query con un TSIG RR conteniendo una firma inválida. En este caso, el server hace echo de prácticamente todo el record al responder. El siguiente script envía una query A al server con un TSIG lo suficientemente grande para que el server, al escribir la respuesta, necesite reservar 501 bytes en msg->reserved.

https://github.com/infobyte/CVE-2016-2776/blob/master/namedown.py

Estado del demonio Bind9

Ejecución del exploit namedown.py

Podemos ver en esta captura que el valor TSIG RR de la query es de 517 bytes. Esto sucede porque el TSIG RR que el server construyó para su respuesta a la query, ocupa 16 bytes menos. Con lo cual, tuvo que adicionar 16 bytes para compensarlo.

Bind failed



Por que funciono?

Luego de procesar el pedido y fallar al validar la firma que trae, el proceso empieza a renderear la respuesta de error. Para ello, antes incluso de llamar a dns_message_renderbegin() (fundamental por cosas que no valen la pena detallar... mejor dicho: "ejercicio para el lector") ya reserva msg->sig_reserved bytes (calculados desde la firma devuelta por spacefortsig()) con la función dns_message_renderreserve(). En nuestro caso, como nosotros queríamos, reserva 501 bytes.



Luego, al llegar a dns_message_renderbegin(), tenemos el escenario que buscábamos: msg->reserved en 501 y r.length en 512. El chequeo de espacio que tendría que arrojar ISC_R_NOSPACE no se triggerea.



Ya con la instrucción posterior a la validación se huele porque es tan importante considerar tambien DNS_MESSAGE_HEADERLEN. Inmediatamente luego de validar que buffer tenga el espacio suficiente para almacenar msg->reserved bytes, se aloca en el buffer DNS_MESSAGE_HEADERLEN (12) bytes. es decir, no se comprobó si luego de reservar msg->reserved, había lugar suficiente para almacenar 12 bytes mas. Y como msg->reserved es externa a la implementación de isc_buffer_add(), la lib no tiene forma de saber que ya tenía espacio reservado, así que lo guarda sin cuestionar. En definitiva, al retornar de la función, tenemos que el espacio disponible de buffer es de 500 bytes (buffer->length - buffer->used = 512 - 12 = 500) pero ya estamos reservando 501 a futuro.



Al pasar por dns_message_rendersection(), msg->reserved recuerda decirle al buffer que tiene memoria reservada, pero la realidad es que no lo consulta, sino que se la quita a lo guapo dejando corrupta la integridad de la estructura isc_buffer_t msg->buffer
Ahora msg->buffer->used es MAYOR que msg->buffer->length.



Como se esperaba, al llamarse a isc_buffer_add() más adelante en la misma función, las aserciones que aseguran la integridad del buffer se rompen.



Conclusiones

Publicar un fix sobre un bug mortal donde haya que salir a patchear toda Internet, no deja tiempo para buscar soluciones mas elegantes, asi que si revisan el fix pueden encontrarse con que es posible que un nuevo bug similar aparezca en dns_message_renderbegin(). Si bien el uso de msg->reserved es bastante limitado, sigue siendo software complejo. Mientras siga existiendo msg->reserved, la existencia de un bug como el de CVE-2016-2776 sigue siendo bastante probable.


Remediacion
Actualizar Bind a sus versiones:
  • BIND 9 version 9.9.9-P3
  • BIND 9 version 9.10.4-P3
  • BIND 9 version 9.11.0rc3
La mayoría de las distribuciones han actualizado sus repositorios.


Créditos
Martin Rocha, Ezequiel Tavella, Alejandro Parodi (Infobyte Security Research Lab)


Referencias

Post a Comment
Thanks for your comment