Skip to main content

Cómo proteger aplicaciones Flask de Python

Escrito por
Headshot of Gourav Singh Bais

Gourav Singh Bais

blog feature playwright tests

21 de mayo de 2024

0 minutos de lectura

Flask es un framework web potente, ligero y versátil para Python, diseñado para que los desarrolladores puedan crear aplicaciones web rápidamente y con un mínimo de código repetitivo. Es un microframework independiente que no necesita bibliotecas ni herramientas adicionales y no cuenta con una capa de abstracción de bases de datos.

Flask incluye funciones como enrutamiento, renderizado de plantillas y manejo de solicitudes, y sigue el estándar Web Server Gateway Interface (WSGI), conocido por su simplicidad y flexibilidad.

Por desgracia, como cualquier otro framework web, Flask es vulnerable si no se protege adecuadamente. Los riesgos de seguridad más comunes para Flask incluyen el cross-site scripting (XSS), la falsificación de solicitudes entre sitios (CSRF) y la inyección SQL.

En Snyk Vulnerability Database puedes ver algunas de las muchas vulnerabilidades de seguridad encontradas en la biblioteca Flask de Python:

Algunas de las muchas vulnerabilidades de seguridad encontradas en la biblioteca Flask de Python en Snyk Vulnerability Database.

En este artículo, conocerás algunas prácticas recomendadas para proteger aplicaciones de Python creadas con el framework web Flask. Primero, revisarás algunos ejemplos de configuraciones inseguras y luego aprenderás a mitigar y corregir los problemas.

¿Qué es el estándar de interfaz de puerta de enlace del servidor web (WSGI)?

WSGI ofrece una forma estandarizada para que los servidores web se comuniquen con aplicaciones web escritas en Python. Es una capa intermediaria que permite a los servidores web enviar solicitudes a una aplicación o framework web y, luego, entregar las respuestas al cliente.

¿Por qué es importante WSGI?

Antes de WSGI, los frameworks y servidores web de Python solían estar estrechamente vinculados; esto significaba que un framework específico solo podía ejecutarse en determinados servidores web. WSGI eliminó esta limitación y permitió una mayor flexibilidad e interoperabilidad.

¿En qué se parece WSGI a conceptos similares de otros lenguajes?

WSGI tiene un propósito similar al de la API Java Servlet en Java o Rack en Ruby. Estas tecnologías abstraen los detalles de las solicitudes y respuestas HTTP, lo que permite a los desarrolladores enfocarse en escribir la lógica de las aplicaciones web en lugar de lidiar con la comunicación subyacente del servidor.

Configuraciones inseguras en aplicaciones Flask de Python

A continuación, se muestran algunos de los problemas de configuración insegura más comunes que afectan a las aplicaciones Flask de Python y que los desarrolladores deben tener muy presentes para evitar repetir estos errores en sus bases de código:

Exposición de claves secretas

La divulgación accidental de una clave secreta es un fallo de seguridad común que implica información confidencial, como la clave secreta de Flask, las claves de API y las contraseñas, dentro del código fuente. Integrar datos confidenciales directamente en el código fuente representa un riesgo de seguridad considerable, ya que actores maliciosos pueden aprovechar esta vulnerabilidad para obtener acceso no autorizado a varios servicios.

Si codificaste por error la clave secreta y la clave de API en el código fuente, se verá así:

# Insecure Code
api_key = "my_sensitive_api_key"
api_url = "https://api.example.com"
SEC_KEY = "your_secret_key"

# use of secret key
app.config["SECRET_KEY"] = SEC_KEY
# usage of the API key
response = requests.get(api_url, headers={"Authorization": f"Bearer {api_key}"})

Consejo: este es un buen momento para instalar la extensión de Snyk en tu IDE y recibir una indicación visual de la exposición de claves secretas, como la línea azul ondulada que puedes ver en mi editor:

La extensión de Snyk para IDE muestra problemas de seguridad en el código, como la exposición de claves de API sensibles y la filtración de credenciales.

