37

So I've got a basic .ajax() POST method to a PHP file.

What security measures do I need?

A few posts around were mentioning using a hidden MD5 input field that you send via AJAX and verify in the PHP file. Is this a good enough method?

Nathan Waters
  • 1,173
  • 4
  • 13
  • 23

4 Answers4

41

The risk from CSRF is that an external site could send data to yours and the users browser will automatically send the authentication cookie along with it.

What you need is some way for the receiving action (that your $.ajax() method is sending POST data to) to be able to check that the request has come from another page on your site, rather than an external site.

There are a couple of ways to do this, but the recommended way is to add a token to the request that you can check for and that the hackers can't get to.

At its simplest:

  • On log on create a long random string token and save it against the user.
  • Add a parameter to the $.ajax() request that includes the token.
  • On request check that the token matches the one that you have saved for the user.
  • If the token doesn't match you have a CSRF hack.

The hacker can't get to your DB and can't actually read the page you've sent to the user (unless they get an XSS attack in, but that's another problem) so can't spoof the token.

All that matters with the token is that you can predict (and validate) it and that the hacker can't.

For this reason it's easiest to generate something long and random and store it in the DB, but you could build up something encrypted instead. I wouldn't just MD5 the username though - if the CSRF attackers figure out how to generate your tokens you'll be hacked.

Another way is to store the token is in a cookie (rather than your database), as the attackers can't read or change your cookies, just cause them to be re-sent. Then you're the token in the HTTP POST data matches token in the cookie.

You can make these a lot more sophisticated, for instance a token that changes every time it's successfully used (preventing resubmission) or a token specific to the user and action, but that's the basic pattern.

