„Keep Me Logged In” - najlepsze podejście

257

Moja aplikacja internetowa używa sesji do przechowywania informacji o użytkowniku po zalogowaniu i do zachowania tych informacji podczas podróży ze strony na stronę w aplikacji. W tym konkretnym zastosowaniu, jestem składowano user_id, first_namea last_nameosoby.

Chciałbym zaoferować opcję „Keep Me Logged In” przy logowaniu, która umieści plik cookie na komputerze użytkownika na dwa tygodnie, który ponownie uruchomi sesję z tymi samymi szczegółami po powrocie do aplikacji.

Jakie jest najlepsze podejście do tego? Nie chcę przechowywać ich user_idw pliku cookie, ponieważ wydaje się, że to ułatwiłoby jednemu użytkownikowi próbę sfałszowania tożsamości innego użytkownika.

Mateusz
źródło

Odpowiedzi:

735

OK, pozwolę sobie to powiedzieć wprost: jeśli umieszczasz dane użytkownika lub cokolwiek pochodzącego z danych użytkownika w pliku cookie w tym celu, robisz coś źle.

Tam. Powiedziałem to. Teraz możemy przejść do właściwej odpowiedzi.

Pytasz, co jest złego w haszowaniu danych użytkownika? Wszystko sprowadza się do powierzchni ekspozycji i bezpieczeństwa poprzez niejasność.

Wyobraź sobie przez chwilę, że jesteś atakującym. Widzisz zestaw kryptograficznych plików cookie dla zapamiętywania w sesji. Ma 32 znaki. Ojej. To może być MD5 ...

Wyobraźmy sobie również przez chwilę, że znają używany algorytm. Na przykład:

md5(salt+username+ip+salt)

Teraz wszystko, co musi zrobić atakujący, to brutalna siła „soli” (która nie jest tak naprawdę solą, ale więcej o tym później), a teraz może wygenerować wszystkie fałszywe tokeny, jakie chce, z dowolną nazwą użytkownika dla swojego adresu IP! Ale brutalne zmuszanie soli jest trudne, prawda? Absolutnie. Ale współczesne procesory graficzne są w tym wyjątkowo dobre. I jeśli nie użyjesz w nim wystarczającej losowości (uczyń ją wystarczająco dużą), szybko spadnie, a wraz z nią klucze do twojego zamku.

Krótko mówiąc, jedyną rzeczą, która cię chroni, jest sól, która tak naprawdę nie chroni cię tak bardzo, jak myślisz.

Ale poczekaj!

Wszystko to przewidywało, że atakujący zna algorytm! Jeśli jest to tajne i zagmatwane, to jesteś bezpieczny, prawda? ŹLE . Ten sposób myślenia ma nazwę: Bezpieczeństwo poprzez zaciemnienie , które NIGDY nie powinno można polegać.

The Better Way

Lepszym sposobem jest nigdy nie pozwolić, aby informacje użytkownika opuściły serwer, z wyjątkiem identyfikatora.

Gdy użytkownik się zaloguje, wygeneruj duży losowy token (128 do 256 bitów). Dodaj to do tabeli bazy danych, która mapuje token na identyfikator użytkownika, a następnie wyślij go do klienta w pliku cookie.

Co jeśli atakujący zgadnie losowy token innego użytkownika?

Zróbmy tutaj matematykę. Generujemy 128-bitowy losowy token. Oznacza to, że istnieją:

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

Teraz, aby pokazać, jak absurdalnie duża jest ta liczba, wyobraźmy sobie każdy serwer w Internecie (powiedzmy dzisiaj 50 000 000), który próbuje brutalnie wymusić tę liczbę z szybkością 1 000 000 000 na sekundę. W rzeczywistości twoje serwery stopiłyby się pod takim obciążeniem, ale zagrajmy to.

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

A więc 50 biliardów domysłów na sekundę. To szybko! Dobrze?

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

A więc 6,8 sekstylionów sekund ...

Spróbujmy sprowadzić to do bardziej przyjaznych liczb.

215,626,585,489,599 years

Lub nawet lepiej:

47917 times the age of the universe

Tak, to 47917 razy więcej niż wiek wszechświata ...

Zasadniczo nie będzie pęknięty.

Więc by podsumować:

Lepszym podejściem, które zalecam, jest przechowywanie pliku cookie z trzema częściami.

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

Następnie, aby sprawdzić poprawność:

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

Uwaga: Nie używaj tokena lub kombinacji użytkownika i tokena do wyszukiwania rekordu w bazie danych. Zawsze pamiętaj o pobraniu rekordu opartego na użytkowniku i skorzystaj z funkcji porównywania bezpiecznej pod względem czasu, aby później porównać pobrany token. Więcej informacji o atakach czasowych .

