Protección de Formularios Públicos
Modelo de Amenazas (Threat Model)
Sección titulada «Modelo de Amenazas (Threat Model)»Cualquier endpoint que acepte entrada de usuarios no autenticados (o en proceso de autenticación) enfrenta riesgos automatizados. El patrón de protección debe mitigar:
- Credential Stuffing / Account Takeover (ATO): Uso masivo de credenciales filtradas para intentar acceder a cuentas legítimas en la pantalla de Login.
- Carding / Fraude de Pagos: Bots probando lotes de tarjetas de crédito robadas en la pantalla de Checkout.
- Scraping / Enumeración de Datos: Extracción automatizada de catálogos, precios o inventario.
- DDoS de Capa 7 (L7): Sobrecarga del backend enviando miles de peticiones aparentemente válidas a los formularios.
- Form Spam / Creación de Cuentas Falsas: Inundación de las pantallas de Registro y Contacto con basura.
reCAPTCHA Enterprise: El Estándar HERA
Sección titulada «reCAPTCHA Enterprise: El Estándar HERA»En Grupo Herdez, reCAPTCHA Enterprise es el único estándar aprobado para la protección contra bots. Queda prohibido el uso de reCAPTCHA v2 (casillas de “No soy un robot”) y v3 estándar.
Dónde NO usar reCAPTCHA
Sección titulada «Dónde NO usar reCAPTCHA»- Endpoints de APIs B2B internas: Si la comunicación es de backend a backend (server-to-server), usa autenticación de servicio (Workload Identity, mTLS, JWT). reCAPTCHA requiere telemetría del navegador.
- Formularios en intranets cerradas: Aplicaciones que solo están disponibles dentro de la VPN corporativa y no enfrentan amenazas de bots masivos de internet.
Flujo de Validación y Arquitectura
Sección titulada «Flujo de Validación y Arquitectura»La seguridad de reCAPTCHA depende de validar el token en el backend. Un token solo generado en el cliente no provee protección.
Flujo en Dos Lados
Sección titulada «Flujo en Dos Lados»- Frontend: Genera un token usando la clave de sitio (Site Key) de reCAPTCHA al momento de interactuar con el formulario.
- Backend: Recibe el token en el payload del request, llama a la API de GCP para obtener el assessment y decide la acción.
Las 3 Validaciones Obligatorias del Backend
Sección titulada «Las 3 Validaciones Obligatorias del Backend»Al recibir el assessment de reCAPTCHA, el backend debe realizar obligatoriamente estas tres validaciones en orden:
- Token Válido (
valid): ¿El token fue emitido por Google, no ha expirado y no ha sido usado antes? - Acción Coincidente (
action): ¿El token fue generado para la acción correcta (ej.loginvscheckout)? Esto previene ataques de repetición. - Umbral de Puntuación (
score): ¿El riesgo es aceptable para el negocio?
Decisión Tri-Tier (Matriz de Umbrales)
Sección titulada «Decisión Tri-Tier (Matriz de Umbrales)»No existe un score “mágico” para todo. Las aplicaciones deben implementar una decisión en tres niveles:
| Nivel de Score | Clasificación | Acción del Backend |
|---|---|---|
| 0.7 a 1.0 | Tráfico legítimo / Humano | Permitir (Allow): Procesar el request normalmente. |
| 0.3 a 0.6 | Tráfico sospechoso | Step-Up / MFA: Requerir fricción adicional (ej. enviar código por SMS, forzar login). |
| 0.0 a 0.2 | Bot confirmado / Malicioso | Rechazar (Deny): Bloquear silenciosamente o retornar error genérico HTTP 403. |
Nota: Estos valores son una calibración inicial recomendada. Los umbrales definitivos deben ajustarse analizando la telemetría real en producción.
Implementación: Ejemplo End-to-End Productizable
Sección titulada «Implementación: Ejemplo End-to-End Productizable»1. Cloud Armor (Defensa Perimetral)
Sección titulada «1. Cloud Armor (Defensa Perimetral)»Antes de que el tráfico llegue al backend, Cloud Armor descarta el ruido en el edge de GCP. La política estándar para formularios públicos combina rate limiting por IP + WAF preconfigurado de Google (SQLi / XSS) + default allow.
Política estándar: hera-public-forms-policy
Sección titulada «Política estándar: hera-public-forms-policy»| Regla | Prioridad | Acción | Condición de match | Propósito |
|---|---|---|---|---|
| Rate limit por IP | 1000 | rate_based_ban → deny(403) al exceder | Todos los orígenes (*) | Mitigar DDoS de capa 7 limitando a 100 req/min por IP |
| WAF preconfigurado | 2000 | deny(403) | sqli-v33-stable OR xss-v33-stable | Bloquear patrones conocidos de inyección SQL y XSS |
| Default | 2147483647 | allow | Todos los orígenes | Permitir el resto del tráfico legítimo |
Parámetros clave del rate limit
Sección titulada «Parámetros clave del rate limit»| Parámetro | Valor estándar | Justificación |
|---|---|---|
conform_action | allow | Tráfico dentro del umbral pasa sin fricción |
exceed_action | deny(403) | Respuesta genérica sin revelar detalle al atacante |
rate_limit_threshold.count | 100 | Cota razonable para un formulario público humano |
rate_limit_threshold.interval_sec | 60 | Ventana de 1 minuto (estándar para rate limits de capa 7) |
enforce_on_key | IP | Cada IP es un bucket independiente |
Por qué estos valores
Sección titulada «Por qué estos valores»- 100 req/min por IP: un humano difícilmente supera este umbral en un formulario; los bots sí. Ajustable si el formulario tiene uso corporativo legítimo detrás de un NAT compartido (ver siguiente sección).
- WAF preconfigurado (
v33-stable): Google mantiene las firmas actualizadas; usar reglas custom requiere operación continua que no aporta valor en formularios públicos estándar. - Default
allow: Cloud Armor actúa como filtro, no como gate. El control de acceso real lo hace la aplicación (reCAPTCHA + sesión).
2. Integración en Frontend (Next.js / React)
Sección titulada «2. Integración en Frontend (Next.js / React)»import { useEffect, useCallback } from 'react';
// # simplificado para ilustración - Reemplazar <SITE_KEY> por variable de entornoconst SITE_KEY = process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY;
export default function LoginForm() { const handleLogin = useCallback(async (event) => { event.preventDefault();
// Obtener el token de reCAPTCHA const token = await window.grecaptcha.enterprise.execute(SITE_KEY, { action: 'login', });
// Enviar credentials + token al backend await fetch('/api/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: 'user@example.com', password: '<TU_PASSWORD_AQUI>', // # simplificado para ilustración, usar refs o state recaptchaToken: token, }), }); }, []);
return ( <form onSubmit={handleLogin}> {/* ... campos del formulario ... */} <button type="submit">Iniciar Sesión</button> </form> );}3. Backend Validación (Node.js)
Sección titulada «3. Backend Validación (Node.js)»Este ejemplo es productizable y utiliza el SDK oficial autenticado automáticamente con ADC (Application Default Credentials).
const { RecaptchaEnterpriseServiceClient } = require('@google-cloud/recaptcha-enterprise');
// Instancia global utilizando Workload Identity de GKEconst recaptchaClient = new RecaptchaEnterpriseServiceClient();
async function createAssessment(token, recaptchaAction, projectID, siteKey) { const projectPath = recaptchaClient.projectPath(projectID); const request = { parent: projectPath, assessment: { event: { token: token, siteKey: siteKey, expectedAction: recaptchaAction, }, }, };
try { const [response] = await recaptchaClient.createAssessment(request);
// 1. Validar integridad del token if (!response.tokenProperties.valid) { console.error(`Token inválido: ${response.tokenProperties.invalidReason}`); return { allow: false, reason: 'INVALID_TOKEN' }; }
// 2. Validar que la acción coincida if (response.tokenProperties.action !== recaptchaAction) { console.error('La acción del token no coincide'); return { allow: false, reason: 'ACTION_MISMATCH' }; }
// 3. Validar el Risk Score const score = response.riskAnalysis.score;
if (score >= 0.7) { return { allow: true, score }; } else if (score >= 0.3) { // Step-Up (ej. solicitar MFA) return { allow: false, stepUp: true, score }; } else { // Rechazar (Bot) return { allow: false, reason: 'BOT_DETECTED', score }; } } catch (error) { console.error('Error invocando API de reCAPTCHA', error); // Fail-open o fail-closed depende de la criticidad del negocio // Para eCommerce: mejor fail-open (permitir compra, alertar) que bloquear ventas por falla de red return { allow: true, reason: 'API_ERROR_FALLBACK' }; }}Calibración por Tipo de Formulario
Sección titulada «Calibración por Tipo de Formulario»Los umbrales y acciones no son iguales para todos los formularios. Cada tipo de endpoint tiene un perfil de riesgo distinto:
| Formulario | Acción reCAPTCHA | Umbral Allow | Umbral Step-Up | Umbral Deny | Rate Limit (Cloud Armor) | Rate Limit (App) |
|---|---|---|---|---|---|---|
| Login | login | ≥ 0.7 | 0.3 – 0.6 (→ MFA) | < 0.3 | 100/min/IP | 5 intentos/email/15 min |
| Registro | signup | ≥ 0.7 | 0.4 – 0.6 (→ verificación email) | < 0.4 | 50/min/IP | 3 cuentas/IP/hora |
| Checkout / Pagos | checkout | ≥ 0.8 | 0.5 – 0.7 (→ re-auth) | < 0.5 | 30/min/IP | 10 transacciones/usuario/hora |
| Contacto / Soporte | contact | ≥ 0.5 | 0.2 – 0.4 (→ honeypot) | < 0.2 | 20/min/IP | 5 mensajes/email/hora |
| Recuperación de contraseña | password_reset | ≥ 0.7 | 0.3 – 0.6 (→ delay progresivo) | < 0.3 | 30/min/IP | 3 solicitudes/email/hora |
Defense in Depth Adicional
Sección titulada «Defense in Depth Adicional»reCAPTCHA no es una bala de plata. El sistema debe complementarse con:
- Rate Limiting por Cuenta: Cloud Armor limita por IP, pero un ataque distribuido usará miles de IPs. El backend debe limitar intentos por nombre de usuario (
user@email.com). - Validación de Input Estricta: Las bibliotecas como Zod o Joi deben sanitizar todo el input antes de que llegue a la lógica de negocio.
- Auditoría: Cada login fallido o evaluación rechazada debe dejar un trazo de log estructurado para análisis de SOC.
Lista de Verificación (Checklist) para MRs
Sección titulada «Lista de Verificación (Checklist) para MRs»Antes de aprobar un Merge Request que exponga un formulario público, el Tech Lead debe validar:
- Se implementa reCAPTCHA Enterprise, no v2 ni v3 estándar.
- El backend valida el token (integridad
valid). - El backend valida explícitamente que el
actionenviado coincide con el esperado. - Existe una lógica de evaluación basada en el
score(tri-tier u otra calibrada). - La autenticación hacia la API de reCAPTCHA utiliza
Workload Identity(sin claves JSON). - La ruta está protegida detrás de una política WAF de
Cloud Armorcon rate limiting por IP. - Existe rate limiting a nivel de aplicación por entidad de negocio (ej. por email).