Jump to content

How to truly prevent CSRF


SarahBear
Go to solution Solved by Jacques1,

Recommended Posts

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?

Link to comment
Share on other sites

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?

Link to comment
Share on other sites

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.

Link to comment
Share on other sites

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'])
Link to comment
Share on other sites

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.

Link to comment
Share on other sites

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.

Link to comment
Share on other sites

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.

 

 

 

 

 

 

 

Link to comment
Share on other sites

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 by SarahBear
Link to comment
Share on other sites

  • Solution

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.

Link to comment
Share on other sites

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.

Link to comment
Share on other sites

This thread is more than a year old. Please don't revive it unless you have something important to add.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.