"Keep Me Logged In" - l'approccio migliore


257

La mia applicazione Web utilizza le sessioni per archiviare informazioni sull'utente una volta effettuato l'accesso e per mantenere tali informazioni mentre viaggiano da una pagina all'altra all'interno dell'app. In questa specifica applicazione, sto memorizzando il user_id, first_namee last_namedella persona.

Vorrei offrire un'opzione "Mantieni l'accesso" all'accesso che inserirà un cookie sul computer dell'utente per due settimane, che riavvierà la sessione con gli stessi dettagli quando torneranno all'app.

Qual è l'approccio migliore per farlo? Non voglio memorizzarli user_idnel cookie, in quanto sembra che sarebbe facile per un utente provare a forgiare l'identità di un altro utente.

Risposte:


735

OK, lasciatemi mettere senza mezzi termini: se stai inserendo dati utente o qualcosa derivato dai dati utente in un cookie per questo scopo, stai facendo qualcosa di sbagliato.

Là. L'ho detto. Ora possiamo passare alla risposta effettiva.

Cosa c'è che non va nell'hash dei dati utente, chiedi? Bene, si riduce alla superficie di esposizione e alla sicurezza attraverso l'oscurità.

Immagina per un secondo di essere un attaccante. Nella sessione viene visualizzato un cookie crittografico impostato per il ricordo di me. È largo 32 caratteri. Gee. Potrebbe essere un MD5 ...

Immaginiamo anche per un secondo che conoscono l'algoritmo che hai usato. Per esempio:

md5(salt+username+ip+salt)

Ora, tutto ciò che un utente malintenzionato deve fare è forzare bruscamente il "sale" (che non è proprio un sale, ma ne parleremo più avanti), e ora può generare tutti i token falsi che desidera con qualsiasi nome utente per il suo indirizzo IP! Ma forzare un sale bruto è difficile, giusto? Assolutamente. Ma le GPU dei giorni nostri sono estremamente brave in questo. E a meno che tu non usi abbastanza casualità (rendilo abbastanza grande), cadrà rapidamente e con esso le chiavi del tuo castello.

In breve, l'unica cosa che ti protegge è il sale, che non ti protegge davvero tanto quanto pensi.

Ma aspetta!

Tutto ciò prevedeva che l'attaccante conosce l'algoritmo! Se è segreto e confuso, allora sei al sicuro, giusto? SBAGLIATO . Quella linea di pensiero ha un nome: Sicurezza attraverso l'oscurità , che non dovrebbe MAI essere invocata.

Il modo migliore

Il modo migliore è quello di non lasciare mai le informazioni di un utente al server, tranne l'id.

Quando l'utente accede, genera un token casuale di grandi dimensioni (da 128 a 256 bit). Aggiungilo a una tabella del database che associa il token al userid, quindi invialo al client nel cookie.

E se l'attaccante indovina il token casuale di un altro utente?

Bene, facciamo un po 'di matematica qui. Stiamo generando un token casuale a 128 bit. Ciò significa che ci sono:

possibilities = 2^128
possibilities = 3.4 * 10^38

Ora, per mostrare quanto assurdamente grande sia quel numero, immaginiamo che tutti i server su Internet (diciamo 50.000.000 oggi) provino a forzare quel numero ad una velocità di 1.000.000.000 al secondo ciascuno. In realtà i tuoi server si scioglierebbero sotto tale carico, ma giochiamo.

guesses_per_second = servers * guesses
guesses_per_second = 50,000,000 * 1,000,000,000
guesses_per_second = 50,000,000,000,000,000

Quindi 50 quadrilioni di ipotesi al secondo. È veloce! Destra?

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

Quindi 6,8 sextillion secondi ...

Proviamo a ridurlo a numeri più amichevoli.

215,626,585,489,599 years

O ancora meglio:

47917 times the age of the universe

Sì, 47917 volte l'età dell'universo ...

Fondamentalmente, non sarà rotto.

Quindi per riassumere:

L'approccio migliore che raccomando è di conservare il cookie in tre parti.

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

