Amateurs hack systems, professionals hack people.
There are many things in life that are beyond our control. One of these is the fact that we are all vulnerable to attack. This is something that we must accept and learn to deal with, because it is a part of life. There are many different types of vulnerabilities, and each one of us has our own unique set.
Some people are more vulnerable to physical attacks, while others are more vulnerable to emotional or mental attacks.
(If you are vulnerable 😜 let's give us a star at https://github.com/blst-security/cherrybomb )
As humans, computers are vulnerable to physical exploitation, or more vulnerable to misconfigurations.
We all have vulnerabilities, and we all have the strength to overcome them.
Our Job is to reduce the surface attack..
In the previous article, I introduced graphQL and explained how it works.
In this article, I'd like to focus on security flaws, followed by concrete examples.
Security Flaws
With new technology comes new vulnerabilities. With the growing popularity of GraphQL on the web, I'd like to talk about a specific type of vulnerability that is frequently overlooked in GraphQL implementations. By default, QraphQL does not implement authentication. This means, by default, graphQL allows anyone to query it. Any sensitive information will be available to attackers unauthenticated.
What types of attack ?
As any API technology, GraphQL is vulnerable to common API vulnerability.
But in this article I will be more specified.
Let's dive in details
Recon/Introspection
GraphQl is introspective.
So the first step is usually to run an introspection query to obtain a copy of the schema. The schema will help with understanding the attack surface of the exposed GraphQL API.
It is widely recommended to get all the fields' names and types before attempting any attack, try this command.
query{__schema{types{name,fields{name}}}}
In order to understand how to query the database you can use the following command, and you will extract all types and arguments.
query{__schema{types{name,fields{name, args{name,description,type{name, kind, ofType{name, kind}}}}}}}
Enumeration
Now we understand the architecture of the data, we can query it and extract some values.
When you start enumerating, keep an eye on the output because data may leak. You can also try to query with a non-existent field and hope to receive useful information from the server. You can use Kiwi.com graphQL to understand how GraphQL queries work and extract data.
We can also search a specific city with passing, search as argument and the city as value.
You can check my previous article , if you want to get more basic info.
Authentication Vulnerabilities and Batching
As mentioned before, GraphQL does not implement authentication by default, which can lead to authentication flaws.
In addition, GraphQL allows you to brute force the login with one API request. This attack is named "Batching Brute-force."
This approach would trick external security monitoring into thinking that there is no brute-force.
A simple example of brute-force login: the input parameter should have the credential's value.
You can also use this technique to brute-force 2FA.
SQL Injection in GraphQL
Injection attacks are a type of attack where malicious code is injected into a vulnerable input field, such as a form field, and then executed by the application. Injection attacks can be used to exploit vulnerabilities in an application to gain access to sensitive data, execute arbitrary code, or perform other malicious actions.
SQL injection is quite well known, but it can also occur in our context.
Any field that has a string type is a potential candidate for string injection. Also, it is worth noting that the ID default scalar is serialized in the same way as a string, making it vulnerable to injections.
type Post {
id: ID
title: String
content: String
}
type Query {
post(id: ID): Post
}
The resolver back-end request:
function post({ id }) {
return query('SELECT * FROM POSTS WHERE id = ' + id);
}
injection looks like:
' or '1'='1
In this case 1=1 is always true, so the server will give back all existing posts. I won't deal to the remediation you can check OWASP article about it.
CSRF in GRAPHQL
CSRF is an acronym for Cross-Site Request Forgery. It is a type of attack that occurs when a malicious web site, email, blog, instant message, or program causes a user's web browser to perform an unwanted action on a trusted site for which the user is currently authenticated.Generally GraphQL use POST request, usually used to change application state. But GraphQL endpoint accept Content-Type set to application/json, which is invulnerable to CSRF. At first glance GraphQL seems to be invulnerable to this type of attack , but the truth GraphQl implementation are often affected by CSRF. You just need to change to content type to text/plain for example, and the middleware will convert it to application/json.
Another problem, because chrome browser has by default Samesite attribute set to Lax, the browser will send cookie only for GET request. So let's say you find a graphQL endpoint configured without CSRF tokens, and change content-type, the last thing is to try to send it as GET request to by pass chrome mechanism.
Not a problem in fact, GraphiQL does allow mutations via GET requests.
Traversal Attack
A traversal attack occurs when an access control list is not configured well. This type of attack is very simple to exploit but not easy to find.
Let's take an example with a user and a customer:
{
user {
id
username
customers {
id
username
}
}
}
Here is simple request that take from an authenticate user his Id and username, in order to get the customer's id and usernames.
By default the user is authorize to get only his customers.
But if the authenticated user try to fetch the customers of his customers?
{
user {
id
username
customers {
id
username
customers{
id
username
}
}
}
}
With a bad configuration, an authenticated user is able to fetch all customers and their customers.
Securing GraphQL from this type of attack seems pretty hard.
I suggest you to use a whitelist approach when validating user input for both the GraphQL query and variables. parameter to help prevent this type of attack.
I found an interesting article that talk about it in more details.
Broken Object level Authorization
Broken Object level authorization is when an object is given permissions to perform an action, such as read, write, or execute. Broken object level authorization is when an object does not have the correct permissions to perform an action. This can happen if the object's permissions are not set correctly, or if the object is not given the correct permissions when it is created.
OWASP API security top 10 has BOLA(
Broken Object level authorization) at top, as I told before authorization is the headache of the developers explicitly for GraphQL developers. When you pen-test an GraphQL API always check if the user is authorized to access the resource, audit the GraphQL queries and mutations.
There are many ways to mitigate broken object level authorization. Some common methods are:
-Implement a least privilege model, where users only have access to the resources they need.
-Use access control lists (ACLs) to explicitly define which users have access to which resources
-Monitor user activity and look for any unusual or unauthorized access.
-Regularly audit your system with DAST (dynamic application testing) use CherryBomb an useful cli tool for audit your API against logical flaws.
Failure Rate Limiting
As the complexity of a GraphQL API grows, it becomes more difficult to protect against denial-of-service attacks. REST APIs limit the number of HTTP requests that can be made, but a GraphQL query can take many actions, which can use a lot of server resources. Rate-limiting strategies for REST APIs, which limit the number of HTTP requests, are not adequate for protecting a GraphQL API.
There are two general ways to defend against denial of service attacks of this type. The first option is to limit recursion depth, so that queries that return large sets of results will be rejected. The second solution is to implement a complexity scoring system, in which each part of a query is given a complexity score. Any request with a total complexity greater than the maximum value allowed would be rejected.
GraphQL as API Gateways
It is common to see GraphQL servers used as API Gateways or proxies for other APIs or micro services.
The GraphQL interface acts as a front-end interface to an internal API.
If implemented badly, an attacker may be able to inject parameters or change the path to the back-end API.
Let's take a scenario of an endpoint that fetches another user's profile.
type Query {
me: User!
userID(id: ID!):User
}
type User {
id: ID!
name: String!
Users: [User!]!
}
The resolver looks like:
export const userID = async (id: string) => {
let results = await req.get(`http://api.com/user/${id}`);
return results.data;
}
By injecting "4/delete/" into the id parameter, the Back-end API may translate the request in this way:
GET http://api.com/user/4/delete
In this case, in order to mitigate these issues, make sure that the variable is exactly what you expect and is not trying to exploit an underlying system.
You can define a Regex pattern or other rules to verify the inputs before injecting them into subsequent requests, database queries, etc.
As GraphQL continues to grow in popularity, it's important for organizations to be aware of the potential security risks involved in using this technology. Adequate authentication and authorization should be implemented to mitigate these risks. By implementing proper authentication and authorization measures as well as correctly validating client inputs, organizations can help mitigate these risks.
In conclusion,
GraphQL has the potential to become the new standard for API design or to become a new font-end API, but it has some inherent risks that need to be mitigated.
Top comments (2)
Good article, important also.
I use row level security in the rdbms behind every API. Most security risks mentioned above, are solved by implementing access control close to the data and never in the application or api-code. And, of course, authentication from client right down to the rdbms. My clients are all allowed to do 'select * from users'. Only their own row is returned.
Thank!