During a web application penetration test, we discovered a vulnerability related to the configuration and mismanagement of access controls on GraphQL.
By also taking advantage of an IDOR, this critical vulnerability allowed us to break out of the partitioning and access data belonging to other instances. We explain how by presenting, first, the different steps to exploit GraphQL before exposing the concrete case encountered during the pentest.
How to exploit GraphQL?
Let’s start with a GraphQL exploitation demonstration performed on a test application accessible on Github that contains vulnerabilities.
Retrieving the GraphQL introspection schema
The first step is to retrieve the GraphQL introspection schema. This schema allows us to obtain important information about the structure of GraphQL, in particular the relations between the different objects and the names of the different fields.
To do this, several methods can be used to retrieve this schema, such as Burp’s InQL extension, or manually by using the following GraphQL query if introspection is enabled.
{__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}}}}}}}}
Note that the schema can be imported into GraphQL Voyager, a tool that provides an interactive graph of the GraphQL schema.
With this tool, the relations between the different objects become clearer and the exploitation will be much easier.
Exploiting GraphQL vulnerabilities
The objective will be to use all the relations to access the different objects containing the information of the application. For example, accessing user information from the query that retrieves private notes.
If we click on the Private Pastes
button and intercept the query with Burp Suite, we notice that the following query is transmitted to the server:
query getPastes {
pastes(public:false) {
id
title
content
ipAddr
userAgent
owner {
name
}
}
}
By comparing this query with the interactive graph of GraphQL Voyager, we can determine the path taken through the different relations of the schema.
The __typename
attribute can be added to the query so that the server returns the name of the object present.
By understanding this logic, it is possible to retrieve information from the users
object, through the users
relation.
query getPastes {
__typename
users {
username
password
__typename
}
}
Thus, the server returns the confidential information of the various users registered on the platform.
Of course, one could also go through the search
link and then UserObject
, to reach the users’ information:
query getPastes {
__typename
search {
... on UserObject {
username
password
__typename
}
__typename
}
}
Finally, in the same way, the following query retrieves data from GraphQL:
query getPastes {
pastes {
id
title
content
public
userAgent
ipAddr
ownerId
burn
owner {
id
name
__typename
}
__typename
}
search {
... on UserObject {
id
username
password
__typename
}
__typename
}
audits {
id
gqloperation
gqlquery
timestamp
__typename
}
__typename
}
Exploiting GraphQL using an IDOR
Context
During a penetration test, we were asked to test a SaaS application with a multi-tenant architecture, naturally with data partitioning between different companies. And of course, each of these companies had its own users, roles and confidential information.
Indeed, our objective was to verify the correct partitioning of data between the different companies, in other words to try to recover information to which we are not supposed to have access at all. Therefore, this was a grey box penetration test, with a “standard” user account of a company at our disposal.
To sum up, we first had to detect a link between our company and another on the platform and find ways to get around the existing silos.
Discovery of an IDOR vulnerability
In our methodical testing, we identified an IDOR vulnerability in a feature of the application. This functionality allowed specific roles to be assigned to projects.
An IDOR (Insecure Direct Object Reference) is a security vulnerability that occurs when a web application does not properly control access to objects. This means that a user can read, modify or delete an object that does not belong to them, such as sensitive information or features.
As a result, we discovered that a user from our company was able to assign a role from another company to one of our projects, due to a broken access control to change the settings of a project. This created a link between a project belonging to our company and a role belonging to another company.
Exploiting broken access control
To exploit the broken access control in GraphQL in combination with the IDOR vulnerability, we can use GraphQL to retrieve information related to our projects, including the roles assigned to them. Then we can use that role to pivot to sensitive information belonging to another company, using access control weaknesses.
Enterprise_Vaadata -> List project roles -> Pivot to Enterprise B role -> List users in Enterprise B -> Retrieve data belonging to users
How to secure GraphQL?
To correct the problems of broken access control on GraphQL, it is important to implement security mechanisms to ensure that only authorised users have access to the desired information and functionality. There are several solutions for this.
First, a robust authentication and authorisation system must be in place. This means that users must provide valid credentials to access the application and that permissions are checked before allowing access to specific information or features.
Secondly, it is important to implement role-based access controls. This means that different roles have different permissions to access specific information and functionality.
Finally, it is important to implement data validation controls to ensure that users cannot access information or functionality that is not intended for them by changing their query parameters.
Author : Alexis Martin – Pentester @Vaadata