OK, deixe-me colocar isso sem rodeios: se você estiver colocando dados do usuário ou algo derivado dos dados do usuário em um cookie para esse fim, está fazendo algo errado.
Lá. Eu disse isso. Agora podemos avançar para a resposta real.
O que há de errado com o hash de dados do usuário, você pergunta? Bem, tudo se resume à superfície de exposição e segurança através da obscuridade.
Imagine por um segundo que você é um atacante. Você vê um cookie criptográfico definido para o lembrete-me na sua sessão. Tem 32 caracteres de largura. Gee. Isso pode ser um MD5 ...
Vamos imaginar também por um segundo que eles conhecem o algoritmo que você usou. Por exemplo:
md5(salt+username+ip+salt)
Agora, tudo o que um invasor precisa fazer é forçar com força bruta o "sal" (que não é realmente um sal, mas mais sobre isso depois), e agora ele pode gerar todos os tokens falsos que quiser com qualquer nome de usuário para seu endereço IP! Mas forçar brutalmente um sal é difícil, certo? Absolutamente. Mas as GPUs modernas são extremamente boas nisso. E a menos que você use aleatoriedade suficiente nele (faça-o grande o suficiente), ele cairá rapidamente, e com ele as chaves do seu castelo.
Em resumo, a única coisa que protege você é o sal, que na verdade não o protege tanto quanto você pensa.
Mas espere!
Tudo isso foi previsto que o invasor conhece o algoritmo! Se é secreto e confuso, então você está seguro, certo? ERRADO . Essa linha de pensamento tem um nome: Segurança através da obscuridade , que NUNCA deve ser invocada.
A melhor maneira
A melhor maneira é nunca deixar as informações de um usuário deixarem o servidor, exceto a identificação.
Quando o usuário efetuar login, gere um token aleatório grande (de 128 a 256 bits). Adicione isso a uma tabela de banco de dados que mapeie o token para o ID do usuário e envie-o para o cliente no cookie.
E se o invasor adivinhar o token aleatório de outro usuário?
Bem, vamos fazer algumas contas aqui. Estamos gerando um token aleatório de 128 bits. Isso significa que existem:
possibilities = 2^128
possibilities = 3.4 * 10^38
Agora, para mostrar quão absurdamente grande esse número é, vamos imaginar todos os servidores da Internet (digamos 50.000.000 hoje) tentando forçar esse número a uma taxa de 1.000.000.000 por segundo cada. Na realidade, seus servidores derreteriam sob tal carga, mas vamos jogar isso.
guesses_per_second = servers * guesses
guesses_per_second = 50,000,000 * 1,000,000,000
guesses_per_second = 50,000,000,000,000,000
Então, 50 quadrilhões de palpites por segundo. É rápido! Certo?
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
Então, 6,8 sextilhões de segundos ...
Vamos tentar reduzir isso para números mais amigáveis.
215,626,585,489,599 years
Ou melhor ainda:
47917 times the age of the universe
Sim, isso é 47917 vezes a idade do universo ...
Basicamente, não será quebrado.
Entao, para resumir:
A melhor abordagem que eu recomendo é armazenar o cookie com três partes.
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);
}
Em seguida, para validar:
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);
}
}
}
Nota: Não use o token ou a combinação de usuário e token para procurar um registro no seu banco de dados. Sempre certifique-se de buscar um registro com base no usuário e use uma função de comparação com tempo seguro para comparar o token buscado posteriormente. Mais sobre ataques de tempo .
Agora, é muito importante que SECRET_KEY
seja um segredo criptográfico (gerado por algo como /dev/urandom
e / ou derivado de uma entrada de alta entropia). Além disso, GenerateRandomToken()
precisa ser uma fonte aleatória forte ( mt_rand()
não é suficientemente forte. Use uma biblioteca, como RandomLib ou random_compat , ou mcrypt_create_iv()
with DEV_URANDOM
) ...
O hash_equals()
é para evitar ataques de tempo . Se você usa uma versão PHP abaixo do PHP 5.6, a função hash_equals()
não é suportada. Nesse caso, você pode substituir hash_equals()
pela função 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;
}
Normalmente eu faço algo assim:
É claro que você pode usar nomes de cookies diferentes, etc. também pode alterar um pouco o conteúdo do cookie, apenas certifique-se de que não seja fácil de criar. Por exemplo, você também pode criar um user_salt quando o usuário é criado e também colocá-lo no cookie.
Além disso, você pode usar sha1 em vez de md5 (ou praticamente qualquer algoritmo)
fonte
Introdução
Seu título “Mantenha-me conectado” - a melhor abordagem dificulta para mim saber por onde começar, porque se você está procurando a melhor abordagem, deve considerar o seguinte:
Biscoitos
Os cookies são vulneráveis. Entre as vulnerabilidades comuns de roubo de cookies do navegador e os ataques de script entre sites, devemos aceitar que os cookies não são seguros. Para ajudar a melhorar a segurança, observe que
php
setcookies
há funcionalidades adicionais, comoDefinições
Abordagem Simples
Uma solução simples seria:
O estudo de caso acima resume todos os exemplos dados nesta página, mas as desvantagens são que
Better Solution
Uma solução melhor seria
Código de exemplo
Classe usada
Teste no Firefox e Chrome
Vantagem
Desvantagem
Conserto rápido
Abordagem de cookies múltiplos
Quando um invasor está prestes a roubar cookies, o único foco é em um site ou domínio específico, por exemplo. example.com
Mas, na verdade, você pode autenticar um usuário em 2 domínios diferentes ( exemplo.com e fakeaddsite.com ) e parecer "Cookie de anúncio"
Algumas pessoas podem se perguntar como você pode usar 2 cookies diferentes? Bem, é possível, imagine
example.com = localhost
efakeaddsite.com = 192.168.1.120
. Se você inspecionar os cookies, ficaria assimDa imagem acima
192.168.1.120
HTTP_REFERER
REMOTE_ADDR
Vantagem
Desvantagem
Melhoria
ajax
fonte
Perguntei a um ângulo de esta questão aqui , e as respostas irão levá-lo para todos os links de cookies com base em token de temporização-out que você precisa.
Basicamente, você não armazena o userId no cookie. Você armazena um token único (cadeia enorme) que o usuário usa para pegar sua sessão de logon antiga. Para torná-lo realmente seguro, você solicita uma senha para operações pesadas (como alterar a própria senha).
fonte
Segmento antigo, mas ainda uma preocupação válida. Notei algumas boas respostas sobre segurança e evitei o uso de 'segurança através da obscuridade', mas os métodos técnicos reais dados não eram suficientes aos meus olhos. Coisas que devo dizer antes de contribuir com meu método:
Tudo dito, há duas ótimas maneiras de fazer logon automático no seu sistema.
Primeiro, a maneira fácil e barata de colocar tudo em outra pessoa. Se você oferecer suporte ao site para fazer login com, por exemplo, sua conta do google +, provavelmente terá um botão do google + simplificado que fará o login do usuário se ele já estiver conectado ao google (fiz isso aqui para responder a essa pergunta, como sempre conectado ao google). Se você desejar que o usuário faça login automaticamente, se já estiver conectado com um autenticador confiável e suportado, e marque a caixa para fazer isso, seus scripts do lado do cliente executam o código por trás do botão correspondente 'entrar com' antes de carregar , certifique-se de que o servidor armazene um ID exclusivo em uma tabela de logon automático com o nome de usuário, o ID da sessão e o autenticador usado para o usuário. Como esses métodos de login usam AJAX, você está esperando uma resposta de qualquer maneira, e essa resposta é uma resposta validada ou uma rejeição. Se você receber uma resposta validada, use-a normalmente e continue carregando o usuário conectado normalmente. Caso contrário, o login falhou, mas não avise o usuário, apenas continue como não logado, eles perceberão. Isso evita que um invasor que roubou cookies (ou forjou-os na tentativa de aumentar privilégios) aprenda que o usuário se autentica no site.
Isso é barato e também pode ser considerado sujo por alguns, porque tenta validar seu potencial já conectado com lugares como Google e Facebook, sem nem mesmo informar. No entanto, ele não deve ser usado em usuários que não solicitaram o login automático no site e esse método específico é apenas para autenticação externa, como no Google+ ou no FB.
Como um autenticador externo foi usado para informar ao servidor nos bastidores se um usuário foi ou não validado, um invasor não pode obter nada além de um ID exclusivo, que é inútil por si só. Vou elaborar:
Independentemente do que seja, mesmo que um invasor use um ID que não exista, a tentativa deverá falhar em todas as tentativas, exceto quando uma resposta validada for recebida.
Esse método pode e deve ser usado em conjunto com o autenticador interno para aqueles que acessam o site usando um autenticador externo.
=========
Agora, para o seu próprio sistema autenticador que pode fazer login automático de usuários, é assim que eu faço:
O DB possui algumas tabelas:
Observe que o nome de usuário pode ter 255 caracteres. Meu programa de servidor limita os nomes de usuários no meu sistema a 32 caracteres, mas os autenticadores externos podem ter nomes de usuários com o @ domain.tld maior que isso, portanto, apenas ofereço suporte ao tamanho máximo de um endereço de email para obter compatibilidade máxima.
Observe que não há campo de usuário nesta tabela, porque o nome de usuário, quando conectado, está nos dados da sessão e o programa não permite dados nulos. O session_id e o session_token podem ser gerados usando hashes aleatórios md5, sha1 / 128/256 hashes, carimbos de data / hora com seqüências aleatórias adicionadas a eles e depois hash, ou o que você quiser, mas a entropia de sua saída deve permanecer tão alta quanto tolerável. atenue os ataques de força bruta, mesmo que decolem, e todos os hashes gerados pela sua classe de sessão devem ser verificados quanto a correspondências na tabela de sessões antes de tentar adicioná-los.
Os endereços MAC, por natureza, devem ser ÚNICOS; portanto, faz sentido que cada entrada tenha um valor único. Os nomes de host, por outro lado, podem ser duplicados em redes separadas legitimamente. Quantas pessoas usam "PC doméstico" como um de seus nomes de computador? O nome de usuário é obtido dos dados da sessão pelo servidor, portanto, é impossível manipulá-lo. Quanto ao token, o mesmo método para gerar tokens de sessão para páginas deve ser usado para gerar tokens em cookies para o logon automático do usuário. Por fim, o código de data e hora é adicionado para quando o usuário precisaria revalidar suas credenciais. Atualize essa data e hora no logon do usuário, mantendo-o dentro de alguns dias ou force-o a expirar, independentemente do último logon, mantendo-o apenas por um mês ou mais, conforme o seu design.
Isso evita que alguém falsifique sistematicamente o MAC e o nome do host de um usuário que ele conheça automaticamente. NUNCAfaça com que o usuário mantenha um cookie com sua senha, texto não criptografado ou outro. Faça com que o token seja regenerado em cada navegação da página, da mesma maneira que faria com o token da sessão. Isso reduz enormemente a probabilidade de um invasor obter um cookie de token válido e usá-lo para efetuar login. Algumas pessoas tentarão dizer que um invasor pode roubar os cookies da vítima e realizar um ataque de repetição de sessão para fazer login. Se um invasor pudesse roubar os cookies (o que é possível), certamente teria comprometido o dispositivo inteiro, o que significa que apenas poderia usá-lo para efetuar login de qualquer maneira, o que anula o objetivo de roubar cookies completamente. Desde que seu site seja executado em HTTPS (o que deveria ser feito ao lidar com senhas, números CC ou outros sistemas de login), você concedeu toda a proteção ao usuário que puder em um navegador.
Um aspecto a ter em mente: os dados da sessão não devem expirar se você usar o logon automático. Você pode expirar a capacidade de continuar a sessão falsamente, mas a validação no sistema deve retomar os dados da sessão se forem dados persistentes que se espera que continuem entre as sessões. Se você deseja dados de sessão persistentes E não persistentes, use outra tabela para dados de sessão persistente com o nome de usuário como PK e faça com que o servidor o recupere como faria com os dados normais da sessão, basta usar outra variável.
Depois que um login for alcançado dessa maneira, o servidor ainda deverá validar a sessão. É aqui que você pode codificar as expectativas para sistemas roubados ou comprometidos; os padrões e outros resultados esperados de logons nos dados da sessão geralmente podem levar à conclusão de que um sistema foi invadido ou que cookies foram forjados para obter acesso. É aqui que a sua ISS Tech pode estabelecer regras que acionariam o bloqueio de conta ou a remoção automática de um usuário do sistema de login automático, mantendo os invasores afastados o tempo suficiente para que o usuário determine como o invasor conseguiu e como cortá-los.
Como observação final, verifique se qualquer tentativa de recuperação, alteração de senha ou falha de login além do limite resulta na desativação do login automático até que o usuário valide adequadamente e reconheça que isso ocorreu.
Peço desculpas se alguém esperava que o código fosse fornecido na minha resposta, isso não vai acontecer aqui. Vou dizer que uso PHP, jQuery e AJAX para executar meus sites, e NUNCA uso o Windows como servidor ... nunca.
fonte
Eu recomendaria a abordagem mencionada por Stefan (ou seja, siga as diretrizes nas Melhores práticas de cookies persistentes para login persistente ) e também recomendaria que você se certificasse de que seus cookies são cookies HttpOnly, para que não sejam acessíveis a JavaScript potencialmente malicioso.
fonte
Gere um hash, talvez com apenas um segredo, e depois armazene-o no seu banco de dados para que ele possa ser associado ao usuário. Deve funcionar muito bem.
fonte
Minha solução é assim. Não é 100% à prova de balas, mas acho que o ajudará na maioria dos casos.
Quando o usuário efetuou login com êxito, crie uma sequência com essas informações:
Criptografar
$data
, defina o tipo como HttpOnly e defina o cookie.Quando o usuário voltar ao seu site, siga estas etapas:
:
caráter.Se o usuário sair, remova este cookie. Crie um novo cookie se o usuário efetuar logon novamente.
fonte
Não entendo o conceito de armazenamento de coisas criptografadas em um cookie quando é a versão criptografada que você precisa fazer para invadir. Se estiver faltando alguma coisa, comente.
Estou pensando em adotar essa abordagem para 'Remember Me'. Se você encontrar algum problema, por favor, comente.
Crie uma tabela para armazenar os dados "Lembrar de mim" - separados da tabela do usuário para que eu possa efetuar login em vários dispositivos.
No login bem-sucedido (com o recurso Remember Me marcado):
a) Gere uma sequência aleatória exclusiva para ser usada como o UserID nesta máquina: bigUserID
b) Gere uma sequência aleatória única: bigKey
c) Armazene um cookie: bigUserID: bigKey
d) Na tabela "Lembrar-me", adicione um registro com: ID do usuário, endereço IP, bigUserID, bigKey
Se estiver tentando acessar algo que exija login:
a) Verifique o cookie e pesquise bigUserID e bigKey com um endereço IP correspondente
b) Se você o encontrar, faça o login da pessoa, mas defina um sinalizador na tabela de usuário "login suave" para que, para qualquer operação perigosa, você possa solicitar um login completo.
No logout, marque todos os registros "Lembrar de mim" para esse usuário como expirados.
As únicas vulnerabilidades que eu posso ver são;
fonte
Li todas as respostas e ainda achava difícil extrair o que deveria fazer. Se uma imagem vale 1k palavras, espero que isso ajude outras pessoas a implementar um armazenamento persistente seguro com base nas práticas recomendadas de cookie persistente aprimorado de Barry Jaspan
Se você tiver dúvidas, comentários ou sugestões, tentarei atualizar o diagrama para refletir o novato que está tentando implementar um login persistente seguro.
fonte
A implementação do recurso "Mantenha-me conectado" significa que você precisa definir exatamente o que isso significa para o usuário. No caso mais simples, eu usaria isso para significar que a sessão tem um tempo limite muito mais longo: 2 dias (digamos) em vez de 2 horas. Para fazer isso, você precisará de seu próprio armazenamento de sessão, provavelmente em um banco de dados, para poder definir tempos de expiração personalizados para os dados da sessão. Em seguida, você precisa definir um cookie que permanecerá por alguns dias (ou mais), em vez de expirar quando fecharem o navegador.
Eu posso ouvir você perguntando "por que 2 dias? Por que não 2 semanas?". Isso ocorre porque o uso de uma sessão no PHP automaticamente atrasa a expiração. Isso ocorre porque a expiração de uma sessão no PHP é na verdade um tempo limite inativo.
Agora, tendo dito isso, provavelmente implementaria um valor de tempo limite mais difícil que armazene na própria sessão e após duas semanas ou mais e adicionaria código para ver isso e invalidar à força a sessão. Ou pelo menos para desconectá-los. Isso significa que o usuário será solicitado a fazer login periodicamente. Yahoo! faz isso.
fonte
Eu acho que você poderia fazer isso:
Loja
$cookiestring
no banco de dados e defina-o como um cookie. Defina também o nome de usuário da pessoa como um cookie. O ponto principal de um hash é que ele não pode ter engenharia reversa.Quando um usuário aparecer, obtenha o nome de usuário de um cookie e não
$cookieString
de outro. Se$cookieString
corresponder ao armazenado no banco de dados, o usuário será autenticado. Como password_hash usa um sal diferente a cada vez, é irrelevante quanto ao texto não criptografado.fonte