"Mantenerme conectado": el mejor enfoque

257

Mi aplicación web utiliza sesiones para almacenar información sobre el usuario una vez que ha iniciado sesión y para mantener esa información a medida que viaja de una página a otra dentro de la aplicación. En esta aplicación específica, estoy almacenando el user_id, first_namey last_namede la persona.

Me gustaría ofrecer una opción "Mantenerme conectado" al iniciar sesión que colocará una cookie en la máquina del usuario durante dos semanas, que reiniciará su sesión con los mismos detalles cuando vuelvan a la aplicación.

¿Cuál es el mejor enfoque para hacer esto? No quiero almacenarlos user_iden la cookie, ya que parece que eso facilitaría que un usuario intente forjar la identidad de otro usuario.

Mateo
fuente

Respuestas:

735

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_KEYsea ​​un secreto criptográfico (generado por algo como /dev/urandomy / 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;
}
ircmaxell
fuente
77
Pero, ¿no significa este enfoque que cualquiera puede tomar este nombre de usuario y cookie e iniciar sesión como este usuario desde cualquier otro dispositivo?
Más simple
8
lol :-), ten en cuenta que 47917 años es el tiempo máximo para adivinar, el token aleatorio también se puede adivinar en 1 hora.
storm_buster
33
Es extraño porque su código contradice su respuesta. Dices "si estás poniendo datos de usuario en una cookie [...] estás haciendo algo mal", ¡pero eso es exactamente lo que está haciendo tu código! ¿No es mejor eliminar el nombre de usuario de la cookie, calcular el hash solo sobre el token (y tal vez agregar la dirección IP para evitar el robo de cookies) y luego buscar fetchUsernameByToken en lugar de fetchTokenByUserName en rememberMe ()?
Leven
9
Desde PHP 5.6, hash_equals se puede usar para evitar ataques de tiempo al hacer comparaciones de cadenas.
F21
55
@Levit evita que alguien tome un token válido y cambie el ID de usuario adjunto.
ircmaxell
93

Aviso de seguridad : basar la cookie en un hash MD5 de datos deterministas es una mala idea; es mejor usar un token aleatorio derivado de un CSPRNG. Vea la respuesta de ircmaxell a esta pregunta para un enfoque más seguro.

Usualmente hago algo como esto:

  1. El usuario inicia sesión con 'mantenerme conectado'
  2. Crear sesión
  3. Cree una cookie llamada ALGO que contenga: md5 (salt + nombre de usuario + ip + salt) y una cookie llamada algo que contenga otra identificación
  4. Almacenar cookies en la base de datos
  5. El usuario hace cosas y se va ----
  6. El usuario regresa, verifica si hay alguna otra cookie, si existe, obtenga el hash anterior de la base de datos para ese usuario, verifique que el contenido de la cookie ALGO coincida con el hash de la base de datos, que también debe coincidir con un hash recién calculado (para el ip) así: cookieHash == databaseHash == md5 (salt + username + ip + salt), si lo hacen, goto 2, si no goto 1

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)

Pim Jager
fuente
30
¿Por qué incluir la IP en el hash? Además, asegúrese de incluir información de marca de tiempo en la cookie y use esta información para establecer una edad máxima para la cookie para que no cree un token de identidad que sea bueno para la eternidad.
Scott Mitchell
44
@Abhishek Dilliwal: este es un hilo bastante antiguo, pero lo encontré buscando la misma respuesta que Mathew. No creo que usar el session_ID funcione para la respuesta de Pim porque no puede verificar el hash db, el hash de cookies y el session_ID actual porque el session_ID cambia cada session_start (); Solo pensé en señalar esto.
Partack
3
Lamento ser aburrido, pero ¿cuál es el propósito de la segunda cookie ALGO? ¿Qué es id en este caso? ¿Es solo un tipo simple de valor "verdadero / falso" para indicar si el usuario desea utilizar la función de mantenerme conectado? Si es así, ¿por qué no simplemente verifica si la cookie ALGO existe en primer lugar? Si el usuario no desea que su inicio de sesión persista, la cookie ALGO no estaría allí en primer lugar, ¿verdad? Finalmente, ¿está generando el hash nuevamente dinámicamente y lo compara con la cookie y la base de datos como una medida adicional de seguridad?
itsmequinn
44
El token debe ser ALEATORIO, no conectado con el usuario / su IP / su agente de uso / cualquier cosa de ninguna manera. Es un gran defecto de seguridad.
pamil
44
¿Por qué usas dos sales? md5 (salt + nombre de usuario + ip + salt)
Aaron Kreider
77

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:

  • Identificación
  • Seguridad

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 setcookiestiene una funcionalidad adicional, como

