miércoles, septiembre 03, 2014

Análisis de un 0-day de Buffer Overflow en BASH 4.3

Un día con tiempo, te pones a revisar código de diversas aplicaciones. Muchas veces vas con la idea de que no encontraras ninguna vulnerabilidad, pero claro, ningún código o programa es perfecto. ¿Qué pasaría si encuentras un 0 Day mientras te paseas por el código fuente de la ultima versión de alguna aplicación? Eso me paso a mi hace unos días, y en este artículo analizaré ese mismo 0 Day que encontré en la última versión disponible de BASH la 4.3 y explicaré un poco el proceso que seguí para crear un exploit en Linux.

1.- Creación del laboratorio

Voy a explicar todo el proceso que hice para saber que impacto tenia mi descubrimiento y como podría ser aprovechado si era posible. Nuestro entorno controlado será un pequeño “laboratorio” que utilizara: · VirtualBox + Guest Additions por mayor comodidad. En él instalé una imagen de Ubuntu Desktop 14.04.1 LTS arch:i386.

Figura 1: Instalación de Ubuntu 14.04 sobre Virtual Box en el laboratorio

Una vez configurada e instalada la imagen sobre VirtualBox, instalamos las Guest Additions para una mayor comodidad con el entorno. ¿Por que hacemos esto? Pues simplemente para que si se nos va de las manos, no salpique a nuestro sistema operativo de uso diario y así evitar posibles futuros problemas.

2.- Descargar y analizar el código vulnerable

Para descargar la aplicación usaremos el comando apt-get o en su defecto aptitude para descargar el código fuente mediante el uso del parámetro source de la siguiente manera:

Figura 2: Descarga del código de fuente de bash con apt-get

Ahora ya podemos pasar a analizar el código vulnerable que esta alojado en la siguiente ruta: bash4.3ubuntu1.tar\bash-4.3\lib\sh\unicode.c linea 65:

static char charsetbuf[40];


static char *stub_charset ()
{
char *locale, *s, *t;

 locale = get_locale_var ("LC_CTYPE");
if (locale == 0 || *locale == 0)
{

strcpy (charsetbuf, "ASCII");
return charsetbuf;
}
s = strrchr (locale, '.');
if (s)
{

strcpy (charsetbuf, s+1);
t = strchr (charsetbuf, '@');
if (t)
*t = 0;
return charsetbuf;
}
strcpy (charsetbuf, locale);
return charsetbuf;
}


Se puede observar una evidente vulnerabilidad en el tratamiento de las variables charsetbuf y locale, y el bug puede reproducirse de la siguiente manera: Todo lo que se tienes que hacer es configurar LC_CTYPE o LC_ALL con un string muy largo pasando por la función de tratamiento de caracteres Unicode y usando printf. Con una simple linea se puede observar el desbordamiento:
$ bash -c "LC_CTYPE='$(printf %40s)' printf '\U876543210'"
Figura 3: Explotación del bug con bash -c "LC_CTYPE='$(printf %40s)' printf '\U876543210'"

Hay que tener en cuenta que esta vulnerabilidad solo esta presente en sistemas que no implementen locale_charset en libc/libintl/libiconv.

3.- En busca del 'Program received signal'.

Juguemos ahora un poco con los caracteres y observemos las diferentes salidas mediante una gran herramienta, GBD:
Program received signal SIGABRT, Aborted in 0xb7fdd424 in __kernel_vsyscall ()
Figura 4: SIGABRT mediante bash -c "LC_CTYPE='$(printf %40s)' printf '\U876543210'"
Program received signal SIGSEGV, Segmentation fault in __gconv_close (cd=cd@entry=0x0)
Figura 5: SIGSEGV mediante bash -c "LC_CTYPE='$(printf %40)' printf '\U876543210'"

Es curioso, que solo cambiando un carácter %40s a %40 salgan salidas distintas. En la primera salida pone __kernel_vsyscal, que para los que no lo sepan es el método utilizado por linux-gate.so, una parte del kernel de Linux, para hacer una llamada al sistema usando el método más rápido disponible, usa preferentemente la instrucción SYSENTER. En la segunda salida, en el archivo gconv_close.c en linea 35 pertenece a una función encargada de abortar calls, y posteriormente liberar todos los recursos.

