Portuguese English German

What is CSRF

Cross-Site Request Forgery (CSRF) is one of the most common and dangerous web application vulnerabilities. In this article we will learn how it works and how to fix it. We will talk about the different methods available to mitigate CSRF and their differences.

Didatic Scenario

Imagine the following scenario:

  1. You have a bank account on MoneyFriend;
  2. You have the habit of performing payments using the bank website;
  3. A certain day you dedide to enter in your account to check your balance;
  4. Then you discover that your balance is $ 100;
  5. Still logged in the bank website (http://banco.com.br), you open a new tab and access http://malicioso.com.br (portuguese name for 'malicious');
  6. After http://malicioso.com.br finished loading, you return to the bank tab and notice that your balance now is $ 70.

In this scenario, you probably were a victim of an attack that explores the vulnerability known as Cross-Site Request Forgery (CSRF). Let's understand what happened behind the curtains, starting with the name of this vulnerability.

"Cross-Site Request Forgery" Meaning

"Cross-Site" means that a vulnerability is explored between sites, in other words, it's possible to identify that at least two websites are involved in this attack. And "Request Forgery" means that the request is forged, a fake request.

Combining all words, we can conclude that this vulnerability is a request forged by an attacker in a website (i.e., http://malicioso.com.br) to affect another site (i.e., the bank website). The attacker forces the victim's browser to perform a request to the bank website, as if the victim itself made the request. In this case this malicious request means the missing $ 30 in your balance.

In more nerd terms, we can say that this vulnerability is an exploitation of the server trust in the client's browser. The server considers that all browser requests are always sent by the user, when it actually could came from another websites (forged requests). In our example, the request was triggered by http://malicioso.com.br.

Diving in the technical realm

In this scenario, we can notice that $ 30 are missing from our account. The "missing amount" actually became a payment or a transference for an individual or a company. Let's suppose that after looking our bank statement we identified a $ 30 transference to John Hacker.

Before understanding how this transference happened, we need to understand how a legitimate transfer works. Let's check it step-by-step:

1) User sign in the bank website and gets a cookie containing the session identificator

CSRF - Login

A little bit about the Session Identificator...

If multiple customers access the bank at the same time, how could the bank identify each of them? The bank gives an unique identificator to each customer, which is the session identificator. This identificator usually is composed by letters and numbers, e.g., 1F9fNJIKSAGFj130imk013mVNH. This identificator usually is stored in a Cookie, which is a temporary file that could store low amounts of data and is managed by the customer's browser. This cookie is then passed along in all requests sent from the customers to the bank.

...Returning

2) User access /transference and receives a form to fill

CSRF - Transference Form

3) User sends the transference form filled

CSRF - Performs Transference

Ok, we understood how a legitimate transference works. Now, thinking as an attacker, we need to develop a way to make the user browser generate a transference request, but specifying the attacker account instead. This request would look like this:

CSRF - Performs Malicious Transference

But we have a problem. How could we, as attacker, discover the session identificador value? That's the gotcha. We don't need to discover the session identificador, because whenever the victim's browser makes a request to the bank, it always embed the bank's cookies, including the session cookie. Actually it's more technically correct to say that the browser embeds all cookies related to that domain in specific, i.e., banco.com.br.

We as attacker can't identify the session identificator inside the cookie but we don't need to. The browser will execute the malicious request that we've built passing this session cookie for us. The browser actually doesn't know that it is being used as a part of an attack. This "naiveness" by the browser is a problem known as "The Confused Deputy Problem" in computer science.

Developing our exploit

Exploit is the term used to denominate programs that explore vulnerabilities in an automated fashion. In other words it means that we will develop a bunch of code to explore Cross-Site Request Forgery in an automated fashion. As a side note, we can say that the website http://malicioso.com.br contains an exploit.

To create our exploit, we need to understand the ecossystem around it.

Notice that so far we understood the request that the victim's browser should send, but we didn't reach the practical part, how the browser will in fact create this request, that is. Also notice that this attack requires the same victim's browser to be used, because the session cookie vary from browser to browser. If the user logged in the bank using Internet Explorer and execute our exploit using Google Chrome, the attack won't work.

See below the HTML code present on http://malicioso.com.br:

