Le mot de passe de 1 Mo : faire planter les backends via l'épuisement du hashing 🏎️💥

Dans le domaine de la cybersécurité, on nous apprend souvent que la lenteur est préférable. Lorsqu’il s’agit de hashing de mot de passe, nous choisissons délibérément des algorithmes comme Argon2id ou Bcrypt car ils sont “coûteux”. Ils sont conçus pour consommer des cycles CPU et de la mémoire afin de rendre économiquement impossible pour les hackers de réaliser des attaques par force brute.
Mais que se passe-t-il lorsque cet “investissement” se retourne contre vous ?
Entrez dans le 1MB Password Attack. C’est un vecteur de déni de service (DoS) silencieux, élégant et d’une simplicité dévastatrice. En envoyant une chaîne surdimensionnée — disons, 1 mégaoctet de caractères aléatoires — dans votre champ de connexion ou d’inscription, un attaquant peut forcer votre serveur à passer des secondes, voire des minutes, à hasher une seule requête.
Une dizaine de ces requêtes peut faire monter votre CPU à 100 %, désactivant efficacement tout votre backend. Dans cet article, nous analyserons la mécanique de l’épuisement du hashing, pourquoi les frameworks modernes vous rendent vulnérable, et comment y remédier sans compromettre la sécurité.
1. Le paradoxe du hashing “lent”
Pour comprendre cette vulnérabilité, il faut examiner comment fonctionnent les fonctions de hashing. Les algorithmes modernes comme Bcrypt, Scrypt et Argon2 sont “adaptatifs”. Ils utilisent un Work Factor (ou coût) pour déterminer le nombre d’itérations de la fonction de hashing.
$O(n)$ vs. Work Factor
La plupart des développeurs se concentrent sur le work factor. Si vous réglez Bcrypt sur un coût de 12, cela prend environ 250 ms pour hasher un mot de passe. Si vous passez à 13, cela prend 500 ms. Ce temps est constant, que le mot de passe soit “password123” ou “correct-horse-battery-staple”.
Cependant, il existe une variable cachée : la longueur de l’entrée ($n$).
Alors que le work factor offre une protection exponentielle contre la force brute, la fonction de hashing doit encore traiter chaque octet de la chaîne d’entrée. La complexité est approximativement $O(n \times \text{work factor})$.
Quand $n$ est de 15 caractères, l’impact est négligeable. Quand $n$ atteint 1 000 000 de caractères (1 Mo), le CPU doit calculer le hash sur une quantité massive de données tout en exécutant des milliers d’itérations.
Pourquoi c’est un “tueur silencieux”
La plupart des attaques DoS sont volumétriques (inondation du réseau) ou basées sur des protocoles (SYN floods). Les pare-feux et WAF (Web Application Firewalls) sont efficaces pour bloquer ces attaques. Mais une requête POST de 1 Mo ressemble à une donnée utilisateur parfaitement valide (même si un peu volumineuse). Comme elle cible la couche applicative, elle contourne de nombreuses défenses réseau traditionnelles.
2. Anatomie de l’attaque : la charge utile de 1 Mo
Imaginez un point d’entrée de connexion standard : POST /api/v1/login.
Une requête typique pourrait ressembler à ceci :
{
"username": "victim@example.com",
"password": "pA$$w0rd123!"
}
Maintenant, imaginez que l’attaquant envoie ceci :
{
"username": "victim@example.com",
"password": "a...[999 990 'a']...a"
}
La montée en charge du CPU
Lorsque votre serveur reçoit cela, il passe cette chaîne de 1 Mo à la bibliothèque de hashing.
- L’impact mémoire : le serveur doit allouer au moins 1 Mo de RAM juste pour contenir la chaîne dans le corps de la requête.
- L’impact hashing : le CPU commence à tourner à plein régime. Pour Argon2id, qui est mémoire-intensive, il pourrait essayer d’allouer 64 Mo de RAM par hash et exécuter 3 itérations sur cette entrée de 1 Mo.
- Le blocage du thread : dans de nombreux environnements (comme Node.js ou Python/Django), le hashing s’effectue sur le thread principal ou un pool limité de workers. Pendant que le CPU hash cette chaîne de 1 Mo, il ne peut pas traiter d’autres requêtes.
3. Comparaison des vulnérabilités : Bcrypt vs. Argon2 vs. PBKDF2
Tous les algorithmes de hashing ne gèrent pas la longueur des chaînes de la même façon. Ironiquement, certains “anciens” défauts offrent en réalité une couche de protection.
Bcrypt : le “bouclier” de 72 octets
Bcrypt a une particularité célèbre : il ne considère que les 72 premiers octets d’un mot de passe. Les caractères après le 72ème sont ignorés.
- Le point faible : les utilisateurs avec des mots de passe de 100 caractères ne sont pas aussi sécurisés qu’ils le pensent.
- L’avantage DoS : comme il tronque l’entrée presque immédiatement, envoyer un mot de passe de 1 Mo à une fonction Bcrypt ne provoque généralement pas de pic CPU. La bibliothèque regarde les 72 premiers octets et ignore le reste.
Argon2id : la vulnérabilité moderne
Argon2id (le standard actuel en 2026) a été conçu pour corriger les limitations de Bcrypt. Il gère des entrées de longueur arbitraire. Si cela renforce la sécurité et les passphrases, cela signifie aussi qu’il essaiera de hasher tout le 1 Mo que vous lui envoyez. Sans limite au niveau de l’application, Argon2 devient une cible privilégiée pour les attaques d’épuisement.
PBKDF2 : le cauchemar des itérations
PBKDF2 est souvent utilisé avec SHA-256. Bien qu’il n’ait pas de limite de 72 octets, il est purement CPU-bound. Si un attaquant envoie une chaîne massive, la charge CPU augmente linéairement avec la longueur. Dans des frameworks comme Django (qui utilise PBKDF2 par défaut), cela peut entraîner une latence importante, même si le serveur ne plante pas immédiatement.
4. Pourquoi les frameworks manquent souvent cette vulnérabilité
En 2025 et 2026, de nombreux frameworks ont évolué vers des réglages “sécurisés par défaut”, mais ils se concentrent sur la longueur minimale, pas maximale.
- Django : possède
MinimumLengthValidator(par défaut 8 caractères). Il ne dispose pas d’unMaximumLengthValidatoractivé par défaut pour les mots de passe. - Laravel : valide
min:8, mais rarementmax:128. - Node.js (librairies Bcrypt/Argon2) : la plupart acceptent simplement une chaîne ou un Buffer. Si l’application passe le corps brut dans la fonction, la librairie tentera de le traiter.
La philosophie a toujours été : “Pourquoi empêcher un utilisateur d’avoir un mot de passe plus long et plus sécurisé ?” On a oublié qu’un “mot de passe” de 1 Mo n’est pas un mot de passe — c’est une grenade.
5. Mitigations stratégiques : protéger votre backend
Se défendre contre l’épuisement du hashing nécessite une approche en plusieurs couches. Il faut arrêter la “grenade” aussi loin que possible de votre CPU.
Mitigation 1 : La règle d’or (limites de longueur maximale)
La défense la plus simple et efficace est de fixer une limite stricte à la longueur du mot de passe.
Recommandation : fixer une longueur maximale de 128 ou 256 caractères.
Même le plus extrême des experts en sécurité n’a pas besoin d’un mot de passe de plus de 256 caractères. C’est suffisamment long pour toute phrase secrète raisonnable, tout en étant court pour que le hashing reste trivial pour le serveur.
Exemple (Django):
# settings.py
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
'OPTIONS': {'min_length': 12},
},
# Ajoutez un MaxLengthValidator personnalisé ou intégré si disponible
]
Mitigation 2 : La technique du “Pre-Hash” SHA-256
Si vous devez absolument autoriser des mots de passe de longueur infinie (pour une raison cryptographique niche), utilisez un Pre-Hash.
Avant de passer le mot de passe à Bcrypt ou Argon2, le hacher rapidement avec une fonction non adaptative comme SHA-256.
- L’utilisateur envoie un mot de passe de 1 Mo.
- Le serveur calcule
temp_hash = SHA-256(mot_de_passe). - La sortie de SHA-256 est toujours une chaîne fixe de 32 octets.
- Le serveur calcule
final_hash = Bcrypt(temp_hash).
Cela garantit que l’algorithme “coûteux” (Bcrypt/Argon2) ne voit qu’une petite entrée de taille fixe.
Note : Si vous implémentez cela, soyez cohérent. Vous ne pouvez pas passer à un pré-hachage pour les utilisateurs existants sans plan de migration, car les hashes ne correspondront plus.
Mitigation 3 : Limitation du débit par IP et nom d’utilisateur
Les attaques d’épuisement reposent sur le volume. Même si un mot de passe de 1 Mo prend 2 secondes à hasher, une seule requête ne tue pas un serveur. Dix requêtes simultanées, oui.
- Implémentez une limitation stricte du débit sur les endpoints de connexion et d’inscription.
- Utilisez un algorithme de “leaky bucket” pour ralentir les tentatives répétées depuis la même IP.
Mitigation 4 : WAF et limites de payload
Configurez votre Load Balancer (Nginx, AWS ALB) ou WAF (Cloudflare, Akamai) pour rejeter les payloads suspectement volumineux.
Si votre formulaire de connexion ne comporte que le nom d’utilisateur et le mot de passe, le corps POST entier ne devrait pas dépasser 10 Ko. Rejetez tout ce qui est plus grand à la périphérie.
6. Exemples concrets d’implémentation
Node.js (Express + Joi)
Utiliser une bibliothèque de validation comme Joi facilite l’application de ces limites avant que les données n’atteignent votre logique de hashing.
const loginSchema = Joi.object({
username: Joi.string().email().required(),
password: Joi.string().min(12).max(128).required() // La ligne critique
});
app.post('/login', (req, res) => {
const { error } = loginSchema.validate(req.body);
if (error) return res.status(400).send(error.details[0].message);
// Ce n'est qu'à ce moment que l'on appelle le hash coûteux
const isValid = await bcrypt.compare(req.body.password, user.hash);
});
PHP (Laravel)
Le moteur de validation de Laravel simplifie cela en une ligne.
$request->validate([
'email' => 'required|email',
'password' => 'required|string|min:12|max:255', // Fixe la limite
]);
7. Tableau comparatif : Algorithmes de hashing et vulnérabilité DoS
| Algorithme | Limite max par défaut | Vulnérabilité DoS (sans limite) | Recommandation 2026 |
|---|---|---|---|
| Bcrypt | 72 octets | Faible (troncature) | Idéal pour legacy / RAM limitée |
| Argon2id | Aucune | Élevée | Optimal avec limite de 128 caractères |
| Scrypt | Aucune | Moyenne/Élevée | Bon, mais Argon2id préféré |
| PBKDF2 | Aucune | Moyenne | À utiliser seulement si Argon2 indisponible |
Conclusion : La disponibilité comme une caractéristique de sécurité
On a tendance à considérer Sécurité et Disponibilité comme deux silos séparés. On durcit nos hashes pour éviter les violations de données (Sécurité), mais on oublie qu’un serveur non réactif, c’est un serveur défaillant (Disponibilité).
Le “mot de passe de 1 Mo” rappelle que chaque octet d’entrée utilisateur a un coût. En ajoutant simplement une contrainte max_length: 128, vous fermez une énorme faille DoS tout en maintenant les plus hauts standards de cryptographie.
Ne laissez pas votre “sécurité forte” devenir la cause de votre chute.
Related InstaTunnel pages
Continue from this article into the most relevant product guides and workflows.
Related Topics
Keep building with InstaTunnel
Read the docs for implementation details or compare plans before you ship.