Keith
  • 150,284
  • 78
  • 298
  • 434
  • 8
    How can a user post an AJAX request from another website although the [Same-origin policy](https://en.wikipedia.org/wiki/Same-origin_policy) prevents such behavior? – Songo May 26 '14 at 22:36
  • 3
    @Songo not all browsers support that, unfortunately. Lots of proxies strip headers and break that too. Finally you can POST from outside the origin, so even though you intend to AJAX to it that doesn't mean an attacker will. Basically you should have a same-origin policy, but as it relies on well behaved browsers you shouldn't rely on it. Use of a CSRF token gives you something you can verify even if same-origin is circumvented. – Keith Jul 29 '14 at 16:24
  • @Songo even with the latest version of Chrome, you can still do GET requests (i.e. `````` tag on a webpage) and it'll work. @Keith I wouldn't trust a cookie as the browser automatically sends the cookie to the webpage for every request. If the attacker uses an iFrame or a form submit the cookie will automatically be sent I believe. – arleslie Sep 10 '15 at 18:52
  • @arleslie yes, the attacker can re-send the CSRF cookie in exactly the same way that they re-send the authentication one, but you're not checking the cookie against an internal value, you're checking it against a value in the posted content. You're always taking the CSRF value to check from the HTTP POST, and then comparing that either against your DB value or the value from an SSL HTTP-only cookie. It's the value in the POST that the attacker can't recreate. – Keith Sep 11 '15 at 07:26
  • 3
    @arleslie just to be clear - comparing the cookie to your DB provides no additional security at all (your authentication cookie already does that). The important thing is to compare a token in the POST data _either_ to a token in your DB or a token in a cookie. The attacker can re-send the cookie but can't read it, so their malicious POST data can't include the right value. – Keith Sep 11 '15 at 07:36
  • @Keith I still don't see any added security to that method. If you're comparing posted data to a cookie, the attacker can see both of that when using the site. Since both are submitted by the user they can both be modified. Maybe I'm still not understanding you, but nothing stops me from saying ```$_REQUEST['token'] = 'hi'; $_HEADER['token'] = 'hi;``` – arleslie Sep 11 '15 at 16:02
  • 3
    @arleslie in a CSRF attack the hacker _can't see your cookie_, they can't parse your requests or read the content you've sent or received. If they've compromised your machine then there's not much you can do, but CSRF attacks are much simpler: the attacker hasn't compromised your machine, but can trick you into submitting their payload into a site you had already signed into. They can't read your cookies, but they can resend them with their content. You block this attack by making sure that the content of the POST (not just the cookie) has verifiable data in it. – Keith Sep 11 '15 at 16:07
  • 2
    @arleslie a typical example would be a page that I (as the hacker) craft and get you to visit - once on that page I submit a POST to Facebook. I don't know anything about your Facebook cookie, or even your name, but that POST will send your FB auth cookie along with my instruction to like and share my hacking page on your wall (this actually happened to Facebook). At no point in the CSRF attack do I intercept your request to or from Facebook - I just rely on your browser sending it again. – Keith Sep 11 '15 at 16:10
  • 1
    @arleslie this is why CSRF attacks are so dangerous - I don't have to compromise your machine to carry them out, I just need to get you to visit my page while still signed in. That's why high security sites like banks log you out so quickly if you're inactive, but low security like Facebook let your cookie remain indefinitely. – Keith Sep 11 '15 at 16:13
  • 1
    @arleslie here's a good article on the anatomy of a CSRF attack: http://www.troyhunt.com/2010/11/owasp-top-10-for-net-developers-part-5.html – Keith Sep 11 '15 at 16:14
  • @Keith Sorry wasn't thinking clearly. I understood the attacker couldn't see the cookie but for some reason I was thinking he could still see the value. (A bit scattered brain lately). – arleslie Sep 11 '15 at 16:20
  • @Songo https://security.stackexchange.com/a/58811/8340 The SOP only prevents reads, not writes in an HTTP context, even via ajax. – SilverlightFox Aug 01 '19 at 06:33
4

Strictly, no token is needed, but you should still protect any functions that change state against CSRF.

CRSF is most definitely a risk, even though the request is made via AJAX. This is because AJAX requests can be passed cross-domain - the Same Origin Policy only guards against reads, not writes. And also a traditional form might be able to send exactly the same POST request as your AJAX does, and your current server-side code might not detect this.

One simple way of allowing your server-side code to detect whether the request has come from your own site is by adding a header that is sent with the AJAX request. It is important that your server-side code checks for the presence of this header. No random token is necessarily needed.

This works because:

  • HTML forms cannot have custom headers added to them by an attacker.
  • Custom headers cannot be passed cross-domain without CORS being enabled.

For a defence against any future developments on the web, it might be a good idea to also implement a random token. This would need to be tied to the current user session in some way. It isn't currently exploitable if the token isn't implemented, but in the web's long and twisted history, lack of tokens could be exploited by Flash and other browser plugins. In a perfect world, HTML5 and the living standard should mean that plugins like these are a thing of the past, however, who knows for sure what is around the corner so to add defence-in-depth and to future proof, tokens are also recommended.

More info: What's the point of the X-Requested-With header?

SilverlightFox
  • 32,436
  • 11
  • 76
  • 145
2

In terms of request forgery, it doesn't matter how the client sends the request it matters how its received. The same CSRF rules apply for an ajax post as any other type of post.

I recommend reading the CSRF prevention cheat sheet. Using a per-user secret token is the most common form of protection.

rook
  • 66,304
  • 38
  • 162
  • 239
  • 2
    Quite common is also per-request one-time token, acquired for specific user and disabled after first use. – Tadeck Feb 01 '12 at 05:45
  • 2
    @Tadeck That approach is more useful for preventing double-submits than CSRF. – rook Feb 01 '12 at 06:08
  • 2
    As stated within [the source you referenced](https://www.owasp.org/index.php/Cross-Site_Request_Forgery_%28CSRF%29_Prevention_Cheat_Sheet), one-time tokens are very strong security used within high-risk functions. This is something opposite than being "_more useful for preventing double-submits than CSRF_", it is more strict way of securing your application against CSRF. – Tadeck Feb 01 '12 at 06:20
  • @Tadeck I have write privileges on that wiki and some of my words are in that document. Trying to guess a massive token is not a realistic attack. – rook Feb 01 '12 at 06:43
  • Will this model suffice? http://docs.jquery.com/Tutorials:Safer_Contact_Forms_Without_CAPTCHAs – Nathan Waters Feb 01 '12 at 09:59
  • @Nathan Waters No, and the title of that paper is baffling. That method is **NOT SUITABLE** for CSRF protection. The current time is known to everyone and there for not a secret. Just read the CSRF prevention cheat sheet. – rook Feb 01 '12 at 18:32
0

Here's a simple demo you can try with django:

On HTML page

{%block content%}
<form id="userForm">
{%csrf_token%}
  <input type="text" id="username" placeholder="User Name">
  <input type="password" id="password" placeholder="Password">
</form>
{%endblock%}

Java-Script Code

%(document).on('submit','#userForm',function(e){
   e.preventDefault();

 $.ajax({

    type = 'POST',

    url:'path/to/url',

    data:{
     username:$('#username').val(),
     password:$('#password').val(),
     csrfmiddlewaretoken:$('input[name=csrfmiddlewaretoken').val()
    },

   success:function(data){
       alert('Successfull');
   }
  });

});