En este caso, para mitigar la exposición de claves secretas, debes generar la clave secreta de forma segura. Aunque hay varias maneras de hacerlo, una de las más comunes es usar el módulo uuid. El módulo uuid se usa principalmente para generar identificadores únicos universales (UUID) según los principios definidos en RFC 4122. Este módulo genera una cadena hexadecimal aleatoria de 32 bytes que se puede usar como clave secreta.

Para generar una clave segura, importa el módulo uuid en tu código y usa la función uuid4().hex:

import uuid

uuid.uuid4().hex

Sin embargo, no basta con generar la clave secreta. También debes usarla de forma segura en tu aplicación Flask. La forma segura recomendada es asignar la clave secreta a una environment variable o usar un archivo de configuración cifrado y guardar la clave allí. Si decides asignar la clave secreta a una environment variable, se verá así:

import os

# Better Code
api_key = os.environ.get("MY_API_KEY")
api_url = "https://api.example.com"
SEC_KEY = os.environ.get("MY_SEC_KEY")

# use of secret key
app.config["SECRET_KEY"] = SEC_KEY

# Usage of the API key
response = requests.get(api_url, headers={"Authorization": f"Bearer {api_key}"})

Los desarrolladores deben seguir convenciones de configuración como las recomendadas en la aplicación de 12 factores, que aconsejan usar variables de entorno para almacenar las credenciales y así evitar filtraciones de datos, accesos no autorizados y posibles usos indebidos.

Modo de depuración activado

El modo de depuración suele estar pensado para entornos de desarrollo y proporciona mensajes de error detallados e información completa sobre el estado de la aplicación. Sin embargo, activarlo en un entorno de producción expone información confidencial y el funcionamiento interno del sistema.

Por ejemplo, en esta aplicación web Flask, el modo de depuración está activado en producción:

# Insecure code
from flask import Flask
appFlask = Flask(__name__)

@appFlask.route('/home')
def home():
    result = "Testing Snyk"
    return 'example of debug mode enabled'

if __name__ == "__main__":
    appFlask.run(debug=True)

En este caso, el modo de depuración está configurado como True, lo que significa que, si ocurre un error en producción, la consola de depuración puede exponer información confidencial, incluidos nombres de usuario y contraseñas.

En esta situación, es muy fácil olvidar que configuraste el modo de depuración como True y no cambiarlo antes de implementar la aplicación en producción. Por eso conviene usar una plataforma como Snyk, que puede ayudarte a encontrar y corregir vulnerabilidades en tu código y tus aplicaciones. Snyk admite una amplia variedad de lenguajes de programación, como Python, Go, PHP, JavaScript y otros.

Además, puedes integrar Snyk fácilmente con varios IDE, como Visual Studio Code y PyCharm, así como con pipelines de CI, como Jenkins, CircleCI y Maven, y con flujos de trabajo.

Por ejemplo, para usar Snyk en este caso de depuración, solo tienes que buscar "Snyk" en Visual Studio Code e instalarlo:

Extensión de seguridad de Snyk para VS Code.

Una vez instalado, Snyk aparecerá en el panel de la izquierda. Haz clic en el botón Connect para iniciar sesión o crear una cuenta de Snyk si aún no tienes una. Luego aparecerá una ventana del navegador como esta:

Conecta la extensión de tu IDE con la aplicación de Snyk.

Una vez conectado, vuelve a Visual Studio Code y abre la carpeta donde está guardado tu script de Flask. Ahora deberías poder ver las vulnerabilidades de tu código:

Una aplicación Flask de Python que muestra vulnerabilidades y problemas de seguridad en el código en el IDE VS Code con la extensión de Snyk.

Como puedes ver, Snyk detectó que el modo de depuración seguía activado. Para mitigar este problema, debes proteger tu aplicación Flask configurando el modo de depuración como False:

# Secure Code
from flask import Flask
appFlask = Flask(__name__)

@appFlask.route('/home')
def home():
    result = "Testing Snyk"
    return 'example of debug mode enabled'

if __name__ == "__main__":
    appFlask.run(debug=False)

