lunes, marzo 30, 2015

OSB-Rastreator: Cómo localizar automáticamente todos los bugs que tiene tu Ubuntu (o cualquier GNU/Linux)

Encontrar vulnerabilidades puede parecer más difícil hoy en día de lo que a priori puede llegar a ser. Este pensamiento es el que nos llevó a realizar una pequeña prueba de concepto de lo que podría hacer cualquiera de nosotros en su casa. Todo este mini proyecto nace de un tweet de mi compañero en Eleven Paths David Barroso en el que, tras los incidentes de seguridad que sufrió el pasado 2014 - y que parecen continuar en 2015 - el mundo del Open Source enunciaba:
“GNU has given a lot to the tech companies. Now it’s time to help GNU and audit many of its core components”. (@lostinsecurity).
Esto me hizo pensar en el gran número de aplicaciones Open Source que diariamente están expuestas en los repositorios. Una de las primeras preguntas que me hice fue, "¿Cuántos paquetes de software tenemos al alcance del comando apt-get install? ¿Este código será revisado en busca de vulnerabilidades?"

Figura 1: OSB-Rastreator: Cómo localizar automáticamente los bugs que tiene tu Ubuntu

Quise resolver la duda a mi primera pregunta realizando un simple ejercicio con apt-get. Para ello instale la última versión de Ubuntu, y ejecuté en una shell la instrucción: apt list | cut –d’/’ -f1 >> openSource.txt. Luego simplemente con un wc –l sobre el fichero podría ver el número de paquetes de software que, por defecto, tenemos accesibles desde Ubuntu. ¿Por qué Ubuntu? Está claro, es una de las distribuciones más utilizadas, si no la que más, en el mundo Linux. El resultado obtenido sobre el número de paquetes de software disponibles por defecto puede sorprender a más de uno. Cada usuario que instala Ubuntu tiene alrededor de 45.000 paquetes de software, en mi caso exactamente 45.494.

Figura 2: 45.494 paquetes disponibles en Ubuntu

A la pregunta de si el código es revisado puede haber varias opiniones, aunque por lo que parece tras un análisis muy básico probable la respuesta correcta es que "No lo suficiente". Ya en el pasado hemos visto estudios de hacking con buscadores sobre repositorios de código OpenSource utilizados para buscar vulnerabilidades de tipo SQL Injection, XSS o RFI, e incluso del tipo LDAP Injection. Solo con lanzar consultas adecuadas sobre el código fuente es posible sacar estos datos.

Creando OSB-Rastreator

Pensando sobre algunas vulnerabilidades clásicas, y que a día de hoy siguen apareciendo, llegué a la conclusión de que un buen punto de partida serían las funciones inseguras ampliamente conocidas. Aquí me surgió una nueva pregunta, ¿Seguirán utilizándose de manera masiva las funciones strcpy(), sprintf(), gets() o scanf(), entre otras? ¿Quién no vio su primer buffer overflow con un strcpy() o alguna de las mencionadas anteriormente? Esto fue el origen de la creación de OSB-Rastreator.

Pensando sobre esto quise buscar estas funciones, pensando que encontraría una cantidad ridícula de software con estas funciones en su código. Pensando un poco la mayoría del código que podemos encontrar en los repositorios de Linux será código en Lenguaje C, pero aun así seguía pensando que estas funciones potencialmente inseguras no tendrán mucho rastro por los repositorios, ¿o quizá sí? El siguiente paso era conseguir, de la manera más sencilla, y que cualquiera en su casa pudiera realizar, descargar todo el código fuente de los más de 45.000 paquetes de software. Para llevar a cabo la descarga de todos los paquetes de software se implementó un script en bash con el siguiente funcionamiento:
1. Leer paquete del fichero openSource.txt
2. Descargar paquete
3. Descomprimir tar.gz
4. Búsqueda de código C
5. Búsqueda de función insegura
6. Eliminar paquete (tar.gz y código descomprimido)
En la siguiente imagen se puede ver parte del código de descarga de los paquetes de software, algo muy sencillo y que automatizaba todo el proceso de obtención del código de los repositorios.

Figura 3: Descarga de paquetes y búsqueda de funciones inseguras en códigos C

