Resumen
La aplicación Checkout Blocks de Shopify (checkout-blocks.shopifyapps.com) tiene páginas reservadas al personal bajo /internal y /api/internal/. Si se piden sin sesión de empleado, el servidor redirige al inicio de sesión corporativo en minerva.shopifycloud.com. Según el investigador, esa redirección era la única barrera: una regla que comprobaba si el texto de la URL empezaba por esas rutas.
El problema es que la aplicación está hecha con Next.js, y Next.js publica cada página también en una segunda dirección:
/_next/data/<buildId>/<route>.json
Esa dirección no empieza por /internal, así que la regla no la reconocía, pero Next.js la enrutaba al mismo código de la página interna. Resultado: las cuatro rutas internas respondían 200 con JSON a peticiones sin cookie y sin cabecera Authorization. El buildId que hace falta para construir la URL no es secreto: aparece en la página pública /login.
La debilidad registrada en HackerOne es Missing Authentication for Critical Function (CWE-306), con severidad media. El reporte se envió el 19 de agosto de 2026, se divulgó el 3 de noviembre de 2026 y recibió recompensa, cuyo importe no es público.
Pasos de reproducción
El investigador adjuntó un script, poc.sh, que se ejecuta sin argumentos ni credenciales (bash poc.sh). Lee el buildId en el momento desde /login y lanza cuatro comprobaciones más una consulta de paginación. Esta es su salida:
1 normal /api/internal/shops -> 302 https://minerva.shopifycloud.com/auth/minerva?...
2 bypass /_next/data/ID/..shops -> 200 628 bytes application/json
3 ghost /_next/data/NOPE/.. -> 404 (expect 404)
4 control /_next/data/ID/..admin -> 302 .../login (expect 302 login)
5 last page -> {'total': 201520, 'pages': 40304, 'current': 40298} | shops returned: 5
Lo que demuestra cada línea:
- Control de acceso normal: pedir
/api/internal/shopsdirectamente devuelve302hacia el login de empleados. La barrera funciona por la ruta "oficial". - El bypass: obtener el
buildIdde la página pública/loginy pedir el mismo endpoint por la ruta/_next/data/<buildId>/…. Responde200conapplication/json, sin ninguna credencial. - No es un comodín: con un
buildIdinventado (NOPE) la respuesta es404, es decir, la ruta alternativa no acepta cualquier cosa. - No es un endpoint público: una ruta que sí comprueba la sesión en su propio código (
/api/admin/…), pedida con el mismo formato/_next/data/, sigue devolviendo302al login. Eso confirma que lo de la línea 2 es una evasión y no un recurso pensado para ser público. - Alcance de los datos: paginando la lista de tiendas se llega a la página 40298 de 40304; el campo
totalque devuelve el propio servidor es 201.520.
El investigador avisa de dos trampas al volver a probarlo, que pueden dar un falso "ya está arreglado":
- El
buildIdcambia en cada despliegue. Uno caducado devuelve404, así que hay que leerlo de/loginen cada ejecución. - Hay que probar rutas bajo
/api/internal/. Las de/api/admin/están bien protegidas y darían la impresión de que el control aguanta.
La tienda usada para las pruebas aparece censurada en el reporte; el investigador indica que no contactó con ninguna tienda de otros comerciantes.
Impacto
Cualquier persona en Internet, sin cuenta de Shopify y sin interacción de nadie, podía consultar herramientas pensadas solo para empleados. En concreto, lo que el investigador llegó a ver:
- La lista de comerciantes que habían instalado la aplicación: 201.520 registros en 40.304 páginas, cada uno con el dominio de la tienda, un identificador interno y la fecha de instalación.
- Qué comerciantes tenían activados flags internos, como
checkout_blocks_allow_plus_featuresyallowPaidUpgrade. - Tiendas de staging no públicas de marcas identificables.
Los datos estaban vivos: el total aumentó cuatro veces durante las pruebas. El investigador se detuvo a propósito sin comprobar cuánto se podía extraer, si se podía escribir algo o qué contenían las otras páginas internas, porque todo eran datos de terceros, y no propuso severidad.
Según el resumen de Shopify, su revisión interna posterior encontró además que un atacante podía activar o desactivar funciones de Checkout Blocks en tiendas ajenas sin permiso, no solo leer la información.
Remediación
Shopify añadió autenticación verificada de empleado y comprobó en producción que el acceso anónimo por la vía alternativa quedaba bloqueado. La corrección protege tanto la información interna de las tiendas como las herramientas que cambian sus funciones.
La recomendación del investigador fue comprobar la sesión dentro del código de cada ruta, como ya hacía /api/admin/* en ese mismo servidor, porque una regla en el borde que mira el principio de la URL no ve las reescrituras del framework. Si se mantenía la regla en el borde, debía cubrir también /_next/data/<buildId>/api/internal/*.
Qué aprender de este caso
- En aplicaciones Next.js, cada ruta protegida por un middleware o proxy que filtre por prefijo (
/internal,/admin…) merece probarse también por/_next/data/<buildId>/<ruta>.json; elbuildIdsuele estar en el HTML de cualquier página pública. - La comparación entre
/api/internal/y/api/admin/es la clave del caso: la ruta que validaba la sesión en su propio handler resistió y la que dependía solo de la regla externa cayó. La autorización tiene que vivir donde se sirven los datos. - Al reportar un bypass de este tipo, incluye una prueba de control como la del paso 4: demostrar que una ruta equivalente con comprobación propia sigue pidiendo login descarta que el
200sea de un recurso público. - Un hueco que parece de solo lectura puede dar acceso también a acciones: aquí el mismo fallo permitía cambiar funciones de tiendas, algo que apareció en la revisión interna de Shopify.