bool setcookie (string $ name [, string $ value [, int $ expire = 0 [, string $ path [, string $ domain [, bool $ secure = false [, bool $ httponly = false]]]]]])

  • seguro (usando la conexión HTTPS)
  • httponly (Reduce el robo de identidad a través del ataque XSS)

Definiciones

  • Token (cadena aleatoria impredecible de n longitud, por ejemplo, / dev / urandom)
  • Referencia (cadena aleatoria impredecible de n longitud, p. Ej. / Dev / urandom)
  • Firma (Genere un valor hash con clave utilizando el método HMAC)

Enfoque simple

Una solución simple sería:

  • El usuario ha iniciado sesión con Remember Me
  • Cookie de inicio de sesión emitida con token y firma
  • Cuando regresa, se marca la firma
  • Si la firma está bien ... entonces se busca el nombre de usuario y el token en la base de datos
  • si no es válido ... regrese a la página de inicio de sesión
  • Si es válido, inicie sesión automáticamente

El estudio de caso anterior resume todos los ejemplos dados en esta página, pero sus desventajas es que

  • No hay forma de saber si las cookies fueron robadas
  • El atacante puede acceder a operaciones confidenciales, como el cambio de contraseña o datos, como información personal y de horneado, etc.
  • La cookie comprometida seguiría siendo válida para la vida útil de la cookie.

Mejor solución

Una mejor solución sería

  • El usuario ha iniciado sesión y recuérdame está seleccionado
  • Genere token y firma y almacene en cookie
  • Los tokens son aleatorios y solo son válidos para autenticación única
  • El token se reemplaza en cada visita al sitio
  • Cuando un usuario no registrado visita el sitio, se verifican la firma, el token y el nombre de usuario
  • Recuerde que el inicio de sesión debe tener acceso limitado y no permitir la modificación de contraseña, información personal, etc.

Código de ejemplo

// 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());
}

Clase utilizada

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);
    }
}

Pruebas en Firefox y Chrome

ingrese la descripción de la imagen aquí

Ventaja

  • Mejor seguridad
  • Acceso limitado para el atacante
  • Cuando se roban las cookies, solo es válido para acceso único
  • La próxima vez que el usuario original acceda al sitio, puede detectar y notificar automáticamente al usuario sobre el robo.

Desventaja

  • No es compatible con la conexión persistente a través de varios navegadores (móvil y web)
  • Todavía se puede robar la cookie porque el usuario solo recibe la notificación después del siguiente inicio de sesión.

Arreglo rapido

  • Introducción del sistema de aprobación para cada sistema que debe tener una conexión persistente
  • Use múltiples cookies para la autenticación

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 "

  • Usuario conectado a example.com con recordarme
  • Almacenar nombre de usuario, token, referencia en cookie
  • Almacenar nombre de usuario, token, referencia en la base de datos, por ejemplo. Memcache
  • Enviar id de referencia a través de get e iframe a fakeaddsite.com
  • fakeaddsite.com utiliza la referencia para buscar usuario y token de la base de datos
  • fakeaddsite.com almacena la firma
  • Cuando un usuario devuelve información de firma de recuperación con iframe de fakeaddsite.com
  • Combina los datos y realiza la validación
  • ..... sabes el resto

Algunas personas podrían preguntarse cómo puedes usar 2 cookies diferentes. Bueno, es posible, imagina example.com = localhosty fakeaddsite.com = 192.168.1.120. Si inspecciona las cookies, se vería así

ingrese la descripción de la imagen aquí

De la imagen de arriba

  • El sitio actual visitado es localhost
  • También contiene cookies establecidas desde 192.168.1.120

192.168.1.120

  • Solo acepta definido HTTP_REFERER
  • Solo acepta la conexión especificada REMOTE_ADDR
  • Sin JavaScript, sin contenido, pero no consiste en nada más que firmar información y agregarla o recuperarla de la cookie

Ventaja

  • 99% por ciento del tiempo que has engañado al atacante
  • Puede bloquear fácilmente la cuenta en el primer intento del atacante
  • El ataque se puede evitar incluso antes del próximo inicio de sesión, como los otros métodos