Este código era la primera prueba de concepto para ver si el proceso era viable en un tiempo razonable. Decidí utilizar una máquina en la nube para llevar a cabo el proceso. Por cada paquete de software del que descargábamos el código fuente, hay que entender que podríamos obtener N ficheros de código en Lenguaje C, y cada fichero debía ser analizado buscando las funciones inseguras. En este punto aparte de pensar en las funciones inseguras recordé grandes “cagadas” de algunos developers a la hora de programar y comentar el código, por lo que pensé, "¿Qué puedo encontrar en los comentarios?" De esta forma decidí que también los comentarios debían ser buscados en esta primera pasada.

Figura 4: Resultados de búsqueda de funciones inseguras por el script

El fichero denominado down.sh tardó más de 8 días en realizar el proceso de descarga y búsqueda de información interesante en el código. ¿Qué ocurría cuando se encontraba una función insegura o un comentario? Pensando en que encontraría pocas cosas para su posterior análisis, decidí almacenar por carpetas las funciones inseguras. Es decir, el script encontraba un strcpy() y almacenaba dentro de la carpeta strcpy un fichero con el nombre de la aplicación y en el interior del fichero los ficheros de Lenguaje C pertenecientes al paquete de software, con la línea dónde se encontraba la función.

Figura 5: Un strcpy en Hydra 7.5

El script puede funcionar mucho más rápido si se distribuye, pero en este caso el tiempo no era importante para la prueba de concepto. En este momento y teniendo los resultados en la mano me quedé sorprendido. Estos son los resultados:
• La función strcpy() tenía 9567 ocurrencias, es decir, fue identificada en ese número de paquetes de software.
• La función gets() tenía 607 ocurrencias.
• La función scanf() tenía 1257 ocurrencias.
• La función sprintf() tenía 9426 ocurrencias.
• Por último, fueron identificados 15647 paquetes de software con comentarios multilínea. En este caso, solo multilínea.
Realmente mi gozo en un pozo. Por un lado me sorprendió ver tanto software que utilizase funciones inseguras, lo que fue algo positivo para esta prueba de concepto. Por otro lado analizar todo esto iba a ser algo inviable, por falta de recursos y tiempo. Para poder ver en qué partes encontraríamos bugs sin que fuera una pérdida el tiempo, decidí analizar a mano algunas aplicaciones.

Analizando los resultados en busca de bugs

Una de las primeras en las que me fijé fue dnsmasq, y analizado las llamadas strcpy() observé una por encima del resto strcpy(ifr.ifr_name, argv[1]) en un fichero llamado dhcp_release.c. Esto tiene que ser vulnerable sí o sí, no puede haber ejemplo más claro. Tras comprobarlo, efectivamente, no se comprueba el límite que se introduce en argv[1] por lo que tendremos un desbordamiento.

Esto me volvió a hacer pensar y preferí automatizar el proceso de búsqueda de patrones “curiosos” o “potencialmente vulnerables” como son los argv[x] que podemos encontrar, o por ejemplo las variables de entorno que son pasadas sin validación a través de la función getenv() a un strcpy(). En la imagen se puede ver como ejecutando el script search.sh se busca dentro de los resultados el patrón que nosotros queramos. En este caso se busca el texto getenv sobre resultados de funciones inseguras, por ejemplo buscando llamadas a getenv() que se pasan directamente a strcpy().

Figura 6: Búsqueda de patrones en los resultados de funciones inseguras

Otra prueba que quise automatizar viendo el gran número de argv[x] que se pasaban a funciones como strcpy() fue la de instalar automáticamente las aplicaciones que contengan esto, que automáticamente se intente desbordar el buffer y, a posteriori, se desinstalasen las aplicaciones. La imagen dónde se puede ver la ejecución de este script se encuentra unificada, para que se pueda ver rápidamente la instalación, la provocación del fallo y la desinstalación. Este script fue denominado buffer.sh y un ejemplo de su ejecución es la siguiente:

Figura 7: Instalación, prueba y explotación de buffer overflow automáticamente

Se encontraron fallos muy sencillos de detectar, pero sorprendió el número de errores, por lo que se puede decir que NO hay un análisis básico de seguridad o de validación de parámetros en la publicación o subida de aplicaciones a los repositorios de forma automatizada. En función de qué permisos utilice una aplicación o con qué usuario se ejecute estos fallos pueden ser más o menos graves.

Antes de instalar un paquete analiza los bugs