Teraz bardzo ważne jest, aby SECRET_KEYbył to kryptograficzny sekret (generowany przez coś podobnego /dev/urandomi / lub pochodzący z wejścia o wysokiej entropii). Ponadto GenerateRandomToken()musi być silnym losowym źródłem ( mt_rand()nie jest wystarczająco silne. Użyj biblioteki, takiej jak RandomLib lub random_compat , lub mcrypt_create_iv()z DEV_URANDOM) ...

Ma hash_equals()to na celu zapobieganie atakom czasowym . Jeśli używasz wersji PHP poniżej PHP 5.6, funkcja hash_equals()nie jest obsługiwana. W takim przypadku możesz zastąpić hash_equals()funkcją 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
źródło
7
Ale czy to podejście nie oznacza, że ​​każdy może pobrać tę nazwę użytkownika i plik cookie i zalogować się jako ten użytkownik z dowolnego innego urządzenia?
Prostszy
8
lol :-), zauważ, że 47917 lat to maksymalny czas na odgadnięcie, losowy token można również zgadnąć w ciągu 1 godziny.
storm_buster
33
To dziwne, ponieważ Twój kod jest sprzeczny z odpowiedzią. Mówisz „jeśli umieszczasz dane użytkownika w pliku cookie [...] robisz coś złego”, ale dokładnie to robi Twój kod! Czy nie lepiej jest usunąć nazwę użytkownika z pliku cookie, obliczyć skrót tylko dla tokena (i być może dodać adres IP, aby zapobiec kradzieży plików cookie), a następnie zrobić fetchUsernameByToken zamiast fetchTokenByUserName w RememberMe ()?
Leven
9
Od wersji PHP 5.6 hash_equals może służyć do zapobiegania atakom czasowym podczas porównywania ciągów.
F21,
5
@ Lewit uniemożliwia komuś pobranie ważnego tokena i zmianę dołączonego do niego identyfikatora użytkownika.
ircmaxell,
93

Uwaga bezpieczeństwa : Opieranie pliku cookie na haszu MD5 danych deterministycznych jest złym pomysłem; lepiej jest użyć losowego tokena pochodzącego z CSPRNG. Zobacz odpowiedź ircmaxell na to pytanie, aby uzyskać bardziej bezpieczne podejście.

Zwykle robię coś takiego:

  1. Użytkownik loguje się za pomocą opcji „nie wylogowuj mnie”
  2. Utwórz sesję
  3. Utwórz plik cookie o nazwie COŚ zawierający: md5 (sól + nazwa użytkownika + ip + sól) i plik cookie o nazwie cośElse zawierający identyfikator
  4. Przechowuj pliki cookie w bazie danych
  5. Użytkownik robi rzeczy i pozostawia ----
  6. Użytkownik powraca, sprawdza, czy istnieje plik cookie Else, jeśli istnieje, pobierz stary hash z bazy danych dla tego użytkownika, sprawdź zawartość pliku cookie COŚ pasuje do hash z bazy danych, który powinien również pasować do nowo obliczonego skrótu (dla ip) zatem: cookieHash == databaseHash == md5 (sól + nazwa użytkownika + ip + sól), jeśli tak, goto 2, jeśli nie dostał 1

Oczywiście możesz używać różnych nazw plików cookie itp. Możesz także nieco zmienić zawartość pliku cookie, upewnij się tylko, że nie można go łatwo utworzyć. Możesz na przykład również utworzyć user_salt, gdy użytkownik jest tworzony, a także umieścić go w pliku cookie.

Możesz także użyć sha1 zamiast md5 (lub praktycznie dowolnego algorytmu)

Pim Jager
źródło
30
Dlaczego warto uwzględnić adres IP w haszu? Pamiętaj także o umieszczeniu w pliku cookie informacji o znaczniku czasu i skorzystaj z tych informacji, aby ustalić maksymalny wiek pliku cookie, aby nie tworzyć tokena tożsamości, który będzie dobry na wieczność.
Scott Mitchell,
4
@Abhishek Dilliwal: To dość stary wątek, ale natknąłem się na niego, szukając tej samej odpowiedzi co Mathew. Nie sądzę, aby użycie identyfikatora session_ID działało dla odpowiedzi Pima, ponieważ nie można sprawdzić skrótu db, skrótu pliku cookie i bieżącego session_ID, ponieważ identyfikator session_ID zmienia każdą sesję_start (); pomyślałem, że zwrócę na to uwagę.
Partack
3
Przepraszam, że jestem nudny, ale jaki jest cel drugiego ciasteczka, jakim jest ELSE? Jaki jest identyfikator w tym przypadku? Czy jest to po prostu rodzaj wartości „prawda / fałsz” wskazujący, czy użytkownik chce w ogóle korzystać z funkcji „zachowaj mnie”? Jeśli tak, to dlaczego nie po prostu sprawdzić, czy plik cookie COŚ istnieje? Jeśli użytkownik nie chciałby, aby jego login trwał, CZYSTO cookie nie byłoby na pierwszym miejscu, prawda? Wreszcie, czy generujesz skrót ponownie dynamicznie i porównujesz go z plikiem cookie i bazą danych jako dodatkową miarą bezpieczeństwa?
itsmequinn
4
Token powinien być LOSOWY, niezwiązany w żaden sposób z użytkownikiem / jego adresem IP / jego użytkownikiem użytkownika / czymkolwiek. To poważna wada bezpieczeństwa.
pamil
4
Dlaczego używasz dwóch soli? md5 (sól + nazwa użytkownika + ip + sól)
Aaron Kreider
77

