Are CSRF Tokens Necessary?
By: Posh Ai
Tags: Csrf, Csrf Protection, Csrf Token, Chatbots, Conversational Ai

by: Matt McEachern, Posh Co-founder and CTO

CSRF, which stands for Cross-Site Request Forgery, is a common attack vector for vulnerable web applications with potentially catastrophic consequences. At number 8 in the 2013 OWASP TOP 10*, CSRF is an age-old attack that has been well-known by both hackers and implementers [1].

While there is a multitude of accepted prevention techniques, each has its pros and cons. Amongst the most popular and recommended techniques are ones that implement a CSRF Token. The CSRF Token technique requires that all state-changing endpoints accept an additional parameter (i.e. the CSRF Token) whose value was sent alongside the html/css/js of the web application. Upon each request, the web application’s backend server verifies the correctness of this token and rejects the request if it does not correspond to the session. A decent analogy is that while session cookies authenticate the request’s browser, CSRF Tokens authenticate the code that’s making the request. While effective, a downside to the CSRF Token pattern is that it requires stringent developer effort and changes to client-side code.

Beyond CSRF Tokens, there are many other techniques that aim to thwart CSRF attacks [2]. Each technique depends on certain assumptions of the web application security model, especially the Same Origin Policy (SOP) which is implemented by all modern browsers today.

In this post, I provide a quick summary of CSRF and the SOP, while highlighting a few shortcomings of the SOP in thwarting CSRF attacks. Then, I discuss a suite of prevention techniques that don’t involve CSRF Tokens and bring attention to their pros and cons.

*In the updated 2017 OWASP Top 10, CSRF has been “retired, but not forgotten.”

Quick Summary of CSRF

CSRF is the execution of a “forged” request to a web application's backend server from an unknown origin. The scenario can best be explained by example:

Let’s say you just logged into banking.example.com to check your account balances and pay off your credit card. Because you’re logged in, your browser now holds a session cookie for banking.example.com. This cookie gets sent by the browser with every request to banking.example.com’s backend server — otherwise, you’d be logged out if you refreshed the page or closed the browser window.

In the same browser, you then decide to check your email and see one that’s titled, “Re: Your Google Account May be Compromised.” That seems scary. You think, “Dang, I hope my account is okay!” Your emotions and fear go against your logic, and you open the email. It says, “go to the following link to secure your account.” Instinctively, you click the link without even reading the URL.

Your browser proceeds to open the link, which loads an inanimate white page. The URL’s origin is getpwned.example.com. Well, it’s only a white screen, and you think, “that was weird” so you close the tab and go on with your day. Later you find out that your entire checking account balance has been wired to an unknown account in China.

What happened? When your browser opened the webpage at getpwned.example.com, it began executing all kinds of nasty javascript code. It turns out this javascript, which was sent amongst the contents of the webpage, made requests to banking.example.com, instructing it to transfer all of your checking account balance to the unknown account. Because your browser was holding cookies for banking.example.com, it’s default behavior was to attach them to each outbound request.

In summary: javascript code from getpwned.example.com made fully authenticated requests to banking.example.com, effectively stealing all your money. This is CSRF.

In the example, it’s important to note that the victim had to be tricked into visiting the malicious website. This trickery is the result of Social Engineering. Such carefully executed Social Engineering is not always needed to perform CSRF attacks, however. In fact, every single webpage you visit can perform CSRF; surfing the Web requires a lot of trust.

Fortunately for users like you, security-minded implementers have introduced CSRF preventions into their web applications that would prevent catastrophic scenarios like the one in the banking.example.com example. As mentioned previously, CSRF Tokens are one such prevention technique. Many of these techniques depend on the Same Origin Policy.

The Same Origin Policy

The Same Origin Policy (SOP) is a critical component of the web application security model. It outlines a series of policies and rules for how code and data can interact across origins and is implemented by all modern web browsers.

One of the major goals of the SOP is to prevent malicious websites from accessing sensitive information or making state-changing requests to other web applications. While the SOP has been widely successful at thwarting a Pandora’s Box of problems, there are subtleties that can still leave web applications vulnerable to cross-origin attacks like CSRF, as discussed below.

Relaxed Restrictions Within the SOP

Despite the various rules and restrictions outlined by the SOP, there are certain resource-sharing actions that are unrestricted.

One type of relaxed restriction is the ability to embed cross-origin content via the following HTML tags [3]:

<script>

<link>

<img>

<video>

<audio>

<object>

<embed>

<applet>

<frame>

<iframe>

It’s important to note that while cross-origin resources can be freely downloaded and embedded using these tags, javascript running in the browser cannot access or read the contents, save high-level information like image dimensions, actions performed by a script, etc.

It’s also important to note that when embedding these resources, you’re instructing the browser to make HTTP(S) GET requests.

While the following look’s strange, your browser would execute the GET request without restriction.

<img src=”https://dashboard.example.com/post-message/hello">

It’s irrelevant whether the response is a valid image — the request is still executed. This is why it’s important that state-changing endpoints on your web application cannot be invoked with the GET method.

Okay, so the browser can make unrestricted GET requests. I’ll just make sure that all of my state-changing requests can only be invoked by other methods like POST. Under the SOP, I’m safe, right?

Unfortunately, it’s not that simple.

There are also ways to execute POST requests without restriction by the SOP. These request formulations are where the most damage is done by CSRF.

