Confidencial · TLP:AMBER
Informe de
Pentest Externo
y Aplicación Web
NutriFlow SaaS — Evaluación de seguridad perímetro + capa web
NutriFlow S.L.
20–27 May 2026
1.2 (post-retest)
Marta Reyes
Carlos Vidal
CS-2026-041
Veredicto de riesgo global
ALTO — Exposición crítica confirmada
1
Resumen Ejecutivo

Entre el 20 y el 27 de mayo de 2026, CULTIVA Seguridad realizó una prueba de penetración externa de la plataforma web NutriFlow (app.nutriflow.io y api.nutriflow.io), que gestiona historiales clínicos y datos nutricionales de más de 340 clínicas clientes.

El hallazgo más grave es una inyección SQL no autenticada en el motor de búsqueda de la API que permite extraer la tabla completa de usuarios —incluyendo contraseñas con hash, emails y datos de suscripción— con una sola petición HTTP. Combinada con un token JWT firmado con una clave que estuvo pública en npm, un atacante podría tomar el control de cualquier cuenta sin necesidad de credenciales.

La postura de seguridad actual es inaceptable para una plataforma que maneja datos sanitarios sujetos al RGPD. Tres vulnerabilidades críticas/altas permiten un escenario de compromiso completo de la base de datos y acceso transversal entre clientes (cross-tenant). La empresa debe ejecutar las remediaciones prioritarias antes de continuar procesando datos de pacientes.

1
Crítico
2
Alto
2
Medio
1
Bajo
0
Info

Recomendaciones estratégicas

1

Implementar SAST en CI/CD para Node.js — las vulnerabilidades de inyección SQL y XSS son detectables automáticamente antes de llegar a producción.

Cubre hallazgos #1, #2, #4
2

Adoptar gestión centralizada de secretos (HashiCorp Vault o AWS Secrets Manager) y rotar inmediatamente todas las claves expuestas en repositorios públicos.

Cubre hallazgo #3
3

Revisar el modelo de autorización multi-tenant: introducir validación de tenant_id en cada capa (API gateway, ORM, base de datos) con tests de regresión de aislamiento entre clientes.

Cubre hallazgo #2 (acceso cross-tenant)

2
Descripción del Encargo
CampoDetalle
ClienteNutriFlow S.L. — Plataforma SaaS de gestión nutricional para clínicas
Tipo de pruebaPentest externo + aplicación web (caja gris)
Alcanceapp.nutriflow.io, api.nutriflow.io
Fuera de alcanceInfraestructura de terceros (Stripe, SendGrid), pruebas DoS, ingeniería social
Ventana de pruebas20–27 mayo 2026, horario 22:00–06:00 UTC
MetodologíaPTES + OWASP Testing Guide v4.2 + CVSS v3.1
Credenciales proporcionadas2 cuentas de usuario estándar en entorno staging (configurado como producción)
EquipoMarta Reyes (lead), Carlos Vidal (web)

Limitaciones


