Manual de Burp Suite Community Edition
Capítulo 12 · Formularios y cuestionarios
Serie: Burp Suite desde cero | unfantasmaenelsistema.com
El capítulo 6 ya recorrió el registro, la dirección y el contacto. Quedan dos formularios por explorar -la reseña de producto y, otra vez, el registro, pero esta vez mirando su respuesta con lupa- y los dos comparten un mismo hilo conductor: qué pasa cuando el servidor confía en un dato que el cliente no debería poder decidir. En la reseña es la identidad de quien la escribe; en el registro, nada menos que el rol de la cuenta.
Qué aprenderás en este capítulo
Qué contiene realmente la petición que deja una reseña de producto · Por qué el campo "author" no debería venir del cliente, y qué pasa si lo cambias en Repeater · Qué revela la respuesta del registro sobre el modelo de datos interno del servidor · Qué es el mass assignment y cómo un campo de más en el JSON crea una cuenta con privilegios · Cómo comprobar el impacto real accediendo a una sección que no deberías poder ver
Cualquier producto tiene un desplegable de reseñas con un cuadro de texto simple -sin estrellas, sin campo de nombre visible. Escribe algo y pulsa Submit:

Cuadro de reseña del Apple Juice, con el contador de caracteres (160 máximo) igual que en el formulario de contacto
La petición es un `PUT /rest/products/1/reviews` con un cuerpo mucho más revelador de lo que la interfaz sugiere:

El cuerpo real: {"message":"...","author":"alumno.ghostacademy@example.com"} -el correo viaja tal cual, escrito por el propio navegador
El cliente no debería poder decir quién es
El servidor ya sabe quién eres -tienes un JWT válido en la cabecera Authorization, y de él podría sacar el email sin preguntarle nada al cuerpo de la petición. En vez de eso, confía en el campo `author` que manda el propio navegador. Eso significa que cualquiera que pueda editar esa petición puede firmar una reseña con la identidad que quiera.
Manda esa petición a Repeater y cambia el valor de `author` por cualquier otro correo -por ejemplo, el del propio administrador de la tienda:

201 Created con "author":"admin@juice-sh.op" -el servidor lo acepta sin comprobar que coincida con el usuario autenticado
La aplicación tiene incluso un nombre oficial para este fallo, y te lo dice sin rodeos en cuanto vuelves a la tienda:

"You successfully solved a challenge: Forged Review (Post a product review as another user or edit any user's existing review.)"
Y el resultado se ve exactamente donde cualquier visitante lo vería: en la lista pública de reseñas del producto, firmado por una identidad que nunca escribió una palabra.

La reseña forjada, indistinguible de una reseña real firmada por admin@juice-sh.op
Vuelve al formulario de registro del capítulo 6 -esta vez, en vez de fijarte en la petición, fíjate en la respuesta:

El formulario de registro: solo pide email, contraseña, pregunta y respuesta de seguridad

La respuesta del servidor tras registrarte: username, role, deluxeToken, lastLoginIp, profileImage, isActive... campos que el formulario nunca mencionó
Una respuesta demasiado generosa es un mapa gratis
Ninguno de esos campos aparece en el formulario, pero la API los devuelve igualmente porque el servidor, internamente, trabaja con el objeto de usuario completo y lo serializa entero en la respuesta. Antes de tocar nada, esa respuesta ya te ha regalado los nombres exactos de los campos que existen en el modelo -incluido uno tan interesante como `role`.
Si el servidor le pasa el cuerpo entero de la petición directamente a la capa que crea el usuario -sin filtrar qué campos están permitidos-, entonces cualquier campo del modelo que acabas de descubrir es, potencialmente, un campo que puedes rellenar tú. Repite el registro en Repeater, cambia el correo para no chocar con la cuenta anterior, y añade una línea que el formulario jamás te dejaría escribir:

El mismo cuerpo del registro, con "role":"admin" añadido a mano

201 Created. La respuesta confirma "role":"admin" y hasta cambia la imagen de perfil por defecto a defaultAdmin.png
Esto es mass assignment (asignación masiva)
El fallo no está en la contraseña ni en la validación del email -está en que el servidor confía en que el cliente solo va a mandar los campos "normales" del formulario. Un endpoint de registro que hace, en esencia, `Usuario.crear(cuerpoDeLaPeticion)` sin filtrar antes qué claves acepta, deja la puerta abierta a que cualquier campo interno del modelo -role, isActive, deluxeToken, lo que sea- se pueda rellenar desde fuera con solo saber que existe. Y acabamos de ver, en la sección anterior, que el propio servidor se encarga de decírtelo.
Otra vez, la aplicación lo confirma con su propio lenguaje de challenges -y esta vez con dos a la vez, porque registrar la cuenta y usarla son dos logros distintos:

"Admin Registration (Register as a user with administrator privileges.)" -conseguido con una sola petición
El paso final es entrar con esa cuenta y comprobar que el privilegio es real, no solo un campo bonito en un JSON:

"Admin Section" resuelto: panel de administración completo, con la lista de todos los usuarios registrados y control sobre cada reseña y cada feedback de la tienda
La defensa contra esto no es complicada, pero hay que acordarse de ponerla: el servidor debería construir el objeto que va a guardar campo a campo -tomando explícitamente email, password y poco más del cuerpo de la petición-, en vez de pasarle el cuerpo entero a la capa de base de datos y confiar en que nadie va a mandar un campo de más.
Dos formularios, un mismo error de fondo: dejar que el cliente decida algo que solo el servidor debería decidir -quién firma una reseña, o con qué privilegio se crea una cuenta. Ninguno de los dos fallos necesitó más herramienta que Repeater y la costumbre de leer la respuesta completa, no solo el código de estado.
El capítulo siguiente vuelve sobre las direcciones del capítulo 6, pero esta vez las trata como lo que de verdad son: el candidato número uno a IDOR en cualquier aplicación con más de un usuario -y la primera vez en este manual que vamos a cazar uno de verdad, con dos cuentas propias, de principio a fin.
unfantasmaenelsistema.com | Seguridad, sistemas y todo lo que no debería funcionar (pero funciona)