Aikido

Se prémunir contre les expressions régulières lentes : 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 quelques secondes ou quelques minutes si elles sont correctement saisies. Un retour en arrière catastrophique se produit lorsque les moteurs d'expressions régulières explorent des chemins qui augmentent de façon exponentielle lorsqu'ils tentent de faire correspondre un motif. Une expression régulière 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 à la fin. Les attaquants exploitent cette situation par le biais d'attaques de déni de service par expression régulière (ReDoS), en envoyant une entrée fabriquée qui fait en sorte que votre moteur regex consomme 100 % de l'unité centrale jusqu'à ce que les délais de requête soient dépassés ou que le processus s'arrête.

Pourquoi c'est important

Implications en matière de sécurité (attaques ReDoS) : Un attaquant peut paralyser votre application avec une seule demande contenant des données élaborées. La validation des courriels et les modèles d'analyse d'URL sont des cibles courantes. Contrairement aux attaques DoS traditionnelles qui nécessitent de la bande passante, ReDoS n'a besoin que de minuscules charges utiles.

Dégradation des performances : Une saisie normale de l'utilisateur peut déclencher un retour en arrière catastrophique, faisant passer les temps de réponse de quelques millisecondes à quelques secondes. Il en résulte 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 : Les expressions rationnelles vulnérables bloquent la boucle d'événements dans Node.js ou consomment les ressources du pool de threads. Au fur et à mesure que les demandes s'accumulent, la mémoire augmente et le système ne répond plus. Dans les microservices, une regex vulnérable entraîne des défaillances en cascade dans les services dépendants.

Difficulté de détection : Les modèles qui fonctionnent bien dans les tests avec des entrées courtes deviennent exponentiellement lents avec des entrées plus longues. La vulnérabilité passe souvent inaperçue jusqu'à la production, ce qui nécessite 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 ce n'est pas sûr : Les quantificateurs imbriqués ([a-zA-Z0-9_\\-\\.]+)+ créent un retour en arrière exponentiel. Pour un courriel comme aaaaaaaaaaaaaaaaaaaaaaaaa !Le moteur d'expressions rationnelles essaie d'innombrables combinaisons avant d'échouer. L'expression rationnelle de l'URL comporte plusieurs quantificateurs imbriqués qui aggravent le problème, ce qui la rend 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 permet d'éviter les retours en arrière catastrophiques. Les quantificateurs simples tels que [a-zA-Z0-9_\-\.]+ s'exécutent en temps linéaire. Le modèle d'URL utilise des groupes non capturants avec un suffixe facultatif (? :...) ? au lieu d'une répétition imbriquée, ce qui garantit des performances prévisibles quelle que soit la longueur ou le contenu de l'entrée.

Conclusion

Les performances des expressions régulières sont un problème de sécurité, et non une simple optimisation. Examinez tous les modèles d'expressions régulières 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 modèles de regex avec des entrées pathologiques (longues chaînes de caractères valides suivies de terminaisons non valides) afin d'identifier les retours en arrière catastrophiques avant le déploiement. Dans la mesure du possible, remplacer les expressions rationnelles complexes par des fonctions d'analyse de chaînes de caractères dont les performances sont prévisibles.

FAQ

Vous avez des questions ?

Quels sont les schémas qui provoquent des retours en arrière catastrophiques ?

Les coupables les plus courants sont les quantificateurs imbriqués tels que (a+)+, (a*)* ou (a+)*b. L'alternance avec des motifs qui se chevauchent comme (a|a)* ou (a|ab)*. Répétition avec des composants facultatifs comme (a ?)+. Tout motif pour lequel le moteur de regex peut faire correspondre la même chaîne de plusieurs façons crée un espace de recherche exponentiel. Attention aux quantificateurs (+, *, {n,m}) à l'intérieur de groupes eux-mêmes quantifiés.

Comment puis-je tester si ma regex est vulnérable à ReDoS ?

Utilisez des outils en ligne tels que regex101.com qui montrent 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 "aaaaaaaaaaaaaaaaa !" (plus de 30 a, pas de b). Si l'exécution prend plus de quelques millisecondes, la regex est vulnérable. Implémentez des délais d'attente dans les opérations de regex de production comme défense en profondeur.

Quelle est la différence entre un retour en arrière catastrophique et un retour en arrière linéaire ?

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

Puis-je utiliser les lookaheads et les 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 de regex qui empêchent les retours en arrière catastrophiques ?

RE2 (utilisé par Google) garantit une exécution en temps linéaire en interdisant totalement le retour en arrière. Il ne prend pas en charge toutes les fonctionnalités (backreferences, lookarounds) mais empêche complètement les ReDoS. Pour les contrôles de sécurité critiques, envisagez d'utiliser des liens RE2 ou des moteurs similaires. Pour JavaScript, il n'y a pas d'alternative intégrée, de sorte que la conception de modèles et les délais d'attente sont vos principales défenses.

Dois-je ajouter des délais à toutes les opérations sur les expressions rationnelles ?

Pour les entrées non fiables (données fournies par l'utilisateur, réponses d'API externes), oui. Définissez des délais raisonnables comme 100-500ms en fonction de la complexité attendue. Dans Node.js, il n'est pas possible de temporiser directement regex.test(), mais il est possible de valider la longueur de l'entrée en premier ou d'exécuter regex dans un thread de travail avec temporisation. Rejeter les entrées dépassant des limites de longueur raisonnables avant d'essayer de faire correspondre les expressions rationnelles.

Comment puis-je corriger un motif d'expression rationnelle vulnérable existant ?

Tout d'abord, déterminez si vous avez besoin de regex. De nombreuses tâches de validation sont plus simples avec des méthodes de chaînes de caractères telles que includes(), startsWith() ou split(). Si les expressions rationnelles 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 de regex les prend en charge. Pour les motifs complexes, envisagez d'analyser l'entrée en plusieurs passes avec des opérations de regex ou de chaîne plus simples.

Obtenir la sécurité gratuitement

Sécurisez votre code, votre cloud et votre environnement d'exécution dans un système central.
Trouvez et corrigez rapidement et automatiquement les vulnérabilités.

Aucune carte de crédit n'est requise | Scanner les résultats en 32sec.