DDoS em sistemas de votação online: como defender com Node.js

DDoS em sistemas de votação online: como defender com Node.js

>Quando li a notícia no Terra sobre os ataques cibernéticos “poderosos” durante as eleições legislativas russas, meu primeiro instinto não foi geopolítico — foi técnico. Sistemas eleitorais online são, do ponto de vista de engenharia, alguns dos alvos mais sensíveis que existem: alta disponibilidade obrigatória, integridade criptográfica inquestionável e zero tolerância a downtime. Se você é dev, vale estudar o caso a fundo, porque os mesmos padrões que protegem (ou falham em proteger) uma urna digital são os que protegem APIs financeiras, sistemas de saúde e plataformas SaaS críticas. Vou destrinchar o que aconteceu, o que dá pra aprender, e onde a maioria dos times tropeça.

O que aconteceu, segundo o Terra

Em resumo: a Comissão Eleitoral Central da Rússia, presidida por Ella Pamfilova, reportou ataques cibernéticos “massivos e intensos” contra o sistema de voto online durante a madrugada de sábado. Moscou e cerca de outras 30 regiões habilitam votação pela internet, e foi justamente essa camada digital que ficou sob fogo. A votação, vale lembrar, renovava as 450 cadeiras da Duma, e participavam dela inclusive os territórios ocupados do leste da Ucrânia — um ponto que, por si só, já levanta uma série de questões sobre legitimidade e auditoria que escapam ao tema técnico.

O mais interessante para nós, devs, é a frase da própria Pamfilova: “a situação está sob controle e a segurança do sistema permite manter a votação ininterrupta”. Isso só é possível porque a arquitetura foi projetada com redundância, rate limiting agressivo e provavelmente alguma camada de scrubbing center. Mas chega de política — vamos ao código.

Como funciona um ataque “poderoso” a um sistema eleitoral

Quando um porta-voz estatal chama um ataque de “poderoso”, geralmente está descrevendo uma combinação de três vetores rodando em paralelo: DDoS volumétrico na camada de rede (UDP amplification, NTP reflection), HTTP flood na camada 7 (requisições legítimas-lookalike que sobrecarregam o app server) e tentativas de exploração de CVEs específicos na pilha que serve a aplicação de votação.

Na minha experiência operando sistemas sob ataque sustentado, o verdadeiro gargalo nunca é a banda — é o banco de dados. Um atacante inteligente faz 50 requisições por segundo que parecem consultas normais de validação de eleitor, e cada uma força o Postgres a validar token, sessão, permissões e fazer um JOIN pesado. Em 10 minutos, sua CPU de banco está em 100% e o sistema cai “misteriosamente” sem nenhum pico óbvio de tráfego.

Um sistema de votação online maduro precisa lidar com três adversários simultâneos: o ataque externo clássico, o insider com credenciais válidas tentando manipular votos, e o bug de aplicação que permite dupla contagem. A Rússia apostou num modelo centralizado — todo voto passa pelos servidores da CEC. Esse é o oposto do que o Brasil tenta fazer com a urna eletrônica (offline, sem rede no momento da votação). Os trade-offs são filosóficos, mas ambos têm seus calcanhares de Aquiles.

Na prática: montando um middleware anti-DDoS com Node.js

Vou mostrar um exemplo real de middleware que eu já usei em produção para mitigar ataques de baixa e média sofisticação. Não é bala de prata contra um estado-nação, mas é exatamente o tipo de defesa em profundidade que diferencia um sistema que cai de um que continua no ar durante uma eleição.

// middleware/ddosGuard.js
const Redis = require('ioredis');
const redis = new Redis({ host: '127.0.0.1', port: 6379 });

// Sliding window counter + challenge progressivo
const WINDOW_SECONDS = 60;
const SOFT_LIMIT = 120;   // requests normais por minuto
const HARD_LIMIT = 300;   // acima disso, assume bot
const BLOCK_TTL = 900;    // 15 min de bloqueio

async function ddosGuard(req, res, next) {
  const ip = req.headers['x-forwarded-for']?.split(',')[0].trim() || req.ip;
  const key = `rl:${ip}`;

  try {
    const count = await redis.incr(key);

    if (count === 1) {
      await redis.expire(key, WINDOW_SECONDS);
    }

    // Cabeçalho informativo para análise posterior
    res.set('X-RateLimit-Count', count);

    if (count > HARD_LIMIT) {
      // Bloqueia agressivamente e registra para o SOC
      await redis.set(`block:${ip}`, '1', 'EX', BLOCK_TTL);
      await redis.zadd('ddos:offenders', Date.now(), ip);

      console.warn(`[DDOS] IP bloqueado: ${ip} (${count} req/min)`);
      return res.status(429).json({
        error: 'too_many_requests',
        retry_after: BLOCK_TTL,
      });
    }

    if (count > SOFT_LIMIT) {
      // Challenge progressivo: exige header customizado
      // que prova que o cliente executou JS leve
      const proof = req.headers['x-client-proof'];
      if (!proof || !verifyProof(proof, ip)) {
        return res.status(425).json({
          error: 'challenge_required',
          hint: 'execute /js/challenge.js',
        });
      }
    }

    next();
  } catch (err) {
    // Fail-open é perigoso, mas fail-closed derruba tudo.
    // Em sistema eleitoral, eu prefiro fail-open + alerta.
    console.error('[DDOS] Redis indisponível:', err.message);
    next();
  }
}

