En la lección anterior colocaste el mapa: los dominios de la disciplina, los marcos que la organizan y —sobre todo— el ciclo de vida de un ataque. Ahora vamos a llenar ese esqueleto de contenido. Esta lección recorre los ataques técnicos que se ejecutan contra sistemas como el de Nimbus Reservas, ordenados por la fase de la cadena en la que aparecen: cómo el adversario recoge información, cómo ataca la red, cómo consigue credenciales, cómo explota una aplicación web, cómo despliega ransomware o compromete una cadena de suministro y cómo abusa de una API. De cada uno verás qué es, un ejemplo mínimo y conceptual sobre la arquitectura de Nimbus, cómo se detecta y hacia dónde apunta su defensa. El enfoque es defensivo en todo momento: los ejemplos son ilustrativos, mínimos y siempre acompañados de su corrección. Conocer el ataque no es un fin, es el requisito para diseñar la defensa.

Contenido

  1. Cómo se organiza este catálogo y una advertencia previa
  2. Fase 1 — Reconocimiento: OSINT y escaneo
  3. Ataques a la red: escucha, suplantación e interposición
  4. Denegación de servicio: volumétrica y aplicativa
  5. Ataques a credenciales y sesiones
  6. Ataques a aplicaciones web: el Top 10 de OWASP como marco
  7. Malware en acción: ransomware moderno y doble extorsión
  8. Ataques a la cadena de suministro
  9. Ataques específicos a APIs
  10. Un ataque encadenado sobre Nimbus, paso a paso
  11. Tabla resumen: ataque, propiedad afectada, detección y defensa

  1. Cómo se organiza este catálogo y una advertencia previa

Un catálogo de ataques sin estructura es una lista imposible de recordar. Por eso usamos el eje que ya conoces —las fases de la Kill Chain— y añadimos una segunda clave de lectura: qué propiedad de la tríada CIA rompe cada ataque, la que estableciste en 01-01.

flowchart LR
    R["RECONOCIMIENTO\nOSINT, escaneo,\nenumeracion"] --> E["ENTREGA Y EXPLOTACION\nRed, credenciales,\naplicacion web"]
    E --> P["POST-EXPLOTACION\nPersistencia, movimiento\nlateral, escalada"]
    P --> A["ACCIONES\nExfiltracion, cifrado,\nfraude, denegacion"]

Advertencia necesaria. Todo lo que sigue está escrito para defender. No encontrarás aquí herramientas listas para atacar, código funcional de malware ni instrucciones aplicables a sistemas de terceros. Los ejemplos son fragmentos mínimos sobre el entorno ficticio de Nimbus, siempre con su corrección al lado. Ejecutar cualquiera de estas técnicas contra sistemas que no son tuyos y sin autorización expresa y por escrito es un delito; el marco legal y ético de las pruebas autorizadas se trata en 06-06, y la metodología de las pruebas de penetración en 05-03.


  1. Fase 1 — Reconocimiento: OSINT y escaneo

El reconocimiento es la fase que las organizaciones ignoran porque no deja rastro en sus sistemas: buena parte ocurre sin tocarlos.

2.1 OSINT: inteligencia de fuentes abiertas

Qué es. Recopilar información públicamente disponible sobre el objetivo. No se explota nada; se lee lo que la organización ha publicado sin darse cuenta.

Qué encuentra un atacante sobre Nimbus sin tocar un solo servidor:

Fuente pública Qué revela Uso para el atacante
Registros de transparencia de certificados Todos los subdominios para los que se ha emitido un certificado, incluidos pre., admin. y demo. Descubre entornos internos que nadie pensaba que fueran públicos
Perfiles profesionales del equipo Nombres, cargos, tecnologías que mencionan («migrando a FastAPI y PostgreSQL») Objetivos de phishing y pila tecnológica
Repositorios de código públicos Ficheros de configuración subidos por error, correos en los commits, nombres de host internos Credenciales y superficie interna
Ofertas de empleo «Buscamos DevOps con experiencia en X, Y, Z» Inventario tecnológico gratis y muy fiable
Registros DNS y WHOIS Proveedores de correo, de nube, de CDN Por dónde entra el correo y dónde está alojado
Filtraciones de credenciales de otros servicios Correos corporativos aparecidos en brechas de terceros Base para credential stuffing (apartado 5)
Documentos publicados (PDF, hojas de cálculo) Metadatos con nombres de usuario, rutas internas, versiones de software Convenciones de nombres de cuenta

Cómo se defiende. No se puede impedir que alguien lea lo público, pero sí reducir lo que se publica sin querer:

  • Revisar periódicamente los subdominios que aparecen en transparencia de certificados y retirar los que ya no existen (es el hallazgo del ejercicio 3 de la lección anterior).
  • Escanear el histórico de los repositorios en busca de secretos —no basta con borrar el fichero: en Git sigue estando en el historial— y rotar cualquier credencial expuesta.
  • Limpiar metadatos de los documentos publicados.
  • Asumir que la lista de correos del equipo es pública. La defensa no es ocultarla, es MFA y formación (02-03, 02-05).

2.2 Escaneo y enumeración

Qué es. Contactar activamente con los sistemas del objetivo para descubrir qué hay: qué IPs responden, qué puertos están abiertos, qué servicios y versiones corren, qué rutas existen en la aplicación web.

A diferencia del OSINT, el escaneo sí deja rastro. Y aquí está la asimetría importante para una PYME: Nimbus ya está siendo escaneada continuamente por bots que no saben qué es. Distinguir el escaneo dirigido del ruido de fondo es difícil; lo que sí es factible es fijarse en escaneos lentos, ordenados y desde una misma fuente, que sugieren interés real.

# Extracto del log del balanceador de Nimbus (fragmento ilustrativo)
203.0.113.44 - - [12/Mar/2026:03:11:02] "GET /.env HTTP/1.1" 404 153
203.0.113.44 - - [12/Mar/2026:03:11:03] "GET /.git/config HTTP/1.1" 404 153
203.0.113.44 - - [12/Mar/2026:03:11:04] "GET /admin HTTP/1.1" 404 153
203.0.113.44 - - [12/Mar/2026:03:11:05] "GET /backup.sql HTTP/1.1" 404 153
203.0.113.44 - - [12/Mar/2026:03:11:06] "GET /wp-login.php HTTP/1.1" 404 153
203.0.113.44 - - [12/Mar/2026:03:11:07] "GET /phpmyadmin/ HTTP/1.1" 404 153

Cómo se lee este extracto:

  • Seis peticiones en seis segundos desde la misma IP, todas a rutas que no existen en Nimbus. Es un escáner automatizado probando el catálogo habitual.
  • /wp-login.php y /phpmyadmin/ delatan que el bot no sabe qué tecnología usa Nimbus: prueba todo. Esto es el ataque oportunista de 01-04 en estado puro.
  • La señal defensiva más valiosa no está en los 404, sino en el día que uno de ellos devuelva 200. Por eso la alerta útil no es «tenemos escaneos» (los tienes siempre), sino «una ruta sensible ha devuelto algo distinto de 404».

Las herramientas de descubrimiento y escaneo se ven en 05-01 y 05-03. Aquí interesa el concepto y su contrapartida defensiva: cada servicio que no expones es un servicio que no aparece en ningún escaneo.


  1. Ataques a la red: escucha, suplantación e interposición

Estos ataques atacan el canal, no los extremos. Comparten una premisa: si el atacante consigue estar en medio o escuchar, la seguridad de los extremos deja de bastar.

Ataque Qué hace Requisito Propiedad rota
Sniffing (escucha) Captura tráfico que circula por la red Estar en el mismo segmento o en un punto de paso Confidencialidad
ARP spoofing Envenena la tabla ARP de la red local para que el tráfico pase por el atacante Estar en la misma red local (la wifi de la oficina) Confidencialidad, integridad
Man-in-the-Middle (MitM) Se interpone y puede leer y modificar el tráfico Alguno de los anteriores, o control de un punto de red Confidencialidad, integridad, autenticidad
DNS spoofing / envenenamiento Responde a consultas DNS con una IP falsa Control del resolvedor, de la red o de la cuenta de DNS Autenticidad, integridad
Punto de acceso wifi falso Ofrece una red con nombre creíble («NimbusGuest») a la que la víctima se conecta Proximidad física Todas