Desventaja

  • Solicitud múltiple al servidor solo para un inicio de sesión único

Mejora

  • Hecho uso uso iframe ajax
Baba
fuente
55
Aunque @ircmaxell describió la teoría bastante bien, prefiero este enfoque, ya que funciona excelente sin la necesidad de almacenar la identificación de usuario (que sería una divulgación no deseada) y también incluye más huellas digitales que solo la identificación de usuario y el hash para identificar el usuario, como el navegador. Esto hace que sea aún más difícil para un atacante usar una cookie robada. Es el enfoque mejor y más seguro que he visto hasta ahora. +1
Marcello Mönkemeyer
6

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í).

Dan Rosenstark
fuente
6

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:

  • NUNCA almacene una contraseña en texto claro ... ¡NUNCA!
  • NUNCA almacene la contraseña cifrada de un usuario en más de una ubicación en su base de datos. El servidor de su servidor siempre es capaz de extraer la contraseña cifrada de la tabla de usuarios. No es más eficiente almacenar datos redundantes en lugar de transacciones de base de datos adicionales, lo inverso es cierto.
  • De su ID de sesión debe ser único, por lo que no hay dos usuarios podrían alguna vez compartir un documento de identidad, por lo tanto, el propósito de un ID (podría número de identificación de su licencia de conducir siempre que coincida con otros personas? No.) Esto genera una de dos piezas de combinación única, basado en 2 Cuerdas únicas. Su tabla de Sesiones debe usar la ID como PK. Para permitir que se confíe en múltiples dispositivos para el inicio de sesión automático, use otra tabla para dispositivos confiables que contenga la lista de todos los dispositivos validados (consulte mi ejemplo a continuación) y se asigna usando el nombre de usuario.
  • No sirve para hacer hash los datos conocidos en una cookie, la cookie se puede copiar. Lo que estamos buscando es un dispositivo de usuario que cumpla para proporcionar información auténtica que no se pueda obtener sin que un atacante comprometa la máquina del usuario (de nuevo, vea mi ejemplo). Sin embargo, esto significaría que un usuario legítimo que prohíbe que la información estática de su máquina (es decir, la dirección MAC, el nombre de host del dispositivo, el agente de uso si está restringido por el navegador, etc.) permanezca coherente (o la falsifique en primer lugar) no podrá usa esta función. Pero si esto le preocupa, considere el hecho de que está ofreciendo inicio de sesión automático a los usuarios que se identifican de manera única, por lo que si se niegan a ser conocidos falsificando su MAC, falsificando su agente de uso, falsificando / cambiando su nombre de host, escondiéndose detrás de servidores proxy, etc., entonces no son identificables y nunca deben autenticarse para un servicio automático. Si desea esto, debe buscar el acceso a la tarjeta inteligente incluido con el software del lado del cliente que establece la identidad del dispositivo que se está utilizando.

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é:

  • El usuario 'joe' visita el sitio por primera vez, la ID de sesión se coloca en la 'sesión' de cookies.
  • El usuario 'joe' inicia sesión, aumenta los privilegios, obtiene una nueva ID de sesión y renueva la 'sesión' de cookies.
  • El usuario 'joe' elige iniciar sesión automáticamente usando google +, obtiene una identificación única colocada en la cookie 'keepmesignedin'.
  • El usuario 'joe' tiene google para mantenerlos conectados, lo que permite que su sitio inicie sesión automáticamente al usuario que usa google en su back-end.
  • El atacante intenta sistemáticamente identificadores únicos para 'keepmesignedin' (esto es conocimiento público entregado a cada usuario), y no se inicia sesión en ningún otro lugar; intenta una identificación única dada a 'joe'.
  • El servidor recibe una identificación única para 'joe', extrae coincidencias en DB para una cuenta de google +.
  • El servidor envía al atacante a la página de inicio de sesión que ejecuta una solicitud AJAX a Google para iniciar sesión.
  • El servidor de Google recibe la solicitud, utiliza su API para ver que el atacante no está conectado actualmente.
  • Google envía una respuesta de que actualmente no hay un usuario registrado en esta conexión.
  • La página del atacante recibe respuesta, el script redirige automáticamente a la página de inicio de sesión con un valor POST codificado en la url.
  • La página de inicio de sesión obtiene el valor POST, envía la cookie para 'keepmesignedin' a un valor vacío y válido hasta la fecha de 1-1-1970 para disuadir un intento automático, haciendo que el navegador del atacante simplemente elimine la cookie.
  • El atacante recibe una página de inicio de sesión normal por primera vez.

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:

TABLE users:
UID - auto increment, PK
username - varchar(255), unique, indexed, NOT NULL
password_hash - varchar(255), NOT NULL
...

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.

TABLE sessions:
session_id - varchar(?), PK
session_token - varchar(?), NOT NULL
session_data - MediumText, NOT NULL

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.

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

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.

usuario253780
fuente
4

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.

Jani Hartikainen
fuente
¿Sería este un identificador único que se crea cuando se crea el usuario, o cambiaría cada vez que el usuario genera una nueva cookie "Keep Me Logged In"?
Mateo
1
La respuesta de Tim Jansson describe un buen enfoque para producir el hash, aunque me sentiría más seguro si no incluyera la contraseña
Jani Hartikainen
2

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:

$data = (SALT + ":" + hash(User Agent) + ":" + username 
                     + ":" + LoginTimestamp + ":"+ SALT)

Cifrar $data, establecer el tipo en HttpOnly y configurar la cookie.

Cuando el usuario regrese a su sitio, siga estos pasos:

  1. Obtenga datos de cookies. Eliminar caracteres peligrosos dentro de la cookie. Explóralo con :carácter.
  2. Verificar validez. Si la cookie es anterior a X días, redirija al usuario a la página de inicio de sesión.
  3. Si la cookie no es vieja; Obtenga el último tiempo de cambio de contraseña de la base de datos. Si se cambia la contraseña después del último inicio de sesión del usuario, redirija al usuario a la página de inicio de sesión.
  4. Si el pase no fue cambiado recientemente; Obtener el agente del navegador actual del usuario. Compruebe si (currentUserAgentHash == cookieUserAgentHash). SI los agentes son los mismos, vaya al siguiente paso; de lo contrario, redirija a la página de inicio de sesión.
  5. Si todos los pasos pasados ​​con éxito autorizar nombre de usuario.

Si el usuario cierra sesión, elimine esta cookie. Cree una nueva cookie si el usuario vuelve a iniciar sesión.

trante
fuente
2

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.

  1. Cree una tabla para almacenar los datos de "Recordarme", separados de la tabla de usuario para que pueda iniciar sesión desde varios dispositivos.

  2. 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

  3. 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.

  4. Al cerrar sesión, marque todos los registros "Recordarme" para ese usuario como caducados.

Las únicas vulnerabilidades que puedo ver es;

  • puede obtener el portátil de alguien y falsificar su dirección IP con la cookie.
  • podría suplantar una dirección IP diferente cada vez y adivinar todo, pero con dos cadenas grandes que coincidan, eso sería ... hacer un cálculo similar al anterior ... No tengo idea ... ¿probabilidades enormes?
Enigma Plus
fuente
Hola, y gracias por esta respuesta, me gusta. Sin embargo, una pregunta: ¿por qué tiene que generar 2 cadenas aleatorias: bigUserID y bigKey? ¿Por qué no genera solo 1 y lo usa?
Jeremy Belolo
2
bigKey caduca después de un período de tiempo predefinido, pero bigUserID no. bigUserID es para permitirle tener múltiples sesiones en diferentes dispositivos en la misma dirección IP. Espero que tenga sentido - Tuve que pensar por un momento :)
Enigma Plus
Una cosa que puede tener hmac puede ayudar, es que si encuentra hmac manipulado, seguramente puede saber que alguien trató de robar cookies, luego puede restablecer cada estado de inicio de sesión. Estoy en lo cierto?
Suraj Jain
2

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

ingrese la descripción de la imagen aquí

Si tiene preguntas, comentarios o sugerencias, intentaré actualizar el diagrama para que el novato intente implementar un inicio de sesión seguro y persistente.

Josh Woodcock
fuente
0

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.

staticsan
fuente
1
Establecer una sesión más larga probablemente sea malo porque desperdicia recursos del servidor y afectará negativamente el rendimiento
user3091530
0

Creo que podrías hacer esto:

$cookieString = password_hash($username, PASSWORD_DEFAULT);

Almacene $cookiestringen 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 $cookieStringde otra. Si $cookieStringcoincide 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.

Hayden O'Sullivan
fuente