Vous le savez, je passe mon temps à décortiquer des failles, qu’elles soient simples ou qu’elles nécessitent d’être un peu tordu pour faire du mal. Ici on va parler d’une attaque sournoise, une simple mauvaise écriture de code. Accrochez-vous, car on va parler du ReDoS, la bête noire des expressions régulières (Regex) pour les développeurs.
Anatomie des Regular expression Denial of Service (ReDoS)
Mais qu’est-ce que le ReDoS, en vrai ?
Lorsque vous installez un plugin de sécurité comme SecuPress, on s’attend à ce qu’il bloque les attaques, les pirates, le brute force, et empêche les vols de comptes. Mais parfois, l’attaque vient d’un endroit que beaucoup de développeurs considèrent comme anodin : les expressions régulières, ou Regex.
Le ReDoS (Regular expression Denial of Service) est une attaque par déni de service qui exploite le fait que la plupart des implémentations d’Expressions Régulières peuvent atteindre des situations extrêmes en faisant travailler votre dev extrêmement lentement, souvent de manière exponentielle par rapport à la taille de l’entrée. L’attaquant n’a plus qu’à envoyer une entrée bien conçue (et si simple…) pour que votre système s’emballe et se bloque pendant trèèèès longtemps.
Imaginez, un simple bout de texte dans votre dev qui met n’importe quel serveur à genoux. Impensable… ou pas ? Voyez la suite…
Le Moteur Naïf : La racine du mal
Pour comprendre pourquoi une expression régulière peut devenir une arme de piratage, il faut plonger un peu dans le moteur des Regex.
La plupart des moteurs d’expressions régulières aujourd’hui utilisent un algorithme dit « naïf ». Cet algorithme construit un Automate Fini Non Déterministe (NFA), qui est une machine à états finis où pour chaque paire état/symbole d’entrée, il peut y avoir plusieurs états suivants possibles. (Ça va ?)
Pour trouver une correspondance, l’algorithme déterministe essaie un par un tous les chemins possibles, si nécessaire, jusqu’à ce qu’une correspondance soit trouvée ou que tous les chemins aient échoué.
La fonctionnalité qui cause ce carnage est le backtracking (retour en arrière). Si l’entrée ne correspond pas, le moteur remonte aux positions précédentes où il aurait pu prendre un autre chemin. Il réessaie, encore et encore, jusqu’à avoir exploré tous les chemins possibles.
Prenons l’exemple classique : le motif Regex ^(a+)+$. Pour l’entrée aaaaX, vous avez 16 chemins possibles. Jusque-là, ça va. Mais pour l’entrée aaaaaaaaaaaaaaaaX (16 ‘a’), il y a maintenant 65 536 chemins possibles ! Et devinez quoi ? Le nombre double à chaque ‘a’ supplémentaire. C’est exponentiel !
Même si toutes les expressions régulières ne sont pas « élargies » avec des ajouts spéciaux, l’algorithme naïf est souvent utilisé quand même, car les moteurs gèrent aussi des cas complexes, et c’est ce qui crée le problème.
Reconnaître l’« Evil Regex »
Une expression régulière est appelée Evil Regex si elle peut se bloquer sur une entrée spécialement conçue.
Les Evil Regex contiennent généralement ces éléments, souvent combinés :
- Un groupement avec répétition.
- À l’intérieur du groupe répété, vous trouvez soit :
- Une autre répétition.
- Une alternance avec chevauchement.
Quelques exemples d’Evil Regex à fuir comme le covid :
(a+)+$([a-zA-Z]+)*$(a|aa)+$(a|a?)+$
Ces motifs sont sensibles à des entrées comme aaaaaaaaaaaaaaaaaaaaaaa! (la longueur minimum pouvant varier). J’ai déjà vu des expressions de validation d’email dans des référentiels en ligne qui contenaient des Evil Regex, comme celle de ReGexLib.
Risques et Injection ReDoS
Si vous utilisez des expressions régulières pour valider vos données côté client, un attaquant peut supposer que la même Regex vulnérable est utilisée côté serveur. Il lui suffit alors d’envoyer une entrée bien construite pour plier votre serveur.
Le Web est truffé d’expressions régulières, dans toutes les couches : navigateurs, pare-feu d’applications Web (WAF), bases de données, et serveurs Web. Un attaquant peut cibler n’importe quelle application en tentant une Evil Regex.
Et attention, on peut même aller plus loin avec l’Injection ReDoS. Si une Regex elle-même est affectée par une entrée utilisateur, l’attaquant peut directement injecter une Evil Regex.
Imaginez un scénario où on vérifie si le nom d’utilisateur est contenu dans le mot de passe afin de lui signaler une faiblesse dans le mot de passe, nous créons une nouvelle Regex basée sur ce nom d’utilisateur. Si l’attaquant entre une Evil Regex comme nom d’utilisateur et une chaîne explosive comme mot de passe, le programme se bloque avec votre serveur…
Protégez vos applications PHP des attaques ReDoS
En tant que webmaster, vous pouvez contrer ces attaques en configurant deux paramètres PHP essentiels : pcre.backtrack_limit et pcre.recursion_limit.
pcre.backtrack_limit limite le nombre de pas de backtracking que le moteur de Regex peut effectuer. Une valeur trop élevée permet aux attaques ReDoS de ralentir ou faire planter votre serveur. La valeur par défaut est 1 million, mais il est recommandé de la réduire à 500 000 ou même 100 000 en production.
pcre.recursion_limit limite la profondeur de récursion dans l’évaluation des Regex, évitant ainsi les débordements de pile. La valeur par défaut est 1 000, mais vous pouvez la réduire à 500 voire 200 pour bloquer les motifs récursifs excessifs.
Exemple de configuration sécurisée pour WordPress dans votre wp-config.php ou dans un hook init :
Ces valeurs sont un compromis entre sécurité et compatibilité. Elles permettent de bloquer la plupart des attaques ReDoS tout en laissant fonctionner les Regex légitimes. Cependant, réduisez-les trop et certaines Regex complexes pourraient échouer. Testez toujours vos applications après modification.
Comment tester l’impact sur votre application ?
- Modifiez donc les valeurs que nous venons de voir, puis, activez les logs d’erreurs PHP sous WP avec un simple
define( 'WP_DEBUG', true );. - Testez toutes les fonctionnalités de votre plugin :
- Formulaires (emails, URLs, téléphones).
- Parsing de contenu (shortcodes, balises).
- Validation de données.
- Vérifiez maintenant les logs pour détecter des erreurs comme :
PHP Warning: preg_match(): Compilation failed: regular expression is too large at offset...PHP Fatal error: Maximum execution time exceeded
Solutions alternatives pour se protéger des ReDoS
Si vous ne pouvez pas réduire pcre.backtrack_limit sans casser votre application, utilisez ces autres stratégies :
Remplacez les regex vulnérables par des parsers
Pour les emails : Utilisez filter_var($email, FILTER_VALIDATE_EMAIL) au lieu d’une regex.
Pour les URLs : Utilisez wp_http_validate_url() ou filter_var($url, FILTER_VALIDATE_URL).
Pour le parsing de contenu : Utilisez des bibliothèques comme PHP DOM ou HTML Purifier ou la WP HTTP API.
Utilisez des regex « atomiques » ou « possessives »
Les regex atomiques ((?>...)) ou possessives (*+, ++, ?+) évitent le backtracking excessif :
Limitez la taille des entrées utilisateur
Utiliser un wrapper sécurisé pour les regex
Correction : Agir avant la crise cardiaque
L’erreur, la base, c’est de laisser le code devenir un éditeur de code, ou d’utiliser des données utilisateur comme si elles étaient innocentes. Toute donnée provenant d’un utilisateur doit être traitée comme malicieuse, c’est la base de la sécurité je l’ai déjà dit 1000 fois.
Concernant les expressions régulières : vérifiez vos patterns ! Si vous voyez des répétitions imbriquées ou des alternances qui se chevauchent, il y a de fortes chances que vous ayez une bombe à retardement dans votre code.
En tant que développeur, vous devez lire la documentation de l’OWASP sur le sujet du ReDoS, utilisez des outils pour détecter ces vulnérabilités (comme SecuPress 2.5 ?!), et surtout, ne copiez/collez pas des Regex trouvées en ligne (coucou stackloverflow) oui via ChatGPT (c’est claqué au sol cette IA pour dev) sans les comprendre, surtout si elles contiennent des parties en gras que l’OWASP a identifiées comme problématiques. C’est toujours copier/penser/coller.
Je vous livre quelques unes de mes Regex qui trouvent justement ces Evil Regex, faites tourner ça sur vos propres devs, et corrigez vos failles :
Si on en revient à WordPress, on voit que l’évolution de PHP est là pour nous aider. Par exemple, le passage à PHP8 a supprimé des fonctions comme create_function qui étaient souvent utilisées par d’anciens malwares que j’en encore dans mes dossiers.
Utiliser des versions à jour de PHP est une manière de se débarrasser des failles connues. De même, pour éviter les injections, comme les SQLi vues dans l’exemple du shortcode maison d’un article précédent, la correction est très simple.
Que ce soit pour une injection SQL, une XSS, ou un ReDoS, la sécurité passe par la méfiance envers l’entrée utilisateur et l’utilisation des outils dédiés.
Tout le monde compte sur vous.
