Guía rápida de buenas prácticas de seguridad en Python
Frank Fischer
27 de septiembre de 2021
0 minutos de lectura
En 2019, Snyk publicó su primera guía rápida de Python. Desde entonces, muchos aspectos de la seguridad en Python han cambiado. Con base en lo que hemos aprendido como empresa de seguridad para desarrolladores y en las buenas prácticas específicas de Python, preparamos esta guía actualizada para ayudarte a mantener seguro tu código Python. Y antes de continuar, quiero agradecer especialmente a Chibo y Daniel por su ayuda con esta guía.
Descarga la guía rápida de buenas prácticas de seguridad en Python de 2021
Estos son los consejos de seguridad en Python que veremos:
Siempre depura los datos externos
Analiza tu código
Ten cuidado al descargar paquetes
Revisa las licencias de tus dependencias
No uses la versión de Python que viene con el sistema
Aprovecha los entornos virtuales de Python
Establece
DEBUG = Falseen producciónTen cuidado al dar formato a las cadenas de texto
Serializa y deserializa con mucha precaución
Usa anotaciones de tipos en Python
Una nota rápida antes de empezar. Es importante mencionar que los datos de Snyk sobre el ecosistema de Python, al igual que las investigaciones académicas, muestran que Python no es ni más ni menos seguro que otros lenguajes de uso generalizado. Esta guía está dirigida específicamente a quienes usan Python. Te recomendamos consultar todas nuestras otras guías rápidas de seguridad para aprender a mantenerte a salvo en otros ecosistemas.
1. Siempre depura los datos externos
Los datos externos son un posible vector de ataque para cualquier aplicación y pueden utilizarse para ataques de inyección, XSS o denegación de servicio (DOS). Una regla general para mantener la seguridad en Python es depurar siempre los datos de fuentes externas (eliminar la información confidencial), ya sea que provengan de un formulario de entrada del usuario, del rastreo de un sitio web o de una solicitud a una base de datos. Además, depura los datos en cuanto ingresen a la aplicación para evitar un manejo inseguro. Así reduces el riesgo de que tu aplicación maneje accidentalmente datos confidenciales sin depurar.
Al depurar los datos, siempre es mejor verificar qué formato deberían tener los datos de entrada que intentar manejar las excepciones. También recomendamos usar bibliotecas con buen mantenimiento para depurar datos. Aquí tienes dos:
schema es “una biblioteca para validar estructuras de datos de Python, como las que se obtienen de archivos de configuración, formularios, servicios externos o análisis de la línea de comandos, y se convierten de JSON/YAML (u otro formato) a tipos de datos de Python”.
bleach es “una biblioteca de depuración de HTML basada en una lista de elementos permitidos que escapa o elimina etiquetas y atributos”.
Los principales frameworks incluyen sus propias funciones de depuración, como flask.escape() de Flask o django.utils.html.escape() de Django. El objetivo de estas funciones es protegerte de entradas HTML potencialmente maliciosas, como:
La limitación de este enfoque es que las bibliotecas no sirven para todo. Están especializadas en su propio ámbito. Ten en cuenta que, al final de esta publicación, encontrarás más información sobre cómo trabajar con otros formatos de datos, como XML, que también pueden contener datos maliciosos.
Otra opción común es dejar que los motores de plantillas, como Jinja, se encarguen de mostrar el HTML. Ofrece muchas funciones, entre ellas el escape automático para prevenir ataques XSS mediante MarkupSafe.
Otro aspecto de la depuración es evitar que los datos se usen como comandos. Un ejemplo típico es la inyección SQL. En lugar de concatenar cadenas y variables para generar una consulta SQL, es recomendable usar parámetros con nombre para indicar a la base de datos qué debe tratar como un comando y qué como datos.
O, mejor aún, usa el mapeo objeto-relacional (ORM), como sqlalchemy, para que la consulta del ejemplo quede así:
Así obtienes código más fácil de leer, además de optimizaciones de ORM como el almacenamiento en caché, y también más seguridad y rendimiento.
Si quieres saber más, consulta nuestra guía rápida sobre inyección SQL.
2. Analiza tu código
Los desarrolladores tienen a su disposición una amplia variedad de herramientas de análisis estático de código para mantener la seguridad en Python. Veamos tres niveles distintos de herramientas.
Primero, el nivel de los linters. PEP8 ha servido durante décadas como guía de estilo para Python. Hay varias herramientas disponibles (y algunas integradas en los IDE) para comprobar el cumplimiento de esta guía, como pep8, pylint, flake8 y otras.
A continuación, herramientas como bandit transforman el código en un árbol de sintaxis abstracta (AST) y ejecutan consultas para detectar problemas de seguridad comunes. Esto va un paso más allá de lo que hacen los linters típicos, que analizan el código a nivel sintáctico. Aun así, bandit tiene limitaciones debido a su representación intermedia y rendimiento. Por ejemplo, no puede detectar problemas relacionados con el flujo de datos (conocidos como análisis de contaminación), que pueden provocar fallas graves, como inyecciones SQL o ataques XSS.
Por último, las herramientas de pruebas estáticas de seguridad de aplicaciones (SAST), como Snyk Code, realizan un análisis semántico y tienen en cuenta incluso problemas complejos entre archivos. A diferencia de otras herramientas de este nivel, Snyk Code está pensado para desarrolladores: analiza el código rápidamente y se integra en el IDE (o directamente en la línea de comandos). Snyk Code explica sus hallazgos de gran precisión y ofrece ayuda, incluidos ejemplos para corregir tus problemas de seguridad en Python. Además, es fácil comenzar a usarlo y es gratis para proyectos de código abierto (además de una cantidad limitada de pruebas de proyectos que no son de código abierto).
3. Ten cuidado al descargar paquetes
Instalar paquetes es fácil, pero también es una forma sencilla de introducir vulnerabilidades de seguridad en Python. Por lo general, los desarrolladores usan el instalador estándar de paquetes de Python (pip), que utiliza el Python Package Index (PyPI). Por eso es importante entender cómo se agregan paquetes a PyPI.
PyPI tiene un procedimiento para reportar problemas de seguridad. Si alguien reporta un paquete malicioso o un problema en PyPI, se atiende el reporte, pero los paquetes agregados a PyPI no se revisan; esperar eso de las personas voluntarias que mantienen PyPI no sería realista.
Por lo tanto, es prudente asumir que hay paquetes maliciosos en PyPI y actuar en consecuencia. Algunas medidas razonables son investigar un poco el paquete que quieres instalar y escribir su nombre con cuidado (un paquete con un nombre que contenga un error ortográfico común de un paquete popular podría ejecutar código malicioso). Antes de descargar un paquete, asegúrate de revisarlo en Snyk Advisor.

