How to Mitigate CSRF Using Origin Headers?
v1 Tao (Lenx) Wei, Aug 2014
Motivation
Cross-site request forgery, also known as a one-click attack or session riding and abbreviated as CSRF (sometimes pronounced sea-surf) or XSRF, is a type of malicious exploit of a website whereby unauthorized commands are transmitted from a user that the website trusts.Unlike cross-site scripting (XSS), which exploits the trust a user has for a particular site, CSRF exploits the trust that a site has in a user's browser. [wikipedia]
There are already many mitigating solutions against CSRF, as described at OWASP and Wikipedia. Some don’t work, some work but with more programming overhead. While OWASP mentioned the ORIGIN header can be used to mitigate CSRF attacks, I am surprised to find there is little document about this simple but elegant solution.
When I digged deeper, I found that it relates to several important parts, including generating “preflight” OPTIONS requests, setting the access-control-allow-origin header, and there are several pitfalls such as header forgeries. I discuss this solution in detail in this report.
TL;DR
(b) Don't host any untrusted flash files on your web sites.
(c) For all the services not permitted in CSRF, add a custom request header (e.g. "X-ANTICSRF": 1 ). In this way, all the data must be submitted using Javascript and browsers will happily attach origin headers for all the cross-site requests.
(d) Deny all the requests to these services without this custom request header.
(e) If origin headers exist in requests, check them against white-list origins. Or deny all the cross-site requests if they are not allowed.
(f) Set Access-Control-Allow-Origin and other access control policies for every request. For sites not allowing cross-site requests at all, set Access-Control-Allow-Origin as null.
(g) Educate users not to use old browsers (before IE 10) and old plugins (before Adobe Flash 13.0.0.214) without proper origin supports.
Origin Headers vs. CSRF
As described in RFC 6454, an HTTP header field, named "Origin", indicates which origins are associated with an HTTP request. Origin headers only contain scheme, host and port information, i.e., no path or parameters.
If all the requests are sent with origin headers, CSRF can be defeated easily by checking these headers. Unfortunately, the real world is always full of challenges. We will list all the cases without origin headers as following.
(A0) Today, same origin requests don’t contain origin headers in most browsers.
Especially, these simple cross-site requests don’t generate origin headers (tested on Chrome v36, FireFox v31, IE 11):
(A1) Classic CSRF using “GET”, such as <img src=http://victim.bank/transfer?money=...>
FireFox v31 and IE 11 doesn’t generate origin headers for this while Chrome v36 and Safari 6 do:
(A2) Forms use cross-site actions instead of JavaScript onsubmit events, including both GET and POST. Please keep in mind that according to the standard, the browser will use GET to replace any methods other than GET and POST used in forms.
(B1) Cross-site Javascript requests, including Javascript in form onsubmit events
For Javascript cross-site requests using methods other than GET and POST, or using custom headers, or sending POST request with an XML payload using application/xml, text/xml, or application/json, browsers will generate a preflighted request using “OPTIONS” headers before the main request to check the access control list, as explained in MDN. And these preflighted requests contain origin headers too.
Does this mean attackers must explicitly ask users to click form buttons to force browsers to send POST requests without origin headers? Nope.
(A3) attackers can use Javascript to generate a submit event of a (A2) form, so that a POST/GET request will be sent without origin headers or preflighted requests, and without a click of users. Here is a sample.
<form method='POST' action='http://victim/act' name="attack">
<input type='hidden' name='pay' value='true'>
</form>
<script>document.attack.submit()</script>
Attackers can set the form's target to a hidden iframe, so that users are not able to see it. Verified on FireFox v31 and IE 11.
In summary, attackers can send CSRF requests without origin headers silently to effectively attack any forms without onsubmit events, i.e. all the basic forms we learned in html classes are still vulnerable to CSRF attacks on many browsers, and origin introduces no direct defense for them -- Interesting, huh?! While the browsers send out a lot of secret cookies, many of them refuse to send the origin in these dangerous cases to ease our defense. We’d better not implement any critical operations using such requests.
Access-Control-Allow-Origin vs. CSRF
Access-Control-Allow-Origin headers are introduced in Cross-site HTTP requests (CORS) to provide a solution for client browsers to enforce the cross-site policies. If Access-Control-Allow-Origin is set to null, all cross-site requests should be denied in theory. However, the real world is full of challenges. It depends on the implementations.
Client browsers must get the Access-Control-Allow-Origin setting first in order to enforce it. If a request has a preflighted request as discussed previously, client browsers can get Access-Control-Allow-Origin and refuse illegal cross-site requests.
However, if the request is a “standard” GET/POST without customized headers or enctype, there will not be a preflighted request, and the request itself will be sent to the server directly. Hence, the client browser is not able to enforce the policy before sending out the request, and the server must enforce the policy. Furthermore, if there is no origin header like cases (A1/2/3), server cannot enforce it too. Interesting, huh ?! We must avoid such dangerous cases.
The Solution
This diagram shows the full interactions between a browser and a server, including the preflighted request, origin validation and ACL (including Access-Control-Allow-Origin) validation.
As discussed previously, we need to do these things to mitigate CSRF attacks:
(D1) Make sure that valid critical cross-site requests must contain origin headers.
(D2) Check requests at step 2/6, based on origin and other header information.
(D3) Set Access-Control-Allow-Origin and other access control policies at step 3/8 for each request. For sites not allowing cross-site requests at all, set Access-Control-Allow-Origin as null.
(D3) Set Access-Control-Allow-Origin and other access control policies at step 3/8 for each request. For sites not allowing cross-site requests at all, set Access-Control-Allow-Origin as null.
Browsers will check Access-Control-Allow-Origin policies at step 3/8.
For (D1), the key point is not to use GET/POST without customized headers to send plaintext data to conduct serious operations.
We have multiple candidate solutions, but here we suggest this solution: For all the services not permitted in CSRF, add a custom request header (e.g. "X-ANTICSRF": null). In this way, all the data must be submitted using Javascript and browsers will happily attach origin headers for all the cross-site requests.
We only need to do these checks then:
(D2a) Deny all the requests to these services without the correct custom request header.
(D2b) Otherwise, if origin headers exist, check them against white-list origins. Or deny all the cross-site requests if they are not allowed.
As you can see, the final solution is quite simple and clean. But this solution depends on the correct implementation of browsers and plugins. We will discuss them in detail next.
Discussions
1) Why “referer” headers are not enough?
While “modern” browsers make it hard for users to disable “referer” headers. There are two methods for attackers to disable them:
- If a website is accessed from a HTTPS connection and a link points to a HTTP site, many browsers will not send the “referer” field.
- link type “noreferrer” can be used to request browsers not to send the “referer” information.
Some privacy software or proxies also remove “referer” headers from the http traffic.
Discussion 5) on forging HTTP request headers is also valid for attacks against the referer header.
As a result, both benign and malicious traffic might not contain the “referer” information, and that is why we cannot depend on referer to mitigate CSRF.
2) Host forward in proxies
If we want to check origin headers against host headers, we must make sure that all load-balancers/reverse-proxies forwards the "Host" field correctly.
For the Apache proxy, we need to add this into the httpd.conf:
ProxyPreserveHost On
There should be a similar option for other devices/software. We can also check origin headers against the X-Forwarded-Host header as a backup solution.
3) Access-Control-Allow-Origin vs. CSRF leakage
A possible use of Access-Control-Allow-Origin is to prevent CSRF leakage attacks. For example, attackers can use <img src=http://victim.bank/check.png?account=...> to get a check photo from a vulnerable bank site, without generating origin headers or preflighted requests. If the browser checks the Access-Control-Allow-Origin header in the response and refuses to display it, it will be an effective defense.
However, I have tested both Chrome v36 and FireFox v31 and they don’t check Access-Control-Allow-Origin against such CSRF leakage attacks. The photos will be displayed, and the attackers can get the photo data using Javascript and send them back.
In summary, it provides little help today.
4) HTML JSON form
Currently, the browser will use application/x-www-form-urlencoded to replace non-standard enctype fields, such as application/json. A future standard might support application/json in forms, but it will handle cross-site cases correctly. Thus we don’t need to worry about this case today. Hopefully browsers will not implement it wrongly.
5) Forging HTTP request headers
This solution depends on browsers' correct support of origin headers, and requires modern browsers. As shown on caniuse, old browsers before IE 8 might not support this correctly.
Furthermore, plugins can be used to manipulate HTTP request headers, such as flash 7/8, or Java applets. Even the newest Java applet (Java 7 update 67) can set fake Referer headers (why such a design?).
If the browser supports CORS correctly, it will generate OPTIONS preflighted request, and check the returned Access-Control-Allow-Origin policy. In this way, the forgery will be defeated.
Furthermore, plugins can be used to manipulate HTTP request headers, such as flash 7/8, or Java applets. Even the newest Java applet (Java 7 update 67) can set fake Referer headers (why such a design?).
If the browser supports CORS correctly, it will generate OPTIONS preflighted request, and check the returned Access-Control-Allow-Origin policy. In this way, the forgery will be defeated.
However the world is full of challenges, flash and Java are always big challenges against security.
For Flash, from 13.0.0.214, adobe fixed CVE-2014-0516, and a side-effect is that addRequestHeader of Action Script 2 is simply disabled. But they haven't disclose the details of the to unspecified vectors to bypass the Same Origin Policy.
A good news is that Java applets cannot get httponly cookies, although Java applets don't generate Origin headers. Thus we can use httponly session cookies to defeat CSRF by Java applets.
In FireFox 32, Java applets cannot set "User-Agent" headers. However, in Safari 6, Java applets can set "User-Agent" to whatever you want, e.g. Chrome. Thus, you cannot use "User-Agent" to identify requests from Java.
For ActionScript 3, the calling SWF file and the loading URL must be in the same domain by default without a URL policy file. Otherwise, Flash will refuse to load the URL.
References
- http://www.securityfocus.com/archive/1/441014
- http://caniuse.com/#feat=cors
- addRequestHeader in ActionScript 2.0: http://help.adobe.com/en_US/AS2LCR/Flash_10.0/help.html?content=00001162.html
- URLRequestHeader in ActionScript 3.0: http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/net/URLRequestHeader.html
- Java Applet Request Header Forgery, http://blog.mindedsecurity.com/2010/10/java-applet-same-ip-host-access.html
- Java Applet Redirect, https://nealpoole.com/blog/2011/10/java-applet-same-origin-policy-bypass-via-http-redirect/
- http://stackoverflow.com/questions/24680302/csrf-protection-with-cors-origin-header-vs-csrf-token/25519755#25519755
- http://www.cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2014-0516
View comments