loading...
Cover image for Protect your application from CSRF attacks
ProdOps

Protect your application from CSRF attacks

omerxx profile image Omer Hamerman Originally published at omerxx.com ・9 min read

Cross-Site Request Forgery attack and mitigations explained
Originally published at https://omerxx.com/csrf-attacks

"CSRF is an attack that forces an end user to execute unwanted actions on a web application in which they are currently authenticated. With a little help of social engineering (such as sending a link via email/chat), an attacker may force the users of a web application to execute actions of the attacker's choosing."


- DVWA

TL;DR
CSRF is as easy to attack as it is easy to protect from! There's no reason any web-facing application should not implement the relevant protection. Lots of known frameworks have it built in as a feature or an opt-in and on some it is offered as a middleware. CSRF will probably die in the next few years, when all modern browsers will adopt default same-site cookies protection. However, until that actually happens, web applications are exposed, and for no good reason.

In this post I'm going to quickly go over the basics, mitigations and then take a deep dive into a hands-on lab. The latter is obviously not a must for mitigation. But stick around if you want to get your hands dirty and get a more firm grasp of the attack vector.


What is it

Cross Site Request Forgery, "CSRF", or "XSRF", is a common vulnerability in web applications. It involves sending malicious requests from an external domain to the backend server, performing actions in the victim's name. The attack assumes a valid cookie from an authenticated victim.

Delivering the exploit involves some social engineering methods where a victim is tricked into visiting a URL or clicking a button on an HTML document. These can be shared via email, instant messaging and various other techniques.

If the attack is successful when the victim visits the malicious link it uses his session cookies to perform an action, usually without him being aware of it.

As an example consider this request:

http://bank.com/account/update?password='attackerPassword123'

If an authenticated user clicks the link, assuming his cookies for bank.com are still unexpired, his bank account password will change with the attacker's password. While this is a naive GET request, the attack is not limited to this method alone. It can be done with POST requests which are can be chained with stored XSS vulnerabilities to increase the attack surface and success rate.

Modern browsers adhere to the same-origin-policy restriction. They assume applications can only send requests to each other within their domain. In order to extend the restriction, applications use CORS headers. For example, the next header allows all domains to send HTTP requests to the server (and it is therefor highly UNrecommended):

Access-Control-Allow-Origin: *

More often than not there's a communication of requests between different services of an application. This requires extending the origins that are allowed to communicate with the backend app. However, many developers choose to solve the problem by setting * in the CORS header, effectively allowing anyone to communicate with them.

Note: SOP (same-origin-policy) and CORS do not prevent requests from reaching the server, they prevent a response. For this reason, CSRF is exploiting known state-changing requests and does not expect an answer.


Why it's important to know

First, because it's a relatively easy and common way for a low-skilled attacker to exploit the application's users. An example of a malicious action can be changing a user's email address or password, effectively overtaking an account. While the business risks differ it's pretty obvious why would any developer want to avoid the risk, especially knowing the simple steps to mitigation.



Mitigations

  • Token-based - One of the common ways and definitely the most robust one is the use of a token. Upon user request, the application generates a unique token that's added to the form as a hidden field. When the user sends the form back, the server is validating its authenticity by comparing the token to the one he had generated earlier.

"The server generates a token comprised of the user's session ID and timestamp (to prevent replay attacks) using a unique key available only on the server. This token is returned to the client and embedded in a hidden field for forms, in the request-header/parameter for AJAX requests. On receipt of this request, the server reads and decrypts the token value with the same key used to create the token."



- OWASP, CSRF Prevention, Encryption based

CSRF tokens should not be transmitted using cookies. If they would, the exploit will still be valid as it assumes the cookie through the victim, essentially using the generated token too.

  • Origin Validation - Using Origin or Referer headers, the application can validate the source of a request. This is not considered a high standard of protection as it is still exposed to stored XSS vulnerabilities. If a user manager to inject a malicious script in a vulnerable location on the application, the script will bypass the protection since it'll be triggered from within the domain name.

  • Prevent cross-site-scripting (XSS) - This is a topic of an entire post on its own but I'll keep it short. Use a protection library if possible, and read through the OWASP XSS prevention cheat sheet. Search and block all kind of scripting to fields, treat them all as texts, or escape as much as possible. Rule of thumb: escaping will never cover ass possibilities if possible avoid.


So, let's get back to earth. There's no need to invent the wheel. Most of the well-known web frameworks and CMS products out there have already done the work for you, or there's a community solution. Here's a partial list you can use to implement CSRF protection:

Tech Framework Comments
Python Django, Flask
Ruby Rails, Middleware The middleware is a standalone gem
Javascript / Typescript Nest, Express, Helmet Helmet is a standalone library
Golang Buffalo Buffalo form is implementing tokens by default

Let's get technical

For the getting-my-hands-dirty part I'm going to use DVWA. If you want to follow this section it's important to setup DVWA on your local machine (instructions below) and follow closely each step both in the post and in DVWA. Here's how to set it up:

docker run --rm -it -p 8080:80 vulnerables/web-dvwa

# The login screen would be @ http://localhost:8080
#
# While the login can be brute-forced, let's keep things simple for now:
# 1. Login - User: "admin", Password: "password"
# 2. Click "Create / Reset Database"
# 3. You're all set. Login again.

When in DVWA interface, select the CSRF module. The instructions below demonstrate all security levels offered by DVWA. In order to change security levels select DVWA Security from the left-hand-side menu, select the preferred level, and hit Submit.

Security level: Low

We're presented with a password and confirmation text box. Providing value and clicking the button fires a request to the server. We can use a proxy or the browser's dev tools to view the password change request:

GET /vulnerabilities/csrf/
  ?password_new=pass
  &password_conf=pass
  &Change=Change HTTP/1.1