Además de desactivar el modo de depuración en producción, también debes usar el mecanismo de registro para recopilar los registros de la aplicación y revisar si hay posibles problemas en el código.

Datos confidenciales sin protección en archivos de configuración

Guardar información crítica (p. ej., credenciales de bases de datos, claves de API y claves de cifrado) en archivos de configuración es una práctica común en las aplicaciones Flask. Sin embargo, los datos pueden quedar expuestos si no proteges estos archivos.

Considera el siguiente caso, en el que tu archivo de configuración (generalmente config.ini) contiene las credenciales para conectarse a la base de datos:

# Insecure Config
[database]
username = admin
password = my_secure_password

En este caso, el nombre de usuario y la contraseña están en texto sin formato. Por lo tanto, si un atacante obtiene acceso a este archivo, puede usar las credenciales para acceder a información confidencial.

La mejor manera de resolver este problema es evitar los archivos de configuración y usar variables de entorno para guardar tus credenciales. Como las variables de entorno almacenan toda la información necesaria en un archivo oculto, como .bash_profile, puedes simplemente usar gitignore en este archivo para evitar subirlo por error a plataformas de control de versiones, como Git o Bitbucket, a las que los atacantes pueden acceder fácilmente.

Si necesitas usar archivos de configuración en tu aplicación Flask, asegúrate de que los datos confidenciales estén cifrados:

# Secure Config
[database]
username = admin
password = $encrypted_password

En este caso, se usa un marcador de posición o un valor cifrado para representar la contraseña. En este escenario, se puede acceder a los datos confidenciales durante la ejecución mediante un sistema externo y seguro de administración de claves o una técnica de descifrado segura.

Vulnerabilidades en aplicaciones Flask

Ahora que conoces un poco mejor algunas configuraciones inseguras en aplicaciones Flask de Python, es momento de revisar algunas vulnerabilidades de seguridad web comunes que también pueden afectar a las aplicaciones Flask.

Cross-site scripting (XSS)

El XSS es una vulnerabilidad de seguridad web en la que un atacante intenta insertar código JavaScript o HTML arbitrario en una página web. Esto permite que los atacantes ejecuten scripts maliciosos en el navegador de un usuario.

Jinja2, el motor de plantillas que usa Flask, ofrece la función Auto Escape para manejar vulnerabilidades XSS. De forma predeterminada, todas las variables se escapan automáticamente (es decir, no contienen ningún elemento HTML, salvo que se indique lo contrario). Esto significa que Jinja2 escapa de forma segura cualquier entrada del usuario que contenga código HTML o JavaScript, lo que impide la ejecución de scripts maliciosos.

Si el escape automático está configurado como False, la aplicación Flask es vulnerable a ataques XSS. Por ejemplo, aquí tienes una aplicación web que necesita una entrada del usuario para renderizar una página:

from flask import Flask, render_template, request

app = Flask(__name__)

@app.route('/')
def index():
    user_input = request.args.get('input')
    return render_template('index.html', input=user_input)

La página index.html correspondiente, donde el escape automático está configurado como false, se vería así:

<!DOCTYPE html>
{% autoescape false %}
<html>
<head>
    <title>XSS Example</title>
</head>
<body>
    <p>User Input: {{ input }}</p>
</body>
{% endautoescape %}
</html>

Si un atacante inserta el script malicioso mediante el parámetro input, este se ejecuta cuando se renderiza la página, lo que puede provocar riesgos de seguridad como el robo de datos, la toma de control de cuentas y ataques de phishing.

Para mitigar el XSS, los desarrolladores deben configurar el sitio web para que escape correctamente el texto y no incluya ningún elemento HTML. Solo tienes que configurar el escape automático como true para proteger la aplicación Flask de un ataque XSS:

<!DOCTYPE html>
{% autoescape true %}
<html>
<head>
    <title>XSS Example</title>
</head>
<body>
    <p>User Input: {{ input }}</p>
</body>
{% endautoescape %}
</html>

