Understanding and Dealing with Cross-Site Request Forgery Attacks (CSRF) in GraphQL
CSRF (Cross-Site Request Forgery) is one of the top 3 most common vulnerabilities of web applications. It forces authenticated users to perform unwanted actions. In this post, we explain how it works and how to remediate it in your GraphQL API.
Receiving an email from a user who lost their account is not really surprising. But what about receiving this kind of email from 20% of your users? And what about also receiving complaints about fund transfers on your web application that weren't initiated by your users?
That is frightening!
This could happen if an attacker is able to impersonate your users and make mutations on their behalf (changing email address, transferring funds, etc).
How is it possible? Maybe with a CSRF attack on your web application or GraphQL API…
What is a CSRF?
CSRF is amongst the top three most common vulnerabilities in web applications and it can be really harmful. Cross-Site Request Forgery (CSRF) is an attack that forces users to perform unwanted actions on a web application where they are currently authenticated. These actions happen without the user even knowing it!
Why “cross-site”?
The initialization of the attack begins with the user visiting another website (i.e., not the website vulnerable to CSRF). This website often belongs to the attacker, and the attacker has used phishing or social engineering techniques to make the user visit it.
Why “request forgery”?
During the attack, a request to the vulnerable website is forged (i.e., created) by the user’s browser. As always, the browser will automatically include any credentials associated with the site (session cookie, IP address, …). Therefore, if the user is currently authenticated to the site, the site will have no way to distinguish between a forged request sent by the victim and a legitimate request sent by the victim.
CSRF attacks target functionality that causes a state change on the server (email address modification, password reset, purchase, fund transfer, etc.) because forcing the victim to retrieve data doesn’t benefit the attacker, as only the victim will receive the response.
Let’s give an example!
Your website superwebsite.com has user accounts. Let’s say that after being authenticated, a user can change their account email by visiting the link superwebsite.com/user/newemail=new@example.com.
In addition, assume there is an attacker with its own website malicioussite.com and this website includes the following code:
<img src="https://example.com/user/newemail=malicious@email.com" />
Using some trick (phishing, social engineering, …), the attacker succeeds in making the user visit his website. They won’t see anything except a broken image, but their browser will fetch the image source link! So, if they are still logged in to your website, the action will be performed and their email will be changed.
Comments:
- The blue part of the schema happens without the user even knowing it!
- If the user is an admin, the whole application is compromised and this can lead to all data being deleted or modified.
- This is a simple CSRF attack using a GET request but more complex ones can also do POST requests.
Attacks based on POST requests
POST requests cannot be delivered with img tags or hyperlinks, but the attacker can create them with forms. The form inputs are filed and hidden from the victim, then the victim presses a button for a totally different reason and the form is submitted! It can also be submitted automatically using JavaScript.
GraphQL endpoints typically accept Content-Type headers set to application/json
only, which is wrongly believed to be invulnerable to CSRF. In fact, many middlewares may translate other formats (form-data
, form-urlencoded
, …) into application/json
.
Is my GraphQL endpoint vulnerable to CSRF attacks?
This is now a question you should ask yourself… but figuring out the answer remains challenging! Indeed, GraphQL is new and lacks just the proper tooling, many development teams are just skipping security...
That's why at Escape, we created the first GraphQL Security scanner that allows developers to find and fix vulnerabilities in GraphQL applications during the development lifecycle before they even reach production!
It understands the business logic of GraphQL APIs and looks for more than 50 kinds of vulnerabilities so that you never have to worry again about:
- Requests forgery (CSRF, SSRF…) and injections (SQL, NoSQL, XSS…)
- Resolver performance (GraphQL bombs, N+1 issues, cyclic queries, query complexity DOS…)
- Tenant isolation (access control, data segregation between users…)
- Sensitive data leaks (personally identifiable information, tokens, stack traces, secrets…)
- … and more than 50+ advanced security issues!
We constantly update our engine with state-of-the-art GraphQL Security research so that you never have to worry about the security of your GraphQL application again!
Remediation
First, security awareness helps reduce the likelihood and severity of CSRF attacks (e.g. being careful with phishing or using an admin account only when necessary). However, it is not enough.
The first step is to allow only POST requests to perform data-altering actions. Obviously, this will prevent GET requests to be used as attack vectors. However it will still be possible to exploit CSRF based on POST requests, so you will have to implement other measures as well.
There exist several solutions to prevent CSRF, however, none of them is perfect. Let’s explain the more reliable and “simple” ones. (You can find more here).
Challenge-Response
One effective measure is to include a challenge for the user such as a CAPTCHA or requiring the re-entry of the user's password. When these techniques are properly implemented, they are fairly successful, however, it isn't always feasible to include a challenge in every request, and it can have a negative impact on user experience.
Synchronizer Token Pattern / CSRF token
Synchronizer token defences have been built into many frameworks. It is strongly recommended to research if the technology you are using has an option to achieve CSRF protection by default before trying to build your custom token generating system. You will still have to do token management but it will be much easier.
As explained in the following schema, the principle is to ask for a CSRF token before submitting the GET/POST request with it. Therefore the attacker doesn't have access to the CSRF token and cannot create a valid request (his attack skips steps 3 to 5 and goes directly to step 6).
Be careful:
- Don’t save the CSRF token as a cookie! Either include it as a hidden field in a form or in header parameters.
- CSRF token is unique per session.
Warnings
Same Origin Policy
Modern browsers have mechanisms to prevent certain CSRF attacks by default, so make sure not to deactivate these protections. For example, don’t set the “Access-Control-Allow-Origin” header to “*”, otherwise you allow every website to make requests to your website.
Prevent XSS
Cross-Site Scripting (XSS) is another vulnerability and if you don’t protect your web application from it, CSRF mitigation techniques can almost all be defeated, and the severity of the CSRF attack is amplified.
GraphQL Playground has had a known XSS security issue since 2020. Here we give 2 very concrete scenarios to show you how it could unfold if it were to be exploited in your organization.
Additional Resources
- Learn interactively how to protect your website from CSRF attacks
- Excellent explanation of CSRF
- CSRF vs XSS: What is the difference?
- Wanna know more about GraphQL Security in general? Check out our blog post "9 GraphQL Security Best Practices" and learn how to build safe GraphQL APIs.
- OWASP mitigation cheat sheet