3.1 Por qué esto sigue importando si todo va por HTTPS

Es la pregunta correcta. TLS bien implementado hace que la escucha pasiva sea inútil: el atacante ve tráfico cifrado. Pero quedan tres huecos reales en Nimbus:

  1. El tráfico interno que no va cifrado. En el DFD de 01-04, el flujo F3 (balanceador → API) era HTTP interno. Alguien que llegue a esa red lo lee en claro. Es el motivo del principio Zero Trust: la red interna no es de confianza.
  2. Los metadatos siguen visibles. Aunque el contenido esté cifrado, un observador ve a qué dominios te conectas, cuándo y cuánto volumen. La consulta DNS previa suele ir en claro.
  3. La degradación y el error de usuario. Si un portátil de Nimbus acepta un certificado no válido con un clic, TLS deja de proteger. Por eso existe HSTS (que veremos en 02-04) y por eso los clientes deben rechazar, no preguntar.

Ejemplo mínimo del error clásico en el código de Nimbus:

# === VULNERABLE: desactivar la verificacion de certificado ===
import requests

# Alguien puso verify=False "porque en pruebas daba error de certificado"
respuesta = requests.get("https://api.pasarela-pago.example/v1/cobros",
                         headers=cabeceras, verify=False)
# === CORRECTO ===
import requests

# La verificacion del certificado es lo que convierte TLS en autenticacion
# del servidor, no solo en cifrado. Sin ella, cualquiera que se interponga
# puede presentar su propio certificado y leer y modificar la comunicacion.
respuesta = requests.get("https://api.pasarela-pago.example/v1/cobros",
                         headers=cabeceras, timeout=10)   # verify=True es el valor por defecto

Por qué verify=False es tan grave y tan frecuente: desactiva la comprobación de que el certificado presentado corresponde realmente al dominio y está firmado por una autoridad reconocida. El tráfico sigue cifrado, lo que da una falsa sensación de seguridad, pero cifrado contra el atacante si este se ha interpuesto. Aparece casi siempre como parche temporal en un entorno de pruebas y acaba en producción. Regla práctica: verify=False no debe existir en ningún repositorio; si un certificado interno da problemas, la solución es añadir la autoridad interna al almacén de confianza, no desactivar la verificación. Un análisis estático en el CI (02-04) lo detecta automáticamente.

3.2 Detección

Señal Dónde se ve
Cambios inesperados en la tabla ARP; una MAC asociada a varias IPs Registros del conmutador; herramientas de detección de ARP en la red local
Respuestas DNS con TTL anómalos o IPs fuera de los rangos esperados Registros del resolvedor
Certificado presentado distinto del esperado Certificate pinning en la app móvil; alertas de transparencia de certificados
Aparición de una SSID con el nombre corporativo fuera de la oficina Inventario de puntos de acceso; alertas de puntos de acceso no autorizados

  1. Denegación de servicio: volumétrica y aplicativa

El objetivo es la disponibilidad: impedir que los clientes de Nimbus usen el servicio. Para un SaaS del que dependen agendas de clínicas en marcha, una hora de caída tiene un coste inmediato y visible.

Tipo Cómo funciona Volumen necesario Ejemplo en Nimbus
DoS volumétrico Saturar el ancho de banda o las conexiones desde un origen Alto Difícil desde un solo origen; poco frecuente hoy
DDoS volumétrico Lo mismo desde miles de orígenes distribuidos, a menudo amplificando el tráfico mediante servicios de terceros mal configurados Muy alto Saturación del enlace del balanceador
DDoS aplicativo (capa 7) Pocas peticiones, pero caras: cada una consume mucha CPU, memoria o base de datos Bajo Peticiones al informe sin paginación de 01-01
Agotamiento de recursos lógicos Consumir un recurso finito no técnico Bajísimo Reservar y cancelar en bucle para llenar la agenda de una clínica

El más peligroso para Nimbus es el tercero, y es el más subestimado. Recuerda el endpoint de informes sin paginación que rompía la disponibilidad en 01-01: 20 peticiones bien elegidas pueden tumbar la API, mientras que un DDoS volumétrico requiere una botnet. La defensa contra el volumétrico se compra (servicio anti-DDoS del proveedor); la defensa contra el aplicativo se programa.

# === PATRON VULNERABLE: coste ilimitado por peticion ===
@app.get("/api/v1/informes/ocupacion")
def informe(desde: str, hasta: str, usuario = Depends(usuario_actual)):
    # El cliente decide el rango: puede pedir 10 anios de datos
    filas = db.execute(
        "SELECT * FROM reservas WHERE tenant_id = :t AND fecha_hora BETWEEN :d AND :h",
        {"t": usuario.tenant_id, "d": desde, "h": hasta}).fetchall()
    return [dict(f) for f in filas]        # y todo se materializa en memoria
# === CORRECCION: acotar el coste ANTES de ejecutar ===
from datetime import date, timedelta
from fastapi import HTTPException

MAX_DIAS = 92          # limite de negocio: un trimestre
MAX_FILAS = 5000       # limite tecnico

@app.get("/api/v1/informes/ocupacion")
@limitador.limit("5/minute")                       # limite de tasa por usuario
def informe(desde: date, hasta: date, pagina: int = 1,
            usuario = Depends(usuario_actual)):
    if (hasta - desde) > timedelta(days=MAX_DIAS):
        raise HTTPException(400, "El rango maximo es de 92 dias")

    filas = db.execute(
        """SELECT id, fecha_hora, servicio_id, estado
             FROM reservas
            WHERE tenant_id = :t AND fecha_hora BETWEEN :d AND :h
            ORDER BY fecha_hora
            LIMIT :lim OFFSET :off""",
        {"t": usuario.tenant_id, "d": desde, "h": hasta,
         "lim": MAX_FILAS, "off": (pagina - 1) * MAX_FILAS}).fetchall()
    return {"pagina": pagina, "datos": [dict(f) for f in filas]}

Las cuatro defensas que aparecen aquí, explicadas:

  1. Validación de tipos (desde: date en vez de str): rechaza entradas malformadas antes de llegar a la base de datos.
  2. Límite de negocio (92 días): un informe de ocupación de diez años no es un caso de uso legítimo. Los límites de negocio son más efectivos que los técnicos porque son defendibles ante el cliente.
  3. Paginación con LIMIT: acota la memoria y el tiempo de cada petición, pase lo que pase.
  4. Límite de tasa (5/minute): acota cuántas veces se puede pagar ese coste. Sin él, los otros tres límites solo obligan al atacante a repetir más.

Detección: latencia media de la API disparada con poco tráfico entrante, consultas lentas en PostgreSQL concentradas en un endpoint, y un mismo user_id o token concentrando la mayor parte del consumo. Estas señales, en un DDoS volumétrico, serían distintas: mucho tráfico y saturación de red.


  1. Ataques a credenciales y sesiones

La identidad es hoy la vía de entrada dominante, porque una credencial válida no dispara ninguna alarma: no hay exploit, no hay malware, no hay anomalía de protocolo. Solo alguien entrando.

Ataque Mecánica Qué lo hace viable Señal característica en los logs
Fuerza bruta Muchas contraseñas contra una cuenta Ausencia de bloqueo o límite Muchos fallos, una cuenta, poco tiempo
Ataque de diccionario Igual, pero con listas de contraseñas probables Contraseñas humanas y predecibles Igual que el anterior
Password spraying Una contraseña muy común contra muchas cuentas Bloqueo por cuenta (que no detecta esto) Pocos fallos por cuenta, muchas cuentas, misma IP
Credential stuffing Pares correo/contraseña de brechas de otros servicios Reutilización de contraseñas Tasa de éxito baja pero no nula; IPs distribuidas
Robo de sesión / token Usar un token o cookie válidos robados Tokens de vida larga, sin vinculación al contexto Mismo token desde IP o país nuevos
Pass-the-cookie Extraer la cookie de sesión del navegador de la víctima e importarla en otro Cookies robadas por malware saltan el MFA Sesión sin evento de autenticación previo

