Skip to main content

Sécuriser les applications Python Flask

Écrit par
Headshot of Gourav Singh Bais

Gourav Singh Bais

blog feature playwright tests

21 mai 2024

0 minutes de lecture

Flask est un framework web Python puissant, léger et polyvalent, conçu pour permettre aux développeurs de créer rapidement des applications web avec un minimum de code standard. Ce microframework autonome ne nécessite aucune bibliothèque ni aucun outil supplémentaire et ne dispose pas de couche d’abstraction de base de données.

Flask comprend des fonctionnalités telles que le routage, le rendu de modèles et la gestion des requêtes, et respecte la norme Web Server Gateway Interface (WSGI), réputée pour sa simplicité et sa flexibilité.

Malheureusement, comme tout autre framework web, Flask est exposé à des vulnérabilités s’il n’est pas correctement sécurisé. Les risques de sécurité les plus courants pour Flask comprennent les scripts intersites (XSS), la falsification de requêtes intersites (CSRF) et l’injection SQL.

La Snyk Vulnerability Database répertorie de nombreuses vulnérabilités de sécurité découvertes dans la bibliothèque Python Flask :

Quelques-unes des nombreuses vulnérabilités de sécurité recensées pour la bibliothèque Python Flask dans la Snyk Vulnerability Database.

Dans cet article, découvrez les bonnes pratiques pour sécuriser les applications Python développées avec le framework web Flask. Vous commencerez par examiner quelques exemples de configurations non sécurisées, puis apprendrez à atténuer et à corriger les problèmes.

Qu’est-ce que la norme Web Server Gateway Interface (WSGI) ?

WSGI fournit un moyen standardisé pour permettre aux serveurs Web de communiquer avec les applications Web écrites en Python. Cette couche intermédiaire permet aux serveurs Web de transmettre les requêtes à une application ou à un framework Web, puis de renvoyer les réponses au client.

Pourquoi WSGI est-il important ?

Avant WSGI, les frameworks web Python et les serveurs étaient souvent étroitement liés : un framework web donné ne pouvait fonctionner qu’avec certains serveurs web. WSGI a supprimé cette contrainte, offrant davantage de flexibilité et d’interopérabilité.

En quoi WSGI est-il comparable à des concepts similaires dans d’autres langages ?

WSGI a un rôle similaire à l’API Java Servlet en Java ou à Rack en Ruby. Ces technologies abstraient les détails des requêtes et des réponses HTTP, permettant aux développeurs de se concentrer sur la logique de leurs applications web plutôt que sur la communication sous-jacente avec le serveur.

Configurations non sécurisées des applications Python Flask

Voici quelques-uns des problèmes de configuration non sécurisée les plus courants qui touchent les applications Python Flask. Les développeurs doivent veiller à ne pas reproduire ces erreurs dans leur base de code :

Exposition de la clé secrète

La divulgation accidentelle d’une clé secrète est un problème de sécurité courant : des informations sensibles, comme la clé secrète Flask, les clés d’API et les mots de passe, sont intégrées au code source. L’intégration directe de données confidentielles dans le code source présente un risque de sécurité important, car des acteurs malveillants peuvent exploiter cette vulnérabilité pour accéder sans autorisation à différents services.

Si vous avez codé en dur votre clé secrète et votre clé d’API dans le code source, le résultat ressemblera à ceci :

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

Conseil : c’est le bon moment pour installer l’extension Snyk dans votre IDE. Vous pourrez ainsi repérer visuellement l’exposition d’une clé secrète, comme le trait bleu ondulé affiché dans mon éditeur :

L’extension IDE de Snyk affiche des problèmes de sécurité du code, comme l’exposition de clés API sensibles et la fuite d’identifiants.

Pour prévenir l’exposition d’une clé secrète dans ce cas, vous devez la générer de manière sécurisée. Il existe plusieurs méthodes, dont l’une des plus courantes consiste à utiliser le module uuid. Le module uuid sert principalement à générer des identifiants uniques universels (UUID) selon les principes définis dans RFC 4122. Il génère une chaîne hexadécimale aléatoire de 32 octets, utilisable comme clé secrète.