Wprowadzenie

Twój tytuł „Keep Me Logged In” - najlepsze podejście utrudnia mi określenie, od czego zacząć, ponieważ jeśli szukasz najlepszego podejścia, musisz wziąć pod uwagę następujące kwestie:

  • Identyfikacja
  • Bezpieczeństwo

Ciasteczka

Pliki cookie są wrażliwe. Pomiędzy typowymi lukami w zabezpieczeniach przeglądarki przed kradzieżą plików cookie a atakami typu cross-site scripting musimy zaakceptować, że pliki cookie nie są bezpieczne. Aby poprawić bezpieczeństwo, musisz pamiętać, że php setcookiesma dodatkowe funkcje, takie jak

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

  • bezpieczne (przy użyciu połączenia HTTPS)
  • httponly (Ogranicz kradzież tożsamości poprzez atak XSS)

Definicje

  • Token (nieprzewidywalny ciąg losowy o długości n, np. / Dev / urandom)
  • Odniesienie (nieprzewidywalny ciąg losowy o długości n, np. / Dev / urandom)
  • Podpis (Wygeneruj kluczową wartość skrótu przy użyciu metody HMAC)

Proste podejście

Prostym rozwiązaniem byłoby:

  • Użytkownik jest zalogowany za pomocą Remember Me
  • Logowanie Plik cookie wydany z tokenem i podpisem
  • Kiedy powraca, podpis jest sprawdzany
  • Jeśli podpis jest w porządku, nazwa użytkownika i token są wyszukiwane w bazie danych
  • jeśli nie jest poprawny .. wróć do strony logowania
  • Jeśli ważny, zaloguj się automatycznie

Powyższe studium przypadku podsumowuje wszystkie przykłady podane na tej stronie, ale są to wady

  • Nie ma możliwości sprawdzenia, czy pliki cookie zostały skradzione
  • Atakujący może mieć dostęp do operacji wrażliwych, takich jak zmiana hasła lub danych, takich jak dane osobowe i informacje o pieczeniu itp.
  • Zagrożone ciasteczko będzie nadal obowiązywać przez cały okres jego ważności

Lepsze rozwiązanie

Lepszym rozwiązaniem byłoby

  • Użytkownik jest zalogowany i pamiętaj, że wybrano mnie
  • Wygeneruj Token i podpis i przechowuj w pliku cookie
  • Tokeny są losowe i są ważne tylko dla pojedynczej uwierzytelnienia
  • Token jest wymieniany przy każdej wizycie na stronie
  • Gdy niezalogowany użytkownik odwiedza witrynę, podpis, token i nazwa użytkownika są weryfikowane
  • Pamiętaj, że logowanie powinno mieć ograniczony dostęp i nie zezwalać na modyfikację hasła, danych osobowych itp.

Przykładowy kod

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

Klasa używana

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

Testowanie w Firefox i Chrome

wprowadź opis zdjęcia tutaj

Korzyść

  • Lepsze bezpieczeństwo
  • Ograniczony dostęp dla atakującego
  • Gdy plik cookie zostanie skradziony, jest ważny tylko na jeden dostęp
  • Gdy następny użytkownik wejdzie na stronę, możesz automatycznie wykryć i powiadomić użytkownika o kradzieży

Niekorzyść

  • Nie obsługuje stałego połączenia za pośrednictwem wielu przeglądarek (urządzenia mobilne i Internet)
  • Ciasteczko może być nadal skradzione, ponieważ użytkownik otrzymuje powiadomienie dopiero po następnym logowaniu.

Szybka naprawa

  • Wprowadzenie systemu zatwierdzania dla każdego systemu, który musi mieć stałe połączenie
  • Użyj wielu plików cookie do uwierzytelnienia

Wielokrotne podejście do plików cookie

Gdy atakujący ma zamiar ukraść pliki cookie, należy skoncentrować je tylko na określonej stronie internetowej lub domenie, np. przyklad.com

