«Keep Me Logged In» - лучший подход


257

Мое веб-приложение использует сеансы для хранения информации о пользователе после того, как он вошел в систему, и для поддержания этой информации при перемещении со страницы на страницу в приложении. В этом конкретном приложении, я храню user_id, first_nameи last_nameчеловека.

Я хотел бы предложить опцию «Keep Me Logged In» при входе в систему, при которой на компьютере пользователя будет сохранен файл cookie в течение двух недель, который перезапустит сессию с теми же подробностями, когда они вернутся в приложение.

Каков наилучший подход для этого? Я не хочу хранить их user_idв cookie-файлах, так как кажется, что одному пользователю будет легко попытаться создать личность другого пользователя.

Ответы:


735

Хорошо, позвольте мне прямо заявить: если вы помещаете пользовательские данные или что-либо, полученное из пользовательских данных, в файл cookie для этой цели, вы делаете что-то не так.

Там. Я сказал это. Теперь мы можем перейти к фактическому ответу.

Вы спрашиваете, что не так с хешированием пользовательских данных? Ну, это сводится к поверхности воздействия и безопасности через неизвестность.

Представьте на секунду, что вы нападающий. Вы видите криптографическое печенье, установленное для запоминания на вашем сеансе. Это 32 символа в ширину. Gee. Это может быть MD5 ...

Давайте на секунду представим, что они знают алгоритм, который вы использовали. Например:

md5(salt+username+ip+salt)

Теперь все, что нужно атакующему, это грубая сила «соли» (которая на самом деле не соль, а об этом позже), и теперь он может генерировать все фальшивые токены, которые ему нужны, с любым именем пользователя для своего IP-адреса! Но грубая соль - это трудно, верно? Абсолютно. Но современные графические процессоры чрезвычайно хороши в этом. И если вы не используете в нем достаточную случайность (сделайте его достаточно большим), он быстро упадет, а вместе с ним и ключи от вашего замка.

Короче говоря, единственное, что защищает вас, это соль, которая на самом деле не защищает вас так сильно, как вы думаете.

Но ждать!

Все это было основано на том, что злоумышленник знает алгоритм! Если это секретно и сбивает с толку, то вы в безопасности, верно? НЕПРАВИЛЬНО . У этой линии мышления есть название: Безопасность через неизвестность , на которую НИКОГДА не следует полагаться.

Лучший путь

Лучший способ - никогда не пускать информацию пользователя с сервера, кроме идентификатора.

Когда пользователь входит в систему, генерирует большой (от 128 до 256 бит) случайный токен. Добавьте это в таблицу базы данных, которая сопоставляет токен с идентификатором пользователя, а затем отправьте его клиенту в файле cookie.

Что если злоумышленник угадает случайный токен другого пользователя?

Хорошо, давайте сделаем немного математики здесь. Мы генерируем 128-битный случайный токен. Это означает, что есть:

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

Теперь, чтобы показать, насколько абсурдно велико это число, давайте представим, что каждый сервер в Интернете (скажем, 50 000 000 сегодня) пытается перебить это число со скоростью 1 000 000 000 в секунду каждый. В действительности ваши серверы будут таять под такой нагрузкой, но давайте поиграем с этим.

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

Итак, 50 квадриллионов догадок в секунду. Это быстро! Правильно?

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

Итак, 6,8 секстиллионных секунд ...

Давайте попробуем свести это к более дружелюбным числам.

215,626,585,489,599 years

Или даже лучше:

47917 times the age of the universe

Да, это 47917 раз возраст вселенной ...

По сути, он не будет взломан.

Итак, подведем итог:

Лучший подход, который я рекомендую, - хранить куки-файл из трех частей.

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

Затем, чтобы проверить:

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

Примечание. Не используйте токен или комбинацию пользователя и токена для поиска записи в вашей базе данных. Обязательно извлекайте запись, основанную на пользователе, и используйте безопасную по времени функцию сравнения для последующего сравнения извлеченного токена. Подробнее о сроках атаки .

