DEV Community

Wanderson Alves Rodrigues
Wanderson Alves Rodrigues

Posted on • Updated on

Tratamento de ataques força bruta e DDoS em API NodeJs

Quando criamos nossa API e disponibilizamos publicamente estamos sujeito a ataques como força bruta e DDoS.
As intenções desses ataques são sobrecarregar nosso servidor com múltiplas requisições e tornar nosso serviço indisponível na rede.
Para evitar tais ataques podemos limitar o número de requisições por IPs, isso vai deixar nosso serviço mais profissional e caso aconteça um ataque não fique indisponível.

1 – Criar Projeto e Instalar Bibliotecas

Crie um simples projeto que será nossa API publica, para isso siga os passos:

  • Crie um diretório:
mkdir brute_force 
Enter fullscreen mode Exit fullscreen mode
  • Entre no diretório:
cd brute_force 
Enter fullscreen mode Exit fullscreen mode
  • Crie as configurações para o projeto NodeJs:
yarn init 
Enter fullscreen mode Exit fullscreen mode
  • Instale as seguintes bibliotecas abaixo:
yarn add express
yarn add @types/express -D
yarn add express-async-errors
Enter fullscreen mode Exit fullscreen mode
yarn add typescript -D
Enter fullscreen mode Exit fullscreen mode
yarn add dotenv
yarn add ts-node-dev -D
yarn add tsconfig-paths -D
yarn add cors
yarn add @types/cors -D
Enter fullscreen mode Exit fullscreen mode

2 – API

Nossa API será bem simples, pois o objetivo é tratar os problemas de múltiplas requisições.
Crie uma estrutura de projeto com segue na imagem abaixo:

Alt Text

Antes de começar a codificação, execute o comando abaixo na raiz o projeto para criar as configurações para usar Typescript:

yarn tsc --init
Enter fullscreen mode Exit fullscreen mode

a - Models

Nosso modelo é simples, crie o arquivo user.ts na pasta models:

export default class User{
  constructor(
    id: number, 
    name:String
    ){}
}
Enter fullscreen mode Exit fullscreen mode

b - Controllers

O controle só retorna uma lista de usuários em memória para simular as requisições, crie o arquivo usersController.ts na pasta controllers:

import { Request, Response } from 'express';

import User from '../models/user';

class UsersController {
  public async show(request: Request, response: Response): Promise<Response> {

    const users:User[] = [
      {id:1,name:'user 01'},
      {id:2,name:'user 02'},
      {id:3,name:'user 03'},
      {id:4,name:'user 04'},
      {id:5,name:'user 05'},
      {id:6,name:'user 06'},
      {id:7,name:'user 07'},
      {id:8,name:'user 08'},
      {id:9,name:'user 09'},
      {id:10,name:'user 10'},
    ];

    return response.status(200).json(users);
  }
}

export default UsersController;
Enter fullscreen mode Exit fullscreen mode

c - Routes

Nossa rotas para o acesso a API, crie o arquivo index.ts dentro da pasta routes:

import { Router } from 'express';

import UsersController from '../controllers/usersController';

const usersController = new UsersController();
const routes = Router();

routes.get('/users', usersController.show);

export default routes;
Enter fullscreen mode Exit fullscreen mode

d - Erros Customizados

Vamos criar uma classe para customizar os erros, crie o arquivo appError.ts dentro da pasta errors:

class AppError {
  public readonly message: string;

  public readonly statusCode: number;

  constructor(message: string, statusCode = 400) {
    this.message = message;
    this.statusCode = statusCode;
  }
}

export default AppError;
Enter fullscreen mode Exit fullscreen mode

e - Servidor

Crie o arquivo server.ts dentro da pasta src para podemos executar no servidor:

import express, { Request, Response, NextFunction } from 'express';
import 'express-async-errors';
import cors from 'cors';

import AppError from './errors/appError';
import routes from './routes';

const app = express();

app.use(cors());
app.use(express.json());
app.use(routes);