5.1 Password spraying: el que evade la defensa clásica

Merece atención especial porque está diseñado para eludir el bloqueo de cuenta. Si Nimbus bloquea tras 5 intentos fallidos por cuenta, el atacante prueba una sola contraseña muy común contra las 38 cuentas del equipo, espera una hora y prueba la siguiente. Ninguna cuenta llega nunca a 5 fallos.

# Fragmento del log de autenticacion de Nimbus (ilustrativo)
2026-03-14T02:14:07Z auth FAIL [email protected]  ip=198.51.100.77
2026-03-14T02:14:11Z auth FAIL [email protected]   ip=198.51.100.77
2026-03-14T02:14:15Z auth FAIL [email protected]  ip=198.51.100.77
2026-03-14T02:14:19Z auth FAIL [email protected]  ip=198.51.100.77
2026-03-14T02:14:23Z auth OK   [email protected]   ip=198.51.100.77

Cómo se lee: cuatro fallos y un éxito, un intento por cuenta, misma IP, cuatro segundos entre cada uno, a las 2 de la madrugada. Ninguna cuenta ha llegado al umbral de bloqueo, y sin embargo el ataque ha tenido éxito. La detección correcta no cuenta fallos por cuenta, sino fallos distintos por origen y la relación entre cuentas atacadas y cuentas existentes. Esta es exactamente la clase de regla que se construye en 05-02.

La defensa que lo neutraliza casi por completo es MFA resistente al phishing, junto con la comprobación de contraseñas contra listas de filtradas. Ambas se desarrollan en 02-05.

5.2 Robo de token: por qué el hallazgo de 01-04 era grave

En el inventario descubriste un token de la cuenta nimbus-deploy-bot sin caducidad y con permisos de escritura en todos los repositorios. Un token así tiene tres propiedades que lo convierten en el objetivo perfecto:

  • No caduca: robarlo una vez basta para siempre.
  • No tiene segundo factor: un token es la credencial completa.
  • Está en muchos sitios: en la configuración del CI, en el portátil de quien lo creó, quizá en un fichero .env, quizá en el historial de un chat.

Su corrección —caducidad corta, ámbito mínimo, credenciales efímeras del propio CI y rotación— se detalla en 02-05.


  1. Ataques a aplicaciones web: el Top 10 de OWASP como marco

El OWASP Top 10 es la lista de las diez categorías de riesgo más extendidas en aplicaciones web. No es un catálogo exhaustivo de vulnerabilidades: es una lista de categorías por frecuencia e impacto, y por eso funciona bien como guía de revisión. Recorremos las más relevantes para la API de Nimbus.

6.1 Control de acceso roto (IDOR y compañía)

Es la primera categoría del Top 10 por prevalencia, y ya te la encontraste en 01-01.

Qué es. El sistema autentica correctamente («sé quién eres») pero no autoriza correctamente («no compruebo que esto sea tuyo»).

# === VULNERABLE (el endpoint de 01-01) ===
@app.get("/api/v1/reservas/{reserva_id}")
def ver_reserva(reserva_id: int, usuario = Depends(usuario_actual)):
    # Hay sesion valida, pero no se comprueba de quien es la reserva
    return db.execute("SELECT * FROM reservas WHERE id = :id",
                      {"id": reserva_id}).fetchone()
# === CORREGIDO ===
@app.get("/api/v1/reservas/{reserva_id}")
def ver_reserva(reserva_id: int, usuario = Depends(usuario_actual)):
    fila = db.execute(
        """SELECT id, fecha_hora, servicio_id, estado, nombre_cliente
             FROM reservas
            WHERE id = :id AND tenant_id = :tenant""",   # el recurso debe ser suyo
        {"id": reserva_id, "tenant": usuario.tenant_id}).fetchone()
    if fila is None:
        raise HTTPException(404)      # 404 y no 403: no revela si existe
    return dict(fila)

Variantes de la misma familia que conviene reconocer:

  • Escalada horizontal: acceder a datos de otro usuario del mismo nivel (el IDOR clásico).
  • Escalada vertical: un usuario de recepción consigue funciones de administrador porque el control está solo en el interfaz y no en el servidor.
  • Manipulación de parámetros: enviar {"rol": "admin"} en el cuerpo de una actualización de perfil y que el servidor lo acepte porque hace una asignación masiva de campos.

Detección. Es difícil desde fuera y fácil desde la auditoría: si el log registra usuario, tenant y recurso, una consulta que busque accesos donde el tenant del recurso no coincide con el del usuario encuentra los intentos. Sin ese log, es invisible. He aquí por qué la auditoría de 01-01 y 01-03 no era burocracia.

6.2 Inyección SQL

Qué es. El dato del usuario se concatena en una consulta y el motor lo interpreta como instrucción en lugar de como dato.

# === VULNERABLE: concatenacion de cadenas ===
def buscar_clientes(texto, tenant_id):
    consulta = f"SELECT id, nombre, email FROM clientes_finales " \
               f"WHERE tenant_id = {tenant_id} AND nombre LIKE '%{texto}%'"
    return db.execute(consulta).fetchall()

Si texto contiene una comilla, la estructura de la consulta cambia. Basta este ejemplo conceptual: un valor como ' OR '1'='1 transforma la condición de búsqueda en una condición siempre cierta, y la consulta devuelve todas las filas a las que el rol de base de datos tenga acceso. Variantes más avanzadas permiten leer otras tablas o inferir datos carácter a carácter por el tiempo de respuesta (inyección ciega).

# === CORRECTO: consultas parametrizadas ===
def buscar_clientes(texto, tenant_id):
    return db.execute(
        """SELECT id, nombre, email
             FROM clientes_finales
            WHERE tenant_id = :t AND nombre ILIKE :patron
            LIMIT 100""",
        {"t": tenant_id, "patron": f"%{texto}%"}).fetchall()

Por qué la parametrización funciona de verdad y el «escapado manual» no: con parámetros, el motor recibe primero la estructura de la consulta y después los valores, ya como datos. No hay ningún contenido de texto que pueda cambiar la estructura, porque la estructura ya está fijada. El escapado manual, en cambio, depende de acertar con todas las combinaciones de comillas, codificaciones y modos del motor: tarde o temprano falla.

Segunda capa (la que salva cuando la primera falla): el rol nimbus_api de 01-03 no tiene DELETE, no tiene DDL y no puede leer nominas ni claves_pasarela. Una inyección con ese rol es grave, pero acotada. Y la RLS de PostgreSQL impide además que devuelva filas de otro tenant. Defensa en profundidad en estado puro.

Detección: errores de SQL en los logs de aplicación (un syntax error at or near es una señal inequívoca de que alguien está probando), picos de consultas anómalamente lentas, y peticiones con comillas o palabras clave SQL en parámetros que nunca deberían contenerlas. Un WAF detecta muchos intentos, pero no sustituye a la parametrización.

6.3 Cross-Site Scripting (XSS)

Qué es. El atacante consigue que el navegador de otra víctima ejecute código en el contexto del sitio de Nimbus. La víctima no es el servidor: es el usuario.

Tipo Dónde vive la carga Ejemplo en Nimbus
Almacenado Guardada en la base de datos y servida a todos Campo «notas de la cita» que se muestra en el panel de la clínica
Reflejado Viaja en la URL y vuelve en la respuesta Buscador que muestra «No hay resultados para: lo que escribiste»
Basado en DOM Nunca llega al servidor; ocurre en el JavaScript de la SPA La SPA lee un parámetro del fragmento de la URL y lo inserta en la página

Ejemplo conceptual y mínimo. El campo «notas» de una reserva admite texto libre. Si la SPA lo inserta en la página sin escapar, un texto que contenga una etiqueta <script> se ejecutará en el navegador de la recepcionista de la clínica que abra esa cita. El impacto no es «una ventana emergente»: es que ese código actúa con la sesión de la recepcionista, y puede leer la agenda completa o realizar acciones en su nombre.