Llegados a este punto pensé en cómo se puede ayudar al usuario, o que al menos esté notificado de la calidad del código que está instalando, y quizá una solución viable es la modificación de apt-get para que cuando un usuario ejecute la instrucción apt-get install se realice un análisis de las funciones de la aplicación mediante expresiones regulares. Al final el trabajo fue presentado en Hackron 2015, (sí, sé que puedo dar envidia por el carnaval vivido allí y que no dejo indiferente a nadie…) Dicho esto, he de decir que por falta de tiempo no me pegue con apt-get y realicé un script que podría ser invocado como alias de apt-get para llevar a cabo las instalaciones de paquetes de software.

Figura 8: La instalación de un paquete antes verifica las funciones inseguras que tiene

El script denominado apt-get-install.sh antes de llevar a cabo la instalación del software realiza una serie de comprobaciones en el código, basadas en lo que se automatizó con down.sh. En primer lugar descarga el código fuente, aplica las expresiones regulares que podemos configurar sobre los ficheros en Lenguaje C y en el instante que detecta alguna función no segura notifica al usuario antes de seguir con la instalación.

Figura 9: Antes de instalar muestra las funciones inseguras

El script te permite ver las líneas dónde se encuentran las funciones no seguras y te permite acceder al código completo por si se quiere hacer alguna comprobación manual. Por último, se solicita al usuario la confirmación de instalación al usuario.

Figura 10: El usuario decide si instalar o no el paquete sabiendo ya la información de las funciones inseguras

Este sistema me parece un buen método de sentido común, sobre todo en casos en los que se quiere realizar un proceso de fortificar un servidor Linux. Con estos avisos se tiene mucha más información a la hora de extender la superficie de exposición, y del riesgo en que se está incurriendo en cada nuevo paquete que se instala.

Personalizando la automatización de búsqueda de bugs

Por último hay que decir que, tal y como se comentó anteriormente, la personalización a la hora de realizar las búsquedas en los paquetes de software es algo imprescindible. Cada uno puede tener distintas intenciones, o puede realizar distintas búsquedas a través de diferentes expresiones regulares basándose en esta arquitectura. Puede ser que en un momento dado salga cierta vulnerabilidad conocida implementada de alguna forma y que añadiendo la expresión regular al sistema podamos localizar más software vulnerable en los repositorios de Linux para explotar algunos ejemplos concretos.

Figura 11: Expresiones regulares para buscar distintos tipos de bugs

Para añadir las expresiones regulares al sistema disponemos de un fichero denominado regexp.txt, dónde por cada línea se indica la carpeta dónde se almacenará y la expresión regular. Este punto es importante porque da mucha flexibilidad al script y las posibilidades ya dependen de lo que cada uno quiera encontrar.

Figura 12: Búsqueda de bugs con ficheros de expresiones regulares

En la imagen anterior se puede ver como se lanzará el script denominado down_customize.sh, siendo el primer parámetro el fichero de texto que tiene el nombre de todos los paquetes de software y el segundo parámetro el fichero dónde se encuentran las expresiones regulares de lo que queremos encontrar en el código fuente.

Resultados y un Exploit de Memory Corruption en Chemtool

Los paquetes de software disponibles por defecto en una instalación de Ubuntu son 45.494. Tras lanzar alguna pasada más con el script personalizado se obtuvieron 13.670 paquetes de software distinto que contenía la función strcpy() y ni mucho menos esto significa que sean vulnerables, habría que llevar a cabo un análisis. Manualmente es una locura, pero basándose en pequeños tricks, intuiciones y herramientas se pueden detectar nuevos bugs.

En 13.430 paquetes de software se encontraron funciones sprintf() que también son potencialmente inseguras. En 1.789 paquetes de software se encontró la función scanf(), mientras que en 973 paquetes de software se encontró la función gets(). Respecto a los comentarios en el código, prácticamente la mitad del código analizado contenía comentarios, lo cual no es malo, pero se podría utilizar el script search.sh para buscar patrones o cadenas clave como password, user, connection, etcétera.

Para la charla de Hackron 2015 se quiso demostrar que cualquier podría sacar bugs de manera sencilla con este sistema o cualquiera similar que una persona se puede montar en casa. Por esta razón una de las aplicaciones encontradas que tenía un bug fue notificada al autor de la misma. Tras esto a través de Exploit-DB se lanzó la vulnerabilidad en la aplicación Chemtool, la cual contenía Memory Corruption.

Figura 13: ChemTool

