hacktricks/network-services-pentesting/pentesting-web/graphql.md

23 KiB

GraphQL

☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥

Introdução

GraphQL atua como uma alternativa à API REST. As APIs REST exigem que o cliente envie várias solicitações a diferentes pontos de extremidade na API para consultar dados do banco de dados do backend. Com o GraphQL, você só precisa enviar uma solicitação para consultar o backend. Isso é muito mais simples porque você não precisa enviar várias solicitações à API, uma única solicitação pode ser usada para coletar todas as informações necessárias.

GraphQL

À medida que novas tecnologias surgem, novas vulnerabilidades também surgem. Por padrão, o GraphQL não implementa autenticação, isso é responsabilidade do desenvolvedor implementar. Isso significa que, por padrão, o GraphQL permite que qualquer pessoa o consulte, e qualquer informação confidencial estará disponível para atacantes não autenticados.

Ao realizar seus ataques de força bruta de diretório, certifique-se de adicionar os seguintes caminhos para verificar as instâncias do GraphQL.

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

Depois de encontrar uma instância aberta do GraphQL, você precisa saber quais consultas ela suporta. Isso pode ser feito usando o sistema de introspecção, mais detalhes podem ser encontrados aqui: GraphQL: uma linguagem de consulta para APIs.
É frequentemente útil perguntar a um esquema GraphQL informações sobre quais consultas ele suporta. O GraphQL nos permite fazer isso…

Fingerprint

A ferramenta graphw00f é capaz de detectar qual mecanismo GraphQL é usado em um servidor e, em seguida, imprime algumas informações úteis para o auditor de segurança.

Enumeração Básica

O GraphQL geralmente suporta GET, POST (x-www-form-urlencoded) e POST (json).

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

Com esta consulta, você encontrará o nome de todos os tipos sendo usados:

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 argumentos). Isso será muito útil para saber como consultar o banco de dados.

Erros

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

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

Enumerar o Esquema do Banco de Dados via Introspecção

A introspecção é uma técnica que permite que um invasor obtenha informações sobre a estrutura do banco de dados GraphQL. Isso pode ser feito usando a ferramenta GraphiQL, que é uma interface de usuário para testar consultas GraphQL. A partir daí, o invasor pode enviar uma consulta introspectiva para obter informações sobre o esquema do banco de dados, incluindo tipos, campos e relacionamentos. Essas informações podem ser usadas para identificar vulnerabilidades e explorar o banco de dados.

/?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 meta-informações do graphql (nomes de objetos, parâmetros, tipos...)

Se a introspecção estiver habilitada, você pode usar o GraphQL Voyager para visualizar em uma GUI todas as opções.

Consultando

Agora que sabemos que tipo de informação é 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.

Observe que o tipo da consulta "flags" é "Flags", e este objeto é definido como abaixo:

Você pode ver que os objetos "Flags" são compostos por nome e valor. Então, você pode obter todos os nomes e valores das flags com a consulta:

query={flags{name, value}}

Observe que, no caso do objeto a ser consultado ser um tipo primitivo como string, como no exemplo a seguir:

Você pode consultá-lo simplesmente com:

query={hiddenFlags}

Em outro exemplo, onde havia 2 objetos dentro do objeto "Query": "user" e "users".
Se esses objetos não precisam de nenhum argumento para pesquisar, você pode recuperar todas as informações deles apenas pedindo pelos dados que você quer. Neste exemplo da Internet, você pode extrair os nomes de usuário e senhas salvos:

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

Parece que de alguma forma ele pesquisará usando o argumento "uid" do tipo Int.
De qualquer forma, já sabíamos disso, na seção Enumeração Básica foi proposta uma consulta que nos 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 eu executo essa consulta, verá que "user" tinha o arg "uid" do tipo Int.

Então, realizando uma força bruta leve em uid encontrei que em uid=1 um nome de usuário e uma senha foram recuperados:
query={user(uid:1){user,password}}

Observe que eu descobri que poderia pedir pelos parâmetros "user" e "password" porque se eu tentar procurar por algo que não existe (query={user(uid:1){noExists}}) recebo este erro:

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 (obrigado a @BinaryShadow_)

Se você pode pesquisar por um tipo de string, como: query={theusers(description: ""){username,password}} e você procura por uma string vazia, ele irá despejar todos os dados. (Observe que este exemplo não está relacionado com o exemplo dos tutoriais, para este exemplo suponha que você possa pesquisar usando "theusers" por um campo String chamado "description").

