Skip to content

GraphQL es un lenguaje de consulta que suelen utilizar las API web como alternativa a REST. Permite al cliente obtener los datos necesarios mediante una sintaxis sencilla, al tiempo que ofrece una amplia variedad de funciones que suelen proporcionar los lenguajes de consulta, como SQL. Al igual que las API REST, las API GraphQL pueden leer, actualizar, crear o eliminar datos. Sin embargo, las API GraphQL suelen implementarse en un único punto final que gestiona todas las consultas. Por lo tanto, una de las principales ventajas de utilizar GraphQL frente a las API REST tradicionales es la eficiencia en el uso de los recursos y las solicitudes.

ejemplos de consultas:

c
{
  users {
    id
    username
    role
  }
}
c
{
  "data": {
    "users": [
      {
        "id": 1,
        "username": "htb-stdnt",
        "role": "user"
      },
      {
        "id": 2,
        "username": "admin",
        "role": "admin"
      }
    ]
  }
}
c
{
  users(username: "admin") {
    id
    username
    role
  }
}
c
{
  users(username: "admin") {
    id
    username
    password
  }
}
c
{
  posts {
    title
    author {
      username
      role
    }
  }
}
c
{
  "data": {
    "posts": [
      {
        "title": "Hello World!",
        "author": {
          "username": "htb-stdnt",
          "role": "user"
        }
      },
      {
        "title": "Test",
        "author": {
          "username": "test",
          "role": "user"
        }
      }
    ]
  }
}

Identificación del motor GraphQL

  • Como primer paso, identificaremos el motor GraphQL que utiliza mediante la herramienta graphw00f
c
$ python3 main.py -d -f -t http://172.17.0.2

                +-------------------+
                |     graphw00f     |
                +-------------------+
                  ***            ***
                **                  **
              **                      **
    +--------------+              +--------------+
    |    Node X    |              |    Node Y    |
    +--------------+              +--------------+
                  ***            ***
                     **        **
                       **    **
                    +------------+
                    |   Node Z   |
                    +------------+

                graphw00f - v1.1.17
          The fingerprinting tool for GraphQL
           Dolev Farhi <dolev@lethalbit.com>
  
[*] Checking http://172.17.0.2/
[*] Checking http://172.17.0.2/graphql
[!] Found GraphQL at http://172.17.0.2/graphql
[*] Attempting to fingerprint...
[*] Discovered GraphQL Engine: (Graphene)
[!] Attack Surface Matrix: https://github.com/nicholasaleks/graphql-threat-matrix/blob/master/implementations/graphene.md
[!] Technologies: Python
[!] Homepage: https://graphene-python.org
[*] Completed.

Introspección

La introspección es una función de GraphQL que permite a los usuarios consultar la API de GraphQL sobre la estructura del sistema backend. De este modo, los usuarios pueden utilizar consultas de introspección para obtener todas las consultas compatibles con el esquema de la API. Estas consultas de introspección consultan el campo __schema.

  • Input
c
{
  __schema {
    types {
      name
    }
  }
}
  • Output
c
{
 "data": {
   "__schema": {
     "types": [
       {
         "name": "Query"
       },
       {
         "name": "Node"
       },
       {
         "name": "ID"
       },
       {
         "name": "SecretObject"
       },
       {
         "name": "String"
       },
       {
         "name": "UserObject"
       },
       {
         "name": "PostObjectConnection"
       },
       {
         "name": "PageInfo"
       },
       {
         "name": "Boolean"
       },
       {
         "name": "PostObjectEdge"
       },
       {
         "name": "PostObject"
       },
       {
         "name": "Int"
       },
       {
         "name": "Mutation"
       },
       {
         "name": "RegisterUser"
       },
       {
         "name": "RegisterUserInput"
       },
       {
         "name": "__Schema"
       },
       {
         "name": "__Type"
       },
       {
         "name": "__TypeKind"
       },
       {
         "name": "__Field"
       },
       {
         "name": "__InputValue"
       },
       {
         "name": "__EnumValue"
       },
       {
         "name": "__Directive"
       },
       {
         "name": "__DirectiveLocation"
       }
     ]
   }
 }
}
  • Input
c
{
  __type(name: "UserObject") {
    name
    fields {
      name
      type {
        name
        kind
      }
    }
  }
}
  • Output