El crasheo de la aplicación era llevado a cabo de dos maneras, la primera y más sencilla por no controlar el parámetro de entrada argv[1] cuando la aplicación era lanzada desde una terminal. Tras ver esto, quise analizar cómo se comportaba la aplicación al pasarle un fichero con contenido no esperado. Para crear el fichero malicioso se utilizó un pequeño script en Ruby:
#/usr/bin/ruby
buf = "a"*3000
filename = "crash.png"
file = open(filename,'w')
file.write(buf)
file.close
puts "file created!"
¿Se podría ejecutar código arbitrario? Es bastante probable, aunque no he comprobado esta parte. Con OSB-Rastreator me basé en encontrar puntos débiles y bugs de aplicaciones que se encuentran expuestas por defecto a millones de usuarios de Ubuntu en el mundo. Un par de días antes del evento de Hackron decidí enviar el bug para que OSVBD la diera de alta, si ellos lo consideraban. Al día siguiente tenía disponible el ID que identificaba el bug y podría mostrarlo en la charla.

Figura 14: El bug en OSVBD

A partir de aquí se puede ver por Internet como distintos sitios se hacen eco de la vulnerabilidad, por ejemplo en 1337day Inj3ct0r. Esto ejemplifica que cualquiera con ideas y conceptos básicos puede llevar a cabo un proyecto que permita de manera masiva hacer un análisis básico y obtener resultados sorprendentes.

Figura 15: Bug de ChemTool

En conclusión, por defecto existe gran cantidad de software en los repositorios que distribuciones como Ubuntu proporcionan a los usuarios que son vulnerables y fácilmente accesibles a los usuarios. Lógicamente, si añadimos repositorios a sources.list el número de paquetes de software a analizar serían mayores, por lo que se podrá detectar un mayor número de vulnerabilidades.

Hoy día técnicas como el Pentesting Persistente que encontramos en servicios como Faast, ayudan a mejorar y detectar vulnerabilidades de forma temprana. Aunque el sistema propuesto aquí está pensado para analizar repositorios muchas de las aplicaciones que podemos encontrar nuestros sistemas Linux de Ubuntu, en dichos repositorios podrían estar los paquetes de servidores Web, FTP, SSH o, directamente, tener interacción remota. Continuaremos evolucionando esta idea.

Autor: Pablo González Pérez (@pablogonzalezpe), Project Manager en Eleven Paths
Escritor de los libros "Metasploit para Pentesters" y "Ethical Hacking"

14 comentarios:

Anónimo dijo...

Bravo! Por una parte nos baja el ego a los usuarios de Linux, pero tambien se intuye un futuro mas seguro.

Anónimo dijo...

En mi opinión Linux tiene una gran ventaja frente a Windows: es código abierto. De hecho gracias a que es código abierto, puedes dedicarte a explorar qué vulnerabilidades tiene. En el caso de Windows esto será imposible, porque no es posible auditar su código fuente. Las vulnerabilidades son las que se quieran o permitan publicar.

Lo que quiero decir es: ¿de qué manera realizarías este mismo análisis en un sistema Windows? No podrías, y por tanto el resultado es que Windows tiene menos vulnerabilidades en este sentido, lo cual no sería rotundamente cierto.

Anónimo dijo...

@Anonimo de la ventaja.
Gracias a que Linux es código abierto es que cualquiera se podría dedicar a explorarlo... lo que incluye también a los chicos malos.
En el caso de Windows sería imposible auditar el código... también para esos chicos malos.

Lo que quiero decir con todo respeto es que estás viendo las cosas de una manera "muy positiva" al grado que estás imaginando cosas e ignorando la otra parte.

De hecho, objetivamente, que el código sea abierto facilita más el trabajo para la gente mala y el código cerrado se lo dificulta a los malos. Si en el código cerrado quieren que los buenos vean y revisen su código sólo tienen que mostrárselo.

Anónimo dijo...

es cierto que porque es código abierto se puede analizar de manera fácil, pero en ciertos ejecutables donde no se encuentran protegidos(cifrados,comprimidos,etc....) , si es por buscar las funciones vulnerables, también se puede realizar, en windows el problema sería buscar las fuentes de todos los programa a analizar ...

Jvare dijo...

Interesante trabajo, aunque para usuarios particulares puede ser interesante saber que programas tienen bugs, esto es fundamental para los numerosos servidores web que están basados en Gnu/Linux.

Jonathan Novel dijo...

Tremendo curro veo aquí, muchas gracias por compartirlo y enhorabuena Pablo.

Saludos!

Abel Sendon dijo...

