GraphQL is an API data query language and query runtime engine. It is an alternative to REST, SOAP and other API query types and works differently for all of them. It is unique in that it has only one endpoint. Where other APIs may have, for example,/v1/test1, /v1/test2 etc which each perform different actions, GraphQL has just a single endpoint (often /graphql, but not always) and uses queries, mutations and subscriptions to request data or make changes in the GraphQL database.
Introspection
One of the greatest gifts a penetration tester dealing with GraphQL can be given from the developers is if they have left introspection on for the instance.
Introspection essentially allows us to ask the GraphQL instance questions about itself and the way its queries, mutations, subscriptions etc. work rather than the data held within it.
The below query will return full information on the layout of the schema:
{__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}}}}}}}}
This can return a lot of data and it’s often not easily comprehendible as-is. Numerous tools exist for making more sense of this data such as GraphQL Voyager and InQL (a BurpSuite add-on). These can make working with large introspection query responses much easier.
This data can then be used to understand the way queries etc. work and are formed, meaning it may be possible to create queries that were unexpected or unintended by the developers and are not part of the standard functionality of the web application or API.
In a recent engagement, BTL found the web application was running using a GraphQL backend. Introspection was turned off for the public endpoint, but included in the scope of the test was a dev copy of the website. The GraphQL endpoint on this dev instance of the website had introspection enabled, so it was possible to use this to enumerate the database schema.
CSRF
Not exclusive to GraphQL requests, Cross-Site Request Forgery allows requests to be sent to the server from any source. Using social engineering through a link in an email or similar, this can be used to have a victim perform an authenticated request against the system without their knowledge.
Most GraphQL requests are in a JSON format by default, but it is often possible to send a POST request as URL encoded form. With a JSON formatted request, a pre-flight check is sent (OPTIONS) which returns several headers to the browser including the Cross-Origin Resource Sharing configuration headers, such as Access-Control-Allow-Origin, Access-Control-Allow-Credentials etc. The browser then compares these configuration headers to the request that has been passed and decides whether or not to send it.
For example, if the Access-Control-Allow-Origin header for Test Bank has only “https://www.testbank.com” set, then a JSON formatted request sent from “ Attacker – The Domain Name Attacker.com is Now For Sale. ” would not be completed by the browser, and thus a user would be safe from this particular attack avenue.
A request formatted as a URL encoded form is what browsers view as a “simple” request, and no pre-flight checks are carried out on them, the request is just sent. It may be that the browser doesn’t show the response to this request, but that doesn’t stop the request you’ve sent from being processed in the GraphQL backend. Therefore, if these requests do not have an anti-CSRF token, then a CSRF attack against the GraphQL instance would be possible.
SQL/NoSQL Injection
At the back of a GraphQL instance will be a database that the GraphQL API is used to interact with. There is support for numerous different databases, with the most common being SQL (MySQL, MSSQL) or NoSQL (Oracle NoSQL, MongoDB) databases.
SQL Injection in these cases work very similarly to going through a normal web request. Often adding a single quote to a query parameter can be enough to cause an error as the request has been split with that single quote denoting the end of the query. This is a very simplistic test, and if it fails it doesn’t mean that blind injections may not still work in some other way or using different combinations of characters.
NoSQL Injection is a little more complex. They use product-specific queries which are often tied to the database used, so knowledge of the particular database engine in use is required to exploit it.
If it can be found, however, it can often be more impactful even than SQL Injection as it may be possible to use it to run code within the application due to the way NoSQL databases are built and run.
Both of these vulnerabilities rely on inputs not being sanitised, which is something that should always be implemented to reduce the risk of these and many other types of attacks.
Batching Brute-Force
Usually, something such as logging in can be protected against brute-force attacks by having a limit on the number of attempts made within a certain period.
GraphQL allows the batching of queries in one request. This means that if an authentication procedure is carried out against the GraphQL API, it would be possible to send as many different sets of credentials as you wanted in one query, therefore not triggering any rate limiting etc. that may be in place.
GraphQL is still a relatively new technology and further vulnerabilities (and safeguards) will likely appear over time. Exploits against it are often unique takes on an existing attack methodology but its possible flaws are not as well documented as REST, SOAP etc. Nonetheless, it is still important to note that these vulnerabilities can exist and should be protected against.