c
{
  "data": {
    "__type": {
      "name": "UserObject",
      "fields": [
        {
          "name": "uuid",
          "type": {
            "name": null,
            "kind": "NON_NULL"
          }
        },
        {
          "name": "id",
          "type": {
            "name": null,
            "kind": "NON_NULL"
          }
        },
        {
          "name": "username",
          "type": {
            "name": "String",
            "kind": "SCALAR"
          }
        },
        {
          "name": "password",
          "type": {
            "name": "String",
            "kind": "SCALAR"
          }
        },
        {
          "name": "role",
          "type": {
            "name": "String",
            "kind": "SCALAR"
          }
        },
        {
          "name": "msg",
          "type": {
            "name": "String",
            "kind": "SCALAR"
          }
        },
        {
          "name": "posts",
          "type": {
            "name": "PostObjectConnection",
            "kind": "OBJECT"
          }
        }
      ]
    }
  }
}

Además, podemos obtener todas las consultas compatibles con el backend utilizando esta consulta:

c
{
  __schema {
    queryType {
      fields {
        name
        description
      }
    }
  }
}

Conocer todas las consultas compatibles nos ayuda a identificar posibles vectores de ataque que podemos utilizar para obtener información confidencial. Por último, podemos utilizar la siguiente consulta de introspección «general» que volca toda la información sobre tipos, campos y consultas compatibles con el backend:

c
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
                  }
                }
              }
            }
          }
        }
      }
    }

El resultado de esta consulta es bastante extenso y complejo. Sin embargo, podemos visualizar el esquema con la herramienta GraphQL-Voyager . Tambien se puede utilizar la demo de GraphQL . No obstante, en un proyecto real, deberíamos seguir las instrucciones de GitHub para alojar la herramienta nosotros mismos y así garantizar que ninguna información confidencial salga de nuestro sistema.

Ejercicio:

  • Obtener detalles de los argumentos:
c

{
  __schema {
    queryType {
      fields {
        name
        args {
          name
          type {
            name
          }
        }
        type {
          name
          kind
        }
      }
    }
  }
}
  • Output
c
{
  "data": {
    "__schema": {
      "queryType": {
        "fields": [
          {
            "name": "node",
            "args": [
              {
                "name": "id",
                "type": {
                  "name": null
                }
              }
            ],
            "type": {
              "name": "Node",
              "kind": "INTERFACE"
            }
          },
          {
            "name": "secrets",
            "args": [],
            "type": {
              "name": null,
              "kind": "LIST"
            }
          },
          {
            "name": "users",
            "args": [],
            "type": {
              "name": null,
              "kind": "LIST"
            }
          },
          {
            "name": "posts",
            "args": [],
            "type": {
              "name": null,
              "kind": "LIST"
            }
          },
          {
            "name": "user",
            "args": [
              {
                "name": "username",
                "type": {
                  "name": null
                }
              }
            ],
            "type": {
              "name": "UserObject",
              "kind": "OBJECT"
            }
          },
          {
            "name": "postByAuthor",
            "args": [
              {
                "name": "author",
                "type": {
                  "name": null
                }
              }
            ],
            "type": {
              "name": null,
              "kind": "LIST"
            }
          },
          {
            "name": "post",
            "args": [
              {
                "name": "id",
                "type": {
                  "name": null
                }
              }
            ],
            "type": {
              "name": "PostObject",
              "kind": "OBJECT"
            }
          }
        ]
      }
    }
  }
}

Otra manera de enumerar:

c
{
  __schema {
    types {
      name
      description
    }
  }
}
c
{
  "data": {
    "__schema": {
      "types": [
        {
          "name": "Query",
          "description": null
        },
        {
          "name": "Node",
          "description": "An object with an ID"
        },
        .
        .
        .
    
        {
          "name": "SecretObject",
          "description": null
        },
        .
        .
        .
  }
}

Ahora tenemos esto:

c
  {
          "name": "SecretObject",
          "description": null
        },

y podemos enumerar los campos:

c
{
  __type(name: "SecretObject") {
    name
    fields {
      name
      type {
        name
        kind
      }
    }
  }
}

Para luego hacer la consulta con esos campos.

  • Input
c
{
  secrets{
    id
    secret
  }
}
  • Output
c
{
  "data": {
    "secrets": [
      {
        "id": "U2VjcmV0T2JqZWN0OjE=",
        "secret": "HTB{********}"
      }
    ]
  }
}