refactor: reorganizar openspec y planificacion con spec recaptcha
- 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>
This commit is contained in:
341
_planificacion/analisis-spam-formularios.md
Normal file
341
_planificacion/analisis-spam-formularios.md
Normal file
@@ -0,0 +1,341 @@
|
||||
# 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.js`
|
||||
- `Assets/Js/modal-contact.js`
|
||||
|
||||
**PHP Backend - Newsletter (FUENTE DEL SPAM):**
|
||||
- `Public/Footer/Infrastructure/Api/WordPress/NewsletterAjaxHandler.php`
|
||||
- `Public/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)
|
||||
|
||||
```html
|
||||
<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:
|
||||
1. Bypassean la validación HTML5 (trivial)
|
||||
2. Ignoran el rate limiting rotando IPs
|
||||
3. Generan valores aleatorios para campos de texto
|
||||
4. 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)
|
||||
|
||||
```html
|
||||
<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)
|
||||
```html
|
||||
<!-- 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`):
|
||||
```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)
|
||||
```php
|
||||
// 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
|
||||
```php
|
||||
// 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
|
||||
```php
|
||||
// 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
|
||||
1. Crear `Shared/Infrastructure/Services/AntiSpamValidator.php`
|
||||
2. Implementar validaciones reutilizables:
|
||||
- `validateHoneypot(string $value): bool`
|
||||
- `validateMinimumTime(int $timestamp, int $minSeconds = 3): bool`
|
||||
- `validateNameFormat(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
|
||||
1. Agregar honeypot + timestamp en `FooterRenderer.php`
|
||||
2. Integrar AntiSpamValidator en `NewsletterAjaxHandler.php`
|
||||
3. Probar que spam es rechazado
|
||||
|
||||
### Fase 3: Aplicar a Contact Form
|
||||
1. Agregar honeypot + timestamp en `ContactFormRenderer.php`
|
||||
2. Integrar AntiSpamValidator en `ContactFormAjaxHandler.php`
|
||||
3. Probar que spam es rechazado
|
||||
|
||||
### Fase 4: Monitoreo y Mejoras (Opcional)
|
||||
1. Agregar logging de intentos sospechosos
|
||||
2. Evaluar CAPTCHA invisible si spam persiste
|
||||
3. 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 Admin
|
||||
- `migracion-theme-options-tabla-personalizada.md` - Sistema de settings
|
||||
- `ROI_THEME_CSS_LOADING_ANALYSIS.md` - Análisis de carga CSS
|
||||
|
||||
Esta memoria debe guardarse como referencia para implementación futura.
|
||||
|
||||
Reference in New Issue
Block a user