Otra forma de prevenir el XSS es escapar explícitamente el marcado. Para hacerlo, debes usar el filtro |safe en una página web, así:

<!DOCTYPE html>
<html>
<head>
    <title>XSS Example</title>
</head>
<body>
    <p>User Input: {{ input|safe }}</p>
</body>
</html>

Sin embargo, debes usar |safe con mucho cuidado, ya que supone que el contenido marcado como seguro no contiene ningún script malicioso.

Para obtener más información sobre las vulnerabilidades de cross-site scripting en Python, visita una breve lección de seguridad en Snyk Learn.

Falsificación de solicitudes entre sitios (CSRF)

El CSRF es un ataque malicioso que engaña a la víctima para que envíe una solicitud maliciosa. En una aplicación web, el CSRF ocurre cuando un atacante realiza una solicitud no autorizada a una página web en nombre de un usuario autorizado. Este tipo de ataque suele verse en plataformas como Facebook: alguien envía una imagen a un grupo y, si los usuarios del grupo hacen clic en ella, se elimina su cuenta.

Para entender mejor el CSRF, considera una aplicación sencilla que cambia el estado y funciona sin protección contra CSRF, como esta:

from flask import Flask, render_template, request, redirect, url_for

app = Flask(__name__)

@app.route('/')
def index():
    return render_template('index.html')

@app.route('/change_password', methods=['POST'])
def change_password():
    new_password = request.form.get('new_password')
    # Change password logic here
    return redirect(url_for('index'))

Este código envía un formulario para cambiar la contraseña mediante una solicitud POST. Como no hay protección contra CSRF, un atacante puede engañar a los usuarios para que cambien su contraseña dirigiéndolos a otro sitio web que contiene HTML o un script diseñado para ese fin. Un formulario sin protección contra CSRF se ve así:

<!DOCTYPE html>
<html>
<head>
    <title>CSRF Example</title>
</head>
<body>
    <form method="post" action="{{ url_for('change_password') }}">
   	 <label for="new_password">New Password:</label>
   	 <input type="password" name="new_password" required>
   	 <button type="submit">Change Password</button>
    </form>
</body>
</html>

Para mitigar las vulnerabilidades CSRF, debes usar la extensión Flask-WTF junto con el middleware CSRFProtect. Flask-WTF es un complemento de Flask que incorpora la biblioteca WTForms, que ofrece funciones prácticas para generar y administrar fácilmente formularios para una aplicación Flask.

Puedes instalarlo con pip de la siguiente manera:

pip install Flask-WTF

Una vez instalado, puedes actualizar tu aplicación Flask con Flask-WTF para protegerla contra CSRF:

from flask import Flask, render_template, request, redirect, url_for
from flask_wtf.csrf import CSRFProtect

app = Flask(__name__)
app.config['SECRET_KEY'] = 'your_secret_key'  # Set a secret key for CSRF protection
csrf = CSRFProtect(app)

@app.route('/')
def index():
    return render_template('index.html')

@app.route('/change_password', methods=['POST'])
def change_password():
    new_password = request.form.get('new_password')
    # Change password logic here
    return redirect(url_for('index'))

En este caso, CSRFProtect se inicializa con la aplicación Flask y se configura SECRET_KEY. Esta clave se usa específicamente para crear y validar tokens CSRF.

No olvides actualizar la plantilla (index.html) para incluir el token CSRF, así:

<!DOCTYPE html>
<html>
<head>
    <title>CSRF Example</title>
</head>
<body>
    <form method="post" action="{{ url_for('change_password') }}">
   	 {{ csrf_token() }}
   	 <label for="new_password">New Password:</label>
   	 <input type="password" name="new_password" required>
   	 <button type="submit">Change Password</button>
    </form>
</body>
</html>

La función csrf_token() genera un campo de entrada oculto que contiene el token CSRF. Cuando se envía el formulario, Flask-WTF valida el token para asegurarse de que la solicitud no sea un ataque CSRF.

Inyección FSQL y seguridad de bases de datos

Ahora, revisemos algunos problemas de Flask relacionados con la seguridad de las bases de datos:

Inyección SQL

La inyección SQL es un problema grave en el que un atacante ejecuta código SQL arbitrario en una base de datos al manipular los parámetros de una consulta SQL. Esto puede provocar accesos no autorizados, manipulación de datos y divulgación de información confidencial.

En las aplicaciones Flask, puede producirse un ataque de inyección SQL si las entradas no se depuran correctamente antes de usarse en una consulta SQL.

Para entender mejor este ataque, considera un ejemplo de código Flask que selecciona datos de una base de datos, así:

from flask import Flask, request, render_template
import sqlite3

app = Flask(__name__)

@app.route('/login', methods=['POST'])
def login():
    username = request.form['username']
    password = request.form['password']

    # Vulnerable SQL query
    query = f"SELECT * FROM users WHERE username = '{username}' AND password = '{password}'"

    # Execute the query
    conn = sqlite3.connect('database.db')
    cursor = conn.cursor()
    cursor.execute(query)
    user = cursor.fetchone()
    conn.close()

    if user:
   	 return 'Login successful'
    else:
   	 return 'Login failed'

Esta consulta espera el nombre de usuario y la contraseña en la cadena de consulta. Si un atacante inserta entradas como ' OR '1'='1'; -- , la consulta queda así:

SELECT * FROM users WHERE username = '' OR '1'='1'; --' AND password = '';

Como esta consulta siempre devuelve True, el atacante iniciaría sesión y obtendría acceso no autorizado a los datos.

Identificar código vulnerable a la inyección SQL es difícil, pero por suerte Snyk puede ayudarte. Como ya configuraste Snyk en Visual Studio Code, solo tienes que abrir el script y Snyk detectará automáticamente el problema de inyección SQL:

Snyk advierte sobre una vulnerabilidad de inyección SQL causada por la concatenación de datos ingresados por el usuario en una consulta SQL.

Una forma sencilla de mitigar este ataque de inyección SQL es usar consultas o instrucciones parametrizadas. En 

En Flask, también puedes usar el método execute del objeto cursor para vincular parámetros de forma segura. Una consulta parametrizada se ve así:

from flask import Flask, request, render_template
import sqlite3

app = Flask(__name__)

@app.route('/login', methods=['POST'])
def login():
    username = request.form['username']
    password = request.form['password']

    # Secure SQL query using parameterized query
    query = "SELECT * FROM users WHERE username = ? AND password = ?"

    # Execute the query with parameters
    conn = sqlite3.connect('database.db')
    cursor = conn.cursor()
    cursor.execute(query, (username, password))
    user = cursor.fetchone()
    conn.close()

    if user:
   	 return 'Login successful'
    else:
   	 return 'Login failed'

En este código se usan marcadores de posición ?, cuyos valores reales se proporcionan como una tupla en el método execute. Así, las entradas de los usuarios se tratan como datos y no como código SQL ejecutable.

Asegúrate de validar y depurar siempre las entradas de los usuarios para evitar este tipo de ataque. Puedes depurar las entradas con técnicas como caracteres de escape, bibliotecas de terceros y expresiones regulares.

Para obtener información más detallada sobre las inyecciones SQL, te recomiendo leer Guía rápida sobre inyección SQL: 8 prácticas recomendadas para prevenir ataques de inyección SQL.

Autenticación y autorización

La autenticación y la autorización son componentes fundamentales de la seguridad web. Una brecha en la autenticación y la autorización puede provocar varios problemas de seguridad, que veremos a continuación.

Problemas de seguridad de autenticación

Uno de los problemas de seguridad de autenticación más comunes es el ataque de fuerza bruta, en el que un atacante intenta repetidamente adivinar un nombre de usuario y una contraseña para acceder a un sitio web o servicio. Otro problema común es la fijación de sesión, en la que el atacante establece el ID de sesión en un valor conocido, posiblemente mediante un ataque de phishing.

Para mitigar los ataques de fuerza bruta, debes implementar un mecanismo de bloqueo de sesión. Con este enfoque, se bloquea al usuario después de cierto número de intentos fallidos de inicio de sesión:

from flask import Flask, request, session

app = Flask(__name__)
app.config['MAX_LOGIN_ATTEMPTS'] = 3

@app.route('/login', methods=['POST'])
def login():
    username = request.form['username']
    password = request.form['password']

    # Check if the user is locked out
    if 'login_attempts' in session and session['login_attempts'] >= app.config['MAX_LOGIN_ATTEMPTS']:
        return 'Account locked. Please try again later.'

    # Validate the username and password
    # ...

    # Update login attempts
    if 'login_attempts' in session:
        session['login_attempts'] += 1
    else:
        session['login_attempts'] = 1

    # Authenticate the user
    # ...

En este código, defines MAX_LOGIN_ATTEMPTS como 3, lo que significa que si alguien intenta ingresar credenciales incorrectas más de tres veces, se le bloqueará.

En el método login(), obtienes el username y la password, y verificas si el usuario está bloqueado. Si no lo está, revisas los intentos de inicio de sesión. Si las credenciales son correctas, todo está listo. De lo contrario, el número de intentos aumenta en uno y el proceso se repite hasta que el usuario inicia sesión correctamente o queda bloqueado.

Además de implementar un mecanismo de bloqueo de sesión, también debes usar políticas de contraseñas seguras y considerar la autenticación multifactor (MFA).

Para mitigar los problemas de fijación de sesión, puedes regenerar los ID de sesión después de que un usuario inicie sesión o después de cierto tiempo (como vencimiento automático), aunque el usuario siga con la sesión iniciada:

from flask import Flask, request, session

app = Flask(__name__)
app.secret_key = 'your_secret_key'

@app.route('/login', methods=['POST'])
def login():
    # Authenticate the user
    # ...

    # Regenerate session ID after successful login
    session.regenerate()
    # ...

En este código, el método login() regenera el ID de sesión con el método regenerate() cuando el usuario inicia sesión correctamente.

Problemas de seguridad de autorización

Al desarrollar una aplicación web, no todas las funciones están pensadas para todos los usuarios (es decir, un usuario específico solo debería tener acceso a un conjunto determinado de funciones). Si los roles no se asignan correctamente a los usuarios, puede producirse un acceso no autorizado; por eso necesitas un control de acceso basado en roles (RBAC).

También es posible que los usuarios autenticados accedan a recursos no autorizados al manipular parámetros o URL, lo que suele llamarse referencias directas a objetos inseguras (IDOR). Te recomiendo leer esta publicación sobre cómo detectar y corregir referencias directas a objetos inseguras en Python para obtener más información.

Para resolver los problemas de RBAC e IDOR, debes asignar roles específicos a los usuarios y aplicar controles de acceso según esos roles, así:

from flask import Flask, request, session

app = Flask(__name__)

@app.route('/admin_dashboard')
def admin_dashboard():
    if 'role' in session and session['role'] == 'admin':
   	 # User has admin role and is authorized to access the admin dashboard
   	 # ...
   	 return 'Admin dashboard page'
    else:
   	 # User is not authorized
   	 return 'Unauthorized access'

En este código, el método admin_dashboard() implementa las funciones a las que se debería acceder si el rol de acceso es admin. De lo contrario, devuelve una advertencia de Unauthorized access.

También debes administrar el control de acceso para que los usuarios solo puedan acceder a los recursos para los que tienen autorización:

from flask import Flask, request, session

app = Flask(__name__)

@app.route('/user_profile/<int:user_id>')
def user_profile(user_id):
    if 'user_id' in session and session['user_id'] == user_id:
   	 # User is authorized to access their own profile
   	 # ...
   	 return 'User profile page'
    else:
   	 # User is not authorized
   	 return 'Unauthorized access'

No debes almacenar credenciales en texto sin formato, porque los atacantes podrían acceder a ellas fácilmente con fines maliciosos. Para evitarlo, debes usar hashing y salt para almacenar las credenciales de los usuarios.

