Volver al blog

Caso de estudio next.js burp javascript

Authentication vs Authorization cómo entré al panel de administración sin robar la contraseña

Por Armando Peña 10 min de lectura 26 de septiembre de 2026

black iphone 5 on yellow textile

Foto de  Franck  en  Unsplash

Authentication vs Authorization cómo entré al panel de administración sin robar la contraseña

Nunca adiviné una contraseña ni me salté un inicio de sesión. Simplemente entré con mi cuenta normal, hice lo que cualquier usuario hace, y una de esas rutas me devolvió la parte de la aplicación que no me correspondía. Este es el camino que me llevó hasta ahí: un botón que no se veía, una ruta escondida en el JavaScript de la interfaz, y un servidor que respondía sin preguntar qué debía poder hacer yo.

Una revisión de seguridad que empezó como revisión de código

Me llamó un colega que había desarrollado una aplicación web para un restaurante. Él la había construido, yo llevo más años haciendo desarrollo, y me pidió que le echara un vistazo al proyecto entero.

Entre lo que iba a revisar estaba también la seguridad. Llevo más de ocho años haciendo desarrollo web, doce si cuento la universidad, y en todo ese tiempo la tuve siempre presente, pero casi siempre como una lista de tareas: compruebo el permiso, monto el middleware, sigo adelante. Nunca lo había mirado desde el otro lado, y por eso no me lo tomaba tan en serio como debería.

Esta vez quise ver qué pasaba cuando alguien no juega con las reglas. Y lo hice, además, como práctica.

La aplicación tenía usuarios, roles y un panel de administración

Lo primero que noté fue que no era una simple página de inicio. Tenía usuarios, sesiones y una jerarquía. El flujo era más o menos este: un usuario entra, la aplicación sabe cuál es, ese usuario tiene un rol, y el rol decide qué puede ver y hacer (RBAC de toda una vida). Y arriba había un panel de administración, integrado en el mismo sitio.

Nada de eso me pareció raro. Es un diseño normal.

La autenticación funcionaba bien

Los usuarios podían autenticarse, el sistema sabía quién era cada uno, la sesión se mantenía entre peticiones y la aplicación respondía distinto según quién estuviera dentro. Todo funcionaba. Hasta aquí la impresión era que la aplicación estaba bien montada.

Entonces empecé a mirar la autorización

Había estado estudiando esa diferencia en los laboratorios de PortSwigger, así que la tenía fresca. Y no es idea mía: es lo primero que te enseñan al mirar el control de acceso en serio.

Pero la diferencia no me era nueva. Autenticación, autorización, roles, RBAC: lo había implementado muchas veces, siempre con varios middlewares en el backend, uno para autenticar y otro para el rol. Eso es exactamente lo que toca hacer. Lo nuevo no era el concepto, era la dirección desde la que lo miraba. Durante años me hice la pregunta de constructor, "¿mi código comprueba el rol?"; nunca me hice la del atacante, "¿hay alguna ruta donde nadie lo pregunte?".

Buscaba algo específico: si los roles se estaban aplicando en el servidor, o solo en la interfaz. No probaba si podía saltarme el inicio de sesión, ya sabía que no.

Un botón que no se veía

La aplicación tenía páginas que no aparecían en la interfaz para ciertos roles. El botón no se representaba, el enlace no estaba en el menú. Todo correcto desde la experiencia de usuario: un usuario normal no ve cosas que no le tocan.

El problema es que esa decisión estaba tomada en el cliente. El "no mostrar" y el "no permitir" eran la misma cosa, y pasaba en el navegador.

En mis apuntes tengo anotado el antipatrón exacto, y me lo había puesto como advertencia a mí mismo:

<!-- ❌ MAL: la protección existe SOLO en el cliente -->
<script>
  var isAdmin = false
  if (isAdmin) {
    // renderizar panel de administración
  }
</script>

El botón no aparece. Perfecto. Pero ocultar un botón no cambia la ruta a la que apunta, ni lo que el servidor responde cuando alguien pide esa ruta directamente.

Ocultar un botón no es autorización. Es experiencia de usuario.

La ruta seguía ahí, escrita en el JavaScript

El enlace no se renderizaba, pero la ruta existía. Y aquí es donde entran las herramientas de desarrollo del navegador: aunque el botón no exista en pantalla, la condición que decide si se muestra está en el JavaScript que la interfaz le descarga a cualquier visitante. Abrí el inspector, busqué esa lógica, y la ruta estaba ahí, escrita al lado, esperando.

