BrunoP.Blog

Cómo evitar el spam en formularios sin CAPTCHA (Honeypot y Time-trap)

reCAPTCHA frena a los bots, pero arruina la conversión e incomoda al usuario. Te enseño a proteger tus formularios usando el método Honeypot y Time-trap, con código limpio de copiar y pegar para tu sitio.

A nadie le gusta resolver rompecabezas visuales para enviar un simple formulario de contacto. El reCAPTCHA tradicional (y alternativas similares) crean fricción en el momento más crítico de tu embudo: la conversión. Los estudios indican que los desafíos de CAPTCHA complejos pueden reducir las tasas de conversión de leads entre un 3% y un 5%.

La alternativa inteligente es la seguridad invisible. En lugar de obligar al humano a demostrar que no es un robot, hacemos que el robot revele su identidad de forma silenciosa. Dos técnicas clásicas y altamente efectivas para lograr esto son el Honeypot y el Time-trap.

Cómo funciona el método Honeypot (Tarro de Miel)

Los bots de spam automatizados rastrean la web leyendo el código HTML bruto de las páginas. Al encontrar una etiqueta <form>, intentan rellenar todos los campos de texto disponibles para maximizar las chances de que su mensaje pase, ignorando las hojas de estilo.

El Honeypot consiste en agregar un campo de formulario extra que se mantiene totalmente invisible para los humanos mediante CSS, pero sigue presente en el código para los bots. Si el formulario se envía y este campo oculto contiene cualquier dato, sabemos con certeza absoluta que fue completado por un bot, y descartamos la solicitud.

Consejo clave: No uses nombres obvios como honeypot, spam o hidden_field en el atributo name. Los bots avanzados los esquivan. Usa nombres realistas que simulen campos normales como website, phone_extension o address_line2, y configura tabindex="-1" para evitar que usuarios que navegan con teclado pasen por ahí.

Cómo funciona el Time-trap (Trampa de Tiempo)

Los scripts automatizados están diseñados para enviar formularios lo más rápido posible, a menudo en fracciones de segundo. Un usuario legítimo se toma su tiempo para leer la página, hacer clic en los campos, escribir su nombre, correo y redactar un mensaje — un proceso que rara vez toma menos de 3 a 5 segundos.

El Time-trap registra la marca de tiempo exacta del momento en que se cargó la página (guardada en la sesión de PHP o encriptada en un token). Al recibir los datos, validamos si la diferencia de tiempo es menor a este umbral mínimo (ej: 3 segundos). Si fue demasiado veloz, la solicitud es rechazada.

Advertencia crucial: Úsalo como PRIMERA CAPA

Aunque el Honeypot y el Time-trap eliminan cerca del 95% del spam de scripts genéricos, no representan una solución de seguridad definitiva. Bots sofisticados que corren en navegadores automatizados (como Puppeteer o Playwright) pueden ejecutar JS, evadir campos invisibles e introducir retrasos artificiales antes de enviar.

Por ende, es fundamental combinar estas técnicas con buenas prácticas en el servidor:

  • Validación y sanitización en el servidor: Nunca confíes únicamente en la validación frontend. Comprueba formatos de email en PHP y limpia las cadenas.
  • Escapa toda salida: Si muestras los datos del formulario (en registros de errores o paneles de administración), filtra las cadenas con htmlspecialchars() para prevenir ataques XSS. Nunca hagas echo de inputs en bruto.
  • Rate Limiting: Evita que un atacante abuse de tu servidor de correos restringiendo el número máximo de envíos por dirección IP por minuto.

Prueba ambas técnicas en vivo en el simulador interactivo a continuación.

Simulator: Honeypot & Time-trap

Honeypot Field Vazio / Empty
Time-trap 0.00s
PHP backend console

[Sistema] Servidor pronto para validação local.

[Sistema] Aguardando envio em modo Humano...

Código Pronto: Cole no seu site

