Skip to main content

Como proteger aplicações Python Flask

Escrito por
Headshot of Gourav Singh Bais

Gourav Singh Bais

blog feature playwright tests

21 de maio de 2024

0 minutos de leitura

Flask é um framework web poderoso, leve e versátil para Python, desenvolvido para facilitar a criação rápida de aplicações web, com o mínimo de código boilerplate. É um microframework independente, que não precisa de bibliotecas ou ferramentas adicionais e não conta com uma camada de abstração de banco de dados.

O Flask inclui recursos como roteamento, renderização de templates e tratamento de requisições, além de seguir o padrão Web Server Gateway Interface (WSGI), conhecido por sua simplicidade e flexibilidade.

Infelizmente, como qualquer outro framework web, o Flask está sujeito a vulnerabilidades quando não é protegido adequadamente. Entre os riscos de segurança mais comuns no Flask estão cross-site scripting (XSS), falsificação de requisições entre sites (CSRF) e injeção de SQL.

Na Snyk Vulnerability Database, você encontra muitas das vulnerabilidades de segurança identificadas na biblioteca Python Flask:

Algumas das muitas vulnerabilidades de segurança encontradas na biblioteca Flask para Python no Snyk Vulnerability Database.

Neste artigo, você vai conhecer as práticas recomendadas para proteger aplicações Python criadas com o framework web Flask. Primeiro, vamos analisar exemplos de configurações inseguras e, em seguida, aprender a corrigir e mitigar os problemas.

O que é o padrão Web Server Gateway Interface (WSGI)?

O WSGI padroniza a comunicação entre servidores web e aplicações web escritas em Python. Ele é uma camada intermediária que permite aos servidores web encaminhar solicitações a uma aplicação ou framework web e, em seguida, enviar as respostas de volta ao cliente.

Por que o WSGI é importante?

Antes do WSGI, frameworks web e servidores Python costumavam ter dependências específicas entre si. Isso significava que um framework web só podia funcionar em determinados servidores. O WSGI eliminou essa limitação, oferecendo mais flexibilidade e interoperabilidade.

Como o WSGI se compara a conceitos semelhantes em outras linguagens?

O WSGI tem uma finalidade semelhante à da API Java Servlet, em Java, ou do Rack, em Ruby. Essas tecnologias abstraem os detalhes das solicitações e respostas HTTP, permitindo que os desenvolvedores se concentrem na lógica da aplicação web, em vez de lidar com a comunicação subjacente com o servidor.

Configurações inseguras em aplicações Python Flask

Veja a seguir alguns dos problemas de configuração insegura mais comuns que afetam aplicações Python Flask. Os desenvolvedores devem ficar atentos para não repetir esses erros em seus códigos:

Exposição de chaves secretas

A divulgação não intencional de chaves secretas é uma falha de segurança comum. Ela pode envolver informações confidenciais, como a chave secreta do Flask, chaves de API e senhas, incluídas no código-fonte. Inserir informações confidenciais diretamente no código representa um risco significativo, pois agentes mal-intencionados podem explorar essa vulnerabilidade para obter acesso não autorizado a diversos serviços.

Se você inseriu acidentalmente sua chave secreta e sua chave de API diretamente no código-fonte, ele ficará assim:

# 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}"})

Dica: este é um bom momento para instalar a extensão da Snyk no seu IDE. Assim, você recebe um alerta visual quando uma chave secreta é exposta, como a linha azul ondulada que aparece no meu editor:

A extensão da Snyk para IDE mostra problemas de segurança no código, como exposição de chaves de API confidenciais e vazamento de credenciais.

Para evitar a exposição da chave secreta neste caso, você precisa gerá-la com segurança. Há várias maneiras de fazer isso; uma das mais comuns é usar o módulo uuid. O módulo uuid é usado principalmente para gerar identificadores universalmente exclusivos (UUIDs) com base nos princípios definidos na RFC 4122. Ele gera uma string hexadecimal aleatória de 32 bytes que pode ser usada como chave secreta.

Para gerar uma chave segura, importe o módulo uuid no seu código e use a função uuid4().hex:

import uuid

uuid.uuid4().hex

No entanto, gerar a chave secreta não basta. Você também precisa usá-la com segurança no seu app Flask. A prática recomendada é atribuir a chave a uma environment variable ou armazená-la em um arquivo de configuração criptografado. Se você optar por usar uma environment variable, ficará assim:

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}"})

Os desenvolvedores devem seguir convenções de configuração, como as recomendadas pelo Twelve-Factor App, e usar variáveis de ambiente para armazenar credenciais. Isso ajuda a evitar vazamento de dados, acesso não autorizado e possíveis usos indevidos.