4.- Preparando y analizando el binario en nuestro entorno controlado.


Vamos a ver qué protecciones tiene este binario y qué podemos y qué no podemos hacer. Para ello, podemos usar checksec.sh para comprobar las implementaciones de seguridad que tiene este binario de fabrica.

Figura 6: chechsec.sh sobre el binario de Bash 4.3

Ahora vamos a comprobar si ASLR esta activado:

Figura 7: Comprobación de estado de ASLR

Por ultimo comprobamos si tiene el SetUID o el SetGID activado:

Figura 8: Comprobación de estado de SetUID y SetGID

A primera vista, el escenario de ataque sobre el que hay que trabajar a partir de ahora se queda con las siguientes implementaciones de seguridad:
Partial RELRO: GOT no modificable.
STACK CANARY(ProPolice): No hay nada que hacer..
NX: Stack no ejecutable.
ASLR: 'Random' Memory.
SETUID: No esta activo.
La pregunta que hay que hacerse a partir de este punto es ¿se puede hacer un bypass a estas protecciones? La respuesta es: , pero NO. Veamos por que:

5.- ¿Que implementaciones de seguridad nos 'molestan' realmente?

Tanto a NX como ASLR se les puede hacer un bypass mediante una técnica llamada ROP. Ahora veremos qué fácil es crear un payload en ROP de forma automática gracias a ROPgadget una gran herramienta que también nos permite hacer búsqueda de gadgets.

Hay otras herramientas como ropeme la cual ofrece la búsqueda de gadgets en el binario y también la creación de payload de forma automática. Hay que destacar que estas dos grandes herramientas solo pueden generar payload de arquitecturas x86, veamos cómo.

Iniciamos ROPgadget con la opción '--ropchain' para que genere nuestro payload del binario bash.
$ python ROPgadget.py --ropchain --binary /bin/bash
Figura 9: ROPchain mediante ROPgadget

Teniendo esto, solo habría que guardarlo en un archivo *.py y cambiar el padding a: p = 'A' * 40 y ya tendríamos nuestro payload funcional. Se pueden dar algunas situaciones con las que tendríamos que lidiar:

- STACK CANARY o ProPolice nos frena en seco: una solución no perfecta es cambiar el valor de canary mediante un proceso hijo, y volverlas a cambiar por el valor canary original antes de que esta sea comprobada de nuevo si no, se detectaría un error.

- RELRO nos implementa un nivel de seguridad más: Cuando el binario es ejecutado y trasladado a la memoria, se hace una llamada a Procedure Linkage Table o PLT que posteriormente hace un jmp a la GOT. Básicamente RELRO nos impide modificar GOT.

6.- Conclusiones

Esto artículo quiere demostrar que no debemos confiar en ningún programa, porque nunca son 100% seguros, somos humanos y por tanto hacemos cosas imperfectas.

En este caso hemos visto un 0 Day, en concreto un Buffer Overflow en la ultima versión de BASH, la 4.3. Hemos seguido unos pasos para comprobar su protección, entender cómo funcionaba, y saber qué podríamos y qué no podríamos hacer.

Hemos visto que hacer un bypass de NX y ASRL no es demasiado complicado, que RELRO no nos molesta demasiado, y que STACK CANARY nos frena en seco toda la diversión. Además, aunque no hubiese estado, simplemente podríamos conseguir un ejecución de código arbitrario, pero no podríamos haber hecho un Privilege Escalation ni nada por el estilo ya que no esta el bit SetUID activado.

En este caso, esta vulnerabilidad se 'salva' de ser completamente explotada debido a STACK CANARY, pero hemos visto que el proceso de investigación es divertido y curioso. Solo nos faltaría informar a los encargados de seguridad de este proyecto, de forma responsable, y dejarles un tiempo para corregirla, en mi caso fueron avisados hace 2 semanas aproximadamente, y me dieron ya el consentimiento para hacer la divulgación publica.

