Aikido

Por que você deve se proteger contra expressões regulares lentas para prevenir ataques ReDoS

Legibilidade

Regra
Proteger contra lento regulares lentas.
As regulares com aninhados quantificadores ou 
ambíguos padrões podem causar catastróficos 
retrocesso e desempenho problemas.
Idiomas suportados: 45+

Introdução

Expressões regulares podem congelar sua aplicação por segundos ou minutos com a entrada correta. O backtracking catastrófico ocorre quando os motores de regex exploram caminhos que aumentam exponencialmente ao tentar corresponder a um padrão. Uma regex como (a+)+b leva microssegundos para corresponder a uma entrada válida, mas pode levar horas para rejeitar uma sequência de 'a's sem um 'b' final. Atacantes exploram isso através de ataques de Negação de Serviço por Expressão Regular (ReDoS), enviando entradas maliciosas que fazem seu motor de regex consumir 100% da CPU até que ocorram timeouts de requisição ou o processo trave.

Por que isso importa

Implicações de segurança (ataques ReDoS): Um atacante pode paralisar sua aplicação com uma única requisição contendo uma entrada maliciosa. Padrões de validação de e-mail e análise de URL são alvos comuns. Ao contrário dos ataques DoS tradicionais que exigem largura de banda, o ReDoS precisa apenas de payloads minúsculos.

Degradação de desempenho: Entrada normal do usuário pode desencadear backtracking catastrófico, fazendo com que os tempos de resposta saltem de milissegundos para segundos. Isso cria uma latência imprevisível que é difícil de depurar porque só se manifesta com padrões de entrada específicos.

Incidentes de produção: Regex vulnerável bloqueia o event loop em Node.js ou consome recursos do pool de threads. À medida que as requisições se acumulam, a memória aumenta e o sistema se torna não responsivo. Em microsserviços, uma regex vulnerável propaga falhas para serviços dependentes.

Dificuldade na detecção: Padrões que funcionam bem em testes com entradas curtas tornam-se exponencialmente lentos com entradas mais longas. A vulnerabilidade muitas vezes passa despercebida até a produção, exigindo implantação de emergência durante um incidente ativo.

Exemplos de código

❌ Não-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);
}

Por que é inseguro: Os quantificadores aninhados ([a-zA-Z0-9_\\-\\.]+)+ criar backtracking exponencial. Para um e-mail como aaaaaaaaaaaaaaaaaaaaaaaaa!, o motor de regex tenta inúmeras combinações antes de falhar. A regex de URL possui múltiplos quantificadores aninhados que agravam o problema, tornando-a trivialmente explorável com entradas como longas strings de caracteres válidos sem a estrutura esperada.

✅ Compatível:

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);
}

Por que é seguro: Remover quantificadores aninhados elimina o backtracking catastrófico. Quantificadores únicos como [a-zA-Z0-9_\-\.]+ são executados em tempo linear. O padrão de URL usa grupos não-capturantes com sufixo opcional (?:...)? em vez de repetição aninhada, garantindo desempenho previsível independentemente do comprimento ou conteúdo da entrada.

Conclusão

O desempenho de expressões regulares é uma preocupação de segurança, não apenas uma otimização. Revise todos os padrões de regex em busca de quantificadores aninhados, classes de caracteres sobrepostas em grupos de repetição e alternativas ambíguas. Teste os padrões de regex com entradas patológicas (longas cadeias de caracteres válidos seguidas por terminações inválidas) para identificar o backtracking catastrófico antes da implantação. Quando possível, substitua regex complexas por funções de análise de string que possuam características de desempenho previsíveis.

FAQs

Dúvidas?

Quais padrões causam backtracking catastrófico?

Os culpados comuns incluem quantificadores aninhados como (a+)+, (a*)* ou (a+)*b. Alternância com padrões sobrepostos como (a|a)* ou (a|ab)*. Repetição com componentes opcionais como (a?)+. Qualquer padrão onde o motor de regex pode corresponder à mesma substring de várias maneiras cria um espaço de busca exponencial. Fique atento a quantificadores (+, *, {n,m}) dentro de grupos que são eles próprios quantificados.

Como testar se minha regex é vulnerável a ReDoS?

Use ferramentas online como regex101.com que mostram os passos de execução e alertam sobre backtracking catastrófico. Crie entradas de teste com longas strings de caracteres válidos seguidos por caracteres que forçam o backtracking. Para o padrão \/^(a+)+b$\/, teste com "aaaaaaaaaaaaaaa!" (mais de 30 'a's, sem 'b'). Se a execução demorar mais de milissegundos, a regex é vulnerável. Implemente timeouts em operações de regex em produção como defesa em profundidade.

Qual é a diferença entre backtracking catastrófico e linear?

O backtracking linear ocorre quando a regex tenta alternativas em sequência, mas não reavalia escolhas anteriores. O trabalho cresce linearmente com o tamanho da entrada. O backtracking catastrófico acontece quando quantificadores aninhados forçam o motor a tentar exponencialmente muitas combinações. Para uma entrada de comprimento n, o tempo de execução pode ser O(2^n) ou pior. A diferença é entre milissegundos e minutos para tamanhos de entrada modestos.

Posso usar lookaheads e lookbehinds com segurança?

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.

Existem engines de regex que previnem o backtracking catastrófico?

RE2 (usado pelo Google) garante execução em tempo linear ao proibir completamente o backtracking. Ele não suporta todos os recursos (backreferences, lookarounds), mas previne o ReDoS completamente. Para verificações de segurança críticas, considere usar bindings RE2 ou engines similares. Para JavaScript, não há alternativa embutida, então o design de padrões e os timeouts são suas defesas primárias.

Devo adicionar timeouts a todas as operações de regex?

Para entrada não confiável (dados fornecidos pelo usuário, respostas de API externas), sim. Defina timeouts razoáveis como 100-500ms dependendo da complexidade esperada. Em Node.js, você não pode definir um timeout diretamente para regex.test(), mas pode validar o comprimento da entrada primeiro ou executar a regex em um worker thread com timeout. Rejeite entradas que excedam limites de comprimento razoáveis antes de tentar a correspondência de regex.

Como corrigir um padrão regex vulnerável existente?

Primeiro, determine se você realmente precisa de regex. Muitas tarefas de validação são mais simples com métodos de string como includes(), startsWith() ou split(). Se regex for necessário, elimine quantificadores aninhados achatando o padrão. Substitua (a+)+ por a+. Use grupos atômicos ou quantificadores possessivos se seu motor de regex os suportar. Para padrões complexos, considere analisar a entrada em múltiplas passagens com regex mais simples ou operações de string.

Fique seguro agora

Proteja seu código, Cloud e runtime em um único sistema centralizado.
Encontre e corrija vulnerabilidades rapidamente de forma automática.

Não é necessário cartão de crédito | Resultados da varredura em 32 segundos.