Aikido

Proteção contra expressões regulares lentas: prevenção de 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

As expressões regulares podem congelar a sua aplicação durante segundos ou minutos com a entrada correta. O retrocesso catastrófico ocorre quando os mecanismos de regex exploram caminhos exponencialmente crescentes enquanto tentam 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. Os atacantes exploram isso por meio de ataques de negação de serviço de expressão regular (ReDoS), enviando entradas criadas que fazem com que o mecanismo regex consuma 100% da CPU até que ocorram tempos limite de solicitação ou que o processo falhe.

Porque é importante

Implicações de segurança (ataques ReDoS): Um atacante pode paralisar a sua aplicação com um único pedido que contenha dados de entrada criados. A validação de correio eletrónico e os padrões de análise de URL são alvos comuns. Ao contrário dos ataques DoS tradicionais que requerem largura de banda, o ReDoS necessita apenas de pequenos payloads.

Degradação do desempenho: A entrada normal do utilizador pode desencadear um retrocesso catastrófico, fazendo com que os tempos de resposta aumentem de milissegundos para segundos. Isto 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: O regex vulnerável bloqueia o loop de eventos no Node.js ou consome recursos do pool de threads. À medida que os pedidos se acumulam, a memória aumenta e o sistema deixa de responder. Nos microsserviços, um regex vulnerável causa falhas em cascata nos serviços dependentes.

Dificuldade de deteção: Os padrões que funcionam bem nos testes com entradas curtas tornam-se exponencialmente lentos com entradas mais longas. A vulnerabilidade passa frequentemente despercebida até à produção, exigindo uma implementaçã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);
}

Porque é que não é seguro: Os quantificadores aninhados ([a-zA-Z0-9_\\-\\.]+)+ criam um retrocesso exponencial. Para uma mensagem de correio eletrónico como aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!O motor regex tenta inúmeras combinações antes de falhar. O URL regex tem múltiplos quantificadores aninhados que agravam o problema, tornando-o trivialmente explorável com entradas como longas cadeias de caracteres válidos sem a estrutura esperada.

Conformidade:

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: A remoção de quantificadores aninhados elimina o retrocesso catastrófico. Quantificadores simples como [a-zA-Z0-9_\-\.]+ são executados em tempo linear. O padrão URL utiliza grupos não capturáveis com sufixo opcional (?:...)? em vez de repetição aninhada, garantindo um desempenho previsível independentemente do comprimento ou conteúdo da entrada.

Conclusão

O desempenho da expressão regular é uma preocupação de segurança, não apenas uma otimização. Reveja todos os padrões regex quanto a quantificadores aninhados, classes de caracteres sobrepostas em grupos de repetição e alternativas ambíguas. Teste os padrões regex com entradas patológicas (cadeias longas de caracteres válidos seguidos de finais inválidos) para identificar retrocessos catastróficos antes da implementação. Sempre que possível, substituir regex complexos por funções de análise de cadeias de caracteres que tenham caraterísticas de desempenho previsíveis.

FAQs

Tem perguntas?

Que padrões causam o retrocesso 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 em que o mecanismo regex possa corresponder à mesma substring de várias maneiras cria um espaço de pesquisa exponencial. Atenção aos quantificadores (+, *, {n,m}) dentro de grupos que são eles próprios quantificados.

Como é que testo se o meu regex é vulnerável ao ReDoS?

Utilize ferramentas online como regex101.com que mostram os passos de execução e avisam sobre retrocessos catastróficos. Crie entradas de teste com longas sequências de caracteres válidos seguidos de caracteres que forçam o retrocesso. Para o padrão /^(a+)+b$/, teste com "aaaaaaaaaaaaaaaa!" (30+ a's, sem b). Se a execução demorar mais de milissegundos, a regex é vulnerável. Implemente timeouts em operações regex de produção como defesa em profundidade.

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

O retrocesso linear ocorre quando o regex tenta alternativas em sequência, mas não reavalia as escolhas anteriores. O trabalho cresce linearmente com o tamanho da entrada. O retrocesso catastrófico acontece quando os 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 utilizar lookaheads e lookbehinds em 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 motores regex que impedem o retrocesso catastrófico?

O RE2 (utilizado pelo Google) garante uma execução em tempo linear, proibindo totalmente o retrocesso. Não suporta todas as funcionalidades (backreferences, lookarounds) mas impede completamente o ReDoS. Para verificações de segurança críticas, considere usar ligações RE2 ou mecanismos semelhantes. Para o JavaScript, não existe uma alternativa incorporada, pelo que a conceção de padrões e os timeouts são as suas principais defesas.

Devo adicionar tempos limite a todas as operações regex?

Para entradas não fiáveis (dados fornecidos pelo utilizador, respostas de API externas), sim. Defina tempos limite razoáveis, como 100-500ms, dependendo da complexidade esperada. No Node.js, não é possível fazer timeout direto de regex.test(), mas é possível validar o comprimento da entrada primeiro ou executar regex em um thread de trabalho com timeout. Rejeitar entradas que excedam limites de comprimento razoáveis antes de tentar a correspondência de regex.

Como é que corrijo um padrão regex vulnerável existente?

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

Obter segurança gratuitamente

Proteja seu código, nuvem e tempo de execução em um sistema central.
Encontre e corrija vulnerabilidades rapidamente de forma automática.

Não é necessário cartão de crédito | Resultados do scan em 32secs.