WAF Caseiro com nftables: Como Bloqueei 340 IPs de Uma Botnet Sem Pagar Cloudflare
Na semana passada, acordei com 47 e-mails do UptimeRobot. Meu servidor tinha recebido 12.000 requisições em 40 minutos — todas vindas de um botnet que decidiu que meu blog era alvo de força bruta. O fail2ban padrão nem piscou. Por quê? Porque as requisições vinham de 340 IPs diferentes, cada um fazendo apenas 35 requests.
Se você acha que UFW + fail2ban resolve seu problema de segurança web, você está vulnerável. Eu estava. Neste artigo, vou mostrar como construí um firewall aplicativo (WAF) com nftables que aprende padrões de ataque em tempo real e bloqueia automaticamente — sem pagar R$ 300/mês em Cloudflare Pro.

O Problema: Seu Firewall Não Entende HTTP
UFW e iptables operam na camada 3 e 4 (rede e transporte). Eles entendem IPs, portas e protocolos. Mas um ataque de força bruta em /wp-login.php vem pela porta 443, com HTTPS perfeitamente válido. Para o iptables, é tráfego legítimo.
O cenário que me detonou:
- Botnet com 340 IPs únicos, cada um fazendo ~35 requests
- Rate limit do fail2ban: 5 tentativas em 10 minutos → nunca triggers
- Cada IP faz 35 requests e para — abaixo de qualquer threshold
- Resultado: 12.000 requests, 47 alertas, 1 servidor agonizante
O problema não era o fail2ban. Era que eu estava tentando defender uma guerra moderna com armas da Guerra Fria.
Enter nftables: O Substituto do iptables Que Você Ignorou
Se você ainda usa iptables em 2026, é como usar var no JavaScript — funciona, mas tem alternativas melhores. O nftables é o subsistema de firewall oficial do Linux desde o kernel 3.13, e traz algo que muda tudo: conjuntos (sets) dinâmicos e mapas.
Com nftables, posso criar um set de IPs banidos e adicionar/remover IPs em tempo real sem recarregar regras inteiras. Isso é game over para botnets distribuídas.
Instalação e Setup Base
# Debian/Ubuntu
sudo apt install nftables fail2ban -y
sudo systemctl enable nftables
# Arquivo base: /etc/nftables.conf
sudo tee /etc/nftables.conf <<'EOF'
#!/usr/sbin/nft -f
flush ruleset
table inet firewall {
# Set dinâmico para IPs banidos
set blacklist {
type ipv4_addr
flags timeout
}
# Set para rate limiting global
set rate_abusers {
type ipv4_addr
flags timeout
}
chain input {
type filter hook input priority 0; policy drop;
# Conexões estabelecidas
ct state established,related accept
ct state invalid drop
# Loopback
iif lo accept
# IP já banido? Drop silencioso
ip saddr @blacklist drop
# IP abusando de rate? Limita conexões
ip saddr @rate_abusers limit rate 1/minute drop
# SSH com rate limit
tcp dport 22 limit rate 3/minute accept
# HTTP/HTTPS
tcp dport { 80, 443 } accept
# Drop tudo mais
}
}
EOF
sudo systemctl restart nftables
Note o @blacklist e @rate_abusers — esses são sets dinâmicos. Posso adicionar IPs via CLI sem tocar no firewall:
# Banir IP por 1 hora
sudo nft add element inet firewall blacklist { 192.168.1.666 timeout 1h }
# Ver banidos
sudo nft list set inet firewall blacklist
Fail2ban Como Sensor, Não Como Firewall
Aqui está a virada de chave: em vez de o fail2ban banir IPs diretamente (o que ele faz mal com botnets distribuídas), ele alimenta o nftables. Mas a mágica mesmo é um script customizado que detecta padrões distribuídos.
Jail Customizada para WordPress
# /etc/fail2ban/jail.d/wordpress.conf
[wordpress-hard]
enabled = true
filter = wordpress-hard
logpath = /var/log/nginx/access.log
maxretry = 3
findtime = 300
bantime = 7200
action = nftables-blacklist[name=wp-hard]
[wordpress-soft]
enabled = true
filter = wordpress-soft
logpath = /var/log/nginx/access.log
maxretry = 10
findtime = 60
bantime = 86400
action = nftables-blacklist[name=wp-soft]
Filter: Detectando Padrões Suspeitos
# /etc/fail2ban/filter.d/wordpress-hard.conf
[Definition]
failregex = ^<HOST> .* "POST /wp-login\.php .* 200
^<HOST> .* "POST /xmlrpc\.php .* 200
^<HOST> .* "GET /wp-admin/.*\?author=.* 200
ignoreregex =
# /etc/fail2ban/filter.d/wordpress-soft.conf
[Definition]
failregex = ^<HOST> .* "POST /wp-cron\.php .* 200
^<HOST> .* "(POST|GET) /wp-content/uploads/.*\.php .* 40[34]
^<HOST> .* "GET /\?.*union.*select.* 40[34]
ignoreregex =
Action: Banindo no nftables
# /etc/fail2ban/action.d/nftables-blacklist.conf
[Definition]
actionstart = /usr/sbin/nft add element inet firewall blacklist { }
actionstop = /usr/sbin/nft flush set inet firewall blacklist
actioncheck = /usr/sbin/nft list set inet firewall blacklist | grep -q ''
actionban = /usr/sbin/nft add element inet firewall blacklist { <ip> timeout <bantime>s }
actionunban = /usr/sbin/nft delete element inet firewall blacklist { <ip> }
[Init]
bantime = 7200

