Skip to content
This page has been auto-translated and may contain errors.View in English

Negação de serviço

O formulário de cadastro tem um campo de nome. Nada impede que um nome tenha dez milhões de caracteres.

js
// O que é enviado como `name`
'a'.repeat(10_000_000)

Não há nada de código nessa string. Ela não será interpretada como nada. Ela só é enorme, e enorme já é o suficiente.

Guarde isso e o banco de dados cresce. Consulte isso e a consulta fica lenta. Faça backup e o backup também cresce, toda noite, para sempre. Faça isso algumas milhares de vezes e o serviço para de conseguir responder a qualquer pessoa.

Uma requisição não precisa ser engenhosa para causar dano. Ela só precisa ser cara.

Envie isso apenas contra o seu próprio serviço

Testes de volume são indistinguíveis de um ataque enquanto estão em andamento, e afetam todos os outros usuários de qualquer sistema para o qual sejam direcionados. Use apenas a sua própria aplicação, ou um sistema para o qual você tenha permissão explícita para testar.

Disponibilidade é uma propriedade de segurança

Segurança costuma ser discutida como manter segredos e manter dados corretos. Existe uma terceira propriedade, e este é o capítulo dela: disponibilidade, ou seja, se o serviço responde ou não.

Um ataque de negação de serviço, ou DoS, ataca justamente essa terceira propriedade. Ele não lê nada, não muda nada e não rouba nada. Ele torna o serviço incapaz de fazer o seu trabalho, o que para um negócio costuma ser o resultado mais caro.

JunoDisponibilidade é uma propriedade de segurança Ataques costumam ser imaginados como roubo, então esse tipo pode parecer que nem conta como ataque de verdade. Nada é levado.

Pense no que uma loja perde num dia em que não consegue abrir. Ali também nada foi roubado.

JunoDisponibilidade é uma propriedade de segurança Esse tipo de ataque aparece de forma diferente numa revisão de código. Não existe uma função perigosa para procurar, nenhum ponto onde um valor vira código. A pergunta aqui é sobre custo: para cada entrada, qual é a coisa mais cara que uma requisição pode fazer o servidor executar, e o que impede alguém de pedir isso repetidamente?

Todo campo sem limite, todo endpoint de lista sem paginação, toda operação síncrona de arquivo é uma resposta a essa pergunta.

JunoDisponibilidade é uma propriedade de segurança As três propriedades costumam ser citadas juntas como confidencialidade, integridade e disponibilidade. Disponibilidade é a que o time de infraestrutura projeta e o time de aplicação esquece.

Isso é lamentável, porque os ataques de negação de serviço mais baratos quase sempre são bugs de aplicação, não volume de tráfego.

Uma única requisição que dispara uma varredura completa de tabela sem índice, ou uma exportação que carrega um ano de registros na memória, causa mais dano por pacote do que qualquer enxurrada de tráfego.

Tráfego você consegue absorver gastando dinheiro. Um endpoint que custa dez segundos de CPU por chamada precisa ser corrigido.

Quatro formas de derrubar um serviço

O curso agrupa esses ataques pelo que o atacante explora, e vale a pena manter esse agrupamento, porque cada um se combate de um jeito diferente.

FormaComo funcionaO que ela esgota
Bala mágicaUma única mensagem cuidadosamente construída que viola uma regra do protocolo, como enviar mais dados do que um campo foi feito para armazenarUm defeito específico, imediatamente
Baseado em volumeTráfego suficiente ou entrada grande o bastante para saturar a conexão ou lotar o discoBanda, memória, armazenamento
ProtocoloAbuso de como um protocolo foi especificado para funcionar, de forma que o servidor mantém recursos reservados sem nunca poder liberá-losTabelas de conexão, temporizadores, sockets
DistribuídoO mesmo tráfego, chegando de milhares de máquinas separadas ao mesmo tempoTudo o que foi listado acima, vindo de todo lugar

