Aprenda / Ruby on Rails

IntermediárioAula 18 de 2017 min0% da trilha

Segurança de API: CORS, força bruta e Brakeman

Autenticação e autorização resolvem quem é o usuário e o que ele pode fazer — mas não impedem um atacante de tentar mil senhas por minuto, nem avisam sobre um SQL cru esquecido no código.

As lições de Devise e CanCanCan resolveram duas perguntas: quem é o usuário, e o que ele pode fazer depois de identificado. Isso não cobre tudo. Faltam três bases que qualquer API de produção precisa: quem além do seu front pode chamar essa API pelo navegador, o que impede alguém de tentar mil senhas seguidas contra o login, e como pegar um erro de segurança antes dele chegar em produção.

CORS — quem o navegador deixa chamar sua API

Por padrão, um navegador bloqueia JavaScript rodando num domínio de ler a resposta de uma API rodando em outro domínio, a menos que a API diga explicitamente, via header, que aquele domínio é permitido. Essa restrição é chamada CORS (Cross-Origin Resource Sharing), e — ponto importante — ela é aplicada pelo navegador, não pelo servidor Rails: um cliente que não é navegador (um curl, um app mobile, outro serviço backend) simplesmente ignora CORS.

config/initializers/cors.rb
Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins "https://app.exemplo.com"
    resource "*", headers: :any, methods: [:get, :post, :patch, :delete]
  end
end

Sem essa configuração, um front-end legítimo rodando em outro domínio também seria bloqueado pelo próprio navegador do usuário — CORS não é sobre esconder a API de atacante nenhum, é sobre declarar explicitamente quem tem permissão de consumir ela a partir de um navegador.

Bloqueio de força bruta — limitando tentativa de login

Sem limite nenhum, um atacante pode automatizar milhares de tentativas de senha por minuto contra /usuarios/sign_in. A defesa mais direta é limitar quantas tentativas o mesmo IP (ou o mesmo e-mail) pode fazer num intervalo de tempo.

config/initializers/rack_attack.rb
class Rack::Attack
  throttle("login/ip", limit: 5, period: 60) do |req|
    req.ip if req.path == "/usuarios/sign_in" && req.post?
  end
end

Depois de 5 tentativas do mesmo IP em 60 segundos, novas tentativas passam a ser bloqueadas até a janela reiniciar. Isso não impede um ataque distribuído entre muitos IPs diferentes, mas elimina de longe a forma mais comum de força bruta contra login.

Brakeman — encontrando a vulnerabilidade antes do merge

terminal
brakeman --no-pager

Brakeman é uma ferramenta de análise estática: ela lê o código Rails sem rodar a aplicação, procurando padrões conhecidos de vulnerabilidade — SQL interpolado direto numa query (a mesma falha da lição de paginação e filtros), mass assignment sem strong parameters, redirecionamento não validado. O uso comum é como etapa de CI: um pull request que introduz um desses padrões falha a build antes de chegar em produção.

Exercício

O rack-attack do exemplo limita tentativa de login só por IP. Um atacante usando muitos IPs diferentes contra o mesmo e-mail passaria batido. Adicione um segundo throttle limitando tentativas por e-mail, além do que já existe por IP.

Quiz rápido

Quem realmente aplica a restrição de CORS quando ela bloqueia uma requisição?
O que o throttle do rack-attack no exemplo (limit: 5, period: 60) faz na prática?