Aikido

Pourquoi il faut se prémunir contre les expressions régulières lentes pour prévenir les attaques ReDoS

Lisibilité

Règle
Se prémunir contre lenteur régulières lentes.
Les régulières avec des imbriquées quantificateurs imbriqués ou 
ambiguës ambiguës peuvent causer catastrophique 
retour en arrière et des performance de performance.
Langues prises en charge : 45+

Introduction

Les expressions régulières peuvent bloquer votre application pendant des secondes ou des minutes avec la bonne entrée. Le backtracking catastrophique se produit lorsque les moteurs d'expressions régulières explorent des chemins augmentant de manière exponentielle en essayant de faire correspondre un motif. Une regex comme (a+)+b prend des microsecondes pour correspondre à une entrée valide, mais peut prendre des heures pour rejeter une chaîne de 'a' sans 'b' final. Les attaquants exploitent cela via des attaques par déni de service par expression régulière (ReDoS), en envoyant une entrée spécialement conçue qui fait consommer 100 % du CPU à votre moteur d'expressions régulières jusqu'à ce que des délais d'attente de requête se produisent ou que le processus plante.

Pourquoi c'est important

Implications de sécurité (attaques ReDoS) : Un attaquant peut paralyser votre application avec une seule requête contenant une entrée spécialement conçue. Les motifs de validation d'e-mail et d'analyse d'URL sont des cibles courantes. Contrairement aux attaques DoS traditionnelles qui nécessitent de la bande passante, les attaques ReDoS n'ont besoin que de très petites charges utiles.

Dégradation des performances: Une entrée utilisateur normale peut déclencher un backtracking catastrophique, faisant passer les temps de réponse de millisecondes à des secondes. Cela crée une latence imprévisible difficile à déboguer car elle ne se manifeste qu'avec des schémas d'entrée spécifiques.

Incidents de production : Une regex vulnérable bloque la boucle d'événements dans Node.js ou consomme les ressources du pool de threads. À mesure que les requêtes s'accumulent, la mémoire augmente et le système devient insensible. Dans les microservices, une regex vulnérable propage les défaillances aux services dépendants.

Difficulté de détection : Les modèles qui fonctionnent bien en test avec des entrées courtes deviennent exponentiellement lents avec des entrées plus longues. La vulnérabilité passe souvent inaperçue jusqu'à la production, nécessitant un déploiement d'urgence lors d'un incident actif.

Exemples de code

❌ Non conforme :

function validateEmail(email) {
    const regex = /^([a-zA-Z0-9_\-\.]+)+@([a-zA-Z0-9_\-\.]+)+\.([a-zA-Z]{2,5})$/;
    return regex.test(email);
}