Ale tak naprawdę możesz uwierzytelnić użytkownika z 2 różnych domen ( example.com i fakeaddsite.com ) i sprawić, by wyglądał jak „Reklama Cookie”

  • Użytkownik zalogowany na example.com z pamiętaj mnie
  • Przechowuj nazwę użytkownika, token, odniesienie w pliku cookie
  • Przechowuj nazwę użytkownika, token, odniesienie w bazie danych np. Memcache
  • Wyślij identyfikator odwołania przez get i iframe na fakeaddsite.com
  • fakeaddsite.com używa odwołania do pobierania użytkownika i tokena z bazy danych
  • fakeaddsite.com przechowuje podpis
  • Gdy użytkownik zwraca, pobierz informacje o podpisie za pomocą elementu iframe z witryny fakeaddsite.com
  • Połącz dane i przeprowadź weryfikację
  • ..... znasz pozostałe

Niektóre osoby mogą się zastanawiać, w jaki sposób można użyć 2 różnych plików cookie? Cóż, to możliwe, wyobraź sobie example.com = localhosti fakeaddsite.com = 192.168.1.120. Jeśli przejrzysz pliki cookie, będzie to wyglądać tak

wprowadź opis zdjęcia tutaj

Z powyższego obrazka

  • Aktualnie odwiedzana strona to localhost
  • Zawiera także pliki cookie ustawione od 192.168.1.120

192.168.1.120

  • Akceptuje tylko zdefiniowane HTTP_REFERER
  • Akceptuje tylko połączenie z określonego REMOTE_ADDR
  • Bez JavaScript, bez zawartości, ale składają się wyłącznie z podpisywania informacji i dodawania lub pobierania ich z plików cookie

Korzyść

  • 99% procent czasu, w którym oszukałeś napastnika
  • Możesz łatwo zablokować konto w pierwszej próbie atakującego
  • Atakowi można zapobiec nawet przed kolejnym logowaniem, podobnie jak inne metody

Niekorzyść

  • Wielokrotne żądanie do serwera tylko dla jednego logowania

Poprawa

  • Zrezygnowano z użycia iframe ajax
Baba
źródło
5
Mimo że @ircmaxell dość dobrze opisał teorię, wolę to podejście, ponieważ działa ono doskonale bez potrzeby przechowywania identyfikatora użytkownika (co byłoby niepożądanym ujawnieniem), a także zawiera więcej odcisków palców niż tylko identyfikator użytkownika i skrót do identyfikacji użytkownik, taki jak przeglądarka. Utrudnia to atakującemu wykorzystanie skradzionego pliku cookie. To najlepsze i najbezpieczniejsze podejście, jakie do tej pory widziałem. +1
Marcello Mönkemeyer
6

Poprosiłem jeden kąt tego pytania tutaj , a odpowiedzi doprowadzi cię do wszystkich tokenów linki czasowe z plików cookie, których potrzebujesz.

Zasadniczo nie przechowujesz identyfikatora użytkownika w pliku cookie. Przechowujesz jednorazowy token (ogromny ciąg), którego użytkownik używa do pobrania starej sesji logowania. Następnie, aby było naprawdę bezpieczne, musisz podać hasło do ciężkich operacji (takich jak zmiana samego hasła).

Dan Rosenstark
źródło
6

Stary wątek, ale wciąż ważny problem. Zauważyłem kilka dobrych odpowiedzi na temat bezpieczeństwa i unikania używania „bezpieczeństwa przez zaciemnienie”, ale rzeczywiste podane metody techniczne nie były wystarczające w moich oczach. Co muszę powiedzieć, zanim przekażę swoją metodę:

  • NIGDY nie przechowuj hasła w postaci zwykłego tekstu ... NIGDY!
  • NIGDY przechowuj hasła użytkownika w więcej niż jednej lokalizacji w bazie danych. Zaplecze serwera zawsze jest w stanie pobrać zaszyfrowane hasło z tabeli użytkowników. Przechowywanie nadmiarowych danych zamiast dodatkowych transakcji DB nie jest bardziej wydajne, odwrotność jest prawdziwa.
  • Twoje identyfikatory sesji powinny być unikalne, aby żaden z dwóch użytkowników nigdy nie mógł udostępnić identyfikatora, stąd cel ID (czy numer identyfikacyjny Twojego prawa jazdy może pasować do innych osób? Nie.) To generuje dwuczęściową unikalną kombinację, opartą na 2 unikalne struny. Tabela Sesji powinna używać identyfikatora jako PK. Aby zezwolić na zaufanie wielu urządzeniom do automatycznego logowania, użyj innej tabeli dla zaufanych urządzeń, która zawiera listę wszystkich sprawdzonych urządzeń (patrz mój przykład poniżej) i jest odwzorowana przy użyciu nazwy użytkownika.
  • Nie służy celowi mieszania znanych danych w pliku cookie, plik cookie można skopiować. To, czego szukamy, to zgodne urządzenie użytkownika do dostarczania autentycznych informacji, których nie można uzyskać bez osoby atakującej narażającej komputer użytkownika (ponownie, patrz mój przykład). Oznaczałoby to jednak, że legalny użytkownik, który zabrania statycznym informacjom swojego komputera (tj. Adresowi MAC, nazwie hosta urządzenia, użytkownika użytkownika, jeśli jest ograniczony przez przeglądarkę itp.) Zachowywania spójności (lub fałszowania go w pierwszej kolejności), nie będzie w stanie użyj tej funkcji. Ale jeśli jest to problem, weź pod uwagę fakt, że oferujesz automatyczne logowanie użytkownikom, którzy jednoznacznie się identyfikują, więc jeśli odmówią bycia rozpoznanym przez sfałszowanie swojego MAC, sfałszowanie swojego użytkownika, sfałszowanie / zmianę nazwy hosta, ukrywanie się za serwerami proxy itp., to nie są możliwe do zidentyfikowania i nigdy nie powinny być uwierzytelniane dla usługi automatycznej. Jeśli chcesz, musisz sprawdzić dostęp do kart inteligentnych w pakiecie z oprogramowaniem po stronie klienta, które ustala tożsamość używanego urządzenia.

