Decorando decoradores
Una de las preguntas que aveces me hacen luego de hablar sobre Taint Mode es: una vez que la aplicación pasa de desarrollo a producción, ¿hay alguna forma de deshabilitar los decoradores?
La biblioteca usa decoradores para marcar entradas no confiables, sumideros sensibles y funciones limpiadoras con la idea de encontrar posibles vulnerabilidades en tiempo de desarrollo. Si en producción no los requerimos más, esos decoradores producen overhead.
Entonces... qué se puede hacer? Editar el código comentando los decoradores no escala[0]. Una alternativa es utilizar un nuevo decorador: llamemoslo @apply_decorator y vamos a utilizarlo para controlar mediante alguna condición (por ejemplo una variable en el archivo de configuración) si se debe usar o no el decorador.
COND = True
def apply_decorator(d):
if COND: return d else: return lambda f: f
Si la condición es verdadera, se retorna el decorador original, sino una función fake (implementada utilizando lambda
) que recibe una función y retorna la misma función: un decorador que no hace nada.
Un ejemplo de su uso en el REPL de Python:
>>> @apply_decorator ... def mydeco(f): ... def inner(*a, **kw): ... print "decorado" ... return f(*a, **kw) ... return inner ... >>> mydeco>>> COND = False >>> @apply_decorator ... def mydeco(f): ... def inner(*a, **kw): ... print "decorado" ... return f(*a, **kw) ... return inner ... >>> mydeco at 0xb7753dbc>
Podemos aplicarlo directamente a la definición de nuestros decoradores
@apply_decorator def mi_decorador(f): ...
o al principio de nuestro programa.
mi_decorador = apply_decorator(mi_decorador)
[0] nessita trademark.
Quoted
Fillipo has just published Implementing Erasure Policies Using Taint Analysis.
With this in mind, and inspired by Conti and Russo’s work, we implement a mechanism to perform taint propagation, i.e. how to mark as erasure-aware data that is computed from other erasure-aware values. From now on, we use taint and erasure-aware as interchangeable terms.
Talk: Taint Mode for Python via a Library (video)
OWASP have just published the video of my talk in OWASP App Sec Research 2010 in Stockholm.
First talk in English ever.
Fue mi primer charla en inglés.
Slides and text are also avaliable.
Python Taint Mode en OWASP App Sec Estocolmo 2010
En febrero de este año estuve trabajando mucho con Alejandro Russo para mejorar la librería de Taint Mode en Python en la que empecé a trabajar el año pasado. De este trabajo surgió un paper que enviamos al congreso OWASP AppSec Research 2010 - Stockholm, Sweden y hace unos días nos avisaron que fue aceptado:
A Taint Mode for Python via a Library
Si Dios quiere, voy a estar allá presentándolo.
Big refactoring en dyntaint.py
Ayer llevé acabo un refactoring importante en mi proyecto Taint Mode para Python.
En resumen: dyntaint.py es un módulo que permite seguirle la huella a datos que ingresan a un programa con el objetivo de evitar que lleguen a ciertas áreas sensibles. Por ejemplo, que "42 or 1=1" no llegue a una consulta SQL que se ejecutará contra una base de datos.
Si lo anterior no te dice nada, te recomiendo que leas mi presentación sobre el tema.
Hasta hoy, el registro de qué variables estaban manchas con qué tipo de manchas en una corrida del programa se llevaba en una estructura de datos auxiliar llamada TAINTED. Básicamente un diccionario en el cual cada clave se corresponde con un tipo de mancha (o vulnerabilidad), y cada valor es un conjunto de variables manchadas con el tipo de mancha de la clave correspondiente.
El refactoring consistió en cambiar de este esquema a uno en el cual cada variable manchada tiene un atributo (taints) que es un conjunto de identificadores de manchas. Entonces si antes tenía algo como:
{XSS: set(['manchado1', 'manchado2']),SQLI: set(['manchado1'])}
ahora tengo:
>>> manchado1.taints set([XSS, SQLI])
>>> manchado2.taints set([XSS])
La revisión 59 también incluye algunas otras limpiezas. Para ver los cambios:
svn diff -r 58:59 http://svn.juanjoconti.com.ar/dyntaint/
Después de hacer las modificaciones necesarias, y corregir errores, las pruebas corren ok:
Ran 84 tests in 0.006s OK
Podemos discutir los cambios en los comentarios.
Semana de la seguridad
En el año 1988, la Association for Computing Machinery (ACM) declaró al 30 de Noviembre como el “Día Internacional de la Seguridad Informática”, con el objetivo de concientizar respecto de las amenazas que atentan contra la seguridad de la información.
Esta semana es la Semana de la Seguridad. En Argentina la organiza la Jefatura de Gabinete de ministros. En Santa Fe AsegurarTe está organizando algunas charlas y me invitaron a dar mi charla sobre Taint Mode en Python el día miércoles 25 a las 12:30 en la Dirección de Informatización y Planificación Tecnológica de Rectorado de la UNL. Desconozco qué porcentaje de los asistentes conocen Python, por lo que voy a intentar centrarme más en los conceptos que en la implementación concreta de esta técnica; así como definir bien el problema, plantear sus implicacias y discutir soluciones.
update: un par de fotos!
Charla: Taint Mode en Python
Estas son los slides (más texto con el grueso de lo que dije) de mi charla en PyCon Argentina 2009. La misma se anunciaba como:
Taint Mode es una característica implementada en algunos lenguajes con el objetivo de prevenir que usuarios malintencionados alteren entradas al sistema para lograr la ejecución de comandos no permitidos u otros tipos de ataques.En esta charla se introducen los conceptos básicos de Taint Mode y se discute la forma de implementarlo en Python mediante el uso de decoradores. Se muestra una implementación concreta y se analizan sus resultados.
Cómo digo ahí, no soy un experto en seguridad aunque es un área que me interesa, sino me considero un desarrollador.
Cómo desarrollador, muchas veces me encuentro dejando de lado la seguridad por cuestiones más apremiantes.
Por hacer lo urgente, no hacemos lo importante.
Entonces las fechas límite para entregas, las presiones por incorporar nuevas características, el pago solo por lo que se ve, hace que mucho software salga a la calle sin los más mínimos recaudos de seguridad.
Esto trae consecuencias graves para muchas organizaciones y personas, como:
- Robo o alteración de información
- Bloqueos en la disponibilidad de un recurso
- Suplantación de identidad
Formas de mitigar este problema sin duda hay muchas. Una puede ser contar con herramientas que en tiempo de desarrollo permitan verificar la existencia de vulnerabilidades en el código escrito. ¿Qué vulnerabilidades? ¿Todas? Eso sería seguramente imposible. Pero existen herramientas que se especializan en distintos nichos: buffer overflow en C, XSS en PHP.
Empecé a prestarle atención a este tipo de cosas cuestiones cuando empecé a participar de un grupo de investigación sobre seguridad en mi facultad. Yo me estaba postulando para obtener una beca de maestría y una de las condiciones era participar de un grupo de investigación.
Así que empecé a buscar en Internet papers que relacionen cuestiones de seguridad con Python, como una forma de encontrar algún tema que me interese y pueda profundizar.
Encontré algunos papers de 2 investigadores de la Universidad Estatal de Moscú, Andrew Petukhov y Dimitry Kozlov en los que discutían cómo implementar Taint Mode en Python. Si bien su enfoque consistía en modificar el intérprete en C de Python para lograrlo, de su trabajo incorporé la mayoría de los conceptos que voy a mencionar en la primer parte de la charla. A diferencia de ellos, mi implementación de Taint Mode para Python está escrita totalmente en Python.
El modelo de las manchas intenta describir cómo se comportan los valores que ingresan a un sistema desde el exterior y tiene 3 componentes fundamentales.
FUENTES NO CONFIABLES, FUNCIONES LIMPIADORAS Y SUMIDEROS SENSIBLES.
Todo valor que ingresa a un programa desde el exterior, es considerado por defecto como un valor manchado, ya que no sabemos de su procedencia. Decimos que viene de una fuente no confiable. Los valores recibidos desde el exterior pueden haber sido deliberadamente modificados para causar daño o simplemente por error causar un un problema dentro del sistema.Estas manchas se propagan por el programa mediante operaciones como la asignación o la concatenación.
Las funciones limpiadoras representan los mecanismos que se pueden utilizar dentro de un programa para convertir un valor manchado en un limpio: escapando, codificando o incluso eliminando caracteres sospechosos.
Los valores manchados no deben ser utilizados para: generar respuestas hacia los clientes o construir comandos a ser utilizados contra servicios externos, como los provistos por el sistema operativo, motores de bases de datos o el mismo intérprete. A estos componentes a los que no queremos que lleguen valores manchados, los denominamos "sumidero sensible".
Si un valor manchado alcanza un sumidero sensible, entonces existe una vulnerabilidad en el programa.
De esto se deduce que los valores manchados deben ser limpiados antes de que el flujo del programa los lleve hacia un sumidero sensible.
Esto es correcto, pero debemos preguntarnos: ¿son todas las manchas iguales? ¿Una función limpiadora puede quitar todas las manchas de un valor? ¿Son todos los sumideros sensibles al mismo tipo de mancha? NO.
- Un valor puede tener más de un tipo de mancha.
- Una función limpiadora, por lo general, limpia un solo tipo de mancha.
- Un sumidero es sensible a un solo tipo de mancha.
Cuando un valor ingresa al programa, no sabemos que manchas tiene ¿Debe un desarrollador aplicar todas las funciones limpiadoras a cada valor que procede de una fuente no confiable? No toda función limpiadora es apropiada para procesar todos los valores manchados e incluso hacerlo consumiría recursos en forma innecesaria.
Una mejor estrategia es, dado un tipo de mancha determinado, aplicar una función limpiadora de esta mancha solo a aquellos valores que alcanzarán un sumidero sensible a la mancha en cuestión.
En concreto. Este valor está manchado con SQL Injection y no deberíamos dejar que forme parte de una sentencia que se va a ejecutar contra una base de datos. Pero, en principio, no habría problemas en que se use para renderizar una página que será devuelta al usuario.
Algo similar sucede con este valor. Si bien está manchado para XSS. En principio no habría problemas en que se utilice para generar un comando a ejecutar contra el sistema operativo.
Tres menciones sobre la implementación antes de ver ejemplos de uso.
1) Existen 2 formas generales de implementar este tipo de análisis. De forma estática o de forma dinámica. Una implementación estática realiza la detección analizando el texto de un programa, sin ejecutarlo. Mientras que una implementación dinámica, analiza el coportamiento del programa. Esta es una implementación dinámica, por lo que va a requerir que el programa a evaluar sea ejecutado y utilizado: ejecutando manualmente casos de uso o corriendo tests automatizados.
2) El uso de módulo requiere que sea importado dentro del programa a evaluar y hacer algunas modificaciones en el código fuente. Es intrusivo.
3) La implementación se basa en el uso de decoradores. Existen decoradores para marcar dentro del programa los 3 tipos de elementos del modelo de las manchas: FUENTES NO CONFIABLES, FUNCIONES LIMPIADORAS Y SUMIDEROS SENSIBLES.
Cada tipo de mancha con la que va a trabajar el módulo es identificada por un entero. En la lista KEYS que se ve en la pantalla, XSS es identificada con el 0, SQLI con el 1 y así. Esta lista de valores debe ser modificada si se quiere agregar nuevas vulnerabilidades para que sean reconocidas por la herramienta.
TAINTED es la la estrucutura de datos dónde se lleva el registro de qué variables están manchadas. Es un diccionario dónde las claves son los enteros de la lista KEYS sus valores conjuntos, sets, inicialmente vacíos. Cuando se recibe un valor manchado del exterior, este es almacenado en todos los conjuntos, ya que por defecto tiene todas las manchas y a medida que se va limpiando, es eliminado del conjunto correspondiente.
STR, en mayúsuculas, es una clase que envuelve a la clase str, en minúsculas, provista por Python. Ya que en Python los strings son inmutables, siempre que se procesa un string, por ejemplo cuando se obtiene un substring, se crea un nuevo objeto. Esta clase es necesaria para sobreescribir los métodos que generan un nuevo string y así hacer que la mancha perdure a lo largo del flujo del programa. Por ejemplo:
Si a está manchada, y b está limpia. El string que se obtiene de concatenarlos, también está manchado. Lo mismo sucede con otras operaciones sobre strings: multiplicación, rebanada, formateo, métodos como upper que convierten el string a mayúsculas, o split que devuelve una lista de strings. Por ejemplo, si en este último caso 'a' sería un string de palabras separadas por coma, esta llamada al método split devolvería una lista de palabras y cada una estaría manchada.
Los siguientes elementos a mostrar son los decoradores necesarios para que los elementos del programa manchen variables, las limpien o se alarmen.
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 son marcados como manchados por todos los tipos de machas.
Si se tiene acceso a la definición de la función o método, se puede utilizar la sintáxis provista por Python para aplicar el decorador.
Al usar módulos de terceros, podemos aplicar de todas formas el decorador. Este ejemplo es de un programa que utiliza el framework web.py.
Algunos frameworks funcionan de una forma diferente: le piden al programador que escriba cierta función o método de forma tal que luego el framework las utiliza para pasarle como parámetro al programa de usuario los valores recibidos desde el exterior. El framework para hacer aplicaciones de red, Twisted, provee un claro ejemplo cuando se extiende la clase LineOnlyReceiver. Nos pide sobreescribir el método lineReceived, el cual se ejecutará cada vez que se recibe una cadena desde el exterior.
En estos casos, utilizar el decorador untrusted es engorroso o incluso imposible. En su lugar se debe utilizar el decorador untrusted_args, que recibe 2 argumentos opcionales: una lista de posiciones de argumentos no confiables y una lista de argumentos de palabra clave.
En el ejemplo se indica que el argumento de posición 1 no es confiable, y debe ser mancado como tal. Entonces cuando se ejecute el método, el valor recibido será manchado.
Tanto para el decorador untrusted como untrusted_args, se utiliza la siguiente regla para marcar valores:
- Si el valor a manchar es un string, se utiliza para crear una instancia de STR y este objeto es guardado en TAINTED.
- Si el valor es una lista. Se aplica este algoritmo a todos los elementos de la lista y se retorna una nueva lista con el resultado de cada aplicación.
- Si el valor es un diccionario. Se aplica este algoritmo a todos los valores del diccionario y se retorna un nuevo diccionario con las claves originales y los resultados de cada aplicaicón como valores.
Una vez que los valores manchados ingresan en el programa, fluirán a lo largo del mismo y en algún momento, puede ser que se encuentren con una función limpiadora como esta. texto_plano elimina las marcas html de un string.
cleaner es un decorador utilizado para indicar que un método o función tiene la habilidad de limpiar manchas en un valor. Se debe indicar mediante un parámetro el tipo de mancha que la función es capás de limpiar. Como con los otros decoradores, tenemos dos formas de aplicarlo: dependiendo de si tenemos acceso o no a la definición de la función limpiadora.
Funciona de la siguiente forma: si la función texto_plano decorada se aplica al primer ejemplo, nada cambia. Como el valor retornado es distinto al argumento, un proceso de limpieza fue llevado acabo. El valor argumento sigue en la estructura de datos TAINTED.
En cambio, si es aplicada al segundo ejemplo, el valor argumento es igual al retornado, no se limpió nada por que no se requería limpiar nada. El valor original, presumido manchado, en realidad no estaba manchado (para XSS) y por eso es removido del conjunto correspondiente.
Los últimos elementos a marcar en el programa son los sumideros sensibles.
Se utiliza el decorador ssink para marcar aquellas funciones o métodos que no queremos sean alcanzadas por valores manchados.
La función eval de Python es un típico ej. de sumidero sensible a ataques de Interpreter Injection, ya que interpreta ciegamente el string que recibe como argumento.
Supongamos q nuestro programa es una calculadora e implementamos la funcionalidad de sumar 2 números evaluando el string formado por: un primer valor recibido del exterior, el signo más y un segundo valor recibido desde el exterior. Si los valores no son apropiadamente validados, un atacante, utilizando nuestra calculadora, podría leer el contenido de archivos locales, borrarlos, colgar el programa o cualquier otra cosa que se pueda hacer desde el intérprete de Python.
Aplicando el decorador a eval podemos detectar cuando esta función es utilizada con un valor no confiable como parámetro. Otra forma de lograr el mismo efecto en el ejemplo sería aplicando el decorador a la función suma en lugar de a eval.
El segundo ejemplo también es tomado del framework web.py y marca como sumideros sensibles a SQL Injection los métodos delete, select e insert del objeto que accede a la base de datos.
Con las fuentes inseguras, las funciones limpiadoras y los sumideros sensibles marcados en el programa, podemos ejecutarlo y utilizarlo para ir obteniendo las advertencias sobre posibles problemas de seguridad. Se pueden obtener mensajes de este tipo por la salida estándar, escribir en un archivo o daler cualquier otro destino.
Algunas otras cosas que podemos encontrar con/en el módulo:
- una batería de más de 80 tests que prueban su funcionalidad
- la posibilidad de definir la función que se ejecutará cuando una variable manchada con X alcanza un sumidero sensible a X
- la posibilidad de establecer una bandera que indique si la ejecución se debe cortar o no cuando una variable manchada con X alcanza un sumidero sensible a X
- un nuevo decorador, validator, para marcar funciones que retornan un valor booleano indicando si una variable puede usarse o no
- funciones auxiliares para manchar un valor o para testear si un valor está manchado
Último slide de la charla. Todo lo que mostré hasta ahora es un experimento. Puede haber errores u omisiones, de hecho mientras lo fui desarrollando, al probarlo con aplicaciones reales encontraba errores en la implementación o situaciones en los que no podía aplicarlo, lo que me obligaba a darle una vuelta más de rosca a la implementación: así surgió por ejemplo el decorador untrusted_args.
La pregunta ahora es cómo seguimos, y el objetivo de la charla era justamente presentar esta idea entre desarrolladores Python para continuarla en conjunto. Hay muchas que podemos pensar, desde detalles de implementación hasta casos de uso que se pueden lograr, integración con frameworks de desarrollo para que alguien que se sienta a codificar un sistema tenga facilitada la aplicación de esta herramienta.
Por ejemplo, para web.py sería relativamente sencillo crear un parche para que por defecto los valores recibidos estén manchados. En Django hice el ejercicio de escribir un middleware que haga ese trabajo. Y así, es necesario ir probando cómo se puede adaptar el módulo a herramientas de desarrollo ya conocidas.
Hace poco cree una lista de correos en Google Groups para charlar estos temas, así que los que estén interesados en discutir al respecto y desarrollar esta herramienta, pueden suscribirse.