Skip to main content

Snyking in - exploração de vulnerabilidade de negação de serviço por expressão regular no pacote ms

Escrito por
Snyking in small

13 de março de 2019

0 minutos de leitura

Boas-vindas a mais uma edição da nossa série de exploração Snyking In! Da última vez, analisamos uma exploração de vulnerabilidade de travessia de diretórios na biblioteca st. Neste episódio, vamos analisar a vulnerabilidade de negação de serviço por expressão regular, mostrar como ela pode ser explorada e explicar os riscos potenciais que representa para seus dados e sistemas.

Também vamos mostrar como encontrar e corrigir esse tipo de vulnerabilidade na sua aplicação. Sem mais delongas, confira o vídeo da exploração e, em seguida, mais informações sobre a vulnerabilidade de negação de serviço por expressão regular.

Snyking In - Regular Expression Denial of Service vulnerability exploit in the ms package

Negação de serviço por expressão regular

Negação de serviço (DoS) é o nome dado a uma família de ataques que têm como objetivo impedir o acesso de usuários legítimos a um sistema. Existem vários tipos de ataques DoS: desde sobrecarregar os canais de rede do sistema com um grande volume de tráfego gerado por várias máquinas (um ataque distribuído de negação de serviço, ou DDoS) até enviar solicitações criadas para fazer o sistema travar ou levar tempo demais para processá-las.

A negação de serviço por expressão regular (ReDoS) é um tipo de ataque de negação de serviço. Expressões regulares são extremamente poderosas, mas não são muito intuitivas e podem acabar facilitando o trabalho de invasores que querem tirar seu site do ar.

O relatório recente sobre o estado da segurança de código aberto, publicado pela Snyk, mostrou que as divulgações de vulnerabilidades de negação de serviço por expressão regular aumentaram 143% somente no último ano.

Gráfico de linhas intitulado “Aumento das divulgações de negação de serviço por expressão regular (ReDoS)”, mostrando que o número de divulgações aumentou de 14 em 2016 para 30 em 2017 e 72 em

Retrocesso catastrófico

Vamos analisar a seguinte expressão regular:

regex = /A(B|C+)+D/

Esta expressão regular faz o seguinte:

  • A A string precisa começar com a letra A

  • (B|C+)+ Em seguida, depois da letra A, a string precisa conter a letra B ou uma ou mais ocorrências da letra C (o + corresponde a uma ou mais ocorrências). O + no final desta seção indica que podemos procurar uma ou mais correspondências desta seção.

  • D Por fim, garantimos que esta seção da string termine com um D

A expressão corresponderia a entradas como ABBD, ABCCCCD, ABCBCCCD e ACCCCCD. Na maioria dos casos, o mecanismo de regex encontra uma correspondência rapidamente:

$ time node -e '/A(B|C+)+D/.test("ACCCCCCCCCCCCCCCCCCCCCCCCCCCCD")'
0.04s user 0.01s system 95% cpu 0.052 total

$ time node -e '/A(B|C+)+D/.test("ACCCCCCCCCCCCCCCCCCCCCCCCCCCCX")'
1.79s user 0.02s system 99% cpu 1.812 total

Todo o processo de testar uma string de 30 caracteres leva cerca de 52 ms. Mas, quando recebe uma string inválida, o teste demora quase dois segundos — mais de dez vezes o tempo necessário para testar uma string válida. Essa diferença drástica se deve à maneira como as expressões regulares são avaliadas.

A maioria dos mecanismos de regex funciona de maneira muito parecida, com pequenas diferenças. O mecanismo encontra a primeira forma possível de corresponder ao caractere atual e avança para o próximo. Se não conseguir corresponder ao caractere seguinte, ele volta atrás para verificar se havia outra maneira de processar o caractere anterior. Se avançar demais por esse caminho e só então descobrir que a string não corresponde ao padrão — e se muitos caracteres tiverem vários caminhos válidos na expressão regular —, o número de etapas de retrocesso pode se tornar muito grande. Esse fenômeno é conhecido como retrocesso catastrófico.

A exploração do pacote ms

O comando a seguir adiciona uma tarefa à lista de afazeres do nosso aplicativo Snyk Goof. O trecho in 20 minutes do texto é identificado pelo mecanismo de regex como uma indicação de tempo. A lógica de negócios do aplicativo pode usar essa informação para criar lembretes ou alertas, por exemplo.

$ echo 'content=Call mom in 20 minutes' | http --form http://localhost:3001/create -v

Podemos tentar forçar o tamanho da entrada da tarefa da seguinte maneira. O comando imprime 60.000 5s como o número de minutos, mas retorna muito rápido, pois o padrão ainda corresponde.

$ echo 'content=Buy milk in '`printf %.0s5 {1..60000}`' minutes' | http --form http://localhost:3001/create -v

Para causar uma negação de serviço, precisamos passar uma string que provoque um cenário de retrocesso catastrófico. Isso exige uma string de entrada longa, como no exemplo anterior, mas também precisamos garantir que o mecanismo de regex nunca corresponda ao padrão e, por isso, retroceda por todas as possibilidades antes de falhar. Esse atraso — ou a negação de serviço — é o que queremos provocar. Para isso, podemos alterar o texto minutes no conteúdo para minutea, por exemplo, um padrão ao qual o mecanismo de regex não corresponderá. O comando a seguir para adicionar uma tarefa causará uma negação de serviço de cerca de 10 a 15 segundos. Se passarmos 600.000 5s, vamos esperar o resto do dia até que o servidor conclua a solicitação.

$ echo 'content=Buy milk in '`printf %.0s5 {1..60000}`' minutea' | http --form http://localhost:3001/create -v

Para testar se sua aplicação tem vulnerabilidades em bibliotecas de terceiros, como ms, experimente o Snyk gratuitamente.

Comece a resolver desafios de capture the flag

Aprenda a resolver desafios de capture the flag assistindo sob demanda ao nosso workshop virtual introdutório.