"Keep Me Logged In" - der beste Ansatz

257

Meine Webanwendung verwendet Sitzungen, um Informationen über den Benutzer zu speichern, sobald dieser angemeldet ist, und um diese Informationen auf dem Weg von Seite zu Seite innerhalb der App zu verwalten. In dieser speziellen Anwendung speichere ich dieuser_id , first_nameund last_nameder Person.

Ich möchte beim Anmelden die Option "Angemeldet bleiben" anbieten, mit der zwei Wochen lang ein Cookie auf dem Computer des Benutzers abgelegt wird, mit dem die Sitzung mit denselben Details neu gestartet wird, wenn sie zur App zurückkehren.

Was ist der beste Ansatz dafür? Ich möchte ihre nicht user_idim Cookie speichern , da dies anscheinend es einem Benutzer leicht machen würde, die Identität eines anderen Benutzers zu fälschen.

Matthew
quelle

Antworten:

735

OK, lassen Sie mich das klar sagen: Wenn Sie zu diesem Zweck Benutzerdaten oder aus Benutzerdaten abgeleitete Daten in ein Cookie einfügen, machen Sie etwas falsch.

Dort. Ich sagte es. Jetzt können wir zur eigentlichen Antwort übergehen.

Was ist los mit dem Hashing von Benutzerdaten? Nun, es kommt auf die Belichtungsfläche und die Sicherheit durch Dunkelheit an.

Stellen Sie sich für eine Sekunde vor, Sie wären ein Angreifer. In Ihrer Sitzung wird ein kryptografisches Cookie für das Erinnerungsset angezeigt. Es ist 32 Zeichen breit. Gee. Das kann ein MD5 sein ...

Stellen wir uns für eine Sekunde auch vor, dass sie den von Ihnen verwendeten Algorithmus kennen. Beispielsweise:

md5(salt+username+ip+salt)

Jetzt muss ein Angreifer nur noch das "Salz" brutal erzwingen (was eigentlich kein Salz ist, aber dazu später mehr), und er kann jetzt alle gewünschten gefälschten Token mit einem beliebigen Benutzernamen für seine IP-Adresse generieren! Aber ein Salz brutal zu erzwingen ist schwer, oder? Absolut. Aber moderne GPUs sind außerordentlich gut darin. Und wenn Sie nicht genügend Zufälligkeit verwenden (machen Sie es groß genug), wird es schnell fallen und damit die Schlüssel zu Ihrem Schloss.

Kurz gesagt, das einzige, was Sie schützt, ist das Salz, das Sie nicht so sehr schützt, wie Sie denken.

Aber warte!

All dies wurde vorausgesetzt, dass der Angreifer den Algorithmus kennt! Wenn es geheim und verwirrend ist, dann bist du in Sicherheit, oder? FALSCH . Diese Denkrichtung hat einen Namen: Sicherheit durch Dunkelheit , auf die man sich NIEMALS verlassen sollte.

Der bessere Weg

Der bessere Weg ist, niemals die Informationen eines Benutzers den Server verlassen zu lassen, außer der ID.

Wenn sich der Benutzer anmeldet, generieren Sie ein großes (128 bis 256 Bit) zufälliges Token. Fügen Sie dies einer Datenbanktabelle hinzu, die das Token der Benutzer-ID zuordnet, und senden Sie es dann im Cookie an den Client.

Was ist, wenn der Angreifer das zufällige Token eines anderen Benutzers errät?

Nun, lassen Sie uns hier etwas rechnen. Wir generieren ein 128-Bit-Zufallstoken. Das heißt, es gibt:

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

Um zu zeigen, wie absurd groß diese Zahl ist, stellen wir uns jeden Server im Internet vor (sagen wir heute 50.000.000), der versucht, diese Zahl mit einer Geschwindigkeit von jeweils 1.000.000.000 pro Sekunde brutal zu erzwingen. In Wirklichkeit würden Ihre Server unter einer solchen Last schmelzen, aber lassen Sie uns dies ausprobieren.

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

Also 50 Billiarden Vermutungen pro Sekunde. Das ist schnell! Richtig?

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

Also 6,8 Sextillionen Sekunden ...

Lassen Sie uns versuchen, das auf freundlichere Zahlen zu reduzieren.

215,626,585,489,599 years

Oder noch besser:

47917 times the age of the universe

Ja, das ist das 47917-fache des Alters des Universums ...

Grundsätzlich wird es nicht geknackt.

Um es zusammenzufassen:

Der bessere Ansatz, den ich empfehle, besteht darin, den Cookie aus drei Teilen zu speichern.

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

Dann, um zu validieren:

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

Hinweis: Verwenden Sie nicht das Token oder die Kombination aus Benutzer und Token, um einen Datensatz in Ihrer Datenbank zu suchen. Stellen Sie immer sicher, dass Sie einen Datensatz basierend auf dem Benutzer abrufen, und verwenden Sie eine zeitsichere Vergleichsfunktion, um das abgerufene Token anschließend zu vergleichen. Mehr über Timing-Angriffe .