Modo de depuração ativado

O modo de depuração geralmente é usado em ambientes de desenvolvimento para exibir mensagens de erro detalhadas e informações abrangentes sobre o estado da aplicação. No entanto, ativá-lo em produção expõe informações confidenciais e detalhes internos do sistema.

Por exemplo, nesta aplicação web Flask, o modo de depuração está ativado em produção:

# 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)

Neste caso, o modo de depuração está definido como True. Isso significa que, se ocorrer algum erro em produção, informações confidenciais, incluindo nomes de usuário e senhas, poderão aparecer no console de depuração.

Nesse cenário, é muito fácil esquecer que você definiu o modo de depuração como True e não o desativar antes de implantar a aplicação em produção. Por isso, é melhor usar uma plataforma como a Snyk, que ajuda você a encontrar e corrigir vulnerabilidades no código e nas aplicações. A Snyk oferece suporte a diversas linguagens de programação, incluindo Python, Go, PHP, JavaScript e outras.

Além disso, é fácil integrar a Snyk a vários IDEs, incluindo Visual Studio Code e PyCharm, bem como a pipelines de CI, como Jenkins, CircleCI e Maven, e a fluxos de trabalho.

Por exemplo, para usar a Snyk neste cenário de depuração, basta pesquisar por "Snyk" no Visual Studio Code e instalar a extensão:

Extensão Snyk Security para VS Code.

Depois de instalar, a Snyk aparecerá no painel à esquerda. Clique no botão Connect para entrar ou criar uma conta da Snyk, caso ainda não tenha uma. Em seguida, será aberta uma janela do navegador semelhante a esta:

Conecte a extensão do seu IDE ao aplicativo Snyk.

Depois de se conectar, volte ao Visual Studio Code e abra a pasta onde seu script Flask está armazenado. Agora você poderá ver as vulnerabilidades presentes no código:

Aplicação Flask em Python exibindo vulnerabilidades e problemas de segurança no código no VS Code com a extensão Snyk.

Como você pode ver, a Snyk identificou que o modo de depuração ainda estava ativado. Para corrigir o problema, proteja sua aplicação Flask definindo o modo de depuração 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)

Além de desativar o modo de depuração em produção, você também deve usar o mecanismo de logging para coletar os logs da aplicação e verificar possíveis problemas no código.

Dados confidenciais desprotegidos em arquivos de configuração

Armazenar informações essenciais (por exemplo, credenciais de banco de dados, chaves de API e chaves de criptografia) em arquivos de configuração é uma prática comum em aplicações Flask. No entanto, esses dados podem ser comprometidos se os arquivos não estiverem protegidos.

Considere o cenário a seguir, em que seu arquivo de configuração (geralmente config.ini) contém as credenciais para se conectar ao banco de dados:

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

Aqui, o nome de usuário e a senha estão em texto simples. Portanto, se um invasor tiver acesso a esse arquivo, poderá usar as credenciais para acessar informações confidenciais.

A melhor maneira de resolver esse problema é evitar arquivos de configuração e usar variáveis de ambiente para armazenar suas credenciais. Como as variáveis de ambiente reúnem todas as informações necessárias em um arquivo oculto, como .bash_profile, basta adicionar esse arquivo ao gitignore para evitar enviá-lo acidentalmente a plataformas de controle de versão, como Git ou Bitbucket, que podem ser facilmente acessadas por invasores.

Se você precisa usar arquivos de configuração no seu app Flask, certifique-se de que os dados confidenciais estejam criptografados:

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

Neste exemplo, um placeholder ou valor criptografado representa a senha. Nesse cenário, os dados confidenciais podem ser acessados durante a execução usando um sistema externo e seguro de gerenciamento de chaves ou uma técnica segura de descriptografia.

Vulnerabilidades em aplicações Flask

Agora que você conhece alguns problemas de configuração insegura em aplicações Python Flask, vamos analisar vulnerabilidades comuns de segurança na web que também podem afetar essas aplicações.

Cross-site scripting (XSS)

XSS é uma vulnerabilidade de segurança na web em que um invasor tenta injetar código JavaScript ou HTML arbitrário em uma página. Isso permite que ele execute scripts maliciosos no navegador de outra pessoa.

O Jinja2, mecanismo de templates usado pelo Flask, oferece o recurso Auto Escape para lidar com vulnerabilidades XSS. Por padrão, todas as variáveis são escapadas automaticamente (ou seja, não contêm nenhum elemento HTML, a menos que isso seja explicitamente permitido). Assim, qualquer entrada fornecida pelo usuário que contenha código HTML ou JavaScript é escapada com segurança pelo Jinja2, impedindo a execução de scripts maliciosos.