No hubo adivinar nada, ni bruteforce, ni intentos a ciegas: leí el código que la propia aplicación le entrega a cualquiera que la visite. Escribí la ruta y la pedí. Y el servidor respondió, sin ninguna señal de alarma, porque para él la petición era perfectamente normal: venía de una sesión válida, de un usuario que había entrado correctamente.

Aquí está el matiz importante. La aplicación nunca decidió que yo no tenía permiso. No hubo una comprobación que saliera mal. Simplemente no hubo comprobación.

Probar los puntos de conexión con Burp

El paso siguiente era ver si era un caso aislado o la norma, y aquí usé Burp.

Configuré el navegador para que pasara por el proxy y fui navegando con mi usuario normal, que no era administrador, sin tocar nada: solo mirando qué peticiones hacía la interfaz por su cuenta mientras la usaba. Burp las quedó capturadas todas. Después repetí esas peticiones una por una, y varias me devolvieron datos o acciones que correspondían a un rol con más privilegios.

Y ahí ya no había duda posible: los puntos de conexión administrativos no comprobaban el rol antes de responder. Confiaban en que la interfaz ya había filtrado lo que me correspondía, y no lo repasaban ellos mismos.

Ese es el patrón clásico de control de acceso roto, y no necesita que una aplicación se rompa de forma espectacular. Basta con que una comprobación se quede únicamente en la interfaz. Ocultar el botón y proteger el servidor son dos cosas distintas, y aquí solo se había hecho la primera.

Y merece la pena decir algo incómodo: el trabajo pesado no lo hace la herramienta. Burp no decide nada, solo te da la petición que ya sale de tu sesión y te deja volver a mandarla. Lo que revela el fallo es que la repitas con un rol que no debería poder y el servidor te conteste igual.

Llegué a la funcionalidad de administrador

Y aquí está el remate, que es la razón de que este artículo exista.

Yo estaba autenticado. El sistema sabía quién era yo, en todo momento. No hubo evasión de la autenticación, ni fuerza bruta, ni robo de sesión, ni nada remotamente parecido. Usé mi sesión legítima, con mi rol legítimo, y pedí directamente lo que la interfaz no me habría dejado pedir.

Llegué a funcionalidad de administrador sin conocer, sin ver, sin obtener y sin robar la contraseña del administrador. Y quiero precisar el alcance, porque exagerar sería deshonesto: conseguí acceso a funcionalidad que no correspondía a mi rol, pero no obtuve control del servidor, ni de la base de datos, ni credenciales de nadie. Fue un problema de autorización, no una toma de control.

Autenticación frente a autorización

Autenticación es: ¿quién eres? Se responde una vez, en el inicio de sesión, y produce una identidad. En esta aplicación la respondía bien: se podía autenticar, la sesión era válida, y el servidor siempre sabía si venía alguien o no.

Autorización es: ¿qué estás permitido a hacer? Es una pregunta distinta, más específica, y no se responde una sola vez. Se responde por cada recurso y por cada acción. ¿Este pedido es de este usuario? ¿Puede este rol tocar esta pantalla? ¿Este punto de conexión acepta mi rol o acepta a cualquiera?

Esa es la diferencia de fondo: la primera pregunta la hizo la aplicación correctamente, y la segunda se la saltó en varios sitios. El servidor nunca preguntó qué debía poder hacer ese usuario; asumía que como la interfaz ya lo había filtrado, podía responder.

Lo que el desarrollador debería haber comprobado

Esto es lo que le diría a mi colega, y lo que aplico en mis propios proyectos. Cada punto viene de algo que vi aquí, no de una lista de manual.

  • No confiar en que ocultar un elemento de la interfaz impide el acceso. La visibilidad en el cliente es experiencia de usuario; el permiso es otra cosa.
  • Comprobar el permiso en el servidor, siempre y en todos sitios. Si la respuesta depende del rol, la respuesta tiene que decidirlo con el rol. Cada punto de conexión sensible tiene que comprobarlo por sí mismo, sin dar por hecho que otro ya lo hizo.
  • No asumir que una ruta rara o "secreta" está protegida. Que no esté en el menú no significa que nadie la pueda pedir.

Yo lo pienso por capas, y solo me interesan estas tres:

Capa Qué protege Qué tiene que hacer
Interfaz Solo lo que se ve Mostrar u ocultar elementos
Ruta o proxy Bloquear antes de renderizar Validar la sesión
Servidor Verificar sesión y permisos Comprobar el permiso en cada punto de conexión

La tercera es la que decide. Las dos primeras son experiencia de usuario, y por eso mismo no pueden ser la protección.

Lo que me faltaba

Sigo sin ser profesional de la seguridad, y quiero ser claro. Pero conviene decir algo que el relato puede dejar ambiguo: esto no fue un error de principiante.

En los proyectos donde importaba usé RBAC sin excepción, con middlewares separados para autenticación y para rol. Eso es exactamente lo que toca hacer. El problema nunca fue el conocimiento: era que tenía la seguridad como una lista de tareas, presente pero abstracta, sin el rigor que merece.

Y eso tiene consecuencias concretas. Es perfectamente posible que en algún proyecto, presionado por una fecha de entrega, haya dejado una ruta sin su middleware de rol. O que haya resuelto algo ocultando el botón en la interfaz y fiándome del apuro del calendario. No lo puedo asegurar, porque no he vuelto a mirar esos proyectos con estos ojos, pero es exactamente el patrón que encontré en la aplicación de mi colega.

Lo interesante es el salto que me dio este caso. Ya había leído sobre autorización y lo entendía de forma abstracta: "hay que comprobar permisos en el servidor", como una regla más de una lista. Verlo fallar en una aplicación que yo estaba realmente revisando lo volvió otra cosa: un botón que no aparecía y una ruta que respondía de todos modos. La diferencia dejó de ser un matiz y pasó a ser el hallazgo entero.

Y hay una lección incómoda: la aplicación autenticaba bien, y eso es justo lo que hace que el error sea fácil de no ver. Si el inicio de sesión hubiera estado roto, lo habría encontrado en dos minutos. El problema estaba en la pregunta que nadie se hacía.

Reflexión final

No necesité la contraseña del administrador. La aplicación ya me había autenticado, con mi propia cuenta y mi propio rol, y aun así nunca comprobó qué estaba autorizado a hacer. Y cuando eso falla, ninguna cantidad de inicio de sesión bien hecho lo arregla: la autenticación te deja entrar, la autorización decide qué puedes hacer una vez dentro.

Creo que esa es la lección que más me llevo, y aplica más allá de la seguridad: las vulnerabilidades graves no siempre son las más interesantes. Muchas veces son una pregunta que la aplicación nunca se hace. ¿Qué debía poder hacer este usuario aquí?

Y la comprobación que ahora aplico a todo lo que construyo: pide directamente lo que la interfaz no te muestra, y mira qué responde el servidor. No necesitas herramientas exóticas: las herramientas de desarrollo del navegador te muestran la ruta, y un proxy como Burp te deja repetir la petición con el rol que quieras. Si la respuesta es la misma que para un usuario sin privilegios, ahí hay algo que revisar.

GitHub: https://github.com/Armando284

¿Te ayudó este artículo?

Si necesitas ayuda con tu presencia digital, cuéntanos tu idea.

Consultar por WhatsApp

Posts similares

man wearing red hoodie
  • ciberseguridad
  • pentesting
  • hacking ético

Intenté hackear una tienda web. Encontré una vulnerabilidad que podía afectar la confianza del negocio.

Intenté hackear una tienda web real con autorización y terminé encontrando una combinación de problemas de lógica de negocio y Clickjacking que podía afectar la confianza entre el negocio y sus clientes. Este es el recorrido completo: reconocimiento, manipulación de productos y carritos, creación de una prueba de concepto, reporte, corrección y verificación.

Caso de estudio1434 palabras · 8 min de lectura

Armando Peña 4 1
person writing on white board
  • low-net
  • low-bandwidth
  • offline-first

Cómo transmitir una pizarra con 120 Kbps y una conexión inestable?

Quería que un dibujo cruzara la conexión de mi clase de inglés, alrededor de 120 kbps y con caídas constantes. Empecé comprimiendo una pizarra y descubrí que comprimir era la parte fácil: el problema era decidir qué información enviar y qué hacer cuando la conexión simplemente no está. Esta es la cadena de descubrimientos que llevó a LOW-NET, el laboratorio donde estoy midiendo esa pregunta.

Caso de estudio1940 palabras · 10 min de lectura

Armando Peña 1 0