Biorąc to wszystko pod uwagę, istnieją dwa świetne sposoby automatycznego logowania w systemie.

Po pierwsze, tani, łatwy sposób, który stawia na kogoś innego. Jeśli włączysz obsługę witryny w, powiedzmy, na swoim koncie google +, prawdopodobnie masz usprawniony przycisk google +, który zaloguje użytkownika, jeśli jest już zalogowany w google (zrobiłem to tutaj, aby odpowiedzieć na to pytanie, jak zawsze zalogowany w Google). Jeśli chcesz, aby użytkownik logował się automatycznie, jeśli jest już zalogowany za pomocą zaufanego i obsługiwanego wystawcy uwierzytelnienia i zaznaczył odpowiednie pole, poproś skrypty po stronie klienta o wykonanie kodu za odpowiednim przyciskiem „Zaloguj się za pomocą” przed załadowaniem , pamiętaj tylko, aby serwer przechowywał unikalny identyfikator w tabeli automatycznego logowania, która zawiera nazwę użytkownika, identyfikator sesji i wystawcę uwierzytelnienia użytego dla użytkownika. Ponieważ te metody logowania korzystają z AJAX, i tak czekasz na odpowiedź, i ta odpowiedź jest albo potwierdzoną odpowiedzią, albo odrzuceniem. Jeśli otrzymasz poprawną odpowiedź, użyj jej jak zwykle, a następnie kontynuuj ładowanie zalogowanego użytkownika jak zwykle. W przeciwnym razie logowanie się nie powiedzie, ale nie mów użytkownikowi, po prostu kontynuuj, ponieważ nie jesteś zalogowany, zauważy to. Ma to na celu zapobieżenie, aby osoba atakująca, która ukradła pliki cookie (lub sfałszowała je w celu zwiększenia uprawnień), nie dowiedziała się, że użytkownik automatycznie loguje się na stronie.

Jest to tanie i przez niektórych może być również uważane za brudne, ponieważ próbuje ono zweryfikować potencjalnie już zalogowany użytkownik w miejscach takich jak Google i Facebook, nawet o tym nie mówiąc. Nie należy go jednak stosować u użytkowników, którzy nie poprosili o automatyczne logowanie do witryny, a ta konkretna metoda służy tylko do zewnętrznego uwierzytelnienia, np. Google+ lub FB.

Ponieważ zewnętrzny serwer uwierzytelniający został użyty do poinformowania serwera za kulisami, czy użytkownik został zweryfikowany, osoba atakująca nie może uzyskać niczego poza unikalnym identyfikatorem, który sam w sobie jest bezużyteczny. Opracuję:

  • Użytkownik „Joe” odwiedza witrynę po raz pierwszy, identyfikator sesji umieszczany jest w „sesji” pliku cookie.
  • Użytkownik „Joe” loguje się, zwiększa uprawnienia, otrzymuje nowy identyfikator sesji i odnawia „sesję” plików cookie.
  • Użytkownik „Joe” decyduje się na automatyczne logowanie przy użyciu Google +, otrzymuje unikalny identyfikator umieszczony w pliku cookie „keepmesignedin”.
  • Użytkownik „Joe” google utrzymuje ich zalogowanie, umożliwiając Twojej stronie automatyczne logowanie użytkownika przy użyciu Google w backend.
  • Atakujący systematycznie próbuje unikatowych identyfikatorów dla „keepmesignedin” (jest to wiedza publiczna przekazywana każdemu użytkownikowi) i nie jest zalogowany nigdzie indziej; próbuje niepowtarzalnego identyfikatora nadanego „joe”.
  • Serwer otrzymuje unikalny identyfikator dla „Joe”, pobiera dopasowanie w DB dla konta Google +.
  • Serwer wysyła atakującego do strony logowania, która uruchamia żądanie AJAX do Google, aby się zalogować.
  • Serwer Google otrzymuje żądanie, używa swojego interfejsu API, aby zobaczyć, że Attacker nie jest aktualnie zalogowany.
  • Google wysyła odpowiedź, że nie ma obecnie zalogowanego użytkownika przez to połączenie.
  • Strona atakującego otrzymuje odpowiedź, skrypt automatycznie przekierowuje na stronę logowania z wartością POST zakodowaną w adresie URL.
  • Strona logowania pobiera wartość POST, wysyła ciasteczko dla „keepmesignedin” na pustą wartość i jest ważne do daty 1-1-1970 w celu powstrzymania automatycznej próby, powodując, że przeglądarka atakującego po prostu usuwa ciasteczko.
  • Atakujący otrzymuje normalną stronę logowania po raz pierwszy.