<!-- Formulário HTML -->
<form action="processa.php" method="POST">
  <!-- Campo de time-trap dinâmico (carregado via PHP) -->
  <input type="hidden" name="form_token" value="<?= time(); ?>">

  <div class="campo-grupo">
    <label for="nome">Nome</label>
    <input type="text" id="nome" name="nome" required>
  </div>

  <div class="campo-grupo">
    <label for="email">E-mail</label>
    <input type="email" id="email" name="email" required>
  </div>

  <!-- CAMPO HONEYPOT (Oculto via CSS de forma orgânica) -->
  <div class="campo-oculto-protecao">
    <label for="website">Website</label>
    <input type="text" id="website" name="website" tabindex="-1" autocomplete="off">
  </div>

  <div class="campo-grupo">
    <label for="mensagem">Mensagem</label>
    <textarea id="mensagem" name="mensagem" required></textarea>
  </div>

  <button type="submit">Enviar</button>
</form>

<style>
/* Esconde o Honeypot sem usar "display: none" */
.campo-oculto-protecao {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  border: 0;
  opacity: 0;
  pointer-events: none;
}
</style>
<?php
/**
 * processa.php - Validação de segurança no Backend
 */
session_start();

// 1. Validação de Time-trap (Mínimo de 3 segundos para digitação)
$tempo_minimo = 3;
$token_carregamento = filter_input(INPUT_POST, 'form_token', FILTER_VALIDATE_INT);

if (!$token_carregamento || (time() - $token_carregamento) < $tempo_minimo) {
    // Rejeita a submissão rápida demais
    http_response_code(400);
    exit("Erro: Envio bloqueado por validação de tempo.");
}

// 2. Validação de Honeypot (O campo oculto "website" deve estar vazio)
$honeypot = filter_input(INPUT_POST, 'website', FILTER_DEFAULT);

if (!empty($honeypot)) {
    // Rejeita se o bot preencheu o campo invisível
    http_response_code(400);
    exit("Erro: Spam detectado.");
}

// 3. Sanitização e Validação do restante dos inputs
$nome = filter_input(INPUT_POST, 'nome', FILTER_DEFAULT);
$email = filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL);
$mensagem = filter_input(INPUT_POST, 'mensagem', FILTER_DEFAULT);

if (!$email) {
    http_response_code(400);
    exit("Erro: E-mail inválido.");
}

// 4. PRIMEIRA CAMADA CONCLUÍDA - Preparação segura dos dados
// Escapa toda e qualquer saída em tela ou relatórios para prevenir XSS
$nome_seguro = htmlspecialchars($nome ?? '', ENT_QUOTES, 'UTF-8');
$mensagem_segura = htmlspecialchars($mensagem ?? '', ENT_QUOTES, 'UTF-8');

// IMPORTANTE: Combine esta camada com validações server-side robustas,
// verificação de CSRF tokens e regras de rate limiting por IP para 
// blindar totalmente o processamento de envios contra ataques distribuídos.

echo "Sucesso! Mensagem de " . $nome_seguro . " processada de forma segura.";

Quero blindar a segurança dos meus formulários e site

Preguntas frecuentes

¿El Honeypot y Time-trap bloquean todos los spams? No. Filtran la mayoría de los bots automatizados sencillos. Bots avanzados que ejecutan navegadores automatizados requieren defensas adicionales en el servidor, control de IP y rate limits. Estas técnicas son la primera capa.
¿Funciona ocultar el Honeypot con display: none? Algunos bots inteligentes detectan display: none. Lo ideal es usar posicionamiento absoluto negativo u opacidad cero en el campo.
¿Cómo aseguro que PHP no ejecute código malicioso? Filtra todas las variables que recibas (ej: usando filter_input) y aplica htmlspecialchars() a todas las salidas. Nunca hagas echo de inputs en bruto.

Esta guía es parte de la serie Seguridad de sitios. Referencias: OWASP — Anti-Spam, MDN — HTML Inputs.