function verifyProof(proof, ip) {
  // Simplificado: em produção use HMAC + timestamp + nonce
  const crypto = require('crypto');
  const secret = process.env.PROOF_SECRET;
  const expected = crypto
    .createHmac('sha256', secret)
    .update(ip + Math.floor(Date.now() / 60000))
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(proof),
    Buffer.from(expected.slice(0, proof.length))
  );
}

module.exports = ddosGuard;

Esse padrão de sliding window em Redis com bloqueio progressivo é o esqueleto que você encontra por trás de Cloudflare, AWS Shield e similares — só que aberto pra você auditar. Para uma eleição real, eu somaria: análise comportamental (um eleitor humano não vota 50 vezes), CAPTCHA adaptativo baseado em reputation score e, crucialmente, separação física entre o banco de votação e qualquer serviço exposto à internet.

Erros comuns que devs cometem em sistemas críticos

Depois de anos revisando arquitetura de sistemas sob pressão, esses são os deslizes que mais vejo:

  • Confiar em WAF sem observabilidade. WAF bloqueia, mas ninguém olha o log. Quando o atacante muda de tática, você descobre três dias depois.
  • Rate limiting só na borda. Borda mitigada, app server derretido. Sempre imponha limite também dentro da VPC, com circuit breaker.
  • Banco de dados acessível pela mesma rota do front-end. Separe leitura e escrita, use fila assíncrona para contagem de votos, e nunca exponha o Postgres diretamente.
  • Logs sem correlação. Em um ataque, você precisa reconstruir a timeline entre edge, app e DB. Se cada camada tem timestamp próprio sem sincronia NTP, boa sorte.
  • Esquecer do “ataque interno”. O dev com SSH no servidor de votação é vetor tão real quanto o botnet externo. MFA, audit log imutável e segregação de função são inegociáveis.
  • Testar carga só em horário comercial. Eleições acontecem em horário adverso. Simule pico real com Locust ou k6 rodando 24h antes.

A frase de Pamfilova — “a situação está sob controle” — só é crível se a equipe russa tinha, no mínimo, observabilidade em tempo real, runbook testado e capacidade de failover entre data centers. Não subestime o peso operacional disso.

Comparativo: voto online centralizado vs. urna offline

Vale a pena olhar como diferentes países tratam o problema, porque o trade-off é real:

Modelo Exemplo Prós Contras
Centralizado online Rússia, Estônia Acessível, resultado rápido Superfície de ataque ampla, dependência de provedor
Offline com urna eletrônica Brasil Sem rede no momento do voto, auditável fisicamente Hardware proprietário, logística cara
Híbrido (papel + auditoria digital) Alemanha, alguns estados dos EUA Verificabilidade end-to-end Mais lento, complexo de auditar

Não existe bala de prata. O que existe é engenharia honesta sobre quais ameaças você aceita e quais mitiga. Quando vejo devs tratando “sistema de votação” como se fosse CRUD de e-commerce, fico preocupado — a consequência de bug aqui é colapso democrático, não carrinho abandonado.

Por que isso importa pra você, dev

Mesmo que você nunca vá trabalhar numa urna eletrônica, os princípios se repetem: rate limiting, defesa em profundidade, observabilidade, segregação de função e auditabilidade. Quando você projeta uma API de pagamento, um sistema de saúde ou um pipeline de votação em assembleia de condomínio, o mesmo playbook se aplica. Ataques “poderosos” não exigem sophistication de estado-nação para derrubar a maioria das aplicações — basta persistência e um pouco de conhecimento de burp suite.

Se quiser ir além, estude os papers do End-to-End Verifiable Voting (iniciativa Verificatum), brinque com um honeypot local simulando um sistema eleitoral, e leia os post-mortems públicos do Cloudflare sobre mitigação de DDoS hipervolumétrico. É o tipo de conhecimento que paga dividendos em qualquer carreira de engenharia de plataforma.

FAQ — Perguntas que um dev faria

Qual a diferença entre DDoS volumétrico e DDoS de camada 7?
Volumétrico satura a banda com tráfego descartável (UDP flood, amplificação DNS). Camada 7 envia requisições HTTP “legítimas” que forçam processamento caro no servidor — é mais difícil de detectar porque parece usuário real.

Por que usar Redis para rate limiting em vez de contador em memória?
Em sistema multi-instância, contador local não é compartilhado — cada réplica tem o seu, e o atacante escala proporcionalmente. Redis centraliza o estado e permite sliding window consistente.

Um CAPTCHA resolve o problema de voto online?
Resolve parte. CAPTCHA avançado (reCAPTCHA v3, hCaptcha) usa reputation score e análise comportamental. Mas bots com browsers reais e LLMs já burlam boa parte disso. Combine com rate limit, MFA e auditoria.

Como testar se meu sistema aguenta um ataque real?
Use ferramentas como Locust, k6 ou hey para simular carga, e plataformas como ChaosBlade ou Gremlin para injetar falhas. Para DDoS autorizado, alguns CDNs oferecem “attack modes” em ambientes de teste.

Sistemas eleitorais deveriam usar blockchain?
Tese controversa. Blockchain resolve integridade do registro, mas não impede coerção do voto, nem resolve o problema do “client-side malware” que troca o voto antes de enviar. Verificabilidade end-to-end resolve melhor, sem a complexidade operacional de uma chain pública.

Gostou? Me segue no GitHub e deixa um comentário se tiver dúvida ou quiser aprofundar algum ponto — seja sobre defesa de aplicações, arquitetura de sistemas críticos ou o debate sobre voto eletrônico.

Y

Yuri Sousa

Front-End Developer / Designer

Desenvolvedor apaixonado por criar experiências digitais acessíveis e visualmente perfeitas. Escrevo sobre desenvolvimento web, design e tecnologia.