Vale a pena ver o ataque de protocolo em detalhe, porque ele mostra o quão pouco tráfego um ataque eficaz pode exigir. Uma conexão TCP, o aperto de mão por trás da maior parte do tráfego da internet, se estabelece em três passos:

  1. O cliente envia um pacote SYN, que significa "eu gostaria de me conectar".
  2. O servidor responde SYN-ACK, que significa "pode prosseguir", e passa a manter um espaço reservado enquanto espera.
  3. O cliente responde ACK, e a conexão é estabelecida.

Em uma inundação SYN, o atacante envia o primeiro passo com um endereço de retorno falsificado. O segundo passo vai para uma máquina que nunca pediu isso e não tem nada a responder. O terceiro passo nunca chega.

O servidor mantém um espaço reservado aberto e um temporizador rodando, enquanto o atacante já passou para o próximo endereço falsificado.

Nada aqui é de alto volume. É barato para quem envia e caro para quem recebe, repetidas vezes, até a tabela de conexões ficar cheia e um visitante de verdade não conseguir um espaço.

Uma negação de serviço distribuída, ou DDoS, é qualquer um desses ataques vindo de uma botnet: uma rede de máquinas comuns controladas pelo atacante, geralmente infectadas sem que seus donos percebam.

É isso que torna esse tipo difícil de combater. Cada máquina é um dispositivo real fazendo requisições que parecem reais, então não existe um único endereço para bloquear nem uma assinatura para filtrar.

JunoQuatro formas de derrubar um serviço O padrão compartilhado pelos quatro tipos é que o atacante gasta pouco e o servidor gasta muito.

Um aperto de mão falsificado custa um pacote para enviar e prende um espaço reservado por segundos. Esse desequilíbrio é todo o jogo, e é por isso que volume não é a única coisa a se observar.

JunoQuatro formas de derrubar um serviço Dos quatro, os dois que você controla como desenvolvedor de aplicação são bala mágica e baseado em volume. Ataques de protocolo e distribuídos se combatem na borda da rede, pelo seu provedor de hospedagem ou por algum serviço na frente da sua aplicação.

Essa divisão é útil quando um incidente começa. Se as requisições estão chegando e o seu serviço está engasgando com elas, o problema é seu. Se as conexões nem sequer estão se completando, isso é uma camada abaixo da sua e exige outro telefonema.

JunoQuatro formas de derrubar um serviço A categoria que a tabela não nomeia, e a mais provável de estar no seu código, é a algorítmica. Algumas entradas são baratas de enviar e superlineares de processar, então o custo sobe muito mais rápido do que o tamanho da entrada.

Expressões regulares costumam ser a culpada. Contra o padrão /^([a-z0-9]+-?)+$/, alimentar uma sequência de letras seguida de um ponto de exclamação obriga o motor a tentar todas as formas possíveis de dividir a string.

Medido no Node 24, uma chamada por processo novo: 21 caracteres leva 38 milissegundos, 25 leva 606 milissegundos, 29 leva 9,8 segundos, 33 leva 105 segundos. Quatro caracteres a mais compram um aumento de aproximadamente dez vezes, a partir de uma requisição pequena o suficiente para não acionar nenhum limite de tamanho.

O nome disso é ReDoS, negação de serviço por expressão regular. Quantificadores aninhados, uma repetição dentro de outra repetição, são a forma a procurar, e a correção geralmente é reescrever o padrão em vez de limitar a entrada.

As defesas se somam

Não existe um único controle aqui. O curso lista seis, e eles atuam em camadas diferentes de propósito:

  • Redundância. Rode mais de uma instância de tudo: servidores, data centers, caminhos de rede, servidores de nomes. A regra prática é três, para que um possa estar falhando e outro sob ataque enquanto um terceiro ainda atende.
  • Limitação e controle de taxa. A limitação de taxa recusa requisições acima de um teto. O controle de taxa as desacelera. Cem chamadas de API por minuto, cinco tentativas de login em dez minutos, um megabyte por segundo.
  • Filtragem. Decida o que aceitar e de onde. Bloqueie endereços ou regiões, rejeite requisições com cargas claramente maliciosas, exija autenticação, faça triagem por cabeçalhos.
  • Fortalecimento. Remova o que você não precisa. Todo serviço em execução é uma porta de entrada, assim como toda conta que ninguém usa: logins de administrador padrão, ex-funcionários que mantiveram acesso, permissões concedidas uma vez para uma migração.
  • Aplicação de patches. O ataque de bala mágica depende de um defeito específico e conhecido. Aplicar a correção do fornecedor é o que o elimina.
  • Monitoramento. Você não consegue identificar o anormal sem conhecer o normal. Um serviço que costuma receber mil requisições por hora e de repente recebe cem mil só fica obviamente errado se alguém tiver registrado o número mil.

