80

For a REST-api it seems that it is sufficient to check the presence of a custom header to protect against CSRF attacks, e.g. client sends

"X-Requested-By: whatever"

and the server checks the presence of "X-Requested-By" and drops the request if the header isn't found. The value of the header is irrelevant. This is how Jersey 1.9's CsrfProtectionFilter works and it is described in this blog post: http://blog.alutam.com/2011/09/14/jersey-and-cross-site-request-forgery-csrf/. The blog post also links to NSA and Stanford papers stating that the custom header itself is sufficient protection:

The first method involves setting custom headers for each REST request such as X-XSRF-Header. The value of this header does not matter; simply the presence should prevent CSRF attacks. If a request comes into a REST endpoint without the custom header then the request should be dropped.

HTTP requests from a web browser performed via form, image, iframe, etc are unable to set custom HTTP headers. The only way to create a HTTP request from a browser with a custom HTTP header is to use a technology such as Javascript XMLHttpRequest or Flash. These technologies can set custom HTTP headers, but have security policies built in to prevent web sites from sending requests to each other unless specifically allowed by policy. This means that a website www.bad.com cannot send a request to http://bank.example.com with the custom header X-XSRFHeader unless they use a technology such as a XMLHttpRequest. That technology would prevent such a request from being made unless the bank.example.com domain specifically allowed it. This then results in a REST endpoint that can only be called via XMLHttpRequest (or similar technology).

It is important to note that this method also prevents any direct access from a web browser to that REST endpoint. Web applications using this approach will need to interface with their REST endpoints via XMLHttpRequest or similar technology.

Source: Guidelines for implementing REST

It seems however, that most other approaches suggest that you should generate a token and also validate this on the server. Is this over-engineering? When would a "presence of" approach be secure, and when is also token validation required?

Mads Mobæk
  • 903
  • 1
  • 7
  • 7
  • What if the CSRF attack is generated by an injected script (for example, thru a stored XSS). This approach would not protect wrt this technique, if i am not wrong. –  May 15 '14 at 14:29
  • 15
    XSS always overrides CSRF - if you're vulnerable to the former then any CSRF protection is undermined. – SilverlightFox May 15 '14 at 14:56
  • http://stackoverflow.com/questions/3315914/is-this-sufficient-to-protect-against-a-csrf-for-an-ajax-driven-application – Ciro Santilli OurBigBook.com May 15 '16 at 08:01
  • If the server has a weak CORS policy ([`null` `*`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin) and cache mistakes), then the cross origin javascript request might be triggered by the browser; tho custom header is better than nothing, but not the safy safest way – Xenos Nov 29 '19 at 16:16

5 Answers5

48

Security is about defence in depth. Simply checking the value is sufficient at the moment, but future technologies and attacks may be leveraged to break your protection. Testing for the presence of a token achieves the absolute minimum defence necessary to deal with current attacks. Adding the random token improves the security against potential future attack vectors. Using a per-request token also helps limit the damage done by an XSS vulnerability, since the attacker needs a way to steal a new token for every request they make.

This is the same reasoning used in modern cryptographic algorithms, where n rounds are considered a minimum for safety, but 2n+1 rounds (for example) are chosen in the official implementation to ensure a decent security margin.

Further reading:

Polynomial
  • 132,208
  • 43
  • 298
  • 379
  • 1
    I'm not sure what you mean by future attacks since the enctype mentioned in your link does not apply to the OP's extra header. Can you elaborate on that? Also, you mentioned limiting an XSS vulnerability, but I don't think CSRF token can help there. If the malicious XSS script can send requests, they can surely read the per-response CSRF token. What am I missing? – Chris H. May 20 '14 at 00:28
  • @ChrisH. Future technologies and changes to his site might allow an attacker to add the header. If I remember correctly, HTML5's web sockets now allow this. Changes to his site might also introduce a HTTP response splitting bug, which may also bypass header checks. Regarding the XSS comment, I'm referencing the fact that if you have an XSS on page A, having a dynamic token creates a requirement for the attacker to read page B, which might block the request based on `X-Requested-With`, or be filtered via CSP. – Polynomial May 24 '14 at 00:24
  • I can't find any proof that Web Sockets can be used to circumvent this protection, nor is it clear what you meant by "a HTTP response splitting bug". Can you please elaborate? – Gili Jun 05 '14 at 03:03
  • @Gili They can't do this; I was mistaken. That said, it is absolutely possible to spoof the X-Requested-With header using Ajax, though of course the CORS policy still applies. Rook's response below demonstrates a vulnerability example; others may also exist (e.g. a CORS bypass in XMLHttpRequest which only works for JSON data). – Polynomial Jul 11 '16 at 16:44
  • 1
    Here is a recent attack that circumvents a static header used as CSRF protection: https://insert-script.blogspot.cz/2018/05/adobe-reader-pdf-client-side-request.html Of course, this specific attack applies to only a subset of IE users. Nonetheless,i t demonstrates why a static header is not a solid CSRF protection mechanism. – bayo May 04 '18 at 09:59
43

TL;DR - Checking the existence of a non-standard header like "X-Requested-By" should be sufficient to guard against CSRF attacks without checking the value of the header.

Non-standard headers cannot be set in a CSRF attack

The Play framework site breaks it down really well:

Simply put, an attacker can coerce a victims browser to make the following types of requests:

  • All GET requests
  • POST requests with bodies of type application/x-www-form-urlencoded, multipart/form-data and text/plain

An attacker can not:

  • Coerce the browser to use other request methods such as PUT and DELETE
  • Coerce the browser to post other content types, such as application/json
  • Coerce the browser to send new cookies, other than those that the server has already set
  • Coerce the browser to set arbitrary headers, other than the normal headers the browser adds to requests

This makes sense if you consider the attack vectors for CSRF:

  • GET requests (e.g. <img>, <iframe>) - which cannot set headers.
  • <form> submitted by a user click - which are limited to a few specific headers.
  • <form> submitted by JavaScript (HTMLFormElement.submit()) - which are limited to a few specific headers.

JavaScript is subject to the same-origin policy, so it can only add non-standard headers if one of the following conditions hold:

  • it is "in-domain" (i.e. loaded from the same domain as the target of the request).
  • it is allowed to do so through CORS.

XSS attacks are out of scope for this question

Non-standard headers can be set in an XSS attack. Using a non-standard header to prevent CSRF attacks does not make a site any more (or any less) vulnerable to XSS attacks regardless of the value of the header. Both non-standard headers and CSRF tokens are vulnerable to XSS attacks. If the XSS attacker can set a non-standard header on a request (e.g. in-domain XHR), he/she can certainly gain access to a CSRF token set in a cookie or embedded in DOM or in a JavaScript variable.

Reference

There is a similar SO question here which is confusing but came to the same conclusion.

Some examples of such non-standard headers in the wild:

  • "X-Requested-By" (mentioned by OP) recognized by Jersey/others
  • "X-Requested-With" set by jQuery
  • "X-XSRF-TOKEN" set by Angular
  • "X-CSRF-TOKEN" recognized by the Play framework