Теперь очень важно, чтобы это SECRET_KEYбыл криптографический секрет (генерируемый чем-то вроде /dev/urandomи / или получаемый из входных данных с высокой энтропией). Кроме того, GenerateRandomToken()должен быть сильный случайный источник ( mt_rand()не достаточно сильный. Используйте библиотеку, такую ​​как RandomLib или random_compat , или mcrypt_create_iv()с DEV_URANDOM) ...

Это hash_equals()для предотвращения времени атаки . Если вы используете версию PHP ниже PHP 5.6, функция hash_equals()не поддерживается. В этом случае вы можете заменить hash_equals()его функцией хронометража:

/**
 * 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
Но разве этот подход не означает, что кто-то может взять это имя пользователя и cookie и войти в систему как этот пользователь с любого другого устройства?
проще

8
lol :-), обратите внимание, что 47917 лет - это максимальное время для угадывания, случайный токен может быть угадан и за 1 час.
storm_buster

33
Это странно, потому что ваш код противоречит вашему ответу. Вы говорите: «если вы помещаете пользовательские данные в файл cookie [...], вы делаете что-то не так», но это именно то, что делает ваш код! Не лучше ли удалить имя пользователя из файла cookie, рассчитать хэш только для токена (и, возможно, добавить IP-адрес, чтобы предотвратить кражу файла cookie), а затем выполнить fetchUsernameByToken вместо fetchTokenByUserName в RememberMe ()?
Левен

9
Начиная с PHP 5.6, hash_equals можно использовать для предотвращения временных атак при сравнении строк.
F21

5
@Levit запрещает кому-либо принимать действующий токен и изменять идентификатор пользователя, прикрепленный к нему.
ircmaxell

93

Замечание по безопасности : выводить cookie из хеша MD5 детерминированных данных - плохая идея; Лучше использовать случайный токен, полученный из CSPRNG. Посмотрите ответ ircmaxell на этот вопрос для более безопасного подхода.

Обычно я делаю что-то вроде этого:

  1. Пользователь входит в систему с «держать меня в системе»
  2. Создать сессию
  3. Создайте cookie с именем SOMETHING, содержащим: md5 (salt + username + ip + salt) и cookie с именем someElse, содержащим id
  4. Хранить куки в базе данных
  5. Пользователь делает вещи и уходит ----
  6. Пользователь возвращает, проверяет что-нибудь cookie, если оно существует, получает старый хеш из базы данных для этого пользователя, проверяет, совпадает ли содержимое cookie с хешем из базы данных, который также должен совпадать с недавно вычисленным хешем (для ip) таким образом: cookieHash == databaseHash == md5 (соль + имя пользователя + ip + соль), если они это делают, переходите к 2, если они не переходят к 1

Конечно, вы можете использовать разные имена файлов cookie и т. Д. Также вы можете немного изменить содержимое файла cookie, просто убедитесь, что его нелегко создать. Вы также можете, например, создать user_salt при создании пользователя и добавить его в файл cookie.

Также вы можете использовать sha1 вместо md5 (или почти любой алгоритм)


30
Зачем включать IP в хеш? Кроме того, обязательно включите информацию о временной метке в файл cookie и используйте эту информацию, чтобы установить максимальный возраст для файла cookie, чтобы вы не создавали идентификационный токен, пригодный для вечности.
Скотт Митчелл

4
@Abhishek Dilliwal: Это довольно старая тема, но я наткнулся на нее в поисках того же ответа, что и Мэтью. Я не думаю, что использование session_ID будет работать для ответа Пима, потому что вы не можете проверить хэш db, cookie и текущий session_ID, потому что session_ID меняет каждый session_start (); Просто подумал, что укажу на это.
Partack

3
Извините, что скучно, но какова цель второго печенья что-то еще? Что такое id в этом случае? Является ли это просто своего рода значением «истина / ложь», чтобы указать, хочет ли пользователь вообще использовать функцию «держать меня в системе»? Если так, то почему бы просто не проверить, существует ли НЕЧТО-то печенье? Если пользователь не хочет, чтобы его логин сохранялся, куки-файл SOMETHING не был бы там в первую очередь, верно? Наконец, вы снова генерируете хеш динамически и сравниваете его с cookie и БД в качестве дополнительной меры безопасности?
itsmequinn

4
Токен должен быть СЛУЧАЙНЫМ, никак не связанным с пользователем / его IP-адресом / его пользовательским агентом / чем-либо еще. Это главный недостаток безопасности.
Памил

4
Почему вы используете две соли? md5 (соль + имя пользователя + ip + соль)
Аарон Крейдер

77

Введение

Ваш заголовок «Keep Me Logged In» - лучший подход, из-за которого мне трудно понять, с чего начать, потому что, если вы ищете наилучший подход, вам придется учитывать следующее:

  • Идентификация
  • Безопасность

Печенье

Файлы cookie являются уязвимыми. Между общими уязвимостями кражи файлов cookie браузера и атаками с использованием межсайтовых сценариев мы должны признать, что файлы cookie не являются безопасными. Чтобы улучшить безопасность, вы должны отметить, что php setcookiesимеет дополнительные функции, такие как

bool setcookie (строка $ name [, строка $ value [, int $ expire = 0 [, строка $ path [, строка $ domain [, bool $ secure = false [, bool $ httponly = false]]]]]]]])

  • безопасный (используя соединение HTTPS)
  • httponly (снижение кражи личных данных с помощью XSS-атаки)

Определения

  • Токен (непредсказуемая случайная строка длиной n, например / dev / urandom)
  • Ссылка (непредсказуемая случайная строка длиной n, например, / dev / urandom)
  • Подпись (Генерация ключевого хеш-значения с использованием метода HMAC)

Простой подход

Простое решение будет:

  • Пользователь вошел в систему с Запомнить меня
  • Логин Cookie с токеном и подписью
  • Когда возвращается, подпись проверяется
  • Если Подпись в порядке .. тогда имя пользователя и токен ищутся в базе данных
  • если не действителен .. вернуться на страницу входа
  • Если действителен автоматически, войдите

В приведенном выше примере приведены все примеры, приведенные на этой странице, но их недостатки заключаются в том, что

  • Там нет никакого способа узнать, если куки были украдены
  • Злоумышленник может получить доступ к конфиденциальным операциям, таким как изменение пароля или данных, таких как личная информация и информация о выпечке и т. Д.
  • Скомпрометированный файл cookie будет по-прежнему действителен в течение срока службы файла cookie.

Лучшее решение

Лучшее решение было бы

  • Пользователь вошел в систему и запомнил меня выбран
  • Создать токен и подпись и сохранить в cookie
  • Токены случайны и действительны только для однократной аутентификации.
  • Токен заменяется при каждом посещении сайта
  • Когда незарегистрированный пользователь посещает сайт, подпись, токен и имя пользователя проверяются
  • Помните, что логин должен иметь ограниченный доступ и не позволять изменять пароль, личную информацию и т. Д.

Пример кода

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

Используемый класс

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

Тестирование в Firefox & Chrome

введите описание изображения здесь

преимущество

  • Лучшая безопасность
  • Ограниченный доступ для злоумышленника
  • Когда куки украдены, они действительны только для однократного доступа
  • Когда в следующий раз первоначальный пользователь заходит на сайт, вы можете автоматически обнаруживать и уведомлять пользователя о краже

Недостаток

  • Не поддерживает постоянное соединение через несколько браузеров (Mobile & Web)
  • Файл cookie все еще может быть украден, потому что пользователь получает уведомление только после следующего входа в систему.

Быстрая починка

  • Внедрение системы одобрения для каждой системы, которая должна иметь постоянное соединение
  • Используйте несколько куки для аутентификации

Подход с использованием нескольких файлов cookie

Когда злоумышленник собирается украсть куки, он фокусируется только на определенном веб-сайте или домене, например. example.com

Но на самом деле вы можете аутентифицировать пользователя из 2 разных доменов ( example.com и fakeaddsite.com ) и сделать его похожим на «Рекламное печенье»

  • Пользователь вошел на example.com с помнить меня
  • Хранить имя пользователя, токен, ссылку в cookie
  • Хранить имя пользователя, токен, ссылку в базе данных, например. Memcache
  • Отправить идентификатор ссылки через get и iframe на fakeaddsite.com
  • fakeaddsite.com использует ссылку для получения пользователя и токена из базы данных
  • fakeaddsite.com хранит подпись
  • Когда пользователь возвращает информацию о подписи с помощью iframe от fakeaddsite.com
  • Объедините это данные и сделайте проверку
  • ..... ты знаешь остальное

Некоторые люди могут спросить, как вы можете использовать 2 разных куки? Ну, это возможно, представьте себе example.com = localhostи fakeaddsite.com = 192.168.1.120. Если вы проверяете куки, это будет выглядеть так

введите описание изображения здесь

С картинки выше

  • Текущий посещенный сайт является localhost
  • Он также содержит файлы cookie, установленные с 192.168.1.120

192.168.1.120

  • Принимает только определенные HTTP_REFERER
  • Принимает соединение только от указанного REMOTE_ADDR
  • Нет JavaScript, нет контента, но не содержит ничего, кроме подписи информации и добавления или извлечения ее из cookie

преимущество

  • 99% процентов времени вы обманули атакующего
  • Вы можете легко заблокировать аккаунт злоумышленника с первой попытки
  • Атака может быть предотвращена даже до следующего входа в систему, как и другие методы

Недостаток

  • Несколько запросов к серверу только для одного входа

улучшение

  • Готово использовать iframe ajax

5
Несмотря на то, что @ircmaxell достаточно хорошо описал теорию, я предпочитаю этот подход, так как он отлично работает без необходимости хранить идентификатор пользователя (что было бы нежелательным раскрытием) и также включает в себя больше отпечатков пальцев, чем просто идентификатор пользователя и хэш для идентификации пользователь, такой как браузер. Из-за этого злоумышленнику еще сложнее использовать украденное печенье. Это лучший и самый безопасный подход, который я когда-либо видел. +1
Марчелло Менкемейер

6

Я задал один угол этого вопроса здесь , и ответы будут вести вас ко всему токен на основе тайм-ауте печенье ссылки вам нужно.

По сути, вы не сохраняете userId в куки. Вы храните одноразовый токен (огромную строку), который пользователь использует для получения своего старого сеанса входа в систему. Затем, чтобы сделать его действительно безопасным, вы запрашиваете пароль для тяжелых операций (например, изменение самого пароля).


6

Старый поток, но все еще действительная проблема. Я заметил несколько хороших отзывов о безопасности и об отказе от использования «безопасности через неизвестность», но настоящие технические методы были недостаточны в моих глазах. Вещи, которые я должен сказать, прежде чем внести свой метод:

  • НИКОГДА не храните пароль открытым текстом ... НИКОГДА !
  • НИКОГДА храните хешированный пароль пользователя в нескольких местах в вашей базе данных. Серверная часть вашего сервера всегда способна извлечь хешированный пароль из таблицы пользователей. Не более эффективно хранить избыточные данные вместо дополнительных транзакций с БД, обратное верно.
  • Идентификаторы вашего сеанса должны быть уникальными, поэтому никакие два пользователя никогда не смогут совместно использовать идентификатор, поэтому назначение идентификатора (может ли ваш номер водительского удостоверения соответствовать другим лицам? Нет.) При этом создается уникальная комбинация из двух частей, основанная на 2 уникальные строки. Ваша таблица Sessions должна использовать идентификатор в качестве PK. Чтобы разрешить нескольким устройствам доверять для автоматической регистрации, используйте другую таблицу для доверенных устройств, которая содержит список всех проверенных устройств (см. Мой пример ниже) и отображается с использованием имени пользователя.
  • Нет смысла хэшировать известные данные в куки, куки могут быть скопированы. То, что мы ищем, это соответствующее пользовательское устройство, которое предоставляет достоверную информацию, которую невозможно получить без того, чтобы злоумышленник не скомпрометировал компьютер пользователя (опять же, см. Мой пример). Это будет означать, однако, что законный пользователь, который запрещает статическую информацию своего компьютера (например, MAC-адрес, имя хоста устройства, идентификатор пользователя, если он ограничен браузером и т. Д.), Остается непротиворечивым (или подделывает его в первую очередь), не сможет используйте эту функцию. Но если это вызывает озабоченность, учтите тот факт, что вы предлагаете автоматическую подписку пользователям, которым идентифицируют себя уникальнопоэтому, если они отказываются быть известными путем подмены своего MAC, подмены своего идентификатора пользователя, подмены / изменения своего имени хоста, скрытия за прокси и т. д., то они не могут быть идентифицированы и никогда не должны проходить аутентификацию для автоматической службы. Если вы хотите этого, вам нужно изучить доступ к смарт-картам в комплекте с программным обеспечением на стороне клиента, которое устанавливает идентичность используемого устройства.

Тем не менее, есть два отличных способа автоматического входа в систему.

Во-первых, дешевый, легкий способ, который ставит все это на кого-то другого. Если вы поддерживаете вход на свой сайт, например, с помощью своего аккаунта google +, возможно, у вас есть упрощенная кнопка google +, которая будет входить в систему, если пользователи уже вошли в google (я сделал это здесь, чтобы ответить на этот вопрос, как и всегда вошел в гугл). Если вы хотите, чтобы пользователь автоматически входил в систему, если он уже вошел с помощью доверенного и поддерживаемого средства проверки подлинности, и поставил флажок, чтобы сделать это, пусть ваши клиентские скрипты перед загрузкой выполняют код, соответствующий соответствующей кнопке «Вход в систему». просто убедитесь, что сервер хранит уникальный идентификатор в таблице автоматического входа, который содержит имя пользователя, идентификатор сеанса и аутентификатор, используемый для пользователя. Поскольку эти методы входа используют AJAX, вы все равно ждете ответа, и этот ответ является либо подтвержденным ответом, либо отклонением. Если вы получили проверенный ответ, используйте его как обычно, затем продолжите загрузку зарегистрированного пользователя как обычно. В противном случае вход в систему не удастся, но не говорите пользователю, просто продолжайте, не входя в систему, они заметят. Это сделано для того, чтобы злоумышленник, который украл куки (или подделал их в попытке повысить привилегии), не узнал, что пользователь автоматически выполняет вход на сайт.

Это дешево, и некоторые также могут посчитать его грязным, потому что оно пытается проверить вашу потенциально уже зарегистрированную личность в таких местах, как Google и Facebook, даже не сказав вам об этом. Однако его не следует использовать для пользователей, которые не запрашивали автоматическую регистрацию на вашем сайте, и этот конкретный метод предназначен только для внешней аутентификации, как в Google+ или FB.

Поскольку внешний аутентификатор использовался для скрытого информирования сервера о том, прошел ли пользователь проверку, злоумышленник не может получить ничего, кроме уникального идентификатора, который сам по себе бесполезен. Я уточню:

  • Пользователь 'joe' впервые заходит на сайт, идентификатор сессии помещен в cookie-файл 'session'.
  • Пользователь 'joe' Вход в систему, повышение привилегий, получение нового идентификатора сеанса и обновление cookie-сеанса.
  • Пользователь "Джо" выбирает автоматическую регистрацию с помощью Google +, получает уникальный идентификатор, помещенный в cookie "keepmesignedin".
  • Пользователь "Джо" имеет Google держать их в системе, что позволяет вашему сайту автоматически входить в систему с помощью Google в вашем бэкэнде.
  • Атакующий систематически пытается использовать уникальные идентификаторы для «keepmesignedin» (это общедоступные знания, которые раздаются каждому пользователю), и нигде не подписывается; пытается уникальный идентификатор, присвоенный «Джо».
  • Сервер получает уникальный идентификатор для «Джо», извлекает совпадения в БД для учетной записи Google +.
  • Сервер отправляет Attacker на страницу входа, на которой выполняется запрос AJAX для входа в Google.
  • Сервер Google получает запрос, использует свой API, чтобы увидеть, что Attacker не вошел в систему.
  • Google отправляет ответ, что в данный момент нет подключенного пользователя через это соединение.
  • Страница злоумышленника получает ответ, скрипт автоматически перенаправляет на страницу входа со значением POST, закодированным в URL.
  • Страница входа получает значение POST, отправляет файл cookie для 'keepmesignedin' на пустое значение и действует до даты 1-1-1970 для предотвращения автоматической попытки, в результате чего браузер атакующего просто удаляет файл cookie.
  • Атакующий получает нормальную страницу входа в первый раз.

Независимо от того, что, даже если злоумышленник использует идентификатор, который не существует, попытка должна завершиться неудачей на всех попытках, кроме тех случаев, когда получен проверенный ответ.

Этот метод может и должен использоваться вместе с вашим внутренним аутентификатором для тех, кто входит на ваш сайт с помощью внешнего аутентификатора.

=========

Теперь, для вашей собственной системы аутентификации, которая может автоматически подписывать пользователей, вот как я это делаю:

БД имеет несколько таблиц:

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

Обратите внимание, что имя пользователя может быть длиной 255 символов. Моя серверная программа ограничивает имена пользователей в моей системе 32 символами, но внешние аутентификаторы могут иметь имена пользователей с их @ domain.tld больше этого значения, поэтому я просто поддерживаю максимальную длину адреса электронной почты для максимальной совместимости.

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

Обратите внимание, что в этой таблице нет поля пользователя, поскольку имя пользователя при входе в систему находится в данных сеанса, и программа не допускает нулевые данные. Идентификатор session_id и session_token могут быть сгенерированы с использованием случайных хэшей md5, хэшей sha1 / 128/256, меток даты и времени со случайными строками, добавленными к ним, затем хэшированными или как вам угодно, но энтропия вашего вывода должна оставаться настолько высокой, насколько допустимо для предотвращать атаки методом "грубой силы", даже отрываясь от земли, и все хеши, сгенерированные вашим классом сеансов, должны быть проверены на совпадения в таблице сеансов, прежде чем пытаться их добавить.

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-адреса по своей природе должны быть УНИКАЛЬНЫМИ, поэтому имеет смысл, что каждая запись имеет уникальное значение. Имена хостов, с другой стороны, могут быть законно дублированы в отдельных сетях. Сколько людей используют «Домашний ПК» в качестве одного из имен своих компьютеров? Имя пользователя берется из данных сеанса серверным бэкэндом, поэтому манипулирование им невозможно. Что касается токена, тот же метод генерации токенов сеанса для страниц должен использоваться для генерации токенов в файлах cookie для автоматической регистрации пользователя. Наконец, добавляется код даты и времени, когда пользователю необходимо будет подтвердить свои учетные данные. Либо обновите эту дату-время при входе пользователя в систему, сохраняя его в течение нескольких дней, либо вынудите его истечь, независимо от того, какой последний вход сохранил его, в течение месяца или около того, в зависимости от того, что диктует ваш дизайн.

Это препятствует тому, чтобы кто-то систематически подделывал MAC и имя хоста для пользователя, которого он знает, выполняет автоматический вход. НИКОГДАпопросите пользователя сохранить cookie со своим паролем, открытым текстом или другим способом. Регенерируйте токен на каждой странице навигации так же, как токен сеанса. Это значительно снижает вероятность того, что злоумышленник сможет получить действительный токен-cookie и использовать его для входа в систему. Некоторые люди пытаются сказать, что злоумышленник может украсть файлы cookie у жертвы и выполнить атаку воспроизведения сеанса для входа в систему. Если бы злоумышленник мог украсть куки (что возможно), он, несомненно, скомпрометировал бы все устройство, то есть он мог бы просто использовать устройство для входа в систему в любом случае, что полностью противоречит цели кражи куки. Пока ваш сайт работает по протоколу HTTPS (что должно происходить при работе с паролями, номерами CC или другими системами входа в систему), вы предоставляете пользователю всю защиту, которую вы можете использовать в браузере.

Следует помнить одну вещь: данные сеанса не должны истекать, если вы используете автоподпись. Вы можете прервать возможность ложного продолжения сеанса, но проверка в системе должна возобновить данные сеанса, если это постоянные данные, которые, как ожидается, будут продолжаться между сеансами. Если вам нужны как постоянные, так и непостоянные данные сеанса, используйте другую таблицу для постоянных данных сеанса с именем пользователя в качестве PK и попросите сервер извлечь их, как если бы это были обычные данные сеанса, просто используйте другую переменную.

После того, как логин получен таким способом, сервер все равно должен проверить сеанс. Здесь вы можете кодировать ожидания для украденных или скомпрометированных систем; паттерны и другие ожидаемые результаты входа в сеансовые данные часто могут привести к выводам о том, что система была взломана или файлы cookie были подделаны для получения доступа. Именно здесь ваш специалист по ISS может установить правила, которые будут инициировать блокировку учетной записи или автоматическое удаление пользователя из системы автоматического входа, не позволяя злоумышленникам оставаться на достаточно долгое время, чтобы пользователь мог определить, как злоумышленник добился успеха и как его отключить.

В качестве заключительного примечания убедитесь, что любая попытка восстановления, изменения пароля или ошибки входа в систему после превышения порогового значения приводят к отключению автоматической регистрации до тех пор, пока пользователь не подтвердит правильность и не подтвердит, что это произошло.

Я прошу прощения, если кто-то ожидал, что код будет выдан в моем ответе, этого не произойдет Я скажу, что я использую PHP, jQuery и AJAX для запуска своих сайтов, и я НИКОГДА не использую Windows как сервер ... никогда.


5

Я бы порекомендовал подход, упомянутый Стефаном (т. Е. Следуйте рекомендациям в разделе «Советы и рекомендации по использованию файлов cookie для улучшенного постоянного входа в систему» ), а также рекомендую убедиться, что ваши файлы cookie являются файлами cookie HttpOnly, поэтому они недоступны для потенциально вредоносного JavaScript.


4

Создайте хеш, возможно, с секретом, который вам известен, а затем сохраните его в своей БД, чтобы он мог быть связан с пользователем. Должно работать хорошо.


Будет ли это уникальный идентификатор, который создается при создании пользователя, или он будет меняться каждый раз, когда пользователь генерирует новый файл cookie «Keep Me Logged In»?
Мэтью

1
Ответ Тима Янссона описывает хороший подход к созданию хэша, хотя я бы чувствовал себя безопаснее, если бы он не содержал пароль
Яни Хартикайнен

2

Мое решение таково. Это не на 100% пуленепробиваемый, но я думаю, что это спасет вас в большинстве случаев.

Когда пользователь успешно вошел в систему, создайте строку с этой информацией:

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

Зашифровать $data, установить тип для HttpOnly и установить cookie.

Когда пользователь вернется на ваш сайт, выполните следующие действия:

  1. Получить данные cookie. Удалить опасные символы внутри cookie. Взорвать его с :характером.
  2. Проверьте правильность. Если cookie старше X дней, перенаправьте пользователя на страницу входа.
  3. Если cookie не старый; Получить последнее время смены пароля из базы данных. Если пароль изменился после последнего входа пользователя, перенаправьте пользователя на страницу входа.
  4. Если проход не был изменен в последнее время; Получить текущий браузер агента пользователя. Проверьте, является ли (currentUserAgentHash == cookieUserAgentHash). Если агенты такие же, переходите к следующему шагу, иначе перенаправьте на страницу входа.
  5. Если все шаги пройдены успешно, авторизуйтесь.

Если пользователь выходит из системы, удалите этот файл cookie. Создайте новый файл cookie, если пользователь повторно войдет в систему.


2

Я не понимаю концепции хранения зашифрованных файлов в cookie-файлах, когда вам нужна взломанная версия. Если я что-то упустил, пожалуйста, прокомментируйте.

Я думаю о том, чтобы применить этот подход к «Помни меня». Если вы видите какие-либо проблемы, пожалуйста, прокомментируйте.

  1. Создайте таблицу для хранения данных «Запомнить меня» - отдельно от пользовательской таблицы, чтобы я мог войти с нескольких устройств.

  2. При успешном входе в систему (с пометкой «Запомнить меня»):

    a) Сгенерируйте уникальную случайную строку, которая будет использоваться в качестве UserID на этом компьютере: bigUserID

    б) генерировать уникальную случайную строку: bigKey

    c) Хранить куки: bigUserID: bigKey

    г) В таблицу «Запомнить меня» добавьте запись с: UserID, IP Address, bigUserID, bigKey

  3. Если вы пытаетесь получить доступ к тому, что требует входа в систему:

    а) Проверьте наличие cookie и найдите bigUserID & bigKey с соответствующим IP-адресом

    б) Если вы найдете его, войдите в систему, но установите флажок в пользовательской таблице «soft login», чтобы при любых опасных операциях вы могли запрашивать полный вход в систему.

  4. При выходе отметьте все записи «Запомнить меня» для этого пользователя как просроченные.

Единственные уязвимости, которые я вижу, это;

  • Вы можете получить чей-то ноутбук и подделать его IP-адрес с помощью cookie.
  • Вы могли бы каждый раз подделывать разные IP-адреса и угадывать все это - но с двумя большими строками, чтобы соответствовать, это было бы ... делать вычисления, аналогичные приведенным выше ... Я понятия не имею ... огромные шансы?

Здравствуйте, и спасибо за этот ответ, мне это нравится. Один вопрос: почему вы должны генерировать 2 случайные строки - bigUserID & bigKey? Почему бы вам не сгенерировать только 1 и использовать его?
Джереми Белоло

2
Срок действия ключа bigKey истекает через заданный промежуток времени, а bigUserID - нет. bigUserID позволяет вам иметь несколько сеансов на разных устройствах с одним IP-адресом. Надеюсь, что это имеет смысл - мне пришлось немного подумать :)
Enigma Plus

В этом может помочь hmac: если вы обнаружили, что hmac был подделан, вы наверняка знаете, что кто-то пытался украсть cookie, тогда вы можете сбросить каждое состояние входа в систему. Я прав?
Сурадж Джейн

2

Я прочитал все ответы и все еще находил трудным извлечь то, что я должен был сделать. Если картинка стоит 1 тыс. Слов, я надеюсь, что это поможет другим внедрить безопасное постоянное хранилище на основе передового опыта использования файлов cookie для улучшенного постоянного входа Барри Джаспана.

введите описание изображения здесь

Если у вас есть вопросы, отзывы или предложения, я постараюсь обновить диаграмму, чтобы учесть новичка, пытающегося внедрить безопасный постоянный вход в систему.


0

Реализация функции «Keep Me Logged In» означает, что вам необходимо точно определить, что это будет означать для пользователя. В простейшем случае я бы использовал это, чтобы означать, что время сеанса намного больше: 2 дня (скажем) вместо 2 часов. Для этого вам понадобится ваше собственное хранилище сеансов, возможно, в базе данных, поэтому вы можете установить время истечения срока действия для данных сеанса. Затем вам нужно убедиться, что вы установили cookie-файл, который будет храниться в течение нескольких дней (или дольше), а не истекает при закрытии браузера.

Я слышу, как вы спрашиваете: «Почему 2 дня? Почему не 2 недели?». Это потому, что использование сессии в PHP автоматически отодвигает срок действия обратно. Это связано с тем, что истечение срока сеанса в PHP на самом деле является временем простоя.

Теперь, сказав это, я бы, вероятно, реализовал более жесткое значение тайм-аута, которое я храню в самом сеансе и примерно через 2 недели, и добавил бы код, чтобы увидеть это и принудительно аннулировать сеанс. Или, по крайней мере, выйти из них. Это будет означать, что пользователю будет предложено периодически входить в систему. Yahoo! Является ли это.


1
Установка более длинного сеанса, вероятно, является плохой, потому что она тратит ресурсы сервера и отрицательно сказывается на производительности
user3091530

0

Я думаю, вы могли бы просто сделать это:

$cookieString = password_hash($username, PASSWORD_DEFAULT);

хранить $cookiestring в БД и установите его как cookie. Также установите имя пользователя человека в виде куки. Весь смысл хэша в том, что его нельзя перепроектировать.

Когда появляется пользователь, получите имя пользователя из одного файла cookie, а не $cookieStringиз другого. Если $cookieStringсовпадает с тем, что хранится в БД, то пользователь проходит аутентификацию. Так как password_hash каждый раз использует различную соль, не имеет значения, что такое открытый текст.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.