SarahBear Posted May 15, 2014 Share Posted May 15, 2014 As the title says, I would like to know how exactly CSRF can be 100% (or close to it) prevented. One of the most recommended solutions is to create a token and insert it into a hidden field, but I've tested it on another domain and you can just do a cURL request and retrieve the token then make another request with it included. Proof: <?php $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, "URL"); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_TIMEOUT, 10); $response = curl_exec($ch); curl_close($ch); $exploded = explode('type="hidden" name="token" value="', $response); $token = substr($exploded[1], 0, 64); echo $token; // ebd9ab96d40bdb21bbaa2e1a18d657be2e413105ae86ecc14def6137f38a1571 ?> I would hate to include captcha on all my forms, so how exactly does one prevent CSRF? Quote Link to comment Share on other sites More sharing options...
trq Posted May 15, 2014 Share Posted May 15, 2014 Just because you can retrieve a token doesn't mean it will be valid when you go to use it. Tokens should only be valid for that same session. Quote Link to comment Share on other sites More sharing options...
SarahBear Posted May 15, 2014 Author Share Posted May 15, 2014 I'm afraid I don't understand. If you change a token on each request, then the session will not match the form. If you rely on the user logging out, or not following a link posted by another user that makes the request, what is the point? Quote Link to comment Share on other sites More sharing options...
Strider64 Posted May 16, 2014 Share Posted May 16, 2014 I found a good way to test it is take the url from one browser (while logged in) and try it in another browser. It won't allow you to stay logged in. I know I have tried it, it works like a charm. Quote Link to comment Share on other sites More sharing options...
SarahBear Posted May 16, 2014 Author Share Posted May 16, 2014 That's because sessions aren't cross-browser by default. Quote Link to comment Share on other sites More sharing options...
Jacques1 Posted May 16, 2014 Share Posted May 16, 2014 Hi, I think you misunderstand CSRF attacks. CSRF means that the attacker tricks another user into making a particular request. For example, if this forum was vulnerable to CSRF, I could set up a form which creates a certain reply. If you or your browser submit this form while you're logged in, the request will look like it's coming from you. In other words, you'll unknowingly write a reply with my content in it. To prevent this, a website will typically establish a shared secret between itself and the user: It generates a random token, stores it in the user's session and dynamically includes it in every form. Whenever the user makes a POST request, the server will check if the submitted token matches the one in the session. Only then will the request be accepted. Since other users generally don't have access to the user's token, they can no longer set up a “forged” form for this user. They simply don't know which token they need to include. Of course every user knows their own token. But they don't know the tokens of other users. That's the point. However, no CSRF protection is perfect. Every technique relies on the same-origin policy which says that one website cannot read the content of another website through the client's browser. This is what prevents an attacker from getting the user's token. If the attacker is able to bypass the same-origin policy, any CSRF protection is useless, because now they're very well able to fetch the token. A common way to break the same-origin policy is a cross-site scripting attack: If I'm able to inject JavaScript code into the target page, then I'm in the same origin and read any content I want. There are also more sophisticated attacks which exploit the DNS system. Quote Link to comment Share on other sites More sharing options...
SarahBear Posted May 16, 2014 Author Share Posted May 16, 2014 I can trick a user to visit http://domain.com/run_csrf.php with the following code (the one I'm using to test): <?php $enabled = true; if($enabled !== true) { header("Location: /404.php"); die; } $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, "URL"); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_TIMEOUT, 10); $response = curl_exec($ch); curl_close($ch); $exploded = explode('type="hidden" name="token" value="', $response); $token = substr($exploded[1], 0, 64); $ch = curl_init(); $fields = array( 'rating' => 5, 'message' => "This message is of a successful CSRF attack", 'token' => $token ); $fields_string = "rating=5&message=This+message+is+of+a+successful+CSRF+attack&token={$token}"; curl_setopt($ch,CURLOPT_POST, count($fields)); curl_setopt($ch,CURLOPT_POSTFIELDS, $fields_string); curl_setopt($ch, CURLOPT_URL, "URL"); curl_setopt($ch, CURLOPT_RETURNTRANSFER, false); curl_setopt($ch, CURLOPT_TIMEOUT, 10); curl_exec($ch); curl_close($ch); ?> And it will be executed and bypass the hidden field tactic. I've tested this on another domain towards my current one and it bypasses the check: if($_SESSION['secure_token'] != $_POST['token']) Quote Link to comment Share on other sites More sharing options...
Jacques1 Posted May 16, 2014 Share Posted May 16, 2014 You don't understand. This request comes from your server, not the user. All you do is make your server send a request. Well, great. But that doesn't affect the user in any way, because it's not their request. For a CSRF attack, you need to trick the user's browser into sending the request. And this is not possible if every user has a secret token. Quote Link to comment Share on other sites More sharing options...
SarahBear Posted May 16, 2014 Author Share Posted May 16, 2014 Yes, it seems I don't understand. However, perhaps by explaining my way of thinking better, it would help. Say I too have a forum, or any way for a user to send another user a link in any way. This link takes the user to another domain (not mine) which includes the code above. Since the user is still logged in on my site, their session will remain intact when the code above requests my server. The code above sends a request from their domain to mine to retrieve their new session token, then send it to the server in the form of an action the user should take. Since the code above retrieves the token, it can be used to process any request. The manner of which I'm testing this is just a rating system at the moment, so it's nothing too complicated. Here is the relevant code to my checks and creation of tokens: <?php session_start(); // Just some requires and checks // Create the secure token if($_SERVER['REQUEST_METHOD'] === "GET") $_SESSION['secure_token'] = create_secure_token(); ?> <!-- HTML --> <?php if($_SERVER['REQUEST_METHOD'] === "POST") { // Some validation checks // Check if the secure token exists in the session as well as the form data if(!isset($_SESSION['secure_token'], $_POST['token'])) { add_error("CSRF", $_SERVER['REQUEST_URI']); $error = "Invalid request. Please try again."; } if(isset($error)) // Display error else { // Check whether the session matches the form data if($_SESSION['secure_token'] != $_POST['token']) { add_error("CSRF", $_SERVER['REQUEST_URI']); $error = "Invalid request. Please try again."; } if(isset($error)) // Display errors else { // Perform intended action } } } ?> <!-- HTML --> <input type="hidden" name="token" value="<?php echo htmlentities($_SESSION['secure_token']); ?>"> <!-- More HTML --> The above checks get bypassed because the other domain had retrieved the token from the hidden field in the first GET request, then sent it to my server in their POST request. Quote Link to comment Share on other sites More sharing options...
Jacques1 Posted May 16, 2014 Share Posted May 16, 2014 The intermediate server does not use the forum session of the visitor. It cannot, because it doesn't have access to cookies for other sites. It gets a request from the visitor. And then it makes its own request to your forum. This is perfectly fine, because the server simply acts as yet another forum user with its own session. The visitor isn't involved in the second request in any way. When the reply shows up in the forum, it will say “server has written: ...” and not “visitor has written: ...”. Try it out. In fact, it's completely irrelevant that the server request was triggered by another request. This has no effect on the authorship of the request going to your forum. If your scenario actually worked like you think it does, this would break the entire Internet. You're basically saying that, for example, PHP Freaks could make requests to PayPal, Facebook or whatever using your current sessions on those sites. This of course is not possible. One site cannot access your cookies for other sites. Quote Link to comment Share on other sites More sharing options...
SarahBear Posted May 16, 2014 Author Share Posted May 16, 2014 (edited) I'm not saying it accesses the cookies directly. The cURL request gets the page source and then from the page source I retrieve the token value from the hidden field. The hidden field is auto-filled with the user's current session "secure_token". That is what these lines do: $exploded = explode('type="hidden" name="token" value="', $response); $token = substr($exploded[1], 0, 64); What I'm saying is that if an attacker were to have their own domain, and requested mine, the user who fell victim to this would still have their session active on my site. The attack can then extract the "secure_token" from the hidden field within the page source. Edited May 16, 2014 by SarahBear Quote Link to comment Share on other sites More sharing options...
Solution Jacques1 Posted May 16, 2014 Solution Share Posted May 16, 2014 Again: If this was actually possible, then PHP Freaks could make PayPal transactions on your behalf, read the inbox of your webmailer and whatnot. This of course is not possible. You somehow have this strange idea that the server can magically “tunnel” the user cookies to another site. It cannot. It can communicate with the client, and it can send its own requests to another site. That's it. If the server makes its own requests, then those of course contain the cookies of the server, not the cookies of some other person. It might be helpful if you read up how HTTP actually works. I think this will clear up the confusion. Quote Link to comment Share on other sites More sharing options...
Jacques1 Posted May 16, 2014 Share Posted May 16, 2014 On a side note: I find it very refreshing to finally see someone who actually wants to understand the problem and not just patch up a bunch of code. You have a good attitude, keep it up. Quote Link to comment Share on other sites More sharing options...
SarahBear Posted May 16, 2014 Author Share Posted May 16, 2014 Okay, I have figured out where I went wrong after adding a print_r($_SESSION) on my server. Sorry I took so long to realize what you were saying I was just assuming it accessed my already active session because it worked when retrieving the secure token. But, the reason that worked was because I set/update the session token on each GET request to that page. Quote Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.