Se o escape automático estiver definido como False, a aplicação Flask ficará vulnerável a ataques XSS. Por exemplo, considere uma aplicação web que recebe uma entrada do usuário para renderizar uma 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)

A página index.html correspondente, com o escape automático definido como false, ficaria assim:

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

Se um invasor injetar um script malicioso pelo parâmetro input, ele será executado quando a página for renderizada, causando possíveis riscos de segurança, como roubo de dados, tomada de contas e ataques de phishing.

Para mitigar XSS, os desenvolvedores precisam configurar o site para escapar corretamente o texto e não incluir elementos HTML. Basta definir o escape automático como true para proteger a aplicação Flask contra ataques XSS:

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

Outra maneira de evitar XSS é escapar a marcação explicitamente. Para isso, use o filtro |safe em uma página web, como neste exemplo:

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

No entanto, use |safe com muito cuidado, pois isso pressupõe que o conteúdo marcado como seguro não contém nenhum script malicioso.

Para saber mais sobre vulnerabilidades de cross-site scripting em Python, acesse uma lição rápida de segurança no Snyk Learn.

Falsificação de requisições entre sites (CSRF)

CSRF é um ataque malicioso que engana a vítima para que ela envie uma requisição maliciosa. Em uma aplicação web, o CSRF ocorre quando um invasor envia uma requisição não autorizada a uma página em nome de um usuário autorizado. Esse tipo de ataque é comum em plataformas como o Facebook: alguém envia uma imagem a um grupo e, se os participantes clicarem nela, suas contas são excluídas.

Para entender melhor o CSRF, considere uma aplicação simples que altera o estado sem proteção 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 envia um formulário para alterar a senha por meio de uma requisição POST. Sem proteção contra CSRF, um invasor pode enganar os usuários e fazê-los alterar a senha, direcionando-os a outro site que contenha HTML ou um script preparado para esse fim. Um formulário sem proteção contra CSRF seria parecido com este:

<!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 vulnerabilidades CSRF, use a extensão Flask-WTF junto com o middleware CSRFProtect. Flask-WTF é um plugin para Flask que integra a biblioteca WTForms, que oferece recursos práticos para criar e gerenciar formulários com facilidade em aplicações Flask.

Você pode instalá-la com o pip da seguinte maneira:

pip install Flask-WTF

Depois de instalar, atualize sua aplicação Flask com o Flask-WTF para adicionar proteção 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'))

Neste exemplo, CSRFProtect é inicializado com o app Flask, e a SECRET_KEY é definida. Essa chave é usada especificamente para criar e validar tokens CSRF.

Não se esqueça de atualizar o template (index.html) para incluir o token CSRF, como neste exemplo:

<!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>

A função csrf_token() gera um campo de entrada oculto que contém o token CSRF. Quando o formulário é enviado, o Flask-WTF valida o token para garantir que a requisição não seja um ataque CSRF.

Injeção FSQL e segurança de banco de dados

Agora, veja alguns problemas do Flask relacionados à segurança de banco de dados:

Injeção de SQL

Injeção de SQL é um problema grave em que um invasor executa código SQL arbitrário em um banco de dados, manipulando os parâmetros usados em uma consulta SQL. Isso pode levar a acesso não autorizado, manipulação de dados e divulgação de informações confidenciais.

Em aplicações Flask, pode ocorrer um ataque de injeção de SQL quando as entradas não são devidamente sanitizadas antes de serem usadas em uma consulta SQL.

Para entender melhor esse ataque, considere um exemplo de código Flask que seleciona dados de um banco de dados:

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 receber o nome de usuário e a senha na string de consulta. Se um invasor injetar entradas como ' OR '1'='1'; -- , a consulta ficará assim:

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

Como essa consulta sempre retorna True, o invasor conseguirá fazer login e obter acesso não autorizado aos dados.

É difícil identificar código vulnerável à injeção de SQL, mas a Snyk pode ajudar. Como você já configurou a Snyk no Visual Studio Code, basta abrir o script para que ela detecte automaticamente o problema de injeção de SQL:

A Snyk alerta sobre uma vulnerabilidade de injeção de SQL causada pela concatenação de dados fornecidos pelo usuário a uma consulta SQL.

Uma maneira simples de mitigar esse ataque de injeção de SQL é usar consultas ou instruções parametrizadas. No

Flask, você também pode usar o método execute do objeto cursor para vincular parâmetros com segurança. Uma consulta parametrizada fica assim:

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'