The application seems to send the password and its verification as URI params. Since there is no Access-Control-Allow-Origin header set, and GET requests do not require any additional preflight requests of verification like POST, we're good to go. In order to exploit it, a simple social engineering is required. The attacker can build a simple HTML page with a link that leads to the GET request. The idea is, that the target is authenticated to the application with their cookies set. If that's the case, the request will go through, authenticating with the backend automatically since the cookies are already there. Here's an example for the page that can be embedded into an email message:

<html>
    <a href="http://192.168.0.10:8000/vulnerabilities/csrf/
            ?password_new=hacked
            &password_conf=hacked
            &Change=Change">
    View my Pictures!
  </a>
</html>



Security level: Medium

The hint by DVWA says:

"The developer believes if it matches the current domain, it must have come from the web application so it can be trusted."

Which means, the request needs to come from the same domain. But if there's a user-interactive system running under the same domain, where a user can POST input and see it, is it really safe?

Here comes in the XSS exploit: only allowing traffic from your own domain is not a bulletproof solution. If you have some kind of system that users interact with, say, a forum, they can post stuff for others to see. If they’re able to exploit it and inject a script (XSS) and others view it, the attacker will be able to bypass the protection of the same-domain setting and still launch the CSRF attack on others.

Trying to use a form here won't do any good since the application blocks any incoming request from an external domain and shoots out:

That request didn't look correct.

In order to make the request originate from the domain, we need to use some kind of script injection to the application pages. As an example: a script / html component that will be loaded and run by the app.

rXSS to the rescue!

Utilizing a reflected XSS bug in the web app allows bypassing the origin verification. With XSS (Reflected) on the menu of DVWA, any name inserted is presented on the screen to the user. HTML tags are not escaped so we can utilize the exact same line from the low-security level:

<a href="http://192.168.0.10:8000/vulnerabilities/csrf/
        ?password_new=hacked
        &password_conf=hacked
        &Change=Change">
  View my Pictures!
</a>

When served, it generates a clickable link. The link can then talk to the application from its own origin, using the cookies that are already stored since the user is already authenticated.

In order to serve the HTML we can set up a private local web server, or as a malicious attacker would do it, send it as an email. With a little help from social engineering, the victim can be lured into opening the mail and hitting the form in it.

In order to test, we can create the HTML file and run it on a local browser providing our input in the form, where we get:

Password Changed.


Security level: High

If we use the same form used in the "Medium level" section above, we end up with the next:

CSRF token is incorrect

What is a CSRF token anyway?

A CSRF token is a unique, secret, unpredictable value that is generated by the server-side application and transmitted to the client in such a way that it is included in a subsequent HTTP request made by the client. When the later request is made, the server-side application validates that the request includes the expected token and rejects the request if the token is missing or invalid.
CSRF tokens can prevent CSRF attacks by making it impossible for an attacker to construct a fully valid HTTP request suitable for feeding to a victim user. Since the attacker cannot determine or predict the value of a user's CSRF token, they cannot construct a request with all the parameters that are necessary for the application to honor the request.


- Portswigger

So the application is protected by a CSRF dynamic token. How can one exploit it anyway?

Using a similar method from the medium level, we now utilize a DOM XSS, using the XSS (DOM) option from the menu. What this means, is that we can run a javascript that's accessible to the browser's DOM. Access to the DOM allows us to programmatically fetch the CSRF token, effectively making it useless.

While I'm not going to go through the process of creating a DOM XSS on DVWA, here's a video that shows the entire exploit. I do think it's important to understand the risk of being exploited with scripts that can access the DOM, and keep these in the back of your mind when developing the frontend of your application.

Once the CSRF token is in our possession we can go ahead and use the same form to exploit our victim. The key takeaway from this level is the understanding of how additional seemingly low-risk vulnerabilities, can be chained together into a much-higher-risk attack.


General tips for preventing XSS and CSRF and safer coding

<script>...NEVER PUT UNTRUSTED DATA HERE...</script>

<!--...NEVER PUT UNTRUSTED DATA HERE...-->

<div ...NEVER PUT UNTRUSTED DATA HERE...=test />

<NEVER PUT UNTRUSTED DATA HERE... href="/test" />

<style>
...NEVER PUT UNTRUSTED DATA HERE...
</style>

Never **ever** use GET requests to change data!
POST is the right method here. Not only it requires a structured request, 
it only utilized preflight requests hat help enforcing same-origin-policy and more.


If you got here, thank you first of all for reading this far. I hope I've helped you understand the risk and maybe even how to exploit it (I know I helped myself). This post was part informational, part notes I've taken trying to dive into the subject. I hope that while it was somewhat a "notebook" structure, it was still valuable and readable. If you have any comments/ideas please do let me know.

ProdOps

We are a collaborative team of experts from different engineering disciplines. We build and deploy scalable solutions and consult on best practices to automate infrastructure, improve workflow and seamlessly manage applications in the cloud.

Discussion

markdown guide
 
 

Nice article Omer 👍
I have a question: Do you need to add csrf protection if you write an api ?
Aren't you safe enough if you accept json only content type and configure cors correctly ?

 

Hi Nikos!
The question is whether the API is authenticating users before letting them interact with it. If you don’t use cookies or basic auth, then like you said there’s no CSRF vector to protect from.
CORS is a very good starting point and accepting JSON only will make sure you don’t accidentally expose yourself to XXE. But I’m assuming that by configuring CORS you are allowing specific domains to access the API? If that’s the case then it’s still your responsibility to provide them with tokens.
Bottom line is, if there’s no cookie that sits in a browser, still valid and can be joined to incoming requests, then CSRF is out of the picture.
I hope this helps. Let me know what you think