// === VULNERABLE: insertar HTML sin escapar ===
elemento.innerHTML = reserva.notas;

// === CORRECTO: insertar como texto, nunca como HTML ===
elemento.textContent = reserva.notas;

Las tres capas de defensa contra XSS, en orden de importancia:

  1. Codificación en la salida según el contexto. El mismo dato se escapa distinto en HTML, en un atributo, en JavaScript o en una URL. Los motores de plantillas modernos y los frameworks de SPA lo hacen por defecto; el problema aparece cuando alguien lo desactiva a propósito (innerHTML, dangerouslySetInnerHTML, |safe).
  2. Content-Security-Policy: cabecera que indica al navegador de qué orígenes puede cargar y ejecutar código. Convierte muchos XSS explotables en intentos fallidos. Se detalla en 02-04.
  3. Cookies HttpOnly: impiden que el JavaScript lea la cookie de sesión, limitando el robo directo. No impiden que el código actúe en nombre del usuario. Se detalla en 02-05.

6.4 CSRF (falsificación de petición entre sitios)

Qué es. Un sitio malicioso hace que el navegador de la víctima, ya autenticado en Nimbus, envíe una petición no deseada. El navegador adjunta la cookie de sesión automáticamente, así que la petición parece legítima.

Ejemplo en Nimbus: la administradora de una clínica está autenticada en el panel y visita otra página que, sin que ella lo note, envía un formulario a POST /api/v1/usuarios/invitar para dar de alta a un usuario nuevo con permisos.

Defensas, y por qué hay que combinarlas:

Defensa Cómo funciona Limitación
Cookie SameSite=Lax o Strict El navegador no envía la cookie en peticiones originadas por otro sitio Es la defensa principal hoy; requiere navegadores modernos y no cubre todos los casos con Lax
Token anti-CSRF Cada formulario incluye un valor impredecible que el servidor verifica Requiere gestión de estado
Verificación de Origin/Referer El servidor rechaza peticiones de origen distinto Cabeceras a veces ausentes
Autenticación por cabecera en vez de cookie Si el token va en Authorization, el navegador no lo adjunta solo Cambia el modelo de sesión de la SPA (ver 02-05)

6.5 SSRF (falsificación de petición del lado del servidor)

Qué es. El atacante consigue que el servidor de Nimbus haga una petición a una URL que él elige. Es especialmente peligroso en la nube, porque el servidor está dentro de la red privada y puede alcanzar sitios a los que nadie llega desde fuera.

Ejemplo en Nimbus: una funcionalidad permite a la clínica indicar la URL de su logotipo para las facturas, y el servidor la descarga. Si no se valida, el atacante puede indicar una dirección interna —el servicio de metadatos del proveedor cloud, un panel administrativo interno, localhost— y hacer que el servidor la consulte por él. El caso más grave es el acceso al servicio de metadatos de la instancia, que en configuraciones antiguas puede devolver credenciales temporales de la cuenta cloud (A-05).

# === VULNERABLE ===
def descargar_logo(url: str):
    return requests.get(url, timeout=5).content     # cualquier URL, incluida una interna

# === CORREGIDO: lista blanca, resolucion previa y bloqueo de rangos internos ===
import ipaddress, socket
from urllib.parse import urlparse

RANGOS_PROHIBIDOS = [ipaddress.ip_network(r) for r in
                     ("127.0.0.0/8", "10.0.0.0/8", "172.16.0.0/12",
                      "192.168.0.0/16", "169.254.0.0/16", "::1/128")]

def descargar_logo(url: str):
    p = urlparse(url)
    if p.scheme != "https":                       # 1. solo HTTPS
        raise ValueError("Esquema no permitido")
    ip = ipaddress.ip_address(socket.gethostbyname(p.hostname))
    if any(ip in red for red in RANGOS_PROHIBIDOS):   # 2. nada interno
        raise ValueError("Destino no permitido")
    return requests.get(url, timeout=5, allow_redirects=False,   # 3. sin redirecciones
                        stream=True).raw.read(2_000_000)          # 4. tamanio maximo

