user1831020
user1831020

Reputation: 215

Is this a secure way to set the token CSRF?

I am wondering if this is a secure way to set a token, unless there actually is a token generated, I generate one, and use it throughout the applications and those forms. One token per session?

if (!isset($_SESSION['token'])) {
    $data['token'] = uniqid(rand(), true);
    session_regenerate_id();
    $_SESSION['token'] = $data['token'];
}

Would it be necessary to clear out the token on a submitted form? or just stay with it, even though I submitted a form?

Upvotes: 11

Views: 1195

Answers (5)

Scott Finlay
Scott Finlay

Reputation: 86

I've already answered a similar question on a different forum: here. Hopefully that's helpful. It explains the basic process of the CSRF prevention and links to some code for a CSRF framework.

If you want higher security, change the token after each request for each session. If you want better usability, keep one token per session.

Upvotes: 1

Baba
Baba

Reputation: 95101

Rather than use per-session token i would prefer per-form/url token for additional security some might argue that per-request token is most secured but affects usability.

I also think its better to separate your session storage from your token storage and use something like Memcache. This is better when you need speed using multiple application servers etc. I also prefer it because i can add a custom expiration to the token without having to affect the whole session

Here is a typical example

HTML

<form method="POST" action="#">
    IP:<input type="text" name="IP" /> <input type="hidden" name="token"
        value="<?php echo Token::_instance()->generate(); ?>" /> <input
        type="Submit" value="Login" />
</form>

Processing

$id = "id44499900";
Token::_instance()->initialise($id); // initialise with session ID , user ID or IP

try {

    Token::_instance()->authenticate();
    // Process your form
} catch ( TokenException $e ) {
    http_response_code(401); // send HTTP Error 401 Unauthorized
    die(sprintf("<h1>%s</h1><i>Thief Thief Thief</i>", $e->getMessage()));
}

Class Used

class Token {
    private $db;
    private $id;
    private static $_instance;

    function __construct() {
        $this->db = new Memcache();
        $this->db->connect("localhost");
    }

    public static function _instance() {
        self::$_instance === null and self::$_instance = new Token();
        return self::$_instance;
    }

    public function initialise($id) {
        $this->id = $id;
    }

    public function authenticate(array $source = null, $key = "token") {
        $source = $source !== null ? $source : $_POST;

        if (empty($this->id)) {
            throw new TokenException("Token not Initialised");
        }

        if (! empty($source)) {
            if (! isset($source[$key]))
                throw new TokenException("Missing Token");
            if (! $this->get($this->id . $source[$key])) {
                throw new TokenException("Invalid Token");
            }
        }
    }

    public function get($key) {
        return $this->db->get($key);
    }

    public function remove($key) {
        return $this->db->delete($key);
    }

    public function generate($time = 120) {
        $key = hash("sha512", mt_rand(0, mt_getrandmax()));
        $this->db->set($this->id . $key, 1, 0, $time);
        return $key;
    }
}
class TokenException extends InvalidArgumentException {
}

Note : Note that the example might affect "Back" button or refresh because the token would be automatically deleted after 120 sec and this might affect user friendly capability

Upvotes: 2

user1914530
user1914530

Reputation:

I am wondering if this is a secure way to set a token

It depends on how secure your web app needs to be. This line is not cryptographically secure (As warned in PHP docs for uniqid() and rand()):

uniqid(rand(), true);

It may be feasible for an attacker to determine/brute force this if the time of token generation is known/determined and the rand() seed is known/determined. However, for your purposes it may be fine as it will still prevent CSRF attacks where the attacker has no knowledge of the token value.

One token per session?

Using one token per session may be fine for your purposes. However, be aware:

  1. If a session is n minutes long then an attacker has an n minute window to attempt to determine or obtain your token value and execute a CSRF attack. Whereas this risk is reduced when tokens are generated per form or when the token is regenerated periodically as they are not long lived enough.
  2. Using a single token per session exposes all of your application's functionality (that uses that token) to attack should an attacker determine/obtain the token. Whereas using a token per form restricts an attack to a single form.

Would it be necessarery to clear out the token on a submitted form? or just stay with it, even though i submitted a form?

It depends upon how high value a target your application is for attackers and the level of disruption an attack would cause you. Your existing measure makes it difficult to execute CSRF attacks but if it is high value and you have very determined attackers then you may want to reduce the risk of CSRF more by:

  1. Using cryptographically secure tokens to prevent risk of determining or brute forcing the token value.
  2. Regenerating the token periodically to reduce token lifespan, decreasing the attack window if the token is determined or obtained.
  3. Generating tokens per form to restrict attacks to a single form in the event that the token be determined or obtained.

Upvotes: 2

palako
palako

Reputation: 3470

If you don't know these links, this should help you understand some scenarios and specifically this will tell you the DOs and DONT's. Hope it helps.

Upvotes: 11

Benjamin Paap
Benjamin Paap

Reputation: 2759

Personally I would generate a new token for every form I want to display. If you do it this way, someone just needs a session cookie to read your token and use it as long as the session stays active.

In my applications I generate a token for each form display like this:

<?php
$token = uniqid(rand(), true);
$_SESSION['csrf_tokens'][$token] = true;

HTML

<form>
    <input type="hidden" name="token" value="<?php echo $token ?>" />
</form>

On form validation I check for that token like this:

if (isset($_SESSION['csrf_tokens'][$token]) && $_SESSION['csrf_tokens'][$token] === true) {
    unset($_SESSION['csrf_tokens'][$token]);
    // additional code here
}

Upvotes: 10

Related Questions