ReDoS em Ação: Um Estudo Prático com C#

C# 16 de Dez de 2024

ReDoS se trata de um ataque de negação de serviço por uso de REGEX que pode causar lentidão da sua aplicação ou até mesmo a queda.

Neste artigo, vamos criar uma aplicação para entender melhor o problema e apresentar uma solução.

Implementação

1º - Crie um projeto API Web do ASP.NET Core e salve-o na pasta de sua preferência:

2º - Mantenha as configurações padrão:

O Conceito de Backtracking

Um conceito importante é o Backtracking. Quando você escreve um regex, ela define um padrão que o regex tenta casar (match) com o texto de entrada. Se o padrão não corresponder inicialmente, o regex pode voltar atrás (backtrack) para explorar outras possibilidades de correspondência, até achar alguma combinação ou não haver combinação para o valor informado.

Por exemplo, a combinação de ( a | aa )+, este padrão é a ocorrência de a ou aa e se a entrada for aaaaaaaaaaaaaaaaaa teremos muitos caminhos para o regex percorrer degradando a performance. Vamos ser sensatos, uma ou duas consultas a cada hora em uma rota que faça o uso desse padrão não vai impactar negativamente, mas imagine agora 1000 request batendo na sua porta, teríamos um grande problema. Existem outros padrões de testes que podem ser bem problemáticos como (a?b|ab?)+, ([a-zA-Z]+)*, etc.

Exemplo prático

Agora vamos para um cenário do dia a dia. No código abaixo, vamos usar um regex para validar e-mail.

Em seu program.cs, adicione o seguinte código:

app.MapGet("/redos", async () =>
{

    string regexPattern = @"^([0-9a-zA-Z]([-.\\w]*[0-9a-zA-Z])*@(([0-9a-zA-Z])+([-\\w]*[0-9a-zA-Z])*\\.)+[azA-Z]{2,9})$";

    string inputMalicioso = "00-0A-0A-0A-0A-0A-0A-0A-0A-0A-0A-0A-0A-0A-0A-0A";

    Console.WriteLine("Testando entrada maliciosa...");
    MensurarPerformance(regexPattern, inputMalicioso);

    void MensurarPerformance(string regexPattern, string input)
    {
        Stopwatch stopwatch = Stopwatch.StartNew();
        try
        {
            bool deuMatch = Regex.IsMatch(input: input,
                                         pattern: regexPattern);

            Console.WriteLine($"Resultado: {deuMatch}");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Erro: {ex.Message}");
        }
        finally
        {
            stopwatch.Stop();
            Console.WriteLine($"Tempo gasto: {stopwatch.ElapsedMilliseconds}ms");
        }
    }

})
    .WithName("Redos")
    .WithOpenApi();

Após executar, o código levou aproximadamente 1 minuto e 49 segundos para realizar o match da entrada. Incrível né? Agora imagine usar uma ferramenta para realizar teste de carga como o artillery, simplesmente nossa API vai morrer em poucos minutos.

Solução

E como resolvemos esse problema no código?

No C#, é possível configurar um timeout para o match. Caso o tempo determinado seja excedido sem encontrar uma correspondência, o regex lançará uma exception.

bool deuMatch = Regex.IsMatch(input: input,
                             pattern: regexPattern,
                             options: RegexOptions.None,
                             matchTimeout: TimeSpan.FromSeconds(10));

Como saber se a sua regex é segura?

Existem sites, como o ReDoS Checker, que verificam se uma regex é vulnerável. Outro ponto importante: não copie regex de qualquer lugar. Embora isso pareça óbvio, mas o óbvio tem que ser dito. Use sites, como o Regexr, para aprender, criar e testar suas regex.

Referências

Regular Expression Denial of Service (ReDoS) Vulnerabilities
Learn what ReDoS attacks are and how they exploit regex vulnerabilities. Discover methods to protect your systems from these denial-of-service threats.
ReDoS checker | Devina.io
Examine regular expressions for potential Denial of Service vulnerabilities - Powered by recheck
RegExr: Learn, Build, & Test RegEx
RegExr is an online tool to learn, build, & test Regular Expressions (RegEx / RegExp).

Marcadores