GraphQL é uma tecnologia relativamente nova que está começando a ganhar força entre startups e grandes corporações. Além de faltar autenticação por padrão, os pontos finais do GraphQL podem ser vulneráveis a outros bugs, como IDOR.

Pesquisa

Para este exemplo, imagine um banco de dados com pessoas identificadas pelo e-mail e pelo nome e filmes identificados pelo nome e pela classificação. Uma pessoa pode ser amiga de outras pessoas e uma pessoa pode ter filmes.

Você pode pesquisar pessoas pelo nome e obter seus e-mails:

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

Você pode procurar pessoas pelo nome e obter os filmes aos quais estão inscritos:

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

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

Você também pode pesquisar vários objetos ao mesmo tempo. Neste caso, é feita uma pesquisa por 2 filmes:

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

Ou até mesmo 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
        }
      }
    }
  }
}

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):

Para este exemplo, imagine um banco de dados com pessoas identificadas pelo e-mail e nome e filmes identificados pelo nome e classificação. Uma pessoa pode ser amiga de outras pessoas e uma pessoa pode ter filmes.

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):

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.

Também pode haver uma mutação para criar pessoas (chamada addPerson neste exemplo) com amigos e arquivos (observe que os amigos e filmes devem existir antes de criar uma pessoa relacionada a eles):

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

Ataque de força bruta em lote em 1 solicitação de API

Esta informação foi retirada de https://lab.wallarm.com/graphql-batching-attack/.
Autenticação através da API GraphQL com envio simultâneo de muitas consultas com credenciais diferentes para verificá-las. É um ataque de força bruta clássico, mas agora é possível enviar mais de um par de login/senha por solicitação HTTP por causa do 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 robô 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 de uma só vez. Obviamente, é possível enviar milhares em uma única solicitação da mesma maneira:

Como podemos ver na captura de tela da resposta, as primeiras e terceiras 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 tem o token de sessão de autenticação correto.

GraphQL sem introspecção

Cada vez mais, os pontos finais do graphql estão desativando a introspecção. No entanto, os erros que o graphql gera quando recebe uma solicitação inesperada são suficientes para que ferramentas como clairvoyance possam recriar a maior parte do esquema.

Além disso, a extensão do Burp Suite GraphQuail observa as solicitações da API GraphQL que passam pelo Burp e constrói um esquema interno do GraphQL com cada nova consulta que vê. Ele 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.

CSRF no GraphQL

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

{% content-ref url="../../pentesting-web/csrf-cross-site-request-forgery.md" %} csrf-cross-site-request-forgery.md {% endcontent-ref %}

Lá fora, você será capaz de encontrar vários pontos finais do GraphQL configurados sem tokens CSRF.

Observe que as solicitações do GraphQL geralmente são enviadas por meio de solicitações 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 pontos finais GraphQL também suporta solicitações POST form-urlencoded:

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é-voo, é 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 por 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 ataque XS-Search, pode ser possível extrair conteúdo do endpoint GraphQL abusando das credenciais do usuário.

Para mais informações, verifique o post original aqui.

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.

A modificação das variáveis de entrada da consulta pode levar a detalhes sensíveis da conta vazados.

A mutação pode até levar à tomada de conta da conta tentando modificar os dados de outras contas.

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

Bypassar autorização no GraphQL

Encadeando consultas é possível burlar um sistema de autenticação fraco.

No exemplo abaixo, você pode ver que a operação é "forgotPassword" e que ela só deve executar a consulta forgotPassword associada a ela. Isso pode ser burlado adicionando uma consulta ao final, neste caso adicionamos "register" e uma variável de usuário para o sistema se registrar como um novo usuário.

Estruturas do GraphQL vazadas

Se a introspecção estiver desativada, tente olhar o código-fonte do site. As consultas geralmente são pré-carregadas no navegador como bibliotecas JavaScript. Essas consultas pré-escritas podem revelar informações poderosas sobre o esquema e o uso de cada objeto e função. A guia Sources das ferramentas do desenvolvedor pode pesquisar todos os arquivos para enumerar onde as consultas estão salvas. Às vezes, até mesmo as consultas protegidas pelo administrador já estão expostas.

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

Ferramentas

Scanners de vulnerabilidades

Clientes

Testes automáticos

{% embed url="https://graphql-dashboard.herokuapp.com/" %}

Referências

☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