Neste código, são usados espaços reservados ?, cujos valores reais são fornecidos como uma tupla no método execute, garantindo que as entradas do usuário sejam tratadas como dados, e não como código SQL executável.

Sempre valide e higienize as entradas do usuário para evitar esse tipo de ataque. Você pode higienizar as entradas usando técnicas como caracteres de escape, bibliotecas de terceiros e expressões regulares.

Para uma explicação detalhada sobre injeções de SQL, recomendo a leitura de Injeção de SQL: 8 práticas recomendadas para evitar ataques de injeção de SQL.

Autenticação e autorização

Autenticação e autorização são componentes essenciais da segurança na web. Uma falha nesses mecanismos pode levar a diversos problemas de segurança, que você vai conhecer a seguir.

Problemas de segurança na autenticação

Um dos problemas de segurança mais comuns na autenticação é o ataque de força bruta, no qual um invasor tenta repetidamente adivinhar um nome de usuário e uma senha para obter acesso a um site ou serviço. Outro problema comum é a fixação de sessão, em que o invasor define o ID da sessão como um valor conhecido, possivelmente por meio de um ataque de phishing.

Para mitigar ataques de força bruta, implemente um mecanismo de bloqueio de sessão. Com essa abordagem, o usuário é bloqueado após um determinado número de tentativas de login malsucedidas:

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
    # ...

Neste código, você define MAX_LOGIN_ATTEMPTS como 3. Isso significa que, se alguém inserir credenciais incorretas mais de três vezes, será bloqueado.

No método login(), você obtém o username e a password e verifica se o usuário está bloqueado. Caso não esteja, você verifica as tentativas de login. Se as credenciais estiverem corretas, o acesso é liberado. Caso contrário, o número de tentativas aumenta em um, e o processo se repete até que o usuário consiga fazer login ou seja bloqueado.

Além de implementar um mecanismo de bloqueio de sessão, você também deve adotar políticas de senhas fortes e considerar o uso da autenticação multifator (MFA).

Para mitigar problemas de fixação de sessão, você pode gerar novos IDs de sessão depois que o usuário faz login ou após um determinado período (como uma expiração automática), mesmo que ele continue conectado:

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()
    # ...

Neste código, o método login() gera um novo ID de sessão usando o método regenerate() depois que o usuário faz login com sucesso.

Problemas de segurança na autorização

Nem todos os recursos de um aplicativo web se destinam a todos os usuários (ou seja, cada usuário deve ter acesso apenas a um conjunto específico de funcionalidades). Quando as funções não são atribuídas corretamente aos usuários, pode ocorrer acesso não autorizado. Por isso, você precisa implementar o controle de acesso baseado em funções (RBAC).

Usuários autenticados também podem acessar recursos não autorizados manipulando parâmetros ou URLs. Esse problema é conhecido como referência direta insegura a objetos (IDOR). Para saber mais, recomendo a leitura deste artigo sobre como encontrar e corrigir referências diretas inseguras a objetos em Python.

Para resolver problemas de RBAC e IDOR, atribua funções específicas aos usuários e aplique controles de acesso com base nessas funções, como neste exemplo:

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'

Neste código, o método admin_dashboard() implementa as funcionalidades que devem estar disponíveis para quem tem a função de administrador. Caso contrário, ele retorna um aviso de Unauthorized access.

Você também precisa gerenciar o controle de acesso para que os usuários só possam acessar os recursos para os quais têm autorização:

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'

Não armazene credenciais em texto simples, pois invasores podem acessá-las facilmente para fins maliciosos. Para evitar isso, use hashing e salt para armazenar as credenciais dos usuários.

O hashing transforma uma senha em uma sequência de caracteres de tamanho fixo, tornando computacionalmente inviável reverter o processo e obter a senha original. O salt consiste em adicionar um valor aleatório e exclusivo a cada senha antes do hashing, o que ajuda a evitar ataques como os de rainbow tables.

Bibliotecas desatualizadas e atualizações de segurança

Se você estiver usando bibliotecas desatualizadas do Flask, elas podem conter vulnerabilidades que invasores podem explorar. As bibliotecas costumam lançar atualizações e correções de segurança para resolver vulnerabilidades. Use sempre a versão mais recente do pacote.

Para garantir que você esteja usando as correções de segurança mais recentes, use ferramentas como pip ou conda. Além disso, você pode usar ferramentas como a Snyk para analisar todo o seu código em busca de dependências e configurações de infraestrutura como código (IaC).

