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
andonField
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:
- GET request
- 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:
- 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.
- 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.
- 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.
- 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:
- 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.
- Configure operation limits. Operation limits enable you to configure the maximum number of unique fields, aliases, and root fields that your API can accept.
- Configure the maximum amount of bytes a query can contain.
- 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:
- API only accepts queries over JSON-encoded POST.
- The API validates that content provided matches the supplied content type.
- The API has a secure Cross Site Request Forgery (CSRF) token mechanism.