<html>
    <body>
        <img width="1" height="1" src="http://banco.com.br/submitTransference?dst_account=5678&amount=30.00" />
    </body>
</html>

How weird! A "img" tag? Isn't this tag used solely to show images in a web page? In visualization terms, yes. However, in practice, what matters for us is that the browser created the (GET) request to the address "http://banco.com.br/transference?dst_account=5678&amount=30.00". We don't really care about the response. What matters is that the browser performs the request. If it works we will known looking at the bank statement from our attacker's account (John Hacker's account).

The Big Picture

CSRF - Big Picture

Common Questions

1) If the <img> tag performs only GET requests, we could just change the transference page from the bank to receive POST requests instead of GET. This way, all GET requests, including the one in this example, won't work. Right? Wrong! We just need to change our exploit to work with POST requests. Check the example below:

<html>
    <!-- jQuery is a popular Javascript library -->
    <!-- It eases the development, e.g., during -->
    <!-- Ajax requests creation (our case) -->
    <script src="jquery.min.js"></script>
    <script>
        $.ajax({
          type: "POST",
          url: "http://banco.com.br/submitTransference",
          data: "dst_account=5678&amount=30.00"
        });
    </script>
</html>

2) But Cross-Origin Resource Sharing (CORS) will prevent this request from being triggered. Maybe. This answer is more complex and mandates a higher understanding of "Same-Origin Policy" and CORS. Let's go:

"Same-Origin Policy" Introduction...

Looking to protect web pages from accessing sensitive data from other web pages, Same-Origin Policy was created, which is nothing more than a security policy implemented by each browser. This policy puts certain restrictions in how web pages from different origins (e.g., different domains as banco.com.br and malicioso.com.br) could interact.

However in some cases web pages from different origins want to interact in full and they want to authorize each other. For such authorization to be recognized by the browser, it's necessary for it be made using Cross-Origin Resource Sharing (CORS), which will be explained below:

CORS Introduction...

Cross-Origin Resource Sharing (CORS) was created to allow certain restrictions applied from Same-Origin Policy using specific criterias.

When the browser detects a request from different origins, it first identifies whether the request is secure to be sent.

To verify if the request it secure, it verifies whether the request is using the methods GET, POST or HEAD and whether there are no unsual headers, such as "X-JWT". If somehow the request has other configuration than the mentioned, the browser is forced to send a "Preflight Request".

A "Preflight Request" is a request that uses the OPTIONS method and passes some extra headers such as "Origin", "Access-Control-Request-Method" and "Access-Control-Request-Headers" and is forwarded to the server without any body. For example:

OPTIONS /resources/post-here/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
Origin: http://foo.example
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER

The server then should answer accepting the origin, the method and the headers, explicitly, using the headers "Access-Control-Allow-Origin", "Access-Control-Allow-Method" and "Access-Control-Allow-Headers". For example:

HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER
Access-Control-Max-Age: 86400
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain

Afterwards the browser understand that the request is secure to be sent and continues sending, allowing also the recently authorized origin to access the response headers and response data.

...Returning to the question

Of course that the bank won't allow malicioso.com.br, because it would require a formal request from malicioso to bank. That said malicioso.com.br would then avoid at all costs to require a "preflight request" from the browser.

It turns out that whether the bank web pages themselves perform requests using unsual methods or unusual headers, the request that will be triggered from the exploit won't work. It's common when the bank processes data using an Application Interface (API) that receives only JSON content, forcing the browser to change the request content type to "application/json". It already configures a non secure request to the browser, thus requiring a "preflight request".

On the other hand, if the request was deemed secure, the exploit will successfully trigger the request, but won't be able to get the response data. But as we've talked abive, we don't care about the response. What matters is to trigger the request, thus CORS may or may not prevent CSRF attacks depending on how bank's frontend requests access the bank's backend.

Mitigation

The attack essence is simple, but as we can see there are many details that influence the attack and consequentely the defense. We will understand some strategies that could be used to prevent CSRF exploitation. I can tell you in advance that such strategies are divided in measures to be taken by users and/or by software developers.

Mitigation techniques for users