Quindi, per convalidare:

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: non utilizzare il token o la combinazione di utente e token per cercare un record nel database. Assicurati sempre di recuperare un record in base all'utente e di utilizzare una funzione di confronto temporizzata per confrontare in seguito il token recuperato. Maggiori informazioni sugli attacchi temporali .

Ora, è molto importante che SECRET_KEYsia un segreto crittografico (generato da qualcosa di simile /dev/urandome / o derivato da un input ad alta entropia). Inoltre, GenerateRandomToken()deve essere una fonte casuale forte ( mt_rand()non è abbastanza forte. Usa una libreria, come RandomLib o random_compat , o mcrypt_create_iv()con DEV_URANDOM) ...

Lo scopo hash_equals()è prevenire attacchi temporali . Se usi una versione di PHP sotto PHP 5.6 la funzione hash_equals()non è supportata. In questo caso è possibile sostituire hash_equals()con la funzione 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;
}

7
Ma questo approccio non significa che chiunque può prendere questo nome utente e cookie e accedere come questo utente da qualsiasi altro dispositivo?
Più semplice il

8
lol :-), nota che 47917 anni è il tempo massimo per indovinare, il token casuale potrebbe essere indovinato anche in 1 ora.
storm_buster,

33
È strano perché il tuo codice contraddice la tua risposta. Dici "se stai inserendo i dati dell'utente in un cookie [...] stai facendo qualcosa di sbagliato", ma è esattamente quello che sta facendo il tuo codice! Non è meglio rimuovere il nome utente dal cookie, calcolare l'hash solo sul token (e forse aggiungere l'indirizzo IP per prevenire il furto dei cookie) e quindi eseguire fetchUsernameByToken invece di fetchTokenByUserName in rememberMe ()?
Leven,

9
A partire da PHP 5.6, hash_equals può essere usato per prevenire attacchi di temporizzazione quando si fanno confronti di stringhe.
F21,

5
@Levit impedisce a qualcuno di prendere un token valido e di modificare l'ID utente ad esso collegato.
Ircmaxell,

93

Avviso di sicurezza : basare il cookie su un hash MD5 di dati deterministici è una cattiva idea; è meglio usare un token casuale derivato da un CSPRNG. Vedi la risposta di ircmaxell a questa domanda per un approccio più sicuro.

Di solito faccio qualcosa del genere:

  1. L'utente accede con "Mantieni l'accesso"
  2. Crea sessione
  3. Crea un cookie chiamato SOMETHING contenente: md5 (salt + username + ip + salt) e un cookie chiamato SometElse contenente id
  4. Conservare i cookie nel database
  5. L'utente fa cose e se ne va ----
  6. L'utente restituisce, controlla qualcosaElse cookie, se esiste, ottiene il vecchio hash dal database per quell'utente, controlla che il contenuto del cookie QUALCOSA corrisponda con l'hash dal database, che dovrebbe anche corrispondere con un hash appena calcolato (per il ip) quindi: cookieHash == databaseHash == md5 (salt + username + ip + salt), se lo fanno, vai a 2, se non vanno a 1

Ovviamente puoi usare nomi di cookie diversi, ecc. Puoi anche cambiare un po 'il contenuto del cookie, assicurati solo che non sia facile da creare. Ad esempio, puoi anche creare un user_salt quando l'utente viene creato e anche inserirlo nel cookie.

Inoltre potresti usare sha1 invece di md5 (o praticamente qualsiasi algoritmo)


30
Perché includere l'IP nell'hash? Inoltre, assicurati di includere le informazioni di data / ora nel cookie e utilizza queste informazioni per stabilire un'età massima per il cookie in modo da non creare un token di identità che sia buono per l'eternità.
Scott Mitchell,

4
@Abhishek Dilliwal: Questo è un thread piuttosto vecchio ma mi sono imbattuto in esso cercando la stessa risposta di Mathew. Non penso che usare session_ID funzionerebbe per la risposta di Pim perché non puoi controllare l'hash del db, l'hash dei cookie e l'attuale session_ID perché session_ID cambia ogni session_start (); ho pensato di segnalarlo.
Partack,