Las cuatro decisiones del código corregido: solo HTTPS (evita file://, gopher:// y otros esquemas); resolución previa del nombre y comprobación de que la IP no cae en un rango interno; sin seguir redirecciones, porque una redirección es la forma clásica de saltarse la comprobación; y límite de tamaño para que la descarga no se convierta en una denegación de servicio. Aun así, la defensa definitiva es de arquitectura: que estas peticiones salgan por un proxy de salida con lista blanca, y que el servicio de metadatos exija la versión que requiere token (se ve en 05-07).

6.6 Deserialización insegura

Qué es. La aplicación reconstruye objetos a partir de datos que vienen del exterior. Algunos formatos permiten que el proceso de reconstrucción ejecute código.

# === MUY PELIGROSO: pickle sobre datos externos ===
import pickle
datos = pickle.loads(cuerpo_de_la_peticion)   # puede ejecutar codigo arbitrario

# === CORRECTO: formato de datos sin capacidad de ejecucion, y con validacion ===
from pydantic import BaseModel

class PreferenciasCliente(BaseModel):
    idioma: str
    zona_horaria: str
    recordatorios: bool

datos = PreferenciasCliente.model_validate_json(cuerpo_de_la_peticion)

La regla: pickle, yaml.load sin SafeLoader y equivalentes en otros lenguajes nunca deben aplicarse a datos que vengan de fuera. Usa JSON y valida el esquema. La validación con un modelo explícito aporta además defensa contra la asignación masiva de campos: solo se aceptan las tres claves declaradas.

6.7 Exposición de datos sensibles

Menos vistosa y muy frecuente. Formas habituales en una API como la de Nimbus:

  • Devolver más de lo necesario: SELECT * que incluye dni, notas_internas o hash_password, y confiar en que el interfaz no los muestre. El dato viaja al cliente y está en el navegador.
  • Mensajes de error verbosos: una traza completa con la versión de la librería, la ruta del fichero y hasta la consulta SQL.
  • Registrar lo que no se debe: tokens, contraseñas o datos de salud en los logs. Se trata en 02-04.
  • Objetos accesibles sin autorización: el bucket de adjuntos (A-02) sin política restrictiva. Ya lo resolviste con URLs firmadas de 120 s en 01-03.

6.8 Dependencias y componentes vulnerables

La API de Nimbus arrastra alrededor de 180 dependencias transitivas de Python. Iván ha escrito una fracción mínima del código que se ejecuta en producción.

$ pip-audit
Found 3 known vulnerabilities in 2 packages
Name        Version  ID                 Fix Versions
----------- -------- ------------------ ------------
py-lib-x    2.4.1    GHSA-xxxx-xxxx-01  2.4.3
py-lib-x    2.4.1    GHSA-xxxx-xxxx-02  2.5.0
py-lib-y    1.9.0    PYSEC-2026-0001    1.9.4

Cómo se lee y qué se hace: cada línea es una vulnerabilidad conocida con su versión corregida. Lo importante no es la lista, es el proceso: que este comando se ejecute en cada construcción, que rompa la integración cuando aparezca algo de severidad alta y que exista un compromiso explícito de plazo de corrección. Un análisis que nadie lee es peor que ninguno, porque genera la sensación de estar cubierto. El yaml completo del CI está en 02-04.


  1. Malware en acción: ransomware moderno y doble extorsión

En 01-02 viste la taxonomía de nueve familias de malware. Aquí interesa cómo opera hoy la que más daño causa a empresas del tamaño de Nimbus.

Lo que cambió respecto al ransomware clásico: ya no es un fichero que cifra un ordenador. Es una operación humana, con varios grupos especializados, que dura semanas y en la que el cifrado es el último paso.

7.1 Cronología típica

Momento Qué ocurre ¿Detectable?
Día 0 Acceso inicial: phishing, credencial de VPN sin MFA o servicio expuesto sin parchear. A menudo lo obtiene un intermediario que vende el acceso Sí: autenticación anómala, ejecución inusual
Días 1-3 Reconocimiento interno: mapa de la red, dónde están las copias, quién es administrador Sí: consultas al directorio, escaneo interno
Días 3-10 Escalada y movimiento lateral; se hacen con credenciales administrativas Sí: uso de herramientas administrativas fuera de lo habitual
Días 10-20 Exfiltración: se llevan los datos antes de cifrar. Es la base de la segunda extorsión Sí: volumen de salida anómalo hacia destinos nuevos
Día 20 Sabotaje de la recuperación: borrado de copias, instantáneas y réplicas. Este paso decide el desenlace Sí, y es la última oportunidad
Día 21 Cifrado, normalmente un viernes por la noche o en festivo, y nota de rescate Demasiado tarde
Después Doble extorsión: pagar por descifrar y por que no se publiquen los datos. A veces triple: se avisa a los clientes de la víctima

Las tres conclusiones defensivas que se derivan de esta cronología:

  1. Hay tres semanas de ventana. El ransomware no es instantáneo: es la conclusión visible de un compromiso largo. Cada día de esa ventana es una oportunidad de detección que Nimbus hoy no aprovecha porque nadie mira los logs.
  2. Las copias son el objetivo, no un daño colateral. El atacante busca activamente las copias y las borra. Por eso una copia accesible con las mismas credenciales que producción no es una copia: es un fichero más que se va a cifrar. La respuesta es la inmutabilidad y la separación de cuentas (02-04, 04-06).
  3. Pagar no resuelve la fuga. Aunque se pague y se descifre, los datos ya salieron. Para Nimbus, con datos de citas de clínicas, eso significa notificación de brecha con independencia de si se recupera el servicio. Las implicaciones concretas se ven en 06-03, y la decisión de pago se analiza en el caso de Colonial Pipeline en 02-06.

Nota legal: el pago de rescates tiene implicaciones legales, fiscales y de sanciones que varían según la jurisdicción y la identidad del grupo atacante. Cualquier decisión en un caso real debe consultarse con asesoría jurídica y con las autoridades competentes.


  1. Ataques a la cadena de suministro

Qué es. En lugar de atacar a Nimbus directamente, el adversario compromete algo en lo que Nimbus confía y espera a que Nimbus lo instale o lo ejecute. Es un ataque de eficiencia extrema: se compromete uno y se llega a miles.

Vector Cómo llega a Nimbus Ejemplo conceptual
Paquete de código comprometido Una dependencia legítima recibe una versión con código malicioso Una librería de Python usada por la API publica una versión troyanizada
Confusión de dependencias Un paquete público con el mismo nombre que uno interno tiene versión mayor y el gestor lo prefiere nimbus-utils interno frente a un nimbus-utils público malicioso
Acción de CI/CD de terceros Un paso del flujo de GitHub Actions se referencia por etiqueta móvil y esa etiqueta se reapunta Una acción de despliegue que roba los secretos del entorno
Proveedor con acceso El proveedor es comprometido y su acceso legítimo se usa contra el cliente La consultora de sistemas (A-19)
Actualización de software firmada El propio fabricante distribuye una actualización comprometida El caso SolarWinds, que analizaremos en 02-06

Por qué es tan difícil de detectar: todo lo anterior parece legítimo. Una dependencia instalada por pip desde el índice oficial, una acción de CI con nombre conocido, una conexión de la consultora en horario laboral. No hay ninguna anomalía de protocolo que detectar; solo comportamiento posterior.

Defensas aplicables hoy en Nimbus:

  • Fijar versiones exactas y un fichero de bloqueo con sumas de verificación, para que la construcción sea reproducible.
  • Fijar las acciones de CI por hash del commit, no por etiqueta:
# Vulnerable: la etiqueta puede reapuntarse a otro codigo
- uses: alguna-org/accion-despliegue@v3

# Robusto: el hash identifica un codigo concreto e inmutable
- uses: alguna-org/accion-despliegue@a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0
  • Mínimo privilegio en el CI: que el flujo de construcción no tenga acceso a los secretos de producción, y que los secretos sean efímeros.
  • Generar un inventario de componentes (SBOM) para poder responder en minutos a «¿usamos la librería que acaba de salir en las noticias?». Es el inventario de 01-04 aplicado al software.

El tratamiento del proveedor como riesgo gestionable —evaluación, contrato, revisión— corresponde a 04-04.


  1. Ataques específicos a APIs

La API de Nimbus (A-04) es su superficie principal: ~120 endpoints usados por la SPA y la app móvil. Los ataques a APIs no siempre son «vulnerabilidades» en el sentido clásico; muchas veces son usos legítimos llevados al extremo.

Ataque Qué explota Ejemplo en Nimbus Defensa
Enumeración de identificadores IDs secuenciales y respuestas que distinguen «no existe» de «no es tuyo» Recorrer /api/v1/reservas/{id} incrementando el número Identificadores no adivinables (UUID); responder siempre 404; límite de tasa
Enumeración de usuarios Diferencias en el mensaje o en el tiempo de respuesta al iniciar sesión o al recuperar contraseña «Ese correo no está registrado» frente a «Contraseña incorrecta» Respuesta y tiempo idénticos en ambos casos
Abuso de lógica de negocio Reglas del negocio, no fallos de código Reservar 200 huecos y cancelarlos, bloqueando la agenda de una clínica; aplicar un descuento repetidamente Límites por usuario y por tenant; validación de reglas en el servidor; detección de patrones
Falta de límite de tasa Ausencia de coste para el atacante Probar contraseñas, exportar datos, agotar el saldo de SMS Límite por IP, por usuario y por token; cuotas por tenant
Exposición excesiva de datos El servidor devuelve todo y el cliente filtra El objeto de reserva incluye dni y notas_internas Esquemas de respuesta explícitos, nunca SELECT *
Endpoints en la sombra Versiones antiguas o de pruebas que siguen vivas /api/v0/ sin las validaciones nuevas Inventario de endpoints; retirada explícita de versiones
Consumo inseguro de APIs de terceros Se confía ciegamente en la respuesta del tercero Aceptar un webhook de la pasarela sin verificar la firma Verificación de firma; validar también lo que viene de un socio

El punto que más se subestima: el abuso de lógica de negocio. No hay ninguna vulnerabilidad técnica en reservar y cancelar 200 citas; el sistema hace exactamente lo que se le pidió. Ningún WAF, ningún análisis estático y ningún escáner lo detectan, porque no hay nada malformado. Solo lo detectan límites de negocio pensados por alguien que conoce el dominio, y alertas sobre métricas de negocio (tasa de cancelación anómala en un tenant). Es la razón por la que la seguridad no puede delegarse por completo en herramientas.


  1. Un ataque encadenado sobre Nimbus, paso a paso

Los ataques reales no son una técnica: son una cadena en la que cada eslabón aprovecha el anterior. Reconstruyamos uno completo, conectando lo visto en la lección.

flowchart TB
    A["1. RECONOCIMIENTO\nOSINT: se localiza en un repositorio\npublico un fichero .env antiguo\ncon un token de API sin caducidad\n(hallazgo de 01-04)"]
    A --> B["2. VALIDACION\nEl token sigue activo: nadie\nlo roto ni le puso caducidad"]
    B --> C["3. ACCESO INICIAL\nPeticiones a la API de produccion\ncon un token legitimo:\nsin exploit, sin alarma"]
    C --> D["4. DESCUBRIMIENTO\nEnumeracion de endpoints\ny prueba del IDOR en\n/api/v1/reservas/{id}"]
    D --> E["5. RECOLECCION\nExportacion en lotes pequenios\ndurante tres semanas\npara no llamar la atencion"]
    E --> F["6. ESCALADA\nEn los datos aparece una URL\nfirmada del bucket de adjuntos;\nse intenta ampliar el acceso"]
    F --> G["7. EXFILTRACION\nSalida de datos de citas\nhacia un almacenamiento externo"]
    G --> H["8. IMPACTO\nDatos que revelan salud fuera\nde control + notificacion de brecha\n+ perdida de confianza"]

Dónde se rompe esta cadena, eslabón por eslabón:

Eslabón Control que lo rompe Dónde se estudia
1 Análisis de secretos en el CI y en el histórico del repositorio 02-04
2 Caducidad obligatoria de todos los tokens y rotación periódica 02-05
3 Ámbito mínimo del token y restricción de origen; alerta por uso desde IP nueva 02-05, 05-02
4 Filtro por tenant_id y RLS (ya corregido en 01-01 y 01-03); IDs no adivinables 02-04
5 Límite de tasa por token y alerta por volumen de exportación anómalo 02-04, 05-02
6 URLs firmadas de 120 s (ya implantado) y bucket privado Ya resuelto en 01-03
7 Filtrado de salida y detección de transferencias anómalas 05-02, 05-04
8 Cifrado y minimización reducen el impacto residual Módulo 3, 06-03

La observación decisiva: de los ocho eslabones, cinco se rompen con medidas que cuestan poco o nada —caducidad de tokens, ámbito mínimo, filtro de tenant, límite de tasa y una alerta de volumen—. Ninguna requiere comprar un producto. Y el eslabón 6 ya estaba roto de antemano gracias a una decisión de diseño tomada en 01-03: las URLs firmadas de vida corta. Eso es exactamente lo que significa «defensa en profundidad» cuando funciona.


  1. Tabla resumen: ataque, propiedad afectada, detección y defensa

Ataque Propiedad CIA afectada Señal de detección Defensa principal Lección
OSINT — (habilitador) No detectable en tus sistemas Reducir lo publicado; rotar secretos expuestos 01-04, 02-04
Escaneo — (habilitador) Ráfagas de 404 a rutas inexistentes Reducir superficie; alerta si una ruta sensible deja de dar 404 02-04, 05-01
Sniffing / MitM Confidencialidad, integridad Certificado inesperado; ARP anómalo TLS con verificación estricta; cifrado también en interno 02-04, módulo 3
DNS spoofing Autenticidad Resoluciones fuera de rango esperado DNSSEC, resolvedores controlados, MFA en la cuenta de DNS 05-04
DDoS volumétrico Disponibilidad Saturación de red con tráfico masivo Servicio anti-DDoS del proveedor; CDN 02-04
DDoS aplicativo Disponibilidad Latencia alta con poco tráfico; un endpoint concentra el coste Paginación, límites de negocio, límite de tasa 02-04, 05-05
Fuerza bruta / diccionario Confidencialidad Muchos fallos en una cuenta Límite de intentos; MFA 02-05
Password spraying Confidencialidad Pocos fallos en muchas cuentas, misma IP Detección por origen; MFA; contraseñas no filtradas 02-05
Credential stuffing Confidencialidad Éxitos aislados desde IPs distribuidas MFA; comprobación contra listas filtradas 02-05
Robo de sesión / pass-the-cookie Confidencialidad, autenticidad Token usado desde contexto nuevo sin autenticación previa Cookies seguras, vida corta, revocación, vinculación al contexto 02-05
IDOR / control de acceso roto Confidencialidad, integridad Accesos donde el tenant del recurso ≠ tenant del usuario Autorización por recurso; RLS; IDs no adivinables 01-01, 01-03
Inyección SQL Todas Errores de sintaxis SQL en logs; consultas anómalas Consultas parametrizadas + rol de mínimo privilegio 02-04, 05-05
XSS Confidencialidad, integridad Informes de CSP; contenido con etiquetas en campos de texto Codificación en salida + CSP + HttpOnly 02-04, 05-05
CSRF Integridad Acciones con Origin externo SameSite; token anti-CSRF 02-04, 02-05
SSRF Confidencialidad Peticiones salientes a rangos internos Lista blanca, bloqueo de rangos internos, proxy de salida 02-04, 05-07
Deserialización insegura Todas Ejecución inesperada tras recibir datos JSON + validación de esquema; nunca pickle externo 05-05
Exposición de datos sensibles Confidencialidad Respuestas con campos de más; trazas en errores Esquemas de salida explícitos; errores genéricos 02-04
Dependencias vulnerables Todas Informe de análisis de composición Análisis en el CI + plazo de corrección + SBOM 02-04
Ransomware Disponibilidad, confidencialidad Movimiento lateral, borrado de copias, cifrado masivo Copias inmutables, MFA, segmentación, EDR 02-04, 04-06
Cadena de suministro Todas Comportamiento nuevo tras una actualización Versiones fijadas por hash, CI sin secretos de producción, SBOM 02-04, 04-04
Abuso de lógica de negocio Integridad, disponibilidad Métricas de negocio anómalas por tenant Límites de negocio; alertas sobre métricas propias 02-04
Enumeración Confidencialidad Recorrido secuencial de IDs; muchos 404 autenticados UUID, respuestas uniformes, límite de tasa 02-05

Errores Comunes y Consejos

Errores comunes:

  • Estudiar los ataques como una lista de nombres. Lo que hay que retener de cada uno son tres cosas: qué propiedad rompe, qué señal deja y qué control lo neutraliza. El nombre es lo de menos.
  • Creer que HTTPS resuelve los ataques de red. Resuelve la escucha del contenido externo. No cubre el tráfico interno en claro, ni los metadatos, ni al usuario que acepta un certificado inválido.
  • Confiar el filtrado de entradas a una lista negra. Intentar prohibir ', <script> o UNION siempre se puede eludir con codificaciones. La defensa correcta es estructural: parametrizar consultas y codificar en la salida.
  • Pensar que un WAF sustituye al código correcto. Un WAF es una capa útil que da tiempo; no arregla una inyección ni un control de acceso roto.
  • Ignorar el DDoS aplicativo. Se invierte en protección volumétrica mientras un solo endpoint sin paginación permite tumbar el servicio desde un portátil.
  • Tratar el ransomware como un problema de antivirus. Es una operación de semanas cuyo desenlace lo deciden las copias inmutables, el MFA y la segmentación, no la firma del fichero final.
  • Suponer que las dependencias son problema de otros. El 95 % del código que corre en producción no lo ha escrito Iván, y las vulnerabilidades de ese 95 % son igual de explotables.
  • Olvidar la lógica de negocio. Ninguna herramienta detecta el abuso de una funcionalidad que funciona exactamente como se diseñó.

Consejos:

  • Cuando revises un endpoint, hazte cuatro preguntas en este orden: ¿quién eres? (autenticación), ¿es tuyo? (autorización), ¿es válido lo que envías? (validación) y ¿cuánto cuesta esto? (límites).
  • Busca en el repositorio de Nimbus estos cinco patrones: verify=False, SELECT *, concatenación de cadenas en SQL, innerHTML y pickle.loads. Encontrarás la mayor parte del riesgo de aplicación en menos de una hora.
  • Para cada ataque nuevo que aprendas, escribe la consulta de log que lo detectaría. Si no puedes escribirla, no lo detectarás.
  • Practica el ejercicio de la cadena: coge cualquier incidente y pregúntate en qué eslabón habría sido más barato romperlo. Casi siempre es uno de los primeros.
  • Recuerda que la mayoría de los ataques con éxito no usan exploits: usan credenciales válidas y configuraciones olvidadas.

Ejercicios

Ejercicio 1 — Clasificar y responder a seis señales

Para cada uno de estos extractos o hechos observados en Nimbus, indica: qué ataque sugiere, qué propiedad CIA está en juego, qué comprobarías a continuación y qué defensa corresponde.

(a) 2026-04-02T03:22:10Z auth FAIL usuario=ivan@... ip=192.0.2.9
    2026-04-02T03:22:14Z auth FAIL usuario=lucia@... ip=192.0.2.9
    2026-04-02T03:22:18Z auth FAIL usuario=sara@... ip=192.0.2.9
    2026-04-02T03:22:22Z auth FAIL usuario=marta@... ip=192.0.2.9
(b) ERROR sqlalchemy.exc.ProgrammingError: syntax error at or near "OR"
    LINE 1: ...clientes_finales WHERE tenant_id = 42 AND nombre LIKE '%' OR '1'='1...
(c) GET /api/v1/reservas/10001  200
    GET /api/v1/reservas/10002  200
    GET /api/v1/reservas/10003  200
    GET /api/v1/reservas/10004  200
    ... 4.800 peticiones en 40 minutos, mismo token, respuestas 200
(d) La API de Nimbus registra 34 peticiones a /api/v1/informes/ocupacion
    en 90 segundos. La CPU de PostgreSQL esta al 100 %. El trafico entrante
    total es de 0,4 Mbps.
(e) El servidor de la API ha realizado peticiones salientes a
    http://169.254.169.254/latest/meta-data/iam/security-credentials/
    justo despues de que una clinica configurase la URL de su logotipo.
(f) Un despliegue rutinario cambia una accion del flujo de GitHub Actions
    referenciada como @v3. Tras el despliegue, el flujo hace una peticion
    saliente a un dominio nunca visto antes.

Ejercicio 2 — Corregir tres fragmentos vulnerables

Los siguientes fragmentos están en el código de Nimbus. Para cada uno: identifica la vulnerabilidad, explica cómo se explotaría conceptualmente, reescribe el código corregido y añade una segunda capa de defensa independiente del código.

# (a) Buscador de clientes del panel de la clinica
@app.get("/api/v1/clientes/buscar")
def buscar(q: str, usuario = Depends(usuario_actual)):
    sql = f"SELECT * FROM clientes_finales WHERE tenant_id={usuario.tenant_id} " \
          f"AND nombre LIKE '%{q}%'"
    return [dict(f) for f in db.execute(sql).fetchall()]
# (b) Descarga del adjunto de una reserva
@app.get("/api/v1/adjuntos/{clave}")
def adjunto(clave: str, usuario = Depends(usuario_actual)):
    return storage.descargar(f"nimbus-adjuntos-prod/{clave}")
# (c) Recepcion del webhook de la pasarela de pago
@app.post("/api/v1/webhooks/pago")
def webhook(evento: dict):
    if evento["estado"] == "pagado":
        db.execute("UPDATE facturas SET estado='pagada' WHERE id=:id",
                   {"id": evento["factura_id"]})
    return {"ok": True}

Ejercicio 3 — Reconstruir y romper una cadena

Nimbus sufre el siguiente incidente:

  1. Rubén recibe un correo con un fichero adjunto que dice ser una incidencia de una clínica cliente. Lo abre.
  2. Su portátil ejecuta un componente que establece un canal saliente con el atacante.
  3. El atacante extrae del navegador de Rubén la cookie de sesión del panel interno de soporte, que no caduca hasta pasados 30 días.
  4. Con esa cookie entra en el panel de soporte sin pasar por el inicio de sesión ni por el MFA.
  5. Desde el panel de soporte, que permite «ver como cliente», accede a los datos de 40 clínicas.
  6. Exporta un CSV con 120.000 registros de citas usando la funcionalidad de exportación del panel.
  7. Tres días después, la exportación aparece publicada en un foro.

Se pide: (a) asigna cada paso a su fase de la Kill Chain y a la técnica del catálogo de esta lección; (b) indica dos controles por paso que lo habrían roto; (c) explica por qué el MFA no protegió en el paso 4 y qué medida concreta sí lo habría hecho; (d) di en qué paso la detección habría sido más fácil y qué señal exacta habrías buscado.


Soluciones

Solución 1

Caso Ataque Propiedad Qué comprobar Defensa
(a) Password spraying: un intento por cuenta, muchas cuentas, misma IP, madrugada Confidencialidad Si hubo algún auth OK desde esa IP; si esas cuentas tienen MFA; si la IP aparece en otros logs MFA obligatorio; detección por origen (no por cuenta); bloqueo progresivo de la IP; contraseñas comprobadas contra listas filtradas
(b) Inyección SQL: el error revela que la entrada del usuario alteró la sintaxis Todas Qué endpoint lo generó, qué parámetro, si alguna variante tuvo éxito (respuestas 200 con volumen anómalo), qué permisos tiene el rol de BD Parametrizar la consulta; rol nimbus_api sin DELETE ni DDL; errores genéricos hacia el cliente; alerta sobre errores de sintaxis SQL
(c) Enumeración de identificadores con IDOR: IDs secuenciales devolviendo 200 Confidencialidad Si los IDs pertenecen a varios tenants (si sí, el control de acceso está roto); qué token es y a quién pertenece Filtro por tenant_id y RLS; identificadores UUID; límite de tasa por token; alerta por número de recursos distintos accedidos por sesión
(d) DDoS aplicativo: coste altísimo con tráfico ridículo Disponibilidad Qué rango de fechas piden esas peticiones; si vienen de un solo token o tenant Paginación obligatoria, límite de rango, límite de tasa, tiempo máximo de consulta en PostgreSQL
(e) SSRF apuntando al servicio de metadatos de la instancia; objetivo: credenciales temporales de A-05 Confidencialidad (grave: puede escalar a todo) Si la petición tuvo éxito y si esas credenciales se han usado desde fuera; revisar el registro de la cuenta cloud Validación de URL con bloqueo de rangos internos y sin redirecciones; proxy de salida con lista blanca; exigir la versión del servicio de metadatos que requiere token
(f) Cadena de suministro vía acción de CI referenciada por etiqueta móvil Todas Qué secretos eran accesibles en ese flujo; qué se envió al dominio nuevo; rotar todo lo expuesto de inmediato Fijar acciones por hash de commit; flujo sin acceso a secretos de producción; credenciales efímeras; filtrado de salida del ejecutor

Solución 2

(a) Buscador de clientes. Vulnerabilidades: inyección SQL por concatenación y exposición excesiva por SELECT *. Explotación conceptual: un valor de q con una comilla cierra la cadena y permite añadir condiciones, convirtiendo el filtro en una condición siempre cierta y devolviendo el contenido completo de la tabla accesible al rol.

@app.get("/api/v1/clientes/buscar")
@limitador.limit("30/minute")
def buscar(q: str, usuario = Depends(usuario_actual)):
    if len(q) < 3:
        raise HTTPException(400, "Introduce al menos 3 caracteres")
    filas = db.execute(
        """SELECT id, nombre, email
             FROM clientes_finales
            WHERE tenant_id = :t AND nombre ILIKE :patron
            ORDER BY nombre LIMIT 50""",
        {"t": usuario.tenant_id, "patron": f"%{q}%"}).fetchall()
    return [dict(f) for f in filas]

Segunda capa independiente del código: el rol nimbus_api sin DELETE ni DDL (01-03) y la RLS por tenant_id, que impide devolver filas ajenas aunque la consulta se manipule. El LIMIT 50 añade además protección contra la extracción masiva y el mínimo de 3 caracteres evita el buscador vacío que devuelve toda la base.

(b) Descarga de adjuntos. Vulnerabilidad: control de acceso roto — se descarga cualquier clave del bucket sin comprobar que la reserva pertenece al tenant del usuario. Además puede permitir recorrido de rutas si la clave contiene ../. Explotación conceptual: conocida o adivinada una clave, cualquier usuario autenticado descarga el adjunto de otra clínica: un parte médico escaneado.

@app.get("/api/v1/adjuntos/{reserva_id}")
def adjunto(reserva_id: int, usuario = Depends(usuario_actual)):
    fila = db.execute(
        "SELECT adjunto_clave FROM reservas WHERE id=:id AND tenant_id=:t",
        {"id": reserva_id, "t": usuario.tenant_id}).fetchone()
    if fila is None or fila.adjunto_clave is None:
        raise HTTPException(404)
    url = generar_url_firmada("nimbus-adjuntos-prod", fila.adjunto_clave,
                              caducidad_segundos=120)
    registrar_auditoria(usuario.id, "descarga_adjunto", reserva_id)
    return {"url": url}

Cambios clave: el cliente ya no elige la clave del objeto, sino el identificador de su reserva; la clave real se obtiene de la base de datos tras verificar la propiedad; se entrega una URL firmada de 120 s en vez de servir el objeto; y se registra el acceso. Segunda capa: el bucket es privado, la política impide el acceso anónimo y la API no tiene permiso de borrado sobre él (01-03).

(c) Webhook de pago. Vulnerabilidades: suplantación (no se verifica que el mensaje venga de la pasarela) y manipulación de la integridad (cualquiera puede marcar facturas como pagadas). Explotación conceptual: una petición HTTP a ese endpoint con un JSON adecuado convierte una factura impagada en pagada. Es la amenaza S del flujo F8 del DFD de 01-04.

import hmac, hashlib
from fastapi import Request, HTTPException

@app.post("/api/v1/webhooks/pago")
async def webhook(peticion: Request):
    cuerpo = await peticion.body()
    firma_recibida = peticion.headers.get("X-Pasarela-Firma", "")
    firma_esperada = hmac.new(SECRETO_WEBHOOK, cuerpo, hashlib.sha256).hexdigest()

    # Comparacion en tiempo constante: evita deducir la firma midiendo tiempos
    if not hmac.compare_digest(firma_recibida, firma_esperada):
        registrar_auditoria(None, "webhook_firma_invalida", peticion.client.host)
        raise HTTPException(401)

    evento = json.loads(cuerpo)
    if evento["tipo"] != "pago.confirmado":
        return {"ok": True}
    # Idempotencia: el mismo evento puede llegar dos veces
    db.execute("""INSERT INTO eventos_pasarela (id_evento) VALUES (:e)
                  ON CONFLICT DO NOTHING""", {"e": evento["id"]})
    ...

Segunda capa: confirmar el estado contra la API de la pasarela antes de dar por buena una operación económica, y restringir el origen a las IPs publicadas por el proveedor. El HMAC se explica en detalle en 03-04.

Solución 3

(a) y (b) Cadena, técnicas y controles:

Paso Fase Técnica Control 1 Control 2
1. Correo con adjunto Entrega Phishing con adjunto (02-03) Filtrado de correo con análisis de adjuntos y aislamiento Banner de correo externo + formación y canal de reporte
2. Ejecución y canal saliente Explotación + Mando y control Ejecución en el endpoint EDR con detección de comportamiento Filtrado de salida: solo destinos permitidos desde portátiles
3. Robo de cookie del navegador Credential Access Pass-the-cookie Sesiones de vida corta (horas, no 30 días) Cifrado del almacén del navegador + política de sesión reautenticada
4. Uso de la cookie Acceso inicial a la aplicación Robo de sesión Vinculación de la sesión al contexto (IP/dispositivo) y reautenticación ante cambio Alerta por sesión activa sin evento de autenticación previo
5. «Ver como cliente» en el panel de soporte Escalada / Privilege Escalation Función administrativa sin control Reautenticación y justificación obligatoria para suplantar; registro nominal Aprobación de un segundo operador y acceso just-in-time (02-05)
6. Exportación de 120.000 registros Recolección Exportación masiva Límite de volumen por exportación y por día Alerta inmediata sobre cualquier exportación por encima de un umbral
7. Publicación Impacto Divulgación Cifrado y minimización reducen el daño residual Plan de respuesta y notificación (04-05, 06-03)

(c) Por qué el MFA no protegió. El MFA se verifica en el momento del inicio de sesión y produce una sesión. Si el atacante roba la sesión ya emitida, no vuelve a pasar por la autenticación: entra directamente con un artefacto válido. Esa es la esencia del pass-the-cookie y la razón por la que «tenemos MFA» no es una respuesta completa.

Lo que sí habría funcionado, en orden de eficacia:

  1. Vincular la sesión a un contexto (dispositivo, IP o certificado de cliente), de modo que una cookie usada desde otro equipo se invalide.
  2. Sesiones cortas con renovación silenciosa: una cookie de 30 días es una credencial permanente disfrazada. Horas, no semanas.
  3. Reautenticación para operaciones sensibles: suplantar a un cliente o exportar datos exige volver a presentar el segundo factor, aunque la sesión sea válida.
  4. Enlace criptográfico del token al cliente (token binding / claves ligadas al dispositivo), que es la línea a la que apuntan las passkeys (02-05).

(d) Dónde detectar y qué señal buscar. El punto más fácil es el paso 6: una exportación de 120.000 registros que abarca 40 tenants distintos es un evento cuantitativamente único en la operación normal de Nimbus. La consulta de detección es directa sobre la tabla de auditoría append-only de 01-01:

-- Exportaciones anomalas: mas de 5.000 registros o mas de 3 tenants
-- distintos tocados por la misma sesion en una hora
SELECT sesion_id, usuario_id,
       COUNT(DISTINCT tenant_id) AS tenants,
       SUM(num_registros)        AS registros
  FROM auditoria_accesos
 WHERE accion = 'exportacion'
   AND ts > now() - interval '1 hour'
 GROUP BY sesion_id, usuario_id
HAVING SUM(num_registros) > 5000
    OR COUNT(DISTINCT tenant_id) > 3;

Lo importante de esta consulta no es su SQL, sino su premisa: solo funciona si la auditoría registra sesion_id, tenant_id y num_registros. La detección no se improvisa cuando ocurre el incidente; se diseña cuando se escribe el log. Una segunda señal útil, más temprana, es el paso 4: una sesión activa sin evento de autenticación previo en el log es una anomalía que no tiene explicación legítima.


Conclusión

Ya conoces al adversario en acción. Has recorrido los ataques ordenados por las fases de la cadena: el reconocimiento que ocurre sin tocar tus sistemas —OSINT en certificados, repositorios y ofertas de empleo— y el escaneo que sí deja rastro, con la lección práctica de que la alerta útil no es «nos escanean» sino «una ruta sensible ha dejado de devolver 404». Has visto los ataques a la red —escucha, ARP y DNS spoofing, interposición— y por qué siguen importando en un mundo con HTTPS: por el tráfico interno en claro, por los metadatos y por el verify=False que alguien dejó en producción. Has distinguido la denegación de servicio volumétrica, que se compra defensa, de la aplicativa, que se programa, y has comprobado que veinte peticiones bien elegidas hacen más daño que una botnet cuando falta la paginación.

En credenciales has aprendido a distinguir fuerza bruta, diccionario, password spraying —diseñado precisamente para eludir el bloqueo por cuenta— credential stuffing y robo de sesión, con la idea incómoda de que una credencial válida no dispara ninguna alarma. En aplicaciones web has recorrido el Top 10 de OWASP sobre la API de Nimbus: control de acceso roto e IDOR, inyección SQL y por qué la parametrización funciona donde el escapado manual falla, XSS con sus tres capas de defensa, CSRF, SSRF y su ruta hacia las credenciales de la cuenta cloud, deserialización insegura, exposición de datos y dependencias vulnerables. Has entendido el ransomware moderno como una operación de tres semanas cuyo desenlace lo deciden las copias inmutables, no el antivirus; los ataques a la cadena de suministro, donde todo parece legítimo; y los ataques a APIs, con el abuso de lógica de negocio que ninguna herramienta detecta. Y has reconstruido una cadena completa sobre Nimbus, comprobando que cinco de sus ocho eslabones se rompen con medidas que no cuestan dinero.

Falta el vector que encabeza casi todas las estadísticas y que no aparece en ninguna de las técnicas anteriores: la persona. En la siguiente lección, Ingeniería Social y Phishing (02-03), veremos por qué el factor humano es la vía dominante, qué principios de influencia explotan los atacantes, el catálogo completo de técnicas —del phishing masivo al fraude del CEO dirigido a Sara, el vishing, el quishing y el consent phishing—, cómo desmontar un correo malicioso indicador por indicador leyendo sus cabeceras, y las defensas técnicas y de proceso que lo contrarrestan, empezando por SPF, DKIM y DMARC.

Curso de Fundamentos de Seguridad Informática

Módulo 1: Introducción a la Seguridad Informática

Módulo 2: Ciberseguridad

Módulo 3: Criptografía

Módulo 4: Gestión de Riesgos y Medidas de Protección

Módulo 5: Herramientas y Técnicas de Seguridad

Módulo 6: Buenas Prácticas y Normativas

Módulo 7: Proyecto Final

© Copyright 2026. Todos los derechos reservados