Pour générer une clé sécurisée, importez le module uuid dans votre code et utilisez la fonctionnalité uuid4().hex :

import uuid

uuid.uuid4().hex

Toutefois, générer la clé secrète ne suffit pas. Vous devez également l’utiliser de manière sécurisée dans votre application Flask. La méthode recommandée consiste à affecter la clé secrète à une environment variable ou à utiliser un fichier de configuration chiffré pour l’y stocker. Si vous choisissez de l’affecter à une environment variable, voici à quoi cela ressemblera :

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

Les développeurs doivent suivre des conventions de configuration telles que celles recommandées par l’application 12 Factor et utiliser des variables d’environnement pour stocker leurs identifiants. Ils évitent ainsi les fuites de données, les accès non autorisés et les utilisations abusives potentielles.

Mode débogage activé

Le mode débogage est généralement destiné aux environnements de développement. Il fournit des messages d’erreur détaillés ainsi que des informations complètes sur l’état de l’application. Cependant, son activation en production expose des informations sensibles et le fonctionnement interne du système.

Par exemple, dans cette application web Flask, le mode débogage est activé en production :

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

Ici, le mode débogage est défini sur True. Ainsi, si une erreur se produit en production, des informations sensibles, notamment les noms d’utilisateur et les mots de passe, peuvent être exposées dans la console de débogage.

Dans ce cas, il est très facile d’oublier que vous avez défini le mode débogage sur True et de ne pas le désactiver avant le déploiement de votre application en production. C’est pourquoi il est préférable d’utiliser une plateforme comme Snyk, qui vous aide à détecter et à corriger les vulnérabilités de votre code et de vos applications. Snyk prend en charge un large éventail de langages de programmation, dont Python, Go, PHP, JavaScript et d’autres.

De plus, Snyk s’intègre facilement à différents IDE, notamment Visual Studio Code et PyCharm, ainsi qu’aux pipelines CI, tels que Jenkins, CircleCI et Maven, et aux workflows.

Par exemple, pour utiliser Snyk dans ce scénario de débogage, il vous suffit de rechercher « Snyk » dans Visual Studio Code et de l’installer :

Extension de sécurité Snyk pour VS Code.

Une fois l’installation terminée, Snyk apparaît dans le panneau de gauche. Cliquez sur le bouton Connect pour vous connecter ou créer un compte Snyk si vous n’en avez pas encore. Une fenêtre de navigateur semblable à celle-ci s’ouvrira alors :

Connectez l’extension de votre IDE à l’application Snyk.

Une fois connecté, retournez dans Visual Studio Code et ouvrez le dossier contenant votre script Flask. Vous devriez maintenant pouvoir repérer les vulnérabilités dans votre code :

Une application Python Flask affichant des vulnérabilités et des problèmes de sécurité du code dans l’IDE VS Code avec l’extension Snyk.

Comme vous pouvez le constater, Snyk a détecté que le mode débogage était toujours activé. Pour corriger ce problème, sécurisez votre application Flask en définissant le mode débogage sur 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)

En plus de désactiver le mode débogage en production, utilisez également le mécanisme de journalisation pour recueillir les journaux de l’application et repérer d’éventuels problèmes dans le code.

Données sensibles non protégées dans les fichiers de configuration

Stocker des informations critiques (par exemple, des identifiants de base de données, des clés d’API et des clés de chiffrement) dans des fichiers de configuration est une pratique courante dans les applications Flask. Cependant, ces données peuvent être compromises si vous ne protégez pas ces fichiers.

Prenons l’exemple d’un fichier de configuration (généralement config.ini) contenant les identifiants de connexion à la base de données :

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

Ici, le nom d’utilisateur et le mot de passe sont indiqués en texte brut. Si un attaquant accède à ce fichier, il peut utiliser ces identifiants pour consulter des informations sensibles.

Pour résoudre ce problème, le mieux est d’éviter les fichiers de configuration et d’utiliser des variables d’environnement pour stocker vos identifiants. Comme les variables d’environnement regroupent toutes les informations nécessaires dans un fichier caché, tel que .bash_profile, vous pouvez simplement ajouter ce fichier à gitignore afin d’éviter de le pousser accidentellement vers des plateformes de gestion de versions telles que Git ou Bitbucket, auxquelles les attaquants peuvent facilement accéder.

