Cerramos el módulo 2 anunciando que en el módulo 3 recorreríamos el Top Ten categoría a categoría sobre el código real de BazarNube, convirtiendo el vocabulario en conocimiento operativo. Empezamos por donde empieza la lista de 2021: A01 – Pérdida de Control de Acceso (Broken Access Control). No es casualidad que ocupe el primer puesto: es la categoría más extendida, la que más aplicaciones afecta y la que más impacto directo tiene sobre la confidencialidad y la integridad de los datos.
El control de acceso decide qué puede hacer cada usuario autenticado. Cuando falla, un cliente normal de BazarNube puede leer los pedidos de otro cliente, un usuario sin permisos puede acceder al panel de administración, o cualquiera puede modificar recursos que no le pertenecen. En este curso lo ilustraremos con un caso muy típico: un endpoint de la API Node/Express de BazarNube que devuelve pedidos usando directamente el identificador que llega en la URL, sin comprobar si ese pedido es del usuario que pregunta.
Aviso legal y ético: todas las técnicas de ataque que se muestran son ilustrativas y con datos ficticios. Practica solo sobre sistemas propios o con autorización explícita y por escrito. Probar control de acceso sobre sistemas de terceros sin permiso es delito en la mayoría de jurisdicciones.
Contenido
- Autenticación vs. autorización: no confundir A01 con A07
- IDOR y referencias directas inseguras a objetos
- Escalada horizontal y vertical de privilegios
- Forzado de rutas y control de acceso en el cliente
- CORS mal configurado como fallo de control de acceso
- Principios de prevención: denegar por defecto, autorizar en el servidor
- Modelos de autorización: RBAC y ABAC
- Errores comunes y consejos
- Ejercicios y soluciones
- Autenticación vs. autorización
Son dos controles distintos y consecutivos:
| Concepto | Pregunta que responde | Categoría OWASP |
|---|---|---|
| Autenticación | ¿Quién eres? ¿Eres quien dices ser? | A07 (lección 03-09) |
| Autorización / Control de acceso | Una vez identificado, ¿qué puedes hacer? | A01 (esta lección) |
Un sistema puede autenticar perfectamente (login sólido, MFA, sesiones bien gestionadas) y aun así tener el control de acceso roto: sabe quién eres, pero no comprueba si puedes acceder al recurso que pides. A01 se ocupa de este segundo control.
- IDOR y referencias directas inseguras a objetos
Un IDOR (Insecure Direct Object Reference) ocurre cuando la aplicación expone una referencia a un objeto interno (un id de base de datos, un nombre de fichero) y decide el acceso solo a partir de ese valor controlado por el usuario, sin verificar la propiedad del recurso.
Código vulnerable en BazarNube
Lucía, la backend lead, escribió este endpoint para consultar el detalle de un pedido:
// routes/orders.js (API Node/Express de BazarNube) — VULNERABLE
router.get('/api/orders/:orderId', requireLogin, async (req, res) => {
// requireLogin solo comprueba que hay sesión valida (autenticacion)
const order = await db.query(
'SELECT * FROM orders WHERE id = $1',
[req.params.orderId]
);
if (order.rows.length === 0) return res.status(404).json({ error: 'No existe' });
res.json(order.rows[0]); // devuelve el pedido sea de quien sea
});El middleware requireLogin garantiza que hay una sesión iniciada, pero nadie comprueba que el pedido pertenezca al usuario de esa sesión. La consulta filtra por id, no por propietario.
Cómo se explota (ilustrativo)
La clienta Ana (usuaria 501) ve su pedido en GET /api/orders/1042. Como los ids son numéricos y correlativos, prueba a incrementarlos:
GET /api/orders/1043 -> pedido de otro cliente (nombre, direccion, importe) GET /api/orders/1044 -> otro mas
Con un simple bucle puede descargar el historial de pedidos de toda la tienda: datos personales, direcciones y líneas de compra. Es una fuga masiva sin necesidad de "hackear" nada; basta con cambiar un número.
Código corregido
La regla es: el servidor debe atar cada acceso a la identidad de la sesión, no al identificador que envía el cliente.
// routes/orders.js — SEGURO
router.get('/api/orders/:orderId', requireLogin, async (req, res) => {
const order = await db.query(
// filtramos TAMBIEN por el propietario tomado de la sesion del servidor
'SELECT * FROM orders WHERE id = $1 AND user_id = $2',
[req.params.orderId, req.session.userId]
);
// Devolvemos 404 (no 403) para no revelar que el id existe pero es de otro
if (order.rows.length === 0) return res.status(404).json({ error: 'No existe' });
res.json(order.rows[0]);
});Detalles clave:
req.session.userIdprocede del servidor (de la sesión), nunca de un parámetro que el cliente pueda manipular.- La condición
AND user_id = $2convierte la comprobación de autorización en parte de la propia consulta: si el pedido no es tuyo, simplemente no existe para ti. - Responder
404en lugar de403evita filtrar la existencia de recursos ajenos (un pequeño refuerzo frente a la enumeración).
Como refuerzo adicional, muchos equipos sustituyen los ids secuenciales por UUID o identificadores no adivinables. Ojo: esto dificulta la enumeración, pero no sustituye a la comprobación de propiedad. Un UUID filtrado (en un log, en un email) seguiría siendo accesible. La defensa real es la comprobación en el servidor.
- Escalada horizontal y vertical de privilegios
- Escalada horizontal: accedes a recursos de otro usuario del mismo nivel. El IDOR de arriba es exactamente esto: Ana leyendo pedidos de otros clientes.
- Escalada vertical: obtienes privilegios de un rol superior. Por ejemplo, un cliente que invoca un endpoint de administración.
Ejemplo vertical en BazarNube
// routes/admin.js — VULNERABLE: solo comprueba login, no el rol
router.post('/api/admin/products/:id/price', requireLogin, async (req, res) => {
await db.query('UPDATE products SET price = $1 WHERE id = $2',
[req.body.price, req.params.id]);
res.json({ ok: true });
});Cualquier cliente autenticado podría cambiar precios. La corrección exige comprobar el rol en el servidor:
// middleware/authorize.js
function requireRole(...roles) {
return (req, res, next) => {
if (!roles.includes(req.session.role)) {
return res.status(403).json({ error: 'Prohibido' });
}
next();
};
}
// routes/admin.js — SEGURO
router.post('/api/admin/products/:id/price',
requireLogin, requireRole('admin'), async (req, res) => { /* ... */ });El rol se toma de req.session.role, establecido en el login a partir de la base de datos, nunca de un campo enviado por el cliente (como un role en el cuerpo o en una cookie manipulable).
- Forzado de rutas y control de acceso en el cliente
Un error frecuente en SPAs como el front React de BazarNube es ocultar botones o rutas de administración en el cliente y creer que eso protege. No protege: el navegador es territorio del atacante.
// AdminLink.jsx — esto es UX, NO seguridad
{user.role === 'admin' && <Link to="/admin">Panel</Link>}Ocultar el enlace mejora la experiencia, pero cualquiera puede navegar directamente a /admin o, más importante, llamar a POST /api/admin/... con curl. Toda decisión de autorización debe repetirse y hacerse cumplir en el servidor. El cliente puede reflejar permisos; el servidor debe imponerlos.
El forzado de rutas (forced browsing) consiste precisamente en pedir directamente URLs no enlazadas (/api/admin, /backup.zip, /api/internal/metrics) confiando en que estén desprotegidas. Se previene aplicando el mismo control de acceso a todos los endpoints, incluidos los que no aparecen en la interfaz.
- CORS mal configurado
CORS (Cross-Origin Resource Sharing) controla qué orígenes web pueden leer respuestas de tu API desde el navegador. Una configuración laxa puede convertirse en un fallo de control de acceso.
// VULNERABLE: refleja cualquier origen y ademas permite credenciales
app.use(cors({
origin: (o, cb) => cb(null, true), // acepta TODOS los origenes
credentials: true // ...enviando cookies de sesion
}));Reflejar el Origin del atacante junto con credentials: true permite que un sitio malicioso haga peticiones autenticadas a la API de BazarNube en nombre de la víctima y lea las respuestas. La corrección es una lista blanca explícita:
// SEGURO
const allowed = ['https://bazarnube.com', 'https://app.bazarnube.com'];
app.use(cors({
origin: (o, cb) => cb(null, !o || allowed.includes(o)),
credentials: true
}));Nunca uses origin: '*' combinado con credenciales (el propio navegador lo bloquea, pero reflejar el origen es el equivalente peligroso).
- Principios de prevención
flowchart TD
A[Peticion entrante] --> B{Sesion valida?}
B -- No --> X[401 No autenticado]
B -- Si --> C{Autorizado para este recurso?}
C -- No --> Y[403/404 Denegado por defecto]
C -- Si --> D[Ejecutar accion]
Las reglas de oro de A01:
- Denegar por defecto. Todo endpoint parte de "prohibido" y solo se permite lo explícitamente autorizado. Nada queda abierto por olvido.
- Autorizar siempre en el servidor, con identidad tomada de la sesión, no de parámetros del cliente.
- Centralizar la lógica de autorización en middlewares/servicios reutilizables, no dispersa y copiada endpoint a endpoint (donde es fácil olvidarla).
- Comprobar propiedad del recurso en cada acceso a objetos (evita IDOR).
- Registrar los fallos de control de acceso y alertar ante patrones de abuso (enlaza con A09, lección 03-11).
- Modelos de autorización: RBAC y ABAC
| Modelo | Idea | Ejemplo en BazarNube | Cuándo usarlo |
|---|---|---|---|
| RBAC (basado en roles) | El permiso depende del rol del usuario | admin, soporte, cliente |
Reglas estables y por rol |
| ABAC (basado en atributos) | El permiso depende de atributos de usuario, recurso y contexto | "un vendedor solo edita productos de su tienda" | Reglas finas, dependientes del dato |
BazarNube combina ambos: RBAC para separar cliente/soporte/admin, y una capa ABAC para "cada vendedor gestiona solo lo suyo". El IDOR de la sección 2 es, en el fondo, una regla ABAC (pedido.user_id == sesion.userId) que faltaba.
Errores Comunes y Consejos
- Confiar en el cliente. Ocultar botones no es autorizar. Repite la comprobación en el servidor, siempre.
- Usar el id del cliente como fuente de identidad. Toma el
userId/rolede la sesión del servidor, nunca del cuerpo, la query o una cookie no firmada. - Autorización copiada y pegada. Si cada endpoint reimplementa la comprobación, alguno se olvidará. Centraliza en middlewares.
- Suponer que un UUID protege. Dificulta enumerar, no autoriza. Mantén la comprobación de propiedad.
- Olvidar los métodos "secundarios". Proteges
GETpero dejasPUT/DELETEabiertos. Cubre todos los verbos y todos los endpoints, incluidos los internos. - Consejo: escribe pruebas automáticas de autorización ("el usuario A no puede leer el pedido de B") y ejecútalas en CI. Los IDOR se detectan muy bien con tests.
Ejercicios
Ejercicio 1. El siguiente endpoint de BazarNube permite descargar una factura. Identifica la vulnerabilidad y corrígela.
router.get('/api/invoices/:id/pdf', requireLogin, async (req, res) => {
const inv = await db.query('SELECT pdf_path FROM invoices WHERE id = $1',
[req.params.id]);
res.download(inv.rows[0].pdf_path);
});Ejercicio 2. Marc propone "arreglar" el IDOR de pedidos cambiando los ids numéricos por UUID y no tocar la consulta. ¿Es suficiente? Justifica.
Ejercicio 3. Clasifica cada escenario como escalada horizontal o vertical: a) Un cliente accede al carrito de otro cliente. b) Un usuario de soporte ejecuta una acción reservada a administración. c) Un vendedor edita un producto de otra tienda.
Soluciones
Solución 1. Es un IDOR: se descarga cualquier factura por id sin comprobar propiedad, y además no valida que la fila exista (fallaría con undefined). Corrección:
router.get('/api/invoices/:id/pdf', requireLogin, async (req, res) => {
const inv = await db.query(
'SELECT pdf_path FROM invoices WHERE id = $1 AND user_id = $2',
[req.params.id, req.session.userId]);
if (inv.rows.length === 0) return res.status(404).json({ error: 'No existe' });
res.download(inv.rows[0].pdf_path);
});Solución 2. No es suficiente. El UUID solo dificulta adivinar ids, pero si uno se filtra (email, log, historial del navegador compartido) el acceso sigue abierto porque no hay comprobación de propiedad. La defensa correcta es añadir AND user_id = $2. El UUID es una capa complementaria, no la principal.
Solución 3. a) horizontal; b) vertical; c) horizontal (mismo nivel de rol, distinto propietario del recurso; es una regla ABAC).
Conclusión
La pérdida de control de acceso encabeza el Top Ten porque es fácil de introducir (basta olvidar una comprobación) y muy dañina (fuga o manipulación de datos ajenos). Las claves que anotamos en el backlog de BazarNube: denegar por defecto, tomar la identidad de la sesión del servidor, comprobar propiedad y rol en cada acceso, centralizar la autorización y no confiar nunca en el cliente.
Entrada de backlog — A01: corregido el IDOR en GET /api/orders/:orderId (añadido filtro por user_id), añadido requireRole('admin') a los endpoints de administración y sustituido el CORS reflejado por lista blanca. Pendiente: tests de autorización en CI.
Una vez que sabemos que solo la persona adecuada accede a un dato, surge la siguiente pregunta: cuando ese dato viaja o se guarda, ¿está protegido? Eso nos lleva a la siguiente lección, A02:2021 – Fallos Criptográficos y Exposición de Datos Sensibles, donde veremos cómo BazarNube protege contraseñas, tarjetas y tráfico.
Curso de OWASP: Directrices y Estándares para la Seguridad en Aplicaciones Web
Módulo 1: Introducción a OWASP
Módulo 2: Principales Proyectos de OWASP
- OWASP Top Ten
- OWASP ASVS (Application Security Verification Standard)
- OWASP SAMM (Software Assurance Maturity Model)
- OWASP ZAP (Zed Attack Proxy)
- Otros Proyectos Clave: WSTG, Cheat Sheets y Dependency-Check
Módulo 3: OWASP Top Ten 2021 en Profundidad
- A01:2021 – Pérdida de Control de Acceso
- A02:2021 – Fallos Criptográficos y Exposición de Datos Sensibles
- A03:2021 – Inyección
- Cross-Site Scripting (XSS) en Profundidad
- A04:2021 – Diseño Inseguro
- A05:2021 – Configuración de Seguridad Incorrecta
- Entidades Externas XML (XXE)
- A06:2021 – Componentes Vulnerables y Desactualizados
- A07:2021 – Fallos de Identificación y Autenticación
- A08:2021 – Fallos de Integridad de Software y Datos (Deserialización Insegura)
- A09:2021 – Fallos de Registro y Monitorización
- A10:2021 – Server-Side Request Forgery (SSRF)
Módulo 4: OWASP ASVS (Application Security Verification Standard)
- Introducción a ASVS
- Niveles de Verificación
- Requisitos de Seguridad
- Implementación de ASVS en Proyectos
Módulo 5: OWASP SAMM (Software Assurance Maturity Model)
Módulo 6: OWASP ZAP (Zed Attack Proxy)
- Introducción a ZAP
- Instalación y Configuración
- Escaneo de Vulnerabilidades
- Automatización de Pruebas de Seguridad
Módulo 7: Buenas Prácticas y Recomendaciones
- Ciclo de Vida de Desarrollo Seguro (SDLC)
- Modelado de Amenazas (Threat Modeling)
- Integración de Seguridad en DevOps (DevSecOps)
- Capacitación y Concienciación en Seguridad
- Herramientas y Recursos Adicionales
Módulo 8: Ejercicios Prácticos y Casos de Estudio
- Ejercicio 1: Identificación de Vulnerabilidades
- Ejercicio 2: Implementación de Controles de Seguridad
- Caso de Estudio 1: Análisis de un Incidente de Seguridad
- Caso de Estudio 2: Mejora de la Seguridad en una Aplicación Web
