Python 2 vs. 3: diferencias de seguridad
10 de octubre de 2017
0 minutos de lecturaPython 3 y Python 2 tienen varias diferencias funcionales. Por sí solas, no son necesariamente mejores ni peores (aunque podría decirse que Python 3 debería ser una mejora), pero cualquier cambio puede introducir riesgos. En esta publicación destacamos y explicamos algunas diferencias entre las versiones que tienen implicaciones de seguridad. Tenlas en cuenta al migrar aplicaciones de Python entre versiones.
Estado actual de las versiones 2 y 3 de Python
Python 3 se lanzó por primera vez en 2008. Un problema que afectó su adopción fue que no era compatible con versiones anteriores. Muchos desarrolladores continuaron trabajando con versiones previas, principalmente la versión 2, para evitar el esfuerzo de refactorizar sus aplicaciones. Por este motivo, se mantuvo el soporte para Python 2 y siguió en desarrollo activo después del lanzamiento de la versión 3.0.
La versión 2.7 es la versión actual y final de Python 2, que muchos desarrolladores todavía usan activamente. La versión 2.7 estuvo disponible en 2010 y la Python Foundation anunció que llegaría al fin de su vida útil en 2020.
Python 3.6 es la versión actual de Python y estuvo disponible por primera vez en 2016. Python 3 se considera el futuro del lenguaje de programación Python.
Python 2 vs. 3: diferencias clave en el lenguaje de programación y sus implicaciones de seguridad
A continuación, se destacan las principales diferencias entre Python 2 y Python 3. Aunque ninguno de estos cambios está orientado a la seguridad, cada uno tiene implicaciones de seguridad que debes tener en cuenta.
Encadenamiento de excepciones
Python 3 incorporó el encadenamiento de excepciones de forma predeterminada. En Python 2, cuando ocurre una excepción en una biblioteca a la que hace referencia el programa principal de Python, los detalles de esa excepción no se muestran en el seguimiento de pila de forma predeterminada. En Python 3, el seguimiento de pila muestra todos los detalles de las excepciones, tanto las que genera el programa principal como las que generan las bibliotecas a las que hace referencia.
Cualquier detalle interno de tu código o del comportamiento en tiempo de ejecución que se exponga al usuario representa un riesgo de seguridad. Aunque el encadenamiento de excepciones facilita la depuración, expone mucha más información a un posible atacante, que puede ejecutar un seguimiento de pila para ver cómo responde el software ante los errores.
Es posible desactivar las excepciones encadenadas en Python 3 si consideras que el riesgo supera los beneficios.
Método de entrada
Python 2 ofrece la función eval(), que se considera insegura porque evalúa el texto que se pasa como parámetro y acepta un segundo argumento opcional con valores globales que se usarán durante la evaluación. En Python 2, la función input() funciona de manera similar a eval() y también es insegura.
Los atacantes pueden aprovechar estas funciones al incluir nombres de variables en la entrada y obtener sus valores, que pueden contener información confidencial.
Por lo tanto, en Python 2, una práctica recomendada de seguridad es no usar eval() ni input(), sino raw_input(), que no evalúa las variables del texto de entrada.
¿Qué cambió en Python 3? Se eliminó raw_input() y su funcionalidad se transfirió a una nueva función llamada input(). La nueva función input() funciona igual que raw_input() en Python 2, por lo que ahora es seguro usarla.
Sin embargo, si ejecutas un programa de Python 3 en Python 2, se pueden aprovechar todas las entradas.
En resumen:
Función | Python 2.x | Python 3.x |
|---|---|---|
| Evalúa variables: inseguro | Evalúa variables: inseguro |
| Evalúa variables: inseguro | No evalúa variables, como |
| No evalúa variables | Se eliminó |
División de enteros
Python 2 trata la división de enteros de manera distinta y menos intuitiva que Python 3. En Python 2, la división de enteros siempre devuelve un valor entero, que es el entero más grande menor o igual que el resultado. Este método se conoce como división por piso:
La división de enteros tiene más sentido en Python 3 porque el lenguaje no aplica automáticamente la división por piso al dividir enteros:
Esta forma más intuitiva de manejar la división de enteros no es compatible con versiones anteriores de Python 2. Por lo tanto, es especialmente peligroso ejecutar código de Python 3 en Python 2, porque el comportamiento de la división de enteros no genera un error de sintaxis.
Esto también puede crear un problema de seguridad. Por ejemplo, imagina una aplicación que solicita un PIN numérico y luego divide el PIN almacenado en el sistema por el que proporciona el usuario para indicar si es correcto. En Python 3, funcionaría bien, pero ejecutar la misma aplicación en Python 2 permitiría realizar muchos intentos fallidos.
Aunque este ejemplo no es realista, ilustra cómo depender de la división de enteros para redondear números (en Python 2) o no redondearlos (en Python 3) puede cambiar el comportamiento, y los atacantes podrían aprovecharlo para eludir mecanismos de protección.
Compatibilidad con Unicode
Ambas versiones de Python manejan las cadenas (secuencias de caracteres) de manera diferente.
Python 2 usa de forma predeterminada la codificación ASCII. ASCII se limita a representar 256 caracteres. Esto limita la flexibilidad de Python para codificar caracteres, en particular los que no son estándar.
Usar Unicode en Python 2 requiere sintaxis adicional. Por ejemplo, al usar print, debes incluir el texto de entrada en la función unicode() para manejar caracteres especiales.
El estándar Unicode es mucho más versátil: admite más de 128 000 caracteres. En Python 3, Unicode es la opción predeterminada. No necesitas sintaxis adicional para definir valores Unicode: se imprimen automáticamente como cadenas utf-8.
Aunque es potente, este cambio puede generar un riesgo considerable de phishing. Por ejemplo, es posible dirigir a los usuarios a una URL en la que los caracteres del nombre de dominio se reemplazaron por letras de alfabetos no ingleses. Una publicación de Xudong Zheng mostró cómo se vería esto para los usuarios:

A primera vista, la URL parece correcta, pero no todo es lo que parece. Aunque la URL del ejemplo de Xudong parece ser “apple.com”, en realidad es “xn-pple-43d.com”. Por ejemplo, al usar la “a” cirílica (U+0430) en lugar de la “a” ASCII (U+0061), puedes hacer que algunos navegadores oculten involuntariamente el dominio real. Así, aunque el sitio parezca seguro, el usuario llega a un dominio diferente que puede presentar riesgos de seguridad. Esto se conoce como ataque homógrafo.
Este ataque podría ser relevante en Python si se dirige a una ruta de directorio que analiza la aplicación Python o a un parámetro de consulta. Por ejemplo, si la aplicación expone una ruta http://domain.com/Acme, un atacante podría reemplazar las letras “Acme” por caracteres de otros alfabetos y dirigir a los usuarios a otra cuenta.
Al migrar una aplicación de Python 2 a 3, debes tener en cuenta que todas las entradas se mostrarán como Unicode, lo que podría abrir una vía para que los atacantes engañen a tus usuarios.
Vulnerabilidades y correcciones de seguridad en Python 3.6 vs. 2.7
Las versiones históricas de Python 2 y 3 tuvieron vulnerabilidades de seguridad graves y deben evitarse (consulta CVE Details para ver las versiones de Python).
Sin embargo, en las versiones más recientes, 2.7.x y 3.6.x, se corrigieron la mayoría de las vulnerabilidades. Python 2 y 3 reciben mantenimiento activo actualmente y, mientras actualices a la versión más reciente, estarás protegido contra las principales vulnerabilidades. Dicho esto, es probable que Python Software Foundation dedique menos atención a Python 2 y que reciba actualizaciones de seguridad con menor frecuencia que Python 3.
Lo más importante es que Python 2 llegará al fin de su vida útil en 2020. A partir de ese momento, el lenguaje dejará de recibir mantenimiento y ya no habrá correcciones de seguridad. Si sigues usando Python 2, ten en cuenta que desde 2020 el lenguaje será inseguro y deberías empezar a planificar tu migración a Python 3.
Diferentes grafos de dependencias, diferentes bibliotecas vulnerables
La falta de compatibilidad con versiones anteriores en Python 3 hizo que pip, el administrador de paquetes principal de Python, también tuviera que crear una rama 2.x y otra 3.x, además de registrar qué paquetes admiten cada versión. Como resultado, migrar una aplicación suele requerir cambiar las bibliotecas que utiliza o, al menos, sus versiones.
Estas bibliotecas, como todo software, no son perfectas y, de vez en cuando, se descubren vulnerabilidades. Una vez que se divulgan, los propietarios de las aplicaciones deben apresurarse a corregirlas antes de que los atacantes las aprovechen; por ejemplo, Equifax perdió estrepitosamente esa carrera.
A menudo, las correcciones de vulnerabilidades no se adaptan a todas las versiones posibles, por lo que es fundamental incluir pruebas para detectar vulnerabilidades conocidas como parte del proceso de migración. Si por algún motivo sigues usando Python 2, el riesgo de vulnerabilidades es aún mayor, ya que cada vez más bibliotecas dejan de admitir la versión anterior, incluidas las correcciones de vulnerabilidades.
Independientemente de la versión de Python que decidas usar, puedes probar tu aplicación para detectar vulnerabilidades conocidas con Snyk mediante nuestra integración con GitHub, interfaz de línea de comandos u otras plataformas integradas.