Outra prática recomendada é fixar as dependências no arquivo requirements.txt. Assim, o projeto usa versões específicas, dando aos desenvolvedores controle sobre as atualizações e a possibilidade de testar a compatibilidade.

Uploads de arquivos seguros

Se um site Flask permitir que qualquer pessoa envie qualquer tipo de arquivo, vários problemas podem ocorrer. Por exemplo, se um invasor enviar arquivos maliciosos para o servidor, poderá causar um ataque de negação de serviço (DoS) e até sobrescrever arquivos existentes. Alguns arquivos executáveis também podem conter diferentes tipos de vírus de computador.

Veja a seguir um exemplo de código que permite que qualquer usuário envie qualquer arquivo:

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))

Neste código, o método upload_file() lê o arquivo enviado pelo usuário e o armazena na pasta uploads.

A primeira medida para evitar arquivos inseguros é definir uma lista de extensões de arquivo permitidas. Nunca permita tipos de arquivo executáveis ou de script no seu aplicativo Flask.

Além disso, armazene os arquivos em um local seguro para evitar acessos não autorizados:

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)

Neste exemplo, várias condições são aplicadas para verificar se os tipos de arquivo são válidos.

Outra prática recomendada é usar nomes de arquivo aleatórios para evitar que arquivos sejam sobrescritos e reduzir o risco de convenções de nomenclatura previsíveis:

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)

Tratamento de erros e divulgação de informações

O tratamento de erros é importante em qualquer tipo de aplicativo. Em produção, não é recomendável usar instruções print nem ativar o modo de depuração. Um mecanismo de registro inadequado pode revelar vários detalhes importantes sobre o seu aplicativo.

Por exemplo, exibir mensagens de erro detalhadas em produção pode revelar informações confidenciais sobre os componentes internos do aplicativo, facilitando a identificação de possíveis vulnerabilidades por invasores:

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

Este exemplo revela as informações mais importantes sobre o banco de dados associado ao aplicativo Flask.

Além disso, exibir rastreamentos completos nas respostas de erro pode revelar a estrutura do código do aplicativo e expor possíveis vulnerabilidades, como neste exemplo:

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

Para evitar a divulgação de informações por meio do tratamento de erros, crie páginas de erro personalizadas para códigos genéricos (por exemplo, 404 Not Found e 500 Internal Server Error), sem expor detalhes confidenciais. Para isso, use o decorador errorhandler para definir manipuladores de erro 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

Além disso, implemente o registro adequado de exceções e erros. As mensagens de log devem conter detalhes suficientes para depuração, sem expor informações confidenciais:

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

Seguindo essas práticas recomendadas, você garante que seu aplicativo Flask seja robusto e seguro.

Conclusão

Flask é um framework web popular para aplicativos de pequeno e médio porte. No entanto, como qualquer outra ferramenta ou framework de desenvolvimento web, o Flask pode apresentar problemas se não for usado corretamente. É importante conhecer esses problemas e saber como mitigá-los para impedir que pessoas mal-intencionadas tirem proveito deles.

Você pode acompanhar este repositório de código no GitHub para conferir exemplos funcionais completos de alguns dos tópicos de segurança de aplicativos Flask que abordamos.

A Snyk ajuda você a encontrar e mitigar vulnerabilidades no seu código com facilidade. A Snyk oferece suporte a diversas linguagens de programação e se integra perfeitamente às suas ferramentas, pipelines e fluxos de trabalho. Comece a usar a Snyk gratuitamente hoje mesmo.

Por fim, siga o Guia de práticas recomendadas de segurança em Python da Snyk e desenvolva suas habilidades de segurança de aplicativos em Python com o site gratuito Snyk Learn.

Leia mais

Blog

Modelos de ponta encontraram as vulnerabilidades. Só o atacante encontrou as cadeias.

A análise estática encontrou as falhas, mas só os testes de ataque em aplicações ativas provaram como elas poderiam ser encadeadas para causar invasões. Uma comparação entre Evo COS, Claude Security e Claude Code Security.

feature insights context
Blog

Os ataques autônomos já chegaram. A defesa precisa acompanhar o ritmo.

Os atacantes autônomos estão reduzindo o tempo disponível para a defesa. Saiba como a descoberta, a correção, a validação e a prevenção contínuas ajudam as equipes de segurança a acompanhar esse ritmo.

Blog

Por que agentes de programação com IA continuam criando falhas de controle de acesso

Agentes de programação com IA podem gerar uma lógica de autorização que compila e passa pela revisão, mas permite que um tenant acesse os dados de outro. Saiba por que é difícil detectar falhas de controle de acesso e como evitá-las.