Mitigando ataques a APIs com Hammer

Rodolpho Atoji
Inside SumUp
Published in
4 min readApr 12, 2022

O maior valor da SumUp são os nossos merchants e trabalhamos incansavelmente para que as pessoas recebam e transfiram seus valores com segurança.

Na área de tecnologia, fornecemos APIs (Application Programming Interfaces) que, resumidamente, são o ponto de contato entre nossos merchants e a SumUp.

Essas APIs ficam expostas à Internet, porém, como toda API, está sujeita a abusos tanto por parte de usuários legítimos quanto fraudadores.

Ilustração de ataque de força bruta

Existe uma infinidade de ataques que podem ser feitos a APIs, e um desses tipos de ataque é o de força bruta. Por meio de um ataque de força bruta, uma pessoa mal-intencionada utiliza um método automatizado onde diversas requisições a uma API são feitas em um curto intervalo de tempo. Essas requisições buscam: extrair informações sobre usuários, validar informações etc.

Neste artigo vamos exemplificar como mitigar um ataque de força bruta usando o Hammer, uma biblioteca de rate-limiter para Elixir.

Rate-limiting: o que é?

É o processo de limitar a frequência de requisições (a uma função, a uma API etc), com base em um critério.

Por exemplo: podemos permitir que determinado usuário da API faça apenas uma transação por segundo, o que parece bem razoável. Comportamentos acima disso indicam que estamos tratando de um abuso automatizado da API.

Algoritmos de rate-limiting

Existem diversos algoritmos para isso, mas nesse artigo vamos focar no Token Bucket.

Esse algoritmo faz analogia com um balde, onde no mesmo são depositados tokens (como a senha de uma fila de espera) a uma determinada taxa (por exemplo: adiciono 100 tokens por minuto).

Token Bucket ilustrado

A ideia é que caso alguém precise fazer uma requisição a uma API, por exemplo, deve requisitar um token antes disso. Caso não haja um token disponível, a requisição é negada, até que novos tokens sejam adicionados ao balde.

Esses tokens podem ser definidos de diversas maneiras:

  • Global: para todas as requisições de um sistema;
  • Usuário: para um determinado usuário;
  • Busca rápida: para buscas que normalmente são frequentes;
  • Busca lenta: para buscas não tão frequentes;
  • Etc.

A ideia é que possamos prevenir abusos a APIs de acordo com necessidades específicas, sem que isso necessariamente prejudique nossos usuários legítimos.

Arquitetura

Na SumUp fazemos uso de Elixir em nossos micro serviços, além de implantar os mesmos em clusters rodando Kubernetes.

Para o exemplo desse artigo, usaremos ainda um backend Redis para armazenar o estado do Token Bucket. O Redis é uma base de dados em memória, e ajudará com que diversas instâncias de um micro serviço de interesse compartilhem a visão global sobre rate-limiting.

Instâncias da aplicação usando Redis para fazer rate-limit pelo Hammer

Implementação

Nossos micro serviços Elixir usam o framework web Phoenix, que possui Plugs onde podem ser adicionados a Routers HTTP.

Primeiro precisamos adicionar o Hammer, Hammer Plug e Hammer Backend Redis como dependências:

E então configurar o Hammer:

No exemplo acima estamos definindo o tempo de expiração de cada entrada no Redis como 2 minutos.

Podemos agora definir uma rota da seguinte maneira:

Nesse exemplo, é definido na API um endpoint /account para uma determinada :account (parâmetro de path) que pode ser acessado por um HTTP GET.

Ao acessar esse endpoint, a função show() deve ser chamada no módulo AccountController, a implementação completa do mesmo está fora do escopo desse artigo.

Agora podemos adicionar um plug de rate-limiting no AccountController:

Esse plug especifica que quando a action for :show, o rate-limit deve ser feito pelo parâmetro :account, sendo permitidas 10 requisições em um intervalo de 60s.

Ou seja, de acordo com o algoritmo de Token Bucket, uma requisição para uma determinada conta possui 10 tokens por minuto. Caso um usuário passe desse limite em menos de um minuto, terá sua requisição bloqueada até que o número de tokens do bucket seja atualizado no próximo minuto.

Essa é uma implementação bem simples de rate-limiting por endpoint de API. O Hammer oferece ainda uma série de outras possibilidades de personalização, que permitem deixar as regras tão específicas quanto se queira.

E na prática?

Será que isso funciona na prática? A resposta é sim.

Segue exemplo de uma ocasião em que uma de nossas APIs teve uma tentativa de abuso, totalmente controlada por esse mecanismo:

Rate-limit em ação

O exemplo ilustra picos de até cerca de 15 requisições/s sendo bloqueadas pelo Hammer, sendo que nesse intervalo o serviço em questão permaneceu totalmente funcional para nossos usuários.

Conclusão

A questão se APIs expostas publicamente serão alvo de mau uso não é apenas "se" isso ocorrerá, mas "quando".

Por isso, para garantir o nível de serviço para usuários legítimos e evitar o vazamento de dados, mecanismos de proteção de APIs são absolutamente indispensáveis.

Para o caso de micro serviços em Elixir com Phoenix, o Hammer foi testado e aprovado em campo de batalha.

É necessário que fique claro ainda que a proteção de rate-limiting é apenas um dos aspectos que devem ser considerados na exposição pública de APIs. Outros aspectos como criptografia forte, autenticação, WAFs devem ser considerados como fatores adicionais para proteção de APIs.

--

--