Si vous devez utiliser des fichiers de configuration dans votre application Flask, veillez à chiffrer les données sensibles :

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

Ici, un espace réservé ou une valeur chiffrée représente le mot de passe. Dans ce cas, les données sensibles peuvent être consultées à l’exécution à l’aide d’un système externe sécurisé de gestion des clés ou d’une méthode de déchiffrement sûre.

Vulnérabilités des applications Flask

Maintenant que vous en savez un peu plus sur les configurations non sécurisées des applications Python Flask, examinons quelques vulnérabilités courantes de sécurité web qui peuvent également toucher les applications Flask.

Scripts intersites (XSS)

Une vulnérabilité XSS est une faille de sécurité web par laquelle un attaquant tente d’injecter du code JavaScript ou HTML arbitraire dans une page web. Les attaquants peuvent ainsi exécuter des scripts malveillants dans le navigateur d’un utilisateur.

Jinja2, le moteur de templates utilisé par Flask, propose une fonctionnalité d’échappement automatique pour traiter les vulnérabilités XSS. Par défaut, toutes les variables sont automatiquement échappées (c’est-à-dire qu’elles ne contiennent aucun élément HTML, sauf indication contraire). Ainsi, toute entrée utilisateur contenant du code HTML ou JavaScript est échappée de manière sécurisée par Jinja2, ce qui empêche l’exécution de scripts malveillants.

Si l’échappement automatique est défini sur False, l’application Flask est vulnérable aux attaques XSS. Par exemple, voici une application web qui demande une saisie à l’utilisateur pour afficher une page :

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 page index.html correspondante, où l’échappement automatique est défini sur false, ressemblerait à ceci :

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

Si un attaquant injecte un script malveillant via le paramètre input, celui-ci s’exécute lors de l’affichage de la page et peut entraîner des risques de sécurité, tels que le vol de données, la prise de contrôle de comptes et des attaques par hameçonnage.

Pour prévenir les attaques XSS, les développeurs doivent configurer leur site web de manière à échapper correctement le texte et à exclure tout élément HTML. Il suffit de définir l’échappement automatique sur true pour protéger l’application Flask contre les attaques XSS :

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

Une autre méthode de prévention des attaques XSS consiste à échapper explicitement le balisage. Pour cela, utilisez le filtre |safe sur une page web, comme ceci :

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

Toutefois, utilisez |safe avec beaucoup de précaution, car ce filtre considère que le contenu marqué comme sûr est exempt de tout script malveillant.

Pour en savoir plus sur les vulnérabilités de type script intersite en Python, consultez une courte leçon de sécurité sur Snyk Learn.

Falsification de requêtes intersites (CSRF)

Une attaque CSRF trompe une victime pour l’amener à soumettre une requête malveillante. Dans une application web, l’attaquant envoie une requête non autorisée à une page au nom de l’utilisateur autorisé. Ce type d’attaque peut se produire sur des plateformes comme Facebook : quelqu’un envoie une image à un groupe et, si des membres cliquent dessus, leur compte est supprimé.

Pour mieux comprendre le CSRF, examinons une simple application qui modifie l’état sans protection CSRF :

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

Ce code envoie un formulaire pour changer le mot de passe à l’aide d’une requête POST. En l’absence de protection CSRF, un attaquant peut inciter les utilisateurs à modifier leur mot de passe en les redirigeant vers un autre site web contenant du code HTML ou un script spécialement conçu. Un formulaire sans protection CSRF ressemble à ceci :

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

Pour prévenir les vulnérabilités CSRF, utilisez l’extension Flask-WTF avec le middleware CSRFProtect. Flask-WTF est un plug-in Flask qui intègre la bibliothèque WTForms, laquelle fournit des fonctionnalités pratiques pour créer et gérer facilement des formulaires dans une application web Flask. 

Vous pouvez l’installer avec pip comme suit :

pip install Flask-WTF

Une fois l’installation terminée, vous pouvez mettre à jour votre application Flask avec Flask-WTF pour la protéger contre les attaques 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'))

