Cross-site request forgery (CSRF) protection is a security measure that prevents attacks in which unauthorized commands are executed on behalf of a user who is working with a web application. Web apps are often integrated with several other systems, and it’s important for developers at all levels to be aware of how to use CSRF protection properly.
Simple, yet powerful
If you’re reading this then you probably already know – when you’re navigating a web application (like Facebook, for example) and submit a form on it, your browser will not just send the form data. It will also include additional metadata like cookies or information about your browser. But, did you know that the application will receive the same data even if the form is submitted from within another application? As a user’s authentication data is sent as part of the request, this can be exploited for malicious activity on behalf of an authenticated user. If the data sent is the same in both cases, how can the webserver of the original application be certain that the request originated in-house and not via a third party? Well … it can’t be reliably detected, and thus CSRF protection needs to come into play. Exploiting missing CSRF protection is straightforward:
- An attacker crafts a malicious web page that makes HTTP calls to the vulnerable URLs (sign in, add item to shopping cart, transfer money, etc).
- The attacker tricks a user to visit the malicious web page.
- The HTTP call is triggered, resulting in an action being made on behalf of the victim in the target application (assuming the victim is already signed into the application and thus their authentication data will be automatically included in the request).
CSRF attacks are both simple and powerful, which is why CSRF protection is so common. That being said, from time to time, it happens that CSRF protection is lacking at some HTTP endpoint, especially in cases of communicating with third-party services. Sadly, that also happened to us, and we are not alone.
Note: The missing CSRF protection was reported to us on our bug bounty program. There is no evidence suggesting that this would have been used for malicious activities before we’ve fixed the vulnerability.
Use case: Facebook login flow
Here’s a real-world example: Implementing CSRF protection properly when manually building a Facebook login flow. Even though the Facebook documentation is comprehensive, a small oversight can result in CSRF vulnerability, allowing an attacker to trick victims into using your service while being signed in using an attacker’s account.
All modern web application frameworks have CSRF protection built in, and it works great while the user is acting within the application. However, what happens when you need to change state in a different application – like requesting Facebook sign in? In short, the automatic CSRF protection instantly becomes unavailable.
Luckily, it’s not difficult to implement CSRF protection yourself. There are two ways to do it: stateful and stateless. Both approaches use a special CSRF token that is sent to the client as part of a server response. In the stateful solution, you would also save the token into a database (associated with the user’s session). When the client application makes a state-modifying request, it needs to include the CSRF token in the request – either as part of form data, or in a header. If the server application fails to verify the token, the request is discarded and flagged as a CSRF attempt.
In this technique, we send a random value in both a cookie and as a request parameter, with the server verifying if the cookie value and request value match.
The OWASP article also mentions potential risks, raised by Rich Lundeen, in using a Double Submit Cookie pattern. Unless you can guarantee HTTPS on all of your subdomains, an attacker could override cookies over an HTTP connection. Also, if a subdomain was vulnerable to XSS attack, the attacker could set cookies for a parent domain from the subdomain. If you encrypt the value sent in cookie, then these problems go away (assuming the attacker doesn’t know the secret key used for encryption).
Now, let’s update our sequence diagram with the CSRF protection methods:
Notice that the communication between the web application and Facebook is not a traditional CSRF protection. Facebook doesn’t verify the CSRF token, as it doesn’t have anything to compare its value with. In this case, the CSRF attack is prevented by utilizing
state parameter that is included in Facebook call from your application, and also included as part of the Facebook response that contains user data. According to the OAuth 2.0 specification, the
state parameter is recommended, not required, and its purpose is to maintain the state between the request and the callback. There are many use cases for the
state parameter, and one of them is CSRF protection.
At the beginning of this section, I teased a small detail in the Facebook documentation that can lead to CSRF risk. It should be clearer, now that the
state parameter has been explained. Since the parameter is only recommended and not required, Facebook login will work correctly without this parameter. It’s also worth mentioning that the
state parameter isn’t part of OAuth 1.0 specifications (commonly used back in 2013). If you depend on a third-party library to provide Facebook login functionality, make sure that you are not using a prehistoric version of it, and that the library makes use of the
The consequences of getting it wrong
Let’s have a look at all the injection points in our Facebook login flow. What would happen if CSRF protection was missing somewhere?
If it were missing in place A, an attacker could send a Facebook sign in request on behalf of the victim. As a result, the victim would get signed in using their own Facebook account. This isn’t necessarily a catastrophic scenario, as the victim is unexpectedly signed in, but no user data is compromised.
Place B is where things get interesting. If we didn’t use
state parameter for the Facebook call, or didn’t validate it properly, the attacker could capture the attacker’s Facebook callback request using a proxy, prepare a malicious page, and, once the victim visits the page, the request would get executed and the victim would became signed in using the attacker’s credentials. This could lead to the victim’s adding their billing credentials to the attacker’s account unknowingly, as they would be under the impression that they are signed into their own account. A million other things could happen as well, but financial fraud and identity theft tend to make people stand up and take notice.
In this post we looked at what cross-site request forgery is, how and why it must be prevented, and why it’s often an issue when communicating with third-party services. Using the example of a Facebook login flow, we analyzed the impact of missing CSRF protection at specific parts on communication flow.
Right now, CSRF protection is certainly an industry best practice. However, due to built-in support in modern frameworks, it happens that developers do not fully understand it, and then forget about it when working out of their comfort zone (aka, third-party services).
Here at Showmax, we understand the value of CSRF protection and care deeply about keeping our users safe. We hope that this article helped you understand CSRF protection better, and explained why it’s an important part of the modern Web.
Disclaimer: Facebook is a trademark of Facebook, Inc. Showmax does not endorse any use of Facebook. Showmax is using “Sign in using Facebook” functionality as an alternative sign in method.
The views and opinions expressed in this article are those of the authors and do not necessarily reflect the official policy or position of Facebook. Assumptions and conclusions made within the analysis are not reflective of the position of Facebook.
Examples of analysis performed within this article are examples for informational purposes only and not for the purpose of providing legal or IT security advice.