Via the <form> tag, modern browsers that implement the SOP still allow cross-origin POST submit actions. An intricate CSRF attack could, for example, use javascript to create a hidden <form>, populate its fields, and auto-submit, effectively sending an authenticated cross-origin POST request. This is bad news, as POST is one of the standard methods for state-changing actions.

But why in the world does the modern web infrastructure allow such madness?

A likely explanation is that to deprecate this functionality would mean lots of breaking changes to the millions of web applications deployed globally. This functionality is a legacy feature that will likely not be changed in the name of backwards compatibility.

So to what degree can these cross-origin POST <form> requests be exploited?

It turns out that there are limits to the formulation of the request itself. To highlight a few:

You cannot set custom headers

The Content-Type header is limited to one of the following values:

application/x-www-form-urlencoded multipart/form-data text/plain

So using a <form> is the only way to submit unrestricted cross-origin POST requests?

Unfortunately, it’s yet-again not that simple.

Certain unrestricted cross-origin requests can also be formulated using XHR/fetch. All requests can in fact be classified into 1 of 2 bins: “simple” and not “simple” [4]. Cross-origin “simple” requests can be freely sent under the SOP.

A “simple” request is one that meets the following conditions:

The method must be either GET, HEAD, or POST

Only the following headers can be set manually:

Accept Accept-Language Content-Language Content-Type* DPR Downlink Save-Data Viewport-Width Width

*For the Content-Type header, there are only 3 allowed values:

application/x-www-form-urlencoded multipart/form-data text/plan

For XMLHttpRequestUpload objects, no event listeners can be registered

The request cannot use a ReadableStream object

It’s interesting to note that the cross-origin requests allowed by various HTML tags are a strict subset of these “simple” requests.

Wow, that’s a lot to take in. So the browser can execute any of these “simple” requests without restriction by the SOP?

Yes, with one distinction:

Unless Cross-Origin Resource Sharing (CORS) is configured, javascript running in the browser will not be able to read the response. Again, it’s important to take note: These cross-origin “simple” requests are still successfully executed.

So what happens when attempting to make a request that isn’t “simple”?

At last, this is where the SOP comes to the rescue!

In this scenario, before making the request, the browser will send a “preflight” request to the target resource URI. A preflight request uses the OPTIONS method and allows the server to respond with a whitelist of allowed actions. This whitelist is used by the browser to determine if the request is allowed. If the request oversteps its bounds, the browser will block the request. It won’t be sent. Plain and simple.

The whitelist of allowed actions is called a Cross-Origin Resource Sharing (CORS) policy. CORS lets web application developers deliberately and intentionally relax certain constraints of the SOP. This is important because making “non-simple” cross-origin requests are still an important functionality of many web applications.

So to prevent CSRF attacks, is it sufficient to require all my endpoints to only accept “non-simple” requests (and let the preflight response disallow all cross-origin invocations)?

This question hints at a suite of CSRF prevention techniques that don’t depend on CSRF tokens. The techniques can be summarized as follows:

Require all backend endpoints to accept only “non-simple” requests.

When incoming requests are “simple” (i.e. they don’t overstep the conditions outlined above), let the server simply reject them.

Carefully Configure your CORS policy to never allow cross-origin requests that aren’t “simple”.

An example technique that falls into this suite would be a web application server whose endpoints require the presence of a Content-Type header with the value, application/json.

So is this sufficient?

Yes, in theory.

But in practice, this is not always the case. Ugh, more caveats!

To assume protection from CSRF by requiring only “non-simple” requests is to trust the browsers’ implementation of very nuanced details of the Same Origin Policy. As others have pointed out, historically there have been various bugs and loopholes that have allowed hackers to bypass preflight for “non-simple” requests [5]. Prominent examples include a vulnerability in Flash that allowed for non-standard headers to bypass preflight, and a bug in Chrome’s Navigator.sendBeacon API which allowed for non-standard values in the Content-Type header to bypass preflight [6,7].

There are arguments for why certain CSRF-prevention techniques that force requests to be “non-simple” are not sufficient enough to prevent CSRF (e.g. the bugs and loopholes referenced above). But even the arguments in favor of CSRF Tokens, however, depend on the proper implementation of the SOP (i.e. not allowing read-access to the response of a cross-origin request). So from the many points of view, we are all placing trust in our friends who are implementing modern browsers to adhere to the SOP. Web security in general requires faith and trust in a lot of different people, institutions, hardware, and software.

Conclusion

If there are only a few things I hope you take away from this post, they’re these:

Even when the Same Origin Policy is implemented perfectly as documented, there are still subtleties that allow for certain authenticated cross-origin requests to be executed, including GET and POST. As defined above, all “simple” requests fall under this category.

Various protection techniques, including the popular CSRF Token or the rejecting of all “simple” requests, still depend on the SOP to be implemented as documented.

CSRF is a very serious attack vector that requires attention by web application developers.

At Posh, we are bringing modern Conversational AI to the forefront. Our mission is to make natural conversation the new user interface.

References

[1] https://www.owasp.org/images/7/72/OWASP_Top_10-2017_%28en%29.pdf.pdf

[2]https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.md

[3] https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy#Cross-origin_network_access

[4] https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#Simple_requests

[5] https://www.nccgroup.trust/us/about-us/newsroom-and-events/blog/2017/september/common-csrf-prevention-misconceptions/

[6] https://hackerone.com/reports/44146

[7] https://bugs.chromium.org/p/chromium/issues/detail?id=490015