3
Mi dispiace essere noioso, ma qual è lo scopo del secondo cookie qualcosa? Cos'è l'id in questo caso? È solo una semplice specie di valore "vero / falso" per indicare se l'utente desidera utilizzare la funzione di mantenermi connesso? Se è così, perché non controllare semplicemente se il cookie QUALCOSA esiste in primo luogo? Se l'utente non volesse che il suo accesso persistesse, il cookie SOMETHING non sarebbe in primo luogo lì, giusto? Infine, stai generando di nuovo l'hash in modo dinamico e confrontandolo con il cookie e il DB come ulteriore misura di sicurezza?
itsmequinn,

4
Il token dovrebbe essere RANDOM, non collegato in alcun modo all'utente / al suo IP / al suo useragent / nulla. È un grave difetto di sicurezza.
pamil,

4
Perché usi due sali? md5 (salt + username + ip + salt)
Aaron Kreider

77

introduzione

Il tuo titolo "Keep Me Logged In" - l'approccio migliore mi rende difficile sapere da dove iniziare perché se stai cercando l'approccio migliore, dovresti considerare quanto segue:

  • Identificazione
  • Sicurezza

Biscotti

I cookie sono vulnerabili, Tra le comuni vulnerabilità di furto di cookie del browser e attacchi di scripting tra siti dobbiamo accettare che i cookie non sono sicuri. Per migliorare la sicurezza, è necessario tenere presente che php setcookiesha funzionalità aggiuntive come