Con una búsqueda rápida de un paquete en Snyk Advisor puedes obtener mucha información sobre él, el respaldo de la comunidad, su historial de errores y correcciones, y mucho más. Snyk Advisor también muestra el comando de instalación en la parte superior de la página de resultados. Una buena práctica es copiar y pegar el nombre tal como aparece para evitar el secuestro de nombres por errores tipográficos. Snyk Advisor te ayuda a decidir si puedes confiar en un paquete. Puedes consultar su historial de problemas de seguridad y cuánto tiempo tardaron en corregirse.
Otra buena práctica es usar entornos virtuales para aislar los proyectos. También puedes usar pip freeze o un comando similar para registrar los cambios del entorno en la lista de requisitos.
Para mantener las referencias actualizadas, Snyk Open Source se basa en una base de datos de vulnerabilidades líder en la industria, que registra problemas de seguridad y posibles correcciones. Snyk Open Source analiza los requisitos y proporciona información práctica sobre las vulnerabilidades detectadas en las dependencias directas y transitivas, y te ayuda a corregirlas de inmediato.
4. Revisa las licencias de tus dependencias
Al considerar el uso de un proyecto de código abierto, es importante entender qué licencias lo rigen. Los proyectos de código abierto son gratuitos y están disponibles para usarse, pero pueden estar sujetos a términos y condiciones. Estos suelen especificar cómo se usa el software, si debes hacer públicos los cambios que realices y otros requisitos similares. Familiarízate con las licencias de código abierto de los proyectos que usas para asegurarte de no exponerte a riesgos legales.
Si el proyecto adopta una licencia más restrictiva de lo que esperabas (GPL, SSPL, etc.), podrías quedar en una situación difícil: tendrías que cumplir con los términos de la licencia o dejar de usar el proyecto. Además, si necesitas modificar un proyecto que no tiene licencia, podrías infringir las leyes de derechos de autor.
Para garantizar que tu proyecto sea sostenible y evitar exponerte a riesgos legales y de seguridad innecesarios en Python, analiza y corrige los problemas de licencias y vulnerabilidades en las dependencias de tu proyecto.
Snyk Open Source puede ayudarte a gestionar el cumplimiento de las licencias de código abierto. Te ofrece una forma fácil de usar para desarrolladores de obtener visibilidad integral y, al mismo tiempo, una gobernanza flexible.
5. No uses la versión de Python que viene con el sistema
La mayoría de los sistemas POSIX vienen con una versión de Python preinstalada. El problema con la mayoría de las distribuciones de Python incluidas es que no están actualizadas.
Por eso, asegúrate de usar la versión más reciente de Python disponible para tu sistema, así como los contenedores oficiales diseñados para ejecutar Python, y mantenlos actualizados. Snyk puede ayudarte: analiza tus contenedores para encontrar las actualizaciones necesarias con Snyk Container y revisa tus dependencias con Snyk Open Source.
6. Usa entornos virtuales de Python
Python permite organizar el desarrollo de aplicaciones en entornos virtuales. Un entorno virtual aísla el intérprete de Python, las bibliotecas y los scripts que se instalan en él. Esto significa que, en lugar de usar una versión global de Python y dependencias globales para todos tus proyectos, puedes crear entornos virtuales específicos para cada proyecto y usar versiones propias de Python y sus dependencias.
La mayoría de los IDE, las interfaces de línea de comandos y los paneles, como Anaconda Navigator, incluyen funciones para cambiar entre entornos virtuales.
Consejo profesional: A partir de Python 3.5, se recomienda usar venv. En la versión 3.6, pyvenv quedó obsoleto.
Los entornos virtuales facilitan el desarrollo, el empaquetado y la distribución de aplicaciones seguras en Python. Se recomienda ampliamente usarlos. Consulta la documentación de Python sobre venv para obtener más detalles.
7. Establece DEBUG = False en producción
En un entorno de desarrollo, es lógico mostrar mensajes de error detallados. En producción, en cambio, debes evitar filtrar información que pueda ayudar a un atacante a conocer más sobre tu entorno, tus bibliotecas o tu código.
La mayoría de los frameworks tienen la depuración activada de forma predeterminada. Por ejemplo, Django la habilita en settings.py. Asegúrate de establecerla en False en producción para evitar que se filtre información confidencial de la aplicación a los atacantes.
Consejo profesional: Al implementar en producción, conviene que tu sistema de implementación continua verifique que esta opción esté desactivada después de la implementación.
8. Ten cuidado al dar formato a las cadenas de texto
A pesar de la idea de Python de que hay una sola manera de hacer las cosas, en realidad existen cuatro formas distintas de dar formato a las cadenas de texto (tres en las versiones anteriores a Python 3.6).
El formato de cadenas de texto se ha vuelto cada vez más flexible y potente (las cadenas f son especialmente interesantes), pero cuanto mayor es la flexibilidad, mayor es también el potencial de explotación. Por eso, quienes usan Python deben tener cuidado al dar formato a cadenas que incluyen datos proporcionados por el usuario.
Python incluye un módulo llamado string. Este módulo contiene la clase Template, que se usa para crear cadenas de plantilla.
Considera el siguiente ejemplo.
Con el código anterior, la variable greeting se evalúa como: “Hello World, my name is Hayley.”
Este formato de cadena resulta un poco engorroso porque requiere una instrucción de importación y es menos flexible con los tipos. Tampoco evalúa instrucciones de Python como lo hacen las cadenas f. Estas limitaciones hacen que las cadenas de plantilla sean una excelente opción para trabajar con datos de entrada del usuario.
Una nota rápida más sobre el formato de cadenas: ten mucho cuidado con el SQL sin procesar, como mencionamos antes.
9. Serializa y deserializa con mucha cautela
Python incluye un mecanismo para serializar y deserializar objetos de Python llamado «pickling», que utiliza el módulo pickle. Se sabe que este mecanismo es inseguro, por lo que se recomienda usarlo con mucha cautela y solo con fuentes de datos confiables.
El nuevo estándar de facto para la serialización y deserialización es YAML. El paquete PyYAML ofrece un mecanismo para serializar tipos de datos personalizados a YAML y viceversa. Sin embargo, PyYAML está plagado de diversos vectores de ataque posibles. Una forma sencilla pero eficaz de proteger el uso de PyYAML es usar yaml.SafeLoader() en lugar de yaml.Loader() como cargador.
Esto impide cargar clases personalizadas, pero admite tipos estándar como hashes y arreglos.
Otro caso de uso común es XML. A menudo se usan bibliotecas estándar, pero son vulnerables a ataques habituales, como los ataques DoS o la expansión de entidades externas (se hace referencia a una fuente externa). Una buena primera línea de defensa es un paquete llamado defusedxml. Cuenta con medidas de protección contra estos problemas de seguridad habituales de XML.
Consejo adicional, no relacionado con la seguridad: usa anotaciones de tipos en Python
En la versión 3.5 se introdujeron las indicaciones de tipo. Aunque el entorno de ejecución de Python no hace cumplir las anotaciones de tipos, herramientas como los verificadores de tipos, los IDE, los linters, las herramientas SAST y otras pueden beneficiarse de una mayor precisión por parte del desarrollador. Aquí tienes un ejemplo que ilustra la idea:
Literal[...] se introdujo en la versión 3.8 y el entorno de ejecución no lo hace cumplir (en nuestro ejemplo, puedes pasar cualquier cadena), pero los verificadores de tipos ahora pueden detectar que el parámetro está fuera del conjunto permitido y advertirte. Es una función muy útil, no solo para la seguridad en Python.
Nota: Como el entorno de ejecución no las hace cumplir, las indicaciones de tipo tienen un uso limitado en materia de seguridad.
Empieza con Capture the Flag
Aprende a resolver desafíos de Capture the Flag viendo nuestro taller virtual introductorio a pedido.



