The vulnerability known as A5 - Cross-Site Request Forgery (CSRF) has many names including session riding and one-click attack. It's a blind attack in the sense that the attacker is not directly attacking the application, but rather tricks a user into doing the attack for him. In this article we'll look at what's going on, how to fix it and also look at an attack specific to single page web applications.
This is the risk rating from OWASP:Threat Agents | Attack Vectors | Security Weakness | Technical Impacts | Business Impacts | |
---|---|---|---|---|---|
______ | Exploitability AVERAGE |
Prevalence WIDESPREAD |
Detectability EASY |
Impact MODERATE |
______ |
Consider anyone who can trick your users into submitting a request to your website. Any website or other HTML feed that your users access could do this. | Attacker creates forged HTTP requests and tricks a victim into submitting them via image tags, XSS, or numerous other techniques. If the user is authenticated, the attack succeeds. | CSRF takes advantage of web applications that allow attackers to predict all the details of a particular action. Since browsers send credentials like session cookies automatically, attackers can create malicious web pages which generate forged requests that are indistinguishable from legitimate ones. Detection of CSRF flaws is fairly easy via penetration testing or code analysis. |
Attackers can cause victims to change any data the victim is allowed to change or perform any function the victim is authorized to use | Consider the business value of the affected data or application functions. Imagine not being sure if users intended to take these actions. Consider the impact to your reputation. |
When a server receives a POST request, there is no secure HTTP or browser specific way in which the server can know how the request was created. Consider a form posting data a given URI of the server. The different fields in that form and their corresponding values are normally formatted in the application/x-www-form-urlencoded
and multipart/form-data
.
Additionally the browser will include a set of headers. The Cookie
header includes any cookies for the given url. The Referer
header contains the url of the page the request originated from. The Origin
header contains the domain name, port and schema of the request the request originated from, but has no path information.
Consider a website A that has no CSRF-protection. A user logged in to site A, is tricked by the attacker into visiting his site B. Javascript on site B automatically builds and submits a form to site A in a hidden iframe. Now because the user is already logged in on site A and the request is headed for site A, the browser will happily include the users cookies for site A. This includes the session cookie. So the server on site A, will see an incoming request with a valid session. And because it trusts the session it will happily accept the request and perform the required action.
For a relatively new list of high value targets having this vulnerability, see Egor Homakov's blog post: "A few CSRF-like vulnerable examples".
There are many ways developers have tried to mitigate this vulnerability.
The first kinds of CSRF attacks often used GET requests. The attacker would typically add an img
tag pointing to a url that performed some action. An example could be:
<img src="http://victimserver.com/add_friend?userid=123" style="visibility: hidden" >
While using GET requests for state changing operations is considered bad practice, as explained above, POST request are just as vulnerable to CSRF.
Referer
headerWhen looking at the headers above, the first thing that might strike you, is to use the Referer
header. But this is probably not the best choice. This header contains path information and is often blocked/removed in firewalls, browser plugins etc. for privacy reasons. Most browsers also strip it when moving between schemas, so a post from https to http would probably result in the header being stripped out.
Origin
headerWhile the Origin
header does not have the same privacy issues as the Referer
header, it lacks support in older browsers. It may be a suitable defense in the future, it may not be the best choice for now.
Here are two of the most popular approaches.
The token approach has quickly become a popular way of mitigating CSRF. Ruby on Rails and ASP.NET MVC both use this approach (authentication_token / anti_forgery_token). The server generates a cryptographically random value - a token or nonce - and sticks that token in the user's session. The token is also included as a hidden field in any form generated by the server. When a POST request comes back to the server, the server can check that the token included in the request, is the same as the token in the user's session. Because the attacker cannot possibly guess this token, any POST request originating from the attacker's server, would have the wrong token.
This approach is somewhat similar to the token approach in the sense that it's using a cryptographically random value. But instead of sticking this value in the user's session, the token is set as a cookie. Javascript in the browser will read out the token from the cookie and add it as a hidden field in the form. Upon receiving a request, the server can now check if the value in the incoming cookie is the same as the value included in the POST body as a form field.
This relies on the Same Origin Policy blocking the a web page on the attacker's server from accessing the token cookie, and thus from including the correct value as a hidden field in the form.
Javascript based applications typically make use of XHR requests for submitting POST requests. In addition many applications built on frameworks like backbone.js, will read and submit pure JSON-objects instead of using the application/x-www-form-urlencoded
and multipart/form-data
encodings.
If we decide to use the token based approach, we typically make a request to the server in order to get a JSON containing the token. In later POST request, we simply include it as a header. This can easily be achieved when using jQuery:
$("body").bind("ajaxSend", function(elm, xhr, s){ if (s.type == "POST") { xhr.setRequestHeader('X-CSRF-Token', csrf_token); } });
If you are using REST services supporting PUT and DELETE, you can easily modify the above code to also send the token for those methods.
If you are on node, there is a server side library for csrf protection in Connect.
Consider an application where we have applied the token and jQuery based mitigation above and it works as intended. Our app includes the tokens on POST requests. Also consider that the application is using hash based navigation. Let's use the Conference application as an example with regards to routing. This application the following routes:
#/talk/1
- show the given talk#/talk/1/edit
- open the given talk in edit mode#/talk/1/delete
- delete the given talkThe first link will show the talks title, description etc. as HTML. Nothing special there.
The second one will open the talk in edit mode, allowing the user to update the title or description. Upon saving the changes, the Javascript will extract the values from the form, stick them inside a JSON object, and submit it back to the server while automatically including our CSRF token. So the protection works as expected.
But the last route can be problematic. If it simply opens a confirmation page and then posts the request to the server when the user confirms the deletion, then we're all right. But if triggering that route is enough for the request to be submitted, we're in trouble. An attacking site can simply open a hidden iframe pointing to that route, and trick an innocent victim into visiting the page. Our javascript will happily submit the request including the CSRF token, just like we programmed it to. In that respect action triggering routes (routes that actuallly cause server side state changes) are similar to the problem with GET request as described under "GET vs. POST".