bool setcookie (stringa $ name [, stringa $ value [, int $ expire = 0 [, stringa $ percorso [, stringa $ dominio [, bool $ secure = false [, bool $ httponly = false]]]]])

  • sicuro (utilizzando la connessione HTTPS)
  • httponly (Riduci il furto di identità attraverso l'attacco XSS)

definizioni

  • Token (stringa casuale imprevedibile di n lunghezza es. / Dev / urandom)
  • Riferimento (stringa casuale imprevedibile di n lunghezza es. / Dev / urandom)
  • Firma (genera un valore hash con chiave utilizzando il metodo HMAC)

Approccio semplice

Una soluzione semplice sarebbe:

  • L'utente è connesso con Remember Me
  • Cookie di accesso emesso con token e firma
  • Al ritorno, la firma è selezionata
  • Se la firma è ok .. quindi nel database vengono cercati nome utente e token
  • se non valido .. torna alla pagina di accesso
  • Se valido accedi automaticamente

Il case study sopra riassume tutti gli esempi forniti in questa pagina, ma gli svantaggi sono che

  • Non c'è modo di sapere se i cookie sono stati rubati
  • L'attaccante può essere operazioni sensibili all'accesso come la modifica di password o dati come informazioni personali e di cottura ecc.
  • Il cookie compromesso sarebbe comunque valido per la durata del cookie

Soluzione migliore

Una soluzione migliore sarebbe

  • L'utente ha effettuato l'accesso e ricorda che è selezionato
  • Genera token e firma e memorizza nei cookie
  • I token sono casuali e sono validi solo per l'autenticazione singola
  • I token vengono sostituiti ad ogni visita al sito
  • Quando un utente non registrato visita il sito, vengono verificati la firma, il token e il nome utente
  • Ricordati di me l'accesso dovrebbe avere un accesso limitato e non consentire la modifica di password, informazioni personali ecc.

Codice di esempio

// 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 utilizzata

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 in Firefox e Chrome

inserisci qui la descrizione dell'immagine

Vantaggio

  • Migliore sicurezza
  • Accesso limitato per attaccante
  • Quando il cookie viene rubato, è valido solo per l'accesso singolo
  • La prossima volta che l'utente originale accede al sito è possibile rilevare automaticamente e avvisare l'utente del furto

Svantaggio

  • Non supporta la connessione persistente tramite più browser (Mobile e Web)
  • Il cookie può ancora essere rubato perché l'utente riceve la notifica solo dopo il prossimo accesso.

Soluzione rapida

  • Introduzione del sistema di approvazione per ciascun sistema che deve avere una connessione persistente
  • Utilizza più cookie per l'autenticazione

Approccio con più cookie

Quando un utente malintenzionato sta per rubare i cookie, concentralo solo su un determinato sito Web o dominio, ad es. example.com

Ma davvero puoi autenticare un utente da 2 domini diversi ( esempio.com e fakeaddsite.com ) e farlo sembrare "Cookie pubblicitario"

  • Utente connesso a example.com con ricordami
  • Memorizza nome utente, token, riferimento nei cookie
  • Memorizza nome utente, token, riferimento nel database ad es. memcache
  • Invia ID di riferimento tramite get e iframe a fakeaddsite.com
  • fakeaddsite.com utilizza il riferimento per recuperare utente e token dal database
  • fakeaddsite.com memorizza la firma
  • Quando un utente restituisce recupera le informazioni sulla firma con iframe da fakeaddsite.com
  • Combina i dati ed esegui la convalida
  • ..... sai il resto

Alcune persone potrebbero chiedersi come è possibile utilizzare 2 cookie diversi? Bene, è possibile, immaginare example.com = localhoste fakeaddsite.com = 192.168.1.120. Se controlli i cookie sembrerebbe così

inserisci qui la descrizione dell'immagine

Dall'immagine sopra

  • Il sito attualmente visitato è localhost
  • Contiene inoltre i cookie impostati dal 192.168.1.120

192.168.1.120

  • Accetta solo definito HTTP_REFERER
  • Accetta solo la connessione da quella specificata REMOTE_ADDR
  • Nessun JavaScript, nessun contenuto ma non consiste altro che firmare informazioni e aggiungerle o recuperarle dai cookie

Vantaggio

  • Il 99% delle volte in cui hai ingannato l'attaccante
  • Puoi facilmente bloccare l'account nel primo tentativo dell'attaccante
  • L'attacco può essere prevenuto anche prima dell'accesso successivo come gli altri metodi

Svantaggio

  • Richiesta multipla al server solo per un singolo accesso

Miglioramento

  • Fatto use iframe use ajax

5
Anche se @ircmaxell ha descritto abbastanza bene la teoria, preferisco questo approccio, poiché funziona in modo eccellente senza la necessità di memorizzare l'ID utente (che sarebbe una divulgazione indesiderata) e include anche più impronte digitali rispetto al solo ID utente e hash per identificare il utente, ad esempio il browser. Ciò rende ancora più difficile per un utente malintenzionato utilizzare un cookie rubato. È l'approccio migliore e più sicuro che abbia mai visto finora. +1
Marcello Mönkemeyer,

6

Ho posto un angolo di questa domanda qui , e le risposte ti porteranno a tutti i collegamenti ai cookie di timeout basati su token di cui hai bisogno.

Fondamentalmente, non memorizzi userId nel cookie. Memorizzi un token una tantum (stringa enorme) che l'utente utilizza per ritirare la sessione di accesso precedente. Quindi, per renderlo davvero sicuro, chiedi una password per operazioni pesanti (come cambiare la password stessa).


6

Vecchio thread, ma ancora una preoccupazione valida. Ho notato alcune buone risposte sulla sicurezza, evitando l'uso della "sicurezza attraverso l'oscurità", ma i metodi tecnici forniti non erano sufficienti ai miei occhi. Cose che devo dire prima di contribuire con il mio metodo:

  • Non memorizzare MAI una password in chiaro ... MAI!
  • MAI archiviare la password con hash di un utente in più di una posizione nel database. Il back-end del server è sempre in grado di estrarre la password con hash dalla tabella degli utenti. Non è più efficiente archiviare dati ridondanti al posto di transazioni DB aggiuntive, è vero il contrario.
  • L'ID della sessione deve essere univoco, quindi nessun utente può mai condividere un ID, quindi lo scopo di un ID (il numero ID della patente di guida potrebbe mai corrispondere ad altre persone? No.) Questo genera una combinazione univoca in due parti, basata su 2 stringhe uniche. La tabella Sessioni dovrebbe utilizzare l'ID come PK. Per consentire l'attendibilità di più dispositivi per l'accesso automatico, utilizzare un'altra tabella per i dispositivi attendibili che contiene l'elenco di tutti i dispositivi convalidati (vedere il mio esempio di seguito) e viene mappato utilizzando il nome utente.
  • Non serve allo scopo di trasferire i dati noti in un cookie, il cookie può essere copiato. Quello che stiamo cercando è un dispositivo utente conforme per fornire informazioni autentiche che non possono essere ottenute senza che un utente malintenzionato comprometta la macchina dell'utente (di nuovo, vedi il mio esempio). Ciò significherebbe, tuttavia, che un utente legittimo che proibisce alle informazioni statiche della sua macchina (ad es. Indirizzo MAC, nome host del dispositivo, useragent se limitato dal browser, ecc.) Di rimanere coerente (o falsificarlo in primo luogo) non sarà in grado di usa questa funzione. Ma se questo è un problema, considera il fatto che stai offrendo l'accesso automatico agli utenti che si identificano in modo univoco, quindi se si rifiutano di essere conosciuti falsificando il proprio MAC, spoofing il proprio useragent, spoofing / cambiando il loro nome host, nascondendosi dietro proxy, ecc., allora non sono identificabili e non dovrebbero mai essere autenticati per un servizio automatico. Se lo si desidera, è necessario esaminare l'accesso alle smart card in bundle con il software lato client che stabilisce l'identità per il dispositivo utilizzato.

Detto questo, ci sono due grandi modi per avere l'auto-accesso sul tuo sistema.

Innanzitutto, il modo economico e semplice che mette tutto su qualcun altro. Se fai in modo che il tuo sito supporti l'accesso con, diciamo, il tuo account google +, probabilmente hai un pulsante google + semplificato che accederà l'utente se sono già collegati a google (l'ho fatto qui per rispondere a questa domanda, come sempre eseguito l'accesso a google). Se si desidera che l'utente acceda automaticamente se ha già effettuato l'accesso con un autenticatore attendibile e supportato e ha selezionato la casella per farlo, fare in modo che gli script sul lato client eseguano il codice dietro il corrispondente pulsante "accedi con" prima di caricare , assicurati solo che il server memorizzi un ID univoco in una tabella di accesso automatico con nome utente, ID sessione e autenticatore utilizzati per l'utente. Poiché questi metodi di accesso utilizzano AJAX, stai comunque aspettando una risposta, e tale risposta è una risposta convalidata o un rifiuto. Se si ottiene una risposta convalidata, utilizzarla normalmente, quindi continuare a caricare l'utente connesso normalmente. Altrimenti, l'accesso non è riuscito, ma non dirlo all'utente, continua semplicemente come non connesso, noteranno. Questo per impedire a un utente malintenzionato che ha rubato i cookie (o li ha falsificati nel tentativo di intensificare i privilegi) di apprendere che l'utente accede automaticamente al sito.

Questo è economico e potrebbe anche essere considerato sporco da alcuni perché cerca di convalidare il tuo potenzialmente già autografato con posti come Google e Facebook, senza nemmeno dirtelo. Tuttavia, non dovrebbe essere utilizzato su utenti che non hanno chiesto di accedere automaticamente al tuo sito e questo particolare metodo è solo per l'autenticazione esterna, come con Google+ o FB.

Poiché è stato utilizzato un autenticatore esterno per indicare al server dietro le quinte se un utente è stato convalidato o meno, un utente malintenzionato non può ottenere altro che un ID univoco, che è inutile da solo. Elaborerò:

  • L'utente "joe" visita il sito per la prima volta, ID sessione inserito nella "sessione" dei cookie.
  • L'utente "joe" effettua l'accesso, intensifica i privilegi, ottiene un nuovo ID sessione e rinnova la "sessione" dei cookie.
  • L'utente "joe" sceglie di accedere automaticamente tramite google + e ottiene un ID univoco inserito nel cookie "keepmesignedin".
  • L'utente "joe" consente a Google di mantenerli connessi, consentendo al tuo sito di accedere automaticamente all'utente utilizzando Google nel back-end.
  • L'attaccante tenta sistematicamente ID univoci per "keepmesignedin" (si tratta di conoscenza pubblica fornita a tutti gli utenti) e non ha accesso in nessun altro luogo; prova un ID univoco assegnato a "joe".
  • Il server riceve l'ID univoco per "joe", estrae la corrispondenza nel DB per un account google +.
  • Il server invia Attacker alla pagina di accesso che esegue una richiesta AJAX a Google per accedere.
  • Il server di Google riceve la richiesta, utilizza la sua API per vedere che Attacker non è attualmente connesso.
  • Google invia la risposta che al momento non esiste alcun utente connesso su questa connessione.
  • La pagina dell'attaccante riceve risposta, lo script reindirizza automaticamente alla pagina di accesso con un valore POST codificato nell'URL.
  • La pagina di accesso ottiene il valore POST, invia il cookie per "keepmesignedin" a un valore vuoto e valido fino alla data 1-1-1970 per scoraggiare un tentativo automatico, facendo sì che il browser dell'attaccante elimini semplicemente il cookie.
  • All'attaccante viene fornita la normale pagina di accesso per la prima volta.

Indipendentemente da ciò, anche se un utente malintenzionato utilizza un ID inesistente, il tentativo dovrebbe fallire su tutti i tentativi tranne quando si riceve una risposta convalidata.

Questo metodo può e deve essere utilizzato insieme all'autenticatore interno per coloro che accedono al tuo sito utilizzando un autenticatore esterno.

=========

Ora, per il tuo sistema di autenticazione personale in grado di accedere automaticamente agli utenti, ecco come lo faccio:

DB ha alcune tabelle:

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

Si noti che il nome utente può contenere 255 caratteri. Ho un programma server che limita i nomi utente nel mio sistema a 32 caratteri, ma gli autenticatori esterni potrebbero avere nomi utente con il loro @ domain.tld più grande di così, quindi supporto solo la lunghezza massima di un indirizzo e-mail per la massima compatibilità.

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

Si noti che non esiste un campo utente in questa tabella, poiché il nome utente, quando si accede, si trova nei dati della sessione e il programma non consente dati nulli. Il session_id e il session_token possono essere generati usando hash md5 casuali, hash sha1 / 128/256, timbri datetime con stringhe casuali aggiunte a loro poi hash, o qualunque cosa tu voglia, ma l'entropia del tuo output dovrebbe rimanere alta quanto tollerabile a mitigare gli attacchi di forza bruta anche dal decollo, e tutti gli hash generati dalla classe della sessione dovrebbero essere controllati per le partite nella tabella delle sessioni prima di tentare di aggiungerli.

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

Gli indirizzi MAC per loro natura dovrebbero essere UNICI, quindi ha senso che ogni voce abbia un valore unico. I nomi host, d'altra parte, potrebbero essere duplicati legittimamente su reti separate. Quante persone usano "Home-PC" come uno dei nomi dei loro computer? Il nome utente viene preso dai dati della sessione dal back-end del server, quindi è impossibile modificarlo. Per quanto riguarda il token, lo stesso metodo per generare token di sessione per le pagine dovrebbe essere utilizzato per generare token nei cookie per l'accesso automatico dell'utente. Infine, il codice datetime viene aggiunto per quando l'utente dovrebbe riconvalidare le proprie credenziali. Aggiorna questo datetime al momento dell'accesso dell'utente conservandolo entro pochi giorni o forzalo a scadere indipendentemente dall'ultimo accesso conservandolo solo per circa un mese, a seconda di ciò che il tuo progetto impone.

Questo impedisce a qualcuno di falsificare sistematicamente il MAC e il nome host per un utente che conoscono gli accessi automatici. MAIchiedi all'utente di conservare un cookie con password, testo in chiaro o altro. Fai rigenerare il token su ogni navigazione della pagina, proprio come faresti con il token di sessione. Ciò riduce enormemente la probabilità che un utente malintenzionato possa ottenere un cookie token valido e utilizzarlo per accedere. Alcune persone proveranno a dire che un utente malintenzionato potrebbe rubare i cookie dalla vittima e fare un attacco di replay della sessione per accedere. Se un utente malintenzionato potesse rubare i cookie (il che è possibile), avrebbero sicuramente compromesso l'intero dispositivo, il che significa che potrebbero utilizzare il dispositivo per accedere comunque, il che vanifica lo scopo di rubare del tutto i cookie. Finché il tuo sito funziona su HTTPS (cosa che dovrebbe fare quando si tratta di password, numeri CC o altri sistemi di accesso), hai garantito all'utente tutta la protezione che puoi all'interno di un browser.

Una cosa da tenere a mente: i dati della sessione non devono scadere se si utilizza l'accesso automatico. È possibile scadere la possibilità di continuare la sessione in modo errato, ma la convalida nel sistema dovrebbe riprendere i dati della sessione se si prevede che i dati persistenti continueranno tra le sessioni. Se desideri dati di sessione sia persistenti che non persistenti, usa un'altra tabella per i dati di sessione persistenti con il nome utente come PK e chiedi al server di recuperarli come se fossero i normali dati di sessione, basta usare un'altra variabile.

Una volta effettuato l'accesso in questo modo, il server dovrebbe comunque convalidare la sessione. È qui che puoi programmare le aspettative per i sistemi rubati o compromessi; modelli e altri risultati attesi di accessi ai dati di sessione possono spesso portare a conclusioni che un sistema è stato dirottato o che i cookie sono stati forgiati al fine di ottenere l'accesso. È qui che la tua ISS Tech può stabilire regole che potrebbero innescare il blocco dell'account o la rimozione automatica di un utente dal sistema di accesso automatico, tenendo gli aggressori fuori abbastanza a lungo da consentire all'utente di determinare in che modo l'attaccante ha avuto successo e come eliminarli.

Come nota di chiusura, assicurarsi che qualsiasi tentativo di recupero, modifica della password o errori di accesso oltre la soglia comportino la disabilitazione dell'accesso automatico fino a quando l'utente non convalida correttamente e riconosce che si è verificato.

Mi scuso se qualcuno si aspettava che il codice venisse distribuito nella mia risposta, non succederà qui. Dirò che uso PHP, jQuery e AJAX per gestire i miei siti e non utilizzo MAI Windows come server ... mai.



4

Genera un hash, magari con un segreto che solo tu conosci, quindi memorizzalo nel tuo DB in modo che possa essere associato all'utente. Dovrebbe funzionare abbastanza bene.


Questo sarebbe un identificatore univoco che viene creato quando l'utente viene creato o cambierebbe ogni volta che l'utente genera un nuovo cookie "Resta connesso"?
Matteo,

1
La risposta di Tim Jansson descrive un buon approccio alla produzione dell'hash, anche se mi sentirei più sicuro se non includesse la password
Jani Hartikainen,

2

La mia soluzione è così. Non è al 100% a prova di proiettile ma penso che ti salverà per la maggior parte dei casi.

Quando l'utente ha effettuato l'accesso, crea una stringa con queste informazioni:

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

Crittografa $data, imposta il tipo su HttpOnly e imposta il cookie.

Quando l'utente torna al tuo sito, procedi come segue:

  1. Ottieni dati sui cookie. Rimuovi caratteri pericolosi all'interno dei cookie. Esploderlo con il :carattere.
  2. Verifica validità Se i cookie sono più vecchi di X giorni, reindirizza l'utente alla pagina di accesso.
  3. Se il cookie non è vecchio; Ricevi l'ora dell'ultima modifica della password dal database. Se la password viene modificata dopo l'ultimo accesso dell'utente, reindirizzare l'utente alla pagina di accesso.
  4. Se il pass non è stato modificato di recente; Ottieni l'agente del browser corrente dell'utente. Controlla se (currentUserAgentHash == cookieUserAgentHash). Se gli agenti sono gli stessi vai al passaggio successivo, altrimenti reindirizza alla pagina di accesso.
  5. Se tutti i passaggi sono stati eseguiti correttamente, autorizzare il nome utente.

Se l'utente firma, rimuovi questo cookie. Crea un nuovo cookie se l'utente accede nuovamente.


2

Non capisco il concetto di archiviare oggetti crittografati in un cookie quando è la versione crittografata di cui hai bisogno per fare il tuo hacking. Se mi manca qualcosa, ti preghiamo di commentare.

Sto pensando di adottare questo approccio per "Ricordami". In caso di problemi, commentare.

  1. Crea una tabella per archiviare i dati "Ricordami" in - separato dalla tabella utente in modo che io possa accedere da più dispositivi.

  2. Ad accesso effettuato (con Remember Me spuntato):

    a) Generare una stringa casuale univoca da utilizzare come UserID su questa macchina: bigUserID

    b) Genera una stringa casuale unica: bigKey

    c) Conservare un cookie: bigUserID: bigKey

    d) Nella tabella "Ricordami", aggiungi un record con: UserID, indirizzo IP, bigUserID, bigKey

  3. Se stai tentando di accedere a qualcosa che richiede l'accesso:

    a) Cerca il cookie e cerca bigUserID e bigKey con un indirizzo IP corrispondente

    b) Se lo trovi, accedi alla persona ma imposta un flag nella tabella utente "soft login" in modo che per qualsiasi operazione pericolosa, puoi richiedere un accesso completo.

  4. Al logout, contrassegnare tutti i record "Ricordami" per quell'utente come scaduti.

