Reputation: 215
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
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
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
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:
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:
Upvotes: 2
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
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