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
- Cómo se organiza este catálogo y una advertencia previa
- Fase 1 — Reconocimiento: OSINT y escaneo
- Ataques a la red: escucha, suplantación e interposición
- Denegación de servicio: volumétrica y aplicativa
- Ataques a credenciales y sesiones
- Ataques a aplicaciones web: el Top 10 de OWASP como marco
- Malware en acción: ransomware moderno y doble extorsión
- Ataques a la cadena de suministro
- Ataques específicos a APIs
- Un ataque encadenado sobre Nimbus, paso a paso
- Tabla resumen: ataque, propiedad afectada, detección y defensa
- 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.
- 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 153Có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.phpy/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.
- 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:
- 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.
- 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.
- 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 defectoPor 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 |
- 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:
- Validación de tipos (
desde: dateen vez destr): rechaza entradas malformadas antes de llegar a la base de datos. - 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.
- Paginación con
LIMIT: acota la memoria y el tiempo de cada petición, pase lo que pase. - 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.
- 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.77Có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.
- 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:
- 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). 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.- 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 maximoLas 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 incluyedni,notas_internasohash_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.4Có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.
- 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:
- 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.
- 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).
- 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.
- 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.
- 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.
- 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.
- 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>oUNIONsiempre 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,innerHTMLypickle.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:
- Rubén recibe un correo con un fichero adjunto que dice ser una incidencia de una clínica cliente. Lo abre.
- Su portátil ejecuta un componente que establece un canal saliente con el atacante.
- 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.
- Con esa cookie entra en el panel de soporte sin pasar por el inicio de sesión ni por el MFA.
- Desde el panel de soporte, que permite «ver como cliente», accede a los datos de 40 clínicas.
- Exporta un CSV con 120.000 registros de citas usando la funcionalidad de exportación del panel.
- 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:
- Vincular la sesión a un contexto (dispositivo, IP o certificado de cliente), de modo que una cookie usada desde otro equipo se invalide.
- Sesiones cortas con renovación silenciosa: una cookie de 30 días es una credencial permanente disfrazada. Horas, no semanas.
- 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.
- 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
- Conceptos Básicos de Seguridad Informática
- Tipos de Amenazas y Vulnerabilidades
- Principios de la Seguridad Informática
- Activos, Superficie de Ataque y Actores de Amenaza
Módulo 2: Ciberseguridad
- Definición y Alcance de la Ciberseguridad
- Tipos de Ataques Cibernéticos
- Ingeniería Social y Phishing
- Medidas de Protección en Ciberseguridad
- Identidad, Autenticación y Control de Acceso
- Casos de Estudio de Incidentes de Ciberseguridad
Módulo 3: Criptografía
- Introducción a la Criptografía
- Criptografía Simétrica
- Criptografía Asimétrica
- Funciones Hash, HMAC y Almacenamiento de Contraseñas
- Protocolos Criptográficos
- Gestión de Claves, Certificados y PKI
- Aplicaciones de la Criptografía
Módulo 4: Gestión de Riesgos y Medidas de Protección
- Evaluación de Riesgos
- Políticas de Seguridad
- Controles de Seguridad
- Riesgo de Terceros y Cadena de Suministro
- Plan de Respuesta a Incidentes
- Recuperación ante Desastres y Continuidad de Negocio
Módulo 5: Herramientas y Técnicas de Seguridad
- Herramientas de Análisis de Vulnerabilidades
- Técnicas de Monitoreo y Detección
- Pruebas de Penetración
- Seguridad en Redes
- Seguridad en Aplicaciones
- Hardening de Sistemas y Seguridad del Endpoint
- Seguridad en la Nube y en Contenedores
Módulo 6: Buenas Prácticas y Normativas
- Buenas Prácticas en Seguridad Informática
- Normativas y Estándares de Seguridad
- Protección de Datos Personales y RGPD en la Práctica
- Cumplimiento y Auditoría
- Formación y Concienciación
- Ética, Aspectos Legales y Divulgación Responsable
