#1
  1. --
    Devshed Expert (3500 - 3999 posts)

    Join Date
    Jul 2012
    Posts
    3,957
    Rep Power
    1046

    Cross-site request forgery


    Hi,

    since there are several misunderstandings surrounding CSRF, which keep coming up, I was hoping to clear things up a bit and avoid confusion in the future.

    Briefly, a CSRF attack means that you trick the browser of a privileged user into taking actions in their name. Let's say you target the member of an online shop. Then you could try to have the victim's browser buy products or change the user password to "123456" or whatever. If the application only checks the session ID, it will actually accept those requests, because the browser automaticalls sends the session ID in all requests.

    The attack is done by, for example, setting up a form on a website you control. The form points to the target application, and there might be JavaScript code to automatically submit the form as soon as it's loaded. When the victim visits your site, their browser will take action and submit the form, causing a request in the target application. If the user is logged in there and the application only checks the session ID, the request will be accepted as valid.

    Protecting against CSRF is actually pretty simple: When a user logs in or visits a form, you generate a random token. You store this token somewhere only you or your user has access to (like the session or a cookie or HTML5 Local Storage), and you also include it in the form. In the processing script, you retrieve the token from where you've stored it and compare it to the token sent with the request. If and only if they match, you accept the request.

    This simple technique makes it impossible for somebody else to "forge" a request as decribed above. They may set up a form, but since they don't know what token to put in, the request will be rejected.

    Now, there are several misunderstandings:

    First of all, anti-CSRF protection is not about preventing "foreign requests" coming from other websites. It doesn't matter where a request comes from, and there's no realiable way of finding that out, anway. It may be true that CSRF attacks are mostly launched from other sites. But they might as well launched from your own application in case it's vulnerable to JavaScript injection. At the same time, "foreign requests" can be totally legitimate and harmless. For example, if I want to make my own portal for your website, what's wrong with that?

    So anti-CSRF is not about making sure that a request comes from your own form. This is impossible to do and doesn't help you anyway. What you want is that nobody except the user can create a valid request. If the user decides to initiate the request from another website, that's fine. You just don't want anybody else to do it.

    Secondly, the anti-CSRF token does not have to be stored in the session. It may very well be stored on the client in a cookie or HTML5 Local Storage or Web SQL or whatever. If you think that you'll run into the same problem you have with the session cookie (it automatically gets sent with each request), that's wrong. The cookie with the token does get sent. But in order to forge a request, you have to actually know it. And that's impossible due to the same origin policy (and if the cookie is set to HttpOnly). You can neither read nor set the cookie, which means you're out of luck setting up your malicious form. You just don't know which value to use in the "anti_csrf_token" field.

    So it's perfectly fine to store the anti-CSRF token in a cookie (as long as you set it to HttpOnly). This doesn't break the protection at all. The only requirement for the token is that it can only be read and set by you and the user. And that's indeed the case for cookies.

    In fact, it doesn't even make sense to claim that storing the token in a cookie is dangerous and at the same time promote storing it in a session. Since sessions are also cookie-based, they're also automatically opened whenever the user makes a request.
    Last edited by Jacques1; July 5th, 2013 at 07:36 AM.
    The 6 worst sins of security ē How to (properly) access a MySQL database with PHP

    Why canít I use certain words like "drop" as part of my Security Question answers?
    There are certain words used by hackers to try to gain access to systems and manipulate data; therefore, the following words are restricted: "select," "delete," "update," "insert," "drop" and "null".
  2. #2
  3. Mad Scientist
    Devshed Expert (3500 - 3999 posts)

    Join Date
    Oct 2007
    Location
    North Yorkshire, UK
    Posts
    3,661
    Rep Power
    4123
    I think I have spotted the confusion (at the point I have always taken the greatest issue with), and here it is:

    By storing the value of the token in a cookie - it always gets sent; we both know this. I think I have interpreted this advice in your previous posts as that's a valid way for the client to tell the server what the value of the token is, rather than the this being the means for the server to know what the 'master' value should be. I think this is the first post you've made (here on devshed/php) about CSRF that also says it has to go in the form as well.

    Originally Posted by Jacques1
    I want to make my own portal for your website, what's wrong with that?
    Nothing, as long as you use my official API - everything else will be rejected! and this approach is something I would encourage others to adopt.

    Originally Posted by Jacques1
    First of all, anti-CSRF protection is not about preventing "foreign requests" coming from other websites.
    I started to write a long reply to this, but I think it's just easier to say I agree - the point is detecting and rejecting a "Forged Request", and we've already covered what that is and how to deal with it.

    I think I've been looking at things from the perspective of my projects and apps where the interface is completely ReSTful, only requests from within the client side app are allowed and everything else must come through the API interface (which issues an api auth token with every request for use similar to the anti CSRF token used in browsers)

    Originally Posted by Jacques1
    Secondly, the anti-CSRF token does not have to be stored in the session. It may very well be stored on the client in a cookie or HTML5 Local Storage or Web SQL or whatever.
    true, as long as this is the master value and is used to compare against the client submitted value in the post (form) data.

    However, by keeping it server side it can't be sniffed out.
    I said I didn't like ORM!!! <?php $this->model->update($this->request->resources[0])->set($this->request->getData())->getData('count'); ?>

    PDO vs mysql_* functions: Find a Migration Guide Here

    [ Xeneco - T'interweb Development ] - [ Are you a Help Vampire? ] - [ Read The manual! ] - [ W3 methods - GET, POST, etc ] - [ Web Design Hell ]
  4. #3
  5. --
    Devshed Expert (3500 - 3999 posts)

    Join Date
    Jul 2012
    Posts
    3,957
    Rep Power
    1046
    Originally Posted by Northie
    By storing the value of the token in a cookie - it always gets sent; we both know this. I think I have interpreted this advice in your previous posts as that's a valid way for the client to tell the server what the value of the token is, rather than the this being the means for the server to know what the 'master' value should be. I think this is the first post you've made (here on devshed/php) about CSRF that also says it has to go in the form as well.
    I've always talked about comparing the token in the form with the token in the cookie (or session), and I'm not even sure how the procedure you thought I meant would work (the cookie confirms itself?). But anyway, if that's sorted out, I'm fine with it.



    Originally Posted by Northie
    However, by keeping it server side it can't be sniffed out.
    Cookies can't be sniffed out either (as long as they're HttpOnly).

    If they could, the whole idea of anti-CSRF tokens would be pointless, because people could simple steal the session ID.

    Security-wise, the difference between storing the token in the session and storing it in a cookie is zero. I'd still store it in the session, because it's somewhat more robust. You don't risk that an authenticated user somehow loses the CSRF token and gets an error message every time they try to submit a form.
    The 6 worst sins of security ē How to (properly) access a MySQL database with PHP

    Why canít I use certain words like "drop" as part of my Security Question answers?
    There are certain words used by hackers to try to gain access to systems and manipulate data; therefore, the following words are restricted: "select," "delete," "update," "insert," "drop" and "null".
  6. #4
  7. No Profile Picture
    Dazed&Confused
    Devshed Novice (500 - 999 posts)

    Join Date
    Jun 2002
    Location
    Tempe, AZ
    Posts
    506
    Rep Power
    128
    Hmm.

    I know the "referrer" field is typically unusable as a reliable source of information since it can so easily be faked, but can it be faked in these situations? If a foreign site is simply triggering an immediate form submission, then it would be the user's browser handling that information--not the would-be hackers.

    If that is true then you could use that to determine if the request originated from a foreign site for the purpose of CSRF detection.
  8. #5
  9. --
    Devshed Expert (3500 - 3999 posts)

    Join Date
    Jul 2012
    Posts
    3,957
    Rep Power
    1046
    Checking the referrer should be secure. However, it's impractical in real life, because it will lock out every user who happens to not send this header. And there's a very good reason for surpressing the referrer: privacy.

    A "solution" punishing users who care about their privacy is just silly in my opinion, especially in these times.

    So there's no reason to give up anti-CSRF tokens.
    The 6 worst sins of security ē How to (properly) access a MySQL database with PHP

    Why canít I use certain words like "drop" as part of my Security Question answers?
    There are certain words used by hackers to try to gain access to systems and manipulate data; therefore, the following words are restricted: "select," "delete," "update," "insert," "drop" and "null".
  10. #6
  11. Mad Scientist
    Devshed Expert (3500 - 3999 posts)

    Join Date
    Oct 2007
    Location
    North Yorkshire, UK
    Posts
    3,661
    Rep Power
    4123
    pretty much most of the _SERVER array can be spoofed. consider it user data and untrustworthy.

    I was once told (can't seem to verify this anywhere) that about 10% of networks in commercial organisations strip out the referer header.
    I said I didn't like ORM!!! <?php $this->model->update($this->request->resources[0])->set($this->request->getData())->getData('count'); ?>

    PDO vs mysql_* functions: Find a Migration Guide Here

    [ Xeneco - T'interweb Development ] - [ Are you a Help Vampire? ] - [ Read The manual! ] - [ W3 methods - GET, POST, etc ] - [ Web Design Hell ]
  12. #7
  13. No Profile Picture
    Dazed&Confused
    Devshed Novice (500 - 999 posts)

    Join Date
    Jun 2002
    Location
    Tempe, AZ
    Posts
    506
    Rep Power
    128
    Originally Posted by Jacques1
    Checking the referrer should be secure. However, it's impractical in real life, because it will lock out every user who happens to not send this header. And there's a very good reason for surpressing the referrer: privacy.

    A "solution" punishing users who care about their privacy is just silly in my opinion, especially in these times.

    So there's no reason to give up anti-CSRF tokens.
    Just trying to find a way a way to achieve the same result without having to explicitly provide the token on every form.

    By the time passed parameters get to my controllers, they're just parameters. There's not much sense between whether they arrived via POST or GET, so I go with the notion that GET requests can update data just as well as POST requests.

    I could use Javascript to dynamically add a hidden field to every form, containing the token, but that wouldn't cover GET requests. In reality that's not a big deal since all my sensitive actions ARE form-based, but still... lacks consistency with what the controllers accept.

    Originally Posted by Northie
    pretty much most of the _SERVER array can be spoofed. consider it user data and untrustworthy.
    Spoofed directly if you're in control of building the request, such as when using curl or even AJAX, but CSRF is specifically about using the user's own browser. Would-be hackers thus lose their chance to spoof headers. Only the users themselves would be able to, but then there's no reason to fake being themselves.

    But if that many people really choose to hide their own referrer data, well... that idea gets shot in the foot.
    Last edited by dmittner; July 5th, 2013 at 11:24 AM.
  14. #8
  15. --
    Devshed Expert (3500 - 3999 posts)

    Join Date
    Jul 2012
    Posts
    3,957
    Rep Power
    1046
    Originally Posted by dmittner
    By the time passed parameters get to my controllers, they're just parameters. There's not much sense between whether they arrived via POST or GET, so I go with the notion that GET requests can update data just as well as POST requests.
    I don't wanna start yet another discussion, but I consider a system that doesn't distinguish between POST and GET to be deeply broken. That's what's need to be fixed, not the tokens.

    I know that in practice many people don't care about standards and do whatever they want. But if a web framework is not even able to support the standards, it's useless. It's like a HTML library that can't create valid HTML.



    Originally Posted by dmittner
    Would-be hackers thus lose their chance to spoof headers. Only the users themselves would be able to, but then there's no reason to fake being themselves.
    Right.
    The 6 worst sins of security ē How to (properly) access a MySQL database with PHP

    Why canít I use certain words like "drop" as part of my Security Question answers?
    There are certain words used by hackers to try to gain access to systems and manipulate data; therefore, the following words are restricted: "select," "delete," "update," "insert," "drop" and "null".
  16. #9
  17. No Profile Picture
    Dazed&Confused
    Devshed Novice (500 - 999 posts)

    Join Date
    Jun 2002
    Location
    Tempe, AZ
    Posts
    506
    Rep Power
    128
    Originally Posted by Jacques1
    I don't wanna start yet another discussion, but I consider a system that doesn't distinguish between POST and GET to be deeply broken. That's what's need to be fixed, not the tokens.

    I know that in practice many people don't care about standards and do whatever they want. But if a web framework is not even able to support the standards, it's useless. It's like a HTML library that can't create valid HTML.
    We're also working over a protocol called "Hypertext Transfer Protocol" that obviously handles the transferring of a lot more than just hypertext. Best-laid plans of mice and men oft go astray.

    If we were expected to use GET and POST as intended, why do browsers not support PUT and DELETE as form methods? Clearly you can have functionality on a website that would logically fit under one of the latter.

    So there's inconsistent handling of that "standard" before the user even gets to the web application. So I'd rather just ignore that standard altogether and create my own through my controllers/path structure which is 100% consistent.

    The fact PHP even offers the $_REQUEST superglobal is indicative of the breakdown of that standard, IMO.
  18. #10
  19. --
    Devshed Expert (3500 - 3999 posts)

    Join Date
    Jul 2012
    Posts
    3,957
    Rep Power
    1046
    Originally Posted by dmittner
    If we were expected to use GET and POST as intended, why do browsers not support PUT and DELETE as form methods?
    They do, actually. You can use any HTTP method in an Ajax request. The problem is that HTML only supports GET and POST in forms. They considered changing that in HTML5, but they eventually gave up the idea, which is a pity.

    In any case: The fact that many standards are poorly implemented is no excuse for making it even worse.



    Originally Posted by dmittner
    So there's inconsistent handling of that "standard" before the user even gets to the web application. So I'd rather just ignore that standard altogether and create my own through my controllers/path structure which is 100% consistent.
    The distinction between POST and GET is well-defined, and it's very useful. There is no inconsistency -- unless application developers create it.

    Mixing POST and GET can easily lead to massive confusion and security issues. A typical example would be a user reloading the page, unwillingly and unexpectedly repeating the previous action. Or think of bookmarks. You also risk overwriting parameters. If a URL parameter happens to have the same name as a form parameter, they'll collide -- which makes no sense whatsoever.

    There's a good reason why the HTTP designers made a clear distinction between specifying a resource, fetching it and changing it. And many users expect a website to work like this. They may not understand the technical details, but they do understand the difference between clicking on a link (which opens a website) and submitting a form (which causes some action).



    Originally Posted by dmittner
    The fact PHP even offers the $_REQUEST superglobal is indicative of the breakdown of that standard, IMO.
    So if many people do crap, then doing crap should be the norm? I don't understand this logic.

    HTTP is alive and well. There's indeed a lot of applications that ignore it -- just like they ignore the HTML standard and instead rely on the browser to fix their errors. But there's also the increasingly popular REST, which uses HTTP instead of perverting and circumventing it.
    The 6 worst sins of security ē How to (properly) access a MySQL database with PHP

    Why canít I use certain words like "drop" as part of my Security Question answers?
    There are certain words used by hackers to try to gain access to systems and manipulate data; therefore, the following words are restricted: "select," "delete," "update," "insert," "drop" and "null".
  20. #11
  21. No Profile Picture
    Dazed&Confused
    Devshed Novice (500 - 999 posts)

    Join Date
    Jun 2002
    Location
    Tempe, AZ
    Posts
    506
    Rep Power
    128
    Originally Posted by Jacques1
    They do, actually. You can use any HTTP method in an Ajax request. The problem is that HTML only supports GET and POST in forms. They considered changing that in HTML5, but they eventually gave up the idea, which is a pity.

    In any case: The fact that many standards are poorly implemented is no excuse for making it even worse.
    I'm not making it worse. I'm ignoring them in favor of something that's consistent. You might want to cling to your standards but I prefer consistency. It's easier to understand, code for, and predict.

    The distinction between POST and GET is well-defined, and it's very useful. There is no inconsistency -- unless application developers create it.
    What's well defined about a URL with a finite length, inherently limiting the amount of data that can pass through a GET request, whereas POST has no such limit?

    Mixing POST and GET can easily lead to massive confusion and security issues. A typical example would be a user reloading the page, unwillingly and unexpectedly repeating the previous action.
    Which they can do even with a POST and clicking the little confirmation, not understanding what it means and just wanting the page reloaded.

    That's why you should ALWAYS perform a redirect to a safe URL after performing a sensitive act. I think I've even seen you tell people to do that. And if you do that like you're supposed to, it doesn't matter if the user refreshes.

    Or think of bookmarks. You also risk overwriting parameters. If a URL parameter happens to have the same name as a form parameter, they'll collide -- which makes no sense whatsoever.
    You're right. It doesn't make sense. Which is all the more reason why browsers' implementation of it all is broken. Programmers already have to ensure request variables are passed in a way that there's no collision, so it's a small matter for them to control the transaction in general.

    There's a good reason why the HTTP designers made a clear distinction between specifying a resource, fetching it and changing it. And many users expect a website to work like this. They may not understand the technical details, but they do understand the difference between clicking on a link (which opens a website) and submitting a form (which causes some action).
    Which is why I still use both links and forms. The user experience hasn't changed. How it's handled in the controller is what's changed, to accept data safely regardless of how it's passed.

    So if many people do crap, then doing crap should be the norm? I don't understand this logic.
    Or just accept reality instead of stubbornly fighting it in the name of some antiquated specification that was violated the instant it went into practice.

    HTTP is alive and well. There's indeed a lot of applications that ignore it -- just like they ignore the HTML standard and instead rely on the browser to fix their errors. But there's also the increasingly popular REST, which uses HTTP instead of perverting and circumventing it.
    So what do you do in REST if you want to pull resources and your criteria is complex, nested, and exceeds URL length limits?

    But regardless, we're not talking REST. That's a completely different interaction structure and done through a completely different interaction medium that does consistently support POST, GET, etc.

IMN logo majestic logo threadwatch logo seochat tools logo