Escritura sin autorización en el endpoint de settings de un plugin de page-builder
Un endpoint del plugin Blocks valida la entrada pero no comprueba autorización: úsalo para flipear un flag de diagnóstico y desredactar un secreto de la organización.
Learn to find this bug
A real HackerOne report, reproduced for you to practice.
Create your account and practice real bugs that got paid. Download the environment, find it and learn the exact technique — your path to your first bounty.
hunters training
labs from real reports
completions
in bounties practiced
Access to all labs · no commitment · cancel anytime
Hunters who solved it· 2
Objectives
Achievement you'll earn
Solve this lab to unlock this shareable achievement
Escritura sin autorización en el endpoint de settings de un plugin de page-builder
Community writeups
Contenido del lab
Escenario
Panelia es una SaaS low-code de construcción de páginas: sus clientes montan landings y storefronts arrastrando bloques sin escribir código, sobre un motor de widgets interno llamado Blocks (/api/blocks/v1/). Blocks expone una consola de administración en /admin donde el operador activa o desactiva widgets, configura integraciones y ajusta los settings del sitio. El storefront público de demostración está construido enteramente con Blocks.
Eres pentester contra el sitio público de Panelia. Tu misión: demostrar que el plugin Blocks tiene un hueco de autorización en alguno de sus endpoints REST, y usarlo para llegar hasta el service_token de la organización — un secreto que la propia plataforma protege detrás de un export de soporte redactado por defecto.
Qué tienes
- Cuenta demo de la consola:
attacker@panelia.local/password123— te sirve para navegar/adminy ver cómo funciona el formulario de settings, pero es una cuenta de editor de baja privilegia. No es el punto de entrada de la vulnerabilidad. - El storefront público de Panelia, con más de una decena de páginas construidas con widgets de Blocks (hero, pricing, testimonios, mapa embebido, etc.).
- El bundle JavaScript de la consola, servido públicamente como cualquier SPA — nadie te lo oculta.
Guía paso a paso
Recon del surface: lee el bundle público de la consola. Navega el storefront y entra a
/admin(verás la pantalla de login). El JavaScript compilado de la consola se sirve en claro bajo/assets/— es el comportamiento normal de cualquier SPA, nada está ofuscado ni requiere sesión para descargarlo. Ábrelo o pásalo porgrep: contiene, literales, las URLs de los dos endpoints que el plugin Blocks expone para settings, y el whitelist de nombres de campo que la consola sabe sincronizar (el mismo conjunto que ves renderizado como formulario en la página de settings). Auto-descubrimiento puro: no adivinas ni una ruta ni un nombre de clave, los lees.Prueba la escritura sin cookie — el contraste que delata el hueco. Con las dos URLs del paso 1, golpea primero una ruta hermana de gestión (p. ej. el listado de páginas del plugin) sin ninguna cookie ni header
Authorization: te da 401, como cabría esperar de cualquier API de administración. Ahora golpea el endpoint de settings del mismo namespace, también sin credencial: primero mándale JSON malformado — te devuelve 400 con un error de parseo, prueba de que el servidor procesa la entrada de verdad, no es un endpoint muerto. Luego mándale un body válido con un campo del whitelist: te devuelve 200 con una confirmación de guardado. Ninguna cookie, ningún token, y aun así el servidor te dice que ha persistido tu cambio. La validación cuidadosa (rechaza lo malformado, acepta solo claves conocidas) es real — pero validar la entrada no es lo mismo que autorizar a quien la envía, y esa disonancia es exactamente el defecto.Localiza la clave a flipear en el propio export. El plugin expone también un endpoint de exportación de soporte que devuelve la configuración combinada del sitio. Por defecto, los campos sensibles (incluido el que buscas) salen redactados con un mensaje que nombra literalmente el mecanismo: te dice qué flag de diagnóstico booleano hay que activar para que el export deje de redactar. Comprueba que esa clave exacta está dentro del whitelist que ya identificaste en el paso 1 — es escribible sin sesión por la misma vía del paso 2.
Flipea el flag y confirma. Repite la escritura sin cookie del paso 2, esta vez con esa clave de diagnóstico puesta a su valor "activado". Vuelve a pedir el export de soporte: el
debug_diagnosticsque antes marcabafalseahora marcatrue, la sección de cambios recientes recoge tu escritura, y el campo que antes decía "hidden" ahora sale en claro. Ese valor crudo es elservice_tokende la organización — la flag.
Canales de feedback (no vuelas a ciegas)
- El bundle público responde
200y es legible/grep-able: confirma que las URLs y el whitelist existen tal cual los lees, sin necesidad de adivinar. - El contraste 401 (rutas hermanas de gestión) vs 400/200 (endpoint de settings) es inmediato y observable en cada request: te dice sin ambigüedad cuál de las dos rutas es la que no autoriza.
- El export de soporte nombra el mecanismo en el propio mensaje de redacción — no hay que inferir qué flag activar, el servidor te lo dice.
- Tras la escritura, releer el export es un confirmador de estado explícito:
debug_diagnostics: true, el registro de cambios recientes con tu escritura, y el secreto en claro — no es un paso ciego.
Decoys (descártalos por razonamiento acotado)
- Desactivar widgets del catálogo. Está en el mismo whitelist y romper visiblemente el render del storefront público es tentador como "impacto". Pero es puro DoS observable: tras desactivarlo, el export de soporte sigue exactamente igual de redactado. Impacto ruidoso ≠ objetivo.
- La clave de integración de mapas. También sobreescribible sin sesión, y suena a secuestro de API key de terceros. Pero nada en el lab la consume — sobreescribirla no cambia ni el export ni ningún otro efecto observable. Dead-end honesto.
- El login de la consola con la cuenta demo. Iniciar sesión no abre ningún camino nuevo: el editor demo no ve el
service_tokenen ningún sitio de la consola, y el export de soporte sigue redactado exactamente igual estés logueado o no — la ruta que importa nunca pidió sesión. - Un campo fuera del whitelist (tipo un flag de rol/admin). El endpoint responde
200igualmente, pero la escritura se ignora en silencio: vuelve a leer el estado y esa clave no aparece por ningún lado. Un200no implica que algo haya cambiado de verdad.
La falla del laboratorio
Clase: CWE-862 (Missing Authorization), con CWE-306 (Missing Authentication for Critical Function) y CWE-285 (Improper Authorization) como paraguas relacionados, más CWE-200 / CWE-1230 (Exposure of Sensitive Information a través de un export de metadatos) en la capa de escalada. OWASP A01:2021 (Broken Access Control) + A05:2021 (Security Misconfiguration).
El endpoint de escritura de settings del plugin Blocks vive en el mismo namespace de API que su familia de rutas de gestión, y comparte con ellas el mismo estilo de validación de entrada. Pero, a diferencia de sus hermanas — que sí exigen sesión de operador antes de tocar nada —, a este endpoint concreto le falta el middleware de autorización. No es una comprobación rota ni un if con la condición invertida: es la ausencia de una línea de wiring, invisible a cualquier revisión de código que busque un if sospechoso. El código de validación que sí existe (rechazar JSON malformado, aceptar solo claves de un whitelist conocido) es correcto y estricto — y esa corrección es justo lo que genera la falsa sensación de seguridad: parece protegido porque "valida", pero validar la forma de la entrada nunca sustituye a decidir quién tiene permiso de enviarla.
La escalada hacia la flag añade una segunda capa de exposición: un export de "soporte" diseñado para adjuntarse a tickets redacta sus secretos por defecto, pero la decisión de redactar o no depende de una opción de configuración booleana — y esa misma opción es escribible por el endpoint sin autorización de la capa anterior. El resultado es que un fallo de control de acceso en un endpoint de settings, aparentemente inocuo, termina exponiendo un secreto de la organización sin que el atacante necesite ninguna credencial en ningún momento de la cadena.
Lo que habría prevenido el bug es trivial de enunciar y fácil de olvidar en la práctica: montar el sub-router de settings-sync detrás del mismo middleware de autorización que ya protege a sus rutas hermanas, y tratar cualquier opción capaz de alterar el comportamiento de redacción de un export como una operación privilegiada, no como un campo de configuración más.
Lección: un control de validación de entrada robusto no es evidencia de que el endpoint esté protegido — son dos preguntas independientes ("¿la entrada tiene la forma correcta?" vs. "¿quién tiene permiso de enviarla?"), y basta con responder bien a la primera para generar una falsa sensación de seguridad sobre la segunda. Cuando una familia de rutas comparte namespace y un middleware de autorización común, el hueco casi siempre está en la ruta que quedó fuera del wiring, no en una que lo tiene y falla.
Laboratorio basado en la reproducción anonimizada (empresa ficticia Panelia) de una técnica real de bug bounty sobre un endpoint REST de settings de un plugin sin control de autorización.