‏إظهار الرسائل ذات التسميات pentest. إظهار كافة الرسائل
‏إظهار الرسائل ذات التسميات pentest. إظهار كافة الرسائل

الاثنين، 3 أكتوبر 2016

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

Introduction

For a number of years now BIND is the most used DNS server on the internet. It is the standard system for name resolutions on UNIX platforms and is used in 10 of the 13 root servers of the Name Domain System on the internet. Basically, it is one of the main function of the entire Internet.

With this in mind, it isn't everyday that someone finds a vulnerability (CVE-2016-2776) rated HIGH in one of the most used services on the internet (https://kb.isc.org/article/AA-01419/0).

The tests done by ISC (Internet Systems Consortium) discovered a critical error when building a response. Additionally, an advisory in the knowledge base of ISC recognizes that an attack can exploit the vulnerability remotely and probably because of that it receives a HIGH score in terms of severity.

One thing that caught our attention from the ISC Advisory was the following quote:
This assertion can be triggered even if the apparent source address isn't allowed to make queries (i.e. doesn't match 'allow-query')
We decided to dedicate a little bit of time to investigate the main cause of this error with the goal of seeing the root cause of the Denial of Service.

Identifying the modifications

Following the tradition of having errors in the necessary software for the survival of humanity, CVE-2016-2776 came to light. With details of the problem basically nowhere to be found, nor what was the mysterious "Specifically Constructed Request", we decided to see what exactly was modified in the repository of Bind9. 
In the diff of the fix,  the most interesting change is found in dns_message_renderbegin() 


Just by seeing the fix we can guess that there's undefined behaviour when r.length < msg->reserved is FALSE but r.length - DNS_MESSAGE_HEADERLEN < msg->reserved is TRUE. Having noticed this, it's worth investigating the program's context when the following condition validates:

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

Now we see what we can do to make that happen. If we see dns_message_renderbegin() we note that r.length describes the space available in isc_buffer_t buffer, that is where the response of the server will be written. This calculates as buffer->length - buffer->used.
Depending on how we craft the query, we can make sure that r.length is a known value given that it is going to be the same as the maximum size a response can have and we didn't do anything to it yet (after all we are in dns_message_render***BEGIN***()).

In our case, we can assure that it is 512, the standard maximum size of a UDP DNS response. Knowing that DNS_MESSAGE_HEADERLEN is the constant value 12, if we are able to make 500 < msg->reserved <= 512, we can create the context that motivated the fix.

So, what is msg->reserved?
In the library  lib/dns/message.c, we can see that it is a variable that indicates how many bytes we wish to reserve in msg->buffer for a later use and only can be manipulated with the functions dns_message_renderreserve() and dns_message_renderrelease(). The interesting thing about this, is what it does to achieve it's purpose. We can see that dns_message_rendersection() modifies the internal state of msg->buffer, or to be precise msg->buffer->length, All of this with a noble intention: make later manipulation attempts over that buffer believe that it's size is smaller than what it actually is.




The famous bufffer.cIf you were able to follow us until here you are probably asking the following:
  • What does the the lib implementation do, to manipulate isc_buffer_t ?
  • Who is then the famous buffer.c?
Each exposed function has a large quantity of assertions about isc_buffer_t to ensure that things are working properly and avoid potential memory corruption bugs. It's important to carefully consider the rest of the state of isc_buffer_t before changing it. Since the published CVE describes an assertion in buffer.c, clearly there exists a context where msg->reserved leaves the structure of isc_buffer_t invalid and it aborts the process on a posterior call to some function on buffer.c


Doing the POC

Now that we are convinced that msg->reserved is potentially dangerous when 500 < msg->reserved <= 512, it is time to see how we can manipulate this variable. Tracking the use of dns_message_renderreserve() in lib/dns/message.c we find that msg->reserved is used to track how many bytes will be necessary to write the Additional RR (OPT, TSIG y SIG(0)) once the response is finished rendering on dns_message_renderend().


The most direct way we've found of manipulating an Additional RR included on the response is sending a query with a TSIG RR containing an invalid signature. When this happens, the server echoes practically all the record when responding.

The following script sends a query A to the server with a TSIG large enough so as to make the server reserve 501 bytes on msg->reserved when writing the response.

Domain Status of Bind9

Running of the exploit namedown.py

We can see that the TSIG RR of the query is 517 bytes long. This is because the TSIG RR included in the server's response is 16 bytes shorter. Because of this, we should add 16 bytes to compensate.

Bind failed


Why did it work?

After parsing the request and failing to validate the signature, the process begins to render the error response. For that, even before calling dns_message_renderbegin() (fundamental for a couple of things not worth detailing... AKA: "exercise for the reader") it already reserves msg->sig_reserved bytes (calculates from the return signature by spacefortsig()) with the function dns_message_renderreserve(). In our case, as we wanted, it reserves 501 bytes.


When it gets to dns_message_renderbegin() we have the context we've looked for:  msg->reserved on 501 and r.length on 512. The if condition which should throw ISC_R_NOSPACE in the patch is not triggered. 



We can see now with the instruction immediatly after the validation why it was so important to consider DNS_MESSAGE_HEADERLEN. Inmediately after validating that the buffer has the sufficient space to store msg->reserved bytes, it allocates DNS_MESSAGE_HEADERLEN (12) bytes in it. In other words it didn't check if after reserving msg->reserved, there is enough space to store 12 bytes more. What happens in the end is that when returning from the function, the available space on buffer is of 500 bytes (buffer->length - buffer->used = 512 - 12 = 500) but we're reserving 501.



When passing through dns_message_rendersection()msg->reserved  remembers to tell the buffer that it has reserved memory, but it doesn't even ask, it just takes it from himThis leaves the integrity of the isc_buffer_t msg->buffer structure corrupt: now msg->buffer->used is BIGGER than msg->buffer->length. All the ingredients are here, we just need to put them in the oven.




As we expected, when isc_buffer_add() was called further ahead in the same function, the assertions that assure the integrity of the buffer break. For every n, msg->buffer->used + n > msg->buffer->length.




Conclusions

Publishing a fix about a lethal bug where you would have to patch the whole internet, doesn't leave a lot of time to find elegant solutions. So if you review the fix it's possible that a new similar bug appears in dns_message_renderbegin(). while the use of msg->reserved is quite limited. It continues being a complex software. Meanwhile msg->reserved is still being used, the existence of a bug like CVE-2016-2776 is quite probable.

Remediation
Update Bind to these versions:
  • BIND 9 version 9.9.9-P3
  • BIND 9 version 9.10.4-P3
  • BIND 9 version 9.11.0rc3
The majority of the distributions have updated their repositories.

Credits
Martin Rocha, Ezequiel Tavella, Alejandro Parodi (Infobyte Security Research Lab)

الجمعة، 30 سبتمبر 2016

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.cSi 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 POCYa 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