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"