March 5, 2012
by Michel Perfetti
0 comments
Bien qu’elles soient pratiques, les expressions régulières ne sont pas toujours nos amies. Lorsqu’elles deviennent trop complexes, elles peuvent devenir une faille dans la sécurité de nos applications. Tout l’inverse de ce que l’on voulait en faire.
L’une des premières utilisations des expressions régulière est la validation des saisies utilisateurs. Par exemple une adresse mail. Prenons cette expression:
“^([a-zA-Z0-9])(([\-.]|[_]+)?([a-zA-Z0-9]+))*(@){1}[a-z0-9]+[.]{1}(([a-z]{2,3})|([a-z]{2,3}[.]{1}[a-z]{2,3}))$”
D’un seul bloc, c’est complètement illisible et incompréhensible. Coupons là en plusieurs morceaux:
- ([a-zA-Z0-9])
- (([\-.]|[_]+)?([a-zA-Z0-9]+))*
- (@)
- [a-z0-9]+[.]{1}
- (([a-z]{2,3})|([a-z]{2,3}[.]{1}[a-z]{2,3}))
Si je prends cet email: john.doe@toto.com. Voila ce qui est validé pour chacun des 5 blocs:
- john
- .doe
- @
- toto.
- com
C’est plus clair maintenant?
Forcement pas tout le monde sait écrire des expressions régulières. Car personne n’en a vraiment envie (sauf ceux qui sont succombés au Perl). Alors, lorsque l’on doit valider une entrée utilisateur, et bien on se retourne sur internet et on prend la première expression qui passe (et qui marche presque). Jouons un peu avec cette expression pour lui faire valider des emails…. mais pas que…
- ‘toto@toto.com’: 0,0068746 seconds
- ‘aaaaaaaaaaaaaaaa!’: 0,0121044 seconds
- ‘aaaaaaaaaaaaaaaaa!’: 0,0254051 seconds
- ‘aaaaaaaaaaaaaaaaaa!’: 0,0495118 seconds
- ‘aaaaaaaaaaaaaaaaaaa!’: 0,0979562 seconds
- ‘aaaaaaaaaaaaaaaaaaaa!’: 0,1938863 seconds
- ‘aaaaaaaaaaaaaaaaaaaaa!’: 0,390627 seconds
- ‘aaaaaaaaaaaaaaaaaaaaaa!’: 0,788265 seconds
- ‘aaaaaaaaaaaaaaaaaaaaaaa!’: 1,5697939 seconds
- ‘aaaaaaaaaaaaaaaaaaaaaaaa!’: 3,1256698 seconds
- ‘aaaaaaaaaaaaaaaaaaaaaaaaa!’: 6,2264587 seconds
- ‘aaaaaaaaaaaaaaaaaaaaaaaaaa!’: 21,9357298 seconds
Que se passe-t-il? à chaque ‘a’ ajoutée, le temps de calcul double au moins. Je ne vais pas rentrer dans les explications ici, mon seul but est de montrer qu’exposer une expression régulière ne limite pas forcément la surface d’attaque d’un programme. Avec cette expression régulière par exemple, nous avons empêché la saisie de mauvaises données, mais nous avons surtout donné la possibilité à un attaquant de bloquer le site (si l’application c’est sur un site web) avec une seule saisie (par exemple aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!) qui vampiriserait tout le CPU: c’est notre “Regular Expression Denial Of Service” ou “REDOS”.
Alors comment faire pour éviter cela:
- éviter de prendre n’importe laquelle des expressions régulières
- éviter celles avec 2 boucles de backtracking imbriquées: c’est ce que l’on a ici dans la clause 2: ([a-zA-Z0-9]+))* avec le + et le *
- Si vous pouvez passer au Framework 4.5, il y a maintenant la possibilité de configurer une durée maximale d’analyse. Avec un timeout de 1 seconde cela donne:

Voici le code:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace RegexDemo
{
class Program
{
static Regex _regex;
static void TestEmail(string pattern)
{
var sw = new Stopwatch();
sw.Start();
try
{
var match = _regex.Match(pattern);
Console.WriteLine(match.Success ? "OK" : "Failed");
}
catch (RegexMatchTimeoutException)
{
Console.WriteLine("Timeout");
}
finally
{
sw.Stop();
}
Console.WriteLine("'{0}': {1} seconds", pattern, sw.Elapsed.TotalSeconds);
}
static void Main(string[] args)
{
#if true
_regex = new Regex(@"^([a-zA-Z0-9])(([\-.]|[_]+)?([a-zA-Z0-9]+))*(@){1}[a-z0-9]+[.]{1}(([a-z]{2,3})|([a-z]{2,3}[.]{1}[a-z]{2,3}))$",
RegexOptions.Compiled,
TimeSpan.FromSeconds(1));
#else
_regex = new Regex(@"^([a-zA-Z0-9])(([\-.]|[_]+)?([a-zA-Z0-9]+))*(@){1}[a-z0-9]+[.]{1}(([a-z]{2,3})|([a-z]{2,3}[.]{1}[a-z]{2,3}))$",
RegexOptions.Compiled);
#endif
TestEmail("toto@toto.com");
TestEmail("aaaaaaaaaaaaaaaa!");
TestEmail("aaaaaaaaaaaaaaaaa!");
TestEmail("aaaaaaaaaaaaaaaaaa!");
TestEmail("aaaaaaaaaaaaaaaaaaa!");
TestEmail("aaaaaaaaaaaaaaaaaaaa!");
TestEmail("aaaaaaaaaaaaaaaaaaaaa!");
TestEmail("aaaaaaaaaaaaaaaaaaaaaa!");
TestEmail("aaaaaaaaaaaaaaaaaaaaaaa!");
TestEmail("aaaaaaaaaaaaaaaaaaaaaaaa!");
TestEmail("aaaaaaaaaaaaaaaaaaaaaaaaa!");
TestEmail("aaaaaaaaaaaaaaaaaaaaaaaaaa!");
TestEmail("aaaaaaaaaaaaaaaaaaaaaaaaaaa!");
TestEmail("aaaaaaaaaaaaaaaaaaaaaaaaaaaa!");
TestEmail("aaaaaaaaaaaaaaaaaaaaaaaaaaaaa!");
TestEmail("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!");
TestEmail("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!");
Console.ReadLine();
}
}
}
Pour terminer quelques liens sur le sujet:
@+