"Тримати мене в системі" - найкращий підхід


257

Моя веб-програма використовує сеанси для зберігання інформації про користувача після входу в систему та для підтримки цієї інформації під час переходу зі сторінки на сторінку в додатку. У цій конкретній програмі я зберігаю і особи user_id, first_nameі last_nameлюдину.

Я хотів би запропонувати ввійти в систему опцію "Тримати мене ввімкненою", яка на два тижні помістить файл cookie на машині користувача, який відновить їх сеанс з тими ж деталями, коли вони повернуться в додаток.

Який найкращий підхід для цього? Я не хочу зберігати їх user_idу файлі cookie, оскільки, схоже, це полегшило б одному користувачеві спробувати і підробити особу іншого користувача.

Відповіді:


735

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

Там. Я це сказав. Тепер ми можемо перейти до фактичної відповіді.

Що не так у хешировании даних користувачів, запитаєте ви? Ну, це зводиться до поверхні експозиції та безпеки через невідомість.

Уявіть на секунду, що ви нападник. На вашому сеансі ви бачите набір криптографічних файлів cookie для пам'яті. Це 32 символи в ширину. Гі. Це може бути MD5 ...

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

md5(salt+username+ip+salt)

Тепер все, що зловмисник повинен зробити, це жорстока сила "солі" (що насправді не є сіллю, але про це пізніше пізніше), і він тепер може генерувати всі підроблені жетони, які він хоче, з будь-яким ім'ям користувача для своєї IP-адреси! Але жорстоко примушувати сіль важко, правда? Абсолютно. Але сучасні графічні процесори надзвичайно хороші в цьому. І якщо ви не використовуєте в ньому достатню випадковість (зробіть її досить великою), вона швидко впаде, а разом із нею і ключі від вашого замку.

Коротше кажучи, єдине, що захищає вас, - це сіль, яка насправді не захищає вас стільки, скільки ви думаєте.

Але зачекайте!

Все це передбачало, що зловмисник знає алгоритм! Якщо це секретно і заплутано, то ви в безпеці, правда? НЕПРАВИЛЬНО . Цей напрямок мислення має назву: Безпека через невміння , на яку НІКОЛИ не слід покладатися.

Кращий шлях

Кращий спосіб - ніколи не дозволяти інформації користувача залишати сервер, крім ідентифікатора.

Коли користувач входить у систему, генеруйте великий (128 - 256 біт) випадковий маркер. Додайте це до таблиці бази даних, яка відображає маркер на userid, а потім надішліть його клієнту в файлі 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()функцію timingSafeCompare:

/**
 * A timing safe equals comparison
 *
 * To prevent leaking length information, it is important
 * that user input is always used as the second parameter.
 *
 * @param string $safe The internal (safe) value to be checked
 * @param string $user The user submitted (unsafe) value
 *
 * @return boolean True if the two strings are identical.
 */
function timingSafeCompare($safe, $user) {
    if (function_exists('hash_equals')) {
        return hash_equals($safe, $user); // PHP 5.6
    }
    // Prevent issues if string length is 0
    $safe .= chr(0);
    $user .= chr(0);

    // mbstring.func_overload can make strlen() return invalid numbers
    // when operating on raw binary strings; force an 8bit charset here:
    if (function_exists('mb_strlen')) {
        $safeLen = mb_strlen($safe, '8bit');
        $userLen = mb_strlen($user, '8bit');
    } else {
        $safeLen = strlen($safe);
        $userLen = strlen($user);
    }

    // Set the result to the difference between the lengths
    $result = $safeLen - $userLen;

    // Note that we ALWAYS iterate over the user-supplied length
    // This is to prevent leaking length information
    for ($i = 0; $i < $userLen; $i++) {
        // Using % here is a trick to prevent notices
        // It's safe, since if the lengths are different
        // $result is already non-0
        $result |= (ord($safe[$i % $safeLen]) ^ ord($user[$i]));
    }

    // They are only identical strings if $result is exactly 0...
    return $result === 0;
}