Chris H.
  • 539
  • 4
  • 4
  • 1
    There are known, solid ways to protect against CSRF attacks (see @rook's answer below) and checking a static header value is not one of them. Here is a recent attack that proves this point: https://insert-script.blogspot.cz/2018/05/adobe-reader-pdf-client-side-request.html. – bayo May 04 '18 at 10:05
  • @bayotop the [owasp](https://www.owasp.org/index.php/Cross-Site_Request_Forgery_%28CSRF%29_Prevention_Cheat_Sheet#Protecting_REST_Services:_Use_of_Custom_Request_Headers) resource linked in rook's answer does in fact recommend validating the presence of a static header value. – James Conkling Jul 31 '18 at 13:51
  • @JamesConkling No, it does not. The authors mention that they believe it's safe when combined with other checks ensuring the request's origin. Also the final note makes clear there is space for discussion. The attack I referenced above, bypasses these checks. – bayo Aug 01 '18 at 14:22
15

EDIT: this csrf-request-builder was exploiting a vulnerability in Flash, which has now been fixed. It is possible to send complex requests with JavaScript, however if you specify additional header elements a preflight OPTIONS HTTP request will be sent prior to the actual request.

I have verified that Jersey is vulnerable to CSRF and the developers of Jersey have been notified. It is possible to leverage this vulnerability using Flash and possibly other scripting technologies. Jersey is vulnerable because the "X-Requested-By" HTTP header is not on flash's header blacklist.

I used the CSRF-Request-Builder with the following arguments to build a post request:

file://var/code/CSRF-Request-Builder/csrf_payload.html#url=http://google.com&X-Requested-By=1&body={'test':1}

You should never come with with your own method of CSRF prevention unless you really understand CSRF exploitation. The CSRF Prevention Cheat sheet is a great resource.

jub0bs
  • 283
  • 2
  • 11
rook
  • 46,916
  • 10
  • 92
  • 181
  • Wouldn't whether the custom header gets sent be subject to the cross-domain policy for the remote site as per http://helpx.adobe.com/flash-player/kb/arbitrary-headers-sent-flash-player.html? – Simon Lieschke May 10 '13 at 01:34
  • @Simon Lieschke No this does not come into play, because a cross-domain policy is not loaded by this ActionScript client. This ability to control headers is not an ability granted by the "cross-domain policy", it is just native behavior. – rook May 10 '13 at 07:19
  • I'm unable to reproduce your example and can't get the CSRF-Request-Builder to perform a cross domain request with the `X-Requested-By` header. It always requests `crossdomain.xml` first and it only sends the POST request if the `crossdomain.xml` allows it with a line like ``. I tried with CSRF-Request-Builder hosted on the filesystem and loaded from another web server. I tested this with Flash 11.7.700.179 with Chrome 26.0.1410.64, Firefox 20.0.1 and Internet Explorer 9.0.8112.16421. – Simon Lieschke May 13 '13 at 02:24
  • @Simon Lieschke something might have changed with the latest version of flash... let me check it out. – rook May 13 '13 at 16:05
  • 2
    @Simon Lieschke So a few months ago, navigateToURL() didn't require a crossdomain.xml policy, it looks like this vulnerability has been patched and my exploit has been fixed. Oah well. It also looks like the CORS rules have changed to have a "preflight" options request for requests with special headers with JS. This type of attack is more difficult to carry out. You might want to post a question about this to all of security.se – rook May 13 '13 at 16:32
  • 3
    The OWASP [CSRF Prevention Cheat Sheet](https://www.owasp.org/index.php/Cross-Site_Request_Forgery_%28CSRF%29_Prevention_Cheat_Sheet#Protecting_REST_Services:_Use_of_Custom_Request_Headers) linked from this answer currently (last revision 2016-05-22) endorses the technique raised by the OP for "REST Services". (It mentions `X-Requested-With` header.) They do mention Flash vulnerabilities that undermine it, but say they *believe* that using it in conjunction with checking `Origin` header makes it secure. – JMM Jul 25 '16 at 20:14
  • @JMM that wiki page has changed a lot, but yeah it looks good. Having a random token and an origin check can only be bypassed using XSS or another SOP bypass similar to XSS. – rook Jul 25 '16 at 20:31
7

https://stackoverflow.com/a/11423778/14731 makes a very important point: the Same Origin Policy (SOP) is concerned with preventing the reading of cross-domain responses, not with the writing of requests.

Meaning, while you might be able to write custom headers in the future, it is extremely unlikely that you would ever be able to read the response of a cross-domain request. As such, the best CSRF protections involve reading a secret value from the server, writing it back, and having the server validate the value.

You don't necessarily need server-side state to accomplish this (Double-Submit Cookies, and Encrypted Token Pattern are two examples of this) but you should validate some secret value on the server.

Gili
  • 2,149
  • 3
  • 23
  • 41
0

As of 2020, MDN now explicitly recommends 'To prevent cross-origin writes, check an unguessable token in the request — known as a Cross-Site Request Forgery (CSRF) token'. See https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy (scroll down to 'How to block cross-origin access').

mti2935
  • 19,868
  • 2
  • 45
  • 64