Le uniche vulnerabilità che posso vedere è;

  • potresti prendere il laptop di qualcuno e falsificare il suo indirizzo IP con il cookie.
  • potresti falsificare ogni volta un indirizzo IP diverso e indovinare il tutto - ma con due grosse stringhe da abbinare, sarebbe ... fare un calcolo simile a quello sopra ... Non ho idea ... enormi probabilità?

Ciao, e grazie per questa risposta, mi piace. Una domanda però: perché devi generare 2 stringhe casuali: bigUserID e bigKey? Perché non ne generi solo 1 e lo usi?
Jeremy Belolo,

2
bigKey scade dopo un periodo di tempo predefinito, ma bigUserID no. bigUserID ti consente di avere più sessioni su dispositivi diversi con lo stesso indirizzo IP. Spero che abbia un senso - ho dovuto pensare un momento :)
Enigma Plus il

Una cosa che può aiutare hmac è che se hai trovato hmac manomesso, puoi sicuramente sapere che qualcuno ha cercato di rubare i cookie, quindi puoi ripristinare tutti gli stati di accesso. Ho ragione?
Suraj Jain,

2

Ho letto tutte le risposte e ho ancora trovato difficile estrarre ciò che avrei dovuto fare. Se un'immagine vale 1k parole, spero che questo aiuti gli altri a implementare un archivio sicuro e persistente basato sulla migliore pratica del cookie di accesso persistente migliorato di Barry Jaspan

