Bien, déjenme decir esto sin rodeos: si está colocando datos de usuario, o cualquier cosa derivada de los datos de usuario en una cookie para este propósito, está haciendo algo mal.
Ahí. Lo dije. Ahora podemos pasar a la respuesta real.
¿Qué tiene de malo el hashing de datos de usuario? Bueno, todo se reduce a la superficie de exposición y la seguridad a través de la oscuridad.
Imagina por un segundo que eres un atacante. Verá una cookie criptográfica establecida para recordarme en su sesión. Tiene 32 caracteres de ancho. Caramba. Eso puede ser un MD5 ...
Imaginemos por un segundo que conocen el algoritmo que usó. Por ejemplo:
md5(salt+username+ip+salt)
Ahora, todo lo que un atacante debe hacer es forzar la "sal" (que no es realmente una sal, pero más sobre eso más adelante), ¡y ahora puede generar todos los tokens falsos que quiera con cualquier nombre de usuario para su dirección IP! Pero forzar una sal con fuerza bruta es difícil, ¿verdad? Absolutamente. Pero las GPU modernas son extremadamente buenas en eso. Y a menos que uses suficiente aleatoriedad (hazlo lo suficientemente grande), caerá rápidamente y con él las llaves de tu castillo.
En resumen, lo único que te protege es la sal, que en realidad no te protege tanto como crees.
¡Pero espera!
¡Todo eso se basaba en que el atacante conoce el algoritmo! Si es secreto y confuso, entonces estás a salvo, ¿verdad? MAL . Esa línea de pensamiento tiene un nombre: Seguridad a través de la oscuridad , que NUNCA se debe confiar.
La mejor manera
La mejor manera es nunca dejar que la información de un usuario abandone el servidor, excepto la identificación.
Cuando el usuario inicie sesión, genere un token aleatorio grande (de 128 a 256 bits). Agregue eso a una tabla de base de datos que asigna el token al ID de usuario y luego envíelo al cliente en la cookie.
¿Qué pasa si el atacante adivina la ficha aleatoria de otro usuario?
Bueno, hagamos algunas matemáticas aquí. Estamos generando un token aleatorio de 128 bits. Eso significa que hay:
possibilities = 2^128
possibilities = 3.4 * 10^38
Ahora, para mostrar cuán absurdamente grande es ese número, imaginemos que cada servidor en Internet (digamos 50,000,000 hoy) intenta forzar ese número a una velocidad de 1,000,000,000 por segundo cada uno. En realidad, sus servidores se derretirían bajo tal carga, pero juguemos esto.
guesses_per_second = servers * guesses
guesses_per_second = 50,000,000 * 1,000,000,000
guesses_per_second = 50,000,000,000,000,000
Entonces 50 cuatrillones de conjeturas por segundo. ¡Eso es rápido! ¿Correcto?
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
Entonces 6.8 sextillones de segundos ...
Intentemos reducir eso a números más amigables.
215,626,585,489,599 years
O mejor:
47917 times the age of the universe
Sí, eso es 47917 veces la edad del universo ...
Básicamente, no se va a romper.
Así que para resumir:
El mejor enfoque que recomiendo es almacenar la cookie con tres 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);
}
Luego, 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: No use el token o la combinación de usuario y token para buscar un registro en su base de datos. Siempre asegúrese de obtener un registro basado en el usuario y utilice una función de comparación segura para comparar el token obtenido posteriormente. Más acerca de los ataques de tiempo .
Ahora, es muy importante que SECRET_KEY
sea un secreto criptográfico (generado por algo como /dev/urandom
y / o derivado de una entrada de alta entropía). Además, GenerateRandomToken()
debe ser una fuente aleatoria fuerte ( mt_rand()
no es lo suficientemente fuerte. Utilice una biblioteca, como RandomLib o random_compat , o mcrypt_create_iv()
con DEV_URANDOM
) ...
El hash_equals()
es para evitar ataques de tiempo . Si usa una versión de PHP por debajo de PHP 5.6, la función hash_equals()
no es compatible. En este caso, puede reemplazarlo hash_equals()
con la función 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;
}
Usualmente hago algo como esto:
Por supuesto, puede usar diferentes nombres de cookies, etc. También puede cambiar un poco el contenido de la cookie, solo asegúrese de que no se cree fácilmente. Por ejemplo, también puede crear un user_salt cuando se crea el usuario y también ponerlo en la cookie.
También podría usar sha1 en lugar de md5 (o casi cualquier algoritmo)
fuente
Introducción
Su título "Mantenerme conectado": el mejor enfoque me dificulta saber por dónde empezar porque si está buscando el mejor enfoque, entonces debería considerar lo siguiente:
Galletas
Las cookies son vulnerables. Entre las vulnerabilidades comunes de robo de cookies del navegador y los ataques de secuencias de comandos entre sitios, debemos aceptar que las cookies no son seguras. Para ayudar a mejorar la seguridad, debe tener en cuenta que
php
setcookies
tiene una funcionalidad adicional, comoDefiniciones
Enfoque simple
Una solución simple sería:
El estudio de caso anterior resume todos los ejemplos dados en esta página, pero sus desventajas es que
Mejor solución
Una mejor solución sería
Código de ejemplo
Clase utilizada
Pruebas en Firefox y Chrome
Ventaja
Desventaja
Arreglo rapido
Enfoque de cookies múltiples
Cuando un atacante está a punto de robar cookies, solo céntralo en un sitio web o dominio en particular, por ejemplo. ejemplo.com
Pero realmente puede autenticar a un usuario de 2 dominios diferentes ( example.com y fakeaddsite.com ) y hacer que se vea como "Cookie de publicidad "
Algunas personas podrían preguntarse cómo puedes usar 2 cookies diferentes. Bueno, es posible, imagina
example.com = localhost
yfakeaddsite.com = 192.168.1.120
. Si inspecciona las cookies, se vería asíDe la imagen de arriba
192.168.1.120
HTTP_REFERER
REMOTE_ADDR
Ventaja
Desventaja
Mejora
ajax
fuente
Hice un ángulo de esta pregunta aquí , y las respuestas lo llevarán a todos los enlaces de cookies de tiempo de espera basados en tokens que necesita.
Básicamente, no almacena el ID de usuario en la cookie. Almacena un token de una sola vez (cadena enorme) que el usuario utiliza para recoger su sesión de inicio de sesión anterior. Luego, para que sea realmente seguro, solicite una contraseña para operaciones pesadas (como cambiar la contraseña en sí).
fuente
Hilo antiguo, pero sigue siendo una preocupación válida. Noté algunas buenas respuestas sobre la seguridad, y evité el uso de 'seguridad a través de la oscuridad', pero los métodos técnicos reales dados no fueron suficientes a mis ojos. Cosas que debo decir antes de aportar mi método:
Dicho todo esto, hay dos excelentes maneras de iniciar sesión automáticamente en su sistema.
Primero, la manera barata y fácil que lo pone todo en otra persona. Si hace que su sitio admita iniciar sesión con, digamos, su cuenta de google +, probablemente tenga un botón optimizado de google + que registrará al usuario si ya ha iniciado sesión en google (lo hice aquí para responder a esta pregunta, ya que siempre estoy iniciado sesión en google). Si desea que el usuario inicie sesión automáticamente si ya inició sesión con un autenticador confiable y compatible, y marcó la casilla para hacerlo, haga que sus scripts del lado del cliente ejecuten el código detrás del botón correspondiente 'iniciar sesión con' antes de cargar , solo asegúrese de que el servidor almacene una ID única en una tabla de inicio de sesión automático que tenga el nombre de usuario, la ID de sesión y el autenticador utilizado por el usuario. Dado que estos métodos de inicio de sesión utilizan AJAX, de todos modos está esperando una respuesta, y esa respuesta es una respuesta validada o un rechazo. Si obtiene una respuesta validada, úsela de manera normal, luego continúe cargando el usuario conectado de manera normal. De lo contrario, el inicio de sesión falló, pero no se lo digas al usuario, solo continúa como no iniciado sesión, se darán cuenta. Esto es para evitar que un atacante que robó cookies (o las falsificó en un intento de aumentar los privilegios) sepa que el usuario inicia sesión automáticamente en el sitio.
Esto es barato, y algunos también pueden considerarlo sucio porque trata de validar su potencial ya firmado en lugares como Google y Facebook, sin siquiera decirle. Sin embargo, no debe usarse en usuarios que no hayan solicitado iniciar sesión automáticamente en su sitio, y este método en particular es solo para autenticación externa, como con Google+ o FB.
Debido a que se usó un autenticador externo para decirle al servidor detrás de escena si un usuario fue validado o no, un atacante no puede obtener nada más que una identificación única, que es inútil por sí sola. Elaboraré:
No importa qué, incluso si un atacante usa una ID que no existe, el intento debe fallar en todos los intentos, excepto cuando se recibe una respuesta validada.
Este método puede y debe usarse junto con su autenticador interno para aquellos que inician sesión en su sitio utilizando un autenticador externo.
=========
Ahora, para su propio sistema de autenticación que puede iniciar sesión automáticamente en los usuarios, así es como lo hago:
DB tiene algunas tablas:
Tenga en cuenta que el nombre de usuario puede tener 255 caracteres de longitud. Mi programa de servidor limita los nombres de usuario en mi sistema a 32 caracteres, pero los autenticadores externos pueden tener nombres de usuario con su @ dominio.
Tenga en cuenta que no hay campo de usuario en esta tabla, porque el nombre de usuario, cuando está conectado, está en los datos de la sesión, y el programa no permite datos nulos. El session_id y el session_token se pueden generar utilizando hash aleatorios md5, hash sha1 / 128/256, sellos de fecha y hora con cadenas aleatorias agregadas y luego hash, o lo que desee, pero la entropía de su salida debe permanecer tan alta como sea tolerable mitigue los ataques de fuerza bruta incluso desde el despegue, y todos los hashes generados por su clase de sesión deben verificarse para encontrar coincidencias en la tabla de sesiones antes de intentar agregarlos.
Se supone que las direcciones MAC por su naturaleza son ÚNICAS, por lo tanto, tiene sentido que cada entrada tenga un valor único. Los nombres de host, por otro lado, podrían duplicarse en redes separadas legítimamente. ¿Cuántas personas usan "Home-PC" como uno de los nombres de sus computadoras? El servidor toma el nombre de usuario de los datos de la sesión, por lo que es imposible manipularlo. En cuanto al token, el mismo método para generar tokens de sesión para páginas debe usarse para generar tokens en cookies para el inicio de sesión automático del usuario. Por último, el código de fecha y hora se agrega para cuando el usuario deba volver a validar sus credenciales. Actualice esta fecha y hora en el inicio de sesión del usuario manteniéndola dentro de unos días, o forzarla a que caduque independientemente del último inicio de sesión manteniéndola solo durante un mes más o menos, lo que dicte su diseño.
Esto evita que alguien falsifique sistemáticamente el MAC y el nombre de host para un usuario en el que conocen el inicio de sesión automático. NUNCAhaga que el usuario guarde una cookie con su contraseña, texto claro o de otra manera. Haga que el token se regenere en cada navegación de página, tal como lo haría con el token de sesión. Esto reduce enormemente la probabilidad de que un atacante pueda obtener una cookie de token válida y usarla para iniciar sesión. Algunas personas intentarán decir que un atacante podría robar las cookies de la víctima y realizar un ataque de reproducción de sesión para iniciar sesión. Si un atacante pudiera robar las cookies (lo cual es posible), ciertamente habría comprometido todo el dispositivo, lo que significa que podría usar el dispositivo para iniciar sesión de todos modos, lo que anula el propósito de robar cookies por completo. Siempre que su sitio se ejecute a través de HTTPS (que debería ser cuando se trata de contraseñas, números CC u otros sistemas de inicio de sesión), ha brindado toda la protección al usuario que puede dentro de un navegador.
Una cosa a tener en cuenta: los datos de la sesión no deben caducar si usa el inicio de sesión automático. Puede expirar la capacidad de continuar la sesión falsamente, pero la validación en el sistema debe reanudar los datos de la sesión si son datos persistentes que se espera que continúen entre sesiones. Si desea datos de sesión tanto persistentes como no persistentes, use otra tabla para datos de sesión persistentes con el nombre de usuario como PK, y haga que el servidor los recupere como lo haría con los datos de sesión normales, simplemente use otra variable.
Una vez que se ha logrado iniciar sesión de esta manera, el servidor aún debe validar la sesión. Aquí es donde puede codificar las expectativas para sistemas robados o comprometidos; los patrones y otros resultados esperados de los inicios de sesión en los datos de la sesión a menudo pueden llevar a conclusiones de que un sistema fue secuestrado o que se falsificaron cookies para obtener acceso. Aquí es donde su ISS Tech puede establecer reglas que desencadenarían el bloqueo de una cuenta o la eliminación automática de un usuario del sistema de inicio de sesión automático, manteniendo a los atacantes fuera el tiempo suficiente para que el usuario pueda determinar cómo el atacante tuvo éxito y cómo cortarlos.
Como nota de cierre, asegúrese de que cualquier intento de recuperación, cambio de contraseña o fallas de inicio de sesión que superen el umbral provoquen que se desactive el inicio de sesión automático hasta que el usuario valide correctamente y reconozca que esto ha ocurrido.
Pido disculpas si alguien esperaba recibir código en mi respuesta, eso no va a suceder aquí. Diré que uso PHP, jQuery y AJAX para ejecutar mis sitios, y NUNCA uso Windows como servidor ... nunca.
fuente
Recomendaría el enfoque mencionado por Stefan (es decir, siga las pautas en Mejores prácticas de cookies de inicio de sesión persistentes mejoradas ) y también le recomiendo que se asegure de que sus cookies son cookies HttpOnly para que no sean accesibles a JavaScript potencialmente malicioso.
fuente
Genere un hash, tal vez con un secreto que solo usted conoce, luego almacénelo en su base de datos para que pueda asociarse con el usuario. Debería funcionar bastante bien.
fuente
Mi solución es así. No es 100% a prueba de balas, pero creo que te salvará para la mayoría de los casos.
Cuando el usuario inició sesión correctamente, cree una cadena con esta información:
Cifrar
$data
, establecer el tipo en HttpOnly y configurar la cookie.Cuando el usuario regrese a su sitio, siga estos pasos:
:
carácter.Si el usuario cierra sesión, elimine esta cookie. Cree una nueva cookie si el usuario vuelve a iniciar sesión.
fuente
No entiendo el concepto de almacenar cosas encriptadas en una cookie cuando es la versión encriptada la que necesitas para hackear. Si me falta algo, por favor comente.
Estoy pensando en adoptar este enfoque para "Recordarme". Si puede ver algún problema, comente.
Cree una tabla para almacenar los datos de "Recordarme", separados de la tabla de usuario para que pueda iniciar sesión desde varios dispositivos.
En el inicio de sesión exitoso (con Recordarme marcado):
a) Genere una cadena aleatoria única para usar como ID de usuario en esta máquina: bigUserID
b) Generar una cadena aleatoria única: bigKey
c) Almacenar una cookie: bigUserID: bigKey
d) En la tabla "Recordarme", agregue un registro con: ID de usuario, Dirección IP, bigUserID, bigKey
Si intenta acceder a algo que requiere inicio de sesión:
a) Verifique la cookie y busque bigUserID & bigKey con una dirección IP coincidente
b) Si lo encuentra, inicie sesión en la persona pero establezca un indicador en la tabla de usuario "inicio de sesión suave" para que, en caso de operaciones peligrosas, pueda solicitar un inicio de sesión completo.
Al cerrar sesión, marque todos los registros "Recordarme" para ese usuario como caducados.
Las únicas vulnerabilidades que puedo ver es;
fuente
Leí todas las respuestas y aún me resultaba difícil extraer lo que se suponía que debía hacer. Si una imagen vale más de mil palabras, espero que esto ayude a otros a implementar un almacenamiento seguro y persistente basado en la mejor práctica de cookies de inicio de sesión persistente mejorado de Barry Jaspan
Si tiene preguntas, comentarios o sugerencias, intentaré actualizar el diagrama para que el novato intente implementar un inicio de sesión seguro y persistente.
fuente
La implementación de la función "Mantenerme conectado" significa que debe definir exactamente lo que eso significará para el usuario. En el caso más simple, usaría eso para significar que la sesión tiene un tiempo de espera mucho más largo: 2 días (digamos) en lugar de 2 horas. Para hacer eso, necesitará su propio almacenamiento de sesión, probablemente en una base de datos, para que pueda establecer tiempos de caducidad personalizados para los datos de la sesión. Luego, debe asegurarse de establecer una cookie que se mantenga por unos días (o más), en lugar de caducar cuando cierren el navegador.
Puedo escucharte preguntando "¿por qué 2 días? ¿Por qué no 2 semanas?". Esto se debe a que el uso de una sesión en PHP retrasará automáticamente la caducidad. Esto se debe a que el vencimiento de una sesión en PHP es en realidad un tiempo de espera inactivo.
Ahora, habiendo dicho eso, probablemente implemente un valor de tiempo de espera más difícil que almaceno en la sesión misma, y fuera a las 2 semanas más o menos, y agregue código para ver eso e invalidar forzosamente la sesión. O al menos cerrar sesión. Esto significará que se le pedirá al usuario que inicie sesión periódicamente. Yahoo! Haz esto.
fuente
Creo que podrías hacer esto:
Almacene
$cookiestring
en el DB y configúrelo como una cookie. Establezca también el nombre de usuario de la persona como una cookie. El objetivo de un hash es que no se puede realizar ingeniería inversa.Cuando aparece un usuario, obtenga el nombre de usuario de una cookie, que
$cookieString
de otra. Si$cookieString
coincide con el almacenado en la base de datos, el usuario se autentica. Como password_hash usa una sal diferente cada vez, es irrelevante en cuanto a cuál es el texto claro.fuente