Bez względu na wszystko, nawet jeśli atakujący użyje nieistniejącego identyfikatora, próba nie powiedzie się przy wszystkich próbach, z wyjątkiem przypadków otrzymania poprawnej odpowiedzi.

Tę metodę można i należy stosować w połączeniu z wewnętrznym uwierzytelniaczem w przypadku osób, które logują się do witryny za pomocą zewnętrznego uwierzytelnienia.

=========

Teraz, dla twojego własnego systemu uwierzytelniania, który może automatycznie logować się użytkowników, robię to w ten sposób:

DB ma kilka tabel:

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

Pamiętaj, że nazwa użytkownika może mieć długość 255 znaków. Mój program serwera ogranicza nazwy użytkowników w moim systemie do 32 znaków, ale zewnętrzni uwierzytelniacze mogą mieć nazwy użytkowników w domenie @ domain.tld mogą być większe, więc po prostu obsługuję maksymalną długość adresu e-mail dla maksymalnej kompatybilności.

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

Należy zauważyć, że w tej tabeli nie ma pola użytkownika, ponieważ nazwa użytkownika po zalogowaniu znajduje się w danych sesji, a program nie zezwala na dane zerowe. Identyfikator_sesji i znacznik_sesji można wygenerować za pomocą losowych skrótów md5, skrótów sha1 / 128/256, znaczników datetime z losowymi ciągami dodanymi do nich, a następnie skrótu lub cokolwiek zechcesz, ale entropia danych wyjściowych powinna pozostać tak wysoka, jak to możliwe ogranicz ataki brutalnej siły nawet przed zejściem z ziemi, a wszystkie skróty generowane przez twoją klasę sesji powinny być sprawdzane pod kątem dopasowania w tabeli sesji przed próbą ich dodania.

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

Adresy MAC z natury mają być UNIKALNE, dlatego sensowne jest, aby każdy wpis miał unikalną wartość. Z drugiej strony nazwy hostów można legalnie powielać w oddzielnych sieciach. Ile osób używa „Home-PC” jako jednej z nazw swoich komputerów? Nazwa użytkownika jest pobierana z danych sesji przez zaplecze serwera, więc manipulowanie nią jest niemożliwe. Jeśli chodzi o token, ta sama metoda generowania tokenów sesji dla stron powinna być używana do generowania tokenów w plikach cookie dla automatycznego logowania użytkownika. Na koniec dodaje się kod daty i godziny, kiedy użytkownik będzie musiał ponownie zweryfikować swoje poświadczenia. Zaktualizuj tę datę i godzinę przy logowaniu użytkownika, zachowując ją w ciągu kilku dni, lub zmuś ją do wygaśnięcia, niezależnie od ostatniego logowania, zachowując ją tylko przez miesiąc lub więcej, w zależności od projektu.

Zapobiega to systematycznemu fałszowaniu adresu MAC i nazwy hosta dla użytkownika, który zna automatyczne logowanie. NIGDYniech użytkownik zachowa plik cookie z hasłem, czystym tekstem lub w inny sposób. Niech token zostanie zregenerowany na każdej stronie nawigacji, tak jak token sesji. To znacznie zmniejsza prawdopodobieństwo, że osoba atakująca może uzyskać prawidłowy plik cookie tokena i użyć go do zalogowania. Niektóre osoby próbują powiedzieć, że osoba atakująca może ukraść pliki cookie ofiary i przeprowadzić atak polegający na powtórzeniu sesji w celu zalogowania. Gdyby atakujący mógł ukraść pliki cookie (co jest możliwe), z pewnością naraziłby na szwank całe urządzenie, co oznacza, że ​​mógł po prostu użyć urządzenia do zalogowania się, co jest sprzeczne z celem kradzieży plików cookie całkowicie. Tak długo, jak twoja strona działa w oparciu o HTTPS (co powinno mieć miejsce w przypadku haseł, numerów CC lub innych systemów logowania), zapewniasz użytkownikowi pełną ochronę, jaką możesz uzyskać w przeglądarce.