app.use((err: Error, request: Request, response: Response, _: NextFunction) => {
  if (err instanceof AppError) {
    return response.status(err.statusCode).json({
      status: 'error',
      message: err.message,
    });
  }

  return response.status(500).json({
    status: 'error',
    message: 'Internal server error',
  });
});

app.listen(3333, () => {
  console.log('Server started on port 3333');
});
Enter fullscreen mode Exit fullscreen mode

f - Executar a API

Para rodar a API, execute o seguinte comando no terminal:

yarn dev:server
Enter fullscreen mode Exit fullscreen mode

Saída:

$ yarn dev:server
yarn run v1.21.1
$ ts-node-dev -r tsconfig-paths/register  --inspect --transpile-only --ignore-watch node_modules src/server.ts
ts-node-dev ver. 1.0.0-pre.63 (using ts-node ver. 8.10.2, typescript ver. 4.0.3)
Debugger listening on ws://127.0.0.1:9229/31789a56-8b3f-4628-944d-56aeb1a6f061
For help, see: https://nodejs.org/en/docs/inspector
Missing baseUrl in compilerOptions. tsconfig-paths will be skipped
Server started on port 33
Enter fullscreen mode Exit fullscreen mode
  • Para obter os dados da API, acesse a rota:
localhost:3333/users
Enter fullscreen mode Exit fullscreen mode

2 – Express Rate Limiter

Com nossa API criada e rodando, agora vamos a parte de limitação de múltiplas requisições por IPs.
Para isso temos que instalar as seguintes bibliotecas:

a - rate-limiter-flexible.

yarn add rate-limiter-flexible
Enter fullscreen mode Exit fullscreen mode

b - Redis

vamos precisar de um lugar para guardar as requisições poderíamos usar a memória porém se nosso servidor reiniciar vamos perder as informações dos IPs e quantidades de requisições.
Para solucionar o problema optei por usar Redis.

yarn add redis
yarn add @types/redis -D
Enter fullscreen mode Exit fullscreen mode

3 – Middleware

Vamos criar um middleware para receber as requisições e verificar se o IP que realizou tem múltiplas requisições de forma simultânea.
Crie o arquivo rateLimiter.ts na pasta middlewares.

import {Request, Response, NextFunction} from 'express';
import redis from 'redis';
import {RateLimiterRedis} from 'rate-limiter-flexible';

const redisClient = redis.createClient({
  host: 'localhost',
  port:6379,
  password: undefined,
});


const limiter = new RateLimiterRedis({
  storeClient: redisClient,
  keyPrefix: 'rateLimiter',
  points: 5,//quantas requisições por IP
  duration: 5,//segundos
});

export default async function rateTimiter(request:Request, response:Response, next:NextFunction):Promise<any>{
  try{
    await limiter.consume(request.ip);
    return next();
  }catch(err){
    return response.status(429).json({message: 'Too many requests', code:429})
  }
}
Enter fullscreen mode Exit fullscreen mode

No código, fiz a configuração que ser nossa API receber 5 requisições em um intervalo de 5 segundos do mesmo IP retornará uma resposta 429..
Então caso o limite seja rompido será retornar a resposta.

{
    "message": "Too many requests",
    "code": 429
}
Enter fullscreen mode Exit fullscreen mode

Por fim volte ao arquivo server.ts e adicione o middleware rateLimiter.

import express, { Request, Response, NextFunction } from 'express';
import 'express-async-errors';
import cors from 'cors';

import AppError from './errors/appError';
import routes from './routes';
import rateLimiter from './middlewares/rateLimiter';

const app = express();

app.use(rateLimiter);
app.use(cors());
app.use(express.json());
app.use(routes);

app.use((err: Error, request: Request, response: Response, _: NextFunction) => {
  if (err instanceof AppError) {
    return response.status(err.statusCode).json({
      status: 'error',
      message: err.message,
    });
  }

  return response.status(500).json({
    status: 'error',
    message: 'Internal server error',
  });
});

app.listen(3333, () => {
  console.log('Server started on port 3333');
});
Enter fullscreen mode Exit fullscreen mode

Github do projeto.

Discussion (0)