Espero que este articulo anime a personas jóvenes como yo, y no tan jóvenes a adentrarse en el mundo de la seguridad informática y el exploiting de Linux. Un saludo muy grande de un chico de 16 años que a veces piensa.

Autor: Hádrien Romero Soria
Twitter: @kaiwaiata.

14 comentarios:

Icapa dijo...

Muy bueno, sí señor !

John doe dijo...

Buen trabajo!

Juan Luis Valverde dijo...

Muy bueno.

Anónimo dijo...

Es la opción "Voy a tener suerte" que te redirecciona al primer sitio que coincide con tu búsqueda.

Esto es perfectamente funcional sin especificar el dominio .com, .es, etc ni www. Y usando google.com

Ejemplo:

https://www.google.com/search?source=&q=elladodelmal&btnI=

Anónimo dijo...

Era para el otro post... sorry

(Típico de google chrome y sus funciones que joden. Al darle clic a un enlace mete preguntas estúpidas en la barra que hace cambiar la posición del contenido)

Titoace dijo...

Buen aporte!!
Que bajonilla me ha dado cuando ha dicho que tenia 16 XD

zhemn dijo...

16 años??!?! Con esa edad estaba yo... no me acuerdo, creo que no tenía ordenador y solo pensaba en... bueno vamos a dejarlo.

Muy buena chaval.

Fernando dijo...

Muy interesante.

Anónimo dijo...

Hey chema, tu sabes algo sobre el proyecto HACIENDA y la iniciativa de los Hackers de GNU para derribarlo?

Anónimo dijo...

¡Enhorabuena! FWIW, arreglado en el snapshot bash-20140815 (581fe5894cf3), pendiente de aparecer en la rama master.

q dijo...

Como ejercicio está muy bien (asombroso para tus 16 años!), pero...

¿cuál es el sentido de explotar el shell (p.ej. rompiendo la pila) para ejecutar código, si el shell es la herramienta que nos deja ejecutar comandos?

Si ya estás alimentando la entrada estándar del shell para explotarlo, ya puedes ejecutar tu código sin explotar nada.

Si explotamos el bash para ejecutar código, el código ejecutará con el UID del shell, exactamente lo mismo que si ejecutamos el código normalmente desde el shell.

¿Cuál es el riesgo de un exploit como este en bash? Deberías discutir esto en tu artículo :)

En todo caso, muy buen trabajo, enhorabuena.

Anónimo dijo...

Aparte de ser un ejercicio, el articulo es un analisis concreto..en el ultimo apartado yo creo que esta la respuesta a la pregunta de 'q' del comentario anterior llamada "6.- Conclusiones"...

PD: IN-CREIBLE solo 16 años, sigue asi!!!

q dijo...

Por si no quedó claro en el primer y el último párrafo de mi comentario... muy buen trabajo!! XD

La discusión que planteo en mi comentario está respondida parcialmente. El punto 6 dice que es posible ejecutar código arbitrario pero no es posible escalar privilegios. Cierto, pero eso no es lo que digo.

Esto en cualquier otro tipo de programa, un exploit que permite ejecutar código arbitrario aunque sea a nombre del mismo UID es una vulnerabilidad crítica (por ejemplo, poner un proceso de Apache a ejecutar un bash).

Pero justo para un shell, un bug como este no es tan importante como parece a primera vista, porque el trabajo de un shell es crear procesos para ejecutar código que no es del shell. Vamos, que si quieres ejecutar tu código arbitrario y puedes alimentar la entrada estándar del shell, te compilas tu programa con gcc y lo ejecutas XD

Mi comentario no debe tomarse como un ataque, es una crítica constructiva, hombre, porque estoy seguro de que los la mayoría de los lectores no ha caído en esto.

Espero que haya quedado más claro lo que quería decir.

while(1) printf("good job!\n");

alexandra v. dijo...

realmente impresionante :) sigue asi !! :D
Te veo un futuro muy prometedor :)

Entrada destacada

Programa de Especialización "Inteligencia Artificial para Expertos en Ciberseguridad" 2ª Edición.

Hoy, en medio del verano, os traigo información de la 2ª Edición del   Programa de Especialización  de "Inteligencia Artificial para Ex...

Entradas populares