Estaría bueno saber que pasaría si se le hace este tipo de pruebas al software de Windows (ni hablemos del Windows), ahh, y no es necesario tener disponible el código fuente para insertar código de máquina "malicioso", con un editor hexadecimal es suficiente ;)

Anónimo dijo...

Mi profesor de informática dice que linux no tiene vulnerabilidades ni virus ;) le pasaré este POST.

Anónimo dijo...

Me parece que Linux no tiene "tanto" malware porque no tiene una gran cuota de mercado. Mas allá de los servicios que corren en servidores activos que son vulnerables (ssl/tls por decir algo), también hay muchos servidores Windows, Mac, etc y ahí esta la papota. Y si me pongo sentimental, me parece que le tienen un poco mas de cariño al pinguino, aunque, si se descubre alguna vulnerabilidad, hay que romper todo por las dudas.

Juan Felipe dijo...

Tuve la suerte de verlo en Hackron este año y te repito que tanto la mala idea como la solución me parecen fantasticos. Quien sabe si dentro de poco añaden tu script a una instalación segura por apt-get jajaja.
Un saludo Pablo y hasta pronto!

Anónimo dijo...

Tener algunas de esas funciones puede hacer inseguro o no tiene por que. Por ejemplo antes de hacer un strcpy() puedes revisar que el string origen no sea mayor que el destino, de esta manera aunque uses esa función susceptible a fallos tu código no será vulnerable. Todo recaerá en la calidad o implicación del programador que usa estas funciones.

Anónimo dijo...

No me puedo creer que los chicos de Debian no implementen scripts de chequeos automáticos en ftp-master.

Mi profesor de informática dice que linux no tiene vulnerabilidades ni virus

Típico fanboy de linux.

Manuel Escudero dijo...

Corrígeme si estoy equivocado pero estos "bugs" que encontraste permiten... ¿Colgar una máquina (si no es que un sólo proceso únicamente) desbordando un búffer? Como lectura ligera está entretenido tu artículo pero ese tipo de problemas son irrelevantes gracias al proceso de QA de los paquetes linux. Independientemente de que el código sea "100% auditado" o no, el proceso de QA de mínimo lo que hace es que varias personas instalen los paquetes en sus equipos durante las versiones alpha o beta de las distros, los "bugs" que encontraste lo que harían a lo mucho sería que una máquina se colgase al usar X paquete (o que se colgase ese proceso nada más), y esas son las situaciones que se detectan en los "Test Days" de las distros linux entre otras muchísimas cosas...

Ahora, los "bugs" que nos deben preocupar son aquellos que permiten ejecutar código malicioso y tu mismo destacas:

"¿Se podría ejecutar código arbitrario? Es bastante probable, aunque no he comprobado esta parte"

Claro que para que ese "bastante probable" suceda, para empezar un "atacante" debería tomar control root de un sistema linux, puesto que un usuario normal no puede hacer daño más allá de "colgar una app" (lo que haces en tu artículo) y eso es algo que no se puede hacer con los "bugs" mencionados (tomar acceso root).

Sobre:

"El crasheo de la aplicación era llevado a cabo de dos maneras, la primera y más sencilla por no controlar el parámetro de entrada argv[1] cuando la aplicación era lanzada desde una terminal..."

Claro, y también puedo darle a leer un archivo de 2,000,000 de líneas a Gedit para que reemplace una palabra por otra en una máquina de 1-4GB de RAM y PUM! desbordo el búffer de la misma manera... Ahora, hablando más enserio, entiendo la "gravedad" de ésto en, por ejemplo un servidor pero basta con unos cuantos kernel vm tunings en /etc/sysctl.conf para hacer inútil este ataque:

# Swappiness & Memory Tunning
vm.swappiness=60
vm.panic_on_oom=0
vm.oom_kill_allocating_task=1
vm.dirty_ratio=10
vm.dirty_background_ratio=5

Como dije, para lectura ligera está padre, pero a un usuario desinformado le puede sonar "impactante" porque está lleno de tecnicismos sensacionalistas. Deberías explicar ambas caras de la moneda...

P.D. Insisto, corrígeme si estoy equivocado.

Anónimo dijo...

No entiendo cual es la gravedad de este supuesto "Bug", alguien me podria explicar?

Entrada destacada

Joinnovation & KeepCoding Connect: 2 Eventos para DOERS en #Madrid

Ayer fue el día de ver los proyectos de EQUINOX , el hackathon de ElevenPaths donde durante 24 horas se lanzan proyectos que normalmente ...

Entradas populares