Należy pamiętać o jednej rzeczy: dane sesji nie powinny wygasać, jeśli użyjesz automatycznego logowania. Możesz utracić możliwość fałszywego kontynuowania sesji, ale sprawdzanie poprawności w systemie powinno wznowić dane sesji, jeśli oczekuje się, że będą to trwałe dane między sesjami. Jeśli chcesz mieć zarówno trwałe, jak i nietrwałe dane sesji, użyj innej tabeli dla trwałych danych sesji z nazwą użytkownika jako PK i poproś serwer, aby pobierał je tak, jak normalne dane sesji, po prostu użyj innej zmiennej.

Po uzyskaniu takiego logowania serwer powinien nadal sprawdzać poprawność sesji. Tutaj możesz kodować oczekiwania dotyczące skradzionych lub zainfekowanych systemów; wzorce i inne oczekiwane wyniki logowania do danych sesji często mogą prowadzić do wniosków, że doszło do przejęcia systemu lub fałszowania plików cookie w celu uzyskania dostępu. W tym miejscu ISS Tech może wprowadzić reguły, które uruchomiłyby blokadę konta lub automatyczne usunięcie użytkownika z systemu automatycznego logowania, utrzymując atakujących wystarczająco długo, aby użytkownik mógł określić, w jaki sposób atakujący odniósł sukces i jak je odciąć.

Na zakończenie należy się upewnić, że wszelkie próby odzyskania, zmiany hasła lub niepowodzenia logowania przekraczające próg powodują wyłączenie automatycznego logowania, dopóki użytkownik nie potwierdzi poprawnie i nie potwierdzi tego.

Przepraszam, jeśli ktoś spodziewał się, że w mojej odpowiedzi zostanie podany kod, to się tutaj nie wydarzy. Powiem, że używam PHP, jQuery i AJAX do uruchamiania moich stron i NIGDY nie używam Windowsa jako serwera ... kiedykolwiek.

użytkownik253780
źródło
4

Wygeneruj skrót, być może z tajemnicą, którą tylko ty znasz, a następnie zapisz go w swojej bazie danych, aby można go było powiązać z użytkownikiem. Powinien działać całkiem dobrze.

Jani Hartikainen
źródło
Czy byłby to unikalny identyfikator tworzony podczas tworzenia użytkownika, czy zmieniłby się za każdym razem, gdy użytkownik generuje nowe ciasteczko „Keep Me Logged In”?
Matthew
1
Odpowiedź Tima Janssona opisuje dobre podejście do tworzenia skrótu, choć czułbym się bezpieczniej, gdyby nie zawierało hasła
Jani Hartikainen,
2

Moje rozwiązanie jest takie. Nie jest w 100% kuloodporny, ale myślę, że pozwoli ci to zaoszczędzić w większości przypadków.

Po pomyślnym zalogowaniu się utwórz ciąg z tymi informacjami:

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

Szyfruj $data, ustaw typ na HttpOnly i ustaw cookie.

Gdy użytkownik wróci do Twojej witryny, wykonaj następujące czynności:

  1. Uzyskaj dane cookie. Usuń niebezpieczne znaki z pliku cookie. Rozbij go :postacią.
  2. Sprawdź ważność. Jeśli plik cookie jest starszy niż X dni, przekieruj użytkownika do strony logowania.
  3. Jeśli plik cookie nie jest stary; Uzyskaj najnowszą godzinę zmiany hasła z bazy danych. Jeśli hasło zostanie zmienione po ostatnim logowaniu, przekieruj użytkownika na stronę logowania.
  4. Jeśli przepustka nie została ostatnio zmieniona; Pobierz bieżącego agenta przeglądarki użytkownika. Sprawdź, czy (currentUserAgentHash == cookieUserAgentHash). JEŚLI agenci są tacy sami, przejdź do następnego kroku, w przeciwnym razie przekieruj na stronę logowania.
  5. Jeśli wszystkie kroki powiodły się, autoryzuj nazwę użytkownika.

W przypadku wylogowania użytkownika usuń ten plik cookie. Utwórz nowy plik cookie, jeśli użytkownik ponownie się zaloguje.

trante
źródło
2

Nie rozumiem koncepcji przechowywania zaszyfrowanych danych w pliku cookie, gdy jest to jego zaszyfrowana wersja, której potrzebujesz do hakowania. Jeśli czegoś brakuje, proszę o komentarz.

Myślę o takim podejściu do „Remember Me”. Jeśli widzisz jakiekolwiek problemy, prosimy o komentarz.

  1. Utwórz tabelę do przechowywania danych „Remember Me” - osobno do tabeli użytkowników, aby móc zalogować się z wielu urządzeń.

  2. Po udanym logowaniu (z zaznaczonym Zapamiętaj mnie):

    a) Wygeneruj unikalny losowy ciąg znaków, który będzie używany jako identyfikator użytkownika na tym komputerze: bigUserID

    b) Wygeneruj unikalny losowy ciąg: bigKey

    c) Przechowuj ciasteczko: bigUserID: bigKey

    d) W tabeli „Remember Me” dodaj rekord z: UserID, Adres IP, bigUserID, bigKey

  3. Jeśli próbujesz uzyskać dostęp do czegoś, co wymaga logowania:

    a) Sprawdź plik cookie i wyszukaj bigUserID i bigKey z pasującym adresem IP

    b) Jeśli go znajdziesz, zaloguj się, ale ustaw w tabeli użytkownika „miękkie logowanie”, aby w przypadku niebezpiecznych operacji można było poprosić o pełne logowanie.

  4. Po wylogowaniu zaznacz wszystkie rekordy „Remember Me” dla tego użytkownika jako wygasłe.