inserisci qui la descrizione dell'immagine

In caso di domande, feedback o suggerimenti, proverò ad aggiornare il diagramma per riflettere per il principiante che cerca di implementare un accesso permanente sicuro.


0

L'implementazione di una funzione "Resta connesso" significa che devi definire esattamente cosa significherà per l'utente. Nel caso più semplice, userei questo per indicare che la sessione ha un timeout molto più lungo: 2 giorni (diciamo) invece di 2 ore. Per fare ciò, avrai bisogno della tua memoria di sessione, probabilmente in un database, in modo da poter impostare tempi di scadenza personalizzati per i dati della sessione. Quindi devi assicurarti di impostare un cookie che rimarrà per alcuni giorni (o più), anziché scadere quando chiudono il browser.

Posso sentirti chiedere "perché 2 giorni? Perché non 2 settimane?". Questo perché l'utilizzo di una sessione in PHP riporterà automaticamente la scadenza. Questo perché la scadenza di una sessione in PHP è in realtà un timeout di inattività.

Ora, detto questo, probabilmente implementerei un valore di timeout più difficile che memorizzerò nella sessione stessa, e fuori a circa 2 settimane circa, e aggiungerei il codice per vederlo e invalidare forzatamente la sessione. O almeno per disconnetterli. Ciò significa che all'utente verrà chiesto di accedere periodicamente. Yahoo! fa questo.


1
L'impostazione di una sessione più lunga è probabilmente errata perché spreca risorse del server e influirà negativamente sulle prestazioni
user3091530

0

Penso che potresti semplicemente fare questo:

$cookieString = password_hash($username, PASSWORD_DEFAULT);

Archiviare $cookiestringnel DB e impostarlo come cookie. Imposta anche il nome utente della persona come cookie. Il punto cruciale di un hash è che non può essere retroingegnerizzato.

Quando un utente si presenta, ottieni il nome utente da un cookie, piuttosto che $cookieStringda un altro. Se $cookieStringcorrisponde a quello memorizzato nel DB, l'utente viene autenticato. Dato che password_hash usa ogni volta un sale diverso, è irrilevante quanto sia chiaro il testo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.