Remember the bank scenario? Well, if the user wasn't logged in, the attack won't be possible, right? Yes, the attack wouldn't be possible, because the user must be logged in for the attack to happen.

But that's not the single measure that a user could take. It's also possible to make use of plugins such as noScript which tries to block malicious requests before they be executed. The problem is that usually noScript goes beyond what it should block and blocks many more quests, thus making the web browsing experience very bad. At least that was my impression in the past. Nevertheless, the idea that a plugin could interfere and identify requests between sites that could be malicious is interesting. Ideally it would be absorbed by the browser, but it takes much more time.

Another approach would be to use different browsers. For example you could have a habit of accessing your bank using one browser and for all other matters you use another browser. This could be a good strategy to prevent CSRF attacks as well.

Mitigation techniques for developers

But seriously, we can't expect that all users will protect themselves, right? That's why we, as software developers, should always prevent CSRF attacks on our applications.

If we review how the CSRF attack anatomy, we will note that the server can't distinguish whether the request is legitimate or forged. The CSRF mitigation focus on applying such validation to the server and there are multiple ways to do it.

Mitigation using Nonce

Number Used Once (Nonce) is one way to prevent CSRF attacks. In short it adds a validation to the server before executing the desired operation (e.g., amount transference on banco.com.br).

The server sends a nonce code to the page containing the transference form and awaits to receive the same token. The secret of this control lies in the fact that the malicious website (malicioso.com.br) don't know the value of this token. Assuming that this token isn't easy to guess, of course. Because if the token be "123", being always composed by 3 digits, we only need to try all possibilities [0-9][0-9][0-9] and quickly guess the token.

Notice that I've used the work token instead of nonce. I did that because nonce means that nonce would be used on single time, being necessary to generate another nonce for the next operation, when the token doesn't have this restriction.

If the user has one single token for session, that is enough. It's not necessary to generate a new token for each operation, although it is still interesting from a security point of view, the token maintainance becomes harder and also could affect the user experience between multiple tabs or multiple devices. For example during a bank transference in one tab, if you're doing another operation in another tab, it could fail if the token don't be regenerated.

It's important to remember that the token must be unique per user, be hard to guess and be generated using a secure Pseudorandom Number Generator (PNRG). Otherwise it would be possible to guess the next token. And last, but not least, nothing of the items above matter if the server doesn't negate requests that don't contain the expected CSRF token.

Mitigation using Double Submit Cookies

Before we talk about "Double Submit Cookies" or "Triple Submit Cookies", we need to talk about how cookies work:

Cookies Introduction...

Cookies could be created by the browser, using the Javascript language, or by the server, while passing "Set-Cookie" headers in HTTP responses. In such header, the server must inform the cookie name and value, but it could go beyond.

It's possible to add a HttpOnly flag, which makes a cookies unreadable from Javascript instructions. Very useful for cookies that only need to be present on Http requests, e.g., session cookie. There's also the Secure flag, which forces the cookie to traffic in HTTPS connections only. Cookies with this flag set won't be sent in HTTP connections.

Besides flags, it's also possible to specify in which domain this cookie will be valid and the path, e.g., /transference.

There's also the "SameSite" attribute which is one of the mitigation strategy that will be covered in the next lines :)

Set-Cookie header example:

Set-Cookie: name=Anderson; domain=banco.com.br; path=/transference; Secure; HttpOnly

...Returning to "Double Submit Cookies"

Basically the idea behind Double Submit Cookies is to send a cookie with a random value and a parameter in the request body containing this very same random value. The server must then verify if the value passed on the cookie is the same as the value passed on the body parameter.

This approach is stateless, because differently from a Nonce/Token solution, the server won't need to know this random value beforehand, and it's also efficient because the attacker won't be able to read the value from the cookie to replicate it in the request body.

It could also be improved if we tie together the Session ID with the random value to guarantee that could not be possible to bypass such control only by sending random values, even if identificals, in the cookie and in the request body.

The problem is that is the attacker has access to any subdomain (in an authentical fashion or through Cross-Site Scripting (in portuguese)), for example malicioso.banco.com.br, that could read cookies from banco.com.br. This way the attacker would be able to read the cookie and use the same random value in the request body. Cookies could also be written through "Man-In-The-Middle" and "Man-In-The-Browser" attacks.

