Informe Confidencial
Auditoría GraphQL — FlowDesk
Análisis de seguridad del endpoint GraphQL · API v2 · Pentest semana 23/2026
GraphQL Apollo Server 3.x Hasura PostgreSQL JWT HS256 WebSocket Subscriptions
CULTIVA IA — Seguridad
Cliente: FlowDesk SaaS · Enterprise
Auditor: Equipo Red Team CULTIVA
Período: 9–13 Jun 2026
Referencia: CULT-SEC-2026-047
Versión: 1.0 · Final
3.2
Security Score

Nivel de Riesgo: CRÍTICO

Se identificaron 14 vulnerabilidades en el endpoint GraphQL de FlowDesk, incluyendo 3 críticas que permiten escalada de privilegios y acceso no autorizado a datos de clientes. Se requiere acción inmediata antes de continuar el proceso de certificación SOC 2.

3
Crítico
4
Alto
4
Medio
2
Bajo
1
Info
🗺
Fases de Auditoría Completadas
🔍
Reconocimiento
Completado
🗂
Análisis Esquema
Completado
🔐
Autorización
3 Hallazgos
💉
Inyección
2 Hallazgos
💥
DoS / Complejidad
4 Hallazgos
🔗
Lógica Negocio
Completado
📡
Subscripciones
2 Hallazgos
🔴
Hallazgos Críticos y Altos
CRÍTICO GQFL-001 Introspección habilitada en producción — esquema completo expuesto
Reconocimiento CVSS 7.5
El endpoint https://api.flowdesk.io/graphql responde a queries de introspección completa, exponiendo 247 tipos, 89 mutations (incluyendo mutaciones de gestión de roles y facturación) y argumentos con nombres reveladores como adminOverride, internalUserId.
# Query de introspección enviada — respuesta en <300ms query { __schema { types { name fields { name type { name kind } } } } } # Extracto de la respuesta — tipos sensibles encontrados: # InternalUserAdmin, BillingOverrideInput, SuperAdminConfig, # HiddenFeatureFlag, LegacyMigrationResolver...
Impacto Directo

El atacante obtiene el mapa completo de la API: todos los tipos, mutations de admin, relaciones entre entidades y estructura de IDs internos.

Superficie Expuesta

247 tipos GraphQL, 89 mutations, 34 queries con argumentos tipo adminOverride: Boolean documentados en el esquema.

✓ Remediación
  • Deshabilitar introspección en NODE_ENV=production en Apollo Server 3.x (introspection: process.env.NODE_ENV !== 'production')
  • Implementar reglas de validación en Graphql Armor para bloquear queries con profundidad excesiva de introspección
  • Mantener documentación interna de la API con GraphQL Voyager en entorno privado (VPN)
CRÍTICO GQFL-002 IDOR + Escalada de privilegios en mutation updateUserRole
Autorización CVSS 9.1
La mutation updateUserRole acepta un userId arbitrario sin verificar que pertenece a la organización del usuario autenticado. Un usuario con rol member puede ascender a otro usuario a admin o incluso superadmin si conoce (o enumera) su ID.
# Ejecutado con token JWT de cuenta "member" (plan Starter) mutation EscaladaPrivilegios { updateUserRole( userId: "VXNlcjoxMjM0" # base64("User:1234") — ID de otro tenant role: SUPERADMIN organizationId: "T3JnOjU2Nzg=" ) { user { id role email } success } } # Respuesta: { "data": { "updateUserRole": { "user": { "role": "SUPERADMIN" }, "success": true } } } # ⚠ Sin error. Sin validación de pertenencia a organización.
Impacto

Control total de cualquier cuenta FlowDesk. Acceso a datos de todos los tenants, gestión de facturación, exportación de CRM completo.

Explotabilidad

Trivial. IDs son Relay Global IDs en base64 (enumerables). No requiere herramientas especiales, solo una cuenta member válida.

✓ Remediación
  • En el resolver: verificar que userId pertenece a la organización del usuario JWT antes de ejecutar la mutation
  • Implementar directiva @auth con verificación de ownership: @authorized(requires: ADMIN, scope: ORGANIZATION)
  • Añadir test de integración que verifique cross-tenant isolation en todas las mutations de gestión de usuarios
