Reports
MEDIA[AUTENTICACIÓN]#3954327

Shopify Checkout Blocks: las rutas de datos de Next.js saltaban el login de empleados y exponían 200.000 tiendas

El panel interno de Checkout Blocks solo se protegía con una regla que miraba el inicio de la URL. Pidiendo las mismas páginas por la ruta /_next/data/ de Next.js, cualquiera obtenía sin sesión la lista de tiendas instaladas y sus flags internos.

Resumen
Resumen en castellano de un reporte público, no una traducción literal. El código y los comandos se mantienen como en el original.

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:

  1. Control de acceso normal: pedir /api/internal/shops directamente devuelve 302 hacia el login de empleados. La barrera funciona por la ruta "oficial".
  2. El bypass: obtener el buildId de la página pública /login y pedir el mismo endpoint por la ruta /_next/data/<buildId>/…. Responde 200 con application/json, sin ninguna credencial.
  3. No es un comodín: con un buildId inventado (NOPE) la respuesta es 404, es decir, la ruta alternativa no acepta cualquier cosa.
  4. 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 devolviendo 302 al login. Eso confirma que lo de la línea 2 es una evasión y no un recurso pensado para ser público.
  5. Alcance de los datos: paginando la lista de tiendas se llega a la página 40298 de 40304; el campo total que 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 buildId cambia en cada despliegue. Uno caducado devuelve 404, así que hay que leerlo de /login en 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_features y allowPaidUpgrade.
  • 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; el buildId suele 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 200 sea 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.