This article is my summary note after learning on the PortSwigger Academy. If you curious about the Academy, You can visit the PortSwigger Academy in this link.


1. Attack

1.1 Finding GraphQL Endpoint

1.1.1 Common endpoint name

GraphQL services if often use some endpoints.

  • /graphql
  • /api
  • /api/graphql
  • /graphql/api
  • /graphql/graphql

Or, if these common endpoints doesn’t return GraphQL response, we can also try appending /v1 to the path, like below:

  • /graphql/v1
  • /api/v1
  • /api/graphql/v1
  • /graphql/api/v1
  • /graphql/graphql/v1

When accessing GraphQL endpoints, it often response to any non-GraphQL request with a "query not present" or similar error.

1.1.2 Request Method

It is best practice for GraphQL endpoints to only accept POST request that have a content-type application/json. To help to protect against Cross Site Request Forgery (CSRF) vulnerabilities. However, some endpoints may accept alternative methods, such a GET request or POST that use a content-type x-www-form-urlencoded. If we can’t find GraphQL endpoint using POST, we also able to try using GET method.

1.1.3 Universal Queries

wip…

1.2 Discovering schema information

GraphQL have useful function called Introspection Queries, it help us to queries/discover the information about schema. We can also use Introspection Queries to disclose potentially sensitive data, such as description fields in the schema. Let’s exploring this Introspection Queries function.

1.2.1 How to use introspection queries?

It is quiet easy to use Introspection Queries to discover the schema information. Query the __schema field, this field is available in the root type of all queries. Like regular queries, we can specify the fields and structure of the response we want to be returned when running an introspection query. For example, we want the response contain only the names of available queries:

{
	"query": "{__schema{queryType{name}}}"
}

Note: It is best practice for disabled Introspection Queries in the production environment, but this advice is not always followed.

1.2.2 Full introspection query

We can run a full Introspection Queries against the endpoint, so that we can get as much information on the schema as possible. For example, we can use query below to return all available queries, mutations, subscriptions, types and fragments.

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

Note: If the Introspection Queries is enabled but the above query doesn’t run, try removing the onOperation, onFragment and onField directives from the structure. Many endpoints do not accept these directive as part of an introspection queries, and we can often have more success with Introspection Queries by removing them.

1.2.3 Visualizing introspection results

Response of Introspection Queries can be full of information and hard to read. We can use tools like graphql-voyager for better discover the result with visual representation.

1.2.4 Suggestions

When the Introspection Queries are disabled, we can use suggestions to glean information on an API’s structure. Suggestion are feature of Apollo GraphQL platform in which the server can suggest query amendments in error messages. These are generally used where a query is slightly incorrect but still recognizable, for example: "There is no entry for 'productInfo'. Did you mean 'productInformation' instead?". We can potentially glean useful information from this.

Note: clairvoyance is a tool that uses suggesions to automatically recover all or part of a GraphQL schema, even when Introspection Queries is disabled.

1.3 Bypassing GraphQL introspection defenses

If we cannot get Introspection Queries to run, try inserting a special character after the __schema keyword. When developer disable Introspection Queries, they could use a regex to exclude the __schema keyword in queries. We should try characters like spaces, new lines and commas as they are ignored GraphQL but not by flawed regex. If developer only excluded __schema{, then the below Introspection Queries would be executed:

{
	"query": "query{__schema {queryType{name}}}"
}

If didn’t work, try to use alternative method, like:

  1. GET request
  2. POST request with content-type of xxx-form-urlencoded For example if we use GET request with URL-encoded parameters.
GET /graphql?query=query%7B__schema%0A%7BqueryType%7Bname%7D%7D%7D

1.4 Bypassing rate limiting using aliases

Ordinarily, GraphQL objects can’t contain multiple properties with the same name. “Aliases” enable us to bypass this restriction. Also we can use “Aliases” to return multiple instances of the same type of object in one request and to brute force a GraphQL endpoint.

Many endpoints will have some sort of rate limiter in place to prevent brute force attack. Some rate limiters work based on the number of HTTP requests received rather than the number of operations performed on the endpoint. Because aliases effectively enable us to send multiple queries in a single HTTP message, they can bypass this restriction.

For example, below shows a series of “Aliases” queries checking whether store discount code are valid. This operation could potentially bypass rate limiting as it is a single HTTP request, even tough it could potentially be used to check a vast number of discount codes at once:

query isValidDiscount($code: Int) {
	isvalidDiscount(code:$code){
		valid
	}
	isValidDiscount2:isValidDiscount(code:$code){
		valid
	}
	isValidDiscount3:isValidDiscount(code:$code){
		valid
	}
}

2. Prevention

To prevent many common GraphQL attacks, take the following steps when deploy API to production:

  1. If the API not used for general public, disable the Introspection Queries. This makes it harder for an attacker to gain information about how the API works, and reduce the risk of unwanted information disclosure.
  2. If the API used for general public then you want to leave Introspection Queries enabled. However, you should review the API’s schema to make sure that it does not expose unintended fields to the public.
  3. Make sure the suggestions are disabled. This prevent attackers from being able to use tools (like Clairoyance or similar) to glean information about the underlying schema.
  4. Make sure that your API’s schema does not expose any private user fields, such as email address of user IDs

2.1 Preventing GraphQL brute force attacks

This generally involves restricting the complexity of queries accepted by the API, and reducing the opportunity for attackers to execute Denial-Of-Service (DOS) attack. To defend against brute force attacks:

  1. Limit the query depth of your API queries. The term “query depth” refers to the number of levels of nesting within a query. Heavily-nested queries can have significant performance implications and can potentially an opportunity for Denial-Of-Service (DOS) attack if they are accepted. By limiting the query depth your API accepts, you can reduce the chances of this happening.
  2. Configure operation limits. Operation limits enable you to configure the maximum number of unique fields, aliases, and root fields that your API can accept.
  3. Configure the maximum amount of bytes a query can contain.
  4. Consider implementing cost analysis on your API. Cost analysis is a process whereby a library application identifies the resource cost associated with running queries as they are received. If a query would be too computationally to run, the API drops it.

2.2 Preventing CSRF over GraphQL

To defend against Cross Site Request Forgery (CSRF) vulnerabilities, need to make sure this following design when designing API:

  1. API only accepts queries over JSON-encoded POST.
  2. The API validates that content provided matches the supplied content type.
  3. The API has a secure Cross Site Request Forgery (CSRF) token mechanism.

References