php – Is an X-Requested-With header server check sufficient to protect against a CSRF for an ajax-driven application?-ThrowExceptions

Exception or error:

I’m working on a completely ajax-driven application where all requests pass through what basically amounts to a main controller which, at its bare bones, looks something like this:

if(strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') {
    fetch($page);
}

Is this generally sufficient to protect against cross-site request forgeries?

It’s rather inconvenient to have a rotating token when the entire page isn’t refreshed with each request.

I suppose I could pass and update unique token as a global javascript variable with every request — but somehow that feels clumsy and seems inherently unsafe anyway.

EDIT – Perhaps a static token, like the user’s UUID, would be better than nothing?

EDIT #2 – As The Rook pointed out, this might be a hair-splitting question. I’ve read speculation both ways and heard distant whispers about older versions of flash being exploitable for this kind of shenanigans. Since I know nothing about that, I’m putting up a bounty for anyone who can explain how this is a CSRF risk. Otherwise, I’m giving it to Artefacto. Thanks.

How to solve:

I’d say it’s enough. If cross-domain requests were permitted, you’d be doomed anyway because the attacker could use Javascript to fetch the CSRF token and use it in the forged request.

A static token is not a great idea. The token should be generated at least once per session.

EDIT2 Mike is not right after all, sorry. I hadn’t read the page I linked to properly. It says:

A simple cross-site request is one that: […]
Does not set custom headers with the HTTP Request (such as X-Modified, etc.)

Therefore, if you set X-Requested-With, the request has to be pre-flown, and unless you respond to pre-flight OPTIONS request authorizing the cross-site request, it won’t get through.

EDIT Mike is right, as of Firefox 3.5, cross-site XMLHttpRequests are permitted. Consequently, you also have to check if the Origin header, when it exists, matches your site.

if (array_key_exists('HTTP_ORIGIN', $_SERVER)) {
    if (preg_match('#^https?://myserver.com$#', $_SERVER['HTTP_ORIGIN'])
        doStuff();
}
elseif (array_key_exists('HTTP_X_REQUESTED_WITH', $_SERVER) &&
        (strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest'))
    doStuff(); 

Answer:

I do not believe that this is safe. The same origin policies are designed to prevent the documents from different domains from accessing the content that is returned from a different domain. This is why XSRF problems exist in the first place. In general XSRF doesn’t care about the response. It is used to execute a specific type of request, like a delete action.
In the simplest form, this can be done with a properly formatted img tag. Your proposed solution would prevent this simplest form, but doesn’t protect someone from using the XMLHttp object to make the request.
You need to use the standard prevention techniques for XSRF. I like to generate a random number in javascript and add it to the cookie and a form variable. This makes sure that the code can also write cookies for that domain. If you want more information please see this entry.

Also, to pre-empt the comments about XMLHttp not working in script. I used the following code with firefox 3.5 to make a request to google from html running in the localhost domain. The content will not be returned, but using firebug, you can see that the request is made.

<script>
var xmlhttp = false; 

if (!xmlhttp && typeof XMLHttpRequest != 'undefined') {
    try {
        xmlhttp = new XMLHttpRequest();
    } catch (e) {
        xmlhttp = false;
    }
}
if (!xmlhttp && window.createRequest) {
    try {
        xmlhttp = window.createRequest();
    } catch (e) {
        xmlhttp = false;
    }
}

xmlhttp.open("GET", "http://www.google.com", true);
xmlhttp.onreadystatechange = function() {
    if (xmlhttp.readyState == 4) {
        alert("Got Response");
        alert(xmlhttp.responseText)
    }
}

xmlhttp.send(null)
alert("test Complete");

Answer:

Yes. It’s a recognized approach to CSRF protection.

https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet#Protecting_REST_Services:_Use_of_Custom_Request_Headers

Answer:

I do not think this offers any kind of protection. An attacking site could still use xmlhttprequest for its cross-site request bypass your check.

Answer:

Short answer : no. Any attacker would just use Ajax himself to attack your website.
You should generate a random token with a short but not too much lifetime which you would update during each ajax request.

You’d have to use an array of tokens in javascript as you may have multiple ajax request running at the same time.

Answer:

What you are doing is secure because xmlhttprequest is usually not vulnerable to cross-site request forgery.

As this is a client side problem, the safest way would be to check the security architecture of each browser 🙂


(This is a summary; I am adding this answer because this question is very confusing, let’s see what the votes say)

Leave a Reply

Your email address will not be published. Required fields are marked *