GraphQL Injection: Discovery and Exploitation | Bug Bountys

Tools Used:

Introduction

This course details how you can use GraphQL Introspection to find data that may not be exposed directly by an application.

If you feel confident, you can try to do this exercise without following the course, then you can come back to the course to read some details and tips. If you want to do it by yourself, you can follow the following steps:

  • Find the GraphiQL endpoint to be able to easily send GraphQL query.
  • Lookup how to use GraphQL Introspection.
  • Find the hidden data to get the key to solve this exercise.

GraphQL

GraphQL is similar to SQL in the way that it’s a standard to request and organize data (as opposed to MySQL that is a product that uses the standard SQL). When coming across a new standard/product/technology it’s always a good idea to go through the “Introduction” course that may be available. For example, for GraphQL, you can read Introduction to GraphQL. This will help you get a better idea of the technology. Some of this “training” also offer you a sandpit/online version to start playing with the tool.

Detection of the issue

First, you will need to look at the traffic sent to the server when accessing the projects link. You should see a query to a /graphql/ endpoint. Here, the frontend graphiql is available, so you should find a nice IDE if you browse to the endpoint.

GraphQL offers to people querying it a way to get metadata about the information available. This is very similar to the information_schema tables in modern databases. It helps users know what is available. However, this has a security impact as it may allow attackers to get access to information that may not be otherwise exposed (security by obscurity).

Depending on the version of the GraphQL standard, you can use one of the following queries:

  • For older versions:
<code>query IntrospectionQuery {
   __schema {
     queryType { name }
     mutationType { name }
     subscriptionType { name }
     types {
       ...FullType
     }
     directives {
       name
       description
       args {
         ...InputValue
       }
       onOperation
       onFragment
       onField
     }
   }
 }

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


</code>
  • For more recent versions:
<code>query IntrospectionQuery {
   __schema {
     queryType { name }
     mutationType { name }
     subscriptionType { name }
     types {
       ...FullType
     }
     directives {
       name
       description
       args {
         ...InputValue
       }
       locations
     }
   }
 }

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


</code>

The only difference being the move from onOperation, onFragment and onFields to locations.

To get in more details, we use __schema to get more information about the data available. Then we ask for more information. The most important part is to ask for types and look at everything with the name Query (“name”: “Query”):

<code>query MyQuery {
  __schema {
   types {
     name
     fields {
      name
     }
   
   }
 }
}
</code>

Using one of these queries, you should get a list of queries that you can run in place of the getprojects used by the application. One of the queries should give you access to the key to solve this exercise.

<code>query Query {
 [NAME] {
   id
 }
}
</code>

where [NAME] is the name of the query. For example, with projects:

<code>query Query {
 projects {
   id
 }
}
</code>

Conclusion

This exercise showed you how you can use GraphQL Introspection to get access to information that is not necessarily exposed by the application, then you manage to write you own GraphQL query to get access to this data. It’s very common that storage and query mechanisms offer a way to get metadata about the information available, this is always a good idea to see if there is not more data than the one exposed by the application. I hope you enjoyed learning with PentesterLab.

Leave a Reply