Las vulnerabilidades que aparecen de verdad
La mayoría de las filtraciones que salen en las noticias no usa técnicas sofisticadas. Usa uno de estos seis fallos, y los seis tienen corrección conocida y barata.
Existe una distancia grande entre la seguridad que aparece en las conferencias y la seguridad que te protege. La primera va de cadenas de explotación y técnicas nuevas. La segunda va de un endpoint que no comprobó si aquel usuario podía ver aquel registro.
Este artículo va de la segunda.
Antes de los fallos: lo que cambia el juego
Dos ideas que valen más que cualquier herramienta.
Superficie de ataque. Todo por donde alguien puede entrar: cada endpoint, cada parámetro, cada dependencia, cada puerto, cada persona con acceso. Reducir la superficie es la medida más eficaz y más barata que existe: un endpoint que no existe no se explota.
STRIDE, para pensar de forma sistemática: Spoofing (hacerse pasar por otro), Tampering (alterar datos), Repudiation (negar lo que hizo), Information disclosure (filtrar), Denial of service (tumbar), Elevation of privilege (volverse admin).
Pasa cualquier funcionalidad por esas seis letras y encontrarás cosas que no habrías pensado. Lleva quince minutos.
1. Fallo de autorización (IDOR)
Cambias el número en la URL (/pedidos/1042 por /pedidos/1043) y ves el pedido de otra persona.
Es el fallo más común que existe. No es sofisticado; un usuario curioso lo encuentra por accidente.
La causa raíz es casi siempre la misma: la autorización se comprobó en la pantalla, no en el servidor. El menú no lo muestra, el botón no aparece, y nadie se acordó de que la ruta acepta cualquier id.
La regla, sin excepción: toda consulta filtra por el dueño.
No es SELECT * FROM pedidos WHERE id = ?
Es SELECT * FROM pedidos WHERE id = ? AND cliente_id = ?
Dos defensas extra: row-level security en la base, si la soporta, garantiza la regla en la capa de abajo aunque alguien la olvide arriba. Y un identificador no secuencial (UUID, ULID) elimina el descubrimiento accidental: es mitigación, no solución.
El primo: mass assignment. Aceptas el cuerpo entero de la petición y lo vuelcas en el objeto. El
usuario manda "role": "admin" de propina. Defensa: lista explícita de los campos que pueden venir
del cliente.
2. Inyección
La causa raíz es siempre la misma: dato del usuario interpretado como código.
En SQL, la defensa es la consulta parametrizada. No es escapar strings: es parametrizar, siempre. La base recibe la estructura de la query y los valores por separado, y un valor nunca se vuelve comando.
Y el mismo principio vale para todo lo demás: comandos del sistema (usa la forma que recibe una lista de argumentos, nunca concatenación en una shell), plantillas (nunca renderices una plantilla que venga del usuario), LDAP, XPath.
Y hoy, el prompt de un modelo de lenguaje, que es el mismo problema con ropa nueva, con el agravante de que no existe equivalente a la consulta parametrizada. Vuelvo a eso al final.
3. XSS
Código del atacante corriendo en el navegador de la víctima, con su sesión.
Defensa en capas:
Escape en la salida, según el contexto. HTML, atributo HTML, JavaScript y URL se escapan de formas
distintas. Los frameworks modernos lo hacen por defecto, y el peligro vive en las escapatorias:
dangerouslySetInnerHTML, v-html, plantillas raw.
Content-Security-Policy. Limita de dónde puede venir un script. Una CSP bien configurada convierte un XSS explotable en un XSS inofensivo. Es la defensa con mejor retorno y la más ignorada.
4. CSRF
El navegador de la víctima hace una petición autenticada sin que ella quiera, porque la cookie viaja automáticamente.
Defensa moderna: SameSite en la cookie (Lax ya resuelve la mayor parte; Strict es más seguro y
rompe algunos flujos), más un token anti-CSRF en las operaciones que cambian estado.
Y ojo: si tu API usa un token en la cabecera Authorization en vez de una cookie, no es vulnerable a
CSRF, porque el navegador no envía esa cabecera automáticamente. Es uno de los pocos argumentos
técnicos reales a favor del token sobre la cookie.
5. SSRF
Haces que el servidor busque una URL proporcionada por el usuario, y esta apunta hacia dentro de tu red.
El objetivo clásico en la nube es el servicio de metadatos, en 169.254.169.254, que devuelve las
credenciales de la instancia. Fue exactamente así como ocurrió el caso de Capital One, en 2019, con
más de cien millones de registros expuestos.
Defensas:
→ Lista de permitidos de destinos, en vez de lista de bloqueo.
→ Bloquear rangos internos, y resolver el nombre antes de validar, porque un dominio controlado por el atacante puede apuntar a una IP interna.
→ En las máquinas de AWS, exigir IMDSv2, que usa token y no es accesible con una petición simple.
→ Timeout corto y sin seguir redirecciones automáticamente.
Dónde aparece sin que te des cuenta: generación de PDF a partir de una URL, importación de imagen por enlace, webhook configurable, previsualización de enlaces.
6. Deserialización y subida de archivos
Deserialización insegura: convertir datos del usuario en objetos puede ejecutar código, según la biblioteca y el lenguaje. Nunca deserialices un formato binario de fuente no confiable. Prefiere JSON con esquema validado.
Subida de archivos: valida el tipo por el contenido, no por la extensión ni por el Content-Type.
Guarda fuera del directorio servido por el servidor web. Sirve desde un dominio separado, para que un
HTML malicioso subido no corra en el dominio de tu aplicación. Limita el tamaño.
- Autorización (IDOR)la más comúnCambiar el id en la URL y ver el registro de otro. Toda consulta filtra por el dueño, sin excepción.
- Inyecciónel dato se vuelve códigoConsulta parametrizada, siempre. Vale para SQL, shell, plantilla, LDAP y prompt.
- XSScódigo en la víctimaEscape por contexto en la salida. Una CSP bien puesta convierte un XSS explotable en inofensivo.
- CSRFla cookie viaja solaSameSite más token anti-CSRF. Una API con token en la cabecera no es vulnerable.
- SSRFel servidor buscaLista de permitidos, bloquear rangos internos, IMDSv2. Fue el caso de Capital One.
- Deserialización y subidaobjeto y archivoNunca deserialices binario no confiable. Valida la subida por contenido y sírvela desde otro dominio.
Las tres cosas que valen más que las seis
Secretos. No van en el repositorio, ni en una variable de entorno que aparece en los logs, ni en el Dockerfile. Van en una bóveda, con rotación. Pasa un escáner tipo gitleaks en el pipeline: encuentra lo que ya se filtró. Y si se filtró, la única respuesta es rotar: borrarlo del historial de git no sirve, alguien ya lo clonó.
Cadena de suministro. Tus dependencias son código de desconocidos corriendo con tus privilegios. Fija versiones, usa lockfile, genera un inventario de componentes, escanea en busca de vulnerabilidades conocidas. El incidente de xz-utils en 2024 (un mantenedor cultivado durante dos años para insertar una puerta trasera) muestra el límite de las herramientas, pero una versión fijada y un build reproducible limitan el destrozo.
Menor privilegio. En la nube, en la base, en el contenedor. La pregunta que hay que hacer en cada revisión: si este componente se ve comprometido, ¿qué consigue hacer el atacante? Si la respuesta es "todo", el problema no es la vulnerabilidad: es el diseño.
Prompt injection: la nueva de la lista
Merece un párrafo porque cambia el cálculo de riesgo de cualquier sistema con IA.
La causa raíz es la misma que la de la inyección de SQL: instrucción y dato en el mismo canal. La diferencia es que no existe consulta parametrizada para lenguaje natural. No hay solución completa hoy.
Y la variante peligrosa es la indirecta: el contenido malicioso está en un documento que tu RAG recupera, en una página que el agente lee. El atacante nunca habló con tu sistema.
Lo que ayuda: delimitar el contenido no confiable, dar el mínimo privilegio a la herramienta, exigir confirmación humana para acciones irreversibles, validar la salida con código, y limitar el alcance de la acción.
La regla mental: trata la salida del modelo como entrada de usuario no confiable.
El ejercicio de una hora
Junta a tres personas:
10 min: dibuja el flujo de datos de la funcionalidad, marcando las fronteras de confianza.
30 min: pasa cada frontera por STRIDE, anotando todo sin filtrar.
15 min: ordena por probabilidad por impacto.
5 min: elige las tres primeras y crea tareas con dueño.
No hace falta herramienta ni consultor, y pilla más cosas que cualquier escaneo automático.
Lee esto después
- IngenieríaPaso 19Pruebas que valen lo que cuestanLa prueba no existe para demostrar que el código está bien. Existe para que puedas cambiarlo mañana sin miedo. Ese cambio de objetivo lo reorganiza todo.Leer artículo
- IngenieríaPaso 18OAuth2, OIDC y JWT sin misterioLas tres siglas más confundidas de la autenticación, qué resuelve cada una, y los errores que aparecen en casi todo el código.Leer artículo
- IngenieríaPaso 16Diagnosticar la red en diez minutosUn guion de seis comandos que convierte "debe de ser la red" en "es la capa X, en el tramo Y".Leer artículo