- renombrar openspec/ a _openspec/ (carpeta auxiliar) - mover specs de features a changes/ - crear specs base: arquitectura-limpia, estandares-codigo, nomenclatura - migrar _planificacion/ con design-system y roi-theme-template - agregar especificacion recaptcha anti-spam (proposal, tasks, spec) - corregir rutas y referencias en todas las specs Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
12 KiB
12 KiB
Análisis de Spam en Formularios - ROI Theme
Fecha de análisis: 2026-01-08 Problema: Recepción de spam con datos aleatorios en formularios
Características del Spam Detectado
- Nombres: Cadenas aleatorias (ej:
kxUcwkDPHRAnUbdRWnDx,SOTbwKzTcZhJfTRBYSTV) - WhatsApp: Cadenas aleatorias en lugar de números
- Emails: Emails reales (posiblemente robados de bases de datos)
- Patrón: Bots automatizados que llenan campos sin validación
- Fuente identificada:
newsletter-footer
Módulos de Formularios Identificados
Formulario 1: Newsletter Footer
- Handler:
Public/Footer/Infrastructure/Api/WordPress/NewsletterAjaxHandler.php - Renderer:
Public/Footer/Infrastructure/Ui/FooterRenderer.php - Acción AJAX:
roi_newsletter_subscribe - Source en payload:
newsletter-footer
Formulario 2: Contact Form (Sección + Modal)
- Handler:
Public/ContactForm/Infrastructure/Api/WordPress/ContactFormAjaxHandler.php - Renderer:
Public/ContactForm/Infrastructure/Ui/ContactFormRenderer.php - Acción AJAX:
roi_contact_form_submit - Source en payload:
contact-form
Hallazgos Durante la Revisión
Archivos Identificados
Schemas:
Schemas/contact-form.json
JavaScript (Frontend):
Assets/Js/footer-contact.jsAssets/Js/modal-contact.js
PHP Backend - Newsletter (FUENTE DEL SPAM):
Public/Footer/Infrastructure/Api/WordPress/NewsletterAjaxHandler.phpPublic/Footer/Infrastructure/Ui/FooterRenderer.php
Análisis del Formulario Newsletter (PRINCIPAL AFECTADO)
Ubicación
- Handler AJAX:
Public/Footer/Infrastructure/Api/WordPress/NewsletterAjaxHandler.php - Renderer HTML/JS:
Public/Footer/Infrastructure/Ui/FooterRenderer.php - Fuente en payload:
newsletter-footer✓ (coincide con spam reportado)
Medidas de Seguridad EXISTENTES ✅
| Medida | Estado | Archivo | Línea |
|---|---|---|---|
| Nonce WordPress | ✅ Implementado | NewsletterAjaxHandler.php | 46 |
| Rate Limiting (60s por IP) | ✅ Implementado | NewsletterAjaxHandler.php | 54-59 |
| Sanitización de inputs | ✅ Implementado | NewsletterAjaxHandler.php | 62-64 |
| Validación de email | ✅ Implementado | NewsletterAjaxHandler.php | 66 |
| Webhook URL oculta | ✅ Implementado | Nunca expuesta al cliente |
VULNERABILIDADES CRÍTICAS ❌
| Vulnerabilidad | Impacto | Prioridad |
|---|---|---|
| NO hay honeypot | Bots pasan sin detección | 🔴 ALTA |
| NO hay CAPTCHA | Sin verificación humana | 🔴 ALTA |
| NO hay validación de nombre | Acepta kxUcwkDPHRAnUbdRWnDx |
🔴 ALTA |
| NO hay validación de WhatsApp | Acepta OGkrLENXqiQAaIYvCV |
🔴 ALTA |
| Rate limiting débil | 60s es poco, bots rotan IPs | 🟡 MEDIA |
| NO hay tiempo mínimo de envío | Bots envían instantáneamente | 🟡 MEDIA |
Campos del Formulario (FooterRenderer.php:336-343)
<form id="roi-newsletter-form">
<input type="hidden" name="nonce" value="...">
<input type="text" name="name" placeholder="Nombre"> <!-- SIN VALIDACIÓN -->
<input type="email" name="email" placeholder="Email" required> <!-- SOLO HTML5 -->
<input type="tel" name="whatsapp" placeholder="WhatsApp"> <!-- SIN VALIDACIÓN -->
<button type="submit">Suscribirse</button>
</form>
Patrón del Spam Detectado
Los datos de spam muestran:
- Nombres: Cadenas alfanuméricas aleatorias (20+ caracteres)
- WhatsApp: Cadenas alfanuméricas aleatorias (NO son números)
- Emails: Emails reales (posiblemente de bases de datos filtradas)
Esto indica bots automatizados que:
- Bypassean la validación HTML5 (trivial)
- Ignoran el rate limiting rotando IPs
- Generan valores aleatorios para campos de texto
- Usan emails reales para parecer legítimos
Análisis del Formulario Contact Form (SEGUNDO FORMULARIO)
Ubicación
- Handler AJAX:
Public/ContactForm/Infrastructure/Api/WordPress/ContactFormAjaxHandler.php - Renderer:
Public/ContactForm/Infrastructure/Ui/ContactFormRenderer.php - Schema:
Schemas/contact-form.json - Fuente en payload:
contact-form
Estado: ✅ ACTIVO (Clean Architecture)
Este formulario está implementado correctamente con Clean Architecture:
- Renderer genera HTML/CSS/JS dinámicamente
- Handler AJAX procesa envíos server-side
- Webhook URL guardada en BD (nunca expuesta al cliente)
Medidas de Seguridad EXISTENTES ✅
| Medida | Estado | Archivo | Línea |
|---|---|---|---|
| Nonce WordPress | ✅ Implementado | ContactFormAjaxHandler.php | 47-48 |
| Rate Limiting (30s por IP) | ✅ Implementado | ContactFormAjaxHandler.php | 55-61 |
| Sanitización de inputs | ✅ Implementado | ContactFormAjaxHandler.php | 122-130 |
| Validación de email | ✅ Implementado | ContactFormAjaxHandler.php | 151-155 |
| Webhook URL oculta | ✅ Implementado | ContactFormAjaxHandler.php | 86 |
VULNERABILIDADES CRÍTICAS ❌
| Vulnerabilidad | Impacto | Prioridad |
|---|---|---|
| NO hay honeypot | Bots pasan sin detección | 🔴 ALTA |
| NO hay CAPTCHA | Sin verificación humana | 🔴 ALTA |
| NO hay validación de nombre | Acepta kxUcwkDPHRAnUbdRWnDx |
🔴 ALTA |
| NO hay validación de WhatsApp | Acepta OGkrLENXqiQAaIYvCV |
🔴 ALTA |
| Rate limiting muy débil | Solo 30s, bots rotan IPs | 🟡 MEDIA |
| NO hay tiempo mínimo de envío | Bots envían instantáneamente | 🟡 MEDIA |
Campos del Formulario (ContactFormRenderer.php:278-320)
<form id="roiContactForm" data-nonce="...">
<input type="text" name="fullName" placeholder="Nombre completo *" required> <!-- SIN VALIDACIÓN FORMATO -->
<input type="text" name="company" placeholder="Empresa">
<input type="tel" name="whatsapp" placeholder="WhatsApp *" required> <!-- SIN VALIDACIÓN FORMATO -->
<input type="email" name="email" placeholder="Correo electrónico *" required> <!-- SOLO required + is_email -->
<textarea name="message"></textarea>
</form>
Nota sobre archivo Legacy (footer-contact.js)
El archivo Assets/Js/footer-contact.js es código legacy NO utilizado:
- NO está encolado en
Inc/enqueue-scripts.php - Tiene webhook URL hardcodeada (mala práctica)
- Fue reemplazado por el sistema Clean Architecture actual
Resumen Comparativo de Ambos Formularios
| Característica | Newsletter Footer | Contact Form |
|---|---|---|
| Estado | ✅ ACTIVO | ✅ ACTIVO |
| Backend | PHP AJAX | PHP AJAX |
| Nonce | ✅ Sí | ✅ Sí |
| Rate Limit | ✅ 60s | ✅ 30s |
| Sanitización | ✅ Sí | ✅ Sí |
| Validación email | ✅ is_email() | ✅ is_email() |
| Validación nombre | ❌ Ninguna | ❌ Solo required |
| Validación WhatsApp | ❌ Ninguna | ❌ Solo required |
| Honeypot | ❌ NO | ❌ NO |
| CAPTCHA | ❌ NO | ❌ NO |
| Tiempo mínimo | ❌ NO | ❌ NO |
Conclusión: AMBOS formularios comparten las MISMAS vulnerabilidades críticas
Soluciones Anti-Spam Propuestas
PRIORIDAD 1: Newsletter Footer (Urgente)
1.1 Honeypot Field (Implementación rápida, efectiva)
<!-- Campo oculto que humanos no llenan pero bots sí -->
<input type="text" name="website_url" style="display:none !important" tabindex="-1" autocomplete="off">
Backend (NewsletterAjaxHandler.php):
// Verificar honeypot
$honeypot = sanitize_text_field($_POST['website_url'] ?? '');
if (!empty($honeypot)) {
// Es bot - responder éxito falso para no alertar
wp_send_json_success(['message' => $successMsg]);
return;
}
1.2 Validación de Contenido (Anti-gibberish)
// Validar nombre: solo letras, espacios, acentos
$name = sanitize_text_field($_POST['name'] ?? '');
if (!empty($name) && !preg_match('/^[\p{L}\s\'-]{2,50}$/u', $name)) {
wp_send_json_error(['message' => 'Nombre inválido'], 422);
return;
}
// Validar WhatsApp: solo números, +, -, espacios
$whatsapp = sanitize_text_field($_POST['whatsapp'] ?? '');
if (!empty($whatsapp) && !preg_match('/^[\d\s\+\-\(\)]{10,20}$/', $whatsapp)) {
wp_send_json_error(['message' => 'Número de WhatsApp inválido'], 422);
return;
}
1.3 Tiempo Mínimo de Envío
// En el formulario HTML agregar:
<input type="hidden" name="form_timestamp" value="<?php echo time(); ?>">
// En el handler:
$timestamp = (int) ($_POST['form_timestamp'] ?? 0);
$minTime = 3; // segundos mínimos
if (time() - $timestamp < $minTime) {
// Envío demasiado rápido = bot
wp_send_json_success(['message' => $successMsg]); // Falso éxito
return;
}
1.4 Rate Limiting Mejorado
// Aumentar de 60s a 300s (5 minutos)
// Y agregar límite diario por IP
private function checkRateLimit(): bool
{
$ip = $this->getClientIP();
// Límite corto (5 minutos)
$shortKey = 'roi_newsletter_short_' . md5($ip);
if (get_transient($shortKey) !== false) {
return false;
}
set_transient($shortKey, time(), 300);
// Límite diario (máximo 5 por día)
$dailyKey = 'roi_newsletter_daily_' . md5($ip);
$dailyCount = (int) get_transient($dailyKey);
if ($dailyCount >= 5) {
return false;
}
set_transient($dailyKey, $dailyCount + 1, DAY_IN_SECONDS);
return true;
}
PRIORIDAD 2: Opciones Adicionales
2.1 Google reCAPTCHA v3 (Invisible)
- Sin fricción para usuarios
- Score basado en comportamiento
- Requiere cuenta Google reCAPTCHA
2.2 Cloudflare Turnstile (Recomendado)
- Gratuito
- Sin tracking de Google
- Más privado que reCAPTCHA
2.3 hCaptcha
- Alternativa a reCAPTCHA
- Mejor privacidad
- Versión gratuita disponible
Plan de Implementación
Fase 1: Crear Servicio Anti-Spam Compartido
- Crear
Shared/Infrastructure/Services/AntiSpamValidator.php - Implementar validaciones reutilizables:
validateHoneypot(string $value): boolvalidateMinimumTime(int $timestamp, int $minSeconds = 3): boolvalidateNameFormat(string $name): bool(regex letras/espacios)validateWhatsAppFormat(string $phone): bool(regex números)checkEnhancedRateLimit(string $ip, int $shortLimit, int $dailyLimit): bool
Fase 2: Aplicar a Newsletter Footer
- Agregar honeypot + timestamp en
FooterRenderer.php - Integrar AntiSpamValidator en
NewsletterAjaxHandler.php - Probar que spam es rechazado
Fase 3: Aplicar a Contact Form
- Agregar honeypot + timestamp en
ContactFormRenderer.php - Integrar AntiSpamValidator en
ContactFormAjaxHandler.php - Probar que spam es rechazado
Fase 4: Monitoreo y Mejoras (Opcional)
- Agregar logging de intentos sospechosos
- Evaluar CAPTCHA invisible si spam persiste
- Dashboard de estadísticas anti-spam
Archivos a Modificar
Newsletter Footer
| Archivo | Cambio |
|---|---|
Public/Footer/Infrastructure/Ui/FooterRenderer.php |
Agregar honeypot + timestamp oculto |
Public/Footer/Infrastructure/Api/WordPress/NewsletterAjaxHandler.php |
Validaciones formato + honeypot check + rate limiting mejorado |
Contact Form
| Archivo | Cambio |
|---|---|
Public/ContactForm/Infrastructure/Ui/ContactFormRenderer.php |
Agregar honeypot + timestamp oculto |
Public/ContactForm/Infrastructure/Api/WordPress/ContactFormAjaxHandler.php |
Validaciones formato + honeypot check + rate limiting mejorado |
Código Reutilizable (Recomendado)
Crear un trait o clase helper compartida para evitar duplicación:
Shared/Infrastructure/Services/AntiSpamValidator.php
- Validación de honeypot
- Validación de tiempo mínimo
- Validación de formato nombre (regex)
- Validación de formato WhatsApp (regex)
- Rate limiting mejorado
Referencias Serena
Las memorias existentes en .serena/Memories/ documentan:
diagnostico-estructura-carpetas-admin.md- Estructura de carpetas Adminmigracion-theme-options-tabla-personalizada.md- Sistema de settingsROI_THEME_CSS_LOADING_ANALYSIS.md- Análisis de carga CSS
Esta memoria debe guardarse como referencia para implementación futura.