Resumen Ejecutivo
Durante el análisis de la plataforma GrowthStack SaaS (staging.growthstack.io) se identificaron 5 vulnerabilidades XSS distribuidas en 6 puntos de entrada auditados. El hallazgo más crítico es un XSS Almacenado en el campo "Nombre de campaña" que afecta a todos los usuarios con acceso al dashboard, incluyendo los dashboards compartidos con clientes finales.
El WAF de Cloudflare bloquea payloads básicos (<script>), pero es bypasseable mediante variantes con SVG y atributos de eventos. La ausencia total de Content Security Policy amplifica el impacto de todas las vulnerabilidades encontradas.
Se recomienda acción inmediata en los hallazgos XSS-001 y XSS-002 antes de volver a producción. El resto puede abordarse en el siguiente sprint de seguridad.
Distribución de Riesgo
Alcance de la Auditoría
§1| Endpoint | Tipo de Input | Método | XSS Tipo | Estado |
|---|---|---|---|---|
/search?q= |
Query parameter | GET | Reflejado | ⚠ Vulnerable (WAF bypass) |
POST /api/campaigns → campo name |
JSON body, almacenado en BD | POST | Almacenado | ✗ CRÍTICO — sin sanitizar |
/reports/:id/comments |
HTML enriquecido (textarea) | POST | Almacenado | ⚠ Vulnerable — bleach mal configurado |
/embed?title= |
Query parameter, renderizado cliente | GET | DOM-based | ⚠ Vulnerable — innerHTML sin escape |
/settings/profile → campo website |
URL almacenada, renderizada como enlace | PUT | Almacenado | ⚠ Medio — javascript: URL permitida |
?next= (post-login redirect) |
Query parameter | GET | Reflejado | ✓ Solo Open Redirect (bajo) |
Hallazgos Detallados
§2Descripción
El campo name de la entidad Campaign se almacena directamente en PostgreSQL sin sanitización y se renderiza en el dashboard mediante React con dangerouslySetInnerHTML. El payload se ejecuta para cualquier usuario que abra el dashboard, incluyendo dashboards compartidos con clientes.
El WAF bloquea <script> pero el payload con <svg onload=> no está en las reglas activas de Cloudflare y pasa sin inspección.
Payload de Prueba de Concepto
Impacto Real
- Robo de sesiones JWT de todos los usuarios que abran el dashboard
- Persistencia: el payload se mantiene hasta que se elimine la campaña
- Exfiltración de datos de clientes visibles en el dashboard actual
- Defacement del dashboard de clientes (shared dashboards)
- Posible escalada a Account Takeover sin interacción adicional
Remediación
- Eliminar
dangerouslySetInnerHTMLdel componente CampaignCard — usar{campaign.name}(React escapa por defecto) - Añadir validación server-side: rechazar nombres con caracteres HTML (
<>&"') - Ejecutar sanitización en ingesta:
html.escape(name)en el endpoint FastAPI - Migrar datos existentes en BD: script de limpieza sobre la tabla campaigns
Descripción & Payload
La sección de comentarios usa bleach para sanitizar HTML, pero la configuración permite la etiqueta <a> con el atributo href sin restricción de protocolo. Esto permite inyectar URLs javascript:.
Remediación (1 línea)
Evidencia del Ataque
Comentario malicioso publicado en reporte compartido:
Excelente reporte! He encontrado los datos que necesitabas.
<a href="javascript:...">Ver análisis completo →</a>
Al hacer click → roba cookie → Account Takeover
Impacto
- Cualquier colaborador invitado al reporte puede inyectar el payload
- Los reports compartidos con clientes multiplican la superficie de ataque
- Phishing de alta credibilidad dentro de la plataforma
Código Vulnerable
Fix
Por qué es difícil de detectar
Los DOM XSS son invisibles para el servidor. El payload nunca aparece en logs de servidor ni en capturas de WAF. Las herramientas de análisis estático server-side no lo detectan.
Se requiere análisis de código JavaScript cliente o herramientas especializadas como DalFox con soporte de headless browser.
Herramienta de Detección
Vector
Fix Rápido
Contexto
La página de búsqueda usa Jinja2 legacy. El WAF bloquea payloads directos pero el double-encoding pasa.
Fix
Content Security Policy — Configuración Recomendada
§3✗ Estado Actual (sin CSP)
✓ CSP Recomendada (con nonce)
Implementación CSP en FastAPI — Middleware de Nonce
Guía de Remediación Completa
§4Codificación de Salida Contextual — output_encoding.py
Fix React — Eliminar dangerouslySetInnerHTML
Plan de Remediación Priorizado
§5Roadmap de Acciones
- Eliminar
dangerouslySetInnerHTMLde CampaignCard (XSS-001) - Añadir
protocolsa bleach en comentarios (XSS-002) - Bloquear endpoint
/api/campaignstemporalmente si no se puede parchear hoy - Limpiar datos maliciosos existentes en BD
- Fix DOM XSS en EmbedWidget (textContent vs innerHTML) (XSS-003)
- Validar protocolo URL en perfil (XSS-004)
- Eliminar filtro
| safede Jinja2 + activar autoescape (XSS-005) - Desplegar middleware CSP con nonce en staging
- Activar CSP en producción (modo report-only primero)
- Pipeline CI/CD con dalfox en cada PR
- Análisis estático de sinks DOM en código JS heredado
- Formación del equipo en codificación de salida contextual
| ID | Severidad | Esfuerzo Fix | Componente | Responsable Sugerido | Plazo |
|---|---|---|---|---|---|
XSS-001 |
Crítico | 30 min | CampaignCard.jsx | Frontend Lead | HOY |
XSS-002 |
Alto | 15 min | backend/utils/sanitize.py | Backend Dev | HOY |
XSS-003 |
Alto | 1-2 h | EmbedWidget.jsx (legacy) | Frontend + QA | 3 días |
XSS-004 |
Medio | 30 min | PUT /settings/profile | Backend Dev | 1 semana |
XSS-005 |
Bajo | 15 min | search.html (Jinja2) | Backend Dev | 1 semana |