5

I have an action in my site:

http://mysite.com/User/Logout

This will log the current user out of his/her session. Since this is a simple GET request, a malicious user could either create links to this page or even put this link in an image's src attribute that would force users to get logged out. I would still like to maintain the simplicity of the logout link without having to go too far, but at the same time I would like to be able to prevent the above scenario from occurring.

Any ideas?

ircmaxell
  • 163,128
  • 34
  • 264
  • 314
Doctor Blue
  • 3,769
  • 6
  • 40
  • 63

6 Answers6

5

If you log people out using GET requests (or do anything more important than that using GET requests, for that matter) then your website will be vulnerable to cross-site request forgery attacks. You should use POST to log out people. GET is only for idempotent actions, see RFC 2616 section 9.1.

Keep in mind that while using GET in this case is not compliant with the RFC, it is not everything that you need to change to prevent the XSRF. See this answer by ircmaxell for an excellent explanation.

Community
  • 1
  • 1
Zed
  • 3,387
  • 19
  • 14
  • 1
    A good site that I found a few years ago that details some CRSF prevention techniques: http://www.aachen-method.com/ He does a really good job of breaking it down it you don't understand it. – Bailey Parker Mar 01 '11 at 12:38
  • This answer is what you need. And the info about CSRF should be read because all your forms should be protected. – Arkh Mar 01 '11 at 12:48
  • Yeah, this sounds like the right way to go. GET was the incorrect method here. – Doctor Blue Mar 01 '11 at 14:02
  • 4
    Note that switching to POST will not make you immune from CSRF (Cross-Site-Request-Forgery) attacks. It will make it trivially more difficult, but you MUST use a nonce or random token to prevent it all together. I would be fairly easy for someone to put some JS that posts to your site instead of clicking a link. Especially with [FF not 100% supporting Same-Origin](https://wiki.mozilla.org/Security/Origin) this is pretty easy. A token will still prevent this type of attack, so don't just switch to POST, also use a token! So for the purposes of this question, this answer is incomplete. – ircmaxell Mar 01 '11 at 14:06
  • @ircmaxell: Very good point. I updated my answer. Thanks for pointing it out. – Zed Mar 01 '11 at 14:29
  • Apologies for changing the accepted answer, but ircmaxell's answer is what I'm primarily using. – Doctor Blue Mar 02 '11 at 00:04
  • @Scott: That's ok, I agree that the answer by ircmaxell is more relevant, that's why I linked to it in my own answer in the first place. – Zed Mar 03 '11 at 03:56
5

Well, there are a few options that you can do to help secure against CSRF attacks:

  1. Use a form and a random token. So instead of having a "link", use a random token that's set in the session into a form

    <form action="/User/logout" method="post">
        <submit name="logout" value="Logout" />
        <input type="hidden" name="token" value="<?php echo getSessionToken(); ?>" />
    </form>
    

    Note that POST is best for this type of action, but you could change the form to a GET without too much trouble.

    Then in php, just do:

    if (getSessionToken(true) != $_POST['token']) {
        die('CSRF!');
    }
    

    Note that getSessionToken should work something like this:

    function getSessionToken($reset = false) {
        if (!isset($_SESSION['random_token'])) {
            $_SESSION['random_token'] = sha1(uniqid(mt_rand(), true));
        }
        $token = $_SESSION['random_token'];
        if ($reset) {
            unset($_SESSION['random_token']);
        }
        return $token;
    }
    

    Also note that whenever you fetch the token to check it, you should reset it to something new (which is what this does). This prevents replay attacks where an attacker detects the token on submission and resubmits the value.

  2. If you must use a link, then embed the token in the link. But note that this is more susceptible to attack since there's a chance the user might copy and paste the link to someone else. As long as it's a self-resetting token, there shouldn't be much issue with multiple tabs. But realize that it's not optimum:

    <a href="/User/logout?token=<?php echo getSessionToken(); ?>">Logout</a>
    

    It's absolutely better than nothing. But I would still suggest using the form for the best protection.

I would highly suggest reading and following the OWASP CSRF Guidelines for preventing CSRF. It will tell you just about all you need to know, and why...

ircmaxell
  • 163,128
  • 34
  • 264
  • 314
  • Great explanation. Good to know that people actually write secure code in PHP. Best regards. – Zed Mar 01 '11 at 14:39
  • Hmm, I don't think your getSessionToken actually resets the `random_token` session variable. It seems to only assign a new token if it doesn't already exist. If one does exist, however, it never assigns a new one. – Doctor Blue Mar 01 '11 at 17:54
0

I guess I am late to the party, but, here is how I handle it:

<?php

session_start();

function logoutButton($name = 'logout', $action = '/User/logout'){
$random_token = md5(uniq_id());
$_SESSION['csrf'] = $random_token;
$form = '<form ><input type="hidden" value="'. $random_token .'" name="' .$name. '"><input type="button" value="logout"><</form>';
return $form;
}

public static isValidRequest($name = 'logout'){
if(isset($_POST[$name]) && $_POST[$name] === $_SESSION['csrf']){
    return true;
}
return false;
}

}
?>

to display a logout button

echo logoutButton();

To securely logout a user

if(isValidRequest()){
    //logout
}

Hope it s helpful to someone.

edit: Here is a class I created https://github.com/sahithvibudhi/PHP-CSRF-Protection-class

Sahith Vibudhi
  • 4,935
  • 2
  • 32
  • 34
0

Without changing the HTTP method you could add a unique id to the users session and append that to the logout link. If a logout action is performed and the two id's match the logout is valid. Else it's not!

tlenss
  • 2,609
  • 2
  • 22
  • 26
0

Rewrite to read like/Logout?#NONCE#, #NONCE# being a value you change from request to request.

Carlos Vergara
  • 657
  • 6
  • 19
  • ...which will break if the user opens a second window. – symcbean Mar 01 '11 at 13:10
  • Which is a good percentage of the point indeed, and should involve nothing more than redirecting the user to a new page with a new nonce with which he can logout. It's a tradeoff between the annoyance of getting prank-logged-out and the page reloading without you logging out yourself. – Carlos Vergara Mar 01 '11 at 14:40
0

You're going to have to require some sort of validator to be passed to the logout page. And it must be soething that's unique to the session and difficult to guess. Now, if only there were a suitable value.....oh yes, the session identifier.

Note that you should be using cookies for session management, and you should be setting the http only flag on the session cookie - so you'll need to populate the link from PHP (or copy the session id into a javascript readable cookie).

<?php
   session_start();
   if ($_GET['logout_valid']!==session_id()) {
       // handle invalid logout request
       exit;
   } else {
       $_SESSION=array();
       session_destroy();
   }
symcbean
  • 47,736
  • 6
  • 59
  • 94