function extractURLs(text) {
    const regex = /(https?:\/\/)?([\w\-])+\.(\w+)+([\w\-\.,@?^=%&:/~\+#]*)+/g;
    return text.match(regex);
}

Pourquoi c'est dangereux : Les quantificateurs imbriqués ([a-zA-Z0-9_\\-\\.]+)+ créer un backtracking exponentiel. Pour un e-mail comme aaaaaaaaaaaaaaaaaaaaaaaaa!, le moteur d'expressions régulières tente d'innombrables combinaisons avant d'échouer. L'expression régulière d'URL comporte plusieurs quantificateurs imbriqués qui aggravent le problème, la rendant trivialement exploitable avec des entrées telles que de longues chaînes de caractères valides sans la structure attendue.

✅ Conforme :

function validateEmail(email) {
    const regex = /^[a-zA-Z0-9_\-\.]+@[a-zA-Z0-9_\-\.]+\.[a-zA-Z]{2,5}$/;
    return regex.test(email);
}

function extractURLs(text) {
    const regex = /https?:\/\/[\w\-]+\.[\w\-]+(?:[\w\-\.,@?^=%&:/~\+#]*)?/g;
    return text.match(regex);
}

Pourquoi c'est sûr : La suppression des quantificateurs imbriqués élimine le backtracking catastrophique. Les quantificateurs simples comme [a-zA-Z0-9_\-\.]+ s'exécutent en temps linéaire. Le motif d'URL utilise des groupes non-capturants avec un suffixe optionnel (?:...)? au lieu d'une répétition imbriquée, garantissant des performances prévisibles quelle que soit la longueur ou le contenu de l'entrée.

Conclusion

La performance des expressions régulières est une préoccupation de sécurité, pas seulement une optimisation. Examinez tous les motifs regex pour détecter les quantificateurs imbriqués, les classes de caractères qui se chevauchent dans les groupes de répétition et les alternatives ambiguës. Testez les motifs regex avec des entrées pathologiques (longues chaînes de caractères valides suivies de terminaisons invalides) pour identifier le backtracking catastrophique avant le déploiement. Lorsque cela est possible, remplacez les regex complexes par des fonctions d'analyse de chaînes de caractères ayant des caractéristiques de performance prévisibles.

FAQ

Des questions ?

Quels motifs provoquent un retour arrière catastrophique ?

Les coupables courants incluent les quantificateurs imbriqués comme (a+)+, (a*)*, ou (a+)*b. L'alternance avec des motifs qui se chevauchent comme (a|a)* ou (a|ab)*. La répétition avec des composants optionnels comme (a?)+. Tout motif où le moteur d'expressions régulières peut faire correspondre la même sous-chaîne de plusieurs manières crée un espace de recherche exponentiel. Surveillez les quantificateurs (+, *, {n,m}) à l'intérieur de groupes qui sont eux-mêmes quantifiés.

Comment vérifier si mon expression régulière est vulnérable aux attaques ReDoS ?

Utilisez des outils en ligne comme regex101.com qui affichent les étapes d'exécution et avertissent des retours en arrière catastrophiques. Créez des entrées de test avec de longues chaînes de caractères valides suivies de caractères qui forcent le retour en arrière. Pour le motif /^(a+)+b$/, testez avec "aaaaaaaaaaaaaaa!" (plus de 30 'a', pas de 'b'). Si l'exécution prend plus de quelques millisecondes, l'expression régulière est vulnérable. Implémentez des délais d'attente dans les opérations d'expressions régulières en production comme mesure de défense en profondeur.

Quelle est la différence entre le backtracking catastrophique et linéaire ?

Le backtracking linéaire se produit lorsque l'expression régulière essaie des alternatives en séquence mais ne réévalue pas les choix précédents. Le travail augmente linéairement avec la taille de l'entrée. Le backtracking catastrophique se produit lorsque des quantificateurs imbriqués forcent le moteur à essayer un nombre exponentiel de combinaisons. Pour une entrée de longueur n, le temps d'exécution peut être de O(2^n) ou pire. La différence est entre des millisecondes et des minutes pour des tailles d'entrée modestes.

Puis-je utiliser les lookaheads et lookbehinds en toute sécurité ?

Lookaheads (?=...) and lookbehinds (?<=...) themselves don't cause catastrophic backtracking, but they can hide vulnerable patterns. A lookahead containing (a+)+ is still vulnerable. Use lookarounds for their intended purpose (assertions without consuming characters), not as a workaround for complex matching. Keep the patterns inside lookarounds simple and test them thoroughly.

Existe-t-il des moteurs d'expressions régulières qui préviennent le backtracking catastrophique ?

RE2 (utilisé par Google) garantit une exécution en temps linéaire en interdisant complètement le backtracking. Il ne prend pas en charge toutes les fonctionnalités (backreferences, lookarounds) mais prévient entièrement les attaques ReDoS. Pour les contrôles de sécurité critiques, envisagez d'utiliser des bindings RE2 ou des moteurs similaires. Pour JavaScript, il n'existe pas d'alternative intégrée, donc la conception des motifs et les timeouts sont vos principales défenses.

Dois-je ajouter des délais d'attente à toutes les opérations regex ?

Pour les entrées non fiables (données fournies par l'utilisateur, réponses d'API externes), oui. Définissez des délais d'attente raisonnables, comme 100-500ms, en fonction de la complexité attendue. En Node.js, vous ne pouvez pas directement définir un timeout pour regex.test(), mais vous pouvez d'abord valider la longueur de l'entrée ou exécuter la regex dans un thread de travail avec un timeout. Rejetez les entrées dépassant les limites de longueur raisonnables avant de tenter une correspondance regex.

Comment corriger un modèle d'expression régulière vulnérable existant ?

Tout d'abord, déterminez si vous avez réellement besoin d'expressions régulières (regex). De nombreuses tâches de validation sont plus simples avec des méthodes de chaîne comme includes(), startsWith() ou split(). Si les regex sont nécessaires, éliminez les quantificateurs imbriqués en aplatissant le motif. Remplacez (a+)+ par a+. Utilisez des groupes atomiques ou des quantificateurs possessifs si votre moteur d'expressions régulières les prend en charge. Pour les motifs complexes, envisagez d'analyser l'entrée en plusieurs passes avec des regex ou des opérations de chaîne plus simples.

Sécurisez-vous maintenant.

Sécuriser votre code, votre cloud et votre runtime dans un système centralisé unique.
Détectez et corrigez les vulnérabilités rapidement et automatiquement.

Pas de carte de crédit requise | Résultats du scan en 32 secondes.