Los peligros de assert en Python
Dhruv Patel
18 de agosto de 2022
0 minutos de lecturaHay muchas formas de encontrar errores en el código de Python: el depurador integrado (pdb), una buena cantidad de pruebas unitarias, un depurador en un IDE como Pycharm o Visual Studio, instrucciones try/catch, instrucciones if/else, assert statements o la práctica confiable de cubrir cada rincón de tu código con instrucciones print(), como si fueran a pasar de moda.
Las instrucciones assert pueden ayudarnos a detectar errores rápidamente y son mucho menos intrusivas que una cantidad excesiva de instrucciones print. Sin embargo, a diferencia de las instrucciones print, ¡usar instrucciones assert puede ser inesperadamente riesgoso!
Este artículo explora cómo usar assert de forma segura y qué hace que su uso no sea seguro. Al terminar, sabrás cómo usar assert de manera óptima sin exponerte inadvertidamente a problemas de seguridad.
Uso de instrucciones assert en Python
¡Usar assert es fácil! Las instrucciones assert son instrucciones simples, lo que significa que pueden escribirse en una sola línea de código Python.
Este es un ejemplo sencillo de una instrucción assert:
En la instrucción anterior, afirmas que la expresión se evalúa como True, de forma similar a una comprobación booleana.
Si la expresión es False, se genera una excepción. Por ejemplo:
Para facilitar la lectura del seguimiento de la pila, puedes especificar un mensaje de aserción que se incluirá como parte de AssertionError. Esto puede ser útil en situaciones complejas de depuración.
La variable integrada de Python __debug__ debe estar configurada en True para usar instrucciones assert. Si está configurada en False, las instrucciones assert no se ejecutarán durante el tiempo de ejecución. Esto es un problema grave porque ejecutar Python en modo optimizado (algo común en entornos de producción, al configurar la marca de línea de comandos PYTHONOPTIMIZE, -O) establece la variable __debug__ en False, lo que desactiva las instrucciones assert y puede introducir vulnerabilidades inadvertidamente en tu proyecto. A continuación, lo veremos con más detalle.
Cuando se usa correctamente, assert es una herramienta potente para depurar. Sin embargo, cuanto más complejo sea su uso, mayor será el riesgo de introducir vulnerabilidades de seguridad durante el desarrollo.
En resumen: las instrucciones assert son muy útiles para depurar aplicaciones Python, pero no se pueden usar para el manejo habitual de errores ni para controlar el flujo porque pueden:
Detener la aplicación al generar una excepción, lo que puede causar interrupciones del servicio en producción.
Provocar efectos secundarios no deseados (que podrían incluir vulnerabilidades) en tu aplicación cuando el modo de depuración está desactivado y el entorno de ejecución de Python omite las instrucciones assert sin avisar.
Uso seguro de assert
Veamos cómo usar instrucciones de aserción para depurar un programa. Para ello, analizaremos una aplicación de ejemplo que calcula el interés simple.
Primero, instala una versión estable de Python. Luego, abre una carpeta y crea un archivo Python llamado safe_assert_example.py. Puedes usar cualquier editor de código compatible.
Agrega el siguiente código a safe_assert_example.py:
Este programa define la función simpleInterest y calcula el interés simple usando el capital, el tiempo y la tasa.
Para calcular correctamente el interés, debes ingresar números enteros positivos para el capital, el tiempo y la tasa. En este ejemplo se usan instrucciones assert para comprobar que los valores p, t y r sean números enteros positivos. El programa devuelve un mensaje de error informativo cuando un valor no es un número entero positivo.
¡Es hora de probarlo! Ejecuta el programa con el siguiente comando:
Deberías obtener el resultado que se muestra abajo, con un interés simple de 1.2:
Como puedes ver, el programa se ejecuta sin problemas y no devuelve ningún mensaje de error.
Ahora, mira qué sucede cuando intentas ejecutar este programa con una entrada no válida. Cambia el valor de t de 5 a la cadena "time":
Ahora, vuelve a ejecutar el programa con este comando:
Esta vez, deberías ver el siguiente error:
Este resultado incluye un AssertionError. Esta excepción muestra que la instrucción assert funciona según lo previsto, ya que detectó correctamente el argumento no válido de la función.
En este ejemplo, la aserción nos ayudó a depurar el programa y detuvo su ejecución en cuanto falló.
Recuerda que ejecutar el programa en modo optimizado (configurando la marca de línea de comandos PYTHONOPTIMIZE, -O) desactiva las instrucciones assert sin avisar. Ahora, mira cómo afecta este comportamiento a la aplicación.
Mantén "`time=" como valor de t y ejecuta el programa de interés simple en modo optimizado con el siguiente comando:
Esto devuelve:
Observa que, aunque el programa tiene un argumento de función no válido, el resultado anterior no incluye el AssertionError esperado. En cambio, Python muestra un TypeError, ya que intentaste multiplicar variables de tipo entero y cadena. Esto sucede porque las instrucciones assert no se ejecutaron y sugiere que deberíamos considerar alternativas como:
Agregar anotaciones de tipo para ayudar a detectar llamadas incorrectas a
simpleInterestantes de ejecutar el código.Usar instrucciones
ifen lugar de aserciones para comprobar los tipos durante el tiempo de ejecución.
Uso no seguro de assert
assert solo debe usarse para pruebas y depuración, no en entornos de producción. Como las instrucciones assert solo se ejecutan cuando la variable __debug__ está configurada en True, el intérprete de Python puede desactivarlas.
Como se demostró en el ejemplo anterior, ejecutar Python en modo optimizado con -O impide que funcionen las instrucciones assert, ya que configura __debug__ en False.
Por eso, no es seguro usar instrucciones assert para nada que deba ejecutarse en un entorno de producción. Por ejemplo, nunca debes usarlas para validar tokens ni entradas de usuario. Estas instrucciones no se ejecutarán en producción, así que no se validarán los tokens ni las entradas de usuario, lo que podría introducir vulnerabilidades en tu aplicación.
Para destacar la gravedad de usar assert para la validación en un entorno de producción, considera una función de autorización de usuarios que recibe un arreglo de usuarios y les permite avanzar según su nivel de acceso. Primero, el programa usa assert para verificar que la lista o el arreglo no sea nulo y que sea una instancia de una lista. Luego, según el rol, permite que el administrador tenga acceso completo a la aplicación. Si encuentra un rol de administrador en la lista de usuarios, no se genera ningún error de aserción. Pero si no encuentra ningún rol de administrador en la lista, se genera un error.
Luego, pega este código en un archivo llamado unsafe_assert_example.py:
La función authorize_admin_user comprueba los roles del usuario. Si es administrador, indica que tiene acceso completo a la aplicación. Por lo general, los roles del usuario se cargan desde la base de datos; si uno de ellos es de administrador, se le concede acceso completo a la aplicación. De lo contrario, la instrucción assert falla y genera una excepción.
La última línea del archivo llama a la función authorize_admin_user y le pasa una lista con dos roles: admin y user.
Como era de esperarse, al usuario se le concede acceso completo a la aplicación.
Llamemos a la función de roles de usuario sin incluir un rol de administrador en la lista. Cambia la última línea de código de unsafe_assert_example.py por:
Luego, ejecuta el programa y observa el resultado:
Como era de esperarse, la función genera un AssertionError porque el rol de administrador no estaba en la lista de roles.
Ahora, ejecuta el mismo programa con la salida optimizada:
El resultado será como el de la imagen de abajo. Aunque no había ningún rol admin en la lista, la autorización de administrador se aprobó porque la instrucción assert no se ejecutó.
Además, cambiar la llamada a la función por una lista de números enteros y ejecutarla con la misma salida optimizada también demuestra que assert solo debe usarse para depurar, no para validar.
Cambia la llamada a la función por authorize_admin_user([1,2]) y ejecútala con el mismo comando: python -O unsafe_assert_example.py.
El resultado del programa sigue siendo el mismo.
En nuestro ejemplo de la calculadora de interés simple, las consecuencias fueron menores cuando no se ejecutaron las instrucciones assert. Aunque no recibimos el mensaje de error que esperábamos, pudimos usar el mensaje de error de Python para entender qué salió mal. Más importante aún, no introdujimos vulnerabilidades porque usamos las instrucciones assert con fines seguros de depuración.
En este ejemplo, usamos assert de forma insegura y volvimos vulnerable nuestra aplicación Python al conceder acceso de administrador a un usuario que no era administrador.
Si quieres probar estos ejemplos más a fondo, puedes encontrar todo el código de este artículo en este repositorio.
Qué hacer y qué evitar al usar assert
Assert ayuda a los desarrolladores a depurar código Python fácilmente y a escribir menos líneas de código. Sin embargo, si no se usa de forma segura, puede introducir problemas de seguridad en tus aplicaciones sin que te des cuenta.
Desactivar assert en un entorno de producción puede tener consecuencias graves. Esta práctica puede introducir varias puertas traseras y puntos de interrupción en la aplicación que los actores maliciosos pueden aprovechar.
La función principal de assert debe ser aislar la causa raíz de un error; nunca debe usarse para el manejo habitual de errores. Aunque las aserciones pueden ser útiles, es fundamental usarlas para depuración y pruebas, no para controlar el flujo lógico del programa o la aplicación, y limitar su uso a situaciones seguras. Si buscas una alternativa a assert, las pruebas unitarias, los depuradores, las condicionales y los validadores son opciones mejores y más seguras.
Ahora que sabes cómo usar assert de forma segura, lee la guía rápida de buenas prácticas de Python para aprender más sobre cómo mantener tus aplicaciones seguras en todas tus plataformas y entornos.
Más recursos sobre desarrollo y seguridad en Python
También puedes probar nuestra herramienta gratuita en línea de revisión de código Python para ver cómo Snyk Code analiza tu código en busca de problemas de seguridad y calidad.
Comienza con Capture the Flag
Aprende a resolver desafíos de Capture the Flag con nuestro taller virtual 101 a pedido.
