Este es el primero. El bug con el que empiezo a llenar este blog, y resulta que es exactamente del tipo que quiero cazar: un fallo de control de acceso. Nada de herramientas mágicas ni payloads exóticos. Solo mirar bien lo que el servidor confía a su cliente, y darme cuenta de que confía demasiado.
Lo hice en un laboratorio de la PortSwigger Web Security Academy, entorno legal y pensado para esto. Pero el error es de los que viven, tal cual, en aplicaciones reales.
El objetivo
Una tienda online cualquiera. Tengo credenciales de un usuario normal (wiener) y, en algún lugar del sitio, existe un panel de administración en /admin. La misión: entrar a ese panel sin ser admin y borrar a otro usuario, carlos.
Lo primero, lo obvio: inicio sesión como wiener y voy directo a /admin.
Admin interface only available if logged in as an administrator
Muro. Como debe ser. Pero esa frase me deja una pregunta: ¿cómo sabe el servidor que no soy administrador? En algún momento, al iniciar sesión, me etiquetó. Si encuentro esa etiqueta y resulta que viaja de mi lado, el muro es de papel.
La pista
Con todo mi tráfico pasando por Burp Suite, me voy al historial y filtro solo las peticiones de la tienda. Reviso la que de verdad importa: el POST /login, el momento en que el servidor decide quién soy.
Y ahí está, en la respuesta:
HTTP/2 302 Found
Set-Cookie: Admin=false; ...
Set-Cookie: session=...
Admin=false.
El servidor no guarda mi rol en su base de datos y lo consulta cuando hace falta. Me lo entrega a mí en una cookie y luego confía en que yo se la devuelva intacta. Es como si un guardia me diera un gafete que dice “NO admin” y me pidiera que yo mismo lo muestre en la puerta. ¿Qué me impide tacharlo y escribir “SÍ admin”?
Nada.
La explotación
Tomo la petición GET /admin (la que me devolvía un 401) y la mando a Repeater, la mesa de trabajo de Burp. Ahí puedo reenviarla cuantas veces quiera, cambiando lo que quiera.
Busco la línea de la cookie y hago el único cambio de todo el ataque:
Cookie: Admin=false → Cookie: Admin=true
Send.
HTTP/2 200 OK
...
<h1>Users</h1>
<a href="/admin/delete?username=wiener">Delete</a>
<a href="/admin/delete?username=carlos">Delete</a>
Dentro. El panel de administración completo, con la lista de usuarios y sus botones de borrado. Una palabra. false por true.
El remate
El panel me regala el siguiente paso en bandeja: la acción de borrar es un simple GET a /admin/delete?username=. Cambio la ruta de mi petición en Repeater, dejo la cookie en Admin=true, y apunto a mi objetivo:
GET /admin/delete?username=carlos HTTP/2
Cookie: Admin=true
Send.
HTTP/2 302 Found
Location: /admin
Carlos ya no existe. Lab resuelto.
La lección
El fallo no fue la cookie en sí. Fue dónde se tomó la decisión de seguridad. El desarrollador delegó en el cliente algo que jamás debe salir del servidor: la verdad sobre quién eres y qué puedes hacer.
La regla que se rompió es simple y absoluta:
Nunca confíes, para una decisión de seguridad, en ningún dato que el cliente pueda modificar.
Cookies, parámetros, cabeceras, campos ocultos de un formulario: todo eso es territorio del atacante. El servidor debió consultar el rol de wiener en su propia base de datos antes de abrir /admin, no preguntarle al propio wiener si era admin.
Vengo del desarrollo, y esta es justo la clase de atajo que entiendo por dentro: es cómodo meter el rol en una cookie y leerlo de vuelta. Funciona en todas las demos. Hasta que alguien abre Burp.
Primer bug caído. El siguiente ya está en camino.
$ whoami
0xkelvin