Ici, CSRFProtect est initialisé avec l’application Flask et la variable SECRET_KEY est définie. Cette clé sert spécifiquement à créer et à valider les jetons CSRF.

N’oubliez pas de mettre à jour le template (index.html) pour y inclure le jeton CSRF, comme ceci :

<!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 fonction csrf_token() génère un champ de saisie masqué contenant le jeton CSRF. Lors de l’envoi du formulaire, Flask-WTF valide le jeton pour s’assurer que la requête ne résulte pas d’une attaque CSRF.

Injection SQL et sécurité des bases de données

Examinons maintenant quelques problèmes liés à la sécurité des bases de données dans Flask :

Injection SQL

L’injection SQL est un problème grave : un attaquant exécute du code SQL arbitraire sur une base de données en manipulant les paramètres d’une requête SQL. Cette attaque peut entraîner des accès non autorisés, la manipulation de données et la divulgation d’informations sensibles.

Dans les applications Flask, une attaque par injection SQL peut se produire si les entrées ne sont pas correctement assainies avant d’être utilisées dans une requête SQL.

Pour mieux comprendre cette attaque, examinons un exemple de code Flask qui récupère des données dans une base de données :

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'

Cette requête attend le nom d’utilisateur et le mot de passe dans la chaîne de requête. Si un attaquant injecte des valeurs telles que ' OR '1'='1'; -- , la requête devient :

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

Comme cette requête renvoie toujours True, l’attaquant peut se connecter et accéder sans autorisation aux données.

Il est difficile de repérer le code vulnérable aux injections SQL, mais Snyk peut vous aider. Snyk étant déjà configuré dans Visual Studio Code, il vous suffit d’ouvrir le script pour que Snyk détecte automatiquement le problème d’injection SQL :

Snyk signale une vulnérabilité d’injection SQL due à la concaténation des entrées utilisateur dans une requête SQL.

Pour atténuer facilement cette attaque par injection SQL, utilisez des requêtes ou des instructions paramétrées. Dans

Flask, vous pouvez également utiliser la méthode execute de l’objet curseur pour lier les paramètres de manière sécurisée. Une requête paramétrée ressemble à ceci :

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'

Dans ce code, des espaces réservés ? sont utilisés. Leurs valeurs réelles sont fournies sous forme de tuple dans la méthode execute, ce qui garantit que les entrées utilisateur sont traitées comme des données et non comme du code SQL exécutable.

Veillez à toujours valider et assainir les entrées utilisateur pour éviter ce type d’attaque. Vous pouvez assainir les entrées à l’aide de techniques comme les caractères d’échappement, les bibliothèques tierces et les expressions régulières.

Pour approfondir le sujet des injections SQL, je vous recommande de lire Le guide de prévention des injections SQL : 8 bonnes pratiques pour les contrer.

Authentification et autorisation

L’authentification et l’autorisation sont des composantes essentielles de la sécurité web. Une faille dans l’authentification ou l’autorisation peut entraîner plusieurs problèmes de sécurité, que nous allons aborder.

Problèmes de sécurité liés à l’authentification

L’un des problèmes de sécurité les plus courants liés à l’authentification est l’attaque par force brute, lors de laquelle un attaquant tente à plusieurs reprises de deviner un nom d’utilisateur et un mot de passe pour accéder à un site web ou à un service. Un autre problème fréquent est la fixation de session, où l’attaquant définit l’ID de session sur une valeur connue, éventuellement au moyen d’une attaque par hameçonnage.

Pour atténuer les attaques par force brute, vous devez mettre en place un mécanisme de verrouillage de session. Dans ce cadre, l’utilisateur est bloqué après un certain nombre de tentatives de connexion infructueuses :

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

Dans ce code, vous définissez MAX_LOGIN_ATTEMPTS sur 3. Ainsi, si une personne saisit des identifiants incorrects plus de trois fois, son compte est bloqué.

Dans la méthode login(), vous récupérez le username et le password, puis vérifiez si l’utilisateur est bloqué. Si ce n’est pas le cas, vous vérifiez le nombre de tentatives de connexion. Si les identifiants sont corrects, tout va bien. Sinon, le nombre de tentatives augmente de un et le processus se répète jusqu’à ce que l’utilisateur parvienne à se connecter ou soit bloqué.

