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:
{
users {
id
username
role
}
}{
"data": {
"users": [
{
"id": 1,
"username": "htb-stdnt",
"role": "user"
},
{
"id": 2,
"username": "admin",
"role": "admin"
}
]
}
}{
users(username: "admin") {
id
username
role
}
}{
users(username: "admin") {
id
username
password
}
}{
posts {
title
author {
username
role
}
}
}{
"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
$ 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
{
__schema {
types {
name
}
}
}- Output
{
"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
{
__type(name: "UserObject") {
name
fields {
name
type {
name
kind
}
}
}
}- Output
{
"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:
{
__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:
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:
{
__schema {
queryType {
fields {
name
args {
name
type {
name
}
}
type {
name
kind
}
}
}
}
}- Output
{
"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:
{
__schema {
types {
name
description
}
}
}{
"data": {
"__schema": {
"types": [
{
"name": "Query",
"description": null
},
{
"name": "Node",
"description": "An object with an ID"
},
.
.
.
{
"name": "SecretObject",
"description": null
},
.
.
.
}
}Ahora tenemos esto:
{
"name": "SecretObject",
"description": null
},y podemos enumerar los campos:
{
__type(name: "SecretObject") {
name
fields {
name
type {
name
kind
}
}
}
}Para luego hacer la consulta con esos campos.
- Input
{
secrets{
id
secret
}
}- Output
{
"data": {
"secrets": [
{
"id": "U2VjcmV0T2JqZWN0OjE=",
"secret": "HTB{********}"
}
]
}
}