Given the weakness of this control above mentioned, another mitigation mechanism was suggested, named "Triple Submit Cookies". However it is also likely to be circumvented. There's also an interesting analysis made here and a presentation here.

Mitigation using "SameSite" Cookie's attribute

Recently introduced by Google Chrome 51, the "SameSite" attribute informs the browser that cookies should only be sent from the origin that created them.

It makes all requests from "malicioso.com.br" to "banco.com.br" do not include any cookie! Ideally all your cookies will have this flag by default, as they should have HttpOnly and Secure as well. They should only be disable if there is a good explanation to give up using them.

Such protection applies only to requests considered 'secure' to the browser when we talk about CORS. Even GET requests would be protected.

That would be great if such protection be added by default to all cookies, but as it is new, if that happens, many websites would stop working on Google Chrome and the users will then move to other browsers that haven't got this control, such as Firefox. It could also result in the return of Internet Explorer ... just kidding.

Mitigation using alternate controls

Other controls that are not tokens, nor the combination of cookies with parameters in the request body helps to prevent CSRF. They're:

  • "Referer" header validation: Yes, it's simple and it helps a lot. An attacker could not modify the "Referer" header. One attempt of CSRF attack will bring in the request the website that originated the request was "malicioso.com.br" instead of "banco.com.br";
  • "Origin" header validation: Similar to the "Referer" validation above, but it's different. The Origin header was created to prevent attacks between different origins and is, differently from "Referer", sent even in HTTP requests originated from HTTPS URLs;
  • Add a Captcha: As Captcha mitigates automated attacks, it also works to block CSRF attacks, because the Captcha would need to be filled correctly.

Common Questions

1) I'm developing an API and I've disabled the CSRF protection from my framework to make the API work. As I'm building an API there is no problem right? Wrong! This is the most common misconceptions from developers. They subestimate the damage that could happen from a CSRF attack. Mostly because they don't know CSRF. Even developing an API, you could store the session identificator in a cookie or on localStorage (a private space for each domain to store more data than a cookie; it uses the "key => value" format and was introduced on HTML 5 with a 5 MB limit) and pass it as a HTTP header. Commonly used in the "Authorization" header, but you can use any other, e.g., "X-JWT" (as a side note, headers that starts with "X" mean customized headers, in other words, headers that aren't present in the HTTP specification) to pass a JSON Web Token for example. However to store a session identificator on localStorage creates another vulnerability (see the next question #2 below).

A little bit more about localStorage...

LocalStorage was introduzed on HTML 5 and has the objective to provide more space than cookies for applications read and write arbitrary data, without an expiration time. It works with keys and values, e.g., an application could store the key "user" containing the following value:

{
  id: 10,
  name: "Anderson Dadario",
  plan: {
    name: "Basic",
    price: {
      amount: 10,
      currency: "USD"
    }
  }
}

There are other storage opttions besides localStorage, as sessionStorage, Indexed DB, Web SQL Database, etc, but they won't be discussed here.

2) I'm storing the session identificator on localStorage. Am I safe? Not yet. You can say that you're preventing CSRF because the browser only embeds cookies and not values from localStorage, but on the other hand you've introduced a new vulnerability in your application.

Your session identificator that before could be protected in a Cookie from Javascript instructions to read the cookie using the "HttpOnly" flag, is now vulnerable to be read by Javascript, as such protection isn't available for localStorage. In other words a Cross-Site Scripting (XSS) (in portuguese) could access localStorage, get the session identificator and send to the attacker's server, thus leading to an account hijacking.

Conclusion

As you can see, the essence of CSRF (also known as XSRF), is simple, but exists a layer of complexity for attacks and defenses that are more sophisticated.

This attack can cause, and have caused, a lot of damage impersonating user actions. It's also commonly presented with high severity in penetration tests reports.

If you liked this article, please share and subscribe to the list below to receive in your inbox my future articles. Soon I will have more online courses that will include practical examples of CSRF attack and defense, not to mention more vulnerabilities.

That's all folks!

Thank you and stay tuned :)

Share on Twitter Share on Facebook Share on Google Plus Share on LinkedIn Share on Hacker News

Popular Posts

Newsletter


Twitter