Mon application Web utilise des sessions pour stocker des informations sur l'utilisateur une fois qu'il est connecté et pour conserver ces informations lorsqu'il se déplace de page en page dans l'application. Dans cette application spécifique, je stocke les user_id
, first_name
et last_name
de la personne.
J'aimerais proposer une option "Keep Me Logged In" lors de la connexion, qui placera un cookie sur la machine de l'utilisateur pendant deux semaines, qui redémarrera sa session avec les mêmes informations à son retour à l'application.
Quelle est la meilleure approche pour faire cela? Je ne souhaite pas stocker leur user_id
dans le cookie, car il semblerait que cela faciliterait la tâche à un utilisateur qui tenterait de forger l'identité d'un autre utilisateur.
OK, permettez-moi de dire ceci sans ambages: si vous insérez des données utilisateur ou tout élément dérivé de données utilisateur dans un cookie à cette fin, vous faites quelque chose de mal.
Là. Je l'ai dit. Nous pouvons maintenant passer à la réponse réelle.
Quel est le problème avec le hachage des données utilisateur, vous demandez? Eh bien, cela dépend de la surface d'exposition et de la sécurité par l'obscurité.
Imaginez une seconde que vous êtes un attaquant. Vous voyez un cookie cryptographique défini pour le souvenir de moi lors de votre session. C'est 32 caractères de large. Gee. C'est peut-être un MD5 ...
Imaginons aussi pendant une seconde qu'ils connaissent l'algorithme que vous avez utilisé. Par exemple:
md5(salt+username+ip+salt)
Maintenant, tout ce qu'un attaquant doit faire est de forcer brutalement le "sel" (ce qui n'est pas vraiment un sel, mais plus sur cela plus tard), et il peut maintenant générer tous les faux jetons qu'il veut avec n'importe quel nom d'utilisateur pour son adresse IP! Mais forcer un sel est difficile, non? Absolument. Mais les GPU des temps modernes y sont extrêmement bons. Et à moins que vous n'utilisiez suffisamment d'aléatoire dans celui-ci (rendez-le suffisamment grand), il va tomber rapidement et avec lui les clés de votre château.
En bref, la seule chose qui vous protège est le sel, qui ne vous protège pas vraiment autant que vous ne le pensez.
Mais attendez!
Tout cela était fondé sur le fait que l'attaquant connaissait l'algorithme! Si c'est secret et déroutant, alors vous êtes en sécurité, non? FAUX. Cette ligne de pensée a un nom: La sécurité par l'obscurité , sur laquelle on devrait NE JAMAIS .
La meilleure façon
Le meilleur moyen est de ne jamais laisser les informations d'un utilisateur quitter le serveur, à l'exception de l'id.
Lorsque l'utilisateur se connecte, générez un jeton aléatoire volumineux (128 à 256 bits). Ajoutez cela à une table de base de données qui mappe le jeton sur l'ID utilisateur, puis envoyez-le au client dans le cookie.
Et si l'attaquant devine le jeton aléatoire d'un autre utilisateur?
Bien, faisons des calculs ici. Nous générons un jeton aléatoire de 128 bits. Cela signifie qu'il y a:
possibilities = 2^128
possibilities = 3.4 * 10^38
Maintenant, pour montrer à quel point ce nombre est absurdement grand, imaginons chaque serveur sur Internet (disons 50 000 000 aujourd'hui) essayant de forcer ce nombre à un rythme de 1 000 000 000 par seconde chacun. En réalité, vos serveurs fondraient sous une telle charge, mais jouons-en.
guesses_per_second = servers * guesses
guesses_per_second = 50,000,000 * 1,000,000,000
guesses_per_second = 50,000,000,000,000,000
Donc, 50 quadrillions de suppositions par seconde. C'est rapide! Droite?
time_to_guess = possibilities / guesses_per_second
time_to_guess = 3.4e38 / 50,000,000,000,000,000
time_to_guess = 6,800,000,000,000,000,000,000
Donc 6,8 sextillions de secondes ...
Essayons de ramener cela à un nombre plus convivial.
215,626,585,489,599 years
Ou même mieux:
47917 times the age of the universe
Oui, c'est 47917 fois l'âge de l'univers ...
En gros, ça ne va pas être craqué.
Pour résumer:
La meilleure approche que je recommande est de stocker le cookie en trois parties.
function onLogin($user) {
$token = GenerateRandomToken(); // generate a token, should be 128 - 256 bit
storeTokenForUser($user, $token);
$cookie = $user . ':' . $token;
$mac = hash_hmac('sha256', $cookie, SECRET_KEY);
$cookie .= ':' . $mac;
setcookie('rememberme', $cookie);
}
Ensuite, pour valider:
function rememberMe() {
$cookie = isset($_COOKIE['rememberme']) ? $_COOKIE['rememberme'] : '';
if ($cookie) {
list ($user, $token, $mac) = explode(':', $cookie);
if (!hash_equals(hash_hmac('sha256', $user . ':' . $token, SECRET_KEY), $mac)) {
return false;
}
$usertoken = fetchTokenByUserName($user);
if (hash_equals($usertoken, $token)) {
logUserIn($user);
}
}
}
Remarque: N'utilisez pas le jeton ni la combinaison utilisateur et jeton pour rechercher un enregistrement dans votre base de données. Assurez-vous toujours d'extraire un enregistrement basé sur l'utilisateur et utilisez une fonction de comparaison sécurisée pour comparer le jeton récupéré par la suite. Plus sur les attaques de synchronisation .
Maintenant, il est très important que le SECRET_KEY
soit un secret cryptographique (généré par quelque chose comme /dev/urandom
et/ou dérivé d'un entrée à entropie élevée). De plus, GenerateRandomToken()
doit être une source aléatoire forte (mt_Rand()
n'est pas assez puissant. Utilisez une bibliothèque, telle que RandomLib ou random_compat , ou mcrypt_create_iv()
avec DEV_URANDOM
) ...
Le hash_equals()
est d'empêcher attaques temporelles . Si vous utilisez une version PHP inférieure à PHP 5.6, la fonction hash_equals()
n'est pas prise en charge. Dans ce cas, vous pouvez remplacer hash_equals()
par la fonction timingSafeCompare:
/**
* A timing safe equals comparison
*
* To prevent leaking length information, it is important
* that user input is always used as the second parameter.
*
* @param string $safe The internal (safe) value to be checked
* @param string $user The user submitted (unsafe) value
*
* @return boolean True if the two strings are identical.
*/
function timingSafeCompare($safe, $user) {
if (function_exists('hash_equals')) {
return hash_equals($safe, $user); // PHP 5.6
}
// Prevent issues if string length is 0
$safe .= chr(0);
$user .= chr(0);
// mbstring.func_overload can make strlen() return invalid numbers
// when operating on raw binary strings; force an 8bit charset here:
if (function_exists('mb_strlen')) {
$safeLen = mb_strlen($safe, '8bit');
$userLen = mb_strlen($user, '8bit');
} else {
$safeLen = strlen($safe);
$userLen = strlen($user);
}
// Set the result to the difference between the lengths
$result = $safeLen - $userLen;
// Note that we ALWAYS iterate over the user-supplied length
// This is to prevent leaking length information
for ($i = 0; $i < $userLen; $i++) {
// Using % here is a trick to prevent notices
// It's safe, since if the lengths are different
// $result is already non-0
$result |= (ord($safe[$i % $safeLen]) ^ ord($user[$i]));
}
// They are only identical strings if $result is exactly 0...
return $result === 0;
}
Avis de sécurité: Baser le cookie sur un hachage MD5 de données déterministes est une mauvaise idée. il est préférable d'utiliser un jeton aléatoire dérivé d'un CSPRNG. Voir la réponse de ircmaxell à cette question pour une approche plus sécurisée.
D'habitude je fais quelque chose comme ça:
Bien sûr, vous pouvez utiliser différents noms de cookies, etc. Vous pouvez également modifier un peu le contenu du cookie. Assurez-vous simplement qu'il ne soit pas facile à créer. Vous pouvez par exemple également créer un user_salt lors de la création de l'utilisateur et le placer également dans le cookie.
Vous pouvez aussi utiliser sha1 au lieu de md5 (ou à peu près n'importe quel algorithme)
Introduction
Votre titre «Gardez-moi connecté» - la meilleure approche rend difficile pour moi de savoir par où commencer car, si vous recherchez la meilleure approche, vous devrez tenir compte des éléments suivants:
Biscuits
Les cookies sont vulnérables. Entre les vulnérabilités courantes de vol de cookies dans les navigateurs et les attaques de script entre sites, nous devons accepter le fait que les cookies ne sont pas sans danger. Pour améliorer la sécurité, vous devez noter que php
setcookies
possède des fonctionnalités supplémentaires telles que
bool setcookie (chaîne $ name [ chaîne $ value [ int $ expire = 0 [ chaîne $ path [ chaîne $ domain [ bool $ secure =] [false, bool $ httponly = false]]]]]])
Définitions
Approche simple
Une solution simple serait:
L’étude de cas ci-dessus résume tous les exemples donnés sur cette page, mais les inconvénients sont que
Meilleure solution
Une meilleure solution serait
Exemple de code
// Set privateKey
// This should be saved securely
$key = 'fc4d57ed55a78de1a7b31e711866ef5a2848442349f52cd470008f6d30d47282';
$key = pack("H*", $key); // They key is used in binary form
// Am Using Memecahe as Sample Database
$db = new Memcache();
$db->addserver("127.0.0.1");
try {
// Start Remember Me
$rememberMe = new RememberMe($key);
$rememberMe->setDB($db); // set example database
// Check if remember me is present
if ($data = $rememberMe->auth()) {
printf("Returning User %s\n", $data['user']);
// Limit Acces Level
// Disable Change of password and private information etc
} else {
// Sample user
$user = "baba";
// Do normal login
$rememberMe->remember($user);
printf("New Account %s\n", $user);
}
} catch (Exception $e) {
printf("#Error %s\n", $e->getMessage());
}
Classe utilisée
class RememberMe {
private $key = null;
private $db;
function __construct($privatekey) {
$this->key = $privatekey;
}
public function setDB($db) {
$this->db = $db;
}
public function auth() {
// Check if remeber me cookie is present
if (! isset($_COOKIE["auto"]) || empty($_COOKIE["auto"])) {
return false;
}
// Decode cookie value
if (! $cookie = @json_decode($_COOKIE["auto"], true)) {
return false;
}
// Check all parameters
if (! (isset($cookie['user']) || isset($cookie['token']) || isset($cookie['signature']))) {
return false;
}
$var = $cookie['user'] . $cookie['token'];
// Check Signature
if (! $this->verify($var, $cookie['signature'])) {
throw new Exception("Cokies has been tampared with");
}
// Check Database
$info = $this->db->get($cookie['user']);
if (! $info) {
return false; // User must have deleted accout
}
// Check User Data
if (! $info = json_decode($info, true)) {
throw new Exception("User Data corrupted");
}
// Verify Token
if ($info['token'] !== $cookie['token']) {
throw new Exception("System Hijacked or User use another browser");
}
/**
* Important
* To make sure the cookie is always change
* reset the Token information
*/
$this->remember($info['user']);
return $info;
}
public function remember($user) {
$cookie = [
"user" => $user,
"token" => $this->getRand(64),
"signature" => null
];
$cookie['signature'] = $this->hash($cookie['user'] . $cookie['token']);
$encoded = json_encode($cookie);
// Add User to database
$this->db->set($user, $encoded);
/**
* Set Cookies
* In production enviroment Use
* setcookie("auto", $encoded, time() + $expiration, "/~root/",
* "example.com", 1, 1);
*/
setcookie("auto", $encoded); // Sample
}
public function verify($data, $hash) {
$Rand = substr($hash, 0, 4);
return $this->hash($data, $Rand) === $hash;
}
private function hash($value, $Rand = null) {
$Rand = $Rand === null ? $this->getRand(4) : $Rand;
return $Rand . bin2hex(hash_hmac('sha256', $value . $Rand, $this->key, true));
}
private function getRand($length) {
switch (true) {
case function_exists("mcrypt_create_iv") :
$r = mcrypt_create_iv($length, MCRYPT_DEV_URANDOM);
break;
case function_exists("openssl_random_pseudo_bytes") :
$r = openssl_random_pseudo_bytes($length);
break;
case is_readable('/dev/urandom') : // deceze
$r = file_get_contents('/dev/urandom', false, null, 0, $length);
break;
default :
$i = 0;
$r = "";
while($i ++ < $length) {
$r .= chr(mt_Rand(0, 255));
}
break;
}
return substr(bin2hex($r), 0, $length);
}
}
Test dans Firefox et Chrome
Avantage
Inconvénient
Solution rapide
Approche de cookie multiple
Lorsqu'un attaquant est sur le point de voler des cookies, il ne peut s'agir que d'un site Web ou d'un domaine particulier, par exemple. exemple.com
Mais en réalité, vous pouvez authentifier un utilisateur de 2 domaines différents (example.com & fakeaddsite.com) et lui donner l'apparence d'un "cookie d'annonce".
Certaines personnes pourraient se demander comment utiliser 2 cookies différents? Eh bien, c’est possible, imaginez example.com = localhost
et fakeaddsite.com = 192.168.1.120
. Si vous inspectez les cookies, cela ressemble à ceci
De l'image ci-dessus
192.168.1.120
HTTP_REFERER
définiREMOTE_ADDR
spécifiéAvantage
Inconvénient
Amélioration
ajax
Il y a deux articles très intéressants que j'ai trouvés en cherchant une solution parfaite au problème "souviens-moi de moi":
J'ai posé un angle de cette question ici , et les réponses vous mèneront à tous les liens de cookie de temporisation basés sur des jetons dont vous avez besoin.
Fondamentalement, vous ne stockez pas l'ID utilisateur dans le cookie. Vous stockez un jeton unique (chaîne énorme) que l'utilisateur utilise pour récupérer son ancienne session de connexion. Ensuite, pour le rendre vraiment sécurisé, vous demandez un mot de passe pour les opérations lourdes (comme changer le mot de passe lui-même).
Je recommanderais l'approche mentionnée par Stefan (c'est-à-dire, suivez les instructions de Meilleures pratiques relatives aux cookies de connexion persistante améliorées ) et recommandez également de vous assurer que vos cookies sont des cookies HttpOnly afin qu'ils ne soient pas accessibles, potentiellement malveillants , JavaScript.
Vieux fil, mais toujours une préoccupation valable. J'ai remarqué de bonnes réponses concernant la sécurité et le fait d'éviter l'utilisation de «la sécurité par l'obscurité», mais les méthodes techniques réellement fournies n'étaient pas suffisantes à mes yeux. Choses que je dois dire avant d’apporter ma méthode:
Cela étant dit, il existe deux excellentes méthodes d’auto-connexion sur votre système.
Premièrement, le moyen facile et peu coûteux qui confie tout à quelqu'un d'autre. Si vous faites en sorte que l’assistance de votre site se connecte avec, par exemple, votre compte Google +, vous avez probablement un bouton simplifié Google + qui connectera l’utilisateur s’il est déjà connecté à Google (je l’ai fait ici pour répondre à cette question, car je suis toujours connecté à google). Si vous souhaitez que l'utilisateur se connecte automatiquement s'il est déjà connecté avec un authentifiant approuvé et pris en charge, et que la case correspondante est cochée, demandez à vos scripts côté client d'exécuter le code derrière le bouton "connexion avec" correspondant avant le chargement. Assurez-vous simplement que le serveur stocke un identifiant unique dans une table de connexion automatique comportant le nom d'utilisateur, l'identifiant de session et l'authentificateur utilisé pour l'utilisateur. Étant donné que ces méthodes de connexion utilisent AJAX, vous attendez quand même une réponse et cette réponse est une réponse validée ou un rejet. Si vous obtenez une réponse validée, utilisez-la normalement, puis continuez de charger normalement l'utilisateur connecté. Sinon, la connexion a échoué, mais ne le dites pas à l'utilisateur, continuez comme s'il n'était pas connecté, il le remarquera. Ceci afin d'empêcher un attaquant qui a volé des cookies (ou les a forgés dans le but de faire grimper ses privilèges) d'apprendre que l'utilisateur se connecte automatiquement au site.
Cela ne coûte pas cher et peut également être considéré comme sale par certains utilisateurs, car il essaie de valider votre connexion déjà existante avec des sites tels que Google et Facebook, sans même vous le dire. Cependant, il ne doit pas être utilisé sur les utilisateurs qui n'ont pas demandé à se connecter automatiquement à votre site. Cette méthode particulière est réservée à l'authentification externe, comme avec Google+ ou FB.
Un authentificateur externe ayant été utilisé pour indiquer au serveur en coulisse si un utilisateur était validé ou non, un attaquant ne peut obtenir rien d'autre qu'un identifiant unique, ce qui est inutile par lui-même. Je vais élaborer:
Cette méthode peut et doit être utilisée conjointement avec votre authentificateur interne pour ceux qui se connectent à votre site à l'aide d'un authentificateur externe.
=========.
Maintenant, pour votre propre système d'authentification capable de se connecter automatiquement, voici comment je le fais:
DB a quelques tables:
TABLE users:
UID - auto increment, PK
username - varchar(255), unique, indexed, NOT NULL
password_hash - varchar(255), NOT NULL
...
TABLE sessions: session_id - varchar(?), PK session_token - varchar(?), NOT NULL session_data - MediumText, NOT NULL
.TABLE autologin: UID - auto increment, PK username - varchar(255), NOT NULL, allow duplicates hostname - varchar(255), NOT NULL, allow duplicates mac_address - char(23), NOT NULL, unique token - varchar(?), NOT NULL, allow duplicates expires - datetime code
Cela empêche toute personne d'usurper systématiquement l'adresse MAC et le nom d'hôte d'un utilisateur pour lequel il sait s'auto-enregistrer. NEVER demande à l'utilisateur de conserver un cookie avec son mot de passe, un texte en clair ou autre. Faites en sorte que le jeton soit régénéré à chaque navigation de page, exactement comme le jeton de session. Cela réduit considérablement la probabilité qu'un attaquant puisse obtenir un cookie de jeton valide et l'utiliser pour se connecter. Certaines personnes vont essayer de dire qu'un attaquant pourrait voler les cookies à la victime et faire une attaque de relecture de session pour se connecter. Si un attaquant pouvait voler les cookies (ce qui est possible), il aurait certainement compromis l'appareil en entier, ce qui signifie qu'il ne pourrait utiliser que l'appareil pour se connecter de toute façon, ce qui irait à l'encontre du but de voler des cookies. Tant que votre site fonctionne sur HTTPS (ce qui devrait être le cas lorsqu'il s'agit de mots de passe, de numéros CC ou d'autres systèmes de connexion), vous offrez toute la protection à l'utilisateur que vous pouvez dans un navigateur.
Une chose à garder à l'esprit: les données de session ne doivent pas expirer si vous utilisez la connexion automatique. Vous pouvez désactiver la possibilité de continuer la session de façon erronée, mais la validation dans le système doit permettre de reprendre les données de la session s'il s'agit de données persistantes censées continuer entre les sessions. Si vous voulez à la fois des données de session persistantes ET non persistantes, utilisez une autre table pour les données de session persistante avec le nom d'utilisateur comme clé PK et demandez au serveur de la récupérer comme les données de session normales. Utilisez simplement une autre variable.
Une fois cette connexion établie, le serveur doit toujours valider la session. C’est là que vous pouvez coder les attentes relatives aux systèmes volés ou compromis; les schémas et autres résultats attendus des connexions aux données de session peuvent souvent conduire à la conclusion qu'un système a été détourné ou que des cookies ont été forgés afin d'obtenir un accès. C’est là que votre ISS Tech peut définir des règles qui déclencheraient le verrouillage du compte ou le retrait automatique d’un utilisateur du système de connexion automatique, en laissant les attaquants suffisamment longtemps pour qu’ils déterminent le mode de réussite de l’attaquant et son mode de fonctionnement. coupez-les.En guise de conclusion, assurez-vous que toute tentative de récupération, tout changement de mot de passe ou tout échec de connexion au-delà du seuil entraîne la désactivation de la connexion automatique jusqu'à la validation correcte de l'utilisateur et la confirmation de son existence.
Je m'excuse si quelqu'un s'attendait à ce que le code soit donné dans ma réponse, cela ne se produira pas ici. Je dirai que j'utilise PHP, jQuery et AJAX pour exécuter mes sites et que je n'utilise JAMAIS Windows en tant que serveur ... jamais.
As a closing note, be sure that any recovery attempt, password changes, or login failures past the threshold result in auto-signin being disabled until the user validates properly and acknowledges this has occurred.
I apologize if anyone was expecting code to be given out in my answer, that's not going to happen here. I will say that I use PHP, jQuery, and AJAX to run my sites, and I NEVER use Windows as a server... ever.
Générez un hachage, avec peut-être uniquement un secret que vous connaissez, puis stockez-le dans votre base de données pour pouvoir l'associer à l'utilisateur. Devrait fonctionner assez bien.
Je ne comprends pas le concept de stockage d'éléments cryptés dans un cookie lorsque c'est la version cryptée de ce dernier qui doit être piratée. Si je manque quelque chose, commentez s'il vous plaît.
Je songe à adopter cette approche pour se souvenir de moi. Si vous pouvez voir des problèmes, veuillez commenter.
Créez une table pour stocker les données "Remember Me" - distincte de la table des utilisateurs afin que je puisse me connecter à partir de plusieurs périphériques.
En cas de connexion réussie (avec Remember Me coché):
a) Générez une chaîne aléatoire unique à utiliser comme ID utilisateur sur cette machine: bigUserID
b) Générer une chaîne aléatoire unique: bigKey
c) Stocker un cookie: bigUserID: bigKey
d) Dans le tableau "Remember Me", ajoutez un enregistrement avec: ID utilisateur, adresse IP, bigUserID, bigKey
Si vous essayez d'accéder à quelque chose qui nécessite une connexion:
a) Recherchez le cookie et recherchez bigUserID & bigKey avec une adresse IP correspondante
b) Si vous le trouvez, connectez-vous à la personne mais définissez un indicateur dans la table utilisateur "Connexion logicielle" afin que, pour toute opération dangereuse, vous puissiez demander une connexion complète.
À la déconnexion, marquez tous les enregistrements "Remember Me" pour cet utilisateur comme expirés.
Les seules vulnérabilités que je peux voir sont;
Ma solution est comme ça. Ce n'est pas à 100% à l'épreuve des balles, mais je pense que cela vous épargnera dans la plupart des cas.
Lorsque l'utilisateur est connecté avec succès, créez une chaîne avec ces informations:
$data = (SALT + ":" + hash(User Agent) + ":" + username
+ ":" + LoginTimestamp + ":"+ SALT)
Cryptez $data
, définissez le type sur HttpOnly et définissez le cookie.
Lorsque l'utilisateur revient sur votre site, procédez comme suit:
:
. Si des utilisateurs se déconnectent, supprimez ce cookie. Créez un nouveau cookie si l'utilisateur se reconnecte.
J'ai lu toutes les réponses et j'ai toujours eu du mal à extraire ce que j'étais censé faire. Si une image vaut 1k mots, j'espère que cela aidera les autres à mettre en place un stockage persistant sécurisé basé sur Barry Jaspan's Meilleure pratique en matière de cookies de connexion persistants améliorés
Si vous avez des questions, des commentaires ou des suggestions, je vais essayer de mettre à jour le diagramme afin que le débutant ait la possibilité de mettre en place une connexion persistante sécurisée.
L'implémentation d'une fonctionnalité "Keep Me Logged In" signifie que vous devez définir exactement ce que cela signifiera pour l'utilisateur. Dans le cas le plus simple, j'utiliserais cela pour signifier que la session a un délai d'expiration beaucoup plus long: 2 jours (par exemple) au lieu de 2 heures. Pour ce faire, vous aurez besoin de votre propre stockage de session, probablement dans une base de données, afin de pouvoir définir des heures d'expiration personnalisées pour les données de session. Ensuite, vous devez vous assurer de définir un cookie qui restera en place pendant quelques jours (ou plus longtemps), plutôt que d’expirer à la fermeture du navigateur.
Je peux vous entendre demander "Pourquoi 2 jours? Pourquoi pas 2 semaines?". En effet, l'utilisation d'une session dans PHP va automatiquement repousser l'expiration. En effet, l'expiration d'une session dans PHP est en réalité un délai d'inactivité.
Cela dit, je mettrais probablement en œuvre une valeur de délai d’attente plus dure que je stocke dans la session elle-même et à l’environ 2 semaines, et j’ajouterais du code pour voir cela et invalider de force la session. Ou du moins pour les déconnecter. Cela signifie que l'utilisateur sera invité à se connecter périodiquement. Yahoo! est ce que ca.