DEV Community

Cover image for Protegendo sua API NodeJs contra ReDos Attack[Parte 3]
Ronaldo Modesto
Ronaldo Modesto

Posted on

Protegendo sua API NodeJs contra ReDos Attack[Parte 3]

Ok! Vimos o que é uma expressão regular, como utilizá-la, o que é um ReDos e como podemos resolver esse problema!

Caso você tenha caído aqui de paraquedas, seguem os links para as duas primeiras partes desse artigo:
Parte 1 (Definição do problema)
Parte 2 (Construção de uma possível solução)

Agora chegou a hora de testar nossa solução e fazer algumas considerações acerca da mesma. Bora ver se o que construímos de fato funciona ? 🙂

Primeiramente vamos executar o mesmo teste que fizemos quando utilizamos a abordagem que acabava travando toda a nossa API.
Conforme podemos ver a seguir, ao utilizar o mesmo payload que causou problemas anteriormente, dessa vez nosso sistema não sofreu com nenhum impacto pois a thread que acabou travando foi a thread da micro vm que criamos, e dado que acabou gerando um timeout, nós apenas recebemos uma mensagem dizendo que um timeout ocorreu, mas nossa API segue firme e forte 💪.

Timeout

Nosso endpoint de teste de responsividade também continua a todo vapor.

Server up and running

Ok vimos que de fato nossa API não está mais travando quando recebe um payload malicioso. Porém temos que nos fazer seguinte pergunta, Ok, funcionou, mas essa solução é escalável?, sempre que implementamos uma solução para um dado problema.

Então vamos testar a responsividade de nossa API sob alta carga e para isso vamos usar uma ferramenta de teste de carga chamada K6.

O K6 é uma ferramenta utilizada para testar aplicação com diferentes cenários de teste de carga(teste de absorção, teste de estresse dentre outros). É uma ótima ferramenta e sugiro, para aqueles que não a conhecem, ler mais a respeito desse carinha. Clique aqui e conheça o K6.

A seguir temos o nosso arquivo de teste de carga. É um teste simples, basicamente vamos testar nossa API contra uma quantidade considerável de requisições simultâneas e ver como ela se comporta no que diz respeito à memória e uso de CPU.

import http from "k6/http";
import { check, sleep } from "k6";

export const options = {
  stages: [
    { duration: "2m", target: 250 }, // Simula um aumento de 0 até 100 requisições, em um período de 2 minutos
    { duration: "2m", target: 300 }, // Permaneçe em 300 requisições por 2 min
    { duration: "1m", target: 0 }, // Desce para 0 requisições ao longo de 1 minuto
  ],
};

const BASE_URL = "http://192.168.2.176:3000"; // Aqui eu coloquei o ip do computador que estava rodando a api, utilizei um outro computador para rodar o teste para garantir que não haveriam interferências

export default () => {
  const headers = { "Content-Type": "application/json" };

  // Primeiro envio um payload malicioso, simulando um ataque
  const maliciusPatternResult = http.post(
    `${BASE_URL}/validate-form-safe`,
    JSON.stringify({
      email: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa@gmail.com",
      password: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
    }),
    { headers: headers }
  );

  // Verifico para ver se recebi um timeout como resposta
  check(maliciusPatternResult, {
    "Bad-request-blocked-successfully": (response) => {
      return (
        JSON.parse(response.body).message === "Email ou senha são inválidos"
      );
    },
  });

  // Depois envio um payload válido paar verificar se a api está funcionando como deveria e não foi prejudicada pelo payload malicioso enviado anteriorment

  const validPatternResult = http.post(
    `${BASE_URL}/validate-form-safe`,
    JSON.stringify({
      email: "email-valido@gmail.com",
      password: "ab.cd.Zz",
    }),
    { headers: headers }
  );

  // Aqui verifico a resposta para checar se a resposta foi correta
  check(validPatternResult, {
    "Valid-Pattern-Accepted-successfully": (response) =>
      JSON.parse(response.body).isValidForm === true,
  });

  // Por fim faço uma requisição para o endpoint de checagem da api, para ver e ele ainda está respondendo
  const serverCheckResponse = http.get(`${BASE_URL}/test-server`).json();

  // e verifico se ele de fato está ativo
  check(serverCheckResponse, {
    "servidor responsivo": (obj) =>
      obj.message === "Servidor está respondendo normalmente",
  });

  sleep(1); // apenas um pequeno timer entre uma iteração do teste e outra
};

Enter fullscreen mode Exit fullscreen mode

Após rodar o teste de carga em outro computador para não afetar o desempenho de nossa API, utilizando um computador com processador de 4 núcles e 8GB de RAM, além de ter subido a nossa API utilizando o gerenciador de processos PM2 que consegue subir as aplicações NodeJs em forma de cluster, caso não conheça o PM2, clique aqui e confira, consegui uma taxa de 300 request/segundo o que é um resultado considerável considerando o que computador não era muito forte e considerando que internamente o nodeJs tem sim um overhead a mais para executar esse script em um contexto separado. Cumprimos nosso objetivo 🙂.

load-test result

Considerações e dicas

Essa solução que foi apresentada aqui é extensível para outras tarefas que podem ser bloqueantes, (cálculo de hashes por exemplo). Se você não tem certeza de que aquela operação será rápida ou se pode ou não ser bloqueante, a solução apresentada neste artigo pode ser uma forma de garantir que seu sistema não irá travar.

Aqui vão algumas dicas que podem ajudar a manter essa falha longe de suas APIs NodeJs.

  1. Não escreva sua própria regex: Evite criar suas próprias reges, principalmente se você não for expert em expressões regulares, pois a chance de você acabar criando um regex com essa falha é muito grande.

  2. Use expressões regulares já prontas e validadas: Essa é um complemento à dica 1. Se precisar validar algo procure por expressões regulares que já foram validadas contra esse tipo de falha, e até mesmo outras falhas.

  3. Use validadores: Existem validadores já pronto que podem validar uma grande variedade de padrões e é muito provável que já exista um validador para o que você quer. Por exemplo:

  4. Validator.js

  5. Safe Regex

  6. Redos Detector

  7. Valide sua regex caso precise fazer uma: Se você de fato precisar construir sua própria expressão regular então tente validá-la para saber se ela está vulnerável a essa falha. Alguns são:

  8. Devina

  9. RedosDetector

  10. Recheck

E por fim, a mais importante dica de todas 🙂

Entenda o que ela está fazendo!!! Estude sobre a expressão que você quer usar, identifique o que ela está reconhecendo e como ela o está fazendo, estude expressões regulares para que você pelo menos consiga dizer o que aquela expressão está validando.

E esse conselho se estende a todas as coisas que você precisar utilizar, evite copiar e colar coisas sem saber o que aquilo está fazendo pois você pode acabar inserindo brechas de segurança em seu sistema 🙂.

Bom por hoje foi isso pessoal, espero que esse artigo lhe ajude de alguma forma, fiquem com Deus e ate a próxima 🙂.

Top comments (0)