El hashing transforma una contraseña en una cadena de caracteres de longitud fija, por lo que resulta computacionalmente inviable revertir el proceso y obtener la contraseña original. El salting consiste en agregar un valor aleatorio único (el salt) a cada contraseña antes de aplicar el hashing, lo que ayuda a prevenir ataques como los de tablas rainbow.

Bibliotecas desactualizadas y actualizaciones de seguridad

Si usas bibliotecas de Flask desactualizadas, pueden contener vulnerabilidades que los atacantes podrían aprovechar. Las bibliotecas suelen publicar actualizaciones y parches de seguridad para corregir vulnerabilidades. Asegúrate de usar siempre la versión más reciente del paquete.

Para asegurarte de usar los parches de seguridad más recientes, puedes usar herramientas como pip o conda. Además, puedes usar herramientas como Snyk para analizar todo tu código en busca de dependencias y configuraciones de infraestructura como código (IaC).

Otra práctica recomendada es fijar las dependencias en el archivo requirements.txt. Así, el proyecto usa versiones específicas, lo que permite a los desarrolladores controlar las actualizaciones y probar la compatibilidad.

Carga segura de archivos

Si un sitio web de Flask permite que cualquier persona cargue cualquier tipo de archivo, pueden surgir varios problemas. Por ejemplo, si un atacante carga archivos maliciosos en el servidor, puede provocar un ataque de denegación de servicio (DoS) y posiblemente sobrescribir archivos existentes. Algunos archivos ejecutables también pueden producir distintos tipos de virus informáticos.

A continuación, se muestra un ejemplo de código que permite a cualquier usuario cargar archivos de cualquier tipo:

from flask import Flask, request, flash, redirect, url_for
from werkzeug.utils import secure_filename
import os

app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = 'uploads'

@app.route('/upload', methods=['POST'])
def upload_file():
    file = request.files['file']
    filename = secure_filename(file.filename)
    file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
    flash('File successfully uploaded')
    return redirect(url_for('uploaded_file', filename=filename))

En este código, el método upload_file() lee el archivo que proporciona el usuario como entrada y lo guarda en la carpeta uploads.

La primera solución para evitar archivos inseguros es definir un conjunto de extensiones de archivo permitidas. Nunca debes permitir tipos de archivos ejecutables o de script en tu aplicación Flask.

Además, asegúrate de guardar los archivos en una ubicación segura para evitar el acceso no autorizado:

from flask import Flask, request, flash, redirect, url_for
from werkzeug.utils import secure_filename
import os

app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = 'uploads'
app.config['ALLOWED_EXTENSIONS'] = {'txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif'}

def allowed_file(filename):
    return '.' in filename and filename.rsplit('.', 1)[1].lower() in app.config['ALLOWED_EXTENSIONS']

@app.route('/upload', methods=['POST'])
def upload_file():
    if 'file' not in request.files:
   	 flash('No file part')
   	 return redirect(request.url)

    file = request.files['file']

    if file.filename == '':
   	 flash('No selected file')
   	 return redirect(request.url)

    if file and allowed_file(file.filename):
   	 filename = secure_filename(file.filename)
   	 file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
   	 flash('File successfully uploaded')
   	 return redirect(url_for('uploaded_file', filename=filename))
    else:
   	 flash('Invalid file type')
   	 return redirect(request.url)

Aquí se aplican varias condiciones para comprobar que los tipos de archivo sean auténticos.

Otra práctica recomendada es asignar nombres aleatorios a los archivos para evitar que se sobrescriban y reducir el riesgo de usar convenciones de nombres predecibles:

from flask import Flask, request, flash, redirect, url_for
import os
import uuid

app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = 'uploads'

def generate_random_filename():
    return str(uuid.uuid4())

@app.route('/upload', methods=['POST'])
def upload_file():
    # ... (other code)
    if file and allowed_file(file.filename):
   	 filename = generate_random_filename()
   	 file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
   	 # ... (other code)

Manejo de errores y divulgación de información