Especificamente para o nome de dez milhões de caracteres, a resposta é um limite de tamanho, verificado no servidor antes de o valor ser armazenado. Isso é validação de esquema, e é a camada para a qual esta seção está caminhando.

JunoAs defesas se somam Nenhuma dessas seis defesas é a resposta sozinha, e esse é justamente o ponto de ter seis.

Redundância ganha tempo. Limites de taxa limitam o dano. Monitoramento é como você descobre o que está acontecendo. Aplicação de patches remove a brecha específica. Elas cobrem momentos diferentes, então você quer todas elas, em vez de escolher a melhor.

JunoAs defesas se somam Monitoramento é a defesa que os times pulam e depois se arrependem, porque é a única que se torna inútil se aplicada retroativamente. Você não consegue estabelecer como era o normal depois que o incidente já começou.

A linha de base que vale a pena ter não é nada chamativa: requisições por minuto por endpoint, percentis de tempo de resposta, taxa de erro. Registre isso antes de precisar, e um alerta se torna possível quando o tráfego dobra, em vez de só quando o serviço já caiu.

JunoAs defesas se somam Vale a pena conhecer os limites de tamanho de corpo no Express antes de você precisar usá-los, porque eles falham silenciosamente.

Um express.json({ limit: '32kb' }) global consome o corpo e rejeita os que excedem o limite com um 413 Payload Too Large antes de qualquer parser de nível de rota rodar. Um express.json({ limit: '2mb' }) por rota, adicionado depois, não faz nada: o body-parser define req._body, então o segundo parser enxerga o trabalho como já feito.

Verificado no Express 4 e 5.2.1. A rota de upload para a qual você deu um limite maior continua sendo rejeitada pelo limite global, e o padrão que funciona é não ter parser global nenhum, com cada rota definindo o seu próprio.

Coloque em prática

O formulário de cadastro aceita name, email e password, os armazena, e retorna os dois primeiros. Sem limites em lugar nenhum.

Resolva três coisas:

  1. Qual requisição custa mais caro ao servidor, e o que a torna cara?
  2. Qual das seis defesas impediria essa requisição, e qual apenas reduziria o dano?
  3. Qual é a mudança mais barata que resolve isso?
Compare suas respostas

1. A requisição mais cara. Um name ou password muito longo. Custa memória para analisar, armazenamento para guardar, tempo em toda consulta que toca a linha, e espaço em todo backup a partir daí.

password é o pior dos dois se a aplicação faz hash dele. O hashing é deliberadamente lento, então um valor enorme transforma uma operação já cara em uma muito mais cara ainda.

2. Quais defesas fazem efeito.

  • Impedem completamente: filtragem e um limite de tamanho. O servidor rejeita a requisição antes de fazer o trabalho caro.
  • Reduzem o dano: limitação e controle de taxa. Elas limitam a frequência da repetição, mas a requisição individual ainda chega.
  • Nenhuma das duas coisas: redundância, aplicação de patches e monitoramento. Elas ajudam você a sobreviver ao ataque, removem brechas não relacionadas, e permitem descobrir que ele está acontecendo.

3. A mudança mais barata. Um comprimento máximo em todo campo de texto, aplicado no servidor. É uma linha por campo em um esquema, e isso elimina a classe inteira do problema, o que explica por que esta seção dedica os capítulos restantes a acertar esse esquema.

Para onde isso leva

Dois capítulos depois, e ambos os bugs vêm da mesma etapa que faltou. Nada verificou o que chegava antes de a aplicação agir sobre isso.

Injeção de SQL é o terceiro e o mais direto. Uma entrada que não apenas fica armazenada no banco de dados, mas diz a ele o que fazer.