Detetar um ataque DDoS: como o identificar sem margem para dúvidas
Nem toda a sobrecarga é um ataque. Veja como distinguir sem margem para dúvidas um ataque DDoS de um pico de carga ou de um erro de software, com ss, contadores de pacotes, mensagens do kernel e logs do servidor web.
O serviço deixou de responder, o indicador de carga está no topo e no chat aparece a pergunta: estamos a ser atacados? Essa pergunta pode ser medida. E pode até ser medida bastante depressa, desde que se saiba que quatro números observar, por que ordem, e que contraprova transforma uma suspeita num diagnóstico.
Este artigo trata exclusivamente do diagnóstico. Não se trata de travar um ataque, mas de o distinguir sem margem para dúvidas de um problema de carga, de um erro de software ou, muito simplesmente, de sucesso. Quem quiser saber o que é tecnicamente um ataque DDoS encontra a resposta em O que é um ataque DDoS. Quem quiser agir depois do diagnóstico continua em Proteger o servidor contra ataques DDoS.
Quatro suspeitos, um sintoma
"O servidor está lento" não é um diagnóstico, é um sintoma com pelo menos quatro causas plausíveis. Antes de escrever um único comando, convém saber que padrões quer realmente distinguir.
| Observação | Ataque | Afluência real de visitantes | Erro de software |
|---|---|---|---|
| Início | repentino, em poucos segundos | subida ao longo de minutos, muitas vezes em curva | logo a seguir a um deploy, a um cronjob ou a uma atualização |
| Carga de rede de entrada | alta a extrema, muitas vezes com muitos pacotes pequenos | moderada, com saída bastante superior à entrada | sem nada de especial |
| Ligações por IP de origem | muitas a partir de poucos IPs ou pouquíssimas a partir de muitíssimos IPs | distribuídas de forma uniforme, poucas por IP | normal |
| Referrer no log | quase sempre vazio | sites de notícias, redes sociais, motores de busca | normal |
| Resposta através de 127.0.0.1 | rápida (o problema é a rede) | lenta (o problema é a aplicação) | lenta ou com erro |
| Depois de reiniciar o serviço | a carga volta de imediato | a carga volta e o quadro de erro mantém-se | o problema desaparece muitas vezes durante minutos |
O quarto suspeito não consta desta tabela porque não precisa de coluna própria: os trabalhos agendados. Backups, reindexações, rotações de logs e atualizações de pacotes correm a horas previsíveis. Um olhar a systemctl list-timers e às crontabs custa dez segundos e encerra um número surpreendente de suspeitas de ataque.
Os primeiros 60 segundos: quatro números
Recolha quatro valores por esta ordem. É a combinação que tem valor informativo, cada valor isolado não tem.
cat /proc/loadavg
ss -s
cat /proc/net/dev
curl -o /dev/null -s -w '%{time_total}\n' http://127.0.0.1/
A leitura:
- Carga alta, carga de rede alta, muitas ligações semiabertas, mas o 127.0.0.1 responde em milissegundos: o problema está à frente da aplicação, ou seja, ao nível da rede. É o quadro clássico de um ataque.
- Carga alta, carga de rede normal, o 127.0.0.1 responde devagar: aplicação ou base de dados. Não é um ataque, é trabalho para quem programa.
- Carga baixa, carga de rede alta: muito suspeito. Uma tempestade de pacotes que nem sequer chega à aplicação consome pouco CPU e muita linha.
- Tudo baixo e, mesmo assim, o serviço inacessível a partir do exterior: veja mais abaixo, na secção sobre casos sem rasto mensurável.
Quanto ao valor de carga em si: o /proc/loadavg conta também os processos que estão à espera de entrada e saída. Um valor de 40 em quatro núcleos tanto pode ser um ataque como um disco sobrecarregado. O vmstat 1 5 separa isso com clareza: a coluna r mostra os processos prontos a executar, a b os bloqueados e a wa a percentagem de tempo em espera.
Contar ligações: ss em vez de netstat
Quase todos os tutoriais na internet começam com netstat. Num sistema atual, isso acaba assim:
Command 'netstat' not found, but can be installed with:
apt install net-tools
O netstat faz parte do pacote net-tools, que já não vem nas instalações padrão do Debian 12, do Debian 13, do Ubuntu 22.04 e do Ubuntu 24.04. Na família Red Hat (AlmaLinux, Rocky, RHEL, Oracle Linux) também não. Pode instalá-lo à posteriori, mas faz mais sentido usar o ss do iproute2: está presente em praticamente todos os servidores, é bastante mais rápido quando há muitas ligações e devolve exatamente a mesma informação.
ss -s
Para que todos os comandos seguintes funcionem, precisa de um punhado de pacotes, e os nomes mudam consoante a família da distribuição. É este o ponto em que um tutorial copiado encalha logo na primeira linha em AlmaLinux:
| Ferramenta | Debian e Ubuntu | AlmaLinux, Rocky, Oracle Linux |
|---|---|---|
| ss, nstat, ip | iproute2 | iproute |
| netstat | net-tools | net-tools |
| vmstat, free, top | procps | procps-ng |
| sar | sysstat | sysstat |
| dig | dnsutils | bind-utils |
| tcpdump | tcpdump | tcpdump |
No Debian e no Ubuntu, portanto, apt-get install -y iproute2 net-tools procps sysstat dnsutils tcpdump; na família Red Hat, dnf -y install iproute net-tools procps-ng sysstat bind-utils tcpdump. O curl é melhor não incluir nessa lista: já está instalado como curl-minimal e o pacote completo colide com ele (curl-minimal ... conflicts with curl). Se mesmo assim precisar dele, resolve com dnf -y --allowerasing install curl.
A primeira linha indica o número total de sockets e a linha TCP faz o detalhe: estab, closed, orphaned, timewait. Um valor de timewait elevado, por si só, não é indício de ataque, é a consequência normal de muitas ligações HTTP curtas.
Mais interessante é a distribuição pelos estados:
ss -Htan | awk '{print $1}' | sort | uniq -c | sort -rn
O que salta à vista é um bloco grande em SYN-RECV. São ligações iniciadas mas nunca confirmadas, que é exatamente o padrão de um SYN flood. Isso conta-se diretamente:
ss -Htn state syn-recv | wc -l
A armadilha das colunas em que falham quase todos os one-liners
Assim que passa um filtro de estado ao ss, a coluna de estado desaparece da saída. O ponto remoto fica então na coluna 4 em vez da coluna 5. É precisamente por isso que os one-liners copiados de fóruns devolvem disparates com regularidade: contam portos em vez de endereços IP ou devolvem linhas vazias. Regra a decorar: sem filtro, o ponto remoto é $5, com filtro é $4. O -H suprime ainda o cabeçalho, para que o wc -l dê o valor certo sem correções.
Ligações por IP de origem, à prova de IPv6:
ss -Htn state established | awk '{print $4}' | sed 's/:[^:]*$//' | sort | uniq -c | sort -rn | head -20
O sed corta apenas o último dois-pontos juntamente com o porto. A abordagem habitual com cut -d: -f1 funciona em IPv4, mas parte os endereços IPv6 logo a seguir ao primeiro bloco e torna a análise inútil. Em IPv6 ficam os parênteses retos, o que não atrapalha a contagem.
A interpretação exige bom senso. Cem ligações a partir de um único IP tanto podem ser um ataque como um NAT empresarial, um carrier-grade NAT de uma operadora móvel ou um reverse proxy à frente do seu servidor. Se houver uma rede de distribuição de conteúdos ou um load balancer à frente, só verá os endereços deles e terá de recorrer ao campo X-Forwarded-For no log do servidor web.
Para UDP vale a mesma construção com -u. E, antes de mais, para saber que serviço está à escuta em que porto:
ss -tulnp
Medir corretamente a carga de rede e a taxa de pacotes
A largura de banda em megabit é o número que toda a gente refere. Mais revelador é a taxa de pacotes. Um ataque com 200 000 pacotes minúsculos por segundo põe um servidor de joelhos, apesar de a largura de banda parecer inofensiva.
Primeiro o nome da interface, porque eth0 há muito que não é a resposta certa em todo o lado. Nomes habituais são ens3, enp1s0 ou eth0:
ip -br link
Depois, duas medições com um segundo de intervalo, sem qualquer pacote adicional. O nome da interface é obtido a partir da rota padrão, em vez de ser escrito à mão:
IF=$(ip -o route get 1.1.1.1 | awk '{print $5}')
A=$(cat /sys/class/net/$IF/statistics/rx_packets) || exit 1; sleep 1; B=$(cat /sys/class/net/$IF/statistics/rx_packets); echo "$((B-A)) pacotes/s de entrada em $IF"
Este rodeio tem uma razão bem concreta. Se escrever aqui eth0 de forma fixa e a interface se chamar na realidade ens3 ou enp1s0, como acontece na maioria dos servidores KVM, o comando emite duas vezes cat: /sys/class/net/eth0/statistics/rx_packets: No such file or directory, mas depois anuncia tranquilamente 0 pacotes/s de entrada e termina com o código de saída 0. Num artigo sobre deteção de ataques, esta é a mais perigosa de todas as variantes: lê zero pacotes, dá o alarme por encerrado e o ataque continua. O || exit 1 evita exatamente isso.
O mesmo com rx_bytes dá os bytes por segundo. A partir dos dois valores calcula o tamanho médio dos pacotes, e é isso que revela o tipo de ataque:
- menos de 100 bytes em média com uma taxa de pacotes muito alta: SYN flood, ACK flood ou UDP flood. O alvo é o processamento de pacotes, não a linha.
- 1200 a 1500 bytes com largura de banda elevada e portos de origem 53, 123, 389 ou 11211: ataque de reflexão e amplificação. Os IPs de origem são servidores alheios, não os atacantes.
- distribuição de tamanhos normal e pedidos HTTP bem formados: nível da aplicação. Nesse caso quem decide é o log do servidor web, não o contador de pacotes.
De forma mais cómoda, com o sar do pacote sysstat:
sar -n DEV 1 3
Ponto de tropeço: a medição em direto funciona logo a seguir à instalação, mas a análise histórica com sar -f não. No Debian e no Ubuntu, a recolha de dados vem desligada de fábrica, e em /etc/default/sysstat está ENABLED="false". Quem só der por isso durante um ataque fica sem valores de comparação de anteontem. É por isso que vale a pena ativar este pacote por precaução, e não apenas quando o caso já é sério.
E a limitação mais importante de todas: dentro do servidor só mede aquilo que conseguiu passar. Se houver filtragem à frente, verá uma fração do volume real ou não verá nada. O número fiável está no gráfico de tráfego da área de cliente, não em /proc/net/dev.
Ler as mensagens do kernel
O kernel regista situações de sobrecarga que ficam invisíveis ao nível da aplicação. No Ubuntu e nas versões recentes do Debian, o buffer circular está bloqueado para os utilizadores normais e, sem sudo, aparece:
dmesg: read kernel buffer failed: Operation not permitted
Não é uma avaria, é o kernel.dmesg_restrict=1. Com privilégios de root e marcas temporais:
dmesg -T | grep -Ei 'syn flood|conntrack|neighbour|drop'
A mensagem que a maioria procura tem exatamente este teor:
TCP: request_sock_TCP: Possible SYN flooding on port 443. Sending cookies. Check SNMP counters.
Há duas variantes e uma diferença de formato que convém conhecer:
- Sending cookies significa que os SYN cookies estão ativos e que as ligações continuam a ser servidas. Dropping request aparece em vez disso quando
net.ipv4.tcp_syncookiesestá a 0. Nesse caso os pedidos são descartados, incluindo os de visitantes reais. - Os kernels mais antigos indicam apenas o número do porto, os mais recentes acrescentam o endereço de escuta na forma
0.0.0.0:443. No Debian 12 vê, por isso, a forma curta, e no Debian 13 e no Ubuntu 24.04 a forma longa. Quem fizer grep pelo teor antigo exato não encontra nada nos sistemas novos.
Importante para a honestidade do diagnóstico: esta mensagem não prova qualquer ataque. Aparece também quando uma aplicação trabalha com um listen backlog demasiado pequeno e um pico de carga legítimo a ultrapassa. É um indício, que tem de ser sustentado por outros valores medidos.
Os contadores correspondentes são fornecidos pelo nstat:
nstat -az TcpExtSyncookiesSent
nstat -az TcpExtListenDrops
nstat -az TcpExtListenOverflows
Também aqui há um ponto de tropeço: o nstat guarda um estado intermédio a cada chamada e, da vez seguinte, mostra apenas a diferença. É intencional e até prático para a medição, mas surpreende quem, na segunda chamada, vê zeros de repente. Com -a força valores absolutos, com -s impede a atualização desse estado.
Mais duas mensagens que surgem com regularidade em caso de ataque e que provocam ambas perda de pacotes junto de visitantes legítimos:
nf_conntrack: table full, dropping packet
neighbour: arp_cache: neighbor table overflow!
O nível de enchimento do connection tracking verifica-se com cat /proc/sys/net/netfilter/nf_conntrack_count em comparação com nf_conntrack_max. Ambos os ficheiros só existem se o módulo estiver carregado, ou seja, se houver uma firewall ativa. Num sistema sem regras não existem, e isso é normal.
Logs do servidor web: padrões em vez de intuição
No Debian e no Ubuntu, os logs estão em /var/log/nginx/access.log ou em /var/log/apache2/access.log. Na família Red Hat, o caminho do Apache chama-se /var/log/httpd/access_log, sem ponto antes da terminação. Para começar, chegam três análises:
awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20
awk '{print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20
awk -F'"' '{print $6}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -10
A distinção entre ataque e afluência real resulta da relação entre estas três listas:
- Relação entre pedidos e IPs distintos. Dez mil pedidos vindos de 6000 endereços são público. Dez mil pedidos vindos de 30 endereços não são.
- Pedidos subsequentes. Um visitante real carrega, a seguir à página HTML, folhas de estilo, scripts e imagens. Se na lista de caminhos estiver apenas um único URL e nem um único ficheiro estático, não esteve ali nenhum browser.
- Origem. Quando uma campanha corre bem, o campo Referrer traz uma fonte: um site de notícias, uma rede social, um motor de busca. Num ataque, esse campo está tipicamente vazio ou preenchido com um endereço inventado.
- Identificação do programa. Um único User-Agent exatamente igual ao longo de dezenas de milhares de pedidos é uma ferramenta. Também são suspeitas as identificações de versões de browser muito antigas.
- Códigos de estado. Predominantemente 200 aponta para público, uma parede de 404 ou 499 aponta para sondagem automatizada ou para clientes que desistem antes da resposta.
Atenção aos motores de busca: um IP gosta de afirmar no User-Agent que é o Googlebot. Isso só se verifica com uma resolução inversa seguida de uma resolução direta. Só quando o nome inverso aponta para um domínio do fornecedor e esse nome volta a resolver para o mesmo IP é que a afirmação se confirma.
dig -x 66.249.66.1 +short
Num endereço verdadeiro do Googlebot vem de volta um nome como crawl-66-249-66-1.googlebot.com., que a seguir manda resolver outra vez para o mesmo IP com dig +short crawl-66-249-66-1.googlebot.com. Se não vier nada de volta, isso também se pode dever a um servidor sem resolução DNS de saída, e nesse caso não prova nada.
E a frase mais importante de todas sobre logs: um ataque ao nível da rede não aparece no log do servidor web. Um SYN flood nunca chega à aplicação e não deixa lá uma única linha. Um log vazio não refuta nada, apenas delimita o nível.
A contraprova: como a suspeita se transforma em diagnóstico
Até aqui tem indícios. Só se tornam sólidos com contraprovas, cada uma delas capaz de refutar exatamente uma hipótese.
- Parar o serviço. Pare o servidor web durante 30 segundos. Se a taxa de pacotes de entrada se mantiver igualmente alta, o ataque situa-se abaixo da aplicação. Se cair, eram pedidos dirigidos à sua aplicação, maliciosos ou não.
- Interior contra exterior. Se o
curlresponder em milissegundos através de 127.0.0.1 enquanto o acesso a partir do exterior termina num timeout, a aplicação está saudável e o problema é a linha. - Segunda medição. Repita a medição de pacotes dois minutos depois. Os ataques persistem ou voltam em ondas. Um pico isolado foi apenas um pico.
- Olhar de fora. Um teste externo de acessibilidade a partir de vários locais separa o "inacessível para todos" do "inacessível apenas para si". O segundo caso é quase sempre um problema de routing ou do operador de quem está a observar, não um ataque.
- Verificar a direção. Compare
rx_packetscomtx_packets. Se a taxa de saída for a anormal, não está a ser atacado: é o seu servidor que ataca. Nesse caso ele está comprometido ou a ser abusado como refletor, e o caso muda de imediato de grau de urgência.
Como saber que o diagnóstico se aguenta: consegue dizer numa frase que protocolo atinge que porto e a que taxa, se de entrada ou de saída, e tem duas medições independentes que mostram o mesmo. Tudo abaixo disso é uma suposição.
Quando não há rasto mensurável
Quatro situações que geram confusão com regularidade:
- Já não consegue sequer entrar no servidor. Com a linha saturada, também o SSH deixa de passar. O acesso faz-se então pela consola na área de cliente, que funciona independentemente da conectividade do sistema. É precisamente para isso que ela existe.
- O serviço esteve em baixo e no servidor não há nada. Com filtragem à frente, isso é a regra e não a exceção. Se o ataque for apanhado na rede, o sistema vê apenas uma pequena quebra. A prova está então no gráfico de tráfego da área de cliente.
- O log termina a meio do incidente. Verifique se entretanto correu uma rotação de logs: o material mais antigo fica ao lado como
access.log.1ouaccess.log.2.gz. Se oaccess_logestiver desligado ou em buffer na configuração, as últimas linhas simplesmente faltam. - O kernel está calado. Em sistemas baseados em containers, o buffer circular pertence ao host e o
dmesgnão mostra ali nada de próprio. Num servidor root KVM com kernel próprio, isso não é problema.
O que documentar antes de contactar o fornecedor
Uma comunicação com "o servidor esteve lento esta tarde" prolonga o tratamento por várias voltas. Uma comunicação com valores medidos é tratada de imediato. Recolha os dados enquanto o incidente decorre, porque os contadores em /proc são repostos ao reiniciar.
mkdir -p /root/incidente && cd /root/incidente
date -u > 01-hora.txt
ss -s > 02-sockets.txt
ss -Htan | awk '{print $1}' | sort | uniq -c | sort -rn > 03-estados.txt
ss -Htn state established | awk '{print $4}' | sed 's/:[^:]*$//' | sort | uniq -c | sort -rn | head -50 > 04-top-ips.txt
ip -s link > 05-interfaces.txt
sar -n DEV 1 10 > 06-taxa-pacotes.txt
dmesg -T | tail -100 > 07-kernel.txt
nstat -az > 08-contadores.txt
Se a linha o permitir, faz parte também uma captura curta de pacotes. Limite-a: uma captura sem limite numa linha cheia enche o disco em minutos e agrava o problema:
tcpdump -D
tcpdump -ni "$IF" -s 96 -c 2000 -w /root/incidente/captura.pcap
O tcpdump -D lista as interfaces disponíveis, caso a variável $IF da secção de medição mais acima já não esteja definida. Ambas as chamadas precisam de privilégios de root.
No ticket devem então constar:
- O momento de início e de fim, com fuso horário. O
date -uevita qualquer discussão sobre isso. - O endereço IP e o porto afetados.
- A taxa de pacotes e a largura de banda medidas, com indicação expressa da direção.
- A distribuição por protocolo e, se for identificável, os portos de origem dos pontos remotos.
- Um punhado de endereços de origem a título de exemplo, com a ressalva de que os remetentes podem estar falsificados.
- Um excerto do log do servidor web com o padrão de pedidos recorrente, três a cinco linhas chegam.
- O que foi alterado pouco antes: um deploy, uma mudança de DNS, uma campanha publicitária, uma nova abertura de porto.
- O resultado da contraprova: o serviço estava acessível através de 127.0.0.1 enquanto falhava a partir do exterior?
Conclusões erradas frequentes
- Tomar muitas ligações TIME-WAIT como prova de ataque. São a consequência normal de ligações HTTP curtas e desaparecem sozinhas.
- Tomar a mensagem de SYN flooding como prova. Um listen backlog configurado com um valor demasiado baixo também a produz com um pico de carga legítimo.
- Bloquear um IP com muitas ligações sem verificar. Por trás de um NAT empresarial, de um NAT de rede móvel ou de um proxy à frente, bloqueia assim grupos inteiros de clientes.
- Concluir que há um ataque a partir de largura de banda elevada. Verifique primeiro a direção. Os picos de saída são quase sempre um backup ou um download popular.
- Concluir "não há ataque" a partir de logs vazios. Os ataques ao nível da rede nunca chegam à aplicação.
- Reiniciar durante o incidente em curso. O reinício apaga todos os contadores de que precisaria para a comunicação, e o ataque continua depois exatamente igual.
Quem já treinou esta sequência uma vez não precisa de cinco minutos para chegar a um diagnóstico sólido. E quem junta os valores medidos ao ticket salta as perguntas de seguimento e chega diretamente à solução. A propósito: a checklist para novos servidores root e Configurar o Fail2ban para o trabalho posterior ao nível da aplicação.
Perguntas frequentes
Como distingo um ataque DDoS de uma afluência real de visitantes?
Porque é que o netstat não está instalado no meu servidor?
A mensagem Possible SYN flooding on port 443 significa sempre um ataque?
Porque é que não encontro rasto nenhum no servidor, apesar de o serviço ter estado inacessível por instantes?
Como entro no servidor se o SSH deixar de responder durante o incidente?
Porque é que o one-liner de ss que copiei devolve números errados?
O que faço se a taxa de pacotes de saída for anormal?
2026 KernelHost GmbH. Todos os direitos reservados. Este guia está protegido por direitos de autor. A sua republicação noutros sites, na íntegra, em parte ou de forma editada, não é permitida sem o nosso consentimento por escrito. Citações com indicação da fonte e ligação são expressamente bem-vindas.