Jetzt ist es sehr wichtig, dass es sich SECRET_KEYum ein kryptografisches Geheimnis handelt (das durch etwas wie /dev/urandomund / oder abgeleitet von einer Eingabe mit hoher Entropie erzeugt wird). Außerdem GenerateRandomToken()muss eine starke Zufallsquelle sein ( mt_rand()ist bei weitem nicht stark genug. Eine Bibliothek verwenden, wie RandomLib oder random_compat oder mcrypt_create_iv()mit DEV_URANDOM) ...

Das hash_equals()soll Timing-Angriffe verhindern . Wenn Sie eine PHP-Version unter PHP 5.6 verwenden, wird die Funktion hash_equals()nicht unterstützt. In diesem Fall können Sie durch hash_equals()die TimingSafeCompare-Funktion ersetzen :

/**
 * 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
quelle
7
Aber bedeutet dieser Ansatz nicht, dass jeder diesen Benutzernamen und dieses Cookie verwenden und sich von jedem anderen Gerät aus als dieser Benutzer anmelden kann?
Einfacher
8
lol :-), beachte, dass 47917 Jahre die maximale Zeit zum Erraten sind, der zufällige Token könnte auch in 1 Stunde erraten werden.
Storm_Buster
33
Es ist komisch, weil Ihr Code Ihrer Antwort widerspricht. Sie sagen "Wenn Sie Benutzerdaten in ein Cookie [...] einfügen, machen Sie etwas falsch", aber genau das tut Ihr Code! Ist es nicht besser, den Benutzernamen aus dem Cookie zu entfernen, den Hash nur über das Token zu berechnen (und möglicherweise die IP-Adresse hinzuzufügen, um den Diebstahl von Cookies zu verhindern) und dann fetchUsernameByToken anstelle von fetchTokenByUserName in RememberMe () auszuführen?
Leven
9
Seit PHP 5.6 können hash_equals verwendet werden, um Timing-Angriffe bei String-Vergleichen zu verhindern.
F21
5
@Levit verhindert, dass jemand ein gültiges Token nimmt und die damit verbundene Benutzer-ID ändert.
Ircmaxell
93

Sicherheitshinweis : Es ist eine schlechte Idee, das Cookie auf einem MD5-Hash deterministischer Daten zu basieren. Es ist besser, ein zufälliges Token zu verwenden, das von einem CSPRNG abgeleitet ist. Siehe ircmaxell Antwort auf diese Frage für einen sichereren Ansatz.

Normalerweise mache ich so etwas:

  1. Der Benutzer meldet sich mit "Angemeldet bleiben" an.
  2. Sitzung erstellen
  3. Erstellen Sie ein Cookie mit dem Namen ETWAS, das Folgendes enthält: md5 (salt + Benutzername + ip + salt) und ein Cookie mit dem Namen "SomethingElse", das id enthält
  4. Cookie in Datenbank speichern
  5. Benutzer macht Sachen und geht ----
  6. Benutzer kehrt zurück, prüft auf etwas anderes Cookie, falls vorhanden, holt den alten Hash aus der Datenbank für diesen Benutzer, überprüft den Inhalt des Cookies ETWAS stimmt mit dem Hash aus der Datenbank überein, der auch mit einem neu berechneten Hash übereinstimmen sollte (für das ip) also: cookieHash == databaseHash == md5 (salt + username + ip + salt), wenn ja, gehe zu 2, wenn sie nicht zu 1 gehen

Natürlich können Sie auch verschiedene Cookie-Namen usw. verwenden. Sie können auch den Inhalt des Cookies ein wenig ändern. Stellen Sie jedoch sicher, dass es nicht zu einfach zu erstellen ist. Sie können beispielsweise auch ein user_salt erstellen, wenn der Benutzer erstellt wird, und dieses auch in das Cookie einfügen.

Sie können auch sha1 anstelle von md5 (oder so ziemlich jeden Algorithmus) verwenden.

Pim Jäger
quelle
30
Warum die IP in den Hash aufnehmen? Stellen Sie außerdem sicher, dass der Cookie Zeitstempelinformationen enthält, und legen Sie anhand dieser Informationen ein Höchstalter für das Cookie fest, damit Sie kein Identitätstoken erstellen, das für die Ewigkeit gültig ist.
Scott Mitchell
4
@Abhishek Dilliwal: Dies ist ein ziemlich alter Thread, aber ich bin darauf gestoßen und habe nach der gleichen Antwort wie Mathew gesucht. Ich glaube nicht, dass die Verwendung der session_ID für Pims Antwort funktionieren würde, da Sie den Datenbank-Hash, den Cookie-Hash und die aktuelle session_ID nicht überprüfen können, da sich die session_ID bei jedem session_start () ändert. Ich dachte nur, ich würde darauf hinweisen.
Partack
3
Es tut mir leid, langweilig zu sein, aber was ist der Zweck des zweiten Cookies etwas anderes? Was ist in diesem Fall id? Ist es nur eine einfache Art von "wahr / falsch" -Wert, um anzugeben, ob der Benutzer die Funktion "Angemeldet bleiben" überhaupt verwenden möchte? Wenn ja, warum nicht einfach prüfen, ob der Cookie ETWAS überhaupt existiert? Wenn der Benutzer nicht möchte, dass sein Login fortbesteht, wäre das ETWAS-Cookie überhaupt nicht vorhanden, oder? Generieren Sie den Hash schließlich erneut dynamisch und vergleichen Sie ihn als zusätzliche Sicherheitsmaßnahme mit dem Cookie und der Datenbank?
itsmequinn
4
Das Token sollte ZUFÄLLIG sein und in keiner Weise mit dem Benutzer / seiner IP / seinem Benutzeragenten / irgendetwas verbunden sein. Es ist eine große Sicherheitslücke.
Pamil
4
Warum benutzt du zwei Salze? md5 (salt + username + ip + salt)
Aaron Kreider
77

Einführung

Ihr Titel "Keep Me Logged In" - der beste Ansatz macht es mir schwer zu wissen, wo ich anfangen soll, denn wenn Sie den besten Ansatz suchen, müssten Sie Folgendes berücksichtigen:

  • Identifizierung
  • Sicherheit

Kekse

Cookies sind anfällig. Zwischen häufigen Sicherheitslücken durch Browser-Cookie-Diebstahl und Cross-Site-Scripting-Angriffen müssen wir akzeptieren, dass Cookies nicht sicher sind. Um die Sicherheit zu verbessern, müssen Sie dies beachtenphp setcookies zusätzliche Funktionen wie z

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

  • sicher (über HTTPS-Verbindung)
  • httponly (Identitätsdiebstahl durch XSS-Angriff reduzieren)

Definitionen

  • Token (unvorhersehbare zufällige Zeichenfolge mit n Länge, z. B. / dev / urandom)
  • Referenz (unvorhersehbare zufällige Zeichenfolge mit n Länge, z. B. / dev / urandom)
  • Signatur (Generieren Sie einen verschlüsselten Hashwert mit der HMAC-Methode.)

Einfacher Ansatz

Eine einfache Lösung wäre:

  • Der Benutzer ist mit Remember Me angemeldet
  • Login-Cookie mit Token & Signatur
  • Bei der Rückkehr wird die Signatur überprüft
  • Wenn die Signatur in Ordnung ist, werden Benutzername und Token in der Datenbank nachgeschlagen
  • Wenn nicht gültig. Zurück zur Anmeldeseite
  • Wenn gültig, automatisch anmelden

Die obige Fallstudie fasst alle Beispiele auf dieser Seite zusammen, aber die Nachteile sind die folgenden

  • Es gibt keine Möglichkeit festzustellen, ob die Cookies gestohlen wurden
  • Angreifer können auf sensible Vorgänge wie das Ändern von Passwörtern oder Daten wie persönliche Informationen und Backinformationen usw. zugreifen.
  • Das kompromittierte Cookie ist weiterhin für die Lebensdauer des Cookies gültig

Bessere Lösung

Eine bessere Lösung wäre

  • Der Benutzer ist angemeldet und erinnert sich, dass ich ausgewählt ist
  • Token & Signatur generieren und im Cookie speichern
  • Die Token sind zufällig und nur für die einzelne Authentifizierung gültig
  • Die Token werden bei jedem Besuch der Site ersetzt
  • Wenn ein nicht protokollierter Benutzer die Site besucht, werden Signatur, Token und Benutzername überprüft
  • Denken Sie daran, dass die Anmeldung nur eingeschränkten Zugriff haben und keine Änderung des Passworts, der persönlichen Daten usw. zulassen darf.

Beispielcode

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

Klasse verwendet

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

Testen in Firefox & Chrome

Geben Sie hier die Bildbeschreibung ein

Vorteil

  • Bessere Sicherheit
  • Eingeschränkter Zugriff für Angreifer
  • Wenn ein Cookie gestohlen wird, gilt es nur für den Einzelzugriff
  • Wenn der ursprüngliche Benutzer das nächste Mal auf die Site zugreift, können Sie den Benutzer automatisch erkennen und über Diebstahl informieren

Nachteil

  • Unterstützt keine dauerhafte Verbindung über mehrere Browser (Mobile & Web)
  • Das Cookie kann weiterhin gestohlen werden, da der Benutzer die Benachrichtigung erst nach dem nächsten Login erhält.

Schnelle Lösung

  • Einführung eines Genehmigungssystems für jedes System, das eine dauerhafte Verbindung haben muss
  • Verwenden Sie zur Authentifizierung mehrere Cookies

Ansatz mit mehreren Cookies

Wenn ein Angreifer Cookies stehlen will, konzentriert er sich nur auf eine bestimmte Website oder Domain, z. example.com

Aber wirklich, Sie können einen Benutzer aus 2 verschiedenen Domains ( example.com & fakeaddsite.com ) authentifizieren und ihn wie "Advert Cookie" aussehen lassen.

  • Benutzer bei example.com angemeldet mit erinnern an mich
  • Speichern Sie den Benutzernamen, das Token und die Referenz im Cookie
  • Speichern Sie den Benutzernamen, das Token und die Referenz in der Datenbank, z. Memcache
  • Senden Sie die Aktualisierungs-ID über get und iframe an fakeaddsite.com
  • fakeaddsite.com verwendet den Verweis, um Benutzer und Token aus der Datenbank abzurufen
  • fakeaddsite.com speichert die Signatur
  • Wenn ein Benutzer Abrufsignaturinformationen mit iframe von fakeaddsite.com zurückgibt
  • Kombinieren Sie die Daten und führen Sie die Validierung durch
  • ..... du kennst die restlichen

Einige Leute fragen sich vielleicht, wie Sie 2 verschiedene Cookies verwenden können? Nun, es ist möglich, stellen Sie sich vor example.com = localhostund fakeaddsite.com = 192.168.1.120. Wenn Sie die Cookies überprüfen, würde es so aussehen

Geben Sie hier die Bildbeschreibung ein

Aus dem Bild oben

  • Die aktuell besuchte Site ist localhost
  • Es enthält auch Cookies von 192.168.1.120

192.168.1.120

  • Akzeptiert nur definierte HTTP_REFERER
  • Akzeptiert nur die Verbindung von angegeben REMOTE_ADDR
  • Kein JavaScript, kein Inhalt, sondern nichts anderes als Informationen signieren und zum Cookie hinzufügen oder daraus abrufen

Vorteil

  • In 99% der Fälle haben Sie den Angreifer ausgetrickst
  • Sie können das Konto beim ersten Versuch des Angreifers einfach sperren
  • Angriffe können bereits vor dem nächsten Login wie bei den anderen Methoden verhindert werden

Nachteil

  • Mehrfachanforderung an den Server nur für eine einzelne Anmeldung

Verbesserung

  • Fertig iframe verwenden ajax
Baba
quelle
5
Obwohl @ircmaxell die Theorie recht gut beschrieben hat, bevorzuge ich diesen Ansatz, da er hervorragend funktioniert, ohne dass die Benutzer-ID gespeichert werden muss (was eine unerwünschte Offenlegung wäre) und außerdem mehr Fingerabdrücke enthält als nur die Benutzer-ID und den Hash, um die zu identifizieren Benutzer, wie der Browser. Dies macht es für einen Angreifer noch schwieriger, einen gestohlenen Cookie zu verwenden. Es ist der beste und sicherste Ansatz, den ich bisher gesehen habe. +1
Marcello Mönkemeyer
6

Ich fragte einen Winkel dieser Frage hier , und die Antworten werden Sie alle Token-basierte Timing-out - Cookie Links führen Sie benötigen.

Grundsätzlich speichern Sie die Benutzer-ID nicht im Cookie. Sie speichern ein einmaliges Token (große Zeichenfolge), mit dem der Benutzer seine alte Anmeldesitzung abruft. Um es wirklich sicher zu machen, fragen Sie nach einem Passwort für schwere Operationen (wie das Ändern des Passworts selbst).

Dan Rosenstark
quelle
6

Alter Thread, aber immer noch ein berechtigtes Anliegen. Ich bemerkte einige gute Reaktionen in Bezug auf Sicherheit und die Vermeidung der Verwendung von "Sicherheit durch Dunkelheit", aber die tatsächlich angegebenen technischen Methoden reichten in meinen Augen nicht aus. Dinge, die ich sagen muss, bevor ich meine Methode beisteuere:

  • Speichern Sie NIEMALS ein Passwort im Klartext ... NIEMALS!
  • NOCH NIE speichert einen Hash - Passwort des Benutzers in mehr als ein Ort in Ihrer Datenbank. Ihr Server-Backend kann das Hash-Passwort immer aus der Benutzertabelle abrufen. Es ist nicht effizienter, redundante Daten anstelle zusätzlicher DB-Transaktionen zu speichern. Das Gegenteil ist der Fall.
  • Ihre Sitzungs-IDs sollten eindeutig sein, damit keine zwei Benutzer jemals eine ID teilen können, daher der Zweck einer ID (könnte die ID-Nummer Ihres Führerscheins jemals mit einer anderen Person übereinstimmen? Nein.) Dies erzeugt eine zweiteilige eindeutige Kombination, basierend auf 2 einzigartige Saiten. Ihre Sitzungstabelle sollte die ID als PK verwenden. Verwenden Sie eine andere Tabelle für vertrauenswürdige Geräte, die die Liste aller validierten Geräte enthält (siehe mein Beispiel unten) und die unter Verwendung des Benutzernamens zugeordnet wird, damit mehreren Geräten für die automatische Anmeldung vertraut werden kann.
  • Es hat keinen Zweck, bekannte Daten in ein Cookie zu hashen, das Cookie kann kopiert werden. Was wir suchen, ist ein kompatibles Benutzergerät, um authentische Informationen bereitzustellen, die nicht abgerufen werden können, ohne dass ein Angreifer den Computer des Benutzers kompromittiert (siehe auch mein Beispiel). Dies würde jedoch bedeuten, dass ein legitimer Benutzer, der die statischen Informationen seines Computers (z. B. MAC-Adresse, Hostname des Geräts, Benutzeragent, wenn durch den Browser eingeschränkt usw.) verbietet, nicht konsistent zu bleiben (oder diese überhaupt fälscht), dies nicht kann Verwenden Sie diese Funktion. Wenn dies jedoch ein Problem darstellt, berücksichtigen Sie die Tatsache, dass Sie Benutzern, denen Sie eine automatische Anmeldung anbieten, anbieten sich eindeutig identifizieren,Wenn sie sich also weigern, bekannt zu werden, indem sie ihren MAC fälschen, ihren Benutzeragenten fälschen, ihren Hostnamen fälschen / ändern, sich hinter Proxys verstecken usw., sind sie nicht identifizierbar und sollten niemals für einen automatischen Dienst authentifiziert werden. Wenn Sie dies möchten, müssen Sie sich mit dem Smartcard-Zugriff befassen, der mit einer clientseitigen Software gebündelt ist, die die Identität des verwendeten Geräts festlegt.

Abgesehen davon gibt es zwei großartige Möglichkeiten, sich automatisch auf Ihrem System anzumelden.

Erstens die billige, einfache Möglichkeit, alles auf jemand anderen zu übertragen. Wenn Sie dafür sorgen, dass sich Ihre Website beispielsweise mit Ihrem Google + -Konto anmeldet, haben Sie wahrscheinlich eine optimierte Google + -Schaltfläche, mit der sich der Nutzer anmeldet, wenn er bereits bei Google angemeldet ist (das habe ich hier getan, um diese Frage zu beantworten, wie ich es immer bin bei Google angemeldet). Wenn Sie möchten, dass sich der Benutzer automatisch anmeldet, wenn er bereits mit einem vertrauenswürdigen und unterstützten Authentifikator angemeldet ist, und das Kontrollkästchen aktiviert haben, lassen Sie Ihre clientseitigen Skripts vor dem Laden den Code hinter der entsprechenden Schaltfläche "Anmelden mit" ausführen Stellen Sie nur sicher, dass der Server eine eindeutige ID in einer Tabelle für die automatische Anmeldung speichert, die den Benutzernamen, die Sitzungs-ID und den für den Benutzer verwendeten Authentifikator enthält. Da diese Anmeldemethoden AJAX verwenden, warten Sie trotzdem auf eine Antwort. und diese Antwort ist entweder eine validierte Antwort oder eine Ablehnung. Wenn Sie eine validierte Antwort erhalten, verwenden Sie diese wie gewohnt und laden Sie den angemeldeten Benutzer wie gewohnt weiter. Andernfalls ist die Anmeldung fehlgeschlagen, aber teilen Sie dem Benutzer dies nicht mit. Fahren Sie einfach fort, da er nicht angemeldet ist. Er wird es bemerken. Dies soll verhindern, dass ein Angreifer, der Cookies gestohlen (oder gefälscht hat, um Berechtigungen zu eskalieren), erfährt, dass sich der Benutzer automatisch auf der Website anmeldet.

Dies ist billig und kann von einigen auch als schmutzig angesehen werden, da es versucht, Ihre möglicherweise bereits selbst angemeldeten Personen mit Orten wie Google und Facebook zu validieren, ohne es Ihnen zu sagen. Es sollte jedoch nicht für Benutzer verwendet werden, die nicht aufgefordert wurden, sich automatisch bei Ihrer Website anzumelden. Diese spezielle Methode dient nur zur externen Authentifizierung, z. B. bei Google+ oder FB.

Da ein externer Authentifikator verwendet wurde, um dem Server hinter den Kulissen mitzuteilen, ob ein Benutzer validiert wurde oder nicht, kann ein Angreifer nur eine eindeutige ID erhalten, die für sich genommen unbrauchbar ist. Ich werde näher darauf eingehen:

  • Benutzer 'joe' besucht die Site zum ersten Mal, Sitzungs-ID in Cookie 'session' platziert.
  • Benutzer 'joe' meldet sich an, eskaliert Berechtigungen, erhält eine neue Sitzungs-ID und erneuert das Cookie 'session'.
  • Der Benutzer 'joe' wählt die automatische Anmeldung mit Google + und erhält eine eindeutige ID im Cookie 'keepmesignedin'.
  • Der Nutzer 'joe' lässt Google sie anmelden, sodass Ihre Website den Nutzer mithilfe von Google in Ihrem Backend automatisch anmelden kann.
  • Der Angreifer versucht systematisch eindeutige IDs für 'keepmesignedin' (dies ist öffentliches Wissen, das jedem Benutzer ausgehändigt wird) und ist an keiner anderen Stelle angemeldet. versucht eine eindeutige ID für 'joe'.
  • Der Server erhält eine eindeutige ID für "Joe" und zieht eine Übereinstimmung in der Datenbank für ein Google + -Konto.
  • Der Server sendet den Angreifer an die Anmeldeseite, auf der eine AJAX-Anforderung an Google ausgeführt wird, um sich anzumelden.
  • Der Google-Server empfängt eine Anfrage und verwendet seine API, um festzustellen, dass der Angreifer derzeit nicht angemeldet ist.
  • Google sendet eine Antwort, dass über diese Verbindung derzeit kein Benutzer angemeldet ist.
  • Die Seite des Angreifers erhält eine Antwort. Das Skript leitet automatisch zur Anmeldeseite mit einem in der URL codierten POST-Wert weiter.
  • Die Anmeldeseite erhält den POST-Wert, sendet das Cookie für 'keepmesignedin' an einen leeren Wert und ist bis zum Datum 1-1-1970 gültig, um einen automatischen Versuch zu verhindern, wodurch der Browser des Angreifers das Cookie einfach löscht.
  • Der Angreifer erhält eine normale Anmeldeseite zum ersten Mal.

Selbst wenn ein Angreifer eine nicht vorhandene ID verwendet, sollte der Versuch bei allen Versuchen fehlschlagen, außer wenn eine validierte Antwort empfangen wird.

Diese Methode kann und sollte in Verbindung mit Ihrem internen Authentifikator für diejenigen verwendet werden, die sich mit einem externen Authentifikator auf Ihrer Website anmelden.

=========

Für Ihr eigenes Authentifizierungssystem, das Benutzer automatisch anmelden kann, gehe ich folgendermaßen vor:

DB hat ein paar Tabellen:

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

Beachten Sie, dass der Benutzername 255 Zeichen lang sein kann. Mein Serverprogramm hat Benutzernamen in meinem System auf 32 Zeichen beschränkt, aber externe Authentifikatoren haben möglicherweise Benutzernamen, deren @ domain.tld größer ist. Daher unterstütze ich nur die maximale Länge einer E-Mail-Adresse, um maximale Kompatibilität zu gewährleisten.

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

Beachten Sie, dass diese Tabelle kein Benutzerfeld enthält, da sich der angemeldete Benutzername in den Sitzungsdaten befindet und das Programm keine Nulldaten zulässt. Die session_id und das session_token können mit zufälligen md5-Hashes, sha1 / 128/256-Hashes, Datums- / Uhrzeitstempeln mit zufälligen Zeichenfolgen, die dann gehasht werden, oder was auch immer Sie möchten generiert werden, aber die Entropie Ihrer Ausgabe sollte so hoch wie tolerierbar bleiben Verhindern Sie, dass Brute-Force-Angriffe überhaupt in Gang kommen, und alle von Ihrer Sitzungsklasse generierten Hashes sollten vor dem Versuch, sie hinzuzufügen, auf Übereinstimmungen in der Sitzungstabelle überprüft werden.

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

MAC-Adressen sollten von Natur aus EINZIGARTIG sein, daher ist es sinnvoll, dass jeder Eintrag einen eindeutigen Wert hat. Hostnamen hingegen könnten legitimerweise in separaten Netzwerken dupliziert werden. Wie viele Leute verwenden "Home-PC" als einen ihrer Computernamen? Der Benutzername wird vom Server-Backend aus den Sitzungsdaten übernommen, sodass eine Manipulation nicht möglich ist. Für das Token sollte dieselbe Methode zum Generieren von Sitzungstoken für Seiten verwendet werden, um Token in Cookies für die automatische Anmeldung des Benutzers zu generieren. Zuletzt wird der Datum / Uhrzeit-Code hinzugefügt, wenn der Benutzer seine Anmeldeinformationen erneut validieren müsste. Aktualisieren Sie diese Datumszeit entweder bei der Benutzeranmeldung, indem Sie sie innerhalb weniger Tage beibehalten, oder erzwingen Sie den Ablauf, unabhängig von der letzten Anmeldung, und halten Sie sie nur etwa einen Monat lang, je nachdem, was Ihr Design vorschreibt.

Dies verhindert, dass jemand den MAC und den Hostnamen eines Benutzers, von dem er weiß, dass er sich automatisch anmeldet, systematisch fälscht. anmeldet, NIEMALSLassen Sie den Benutzer ein Cookie mit seinem Passwort, Klartext oder auf andere Weise aufbewahren. Lassen Sie das Token bei jeder Seitennavigation neu generieren, genau wie beim Sitzungstoken. Dies verringert massiv die Wahrscheinlichkeit, dass ein Angreifer ein gültiges Token-Cookie erhält und es zum Anmelden verwendet. Einige Leute werden versuchen zu sagen, dass ein Angreifer dem Opfer die Cookies stehlen und einen Sitzungswiederholungsangriff ausführen könnte, um sich anzumelden. Wenn ein Angreifer die Cookies stehlen könnte (was möglich ist), hätte er sicherlich das gesamte Gerät kompromittiert, was bedeutet, dass er das Gerät trotzdem nur zum Anmelden verwenden könnte, was den Zweck des vollständigen Diebstahls von Cookies zunichte macht. Solange Ihre Site über HTTPS läuft (was beim Umgang mit Passwörtern, CC-Nummern oder anderen Anmeldesystemen der Fall sein sollte), haben Sie dem Benutzer den Schutz gewährt, den Sie in einem Browser haben können.

Beachten Sie Folgendes: Sitzungsdaten sollten nicht ablaufen, wenn Sie die automatische Anmeldung verwenden. Sie können die Möglichkeit, die Sitzung fälschlicherweise fortzusetzen, auslaufen lassen. Bei der Validierung im System sollten jedoch die Sitzungsdaten fortgesetzt werden, wenn es sich um persistente Daten handelt, die voraussichtlich zwischen den Sitzungen fortgesetzt werden. Wenn Sie sowohl persistente als auch nicht persistente Sitzungsdaten möchten, verwenden Sie eine andere Tabelle für persistente Sitzungsdaten mit dem Benutzernamen als PK und lassen Sie den Server diese wie die normalen Sitzungsdaten abrufen. Verwenden Sie einfach eine andere Variable.

Sobald eine Anmeldung auf diese Weise erreicht wurde, sollte der Server die Sitzung noch validieren. Hier können Sie Erwartungen für gestohlene oder kompromittierte Systeme codieren. Muster und andere erwartete Ergebnisse von Anmeldungen an Sitzungsdaten können häufig zu Schlussfolgerungen führen, dass ein System entführt oder Cookies gefälscht wurden, um Zugriff zu erhalten. Hier kann Ihr ISS Tech Regeln festlegen, die eine Kontosperrung oder das automatische Entfernen eines Benutzers aus dem automatischen Anmeldesystem auslösen und Angreifer so lange fernhalten, dass der Benutzer feststellen kann, wie der Angreifer erfolgreich war und wie er sie abschneiden kann.

Stellen Sie abschließend sicher, dass Wiederherstellungsversuche, Kennwortänderungen oder Anmeldefehler nach dem Schwellenwert dazu führen, dass die automatische Anmeldung deaktiviert wird, bis der Benutzer ordnungsgemäß validiert und bestätigt hat, dass dies geschehen ist.

Ich entschuldige mich, wenn jemand erwartet hat, dass in meiner Antwort Code ausgegeben wird, das wird hier nicht passieren. Ich werde sagen, dass ich PHP, jQuery und AJAX verwende, um meine Websites auszuführen, und Windows NIEMALS als Server verwende ... niemals.

user253780
quelle
4

Generieren Sie einen Hash, möglicherweise mit einem Geheimnis, das nur Sie kennen, und speichern Sie ihn dann in Ihrer Datenbank, damit er dem Benutzer zugeordnet werden kann. Sollte ganz gut funktionieren.

Jani Hartikainen
quelle
Wäre dies eine eindeutige Kennung, die beim Erstellen des Benutzers erstellt wird, oder würde sie sich jedes Mal ändern, wenn der Benutzer ein neues Cookie "Angemeldet bleiben" generiert?
Matthew
1
Tim Janssons Antwort beschreibt einen guten Ansatz zur Erstellung des Hashs, obwohl ich mich sicherer fühlen würde, wenn er nicht das Passwort enthalten würde
Jani Hartikainen
2

Meine Lösung ist so. Es ist nicht 100% kugelsicher, aber ich denke, es wird Sie für die meisten Fälle retten.

Wenn sich der Benutzer erfolgreich angemeldet hat, erstellen Sie eine Zeichenfolge mit folgenden Informationen:

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

Verschlüsseln $data, Typ auf HttpOnly setzen und Cookie setzen.

Wenn der Benutzer zu Ihrer Site zurückkehrt, führen Sie die folgenden Schritte aus:

  1. Cookie-Daten abrufen. Entfernen Sie gefährliche Zeichen im Cookie. Explodiere es mit :Charakter.
  2. Überprüfen Sie die Gültigkeit. Wenn das Cookie älter als X Tage ist, leiten Sie den Benutzer zur Anmeldeseite weiter.
  3. Wenn der Cookie nicht alt ist; Holen Sie sich die letzte Kennwortänderungszeit aus der Datenbank. Wenn das Kennwort nach der letzten Anmeldung des Benutzers geändert wird, leiten Sie den Benutzer zur Anmeldeseite weiter.
  4. Wenn der Pass kürzlich nicht geändert wurde; Rufen Sie den aktuellen Browser-Agenten des Benutzers ab. Überprüfen Sie, ob (currentUserAgentHash == cookieUserAgentHash). Wenn die Agenten gleich sind, fahren Sie mit dem nächsten Schritt fort, andernfalls leiten Sie zur Anmeldeseite weiter.
  5. Wenn alle Schritte erfolgreich bestanden wurden, autorisieren Sie den Benutzernamen.

Wenn sich Benutzer abmelden, entfernen Sie dieses Cookie. Erstellen Sie ein neues Cookie, wenn sich der Benutzer erneut anmeldet.

trante
quelle
2

Ich verstehe das Konzept des Speicherns verschlüsselter Inhalte in einem Cookie nicht, wenn es sich um die verschlüsselte Version handelt, die Sie zum Hacken benötigen. Wenn mir etwas fehlt, kommentieren Sie bitte.

Ich denke darüber nach, diesen Ansatz für "Remember Me" zu wählen. Wenn Sie Probleme sehen können, kommentieren Sie diese bitte.

  1. Erstellen Sie eine Tabelle zum Speichern von "Remember Me" -Daten - getrennt von der Benutzertabelle, damit ich mich von mehreren Geräten aus anmelden kann.

  2. Bei erfolgreicher Anmeldung (mit aktiviertem Remember Me):

    a) Generieren Sie eine eindeutige Zufallszeichenfolge, die als Benutzer-ID auf diesem Computer verwendet werden soll: bigUserID

    b) Generieren Sie eine eindeutige Zufallszeichenfolge: bigKey

    c) Speichern Sie ein Cookie: bigUserID: bigKey

    d) Fügen Sie in der Tabelle "Remember Me" einen Datensatz hinzu mit: UserID, IP Address, bigUserID, bigKey

  3. Wenn Sie versuchen, auf etwas zuzugreifen, für das eine Anmeldung erforderlich ist:

    a) Suchen Sie nach dem Cookie und suchen Sie nach bigUserID & bigKey mit einer passenden IP-Adresse

    b) Wenn Sie es finden, melden Sie die Person an, setzen Sie jedoch ein Flag in der Benutzertabelle "Soft Login", damit Sie bei gefährlichen Vorgängen eine vollständige Anmeldung anfordern können.

  4. Markieren Sie beim Abmelden alle "Remember Me" -Datensätze für diesen Benutzer als abgelaufen.

Die einzigen Schwachstellen, die ich sehen kann, sind:

  • Sie könnten sich einen Laptop von jemandem schnappen und dessen IP-Adresse mit dem Cookie fälschen.
  • Sie könnten jedes Mal eine andere IP-Adresse fälschen und das Ganze erraten - aber mit zwei großen Zeichenfolgen würde das ... eine ähnliche Berechnung wie oben durchführen ... Ich habe keine Ahnung ... große Chancen?
Enigma Plus
quelle
Hallo und danke für diese Antwort, ich mag es. Eine Frage: Warum müssen Sie zwei zufällige Zeichenfolgen generieren - bigUserID & bigKey? Warum generierst du nicht nur 1 und verwendest es?
Jeremy Belolo
2
bigKey läuft nach einer vordefinierten Zeit ab, bigUserID jedoch nicht. Mit bigUserID können Sie mehrere Sitzungen auf verschiedenen Geräten unter derselben IP-Adresse durchführen. Hoffe das macht Sinn - ich musste einen Moment nachdenken :)
Enigma Plus
Eine Sache mit hmac kann helfen: Wenn Sie festgestellt haben, dass hmac manipuliert wurde, können Sie sicher wissen, dass jemand versucht hat, Cookies zu stehlen, und Sie können jeden Anmeldestatus zurücksetzen. Habe ich recht?
Suraj Jain
2

Ich las alle Antworten und fand es immer noch schwierig herauszufinden, was ich tun sollte. Wenn ein Bild mehr als 1.000 Wörter wert ist, hoffe ich, dass dies anderen hilft, einen sicheren dauerhaften Speicher zu implementieren, der auf Barry Jaspans Best Practice für verbesserte dauerhafte Anmelde-Cookies basiert

Geben Sie hier die Bildbeschreibung ein

Wenn Sie Fragen, Feedback oder Vorschläge haben, werde ich versuchen, das Diagramm zu aktualisieren, um es dem Neuling widerzuspiegeln, der versucht, eine sichere dauerhafte Anmeldung zu implementieren.

Josh Woodcock
quelle
0

Wenn Sie die Funktion "Angemeldet bleiben" implementieren, müssen Sie genau definieren, was dies für den Benutzer bedeutet. Im einfachsten Fall würde ich das verwenden, um zu bedeuten, dass die Sitzung eine viel längere Zeitüberschreitung hat: 2 Tage (sagen wir) statt 2 Stunden. Dazu benötigen Sie Ihren eigenen Sitzungsspeicher, wahrscheinlich in einer Datenbank, damit Sie benutzerdefinierte Ablaufzeiten für die Sitzungsdaten festlegen können. Dann müssen Sie sicherstellen, dass Sie ein Cookie setzen, das einige Tage (oder länger) anhält und nicht abläuft, wenn der Browser geschlossen wird.

Ich kann Sie fragen hören "warum 2 Tage? Warum nicht 2 Wochen?". Dies liegt daran, dass die Verwendung einer Sitzung in PHP den Ablauf automatisch zurückschiebt. Dies liegt daran, dass der Ablauf einer Sitzung in PHP tatsächlich eine Leerlaufzeit ist.

Nachdem ich das gesagt habe, würde ich wahrscheinlich einen schwierigeren Timeout-Wert implementieren, den ich in der Sitzung selbst speichere, und nach ungefähr zwei Wochen auslaufen, und Code hinzufügen, um dies zu sehen und die Sitzung zwangsweise ungültig zu machen. Oder zumindest um sie abzumelden. Dies bedeutet, dass der Benutzer aufgefordert wird, sich regelmäßig anzumelden. Yahoo! macht dies.

staticsan
quelle
1
Das Festlegen einer längeren Sitzung ist wahrscheinlich schlecht, da dadurch Serverressourcen verschwendet werden und die Leistung beeinträchtigt wird
user3091530
0

Ich denke, Sie könnten dies einfach tun:

$cookieString = password_hash($username, PASSWORD_DEFAULT);

Geschäft $cookiestring in der DB und und legen Sie es als Cookie. Legen Sie auch den Benutzernamen der Person als Cookie fest. Der springende Punkt bei einem Hash ist, dass er nicht rückentwickelt werden kann.

Wenn ein Benutzer auftaucht, erhalten Sie den Benutzernamen von einem Cookie als $cookieStringvon einem anderen. Wenn es $cookieStringmit dem in der Datenbank gespeicherten übereinstimmt, wird der Benutzer authentifiziert. Da password_hash jedes Mal ein anderes Salz verwendet, spielt es keine Rolle, wie der Klartext lautet.

Hayden O'Sullivan
quelle