Lors du développement d’outils web et d’APIs avec Python, la plupart des développeurs se concentrent sur l’implémentation des fonctionnalités. Mais dans les projets réels, la qualité de la conception sécurité équivaut à la qualité du service. Un service sans protection des données, résistance aux attaques et sécurité opérationnelle ne gagnera jamais la confiance des utilisateurs.
Cet article couvre 10 patterns de sécurité que tout développeur Python devrait implémenter au minimum, avec des exemples de code NG (mauvais) et OK (bon). Que vous utilisiez Django, FastAPI ou écriviez des scripts Python, ce guide est pour vous.
Pour les connaissances fondamentales sur les mots de passe, hashes et tokens, consultez « Comprendre les Mots de Passe, UUIDs, Hashes et Tokens ».
Vue d’ensemble : 10 Patterns de Sécurité Python
Commençons par la vue d’ensemble. Voici les 10 patterns en un coup d’œil, avec des liens vers chaque section.
| Pattern | Objectif | Priorité | OWASP |
|---|---|---|---|
| ① Génération aléatoire sécurisée | Tokens imprévisibles | Obligatoire | A02 Défaillances cryptographiques |
| ② Stockage des mots de passe | Protection des identifiants | Obligatoire | A02 Défaillances cryptographiques |
| ③ Conception JWT sécurisée | Contrôle d’authentification | Obligatoire | A07 Défaillances d’authentification |
| ④ Prévention SQL Injection | Protection de la base de données | Obligatoire | A03 Injection |
| ⑤ Prévention XSS | Sécurité d’affichage | Obligatoire | A03 Injection |
| ⑥ Protection CSRF | Sécurité des formulaires | Obligatoire | A01 Contrôle d’accès défaillant |
| ⑦ Limitation de débit | Prévention des abus | Important | — |
| ⑧ Conception des logs | Prévention des incidents | Important | A09 Défaillances de journalisation |
| ⑨ Gestion des secrets | Prévention des fuites | Obligatoire | A02 Défaillances cryptographiques |
| ⑩ Configuration par environnement | Sécurité opérationnelle | Obligatoire | A05 Mauvaise configuration |
Nous avons également inclus les correspondances OWASP Top 10. Couvrir ces 10 patterns seul adresse la majorité des risques identifiés par l’OWASP.
① Génération Aléatoire Sécurisée — N’utilisez jamais random
Lors de la génération de tokens, codes d’authentification ou URLs de réinitialisation de mot de passe, le module random de Python ne doit jamais être utilisé. random utilise l’algorithme Mersenne Twister, qui est prévisible — un attaquant observant quelques centaines de sorties peut prédire les valeurs futures.
Mauvais exemple :
import random
token = random.randint(100000, 999999) # Prévisible !
Bon exemple :
import secrets
token = secrets.token_hex(16)
print(token)
# Exemple : 9f3c0c6d61c3c2e7b2c5a2b41a6f9d88
Le module secrets utilise le générateur de nombres pseudo-aléatoires cryptographiquement sécurisé (CSPRNG) du système, rendant la prédiction pratiquement impossible. Utilisez secrets pour toute génération aléatoire liée à la sécurité.
secrets.token_urlsafe(32) génère des tokens sûrs pour les URLs, parfaits pour les liens de réinitialisation de mot de passe. Consultez « Patterns de Génération de Mots de Passe en Python » pour plus de détails.
② Stockage des Mots de Passe — Le Texte Clair Est Inacceptable
Stocker des mots de passe en texte clair est l’une des défaillances de sécurité les plus catastrophiques. Dès que votre base de données fuite, tous les mots de passe des utilisateurs tombent entre les mains de l’attaquant.
Mauvais exemple :
password = "user_password"
db.save(password) # Texte clair → brèche instantanée
Bon exemple :
import bcrypt
password = b"mypassword"
hashed = bcrypt.hashpw(password, bcrypt.gensalt())
# Vérifier
is_valid = bcrypt.checkpw(password, hashed)
print(is_valid) # True
pip install bcrypt
Utiliser des fonctions de hachage génériques comme SHA-256 seules est également dangereux. SHA-256 est trop rapide — les attaquants peuvent tester des milliards de hashes par seconde. bcrypt et Argon2 sont intentionnellement lents, rendant les attaques par force brute exponentiellement plus coûteuses.
« J’ai hashé avec SHA-256, donc c’est sécurisé » est faux. Utilisez toujours des algorithmes intentionnellement lents comme bcrypt, Argon2 ou scrypt. Pour les fondamentaux du hashing, consultez « Mots de Passe, UUIDs, Hashes et Tokens ».
③ Conception JWT Sécurisée
JWT (JSON Web Token) est largement utilisé pour l’authentification sans état, mais une mauvaise configuration entraîne des vulnérabilités graves.
Mauvais exemple :
import jwt
token = jwt.encode({"user_id": 1}, "secret") # Sans expiration, secret faible
Bon exemple :
import jwt
import datetime
payload = {
"user_id": 1,
"exp": datetime.datetime.utcnow() + datetime.timedelta(hours=1)
}
token = jwt.encode(payload, "STRONG_SECRET_KEY_HERE", algorithm="HS256")
# Vérifier
data = jwt.decode(token, "STRONG_SECRET_KEY_HERE", algorithms=["HS256"])
pip install PyJWT
Trois règles pour une conception JWT sécurisée :
- Toujours définir une expiration (exp) — les tokens sans expiration peuvent être exploités indéfiniment
- Spécifier explicitement l’algorithme — l’omettre risque des attaques d’algorithme « none »
- Utiliser une clé secrète forte — générer avec
secrets.token_hex(32)et stocker dans les variables d’environnement
L’expiration de l’access token devrait être de 15 minutes à 1 heure. Pour les sessions persistantes, combinez avec des refresh tokens et gardez les access tokens à courte durée de vie.
④ Prévention de l’Injection SQL
L’injection SQL est l’une des vulnérabilités les plus anciennes et encore les plus courantes. Concaténer directement l’entrée utilisateur dans les requêtes SQL permet aux attaquants de manipuler votre base de données à volonté.
Mauvais exemple :
# Ne faites jamais cela
query = "SELECT * FROM users WHERE name='" + name + "'"
cursor.execute(query)
Si name contient admin' OR '1'='1, toutes les données utilisateurs sont exposées.
Bon exemple :
# Utiliser le parameter binding
cursor.execute("SELECT * FROM users WHERE name = %s", (name,))
Avec le parameter binding, l’entrée utilisateur est traitée comme des données, pas comme partie de la requête SQL. Django ORM et SQLAlchemy utilisent cette approche par défaut, mais utilisez toujours le parameter binding lors de l’écriture de SQL brut.
Les f-strings (f"SELECT ... WHERE name='{name}'") sont tout aussi dangereuses que la concaténation. Elles peuvent sembler plus propres, mais le risque d’injection SQL est identique.
⑤ Prévention XSS
Le XSS (Cross-Site Scripting) survient lorsque l’entrée utilisateur est rendue directement en HTML, permettant l’exécution de scripts malveillants dans le navigateur.
Mauvais exemple :
# Retourner l'entrée utilisateur telle quelle
return user_input
# Si l'attaquant saisit <script>alert(1)</script> il s'exécute
Bon exemple :
import html
safe_output = html.escape(user_input)
return safe_output
# <script> → <script> — neutralisé
Le principe de prévention XSS est « ne jamais faire confiance à la sortie ». Échappez à l’étape de sortie (lors du rendu HTML), pas à l’entrée. Les templates Django et Jinja2 activent l’échappement HTML par défaut, mais soyez particulièrement prudent avec les filtres |safe ou mark_safe().
Lorsque vous utilisez {{ variable|safe }} dans les templates Django, appliquez-le uniquement aux données de confiance. Appliquer |safe à l’entrée utilisateur revient à ouvrir la porte au XSS.
⑥ Protection CSRF
Le CSRF (Cross-Site Request Forgery) trompe les utilisateurs authentifiés pour qu’ils envoient des requêtes non souhaitées. La défense consiste à intégrer des tokens émis par le serveur dans les formulaires et à les valider lors de la soumission.
Django (intégré) :
<form method="POST">
{% csrf_token %}
<input type="text" name="data">
<button type="submit">Envoyer</button>
</form>
FastAPI :
from itsdangerous import URLSafeSerializer
s = URLSafeSerializer("secret-key")
csrf_token = s.dumps("session_id")
# Vérifier
try:
data = s.loads(received_token)
except Exception:
raise HTTPException(status_code=403)
pip install itsdangerous
Django a la protection CSRF activée par défaut. Pour les frameworks sans protection CSRF intégrée comme FastAPI, vous devrez implémenter la génération et la validation de tokens vous-même.
⑦ Limitation de Débit (Rate Limiting)
La limitation de débit restreint le nombre de requêtes dans une fenêtre de temps donnée. Elle prévient les attaques par force brute, l’abus d’API et le flooding par bots.
from slowapi import Limiter
from slowapi.util import get_remote_address
limiter = Limiter(key_func=get_remote_address)
@app.get("/api/data")
@limiter.limit("10/minute")
def get_data():
return {"status": "ok"}
pip install slowapi
Pour Django, django-ratelimit est le choix standard. Définissez toujours des limites de débit sur ces endpoints :
- Login — cible directe des attaques par force brute
- Réinitialisation de mot de passe — prévient le bombardement d’emails
- Endpoints API — prévient l’abus et l’escalade des coûts
- Formulaires d’inscription — prévient la création de comptes spam
La limitation de débit ne « prévient pas complètement » les attaques — elle augmente dramatiquement le coût d’attaque. La combiner avec une limitation de débit au niveau Nginx bloque les requêtes avant qu’elles n’atteignent votre application.
⑧ Conception des Logs — Ce Que Vous Ne Devez Jamais Enregistrer
La journalisation est essentielle pour le débogage et la surveillance de sécurité, mais il y a des informations qui ne doivent jamais apparaître dans les logs. Si des mots de passe, tokens, API keys ou session IDs se retrouvent dans les fichiers de log, ces fichiers deviennent des vecteurs d’attaque.
Mauvais exemple :
# Ne faites jamais cela
print(f"Login: user={username}, password={password}")
logging.info(f"Token: {api_token}")
Bon exemple :
import logging
logging.info("Login attempt: user=%s, ip=%s", username, request_ip)
logging.warning("Failed login: user=%s, ip=%s", username, request_ip)
Les règles sont simples :
- Ne jamais enregistrer : mots de passe, tokens, API keys, cookies, données personnelles
- À enregistrer : IDs utilisateur, adresses IP, noms d’action, timestamps, résultats (succès/échec)
Déployer avec DEBUG=True en production expose les stack traces, variables d’environnement et détails de connexion à la base de données. Dans Django, configurez toujours DEBUG=False en production.
⑨ Gestion des Secrets
API keys, mots de passe de bases de données, clés secrètes JWT — ces secrets ne doivent jamais être codés en dur dans le code source. Dès que votre code apparaît sur GitHub, tout fuite.
Mauvais exemple :
# Codé en dur → fuite instantanée au push Git
SECRET_KEY = "abc123superSecret"
DB_PASSWORD = "production_password"
Bon exemple :
import os
from dotenv import load_dotenv
load_dotenv()
SECRET_KEY = os.getenv("SECRET_KEY")
DB_PASSWORD = os.getenv("DB_PASSWORD")
pip install python-dotenv
Exemple de fichier .env :
SECRET_KEY=9f3c0c6d61c3c2e7b2c5a2b41a6f9d88
DB_PASSWORD=strong_random_password_here
Et ajoutez toujours .env à votre .gitignore.
GitHub dispose du « Secret Scanning » qui détecte automatiquement les API keys commises. Cependant, les attaquants peuvent être plus rapides. La meilleure défense est de ne jamais commiter de secrets.
⑩ Configuration par Environnement
Utiliser la même configuration en développement, staging et production invite aux accidents. Mode debug actif en production, connexions de test manipulant des données en production — ce sont des incidents réels.
import os
ENV = os.getenv("ENV", "development")
if ENV == "production":
DEBUG = False
ALLOWED_HOSTS = ["example.com"]
else:
DEBUG = True
ALLOWED_HOSTS = ["*"]
Séparer la configuration par variables d’environnement permet de changer le comportement par environnement sans modifier le code. Pour Django, django-environ ajoute une gestion typée ; pour FastAPI, pydantic-settings offre des avantages similaires.
Checklist de Sécurité et Incidents Courants
Vérifiez cette liste avant chaque mise en production :
- □ Utilisation de
secretspour la génération aléatoire ? - □ Mots de passe hashés avec bcrypt/Argon2 ?
- □ JWT avec expiration (exp) définie ?
- □ SQL utilisant le parameter binding ?
- □ Sortie HTML échappée ?
- □ Formulaires incluant des tokens CSRF ?
- □ Login/API avec limitation de débit ?
- □ Logs sans mots de passe/tokens ?
- □ Secrets gérés via variables d’environnement ?
- □ DEBUG=False en production ?
Connaissez également les incidents de sécurité les plus courants :
| Rang | Incident | Contre-mesure |
|---|---|---|
| #1 | Secrets poussés sur Git | .env + .gitignore |
| #2 | DEBUG actif en production | Configuration par environnement |
| #3 | Concaténation de chaînes SQL | Parameter binding |
| #4 | JWT sans expiration | Configuration exp obligatoire |
| #5 | Pas de limitation de débit | slowapi / django-ratelimit |
Prévenir ces cinq seuls couvre la grande majorité des incidents de sécurité rencontrés en pratique.
FAQ
Q : Quel est le minimum pour la sécurité Python ?
secrets (aléatoire sécurisé), bcrypt (stockage de mots de passe), variables d’environnement (gestion des secrets), parameter binding (sécurité SQL) et html.escape (prévention XSS). Ces cinq seuls créent un écart considérable par rapport aux applications non sécurisées.
Q : Django est-il sécurisé par défaut ?
Les paramètres par défaut de Django sont solides — tokens CSRF, échappement XSS et prévention d’injection SQL sont tous intégrés. Cependant, la mauvaise configuration est courante : laisser DEBUG=True en production, mauvais usage des filtres |safe et SQL brut négligé sont des sources fréquentes de vulnérabilités.
Q : JWT est-il toujours nécessaire ?
Non. Pour les petites applications web, l’authentification par sessions fonctionne bien. JWT brille dans les architectures microservices ou les SPAs où l’authentification sans état est nécessaire.
Q : La limitation de débit est-elle nécessaire ?
Pour toute API ou formulaire de login accessible publiquement, oui. Elle peut ne pas être nécessaire pour des scripts internes, mais les endpoints publics sans limitation de débit sont des cibles de choix pour les bots et attaques par force brute.
Q : Quel est l’incident de sécurité le plus courant ?
Les secrets (API keys, mots de passe) poussés sur GitHub. Des bots scannent GitHub en permanence à la recherche d’API keys, et l’exploitation peut survenir quelques minutes après l’exposition.
Conclusion
Construire des outils web sécurisés en Python ne nécessite pas de cryptographie avancée — cela nécessite de maîtriser les fondamentaux. Parmi les 10 patterns couverts, voici les 5 principaux :
- Générer des valeurs aléatoires sécurisées avec
secrets - Hasher les mots de passe avec bcrypt
- Gérer les secrets via les variables d’environnement
- Utiliser le parameter binding pour la sécurité SQL
- Prévenir le XSS avec
html.escape
La sécurité n’est pas quelque chose qu’on ajoute après coup — c’est quelque chose qu’on conçoit dès le départ. Ce principe est le fondement du développement Python professionnel.

Laisser un commentaire