Análisis de Seguridad según CWE-699¶
Resumen Ejecutivo¶
Este documento analiza el código de la aplicación médica según la vista CWE-699: Software Development de MITRE, que categoriza debilidades comunes en el desarrollo de software. Se identifican posibles vulnerabilidades y se proporcionan recomendaciones de mejora.
Referencia: CWE-699
1. Type Errors (Errores de Tipo)¶
1.1 CWE-1287: Improper Validation of Specified Type of Input¶
Ubicación: app/routes.py
Problema identificado:
En las líneas 37, 75, se usa
float()directamente sobre datos de entrada sin validar primero el tipo.En la línea 44, se usa
datetime.strptime()sin validar que el formato sea correcto.
Código afectado:
# Línea 37
height_m = float(data['talla_m'])
# Línea 75
weight_kg = float(data['peso_kg'])
# Línea 44
birth_date = datetime.strptime(data['fecha_nacimiento'], '%Y-%m-%d').date()
Riesgo: Si data['talla_m'] o data['peso_kg'] no son números válidos, float() puede lanzar excepciones no controladas o producir valores inesperados (NaN, Infinity).
Recomendación:
# Validar tipo antes de convertir
if not isinstance(data.get('talla_m'), (int, float, str)):
return jsonify({"error": get_error("invalid_height")}), 400
try:
height_m = float(data['talla_m'])
if not math.isfinite(height_m):
return jsonify({"error": get_error("invalid_height")}), 400
except (ValueError, TypeError):
return jsonify({"error": get_error("invalid_height")}), 400
Severidad: Media
1.2 CWE-843: Access of Resource Using Incompatible Type (‘Type Confusion’)¶
Ubicación: app/static/js/storage.js, app/static/js/main.js
Problema identificado:
En
storage.jslínea 84, se usaparseFloat()sin validar que el resultado sea un número válido.En
main.jslínea 139, se usaparseFloat()sin verificar NaN.
Código afectado:
// storage.js línea 84
peso_kg: parseFloat(weight.peso_kg),
// main.js línea 139
const talla_m = parseFloat(document.getElementById('talla_m').value);
Riesgo: parseFloat() puede retornar NaN, que luego se propaga en cálculos matemáticos causando resultados incorrectos.
Recomendación:
const talla_m = parseFloat(document.getElementById('talla_m').value);
if (isNaN(talla_m) || !isFinite(talla_m)) {
alert(MESSAGES.errors.invalid_height || 'Altura inválida');
return;
}
Severidad: Media
2. User Interface Security Issues (Problemas de Seguridad en la UI)¶
2.1 CWE-356: Product UI does not Warn User of Unsafe Actions¶
Ubicación: app/static/js/dev-tools.js (si existe)
Problema identificado:
La función
clearAllData()en dev-tools muestra unconfirm(), pero no advierte claramente sobre la pérdida permanente de datos.No hay advertencia visual antes de acciones destructivas en la UI principal.
Recomendación:
Implementar advertencias más claras con iconos y texto destacado.
Agregar confirmación doble para acciones destructivas.
Severidad: Baja (solo afecta dev-tools)
2.2 CWE-1021: Improper Restriction of Rendered UI Layers or Frames¶
Ubicación: app/templates/index.html
Problema identificado:
No se encontraron restricciones explícitas de iframes o frames en el código HTML.
La aplicación no implementa políticas de seguridad de contenido (CSP) para prevenir clickjacking.
Recomendación:
# En app/__init__.py, agregar headers de seguridad
@app.after_request
def set_security_headers(response):
response.headers['X-Frame-Options'] = 'DENY'
response.headers['X-Content-Type-Options'] = 'nosniff'
response.headers['Content-Security-Policy'] = "frame-ancestors 'none'"
return response
Severidad: Media
3. Input Validation (Validación de Entrada)¶
3.1 CWE-20: Improper Input Validation (relacionado) ✅ RESUELTO¶
Ubicación: app/routes.py, app/helpers.py, app/static/js/config.js, app/static/js/main.js
Estado: ✅ IMPLEMENTADO
Solución implementada:
Se creó la función
validate_and_sanitize_name()enapp/helpers.pyque:Valida que el nombre no esté vacío
Valida longitud (1-100 caracteres, configurable)
Elimina caracteres peligrosos:
< > " 'Normaliza espacios múltiples
Permite solo letras (incluyendo Unicode/acentes), espacios, guiones y apóstrofes
Sanitiza el string antes de almacenarlo
Se implementó validación en el backend (
app/routes.pylíneas 53-71):# Validar y sanitizar nombre nombre_raw = data.get('nombre', '') is_valid_nombre, nombre_sanitized, error_key_nombre = validate_and_sanitize_name( nombre_raw, min_length=VALIDATION_LIMITS["name_min_length"], max_length=VALIDATION_LIMITS["name_max_length"] ) if not is_valid_nombre: return jsonify({"error": get_error(error_key_nombre or "invalid_name")}), 400
Se implementó validación en el frontend (
app/static/js/main.jslíneas 147-167):// Validar y sanitizar nombre const nombreValidation = AppConfig.validateAndSanitizeName(nombreInput); if (!nombreValidation.isValid) { alert(MESSAGES.errors[nombreValidation.errorKey] || 'El nombre no es válido'); return; }
Se agregaron límites de validación en
app/config.py:"name_min_length": 1, "name_max_length": 100,
Se agregaron mensajes de error específicos en
app/languages/es.py
Severidad: Media → ✅ Resuelto
3.2 Validación de JSON Malformado¶
Ubicación: app/routes.py línea 34
Problema identificado:
request.json or {}puede fallar si el JSON está malformado, causando una excepción no manejada.
Código afectado:
data = request.json or {}
Riesgo: Si el cliente envía JSON malformado, Flask puede lanzar una excepción que no se captura.
Recomendación:
try:
data = request.get_json(force=True) or {}
except Exception:
return jsonify({"error": get_error("invalid_json")}), 400
Severidad: Baja
4. Error Handling (Manejo de Errores)¶
4.1 CWE-703: Improper Check or Handling of Exceptional Conditions¶
Ubicación: app/routes.py, app/static/js/sync.js
Estado: ⚠️ MEJORADO PARCIALMENTE
Problema identificado:
En
routes.pylíneas 38 y 76, se capturaExceptiongenérico para conversión de tipos, lo que puede ocultar errores inesperados.En
sync.jslínea 119, se captura cualquier error sin diferenciar tipos.
Código afectado:
# routes.py línea 36-39
try:
height_m = float(data['talla_m'])
except Exception: # Muy genérico
return jsonify({"error": get_error("invalid_height")}), 400
# routes.py línea 74-77
try:
weight_kg = float(data['peso_kg'])
except Exception: # Muy genérico
return jsonify({"error": get_error("invalid_weight")}), 400
Mejora implementada:
✅ Validación de nombres mejorada: La función
validate_and_sanitize_name()enapp/helpers.pyy su uso enapp/routes.py(líneas 53-71) implementa un manejo de errores más estructurado:No usa
try/except ExceptiongenéricoRetorna tuplas con información específica de error:
(is_valid, sanitized, error_key)Proporciona códigos de error específicos (
name_empty,name_too_long,invalid_name, etc.)Permite un manejo más granular y específico de errores
Código mejorado:
# routes.py líneas 53-71 - Validación de nombres (mejorada)
nombre_raw = data.get('nombre', '')
is_valid_nombre, nombre_sanitized, error_key_nombre = validate_and_sanitize_name(
nombre_raw,
min_length=VALIDATION_LIMITS["name_min_length"],
max_length=VALIDATION_LIMITS["name_max_length"]
)
if not is_valid_nombre:
return jsonify({"error": get_error(error_key_nombre or "invalid_name")}), 400
Pendiente:
⚠️ Aún se usa
Exceptiongenérico en las conversiones defloat()para altura y peso (líneas 38, 76)⚠️ El manejo de errores en
sync.jssigue siendo genérico
Recomendación para las partes pendientes:
try:
height_m = float(data['talla_m'])
except (ValueError, TypeError, KeyError) as e:
# Log del error para debugging
current_app.logger.warning(f"Error al convertir altura: {e}")
return jsonify({"error": get_error("invalid_height")}), 400
Severidad: Baja (mejorada parcialmente)
5. CORS Configuration (Configuración CORS)¶
6.1 CWE-942: Overly Permissive Cross-domain Whitelist¶
Ubicación: app/__init__.py línea 13-19
Problema identificado:
CORS está configurado para permitir cualquier origen (
"origins": "*").
Código afectado:
CORS(app, resources={
r"/api/*": {
"origins": "*", # Permite cualquier origen
"methods": ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
"allow_headers": ["Content-Type"]
}
})
Riesgo:
Cualquier sitio web puede hacer peticiones a la API.
Aumenta la superficie de ataque si se expone el backend fuera de
localhost.
Estado: ⏸️ PENDIENTE
Recomendación:
# En producción, restringir a dominios específicos
CORS(app, resources={
r"/api/*": {
"origins": ["https://tu-dominio.com", "https://www.tu-dominio.com"],
"methods": ["GET", "POST"],
"allow_headers": ["Content-Type"]
}
})
Severidad: Media (alta en producción)
6. Data Integrity (Integridad de Datos)¶
7.1 Validación Defensiva Implementada ✅¶
Ubicación: app/routes.py líneas 130-133, app/static/js/main.js líneas 100-114
Estado: La aplicación implementa validaciones defensivas antes de calcular el IMC, lo cual es una buena práctica.
Código positivo:
# Backend - Validación defensiva
if not (VALIDATION_LIMITS["weight_min"] <= last_weight.weight_kg <= VALIDATION_LIMITS["weight_max"]):
return jsonify({"error": get_error("weight_out_of_range")}), 400
Evaluación: ✅ Buena práctica implementada
Resumen de Debilidades Identificadas¶
CWE |
Descripción |
Severidad |
Ubicación |
Estado |
|---|---|---|---|---|
CWE-1287 |
Validación de tipo insuficiente |
Media |
|
⚠️ Requiere atención |
CWE-843 |
Confusión de tipos (NaN no validado) |
Media |
|
⚠️ Requiere atención |
CWE-356 |
Falta de advertencia en acciones destructivas |
Baja |
|
⚠️ Mejora recomendada |
CWE-1021 |
Falta de protección contra clickjacking |
Media |
|
⚠️ Requiere atención |
CWE-20 |
Validación de entrada insuficiente (nombres) |
Media |
|
✅ RESUELTO |
CWE-703 |
Manejo de excepciones demasiado genérico |
Baja |
|
⚠️ MEJORADO PARCIALMENTE |
CWE-942 |
CORS demasiado permisivo |
Media/Alta |
|
⏸️ Pendiente |
Recomendaciones Prioritarias¶
Prioridad Alta¶
Restringir CORS en producción - Limitar orígenes permitidos cuando la API se exponga fuera de
localhostAgregar headers de seguridad - Implementar X-Frame-Options y CSP
Prioridad Media¶
Validar tipos antes de conversión - Verificar que los datos sean del tipo esperado antes de
float()oparseFloat()~~Validar y sanitizar nombres~~ - ✅ IMPLEMENTADO - Validación robusta en backend y frontend
Manejar NaN e Infinity - Validar que los números sean finitos después de conversión
Prioridad Baja¶
Mejorar manejo de excepciones - ⚠️ MEJORADO PARCIALMENTE: La validación de nombres usa manejo estructurado de errores. Pendiente: mejorar conversiones de
float()y manejo ensync.jsMejorar advertencias en UI - Hacer más claras las advertencias para acciones destructivas
Conclusión¶
La aplicación implementa buenas prácticas de validación defensiva y manejo de errores en general. Sin embargo, hay áreas de mejora relacionadas con:
Validación de tipos más estricta antes de conversiones
Configuración de seguridad (CORS, headers de seguridad)
Validación de entrada más completa (especialmente en campos de texto)
La mayoría de las debilidades identificadas son de severidad media o baja, y pueden corregirse sin cambios arquitectónicos significativos.
Fecha de análisis: 2025-01-27
Versión analizada: Mayor Update (commit 24bfc76)
Referencia CWE: CWE-699