En plus d’un mécanisme de verrouillage de session, vous devriez également appliquer des règles robustes en matière de mots de passe et envisager d’utiliser l’authentification multifacteur (MFA).

Pour atténuer les problèmes de fixation de session, vous pouvez régénérer les ID de session après la connexion de l’utilisateur ou au bout d’un certain temps (expiration automatique), même si l’utilisateur reste connecté :

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

Dans ce code, la méthode login() régénère l’ID de session à l’aide de la méthode regenerate() une fois la connexion de l’utilisateur réussie.

Problèmes de sécurité liés à l’autorisation

Lors du développement d’une application web, toutes les fonctionnalités ne sont pas destinées à chaque utilisateur (c.-à-d. un utilisateur donné ne devrait avoir accès qu’à un ensemble limité de fonctionnalités). Si les rôles ne sont pas correctement attribués aux utilisateurs, des accès non autorisés peuvent se produire. C’est pourquoi vous devez mettre en place un contrôle d’accès basé sur les rôles (RBAC).

Il est également possible que des utilisateurs authentifiés accèdent à des ressources non autorisées en manipulant des paramètres ou des URL. Ce problème est souvent appelé référence directe non sécurisée à un objet (IDOR). Pour en savoir plus, je vous recommande vivement de lire cet article sur la détection et la correction des références directes non sécurisées à un objet en Python.

Pour résoudre les problèmes liés au RBAC et aux IDOR, vous devez attribuer des rôles spécifiques aux utilisateurs et appliquer des contrôles d’accès en fonction de ces rôles, comme ceci :

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'

Dans ce code, la méthode admin_dashboard() implémente les fonctionnalités auxquelles les utilisateurs ayant le rôle admin doivent pouvoir accéder. Dans le cas contraire, elle renvoie un avertissement « Unauthorized access ».

Vous devez également gérer le contrôle d’accès afin que les utilisateurs ne puissent accéder qu’aux ressources auxquelles ils sont autorisés :

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'

Vous ne devriez pas stocker les identifiants en texte brut, car des attaquants pourraient facilement y accéder à des fins malveillantes. Pour l’éviter, vous devriez utiliser le hachage et le salage pour stocker les identifiants des utilisateurs.

Le hachage transforme un mot de passe en une chaîne de caractères de longueur fixe, rendant le processus inverse et la récupération du mot de passe d’origine irréalisables sur le plan informatique. Le salage consiste à ajouter une valeur aléatoire unique (le sel) à chaque mot de passe avant le hachage, ce qui contribue à prévenir les attaques par tables arc-en-ciel.

Bibliothèques obsolètes et mises à jour de sécurité

Si vous utilisez des bibliothèques Flask obsolètes, elles peuvent contenir des vulnérabilités exploitables par des attaquants. Les bibliothèques publient régulièrement des mises à jour de sécurité et des correctifs pour résoudre les vulnérabilités. Veillez toujours à utiliser la version la plus récente des packages.

Pour vous assurer d’utiliser les derniers correctifs de sécurité, vous pouvez utiliser des outils comme pip ou conda. Vous pouvez également utiliser des outils comme Snyk pour analyser l’ensemble de votre code, ainsi que vos dépendances et vos configurations d’infrastructure as code (IaC).

Une autre bonne pratique consiste à figer les dépendances dans le fichier requirements.txt. Le projet utilise ainsi des versions spécifiques, ce qui permet aux développeurs de maîtriser les mises à jour et de tester la compatibilité.

Sécuriser les téléversements de fichiers

Si un site Flask permet à n’importe qui de téléverser n’importe quel type de fichier, plusieurs problèmes peuvent survenir. Par exemple, si un attaquant téléverse des fichiers malveillants sur le serveur, une attaque par déni de service (DoS) peut se produire, avec un risque d’écrasement des fichiers existants. Certains fichiers exécutables peuvent également contenir différents types de virus informatiques.

Voici un exemple de code qui permet à n’importe quel utilisateur de téléverser n’importe quel fichier :

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

