Manual de Burp Suite Community Edition

Capítulo 15 · APIs REST/GraphQL bajo una SPA

Serie: Burp Suite desde cero  |  unfantasmaenelsistema.com

Hasta ahora, cada endpoint de este manual apareció porque lo tocamos navegando, o porque ya sabíamos que existía. Este capítulo cambia el método: en vez de esperar a tropezar con un endpoint, vamos a sacar la lista completa de golpe -incluida una pantalla entera que ningún menú enlaza- leyendo el propio código compilado que el navegador ya se ha descargado.

Qué aprenderás en este capítulo

Por qué mirar solo lo que la interfaz muestra deja fuera rutas y endpoints reales · Cómo sacar la lista completa de endpoints del bundle JavaScript compilado de una SPA, con la propia búsqueda de Burp · Un caso real: una pantalla entera sin ningún enlace en el menú, y el endpoint que la acompaña · Qué cambia si la SPA hablase GraphQL en vez de REST, y por qué Burp no necesita nada especial para ello

1. Lo que la interfaz nunca enlaza también es superficie

Prueba a entrar directamente en `http://localhost:3000/#/wallet` con cualquier cuenta logueada -sin haber visto jamás un botón que lleve ahí.

Digital Wallet: una pantalla completa y funcional, y ningún enlace a ella en el menú de cuenta

Digital Wallet: una pantalla completa y funcional, y ningún enlace a ella en el menú de cuenta

Adivinar la ruta a mano funcionó esta vez, pero no es un método -es suerte. Para encontrar esto de forma sistemática, y todo lo demás que la interfaz tampoco enseña, hace falta mirar en otro sitio: el código que el navegador ya descargó entero para poder dibujar la aplicación.

2. El bundle compilado ya tiene la lista completa

Una SPA como esta se compila entera -todas sus rutas, todos sus formularios, todas las llamadas a la API que cualquiera de sus pantallas pueda hacer algún día- en uno o varios ficheros JavaScript que el navegador carga de una vez al abrir la página, típicamente `main.js`. Ese fichero es, en la práctica, un mapa completo de la aplicación -exista o no un botón que lleve a cada parte de ese mapa.

Burp oculta los .js del histórico por defecto

Proxy > HTTP history trae de fábrica un filtro que esconde JavaScript, CSS e imágenes para no saturar la vista -muy útil el resto del tiempo, un estorbo justo ahora. Filter settings (arriba de la tabla) → quitar "js" de la lista de extensiones ocultas y marcar "Script" en Filter by MIME type. `main.js` reaparece en la lista, con toda la aplicación dentro.

Antes de perseguir nada en el bundle, vale la pena comprobar algo rápido: ¿tiene esta aplicación GraphQL además de REST? Un único endpoint (`/graphql`) probado directamente lo confirma o lo descarta en tres peticiones:

/graphql devuelve la SPA de siempre (ninguna API detrás); /api/graphql y /rest/graphql, ni existen -Juice Shop es REST de principio a fin

/graphql devuelve la SPA de siempre (ninguna API detrás); /api/graphql y /rest/graphql, ni existen -Juice Shop es REST de principio a fin

3. Buscar dentro del bundle con la propia barra de Burp

Con `main.js` ya visible en el histórico, ábrelo y usa el cuadro de búsqueda de la parte inferior del panel de Response -sin extensiones ni herramientas externas, viene integrado en Burp Community:

Buscar

Buscar "/rest/" dentro de main.js: 38 coincidencias, cada una un endpoint real usado en algún punto de la aplicación

Esta aplicación usa dos prefijos, `/rest/` y `/api/` -en otra SPA real conviene probar también `/v1/`, `/v2/`, `graphql` o `.json` según lo que vayas viendo. Cada coincidencia aparece en su contexto real, y ese contexto suele ser legible incluso en código minificado:

Una clase de servicio completa: host+'/api/Recycles', con find() haciendo GET y save() haciendo POST -la firma exacta del endpoint, sacada del propio código

Una clase de servicio completa: host+'/api/Recycles', con find() haciendo GET y save() haciendo POST -la firma exacta del endpoint, sacada del propio código

Minificado no significa ilegible

El código compilado cambia los nombres de variables y funciones a letras sueltas, pero las cadenas de texto -las rutas, los nombres de campo- casi nunca se tocan, porque tienen que seguir coincidiendo con lo que espera el servidor. Eso es justo lo que hace que buscar `/rest/` o `/api/` con un simple Ctrl+F siga funcionando aunque el resto del fichero sea ilegible a primera vista.

4. El pago de la búsqueda: una ruta que la interfaz nunca iba a enseñar

El wallet de la sección 1 tiene su propio endpoint, y aparece exactamente igual que cualquier otro en el bundle -sin ninguna marca que lo distinga de los que sí tienen un botón visible:

GET /rest/wallet/balance: 200 OK, saldo 0 -un endpoint real, nunca enlazado desde el menú, encontrado sin adivinar nada

GET /rest/wallet/balance: 200 OK, saldo 0 -un endpoint real, nunca enlazado desde el menú, encontrado sin adivinar nada

La lista completa que deja este método incluye, solo en esta aplicación, rutas que ningún capítulo anterior había tocado -`/api/Recycles`, `/api/Complaints`, `/rest/memories`, `/rest/deluxe-membership`, `/rest/saveLoginIp`, `/rest/chat`- cada una con su propio comportamiento por descubrir. Ese es precisamente el punto: la lista de endpoints de una aplicación no es la lista de sus botones.

5. Y si hubiera GraphQL debajo

Juice Shop no lo usa, pero merece la pena saber qué cambiaría si el objetivo sí hablase GraphQL: en vez de decenas de rutas distintas, casi todo pasa por un único endpoint -normalmente `POST /graphql`- con un cuerpo JSON que lleva la consulta como texto dentro de un campo `query`:

{
  "query": "query { products { id name price } }"
}

Burp no necesita nada especial para esto -sigue siendo una petición POST con un cuerpo JSON, editable en Repeater exactamente igual que cualquier otro de este manual. Lo que cambia es la estrategia de reconocimiento: en vez de buscar rutas sueltas en el bundle, el objetivo es conseguir el esquema completo con una consulta de introspección -una query especial (`{ __schema { types { name fields { name } } } }`) que, si el servidor no la ha desactivado, devuelve sola la lista entera de tipos, campos y mutaciones disponibles. Es el equivalente exacto, en una sola petición, a leer el bundle entero a mano -y la BApp Store del capítulo 10 tiene extensiones (GraphQL Raider, InQL) que la automatizan y presentan el esquema resultante de forma navegable.

Conclusión

El mapa real de una aplicación no es su barra de navegación, es su código compilado -y ese código está siempre a un clic de distancia en el histórico de Burp, aunque venga oculto por el filtro por defecto. Antes de dar por completamente explorada cualquier SPA nueva, vale la pena repetir esta búsqueda: puede que la aplicación tenga más superficie de la que nunca llegó a enseñar.

El capítulo siguiente vuelve al IDOR que ya se cazó de pasada en el capítulo 13, pero esta vez como metodología completa y repetible -qué probar, en qué orden, y cómo distinguir un objeto bien protegido de uno que solo lo parece.

unfantasmaenelsistema.com | Seguridad, sistemas y todo lo que no debería funcionar (pero funciona)