3
Resumen de Riesgo
Crítico
1
SQLi no autenticada → extracción BD (Finding #2)
Alto
2
JWT hardcoded (#3) · IDOR cross-tenant (#1)
Medio
2
Stored XSS (#4) · CORS wildcard (#5)
Bajo
1
TLS 1.0 activo (#6)

4
Hallazgos Técnicos
Finding #2 · CS-2026-041-002
Inyección SQL en motor de búsqueda — Extracción completa de base de datos
CWE-89 OWASP A03:2021
Crítico
9.8
api.nutriflow.io
No
✓ Corregido
Resumen

El parámetro q del endpoint /api/v2/search se concatena directamente en la consulta SQL sin sanitización. Un atacante externo no autenticado puede extraer la totalidad de la base de datos, incluyendo ~34.000 registros de pacientes con datos de salud (RGPD categoría especial) y ~1.200 cuentas de profesionales con hashes de contraseña.

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H = 9.8 (Crítico)
AV:N → expuesto en internet (443/tcp) · AC:L · PR:N → sin autenticación · UI:N · C:H I:H A:H
Pasos de reproducción
# Paso 1: Detección — número de columnas GET /api/v2/search?q=' ORDER BY 5-- HTTP/1.1 Host: api.nutriflow.io # Paso 2: Extracción de tabla users (sin credenciales) GET /api/v2/search?q=' UNION SELECT email,password_hash,role,tenant_id,created_at FROM users-- Host: api.nutriflow.io # Respuesta parcial (redactada) HTTP/2 200 OK {"results":[ {"name":"admin@clinica-xyz.com","surname":"$2b$12$HASH_REDACTADO","role":"admin","tenant":14,"created":"2024-11-03"} ]}
Impacto
  • Lectura total de la tabla users (~1.200 registros de profesionales de salud)
  • Lectura total de la tabla patients (~34.000 registros con datos de salud RGPD Art.9)
  • Acceso cross-tenant confirmado: datos de 340 clínicas distintas accesibles desde una sola petición
  • Potencial obtención de privilegios de administrador mediante cracking offline de hashes bcrypt débiles
Remediación
  1. Corrección inmediata: Reemplazar toda concatenación SQL por prepared statements o un ORM con parámetros vinculados (p.ej. Prisma, TypeORM). Verificado corregido en retest.
  2. Defensa en profundidad: Añadir validación de tipo en la capa de API gateway; rechazar caracteres ', --, UNION en campos de búsqueda.
  3. Detección: Configurar alerta en SIEM para patrones UNION SELECT en logs de WAF/API.
Finding #3 · CS-2026-041-003
Clave secreta JWT expuesta en repositorio npm público
CWE-321 OWASP A02:2021
Alto
8.1
Todos los tokens
No
✓ Corregido
Descripción

Se encontró la clave de firma de tokens JWT (JWT_SECRET=nutrifl0w_s3cr3t_2024) hardcodeada en el archivo config/defaults.js del paquete npm nutriflow-shared-utils@0.8.2, publicado en el registro público de npm. Con esta clave, cualquier persona puede forjar tokens JWT válidos para cualquier usuario de la plataforma.

# Forja de token de administrador (proof of concept) python3 -c " import jwt payload = {'sub': 1, 'role': 'superadmin', 'tenant': '*'} token = jwt.encode(payload, 'nutrifl0w_s3cr3t_2024', algorithm='HS256') print(token) " eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... # Token forjado aceptado como superadmin: GET /api/v2/admin/users HTTP/1.1 Authorization: Bearer eyJhbGc... → HTTP/2 200 OK [ACCESO CONCEDIDO — 1.200 usuarios]
Impacto
  • Suplantación de identidad de cualquier usuario, incluidos administradores de clínica
  • Acceso completo a todos los endpoints de la API sin credenciales reales
  • Dado que la clave estuvo pública desde noviembre 2024, no es descartable que terceros la hayan extraído
Remediación
  1. Inmediata: Rotar JWT_SECRET a una clave aleatoria de 256 bits. Invalidar todos los tokens activos. Verificado en retest.
  2. Proceso: Mover todos los secretos a variables de entorno o gestor de secretos (AWS Secrets Manager). Nunca incluir secretos en código fuente ni en paquetes publicados.
  3. Detección: Integrar git-secrets o Trufflehog en el pipeline de CI para detectar futuros leaks antes del push.
Finding #1 · CS-2026-041-001
IDOR — Acceso a historiales de pacientes de otras clínicas
CWE-639 OWASP A01:2021
Alto
7.5
/api/v2/patients/{id}
Sí (usuario)
✓ Corregido
Descripción

El endpoint de consulta de pacientes acepta IDs numéricos secuenciales sin verificar que el paciente pertenezca al tenant del usuario autenticado. Un profesional de la Clínica A puede leer, modificar y eliminar historiales de cualquier paciente registrado en la plataforma —incluidos pacientes de competidores o clínicas no relacionadas.

# Usuario autenticado como nutricionista de Clínica A (tenant_id=14) GET /api/v2/patients/99127 HTTP/1.1 ← paciente de Clínica B (tenant_id=7) Authorization: Bearer <token_clinica_A> HTTP/2 200 OK {"patient_id":99127,"name":"[REDACTADO]","tenant_id":7, "historial":[...datos clínicos sensibles...]}
Remediación
  1. Corrección: Añadir WHERE patient_id=? AND tenant_id=? en todas las consultas de pacientes, extrayendo tenant_id del JWT verificado, no del request. Verificado en retest.
  2. Tests: Añadir suite de tests de aislamiento multi-tenant en cada release.
Finding #4 · CS-2026-041-004
Stored XSS en notas clínicas — Ejecución en navegador del médico
CWE-79 OWASP A03:2021
Medio
5.4
/pacientes/{id}/notas
Sí (paciente)
⚠ Parcial
Descripción

Las notas clínicas se renderizan en el panel del médico con innerHTML sin escapar. El input se sanitiza ahora en el servidor (corrección parcial), pero no existe Content Security Policy. Un paciente podría inyectar JavaScript que exfiltre el token de sesión del profesional o realice acciones en su nombre.

⚠ Corrección parcial: el input está sanitizado pero la ausencia de CSP permite vectores alternativos. Pendiente de retest completo tras implementación de CSP.
Remediación pendiente
  1. CSP: Implementar Content-Security-Policy: default-src 'self'; script-src 'self' eliminando fuentes inline.
  2. Render: Usar textContent en lugar de innerHTML para notas de usuario.
Finding #5 · CS-2026-041-005
CORS permisivo — Cualquier origen puede realizar peticiones autenticadas
CWE-346 OWASP A05:2021
Medio
4.8
api.nutriflow.io (todos)
Sí (víctima)
✗ Abierto
Descripción

La cabecera Access-Control-Allow-Origin: * combinada con Access-Control-Allow-Credentials: true permite que cualquier página web maliciosa realice peticiones cross-origin autenticadas a la API usando la sesión activa del médico/administrador.

Remediación
  1. Corrección: Definir whitelist de orígenes permitidos (app.nutriflow.io) y eliminar el wildcard cuando credentials: true.

5
Cadena de Ataque — Compromiso Total sin Credenciales
Escenario: Atacante externo → Dump completo de BBDD sanitaria 4 pasos · ~35 min
1
Reconocimiento pasivo — Clave JWT en npm

Búsqueda en npm registry del paquete nutriflow-shared-utils. Extracción de JWT_SECRET del bundle publicado.

Finding #3 Alto · CVSS 8.1
2
Forja de token JWT con rol superadmin

Con la clave extraída, se firma un JWT con role: superadmin. Acceso inmediato a todos los endpoints de administración.

Finding #3 Tiempo estimado: 2 min
3
Inyección SQL — Enumeración de base de datos

Confirmada independientemente del token: el endpoint /api/v2/search es vulnerable sin autenticación. UNION-based injection extrae todas las tablas.

Finding #2 Crítico · CVSS 9.8
4
Extracción completa: 34.000 historiales de pacientes

Dump de tablas patients, users, appointments y clinical_notes. Datos de salud de categoría especial RGPD Art.9 expuestos. Sanción potencial: hasta el 4% del volumen de negocio anual.

Impacto máximo Notificación AEPD obligatoria <72h

6
Resultados del Retest (11 Jun 2026)
Finding Severidad original Estado Verificación Fecha
#1 — IDOR pacientes Alto ✓ Corregido Validación tenant_id confirmada en respuesta 403 2026-06-11
#2 — SQLi búsqueda Crítico ✓ Corregido UNION payload retorna error tipado; prepared statements verificados 2026-06-11
#3 — JWT hardcoded Alto ✓ Corregido Token forjado con clave antigua retorna 401; nueva clave no extraíble 2026-06-11
#4 — Stored XSS Medio ⚠ Parcial Input sanitizado pero CSP no implementado. Payload <img onerror> sigue ejecutando en Safari 2026-06-11
#5 — CORS permisivo Medio ✗ No corregido ACAO: * con credenciales sigue presente en todas las rutas 2026-06-11
#6 — TLS 1.0 Bajo ✓ Corregido ssl-enum-ciphers confirma TLS 1.2 mínimo; TLS 1.0/1.1 deshabilitados 2026-06-11

Próximo retest parcial: pendiente para findings #4 (XSS/CSP) y #5 (CORS) en cuanto el equipo de desarrollo confirme el despliegue. Contacto: seguridad@nutriflow.io


A
Apéndices

A. Herramientas utilizadas

HerramientaUso
Burp Suite Pro 2025.6Proxy, escáner activo, fuzzing de parámetros
sqlmap 1.8Verificación automatizada de inyección SQL
jwt_toolAnálisis y forja de tokens JWT
nmap 7.95 + ssl-enum-ciphersEnumeración de servicios y evaluación TLS
nuclei 3.2Detección de vulnerabilidades conocidas

B. Referencias normativas