When working on some new AJAX features for bbPress and WordPress we’ve noticed that AJAX requests don’t seem to send HTTP_REFERER values. We check referrers as one level of protection against cross-site-scripting, or XSS, so when they’re not set we aren’t able to use that value. How are most people using AJAX protecting against XSS? It seems the same things we’re doing to make things easily accesible in a dynamic fashion are also opening new vectors for attack.

30 replies on “AJAX and CSRF”

  1. “It seems the same things we’re doing to make things easily accesible in a dynamic fashion are also opening new vectors for attack.”

    Off in the distance, the engineers of Microsoft collectively choke on their spluttered attempts to agree.

  2. What’s the point of checking the Referer header at all when 1) it is ridiculously easy to circumvent, 2) often modified by proxies, and 3) will never be a reliable method of determining where someone came from?

  3. As Justin noted, checking HTTP_REFERER is pointless against malicious users. As for everyone else, XMLHTTP/XMLHttpRequest already does not allow requests from a different domain.

    There are no new concerns brought up by AJAX in this regard. The same concerns exist with every traditional form-based application. And the same solutions are applicable. I would suggest a quick google or two.

  4. “What’s the point of checking the Referer header at all” and “checking HTTP_REFERER is pointless against malicious users”:

    As I understand it, WordPress checks HTTP_REFERER to prevent trusted users from being tricked by malicious links, not to stop malicious users.

  5. The Referer header is unreliable. The only reliable way of preventing this, to my knowledge, is to provide a session token when generating the page that has the AJAX script on, and transmit this token when you send AJAX requests to the server.

    For example, include a meta name=”session-token” content=”238476223″, and, when submitting the AJAX request, pull this token out of the current page and send it along with the rest of the data.

  6. I’m not a fan of the referrer check either. Sometimes I like to manage my blog on the go, and certain mobile browsers or services strip out the referrer. I’m all in favor of the “secret key” method for protecting against attack. This could also be used for AJAX.

  7. Ryan this doesn’t have to do with the *type* of content going through, it has to do with users who are logged in and authenticated being “tricked” into doing something on their site. An example would be if you came to photomatt.net and it had an image tag with a src of ryanking.com/delete-posts.php?posts=all or something similar and because you were logged in and authenticated for your site when your browser requests that page it nukes the posts.

    A good first protection it to use POST for things that change resources, but POSTs can be faked as well, just not as simply as the example I gave above. In WordPress the solution we came to after talking to many security-minded people was that requests to change resources, POST or otherwise, should always come from within the WordPress admin directory. If not you get a polite message telling you why the request was blocked, and if you have a problem with referrers, how to fix it.

    However now we have an entire set of that are designed to be triggered from other pages using JS and all the proper headers aren’t being sent by UAs.

    So to summarize, I’ll quote Jason from above, “WordPress checks HTTP_REFERER to prevent trusted users from being tricked by malicious links, not to stop malicious users.” (We use KSES to stop malicious users.)

  8. Hi Matt,

    The attack you’re describing is called cross-site request forgeries (CSRF), and despite the similarity in names, it is quite different from cross-site scripting (XSS). Ryan’s explanation is accurate for XSS.

    Referer can be effective for reducing the risk of CSRF, but it is not as strong nor as reliable as using a unique token.

    I’d be happy to help – just send me an email if you’d like to discuss further.

    (Eric, thanks for the link, and I hope you found the article helpful. I need to update it, because I’ve changed the way that I present this topic in an attempt to help clarify the distinction between XSS and CSRF.)

  9. Thanks Chris, I updated the article title. Is the method you describe the most secure way to addresss this? We’ve been avoiding using sessions thus far.

  10. I hesitate to describe anything as the most secure, but using a token is the approach that has become the industry standard way to protect against CSRF. I’m only aware of one attack that can avoid this protection, and it only works in environments where the attacker can inject content in a page within the same domain – this can either be the fault of a XSS vulnerability or just a characteristic of the environment (e.g., if multiple sites are hosted under the same domain, such as http://example.org/mysite/ and http://example.org/yoursite/). I’d rather not discuss this in public, because this information is very new, and I’m not sure of the ramifications yet.

    You can implement the token approach without sessions, but there has to be a way to persist data on the server, so that you can check the incoming token. A token must be unique for each user+form combination, so sessions tend to be a natural way to make this association. There might be an elegant solution that doesn’t involve sessions, but I haven’t really thought about it. I’ll give it some thought.

    Anyway, hope that helps. 🙂

  11. One way to avoid common CRSF is to make all GET requests in wp-admin idempotent. That means uglifying some delete/modify links, but at least then we only have to worry about forged POST requests which are a bit harder to pull off discretly…
    There was some proof of concept work done in the trunk some months back, I remember it was put on the backburner because of a fast-approaching release deadline. Now that 1.6 is but an alpha, it could be time to investigate the conversion of non-idempotent commands to POST requests.

  12. Agreed, but “A good first protection it to use POST for things that change resources, but POSTs can be faked as well, just not as simply as the example I gave above.”

  13. I’ve developed a technique for discretely forging POST requests, so while enforcing POST raises the bar, it does not offer sufficient protection. I haven’t disclosed this technique yet, but I feel confident that others can independently discover it (and may already have). Of course, it is trivial to forge a POST request without discreetness, and this is still damaging.

    My technique does not work when a unique token is used.

  14. If the attacker knew what page the secret token was stored, couldn’t they just do an async request for that page and grab the token before sending the malicious request?

  15. Call me paranoid, but any kind of token isn’t going to be secure as long as the request is done in the clear (non-ssl). But making it unique, time sensitive, and restricting it to a particular session or perhaps remote ip address (not quite as good as a session though) might make it worthwhile.

    Is the admin/login portion in 1.6 going to finally be easily convertible to ssl without using plugins/hacks/mod_rewrite? It would be really nice if the admin could be configured as a standalone application, so that it’s location could be easily changed, and so that it could work with other servers (such as lighttpd).

  16. I’m too lazy to check if this can be done in IE, but with XMLHttpRequest, you should be able to use setRequestHeader to have the AJAX call send a referrer.

  17. I may just not be getting it, but this is how I see it. One part of your authentication is via cookies. That tells you that the person requesting the action or resource is trusted. That part is fine. The part we’re worried about is people tricking trusted users into clicking on links that will do those actions. This hole exists because the only type of authentication (right now) is via cookies. We’re not verifying that they came from a trusted page. Well, we’re the ones generating the only pages that are trusted, so we can imbed information into that page (validating the user with a cookie), that will be unguessable and unique to that user, like MD5(unknown_seed + user_id). This could be hardcoded via PHP into the Javascript AJAX function, and sent along with the request. On the server side, cookies are used to validate the user. Then, the userID in the cookie is added to the unknown_seed and MD5’d and then checked to see if it matches the “secret key” sent along with the XMLHttpRequest. If they don’t match, it’s a CSF attack.

    This would be a persistent key, but you could also encode a timestamp into the hash, and send the timestamp (in plaintext) with the XMLHttpRequest. Then, you not only integrate the timestamp into the hash and check it, you check the timestamp (which has been verified via the hash), and make sure it isn’t X minutes old.

    What is the downside to this approach?

  18. Why they dont have any reliable method then ?

    Everything you try to do, they have an article about ‘security vulnerability’
    about that method and 107 pages of preventive methods to ‘try to stop’
    those kind of vulnerability.

    HTTP_REFERER can be circumvented ‘easily’ they say. Why they don’t
    think they have to design some robust and authenticated method of
    communicating ?

    They have stupid OS, Servers, browsers, scripts and versions which do
    not match with each other and constantly you have to keep tuning them
    when other one goes out due to that tuning for first problem.

    Let me know when they have a variable which can not be circumvented

  19. i think making the request POST wont solve this.The victim can be redirected to some other website and from there a form can be submitted to circumvent this restriction , so only using random token would prevent this

  20. OK lets get one thing straight. There is totally a point to using HTTP_REFERER in an ajax request in the case where the sending page is already authed and the recieving ajax page needs to know who called her.
    So everyone out there that keeps saying “whats the point” just stop wasting everyones time if you dont know the answer.

    1. @Eugene: The reason people are saying HTTP_REFERER is pointless for security purposes is that you can’t trust it very much. It’s easy for malicious types to forge, and it may potentially be altered or cleared on legitimate users’ requests through some proxies/firewalls. If an AJAX endpoint trusts HTTP_REFERER to indicate that a request is originating from a particular location, it might as well have no security at all.

Comments are closed.