Dans ce code, la méthode upload_file() lit le fichier fourni par l’utilisateur, puis le stocke dans le dossier uploads.

La première solution pour éviter les fichiers non sécurisés consiste à définir une liste d’extensions de fichiers autorisées. Vous ne devriez jamais autoriser les fichiers exécutables ou les scripts dans votre application Flask.

Veillez également à stocker les fichiers dans un emplacement sécurisé afin d’éviter tout accès non autorisé :

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)

Ici, plusieurs conditions ont été appliquées pour vérifier l’authenticité des types de fichiers.

Une autre bonne pratique consiste à attribuer des noms aléatoires aux fichiers afin d’éviter leur écrasement et de réduire le risque lié à des conventions de nommage prévisibles :

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)

Gestion des erreurs et divulgation d’informations

La gestion des erreurs est importante dans tout type d’application. En production, il est déconseillé d’utiliser des instructions print et d’activer le mode débogage. Un mécanisme de journalisation inapproprié peut révéler des informations importantes sur votre application.

Par exemple, afficher un message d’erreur détaillé en production peut révéler des informations sensibles sur le fonctionnement interne de l’application et faciliter l’identification de vulnérabilités potentielles par des attaquants :

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

Cet exemple révèle les informations les plus importantes sur la base de données associée à l’application Flask.

De plus, afficher les traces complètes dans les réponses d’erreur peut révéler la structure du code de l’application et potentiellement exposer des vulnérabilités, comme ceci :

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

Pour atténuer la divulgation d’informations lors de la gestion des erreurs, vous pouvez créer des pages d’erreur personnalisées pour les codes d’erreur génériques (p. ex. 404 Not Found et 500 Internal Server Error), sans révéler de détails sensibles. Pour cela, vous pouvez utiliser le décorateur errorhandler afin de définir des gestionnaires d’erreurs personnalisés :

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

Vous devriez également mettre en place une journalisation adéquate des exceptions et des erreurs. Les messages de journal doivent être suffisamment détaillés pour faciliter le débogage, sans révéler d’informations sensibles :

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

En appliquant ces bonnes pratiques, vous pouvez vous assurer que votre application Flask est robuste et sécurisée.

Conclusion

Flask est un framework web populaire pour les applications web de petite et moyenne envergure. Cependant, comme tout autre outil ou framework de développement web, Flask peut présenter des problèmes s’il n’est pas utilisé correctement. Il est important non seulement de connaître ces problèmes, mais aussi de savoir les atténuer pour empêcher les acteurs malveillants d’en tirer parti.

Nous vous invitons à suivre ce dépôt de code sur GitHub pour découvrir des exemples complets et fonctionnels de certains des sujets liés à la sécurité des applications Flask abordés dans cet article.

Snyk vous aide à détecter et à atténuer facilement les vulnérabilités de votre code. Snyk prend en charge différents langages de programmation et s’intègre parfaitement à vos outils, pipelines et workflows. Commencez gratuitement avec Snyk dès aujourd’hui.

Enfin, suivez le guide des bonnes pratiques de sécurité Python de Snyk et développez vos compétences en sécurité des applications Python grâce au site gratuit Snyk Learn.

Lire la suite

Blog

Les modèles de pointe ont trouvé les vulnérabilités. Seul l’attaquant a trouvé les chaînes d’exploitation.

L’analyse statique a détecté les failles, mais seuls des tests d’attaque en conditions réelles ont prouvé comment elles pouvaient être enchaînées pour provoquer des compromissions. Comparaison d’Evo COS, de Claude Security et de Claude Code Security.

feature insights context
Blog

Les attaques autonomes sont déjà là. La défense doit suivre leur rythme.

Les attaquants autonomes réduisent la fenêtre de défense. Découvrez comment la découverte, la correction, la validation et la prévention continues peuvent aider les équipes de sécurité à suivre le rythme.

Blog

Pourquoi les agents de codage IA créent-ils sans cesse des failles de contrôle d’accès ?

Les agents de codage IA peuvent générer une logique d’autorisation qui compile et passe la revue, tout en exposant les données d’un tenant à un autre. Découvrez pourquoi les failles de contrôle d’accès sont difficiles à détecter et comment les prévenir.