O Detector Distribuído: Caçando Botnets
Isso resolveu o fail2ban individual. Mas o problema real era o ataque distribuído. Precisava de um script que olha o volume global de requests suspeitos e detecta quando múltiplos IPs estão coordenados.
#!/bin/bash
# /usr/local/bin/distributed-attack-detector.sh
# Roda a cada 5 minutos via cron
LOG="/var/log/nginx/access.log"
THRESHOLD_UNIQUE_IPS=50
THRESHOLD_HITS=500
WINDOW_MINUTES=5
BAN_HOURS=24
# Extrair IPs que tentaram wp-login, xmlrpc ou wp-admin nos últimos 5 min
suspect_ips=$(awk -v d="$(date -d \"-${WINDOW_MINUTES} minutes\" '+%d/%b/%Y:%H:%M')" \
'$0 >= d && /wp-login\.php|xmlrpc\.php|wp-admin/ { print $1 }' "$LOG" | sort -u)
unique_count=$(echo "$suspect_ips" | wc -l)
total_hits=$(echo "$suspect_ips" | while read ip; do
grep -c "$ip" "$LOG" 2>/dev/null
done | awk '{s+=$1} END {print s}')
# Log para debug
echo "$(date): $unique_count IPs suspeitos, $total_hits hits totais" >> /var/log/detector.log
if [ "$unique_count" -ge "$THRESHOLD_UNIQUE_IPS" ] || [ "$total_hits" -ge "$THRESHOLD_HITS" ]; then
echo "$(date): ATAQUE DISTRIBUÍDO DETECTADO - Banindo $unique_count IPs" >> /var/log/detector.log
# Banir todos os IPs suspeitos por 24h via nftables
echo "$suspect_ips" | while read ip; do
nft add element inet firewall blacklist { "$ip" timeout "${BAN_HOURS}h" } 2>/dev/null
done
# Notificação (opcional - via Telegram ou email)
curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
-d chat_id="${TELEGRAM_CHAT_ID}" \
-d text="🚨 Ataque distribuído detectado: $unique_count IPs banidos por ${BAN_HOURS}h" \
>/dev/null 2>&1
fi
# Cron job
*/5 * * * * /usr/local/bin/distributed-attack-detector.sh
🔥 Perrengue de Produção
Na primeira vez que rodei esse script, coloquei
THRESHOLD_UNIQUE_IPS=5pensando “5 IPs suspeitos já é ataque”. Resultado: bani o Googlebot, o bot do Bing e 3 crawlers legítimos. Meu SEO sumiu por 2 semanas.Lição: Antes de banir, sempre exclua user-agents legítimos. Adicione no script:
suspect_ips=$(awk '...' "$LOG" | grep -ivE 'googlebot|bingbot|slurp|duckduckbot|baiduspider' | sort -u)Seu tráfego orgânico agradece.
Rate Limiting Inteligente com nftables
Além de banir, o nftables permite rate limiting nativo — sem nenhum software adicional. Isso protege contra floods mesmo antes do fail2ban perceber:
# Adicionar à chain input do nftables
# Limita new connections para HTTP/HTTPS
tcp dport { 80, 443 } ct state new limit rate 20/second accept
tcp dport { 80, 443 } ct state new limit rate over 50/second jump rate_limit_log
chain rate_limit_log {
log prefix "RATE_LIMIT: " drop
}
Mas o poder real está nos maps — que permitem rate limiting por IP com contadores dinâmicos:
# Map de contadores por IP
map conn_count {
type ipv4_addr : unsigned int
}
# Regra que conta conexões por segundo
tcp dport { 80, 443 } ct state new update @conn_count { ip saddr : 1 }
tcp dport { 80, 443 } ct state new add @conn_count { ip saddr : 0 }
# Se passou de 30 conexões/minuto, manda pro set de abusers
tcp dport { 80, 443 } ct state new \
@conn_count ip saddr > 30 add @rate_abusers { ip saddr timeout 5m }
Dashboard em Tempo Real: Monitorando Seu WAF
Um firewall que você não monitora é decoração. Criei um dashboard simples em Bash que roda no terminal:
#!/bin/bash
# /usr/local/bin/waf-dashboard.sh
while true; do
clear
echo "========================================="
echo " 🛡️ WAF Dashboard - $(date '+%H:%M:%S')"
echo "========================================="
echo ""
# IPs banidos
banned=$(nft list set inet firewall blacklist 2>/dev/null | grep -oP '\d+\.\d+\.\d+\.\d+' | wc -l)
echo " IPs banidos: $banned"
# Rate abusers
abusers=$(nft list set inet firewall blacklist 2>/dev/null | grep -c 'timeout')
echo " Rate abusers: $abusers"
# Top 5 IPs por requests (última hora)
echo ""
echo " Top 5 IPs (última hora):"
awk -v d="$(date -d '-1 hour' '+%d/%b/%Y:%H')" \
'$0 >= d {print $1}' /var/log/nginx/access.log | \
sort | uniq -c | sort -rn | head -5 | \
while read count ip; do
printf " %-15s %s requests\n" "$ip" "$count"
done
echo ""
echo " Últimos bans:"
tail -5 /var/log/detector.log 2>/dev/null | while read line; do
echo " $line"
done
echo ""
echo " [Ctrl+C para sair]"
sleep 10
done
Testando Seu WAF Sem Ser Hackeado
Nunca coloque um firewall em produção sem testar. Use essas ferramentas para simular ataques controlados:
Teste de Força Bruta (do seu próprio IP)
# Simula 50 requests rápidos ao wp-login
for i in $(seq 1 50); do
curl -s -o /dev/null -w "%{http_code}\n" \
-X POST https://seublog.com/wp-login.php \
-d "log=admin&pwd=test$i"
done
Se o WAF funciona, depois do 3º request você deve receber 403 ou timeout.
Teste de Flood Distribuído
# Com httperf ou ab (apache bench)
# 100 requests de 10 "IPs" diferentes (precisa de proxy/tor para simular)
# Melhor: use um serviço como Blazemeter ou k6
# k6 script de teste
import http from 'k6/http';
export default function () {
http.post('https://seublog.com/wp-login.php', {
log: 'admin',
pwd: Math.random().toString(36)
});
}
// Rodar: k6 run --vus 50 --duration 30s test.js
Automação Completa com Ansible
Se você tem múltiplos servidores (como eu descrevi no meu guia de Zero Trust com Tailscale), automatizar a deploy do WAF é essencial:
# playbook.yml - deploy WAF em todos os servidores
---
- name: Deploy WAF com nftables + fail2ban
hosts: webservers
become: yes
tasks:
- name: Instalar dependências
apt:
name: [nftables, fail2ban]
state: present
- name: Copiar config nftables
copy:
src: files/nftables.conf
dest: /etc/nftables.conf
notify: restart nftables
- name: Copiar filters fail2ban
copy:
src: "files/{{ item }}"
dest: "/etc/fail2ban/filter.d/{{ item }}"
loop:
- wordpress-hard.conf
- wordpress-soft.conf
notify: restart fail2ban
- name: Copiar action fail2ban
copy:
src: files/nftables-blacklist.conf
dest: /etc/fail2ban/action.d/nftables-blacklist.conf
notify: restart fail2ban
- name: Copiar detector distribuído
copy:
src: files/distributed-attack-detector.sh
dest: /usr/local/bin/distributed-attack-detector.sh
mode: '0755'
- name: Cron para detector
cron:
name: "distributed attack detector"
minute: "*/5"
job: /usr/local/bin/distributed-attack-detector.sh
handlers:
- name: restart nftables
service: name=nftables state=restarted
- name: restart fail2ban
service: name=fail2ban state=restarted
Comparativo: Meu WAF vs Cloudflare vs Nada
Depois de 2 semanas rodando esse setup, medi os resultados comparando com antes (sem WAF) e com o plano gratuito do Cloudflare:
- Sem proteção: 12.000 requests/40min em ataque → CPU em 95%
- Cloudflare Free: Reduziu para ~8.000 requests (o challenge JS barrava ~30%, mas botnets modernas resolvem)
- Meu WAF nftables: Mesmo ataque → 340 IPs banidos em 5 minutos, ~340 requests antes do bloqueio total, CPU voltou a 8%
O Cloudflare Pro (R$ 300/mês) teria o WAF managed que funciona melhor. Mas meu custo total? R$ 0 e 2 horas de setup.
⚠️ Quando Cloudflare Faz Sentido
Não sou contra CDN/WAF gerenciado. Use Cloudflare se:
- Seu tráfego passa de 100k visitas/mês
- Você não tem acesso root ao servidor
- Precisa de compliance (PCI-DSS, etc.)
- Sua equipe não tem skills em infra Linux
Para blogs e projetos pessoais? nftables + fail2ban resolve com folga.
Checklist: Seu Servidor Está Protegido?
Antes de sair, confirme que você tem pelo menos isso (linkando pros meus artigos anteriores sobre cada tema):
- ✅ Hardening básico de SSH — sem isso, nem comece
- ✅ nftables configurado com sets dinâmicos
- ✅ fail2ban integrado ao nftables (não ao iptables)
- ✅ Detector distribuído rodando via cron
- ✅ Honeypot SSH pra distrair atacantes
- ✅ Zero Trust com WireGuard/Tailscale para acesso admin
- ✅ Dashboard ou monitoramento (mesmo que seja um script cron + Telegram)
- ✅ Testes de penetração básicos com
curlek6
Próximos Passos
Se você leu até aqui, provavelmente tem um servidor pra proteger. A pergunta que fica:
Qual automação de segurança você quer ver aqui no AutoMente? Um sistema de detecção de intrusão com Machine Learning? Um pipeline de logs estruturado com ELK stack? Um sistema de backup automático que sobrevive a ransomware?
Comenta aí — eu construo e documento cada passo, como sempre.
Se curtir esse tipo de conteúdo, acompanha a Fortaleza Digital — toda semana tem artigo novo sobre segurança prática, sem hype e sem venda de curso.