El manejo de errores es importante en cualquier tipo de aplicación. En producción, no es recomendable usar instrucciones print ni habilitar el modo de depuración. Si usas un mecanismo de registro inadecuado, podrías revelar varios detalles importantes sobre tu aplicación.

Por ejemplo, mostrar mensajes de error detallados en producción puede revelar información confidencial sobre el funcionamiento interno de la aplicación, lo que facilita que los atacantes identifiquen posibles vulnerabilidades:

OperationalError: (1045, "Access denied for user 'root'@'localhost' (using password: YES)")

Este ejemplo revela la información más importante sobre la base de datos asociada con la aplicación Flask.

Además, mostrar los seguimientos completos de errores en las respuestas puede revelar la estructura del código de la aplicación y posiblemente exponer vulnerabilidades como esta:

File "/path/to/app.py", line 42, in some_function
	result = 1 / 0
ZeroDivisionError: division by zero

Para mitigar la divulgación de información mediante el manejo de errores, puedes crear páginas de error personalizadas para códigos de error genéricos (por ejemplo, 404 Not Found y 500 Internal Server Error) sin exponer detalles confidenciales. Para hacerlo, puedes usar el decorador errorhandler para definir controladores de errores personalizados:

from flask import Flask, render_template

app = Flask(__name__)

@app.errorhandler(404)
def page_not_found(error):
    return render_template('404.html'), 404

@app.errorhandler(500)
def internal_server_error(error):
    return render_template('500.html'), 500

Además, debes implementar un registro adecuado para excepciones y errores. Los mensajes de registro deben tener suficiente detalle para depurar, pero no deben exponer información confidencial:

import logging
from flask import Flask

app = Flask(__name__)

logging.basicConfig(filename='error.log', level=logging.ERROR)

@app.route('/divide')
def divide_by_zero():
    try:
   	 result = 1 / 0
   	 return str(result)
    except Exception as e:
   	 app.logger.error(f"An error occurred: {str(e)}")
   	 return "Internal Server Error", 500

Si sigues estas prácticas recomendadas, podrás garantizar que tu aplicación Flask sea sólida y segura.

Conclusión

Flask es un framework web popular para aplicaciones web de pequeña y mediana escala. Sin embargo, como cualquier otra herramienta o framework de desarrollo web, Flask puede presentar problemas si no se maneja correctamente. Es importante que conozcas estos problemas y también sepas cómo mitigarlos para evitar que actores maliciosos se aprovechen de ellos.

Te invitamos a seguir este repositorio de código en GitHub, donde encontrarás ejemplos completos y funcionales de algunos de los temas de seguridad de aplicaciones Flask que analizamos.

Snyk puede ayudarte a encontrar y mitigar fácilmente cualquier vulnerabilidad en tu código. Snyk es compatible con varios lenguajes de programación y se integra sin problemas con tus herramientas, pipelines y flujos de trabajo. Empieza a usar Snyk gratis hoy mismo.

Por último, asegúrate de seguir la guía rápida de prácticas recomendadas de seguridad en Python de Snyk y adquirir habilidades de seguridad de aplicaciones en Python a través del sitio gratuito Snyk Learn.

Leer más

Blog

Los modelos de frontera encontraron las vulnerabilidades. Solo el atacante encontró las cadenas.

El análisis estático encontró las fallas, pero solo las pruebas de ataque en vivo demostraron cómo podían encadenarse para provocar brechas. Una comparación de Evo COS, Claude Security y Claude Code Security.

feature insights context
Blog

Los ataques autónomos ya están aquí. La defensa debe estar a su altura.

Los atacantes autónomos están reduciendo el tiempo disponible para defenderse. Descubre cómo el descubrimiento, la corrección, la validación y la prevención continuos pueden ayudar a los equipos de seguridad a seguirles el ritmo.

Blog

Por qué los agentes de programación con IA siguen generando fallas de control de acceso

Los agentes de programación con IA pueden generar lógica de autorización que compila y supera la revisión, pero expone los datos de un inquilino a otro. Descubre por qué es difícil detectar el control de acceso roto y cómo prevenirlo.