7
Але чи не означає такий підхід, що хтось може скористатись цим ім’ям користувача та файлом cookie та увійти як цей користувач з будь-якого іншого пристрою?
Простіше

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

33
Це дивно, тому що ваш код суперечить вашій відповіді. Ви говорите: "якщо ви вводите дані користувачів у файл cookie [...], ви робите щось не так", але саме це робить ваш код! Хіба не краще видалити ім’я користувача з файлу cookie, обчислити хеш лише через маркер (і, можливо, додати ip адресу, щоб запобігти крадіжці файлів cookie), а потім зробити fetchUsernameByToken замість fetchTokenByUserName в RememberMe ()?
Левен

9
Оскільки PHP 5.6, hash_equals можна використовувати для запобігання тимчасових атак при порівнянні рядків.
F21

5
@Levit забороняє комусь брати дійсний маркер і змінювати доданий до нього userid.
ircmaxell

93

Повідомлення про безпеку : використання файлу cookie від хешу MD5 детермінованих даних є поганою ідеєю; краще використовувати випадковий маркер, отриманий із CSPRNG. Дивіться відповідь ircmaxell на це питання для більш безпечного підходу.

Зазвичай я роблю щось подібне:

  1. Користувач увійде в систему за допомогою "тримати мене в системі"
  2. Створіть сеанс
  3. Створіть файл cookie під назвою SOMETHING, що містить: md5 (сіль + ім’я користувача + ip + сіль) та файл cookie, який називається щосьElse містить id
  4. Зберігати файли cookie в базі даних
  5. Користувач робить речі та залишає ----
  6. Користувач повертається, перевіряє щось cookieElse, якщо він існує, дістаньте старий хеш із бази даних для цього користувача, перевірте вміст файлу cookie SOMETHING, який відповідає хешу з бази даних, який також повинен відповідати новоспеченому хешу (для ip) таким чином: cookieHash == databaseHash == md5 (сіль + ім'я користувача + ip + сіль), якщо вони є, goto 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
Вибачте, що я тьмяний, але яка мета другого печива? Що в цьому випадку ідентично? Це просто просте значення "true / false", щоб вказати, чи хоче користувач взагалі використовувати функцію "підтримувати мене"? Якщо так, то чому б не просто перевірити, чи існує файл cookie SOMETHING в першу чергу? Якби користувач не хотів, щоб їх логін зберігався, файл cookie SOMETHING не був би там в першу чергу так? Нарешті, ви знову динамічно генеруєте хеш і перевіряєте його на файлі cookie та БД як додатковий захід безпеки?
itsmequinn

4
Токен повинен бути RANDOM, не пов’язаний з користувачем / його IP / його користувачем / будь-яким чином. Це головний недолік безпеки.
памил

4
Для чого ви використовуєте дві солі? md5 (сіль + ім’я користувача + ip + сіль)
Аарон Крейдер

77

Вступ

Ваш заголовок "Тримай мене в системі" - найкращий підхід утрудняє мене з того, з чого почати, тому що якщо ти шукаєш найкращий підхід, то вам доведеться врахувати наступне:

  • Ідентифікація
  • Безпека

Печиво

Файли cookie є вразливими, між поширеними вразливими місцями розкрадання файлів cookie та атаками сценаріїв між сайтом, ми повинні визнати, що файли cookie не є безпечними. Щоб покращити безпеку, потрібно зауважити, що він php setcookiesмає додаткові функціональні можливості, такі як

bool setcookie (рядок $ name [, рядок $ value [, int $ expire = 0 [, рядок $ path [, рядок $ домен [, 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

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

Перевага

  • Краща безпека
  • Обмежений доступ для нападника
  • При вкраденні файлу cookie діє лише для єдиного доступу
  • Після наступного доступу оригінальний користувач на сайт ви зможете автоматично виявити та повідомити користувача про крадіжку

Недоліки

  • Не підтримує постійне з'єднання через кілька веб-переглядачів (для мобільних пристроїв та веб)
  • Файли cookie все ще можуть бути викрадені, оскільки користувач отримує сповіщення лише після наступного входу.

Швидке виправлення

  • Впровадження системи затвердження для кожної системи, яка повинна мати постійне з'єднання
  • Використовуйте кілька файлів cookie для аутентифікації

Багаторазовий підхід до печива

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

Але насправді ви можете автентифікувати користувача з двох різних доменів ( example.com та fakeaddsite.com ) і зробити його схожим на "Рекламне cookie"

  • Користувач увійшов на сторінку example.com, запам'ятавши мене
  • Зберігайте ім’я користувача, маркер, посилання у файлі cookie
  • Зберігати ім'я користувача, маркер, посилання в базі даних, наприклад. Memcache
  • Надішліть ідентифікатор реферату через get та iframe на fakeaddsite.com
  • fakeaddsite.com використовує посилання для отримання користувача та маркера з бази даних
  • fakeaddsite.com зберігає підпис
  • Коли користувач повертає інформацію про підписи за допомогою iframe з fakeaddsite.com
  • Об’єднайте ці дані та зробіть перевірку
  • ..... ви знаєте решту

Деякі люди можуть задатися питанням, як можна використовувати два різних файли cookie? Ну можливо, уявіть example.com = localhostі fakeaddsite.com = 192.168.1.120. Якщо ви оглянете файли cookie, це виглядатиме так

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

З наведеного зображення

  • Поточний відвідуваний сайт - localhost
  • Він також містить файли cookie, встановлені з 192.168.1.120

192.168.1.120

  • Лише приймає визначені HTTP_REFERER
  • Лише приймає з'єднання із вказаного REMOTE_ADDR
  • Немає JavaScript, не вміст, але нічого не складається, аніж підписувати інформацію та додавати або отримувати її з файлу cookie

Перевага

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

Недоліки

  • Кілька запитів на сервер лише за один вхід

Поліпшення

  • Виконано використання iframe ajax

5
Хоча @ircmaxell досить добре описав цю теорію, я віддаю перевагу такому підходу, оскільки він відмінно працює без необхідності зберігання ідентифікатора користувача (що було б небажаним розкриттям), а також включає більше відбитків пальців, ніж просто ідентифікатор користувача та хеш для ідентифікації користувач, наприклад браузер. Це ще більше ускладнює зловмиснику використання вкраденого файлу cookie. Це найкращий і найбезпечніший підхід, який я бачив досі. +1
Marcello Mönkemeyer

6

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

В основному, ви не зберігаєте userId у файлі cookie. Ви зберігаєте одноразовий маркер (величезний рядок), який користувач використовує для підбору свого старого сеансу входу. Потім, щоб зробити це дійсно безпечним, ви запитаєте пароль для важких операцій (наприклад, зміни самого пароля).


6

Стара нитка, але все ж дійсна турбота. Я помітив кілька хороших відповідей щодо безпеки та уникнення використання "безпеки через невідомість", але фактичні технічні методи, надіслані в моїх очах, були недостатніми. Що я повинен сказати, перш ніж внести свій внесок у метод:

  • НІКОЛИ не зберігайте пароль чітким текстом ... ВСЕ!
  • НІКОЛИ не зберігайте хешований пароль користувача у більш ніж одному місці вашої бази даних. Ваш серверний сервер завжди може витягнути хешований пароль із таблиці користувачів. Зберігати зайві дані замість додаткових транзакцій БД не є більш ефективним, зворотне - це правда.
  • Ваш ідентифікатор сеансу повинен бути унікальним, так що ніякі два користувача не могли коли - або поділитися ID, отже , призначення ідентифікатора (може Ліцензія ідентифікаційний номер вашого водія коли - або відповідати іншим особам? Ні . ) Це створює двосекційний унікальне поєднання, засноване на 2 унікальні струни. Ваша таблиця сеансів повинна використовувати ідентифікатор як ПК. Щоб дозволити довіряти декілька пристроїв для автоматичного входу, використовуйте іншу таблицю для надійних пристроїв, яка містить список усіх перевірених пристроїв (див. Мій приклад нижче) та відображається за допомогою імені користувача.
  • Він не призначений для хешування відомих даних у файл cookie, файл cookie можна скопіювати. Ми шукаємо відповідний користувальницький пристрій для надання автентичної інформації, яку неможливо отримати, якщо зловмисник не загрожує машині користувача (ще раз див. Мій приклад). Це означатиме, однак, що законний користувач, який забороняє статичну інформацію про свою машину (тобто MAC-адресу, ім'я хоста пристрою, useragent, якщо обмежений браузером тощо), залишатися послідовним (або в першу чергу підробляє його) не зможе використовувати цю функцію. Але якщо це викликає занепокоєння, врахуйте той факт, що ви пропонуєте автоматичне вхід користувачам, які ідентифікують себе однозначно, тому, якщо вони відмовляються бути відомими, підробляючи MAC, підробляючи їх засіб, підробляючи / змінюючи ім'я хоста, ховаючись за проксі-серверами тощо, вони не піддаються ідентифікації та ніколи не повинні бути автентифіковані для автоматичного обслуговування. Якщо ви цього хочете, вам потрібно ознайомитися з доступом до смарт-карт у комплекті з програмним забезпеченням на стороні клієнта, який встановлює особу використовуваного пристрою.

Незважаючи на це, є два чудових способи автоматичного входу в систему.

По-перше, дешевий, простий спосіб, який ставить усе це на когось іншого. Якщо ви зробите підтримку свого веб-сайту входом у систему, скажімо, своїм обліковим записом google +, у вас, ймовірно, є спрощена кнопка google +, яка ввійде користувач, якщо він уже ввійшов у google (я це зробив тут, щоб відповісти на це запитання, як я завжди увійшли в google). Якщо ви хочете, щоб користувач автоматично входив, якщо він уже входив з довіреним і підтримуваним автентифікатором, і встановив прапорець, щоб ваші сценарії на стороні клієнта виконували код за відповідною кнопкою "вхід з" перед завантаженням , не забудьте, щоб сервер зберігав унікальний ідентифікатор у таблиці автоматичного входу, що містить ім’я користувача, ідентифікатор сесії та автентифікатор, що використовується для користувача. Оскільки ці методи входу використовують AJAX, ви все одно чекаєте відповіді, і ця відповідь є або підтвердженою відповіддю, або відхиленням. Якщо ви отримали підтверджену відповідь, використовуйте її як звичайну, а потім продовжуйте завантажувати зареєстрованого користувача як звичайного. Інакше вхід не вдався, але не повідомляйте користувачеві, просто продовжуйте, як не входили в систему, вони помітять. Це робиться для того, щоб зловмисник, який викрав файли cookie (або підробив їх у спробі ескалації привілеїв), не дізнався, що користувач автоматично підписується на сайт.

Це дешево, а хтось також може вважати його брудним, оскільки він намагається перевірити потенційно вже зареєстровану себе в таких місцях, як Google та Facebook, навіть не повідомивши вам. Однак він не повинен використовуватися для користувачів, які не просили авторизуватися на вашому веб-сайті, і цей конкретний метод застосовується лише для зовнішньої аутентифікації, як, наприклад, у Google+ або FB.

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

  • Користувач "joe" відвідує сайт вперше, ідентифікатор сесії розміщений у файлі cookie "сеанс".
  • Користувач 'joe' входить у систему, збільшує привілеї, отримує новий ідентифікатор сесії та відновлює 'сеанс cookie'.
  • Користувач 'joe' обирає автоматичний вхід за допомогою Google +, отримує унікальний ідентифікатор, розміщений у файлі cookie 'Keepmesignedin'.
  • Користувач 'joe' має google підтримувати їх увімкнутими, що дозволяє вашому сайту автоматично входити в систему користувача, використовуючи google у вашому бекенді.
  • Зловмисник систематично пробує унікальні ідентифікатори для "Keepmesignedin" (це загальнодоступні знання, що роздаються кожному користувачеві), і не входить ніде більше; намагається унікальний ідентифікатор, наданий 'joe'.
  • Сервер отримує унікальний ідентифікатор для 'joe', тягне відповідність у БД для облікового запису google +.
  • Сервер надсилає Attacker на сторінку входу, яка виконує запит AJAX, щоб Google входив у систему.
  • Сервер Google отримує запит, використовує його API, щоб побачити, що Нападник наразі не входить у систему.
  • Google надсилає відповідь про те, що наразі користувач не ввійшов через цей зв’язок.
  • Сторінка зловмисника отримує відповідь, сценарій автоматично перенаправляє на сторінку входу зі значенням POST, закодованим у URL-адресі.
  • Сторінка входу отримує значення POST, надсилає файл cookie для 'Keepmesignedin' в порожнє значення та діє до дати 1-1-1970 для запобігання автоматичній спробі, внаслідок чого браузер Attacker просто видаляє файл 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 у жертви і зробити сеанс повторної атаки для входу. Якщо зловмисник міг би вкрасти файли cookie (що можливо), вони, безумовно, поставили б під загрозу весь пристрій, це означає, що вони могли просто використовувати пристрій для входу в будь-який момент, що перешкоджає цілі крадіжки файлів cookie. Поки ваш сайт працює над HTTPS (що він повинен мати при роботі з паролями, номерами CC або іншими системами входу), ви забезпечили користувачеві весь захист, який ви можете мати в браузері.

Слід пам’ятати: дані сеансу не повинні закінчуватися, якщо ви використовуєте автоматичний вхід. Ви можете закінчити можливість помилкового продовження сеансу, але перевірка в системі повинна відновити дані сеансу, якщо це стійкі дані, які, як очікується, продовжуватимуться між сесіями. Якщо ви хочете отримувати як стійкі, так і непостійні дані сеансу, використовуйте іншу таблицю для постійних даних сеансу з іменем користувача як ПК, а сервер повинен отримати його так, як це були б звичайні дані сеансу, просто використовуйте іншу змінну.

Після того, як реєстрація буде досягнута таким чином, сервер все ще повинен перевірити сеанс. Тут ви можете кодувати очікування для викрадених або порушених систем; шаблони та інші очікувані результати входу в дані сеансу часто можуть призвести до висновків про те, що система була викрадена або підроблені файли cookie, щоб отримати доступ. Тут ваша ISS Tech може встановити правила, які спричинить блокування облікового запису або автоматичне видалення користувача з системи автоматичного входу, утримуючи зловмисників достатньо довго, щоб користувач міг визначити, як зловмисник досяг успіху, і як їх знищити.

Як завершальне зауваження, будьте впевнені, що будь-яка спроба відновлення, зміна пароля або помилка входу через поріг призводять до відключення автоматичного входу до тих пір, поки користувач не перевірить належним чином і не підтвердить, що це сталося.

Прошу вибачення, якщо хтось очікував, що у моїй відповіді буде видано код, це не відбудеться тут. Я скажу, що я використовую PHP, jQuery і AJAX для запуску своїх сайтів, і я НІКОЛИ не використовую Windows як сервер ... ніколи.


5

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


4

Створіть хеш, можливо, з відомим вам секретом, а потім зберігайте його у вашій БД, щоб він міг бути пов’язаний з користувачем. Має працювати досить добре.


Це буде унікальний ідентифікатор, який створюється під час створення користувача, чи він змінюватиметься кожного разу, коли користувач генерує нове cookie "Тримати мене в системі"?
Метью

1
Відповідь Тіма Янссона описує хороший підхід до створення хешу, хоча я б почувався безпечнішим, якщо він не включив пароль
Jani Hartikainen

2

Моє рішення таке. Це не 100% куленепроникність, але я думаю, що це врятує вас у більшості випадків.

Коли користувач успішно ввійшов, створіть рядок із цією інформацією:

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

Шифрування $data, набір типу для HttpOnly і набору печива.

Коли користувач повернеться на ваш сайт, зробіть наступні дії:

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

Якщо користувач підписався, видаліть це cookie. Створіть новий файл cookie, якщо користувач повторно ввійде в систему.


2

Я не розумію концепції зберігання зашифрованих речей у файлі cookie, коли вам потрібно зробити хакерську версію. Якщо я щось пропускаю, будь ласка, прокоментуйте.

Я думаю про такий підхід до «Запам'ятай мене». Якщо ви можете побачити будь-які проблеми, будь ласка, прокоментуйте.

  1. Створіть таблицю для зберігання даних "Запам'ятати мене" - окремо до таблиці користувачів, щоб я міг увійти з декількох пристроїв.

  2. Після успішного входу (з позначкою "Запам'ятати мене"):

    a) Створіть унікальну випадкову рядок, який буде використовуватись як UserID на цій машині: bigUserID

    б) Створити унікальний випадковий рядок: bigKey

    в) Зберігайте файл cookie: bigUserID: bigKey

    г) У таблиці "Запам'ятати мене" додайте запис із: UserID, IP-адресою, bigUserID, bigKey

  3. Якщо ви намагаєтеся отримати доступ до чогось, що вимагає входу:

    a) Перевірте файл cookie та знайдіть bigUserID & bigKey з відповідною IP-адресою

    b) Якщо ви знайдете його, увійдіть до нього, але встановіть прапорець у таблиці користувачів «м'який вхід», щоб для будь-яких небезпечних операцій ви могли запропонувати повне вхід.

  4. Під час виходу позначте всі записи "Запам'ятати мене" для цього користувача як закінчені.

Єдина вразливість, яку я бачу, - це;

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

Привіт, і дякую за цю відповідь, мені це подобається. Однак одне питання: чому вам потрібно генерувати 2 випадкових рядки - bigUserID & bigKey? Чому ви не генеруєте лише 1 і не використовуєте його?
Джеремі Білоло

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

Одна річ, що hmac може допомогти, це якщо ви виявили, що hmac підроблений, ви можете точно знати, що хтось намагався вкрасти файли cookie, то ви можете скинути кожен стан входу. Маю рацію?
Сурай Джайн

2

Я прочитав усі відповіді і все-таки важко було витягти те, що я мав робити. Якщо зображення вартує 1 тис. Слів, я сподіваюся, що це допоможе іншим реалізувати безпечне постійне сховище, засноване на кращих практичних файлах cookie вдосконаленого файлу Barry Jaspan

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

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


0

Реалізація функції "Тримати мене в системі" означає, що вам потрібно точно визначити, що це буде означати для користувача. У найпростішому випадку я би використовував це, щоб означати, що сеанс має набагато довший час: 2 дні (скажімо) замість 2 годин. Для цього вам знадобиться власне сховище сеансу, ймовірно, в базі даних, так що ви можете встановити власні терміни дії для даних сеансу. Тоді вам потрібно переконатися, що ви встановили файл cookie, який буде зберігатися протягом декількох днів (або довше), а не закінчується, коли вони закриють браузер.

Я чую, як ви запитуєте "чому 2 дні? Чому не 2 тижні?". Це тому, що використання сеансу в PHP автоматично відсуне термін дії назад. Це тому, що закінчення сеансу в PHP - насправді час очікування.

Тепер, сказавши це, я, мабуть, застосував би більш важке значення тайм-ауту, яке я зберігаю в сеансі, і вийдіть через 2 тижні або близько того, і додаю код, щоб побачити це і примусово визнати недійсним сеанс. Або принаймні вийти з них. Це означає, що користувачеві буде запропоновано періодично входити в систему. Yahoo! робить це.


1
Налаштування більш тривалого сеансу, ймовірно, погано, оскільки воно витрачає ресурси сервера, і це негативно вплине на продуктивність
user3091530

0

Я думаю, ви могли просто зробити це:

$cookieString = password_hash($username, PASSWORD_DEFAULT);

Зберігайте $cookiestringв БД і встановлюйте його як файл cookie. Також встановіть ім’я користувача як cookie. Вся суть хешу в тому, що він не може бути реверсивним.

Коли користувач з'являється, отримайте ім'я користувача від одного файлу cookie, ніж $cookieStringвід іншого. Якщо він $cookieStringвідповідає тому, що зберігається в БД, тоді користувач має автентифікацію. Оскільки password_hash щоразу використовує іншу сіль, це не має значення щодо того, що є чітким текстом.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.