CRÍTICO GQFL-003 Hasura — Header injection x-hasura-role permite bypass de permisos
Hasura / Federación CVSS 9.4
El endpoint de reportes Hasura (https://reports.flowdesk.io/v1/graphql) acepta y confía en el header x-hasura-role enviado directamente por el cliente, sin validación contra el JWT. Esto permite inyectar el rol admin o superadmin en cualquier sesión autenticada.
# Request HTTP — usuario autenticado como "viewer" POST https://reports.flowdesk.io/v1/graphql Authorization: Bearer eyJhbGciOiJIUzI1NiJ9... # Token viewer legítimo x-hasura-role: admin # Header inyectado manualmente x-hasura-org-id: 5678 # Cualquier organización Content-Type: application/json query { billing_records(limit: 1000) { id amount customer_email card_last4 plan } } # Respuesta: 1.000 registros de facturación de todos los tenants ✓
✓ Remediación
  • Configurar Hasura para leer el rol exclusivamente desde el JWT claim (x-hasura-allowed-roles en el token), nunca de headers externos
  • Habilitar HASURA_GRAPHQL_UNAUTHORIZED_ROLE y eliminar permisos de bypass en JWT webhook
  • Añadir WAF rule para bloquear requests con headers x-hasura-* provenientes de clientes externos
Alto GQFL-004 DoS por query batching sin límite — servidor inutilizable en 8s
DoS / Batching CVSS 7.5
El servidor Apollo acepta arrays de operaciones en una sola request HTTP sin límite configurado. Enviando un array de 500 operaciones costosas con profundidad 12, se provocó un tiempo de respuesta de 47s y consumo de CPU al 100% durante 8s, haciendo el servicio inutilizable para el resto de usuarios.
# 500 operaciones en un único POST — sin autenticación especial [ { "query": "{ projects { tasks { comments { author { projects { tasks { id } } } } } } }" }, { "query": "{ projects { tasks { comments { author { projects { tasks { id } } } } } } }" }, // ... x500 ] # Resultado: Response time p99: 47.3s | CPU: 100% (8s) | Servicio degradado
✓ Remediación
  • Limitar batch a máximo 10 operaciones por request (allowBatchedHttpRequests: false o plugin custom)
  • Implementar GraphQL Armor con maxDepth: 7 y maxAliases: 15
  • Rate limiting a nivel de operación por usuario: máx. 100 req/min en Apollo Server
Alto GQFL-005 WebSocket subscriptions — token JWT no revalidado tras expiración
WebSocket / Auth CVSS 8.1
Las subscriptions GraphQL sobre WebSocket (wss://api.flowdesk.io/graphql) autentican al cliente en el mensaje connection_init pero no re-validan el token en mensajes posteriores. Un JWT revocado o expirado sigue recibiendo eventos durante horas.
# 1. Conectar con token válido connection_init: { "authToken": "eyJ...valid" } # 2. Revocar el token en el servidor (logout, ban de cuenta) DELETE /api/auth/sessions/abc123 # 3. La subscripción WS continúa recibiendo datos 3h 47m después subscription { projectUpdated(projectId: "abc") { title tasks { assignee { email } } } } # ← Sigue recibiendo updates en tiempo real tras revocación ✓
✓ Remediación
  • Implementar middleware onMessage que re-valide el JWT en cada mensaje WS o cada N segundos
  • Publicar evento de revocación en Redis y verificar en el servidor WS antes de enviar cada update
  • Definir TTL máximo de conexión WS igual al tiempo de expiración del JWT (típicamente 15min)
🟠
Hallazgos Medios y Bajos
Medio GQFL-006 Field suggestions revelan esquema privado (introspección deshabilitable pero field hints activos)
Info Disclosure CVSS 5.3
Medio GQFL-007 NoSQL injection en resolver searchProjects vía argumento filter.name
Inyección CVSS 6.5
Medio GQFL-008 Directive flooding — 10.000 directivas @include colapsan el parser (CVE-2024-47614 pattern)
DoS / Parser CVSS 6.2
Medio GQFL-009 Upload mutation — sin validación de content-type real (magic bytes); path traversal posible
File Upload CVSS 5.9
Bajo GQFL-010 Relay Global IDs predecibles — enumeración trivial de entidades internas
Reconocimiento CVSS 4.3
Bajo GQFL-011 Ausencia de CSRF protection en endpoint GraphQL con sesiones por cookie
CSRF CVSS 4.1
Cadenas de Ataque de Mayor Impacto
Cadena Crítica Acceso completo a datos de todos los tenants
GQFL-001
Introspección
GQFL-010
Enum. IDs
GQFL-002
IDOR + Rol
GQFL-003
Hasura Admin
Full DB Access
12.000 tenants
Impacto final: Un atacante con una cuenta member legítima puede obtener acceso de superadmin y volcar todos los datos de clientes, incluyendo contratos, CRM completo y registros de facturación de los 12.000 usuarios activos de FlowDesk Enterprise. Tiempo estimado de explotación: ~15 minutos.
Cadena Alta DoS dirigido + extracción de datos durante incidente
GQFL-004
Batch DoS
GQFL-008
Directive Flood
Servicio degradado
SLA breach
Impacto final: Degradación del servicio durante ventanas críticas (fin de mes, cierres de sprint), posible breach del SLA Enterprise. Costo estimado: penalizaciones contractuales por disponibilidad + reputación.
🛡
Hoja de Ruta de Remediación
ID Hallazgo Prioridad Esfuerzo Equipo Deadline Sugerido
GQFL-003 Hasura header injection Inmediato
Bajo (config)
DevOps / Backend Hoy
GQFL-002 IDOR + Escalada de privilegios Inmediato
Medio
Backend 24h
GQFL-001 Deshabilitar introspección en prod Esta semana
Bajo (1 línea)
Backend 16 Jun
GQFL-004 Rate limit en batch queries Esta semana
Medio
Backend 17 Jun
GQFL-005 Re-validación JWT en WebSocket Esta semana
Alto
Backend / Infra 20 Jun
GQFL-006..009 Field hints, NoSQLi, Directives, Upload Este mes
Medio
Backend / Security 30 Jun
GQFL-010..011 Relay IDs + CSRF hardening Trimestre
Medio
Backend 31 Jul
🔧
Herramientas Utilizadas en la Auditoría
Reconocimiento
graphw00f · graphql-cop · Graphinder · clairvoyance
Explotación
inql (Burp) · GraphQLmap · CrackQL · BatchQL
Intercepción
Burp Suite Pro · OWASP ZAP · Insomnia · WebSocket King
Visualización
GraphQL Voyager · Nuclei GraphQL templates