Jedyne luki, jakie widzę, to;

  • możesz zdobyć czyjś laptop i sfałszować jego adres IP za pomocą pliku cookie.
  • możesz za każdym razem sfałszować inny adres IP i odgadnąć całą rzecz - ale przy dwóch dużych ciągach pasujących do siebie, to byłoby ... wykonywanie podobnych obliczeń jak powyżej ... Nie mam pojęcia ... ogromne szanse?
Enigma Plus
źródło
Witam i dziękuję za tę odpowiedź, podoba mi się. Jedno pytanie: dlaczego musisz wygenerować 2 losowe ciągi - bigUserID i bigKey? Dlaczego nie wygenerujesz tylko 1 i nie wykorzystasz go?
Jeremy Belolo,
2
bigKey wygasa po upływie określonego czasu, ale bigUserID nie. bigUserID pozwala ci mieć wiele sesji na różnych urządzeniach pod tym samym adresem IP. Mam nadzieję, że to ma sens - musiałem pomyśleć przez chwilę :)
Enigma Plus
Jedną z rzeczy, które można mieć przy pomocy hmac, jest to, że jeśli odkryłeś, że hmac został sfałszowany, możesz z pewnością wiedzieć, że ktoś próbował ukraść plik cookie, a następnie możesz zresetować każdy stan logowania. Czy mam rację?
Suraj Jain,
2

Przeczytałem wszystkie odpowiedzi i nadal trudno mi było wydobyć to, co miałem zrobić. Jeśli zdjęcie jest warte 1 tys. Słów, mam nadzieję, że pomoże to innym wdrożyć bezpieczne trwałe przechowywanie w oparciu o Ulepszone trwałe logowanie Barry'ego Jaspana.

wprowadź opis zdjęcia tutaj

Jeśli masz pytania, opinie lub sugestie, postaram się zaktualizować schemat, aby odzwierciedlić dla nowicjuszy próbującego wdrożyć bezpieczne trwałe logowanie.

Josh Woodcock
źródło
0

Wdrożenie funkcji „Keep Me Logged In” oznacza, że ​​musisz dokładnie zdefiniować, co to będzie oznaczać dla użytkownika. W najprostszym przypadku użyłbym tego, aby sesja miała znacznie dłuższy limit czasu: 2 dni (powiedzmy) zamiast 2 godzin. Aby to zrobić, będziesz potrzebować własnego miejsca do przechowywania sesji, prawdopodobnie w bazie danych, abyś mógł ustawić niestandardowe czasy wygaśnięcia dla danych sesji. Następnie musisz upewnić się, że ustawiłeś plik cookie, który będzie trwał przez kilka dni (lub dłużej), a nie wygasa po zamknięciu przeglądarki.

Słyszę, jak pytasz „dlaczego 2 dni? Dlaczego nie 2 tygodnie?”. Wynika to z faktu, że użycie sesji w PHP automatycznie przesunie termin ważności z powrotem. Wynika to z faktu, że wygaśnięcie sesji w PHP jest w rzeczywistości bezczynnością.

Teraz, powiedziawszy to, prawdopodobnie zaimplementuję trudniejszą wartość limitu czasu, którą przechowuję w samej sesji i po około 2 tygodniach, i dodam kod, aby to zobaczyć i na siłę unieważnić sesję. A przynajmniej je wylogować. Oznacza to, że użytkownik będzie musiał okresowo logować się. Wieśniak! robi to

staticsan
źródło
1
Ustawienie dłuższej sesji jest prawdopodobnie złe, ponieważ marnuje zasoby serwera i wpłynie negatywnie na wydajność
user3091530
0

Myślę, że możesz po prostu to zrobić:

$cookieString = password_hash($username, PASSWORD_DEFAULT);

Sklep $cookiestring w bazie danych i ustaw jako plik cookie. Ustaw także nazwę użytkownika tej osoby jako plik cookie. Chodzi o to, że skrótu nie można poddać inżynierii wstecznej.

Gdy pojawi się użytkownik, uzyskaj nazwę użytkownika z jednego pliku cookie, niż $cookieStringz innego. Jeśli $cookieStringpasuje do tego zapisanego w bazie danych, użytkownik jest uwierzytelniany. Ponieważ hasło_hash używa za każdym razem innej soli, nie ma znaczenia, co to jest czysty tekst.

Hayden O'Sullivan
źródło