Existe alguma maneira de colocar código malicioso em uma expressão regular?

138

Quero adicionar o recurso de pesquisa de expressão regular à minha página da web pública. Além de HTML que codifica a saída, preciso fazer algo para me proteger contra entrada maliciosa do usuário?

As pesquisas no Google são inundadas por pessoas que resolvem o problema inverso - usando expressões regulares para detectar entradas maliciosas - das quais não estou interessado. No meu cenário, a entrada do usuário é uma expressão regular.

Vou usar a biblioteca Regex no .NET (C #).

MatthewMartin
fonte
4
Isso pode depender de qual idioma e / ou biblioteca de expressões regulares você usa.
Aschepler
Um pouco mais de material de leitura: ReDoS no OWASP , ReDoS na Wikipedia
joeytwiddle

Respostas:

216

Preocupações de negação de serviço

A preocupação mais comum com as expressões regulares é um ataque de negação de serviço através de padrões patológicos que se tornam exponenciais - ou até superexponenciais! - e, portanto, parece levar uma eternidade para resolver. Eles podem aparecer apenas em dados de entrada específicos, mas geralmente é possível criar um em que isso não importe.

Quais são esses dependerão um pouco da inteligência do compilador regex que você está usando, porque alguns deles podem ser detectados durante o tempo de compilação. Os compiladores Regex que implementam recursão geralmente têm um contador de profundidade de recursão interno para verificar a não progressão.

O excelente artigo de Russ Cox, de 2007, sobre a correspondência de expressões regulares pode ser simples e rápido (mas é lento em Java, Perl, PHP, Python, Ruby, ...) fala sobre maneiras pelas quais as NFAs mais modernas, que parecem derivar do código de Henry Spencer , sofrem severa degradação do desempenho, mas onde um NFA no estilo Thompson não apresenta esses problemas.

Se você apenas admitir padrões que podem ser resolvidos pelos DFAs, poderá compilá-los como tal e eles serão executados mais rapidamente, possivelmente muito mais rápido. No entanto, leva tempo para fazer isso. O documento de Cox menciona essa abordagem e seus problemas. Tudo se resume a uma troca clássica de tempo e espaço.

Com um DFA, você gasta mais tempo construindo-o (e alocando mais estados), enquanto que com um NFA você gasta mais tempo executando-o, pois pode haver vários estados ao mesmo tempo, e o retorno pode comer seu almoço - e sua CPU.

Soluções de negação de serviço

Provavelmente, a maneira mais razoável de abordar esses padrões que estão no fim perdedor de uma corrida com a morte por calor do universo é envolvê-los com um cronômetro que efetivamente coloque o tempo máximo permitido para sua execução. Normalmente, isso será muito, muito menor que o tempo limite padrão que a maioria dos servidores HTTP fornece.

Existem várias maneiras de implementá-las, variando de simples alarm(N)ao nível C, para try {}bloquear as exceções do tipo de alarme, até gerar um novo encadeamento criado especialmente com uma restrição de tempo embutida nele.

Texto explicativo de código

Em linguagens regex que admitem frases de destaque de código, deve ser fornecido algum mecanismo para permitir ou não essas da cadeia de caracteres que você irá compilar . Mesmo que as frases de destaque do código sejam apenas para codificar no idioma que você está usando, você deve restringi-las; eles não precisam chamar códigos externos, embora, se puderem, você tenha problemas muito maiores.

Por exemplo, no Perl, não é possível ter chamadas de código em expressões regulares criadas a partir da interpolação de strings (como seriam, pois são compiladas durante o tempo de execução), a menos que o pragma lexicamente de escopo especial esteja use re "eval";ativo no escopo atual.

Dessa forma, ninguém pode ocultar uma chamada de código para executar programas do sistema como rm -rf *, por exemplo. Como as frases de destaque de código são muito sensíveis à segurança, o Perl as desativa por padrão em todas as seqüências interpoladas, e você deve fazer o possível para reativá-las.

\ P {roperties} definidas pelo usuário

Resta uma questão mais sensível à segurança relacionadas com propriedades Unicode de estilo - como \pM, \p{Pd}, \p{Pattern_Syntax}, ou \p{Script=Greek}- de que podem existir em alguns compiladores regex que o apoio que notação.

O problema é que, em algumas delas, o conjunto de propriedades possíveis é extensível pelo usuário. Isso significa que você pode ter propriedades personalizadas que são chamadas de código reais para funções nomeadas em algum espaço de nome específico, como \p{GoodChars}ou \p{Class::Good_Characters}. Como seu idioma lida com isso pode valer a pena analisar.

Sandboxing

No Perl, um compartimento em área restrita por meio do Safemódulo daria controle sobre a visibilidade do espaço para nome. Outros idiomas oferecem tecnologias similares de sandbox. Se esses dispositivos estiverem disponíveis, convém procurá-los, porque eles foram projetados especificamente para execução limitada de código não confiável.

tchrist
fonte
4
A conversão de NFA-> DFA pode produzir explosão de estado exponencial, transformando um DoS de tempo em um DoS de espaço, bem como o custo de tempo para gerar o número exponencial de estados.
Barry Kelly
mas provavelmente ele não precisará de todos os recursos de expressões regulares, o que você acha de restringir o poder das expressões regulares como o google: google.com/intl/en/help/faq_codesearch.html#regexp
systemsfault
1
@ Barry Muito bem. Eu estava pensando na estratégia de Russ Cox descrita em um de seus trabalhos de compilar partes da NFA de forma incremental em um DFA equivalente, mas jogá-lo fora se ele ficasse muito grande. Mas não há uma bala de prata em um DFA, mesmo que Thompson tenha provado que é equivalente a um NFA, porque você precisa pagar o flautista em algum momento ou outro. O tempo gasto implorando ao sistema operacional por mais espaço e os custos de configuração da tabela de páginas atendente às vezes pode levar a escala de equilíbrio ainda mais para o outro lado e tornar a conversão de tempo em espaço menos atraente do que seria.
tchrist
20

Acrescentando a excelente resposta de tchrist: o mesmo Russ Cox que escreveu a página "Expressão regular" também lançou código! re2 é uma biblioteca C ++ que garante tempo de execução O (length_of_regex) e limite configurável de uso de memória. Ele é usado no Google para que você possa digitar um regex na pesquisa de código do Google - o que significa que foi testado em batalha.

Brian Bloniarz
fonte
2
De fato sim. Você pode trocar o re2 pelo mecanismo de regex do Perl com um módulo, e ele usará o re2, se possível, e o Perl, se não. Funciona muito bem.
precisa
6

Você vai querer ler este artigo:

Troca insegura de contexto: inoculando expressões regulares para capacidade de sobrevivência O artigo é mais sobre o que pode dar errado com os mecanismos de expressão regular (por exemplo, PCRE), mas pode ajudar a entender o que você está enfrentando.

Bruce Ediger
fonte
1
Aqui está um aviso de segurança sobre o código GNU libc regcomp (3): securityreason.com/achievement_securityalert/93 Quão oportuno! Pelo menos no Linux, a vulnerabilidade é fácil de demonstrar: grep -E ". * {10,} {10,} {10,} {10,} {10,}"
Bruce Ediger
5

Você precisa não apenas se preocupar com a correspondência em si, mas como fazer a correspondência. Por exemplo, se sua entrada passar por algum tipo de fase de avaliação ou substituição de comando no caminho para o mecanismo de expressão regular, pode haver código que é executado dentro do padrão. Ou, se sua sintaxe de expressão regular permitir comandos incorporados, você também deve ter cuidado com isso. Como você não especificou o idioma em sua pergunta, é difícil dizer com certeza quais são todas as implicações de segurança.

Bryan Oakley
fonte
1

Uma boa maneira de testar o seu RegEx quanto a problemas de segurança (pelo menos no Windows) é a ferramenta de difusão SDL RegEx, lançada recentemente pela Microsoft. Isso pode ajudar a evitar a construção RegEx patologicamente ruim.

RandomNickName42
fonte